Merge "Refactor PiP2 to include PiP menu related classes" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 9e30843..98b62b3 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -13,66 +13,148 @@
 // limitations under the License.
 
 aconfig_srcjars = [
-    ":android.app.usage.flags-aconfig-java{.generated_srcjars}",
+    // !!! KEEP THIS LIST ALPHABETICAL !!!
+    ":aconfig_mediacodec_flags_java_lib{.generated_srcjars}",
+    ":android.adaptiveauth.flags-aconfig-java{.generated_srcjars}",
+    ":android.app.flags-aconfig-java{.generated_srcjars}",
     ":android.app.smartspace.flags-aconfig-java{.generated_srcjars}",
+    ":android.app.usage.flags-aconfig-java{.generated_srcjars}",
+    ":android.appwidget.flags-aconfig-java{.generated_srcjars}",
+    ":android.chre.flags-aconfig-java{.generated_srcjars}",
     ":android.companion.flags-aconfig-java{.generated_srcjars}",
+    ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
+    ":android.content.flags-aconfig-java{.generated_srcjars}",
     ":android.content.pm.flags-aconfig-java{.generated_srcjars}",
     ":android.content.res.flags-aconfig-java{.generated_srcjars}",
+    ":android.crashrecovery.flags-aconfig-java{.generated_srcjars}",
+    ":android.credentials.flags-aconfig-java{.generated_srcjars}",
+    ":android.database.sqlite-aconfig-java{.generated_srcjars}",
+    ":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}",
     ":android.hardware.flags-aconfig-java{.generated_srcjars}",
     ":android.hardware.radio.flags-aconfig-java{.generated_srcjars}",
+    ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}",
     ":android.location.flags-aconfig-java{.generated_srcjars}",
+    ":android.media.tv.flags-aconfig-java{.generated_srcjars}",
+    ":android.multiuser.flags-aconfig-java{.generated_srcjars}",
     ":android.net.vcn.flags-aconfig-java{.generated_srcjars}",
     ":android.nfc.flags-aconfig-java{.generated_srcjars}",
     ":android.os.flags-aconfig-java{.generated_srcjars}",
     ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
+    ":android.permission.flags-aconfig-java{.generated_srcjars}",
+    ":android.provider.flags-aconfig-java{.generated_srcjars}",
     ":android.security.flags-aconfig-java{.generated_srcjars}",
     ":android.server.app.flags-aconfig-java{.generated_srcjars}",
+    ":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
     ":android.service.chooser.flags-aconfig-java{.generated_srcjars}",
+    ":android.service.controls.flags-aconfig-java{.generated_srcjars}",
     ":android.service.dreams.flags-aconfig-java{.generated_srcjars}",
     ":android.service.notification.flags-aconfig-java{.generated_srcjars}",
-    ":android.view.flags-aconfig-java{.generated_srcjars}",
+    ":android.service.voice.flags-aconfig-java{.generated_srcjars}",
+    ":android.speech.flags-aconfig-java{.generated_srcjars}",
+    ":android.tracing.flags-aconfig-java{.generated_srcjars}",
     ":android.view.accessibility.flags-aconfig-java{.generated_srcjars}",
+    ":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}",
+    ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
+    ":android.view.flags-aconfig-java{.generated_srcjars}",
+    ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}",
+    ":android.webkit.flags-aconfig-java{.generated_srcjars}",
+    ":android.widget.flags-aconfig-java{.generated_srcjars}",
     ":audio-framework-aconfig",
     ":camera_platform_flags_core_java_lib{.generated_srcjars}",
-    ":com.android.window.flags.window-aconfig-java{.generated_srcjars}",
-    ":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}",
     ":com.android.hardware.input-aconfig-java{.generated_srcjars}",
     ":com.android.input.flags-aconfig-java{.generated_srcjars}",
+    ":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
+    ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
+    ":com.android.media.flags.editing-aconfig-java{.generated_srcjars}",
+    ":com.android.net.flags-aconfig-java{.generated_srcjars}",
+    ":com.android.net.thread.flags-aconfig-java{.generated_srcjars}",
+    ":com.android.server.flags.services-aconfig-java{.generated_srcjars}",
     ":com.android.text.flags-aconfig-java{.generated_srcjars}",
+    ":com.android.window.flags.window-aconfig-java{.generated_srcjars}",
+    ":device_policy_aconfig_flags_lib{.generated_srcjars}",
+    ":display_flags_lib{.generated_srcjars}",
     ":framework-jobscheduler-job.flags-aconfig-java{.generated_srcjars}",
+    ":framework_graphics_flags_java_lib{.generated_srcjars}",
+    ":hwui_flags_java_lib{.generated_srcjars}",
+    ":power_flags_lib{.generated_srcjars}",
+    ":sdk_sandbox_flags_lib{.generated_srcjars}",
+    ":surfaceflinger_flags_java_lib{.generated_srcjars}",
     ":telecom_flags_core_java_lib{.generated_srcjars}",
     ":telephony_flags_core_java_lib{.generated_srcjars}",
-    ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
-    ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}",
-    ":android.widget.flags-aconfig-java{.generated_srcjars}",
-    ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
-    ":sdk_sandbox_flags_lib{.generated_srcjars}",
-    ":android.permission.flags-aconfig-java{.generated_srcjars}",
-    ":android.database.sqlite-aconfig-java{.generated_srcjars}",
-    ":hwui_flags_java_lib{.generated_srcjars}",
-    ":framework_graphics_flags_java_lib{.generated_srcjars}",
-    ":display_flags_lib{.generated_srcjars}",
-    ":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
-    ":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}",
-    ":com.android.server.flags.services-aconfig-java{.generated_srcjars}",
-    ":android.service.controls.flags-aconfig-java{.generated_srcjars}",
-    ":android.service.voice.flags-aconfig-java{.generated_srcjars}",
-    ":android.media.tv.flags-aconfig-java{.generated_srcjars}",
-    ":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
-    ":com.android.net.flags-aconfig-java{.generated_srcjars}",
-    ":device_policy_aconfig_flags_lib{.generated_srcjars}",
-    ":surfaceflinger_flags_java_lib{.generated_srcjars}",
-    ":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}",
-    ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}",
-    ":android.tracing.flags-aconfig-java{.generated_srcjars}",
-    ":android.appwidget.flags-aconfig-java{.generated_srcjars}",
-    ":android.webkit.flags-aconfig-java{.generated_srcjars}",
-    ":android.provider.flags-aconfig-java{.generated_srcjars}",
+    // !!! KEEP THIS LIST ALPHABETICAL !!!
 ]
 
+stubs_defaults {
+    name: "framework-minus-apex-aconfig-declarations",
+    aconfig_declarations: [
+        "android.app.flags-aconfig",
+        "android.app.smartspace.flags-aconfig",
+        "android.app.usage.flags-aconfig",
+        "android.appwidget.flags-aconfig",
+        "android.companion.flags-aconfig",
+        "android.companion.virtual.flags-aconfig",
+        "android.content.pm.flags-aconfig",
+        "android.content.res.flags-aconfig",
+        "android.crashrecovery.flags-aconfig",
+        "android.credentials.flags-aconfig",
+        "android.database.sqlite-aconfig",
+        "android.hardware.biometrics.flags-aconfig",
+        "android.hardware.flags-aconfig",
+        "android.hardware.radio.flags-aconfig",
+        "android.hardware.usb.flags-aconfig",
+        "android.location.flags-aconfig",
+        "android.media.audio-aconfig",
+        "android.media.audiopolicy-aconfig",
+        "android.media.midi-aconfig",
+        "android.media.tv.flags-aconfig",
+        "android.multiuser.flags-aconfig",
+        "android.net.vcn.flags-aconfig",
+        "android.nfc.flags-aconfig",
+        "android.os.flags-aconfig",
+        "android.os.vibrator.flags-aconfig",
+        "android.permission.flags-aconfig",
+        "android.provider.flags-aconfig",
+        "android.security.flags-aconfig",
+        "android.server.app.flags-aconfig",
+        "android.service.autofill.flags-aconfig",
+        "android.service.chooser.flags-aconfig",
+        "android.service.controls.flags-aconfig",
+        "android.service.dreams.flags-aconfig",
+        "android.service.notification.flags-aconfig",
+        "android.service.voice.flags-aconfig",
+        "android.speech.flags-aconfig",
+        "android.tracing.flags-aconfig",
+        "android.view.accessibility.flags-aconfig",
+        "android.view.contentcapture.flags-aconfig",
+        "android.view.contentprotection.flags-aconfig",
+        "android.view.flags-aconfig",
+        "android.view.inputmethod.flags-aconfig",
+        "android.webkit.flags-aconfig",
+        "android.widget.flags-aconfig",
+        "camera_platform_flags",
+        "chre_flags",
+        "com.android.hardware.input.input-aconfig",
+        "com.android.input.flags-aconfig",
+        "com.android.media.flags.bettertogether-aconfig",
+        "com.android.net.flags-aconfig",
+        "com.android.net.thread.flags-aconfig",
+        "com.android.server.flags.services-aconfig",
+        "com.android.text.flags-aconfig",
+        "com.android.window.flags.window-aconfig",
+        "device_policy_aconfig_flags",
+        "display_flags",
+        "fold_lock_setting_flags",
+        "framework-jobscheduler-job.flags-aconfig",
+        "framework_graphics_flags",
+        "hwui_flags",
+        "power_flags",
+        "sdk_sandbox_flags",
+        "surfaceflinger_flags",
+        "telecom_flags",
+        "telephony_flags",
+    ],
+}
+
 filegroup {
     name: "framework-minus-apex-aconfig-srcjars",
     srcs: aconfig_srcjars,
@@ -196,7 +278,7 @@
 aconfig_declarations {
     name: "android.nfc.flags-aconfig",
     package: "android.nfc",
-    srcs: ["core/java/android/nfc/*.aconfig"],
+    srcs: ["nfc/java/android/nfc/*.aconfig"],
 }
 
 cc_aconfig_library {
@@ -214,7 +296,7 @@
 java_aconfig_library {
     name: "android.nfc.flags-aconfig-java",
     aconfig_declarations: "android.nfc.flags-aconfig",
-    min_sdk_version: "VanillaIceCream",
+    min_sdk_version: "34",
     apex_available: [
         "//apex_available:platform",
         "com.android.nfcservices",
@@ -452,6 +534,28 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "com.android.media.flags.bettertogether-aconfig-java-host",
+    aconfig_declarations: "com.android.media.flags.bettertogether-aconfig",
+    host_supported: true,
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// Media Editing
+aconfig_declarations {
+    name: "com.android.media.flags.editing-aconfig",
+    package: "com.android.media.editing.flags",
+    srcs: [
+        "media/java/android/media/flags/editing.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "com.android.media.flags.editing-aconfig-java",
+    aconfig_declarations: "com.android.media.flags.editing-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Media TV
 aconfig_declarations {
     name: "android.media.tv.flags-aconfig",
@@ -480,8 +584,8 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.permission",
+        "com.android.nfcservices",
     ],
-
 }
 
 // SQLite
@@ -676,12 +780,25 @@
     srcs: ["core/java/android/net/flags.aconfig"],
 }
 
+// Thread network
+aconfig_declarations {
+    name: "com.android.net.thread.flags-aconfig",
+    package: "com.android.net.thread.flags",
+    srcs: ["core/java/android/net/thread/flags.aconfig"],
+}
+
 java_aconfig_library {
     name: "com.android.net.flags-aconfig-java",
     aconfig_declarations: "com.android.net.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "com.android.net.thread.flags-aconfig-java",
+    aconfig_declarations: "com.android.net.thread.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Media
 aconfig_declarations {
     name: "android.media.playback.flags-aconfig",
@@ -743,6 +860,11 @@
 java_aconfig_library {
     name: "android.service.chooser.flags-aconfig-java",
     aconfig_declarations: "android.service.chooser.flags-aconfig",
+    min_sdk_version: "34",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.nfcservices",
+    ],
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
@@ -905,3 +1027,69 @@
     aconfig_declarations: "android.provider.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// ContextHub
+java_aconfig_library {
+    name: "android.chre.flags-aconfig-java",
+    aconfig_declarations: "chre_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// Speech
+aconfig_declarations {
+    name: "android.speech.flags-aconfig",
+    package: "android.speech.flags",
+    srcs: ["core/java/android/speech/flags/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.speech.flags-aconfig-java",
+    aconfig_declarations: "android.speech.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// Power
+java_aconfig_library {
+    name: "power_flags_lib",
+    aconfig_declarations: "power_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// Content
+aconfig_declarations {
+    name: "android.content.flags-aconfig",
+    package: "android.content.flags",
+    srcs: ["core/java/android/content/flags/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.content.flags-aconfig-java",
+    aconfig_declarations: "android.content.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// Adaptive Auth
+aconfig_declarations {
+    name: "android.adaptiveauth.flags-aconfig",
+    package: "android.adaptiveauth",
+    srcs: ["core/java/android/adaptiveauth/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.adaptiveauth.flags-aconfig-java",
+    aconfig_declarations: "android.adaptiveauth.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// CrashRecovery Module
+aconfig_declarations {
+    name: "android.crashrecovery.flags-aconfig",
+    package: "android.crashrecovery.flags",
+    srcs: ["packages/CrashRecovery/aconfig/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.crashrecovery.flags-aconfig-java",
+    aconfig_declarations: "android.crashrecovery.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
\ No newline at end of file
diff --git a/Android.bp b/Android.bp
index 82b844b..e12f74f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -95,6 +95,7 @@
         ":platform-compat-native-aidl",
 
         // AIDL sources from external directories
+        ":android.frameworks.location.altitude-V2-java-source",
         ":android.hardware.biometrics.common-V4-java-source",
         ":android.hardware.biometrics.fingerprint-V3-java-source",
         ":android.hardware.biometrics.face-V4-java-source",
@@ -149,6 +150,7 @@
         ":framework-javastream-protos",
         ":statslog-framework-java-gen", // FrameworkStatsLog.java
         ":audio_policy_configuration_V7_0",
+        ":perfetto_trace_javastream_protos",
     ],
 }
 
@@ -175,9 +177,6 @@
         // and remove this line.
         "//frameworks/base/tools/hoststubgen:__subpackages__",
     ],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // AIDL files under these paths are mixture of public and private ones.
@@ -218,7 +217,6 @@
         "apex_aidl_interface-java",
         "packagemanager_aidl-java",
         "framework-protos",
-        "libtombstone_proto_java",
         "updatable-driver-protos",
         "ota_metadata_proto_java",
         "android.hidl.base-V1.0-java",
@@ -270,9 +268,6 @@
     ],
     sdk_version: "core_platform",
     installable: false,
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // NOTE: This filegroup is exposed for vendor libraries to depend on and is referenced in
@@ -437,13 +432,9 @@
     name: "framework-non-updatable-unbundled-impl-libs",
     static_libs: [
         "framework-location.impl",
-        "framework-nfc.impl",
     ],
     sdk_version: "core_platform",
     installable: false,
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // Separated so framework-minus-apex-defaults can be used without the libs dependency
@@ -487,9 +478,6 @@
     ],
     compile_dex: false,
     headers_only: true,
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 java_library {
@@ -534,7 +522,7 @@
     },
     lint: {
         enabled: false,
-        baseline_filename: "lint-baseline.xml",
+
     },
 }
 
@@ -559,9 +547,6 @@
     ],
     sdk_version: "core_platform",
     apex_available: ["//apex_available:platform"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 java_library {
@@ -577,9 +562,6 @@
         "calendar-provider-compat-config",
         "contacts-provider-platform-compat-config",
     ],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 platform_compat_config {
@@ -634,9 +616,6 @@
         "rappor",
     ],
     dxflags: ["--core-library"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // utility classes statically linked into framework-wifi and dynamically linked
@@ -665,6 +644,7 @@
     lint: {
         baseline_filename: "lint-baseline.xml",
     },
+    apex_available: ["com.android.wifi"],
 }
 
 filegroup {
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index d03bbd2..e7adf20 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -34,6 +34,7 @@
         ":ipconnectivity-proto-src",
         ":libstats_atom_enum_protos",
         ":libstats_atom_message_protos",
+        ":libtombstone_proto-src",
         "core/proto/**/*.proto",
         "libs/incident/**/*.proto",
     ],
diff --git a/Ravenwood.bp b/Ravenwood.bp
index f330ad1..0877bce 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -75,16 +75,36 @@
     ],
 }
 
+java_library {
+    name: "mockito-ravenwood-prebuilt",
+    installable: false,
+    static_libs: [
+        "mockito-robolectric-prebuilt",
+    ],
+}
+
+java_library {
+    name: "inline-mockito-ravenwood-prebuilt",
+    installable: false,
+    static_libs: [
+        "inline-mockito-robolectric-prebuilt",
+    ],
+}
+
 android_ravenwood_libgroup {
     name: "ravenwood-runtime",
     libs: [
         "framework-minus-apex.ravenwood",
         "hoststubgen-helper-runtime.ravenwood",
         "hoststubgen-helper-framework-runtime.ravenwood",
+        "core-libart-for-host",
+        "all-updatable-modules-system-stubs",
         "junit",
         "truth",
         "ravenwood-junit-impl",
         "android.test.mock.ravenwood",
+        "mockito-ravenwood-prebuilt",
+        "inline-mockito-ravenwood-prebuilt",
     ],
 }
 
@@ -94,5 +114,7 @@
         "junit",
         "truth",
         "ravenwood-junit",
+        "mockito-ravenwood-prebuilt",
+        "inline-mockito-ravenwood-prebuilt",
     ],
 }
diff --git a/STABILITY_OWNERS b/STABILITY_OWNERS
deleted file mode 100644
index a7ecb4d..0000000
--- a/STABILITY_OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-gaillard@google.com
-
diff --git a/TEST_MAPPING b/TEST_MAPPING
index d59775f..c904eb4 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -138,15 +138,13 @@
     }
   ],
   "postsubmit-ravenwood": [
-    // TODO(ravenwood) promote it to presubmit
-    // TODO: Enable it once the infra knows how to run it.
-//    {
-//      "name": "CtsUtilTestCasesRavenwood",
-//      "file_patterns": [
-//        "*Ravenwood*",
-//        "*ravenwood*"
-//      ]
-//    }
+    {
+      "name": "CtsUtilTestCasesRavenwood",
+      "host": true,
+      "file_patterns": [
+        "[Rr]avenwood"
+      ]
+    }
   ],
   "postsubmit-managedprofile-stress": [
     {
diff --git a/WEAR_OWNERS b/WEAR_OWNERS
index 4f3bc27..4127f99 100644
--- a/WEAR_OWNERS
+++ b/WEAR_OWNERS
@@ -4,3 +4,9 @@
 adsule@google.com
 andriyn@google.com
 yfz@google.com
+con@google.com
+leetodd@google.com
+sadrul@google.com
+rwmyers@google.com
+nalmalki@google.com
+shijianli@google.com
diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java
index 0ea2daf..bc84708 100644
--- a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java
+++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java
@@ -248,6 +248,11 @@
         return mServiceWatcher.waitOnCreate();
     }
 
+    /** Wait for session paused. */
+    public void waitForSessionPaused() throws InterruptedException {
+        mServiceWatcher.waitSessionPaused();
+    }
+
     @NonNull
     protected ActivityWatcher startWatcher() {
         return mActivitiesWatcher.watch(CustomTestActivity.class);
diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java
index aa95dfd..44e8a67 100644
--- a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java
+++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java
@@ -80,6 +80,34 @@
         testActivityLaunchTime(R.layout.test_container_activity, 500);
     }
 
+    @Test
+    public void testSendEventsLatency() throws Throwable {
+        enableService();
+
+        testSendEventLatency(R.layout.test_container_activity, 0);
+    }
+
+    @Test
+    public void testSendEventsLatency_contains100Views() throws Throwable {
+        enableService();
+
+        testSendEventLatency(R.layout.test_container_activity, 100);
+    }
+
+    @Test
+    public void testSendEventsLatency_contains300Views() throws Throwable {
+        enableService();
+
+        testSendEventLatency(R.layout.test_container_activity, 300);
+    }
+
+    @Test
+    public void testSendEventsLatency_contains500Views() throws Throwable {
+        enableService();
+
+        testSendEventLatency(R.layout.test_container_activity, 500);
+    }
+
     private void testActivityLaunchTime(int layoutId, int numViews) throws Throwable {
         final Object drawNotifier = new Object();
         final Intent intent = getLaunchIntent(layoutId, numViews);
@@ -111,6 +139,38 @@
         }
     }
 
+    private void testSendEventLatency(int layoutId, int numViews) throws Throwable {
+        final Object drawNotifier = new Object();
+        final Intent intent = getLaunchIntent(layoutId, numViews);
+        intent.putExtra(CustomTestActivity.INTENT_EXTRA_FINISH_ON_IDLE, true);
+        intent.putExtra(CustomTestActivity.INTENT_EXTRA_DRAW_CALLBACK,
+                new RemoteCallback(result -> {
+                    synchronized (drawNotifier) {
+                        drawNotifier.notifyAll();
+                    }
+                }));
+        final ActivityWatcher watcher = startWatcher();
+
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            mEntryActivity.startActivity(intent);
+            synchronized (drawNotifier) {
+                try {
+                    drawNotifier.wait(GENERIC_TIMEOUT_MS);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+            waitForSessionPaused();
+
+            // Ignore the time to finish the activity
+            state.pauseTiming();
+            watcher.waitFor(DESTROYED);
+            sInstrumentation.waitForIdleSync();
+            state.resumeTiming();
+        }
+    }
+
     @Test
     public void testOnVisibilityAggregated_visibleChanged() throws Throwable {
         enableService();
diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java
index ecc5112..0b5345f 100644
--- a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java
+++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java
@@ -114,6 +114,10 @@
     public void onContentCaptureEvent(ContentCaptureSessionId sessionId,
             ContentCaptureEvent event) {
         Log.i(TAG, "onContentCaptureEventsRequest(session=" + sessionId + "): " + event);
+        if (sServiceWatcher != null
+                && event.getType() == ContentCaptureEvent.TYPE_SESSION_PAUSED) {
+            sServiceWatcher.mSessionPaused.countDown();
+        }
     }
 
     @Override
@@ -126,6 +130,7 @@
         private static final long GENERIC_TIMEOUT_MS = 10_000;
         private final CountDownLatch mCreated = new CountDownLatch(1);
         private final CountDownLatch mDestroyed = new CountDownLatch(1);
+        private final CountDownLatch mSessionPaused = new CountDownLatch(1);
         private boolean mReadyToClear = true;
         private Pair<Set<String>, Set<ComponentName>> mAllowList;
 
@@ -151,6 +156,11 @@
             await(mDestroyed, "not destroyed");
         }
 
+        /** Wait for session paused. */
+        public void waitSessionPaused() throws InterruptedException {
+            await(mSessionPaused, "no Paused");
+        }
+
         /**
          * Allow just this package.
          */
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java
index 3c361d7..95730e8 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java
@@ -122,6 +122,8 @@
             Bitmap.createScaledBitmap(source, source.getWidth() / 2, source.getHeight() / 2, true)
                     .recycle();
         }
+        source.recycle();
+        Runtime.getRuntime().gc();
     }
 
     @Test
@@ -141,6 +143,8 @@
             Bitmap.createScaledBitmap(source, source.getWidth() / 2, source.getHeight() / 2, true)
                     .recycle();
         }
+        source.recycle();
+        Runtime.getRuntime().gc();
     }
 
     @Test
@@ -158,5 +162,7 @@
             Bitmap.createScaledBitmap(source, source.getWidth() / 2, source.getHeight() / 2, true)
                     .recycle();
         }
+        source.recycle();
+        Runtime.getRuntime().gc();
     }
 }
diff --git a/apct-tests/perftests/permission/Android.bp b/apct-tests/perftests/permission/Android.bp
new file mode 100644
index 0000000..b80a6af
--- /dev/null
+++ b/apct-tests/perftests/permission/Android.bp
@@ -0,0 +1,87 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "PermissionServicePerfTests",
+
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+
+    static_libs: [
+        "platform-compat-test-rules",
+        "androidx.appcompat_appcompat",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "androidx.annotation_annotation",
+        "apct-perftests-utils",
+        "androidx.benchmark_benchmark-common",
+        "androidx.benchmark_benchmark-junit4",
+        "collector-device-lib-platform",
+        "cts-install-lib-java",
+    ],
+
+    libs: ["android.test.base"],
+
+    platform_apis: true,
+
+    test_suites: ["device-tests"],
+
+    data: [
+        ":UsePermissionApp0",
+        ":UsePermissionApp1",
+        ":UsePermissionApp2",
+        ":UsePermissionApp3",
+        ":UsePermissionApp4",
+        ":UsePermissionApp5",
+        ":UsePermissionApp6",
+        ":UsePermissionApp7",
+        ":UsePermissionApp8",
+        ":UsePermissionApp9",
+        ":UsePermissionApp10",
+        ":UsePermissionApp11",
+        ":UsePermissionApp12",
+        ":UsePermissionApp13",
+        ":UsePermissionApp14",
+        ":UsePermissionApp15",
+        ":UsePermissionApp16",
+        ":UsePermissionApp17",
+        ":UsePermissionApp18",
+        ":UsePermissionApp19",
+        ":UsePermissionApp20",
+        ":UsePermissionApp21",
+        ":UsePermissionApp22",
+        ":UsePermissionApp23",
+        ":UsePermissionApp24",
+        ":UsePermissionApp25",
+        ":UsePermissionApp26",
+        ":UsePermissionApp27",
+        ":UsePermissionApp28",
+        ":UsePermissionApp29",
+        ":perfetto_artifacts",
+    ],
+
+    certificate: "platform",
+
+}
diff --git a/apct-tests/perftests/permission/AndroidManifest.xml b/apct-tests/perftests/permission/AndroidManifest.xml
new file mode 100644
index 0000000..fa29ad0
--- /dev/null
+++ b/apct-tests/perftests/permission/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.perftests.permission">
+
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.perftests.utils.PerfTestActivity"
+            android:exported="true">
+          <intent-filter>
+            <action android:name="android.perftests.permission.PERFTEST" />
+          </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.perftests.permission"/>
+
+</manifest>
diff --git a/apct-tests/perftests/permission/AndroidTest.xml b/apct-tests/perftests/permission/AndroidTest.xml
new file mode 100644
index 0000000..07558de
--- /dev/null
+++ b/apct-tests/perftests/permission/AndroidTest.xml
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Runs PermissionServicePerfTests metric instrumentation.">
+    <option name="test-suite-tag" value="apct"/>
+    <option name="test-suite-tag" value="apct-metric-instrumentation"/>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true"/>
+        <option name="test-file-name" value="PermissionServicePerfTests.apk"/>
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true"/>
+        <option name="force-queryable" value="false"/>
+        <option name="test-file-name" value="UsePermissionApp0.apk"/>
+        <option name="test-file-name" value="UsePermissionApp1.apk"/>
+        <option name="test-file-name" value="UsePermissionApp2.apk"/>
+        <option name="test-file-name" value="UsePermissionApp3.apk"/>
+        <option name="test-file-name" value="UsePermissionApp4.apk"/>
+        <option name="test-file-name" value="UsePermissionApp5.apk"/>
+        <option name="test-file-name" value="UsePermissionApp6.apk"/>
+        <option name="test-file-name" value="UsePermissionApp7.apk"/>
+        <option name="test-file-name" value="UsePermissionApp8.apk"/>
+        <option name="test-file-name" value="UsePermissionApp9.apk"/>
+        <option name="test-file-name" value="UsePermissionApp10.apk"/>
+        <option name="test-file-name" value="UsePermissionApp11.apk"/>
+        <option name="test-file-name" value="UsePermissionApp12.apk"/>
+        <option name="test-file-name" value="UsePermissionApp13.apk"/>
+        <option name="test-file-name" value="UsePermissionApp14.apk"/>
+        <option name="test-file-name" value="UsePermissionApp15.apk"/>
+        <option name="test-file-name" value="UsePermissionApp16.apk"/>
+        <option name="test-file-name" value="UsePermissionApp17.apk"/>
+        <option name="test-file-name" value="UsePermissionApp18.apk"/>
+        <option name="test-file-name" value="UsePermissionApp19.apk"/>
+        <option name="test-file-name" value="UsePermissionApp20.apk"/>
+        <option name="test-file-name" value="UsePermissionApp21.apk"/>
+        <option name="test-file-name" value="UsePermissionApp22.apk"/>
+        <option name="test-file-name" value="UsePermissionApp23.apk"/>
+        <option name="test-file-name" value="UsePermissionApp24.apk"/>
+        <option name="test-file-name" value="UsePermissionApp25.apk"/>
+        <option name="test-file-name" value="UsePermissionApp26.apk"/>
+        <option name="test-file-name" value="UsePermissionApp27.apk"/>
+        <option name="test-file-name" value="UsePermissionApp28.apk"/>
+        <option name="test-file-name" value="UsePermissionApp29.apk"/>
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.perftests.permission"/>
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/data/local/PermissionServicePerfTests"/>
+        <option name="collect-on-run-ended-only" value="true"/>
+    </metrics_collector>
+
+    <!-- Needed for pushing the trace config file -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="push-file" key="trace_config_detailed.textproto"
+                value="/data/misc/perfetto-traces/trace_config.textproto"/>
+        <!--Install the content provider automatically when we push some file in sdcard folder.-->
+        <!--Needed to avoid the installation during the test suite.-->
+        <option name="push-file" key="trace_config_detailed.textproto"
+                value="/sdcard/sample.textproto"/>
+        <option name="push-file" key="UsePermissionApp0.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp0.apk" />
+        <option name="push-file" key="UsePermissionApp1.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp1.apk" />
+        <option name="push-file" key="UsePermissionApp2.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp2.apk" />
+        <option name="push-file" key="UsePermissionApp3.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp3.apk" />
+        <option name="push-file" key="UsePermissionApp4.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp4.apk" />
+        <option name="push-file" key="UsePermissionApp5.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp5.apk" />
+        <option name="push-file" key="UsePermissionApp6.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp6.apk" />
+        <option name="push-file" key="UsePermissionApp7.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp7.apk" />
+        <option name="push-file" key="UsePermissionApp8.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp8.apk" />
+        <option name="push-file" key="UsePermissionApp9.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp9.apk" />
+        <option name="push-file" key="UsePermissionApp10.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp10.apk" />
+        <option name="push-file" key="UsePermissionApp11.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp11.apk" />
+        <option name="push-file" key="UsePermissionApp12.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp12.apk" />
+        <option name="push-file" key="UsePermissionApp13.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp13.apk" />
+        <option name="push-file" key="UsePermissionApp14.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp14.apk" />
+        <option name="push-file" key="UsePermissionApp15.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp15.apk" />
+        <option name="push-file" key="UsePermissionApp16.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp16.apk" />
+        <option name="push-file" key="UsePermissionApp17.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp17.apk" />
+        <option name="push-file" key="UsePermissionApp18.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp18.apk" />
+        <option name="push-file" key="UsePermissionApp19.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp19.apk" />
+        <option name="push-file" key="UsePermissionApp20.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp20.apk" />
+        <option name="push-file" key="UsePermissionApp21.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp21.apk" />
+        <option name="push-file" key="UsePermissionApp22.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp22.apk" />
+        <option name="push-file" key="UsePermissionApp23.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp23.apk" />
+        <option name="push-file" key="UsePermissionApp24.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp24.apk" />
+        <option name="push-file" key="UsePermissionApp25.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp25.apk" />
+        <option name="push-file" key="UsePermissionApp26.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp26.apk" />
+        <option name="push-file" key="UsePermissionApp27.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp27.apk" />
+        <option name="push-file" key="UsePermissionApp28.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp28.apk" />
+        <option name="push-file" key="UsePermissionApp29.apk"
+                value="/data/local/tmp/perftests/UsePermissionApp29.apk" />
+    </target_preparer>
+
+    <!-- Needed for pulling the collected trace config on to the host -->
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="pull-pattern-keys" value="perfetto_file_path"/>
+    </metrics_collector>
+
+    <!-- Needed for storing the perfetto trace files in the sdcard/test_results -->
+    <option name="isolated-storage" value="false"/>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.perftests.permission"/>
+        <option name="hidden-api-checks" value="false"/>
+
+        <!-- Listener related args for collecting the traces and waiting for the device to
+             stabilize. -->
+        <option name="device-listeners"
+                value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener"/>
+        <!-- Guarantee that user defined RunListeners will be running before any of the default
+             listeners defined in this runner. -->
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true"/>
+
+        <!-- ProcLoadListener related arguments -->
+        <!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting
+             the test run -->
+        <option name="instrumentation-arg" key="procload-collector:per_run" value="true"/>
+        <option name="instrumentation-arg" key="proc-loadavg-threshold" value="3"/>
+        <option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000"/>
+        <option name="instrumentation-arg" key="proc-loadavg-interval" value="10000"/>
+
+        <!-- PerfettoListener related arguments -->
+        <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
+        <option name="instrumentation-arg" key="perfetto_config_file"
+                value="trace_config.textproto"/>
+
+        <!--
+         PackageInstallerBenchmark will break for 5 minutes time out so it changes to 10 minutes
+          -->
+        <option name="test-timeout" value="600000" />
+    </test>
+
+
+</configuration>
diff --git a/apct-tests/perftests/permission/apps/usepermissionapp/Android.bp b/apct-tests/perftests/permission/apps/usepermissionapp/Android.bp
new file mode 100644
index 0000000..1ad20b6
--- /dev/null
+++ b/apct-tests/perftests/permission/apps/usepermissionapp/Android.bp
@@ -0,0 +1,232 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp0",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration0",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp1",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration1",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp2",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration2",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp3",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration3",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp4",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration4",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp5",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration5",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp6",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration6",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp7",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration7",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp8",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration8",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp9",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration9",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp10",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration10",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp11",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration11",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp12",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration12",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp13",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration13",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp14",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration14",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp15",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration15",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp16",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration16",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp17",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration17",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp18",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration18",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp19",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration19",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp20",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration20",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp21",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration21",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp22",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration22",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp23",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration23",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp24",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration24",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp25",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration25",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp26",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration26",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp27",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration27",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp28",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration28",
+    ],
+}
+
+android_test_helper_app {
+    name: "UsePermissionApp29",
+    aaptflags: [
+        "--rename-manifest-package android.perftests.appenumeration29",
+    ],
+}
diff --git a/apct-tests/perftests/permission/apps/usepermissionapp/AndroidManifest.xml b/apct-tests/perftests/permission/apps/usepermissionapp/AndroidManifest.xml
new file mode 100644
index 0000000..3bccefd
--- /dev/null
+++ b/apct-tests/perftests/permission/apps/usepermissionapp/AndroidManifest.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.perftests.appenumeration">
+
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
+    <uses-permission android:name="android.permission.RECORD_BACKGROUND_AUDIO" />
+    <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
+    <uses-permission android:name="android.permission.BACKGROUND_CAMERA" />
+    <uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS" />
+    <uses-permission android:name="android.permission.BODY_SENSORS" />
+    <uses-permission android:name="android.permission.USE_BIOMETRIC" />
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+    <uses-permission android:name="android.permission.SET_WALLPAPER" />
+    <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
+
+    <queries>
+        <package android:name="android.perftests.appenumeration0" />
+        <package android:name="android.perftests.appenumeration1" />
+        <package android:name="android.perftests.appenumeration2" />
+        <package android:name="android.perftests.appenumeration3" />
+        <package android:name="android.perftests.appenumeration4" />
+        <package android:name="android.perftests.appenumeration5" />
+        <package android:name="android.perftests.appenumeration6" />
+        <package android:name="android.perftests.appenumeration7" />
+        <package android:name="android.perftests.appenumeration8" />
+        <package android:name="android.perftests.appenumeration9" />
+        <package android:name="android.perftests.appenumeration10" />
+        <package android:name="android.perftests.appenumeration11" />
+        <package android:name="android.perftests.appenumeration12" />
+        <package android:name="android.perftests.appenumeration13" />
+        <package android:name="android.perftests.appenumeration14" />
+        <package android:name="android.perftests.appenumeration15" />
+        <package android:name="android.perftests.appenumeration16" />
+        <package android:name="android.perftests.appenumeration17" />
+        <package android:name="android.perftests.appenumeration18" />
+        <package android:name="android.perftests.appenumeration19" />
+        <package android:name="android.perftests.appenumeration20" />
+        <package android:name="android.perftests.appenumeration21" />
+        <package android:name="android.perftests.appenumeration22" />
+        <package android:name="android.perftests.appenumeration23" />
+        <package android:name="android.perftests.appenumeration24" />
+        <package android:name="android.perftests.appenumeration25" />
+        <package android:name="android.perftests.appenumeration26" />
+        <package android:name="android.perftests.appenumeration27" />
+        <package android:name="android.perftests.appenumeration28" />
+        <package android:name="android.perftests.appenumeration29" />
+    </queries>
+
+    <application android:hasCode="false" >
+        <activity android:name="android.perftests.utils.PerfTestActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.perftests.permission.PERFTEST" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/apct-tests/perftests/permission/src/android/perftests/permission/PermissionServicePerfTest.kt b/apct-tests/perftests/permission/src/android/perftests/permission/PermissionServicePerfTest.kt
new file mode 100644
index 0000000..13e67e3
--- /dev/null
+++ b/apct-tests/perftests/permission/src/android/perftests/permission/PermissionServicePerfTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.perftests.permission
+
+import android.Manifest
+import android.os.ParcelFileDescriptor
+import android.os.Trace
+import android.perftests.utils.PerfManualStatusReporter
+import android.perftests.utils.TraceMarkParser
+import android.util.Log
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.AdoptShellPermissionsRule
+import com.android.compatibility.common.util.SystemUtil.eventually
+import com.android.compatibility.common.util.SystemUtil.runShellCommand
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.BufferedReader
+import java.io.IOException
+import java.io.InputStreamReader
+import java.util.concurrent.TimeUnit
+import java.util.function.BiConsumer
+
+@RunWith(AndroidJUnit4::class)
+class PermissionServicePerfTest {
+    @get:Rule val mPerfManualStatusReporter = PerfManualStatusReporter()
+    @get:Rule val mAdoptShellPermissionsRule = AdoptShellPermissionsRule(
+        InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+        Manifest.permission.INSTALL_PACKAGES,
+        Manifest.permission.DELETE_PACKAGES
+    )
+    val mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation()
+
+    @Test
+    fun testInstallPackages() {
+        mUiAutomation.executeShellCommand(COMMAND_TRACE_START)
+        eventually { assertThat(Trace.isTagEnabled(TRACE_TAG)).isTrue() }
+        val benchmarkState = mPerfManualStatusReporter.benchmarkState
+        val durations = ArrayList<Long>()
+
+        while (benchmarkState.keepRunning(durations)) {
+            uninstallAllTestApps()
+            installAllTestApps()
+
+            val parser = TraceMarkParser { line -> line.name.contains(PKG_INSTALL_TRACE_PREFIX) }
+            dumpResult(parser) { _, slices ->
+                slices.forEachIndexed { _, slice ->
+                    durations.add(TimeUnit.MICROSECONDS.toNanos(slice.durationInMicroseconds))
+                }
+            }
+        }
+
+        mUiAutomation.executeShellCommand(COMMAND_TRACE_END)
+    }
+
+    private fun installAllTestApps() {
+        for (i in 0..29) {
+            installTestApp(i)
+        }
+    }
+
+    private fun installTestApp(appId: Int) {
+        val apkPath = "$APK_DIR$APK_NAME$appId.apk"
+        runShellCommand("pm install -t $apkPath")
+    }
+
+    private fun uninstallAllTestApps() {
+        for (i in 0..29) {
+            uninstallTestApp(i)
+        }
+    }
+
+    private fun uninstallTestApp(appId: Int) {
+        val packageName = "$PKG_NAME$appId"
+        runShellCommand("pm uninstall $packageName")
+    }
+
+    private fun dumpResult(
+        parser: TraceMarkParser,
+        handler: BiConsumer<String, List<TraceMarkParser.TraceMarkSlice>>
+    ) {
+        parser.reset()
+        try {
+            val inputStream = ParcelFileDescriptor.AutoCloseInputStream(
+                mUiAutomation.executeShellCommand(COMMAND_TRACE_DUMP)
+            )
+            val reader = BufferedReader(InputStreamReader(inputStream))
+            var line = reader.readLine()
+            while (line != null) {
+                parser.visit(line)
+                line = reader.readLine()
+            }
+        } catch (e: IOException) {
+            Log.e(LOG_TAG, "IO error while reading trace dump file.")
+        }
+        parser.forAllSlices(handler)
+    }
+
+    companion object {
+        private val LOG_TAG = PermissionServicePerfTest::class.java.simpleName
+        private const val TRACE_TAG = Trace.TRACE_TAG_PACKAGE_MANAGER
+        private const val PKG_INSTALL_TRACE_PREFIX =
+            "TaggedTracingPermissionManagerServiceImpl#onPackageInstalled"
+        private const val COMMAND_TRACE_START = "atrace --async_start -b 16000 pm"
+        private const val COMMAND_TRACE_END = "atrace --async_stop"
+        private const val COMMAND_TRACE_DUMP = "atrace --async_dump"
+        private const val APK_DIR = "/data/local/tmp/perftests/"
+        private const val APK_NAME = "UsePermissionApp"
+        private const val PKG_NAME = "android.perftests.appenumeration"
+    }
+}
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig
index e73b434..788e824 100644
--- a/apex/jobscheduler/framework/aconfig/job.aconfig
+++ b/apex/jobscheduler/framework/aconfig/job.aconfig
@@ -13,3 +13,10 @@
     description: "Add APIs to let apps attach debug information to jobs"
     bug: "293491637"
 }
+
+flag {
+    name: "backup_jobs_exemption"
+    namespace: "backstage_power"
+    description: "Introduce a new RUN_BACKUP_JOBS permission and exemption logic allowing for longer running jobs for apps whose primary purpose is to backup or sync content."
+    bug: "318731461"
+}
diff --git a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java
index caf7e7f..1fc888b 100644
--- a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.PowerExemptionManager;
 import android.os.PowerExemptionManager.ReasonCode;
@@ -77,6 +78,9 @@
 
     int[] getPowerSaveTempWhitelistAppIds();
 
+    @NonNull
+    String[] getFullPowerWhitelistExceptIdle();
+
     /**
      * Listener to be notified when DeviceIdleController determines that the device has moved or is
      * stationary.
diff --git a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
index 6c8af39..ae98fe1 100644
--- a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
@@ -77,6 +77,12 @@
             @NonNull String notificationChannel, int userId, @NonNull String packageName);
 
     /**
+     * @return {@code true} if the given package holds the
+     * {@link android.Manifest.permission.RUN_BACKUP_JOBS} permission.
+     */
+    boolean hasRunBackupJobsPermission(@NonNull String packageName, int packageUid);
+
+    /**
      * Report a snapshot of sync-related jobs back to the sync manager
      */
     JobStorePersistStats getPersistStats();
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index de6f023..5d65d9d 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -1,6 +1,13 @@
 package: "com.android.server.job"
 
 flag {
+    name: "do_not_force_rush_execution_at_boot"
+    namespace: "backstage_power"
+    description: "Don't force rush job execution right after boot completion"
+    bug: "321598070"
+}
+
+flag {
     name: "relax_prefetch_connectivity_constraint_only_on_charger"
     namespace: "backstage_power"
     description: "Only relax a prefetch job's connectivity constraint when the device is charging and battery is not low"
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 6383ed8..696c317 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -2374,6 +2374,11 @@
             return DeviceIdleController.this.isAppOnWhitelistInternal(appid);
         }
 
+        @Override
+        public String[] getFullPowerWhitelistExceptIdle() {
+            return DeviceIdleController.this.getFullPowerWhitelistInternalUnchecked();
+        }
+
         /**
          * Returns the array of app ids whitelisted by user. Take care not to
          * modify this, as it is a reference to the original copy. But the reference
@@ -3100,10 +3105,14 @@
     }
 
     private String[] getFullPowerWhitelistInternal(final int callingUid, final int callingUserId) {
-        final String[] apps;
+        return ArrayUtils.filter(getFullPowerWhitelistInternalUnchecked(), String[]::new,
+                (pkg) -> !mPackageManagerInternal.filterAppAccess(pkg, callingUid, callingUserId));
+    }
+
+    private String[] getFullPowerWhitelistInternalUnchecked() {
         synchronized (this) {
             int size = mPowerSaveWhitelistApps.size() + mPowerSaveWhitelistUserApps.size();
-            apps = new String[size];
+            final String[] apps = new String[size];
             int cur = 0;
             for (int i = 0; i < mPowerSaveWhitelistApps.size(); i++) {
                 apps[cur] = mPowerSaveWhitelistApps.keyAt(i);
@@ -3113,9 +3122,8 @@
                 apps[cur] = mPowerSaveWhitelistUserApps.keyAt(i);
                 cur++;
             }
+            return apps;
         }
-        return ArrayUtils.filter(apps, String[]::new,
-                (pkg) -> !mPackageManagerInternal.filterAppAccess(pkg, callingUid, callingUserId));
     }
 
     public boolean isPowerSaveWhitelistExceptIdleAppInternal(String packageName) {
@@ -3911,6 +3919,7 @@
                     if (locationManager != null
                             && locationManager.getProvider(LocationManager.FUSED_PROVIDER)
                                     != null) {
+                        mHasFusedLocation = true;
                         locationManager.requestLocationUpdates(LocationManager.FUSED_PROVIDER,
                                 mLocationRequest,
                                 AppSchedulingModuleThread.getExecutor(),
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index d940e38..57467e3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -159,6 +159,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -513,6 +514,10 @@
                     if (name == null) {
                         continue;
                     }
+                    if (DEBUG) {
+                        Slog.d(TAG, "DeviceConfig " + name
+                                + " changed to " + properties.getString(name, null));
+                    }
                     switch (name) {
                         case Constants.KEY_ENABLE_API_QUOTAS:
                         case Constants.KEY_ENABLE_EXECUTION_SAFEGUARDS_UDC:
@@ -1835,7 +1840,9 @@
                     /* isFlexConstraintSatisfied */ false,
                     jobStatus.canApplyTransportAffinities(),
                     jobStatus.getNumAppliedFlexibleConstraints(),
-                    jobStatus.getNumDroppedFlexibleConstraints());
+                    jobStatus.getNumDroppedFlexibleConstraints(),
+                    jobStatus.getFilteredTraceTag(),
+                    jobStatus.getFilteredDebugTags());
 
             // If the job is immediately ready to run, then we can just immediately
             // put it in the pending list and try to schedule it.  This is especially
@@ -2283,7 +2290,9 @@
                     cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
                     cancelled.canApplyTransportAffinities(),
                     cancelled.getNumAppliedFlexibleConstraints(),
-                    cancelled.getNumDroppedFlexibleConstraints());
+                    cancelled.getNumDroppedFlexibleConstraints(),
+                    cancelled.getFilteredTraceTag(),
+                    cancelled.getFilteredDebugTags());
         }
         // If this is a replacement, bring in the new version of the job
         if (incomingJob != null) {
@@ -2715,8 +2724,10 @@
                         sc.maybeStartTrackingJobLocked(job, null);
                     }
                 });
-                // GO GO GO!
-                mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+                if (!Flags.doNotForceRushExecutionAtBoot()) {
+                    // GO GO GO!
+                    mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+                }
             }
         }
     }
@@ -3507,7 +3518,10 @@
                 }
 
                 final boolean shouldForceBatchJob;
-                if (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiatedJob()) {
+                if (job.overrideState > JobStatus.OVERRIDE_NONE) {
+                    // The job should run for some test. Don't force batch it.
+                    shouldForceBatchJob = false;
+                } else if (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiatedJob()) {
                     // Never batch expedited or user-initiated jobs, even for RESTRICTED apps.
                     shouldForceBatchJob = false;
                 } else if (job.getEffectiveStandbyBucket() == RESTRICTED_INDEX) {
@@ -4183,6 +4197,11 @@
         }
 
         @Override
+        public boolean hasRunBackupJobsPermission(@NonNull String packageName, int packageUid) {
+            return JobSchedulerService.this.hasRunBackupJobsPermission(packageName, packageUid);
+        }
+
+        @Override
         public JobStorePersistStats getPersistStats() {
             synchronized (mLock) {
                 return new JobStorePersistStats(mJobs.getPersistStats());
@@ -4345,6 +4364,22 @@
     }
 
     /**
+     * Returns whether the app holds the {@link Manifest.permission.RUN_BACKUP_JOBS} permission.
+     */
+    private boolean hasRunBackupJobsPermission(@NonNull String packageName, int packageUid) {
+        if (packageName == null) {
+            Slog.wtfStack(TAG,
+                    "Expected a non-null package name when calling hasRunBackupJobsPermission");
+            return false;
+        }
+
+        return PermissionChecker.checkPermissionForPreflight(getTestableContext(),
+                android.Manifest.permission.RUN_BACKUP_JOBS,
+                PermissionChecker.PID_UNKNOWN, packageUid, packageName)
+                    == PermissionChecker.PERMISSION_GRANTED;
+    }
+
+    /**
      * Binder stub trampoline implementation
      */
     final class JobSchedulerStub extends IJobScheduler.Stub {
@@ -4960,6 +4995,8 @@
         Slog.d(TAG, "executeRunCommand(): " + pkgName + "/" + namespace + "/" + userId
                 + " " + jobId + " s=" + satisfied + " f=" + force);
 
+        final CountDownLatch delayLatch = new CountDownLatch(1);
+        final JobStatus js;
         try {
             final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
                     userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
@@ -4968,7 +5005,7 @@
             }
 
             synchronized (mLock) {
-                final JobStatus js = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
+                js = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
                 if (js == null) {
                     return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
                 }
@@ -4979,23 +5016,71 @@
                 // Re-evaluate constraints after the override is set in case one of the overridden
                 // constraints was preventing another constraint from thinking it needed to update.
                 for (int c = mControllers.size() - 1; c >= 0; --c) {
-                    mControllers.get(c).reevaluateStateLocked(uid);
+                    mControllers.get(c).evaluateStateLocked(js);
                 }
 
                 if (!js.isConstraintsSatisfied()) {
-                    js.overrideState = JobStatus.OVERRIDE_NONE;
-                    return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
+                    if (js.hasConnectivityConstraint()
+                            && !js.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)
+                            && js.wouldBeReadyWithConstraint(JobStatus.CONSTRAINT_CONNECTIVITY)) {
+                        // Because of how asynchronous the connectivity signals are, JobScheduler
+                        // may not get the connectivity satisfaction signal immediately. In this
+                        // case, wait a few seconds to see if it comes in before saying the
+                        // connectivity constraint isn't satisfied.
+                        mHandler.postDelayed(
+                                checkConstraintRunnableForTesting(
+                                        mHandler, js, delayLatch, 5, 1000),
+                                1000);
+                    } else {
+                        // There's no asynchronous signal to wait for. We can immediately say the
+                        // job's constraints aren't satisfied and return.
+                        js.overrideState = JobStatus.OVERRIDE_NONE;
+                        return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
+                    }
+                } else {
+                    delayLatch.countDown();
                 }
-
-                queueReadyJobsForExecutionLocked();
-                maybeRunPendingJobsLocked();
             }
         } catch (RemoteException e) {
             // can't happen
+            return 0;
+        }
+
+        // Choose to block the return until we're sure about the state of the connectivity job
+        // so that tests can expect a reliable state after calling the run command.
+        try {
+            delayLatch.await(7L, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            Slog.e(TAG, "Couldn't wait for asynchronous constraint change", e);
+        }
+
+        synchronized (mLock) {
+            if (!js.isConstraintsSatisfied()) {
+                js.overrideState = JobStatus.OVERRIDE_NONE;
+                return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
+            }
+
+            queueReadyJobsForExecutionLocked();
+            maybeRunPendingJobsLocked();
         }
         return 0;
     }
 
+    private static Runnable checkConstraintRunnableForTesting(@NonNull final Handler handler,
+            @NonNull final JobStatus js, @NonNull final CountDownLatch latch,
+            final int remainingAttempts, final long delayMs) {
+        return () -> {
+            if (remainingAttempts <= 0 || js.isConstraintsSatisfied()) {
+                latch.countDown();
+                return;
+            }
+            handler.postDelayed(
+                    checkConstraintRunnableForTesting(
+                            handler, js, latch, remainingAttempts - 1, delayMs),
+                    delayMs);
+        };
+    }
+
     // Shell command infrastructure: immediately timeout currently executing jobs
     int executeStopCommand(PrintWriter pw, String pkgName, int userId,
             @Nullable String namespace, boolean hasJobId, int jobId,
@@ -5383,9 +5468,14 @@
 
             pw.println("Aconfig flags:");
             pw.increaseIndent();
+            pw.print(Flags.FLAG_DO_NOT_FORCE_RUSH_EXECUTION_AT_BOOT,
+                    Flags.doNotForceRushExecutionAtBoot());
             pw.print(Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE,
                     Flags.throwOnUnsupportedBiasUsage());
             pw.println();
+            pw.print(android.app.job.Flags.FLAG_BACKUP_JOBS_EXEMPTION,
+                    android.app.job.Flags.backupJobsExemption());
+            pw.println();
             pw.decreaseIndent();
             pw.println();
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index c14efae..0cf6a7a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -344,15 +344,21 @@
         final String flagName = getNextArgRequired();
 
         switch (flagName) {
-            case android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS:
-                pw.println(android.app.job.Flags.jobDebugInfoApis());
-                break;
             case android.app.job.Flags.FLAG_ENFORCE_MINIMUM_TIME_WINDOWS:
                 pw.println(android.app.job.Flags.enforceMinimumTimeWindows());
                 break;
+            case android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS:
+                pw.println(android.app.job.Flags.jobDebugInfoApis());
+                break;
+            case com.android.server.job.Flags.FLAG_DO_NOT_FORCE_RUSH_EXECUTION_AT_BOOT:
+                pw.println(com.android.server.job.Flags.doNotForceRushExecutionAtBoot());
+                break;
             case com.android.server.job.Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE:
                 pw.println(com.android.server.job.Flags.throwOnUnsupportedBiasUsage());
                 break;
+            case android.app.job.Flags.FLAG_BACKUP_JOBS_EXEMPTION:
+                pw.println(android.app.job.Flags.backupJobsExemption());
+                break;
             default:
                 pw.println("Unknown flag: " + flagName);
                 break;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index fe55e27..8ab7d2f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -541,7 +541,9 @@
                     job.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
                     job.canApplyTransportAffinities(),
                     job.getNumAppliedFlexibleConstraints(),
-                    job.getNumDroppedFlexibleConstraints());
+                    job.getNumDroppedFlexibleConstraints(),
+                    job.getFilteredTraceTag(),
+                    job.getFilteredDebugTags());
             sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
             final String sourcePackage = job.getSourcePackageName();
             if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -1630,7 +1632,9 @@
                 completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
                 completedJob.canApplyTransportAffinities(),
                 completedJob.getNumAppliedFlexibleConstraints(),
-                completedJob.getNumDroppedFlexibleConstraints());
+                completedJob.getNumDroppedFlexibleConstraints(),
+                completedJob.getFilteredTraceTag(),
+                completedJob.getFilteredDebugTags());
         if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
             Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
                     getId());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index e3ba50d..e3ac780 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -318,6 +318,10 @@
 
         try {
             final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid);
+            if (DEBUG) {
+                Slog.d(TAG,
+                        "Pulled stopped state of " + packageName + " (" + uid + "): " + isStopped);
+            }
             mPackageStoppedState.add(uid, packageName, isStopped);
             return isStopped;
         } catch (PackageManager.NameNotFoundException e) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 0e67b9a..6883d18 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -16,6 +16,11 @@
 
 package com.android.server.job.controllers;
 
+import static android.app.job.JobInfo.PRIORITY_DEFAULT;
+import static android.app.job.JobInfo.PRIORITY_HIGH;
+import static android.app.job.JobInfo.PRIORITY_LOW;
+import static android.app.job.JobInfo.PRIORITY_MAX;
+import static android.app.job.JobInfo.PRIORITY_MIN;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
@@ -30,24 +35,33 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.job.JobInfo;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PowerManager;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
+import android.util.KeyValueListParser;
 import android.util.Log;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.SparseArrayMap;
+import android.util.SparseIntArray;
 import android.util.SparseLongArray;
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.AppSchedulingModuleThread;
+import com.android.server.DeviceIdleInternal;
+import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.utils.AlarmQueue;
 
@@ -85,6 +99,23 @@
      */
     private long mFallbackFlexibilityDeadlineMs =
             FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
+    /**
+     * The default deadline that all flexible constraints should be dropped by if a job lacks
+     * a deadline, keyed by job priority.
+     */
+    private SparseLongArray mFallbackFlexibilityDeadlines =
+            FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES;
+    /**
+     * The scores to use for each job, keyed by job priority.
+     */
+    private SparseIntArray mFallbackFlexibilityDeadlineScores =
+            FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES;
+    /**
+     * The amount of time to add (scaled by job run score) to the fallback flexibility deadline,
+     * keyed by job priority.
+     */
+    private SparseLongArray mFallbackFlexibilityAdditionalScoreTimeFactors =
+            FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS;
 
     private long mRescheduledJobDeadline = FcConfig.DEFAULT_RESCHEDULED_JOB_DEADLINE_MS;
     private long mMaxRescheduledDeadline = FcConfig.DEFAULT_MAX_RESCHEDULED_DEADLINE_MS;
@@ -111,10 +142,10 @@
 
     /**
      * The percent of a job's lifecycle to drop number of required constraints.
-     * mPercentToDropConstraints[i] denotes that at x% of a Jobs lifecycle,
-     * the controller should have i+1 constraints dropped.
+     * mPercentsToDropConstraints[i] denotes that at x% of a Jobs lifecycle,
+     * the controller should have i+1 constraints dropped. Keyed by job priority.
      */
-    private int[] mPercentToDropConstraints;
+    private SparseArray<int[]> mPercentsToDropConstraints;
 
     /**
      * Keeps track of what flexible constraints are satisfied at the moment.
@@ -127,6 +158,19 @@
     @GuardedBy("mLock")
     private final SparseLongArray mLastSeenConstraintTimesElapsed = new SparseLongArray();
 
+    private DeviceIdleInternal mDeviceIdleInternal;
+    private final ArraySet<String> mPowerAllowlistedApps = new ArraySet<>();
+
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
+                    mHandler.post(FlexibilityController.this::updatePowerAllowlistCache);
+                    break;
+            }
+        }
+    };
     @VisibleForTesting
     @GuardedBy("mLock")
     final FlexibilityTracker mFlexibilityTracker;
@@ -180,8 +224,96 @@
                 }
             };
 
-    private static final int MSG_UPDATE_JOBS = 0;
-    private static final int MSG_UPDATE_JOB = 1;
+    /** Helper object to track job run score for each app. */
+    private static class JobScoreTracker {
+        private static class JobScoreBucket {
+            @ElapsedRealtimeLong
+            public long startTimeElapsed;
+            public int score;
+
+            private void reset() {
+                startTimeElapsed = 0;
+                score = 0;
+            }
+        }
+
+        private static final int NUM_SCORE_BUCKETS = 24;
+        private static final long MAX_TIME_WINDOW_MS = 24 * HOUR_IN_MILLIS;
+        private final JobScoreBucket[] mScoreBuckets = new JobScoreBucket[NUM_SCORE_BUCKETS];
+        private int mScoreBucketIndex = 0;
+
+        public void addScore(int add, long nowElapsed) {
+            JobScoreBucket bucket = mScoreBuckets[mScoreBucketIndex];
+            if (bucket == null) {
+                bucket = new JobScoreBucket();
+                bucket.startTimeElapsed = nowElapsed;
+                mScoreBuckets[mScoreBucketIndex] = bucket;
+            } else if (bucket.startTimeElapsed < nowElapsed - MAX_TIME_WINDOW_MS) {
+                // The bucket is too old.
+                bucket.reset();
+                bucket.startTimeElapsed = nowElapsed;
+            } else if (bucket.startTimeElapsed
+                    < nowElapsed - MAX_TIME_WINDOW_MS / NUM_SCORE_BUCKETS) {
+                // The current bucket's duration has completed. Move on to the next bucket.
+                mScoreBucketIndex = (mScoreBucketIndex + 1) % NUM_SCORE_BUCKETS;
+                addScore(add, nowElapsed);
+                return;
+            }
+
+            bucket.score += add;
+        }
+
+        public int getScore(long nowElapsed) {
+            int score = 0;
+            final long earliestElapsed = nowElapsed - MAX_TIME_WINDOW_MS;
+            for (JobScoreBucket bucket : mScoreBuckets) {
+                if (bucket != null && bucket.startTimeElapsed >= earliestElapsed) {
+                    score += bucket.score;
+                }
+            }
+            return score;
+        }
+
+        public void dump(@NonNull IndentingPrintWriter pw, long nowElapsed) {
+            pw.print("{");
+
+            boolean printed = false;
+            for (int x = 0; x < mScoreBuckets.length; ++x) {
+                final int idx = (mScoreBucketIndex + 1 + x) % mScoreBuckets.length;
+                final JobScoreBucket jsb = mScoreBuckets[idx];
+                if (jsb == null || jsb.startTimeElapsed == 0) {
+                    continue;
+                }
+                if (printed) {
+                    pw.print(", ");
+                }
+                TimeUtils.formatDuration(jsb.startTimeElapsed, nowElapsed, pw);
+                pw.print("=");
+                pw.print(jsb.score);
+                printed = true;
+            }
+
+            pw.print("}");
+        }
+    }
+
+    /**
+     * Set of {@link JobScoreTracker JobScoreTrackers} for each app.
+     * Keyed by source UID -> source package.
+     **/
+    private final SparseArrayMap<String, JobScoreTracker> mJobScoreTrackers =
+            new SparseArrayMap<>();
+
+    private static final int MSG_CHECK_ALL_JOBS = 0;
+    /** Check the jobs in {@link #mJobsToCheck} */
+    private static final int MSG_CHECK_JOBS = 1;
+    /** Check the jobs of packages in {@link #mPackagesToCheck} */
+    private static final int MSG_CHECK_PACKAGES = 2;
+
+    @GuardedBy("mLock")
+    private final ArraySet<JobStatus> mJobsToCheck = new ArraySet<>();
+    @GuardedBy("mLock")
+    private final ArraySet<String> mPackagesToCheck = new ArraySet<>();
 
     public FlexibilityController(
             JobSchedulerService service, PrefetchController prefetchController) {
@@ -201,9 +333,19 @@
         mFcConfig = new FcConfig();
         mFlexibilityAlarmQueue = new FlexibilityAlarmQueue(
                 mContext, AppSchedulingModuleThread.get().getLooper());
-        mPercentToDropConstraints =
-                mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+        mPercentsToDropConstraints =
+                FcConfig.DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
         mPrefetchController = prefetchController;
+
+        if (mFlexibilityEnabled) {
+            registerBroadcastReceiver();
+        }
+    }
+
+    @Override
+    public void onSystemServicesReady() {
+        mDeviceIdleInternal = LocalServices.getService(DeviceIdleInternal.class);
+        mHandler.post(FlexibilityController.this::updatePowerAllowlistCache);
     }
 
     @Override
@@ -235,12 +377,43 @@
     }
 
     @Override
+    public void prepareForExecutionLocked(JobStatus jobStatus) {
+        // Use the job's requested priority to determine its score since that is what the developer
+        // selected and it will be stable across job runs.
+        final int score = mFallbackFlexibilityDeadlineScores
+                .get(jobStatus.getJob().getPriority(), jobStatus.getJob().getPriority() / 100);
+        JobScoreTracker jobScoreTracker =
+                mJobScoreTrackers.get(jobStatus.getSourceUid(), jobStatus.getSourcePackageName());
+        if (jobScoreTracker == null) {
+            jobScoreTracker = new JobScoreTracker();
+            mJobScoreTrackers.add(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(),
+                    jobScoreTracker);
+        }
+        jobScoreTracker.addScore(score, sElapsedRealtimeClock.millis());
+    }
+
+    @Override
+    public void unprepareFromExecutionLocked(JobStatus jobStatus) {
+        // The job didn't actually start. Undo the score increase.
+        JobScoreTracker jobScoreTracker =
+                mJobScoreTrackers.get(jobStatus.getSourceUid(), jobStatus.getSourcePackageName());
+        if (jobScoreTracker == null) {
+            Slog.e(TAG, "Unprepared a job that didn't result in a score change");
+            return;
+        }
+        final int score = mFallbackFlexibilityDeadlineScores
+                .get(jobStatus.getJob().getPriority(), jobStatus.getJob().getPriority() / 100);
+        jobScoreTracker.addScore(-score, sElapsedRealtimeClock.millis());
+    }
+
+    @Override
     @GuardedBy("mLock")
     public void maybeStopTrackingJobLocked(JobStatus js, JobStatus incomingJob) {
         if (js.clearTrackingController(JobStatus.TRACKING_FLEXIBILITY)) {
             mFlexibilityAlarmQueue.removeAlarmForKey(js);
             mFlexibilityTracker.remove(js);
         }
+        mJobsToCheck.remove(js);
     }
 
     @Override
@@ -248,12 +421,33 @@
     public void onAppRemovedLocked(String packageName, int uid) {
         final int userId = UserHandle.getUserId(uid);
         mPrefetchLifeCycleStart.delete(userId, packageName);
+        mJobScoreTrackers.delete(uid, packageName);
+        for (int i = mJobsToCheck.size() - 1; i >= 0; --i) {
+            final JobStatus js = mJobsToCheck.valueAt(i);
+            if ((js.getSourceUid() == uid && js.getSourcePackageName().equals(packageName))
+                    || (js.getUid() == uid && js.getCallingPackageName().equals(packageName))) {
+                mJobsToCheck.removeAt(i);
+            }
+        }
     }
 
     @Override
     @GuardedBy("mLock")
     public void onUserRemovedLocked(int userId) {
         mPrefetchLifeCycleStart.delete(userId);
+        for (int u = mJobScoreTrackers.numMaps() - 1; u >= 0; --u) {
+            final int uid = mJobScoreTrackers.keyAt(u);
+            if (UserHandle.getUserId(uid) == userId) {
+                mJobScoreTrackers.deleteAt(u);
+            }
+        }
+        for (int i = mJobsToCheck.size() - 1; i >= 0; --i) {
+            final JobStatus js = mJobsToCheck.valueAt(i);
+            if (UserHandle.getUserId(js.getSourceUid()) == userId
+                    || UserHandle.getUserId(js.getUid()) == userId) {
+                mJobsToCheck.removeAt(i);
+            }
+        }
     }
 
     boolean isEnabled() {
@@ -266,7 +460,14 @@
     @GuardedBy("mLock")
     boolean isFlexibilitySatisfiedLocked(JobStatus js) {
         return !mFlexibilityEnabled
+                // Exclude all jobs of the TOP app
                 || mService.getUidBias(js.getSourceUid()) == JobInfo.BIAS_TOP_APP
+                // Only exclude DEFAULT+ priority jobs for BFGS+ apps
+                || (mService.getUidBias(js.getSourceUid()) >= JobInfo.BIAS_BOUND_FOREGROUND_SERVICE
+                        && js.getEffectivePriority() >= PRIORITY_DEFAULT)
+                // For apps in the power allowlist, automatically exclude DEFAULT+ priority jobs.
+                || (js.getEffectivePriority() >= PRIORITY_DEFAULT
+                        && mPowerAllowlistedApps.contains(js.getSourcePackageName()))
                 || hasEnoughSatisfiedConstraintsLocked(js)
                 || mService.isCurrentlyRunningLocked(js);
     }
@@ -371,7 +572,7 @@
 
                 // Push the job update to the handler to avoid blocking other controllers and
                 // potentially batch back-to-back controller state updates together.
-                mHandler.obtainMessage(MSG_UPDATE_JOBS).sendToTarget();
+                mHandler.obtainMessage(MSG_CHECK_ALL_JOBS).sendToTarget();
             }
         }
     }
@@ -417,7 +618,14 @@
 
     @VisibleForTesting
     @GuardedBy("mLock")
-    long getLifeCycleEndElapsedLocked(JobStatus js, long earliest) {
+    int getScoreLocked(int uid, @NonNull String pkgName, long nowElapsed) {
+        final JobScoreTracker scoreTracker = mJobScoreTrackers.get(uid, pkgName);
+        return scoreTracker == null ? 0 : scoreTracker.getScore(nowElapsed);
+    }
+
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    long getLifeCycleEndElapsedLocked(JobStatus js, long nowElapsed, long earliest) {
         if (js.getJob().isPrefetch()) {
             final long estimatedLaunchTime =
                     mPrefetchController.getNextEstimatedLaunchTimeLocked(js);
@@ -441,15 +649,28 @@
                     (long) Math.scalb(mRescheduledJobDeadline, js.getNumPreviousAttempts() - 2),
                     mMaxRescheduledDeadline);
         }
-        return js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME
-                ? earliest + mFallbackFlexibilityDeadlineMs : js.getLatestRunTimeElapsed();
+        if (js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME) {
+            // Intentionally use the effective priority here. If a job's priority was effectively
+            // lowered, it will be less likely to run quickly given other policies in JobScheduler.
+            // Thus, there's no need to further delay the job based on flex policy.
+            final int jobPriority = js.getEffectivePriority();
+            final int jobScore =
+                    getScoreLocked(js.getSourceUid(), js.getSourcePackageName(), nowElapsed);
+            // Set an upper limit on the fallback deadline so that the delay doesn't become extreme.
+            final long fallbackDeadlineMs = Math.min(3 * mFallbackFlexibilityDeadlineMs,
+                    mFallbackFlexibilityDeadlines.get(jobPriority, mFallbackFlexibilityDeadlineMs)
+                            + mFallbackFlexibilityAdditionalScoreTimeFactors
+                                    .get(jobPriority, MINUTE_IN_MILLIS) * jobScore);
+            return earliest + fallbackDeadlineMs;
+        }
+        return js.getLatestRunTimeElapsed();
     }
 
     @VisibleForTesting
     @GuardedBy("mLock")
     int getCurPercentOfLifecycleLocked(JobStatus js, long nowElapsed) {
         final long earliest = getLifeCycleBeginningElapsedLocked(js);
-        final long latest = getLifeCycleEndElapsedLocked(js, earliest);
+        final long latest = getLifeCycleEndElapsedLocked(js, nowElapsed, earliest);
         if (latest == NO_LIFECYCLE_END || earliest >= nowElapsed) {
             return 0;
         }
@@ -465,7 +686,8 @@
     @GuardedBy("mLock")
     long getNextConstraintDropTimeElapsedLocked(JobStatus js) {
         final long earliest = getLifeCycleBeginningElapsedLocked(js);
-        final long latest = getLifeCycleEndElapsedLocked(js, earliest);
+        final long latest =
+                getLifeCycleEndElapsedLocked(js, sElapsedRealtimeClock.millis(), earliest);
         return getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
     }
 
@@ -473,19 +695,33 @@
     @ElapsedRealtimeLong
     @GuardedBy("mLock")
     long getNextConstraintDropTimeElapsedLocked(JobStatus js, long earliest, long latest) {
+        final int[] percentsToDropConstraints =
+                getPercentsToDropConstraints(js.getEffectivePriority());
         if (latest == NO_LIFECYCLE_END
-                || js.getNumDroppedFlexibleConstraints() == mPercentToDropConstraints.length) {
+                || js.getNumDroppedFlexibleConstraints() == percentsToDropConstraints.length) {
             return NO_LIFECYCLE_END;
         }
-        final int percent = mPercentToDropConstraints[js.getNumDroppedFlexibleConstraints()];
+        final int percent = percentsToDropConstraints[js.getNumDroppedFlexibleConstraints()];
         final long percentInTime = ((latest - earliest) * percent) / 100;
         return earliest + percentInTime;
     }
 
+    @NonNull
+    private int[] getPercentsToDropConstraints(int priority) {
+        int[] percentsToDropConstraints = mPercentsToDropConstraints.get(priority);
+        if (percentsToDropConstraints == null) {
+            Slog.wtf(TAG, "No %-to-drop for priority " + JobInfo.getPriorityString(priority));
+            return new int[]{50, 60, 70, 80};
+        }
+        return percentsToDropConstraints;
+    }
+
     @Override
     @GuardedBy("mLock")
     public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
-        if (prevBias != JobInfo.BIAS_TOP_APP && newBias != JobInfo.BIAS_TOP_APP) {
+        if (prevBias < JobInfo.BIAS_BOUND_FOREGROUND_SERVICE
+                && newBias < JobInfo.BIAS_BOUND_FOREGROUND_SERVICE) {
+            // All changes are below BFGS. There's no significant change to care about.
             return;
         }
         final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -557,6 +793,39 @@
         mFcConfig.processConstantLocked(properties, key);
     }
 
+    private void registerBroadcastReceiver() {
+        IntentFilter filter = new IntentFilter(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
+        mContext.registerReceiver(mBroadcastReceiver, filter);
+    }
+
+    private void unregisterBroadcastReceiver() {
+        mContext.unregisterReceiver(mBroadcastReceiver);
+    }
+
+    private void updatePowerAllowlistCache() {
+        if (mDeviceIdleInternal == null) {
+            return;
+        }
+
+        // Don't call out to DeviceIdleController with the lock held.
+        final String[] allowlistedPkgs = mDeviceIdleInternal.getFullPowerWhitelistExceptIdle();
+        final ArraySet<String> changedPkgs = new ArraySet<>();
+        synchronized (mLock) {
+            changedPkgs.addAll(mPowerAllowlistedApps);
+            mPowerAllowlistedApps.clear();
+            for (final String pkgName : allowlistedPkgs) {
+                mPowerAllowlistedApps.add(pkgName);
+                if (changedPkgs.contains(pkgName)) {
+                    changedPkgs.remove(pkgName);
+                } else {
+                    changedPkgs.add(pkgName);
+                }
+            }
+            mPackagesToCheck.addAll(changedPkgs);
+            mHandler.sendEmptyMessage(MSG_CHECK_PACKAGES);
+        }
+    }
+
     @VisibleForTesting
     class FlexibilityTracker {
         final ArrayList<ArraySet<JobStatus>> mTrackedJobs;
@@ -601,10 +870,12 @@
                     Integer.bitCount(getRelevantAppliedConstraintsLocked(js));
             js.setNumAppliedFlexibleConstraints(numAppliedConstraints);
 
+            final int[] percentsToDropConstraints =
+                    getPercentsToDropConstraints(js.getEffectivePriority());
             final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed);
             int toDrop = 0;
             for (int i = 0; i < numAppliedConstraints; i++) {
-                if (curPercent >= mPercentToDropConstraints[i]) {
+                if (curPercent >= percentsToDropConstraints[i]) {
                     toDrop++;
                 }
             }
@@ -625,8 +896,10 @@
             final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed);
             int toDrop = 0;
             final int jsMaxFlexibleConstraints = js.getNumAppliedFlexibleConstraints();
+            final int[] percentsToDropConstraints =
+                    getPercentsToDropConstraints(js.getEffectivePriority());
             for (int i = 0; i < jsMaxFlexibleConstraints; i++) {
-                if (curPercent >= mPercentToDropConstraints[i]) {
+                if (curPercent >= percentsToDropConstraints[i]) {
                     toDrop++;
                 }
             }
@@ -653,7 +926,7 @@
             return mTrackedJobs.size();
         }
 
-        public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
+        public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate, long nowElapsed) {
             for (int i = 0; i < mTrackedJobs.size(); i++) {
                 ArraySet<JobStatus> jobs = mTrackedJobs.get(i);
                 for (int j = 0; j < jobs.size(); j++) {
@@ -664,8 +937,18 @@
                     js.printUniqueId(pw);
                     pw.print(" from ");
                     UserHandle.formatUid(pw, js.getSourceUid());
-                    pw.print(" Num Required Constraints: ");
+                    pw.print("-> Num Required Constraints: ");
                     pw.print(js.getNumRequiredFlexibleConstraints());
+
+                    pw.print(", lifecycle=[");
+                    final long earliest = getLifeCycleBeginningElapsedLocked(js);
+                    pw.print(earliest);
+                    pw.print(", (");
+                    pw.print(getCurPercentOfLifecycleLocked(js, nowElapsed));
+                    pw.print("%), ");
+                    pw.print(getLifeCycleEndElapsedLocked(js, nowElapsed, earliest));
+                    pw.print("]");
+
                     pw.println();
                 }
             }
@@ -688,7 +971,21 @@
         public void scheduleDropNumConstraintsAlarm(JobStatus js, long nowElapsed) {
             synchronized (mLock) {
                 final long earliest = getLifeCycleBeginningElapsedLocked(js);
-                final long latest = getLifeCycleEndElapsedLocked(js, earliest);
+                final long latest = getLifeCycleEndElapsedLocked(js, nowElapsed, earliest);
+                if (latest <= earliest) {
+                    // Something has gone horribly wrong. This has only occurred on incorrectly
+                    // configured tests, but add a check here for safety.
+                    Slog.wtf(TAG, "Got invalid latest when scheduling alarm."
+                            + " Prefetch=" + js.getJob().isPrefetch());
+                    // Since things have gone wrong, the safest and most reliable thing to do is
+                    // stop applying flex policy to the job.
+                    mFlexibilityTracker.setNumDroppedFlexibleConstraints(js,
+                            js.getNumAppliedFlexibleConstraints());
+                    mJobsToCheck.add(js);
+                    mHandler.sendEmptyMessage(MSG_CHECK_JOBS);
+                    return;
+                }
+
                 final long nextTimeElapsed =
                         getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
 
@@ -710,7 +1007,8 @@
                     }
                     mFlexibilityTracker.setNumDroppedFlexibleConstraints(js,
                             js.getNumAppliedFlexibleConstraints());
-                    mHandler.obtainMessage(MSG_UPDATE_JOB, js).sendToTarget();
+                    mJobsToCheck.add(js);
+                    mHandler.sendEmptyMessage(MSG_CHECK_JOBS);
                     return;
                 }
                 if (nextTimeElapsed == NO_LIFECYCLE_END) {
@@ -761,10 +1059,12 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_UPDATE_JOBS:
-                    removeMessages(MSG_UPDATE_JOBS);
+                case MSG_CHECK_ALL_JOBS:
+                    removeMessages(MSG_CHECK_ALL_JOBS);
 
                     synchronized (mLock) {
+                        mJobsToCheck.clear();
+                        mPackagesToCheck.clear();
                         final long nowElapsed = sElapsedRealtimeClock.millis();
                         final ArraySet<JobStatus> changedJobs = new ArraySet<>();
 
@@ -790,19 +1090,50 @@
                     }
                     break;
 
-                case MSG_UPDATE_JOB:
+                case MSG_CHECK_JOBS:
                     synchronized (mLock) {
-                        final JobStatus js = (JobStatus) msg.obj;
-                        if (DEBUG) {
-                            Slog.d("blah", "Checking on " + js.toShortString());
-                        }
                         final long nowElapsed = sElapsedRealtimeClock.millis();
-                        if (js.setFlexibilityConstraintSatisfied(
-                                nowElapsed, isFlexibilitySatisfiedLocked(js))) {
-                            // TODO(141645789): add method that will take a single job
-                            ArraySet<JobStatus> changedJob = new ArraySet<>();
-                            changedJob.add(js);
-                            mStateChangedListener.onControllerStateChanged(changedJob);
+                        ArraySet<JobStatus> changedJobs = new ArraySet<>();
+
+                        for (int i = mJobsToCheck.size() - 1; i >= 0; --i) {
+                            final JobStatus js = mJobsToCheck.valueAt(i);
+                            if (DEBUG) {
+                                Slog.d(TAG, "Checking on " + js.toShortString());
+                            }
+                            if (js.setFlexibilityConstraintSatisfied(
+                                    nowElapsed, isFlexibilitySatisfiedLocked(js))) {
+                                changedJobs.add(js);
+                            }
+                        }
+
+                        mJobsToCheck.clear();
+                        if (changedJobs.size() > 0) {
+                            mStateChangedListener.onControllerStateChanged(changedJobs);
+                        }
+                    }
+                    break;
+
+                case MSG_CHECK_PACKAGES:
+                    synchronized (mLock) {
+                        final long nowElapsed = sElapsedRealtimeClock.millis();
+                        final ArraySet<JobStatus> changedJobs = new ArraySet<>();
+
+                        mService.getJobStore().forEachJob(
+                                (js) -> mPackagesToCheck.contains(js.getSourcePackageName())
+                                        || mPackagesToCheck.contains(js.getCallingPackageName()),
+                                (js) -> {
+                                    if (DEBUG) {
+                                        Slog.d(TAG, "Checking on " + js.toShortString());
+                                    }
+                                    if (js.setFlexibilityConstraintSatisfied(
+                                            nowElapsed, isFlexibilitySatisfiedLocked(js))) {
+                                        changedJobs.add(js);
+                                    }
+                                });
+
+                        mPackagesToCheck.clear();
+                        if (changedJobs.size() > 0) {
+                            mStateChangedListener.onControllerStateChanged(changedJobs);
                         }
                     }
                     break;
@@ -822,10 +1153,16 @@
                 FC_CONFIG_PREFIX + "flexibility_deadline_proximity_limit_ms";
         static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE =
                 FC_CONFIG_PREFIX + "fallback_flexibility_deadline_ms";
+        static final String KEY_FALLBACK_FLEXIBILITY_DEADLINES =
+                FC_CONFIG_PREFIX + "fallback_flexibility_deadlines";
+        static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES =
+                FC_CONFIG_PREFIX + "fallback_flexibility_deadline_scores";
+        static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS =
+                FC_CONFIG_PREFIX + "fallback_flexibility_deadline_additional_score_time_factors";
         static final String KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS =
                 FC_CONFIG_PREFIX + "min_time_between_flexibility_alarms_ms";
-        static final String KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS =
-                FC_CONFIG_PREFIX + "percents_to_drop_num_flexible_constraints";
+        static final String KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS =
+                FC_CONFIG_PREFIX + "percents_to_drop_flexible_constraints";
         static final String KEY_MAX_RESCHEDULED_DEADLINE_MS =
                 FC_CONFIG_PREFIX + "max_rescheduled_deadline_ms";
         static final String KEY_RESCHEDULED_JOB_DEADLINE_MS =
@@ -837,10 +1174,51 @@
         @VisibleForTesting
         static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
         @VisibleForTesting
-        static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 72 * HOUR_IN_MILLIS;
-        private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS;
+        static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 24 * HOUR_IN_MILLIS;
+        static final SparseLongArray DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES = new SparseLongArray();
+        static final SparseIntArray DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES =
+                new SparseIntArray();
+        static final SparseLongArray
+                DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS =
+                new SparseLongArray();
         @VisibleForTesting
-        final int[] DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS = {50, 60, 70, 80};
+        static final SparseArray<int[]> DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS =
+                new SparseArray<>();
+
+        static {
+            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_MAX, HOUR_IN_MILLIS);
+            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_HIGH, 6 * HOUR_IN_MILLIS);
+            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_DEFAULT, 12 * HOUR_IN_MILLIS);
+            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_LOW, 24 * HOUR_IN_MILLIS);
+            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_MIN, 48 * HOUR_IN_MILLIS);
+            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_MAX, 5);
+            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_HIGH, 4);
+            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_DEFAULT, 3);
+            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_LOW, 2);
+            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_MIN, 1);
+            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
+                    .put(PRIORITY_MAX, 0);
+            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
+                    .put(PRIORITY_HIGH, 4 * MINUTE_IN_MILLIS);
+            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
+                    .put(PRIORITY_DEFAULT, 3 * MINUTE_IN_MILLIS);
+            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
+                    .put(PRIORITY_LOW, 2 * MINUTE_IN_MILLIS);
+            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
+                    .put(PRIORITY_MIN, 1 * MINUTE_IN_MILLIS);
+            DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+                    .put(PRIORITY_MAX, new int[]{1, 2, 3, 4});
+            DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+                    .put(PRIORITY_HIGH, new int[]{33, 50, 60, 75});
+            DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+                    .put(PRIORITY_DEFAULT, new int[]{50, 60, 70, 80});
+            DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+                    .put(PRIORITY_LOW, new int[]{50, 60, 70, 80});
+            DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+                    .put(PRIORITY_MIN, new int[]{55, 65, 75, 85});
+        }
+
+        private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS;
         private static final long DEFAULT_RESCHEDULED_JOB_DEADLINE_MS = HOUR_IN_MILLIS;
         private static final long DEFAULT_MAX_RESCHEDULED_DEADLINE_MS = 5 * DAY_IN_MILLIS;
         @VisibleForTesting
@@ -854,9 +1232,11 @@
         public long FALLBACK_FLEXIBILITY_DEADLINE_MS = DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
         public long MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS =
                 DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS;
-        /** The percentages of a jobs' lifecycle to drop the number of required constraints. */
-        public int[] PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS =
-                DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+        /**
+         * The percentages of a jobs' lifecycle to drop the number of required constraints.
+         * Keyed by job priority.
+         */
+        public SparseArray<int[]> PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS = new SparseArray<>();
         /** Initial fallback flexible deadline for rescheduled jobs. */
         public long RESCHEDULED_JOB_DEADLINE_MS = DEFAULT_RESCHEDULED_JOB_DEADLINE_MS;
         /** The max deadline for rescheduled jobs. */
@@ -866,10 +1246,56 @@
          * it in order to run jobs.
          */
         public long UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS;
+        /**
+         * The base fallback deadlines to use if a job doesn't have its own deadline. Values are in
+         * milliseconds and keyed by job priority.
+         */
+        public final SparseLongArray FALLBACK_FLEXIBILITY_DEADLINES = new SparseLongArray();
+        /**
+         * The score to ascribe to each job, keyed by job priority.
+         */
+        public final SparseIntArray FALLBACK_FLEXIBILITY_DEADLINE_SCORES = new SparseIntArray();
+        /**
+         * How much additional time to increase the fallback deadline by based on the app's current
+         * job run score. Values are in
+         * milliseconds and keyed by job priority.
+         */
+        public final SparseLongArray FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS =
+                new SparseLongArray();
+
+        FcConfig() {
+            // Copy the values from the DEFAULT_* data structures to avoid accidentally modifying
+            // the DEFAULT_* data structures in other parts of the code.
+            for (int i = 0; i < DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.size(); ++i) {
+                FALLBACK_FLEXIBILITY_DEADLINES.put(
+                        DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.keyAt(i),
+                        DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.valueAt(i));
+            }
+            for (int i = 0; i < DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.size(); ++i) {
+                FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(
+                        DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.keyAt(i),
+                        DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.valueAt(i));
+            }
+            for (int i = 0;
+                    i < DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS.size();
+                    ++i) {
+                FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS.put(
+                        DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
+                                .keyAt(i),
+                        DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
+                                .valueAt(i));
+            }
+            for (int i = 0; i < DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.size(); ++i) {
+                PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.put(
+                        DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.keyAt(i),
+                        DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.valueAt(i));
+            }
+        }
 
         @GuardedBy("mLock")
         public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
                 @NonNull String key) {
+            // TODO(257322915): add appropriate minimums and maximums to constants when parsing
             switch (key) {
                 case KEY_APPLIED_CONSTRAINTS:
                     APPLIED_CONSTRAINTS =
@@ -882,10 +1308,12 @@
                             mFlexibilityEnabled = true;
                             mPrefetchController
                                     .registerPrefetchChangedListener(mPrefetchChangedListener);
+                            registerBroadcastReceiver();
                         } else {
                             mFlexibilityEnabled = false;
                             mPrefetchController
                                     .unRegisterPrefetchChangedListener(mPrefetchChangedListener);
+                            unregisterBroadcastReceiver();
                         }
                     }
                     break;
@@ -918,6 +1346,33 @@
                             properties.getLong(key, DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS);
                     if (mFallbackFlexibilityDeadlineMs != FALLBACK_FLEXIBILITY_DEADLINE_MS) {
                         mFallbackFlexibilityDeadlineMs = FALLBACK_FLEXIBILITY_DEADLINE_MS;
+                    }
+                    break;
+                case KEY_FALLBACK_FLEXIBILITY_DEADLINES:
+                    if (parsePriorityToLongKeyValueString(
+                            properties.getString(key, null),
+                            FALLBACK_FLEXIBILITY_DEADLINES,
+                            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES)) {
+                        mFallbackFlexibilityDeadlines = FALLBACK_FLEXIBILITY_DEADLINES;
+                        mShouldReevaluateConstraints = true;
+                    }
+                    break;
+                case KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES:
+                    if (parsePriorityToIntKeyValueString(
+                            properties.getString(key, null),
+                            FALLBACK_FLEXIBILITY_DEADLINE_SCORES,
+                            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES)) {
+                        mFallbackFlexibilityDeadlineScores = FALLBACK_FLEXIBILITY_DEADLINE_SCORES;
+                        mShouldReevaluateConstraints = true;
+                    }
+                    break;
+                case KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS:
+                    if (parsePriorityToLongKeyValueString(
+                            properties.getString(key, null),
+                            FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS,
+                            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS)) {
+                        mFallbackFlexibilityAdditionalScoreTimeFactors =
+                                FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS;
                         mShouldReevaluateConstraints = true;
                     }
                     break;
@@ -940,25 +1395,69 @@
                         mShouldReevaluateConstraints = true;
                     }
                     break;
-                case KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS:
-                    String dropPercentString = properties.getString(key, "");
-                    PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS =
-                            parsePercentToDropString(dropPercentString);
-                    if (PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS != null
-                            && !Arrays.equals(mPercentToDropConstraints,
-                            PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS)) {
-                        mPercentToDropConstraints = PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS;
+                case KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS:
+                    if (parsePercentToDropKeyValueString(
+                            properties.getString(key, null),
+                            PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+                            DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS)) {
+                        mPercentsToDropConstraints = PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
                         mShouldReevaluateConstraints = true;
                     }
                     break;
             }
         }
 
-        private int[] parsePercentToDropString(String s) {
-            String[] dropPercentString = s.split(",");
+        private boolean parsePercentToDropKeyValueString(@Nullable String s,
+                SparseArray<int[]> into, SparseArray<int[]> defaults) {
+            final KeyValueListParser priorityParser = new KeyValueListParser(',');
+            try {
+                priorityParser.setString(s);
+            } catch (IllegalArgumentException e) {
+                Slog.wtf(TAG, "Bad percent to drop key value string given", e);
+                // Clear the string and continue with the defaults.
+                priorityParser.setString(null);
+            }
+
+            final int[] oldMax = into.get(PRIORITY_MAX);
+            final int[] oldHigh = into.get(PRIORITY_HIGH);
+            final int[] oldDefault = into.get(PRIORITY_DEFAULT);
+            final int[] oldLow = into.get(PRIORITY_LOW);
+            final int[] oldMin = into.get(PRIORITY_MIN);
+
+            final int[] newMax = parsePercentToDropString(priorityParser.getString(
+                    String.valueOf(PRIORITY_MAX), null));
+            final int[] newHigh = parsePercentToDropString(priorityParser.getString(
+                    String.valueOf(PRIORITY_HIGH), null));
+            final int[] newDefault = parsePercentToDropString(priorityParser.getString(
+                    String.valueOf(PRIORITY_DEFAULT), null));
+            final int[] newLow = parsePercentToDropString(priorityParser.getString(
+                    String.valueOf(PRIORITY_LOW), null));
+            final int[] newMin = parsePercentToDropString(priorityParser.getString(
+                    String.valueOf(PRIORITY_MIN), null));
+
+            into.put(PRIORITY_MAX, newMax == null ? defaults.get(PRIORITY_MAX) : newMax);
+            into.put(PRIORITY_HIGH, newHigh == null ? defaults.get(PRIORITY_HIGH) : newHigh);
+            into.put(PRIORITY_DEFAULT,
+                    newDefault == null ? defaults.get(PRIORITY_DEFAULT) : newDefault);
+            into.put(PRIORITY_LOW, newLow == null ? defaults.get(PRIORITY_LOW) : newLow);
+            into.put(PRIORITY_MIN, newMin == null ? defaults.get(PRIORITY_MIN) : newMin);
+
+            return !Arrays.equals(oldMax, into.get(PRIORITY_MAX))
+                    || !Arrays.equals(oldHigh, into.get(PRIORITY_HIGH))
+                    || !Arrays.equals(oldDefault, into.get(PRIORITY_DEFAULT))
+                    || !Arrays.equals(oldLow, into.get(PRIORITY_LOW))
+                    || !Arrays.equals(oldMin, into.get(PRIORITY_MIN));
+        }
+
+        @Nullable
+        private int[] parsePercentToDropString(@Nullable String s) {
+            if (s == null || s.isEmpty()) {
+                return null;
+            }
+            final String[] dropPercentString = s.split("\\|");
             int[] dropPercentInt = new int[Integer.bitCount(FLEXIBLE_CONSTRAINTS)];
             if (dropPercentInt.length != dropPercentString.length) {
-                return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+                return null;
             }
             int prevPercent = 0;
             for (int i = 0; i < dropPercentString.length; i++) {
@@ -967,11 +1466,15 @@
                             Integer.parseInt(dropPercentString[i]);
                 } catch (NumberFormatException ex) {
                     Slog.e(TAG, "Provided string was improperly formatted.", ex);
-                    return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+                    return null;
                 }
                 if (dropPercentInt[i] < prevPercent) {
                     Slog.wtf(TAG, "Percents to drop constraints were not in increasing order.");
-                    return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+                    return null;
+                }
+                if (dropPercentInt[i] > 100) {
+                    Slog.e(TAG, "Found % over 100");
+                    return null;
                 }
                 prevPercent = dropPercentInt[i];
             }
@@ -979,19 +1482,127 @@
             return dropPercentInt;
         }
 
+        /**
+         * Parses the input string, expecting it to a key-value string where the keys are job
+         * priorities, and replaces everything in {@code into} with the values from the string,
+         * or the default values if the string contains none.
+         *
+         * Returns true if any values changed.
+         */
+        private boolean parsePriorityToIntKeyValueString(@Nullable String s,
+                SparseIntArray into, SparseIntArray defaults) {
+            final KeyValueListParser parser = new KeyValueListParser(',');
+            try {
+                parser.setString(s);
+            } catch (IllegalArgumentException e) {
+                Slog.wtf(TAG, "Bad string given", e);
+                // Clear the string and continue with the defaults.
+                parser.setString(null);
+            }
+
+            final int oldMax = into.get(PRIORITY_MAX);
+            final int oldHigh = into.get(PRIORITY_HIGH);
+            final int oldDefault = into.get(PRIORITY_DEFAULT);
+            final int oldLow = into.get(PRIORITY_LOW);
+            final int oldMin = into.get(PRIORITY_MIN);
+
+            final int newMax = parser.getInt(String.valueOf(PRIORITY_MAX),
+                    defaults.get(PRIORITY_MAX));
+            final int newHigh = parser.getInt(String.valueOf(PRIORITY_HIGH),
+                    defaults.get(PRIORITY_HIGH));
+            final int newDefault = parser.getInt(String.valueOf(PRIORITY_DEFAULT),
+                    defaults.get(PRIORITY_DEFAULT));
+            final int newLow = parser.getInt(String.valueOf(PRIORITY_LOW),
+                    defaults.get(PRIORITY_LOW));
+            final int newMin = parser.getInt(String.valueOf(PRIORITY_MIN),
+                    defaults.get(PRIORITY_MIN));
+
+            into.put(PRIORITY_MAX, newMax);
+            into.put(PRIORITY_HIGH, newHigh);
+            into.put(PRIORITY_DEFAULT, newDefault);
+            into.put(PRIORITY_LOW, newLow);
+            into.put(PRIORITY_MIN, newMin);
+
+            return oldMax != newMax
+                    || oldHigh != newHigh
+                    || oldDefault != newDefault
+                    || oldLow != newLow
+                    || oldMin != newMin;
+        }
+
+        /**
+         * Parses the input string, expecting it to a key-value string where the keys are job
+         * priorities, and replaces everything in {@code into} with the values from the string,
+         * or the default values if the string contains none.
+         *
+         * Returns true if any values changed.
+         */
+        private boolean parsePriorityToLongKeyValueString(@Nullable String s,
+                SparseLongArray into, SparseLongArray defaults) {
+            final KeyValueListParser parser = new KeyValueListParser(',');
+            try {
+                parser.setString(s);
+            } catch (IllegalArgumentException e) {
+                Slog.wtf(TAG, "Bad string given", e);
+                // Clear the string and continue with the defaults.
+                parser.setString(null);
+            }
+
+            final long oldMax = into.get(PRIORITY_MAX);
+            final long oldHigh = into.get(PRIORITY_HIGH);
+            final long oldDefault = into.get(PRIORITY_DEFAULT);
+            final long oldLow = into.get(PRIORITY_LOW);
+            final long oldMin = into.get(PRIORITY_MIN);
+
+            final long newMax = parser.getLong(String.valueOf(PRIORITY_MAX),
+                    defaults.get(PRIORITY_MAX));
+            final long newHigh = parser.getLong(String.valueOf(PRIORITY_HIGH),
+                    defaults.get(PRIORITY_HIGH));
+            final long newDefault = parser.getLong(String.valueOf(PRIORITY_DEFAULT),
+                    defaults.get(PRIORITY_DEFAULT));
+            final long newLow = parser.getLong(String.valueOf(PRIORITY_LOW),
+                    defaults.get(PRIORITY_LOW));
+            final long newMin = parser.getLong(String.valueOf(PRIORITY_MIN),
+                    defaults.get(PRIORITY_MIN));
+
+            into.put(PRIORITY_MAX, newMax);
+            into.put(PRIORITY_HIGH, newHigh);
+            into.put(PRIORITY_DEFAULT, newDefault);
+            into.put(PRIORITY_LOW, newLow);
+            into.put(PRIORITY_MIN, newMin);
+
+            return oldMax != newMax
+                    || oldHigh != newHigh
+                    || oldDefault != newDefault
+                    || oldLow != newLow
+                    || oldMin != newMin;
+        }
+
         private void dump(IndentingPrintWriter pw) {
             pw.println();
             pw.print(FlexibilityController.class.getSimpleName());
             pw.println(":");
             pw.increaseIndent();
 
-            pw.print(KEY_APPLIED_CONSTRAINTS, APPLIED_CONSTRAINTS).println();
+            pw.print(KEY_APPLIED_CONSTRAINTS, APPLIED_CONSTRAINTS);
+            pw.print("(");
+            if (APPLIED_CONSTRAINTS != 0) {
+                JobStatus.dumpConstraints(pw, APPLIED_CONSTRAINTS);
+            } else {
+                pw.print("nothing");
+            }
+            pw.println(")");
             pw.print(KEY_DEADLINE_PROXIMITY_LIMIT, DEADLINE_PROXIMITY_LIMIT_MS).println();
             pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE, FALLBACK_FLEXIBILITY_DEADLINE_MS).println();
+            pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINES, FALLBACK_FLEXIBILITY_DEADLINES).println();
+            pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES,
+                    FALLBACK_FLEXIBILITY_DEADLINE_SCORES).println();
+            pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS,
+                    FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS).println();
             pw.print(KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS,
                     MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS).println();
-            pw.print(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS,
-                    PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS).println();
+            pw.print(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+                    PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS).println();
             pw.print(KEY_RESCHEDULED_JOB_DEADLINE_MS, RESCHEDULED_JOB_DEADLINE_MS).println();
             pw.print(KEY_MAX_RESCHEDULED_DEADLINE_MS, MAX_RESCHEDULED_DEADLINE_MS).println();
             pw.print(KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS, UNSEEN_CONSTRAINT_GRACE_PERIOD_MS)
@@ -1044,7 +1655,25 @@
         pw.decreaseIndent();
 
         pw.println();
-        mFlexibilityTracker.dump(pw, predicate);
+        pw.print("Power allowlisted packages: ");
+        pw.println(mPowerAllowlistedApps);
+
+        pw.println();
+        mFlexibilityTracker.dump(pw, predicate, nowElapsed);
+
+        pw.println();
+        pw.println("Job scores:");
+        pw.increaseIndent();
+        mJobScoreTrackers.forEach((uid, pkgName, jobScoreTracker) -> {
+            pw.print(uid);
+            pw.print("/");
+            pw.print(pkgName);
+            pw.print(": ");
+            jobScoreTracker.dump(pw, nowElapsed);
+            pw.println();
+        });
+        pw.decreaseIndent();
+
         pw.println();
         mFlexibilityAlarmQueue.dump(pw);
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index bdc2246..2ea980d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -48,6 +48,7 @@
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.Pair;
+import android.util.Patterns;
 import android.util.Range;
 import android.util.Slog;
 import android.util.TimeUtils;
@@ -76,6 +77,7 @@
 import java.util.Objects;
 import java.util.Random;
 import java.util.function.Predicate;
+import java.util.regex.Pattern;
 
 /**
  * Uniquely identifies a job internally.
@@ -203,6 +205,17 @@
     // TODO(b/129954980): ensure this doesn't spam statsd, especially at boot
     private static final boolean STATS_LOG_ENABLED = false;
 
+    /**
+     * Simple patterns to match some common forms of PII. This is not intended all-encompassing and
+     * any clients should aim to do additional filtering.
+     */
+    private static final ArrayMap<Pattern, String> BASIC_PII_FILTERS = new ArrayMap<>();
+
+    static {
+        BASIC_PII_FILTERS.put(Patterns.EMAIL_ADDRESS, "[EMAIL]");
+        BASIC_PII_FILTERS.put(Patterns.PHONE, "[PHONE]");
+    }
+
     // No override.
     public static final int OVERRIDE_NONE = 0;
     // Override to improve sorting order. Does not affect constraint evaluation.
@@ -250,6 +263,18 @@
     private final long mLoggingJobId;
 
     /**
+     * List of tags from {@link JobInfo#getDebugTags()}, filtered using {@link #BASIC_PII_FILTERS}.
+     * Lazily loaded in {@link #getFilteredDebugTags()}.
+     */
+    @Nullable
+    private String[] mFilteredDebugTags;
+    /**
+     * Trace tag from {@link JobInfo#getTraceTag()}, filtered using {@link #BASIC_PII_FILTERS}.
+     * Lazily loaded in {@link #getFilteredTraceTag()}.
+     */
+    @Nullable
+    private String mFilteredTraceTag;
+    /**
      * Tag to identify the wakelock held for this job. Lazily loaded in
      * {@link #getWakelockTag()} since it's not typically needed until the job is about to run.
      */
@@ -1197,21 +1222,25 @@
             return ACTIVE_INDEX;
         }
 
-        final int bucketWithMediaExemption;
-        if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX
-                && mHasMediaBackupExemption) {
+        final boolean isEligibleAsBackupJob = job.getTriggerContentUris() != null
+                && job.getRequiredNetwork() != null
+                && !job.hasLateConstraint()
+                && mJobSchedulerInternal.hasRunBackupJobsPermission(sourcePackageName, sourceUid);
+        final boolean isBackupExempt = mHasMediaBackupExemption || isEligibleAsBackupJob;
+        final int bucketWithBackupExemption;
+        if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX && isBackupExempt) {
             // Treat it as if it's at most WORKING_INDEX (lower index grants higher quota) since
             // media backup jobs are important to the user, and the source package may not have
             // been used directly in a while.
-            bucketWithMediaExemption = Math.min(WORKING_INDEX, actualBucket);
+            bucketWithBackupExemption = Math.min(WORKING_INDEX, actualBucket);
         } else {
-            bucketWithMediaExemption = actualBucket;
+            bucketWithBackupExemption = actualBucket;
         }
 
         // If the app is considered buggy, but hasn't yet been put in the RESTRICTED bucket
         // (potentially because it's used frequently by the user), limit its effective bucket
         // so that it doesn't get to run as much as a normal ACTIVE app.
-        if (isBuggy && bucketWithMediaExemption < WORKING_INDEX) {
+        if (isBuggy && bucketWithBackupExemption < WORKING_INDEX) {
             if (!mIsDowngradedDueToBuggyApp) {
                 // Safety check to avoid logging multiple times for the same job.
                 Counter.logIncrementWithUid(
@@ -1221,7 +1250,7 @@
             }
             return WORKING_INDEX;
         }
-        return bucketWithMediaExemption;
+        return bucketWithBackupExemption;
     }
 
     /** Returns the real standby bucket of the job. */
@@ -1325,6 +1354,47 @@
         return batteryName;
     }
 
+    @VisibleForTesting
+    @NonNull
+    static String applyBasicPiiFilters(@NonNull String val) {
+        for (int i = BASIC_PII_FILTERS.size() - 1; i >= 0; --i) {
+            val = BASIC_PII_FILTERS.keyAt(i).matcher(val).replaceAll(BASIC_PII_FILTERS.valueAt(i));
+        }
+        return val;
+    }
+
+    /**
+     * List of tags from {@link JobInfo#getDebugTags()}, filtered using a basic set of PII filters.
+     */
+    @NonNull
+    public String[] getFilteredDebugTags() {
+        if (mFilteredDebugTags != null) {
+            return mFilteredDebugTags;
+        }
+        final ArraySet<String> debugTags = job.getDebugTagsArraySet();
+        mFilteredDebugTags = new String[debugTags.size()];
+        for (int i = 0; i < mFilteredDebugTags.length; ++i) {
+            mFilteredDebugTags[i] = applyBasicPiiFilters(debugTags.valueAt(i));
+        }
+        return mFilteredDebugTags;
+    }
+
+    /**
+     * Trace tag from {@link JobInfo#getTraceTag()}, filtered using a basic set of PII filters.
+     */
+    @Nullable
+    public String getFilteredTraceTag() {
+        if (mFilteredTraceTag != null) {
+            return mFilteredTraceTag;
+        }
+        final String rawTag = job.getTraceTag();
+        if (rawTag == null) {
+            return null;
+        }
+        mFilteredTraceTag = applyBasicPiiFilters(rawTag);
+        return mFilteredTraceTag;
+    }
+
     /** Return the String to be used as the tag for the wakelock held for this job. */
     @NonNull
     public String getWakelockTag() {
@@ -2192,7 +2262,7 @@
      * @return Whether or not this job would be ready to run if it had the specified constraint
      * granted, based on its requirements.
      */
-    boolean wouldBeReadyWithConstraint(int constraint) {
+    public boolean wouldBeReadyWithConstraint(int constraint) {
         return readinessStatusWithConstraint(constraint, true);
     }
 
diff --git a/api/Android.bp b/api/Android.bp
index 363197a..b3b18b6 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -60,8 +60,40 @@
 metalava_cmd += " -J--add-opens=java.base/java.util=ALL-UNNAMED "
 metalava_cmd += " --quiet "
 
+soong_config_module_type {
+    name: "enable_crashrecovery_module",
+    module_type: "combined_apis_defaults",
+    config_namespace: "ANDROID",
+    bool_variables: ["release_crashrecovery_module"],
+    properties: [
+        "bootclasspath",
+        "system_server_classpath",
+    ],
+}
+
+soong_config_bool_variable {
+    name: "release_crashrecovery_module",
+}
+
+enable_crashrecovery_module {
+    name: "crashrecovery_module_defaults",
+    soong_config_variables: {
+        release_crashrecovery_module: {
+            bootclasspath: [
+                "framework-crashrecovery",
+            ],
+            system_server_classpath: [
+                "service-crashrecovery",
+            ],
+        },
+    },
+}
+
 combined_apis {
     name: "frameworks-base-api",
+    defaults: [
+        "crashrecovery_module_defaults",
+    ],
     bootclasspath: [
         "android.net.ipsec.ike",
         "art.module.public.api",
@@ -269,6 +301,7 @@
 // classpath (or sources) somehow.
 stubs_defaults {
     name: "android-non-updatable-stubs-defaults",
+    defaults: ["framework-minus-apex-aconfig-declarations"],
     srcs: [":android-non-updatable-stub-sources"],
     sdk_version: "none",
     system_modules: "none",
@@ -444,3 +477,12 @@
         targets: ["droid"],
     },
 }
+
+phony_rule {
+    name: "checkapi",
+    phony_deps: [
+        "frameworks-base-api-current-compat",
+        "frameworks-base-api-system-current-compat",
+        "frameworks-base-api-module-lib-current-compat",
+    ],
+}
diff --git a/api/Android.mk b/api/Android.mk
deleted file mode 100644
index ce5f995..0000000
--- a/api/Android.mk
+++ /dev/null
@@ -1,2 +0,0 @@
-.PHONY: checkapi
-checkapi: frameworks-base-api-current-compat frameworks-base-api-system-current-compat frameworks-base-api-module-lib-current-compat
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index bcfb68f..7ae3224 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -63,6 +63,7 @@
         ":framework-graphics-srcs",
         ":framework-mediaprovider-sources",
         ":framework-nearby-sources",
+        ":framework-nfc-updatable-sources",
         ":framework-ondevicepersonalization-sources",
         ":framework-permission-sources",
         ":framework-permission-s-sources",
@@ -183,6 +184,7 @@
         "-federationapi AndroidX $(location :current-androidx-api)",
         // doclava contains checks for a few issues that are have been migrated to metalava.
         // disable them in doclava, to avoid mistriggering or double triggering.
+        "-hide 101", // TODO: turn Lint 101 back into an error again
         "-hide 111", // HIDDEN_SUPERCLASS
         "-hide 113", // DEPRECATION_MISMATCH
         "-hide 125", // REQUIRES_PERMISSION
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index ef1fa60..59c0128 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -239,6 +239,10 @@
     name: "android-non-updatable_from_source_defaults",
     libs: ["stub-annotations"],
     static_libs: ["framework-res-package-jar"], // Export package of framework-res
+}
+
+java_defaults {
+    name: "android-non-updatable_exportable_from_source_defaults",
     dist: {
         targets: ["sdk"],
         tag: ".jar",
@@ -265,6 +269,14 @@
 }
 
 java_library {
+    name: "android-non-updatable.stubs.exportable",
+    defaults: ["android-non-updatable_defaults"],
+    static_libs: [
+        "android-non-updatable.stubs.exportable.from-source",
+    ],
+}
+
+java_library {
     name: "android-non-updatable.stubs.system",
     defaults: ["android-non-updatable_defaults"],
     static_libs: [
@@ -283,6 +295,14 @@
 }
 
 java_library {
+    name: "android-non-updatable.stubs.exportable.system",
+    defaults: ["android-non-updatable_defaults"],
+    static_libs: [
+        "android-non-updatable.stubs.exportable.system.from-source",
+    ],
+}
+
+java_library {
     name: "android-non-updatable.stubs.module_lib",
     defaults: ["android-non-updatable_defaults"],
     static_libs: [
@@ -301,6 +321,14 @@
 }
 
 java_library {
+    name: "android-non-updatable.stubs.exportable.module_lib",
+    defaults: ["android-non-updatable_defaults"],
+    static_libs: [
+        "android-non-updatable.stubs.exportable.module_lib.from-source",
+    ],
+}
+
+java_library {
     name: "android-non-updatable.stubs.test",
     defaults: ["android-non-updatable_defaults"],
     static_libs: [
@@ -319,6 +347,14 @@
 }
 
 java_library {
+    name: "android-non-updatable.stubs.exportable.test",
+    defaults: ["android-non-updatable_defaults"],
+    static_libs: [
+        "android-non-updatable.stubs.exportable.test.from-source",
+    ],
+}
+
+java_library {
     name: "android-non-updatable.stubs.from-source",
     defaults: [
         "android-non-updatable_defaults",
@@ -326,6 +362,17 @@
     ],
     srcs: [":api-stubs-docs-non-updatable"],
     libs: ["all-modules-public-stubs"],
+}
+
+java_library {
+    name: "android-non-updatable.stubs.exportable.from-source",
+    defaults: [
+        "android-non-updatable_defaults",
+        "android-non-updatable_from_source_defaults",
+        "android-non-updatable_exportable_from_source_defaults",
+    ],
+    srcs: [":api-stubs-docs-non-updatable{.exportable}"],
+    libs: ["all-modules-public-stubs"],
     dist: {
         dir: "apistubs/android/public",
     },
@@ -339,6 +386,17 @@
     ],
     srcs: [":system-api-stubs-docs-non-updatable"],
     libs: ["all-modules-system-stubs"],
+}
+
+java_library {
+    name: "android-non-updatable.stubs.exportable.system.from-source",
+    defaults: [
+        "android-non-updatable_defaults",
+        "android-non-updatable_from_source_defaults",
+        "android-non-updatable_exportable_from_source_defaults",
+    ],
+    srcs: [":system-api-stubs-docs-non-updatable{.exportable}"],
+    libs: ["all-modules-system-stubs"],
     dist: {
         dir: "apistubs/android/system",
     },
@@ -352,6 +410,17 @@
     ],
     srcs: [":module-lib-api-stubs-docs-non-updatable"],
     libs: non_updatable_api_deps_on_modules,
+}
+
+java_library {
+    name: "android-non-updatable.stubs.exportable.module_lib.from-source",
+    defaults: [
+        "android-non-updatable_defaults",
+        "android-non-updatable_from_source_defaults",
+        "android-non-updatable_exportable_from_source_defaults",
+    ],
+    srcs: [":module-lib-api-stubs-docs-non-updatable{.exportable}"],
+    libs: non_updatable_api_deps_on_modules,
     dist: {
         dir: "apistubs/android/module-lib",
     },
@@ -365,6 +434,17 @@
     ],
     srcs: [":test-api-stubs-docs-non-updatable"],
     libs: ["all-modules-system-stubs"],
+}
+
+java_library {
+    name: "android-non-updatable.stubs.exportable.test.from-source",
+    defaults: [
+        "android-non-updatable_defaults",
+        "android-non-updatable_from_source_defaults",
+        "android-non-updatable_exportable_from_source_defaults",
+    ],
+    srcs: [":test-api-stubs-docs-non-updatable{.exportable}"],
+    libs: ["all-modules-system-stubs"],
     dist: {
         dir: "apistubs/android/test",
     },
@@ -462,6 +542,16 @@
 }
 
 java_library {
+    name: "android_stubs_current_exportable.from-source",
+    static_libs: [
+        "all-modules-public-stubs-exportable",
+        "android-non-updatable.stubs.exportable",
+        "private-stub-annotations-jar",
+    ],
+    defaults: ["android.jar_defaults"],
+}
+
+java_library {
     name: "android_system_stubs_current.from-source",
     static_libs: [
         "all-modules-system-stubs",
@@ -470,6 +560,19 @@
     ],
     defaults: [
         "android.jar_defaults",
+    ],
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library {
+    name: "android_system_stubs_current_exportable.from-source",
+    static_libs: [
+        "all-modules-system-stubs-exportable",
+        "android-non-updatable.stubs.exportable.system",
+        "private-stub-annotations-jar",
+    ],
+    defaults: [
+        "android.jar_defaults",
         "android_stubs_dists_default",
     ],
     dist: {
@@ -498,6 +601,23 @@
     ],
     defaults: [
         "android.jar_defaults",
+    ],
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library {
+    name: "android_test_stubs_current_exportable.from-source",
+    static_libs: [
+        // Updatable modules do not have test APIs, but we want to include their SystemApis, like we
+        // include the SystemApi of framework-non-updatable-sources.
+        "all-updatable-modules-system-stubs-exportable",
+        // Non-updatable modules on the other hand can have test APIs, so include their test-stubs.
+        "all-non-updatable-modules-test-stubs-exportable",
+        "android-non-updatable.stubs.exportable.test",
+        "private-stub-annotations-jar",
+    ],
+    defaults: [
+        "android.jar_defaults",
         "android_stubs_dists_default",
     ],
     dist: {
@@ -505,6 +625,7 @@
     },
 }
 
+// This module does not need to be copied to dist
 java_library {
     name: "android_test_frameworks_core_stubs_current.from-source",
     static_libs: [
@@ -513,24 +634,34 @@
     ],
     defaults: [
         "android.jar_defaults",
-        "android_stubs_dists_default",
     ],
-    dist: {
-        dir: "apistubs/android/test-core",
-    },
+    visibility: ["//frameworks/base/services"],
 }
 
 java_library {
     name: "android_module_lib_stubs_current.from-source",
     defaults: [
         "android.jar_defaults",
-        "android_stubs_dists_default",
     ],
     static_libs: [
         "android-non-updatable.stubs.module_lib",
         "art.module.public.api.stubs.module_lib",
         "i18n.module.public.api.stubs",
     ],
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library {
+    name: "android_module_lib_stubs_current_exportable.from-source",
+    defaults: [
+        "android.jar_defaults",
+        "android_stubs_dists_default",
+    ],
+    static_libs: [
+        "android-non-updatable.stubs.exportable.module_lib",
+        "art.module.public.api.stubs.exportable.module_lib",
+        "i18n.module.public.api.stubs.exportable",
+    ],
     dist: {
         dir: "apistubs/android/module-lib",
     },
@@ -540,13 +671,26 @@
     name: "android_system_server_stubs_current.from-source",
     defaults: [
         "android.jar_defaults",
-        "android_stubs_dists_default",
     ],
     srcs: [":services-non-updatable-stubs"],
     installable: false,
     static_libs: [
         "android_module_lib_stubs_current.from-source",
     ],
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library {
+    name: "android_system_server_stubs_current_exportable.from-source",
+    defaults: [
+        "android.jar_defaults",
+        "android_stubs_dists_default",
+    ],
+    srcs: [":services-non-updatable-stubs{.exportable}"],
+    installable: false,
+    static_libs: [
+        "android_module_lib_stubs_current_exportable.from-source",
+    ],
     dist: {
         dir: "apistubs/android/system-server",
     },
@@ -635,7 +779,6 @@
     api_contributions: [
         "framework-virtualization.stubs.source.test.api.contribution",
         "framework-location.stubs.source.test.api.contribution",
-        "framework-nfc.stubs.source.test.api.contribution",
     ],
 }
 
diff --git a/api/api.go b/api/api.go
index 2668999..fa2be21 100644
--- a/api/api.go
+++ b/api/api.go
@@ -31,7 +31,6 @@
 const i18n = "i18n.module.public.api"
 const virtualization = "framework-virtualization"
 const location = "framework-location"
-const nfc = "framework-nfc"
 
 var core_libraries_modules = []string{art, conscrypt, i18n}
 
@@ -43,7 +42,7 @@
 // APIs.
 // In addition, the modules in this list are allowed to contribute to test APIs
 // stubs.
-var non_updatable_modules = []string{virtualization, location, nfc}
+var non_updatable_modules = []string{virtualization, location}
 
 // The intention behind this soong plugin is to generate a number of "merged"
 // API-related modules that would otherwise require a large amount of very
@@ -64,6 +63,7 @@
 
 type CombinedApis struct {
 	android.ModuleBase
+	android.DefaultableModuleBase
 
 	properties CombinedApisProperties
 }
@@ -74,6 +74,7 @@
 
 func registerBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("combined_apis", combinedApisModuleFactory)
+	ctx.RegisterModuleType("combined_apis_defaults", CombinedApisModuleDefaultsFactory)
 }
 
 var PrepareForCombinedApisTest = android.FixtureRegisterWithContext(registerBuildComponents)
@@ -203,6 +204,15 @@
 	ctx.CreateModule(java.LibraryFactory, &props)
 }
 
+func createMergedPublicExportableStubs(ctx android.LoadHookContext, modules []string) {
+	props := libraryProps{}
+	props.Name = proptools.StringPtr("all-modules-public-stubs-exportable")
+	props.Static_libs = transformArray(modules, "", ".stubs.exportable")
+	props.Sdk_version = proptools.StringPtr("module_current")
+	props.Visibility = []string{"//frameworks/base"}
+	ctx.CreateModule(java.LibraryFactory, &props)
+}
+
 func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) {
 	// First create the all-updatable-modules-system-stubs
 	{
@@ -227,6 +237,30 @@
 	}
 }
 
+func createMergedSystemExportableStubs(ctx android.LoadHookContext, modules []string) {
+	// First create the all-updatable-modules-system-stubs
+	{
+		updatable_modules := removeAll(modules, non_updatable_modules)
+		props := libraryProps{}
+		props.Name = proptools.StringPtr("all-updatable-modules-system-stubs-exportable")
+		props.Static_libs = transformArray(updatable_modules, "", ".stubs.exportable.system")
+		props.Sdk_version = proptools.StringPtr("module_current")
+		props.Visibility = []string{"//frameworks/base"}
+		ctx.CreateModule(java.LibraryFactory, &props)
+	}
+	// Now merge all-updatable-modules-system-stubs and stubs from non-updatable modules
+	// into all-modules-system-stubs.
+	{
+		props := libraryProps{}
+		props.Name = proptools.StringPtr("all-modules-system-stubs-exportable")
+		props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.exportable.system")
+		props.Static_libs = append(props.Static_libs, "all-updatable-modules-system-stubs-exportable")
+		props.Sdk_version = proptools.StringPtr("module_current")
+		props.Visibility = []string{"//frameworks/base"}
+		ctx.CreateModule(java.LibraryFactory, &props)
+	}
+}
+
 func createMergedTestStubsForNonUpdatableModules(ctx android.LoadHookContext) {
 	props := libraryProps{}
 	props.Name = proptools.StringPtr("all-non-updatable-modules-test-stubs")
@@ -236,6 +270,15 @@
 	ctx.CreateModule(java.LibraryFactory, &props)
 }
 
+func createMergedTestExportableStubsForNonUpdatableModules(ctx android.LoadHookContext) {
+	props := libraryProps{}
+	props.Name = proptools.StringPtr("all-non-updatable-modules-test-stubs-exportable")
+	props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.exportable.test")
+	props.Sdk_version = proptools.StringPtr("module_current")
+	props.Visibility = []string{"//frameworks/base"}
+	ctx.CreateModule(java.LibraryFactory, &props)
+}
+
 func createMergedFrameworkImpl(ctx android.LoadHookContext, modules []string) {
 	// This module is for the "framework-all" module, which should not include the core libraries.
 	modules = removeAll(modules, core_libraries_modules)
@@ -266,6 +309,19 @@
 	}
 }
 
+func createMergedFrameworkModuleLibExportableStubs(ctx android.LoadHookContext, modules []string) {
+	// The user of this module compiles against the "core" SDK and against non-updatable modules,
+	// so remove to avoid dupes.
+	modules = removeAll(modules, core_libraries_modules)
+	modules = removeAll(modules, non_updatable_modules)
+	props := libraryProps{}
+	props.Name = proptools.StringPtr("framework-updatable-stubs-module_libs_api-exportable")
+	props.Static_libs = transformArray(modules, "", ".stubs.exportable.module_lib")
+	props.Sdk_version = proptools.StringPtr("module_current")
+	props.Visibility = []string{"//frameworks/base"}
+	ctx.CreateModule(java.LibraryFactory, &props)
+}
+
 func createMergedFrameworkModuleLibStubs(ctx android.LoadHookContext, modules []string) {
 	// The user of this module compiles against the "core" SDK and against non-updatable modules,
 	// so remove to avoid dupes.
@@ -381,6 +437,27 @@
 	}
 }
 
+func createFullExportableApiLibraries(ctx android.LoadHookContext) {
+	javaLibraryNames := []string{
+		"android_stubs_current_exportable",
+		"android_system_stubs_current_exportable",
+		"android_test_stubs_current_exportable",
+		"android_module_lib_stubs_current_exportable",
+		"android_system_server_stubs_current_exportable",
+	}
+
+	for _, libraryName := range javaLibraryNames {
+		props := libraryProps{}
+		props.Name = proptools.StringPtr(libraryName)
+		staticLib := libraryName + ".from-source"
+		props.Static_libs = []string{staticLib}
+		props.Defaults = []string{"android.jar_defaults"}
+		props.Visibility = []string{"//visibility:public"}
+
+		ctx.CreateModule(java.LibraryFactory, &props)
+	}
+}
+
 func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) {
 	bootclasspath := a.properties.Bootclasspath
 	system_server_classpath := a.properties.System_server_classpath
@@ -396,6 +473,11 @@
 	createMergedFrameworkModuleLibStubs(ctx, bootclasspath)
 	createMergedFrameworkImpl(ctx, bootclasspath)
 
+	createMergedPublicExportableStubs(ctx, bootclasspath)
+	createMergedSystemExportableStubs(ctx, bootclasspath)
+	createMergedTestExportableStubsForNonUpdatableModules(ctx)
+	createMergedFrameworkModuleLibExportableStubs(ctx, bootclasspath)
+
 	createMergedAnnotationsFilegroups(ctx, bootclasspath, system_server_classpath)
 
 	createPublicStubsSourceFilegroup(ctx, bootclasspath)
@@ -403,12 +485,15 @@
 	createApiContributionDefaults(ctx, bootclasspath)
 
 	createFullApiLibraries(ctx)
+
+	createFullExportableApiLibraries(ctx)
 }
 
 func combinedApisModuleFactory() android.Module {
 	module := &CombinedApis{}
 	module.AddProperties(&module.properties)
 	android.InitAndroidModule(module)
+	android.InitDefaultableModule(module)
 	android.AddLoadHook(module, func(ctx android.LoadHookContext) { module.createInternalModules(ctx) })
 	return module
 }
@@ -445,3 +530,16 @@
 	}
 	return s2
 }
+
+// Defaults
+type CombinedApisModuleDefaults struct {
+	android.ModuleBase
+	android.DefaultsModuleBase
+}
+
+func CombinedApisModuleDefaultsFactory() android.Module {
+	module := &CombinedApisModuleDefaults{}
+	module.AddProperties(&CombinedApisProperties{})
+	android.InitDefaultsModule(module)
+	return module
+}
diff --git a/boot/Android.bp b/boot/Android.bp
index 8a3d35e..228d060 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -26,9 +26,11 @@
 soong_config_module_type {
     name: "custom_platform_bootclasspath",
     module_type: "platform_bootclasspath",
-    config_namespace: "AUTO",
+    config_namespace: "bootclasspath",
     bool_variables: [
         "car_bootclasspath_fragment",
+        "nfc_apex_bootclasspath_fragment",
+        "release_crashrecovery_module",
     ],
     properties: [
         "fragments",
@@ -155,6 +157,24 @@
                 },
             ],
         },
+        nfc_apex_bootclasspath_fragment: {
+            fragments: [
+                // only used if NFC mainline is enabled.
+                {
+                    apex: "com.android.nfcservices",
+                    module: "com.android.nfcservices-bootclasspath-fragment",
+                },
+            ],
+        },
+        release_crashrecovery_module: {
+            fragments: [
+                // only used when crashrecovery is enabled
+                {
+                    apex: "com.android.crashrecovery",
+                    module: "com.android.crashrecovery-bootclasspath-fragment",
+                },
+            ],
+        },
     },
 
     // Additional information needed by hidden api processing.
diff --git a/boot/hiddenapi/hiddenapi-max-target-o.txt b/boot/hiddenapi/hiddenapi-max-target-o.txt
index 3c16915..2d417ce 100644
--- a/boot/hiddenapi/hiddenapi-max-target-o.txt
+++ b/boot/hiddenapi/hiddenapi-max-target-o.txt
@@ -33834,649 +33834,6 @@
 Landroid/net/WifiLinkQualityInfo;->setTxBad(J)V
 Landroid/net/WifiLinkQualityInfo;->setTxGood(J)V
 Landroid/net/WifiLinkQualityInfo;->setType(I)V
-Landroid/nfc/ApduList;-><init>()V
-Landroid/nfc/ApduList;-><init>(Landroid/os/Parcel;)V
-Landroid/nfc/ApduList;->add([B)V
-Landroid/nfc/ApduList;->commands:Ljava/util/ArrayList;
-Landroid/nfc/ApduList;->CREATOR:Landroid/os/Parcelable$Creator;
-Landroid/nfc/ApduList;->get()Ljava/util/List;
-Landroid/nfc/BeamShareData;-><init>(Landroid/nfc/NdefMessage;[Landroid/net/Uri;Landroid/os/UserHandle;I)V
-Landroid/nfc/BeamShareData;->CREATOR:Landroid/os/Parcelable$Creator;
-Landroid/nfc/BeamShareData;->flags:I
-Landroid/nfc/BeamShareData;->ndefMessage:Landroid/nfc/NdefMessage;
-Landroid/nfc/BeamShareData;->uris:[Landroid/net/Uri;
-Landroid/nfc/BeamShareData;->userHandle:Landroid/os/UserHandle;
-Landroid/nfc/cardemulation/AidGroup;-><init>(Ljava/util/List;Ljava/lang/String;)V
-Landroid/nfc/cardemulation/AidGroup;->isValidCategory(Ljava/lang/String;)Z
-Landroid/nfc/cardemulation/AidGroup;->MAX_NUM_AIDS:I
-Landroid/nfc/cardemulation/AidGroup;->TAG:Ljava/lang/String;
-Landroid/nfc/cardemulation/ApduServiceInfo;->dump(Ljava/io/FileDescriptor;Ljava/io/PrintWriter;[Ljava/lang/String;)V
-Landroid/nfc/cardemulation/ApduServiceInfo;->getAidGroups()Ljava/util/ArrayList;
-Landroid/nfc/cardemulation/ApduServiceInfo;->getAids()Ljava/util/List;
-Landroid/nfc/cardemulation/ApduServiceInfo;->getCategoryForAid(Ljava/lang/String;)Ljava/lang/String;
-Landroid/nfc/cardemulation/ApduServiceInfo;->getComponent()Landroid/content/ComponentName;
-Landroid/nfc/cardemulation/ApduServiceInfo;->getDynamicAidGroupForCategory(Ljava/lang/String;)Landroid/nfc/cardemulation/AidGroup;
-Landroid/nfc/cardemulation/ApduServiceInfo;->getPrefixAids()Ljava/util/List;
-Landroid/nfc/cardemulation/ApduServiceInfo;->getSubsetAids()Ljava/util/List;
-Landroid/nfc/cardemulation/ApduServiceInfo;->hasCategory(Ljava/lang/String;)Z
-Landroid/nfc/cardemulation/ApduServiceInfo;->loadAppLabel(Landroid/content/pm/PackageManager;)Ljava/lang/CharSequence;
-Landroid/nfc/cardemulation/ApduServiceInfo;->loadIcon(Landroid/content/pm/PackageManager;)Landroid/graphics/drawable/Drawable;
-Landroid/nfc/cardemulation/ApduServiceInfo;->loadLabel(Landroid/content/pm/PackageManager;)Ljava/lang/CharSequence;
-Landroid/nfc/cardemulation/ApduServiceInfo;->mBannerResourceId:I
-Landroid/nfc/cardemulation/ApduServiceInfo;->mDescription:Ljava/lang/String;
-Landroid/nfc/cardemulation/ApduServiceInfo;->mOnHost:Z
-Landroid/nfc/cardemulation/ApduServiceInfo;->mRequiresDeviceUnlock:Z
-Landroid/nfc/cardemulation/ApduServiceInfo;->mSettingsActivityName:Ljava/lang/String;
-Landroid/nfc/cardemulation/ApduServiceInfo;->mUid:I
-Landroid/nfc/cardemulation/ApduServiceInfo;->removeDynamicAidGroupForCategory(Ljava/lang/String;)Z
-Landroid/nfc/cardemulation/ApduServiceInfo;->setOrReplaceDynamicAidGroup(Landroid/nfc/cardemulation/AidGroup;)V
-Landroid/nfc/cardemulation/ApduServiceInfo;->TAG:Ljava/lang/String;
-Landroid/nfc/cardemulation/CardEmulation;-><init>(Landroid/content/Context;Landroid/nfc/INfcCardEmulation;)V
-Landroid/nfc/cardemulation/CardEmulation;->getServices(Ljava/lang/String;)Ljava/util/List;
-Landroid/nfc/cardemulation/CardEmulation;->isValidAid(Ljava/lang/String;)Z
-Landroid/nfc/cardemulation/CardEmulation;->mContext:Landroid/content/Context;
-Landroid/nfc/cardemulation/CardEmulation;->recoverService()V
-Landroid/nfc/cardemulation/CardEmulation;->sCardEmus:Ljava/util/HashMap;
-Landroid/nfc/cardemulation/CardEmulation;->setDefaultForNextTap(Landroid/content/ComponentName;)Z
-Landroid/nfc/cardemulation/CardEmulation;->setDefaultServiceForCategory(Landroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/cardemulation/CardEmulation;->sIsInitialized:Z
-Landroid/nfc/cardemulation/CardEmulation;->sService:Landroid/nfc/INfcCardEmulation;
-Landroid/nfc/cardemulation/CardEmulation;->TAG:Ljava/lang/String;
-Landroid/nfc/cardemulation/HostApduService;->KEY_DATA:Ljava/lang/String;
-Landroid/nfc/cardemulation/HostApduService;->mMessenger:Landroid/os/Messenger;
-Landroid/nfc/cardemulation/HostApduService;->mNfcService:Landroid/os/Messenger;
-Landroid/nfc/cardemulation/HostApduService;->MSG_COMMAND_APDU:I
-Landroid/nfc/cardemulation/HostApduService;->MSG_DEACTIVATED:I
-Landroid/nfc/cardemulation/HostApduService;->MSG_RESPONSE_APDU:I
-Landroid/nfc/cardemulation/HostApduService;->MSG_UNHANDLED:I
-Landroid/nfc/cardemulation/HostApduService;->TAG:Ljava/lang/String;
-Landroid/nfc/cardemulation/HostNfcFService;->KEY_DATA:Ljava/lang/String;
-Landroid/nfc/cardemulation/HostNfcFService;->KEY_MESSENGER:Ljava/lang/String;
-Landroid/nfc/cardemulation/HostNfcFService;->mMessenger:Landroid/os/Messenger;
-Landroid/nfc/cardemulation/HostNfcFService;->mNfcService:Landroid/os/Messenger;
-Landroid/nfc/cardemulation/HostNfcFService;->MSG_COMMAND_PACKET:I
-Landroid/nfc/cardemulation/HostNfcFService;->MSG_DEACTIVATED:I
-Landroid/nfc/cardemulation/HostNfcFService;->MSG_RESPONSE_PACKET:I
-Landroid/nfc/cardemulation/HostNfcFService;->TAG:Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFCardEmulation;-><init>(Landroid/content/Context;Landroid/nfc/INfcFCardEmulation;)V
-Landroid/nfc/cardemulation/NfcFCardEmulation;->getMaxNumOfRegisterableSystemCodes()I
-Landroid/nfc/cardemulation/NfcFCardEmulation;->getNfcFServices()Ljava/util/List;
-Landroid/nfc/cardemulation/NfcFCardEmulation;->isValidNfcid2(Ljava/lang/String;)Z
-Landroid/nfc/cardemulation/NfcFCardEmulation;->isValidSystemCode(Ljava/lang/String;)Z
-Landroid/nfc/cardemulation/NfcFCardEmulation;->mContext:Landroid/content/Context;
-Landroid/nfc/cardemulation/NfcFCardEmulation;->recoverService()V
-Landroid/nfc/cardemulation/NfcFCardEmulation;->sCardEmus:Ljava/util/HashMap;
-Landroid/nfc/cardemulation/NfcFCardEmulation;->sIsInitialized:Z
-Landroid/nfc/cardemulation/NfcFCardEmulation;->sService:Landroid/nfc/INfcFCardEmulation;
-Landroid/nfc/cardemulation/NfcFCardEmulation;->TAG:Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;-><init>(Landroid/content/pm/PackageManager;Landroid/content/pm/ResolveInfo;)V
-Landroid/nfc/cardemulation/NfcFServiceInfo;-><init>(Landroid/content/pm/ResolveInfo;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;)V
-Landroid/nfc/cardemulation/NfcFServiceInfo;->CREATOR:Landroid/os/Parcelable$Creator;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->DEFAULT_T3T_PMM:Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->dump(Ljava/io/FileDescriptor;Ljava/io/PrintWriter;[Ljava/lang/String;)V
-Landroid/nfc/cardemulation/NfcFServiceInfo;->getComponent()Landroid/content/ComponentName;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->getDescription()Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->getNfcid2()Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->getSystemCode()Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->getT3tPmm()Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->getUid()I
-Landroid/nfc/cardemulation/NfcFServiceInfo;->loadIcon(Landroid/content/pm/PackageManager;)Landroid/graphics/drawable/Drawable;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->loadLabel(Landroid/content/pm/PackageManager;)Ljava/lang/CharSequence;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->mDescription:Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->mDynamicNfcid2:Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->mDynamicSystemCode:Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->mNfcid2:Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->mService:Landroid/content/pm/ResolveInfo;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->mSystemCode:Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->mT3tPmm:Ljava/lang/String;
-Landroid/nfc/cardemulation/NfcFServiceInfo;->mUid:I
-Landroid/nfc/cardemulation/NfcFServiceInfo;->setOrReplaceDynamicNfcid2(Ljava/lang/String;)V
-Landroid/nfc/cardemulation/NfcFServiceInfo;->setOrReplaceDynamicSystemCode(Ljava/lang/String;)V
-Landroid/nfc/cardemulation/NfcFServiceInfo;->TAG:Ljava/lang/String;
-Landroid/nfc/ErrorCodes;-><init>()V
-Landroid/nfc/ErrorCodes;->asString(I)Ljava/lang/String;
-Landroid/nfc/ErrorCodes;->ERROR_BUFFER_TO_SMALL:I
-Landroid/nfc/ErrorCodes;->ERROR_BUSY:I
-Landroid/nfc/ErrorCodes;->ERROR_CANCELLED:I
-Landroid/nfc/ErrorCodes;->ERROR_CONNECT:I
-Landroid/nfc/ErrorCodes;->ERROR_DISCONNECT:I
-Landroid/nfc/ErrorCodes;->ERROR_INSUFFICIENT_RESOURCES:I
-Landroid/nfc/ErrorCodes;->ERROR_INVALID_PARAM:I
-Landroid/nfc/ErrorCodes;->ERROR_IO:I
-Landroid/nfc/ErrorCodes;->ERROR_NFC_ON:I
-Landroid/nfc/ErrorCodes;->ERROR_NOT_INITIALIZED:I
-Landroid/nfc/ErrorCodes;->ERROR_NOT_SUPPORTED:I
-Landroid/nfc/ErrorCodes;->ERROR_NO_SE_CONNECTED:I
-Landroid/nfc/ErrorCodes;->ERROR_READ:I
-Landroid/nfc/ErrorCodes;->ERROR_SAP_USED:I
-Landroid/nfc/ErrorCodes;->ERROR_SERVICE_NAME_USED:I
-Landroid/nfc/ErrorCodes;->ERROR_SE_ALREADY_SELECTED:I
-Landroid/nfc/ErrorCodes;->ERROR_SE_CONNECTED:I
-Landroid/nfc/ErrorCodes;->ERROR_SOCKET_CREATION:I
-Landroid/nfc/ErrorCodes;->ERROR_SOCKET_NOT_CONNECTED:I
-Landroid/nfc/ErrorCodes;->ERROR_SOCKET_OPTIONS:I
-Landroid/nfc/ErrorCodes;->ERROR_TIMEOUT:I
-Landroid/nfc/ErrorCodes;->ERROR_WRITE:I
-Landroid/nfc/ErrorCodes;->SUCCESS:I
-Landroid/nfc/IAppCallback$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/nfc/IAppCallback$Stub$Proxy;->createBeamShareData(B)Landroid/nfc/BeamShareData;
-Landroid/nfc/IAppCallback$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/nfc/IAppCallback$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/nfc/IAppCallback$Stub$Proxy;->onNdefPushComplete(B)V
-Landroid/nfc/IAppCallback$Stub$Proxy;->onTagDiscovered(Landroid/nfc/Tag;)V
-Landroid/nfc/IAppCallback$Stub;-><init>()V
-Landroid/nfc/IAppCallback$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/IAppCallback;
-Landroid/nfc/IAppCallback$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/nfc/IAppCallback$Stub;->TRANSACTION_createBeamShareData:I
-Landroid/nfc/IAppCallback$Stub;->TRANSACTION_onNdefPushComplete:I
-Landroid/nfc/IAppCallback$Stub;->TRANSACTION_onTagDiscovered:I
-Landroid/nfc/IAppCallback;->createBeamShareData(B)Landroid/nfc/BeamShareData;
-Landroid/nfc/IAppCallback;->onNdefPushComplete(B)V
-Landroid/nfc/IAppCallback;->onTagDiscovered(Landroid/nfc/Tag;)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->addNfcUnlockHandler(Landroid/nfc/INfcUnlockHandler;[I)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->disable(Z)Z
-Landroid/nfc/INfcAdapter$Stub$Proxy;->disableNdefPush()Z
-Landroid/nfc/INfcAdapter$Stub$Proxy;->dispatch(Landroid/nfc/Tag;)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->enable()Z
-Landroid/nfc/INfcAdapter$Stub$Proxy;->enableNdefPush()Z
-Landroid/nfc/INfcAdapter$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/nfc/INfcAdapter$Stub$Proxy;->getNfcAdapterExtrasInterface(Ljava/lang/String;)Landroid/nfc/INfcAdapterExtras;
-Landroid/nfc/INfcAdapter$Stub$Proxy;->getNfcCardEmulationInterface()Landroid/nfc/INfcCardEmulation;
-Landroid/nfc/INfcAdapter$Stub$Proxy;->getNfcDtaInterface(Ljava/lang/String;)Landroid/nfc/INfcDta;
-Landroid/nfc/INfcAdapter$Stub$Proxy;->getNfcFCardEmulationInterface()Landroid/nfc/INfcFCardEmulation;
-Landroid/nfc/INfcAdapter$Stub$Proxy;->getNfcTagInterface()Landroid/nfc/INfcTag;
-Landroid/nfc/INfcAdapter$Stub$Proxy;->getState()I
-Landroid/nfc/INfcAdapter$Stub$Proxy;->ignore(IILandroid/nfc/ITagRemovedCallback;)Z
-Landroid/nfc/INfcAdapter$Stub$Proxy;->invokeBeam()V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->invokeBeamInternal(Landroid/nfc/BeamShareData;)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->isNdefPushEnabled()Z
-Landroid/nfc/INfcAdapter$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/nfc/INfcAdapter$Stub$Proxy;->pausePolling(I)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->removeNfcUnlockHandler(Landroid/nfc/INfcUnlockHandler;)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->resumePolling()V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->setAppCallback(Landroid/nfc/IAppCallback;)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->setForegroundDispatch(Landroid/app/PendingIntent;[Landroid/content/IntentFilter;Landroid/nfc/TechListParcel;)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->setP2pModes(II)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->setReaderMode(Landroid/os/IBinder;Landroid/nfc/IAppCallback;ILandroid/os/Bundle;)V
-Landroid/nfc/INfcAdapter$Stub$Proxy;->verifyNfcPermission()V
-Landroid/nfc/INfcAdapter$Stub;-><init>()V
-Landroid/nfc/INfcAdapter$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcAdapter;
-Landroid/nfc/INfcAdapter$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_addNfcUnlockHandler:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_disable:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_disableNdefPush:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_dispatch:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_enableNdefPush:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getNfcAdapterExtrasInterface:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getNfcCardEmulationInterface:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getNfcDtaInterface:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getNfcFCardEmulationInterface:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getNfcTagInterface:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getState:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_ignore:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_invokeBeam:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_invokeBeamInternal:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_isNdefPushEnabled:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_pausePolling:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_removeNfcUnlockHandler:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_resumePolling:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_setAppCallback:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_setForegroundDispatch:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_setP2pModes:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_setReaderMode:I
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_verifyNfcPermission:I
-Landroid/nfc/INfcAdapter;->addNfcUnlockHandler(Landroid/nfc/INfcUnlockHandler;[I)V
-Landroid/nfc/INfcAdapter;->disable(Z)Z
-Landroid/nfc/INfcAdapter;->disableNdefPush()Z
-Landroid/nfc/INfcAdapter;->dispatch(Landroid/nfc/Tag;)V
-Landroid/nfc/INfcAdapter;->enable()Z
-Landroid/nfc/INfcAdapter;->enableNdefPush()Z
-Landroid/nfc/INfcAdapter;->getNfcAdapterExtrasInterface(Ljava/lang/String;)Landroid/nfc/INfcAdapterExtras;
-Landroid/nfc/INfcAdapter;->getNfcCardEmulationInterface()Landroid/nfc/INfcCardEmulation;
-Landroid/nfc/INfcAdapter;->getNfcDtaInterface(Ljava/lang/String;)Landroid/nfc/INfcDta;
-Landroid/nfc/INfcAdapter;->getNfcFCardEmulationInterface()Landroid/nfc/INfcFCardEmulation;
-Landroid/nfc/INfcAdapter;->getNfcTagInterface()Landroid/nfc/INfcTag;
-Landroid/nfc/INfcAdapter;->getState()I
-Landroid/nfc/INfcAdapter;->ignore(IILandroid/nfc/ITagRemovedCallback;)Z
-Landroid/nfc/INfcAdapter;->invokeBeam()V
-Landroid/nfc/INfcAdapter;->invokeBeamInternal(Landroid/nfc/BeamShareData;)V
-Landroid/nfc/INfcAdapter;->isNdefPushEnabled()Z
-Landroid/nfc/INfcAdapter;->pausePolling(I)V
-Landroid/nfc/INfcAdapter;->removeNfcUnlockHandler(Landroid/nfc/INfcUnlockHandler;)V
-Landroid/nfc/INfcAdapter;->resumePolling()V
-Landroid/nfc/INfcAdapter;->setAppCallback(Landroid/nfc/IAppCallback;)V
-Landroid/nfc/INfcAdapter;->setForegroundDispatch(Landroid/app/PendingIntent;[Landroid/content/IntentFilter;Landroid/nfc/TechListParcel;)V
-Landroid/nfc/INfcAdapter;->setP2pModes(II)V
-Landroid/nfc/INfcAdapter;->setReaderMode(Landroid/os/IBinder;Landroid/nfc/IAppCallback;ILandroid/os/Bundle;)V
-Landroid/nfc/INfcAdapter;->verifyNfcPermission()V
-Landroid/nfc/INfcAdapterExtras$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->authenticate(Ljava/lang/String;[B)V
-Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->close(Ljava/lang/String;Landroid/os/IBinder;)Landroid/os/Bundle;
-Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->getCardEmulationRoute(Ljava/lang/String;)I
-Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->getDriverName(Ljava/lang/String;)Ljava/lang/String;
-Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->open(Ljava/lang/String;Landroid/os/IBinder;)Landroid/os/Bundle;
-Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->setCardEmulationRoute(Ljava/lang/String;I)V
-Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->transceive(Ljava/lang/String;[B)Landroid/os/Bundle;
-Landroid/nfc/INfcAdapterExtras$Stub;-><init>()V
-Landroid/nfc/INfcAdapterExtras$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcAdapterExtras;
-Landroid/nfc/INfcAdapterExtras$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_authenticate:I
-Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_close:I
-Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_getCardEmulationRoute:I
-Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_getDriverName:I
-Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_open:I
-Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_setCardEmulationRoute:I
-Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_transceive:I
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->getAidGroupForService(ILandroid/content/ComponentName;Ljava/lang/String;)Landroid/nfc/cardemulation/AidGroup;
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->getServices(ILjava/lang/String;)Ljava/util/List;
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->isDefaultServiceForAid(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->isDefaultServiceForCategory(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->registerAidGroupForService(ILandroid/content/ComponentName;Landroid/nfc/cardemulation/AidGroup;)Z
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->removeAidGroupForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->setDefaultForNextTap(ILandroid/content/ComponentName;)Z
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->setDefaultServiceForCategory(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->setPreferredService(Landroid/content/ComponentName;)Z
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->supportsAidPrefixRegistration()Z
-Landroid/nfc/INfcCardEmulation$Stub$Proxy;->unsetPreferredService()Z
-Landroid/nfc/INfcCardEmulation$Stub;-><init>()V
-Landroid/nfc/INfcCardEmulation$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcCardEmulation;
-Landroid/nfc/INfcCardEmulation$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_getAidGroupForService:I
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_getServices:I
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_isDefaultServiceForAid:I
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_isDefaultServiceForCategory:I
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_registerAidGroupForService:I
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_removeAidGroupForService:I
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_setDefaultForNextTap:I
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_setDefaultServiceForCategory:I
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_setPreferredService:I
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_supportsAidPrefixRegistration:I
-Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_unsetPreferredService:I
-Landroid/nfc/INfcCardEmulation;->getAidGroupForService(ILandroid/content/ComponentName;Ljava/lang/String;)Landroid/nfc/cardemulation/AidGroup;
-Landroid/nfc/INfcCardEmulation;->getServices(ILjava/lang/String;)Ljava/util/List;
-Landroid/nfc/INfcCardEmulation;->isDefaultServiceForAid(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcCardEmulation;->isDefaultServiceForCategory(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcCardEmulation;->registerAidGroupForService(ILandroid/content/ComponentName;Landroid/nfc/cardemulation/AidGroup;)Z
-Landroid/nfc/INfcCardEmulation;->removeAidGroupForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcCardEmulation;->setDefaultForNextTap(ILandroid/content/ComponentName;)Z
-Landroid/nfc/INfcCardEmulation;->setDefaultServiceForCategory(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcCardEmulation;->setPreferredService(Landroid/content/ComponentName;)Z
-Landroid/nfc/INfcCardEmulation;->supportsAidPrefixRegistration()Z
-Landroid/nfc/INfcCardEmulation;->unsetPreferredService()Z
-Landroid/nfc/INfcDta$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/nfc/INfcDta$Stub$Proxy;->disableClient()V
-Landroid/nfc/INfcDta$Stub$Proxy;->disableDta()V
-Landroid/nfc/INfcDta$Stub$Proxy;->disableServer()V
-Landroid/nfc/INfcDta$Stub$Proxy;->enableClient(Ljava/lang/String;III)Z
-Landroid/nfc/INfcDta$Stub$Proxy;->enableDta()V
-Landroid/nfc/INfcDta$Stub$Proxy;->enableServer(Ljava/lang/String;IIII)Z
-Landroid/nfc/INfcDta$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/nfc/INfcDta$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/nfc/INfcDta$Stub$Proxy;->registerMessageService(Ljava/lang/String;)Z
-Landroid/nfc/INfcDta$Stub;-><init>()V
-Landroid/nfc/INfcDta$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcDta;
-Landroid/nfc/INfcDta$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/nfc/INfcDta$Stub;->TRANSACTION_disableClient:I
-Landroid/nfc/INfcDta$Stub;->TRANSACTION_disableDta:I
-Landroid/nfc/INfcDta$Stub;->TRANSACTION_disableServer:I
-Landroid/nfc/INfcDta$Stub;->TRANSACTION_enableClient:I
-Landroid/nfc/INfcDta$Stub;->TRANSACTION_enableDta:I
-Landroid/nfc/INfcDta$Stub;->TRANSACTION_enableServer:I
-Landroid/nfc/INfcDta$Stub;->TRANSACTION_registerMessageService:I
-Landroid/nfc/INfcDta;->disableClient()V
-Landroid/nfc/INfcDta;->disableDta()V
-Landroid/nfc/INfcDta;->disableServer()V
-Landroid/nfc/INfcDta;->enableClient(Ljava/lang/String;III)Z
-Landroid/nfc/INfcDta;->enableDta()V
-Landroid/nfc/INfcDta;->enableServer(Ljava/lang/String;IIII)Z
-Landroid/nfc/INfcDta;->registerMessageService(Ljava/lang/String;)Z
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->disableNfcFForegroundService()Z
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->enableNfcFForegroundService(Landroid/content/ComponentName;)Z
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->getMaxNumOfRegisterableSystemCodes()I
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->getNfcFServices(I)Ljava/util/List;
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->getNfcid2ForService(ILandroid/content/ComponentName;)Ljava/lang/String;
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->getSystemCodeForService(ILandroid/content/ComponentName;)Ljava/lang/String;
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->registerSystemCodeForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->removeSystemCodeForService(ILandroid/content/ComponentName;)Z
-Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->setNfcid2ForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcFCardEmulation$Stub;-><init>()V
-Landroid/nfc/INfcFCardEmulation$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcFCardEmulation;
-Landroid/nfc/INfcFCardEmulation$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_disableNfcFForegroundService:I
-Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_enableNfcFForegroundService:I
-Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_getMaxNumOfRegisterableSystemCodes:I
-Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_getNfcFServices:I
-Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_getNfcid2ForService:I
-Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_getSystemCodeForService:I
-Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_registerSystemCodeForService:I
-Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_removeSystemCodeForService:I
-Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_setNfcid2ForService:I
-Landroid/nfc/INfcFCardEmulation;->disableNfcFForegroundService()Z
-Landroid/nfc/INfcFCardEmulation;->enableNfcFForegroundService(Landroid/content/ComponentName;)Z
-Landroid/nfc/INfcFCardEmulation;->getMaxNumOfRegisterableSystemCodes()I
-Landroid/nfc/INfcFCardEmulation;->getNfcFServices(I)Ljava/util/List;
-Landroid/nfc/INfcFCardEmulation;->getNfcid2ForService(ILandroid/content/ComponentName;)Ljava/lang/String;
-Landroid/nfc/INfcFCardEmulation;->getSystemCodeForService(ILandroid/content/ComponentName;)Ljava/lang/String;
-Landroid/nfc/INfcFCardEmulation;->registerSystemCodeForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcFCardEmulation;->removeSystemCodeForService(ILandroid/content/ComponentName;)Z
-Landroid/nfc/INfcFCardEmulation;->setNfcid2ForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z
-Landroid/nfc/INfcTag$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/nfc/INfcTag$Stub$Proxy;->canMakeReadOnly(I)Z
-Landroid/nfc/INfcTag$Stub$Proxy;->connect(II)I
-Landroid/nfc/INfcTag$Stub$Proxy;->formatNdef(I[B)I
-Landroid/nfc/INfcTag$Stub$Proxy;->getExtendedLengthApdusSupported()Z
-Landroid/nfc/INfcTag$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/nfc/INfcTag$Stub$Proxy;->getMaxTransceiveLength(I)I
-Landroid/nfc/INfcTag$Stub$Proxy;->getTechList(I)[I
-Landroid/nfc/INfcTag$Stub$Proxy;->getTimeout(I)I
-Landroid/nfc/INfcTag$Stub$Proxy;->isNdef(I)Z
-Landroid/nfc/INfcTag$Stub$Proxy;->isPresent(I)Z
-Landroid/nfc/INfcTag$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/nfc/INfcTag$Stub$Proxy;->ndefIsWritable(I)Z
-Landroid/nfc/INfcTag$Stub$Proxy;->ndefMakeReadOnly(I)I
-Landroid/nfc/INfcTag$Stub$Proxy;->ndefRead(I)Landroid/nfc/NdefMessage;
-Landroid/nfc/INfcTag$Stub$Proxy;->ndefWrite(ILandroid/nfc/NdefMessage;)I
-Landroid/nfc/INfcTag$Stub$Proxy;->reconnect(I)I
-Landroid/nfc/INfcTag$Stub$Proxy;->rediscover(I)Landroid/nfc/Tag;
-Landroid/nfc/INfcTag$Stub$Proxy;->resetTimeouts()V
-Landroid/nfc/INfcTag$Stub$Proxy;->setTimeout(II)I
-Landroid/nfc/INfcTag$Stub$Proxy;->transceive(I[BZ)Landroid/nfc/TransceiveResult;
-Landroid/nfc/INfcTag$Stub;-><init>()V
-Landroid/nfc/INfcTag$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcTag;
-Landroid/nfc/INfcTag$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_canMakeReadOnly:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_connect:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_formatNdef:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_getExtendedLengthApdusSupported:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_getMaxTransceiveLength:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_getTechList:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_getTimeout:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_isNdef:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_isPresent:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_ndefIsWritable:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_ndefMakeReadOnly:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_ndefRead:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_ndefWrite:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_reconnect:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_rediscover:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_resetTimeouts:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_setTimeout:I
-Landroid/nfc/INfcTag$Stub;->TRANSACTION_transceive:I
-Landroid/nfc/INfcTag;->canMakeReadOnly(I)Z
-Landroid/nfc/INfcTag;->connect(II)I
-Landroid/nfc/INfcTag;->formatNdef(I[B)I
-Landroid/nfc/INfcTag;->getExtendedLengthApdusSupported()Z
-Landroid/nfc/INfcTag;->getMaxTransceiveLength(I)I
-Landroid/nfc/INfcTag;->getTechList(I)[I
-Landroid/nfc/INfcTag;->getTimeout(I)I
-Landroid/nfc/INfcTag;->isNdef(I)Z
-Landroid/nfc/INfcTag;->isPresent(I)Z
-Landroid/nfc/INfcTag;->ndefIsWritable(I)Z
-Landroid/nfc/INfcTag;->ndefMakeReadOnly(I)I
-Landroid/nfc/INfcTag;->ndefRead(I)Landroid/nfc/NdefMessage;
-Landroid/nfc/INfcTag;->ndefWrite(ILandroid/nfc/NdefMessage;)I
-Landroid/nfc/INfcTag;->reconnect(I)I
-Landroid/nfc/INfcTag;->rediscover(I)Landroid/nfc/Tag;
-Landroid/nfc/INfcTag;->resetTimeouts()V
-Landroid/nfc/INfcTag;->setTimeout(II)I
-Landroid/nfc/INfcTag;->transceive(I[BZ)Landroid/nfc/TransceiveResult;
-Landroid/nfc/INfcUnlockHandler$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/nfc/INfcUnlockHandler$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/nfc/INfcUnlockHandler$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/nfc/INfcUnlockHandler$Stub$Proxy;->onUnlockAttempted(Landroid/nfc/Tag;)Z
-Landroid/nfc/INfcUnlockHandler$Stub;-><init>()V
-Landroid/nfc/INfcUnlockHandler$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcUnlockHandler;
-Landroid/nfc/INfcUnlockHandler$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/nfc/INfcUnlockHandler$Stub;->TRANSACTION_onUnlockAttempted:I
-Landroid/nfc/INfcUnlockHandler;->onUnlockAttempted(Landroid/nfc/Tag;)Z
-Landroid/nfc/ITagRemovedCallback$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/nfc/ITagRemovedCallback$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/nfc/ITagRemovedCallback$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/nfc/ITagRemovedCallback$Stub$Proxy;->onTagRemoved()V
-Landroid/nfc/ITagRemovedCallback$Stub;-><init>()V
-Landroid/nfc/ITagRemovedCallback$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/ITagRemovedCallback;
-Landroid/nfc/ITagRemovedCallback$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/nfc/ITagRemovedCallback$Stub;->TRANSACTION_onTagRemoved:I
-Landroid/nfc/ITagRemovedCallback;->onTagRemoved()V
-Landroid/nfc/NdefMessage;->mRecords:[Landroid/nfc/NdefRecord;
-Landroid/nfc/NdefRecord;->bytesToString([B)Ljava/lang/StringBuilder;
-Landroid/nfc/NdefRecord;->EMPTY_BYTE_ARRAY:[B
-Landroid/nfc/NdefRecord;->ensureSanePayloadSize(J)V
-Landroid/nfc/NdefRecord;->FLAG_CF:B
-Landroid/nfc/NdefRecord;->FLAG_IL:B
-Landroid/nfc/NdefRecord;->FLAG_MB:B
-Landroid/nfc/NdefRecord;->FLAG_ME:B
-Landroid/nfc/NdefRecord;->FLAG_SR:B
-Landroid/nfc/NdefRecord;->getByteLength()I
-Landroid/nfc/NdefRecord;->MAX_PAYLOAD_SIZE:I
-Landroid/nfc/NdefRecord;->mPayload:[B
-Landroid/nfc/NdefRecord;->mTnf:S
-Landroid/nfc/NdefRecord;->mType:[B
-Landroid/nfc/NdefRecord;->parse(Ljava/nio/ByteBuffer;Z)[Landroid/nfc/NdefRecord;
-Landroid/nfc/NdefRecord;->parseWktUri()Landroid/net/Uri;
-Landroid/nfc/NdefRecord;->RTD_ANDROID_APP:[B
-Landroid/nfc/NdefRecord;->TNF_RESERVED:S
-Landroid/nfc/NdefRecord;->toUri(Z)Landroid/net/Uri;
-Landroid/nfc/NdefRecord;->URI_PREFIX_MAP:[Ljava/lang/String;
-Landroid/nfc/NdefRecord;->validateTnf(S[B[B[B)Ljava/lang/String;
-Landroid/nfc/NdefRecord;->writeToByteBuffer(Ljava/nio/ByteBuffer;ZZ)V
-Landroid/nfc/NfcActivityManager$NfcActivityState;->activity:Landroid/app/Activity;
-Landroid/nfc/NfcActivityManager$NfcActivityState;->destroy()V
-Landroid/nfc/NfcActivityManager$NfcActivityState;->flags:I
-Landroid/nfc/NfcActivityManager$NfcActivityState;->ndefMessage:Landroid/nfc/NdefMessage;
-Landroid/nfc/NfcActivityManager$NfcActivityState;->ndefMessageCallback:Landroid/nfc/NfcAdapter$CreateNdefMessageCallback;
-Landroid/nfc/NfcActivityManager$NfcActivityState;->onNdefPushCompleteCallback:Landroid/nfc/NfcAdapter$OnNdefPushCompleteCallback;
-Landroid/nfc/NfcActivityManager$NfcActivityState;->readerCallback:Landroid/nfc/NfcAdapter$ReaderCallback;
-Landroid/nfc/NfcActivityManager$NfcActivityState;->readerModeExtras:Landroid/os/Bundle;
-Landroid/nfc/NfcActivityManager$NfcActivityState;->readerModeFlags:I
-Landroid/nfc/NfcActivityManager$NfcActivityState;->resumed:Z
-Landroid/nfc/NfcActivityManager$NfcActivityState;->token:Landroid/os/Binder;
-Landroid/nfc/NfcActivityManager$NfcActivityState;->uriCallback:Landroid/nfc/NfcAdapter$CreateBeamUrisCallback;
-Landroid/nfc/NfcActivityManager$NfcActivityState;->uris:[Landroid/net/Uri;
-Landroid/nfc/NfcActivityManager$NfcApplicationState;->app:Landroid/app/Application;
-Landroid/nfc/NfcActivityManager$NfcApplicationState;->refCount:I
-Landroid/nfc/NfcActivityManager$NfcApplicationState;->register()V
-Landroid/nfc/NfcActivityManager$NfcApplicationState;->unregister()V
-Landroid/nfc/NfcActivityManager;-><init>(Landroid/nfc/NfcAdapter;)V
-Landroid/nfc/NfcActivityManager;->createBeamShareData(B)Landroid/nfc/BeamShareData;
-Landroid/nfc/NfcActivityManager;->DBG:Ljava/lang/Boolean;
-Landroid/nfc/NfcActivityManager;->destroyActivityState(Landroid/app/Activity;)V
-Landroid/nfc/NfcActivityManager;->disableReaderMode(Landroid/app/Activity;)V
-Landroid/nfc/NfcActivityManager;->enableReaderMode(Landroid/app/Activity;Landroid/nfc/NfcAdapter$ReaderCallback;ILandroid/os/Bundle;)V
-Landroid/nfc/NfcActivityManager;->findActivityState(Landroid/app/Activity;)Landroid/nfc/NfcActivityManager$NfcActivityState;
-Landroid/nfc/NfcActivityManager;->findAppState(Landroid/app/Application;)Landroid/nfc/NfcActivityManager$NfcApplicationState;
-Landroid/nfc/NfcActivityManager;->findResumedActivityState()Landroid/nfc/NfcActivityManager$NfcActivityState;
-Landroid/nfc/NfcActivityManager;->getActivityState(Landroid/app/Activity;)Landroid/nfc/NfcActivityManager$NfcActivityState;
-Landroid/nfc/NfcActivityManager;->mActivities:Ljava/util/List;
-Landroid/nfc/NfcActivityManager;->mApps:Ljava/util/List;
-Landroid/nfc/NfcActivityManager;->onNdefPushComplete(B)V
-Landroid/nfc/NfcActivityManager;->onTagDiscovered(Landroid/nfc/Tag;)V
-Landroid/nfc/NfcActivityManager;->registerApplication(Landroid/app/Application;)V
-Landroid/nfc/NfcActivityManager;->requestNfcServiceCallback()V
-Landroid/nfc/NfcActivityManager;->setNdefPushContentUri(Landroid/app/Activity;[Landroid/net/Uri;)V
-Landroid/nfc/NfcActivityManager;->setNdefPushContentUriCallback(Landroid/app/Activity;Landroid/nfc/NfcAdapter$CreateBeamUrisCallback;)V
-Landroid/nfc/NfcActivityManager;->setNdefPushMessage(Landroid/app/Activity;Landroid/nfc/NdefMessage;I)V
-Landroid/nfc/NfcActivityManager;->setNdefPushMessageCallback(Landroid/app/Activity;Landroid/nfc/NfcAdapter$CreateNdefMessageCallback;I)V
-Landroid/nfc/NfcActivityManager;->setOnNdefPushCompleteCallback(Landroid/app/Activity;Landroid/nfc/NfcAdapter$OnNdefPushCompleteCallback;)V
-Landroid/nfc/NfcActivityManager;->setReaderMode(Landroid/os/Binder;ILandroid/os/Bundle;)V
-Landroid/nfc/NfcActivityManager;->TAG:Ljava/lang/String;
-Landroid/nfc/NfcActivityManager;->unregisterApplication(Landroid/app/Application;)V
-Landroid/nfc/NfcActivityManager;->verifyNfcPermission()V
-Landroid/nfc/NfcAdapter;-><init>(Landroid/content/Context;)V
-Landroid/nfc/NfcAdapter;->ACTION_HANDOVER_TRANSFER_DONE:Ljava/lang/String;
-Landroid/nfc/NfcAdapter;->ACTION_HANDOVER_TRANSFER_STARTED:Ljava/lang/String;
-Landroid/nfc/NfcAdapter;->ACTION_TAG_LEFT_FIELD:Ljava/lang/String;
-Landroid/nfc/NfcAdapter;->disableForegroundDispatchInternal(Landroid/app/Activity;Z)V
-Landroid/nfc/NfcAdapter;->dispatch(Landroid/nfc/Tag;)V
-Landroid/nfc/NfcAdapter;->enforceResumed(Landroid/app/Activity;)V
-Landroid/nfc/NfcAdapter;->EXTRA_HANDOVER_TRANSFER_STATUS:Ljava/lang/String;
-Landroid/nfc/NfcAdapter;->EXTRA_HANDOVER_TRANSFER_URI:Ljava/lang/String;
-Landroid/nfc/NfcAdapter;->getCardEmulationService()Landroid/nfc/INfcCardEmulation;
-Landroid/nfc/NfcAdapter;->getNfcDtaInterface()Landroid/nfc/INfcDta;
-Landroid/nfc/NfcAdapter;->getNfcFCardEmulationService()Landroid/nfc/INfcFCardEmulation;
-Landroid/nfc/NfcAdapter;->getSdkVersion()I
-Landroid/nfc/NfcAdapter;->getServiceInterface()Landroid/nfc/INfcAdapter;
-Landroid/nfc/NfcAdapter;->getTagService()Landroid/nfc/INfcTag;
-Landroid/nfc/NfcAdapter;->HANDOVER_TRANSFER_STATUS_FAILURE:I
-Landroid/nfc/NfcAdapter;->HANDOVER_TRANSFER_STATUS_SUCCESS:I
-Landroid/nfc/NfcAdapter;->hasNfcFeature()Z
-Landroid/nfc/NfcAdapter;->hasNfcHceFeature()Z
-Landroid/nfc/NfcAdapter;->invokeBeam(Landroid/nfc/BeamShareData;)Z
-Landroid/nfc/NfcAdapter;->mContext:Landroid/content/Context;
-Landroid/nfc/NfcAdapter;->mForegroundDispatchListener:Landroid/app/OnActivityPausedListener;
-Landroid/nfc/NfcAdapter;->mLock:Ljava/lang/Object;
-Landroid/nfc/NfcAdapter;->mNfcActivityManager:Landroid/nfc/NfcActivityManager;
-Landroid/nfc/NfcAdapter;->mNfcUnlockHandlers:Ljava/util/HashMap;
-Landroid/nfc/NfcAdapter;->mTagRemovedListener:Landroid/nfc/ITagRemovedCallback;
-Landroid/nfc/NfcAdapter;->pausePolling(I)V
-Landroid/nfc/NfcAdapter;->resumePolling()V
-Landroid/nfc/NfcAdapter;->sCardEmulationService:Landroid/nfc/INfcCardEmulation;
-Landroid/nfc/NfcAdapter;->setP2pModes(II)V
-Landroid/nfc/NfcAdapter;->sHasNfcFeature:Z
-Landroid/nfc/NfcAdapter;->sIsInitialized:Z
-Landroid/nfc/NfcAdapter;->sNfcAdapters:Ljava/util/HashMap;
-Landroid/nfc/NfcAdapter;->sNfcFCardEmulationService:Landroid/nfc/INfcFCardEmulation;
-Landroid/nfc/NfcAdapter;->sNullContextNfcAdapter:Landroid/nfc/NfcAdapter;
-Landroid/nfc/NfcAdapter;->sTagService:Landroid/nfc/INfcTag;
-Landroid/nfc/NfcAdapter;->TAG:Ljava/lang/String;
-Landroid/nfc/NfcEvent;-><init>(Landroid/nfc/NfcAdapter;B)V
-Landroid/nfc/NfcManager;->mAdapter:Landroid/nfc/NfcAdapter;
-Landroid/nfc/Tag;-><init>([B[I[Landroid/os/Bundle;ILandroid/nfc/INfcTag;)V
-Landroid/nfc/Tag;->createMockTag([B[I[Landroid/os/Bundle;)Landroid/nfc/Tag;
-Landroid/nfc/Tag;->generateTechStringList([I)[Ljava/lang/String;
-Landroid/nfc/Tag;->getConnectedTechnology()I
-Landroid/nfc/Tag;->getTechCodeList()[I
-Landroid/nfc/Tag;->getTechCodesFromStrings([Ljava/lang/String;)[I
-Landroid/nfc/Tag;->getTechExtras(I)Landroid/os/Bundle;
-Landroid/nfc/Tag;->getTechStringToCodeMap()Ljava/util/HashMap;
-Landroid/nfc/Tag;->hasTech(I)Z
-Landroid/nfc/Tag;->mConnectedTechnology:I
-Landroid/nfc/Tag;->mServiceHandle:I
-Landroid/nfc/Tag;->mTagService:Landroid/nfc/INfcTag;
-Landroid/nfc/Tag;->mTechExtras:[Landroid/os/Bundle;
-Landroid/nfc/Tag;->mTechList:[I
-Landroid/nfc/Tag;->mTechStringList:[Ljava/lang/String;
-Landroid/nfc/Tag;->readBytesWithNull(Landroid/os/Parcel;)[B
-Landroid/nfc/Tag;->rediscover()Landroid/nfc/Tag;
-Landroid/nfc/Tag;->setConnectedTechnology(I)V
-Landroid/nfc/Tag;->setTechnologyDisconnected()V
-Landroid/nfc/Tag;->writeBytesWithNull(Landroid/os/Parcel;[B)V
-Landroid/nfc/tech/BasicTagTechnology;-><init>(Landroid/nfc/Tag;I)V
-Landroid/nfc/tech/BasicTagTechnology;->checkConnected()V
-Landroid/nfc/tech/BasicTagTechnology;->getMaxTransceiveLengthInternal()I
-Landroid/nfc/tech/BasicTagTechnology;->mIsConnected:Z
-Landroid/nfc/tech/BasicTagTechnology;->mSelectedTechnology:I
-Landroid/nfc/tech/BasicTagTechnology;->mTag:Landroid/nfc/Tag;
-Landroid/nfc/tech/BasicTagTechnology;->reconnect()V
-Landroid/nfc/tech/BasicTagTechnology;->TAG:Ljava/lang/String;
-Landroid/nfc/tech/BasicTagTechnology;->transceive([BZ)[B
-Landroid/nfc/tech/IsoDep;-><init>(Landroid/nfc/Tag;)V
-Landroid/nfc/tech/IsoDep;->EXTRA_HIST_BYTES:Ljava/lang/String;
-Landroid/nfc/tech/IsoDep;->EXTRA_HI_LAYER_RESP:Ljava/lang/String;
-Landroid/nfc/tech/IsoDep;->mHiLayerResponse:[B
-Landroid/nfc/tech/IsoDep;->mHistBytes:[B
-Landroid/nfc/tech/IsoDep;->TAG:Ljava/lang/String;
-Landroid/nfc/tech/MifareClassic;-><init>(Landroid/nfc/Tag;)V
-Landroid/nfc/tech/MifareClassic;->authenticate(I[BZ)Z
-Landroid/nfc/tech/MifareClassic;->isEmulated()Z
-Landroid/nfc/tech/MifareClassic;->MAX_BLOCK_COUNT:I
-Landroid/nfc/tech/MifareClassic;->MAX_SECTOR_COUNT:I
-Landroid/nfc/tech/MifareClassic;->mIsEmulated:Z
-Landroid/nfc/tech/MifareClassic;->mSize:I
-Landroid/nfc/tech/MifareClassic;->mType:I
-Landroid/nfc/tech/MifareClassic;->TAG:Ljava/lang/String;
-Landroid/nfc/tech/MifareClassic;->validateBlock(I)V
-Landroid/nfc/tech/MifareClassic;->validateSector(I)V
-Landroid/nfc/tech/MifareClassic;->validateValueOperand(I)V
-Landroid/nfc/tech/MifareUltralight;-><init>(Landroid/nfc/Tag;)V
-Landroid/nfc/tech/MifareUltralight;->EXTRA_IS_UL_C:Ljava/lang/String;
-Landroid/nfc/tech/MifareUltralight;->MAX_PAGE_COUNT:I
-Landroid/nfc/tech/MifareUltralight;->mType:I
-Landroid/nfc/tech/MifareUltralight;->NXP_MANUFACTURER_ID:I
-Landroid/nfc/tech/MifareUltralight;->TAG:Ljava/lang/String;
-Landroid/nfc/tech/MifareUltralight;->validatePageIndex(I)V
-Landroid/nfc/tech/Ndef;-><init>(Landroid/nfc/Tag;)V
-Landroid/nfc/tech/Ndef;->EXTRA_NDEF_CARDSTATE:Ljava/lang/String;
-Landroid/nfc/tech/Ndef;->EXTRA_NDEF_MAXLENGTH:Ljava/lang/String;
-Landroid/nfc/tech/Ndef;->EXTRA_NDEF_MSG:Ljava/lang/String;
-Landroid/nfc/tech/Ndef;->EXTRA_NDEF_TYPE:Ljava/lang/String;
-Landroid/nfc/tech/Ndef;->ICODE_SLI:Ljava/lang/String;
-Landroid/nfc/tech/Ndef;->mCardState:I
-Landroid/nfc/tech/Ndef;->mMaxNdefSize:I
-Landroid/nfc/tech/Ndef;->mNdefMsg:Landroid/nfc/NdefMessage;
-Landroid/nfc/tech/Ndef;->mNdefType:I
-Landroid/nfc/tech/Ndef;->NDEF_MODE_READ_ONLY:I
-Landroid/nfc/tech/Ndef;->NDEF_MODE_READ_WRITE:I
-Landroid/nfc/tech/Ndef;->NDEF_MODE_UNKNOWN:I
-Landroid/nfc/tech/Ndef;->TAG:Ljava/lang/String;
-Landroid/nfc/tech/Ndef;->TYPE_1:I
-Landroid/nfc/tech/Ndef;->TYPE_2:I
-Landroid/nfc/tech/Ndef;->TYPE_3:I
-Landroid/nfc/tech/Ndef;->TYPE_4:I
-Landroid/nfc/tech/Ndef;->TYPE_ICODE_SLI:I
-Landroid/nfc/tech/Ndef;->TYPE_MIFARE_CLASSIC:I
-Landroid/nfc/tech/Ndef;->TYPE_OTHER:I
-Landroid/nfc/tech/Ndef;->UNKNOWN:Ljava/lang/String;
-Landroid/nfc/tech/NdefFormatable;-><init>(Landroid/nfc/Tag;)V
-Landroid/nfc/tech/NdefFormatable;->format(Landroid/nfc/NdefMessage;Z)V
-Landroid/nfc/tech/NdefFormatable;->TAG:Ljava/lang/String;
-Landroid/nfc/tech/NfcA;-><init>(Landroid/nfc/Tag;)V
-Landroid/nfc/tech/NfcA;->EXTRA_ATQA:Ljava/lang/String;
-Landroid/nfc/tech/NfcA;->EXTRA_SAK:Ljava/lang/String;
-Landroid/nfc/tech/NfcA;->mAtqa:[B
-Landroid/nfc/tech/NfcA;->mSak:S
-Landroid/nfc/tech/NfcA;->TAG:Ljava/lang/String;
-Landroid/nfc/tech/NfcB;-><init>(Landroid/nfc/Tag;)V
-Landroid/nfc/tech/NfcB;->EXTRA_APPDATA:Ljava/lang/String;
-Landroid/nfc/tech/NfcB;->EXTRA_PROTINFO:Ljava/lang/String;
-Landroid/nfc/tech/NfcB;->mAppData:[B
-Landroid/nfc/tech/NfcB;->mProtInfo:[B
-Landroid/nfc/tech/NfcBarcode;-><init>(Landroid/nfc/Tag;)V
-Landroid/nfc/tech/NfcBarcode;->EXTRA_BARCODE_TYPE:Ljava/lang/String;
-Landroid/nfc/tech/NfcBarcode;->mType:I
-Landroid/nfc/tech/NfcF;-><init>(Landroid/nfc/Tag;)V
-Landroid/nfc/tech/NfcF;->EXTRA_PMM:Ljava/lang/String;
-Landroid/nfc/tech/NfcF;->EXTRA_SC:Ljava/lang/String;
-Landroid/nfc/tech/NfcF;->mManufacturer:[B
-Landroid/nfc/tech/NfcF;->mSystemCode:[B
-Landroid/nfc/tech/NfcF;->TAG:Ljava/lang/String;
-Landroid/nfc/tech/NfcV;-><init>(Landroid/nfc/Tag;)V
-Landroid/nfc/tech/NfcV;->EXTRA_DSFID:Ljava/lang/String;
-Landroid/nfc/tech/NfcV;->EXTRA_RESP_FLAGS:Ljava/lang/String;
-Landroid/nfc/tech/NfcV;->mDsfId:B
-Landroid/nfc/tech/NfcV;->mRespFlags:B
-Landroid/nfc/tech/TagTechnology;->ISO_DEP:I
-Landroid/nfc/tech/TagTechnology;->MIFARE_CLASSIC:I
-Landroid/nfc/tech/TagTechnology;->MIFARE_ULTRALIGHT:I
-Landroid/nfc/tech/TagTechnology;->NDEF:I
-Landroid/nfc/tech/TagTechnology;->NDEF_FORMATABLE:I
-Landroid/nfc/tech/TagTechnology;->NFC_A:I
-Landroid/nfc/tech/TagTechnology;->NFC_B:I
-Landroid/nfc/tech/TagTechnology;->NFC_BARCODE:I
-Landroid/nfc/tech/TagTechnology;->NFC_F:I
-Landroid/nfc/tech/TagTechnology;->NFC_V:I
-Landroid/nfc/tech/TagTechnology;->reconnect()V
-Landroid/nfc/TechListParcel;->CREATOR:Landroid/os/Parcelable$Creator;
-Landroid/nfc/TechListParcel;->getTechLists()[[Ljava/lang/String;
-Landroid/nfc/TechListParcel;->mTechLists:[[Ljava/lang/String;
-Landroid/nfc/TransceiveResult;-><init>(I[B)V
-Landroid/nfc/TransceiveResult;->CREATOR:Landroid/os/Parcelable$Creator;
-Landroid/nfc/TransceiveResult;->getResponseOrThrow()[B
-Landroid/nfc/TransceiveResult;->mResponseData:[B
-Landroid/nfc/TransceiveResult;->mResult:I
-Landroid/nfc/TransceiveResult;->RESULT_EXCEEDED_LENGTH:I
-Landroid/nfc/TransceiveResult;->RESULT_FAILURE:I
-Landroid/nfc/TransceiveResult;->RESULT_SUCCESS:I
-Landroid/nfc/TransceiveResult;->RESULT_TAGLOST:I
 Landroid/opengl/EGL14;->eglCreatePbufferFromClientBuffer(Landroid/opengl/EGLDisplay;IJLandroid/opengl/EGLConfig;[II)Landroid/opengl/EGLSurface;
 Landroid/opengl/EGL14;->_eglCreateWindowSurface(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLConfig;Ljava/lang/Object;[II)Landroid/opengl/EGLSurface;
 Landroid/opengl/EGL14;->_eglCreateWindowSurfaceTexture(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLConfig;Ljava/lang/Object;[II)Landroid/opengl/EGLSurface;
diff --git a/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt b/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt
index f5184e7..4df1dca 100644
--- a/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt
+++ b/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt
@@ -19,7 +19,6 @@
 Landroid/media/IVolumeController$Stub;->asInterface(Landroid/os/IBinder;)Landroid/media/IVolumeController;
 Landroid/net/INetworkPolicyListener$Stub;-><init>()V
 Landroid/net/sip/ISipSession$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/sip/ISipSession;
-Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_enable:I
 Landroid/os/IPowerManager$Stub;->TRANSACTION_acquireWakeLock:I
 Landroid/os/IPowerManager$Stub;->TRANSACTION_goToSleep:I
 Landroid/service/euicc/IEuiccService$Stub;-><init>()V
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index 16bb896..55ea15d 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -468,9 +468,9 @@
                                        entry.name().c_str());
         const auto& res_value = entry.res_value();
         result.pairs.emplace_back(OverlayData::Value{
-            name, TargetValueWithConfig{.config = entry.configuration(), .value = TargetValue{
+            name, TargetValueWithConfig{.value = TargetValue{
                     .data_type = static_cast<uint8_t>(res_value.data_type()),
-                    .data_value = res_value.data_value()}}});
+                    .data_value = res_value.data_value()}, .config = entry.configuration()}});
       }
     }
   }
diff --git a/cmds/idmap2/libidmap2/ResourceContainer.cpp b/cmds/idmap2/libidmap2/ResourceContainer.cpp
index 7869fbd..3c0e118 100644
--- a/cmds/idmap2/libidmap2/ResourceContainer.cpp
+++ b/cmds/idmap2/libidmap2/ResourceContainer.cpp
@@ -227,9 +227,9 @@
     } else {
       overlay_data.pairs.emplace_back(
           OverlayData::Value{*target_resource, TargetValueWithConfig{
-              .config = std::string(),
               .value = TargetValue{.data_type = overlay_resource->dataType,
-                                   .data_value = overlay_resource->data}}});
+                                   .data_value = overlay_resource->data},
+              .config = std::string()}});
     }
   }
 
@@ -268,10 +268,11 @@
   std::unique_ptr<AssetManager2> am;
   ZipAssetsProvider* zip_assets;
 
-  static Result<ResState> Initialize(std::unique_ptr<ZipAssetsProvider> zip) {
+  static Result<ResState> Initialize(std::unique_ptr<ZipAssetsProvider> zip,
+                                     package_property_t flags) {
     ResState state;
     state.zip_assets = zip.get();
-    if ((state.apk_assets = ApkAssets::Load(std::move(zip))) == nullptr) {
+    if ((state.apk_assets = ApkAssets::Load(std::move(zip), flags)) == nullptr) {
       return Error("failed to load apk asset");
     }
 
@@ -284,7 +285,7 @@
     }
 
     state.am = std::make_unique<AssetManager2>();
-    if (!state.am->SetApkAssets({state.apk_assets})) {
+    if (!state.am->SetApkAssets({state.apk_assets}, false)) {
       return Error("failed to create asset manager");
     }
 
@@ -343,8 +344,8 @@
     return state;
   }
 
-  auto state =
-      ResState::Initialize(std::move(std::get<std::unique_ptr<ZipAssetsProvider>>(state_)));
+  auto state = ResState::Initialize(std::move(std::get<std::unique_ptr<ZipAssetsProvider>>(state_)),
+                                    PROPERTY_OPTIMIZE_NAME_LOOKUPS);
   if (!state) {
     return state.GetError();
   }
diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md
index e7361fe..b6e4e0d 100644
--- a/cmds/uinput/README.md
+++ b/cmds/uinput/README.md
@@ -7,11 +7,23 @@
   or app (such as the CTS tests via [`UinputDevice`][UinputDevice]).
 * `uinput <filename>` reads commands from a file instead of standard input.
 
+There are also two supported input formats, described in the sections below. The tool will
+automatically detect which format is being used.
+
 [UinputDevice]: https://cs.android.com/android/platform/superproject/main/+/main:cts/libs/input/src/com/android/cts/input/UinputDevice.java
 
-## Command format
+## evemu recording format (recommended)
 
-Input commands should be in JSON format, though the parser is in [lenient mode] to allow comments,
+`uinput` supports the evemu format, as used by the [FreeDesktop project's evemu suite][FreeDesktop].
+This is a simple text-based format compatible with recording and replay tools on other platforms.
+However, it only supports playback of events from one device from a single recording. Recordings can
+be made using the `evemu-record` command on Android or other Linux-based OSes.
+
+[FreeDesktop]: https://gitlab.freedesktop.org/libevdev/evemu
+
+## JSON-like format
+
+The other supported format is JSON-based, though the parser is in [lenient mode] to allow comments,
 and integers can be specified in hexadecimal (e.g. `0xABCD`). The input file (or standard input) can
 contain multiple commands, which will be executed in sequence. Simply add multiple JSON objects to
 the file, one after the other without separators:
@@ -34,9 +46,9 @@
 [lenient mode]: https://developer.android.com/reference/android/util/JsonReader#setLenient(boolean)
 [cts-example-jsons]: https://cs.android.com/android/platform/superproject/main/+/main:cts/tests/tests/hardware/res/raw/
 
-## Command reference
+### Command reference
 
-### `register`
+#### `register`
 
 Register a new uinput device
 
@@ -122,7 +134,7 @@
 
 [struct input_absinfo]: https://cs.android.com/android/platform/superproject/main/+/main:bionic/libc/kernel/uapi/linux/input.h?q=%22struct%20input_absinfo%22
 
-#### Waiting for registration
+##### Waiting for registration
 
 After the command is sent, there will be a delay before the device is set up by the Android input
 stack, and `uinput` does not wait for that process to finish. Any commands sent to the device during
@@ -135,12 +147,12 @@
 
 [onInputDeviceAdded]: https://developer.android.com/reference/android/hardware/input/InputManager.InputDeviceListener.html
 
-#### Unregistering the device
+##### Unregistering the device
 
 As soon as EOF is reached (either in interactive mode, or in file mode), the device that was created
 will be unregistered. There is no explicit command for unregistering a device.
 
-### `delay`
+#### `delay`
 
 Add a delay to command processing
 
@@ -160,7 +172,7 @@
 }
 ```
 
-### `inject`
+#### `inject`
 
 Send an array of uinput event packets to the uinput device
 
@@ -190,7 +202,7 @@
 }
 ```
 
-### `sync`
+#### `sync`
 
 A command used to get a response once the command is processed. When several `inject` and `delay`
 commands are used in a row, the `sync` command can be used to track the progress of the command
diff --git a/config/preloaded-classes b/config/preloaded-classes
index c49971e..11b24f5 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -6172,8 +6172,6 @@
 android.os.VibratorInfo$FrequencyProfile
 android.os.VibratorInfo
 android.os.VibratorManager
-android.os.VintfObject
-android.os.VintfRuntimeInfo
 android.os.WorkSource$1
 android.os.WorkSource$WorkChain$1
 android.os.WorkSource$WorkChain
diff --git a/core/TEST_MAPPING b/core/TEST_MAPPING
index fd571c9..24ba5c4 100644
--- a/core/TEST_MAPPING
+++ b/core/TEST_MAPPING
@@ -20,5 +20,15 @@
         "core/tests/coretests/src/com/android/internal/inputmethod/.*"
       ]
     }
-  ]
+  ],
+  "postsubmit": [
+      {
+        "name": "ContactKeysManagerTest",
+        "options": [
+          {
+            "include-filter": "android.provider.cts.contactkeys."
+          }
+        ]
+      }
+    ]
 }
diff --git a/core/api/Android.bp b/core/api/Android.bp
index 907916a..8d8a82b 100644
--- a/core/api/Android.bp
+++ b/core/api/Android.bp
@@ -96,21 +96,3 @@
     name: "non-updatable-test-lint-baseline.txt",
     srcs: ["test-lint-baseline.txt"],
 }
-
-java_api_contribution {
-    name: "api-stubs-docs-non-updatable-public-stubs",
-    api_surface: "public",
-    api_file: "current.txt",
-    visibility: [
-        "//build/orchestrator/apis",
-    ],
-}
-
-java_api_contribution {
-    name: "frameworks-base-core-api-module-lib-stubs",
-    api_surface: "module-lib",
-    api_file: "module-lib-current.txt",
-    visibility: [
-        "//build/orchestrator/apis",
-    ],
-}
diff --git a/core/api/current.txt b/core/api/current.txt
index e0b224e..55ea2f4 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -49,6 +49,7 @@
     field public static final String BIND_SCREENING_SERVICE = "android.permission.BIND_SCREENING_SERVICE";
     field public static final String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
     field public static final String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String BIND_TV_AD_SERVICE = "android.permission.BIND_TV_AD_SERVICE";
     field public static final String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT";
     field public static final String BIND_TV_INTERACTIVE_APP = "android.permission.BIND_TV_INTERACTIVE_APP";
     field public static final String BIND_VISUAL_VOICEMAIL_SERVICE = "android.permission.BIND_VISUAL_VOICEMAIL_SERVICE";
@@ -146,6 +147,7 @@
     field public static final String MANAGE_DEVICE_POLICY_CAMERA = "android.permission.MANAGE_DEVICE_POLICY_CAMERA";
     field public static final String MANAGE_DEVICE_POLICY_CERTIFICATES = "android.permission.MANAGE_DEVICE_POLICY_CERTIFICATES";
     field public static final String MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE = "android.permission.MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE";
+    field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final String MANAGE_DEVICE_POLICY_CONTENT_PROTECTION = "android.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION";
     field public static final String MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES = "android.permission.MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES";
     field public static final String MANAGE_DEVICE_POLICY_DEFAULT_SMS = "android.permission.MANAGE_DEVICE_POLICY_DEFAULT_SMS";
     field public static final String MANAGE_DEVICE_POLICY_DEVICE_IDENTIFIERS = "android.permission.MANAGE_DEVICE_POLICY_DEVICE_IDENTIFIERS";
@@ -194,6 +196,7 @@
     field public static final String MANAGE_DEVICE_POLICY_SYSTEM_APPS = "android.permission.MANAGE_DEVICE_POLICY_SYSTEM_APPS";
     field public static final String MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS = "android.permission.MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS";
     field public static final String MANAGE_DEVICE_POLICY_SYSTEM_UPDATES = "android.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES";
+    field @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled") public static final String MANAGE_DEVICE_POLICY_THREAD_NETWORK = "android.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK";
     field public static final String MANAGE_DEVICE_POLICY_TIME = "android.permission.MANAGE_DEVICE_POLICY_TIME";
     field public static final String MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING = "android.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING";
     field public static final String MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER = "android.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER";
@@ -272,8 +275,10 @@
     field public static final String REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
     field public static final String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES";
     field public static final String REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE = "android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE";
+    field @FlaggedApi("android.companion.flags.device_presence") public static final String REQUEST_OBSERVE_DEVICE_UUID_PRESENCE = "android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE";
     field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY";
     field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
+    field @FlaggedApi("android.app.job.backup_jobs_exemption") public static final String RUN_BACKUP_JOBS = "android.permission.RUN_BACKUP_JOBS";
     field public static final String RUN_USER_INITIATED_JOBS = "android.permission.RUN_USER_INITIATED_JOBS";
     field public static final String SCHEDULE_EXACT_ALARM = "android.permission.SCHEDULE_EXACT_ALARM";
     field public static final String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
@@ -281,6 +286,7 @@
     field public static final String SET_ALARM = "com.android.alarm.permission.SET_ALARM";
     field public static final String SET_ALWAYS_FINISH = "android.permission.SET_ALWAYS_FINISH";
     field public static final String SET_ANIMATION_SCALE = "android.permission.SET_ANIMATION_SCALE";
+    field @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public static final String SET_BIOMETRIC_DIALOG_LOGO = "android.permission.SET_BIOMETRIC_DIALOG_LOGO";
     field public static final String SET_DEBUG_APP = "android.permission.SET_DEBUG_APP";
     field @Deprecated public static final String SET_PREFERRED_APPLICATIONS = "android.permission.SET_PREFERRED_APPLICATIONS";
     field public static final String SET_PROCESS_LIMIT = "android.permission.SET_PROCESS_LIMIT";
@@ -439,6 +445,7 @@
     field public static final int alertDialogTheme = 16843529; // 0x1010309
     field public static final int alignmentMode = 16843642; // 0x101037a
     field public static final int allContactsName = 16843468; // 0x10102cc
+    field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int allow;
     field public static final int allowAudioPlaybackCapture = 16844289; // 0x1010601
     field public static final int allowBackup = 16843392; // 0x1010280
     field public static final int allowClearUserData = 16842757; // 0x1010005
@@ -842,6 +849,7 @@
     field public static final int format24Hour = 16843723; // 0x10103cb
     field public static final int fraction = 16843992; // 0x10104d8
     field public static final int fragment = 16843491; // 0x10102e3
+    field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentAdvancedPattern;
     field public static final int fragmentAllowEnterTransitionOverlap = 16843976; // 0x10104c8
     field public static final int fragmentAllowReturnTransitionOverlap = 16843977; // 0x10104c9
     field public static final int fragmentCloseEnterAnimation = 16843495; // 0x10102e7
@@ -852,10 +860,13 @@
     field public static final int fragmentFadeExitAnimation = 16843498; // 0x10102ea
     field public static final int fragmentOpenEnterAnimation = 16843493; // 0x10102e5
     field public static final int fragmentOpenExitAnimation = 16843494; // 0x10102e6
+    field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentPattern;
+    field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentPrefix;
     field public static final int fragmentReenterTransition = 16843975; // 0x10104c7
     field public static final int fragmentReturnTransition = 16843973; // 0x10104c5
     field public static final int fragmentSharedElementEnterTransition = 16843972; // 0x10104c4
     field public static final int fragmentSharedElementReturnTransition = 16843974; // 0x10104c6
+    field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentSuffix;
     field public static final int freezesText = 16843116; // 0x101016c
     field public static final int fromAlpha = 16843210; // 0x10101ca
     field public static final int fromDegrees = 16843187; // 0x10101b3
@@ -1324,10 +1335,15 @@
     field public static final int propertyYName = 16843893; // 0x1010475
     field public static final int protectionLevel = 16842761; // 0x1010009
     field public static final int publicKey = 16843686; // 0x10103a6
+    field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int query;
     field public static final int queryActionMsg = 16843227; // 0x10101db
+    field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int queryAdvancedPattern;
     field public static final int queryAfterZeroResults = 16843394; // 0x1010282
     field public static final int queryBackground = 16843911; // 0x1010487
     field public static final int queryHint = 16843608; // 0x1010358
+    field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int queryPattern;
+    field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int queryPrefix;
+    field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int querySuffix;
     field public static final int quickContactBadgeStyleSmallWindowLarge = 16843443; // 0x10102b3
     field public static final int quickContactBadgeStyleSmallWindowMedium = 16843442; // 0x10102b2
     field public static final int quickContactBadgeStyleSmallWindowSmall = 16843441; // 0x10102b1
@@ -1601,6 +1617,7 @@
     field public static final int switchTextOff = 16843628; // 0x101036c
     field public static final int switchTextOn = 16843627; // 0x101036b
     field public static final int syncable = 16842777; // 0x1010019
+    field @FlaggedApi("android.multiuser.enable_system_user_only_for_services_and_providers") public static final int systemUserOnly;
     field public static final int tabStripEnabled = 16843453; // 0x10102bd
     field public static final int tabStripLeft = 16843451; // 0x10102bb
     field public static final int tabStripRight = 16843452; // 0x10102bc
@@ -4370,7 +4387,7 @@
     method public final android.media.session.MediaController getMediaController();
     method @NonNull public android.view.MenuInflater getMenuInflater();
     method @NonNull public android.window.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
-    method @Deprecated public final android.app.Activity getParent();
+    method public final android.app.Activity getParent();
     method @Nullable public android.content.Intent getParentActivityIntent();
     method public android.content.SharedPreferences getPreferences(int);
     method @Nullable public android.net.Uri getReferrer();
@@ -4388,7 +4405,7 @@
     method public void invalidateOptionsMenu();
     method public boolean isActivityTransitionRunning();
     method public boolean isChangingConfigurations();
-    method @Deprecated public final boolean isChild();
+    method public final boolean isChild();
     method public boolean isDestroyed();
     method public boolean isFinishing();
     method public boolean isImmersive();
@@ -4623,9 +4640,9 @@
 
   public class ActivityManager {
     method public int addAppTask(@NonNull android.app.Activity, @NonNull android.content.Intent, @Nullable android.app.ActivityManager.TaskDescription, @NonNull android.graphics.Bitmap);
+    method @FlaggedApi("android.app.app_start_info") public void addApplicationStartInfoCompletionListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.ApplicationStartInfo>);
     method @FlaggedApi("android.app.app_start_info") public void addStartInfoTimestamp(@IntRange(from=android.app.ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER_START, to=android.app.ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER) int, long);
     method public void appNotResponding(@NonNull String);
-    method @FlaggedApi("android.app.app_start_info") public void clearApplicationStartInfoCompletionListener();
     method public boolean clearApplicationUserData();
     method public void clearWatchHeapLimit();
     method @RequiresPermission(android.Manifest.permission.DUMP) public void dumpPackageState(java.io.FileDescriptor, String);
@@ -4659,8 +4676,8 @@
     method @RequiresPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES) public void killBackgroundProcesses(String);
     method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int);
     method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int, android.os.Bundle);
+    method @FlaggedApi("android.app.app_start_info") public void removeApplicationStartInfoCompletionListener(@NonNull java.util.function.Consumer<android.app.ApplicationStartInfo>);
     method @Deprecated public void restartPackage(String);
-    method @FlaggedApi("android.app.app_start_info") public void setApplicationStartInfoCompletionListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.ApplicationStartInfo>);
     method public void setProcessStateSummary(@Nullable byte[]);
     method public static void setVrThread(int);
     method public void setWatchHeapLimit(long);
@@ -5316,7 +5333,6 @@
     ctor @Deprecated public AutomaticZenRule(String, android.content.ComponentName, android.net.Uri, int, boolean);
     ctor public AutomaticZenRule(@NonNull String, @Nullable android.content.ComponentName, @Nullable android.content.ComponentName, @NonNull android.net.Uri, @Nullable android.service.notification.ZenPolicy, int, boolean);
     ctor public AutomaticZenRule(android.os.Parcel);
-    method @FlaggedApi("android.app.modes_api") public boolean canUpdate();
     method public int describeContents();
     method public android.net.Uri getConditionId();
     method @Nullable public android.content.ComponentName getConfigurationActivity();
@@ -7698,7 +7714,7 @@
     method @Nullable public android.app.WallpaperColors getWallpaperColors(int);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.os.ParcelFileDescriptor getWallpaperFile(int);
     method public int getWallpaperId(int);
-    method public android.app.WallpaperInfo getWallpaperInfo();
+    method @RequiresPermission(value="QUERY_ALL_PACKAGES", conditional=true) public android.app.WallpaperInfo getWallpaperInfo();
     method @Nullable public android.app.WallpaperInfo getWallpaperInfo(int);
     method public boolean hasResourceWallpaper(@RawRes int);
     method public boolean isSetWallpaperAllowed();
@@ -8179,6 +8195,9 @@
     field public static final String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
     field public static final String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
     field public static final String ACTION_SYSTEM_UPDATE_POLICY_CHANGED = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED";
+    field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final int CONTENT_PROTECTION_DISABLED = 1; // 0x1
+    field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final int CONTENT_PROTECTION_ENABLED = 2; // 0x2
+    field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final int CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY = 0; // 0x0
     field public static final String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions";
     field public static final String DELEGATION_BLOCK_UNINSTALL = "delegation-block-uninstall";
     field public static final String DELEGATION_CERT_INSTALL = "delegation-cert-install";
@@ -9379,15 +9398,17 @@
     method public int describeContents();
     method public long getBeginTimeMillis();
     method public long getEndTimeMillis();
-    method @NonNull public java.util.Set<java.lang.Integer> getEventTypes();
+    method @NonNull public int[] getEventTypes();
+    method @NonNull public java.util.Set<java.lang.String> getPackageNames();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.usage.UsageEventsQuery> CREATOR;
   }
 
   public static final class UsageEventsQuery.Builder {
     ctor public UsageEventsQuery.Builder(long, long);
-    method @NonNull public android.app.usage.UsageEventsQuery.Builder addEventTypes(@NonNull int...);
     method @NonNull public android.app.usage.UsageEventsQuery build();
+    method @NonNull public android.app.usage.UsageEventsQuery.Builder setEventTypes(@NonNull int...);
+    method @NonNull public android.app.usage.UsageEventsQuery.Builder setPackageNames(@NonNull java.lang.String...);
   }
 
   public final class UsageStats implements android.os.Parcelable {
@@ -9414,7 +9435,7 @@
     method public java.util.List<android.app.usage.ConfigurationStats> queryConfigurations(int, long, long);
     method public java.util.List<android.app.usage.EventStats> queryEventStats(int, long, long);
     method public android.app.usage.UsageEvents queryEvents(long, long);
-    method @FlaggedApi("android.app.usage.filter_based_event_query_api") @NonNull @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public android.app.usage.UsageEvents queryEvents(@NonNull android.app.usage.UsageEventsQuery);
+    method @FlaggedApi("android.app.usage.filter_based_event_query_api") @Nullable @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public android.app.usage.UsageEvents queryEvents(@NonNull android.app.usage.UsageEventsQuery);
     method public android.app.usage.UsageEvents queryEventsForSelf(long, long);
     method public java.util.List<android.app.usage.UsageStats> queryUsageStats(int, long, long);
     field @FlaggedApi("android.app.usage.user_interaction_type_api") public static final String EXTRA_EVENT_ACTION = "android.app.usage.extra.EVENT_ACTION";
@@ -9484,12 +9505,15 @@
     method @NonNull public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForPackage(@NonNull String, @Nullable android.os.UserHandle);
     method @NonNull public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(@Nullable android.os.UserHandle);
     method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
+    method @FlaggedApi("android.appwidget.flags.generated_previews") @Nullable public android.widget.RemoteViews getWidgetPreview(@NonNull android.content.ComponentName, @Nullable android.os.UserHandle, int);
     method public boolean isRequestPinAppWidgetSupported();
     method @Deprecated public void notifyAppWidgetViewDataChanged(int[], int);
     method @Deprecated public void notifyAppWidgetViewDataChanged(int, int);
     method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
     method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews);
+    method @FlaggedApi("android.appwidget.flags.generated_previews") public void removeWidgetPreview(@NonNull android.content.ComponentName, int);
     method public boolean requestPinAppWidget(@NonNull android.content.ComponentName, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
+    method @FlaggedApi("android.appwidget.flags.generated_previews") public void setWidgetPreview(@NonNull android.content.ComponentName, int, @NonNull android.widget.RemoteViews);
     method public void updateAppWidget(int[], android.widget.RemoteViews);
     method public void updateAppWidget(int, android.widget.RemoteViews);
     method public void updateAppWidget(android.content.ComponentName, android.widget.RemoteViews);
@@ -9563,6 +9587,7 @@
     field public int autoAdvanceViewId;
     field public android.content.ComponentName configure;
     field @IdRes public int descriptionRes;
+    field @FlaggedApi("android.appwidget.flags.generated_previews") public int generatedPreviewCategories;
     field public int icon;
     field public int initialKeyguardLayout;
     field public int initialLayout;
@@ -9689,8 +9714,10 @@
     method public void requestNotificationAccess(android.content.ComponentName);
     method @FlaggedApi("android.companion.association_tag") public void setAssociationTag(int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
+    method @FlaggedApi("android.companion.device_presence") @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull android.companion.ObservingDevicePresenceRequest);
     method public void startSystemDataTransfer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.companion.CompanionException>) throws android.companion.DeviceNotAssociatedException;
     method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
+    method @FlaggedApi("android.companion.device_presence") @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull android.companion.ObservingDevicePresenceRequest);
     field public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION";
     field @Deprecated public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
     field public static final int FLAG_CALL_METADATA = 1; // 0x1
@@ -9718,13 +9745,7 @@
     method @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
     method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String);
     method @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo);
-    method @FlaggedApi("android.companion.device_presence") @MainThread public void onDeviceEvent(@NonNull android.companion.AssociationInfo, int);
-    field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BLE_APPEARED = 0; // 0x0
-    field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BLE_DISAPPEARED = 1; // 0x1
-    field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BT_CONNECTED = 2; // 0x2
-    field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BT_DISCONNECTED = 3; // 0x3
-    field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4; // 0x4
-    field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5; // 0x5
+    method @FlaggedApi("android.companion.device_presence") @MainThread public void onDevicePresenceEvent(@NonNull android.companion.DevicePresenceEvent);
     field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
   }
 
@@ -9737,6 +9758,38 @@
   public class DeviceNotAssociatedException extends java.lang.RuntimeException {
   }
 
+  @FlaggedApi("android.companion.device_presence") public final class DevicePresenceEvent implements android.os.Parcelable {
+    ctor public DevicePresenceEvent(int, int, @Nullable android.os.ParcelUuid);
+    method public int describeContents();
+    method public int getAssociationId();
+    method public int getEvent();
+    method @Nullable public android.os.ParcelUuid getUuid();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.companion.DevicePresenceEvent> CREATOR;
+    field public static final int EVENT_BLE_APPEARED = 0; // 0x0
+    field public static final int EVENT_BLE_DISAPPEARED = 1; // 0x1
+    field public static final int EVENT_BT_CONNECTED = 2; // 0x2
+    field public static final int EVENT_BT_DISCONNECTED = 3; // 0x3
+    field public static final int EVENT_SELF_MANAGED_APPEARED = 4; // 0x4
+    field public static final int EVENT_SELF_MANAGED_DISAPPEARED = 5; // 0x5
+    field public static final int NO_ASSOCIATION = -1; // 0xffffffff
+  }
+
+  @FlaggedApi("android.companion.device_presence") public final class ObservingDevicePresenceRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAssociationId();
+    method @Nullable public android.os.ParcelUuid getUuid();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.companion.ObservingDevicePresenceRequest> CREATOR;
+  }
+
+  public static final class ObservingDevicePresenceRequest.Builder {
+    ctor public ObservingDevicePresenceRequest.Builder();
+    method @NonNull public android.companion.ObservingDevicePresenceRequest build();
+    method @NonNull public android.companion.ObservingDevicePresenceRequest.Builder setAssociationId(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE) public android.companion.ObservingDevicePresenceRequest.Builder setUuid(@NonNull android.os.ParcelUuid);
+  }
+
   public final class WifiDeviceFilter implements android.companion.DeviceFilter<android.net.wifi.ScanResult> {
     method public int describeContents();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -10381,6 +10434,7 @@
     method @CheckResult(suggest="#enforceCallingPermission(String,String)") public abstract int checkCallingPermission(@NonNull String);
     method @CheckResult(suggest="#enforceCallingUriPermission(Uri,int,String)") public abstract int checkCallingUriPermission(android.net.Uri, int);
     method @NonNull public int[] checkCallingUriPermissions(@NonNull java.util.List<android.net.Uri>, int);
+    method @FlaggedApi("android.security.content_uri_permission_apis") public int checkContentUriPermissionFull(@NonNull android.net.Uri, int, int, int);
     method @CheckResult(suggest="#enforcePermission(String,int,int,String)") public abstract int checkPermission(@NonNull String, int, int);
     method public abstract int checkSelfPermission(@NonNull String);
     method @CheckResult(suggest="#enforceUriPermission(Uri,int,int,String)") public abstract int checkUriPermission(android.net.Uri, int, int, int);
@@ -10539,6 +10593,7 @@
     field public static final int BIND_INCLUDE_CAPABILITIES = 4096; // 0x1000
     field public static final int BIND_NOT_FOREGROUND = 4; // 0x4
     field public static final int BIND_NOT_PERCEPTIBLE = 256; // 0x100
+    field @FlaggedApi("android.content.flags.enable_bind_package_isolated_process") public static final int BIND_PACKAGE_ISOLATED_PROCESS = 16384; // 0x4000
     field public static final int BIND_SHARED_ISOLATED_PROCESS = 8192; // 0x2000
     field public static final int BIND_WAIVE_PRIORITY = 32; // 0x20
     field public static final String BIOMETRIC_SERVICE = "biometric";
@@ -10553,6 +10608,7 @@
     field public static final String CONNECTIVITY_DIAGNOSTICS_SERVICE = "connectivity_diagnostics";
     field public static final String CONNECTIVITY_SERVICE = "connectivity";
     field public static final String CONSUMER_IR_SERVICE = "consumer_ir";
+    field @FlaggedApi("android.provider.user_keys") public static final String CONTACT_KEYS_SERVICE = "contact_keys";
     field public static final int CONTEXT_IGNORE_SECURITY = 2; // 0x2
     field public static final int CONTEXT_INCLUDE_CODE = 1; // 0x1
     field public static final int CONTEXT_RESTRICTED = 4; // 0x4
@@ -11346,10 +11402,12 @@
     method public final void addDataScheme(String);
     method public final void addDataSchemeSpecificPart(String, int);
     method public final void addDataType(String) throws android.content.IntentFilter.MalformedMimeTypeException;
+    method @FlaggedApi("android.content.pm.relative_reference_intent_filters") public final void addUriRelativeFilterGroup(@NonNull android.content.UriRelativeFilterGroup);
     method @NonNull public java.util.function.Predicate<android.content.Intent> asPredicate();
     method @NonNull public java.util.function.Predicate<android.content.Intent> asPredicateWithTypeResolution(@NonNull android.content.ContentResolver);
     method public final java.util.Iterator<android.content.IntentFilter.AuthorityEntry> authoritiesIterator();
     method public final java.util.Iterator<java.lang.String> categoriesIterator();
+    method @FlaggedApi("android.content.pm.relative_reference_intent_filters") public final void clearUriRelativeFilterGroups();
     method public final int countActions();
     method public final int countCategories();
     method public final int countDataAuthorities();
@@ -11357,6 +11415,7 @@
     method public final int countDataSchemeSpecificParts();
     method public final int countDataSchemes();
     method public final int countDataTypes();
+    method @FlaggedApi("android.content.pm.relative_reference_intent_filters") public final int countUriRelativeFilterGroups();
     method public static android.content.IntentFilter create(String, String);
     method public final int describeContents();
     method public void dump(android.util.Printer, String);
@@ -11368,6 +11427,7 @@
     method public final android.os.PatternMatcher getDataSchemeSpecificPart(int);
     method public final String getDataType(int);
     method public final int getPriority();
+    method @FlaggedApi("android.content.pm.relative_reference_intent_filters") @NonNull public final android.content.UriRelativeFilterGroup getUriRelativeFilterGroup(int);
     method public final boolean hasAction(String);
     method public final boolean hasCategory(String);
     method public final boolean hasDataAuthority(android.net.Uri);
@@ -11789,6 +11849,27 @@
     field public static final long INVALID_TIME = -9223372036854775808L; // 0x8000000000000000L
   }
 
+  @FlaggedApi("android.content.pm.relative_reference_intent_filters") public final class UriRelativeFilter {
+    ctor public UriRelativeFilter(int, int, @NonNull String);
+    method @NonNull public String getFilter();
+    method public int getPatternType();
+    method public int getUriPart();
+    method public boolean matchData(@NonNull android.net.Uri);
+    field public static final int FRAGMENT = 2; // 0x2
+    field public static final int PATH = 0; // 0x0
+    field public static final int QUERY = 1; // 0x1
+  }
+
+  @FlaggedApi("android.content.pm.relative_reference_intent_filters") public final class UriRelativeFilterGroup {
+    ctor public UriRelativeFilterGroup(int);
+    method public void addUriRelativeFilter(@NonNull android.content.UriRelativeFilter);
+    method public int getAction();
+    method @NonNull public java.util.Collection<android.content.UriRelativeFilter> getUriRelativeFilters();
+    method public boolean matchData(@NonNull android.net.Uri);
+    field public static final int ACTION_ALLOW = 0; // 0x0
+    field public static final int ACTION_BLOCK = 1; // 0x1
+  }
+
 }
 
 package android.content.om {
@@ -12438,8 +12519,9 @@
     method @NonNull public android.content.pm.PackageInstaller.Session openSession(int) throws java.io.IOException;
     method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback);
     method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback, @NonNull android.os.Handler);
+    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalState(@NonNull android.content.pm.PackageInstaller.UnarchivalState) throws android.content.pm.PackageManager.NameNotFoundException;
     method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalStatus(int, int, long, @Nullable android.app.PendingIntent) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
     method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String, @NonNull android.content.IntentSender) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
     method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull String, @NonNull android.content.IntentSender);
     method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull android.content.pm.VersionedPackage, @NonNull android.content.IntentSender);
@@ -12667,6 +12749,14 @@
     field public static final int USER_ACTION_UNSPECIFIED = 0; // 0x0
   }
 
+  @FlaggedApi("android.content.pm.archiving") public static final class PackageInstaller.UnarchivalState {
+    method @NonNull public static android.content.pm.PackageInstaller.UnarchivalState createGenericErrorState(int);
+    method @NonNull public static android.content.pm.PackageInstaller.UnarchivalState createInsufficientStorageState(int, long, @Nullable android.app.PendingIntent);
+    method @NonNull public static android.content.pm.PackageInstaller.UnarchivalState createNoConnectivityState(int);
+    method @NonNull public static android.content.pm.PackageInstaller.UnarchivalState createOkState(int);
+    method @NonNull public static android.content.pm.PackageInstaller.UnarchivalState createUserActionRequiredState(int, @NonNull android.app.PendingIntent);
+  }
+
   public class PackageItemInfo {
     ctor public PackageItemInfo();
     ctor public PackageItemInfo(android.content.pm.PackageItemInfo);
@@ -12866,7 +12956,6 @@
     field public static final int COMPONENT_ENABLED_STATE_DISABLED_USER = 3; // 0x3
     field public static final int COMPONENT_ENABLED_STATE_ENABLED = 1; // 0x1
     field @FlaggedApi("android.content.pm.archiving") public static final int DELETE_ARCHIVE = 16; // 0x10
-    field @FlaggedApi("android.content.pm.archiving") public static final int DELETE_SHOW_DIALOG = 32; // 0x20
     field public static final int DONT_KILL_APP = 1; // 0x1
     field public static final String EXTRA_VERIFICATION_ID = "android.content.pm.extra.VERIFICATION_ID";
     field public static final String EXTRA_VERIFICATION_RESULT = "android.content.pm.extra.VERIFICATION_RESULT";
@@ -13669,11 +13758,8 @@
   @FlaggedApi("android.content.res.font_scale_converter_public") public interface FontScaleConverter {
     method public float convertDpToSp(float);
     method public float convertSpToDp(float);
-  }
-
-  @FlaggedApi("android.content.res.font_scale_converter_public") public class FontScaleConverterFactory {
-    method @FlaggedApi("android.content.res.font_scale_converter_public") @AnyThread @Nullable public static android.content.res.FontScaleConverter forScale(float);
-    method @FlaggedApi("android.content.res.font_scale_converter_public") @AnyThread public static boolean isNonLinearFontScalingActive(float);
+    method @AnyThread @Nullable public static android.content.res.FontScaleConverter forScale(float);
+    method @AnyThread public static boolean isNonLinearFontScalingActive(float);
   }
 
   public class ObbInfo implements android.os.Parcelable {
@@ -16393,6 +16479,8 @@
     field public static final int START_HYPHEN_EDIT_NO_EDIT = 0; // 0x0
     field public static final int STRIKE_THRU_TEXT_FLAG = 16; // 0x10
     field public static final int SUBPIXEL_TEXT_FLAG = 128; // 0x80
+    field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_LEFT_EDGE = 8192; // 0x2000
+    field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 16384; // 0x4000
     field public static final int UNDERLINE_TEXT_FLAG = 8; // 0x8
   }
 
@@ -16700,7 +16788,6 @@
   }
 
   public final class RecordingCanvas extends android.graphics.Canvas {
-    method public final void drawMesh(@NonNull android.graphics.Mesh, android.graphics.BlendMode, @NonNull android.graphics.Paint);
   }
 
   public final class Rect implements android.os.Parcelable {
@@ -17870,6 +17957,7 @@
     field public static final int HYPHENATION_FREQUENCY_FULL = 2; // 0x2
     field public static final int HYPHENATION_FREQUENCY_NONE = 0; // 0x0
     field public static final int HYPHENATION_FREQUENCY_NORMAL = 1; // 0x1
+    field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
     field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1
     field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0
   }
@@ -18323,8 +18411,8 @@
     field public static final int RGBX_8888 = 2; // 0x2
     field public static final int RGB_565 = 4; // 0x4
     field public static final int RGB_888 = 3; // 0x3
-    field @FlaggedApi("com.android.graphics.hwui.flags.requested_formats_v") public static final int RG_1616_UINT = 58; // 0x3a
-    field @FlaggedApi("com.android.graphics.hwui.flags.requested_formats_v") public static final int R_16_UINT = 57; // 0x39
+    field @FlaggedApi("com.android.graphics.hwui.flags.requested_formats_v") public static final int RG_1616 = 58; // 0x3a
+    field @FlaggedApi("com.android.graphics.hwui.flags.requested_formats_v") public static final int R_16 = 57; // 0x39
     field @FlaggedApi("com.android.graphics.hwui.flags.requested_formats_v") public static final int R_8 = 56; // 0x38
     field public static final int S_UI8 = 53; // 0x35
     field public static final long USAGE_COMPOSER_OVERLAY = 2048L; // 0x800L
@@ -18617,6 +18705,7 @@
   }
 
   public final class SyncFence implements java.lang.AutoCloseable android.os.Parcelable {
+    ctor @FlaggedApi("com.android.window.flags.sdk_desired_present_time") public SyncFence(@NonNull android.hardware.SyncFence);
     method public boolean await(@NonNull java.time.Duration);
     method public boolean awaitForever();
     method public void close();
@@ -18673,7 +18762,10 @@
     method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public void authenticate(@NonNull android.hardware.biometrics.BiometricPrompt.CryptoObject, @NonNull android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback);
     method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public void authenticate(@NonNull android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback);
     method @Nullable public int getAllowedAuthenticators();
+    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable public android.hardware.biometrics.PromptContentView getContentView();
     method @Nullable public CharSequence getDescription();
+    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.graphics.Bitmap getLogoBitmap();
+    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @DrawableRes @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public int getLogoRes();
     method @Nullable public CharSequence getNegativeButtonText();
     method @Nullable public CharSequence getSubtitle();
     method @NonNull public CharSequence getTitle();
@@ -18720,8 +18812,11 @@
     method @NonNull public android.hardware.biometrics.BiometricPrompt build();
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setAllowedAuthenticators(int);
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setConfirmationRequired(boolean);
+    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setContentView(@NonNull android.hardware.biometrics.PromptContentView);
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence);
     method @Deprecated @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean);
+    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.hardware.biometrics.BiometricPrompt.Builder setLogoBitmap(@NonNull android.graphics.Bitmap);
+    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.hardware.biometrics.BiometricPrompt.Builder setLogoRes(@DrawableRes int);
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener);
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence);
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setTitle(@NonNull CharSequence);
@@ -18743,6 +18838,44 @@
     method @Nullable public java.security.Signature getSignature();
   }
 
+  @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public interface PromptContentItem {
+  }
+
+  @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentItemBulletedText implements android.os.Parcelable android.hardware.biometrics.PromptContentItem {
+    ctor public PromptContentItemBulletedText(@NonNull String);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentItemBulletedText> CREATOR;
+  }
+
+  @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentItemPlainText implements android.os.Parcelable android.hardware.biometrics.PromptContentItem {
+    ctor public PromptContentItemPlainText(@NonNull String);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentItemPlainText> CREATOR;
+  }
+
+  @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public interface PromptContentView {
+  }
+
+  @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptVerticalListContentView implements android.os.Parcelable android.hardware.biometrics.PromptContentView {
+    method public int describeContents();
+    method @Nullable public String getDescription();
+    method @NonNull public java.util.List<android.hardware.biometrics.PromptContentItem> getListItems();
+    method public static int getMaxEachItemCharacterNumber();
+    method public static int getMaxItemCount();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptVerticalListContentView> CREATOR;
+  }
+
+  public static final class PromptVerticalListContentView.Builder {
+    ctor public PromptVerticalListContentView.Builder();
+    method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentItem);
+    method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentItem, int);
+    method @NonNull public android.hardware.biometrics.PromptVerticalListContentView build();
+    method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder setDescription(@NonNull String);
+  }
+
 }
 
 package android.hardware.camera2 {
@@ -19732,7 +19865,7 @@
   @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class LensIntrinsicsSample {
     ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public LensIntrinsicsSample(long, @NonNull float[]);
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public float[] getLensIntrinsics();
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public long getTimestamp();
+    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public long getTimestampNanos();
   }
 
   public final class LensShadingMap {
@@ -22103,16 +22236,16 @@
     method public void onJetUserIdUpdate(android.media.JetPlayer, int, int);
   }
 
-  @FlaggedApi("android.media.audio.loudness_configurator_api") public class LoudnessCodecConfigurator {
+  @FlaggedApi("android.media.audio.loudness_configurator_api") public class LoudnessCodecController implements java.lang.AutoCloseable {
     method @FlaggedApi("android.media.audio.loudness_configurator_api") public boolean addMediaCodec(@NonNull android.media.MediaCodec);
-    method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecConfigurator create();
-    method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecConfigurator create(@NonNull java.util.concurrent.Executor, @NonNull android.media.LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener);
-    method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public android.os.Bundle getLoudnessCodecParams(@NonNull android.media.AudioTrack, @NonNull android.media.MediaCodec);
+    method @FlaggedApi("android.media.audio.loudness_configurator_api") public void close();
+    method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecController create(int);
+    method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecController create(int, @NonNull java.util.concurrent.Executor, @NonNull android.media.LoudnessCodecController.OnLoudnessCodecUpdateListener);
+    method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public android.os.Bundle getLoudnessCodecParams(@NonNull android.media.MediaCodec);
     method @FlaggedApi("android.media.audio.loudness_configurator_api") public void removeMediaCodec(@NonNull android.media.MediaCodec);
-    method @FlaggedApi("android.media.audio.loudness_configurator_api") public void setAudioTrack(@Nullable android.media.AudioTrack);
   }
 
-  @FlaggedApi("android.media.audio.loudness_configurator_api") public static interface LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener {
+  @FlaggedApi("android.media.audio.loudness_configurator_api") public static interface LoudnessCodecController.OnLoudnessCodecUpdateListener {
     method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public default android.os.Bundle onLoudnessCodecUpdate(@NonNull android.media.MediaCodec, @NonNull android.os.Bundle);
   }
 
@@ -22242,6 +22375,7 @@
     method @NonNull public java.util.List<java.lang.String> getSupportedVendorParameters();
     method @Nullable public static android.media.Image mapHardwareBuffer(@NonNull android.hardware.HardwareBuffer);
     method public void queueInputBuffer(int, int, int, long, int) throws android.media.MediaCodec.CryptoException;
+    method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") public void queueInputBuffers(int, @NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>);
     method public void queueSecureInputBuffer(int, int, @NonNull android.media.MediaCodec.CryptoInfo, long, int) throws android.media.MediaCodec.CryptoException;
     method public void release();
     method public void releaseOutputBuffer(int, boolean);
@@ -22303,6 +22437,7 @@
     method public abstract void onError(@NonNull android.media.MediaCodec, @NonNull android.media.MediaCodec.CodecException);
     method public abstract void onInputBufferAvailable(@NonNull android.media.MediaCodec, int);
     method public abstract void onOutputBufferAvailable(@NonNull android.media.MediaCodec, int, @NonNull android.media.MediaCodec.BufferInfo);
+    method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") public void onOutputBuffersAvailable(@NonNull android.media.MediaCodec, int, @NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>);
     method public abstract void onOutputFormatChanged(@NonNull android.media.MediaCodec, @NonNull android.media.MediaFormat);
   }
 
@@ -22390,6 +22525,7 @@
   }
 
   public static final class MediaCodec.OutputFrame {
+    method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") @NonNull public java.util.ArrayDeque<android.media.MediaCodec.BufferInfo> getBufferInfos();
     method @NonNull public java.util.Set<java.lang.String> getChangedKeys();
     method public int getFlags();
     method @NonNull public android.media.MediaFormat getFormat();
@@ -22405,6 +22541,7 @@
 
   public final class MediaCodec.QueueRequest {
     method public void queue();
+    method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") @NonNull public android.media.MediaCodec.QueueRequest setBufferInfos(@NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>);
     method @NonNull public android.media.MediaCodec.QueueRequest setByteBufferParameter(@NonNull String, @NonNull java.nio.ByteBuffer);
     method @NonNull public android.media.MediaCodec.QueueRequest setEncryptedLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, int, int, @NonNull android.media.MediaCodec.CryptoInfo);
     method @NonNull public android.media.MediaCodec.QueueRequest setFlags(int);
@@ -23266,6 +23403,8 @@
     field public static final String KEY_AUDIO_SESSION_ID = "audio-session-id";
     field public static final String KEY_BITRATE_MODE = "bitrate-mode";
     field public static final String KEY_BIT_RATE = "bitrate";
+    field @FlaggedApi("com.android.media.codec.flags.large_audio_frame") public static final String KEY_BUFFER_BATCH_MAX_OUTPUT_SIZE = "buffer-batch-max-output-size";
+    field @FlaggedApi("com.android.media.codec.flags.large_audio_frame") public static final String KEY_BUFFER_BATCH_THRESHOLD_OUTPUT_SIZE = "buffer-batch-threshold-output-size";
     field public static final String KEY_CAPTION_SERVICE_NUMBER = "caption-service-number";
     field public static final String KEY_CAPTURE_RATE = "capture-rate";
     field public static final String KEY_CHANNEL_COUNT = "channel-count";
@@ -23294,6 +23433,7 @@
     field public static final String KEY_HDR10_PLUS_INFO = "hdr10-plus-info";
     field public static final String KEY_HDR_STATIC_INFO = "hdr-static-info";
     field public static final String KEY_HEIGHT = "height";
+    field @FlaggedApi("com.android.media.codec.flags.codec_importance") public static final String KEY_IMPORTANCE = "importance";
     field public static final String KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period";
     field public static final String KEY_IS_ADTS = "is-adts";
     field public static final String KEY_IS_AUTOSELECT = "is-autoselect";
@@ -24205,7 +24345,7 @@
     method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String);
     method @NonNull public java.util.List<android.media.MediaRouter2.RoutingController> getControllers();
     method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context);
-    method @FlaggedApi("com.android.media.flags.enable_cross_user_routing_in_media_router2") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MEDIA_CONTENT_CONTROL, android.Manifest.permission.MEDIA_ROUTING_CONTROL}) public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.os.UserHandle);
+    method @FlaggedApi("com.android.media.flags.enable_cross_user_routing_in_media_router2") @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MEDIA_CONTENT_CONTROL, android.Manifest.permission.MEDIA_ROUTING_CONTROL}) public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String);
     method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") @Nullable public android.media.RouteListingPreference getRouteListingPreference();
     method @NonNull public java.util.List<android.media.MediaRoute2Info> getRoutes();
     method @NonNull public android.media.MediaRouter2.RoutingController getSystemController();
@@ -24256,7 +24396,7 @@
     method public void release();
     method public void selectRoute(@NonNull android.media.MediaRoute2Info);
     method public void setVolume(int);
-    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public boolean wasTransferRequestedBySelf();
+    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public boolean wasTransferInitiatedBySelf();
   }
 
   public abstract static class MediaRouter2.TransferCallback {
@@ -25421,34 +25561,34 @@
     field public short preset;
   }
 
-  public class Virtualizer extends android.media.audiofx.AudioEffect {
-    ctor public Virtualizer(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.RuntimeException, java.lang.UnsupportedOperationException;
-    method public boolean canVirtualize(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
-    method public boolean forceVirtualizationMode(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
-    method public android.media.audiofx.Virtualizer.Settings getProperties() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
-    method public short getRoundedStrength() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
-    method public boolean getSpeakerAngles(int, int, int[]) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
-    method public boolean getStrengthSupported();
-    method public int getVirtualizationMode() throws java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
-    method public void setParameterListener(android.media.audiofx.Virtualizer.OnParameterChangeListener);
-    method public void setProperties(android.media.audiofx.Virtualizer.Settings) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
-    method public void setStrength(short) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
-    field public static final int PARAM_STRENGTH = 1; // 0x1
-    field public static final int PARAM_STRENGTH_SUPPORTED = 0; // 0x0
-    field public static final int VIRTUALIZATION_MODE_AUTO = 1; // 0x1
-    field public static final int VIRTUALIZATION_MODE_BINAURAL = 2; // 0x2
-    field public static final int VIRTUALIZATION_MODE_OFF = 0; // 0x0
-    field public static final int VIRTUALIZATION_MODE_TRANSAURAL = 3; // 0x3
+  @Deprecated public class Virtualizer extends android.media.audiofx.AudioEffect {
+    ctor @Deprecated public Virtualizer(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.RuntimeException, java.lang.UnsupportedOperationException;
+    method @Deprecated public boolean canVirtualize(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+    method @Deprecated public boolean forceVirtualizationMode(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+    method @Deprecated public android.media.audiofx.Virtualizer.Settings getProperties() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+    method @Deprecated public short getRoundedStrength() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+    method @Deprecated public boolean getSpeakerAngles(int, int, int[]) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+    method @Deprecated public boolean getStrengthSupported();
+    method @Deprecated public int getVirtualizationMode() throws java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+    method @Deprecated public void setParameterListener(android.media.audiofx.Virtualizer.OnParameterChangeListener);
+    method @Deprecated public void setProperties(android.media.audiofx.Virtualizer.Settings) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+    method @Deprecated public void setStrength(short) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+    field @Deprecated public static final int PARAM_STRENGTH = 1; // 0x1
+    field @Deprecated public static final int PARAM_STRENGTH_SUPPORTED = 0; // 0x0
+    field @Deprecated public static final int VIRTUALIZATION_MODE_AUTO = 1; // 0x1
+    field @Deprecated public static final int VIRTUALIZATION_MODE_BINAURAL = 2; // 0x2
+    field @Deprecated public static final int VIRTUALIZATION_MODE_OFF = 0; // 0x0
+    field @Deprecated public static final int VIRTUALIZATION_MODE_TRANSAURAL = 3; // 0x3
   }
 
-  public static interface Virtualizer.OnParameterChangeListener {
-    method public void onParameterChange(android.media.audiofx.Virtualizer, int, int, short);
+  @Deprecated public static interface Virtualizer.OnParameterChangeListener {
+    method @Deprecated public void onParameterChange(android.media.audiofx.Virtualizer, int, int, short);
   }
 
-  public static class Virtualizer.Settings {
-    ctor public Virtualizer.Settings();
-    ctor public Virtualizer.Settings(String);
-    field public short strength;
+  @Deprecated public static class Virtualizer.Settings {
+    ctor @Deprecated public Virtualizer.Settings();
+    ctor @Deprecated public Virtualizer.Settings(String);
+    field @Deprecated public short strength;
   }
 
   public class Visualizer {
@@ -25621,9 +25761,48 @@
     field public static final String KEY_STATSD_ATOM = "bundlesession-statsd-atom";
   }
 
+  @FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public final class EditingEndedEvent extends android.media.metrics.Event implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getErrorCode();
+    method public int getFinalState();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.EditingEndedEvent> CREATOR;
+    field public static final int ERROR_CODE_AUDIO_PROCESSING_FAILED = 18; // 0x12
+    field public static final int ERROR_CODE_DECODER_INIT_FAILED = 11; // 0xb
+    field public static final int ERROR_CODE_DECODING_FAILED = 12; // 0xc
+    field public static final int ERROR_CODE_DECODING_FORMAT_UNSUPPORTED = 13; // 0xd
+    field public static final int ERROR_CODE_ENCODER_INIT_FAILED = 14; // 0xe
+    field public static final int ERROR_CODE_ENCODING_FAILED = 15; // 0xf
+    field public static final int ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED = 16; // 0x10
+    field public static final int ERROR_CODE_FAILED_RUNTIME_CHECK = 2; // 0x2
+    field public static final int ERROR_CODE_IO_BAD_HTTP_STATUS = 6; // 0x6
+    field public static final int ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED = 9; // 0x9
+    field public static final int ERROR_CODE_IO_FILE_NOT_FOUND = 7; // 0x7
+    field public static final int ERROR_CODE_IO_NETWORK_CONNECTION_FAILED = 4; // 0x4
+    field public static final int ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT = 5; // 0x5
+    field public static final int ERROR_CODE_IO_NO_PERMISSION = 8; // 0x8
+    field public static final int ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE = 10; // 0xa
+    field public static final int ERROR_CODE_IO_UNSPECIFIED = 3; // 0x3
+    field public static final int ERROR_CODE_MUXING_FAILED = 19; // 0x13
+    field public static final int ERROR_CODE_NONE = 1; // 0x1
+    field public static final int ERROR_CODE_VIDEO_FRAME_PROCESSING_FAILED = 17; // 0x11
+    field public static final int FINAL_STATE_CANCELED = 2; // 0x2
+    field public static final int FINAL_STATE_ERROR = 3; // 0x3
+    field public static final int FINAL_STATE_SUCCEEDED = 1; // 0x1
+  }
+
+  @FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public static final class EditingEndedEvent.Builder {
+    ctor public EditingEndedEvent.Builder(int);
+    method @NonNull public android.media.metrics.EditingEndedEvent build();
+    method @NonNull public android.media.metrics.EditingEndedEvent.Builder setErrorCode(int);
+    method @NonNull public android.media.metrics.EditingEndedEvent.Builder setMetricsBundle(@NonNull android.os.Bundle);
+    method @NonNull public android.media.metrics.EditingEndedEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
+  }
+
   public final class EditingSession implements java.lang.AutoCloseable {
     method public void close();
     method @NonNull public android.media.metrics.LogSessionId getSessionId();
+    method @FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public void reportEditingEndedEvent(@NonNull android.media.metrics.EditingEndedEvent);
   }
 
   public abstract class Event {
@@ -26622,12 +26801,16 @@
 
   public static final class TvContract.Channels implements android.media.tv.TvContract.BaseTvColumns {
     method @Nullable public static String getVideoResolution(String);
+    field @FlaggedApi("android.media.tv.flags.broadcast_visibility_types") public static final int BROADCAST_VISIBILITY_TYPE_INVISIBLE = 2; // 0x2
+    field @FlaggedApi("android.media.tv.flags.broadcast_visibility_types") public static final int BROADCAST_VISIBILITY_TYPE_NUMERIC_SELECTABLE_ONLY = 1; // 0x1
+    field @FlaggedApi("android.media.tv.flags.broadcast_visibility_types") public static final int BROADCAST_VISIBILITY_TYPE_VISIBLE = 0; // 0x0
     field public static final String COLUMN_APP_LINK_COLOR = "app_link_color";
     field public static final String COLUMN_APP_LINK_ICON_URI = "app_link_icon_uri";
     field public static final String COLUMN_APP_LINK_INTENT_URI = "app_link_intent_uri";
     field public static final String COLUMN_APP_LINK_POSTER_ART_URI = "app_link_poster_art_uri";
     field public static final String COLUMN_APP_LINK_TEXT = "app_link_text";
     field public static final String COLUMN_BROADCAST_GENRE = "broadcast_genre";
+    field @FlaggedApi("android.media.tv.flags.broadcast_visibility_types") public static final String COLUMN_BROADCAST_VISIBILITY_TYPE = "broadcast_visibility_type";
     field public static final String COLUMN_BROWSABLE = "browsable";
     field public static final String COLUMN_CHANNEL_LIST_ID = "channel_list_id";
     field public static final String COLUMN_DESCRIPTION = "description";
@@ -28746,460 +28929,6 @@
 
 }
 
-package android.nfc {
-
-  public final class AvailableNfcAntenna implements android.os.Parcelable {
-    ctor public AvailableNfcAntenna(int, int);
-    method public int describeContents();
-    method public int getLocationX();
-    method public int getLocationY();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.AvailableNfcAntenna> CREATOR;
-  }
-
-  public class FormatException extends java.lang.Exception {
-    ctor public FormatException();
-    ctor public FormatException(String);
-    ctor public FormatException(String, Throwable);
-  }
-
-  public final class NdefMessage implements android.os.Parcelable {
-    ctor public NdefMessage(byte[]) throws android.nfc.FormatException;
-    ctor public NdefMessage(android.nfc.NdefRecord, android.nfc.NdefRecord...);
-    ctor public NdefMessage(android.nfc.NdefRecord[]);
-    method public int describeContents();
-    method public int getByteArrayLength();
-    method public android.nfc.NdefRecord[] getRecords();
-    method public byte[] toByteArray();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NdefMessage> CREATOR;
-  }
-
-  public final class NdefRecord implements android.os.Parcelable {
-    ctor public NdefRecord(short, byte[], byte[], byte[]);
-    ctor @Deprecated public NdefRecord(byte[]) throws android.nfc.FormatException;
-    method public static android.nfc.NdefRecord createApplicationRecord(String);
-    method public static android.nfc.NdefRecord createExternal(String, String, byte[]);
-    method public static android.nfc.NdefRecord createMime(String, byte[]);
-    method public static android.nfc.NdefRecord createTextRecord(String, String);
-    method public static android.nfc.NdefRecord createUri(android.net.Uri);
-    method public static android.nfc.NdefRecord createUri(String);
-    method public int describeContents();
-    method public byte[] getId();
-    method public byte[] getPayload();
-    method public short getTnf();
-    method public byte[] getType();
-    method @Deprecated public byte[] toByteArray();
-    method public String toMimeType();
-    method public android.net.Uri toUri();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NdefRecord> CREATOR;
-    field public static final byte[] RTD_ALTERNATIVE_CARRIER;
-    field public static final byte[] RTD_HANDOVER_CARRIER;
-    field public static final byte[] RTD_HANDOVER_REQUEST;
-    field public static final byte[] RTD_HANDOVER_SELECT;
-    field public static final byte[] RTD_SMART_POSTER;
-    field public static final byte[] RTD_TEXT;
-    field public static final byte[] RTD_URI;
-    field public static final short TNF_ABSOLUTE_URI = 3; // 0x3
-    field public static final short TNF_EMPTY = 0; // 0x0
-    field public static final short TNF_EXTERNAL_TYPE = 4; // 0x4
-    field public static final short TNF_MIME_MEDIA = 2; // 0x2
-    field public static final short TNF_UNCHANGED = 6; // 0x6
-    field public static final short TNF_UNKNOWN = 5; // 0x5
-    field public static final short TNF_WELL_KNOWN = 1; // 0x1
-  }
-
-  public final class NfcAdapter {
-    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean allowTransaction();
-    method public void disableForegroundDispatch(android.app.Activity);
-    method public void disableReaderMode(android.app.Activity);
-    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean disallowTransaction();
-    method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]);
-    method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
-    method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
-    method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo();
-    method @FlaggedApi("android.nfc.enable_nfc_charging") @Nullable public android.nfc.WlcLDeviceInfo getWlcLDeviceInfo();
-    method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
-    method public boolean isEnabled();
-    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported();
-    method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionEnabled();
-    method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported();
-    method public boolean isSecureNfcEnabled();
-    method public boolean isSecureNfcSupported();
-    method @FlaggedApi("android.nfc.enable_nfc_charging") public boolean isWlcEnabled();
-    method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void resetDiscoveryTechnology(@NonNull android.app.Activity);
-    method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void setDiscoveryTechnology(@NonNull android.app.Activity, int, int);
-    field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
-    field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
-    field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
-    field public static final String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
-    field public static final String ACTION_TECH_DISCOVERED = "android.nfc.action.TECH_DISCOVERED";
-    field @RequiresPermission(android.Manifest.permission.NFC_TRANSACTION_EVENT) public static final String ACTION_TRANSACTION_DETECTED = "android.nfc.action.TRANSACTION_DETECTED";
-    field public static final String EXTRA_ADAPTER_STATE = "android.nfc.extra.ADAPTER_STATE";
-    field public static final String EXTRA_AID = "android.nfc.extra.AID";
-    field public static final String EXTRA_DATA = "android.nfc.extra.DATA";
-    field public static final String EXTRA_ID = "android.nfc.extra.ID";
-    field public static final String EXTRA_NDEF_MESSAGES = "android.nfc.extra.NDEF_MESSAGES";
-    field public static final String EXTRA_PREFERRED_PAYMENT_CHANGED_REASON = "android.nfc.extra.PREFERRED_PAYMENT_CHANGED_REASON";
-    field public static final String EXTRA_READER_PRESENCE_CHECK_DELAY = "presence";
-    field public static final String EXTRA_SECURE_ELEMENT_NAME = "android.nfc.extra.SECURE_ELEMENT_NAME";
-    field public static final String EXTRA_TAG = "android.nfc.extra.TAG";
-    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_DISABLE = 0; // 0x0
-    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_KEEP = -1; // 0xffffffff
-    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_A = 1; // 0x1
-    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_B = 2; // 0x2
-    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_F = 4; // 0x4
-    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_DISABLE = 0; // 0x0
-    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_KEEP = -1; // 0xffffffff
-    field public static final int FLAG_READER_NFC_A = 1; // 0x1
-    field public static final int FLAG_READER_NFC_B = 2; // 0x2
-    field public static final int FLAG_READER_NFC_BARCODE = 16; // 0x10
-    field public static final int FLAG_READER_NFC_F = 4; // 0x4
-    field public static final int FLAG_READER_NFC_V = 8; // 0x8
-    field public static final int FLAG_READER_NO_PLATFORM_SOUNDS = 256; // 0x100
-    field public static final int FLAG_READER_SKIP_NDEF_CHECK = 128; // 0x80
-    field public static final int PREFERRED_PAYMENT_CHANGED = 2; // 0x2
-    field public static final int PREFERRED_PAYMENT_LOADED = 1; // 0x1
-    field public static final int PREFERRED_PAYMENT_UPDATED = 3; // 0x3
-    field public static final int STATE_OFF = 1; // 0x1
-    field public static final int STATE_ON = 3; // 0x3
-    field public static final int STATE_TURNING_OFF = 4; // 0x4
-    field public static final int STATE_TURNING_ON = 2; // 0x2
-  }
-
-  @Deprecated public static interface NfcAdapter.CreateBeamUrisCallback {
-    method @Deprecated public android.net.Uri[] createBeamUris(android.nfc.NfcEvent);
-  }
-
-  @Deprecated public static interface NfcAdapter.CreateNdefMessageCallback {
-    method @Deprecated public android.nfc.NdefMessage createNdefMessage(android.nfc.NfcEvent);
-  }
-
-  @Deprecated public static interface NfcAdapter.OnNdefPushCompleteCallback {
-    method @Deprecated public void onNdefPushComplete(android.nfc.NfcEvent);
-  }
-
-  public static interface NfcAdapter.OnTagRemovedListener {
-    method public void onTagRemoved();
-  }
-
-  public static interface NfcAdapter.ReaderCallback {
-    method public void onTagDiscovered(android.nfc.Tag);
-  }
-
-  public final class NfcAntennaInfo implements android.os.Parcelable {
-    ctor public NfcAntennaInfo(int, int, boolean, @NonNull java.util.List<android.nfc.AvailableNfcAntenna>);
-    method public int describeContents();
-    method @NonNull public java.util.List<android.nfc.AvailableNfcAntenna> getAvailableNfcAntennas();
-    method public int getDeviceHeight();
-    method public int getDeviceWidth();
-    method public boolean isDeviceFoldable();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NfcAntennaInfo> CREATOR;
-  }
-
-  public final class NfcEvent {
-    field public final android.nfc.NfcAdapter nfcAdapter;
-    field public final int peerLlcpMajorVersion;
-    field public final int peerLlcpMinorVersion;
-  }
-
-  public final class NfcManager {
-    method public android.nfc.NfcAdapter getDefaultAdapter();
-  }
-
-  public final class Tag implements android.os.Parcelable {
-    method public int describeContents();
-    method public byte[] getId();
-    method public String[] getTechList();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.Tag> CREATOR;
-  }
-
-  public class TagLostException extends java.io.IOException {
-    ctor public TagLostException();
-    ctor public TagLostException(String);
-  }
-
-  @FlaggedApi("android.nfc.enable_nfc_charging") public final class WlcLDeviceInfo implements android.os.Parcelable {
-    ctor public WlcLDeviceInfo(double, double, double, int);
-    method public int describeContents();
-    method public double getBatteryLevel();
-    method public double getProductId();
-    method public int getState();
-    method public double getTemperature();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field public static final int CONNECTED_CHARGING = 2; // 0x2
-    field public static final int CONNECTED_DISCHARGING = 3; // 0x3
-    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.WlcLDeviceInfo> CREATOR;
-    field public static final int DISCONNECTED = 1; // 0x1
-  }
-
-}
-
-package android.nfc.cardemulation {
-
-  public final class CardEmulation {
-    method public boolean categoryAllowsForegroundPreference(String);
-    method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public java.util.List<java.lang.String> getAidsForPreferredPaymentService();
-    method public java.util.List<java.lang.String> getAidsForService(android.content.ComponentName, String);
-    method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
-    method public static android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter);
-    method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
-    method public int getSelectionModeForCategory(String);
-    method public boolean isDefaultServiceForAid(android.content.ComponentName, String);
-    method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
-    method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
-    method public boolean removeAidsForService(android.content.ComponentName, String);
-    method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean setOffHostForService(@NonNull android.content.ComponentName, @NonNull String);
-    method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
-    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setServiceObserveModeDefault(@NonNull android.content.ComponentName, boolean);
-    method public boolean supportsAidPrefixRegistration();
-    method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName);
-    method public boolean unsetPreferredService(android.app.Activity);
-    field public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
-    field public static final String CATEGORY_OTHER = "other";
-    field public static final String CATEGORY_PAYMENT = "payment";
-    field public static final String EXTRA_CATEGORY = "category";
-    field public static final String EXTRA_SERVICE_COMPONENT = "component";
-    field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1
-    field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2
-    field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
-  }
-
-  public abstract class HostApduService extends android.app.Service {
-    ctor public HostApduService();
-    method public final void notifyUnhandled();
-    method public final android.os.IBinder onBind(android.content.Intent);
-    method public abstract void onDeactivated(int);
-    method public abstract byte[] processCommandApdu(byte[], android.os.Bundle);
-    method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.os.Bundle>);
-    method public final void sendResponseApdu(byte[]);
-    field public static final int DEACTIVATION_DESELECTED = 1; // 0x1
-    field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_A = 65; // 0x0041 'A'
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_B = 66; // 0x0042 'B'
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_F = 70; // 0x0046 'F'
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_OFF = 88; // 0x0058 'X'
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_ON = 79; // 0x004f 'O'
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x0055 'U'
-    field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_APDU_SERVICE";
-    field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_apdu_service";
-  }
-
-  public abstract class HostNfcFService extends android.app.Service {
-    ctor public HostNfcFService();
-    method public final android.os.IBinder onBind(android.content.Intent);
-    method public abstract void onDeactivated(int);
-    method public abstract byte[] processNfcFPacket(byte[], android.os.Bundle);
-    method public final void sendResponsePacket(byte[]);
-    field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
-    field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_NFCF_SERVICE";
-    field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_nfcf_service";
-  }
-
-  public final class NfcFCardEmulation {
-    method public boolean disableService(android.app.Activity) throws java.lang.RuntimeException;
-    method public boolean enableService(android.app.Activity, android.content.ComponentName) throws java.lang.RuntimeException;
-    method public static android.nfc.cardemulation.NfcFCardEmulation getInstance(android.nfc.NfcAdapter);
-    method public String getNfcid2ForService(android.content.ComponentName) throws java.lang.RuntimeException;
-    method public String getSystemCodeForService(android.content.ComponentName) throws java.lang.RuntimeException;
-    method public boolean registerSystemCodeForService(android.content.ComponentName, String) throws java.lang.RuntimeException;
-    method public boolean setNfcid2ForService(android.content.ComponentName, String) throws java.lang.RuntimeException;
-    method public boolean unregisterSystemCodeForService(android.content.ComponentName) throws java.lang.RuntimeException;
-  }
-
-  public abstract class OffHostApduService extends android.app.Service {
-    ctor public OffHostApduService();
-    field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE";
-    field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.off_host_apdu_service";
-  }
-
-}
-
-package android.nfc.tech {
-
-  public final class IsoDep implements android.nfc.tech.TagTechnology {
-    method public void close() throws java.io.IOException;
-    method public void connect() throws java.io.IOException;
-    method public static android.nfc.tech.IsoDep get(android.nfc.Tag);
-    method public byte[] getHiLayerResponse();
-    method public byte[] getHistoricalBytes();
-    method public int getMaxTransceiveLength();
-    method public android.nfc.Tag getTag();
-    method public int getTimeout();
-    method public boolean isConnected();
-    method public boolean isExtendedLengthApduSupported();
-    method public void setTimeout(int);
-    method public byte[] transceive(byte[]) throws java.io.IOException;
-  }
-
-  public final class MifareClassic implements android.nfc.tech.TagTechnology {
-    method public boolean authenticateSectorWithKeyA(int, byte[]) throws java.io.IOException;
-    method public boolean authenticateSectorWithKeyB(int, byte[]) throws java.io.IOException;
-    method public int blockToSector(int);
-    method public void close() throws java.io.IOException;
-    method public void connect() throws java.io.IOException;
-    method public void decrement(int, int) throws java.io.IOException;
-    method public static android.nfc.tech.MifareClassic get(android.nfc.Tag);
-    method public int getBlockCount();
-    method public int getBlockCountInSector(int);
-    method public int getMaxTransceiveLength();
-    method public int getSectorCount();
-    method public int getSize();
-    method public android.nfc.Tag getTag();
-    method public int getTimeout();
-    method public int getType();
-    method public void increment(int, int) throws java.io.IOException;
-    method public boolean isConnected();
-    method public byte[] readBlock(int) throws java.io.IOException;
-    method public void restore(int) throws java.io.IOException;
-    method public int sectorToBlock(int);
-    method public void setTimeout(int);
-    method public byte[] transceive(byte[]) throws java.io.IOException;
-    method public void transfer(int) throws java.io.IOException;
-    method public void writeBlock(int, byte[]) throws java.io.IOException;
-    field public static final int BLOCK_SIZE = 16; // 0x10
-    field public static final byte[] KEY_DEFAULT;
-    field public static final byte[] KEY_MIFARE_APPLICATION_DIRECTORY;
-    field public static final byte[] KEY_NFC_FORUM;
-    field public static final int SIZE_1K = 1024; // 0x400
-    field public static final int SIZE_2K = 2048; // 0x800
-    field public static final int SIZE_4K = 4096; // 0x1000
-    field public static final int SIZE_MINI = 320; // 0x140
-    field public static final int TYPE_CLASSIC = 0; // 0x0
-    field public static final int TYPE_PLUS = 1; // 0x1
-    field public static final int TYPE_PRO = 2; // 0x2
-    field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
-  }
-
-  public final class MifareUltralight implements android.nfc.tech.TagTechnology {
-    method public void close() throws java.io.IOException;
-    method public void connect() throws java.io.IOException;
-    method public static android.nfc.tech.MifareUltralight get(android.nfc.Tag);
-    method public int getMaxTransceiveLength();
-    method public android.nfc.Tag getTag();
-    method public int getTimeout();
-    method public int getType();
-    method public boolean isConnected();
-    method public byte[] readPages(int) throws java.io.IOException;
-    method public void setTimeout(int);
-    method public byte[] transceive(byte[]) throws java.io.IOException;
-    method public void writePage(int, byte[]) throws java.io.IOException;
-    field public static final int PAGE_SIZE = 4; // 0x4
-    field public static final int TYPE_ULTRALIGHT = 1; // 0x1
-    field public static final int TYPE_ULTRALIGHT_C = 2; // 0x2
-    field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
-  }
-
-  public final class Ndef implements android.nfc.tech.TagTechnology {
-    method public boolean canMakeReadOnly();
-    method public void close() throws java.io.IOException;
-    method public void connect() throws java.io.IOException;
-    method public static android.nfc.tech.Ndef get(android.nfc.Tag);
-    method public android.nfc.NdefMessage getCachedNdefMessage();
-    method public int getMaxSize();
-    method public android.nfc.NdefMessage getNdefMessage() throws android.nfc.FormatException, java.io.IOException;
-    method public android.nfc.Tag getTag();
-    method public String getType();
-    method public boolean isConnected();
-    method public boolean isWritable();
-    method public boolean makeReadOnly() throws java.io.IOException;
-    method public void writeNdefMessage(android.nfc.NdefMessage) throws android.nfc.FormatException, java.io.IOException;
-    field public static final String MIFARE_CLASSIC = "com.nxp.ndef.mifareclassic";
-    field public static final String NFC_FORUM_TYPE_1 = "org.nfcforum.ndef.type1";
-    field public static final String NFC_FORUM_TYPE_2 = "org.nfcforum.ndef.type2";
-    field public static final String NFC_FORUM_TYPE_3 = "org.nfcforum.ndef.type3";
-    field public static final String NFC_FORUM_TYPE_4 = "org.nfcforum.ndef.type4";
-  }
-
-  public final class NdefFormatable implements android.nfc.tech.TagTechnology {
-    method public void close() throws java.io.IOException;
-    method public void connect() throws java.io.IOException;
-    method public void format(android.nfc.NdefMessage) throws android.nfc.FormatException, java.io.IOException;
-    method public void formatReadOnly(android.nfc.NdefMessage) throws android.nfc.FormatException, java.io.IOException;
-    method public static android.nfc.tech.NdefFormatable get(android.nfc.Tag);
-    method public android.nfc.Tag getTag();
-    method public boolean isConnected();
-  }
-
-  public final class NfcA implements android.nfc.tech.TagTechnology {
-    method public void close() throws java.io.IOException;
-    method public void connect() throws java.io.IOException;
-    method public static android.nfc.tech.NfcA get(android.nfc.Tag);
-    method public byte[] getAtqa();
-    method public int getMaxTransceiveLength();
-    method public short getSak();
-    method public android.nfc.Tag getTag();
-    method public int getTimeout();
-    method public boolean isConnected();
-    method public void setTimeout(int);
-    method public byte[] transceive(byte[]) throws java.io.IOException;
-  }
-
-  public final class NfcB implements android.nfc.tech.TagTechnology {
-    method public void close() throws java.io.IOException;
-    method public void connect() throws java.io.IOException;
-    method public static android.nfc.tech.NfcB get(android.nfc.Tag);
-    method public byte[] getApplicationData();
-    method public int getMaxTransceiveLength();
-    method public byte[] getProtocolInfo();
-    method public android.nfc.Tag getTag();
-    method public boolean isConnected();
-    method public byte[] transceive(byte[]) throws java.io.IOException;
-  }
-
-  public final class NfcBarcode implements android.nfc.tech.TagTechnology {
-    method public void close() throws java.io.IOException;
-    method public void connect() throws java.io.IOException;
-    method public static android.nfc.tech.NfcBarcode get(android.nfc.Tag);
-    method public byte[] getBarcode();
-    method public android.nfc.Tag getTag();
-    method public int getType();
-    method public boolean isConnected();
-    field public static final int TYPE_KOVIO = 1; // 0x1
-    field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
-  }
-
-  public final class NfcF implements android.nfc.tech.TagTechnology {
-    method public void close() throws java.io.IOException;
-    method public void connect() throws java.io.IOException;
-    method public static android.nfc.tech.NfcF get(android.nfc.Tag);
-    method public byte[] getManufacturer();
-    method public int getMaxTransceiveLength();
-    method public byte[] getSystemCode();
-    method public android.nfc.Tag getTag();
-    method public int getTimeout();
-    method public boolean isConnected();
-    method public void setTimeout(int);
-    method public byte[] transceive(byte[]) throws java.io.IOException;
-  }
-
-  public final class NfcV implements android.nfc.tech.TagTechnology {
-    method public void close() throws java.io.IOException;
-    method public void connect() throws java.io.IOException;
-    method public static android.nfc.tech.NfcV get(android.nfc.Tag);
-    method public byte getDsfId();
-    method public int getMaxTransceiveLength();
-    method public byte getResponseFlags();
-    method public android.nfc.Tag getTag();
-    method public boolean isConnected();
-    method public byte[] transceive(byte[]) throws java.io.IOException;
-  }
-
-  public interface TagTechnology extends java.io.Closeable {
-    method public void connect() throws java.io.IOException;
-    method public android.nfc.Tag getTag();
-    method public boolean isConnected();
-  }
-
-}
-
 package android.opengl {
 
   public class EGL14 {
@@ -32252,6 +31981,7 @@
     method public long computeChargeTimeRemaining();
     method public int getIntProperty(int);
     method public long getLongProperty(int);
+    method @FlaggedApi("android.os.battery_part_status_api") @Nullable public String getStringProperty(int);
     method public boolean isCharging();
     field public static final String ACTION_CHARGING = "android.os.action.CHARGING";
     field public static final String ACTION_DISCHARGING = "android.os.action.DISCHARGING";
@@ -33399,7 +33129,7 @@
 
   @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public final class PowerMonitorReadings {
     method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public long getConsumedEnergy(@NonNull android.os.PowerMonitor);
-    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public long getTimestamp(@NonNull android.os.PowerMonitor);
+    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public long getTimestampMillis(@NonNull android.os.PowerMonitor);
     field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int ENERGY_UNAVAILABLE = -1; // 0xffffffff
   }
 
@@ -33409,6 +33139,7 @@
     method public static final long getElapsedCpuTime();
     method public static final int[] getExclusiveCores();
     method public static final int getGidForName(String);
+    method @FlaggedApi("com.android.sdksandbox.flags.sdk_sandbox_uid_to_app_uid_api") public static final int getSdkSandboxUidForAppUid(int);
     method public static long getStartElapsedRealtime();
     method public static long getStartRequestedElapsedRealtime();
     method public static long getStartRequestedUptimeMillis();
@@ -33783,6 +33514,7 @@
     field public static final String DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI = "no_sharing_admin_configured_wifi";
     field public static final String DISALLOW_SMS = "no_sms";
     field public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
+    field @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled") public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
     field public static final String DISALLOW_ULTRA_WIDEBAND_RADIO = "no_ultra_wideband_radio";
     field public static final String DISALLOW_UNIFIED_PASSWORD = "no_unified_password";
     field public static final String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
@@ -33910,7 +33642,6 @@
 
   @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public final class WorkDuration implements android.os.Parcelable {
     ctor public WorkDuration();
-    ctor public WorkDuration(long, long, long, long);
     method public int describeContents();
     method public long getActualCpuDurationNanos();
     method public long getActualGpuDurationNanos();
@@ -33993,8 +33724,8 @@
   }
 
   public class SystemHealthManager {
-    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getPowerMonitorReadings(@NonNull java.util.List<android.os.PowerMonitor>, @Nullable android.os.Handler, @NonNull java.util.function.Consumer<android.os.PowerMonitorReadings>, @NonNull java.util.function.Consumer<java.lang.RuntimeException>);
-    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getSupportedPowerMonitors(@Nullable android.os.Handler, @NonNull java.util.function.Consumer<java.util.List<android.os.PowerMonitor>>);
+    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getPowerMonitorReadings(@NonNull java.util.List<android.os.PowerMonitor>, @Nullable java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PowerMonitorReadings,java.lang.RuntimeException>);
+    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getSupportedPowerMonitors(@Nullable java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.os.PowerMonitor>>);
     method public android.os.health.HealthStats takeMyUidSnapshot();
     method public android.os.health.HealthStats takeUidSnapshot(int);
     method public android.os.health.HealthStats[] takeUidSnapshots(int[]);
@@ -35509,6 +35240,54 @@
     field public static final String LONGITUDE = "longitude";
   }
 
+  @FlaggedApi("android.provider.user_keys") public class ContactKeysManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public java.util.List<android.provider.ContactKeysManager.ContactKey> getAllContactKeys(@NonNull String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public java.util.List<android.provider.ContactKeysManager.SelfKey> getAllSelfKeys();
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public android.provider.ContactKeysManager.ContactKey getContactKey(@NonNull String, @NonNull String, @NonNull String);
+    method public static int getMaxKeySizeBytes();
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public java.util.List<android.provider.ContactKeysManager.ContactKey> getOwnerContactKeys(@NonNull String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public java.util.List<android.provider.ContactKeysManager.SelfKey> getOwnerSelfKeys();
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public android.provider.ContactKeysManager.SelfKey getSelfKey(@NonNull String, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean removeContactKey(@NonNull String, @NonNull String, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean removeSelfKey(@NonNull String, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateContactKeyLocalVerificationState(@NonNull String, @NonNull String, @NonNull String, int);
+    method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateContactKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, int);
+    method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public void updateOrInsertContactKey(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+    method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateOrInsertSelfKey(@NonNull String, @NonNull String, @NonNull byte[]);
+    method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateSelfKeyRemoteVerificationState(@NonNull String, @NonNull String, int);
+    field public static final int UNVERIFIED = 0; // 0x0
+    field public static final int VERIFICATION_FAILED = 1; // 0x1
+    field public static final int VERIFIED = 2; // 0x2
+  }
+
+  public static final class ContactKeysManager.ContactKey implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getAccountId();
+    method @Nullable public String getDeviceId();
+    method @Nullable public String getDisplayName();
+    method @Nullable public String getEmailAddress();
+    method @Nullable public byte[] getKeyValue();
+    method public int getLocalVerificationState();
+    method @NonNull public String getOwnerPackageName();
+    method @Nullable public String getPhoneNumber();
+    method public int getRemoteVerificationState();
+    method public long getTimeUpdated();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.provider.ContactKeysManager.ContactKey> CREATOR;
+  }
+
+  public static final class ContactKeysManager.SelfKey implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getAccountId();
+    method @Nullable public String getDeviceId();
+    method @Nullable public byte[] getKeyValue();
+    method @NonNull public String getOwnerPackageName();
+    method public int getRemoteVerificationState();
+    method public long getTimeUpdated();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.provider.ContactKeysManager.SelfKey> CREATOR;
+  }
+
   @Deprecated public class Contacts {
     field @Deprecated public static final String AUTHORITY = "contacts";
     field @Deprecated public static final android.net.Uri CONTENT_URI;
@@ -36977,6 +36756,7 @@
     field @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control") public static final String ACTION_REQUEST_MEDIA_ROUTING_CONTROL = "android.settings.REQUEST_MEDIA_ROUTING_CONTROL";
     field public static final String ACTION_REQUEST_SCHEDULE_EXACT_ALARM = "android.settings.REQUEST_SCHEDULE_EXACT_ALARM";
     field public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE = "android.settings.REQUEST_SET_AUTOFILL_SERVICE";
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String ACTION_SATELLITE_SETTING = "android.settings.SATELLITE_SETTING";
     field public static final String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS";
     field public static final String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS";
     field public static final String ACTION_SETTINGS = "android.settings.SETTINGS";
@@ -40919,7 +40699,6 @@
 
   public final class ZenPolicy implements android.os.Parcelable {
     method public int describeContents();
-    method @FlaggedApi("android.app.modes_api") public int getAllowedChannels();
     method public int getPriorityCallSenders();
     method public int getPriorityCategoryAlarms();
     method public int getPriorityCategoryCalls();
@@ -40930,6 +40709,7 @@
     method public int getPriorityCategoryReminders();
     method public int getPriorityCategoryRepeatCallers();
     method public int getPriorityCategorySystem();
+    method @FlaggedApi("android.app.modes_api") public int getPriorityChannelsAllowed();
     method public int getPriorityConversationSenders();
     method public int getPriorityMessageSenders();
     method public int getVisualEffectAmbient();
@@ -40940,9 +40720,6 @@
     method public int getVisualEffectPeek();
     method public int getVisualEffectStatusBar();
     method public void writeToParcel(android.os.Parcel, int);
-    field @FlaggedApi("android.app.modes_api") public static final int CHANNEL_TYPE_NONE = 2; // 0x2
-    field @FlaggedApi("android.app.modes_api") public static final int CHANNEL_TYPE_PRIORITY = 1; // 0x1
-    field @FlaggedApi("android.app.modes_api") public static final int CHANNEL_TYPE_UNSET = 0; // 0x0
     field public static final int CONVERSATION_SENDERS_ANYONE = 1; // 0x1
     field public static final int CONVERSATION_SENDERS_IMPORTANT = 2; // 0x2
     field public static final int CONVERSATION_SENDERS_NONE = 3; // 0x3
@@ -40963,11 +40740,11 @@
     method @NonNull public android.service.notification.ZenPolicy.Builder allowAlarms(boolean);
     method @NonNull public android.service.notification.ZenPolicy.Builder allowAllSounds();
     method @NonNull public android.service.notification.ZenPolicy.Builder allowCalls(int);
-    method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy.Builder allowChannels(int);
     method @NonNull public android.service.notification.ZenPolicy.Builder allowConversations(int);
     method @NonNull public android.service.notification.ZenPolicy.Builder allowEvents(boolean);
     method @NonNull public android.service.notification.ZenPolicy.Builder allowMedia(boolean);
     method @NonNull public android.service.notification.ZenPolicy.Builder allowMessages(int);
+    method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy.Builder allowPriorityChannels(boolean);
     method @NonNull public android.service.notification.ZenPolicy.Builder allowReminders(boolean);
     method @NonNull public android.service.notification.ZenPolicy.Builder allowRepeatCallers(boolean);
     method @NonNull public android.service.notification.ZenPolicy.Builder allowSystem(boolean);
@@ -41549,6 +41326,8 @@
     field public static final String EXTRA_LANGUAGE_MODEL = "android.speech.extra.LANGUAGE_MODEL";
     field public static final String EXTRA_LANGUAGE_PREFERENCE = "android.speech.extra.LANGUAGE_PREFERENCE";
     field public static final String EXTRA_LANGUAGE_SWITCH_ALLOWED_LANGUAGES = "android.speech.extra.LANGUAGE_SWITCH_ALLOWED_LANGUAGES";
+    field @FlaggedApi("android.speech.flags.multilang_extra_launch") public static final String EXTRA_LANGUAGE_SWITCH_INITIAL_ACTIVE_DURATION_TIME_MILLIS = "android.speech.extra.LANGUAGE_SWITCH_INITIAL_ACTIVE_DURATION_TIME_MILLIS";
+    field @FlaggedApi("android.speech.flags.multilang_extra_launch") public static final String EXTRA_LANGUAGE_SWITCH_MAX_SWITCHES = "android.speech.extra.LANGUAGE_SWITCH_MAX_SWITCHES";
     field public static final String EXTRA_MASK_OFFENSIVE_WORDS = "android.speech.extra.MASK_OFFENSIVE_WORDS";
     field public static final String EXTRA_MAX_RESULTS = "android.speech.extra.MAX_RESULTS";
     field public static final String EXTRA_ONLY_RETURN_LANGUAGE_PREFERENCE = "android.speech.extra.ONLY_RETURN_LANGUAGE_PREFERENCE";
@@ -42058,6 +41837,7 @@
     method public void disconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
     method @NonNull public android.os.ParcelUuid getCallId();
     method public void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
+    method @FlaggedApi("com.android.server.telecom.flags.set_mute_state") public void requestMuteState(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
     method public void sendEvent(@NonNull String, @NonNull android.os.Bundle);
     method public void setActive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
     method public void setInactive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
@@ -42616,9 +42396,11 @@
     method public android.graphics.drawable.Icon getIcon();
     method public CharSequence getLabel();
     method public CharSequence getShortDescription();
+    method @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") @NonNull public java.util.Set<android.telecom.PhoneAccountHandle> getSimultaneousCallingRestriction();
     method public android.net.Uri getSubscriptionAddress();
     method public java.util.List<java.lang.String> getSupportedUriSchemes();
     method public boolean hasCapabilities(int);
+    method @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") public boolean hasSimultaneousCallingRestriction();
     method public boolean isEnabled();
     method public boolean supportsUriScheme(String);
     method public android.telecom.PhoneAccount.Builder toBuilder();
@@ -42659,12 +42441,14 @@
     ctor public PhoneAccount.Builder(android.telecom.PhoneAccount);
     method public android.telecom.PhoneAccount.Builder addSupportedUriScheme(String);
     method public android.telecom.PhoneAccount build();
+    method @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") @NonNull public android.telecom.PhoneAccount.Builder clearSimultaneousCallingRestriction();
     method public android.telecom.PhoneAccount.Builder setAddress(android.net.Uri);
     method public android.telecom.PhoneAccount.Builder setCapabilities(int);
     method public android.telecom.PhoneAccount.Builder setExtras(android.os.Bundle);
     method public android.telecom.PhoneAccount.Builder setHighlightColor(int);
     method public android.telecom.PhoneAccount.Builder setIcon(android.graphics.drawable.Icon);
     method public android.telecom.PhoneAccount.Builder setShortDescription(CharSequence);
+    method @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") @NonNull public android.telecom.PhoneAccount.Builder setSimultaneousCallingRestriction(@NonNull java.util.Set<android.telecom.PhoneAccountHandle>);
     method public android.telecom.PhoneAccount.Builder setSubscriptionAddress(android.net.Uri);
     method public android.telecom.PhoneAccount.Builder setSupportedUriSchemes(java.util.List<java.lang.String>);
   }
@@ -43479,6 +43263,8 @@
     field public static final String KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL = "rtt_upgrade_supported_for_downgraded_vt_call";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL = "satellite_attach_supported_bool";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT = "satellite_connection_hysteresis_sec_int";
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT = "satellite_entitlement_status_refresh_days_int";
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL = "satellite_entitlement_supported_bool";
     field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool";
     field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool";
     field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
@@ -45340,7 +45126,7 @@
     method public void addOnSubscriptionsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void addSubscriptionsIntoGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid);
     method public boolean canManageSubscription(android.telephony.SubscriptionInfo);
-    method @FlaggedApi("com.android.internal.telephony.flags.work_profile_api_split") @NonNull public android.telephony.SubscriptionManager createForAllUserProfiles();
+    method @FlaggedApi("com.android.internal.telephony.flags.enforce_subscription_user_filter") @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES) public android.telephony.SubscriptionManager createForAllUserProfiles();
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.os.ParcelUuid createSubscriptionGroup(@NonNull java.util.List<java.lang.Integer>);
     method @Deprecated public static android.telephony.SubscriptionManager from(android.content.Context);
     method public java.util.List<android.telephony.SubscriptionInfo> getAccessibleSubscriptionInfoList();
@@ -47292,6 +47078,7 @@
     method public int getLineForOffset(int);
     method public int getLineForVertical(int);
     method public float getLineLeft(int);
+    method @FlaggedApi("com.android.text.flags.inter_character_justification") @IntRange(from=0) public int getLineLetterSpacingUnitCount(@IntRange(from=0) int, boolean);
     method public float getLineMax(int);
     method public float getLineRight(int);
     method @FlaggedApi("com.android.text.flags.use_bounds_for_width") public final float getLineSpacingAmount();
@@ -47342,6 +47129,7 @@
     field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_ANY_OVERLAP;
     field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_ALL;
     field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_CENTER;
+    field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
     field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1
     field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0
   }
@@ -51798,6 +51586,7 @@
   public static class SurfaceControl.Transaction implements java.io.Closeable android.os.Parcelable {
     ctor public SurfaceControl.Transaction();
     method @NonNull public android.view.SurfaceControl.Transaction addTransactionCommittedListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.SurfaceControl.TransactionCommittedListener);
+    method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.view.SurfaceControl.Transaction addTransactionCompletedListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.SurfaceControl.TransactionStats>);
     method public void apply();
     method @NonNull public android.view.SurfaceControl.Transaction clearFrameRate(@NonNull android.view.SurfaceControl);
     method @NonNull public android.view.SurfaceControl.Transaction clearTrustedPresentationCallback(@NonNull android.view.SurfaceControl);
@@ -51814,9 +51603,11 @@
     method @NonNull public android.view.SurfaceControl.Transaction setCrop(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect);
     method @NonNull public android.view.SurfaceControl.Transaction setDamageRegion(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Region);
     method @NonNull public android.view.SurfaceControl.Transaction setDataSpace(@NonNull android.view.SurfaceControl, int);
+    method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.view.SurfaceControl.Transaction setDesiredPresentTimeNanos(long);
     method @NonNull public android.view.SurfaceControl.Transaction setExtendedRangeBrightness(@NonNull android.view.SurfaceControl, float, float);
     method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int);
     method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int, int);
+    method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.view.SurfaceControl.Transaction setFrameTimeline(long);
     method @Deprecated @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int);
     method @NonNull public android.view.SurfaceControl.Transaction setLayer(@NonNull android.view.SurfaceControl, @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int);
     method @NonNull public android.view.SurfaceControl.Transaction setOpaque(@NonNull android.view.SurfaceControl, boolean);
@@ -51832,10 +51623,19 @@
     method public void onTransactionCommitted();
   }
 
+  @FlaggedApi("com.android.window.flags.sdk_desired_present_time") public static final class SurfaceControl.TransactionStats {
+    method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") public long getLatchTimeNanos();
+    method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.hardware.SyncFence getPresentFence();
+  }
+
   public static final class SurfaceControl.TrustedPresentationThresholds {
     ctor public SurfaceControl.TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int);
   }
 
+  @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public interface SurfaceControlInputReceiver {
+    method public boolean onInputEvent(@NonNull android.view.InputEvent);
+  }
+
   public class SurfaceControlViewHost {
     ctor public SurfaceControlViewHost(@NonNull android.content.Context, @NonNull android.view.Display, @Nullable android.os.IBinder);
     method @Nullable public android.view.SurfaceControlViewHost.SurfacePackage getSurfacePackage();
@@ -52798,9 +52598,9 @@
     field protected static final int[] PRESSED_STATE_SET;
     field protected static final int[] PRESSED_WINDOW_FOCUSED_STATE_SET;
     field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = 0.0f;
-    field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -120.0f;
-    field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_LOW = -30.0f;
-    field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_NORMAL = -60.0f;
+    field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -4.0f;
+    field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_LOW = -2.0f;
+    field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_NORMAL = -3.0f;
     field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE = -1.0f;
     field public static final android.util.Property<android.view.View,java.lang.Float> ROTATION;
     field public static final android.util.Property<android.view.View,java.lang.Float> ROTATION_X;
@@ -53959,10 +53759,13 @@
     method @Deprecated public android.view.Display getDefaultDisplay();
     method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics();
     method public default boolean isCrossWindowBlurEnabled();
+    method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerBatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.view.Choreographer, @NonNull android.view.SurfaceControlInputReceiver);
     method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void registerTrustedPresentationListener(@NonNull android.os.IBinder, @NonNull android.window.TrustedPresentationThresholds, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver);
     method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer);
     method public void removeViewImmediate(android.view.View);
+    method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void unregisterSurfaceControlInputReceiver(@NonNull android.view.SurfaceControl);
     method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
     field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
@@ -53975,6 +53778,8 @@
     field public static final String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
     field @FlaggedApi("com.android.window.flags.app_compat_properties_api") public static final String PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES = "android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
     field public static final String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS = "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
+    field @FlaggedApi("com.android.window.flags.app_compat_properties_api") public static final String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE";
+    field @FlaggedApi("com.android.window.flags.app_compat_properties_api") public static final String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE";
     field public static final String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
     field public static final String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
     field @FlaggedApi("com.android.window.flags.supports_multi_instance_system_ui") public static final String PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI = "android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI";
@@ -56489,6 +56294,7 @@
     field public static final String TYPE_EMAIL = "email";
     field public static final String TYPE_FLIGHT_NUMBER = "flight";
     field public static final String TYPE_OTHER = "other";
+    field @FlaggedApi("android.service.notification.redact_sensitive_notifications_from_untrusted_listeners") public static final String TYPE_OTP_CODE = "otp_code";
     field public static final String TYPE_PHONE = "phone";
     field public static final String TYPE_UNKNOWN = "";
     field public static final String TYPE_URL = "url";
@@ -57448,7 +57254,7 @@
     method public abstract boolean getBuiltInZoomControls();
     method public abstract int getCacheMode();
     method public abstract String getCursiveFontFamily();
-    method @Deprecated public abstract boolean getDatabaseEnabled();
+    method public abstract boolean getDatabaseEnabled();
     method @Deprecated public abstract String getDatabasePath();
     method public abstract int getDefaultFixedFontSize();
     method public abstract int getDefaultFontSize();
@@ -57494,7 +57300,7 @@
     method public abstract void setBuiltInZoomControls(boolean);
     method public abstract void setCacheMode(int);
     method public abstract void setCursiveFontFamily(String);
-    method @Deprecated public abstract void setDatabaseEnabled(boolean);
+    method public abstract void setDatabaseEnabled(boolean);
     method @Deprecated public abstract void setDatabasePath(String);
     method public abstract void setDefaultFixedFontSize(int);
     method public abstract void setDefaultFontSize(int);
@@ -59625,6 +59431,7 @@
     ctor public RemoteViews(@NonNull java.util.Map<android.util.SizeF,android.widget.RemoteViews>);
     ctor public RemoteViews(android.widget.RemoteViews);
     ctor public RemoteViews(android.os.Parcel);
+    ctor @FlaggedApi("android.appwidget.flags.draw_data_parcel") public RemoteViews(@NonNull android.widget.RemoteViews.DrawInstructions);
     method public void addStableView(@IdRes int, @NonNull android.widget.RemoteViews, int);
     method public void addView(@IdRes int, android.widget.RemoteViews);
     method public android.view.View apply(android.content.Context, android.view.ViewGroup);
@@ -59733,6 +59540,15 @@
     ctor public RemoteViews.ActionException(String);
   }
 
+  @FlaggedApi("android.appwidget.flags.draw_data_parcel") public static final class RemoteViews.DrawInstructions {
+    method @FlaggedApi("android.appwidget.flags.draw_data_parcel") public void appendInstructions(@NonNull byte[]);
+  }
+
+  @FlaggedApi("android.appwidget.flags.draw_data_parcel") public static final class RemoteViews.DrawInstructions.Builder {
+    ctor @FlaggedApi("android.appwidget.flags.draw_data_parcel") public RemoteViews.DrawInstructions.Builder(@NonNull java.util.List<byte[]>);
+    method @FlaggedApi("android.appwidget.flags.draw_data_parcel") @NonNull public android.widget.RemoteViews.DrawInstructions build();
+  }
+
   public static final class RemoteViews.RemoteCollectionItems implements android.os.Parcelable {
     method public int describeContents();
     method public int getItemCount();
diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt
index f331e7f..162f54c 100644
--- a/core/api/lint-baseline.txt
+++ b/core/api/lint-baseline.txt
@@ -181,12 +181,6 @@
     Field 'ACTION_WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED' is missing @BroadcastBehavior
 BroadcastBehavior: android.net.Proxy#PROXY_CHANGE_ACTION:
     Field 'PROXY_CHANGE_ACTION' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_ADAPTER_STATE_CHANGED:
-    Field 'ACTION_ADAPTER_STATE_CHANGED' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_PREFERRED_PAYMENT_CHANGED:
-    Field 'ACTION_PREFERRED_PAYMENT_CHANGED' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED:
-    Field 'ACTION_TRANSACTION_DETECTED' is missing @BroadcastBehavior
 BroadcastBehavior: android.os.DropBoxManager#ACTION_DROPBOX_ENTRY_ADDED:
     Field 'ACTION_DROPBOX_ENTRY_ADDED' is missing @BroadcastBehavior
 BroadcastBehavior: android.provider.CalendarContract#ACTION_EVENT_REMINDER:
@@ -715,86 +709,6 @@
     Method 'setSpeakerMode' documentation mentions permissions without declaring @RequiresPermission
 RequiresPermission: android.net.sip.SipAudioCall#startAudio():
     Method 'startAudio' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.NfcAdapter#disableForegroundDispatch(android.app.Activity):
-    Method 'disableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.NfcAdapter#enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]):
-    Method 'enableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForAid(android.content.ComponentName, String):
-    Method 'isDefaultServiceForAid' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String):
-    Method 'isDefaultServiceForCategory' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.cardemulation.CardEmulation#setOffHostForService(android.content.ComponentName, String):
-    Method 'setOffHostForService' documentation mentions permissions already declared by @RequiresPermission
-RequiresPermission: android.nfc.tech.IsoDep#getTimeout():
-    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.IsoDep#setTimeout(int):
-    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.IsoDep#transceive(byte[]):
-    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyA(int, byte[]):
-    Method 'authenticateSectorWithKeyA' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyB(int, byte[]):
-    Method 'authenticateSectorWithKeyB' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#decrement(int, int):
-    Method 'decrement' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#getTimeout():
-    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#increment(int, int):
-    Method 'increment' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#readBlock(int):
-    Method 'readBlock' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#restore(int):
-    Method 'restore' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#setTimeout(int):
-    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#transceive(byte[]):
-    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#transfer(int):
-    Method 'transfer' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#writeBlock(int, byte[]):
-    Method 'writeBlock' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#getTimeout():
-    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#readPages(int):
-    Method 'readPages' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#setTimeout(int):
-    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#transceive(byte[]):
-    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#writePage(int, byte[]):
-    Method 'writePage' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#getNdefMessage():
-    Method 'getNdefMessage' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#isWritable():
-    Method 'isWritable' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#makeReadOnly():
-    Method 'makeReadOnly' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#writeNdefMessage(android.nfc.NdefMessage):
-    Method 'writeNdefMessage' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NdefFormatable#format(android.nfc.NdefMessage):
-    Method 'format' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NdefFormatable#formatReadOnly(android.nfc.NdefMessage):
-    Method 'formatReadOnly' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcA#getTimeout():
-    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcA#setTimeout(int):
-    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcA#transceive(byte[]):
-    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcB#transceive(byte[]):
-    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcF#getTimeout():
-    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcF#setTimeout(int):
-    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcF#transceive(byte[]):
-    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcV#transceive(byte[]):
-    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.TagTechnology#close():
-    Method 'close' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.TagTechnology#connect():
-    Method 'connect' documentation mentions permissions without declaring @RequiresPermission
 RequiresPermission: android.os.BugreportManager#cancelBugreport():
     Method 'cancelBugreport' documentation mentions permissions without declaring @RequiresPermission
 RequiresPermission: android.os.Build#getSerial():
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index c1b9f64..24b9233 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -319,11 +319,6 @@
 
 package android.nfc {
 
-  public class NfcFrameworkInitializer {
-    method public static void registerServiceWrappers();
-    method public static void setNfcServiceManager(@NonNull android.nfc.NfcServiceManager);
-  }
-
   public class NfcServiceManager {
     method @NonNull public android.nfc.NfcServiceManager.ServiceRegisterer getNfcManagerServiceRegisterer();
   }
diff --git a/core/api/module-lib-lint-baseline.txt b/core/api/module-lib-lint-baseline.txt
index a6a948c..a2179bc 100644
--- a/core/api/module-lib-lint-baseline.txt
+++ b/core/api/module-lib-lint-baseline.txt
@@ -235,14 +235,6 @@
     Field 'ACTION_SCORE_NETWORKS' is missing @BroadcastBehavior
 BroadcastBehavior: android.net.Proxy#PROXY_CHANGE_ACTION:
     Field 'PROXY_CHANGE_ACTION' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_ADAPTER_STATE_CHANGED:
-    Field 'ACTION_ADAPTER_STATE_CHANGED' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_PREFERRED_PAYMENT_CHANGED:
-    Field 'ACTION_PREFERRED_PAYMENT_CHANGED' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_REQUIRE_UNLOCK_FOR_NFC:
-    Field 'ACTION_REQUIRE_UNLOCK_FOR_NFC' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED:
-    Field 'ACTION_TRANSACTION_DETECTED' is missing @BroadcastBehavior
 BroadcastBehavior: android.os.DropBoxManager#ACTION_DROPBOX_ENTRY_ADDED:
     Field 'ACTION_DROPBOX_ENTRY_ADDED' is missing @BroadcastBehavior
 BroadcastBehavior: android.provider.CalendarContract#ACTION_EVENT_REMINDER:
@@ -1009,86 +1001,6 @@
     Method 'applyVcnNetworkPolicy' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.net.vcn.VcnManager#removeVcnNetworkPolicyChangeListener(android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener):
     Method 'removeVcnNetworkPolicyChangeListener' documentation mentions permissions already declared by @RequiresPermission
-RequiresPermission: android.nfc.NfcAdapter#disableForegroundDispatch(android.app.Activity):
-    Method 'disableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.NfcAdapter#enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]):
-    Method 'enableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForAid(android.content.ComponentName, String):
-    Method 'isDefaultServiceForAid' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String):
-    Method 'isDefaultServiceForCategory' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.cardemulation.CardEmulation#setOffHostForService(android.content.ComponentName, String):
-    Method 'setOffHostForService' documentation mentions permissions already declared by @RequiresPermission
-RequiresPermission: android.nfc.tech.IsoDep#getTimeout():
-    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.IsoDep#setTimeout(int):
-    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.IsoDep#transceive(byte[]):
-    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyA(int, byte[]):
-    Method 'authenticateSectorWithKeyA' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyB(int, byte[]):
-    Method 'authenticateSectorWithKeyB' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#decrement(int, int):
-    Method 'decrement' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#getTimeout():
-    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#increment(int, int):
-    Method 'increment' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#readBlock(int):
-    Method 'readBlock' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#restore(int):
-    Method 'restore' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#setTimeout(int):
-    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#transceive(byte[]):
-    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#transfer(int):
-    Method 'transfer' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#writeBlock(int, byte[]):
-    Method 'writeBlock' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#getTimeout():
-    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#readPages(int):
-    Method 'readPages' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#setTimeout(int):
-    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#transceive(byte[]):
-    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#writePage(int, byte[]):
-    Method 'writePage' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#getNdefMessage():
-    Method 'getNdefMessage' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#isWritable():
-    Method 'isWritable' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#makeReadOnly():
-    Method 'makeReadOnly' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#writeNdefMessage(android.nfc.NdefMessage):
-    Method 'writeNdefMessage' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NdefFormatable#format(android.nfc.NdefMessage):
-    Method 'format' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NdefFormatable#formatReadOnly(android.nfc.NdefMessage):
-    Method 'formatReadOnly' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcA#getTimeout():
-    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcA#setTimeout(int):
-    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcA#transceive(byte[]):
-    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcB#transceive(byte[]):
-    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcF#getTimeout():
-    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcF#setTimeout(int):
-    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcF#transceive(byte[]):
-    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcV#transceive(byte[]):
-    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.TagTechnology#close():
-    Method 'close' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.TagTechnology#connect():
-    Method 'connect' documentation mentions permissions without declaring @RequiresPermission
 RequiresPermission: android.os.BugreportManager#cancelBugreport():
     Method 'cancelBugreport' documentation mentions permissions without declaring @RequiresPermission
 RequiresPermission: android.os.BugreportManager#preDumpUiData():
@@ -1769,8 +1681,6 @@
     Field 'ACTION_USB_PORT_COMPLIANCE_CHANGED' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
 SdkConstant: android.hardware.usb.UsbManager#ACTION_USB_STATE:
     Field 'ACTION_USB_STATE' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-SdkConstant: android.nfc.NfcAdapter#ACTION_REQUIRE_UNLOCK_FOR_NFC:
-    Field 'ACTION_REQUIRE_UNLOCK_FOR_NFC' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
 SdkConstant: android.service.euicc.EuiccService#ACTION_DELETE_SUBSCRIPTION_PRIVILEGED:
     Field 'ACTION_DELETE_SUBSCRIPTION_PRIVILEGED' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
 SdkConstant: android.service.euicc.EuiccService#ACTION_RENAME_SUBSCRIPTION_PRIVILEGED:
diff --git a/core/api/removed.txt b/core/api/removed.txt
index b58c822..3c7c0d6 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -190,22 +190,6 @@
 
 }
 
-package android.nfc {
-
-  public final class NfcAdapter {
-    method @Deprecated public void disableForegroundNdefPush(android.app.Activity);
-    method @Deprecated public void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage);
-    method @Deprecated public boolean invokeBeam(android.app.Activity);
-    method @Deprecated public boolean isNdefPushEnabled();
-    method @Deprecated public void setBeamPushUris(android.net.Uri[], android.app.Activity);
-    method @Deprecated public void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity);
-    method @Deprecated public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
-    method @Deprecated public void setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...);
-    method @Deprecated public void setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...);
-  }
-
-}
-
 package android.os {
 
   public class BatteryManager {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 9077d02..94cde98 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -12,6 +12,7 @@
     field @Deprecated public static final String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO";
     field public static final String ACCESS_FPS_COUNTER = "android.permission.ACCESS_FPS_COUNTER";
     field public static final String ACCESS_INSTANT_APPS = "android.permission.ACCESS_INSTANT_APPS";
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ACCESS_LAST_KNOWN_CELL_ID = "android.permission.ACCESS_LAST_KNOWN_CELL_ID";
     field public static final String ACCESS_LOCUS_ID_USAGE_STATS = "android.permission.ACCESS_LOCUS_ID_USAGE_STATS";
     field public static final String ACCESS_MOCK_LOCATION = "android.permission.ACCESS_MOCK_LOCATION";
     field public static final String ACCESS_MTP = "android.permission.ACCESS_MTP";
@@ -21,7 +22,7 @@
     field public static final String ACCESS_RCS_USER_CAPABILITY_EXCHANGE = "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE";
     field public static final String ACCESS_SHARED_LIBRARIES = "android.permission.ACCESS_SHARED_LIBRARIES";
     field public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS";
-    field public static final String ACCESS_SMARTSPACE = "android.permission.ACCESS_SMARTSPACE";
+    field @FlaggedApi("android.app.smartspace.flags.access_smartspace") public static final String ACCESS_SMARTSPACE = "android.permission.ACCESS_SMARTSPACE";
     field public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER";
     field public static final String ACCESS_TUNED_INFO = "android.permission.ACCESS_TUNED_INFO";
     field public static final String ACCESS_TV_DESCRAMBLER = "android.permission.ACCESS_TV_DESCRAMBLER";
@@ -55,6 +56,7 @@
     field public static final String BIND_CONTENT_SUGGESTIONS_SERVICE = "android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE";
     field public static final String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH";
     field public static final String BIND_DISPLAY_HASHING_SERVICE = "android.permission.BIND_DISPLAY_HASHING_SERVICE";
+    field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String BIND_DOMAIN_SELECTION_SERVICE = "android.permission.BIND_DOMAIN_SELECTION_SERVICE";
     field public static final String BIND_DOMAIN_VERIFICATION_AGENT = "android.permission.BIND_DOMAIN_VERIFICATION_AGENT";
     field public static final String BIND_EUICC_SERVICE = "android.permission.BIND_EUICC_SERVICE";
     field public static final String BIND_EXTERNAL_STORAGE_SERVICE = "android.permission.BIND_EXTERNAL_STORAGE_SERVICE";
@@ -127,12 +129,15 @@
     field public static final String DISABLE_SYSTEM_SOUND_EFFECTS = "android.permission.DISABLE_SYSTEM_SOUND_EFFECTS";
     field public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE";
     field public static final String DOMAIN_VERIFICATION_AGENT = "android.permission.DOMAIN_VERIFICATION_AGENT";
+    field @FlaggedApi("com.android.window.flags.untrusted_embedding_any_app_permission") public static final String EMBED_ANY_APP_IN_UNTRUSTED_MODE = "android.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE";
+    field @FlaggedApi("android.content.pm.emergency_install_permission") public static final String EMERGENCY_INSTALL_PACKAGES = "android.permission.EMERGENCY_INSTALL_PACKAGES";
     field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED";
     field public static final String EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS = "android.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS";
     field public static final String FORCE_BACK = "android.permission.FORCE_BACK";
     field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
     field public static final String GET_APP_METADATA = "android.permission.GET_APP_METADATA";
     field public static final String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS";
+    field @FlaggedApi("android.app.bic_client") public static final String GET_BACKGROUND_INSTALLED_PACKAGES = "android.permission.GET_BACKGROUND_INSTALLED_PACKAGES";
     field @FlaggedApi("android.app.get_binding_uid_importance") public static final String GET_BINDING_UID_IMPORTANCE = "android.permission.GET_BINDING_UID_IMPORTANCE";
     field public static final String GET_HISTORICAL_APP_OPS_STATS = "android.permission.GET_HISTORICAL_APP_OPS_STATS";
     field public static final String GET_PROCESS_STATE_AND_OOM_SCORE = "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE";
@@ -187,6 +192,7 @@
     field public static final String MANAGE_DEFAULT_APPLICATIONS = "android.permission.MANAGE_DEFAULT_APPLICATIONS";
     field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
     field public static final String MANAGE_DEVICE_POLICY_APP_EXEMPTIONS = "android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS";
+    field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String MANAGE_ENHANCED_CONFIRMATION_STATES = "android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES";
     field public static final String MANAGE_ETHERNET_NETWORKS = "android.permission.MANAGE_ETHERNET_NETWORKS";
     field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION";
     field public static final String MANAGE_GAME_ACTIVITY = "android.permission.MANAGE_GAME_ACTIVITY";
@@ -294,6 +300,7 @@
     field public static final String READ_RUNTIME_PROFILES = "android.permission.READ_RUNTIME_PROFILES";
     field public static final String READ_SAFETY_CENTER_STATUS = "android.permission.READ_SAFETY_CENTER_STATUS";
     field public static final String READ_SEARCH_INDEXABLES = "android.permission.READ_SEARCH_INDEXABLES";
+    field @FlaggedApi("android.app.system_terms_of_address_enabled") public static final String READ_SYSTEM_GRAMMATICAL_GENDER = "android.permission.READ_SYSTEM_GRAMMATICAL_GENDER";
     field public static final String READ_SYSTEM_UPDATE_INFO = "android.permission.READ_SYSTEM_UPDATE_INFO";
     field public static final String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL";
     field public static final String READ_WIFI_CREDENTIAL = "android.permission.READ_WIFI_CREDENTIAL";
@@ -389,6 +396,7 @@
     field @Deprecated public static final String UPDATE_TIME_ZONE_RULES = "android.permission.UPDATE_TIME_ZONE_RULES";
     field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
     field public static final String USER_ACTIVITY = "android.permission.USER_ACTIVITY";
+    field @FlaggedApi("android.hardware.biometrics.face_background_authentication") public static final String USE_BACKGROUND_FACE_AUTHENTICATION = "android.permission.USE_BACKGROUND_FACE_AUTHENTICATION";
     field public static final String USE_COLORIZED_NOTIFICATIONS = "android.permission.USE_COLORIZED_NOTIFICATIONS";
     field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
     field public static final String UWB_PRIVILEGED = "android.permission.UWB_PRIVILEGED";
@@ -419,6 +427,7 @@
 
   public static final class R.attr {
     field public static final int allowClearUserDataOnFailedRestore = 16844288; // 0x1010600
+    field @FlaggedApi("android.content.res.manifest_flagging") public static final int featureFlag;
     field public static final int gameSessionService = 16844373; // 0x1010655
     field public static final int hotwordDetectionService = 16844326; // 0x1010626
     field @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") public static final int isVirtualDeviceOnly;
@@ -475,6 +484,7 @@
     field public static final int config_defaultNotes = 17039429; // 0x1040045
     field @FlaggedApi("android.permission.flags.retail_demo_role_enabled") public static final int config_defaultRetailDemo;
     field public static final int config_defaultSms = 17039396; // 0x1040024
+    field @FlaggedApi("android.permission.flags.wallet_role_enabled") public static final int config_defaultWallet;
     field public static final int config_devicePolicyManagement = 17039421; // 0x104003d
     field public static final int config_feedbackIntentExtraKey = 17039391; // 0x104001f
     field public static final int config_feedbackIntentNameKey = 17039392; // 0x1040020
@@ -551,6 +561,7 @@
 
   public class ActivityManager {
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void addOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener, int);
+    method @FlaggedApi("android.app.uid_importance_listener_for_uids") @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void addOnUidImportanceListener(@NonNull android.app.ActivityManager.OnUidImportanceListener, int, @Nullable int[]);
     method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public void forceStopPackage(String);
     method @FlaggedApi("android.app.get_binding_uid_importance") @RequiresPermission(android.Manifest.permission.GET_BINDING_UID_IMPORTANCE) public int getBindingUidImportance(int);
     method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.INTERACT_ACROSS_USERS_FULL"}) public static int getCurrentUser();
@@ -621,6 +632,7 @@
     field public static final String OPSTR_ACCEPT_HANDOVER = "android:accept_handover";
     field public static final String OPSTR_ACCESS_ACCESSIBILITY = "android:access_accessibility";
     field public static final String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications";
+    field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String OPSTR_ACCESS_RESTRICTED_SETTINGS = "android:access_restricted_settings";
     field public static final String OPSTR_ACTIVATE_PLATFORM_VPN = "android:activate_platform_vpn";
     field public static final String OPSTR_ACTIVATE_VPN = "android:activate_vpn";
     field public static final String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot";
@@ -861,6 +873,10 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.PackageOps> CREATOR;
   }
 
+  @FlaggedApi("android.app.bic_client") public final class BackgroundInstallControlManager {
+    method @FlaggedApi("android.app.bic_client") @NonNull @RequiresPermission(android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES) public java.util.List<android.content.pm.PackageInfo> getBackgroundInstalledPackages(long);
+  }
+
   public class BroadcastOptions {
     method public void clearRequireCompatChange();
     method public int getPendingIntentBackgroundActivityStartMode();
@@ -1595,12 +1611,14 @@
     method public int getDensityLevel();
     method @NonNull public java.time.Instant getEndTime();
     method public int getEventType();
+    method @FlaggedApi("android.app.ambient_heart_rate") @IntRange(from=0xffffffff) public int getRatePerMinute();
     method @NonNull public java.time.Instant getStartTime();
     method @NonNull public android.os.PersistableBundle getVendorData();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.ambientcontext.AmbientContextEvent> CREATOR;
     field public static final int EVENT_BACK_DOUBLE_TAP = 3; // 0x3
     field public static final int EVENT_COUGH = 1; // 0x1
+    field @FlaggedApi("android.app.ambient_heart_rate") public static final int EVENT_HEART_RATE = 4; // 0x4
     field public static final int EVENT_SNORE = 2; // 0x2
     field public static final int EVENT_UNKNOWN = 0; // 0x0
     field public static final int EVENT_VENDOR_WEARABLE_START = 100000; // 0x186a0
@@ -1611,6 +1629,7 @@
     field public static final int LEVEL_MEDIUM_HIGH = 4; // 0x4
     field public static final int LEVEL_MEDIUM_LOW = 2; // 0x2
     field public static final int LEVEL_UNKNOWN = 0; // 0x0
+    field @FlaggedApi("android.app.ambient_heart_rate") public static final int RATE_PER_MINUTE_UNKNOWN = -1; // 0xffffffff
   }
 
   public static final class AmbientContextEvent.Builder {
@@ -1620,6 +1639,7 @@
     method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setDensityLevel(int);
     method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setEndTime(@NonNull java.time.Instant);
     method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setEventType(int);
+    method @FlaggedApi("android.app.ambient_heart_rate") @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setRatePerMinute(@IntRange(from=0xffffffff) int);
     method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setStartTime(@NonNull java.time.Instant);
     method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setVendorData(@NonNull android.os.PersistableBundle);
   }
@@ -3190,6 +3210,7 @@
 
   public final class VirtualDeviceManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.VirtualDeviceManager.VirtualDevice createVirtualDevice(int, @NonNull android.companion.virtual.VirtualDeviceParams);
+    method @FlaggedApi("android.companion.virtual.flags.persistent_device_id_api") @Nullable public CharSequence getDisplayNameForPersistentDeviceId(@NonNull String);
     field public static final int LAUNCH_FAILURE_NO_ACTIVITY = 2; // 0x2
     field public static final int LAUNCH_FAILURE_PENDING_INTENT_CANCELED = 1; // 0x1
     field public static final int LAUNCH_SUCCESS = 0; // 0x0
@@ -3217,6 +3238,7 @@
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
     method @NonNull public android.content.Context createContext();
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.audio.VirtualAudioDevice createVirtualAudioDevice(@NonNull android.hardware.display.VirtualDisplay, @Nullable java.util.concurrent.Executor, @Nullable android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback);
+    method @FlaggedApi("android.companion.virtual.flags.virtual_camera") @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.camera.VirtualCamera createVirtualCamera(@NonNull android.companion.virtual.camera.VirtualCameraConfig);
     method @Deprecated @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
     method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull android.hardware.display.VirtualDisplayConfig, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualDpad createVirtualDpad(@NonNull android.hardware.input.VirtualDpadConfig);
@@ -3225,6 +3247,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.input.VirtualMouseConfig);
     method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualNavigationTouchpad createVirtualNavigationTouchpad(@NonNull android.hardware.input.VirtualNavigationTouchpadConfig);
+    method @FlaggedApi("android.companion.virtual.flags.virtual_stylus") @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualStylus createVirtualStylus(@NonNull android.hardware.input.VirtualStylusConfig);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.input.VirtualTouchscreenConfig);
     method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
     method public int getDeviceId();
@@ -3270,6 +3293,7 @@
     field @Deprecated public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
     field @FlaggedApi("android.companion.virtual.flags.dynamic_policy") public static final int POLICY_TYPE_ACTIVITY = 3; // 0x3
     field public static final int POLICY_TYPE_AUDIO = 1; // 0x1
+    field @FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final int POLICY_TYPE_CAMERA = 5; // 0x5
     field @FlaggedApi("android.companion.virtual.flags.cross_device_clipboard") public static final int POLICY_TYPE_CLIPBOARD = 4; // 0x4
     field public static final int POLICY_TYPE_RECENTS = 2; // 0x2
     field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
@@ -3352,30 +3376,37 @@
   @FlaggedApi("android.companion.virtual.flags.virtual_camera") public interface VirtualCameraCallback {
     method public default void onProcessCaptureRequest(int, long);
     method public void onStreamClosed(int);
-    method public void onStreamConfigured(int, @NonNull android.view.Surface, @NonNull android.companion.virtual.camera.VirtualCameraStreamConfig);
+    method public void onStreamConfigured(int, @NonNull android.view.Surface, @IntRange(from=1) int, @IntRange(from=1) int, int);
   }
 
   @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraConfig implements android.os.Parcelable {
     method public int describeContents();
+    method public int getLensFacing();
     method @NonNull public String getName();
+    method public int getSensorOrientation();
     method @NonNull public java.util.Set<android.companion.virtual.camera.VirtualCameraStreamConfig> getStreamConfigs();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraConfig> CREATOR;
+    field public static final int SENSOR_ORIENTATION_0 = 0; // 0x0
+    field public static final int SENSOR_ORIENTATION_180 = 180; // 0xb4
+    field public static final int SENSOR_ORIENTATION_270 = 270; // 0x10e
+    field public static final int SENSOR_ORIENTATION_90 = 90; // 0x5a
   }
 
   @FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final class VirtualCameraConfig.Builder {
-    ctor public VirtualCameraConfig.Builder();
-    method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder addStreamConfig(int, int, int);
+    ctor public VirtualCameraConfig.Builder(@NonNull String);
+    method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder addStreamConfig(@IntRange(from=1) int, @IntRange(from=1) int, int, @IntRange(from=1) int);
     method @NonNull public android.companion.virtual.camera.VirtualCameraConfig build();
-    method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setName(@NonNull String);
+    method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setLensFacing(int);
+    method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setSensorOrientation(int);
     method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setVirtualCameraCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.camera.VirtualCameraCallback);
   }
 
   @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraStreamConfig implements android.os.Parcelable {
-    ctor public VirtualCameraStreamConfig(@IntRange(from=1) int, @IntRange(from=1) int, int);
     method public int describeContents();
     method public int getFormat();
     method @IntRange(from=1) public int getHeight();
+    method @IntRange(from=1) public int getMaximumFramesPerSecond();
     method @IntRange(from=1) public int getWidth();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraStreamConfig> CREATOR;
@@ -3526,8 +3557,10 @@
     field public static final String CLOUDSEARCH_SERVICE = "cloudsearch";
     field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
     field public static final String CONTEXTHUB_SERVICE = "contexthub";
+    field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation";
     field public static final String ETHERNET_SERVICE = "ethernet";
     field public static final String EUICC_CARD_SERVICE = "euicc_card";
+    field @FlaggedApi("android.hardware.biometrics.face_background_authentication") public static final String FACE_SERVICE = "face";
     field public static final String FONT_SERVICE = "font";
     field public static final String HDMI_CONTROL_SERVICE = "hdmi_control";
     field public static final String MEDIA_TRANSCODING_SERVICE = "media_transcoding";
@@ -3932,6 +3965,7 @@
     method public void setInstallAsInstantApp(boolean);
     method public void setInstallAsVirtualPreload();
     method public void setRequestDowngrade(boolean);
+    method @FlaggedApi("android.content.pm.recoverability_detection") @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void setRollbackImpactLevel(int);
     method @FlaggedApi("android.content.pm.rollback_lifetime") @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void setRollbackLifetimeMillis(long);
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setStaged();
   }
@@ -4100,6 +4134,9 @@
     field public static final int ROLLBACK_DATA_POLICY_RESTORE = 0; // 0x0
     field public static final int ROLLBACK_DATA_POLICY_RETAIN = 2; // 0x2
     field public static final int ROLLBACK_DATA_POLICY_WIPE = 1; // 0x1
+    field @FlaggedApi("android.content.pm.recoverability_detection") public static final int ROLLBACK_USER_IMPACT_HIGH = 1; // 0x1
+    field @FlaggedApi("android.content.pm.recoverability_detection") public static final int ROLLBACK_USER_IMPACT_LOW = 0; // 0x0
+    field @FlaggedApi("android.content.pm.recoverability_detection") public static final int ROLLBACK_USER_IMPACT_ONLY_MANUAL = 2; // 0x2
     field public static final int SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN = 0; // 0x0
     field public static final int SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_VISIBLE = 1; // 0x1
     field public static final int SYSTEM_APP_STATE_INSTALLED = 2; // 0x2
@@ -4196,6 +4233,7 @@
   public final class UserProperties implements android.os.Parcelable {
     method public int describeContents();
     method public int getCrossProfileContentSharingStrategy();
+    method @FlaggedApi("android.multiuser.support_hiding_profiles") @NonNull public int getProfileApiVisibility();
     method public int getShowInQuietMode();
     method public int getShowInSharingSurfaces();
     method public boolean isCredentialShareableWithParent();
@@ -4204,11 +4242,17 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.UserProperties> CREATOR;
     field public static final int CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT = 1; // 0x1
     field public static final int CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION = 0; // 0x0
+    field public static final int CROSS_PROFILE_CONTENT_SHARING_UNKNOWN = -1; // 0xffffffff
+    field @FlaggedApi("android.multiuser.support_hiding_profiles") public static final int PROFILE_API_VISIBILITY_HIDDEN = 1; // 0x1
+    field @FlaggedApi("android.multiuser.support_hiding_profiles") public static final int PROFILE_API_VISIBILITY_UNKNOWN = -1; // 0xffffffff
+    field @FlaggedApi("android.multiuser.support_hiding_profiles") public static final int PROFILE_API_VISIBILITY_VISIBLE = 0; // 0x0
     field public static final int SHOW_IN_QUIET_MODE_DEFAULT = 2; // 0x2
     field public static final int SHOW_IN_QUIET_MODE_HIDDEN = 1; // 0x1
     field public static final int SHOW_IN_QUIET_MODE_PAUSED = 0; // 0x0
+    field public static final int SHOW_IN_QUIET_MODE_UNKNOWN = -1; // 0xffffffff
     field public static final int SHOW_IN_SHARING_SURFACES_NO = 2; // 0x2
     field public static final int SHOW_IN_SHARING_SURFACES_SEPARATE = 1; // 0x1
+    field public static final int SHOW_IN_SHARING_SURFACES_UNKNOWN = -1; // 0xffffffff
     field public static final int SHOW_IN_SHARING_SURFACES_WITH_PARENT = 0; // 0x0
   }
 
@@ -4337,6 +4381,41 @@
 
 }
 
+package android.credentials.selection {
+
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class FailureResult {
+    ctor public FailureResult(int, @Nullable String);
+    method public int getErrorCode();
+    method @Nullable public String getErrorMessage();
+    field public static final int ERROR_CODE_CANCELED_AND_LAUNCHED_SETTINGS = 2; // 0x2
+    field public static final int ERROR_CODE_DIALOG_CANCELED_BY_USER = 1; // 0x1
+    field public static final int ERROR_CODE_UI_FAILURE = 0; // 0x0
+  }
+
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class ProviderPendingIntentResponse implements android.os.Parcelable {
+    ctor public ProviderPendingIntentResponse(int, @Nullable android.content.Intent);
+    method public int describeContents();
+    method public int getResultCode();
+    method @Nullable public android.content.Intent getResultData();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.ProviderPendingIntentResponse> CREATOR;
+  }
+
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class ResultHelper {
+    method public static void sendFailureResult(@NonNull android.os.ResultReceiver, @NonNull android.credentials.selection.FailureResult);
+    method public static void sendUserSelectionResult(@NonNull android.os.ResultReceiver, @NonNull android.credentials.selection.UserSelectionResult);
+  }
+
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class UserSelectionResult {
+    ctor public UserSelectionResult(@NonNull String, @NonNull String, @NonNull String, @Nullable android.credentials.selection.ProviderPendingIntentResponse);
+    method @NonNull public String getEntryKey();
+    method @NonNull public String getEntrySubkey();
+    method @Nullable public android.credentials.selection.ProviderPendingIntentResponse getPendingIntentProviderResponse();
+    method @NonNull public String getProviderId();
+  }
+
+}
+
 package android.database {
 
   public abstract class ContentObserver {
@@ -4705,6 +4784,15 @@
 
 }
 
+package android.hardware.face {
+
+  @FlaggedApi("android.hardware.biometrics.face_background_authentication") public class FaceManager {
+    method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @RequiresPermission(android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION) public void authenticateInBackground(@Nullable java.util.concurrent.Executor, @Nullable android.hardware.biometrics.BiometricPrompt.CryptoObject, @Nullable android.os.CancellationSignal, @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback);
+    method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @RequiresPermission(anyOf={"android.permission.USE_BIOMETRIC_INTERNAL", android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION}) public boolean hasEnrolledTemplates();
+  }
+
+}
+
 package android.hardware.hdmi {
 
   public abstract class HdmiClient {
@@ -5300,6 +5388,78 @@
     method @NonNull public android.hardware.input.VirtualNavigationTouchpadConfig build();
   }
 
+  @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public class VirtualStylus implements java.io.Closeable {
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendButtonEvent(@NonNull android.hardware.input.VirtualStylusButtonEvent);
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendMotionEvent(@NonNull android.hardware.input.VirtualStylusMotionEvent);
+  }
+
+  @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public final class VirtualStylusButtonEvent implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAction();
+    method public int getButtonCode();
+    method public long getEventTimeNanos();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int ACTION_BUTTON_PRESS = 11; // 0xb
+    field public static final int ACTION_BUTTON_RELEASE = 12; // 0xc
+    field public static final int BUTTON_PRIMARY = 32; // 0x20
+    field public static final int BUTTON_SECONDARY = 64; // 0x40
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualStylusButtonEvent> CREATOR;
+  }
+
+  @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public static final class VirtualStylusButtonEvent.Builder {
+    ctor public VirtualStylusButtonEvent.Builder();
+    method @NonNull public android.hardware.input.VirtualStylusButtonEvent build();
+    method @NonNull public android.hardware.input.VirtualStylusButtonEvent.Builder setAction(int);
+    method @NonNull public android.hardware.input.VirtualStylusButtonEvent.Builder setButtonCode(int);
+    method @NonNull public android.hardware.input.VirtualStylusButtonEvent.Builder setEventTimeNanos(long);
+  }
+
+  @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public final class VirtualStylusConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getHeight();
+    method public int getWidth();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualStylusConfig> CREATOR;
+  }
+
+  @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public static final class VirtualStylusConfig.Builder extends android.hardware.input.VirtualInputDeviceConfig.Builder<android.hardware.input.VirtualStylusConfig.Builder> {
+    ctor public VirtualStylusConfig.Builder(@IntRange(from=1) int, @IntRange(from=1) int);
+    method @NonNull public android.hardware.input.VirtualStylusConfig build();
+  }
+
+  @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public final class VirtualStylusMotionEvent implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAction();
+    method public long getEventTimeNanos();
+    method public int getPressure();
+    method public int getTiltX();
+    method public int getTiltY();
+    method public int getToolType();
+    method public int getX();
+    method public int getY();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int ACTION_DOWN = 0; // 0x0
+    field public static final int ACTION_MOVE = 2; // 0x2
+    field public static final int ACTION_UP = 1; // 0x1
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualStylusMotionEvent> CREATOR;
+    field public static final int TOOL_TYPE_ERASER = 4; // 0x4
+    field public static final int TOOL_TYPE_STYLUS = 2; // 0x2
+  }
+
+  @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public static final class VirtualStylusMotionEvent.Builder {
+    ctor public VirtualStylusMotionEvent.Builder();
+    method @NonNull public android.hardware.input.VirtualStylusMotionEvent build();
+    method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setAction(int);
+    method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setEventTimeNanos(long);
+    method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setPressure(@IntRange(from=0x0, to=0xff) int);
+    method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setTiltX(@IntRange(from=0xffffffa6, to=0x5a) int);
+    method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setTiltY(@IntRange(from=0xffffffa6, to=0x5a) int);
+    method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setToolType(int);
+    method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setX(int);
+    method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setY(int);
+  }
+
   public final class VirtualTouchEvent implements android.os.Parcelable {
     method public int describeContents();
     method public int getAction();
@@ -5747,7 +5907,7 @@
     method public void addOnCompleteListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.OnCompleteListener);
     method public void addOnCompleteListener(@NonNull android.hardware.radio.ProgramList.OnCompleteListener);
     method public void close();
-    method @Deprecated @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier);
+    method @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier);
     method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramInfos(@NonNull android.hardware.radio.ProgramSelector.Identifier);
     method public void registerListCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.ListCallback);
     method public void registerListCallback(@NonNull android.hardware.radio.ProgramList.ListCallback);
@@ -5799,7 +5959,7 @@
     field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5
     field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SID_EXT = 5; // 0x5
     field public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; // 0xa
-    field @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb
+    field public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb
     field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9
     field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3
     field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int IDENTIFIER_TYPE_HD_STATION_LOCATION = 15; // 0xf
@@ -5807,8 +5967,8 @@
     field @Deprecated public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4
     field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0
     field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2
-    field @Deprecated public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
-    field @Deprecated public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
+    field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
+    field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
     field public static final int IDENTIFIER_TYPE_VENDOR_END = 1999; // 0x7cf
     field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = 1999; // 0x7cf
     field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = 1000; // 0x3e8
@@ -5861,7 +6021,7 @@
     field public static final int CONFIG_DAB_DAB_SOFT_LINKING = 8; // 0x8
     field public static final int CONFIG_DAB_FM_LINKING = 7; // 0x7
     field public static final int CONFIG_DAB_FM_SOFT_LINKING = 9; // 0x9
-    field @Deprecated public static final int CONFIG_FORCE_ANALOG = 2; // 0x2
+    field public static final int CONFIG_FORCE_ANALOG = 2; // 0x2
     field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_AM = 11; // 0xb
     field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_FM = 10; // 0xa
     field public static final int CONFIG_FORCE_DIGITAL = 3; // 0x3
@@ -6770,7 +6930,6 @@
   public final class MediaRouter2 {
     method @NonNull public java.util.List<android.media.MediaRoute2Info> getAllRoutes();
     method @Nullable public String getClientPackageName();
-    method @Nullable @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void setRouteVolume(@NonNull android.media.MediaRoute2Info, int);
     method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void startScan();
     method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void stopScan();
@@ -9875,49 +10034,6 @@
 
 }
 
-package android.nfc {
-
-  public final class NfcAdapter {
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean addNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler, String[]);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable();
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(boolean);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
-    method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
-    method @FlaggedApi("android.nfc.enable_nfc_charging") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableWlc(boolean);
-    method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getAdapterState();
-    method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.Map<java.lang.String,java.lang.Boolean> getTagIntentAppPreferenceForUser(int);
-    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
-    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported();
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagIntentAppPreferenceSupported();
-    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
-    method @FlaggedApi("android.nfc.enable_nfc_charging") public void registerWlcStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.WlcStateListener);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
-    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
-    method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
-    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
-    method @FlaggedApi("android.nfc.enable_nfc_charging") public void unregisterWlcStateListener(@NonNull android.nfc.NfcAdapter.WlcStateListener);
-    field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
-    field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff
-    field public static final int TAG_INTENT_APP_PREF_RESULT_SUCCESS = 0; // 0x0
-    field public static final int TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE = -2; // 0xfffffffe
-  }
-
-  public static interface NfcAdapter.ControllerAlwaysOnListener {
-    method public void onControllerAlwaysOnChanged(boolean);
-  }
-
-  public static interface NfcAdapter.NfcUnlockHandler {
-    method public boolean onUnlockAttempted(android.nfc.Tag);
-  }
-
-  @FlaggedApi("android.nfc.enable_nfc_charging") public static interface NfcAdapter.WlcStateListener {
-    method public void onWlcStateChanged(@NonNull android.nfc.WlcLDeviceInfo);
-  }
-
-}
-
 package android.nfc.cardemulation {
 
   @FlaggedApi("android.nfc.enable_nfc_mainline") public final class AidGroup implements android.os.Parcelable {
@@ -9934,6 +10050,7 @@
 
   @FlaggedApi("android.nfc.enable_nfc_mainline") public final class ApduServiceInfo implements android.os.Parcelable {
     ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilter(@NonNull String);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream);
@@ -9944,6 +10061,7 @@
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getDescription();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.nfc.cardemulation.AidGroup getDynamicAidGroupForCategory(@NonNull String);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public String getOffHostSecureElement();
+    method @FlaggedApi("android.nfc.nfc_read_polling_loop") @NonNull public java.util.List<java.lang.String> getPollingLoopFilters();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getPrefixAids();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getSettingsActivityName();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getSubsetAids();
@@ -9956,6 +10074,7 @@
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.graphics.drawable.Drawable loadIcon(@NonNull android.content.pm.PackageManager);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public boolean removeDynamicAidGroupForCategory(@NonNull String);
+    method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void removePollingLoopFilter(@NonNull String);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresScreenOn();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresUnlock();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void resetOffHostSecureElement();
@@ -9966,10 +10085,6 @@
     field @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.ApduServiceInfo> CREATOR;
   }
 
-  public final class CardEmulation {
-    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<android.nfc.cardemulation.ApduServiceInfo> getServices(@NonNull String, int);
-  }
-
   @FlaggedApi("android.nfc.enable_nfc_mainline") public final class NfcFServiceInfo implements android.os.Parcelable {
     ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public NfcFServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents();
@@ -9998,12 +10113,17 @@
     field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_CHARGING_POLICY = 9; // 0x9
     field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_FIRST_USAGE_DATE = 8; // 0x8
     field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_MANUFACTURING_DATE = 7; // 0x7
+    field @FlaggedApi("android.os.battery_part_status_api") @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_PART_STATUS = 12; // 0xc
+    field @FlaggedApi("android.os.battery_part_status_api") @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_SERIAL_NUMBER = 11; // 0xb
     field public static final int CHARGING_POLICY_ADAPTIVE_AC = 3; // 0x3
     field public static final int CHARGING_POLICY_ADAPTIVE_AON = 2; // 0x2
     field public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE = 4; // 0x4
     field public static final int CHARGING_POLICY_DEFAULT = 1; // 0x1
     field public static final String EXTRA_EVENTS = "android.os.extra.EVENTS";
     field public static final String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP";
+    field @FlaggedApi("android.os.battery_part_status_api") public static final int PART_STATUS_ORIGINAL = 1; // 0x1
+    field @FlaggedApi("android.os.battery_part_status_api") public static final int PART_STATUS_REPLACED = 2; // 0x2
+    field @FlaggedApi("android.os.battery_part_status_api") public static final int PART_STATUS_UNSUPPORTED = 0; // 0x0
   }
 
   public final class BatterySaverPolicyConfig implements android.os.Parcelable {
@@ -14514,6 +14634,7 @@
     field public static final int EVENT_SERVICE_STATE_CHANGED = 1; // 0x1
     field public static final int EVENT_SIGNAL_STRENGTHS_CHANGED = 9; // 0x9
     field public static final int EVENT_SIGNAL_STRENGTH_CHANGED = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED = 41; // 0x29
     field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_SRVCC_STATE_CHANGED = 16; // 0x10
     field public static final int EVENT_USER_MOBILE_DATA_STATE_CHANGED = 20; // 0x14
     field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_VOICE_ACTIVATION_STATE_CHANGED = 18; // 0x12
@@ -14560,6 +14681,10 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onRadioPowerStateChanged(int);
   }
 
+  @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") public static interface TelephonyCallback.SimultaneousCellularCallingSupportListener {
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onSimultaneousCellularCallingSubscriptionsChanged(@NonNull java.util.Set<java.lang.Integer>);
+  }
+
   public static interface TelephonyCallback.SrvccStateListener {
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onSrvccStateChanged(int);
   }
@@ -14635,6 +14760,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst();
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @Nullable @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_LAST_KNOWN_CELL_ID}) public android.telephony.CellIdentity getLastKnownCellIdentity();
     method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping();
     method public int getMaxNumberOfSimultaneouslyActiveSims();
     method public static long getMaxNumberVerificationTimeoutMillis();
@@ -14679,6 +14805,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isLteCdmaEvdoGsmWcdmaEnabled();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isMobileDataPolicyEnabled(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isNrDualConnectivityEnabled();
+    method @FlaggedApi("com.android.internal.telephony.flags.enable_modem_cipher_transparency") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isNullCipherNotificationsEnabled();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled();
     method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String);
@@ -14690,6 +14817,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean matchesCurrentSimOperator(@NonNull String, int, @Nullable String);
     method public boolean needsOtaServiceProvisioning();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.DUMP) public void persistEmergencyCallDiagnosticData(@NonNull String, @NonNull android.telephony.TelephonyManager.EmergencyCallDiagnosticParams);
     method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot();
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerCarrierPrivilegesCallback(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CarrierPrivilegesCallback);
@@ -14718,6 +14846,7 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
     method @FlaggedApi("com.android.internal.telephony.flags.enable_identifier_disclosure_transparency") @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setEnableCellularIdentifierDisclosureNotifications(boolean);
+    method @FlaggedApi("com.android.internal.telephony.flags.enable_modem_cipher_transparency") @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setEnableNullCipherNotifications(boolean);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult setIccLockEnabled(boolean, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabled(int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
@@ -14736,6 +14865,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSystemSelectionChannels(@NonNull java.util.List<android.telephony.RadioAccessSpecifier>);
     method @Deprecated public void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoiceActivationState(int);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE) public void setVoiceServiceStateOverride(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void shutdownAllRadios();
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult supplyIccLockPin(@NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult supplyIccLockPuk(@NonNull String, @NonNull String);
@@ -14866,6 +14996,21 @@
     method public default void onCarrierServiceChanged(@Nullable String, int);
   }
 
+  @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final class TelephonyManager.EmergencyCallDiagnosticParams {
+    method public long getLogcatCollectionStartTimeMillis();
+    method public boolean isLogcatCollectionEnabled();
+    method public boolean isTelecomDumpSysCollectionEnabled();
+    method public boolean isTelephonyDumpSysCollectionEnabled();
+  }
+
+  public static final class TelephonyManager.EmergencyCallDiagnosticParams.Builder {
+    ctor public TelephonyManager.EmergencyCallDiagnosticParams.Builder();
+    method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams build();
+    method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams.Builder setLogcatCollectionStartTimeMillis(long);
+    method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams.Builder setTelecomDumpSysCollectionEnabled(boolean);
+    method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams.Builder setTelephonyDumpSysCollectionEnabled(boolean);
+  }
+
   public static class TelephonyManager.ModemActivityInfoException extends java.lang.Exception {
     ctor public TelephonyManager.ModemActivityInfoException(int);
     method public int getErrorCode();
@@ -14875,6 +15020,11 @@
     field public static final int ERROR_UNKNOWN = 0; // 0x0
   }
 
+  @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public class TelephonyRegistryManager {
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyCallStateChangedForAllSubscriptions(int, @Nullable String);
+    method public void notifyOutgoingEmergencyCall(int, int, @NonNull android.telephony.emergency.EmergencyNumber);
+  }
+
   public final class ThermalMitigationRequest implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public android.telephony.DataThrottlingRequest getDataThrottlingRequest();
@@ -15125,7 +15275,7 @@
     method public final void notifyDataCallListChanged(java.util.List<android.telephony.data.DataCallResponse>);
     method public final void notifyDataProfileUnthrottled(@NonNull android.telephony.data.DataProfile);
     method public void requestDataCallList(@NonNull android.telephony.data.DataServiceCallback);
-    method @FlaggedApi("com.android.internal.telephony.flags.network_validation") public void requestValidation(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.network_validation") public void requestNetworkValidation(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method public void setDataProfile(@NonNull java.util.List<android.telephony.data.DataProfile>, boolean, @NonNull android.telephony.data.DataServiceCallback);
     method public void setInitialAttachApn(@NonNull android.telephony.data.DataProfile, boolean, @NonNull android.telephony.data.DataServiceCallback);
     method public void setupDataCall(int, @NonNull android.telephony.data.DataProfile, boolean, boolean, int, @Nullable android.net.LinkProperties, @NonNull android.telephony.data.DataServiceCallback);
@@ -16332,7 +16482,7 @@
 
   public interface RegistrationManager {
     field public static final int SUGGESTED_ACTION_NONE = 0; // 0x0
-    field @FlaggedApi("com.android.internal.telephony.flags.add_rat_related_suggested_action_to_ims_registration") public static final int SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCK = 4; // 0x4
+    field @FlaggedApi("com.android.internal.telephony.flags.add_rat_related_suggested_action_to_ims_registration") public static final int SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCKS = 4; // 0x4
     field public static final int SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK = 1; // 0x1
     field public static final int SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT = 2; // 0x2
     field @FlaggedApi("com.android.internal.telephony.flags.add_rat_related_suggested_action_to_ims_registration") public static final int SUGGESTED_ACTION_TRIGGER_RAT_BLOCK = 3; // 0x3
@@ -17030,8 +17180,8 @@
 
   @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class PointingInfo implements android.os.Parcelable {
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getSatelliteAzimuthDegrees();
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getSatelliteElevationDegrees();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @FloatRange(from=0xffffff4c, to=180) public float getSatelliteAzimuthDegrees();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @FloatRange(from=0xffffffa6, to=90) public float getSatelliteElevationDegrees();
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int);
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.PointingInfo> CREATOR;
   }
@@ -17062,37 +17212,38 @@
   }
 
   @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteManager {
-    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatelliteService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback) throws android.telephony.satellite.SatelliteManager.SatelliteException;
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteCapabilitiesChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteCapabilitiesCallback);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
-    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void removeSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getAttachRestrictionReasonsForCarrier(int);
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.List<java.lang.String> getSatellitePlmnsForCarrier(int);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForCapabilitiesChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteCapabilitiesCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForIncomingDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteModemStateCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void removeAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsAttachEnabledForCarrier(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsCommunicationAllowedForCurrentLocation(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsDemoModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
-    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteAttachEnabledForCarrier(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteCommunicationAllowedForCurrentLocation(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void requestIsSatelliteSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void requestIsSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestNtnSignalStrength(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.NtnSignalStrength,android.telephony.satellite.SatelliteManager.SatelliteException>);
-    method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestTimeForNextSatelliteVisibility(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.time.Duration,android.telephony.satellite.SatelliteManager.SatelliteException>);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void sendSatelliteDatagram(int, @NonNull android.telephony.satellite.SatelliteDatagram, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void sendDatagram(int, @NonNull android.telephony.satellite.SatelliteDatagram, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void setDeviceAlignedWithSatellite(boolean);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void startSatelliteTransmissionUpdates(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void stopSatelliteTransmissionUpdates(@NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void startTransmissionUpdates(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void stopTransmissionUpdates(@NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForCapabilitiesChanged(@NonNull android.telephony.satellite.SatelliteCapabilitiesCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForIncomingDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForModemStateChanged(@NonNull android.telephony.satellite.SatelliteModemStateCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrengthCallback);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteCapabilitiesChanged(@NonNull android.telephony.satellite.SatelliteCapabilitiesCallback);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteModemStateChanged(@NonNull android.telephony.satellite.SatelliteStateCallback);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2; // 0x2
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_SOS_MESSAGE = 1; // 0x1
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_UNKNOWN = 0; // 0x0
@@ -17111,6 +17262,7 @@
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT = 2; // 0x2
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; // 0x1
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7
@@ -17133,6 +17285,7 @@
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ACCESS_BARRED = 16; // 0x10
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ERROR = 1; // 0x1
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ILLEGAL_STATE = 23; // 0x17
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8; // 0x8
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7; // 0x7
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; // 0x6
@@ -17161,12 +17314,12 @@
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getErrorCode();
   }
 
-  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteProvisionStateCallback {
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteProvisionStateChanged(boolean);
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteModemStateCallback {
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteModemStateChanged(int);
   }
 
-  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteStateCallback {
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteModemStateChanged(int);
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteProvisionStateCallback {
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteProvisionStateChanged(boolean);
   }
 
   @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteTransmissionUpdateCallback {
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index b2a28b2..6c83fd0 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -239,14 +239,6 @@
     Field 'ACTION_SCORE_NETWORKS' is missing @BroadcastBehavior
 BroadcastBehavior: android.net.Proxy#PROXY_CHANGE_ACTION:
     Field 'PROXY_CHANGE_ACTION' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_ADAPTER_STATE_CHANGED:
-    Field 'ACTION_ADAPTER_STATE_CHANGED' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_PREFERRED_PAYMENT_CHANGED:
-    Field 'ACTION_PREFERRED_PAYMENT_CHANGED' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_REQUIRE_UNLOCK_FOR_NFC:
-    Field 'ACTION_REQUIRE_UNLOCK_FOR_NFC' is missing @BroadcastBehavior
-BroadcastBehavior: android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED:
-    Field 'ACTION_TRANSACTION_DETECTED' is missing @BroadcastBehavior
 BroadcastBehavior: android.os.DropBoxManager#ACTION_DROPBOX_ENTRY_ADDED:
     Field 'ACTION_DROPBOX_ENTRY_ADDED' is missing @BroadcastBehavior
 BroadcastBehavior: android.provider.CalendarContract#ACTION_EVENT_REMINDER:
@@ -1077,86 +1069,6 @@
     Method 'applyVcnNetworkPolicy' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.net.vcn.VcnManager#removeVcnNetworkPolicyChangeListener(android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener):
     Method 'removeVcnNetworkPolicyChangeListener' documentation mentions permissions already declared by @RequiresPermission
-RequiresPermission: android.nfc.NfcAdapter#disableForegroundDispatch(android.app.Activity):
-    Method 'disableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.NfcAdapter#enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]):
-    Method 'enableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForAid(android.content.ComponentName, String):
-    Method 'isDefaultServiceForAid' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String):
-    Method 'isDefaultServiceForCategory' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.cardemulation.CardEmulation#setOffHostForService(android.content.ComponentName, String):
-    Method 'setOffHostForService' documentation mentions permissions already declared by @RequiresPermission
-RequiresPermission: android.nfc.tech.IsoDep#getTimeout():
-    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.IsoDep#setTimeout(int):
-    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.IsoDep#transceive(byte[]):
-    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyA(int, byte[]):
-    Method 'authenticateSectorWithKeyA' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyB(int, byte[]):
-    Method 'authenticateSectorWithKeyB' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#decrement(int, int):
-    Method 'decrement' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#getTimeout():
-    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#increment(int, int):
-    Method 'increment' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#readBlock(int):
-    Method 'readBlock' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#restore(int):
-    Method 'restore' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#setTimeout(int):
-    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#transceive(byte[]):
-    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#transfer(int):
-    Method 'transfer' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareClassic#writeBlock(int, byte[]):
-    Method 'writeBlock' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#getTimeout():
-    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#readPages(int):
-    Method 'readPages' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#setTimeout(int):
-    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#transceive(byte[]):
-    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.MifareUltralight#writePage(int, byte[]):
-    Method 'writePage' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#getNdefMessage():
-    Method 'getNdefMessage' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#isWritable():
-    Method 'isWritable' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#makeReadOnly():
-    Method 'makeReadOnly' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.Ndef#writeNdefMessage(android.nfc.NdefMessage):
-    Method 'writeNdefMessage' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NdefFormatable#format(android.nfc.NdefMessage):
-    Method 'format' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NdefFormatable#formatReadOnly(android.nfc.NdefMessage):
-    Method 'formatReadOnly' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcA#getTimeout():
-    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcA#setTimeout(int):
-    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcA#transceive(byte[]):
-    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcB#transceive(byte[]):
-    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcF#getTimeout():
-    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcF#setTimeout(int):
-    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcF#transceive(byte[]):
-    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.NfcV#transceive(byte[]):
-    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.TagTechnology#close():
-    Method 'close' documentation mentions permissions without declaring @RequiresPermission
-RequiresPermission: android.nfc.tech.TagTechnology#connect():
-    Method 'connect' documentation mentions permissions without declaring @RequiresPermission
 RequiresPermission: android.os.BugreportManager#cancelBugreport():
     Method 'cancelBugreport' documentation mentions permissions without declaring @RequiresPermission
 RequiresPermission: android.os.BugreportManager#preDumpUiData():
@@ -1863,10 +1775,6 @@
     SAM-compatible parameters (such as parameter 1, "sessionListener", in android.media.session.MediaSessionManager.addOnActiveSessionsChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.media.session.MediaSessionManager#addOnSession2TokensChangedListener(android.media.session.MediaSessionManager.OnSession2TokensChangedListener, android.os.Handler):
     SAM-compatible parameters (such as parameter 1, "listener", in android.media.session.MediaSessionManager.addOnSession2TokensChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
-SamShouldBeLast: android.nfc.NfcAdapter#enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle):
-    SAM-compatible parameters (such as parameter 2, "callback", in android.nfc.NfcAdapter.enableReaderMode) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
-SamShouldBeLast: android.nfc.NfcAdapter#ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler):
-    SAM-compatible parameters (such as parameter 3, "tagRemovedListener", in android.nfc.NfcAdapter.ignore) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.os.Binder#attachInterface(android.os.IInterface, String):
     SAM-compatible parameters (such as parameter 1, "owner", in android.os.Binder.attachInterface) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.os.Binder#linkToDeath(android.os.IBinder.DeathRecipient, int):
@@ -1933,8 +1841,6 @@
     Field 'ACTION_USB_PORT_COMPLIANCE_CHANGED' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
 SdkConstant: android.hardware.usb.UsbManager#ACTION_USB_STATE:
     Field 'ACTION_USB_STATE' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-SdkConstant: android.nfc.NfcAdapter#ACTION_REQUIRE_UNLOCK_FOR_NFC:
-    Field 'ACTION_REQUIRE_UNLOCK_FOR_NFC' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
 SdkConstant: android.service.euicc.EuiccService#ACTION_DELETE_SUBSCRIPTION_PRIVILEGED:
     Field 'ACTION_DELETE_SUBSCRIPTION_PRIVILEGED' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
 SdkConstant: android.service.euicc.EuiccService#ACTION_RENAME_SUBSCRIPTION_PRIVILEGED:
@@ -2359,8 +2265,8 @@
     New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.provisionSatelliteService(String,byte[],android.os.CancellationSignal,java.util.concurrent.Executor,java.util.function.Consumer<java.lang.Integer>)
 UnflaggedApi: android.telephony.satellite.SatelliteManager#registerForSatelliteDatagram(java.util.concurrent.Executor, android.telephony.satellite.SatelliteDatagramCallback):
     New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.registerForSatelliteDatagram(java.util.concurrent.Executor,android.telephony.satellite.SatelliteDatagramCallback)
-UnflaggedApi: android.telephony.satellite.SatelliteManager#registerForSatelliteModemStateChanged(java.util.concurrent.Executor, android.telephony.satellite.SatelliteStateCallback):
-    New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.registerForSatelliteModemStateChanged(java.util.concurrent.Executor,android.telephony.satellite.SatelliteStateCallback)
+UnflaggedApi: android.telephony.satellite.SatelliteManager#registerForSatelliteModemStateChanged(java.util.concurrent.Executor, android.telephony.satellite.SatelliteModemStateCallback):
+    New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.registerForSatelliteModemStateChanged(java.util.concurrent.Executor,android.telephony.satellite.SatelliteModemStateCallback)
 UnflaggedApi: android.telephony.satellite.SatelliteManager#registerForSatelliteProvisionStateChanged(java.util.concurrent.Executor, android.telephony.satellite.SatelliteProvisionStateCallback):
     New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.registerForSatelliteProvisionStateChanged(java.util.concurrent.Executor,android.telephony.satellite.SatelliteProvisionStateCallback)
 UnflaggedApi: android.telephony.satellite.SatelliteManager#requestIsDemoModeEnabled(java.util.concurrent.Executor, android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>):
@@ -2389,8 +2295,8 @@
     New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.stopSatelliteTransmissionUpdates(android.telephony.satellite.SatelliteTransmissionUpdateCallback,java.util.concurrent.Executor,java.util.function.Consumer<java.lang.Integer>)
 UnflaggedApi: android.telephony.satellite.SatelliteManager#unregisterForSatelliteDatagram(android.telephony.satellite.SatelliteDatagramCallback):
     New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.unregisterForSatelliteDatagram(android.telephony.satellite.SatelliteDatagramCallback)
-UnflaggedApi: android.telephony.satellite.SatelliteManager#unregisterForSatelliteModemStateChanged(android.telephony.satellite.SatelliteStateCallback):
-    New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.unregisterForSatelliteModemStateChanged(android.telephony.satellite.SatelliteStateCallback)
+UnflaggedApi: android.telephony.satellite.SatelliteManager#unregisterForSatelliteModemStateChanged(android.telephony.satellite.SatelliteModemStateCallback):
+    New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.unregisterForSatelliteModemStateChanged(android.telephony.satellite.SatelliteModemStateCallback)
 UnflaggedApi: android.telephony.satellite.SatelliteManager#unregisterForSatelliteProvisionStateChanged(android.telephony.satellite.SatelliteProvisionStateCallback):
     New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.unregisterForSatelliteProvisionStateChanged(android.telephony.satellite.SatelliteProvisionStateCallback)
 UnflaggedApi: android.telephony.satellite.SatelliteManager.SatelliteException:
@@ -2403,10 +2309,10 @@
     New API must be flagged with @FlaggedApi: class android.telephony.satellite.SatelliteProvisionStateCallback
 UnflaggedApi: android.telephony.satellite.SatelliteProvisionStateCallback#onSatelliteProvisionStateChanged(boolean):
     New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteProvisionStateCallback.onSatelliteProvisionStateChanged(boolean)
-UnflaggedApi: android.telephony.satellite.SatelliteStateCallback:
-    New API must be flagged with @FlaggedApi: class android.telephony.satellite.SatelliteStateCallback
-UnflaggedApi: android.telephony.satellite.SatelliteStateCallback#onSatelliteModemStateChanged(int):
-    New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteStateCallback.onSatelliteModemStateChanged(int)
+UnflaggedApi: android.telephony.satellite.SatelliteModemStateCallback:
+    New API must be flagged with @FlaggedApi: class android.telephony.satellite.SatelliteModemStateCallback
+UnflaggedApi: android.telephony.satellite.SatelliteModemStateCallback#onSatelliteModemStateChanged(int):
+    New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteModemStateCallback.onSatelliteModemStateChanged(int)
 UnflaggedApi: android.telephony.satellite.SatelliteTransmissionUpdateCallback:
     New API must be flagged with @FlaggedApi: class android.telephony.satellite.SatelliteTransmissionUpdateCallback
 UnflaggedApi: android.telephony.satellite.SatelliteTransmissionUpdateCallback#onReceiveDatagramStateChanged(int, int, int):
diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt
index 51b8a11..bbfa0ec 100644
--- a/core/api/system-removed.txt
+++ b/core/api/system-removed.txt
@@ -142,17 +142,6 @@
 
 }
 
-package android.nfc {
-
-  public final class NfcAdapter {
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disableNdefPush();
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableNdefPush();
-    method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int);
-    field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1
-  }
-
-}
-
 package android.os {
 
   public class Build {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 2e22071..1fa2b5c 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -156,6 +156,7 @@
     field public static final int PROCESS_CAPABILITY_ALL_IMPLICIT = 6; // 0x6
     field public static final int PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK = 8; // 0x8
     field public static final int PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK = 32; // 0x20
+    field public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 5; // 0x5
     field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4
     field public static final int PROCESS_STATE_TOP = 2; // 0x2
     field public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; // 0xffffffff
@@ -186,6 +187,7 @@
     method public void setEligibleForLegacyPermissionPrompt(boolean);
     method public static void setExitTransitionTimeout(long);
     method public void setLaunchActivityType(int);
+    method public void setLaunchCookie(@NonNull android.app.ActivityOptions.LaunchCookie);
     method public void setLaunchTaskDisplayAreaFeatureId(int);
     method public void setLaunchWindowingMode(int);
     method public void setLaunchedFromBubble(boolean);
@@ -193,6 +195,13 @@
     method public void setTaskOverlay(boolean, boolean);
   }
 
+  public static final class ActivityOptions.LaunchCookie implements android.os.Parcelable {
+    ctor public ActivityOptions.LaunchCookie();
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.ActivityOptions.LaunchCookie> CREATOR;
+  }
+
   public static interface ActivityOptions.OnAnimationFinishedListener {
     method public void onAnimationFinished(long);
   }
@@ -284,16 +293,6 @@
     method public default void onOpActiveChanged(@NonNull String, int, @NonNull String, @Nullable String, boolean, int, int);
   }
 
-  public final class AutomaticZenRule implements android.os.Parcelable {
-    method @FlaggedApi("android.app.modes_api") public int getUserModifiedFields();
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_INTERRUPTION_FILTER = 2; // 0x2
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_NAME = 1; // 0x1
-  }
-
-  @FlaggedApi("android.app.modes_api") public static final class AutomaticZenRule.Builder {
-    method @FlaggedApi("android.app.modes_api") @NonNull public android.app.AutomaticZenRule.Builder setUserModifiedFields(int);
-  }
-
   public class BroadcastOptions extends android.app.ComponentOptions {
     ctor public BroadcastOptions();
     ctor public BroadcastOptions(@NonNull android.os.Bundle);
@@ -381,6 +380,7 @@
   public class NotificationManager {
     method @FlaggedApi("android.app.modes_api") @NonNull public String addAutomaticZenRule(@NonNull android.app.AutomaticZenRule, boolean);
     method public void cleanUpCallersAfter(long);
+    method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy getDefaultZenPolicy();
     method public android.content.ComponentName getEffectsSuppressor();
     method public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String);
     method @FlaggedApi("android.app.modes_api") public boolean removeAutomaticZenRule(@NonNull String, boolean);
@@ -493,10 +493,15 @@
   }
 
   public class UiModeManager {
+    method @FlaggedApi("android.app.modes_api") @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) public int getAttentionModeThemeOverlay();
     method public boolean isNightModeLocked();
     method public boolean isUiModeLocked();
     method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean releaseProjection(int);
     method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean requestProjection(int);
+    field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_DAY = 1002; // 0x3ea
+    field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_NIGHT = 1001; // 0x3e9
+    field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_OFF = 1000; // 0x3e8
+    field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_UNKNOWN = -1; // 0xffffffff
     field public static final int PROJECTION_TYPE_ALL = -1; // 0xffffffff
     field public static final int PROJECTION_TYPE_AUTOMOTIVE = 1; // 0x1
     field public static final int PROJECTION_TYPE_NONE = 0; // 0x0
@@ -1177,6 +1182,7 @@
     method public int getShowInLauncher();
     field public static final int SHOW_IN_LAUNCHER_NO = 2; // 0x2
     field public static final int SHOW_IN_LAUNCHER_SEPARATE = 1; // 0x1
+    field public static final int SHOW_IN_LAUNCHER_UNKNOWN = -1; // 0xffffffff
     field public static final int SHOW_IN_LAUNCHER_WITH_PARENT = 0; // 0x0
   }
 
@@ -1208,6 +1214,10 @@
 
 package android.content.rollback {
 
+  public final class RollbackInfo implements android.os.Parcelable {
+    method @FlaggedApi("android.content.pm.recoverability_detection") public int getRollbackImpactLevel();
+  }
+
   public final class RollbackManager {
     method @RequiresPermission(android.Manifest.permission.TEST_MANAGE_ROLLBACKS) public void blockRollbackManager(long);
     method @RequiresPermission(android.Manifest.permission.TEST_MANAGE_ROLLBACKS) public void expireRollbackForPackage(@NonNull String);
@@ -1255,9 +1265,9 @@
 
 }
 
-package android.credentials.ui {
+package android.credentials.selection {
 
-  public final class AuthenticationEntry implements android.os.Parcelable {
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class AuthenticationEntry implements android.os.Parcelable {
     ctor public AuthenticationEntry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice, int);
     ctor public AuthenticationEntry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice, int, @NonNull android.content.Intent);
     method public int describeContents();
@@ -1267,32 +1277,47 @@
     method @NonNull public int getStatus();
     method @NonNull public String getSubkey();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.ui.AuthenticationEntry> CREATOR;
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.AuthenticationEntry> CREATOR;
     field public static final int STATUS_LOCKED = 0; // 0x0
     field public static final int STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT = 1; // 0x1
     field public static final int STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT = 2; // 0x2
   }
 
-  public final class CreateCredentialProviderData extends android.credentials.ui.ProviderData implements android.os.Parcelable {
-    ctor public CreateCredentialProviderData(@NonNull String, @NonNull java.util.List<android.credentials.ui.Entry>, @Nullable android.credentials.ui.Entry);
-    method @Nullable public android.credentials.ui.Entry getRemoteEntry();
-    method @NonNull public java.util.List<android.credentials.ui.Entry> getSaveEntries();
-    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.ui.CreateCredentialProviderData> CREATOR;
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public class BaseDialogResult implements android.os.Parcelable {
+    ctor public BaseDialogResult(@Nullable android.os.IBinder);
+    ctor protected BaseDialogResult(@NonNull android.os.Parcel);
+    method public static void addToBundle(@NonNull android.credentials.selection.BaseDialogResult, @NonNull android.os.Bundle);
+    method public int describeContents();
+    method @Nullable public static android.credentials.selection.BaseDialogResult fromResultData(@NonNull android.os.Bundle);
+    method @Deprecated @Nullable public android.os.IBinder getRequestToken();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.BaseDialogResult> CREATOR;
+    field public static final int RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS = 1; // 0x1
+    field public static final int RESULT_CODE_DATA_PARSING_FAILURE = 3; // 0x3
+    field public static final int RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION = 2; // 0x2
+    field public static final int RESULT_CODE_DIALOG_USER_CANCELED = 0; // 0x0
   }
 
-  public static final class CreateCredentialProviderData.Builder {
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class CreateCredentialProviderData extends android.credentials.selection.ProviderData implements android.os.Parcelable {
+    ctor public CreateCredentialProviderData(@NonNull String, @NonNull java.util.List<android.credentials.selection.Entry>, @Nullable android.credentials.selection.Entry);
+    method @Nullable public android.credentials.selection.Entry getRemoteEntry();
+    method @NonNull public java.util.List<android.credentials.selection.Entry> getSaveEntries();
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.CreateCredentialProviderData> CREATOR;
+  }
+
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public static final class CreateCredentialProviderData.Builder {
     ctor public CreateCredentialProviderData.Builder(@NonNull String);
-    method @NonNull public android.credentials.ui.CreateCredentialProviderData build();
-    method @NonNull public android.credentials.ui.CreateCredentialProviderData.Builder setRemoteEntry(@Nullable android.credentials.ui.Entry);
-    method @NonNull public android.credentials.ui.CreateCredentialProviderData.Builder setSaveEntries(@NonNull java.util.List<android.credentials.ui.Entry>);
+    method @NonNull public android.credentials.selection.CreateCredentialProviderData build();
+    method @NonNull public android.credentials.selection.CreateCredentialProviderData.Builder setRemoteEntry(@Nullable android.credentials.selection.Entry);
+    method @NonNull public android.credentials.selection.CreateCredentialProviderData.Builder setSaveEntries(@NonNull java.util.List<android.credentials.selection.Entry>);
   }
 
-  public final class DisabledProviderData extends android.credentials.ui.ProviderData implements android.os.Parcelable {
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class DisabledProviderData extends android.credentials.selection.ProviderData implements android.os.Parcelable {
     ctor public DisabledProviderData(@NonNull String);
-    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.ui.DisabledProviderData> CREATOR;
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.DisabledProviderData> CREATOR;
   }
 
-  public final class Entry implements android.os.Parcelable {
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class Entry implements android.os.Parcelable {
     ctor public Entry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice);
     ctor public Entry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice, @NonNull android.content.Intent);
     method public int describeContents();
@@ -1302,56 +1327,81 @@
     method @NonNull public android.app.slice.Slice getSlice();
     method @NonNull public String getSubkey();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.ui.Entry> CREATOR;
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.Entry> CREATOR;
   }
 
-  public final class GetCredentialProviderData extends android.credentials.ui.ProviderData implements android.os.Parcelable {
-    ctor public GetCredentialProviderData(@NonNull String, @NonNull java.util.List<android.credentials.ui.Entry>, @NonNull java.util.List<android.credentials.ui.Entry>, @NonNull java.util.List<android.credentials.ui.AuthenticationEntry>, @Nullable android.credentials.ui.Entry);
-    method @NonNull public java.util.List<android.credentials.ui.Entry> getActionChips();
-    method @NonNull public java.util.List<android.credentials.ui.AuthenticationEntry> getAuthenticationEntries();
-    method @NonNull public java.util.List<android.credentials.ui.Entry> getCredentialEntries();
-    method @Nullable public android.credentials.ui.Entry getRemoteEntry();
-    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.ui.GetCredentialProviderData> CREATOR;
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class FailureDialogResult extends android.credentials.selection.BaseDialogResult implements android.os.Parcelable {
+    ctor public FailureDialogResult(@Nullable android.os.IBinder, @Nullable String);
+    method public static void addToBundle(@NonNull android.credentials.selection.FailureDialogResult, @NonNull android.os.Bundle);
+    method @Nullable public static android.credentials.selection.FailureDialogResult fromResultData(@NonNull android.os.Bundle);
+    method @Nullable public String getErrorMessage();
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.FailureDialogResult> CREATOR;
   }
 
-  public static final class GetCredentialProviderData.Builder {
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class GetCredentialProviderData extends android.credentials.selection.ProviderData implements android.os.Parcelable {
+    ctor public GetCredentialProviderData(@NonNull String, @NonNull java.util.List<android.credentials.selection.Entry>, @NonNull java.util.List<android.credentials.selection.Entry>, @NonNull java.util.List<android.credentials.selection.AuthenticationEntry>, @Nullable android.credentials.selection.Entry);
+    method @NonNull public java.util.List<android.credentials.selection.Entry> getActionChips();
+    method @NonNull public java.util.List<android.credentials.selection.AuthenticationEntry> getAuthenticationEntries();
+    method @NonNull public java.util.List<android.credentials.selection.Entry> getCredentialEntries();
+    method @Nullable public android.credentials.selection.Entry getRemoteEntry();
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.GetCredentialProviderData> CREATOR;
+  }
+
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public static final class GetCredentialProviderData.Builder {
     ctor public GetCredentialProviderData.Builder(@NonNull String);
-    method @NonNull public android.credentials.ui.GetCredentialProviderData build();
-    method @NonNull public android.credentials.ui.GetCredentialProviderData.Builder setActionChips(@NonNull java.util.List<android.credentials.ui.Entry>);
-    method @NonNull public android.credentials.ui.GetCredentialProviderData.Builder setAuthenticationEntries(@NonNull java.util.List<android.credentials.ui.AuthenticationEntry>);
-    method @NonNull public android.credentials.ui.GetCredentialProviderData.Builder setCredentialEntries(@NonNull java.util.List<android.credentials.ui.Entry>);
-    method @NonNull public android.credentials.ui.GetCredentialProviderData.Builder setRemoteEntry(@Nullable android.credentials.ui.Entry);
+    method @NonNull public android.credentials.selection.GetCredentialProviderData build();
+    method @NonNull public android.credentials.selection.GetCredentialProviderData.Builder setActionChips(@NonNull java.util.List<android.credentials.selection.Entry>);
+    method @NonNull public android.credentials.selection.GetCredentialProviderData.Builder setAuthenticationEntries(@NonNull java.util.List<android.credentials.selection.AuthenticationEntry>);
+    method @NonNull public android.credentials.selection.GetCredentialProviderData.Builder setCredentialEntries(@NonNull java.util.List<android.credentials.selection.Entry>);
+    method @NonNull public android.credentials.selection.GetCredentialProviderData.Builder setRemoteEntry(@Nullable android.credentials.selection.Entry);
   }
 
-  public class IntentFactory {
-    method @NonNull public static android.content.Intent createCredentialSelectorIntent(@NonNull android.credentials.ui.RequestInfo, @NonNull java.util.ArrayList<android.credentials.ui.ProviderData>, @NonNull java.util.ArrayList<android.credentials.ui.DisabledProviderData>, @NonNull android.os.ResultReceiver);
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public class IntentFactory {
+    method @NonNull public static android.content.Intent createCredentialSelectorIntent(@NonNull android.credentials.selection.RequestInfo, @NonNull java.util.ArrayList<android.credentials.selection.ProviderData>, @NonNull java.util.ArrayList<android.credentials.selection.DisabledProviderData>, @NonNull android.os.ResultReceiver);
   }
 
-  public abstract class ProviderData implements android.os.Parcelable {
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public abstract class ProviderData implements android.os.Parcelable {
     ctor public ProviderData(@NonNull String);
     ctor protected ProviderData(@NonNull android.os.Parcel);
     method public int describeContents();
     method @NonNull public String getProviderFlattenedComponentName();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field public static final String EXTRA_DISABLED_PROVIDER_DATA_LIST = "android.credentials.ui.extra.DISABLED_PROVIDER_DATA_LIST";
-    field public static final String EXTRA_ENABLED_PROVIDER_DATA_LIST = "android.credentials.ui.extra.ENABLED_PROVIDER_DATA_LIST";
+    field public static final String EXTRA_DISABLED_PROVIDER_DATA_LIST = "android.credentials.selection.extra.DISABLED_PROVIDER_DATA_LIST";
+    field public static final String EXTRA_ENABLED_PROVIDER_DATA_LIST = "android.credentials.selection.extra.ENABLED_PROVIDER_DATA_LIST";
   }
 
-  public final class RequestInfo implements android.os.Parcelable {
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class RequestInfo implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public String getAppPackageName();
     method @Nullable public android.credentials.CreateCredentialRequest getCreateCredentialRequest();
+    method @NonNull public java.util.List<java.lang.String> getDefaultProviderIds();
     method @Nullable public android.credentials.GetCredentialRequest getGetCredentialRequest();
+    method @NonNull public java.util.List<java.lang.String> getRegistryProviderIds();
     method @NonNull public android.os.IBinder getToken();
     method @NonNull public String getType();
-    method @NonNull public static android.credentials.ui.RequestInfo newCreateRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.CreateCredentialRequest, @NonNull String);
-    method @NonNull public static android.credentials.ui.RequestInfo newGetRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.GetCredentialRequest, @NonNull String);
+    method public boolean hasPermissionToOverrideDefault();
+    method @NonNull public static android.credentials.selection.RequestInfo newCreateRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.CreateCredentialRequest, @NonNull String);
+    method @NonNull public static android.credentials.selection.RequestInfo newCreateRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.CreateCredentialRequest, @NonNull String, boolean, @NonNull java.util.List<java.lang.String>);
+    method @NonNull public static android.credentials.selection.RequestInfo newGetRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.GetCredentialRequest, @NonNull String, boolean);
+    method @NonNull public static android.credentials.selection.RequestInfo newGetRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.GetCredentialRequest, @NonNull String);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.ui.RequestInfo> CREATOR;
-    field @NonNull public static final String EXTRA_REQUEST_INFO = "android.credentials.ui.extra.REQUEST_INFO";
-    field @NonNull public static final String TYPE_CREATE = "android.credentials.ui.TYPE_CREATE";
-    field @NonNull public static final String TYPE_GET = "android.credentials.ui.TYPE_GET";
-    field @NonNull public static final String TYPE_UNDEFINED = "android.credentials.ui.TYPE_UNDEFINED";
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.RequestInfo> CREATOR;
+    field @NonNull public static final String EXTRA_REQUEST_INFO = "android.credentials.selection.extra.REQUEST_INFO";
+    field @NonNull public static final String TYPE_CREATE = "android.credentials.selection.TYPE_CREATE";
+    field @NonNull public static final String TYPE_GET = "android.credentials.selection.TYPE_GET";
+    field @NonNull public static final String TYPE_UNDEFINED = "android.credentials.selection.TYPE_UNDEFINED";
+  }
+
+  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class UserSelectionDialogResult extends android.credentials.selection.BaseDialogResult implements android.os.Parcelable {
+    ctor public UserSelectionDialogResult(@Nullable android.os.IBinder, @NonNull String, @NonNull String, @NonNull String);
+    ctor public UserSelectionDialogResult(@Nullable android.os.IBinder, @NonNull String, @NonNull String, @NonNull String, @Nullable android.credentials.selection.ProviderPendingIntentResponse);
+    method public static void addToBundle(@NonNull android.credentials.selection.UserSelectionDialogResult, @NonNull android.os.Bundle);
+    method @Nullable public static android.credentials.selection.UserSelectionDialogResult fromResultData(@NonNull android.os.Bundle);
+    method @NonNull public String getEntryKey();
+    method @NonNull public String getEntrySubkey();
+    method @Nullable public android.credentials.selection.ProviderPendingIntentResponse getPendingIntentProviderResponse();
+    method @NonNull public String getProviderId();
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.UserSelectionDialogResult> CREATOR;
   }
 
 }
@@ -2061,6 +2111,14 @@
 
 }
 
+package android.media.projection {
+
+  public final class MediaProjectionManager {
+    method @NonNull public android.content.Intent createScreenCaptureIntent(@Nullable android.app.ActivityOptions.LaunchCookie);
+  }
+
+}
+
 package android.media.soundtrigger {
 
   public final class SoundTriggerInstrumentation {
@@ -3021,47 +3079,12 @@
     method @Deprecated public boolean isBound();
   }
 
-  @FlaggedApi("android.app.modes_api") public final class ZenDeviceEffects implements android.os.Parcelable {
-    method public int getUserModifiedFields();
-    field public static final int FIELD_DIM_WALLPAPER = 4; // 0x4
-    field public static final int FIELD_DISABLE_AUTO_BRIGHTNESS = 16; // 0x10
-    field public static final int FIELD_DISABLE_TAP_TO_WAKE = 32; // 0x20
-    field public static final int FIELD_DISABLE_TILT_TO_WAKE = 64; // 0x40
-    field public static final int FIELD_DISABLE_TOUCH = 128; // 0x80
-    field public static final int FIELD_GRAYSCALE = 1; // 0x1
-    field public static final int FIELD_MAXIMIZE_DOZE = 512; // 0x200
-    field public static final int FIELD_MINIMIZE_RADIO_USAGE = 256; // 0x100
-    field public static final int FIELD_NIGHT_MODE = 8; // 0x8
-    field public static final int FIELD_SUPPRESS_AMBIENT_DISPLAY = 2; // 0x2
-  }
-
-  @FlaggedApi("android.app.modes_api") public static final class ZenDeviceEffects.Builder {
-    method @NonNull public android.service.notification.ZenDeviceEffects.Builder setUserModifiedFields(int);
-  }
-
   public final class ZenPolicy implements android.os.Parcelable {
-    method @FlaggedApi("android.app.modes_api") public int getUserModifiedFields();
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_ALLOW_CHANNELS = 8; // 0x8
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_CALLS = 2; // 0x2
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_CONVERSATIONS = 4; // 0x4
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_MESSAGES = 1; // 0x1
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_ALARMS = 128; // 0x80
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_EVENTS = 32; // 0x20
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_MEDIA = 256; // 0x100
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS = 64; // 0x40
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_SYSTEM = 512; // 0x200
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_AMBIENT = 32768; // 0x8000
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_BADGE = 16384; // 0x4000
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT = 1024; // 0x400
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_LIGHTS = 2048; // 0x800
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_NOTIFICATION_LIST = 65536; // 0x10000
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_PEEK = 4096; // 0x1000
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_STATUS_BAR = 8192; // 0x2000
+    method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy overwrittenWith(@Nullable android.service.notification.ZenPolicy);
   }
 
   public static final class ZenPolicy.Builder {
     ctor public ZenPolicy.Builder(@Nullable android.service.notification.ZenPolicy);
-    method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy.Builder setUserModifiedFields(int);
   }
 
 }
@@ -3292,7 +3315,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
     method @Deprecated public void setCarrierTestOverride(String, String, String, String, String, String, String);
     method public void setCarrierTestOverride(String, String, String, String, String, String, String, String, String);
-    method @RequiresPermission(android.Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE) public void setVoiceServiceStateOverride(boolean);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE) public void setVoiceServiceStateOverride(boolean);
     field public static final int HAL_SERVICE_DATA = 1; // 0x1
     field public static final int HAL_SERVICE_IMS = 7; // 0x7
     field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2
@@ -3674,6 +3697,7 @@
 
   public interface WindowManager extends android.view.ViewManager {
     method public default int getDisplayImePolicy(int);
+    method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @Nullable public default android.os.IBinder getSurfaceControlInputClientToken(@NonNull android.view.SurfaceControl);
     method public static boolean hasWindowExtensionsEnabled();
     method public default void holdLock(android.os.IBinder, int);
     method public default boolean isGlobalKey(int);
@@ -3735,6 +3759,7 @@
 
   public class AnimationUtils {
     method @FlaggedApi("android.view.flags.expected_presentation_time_read_only") public static void lockAnimationClock(long, long);
+    method public static void lockAnimationClock(long);
     method public static void unlockAnimationClock();
   }
 
diff --git a/core/java/Android.bp b/core/java/Android.bp
index fb1e16a..eba500d 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -14,20 +14,12 @@
     hdrs: ["android/hardware/HardwareBuffer.aidl"],
 }
 
-// TODO (b/303286040): Remove this once |ENABLE_NFC_MAINLINE_FLAG| is rolled out
-filegroup {
-    name: "framework-core-nfc-infcadapter-sources",
-    srcs: [
-        "android/nfc/INfcAdapter.aidl",
-    ],
-    visibility: ["//frameworks/base/services/core"],
-}
-
 filegroup {
     name: "framework-core-sources",
     srcs: [
         "**/*.java",
         "**/*.aidl",
+        ":framework-nfc-non-updatable-sources",
     ],
     visibility: ["//frameworks/base"],
 }
diff --git a/core/java/android/adaptiveauth/OWNERS b/core/java/android/adaptiveauth/OWNERS
new file mode 100644
index 0000000..0218a78
--- /dev/null
+++ b/core/java/android/adaptiveauth/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/adaptiveauth/OWNERS
\ No newline at end of file
diff --git a/core/java/android/adaptiveauth/flags.aconfig b/core/java/android/adaptiveauth/flags.aconfig
new file mode 100644
index 0000000..39e46bb
--- /dev/null
+++ b/core/java/android/adaptiveauth/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.adaptiveauth"
+
+flag {
+  name: "report_biometric_auth_attempts"
+  namespace: "biometrics"
+  description: "Control the usage of the biometric auth signal in adaptive auth"
+  bug: "285053096"
+}
\ No newline at end of file
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index b4a6955..845a346 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -1311,8 +1311,9 @@
         if (!node.mEnded) {
             float durationScale = ValueAnimator.getDurationScale();
             durationScale = durationScale == 0  ? 1 : durationScale;
-            node.mEnded = node.mAnimation.pulseAnimationFrame(
-                    (long) (animPlayTime * durationScale));
+            if (node.mAnimation.pulseAnimationFrame((long) (animPlayTime * durationScale))) {
+                node.mEnded = true;
+            }
         }
     }
 
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
index 1e1f155..5840f02 100644
--- a/core/java/android/animation/ObjectAnimator.java
+++ b/core/java/android/animation/ObjectAnimator.java
@@ -25,8 +25,6 @@
 import android.util.Property;
 import android.view.animation.AccelerateDecelerateInterpolator;
 
-import java.lang.ref.WeakReference;
-
 /**
  * This subclass of {@link ValueAnimator} provides support for animating properties on target objects.
  * The constructors of this class take parameters to define the target object that will be animated
@@ -73,11 +71,7 @@
 
     private static final boolean DBG = false;
 
-    /**
-     * A weak reference to the target object on which the property exists, set
-     * in the constructor. We'll cancel the animation if this goes away.
-     */
-    private WeakReference<Object> mTarget;
+    private Object mTarget;
 
     private String mPropertyName;
 
@@ -919,7 +913,7 @@
      */
     @Nullable
     public Object getTarget() {
-        return mTarget == null ? null : mTarget.get();
+        return mTarget;
     }
 
     @Override
@@ -929,7 +923,7 @@
             if (isStarted()) {
                 cancel();
             }
-            mTarget = target == null ? null : new WeakReference<Object>(target);
+            mTarget = target;
             // New target should cause re-initialization prior to starting
             mInitialized = false;
         }
@@ -977,13 +971,6 @@
     @Override
     void animateValue(float fraction) {
         final Object target = getTarget();
-        if (mTarget != null && target == null) {
-            // We lost the target reference, cancel and clean up. Note: we allow null target if the
-            /// target has never been set.
-            cancel();
-            return;
-        }
-
         super.animateValue(fraction);
         int numValues = mValues.length;
         for (int i = 0; i < numValues; ++i) {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index f9583d2..2103055 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1177,23 +1177,12 @@
         return mApplication;
     }
 
-    /**
-     * Whether this is a child {@link Activity} of an {@link ActivityGroup}.
-     *
-     * @deprecated {@link ActivityGroup} is deprecated.
-     */
-    @Deprecated
+    /** Is this activity embedded inside of another activity? */
     public final boolean isChild() {
         return mParent != null;
     }
 
-    /**
-     * Returns the parent {@link Activity} if this is a child {@link Activity} of an
-     * {@link ActivityGroup}.
-     *
-     * @deprecated {@link ActivityGroup} is deprecated.
-     */
-    @Deprecated
+    /** Return the parent activity if this view is an embedded child. */
     public final Activity getParent() {
         return mParent;
     }
@@ -3075,7 +3064,7 @@
     }
 
     /**
-     * Request to put the freeform activity into fullscreen. The requester has to be the top-most
+     * Request to put the activity into fullscreen. The requester must be pinned or the top-most
      * activity of the focused display which can be verified using
      * {@link #onTopResumedActivityChanged(boolean)}. The request should also be a response to a
      * user input. When getting fullscreen and receiving corresponding
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 6b7f4880..9d20f3c 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -712,6 +712,8 @@
 
     /** @hide Process is hosting a foreground service due to a system binding. */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+    @TestApi
     public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE =
             ProcessStateEnum.BOUND_FOREGROUND_SERVICE;
 
@@ -3574,7 +3576,7 @@
          * foreground.  This may be running a window that is behind the current
          * foreground (so paused and with its state saved, not interacting with
          * the user, but visible to them to some degree); it may also be running
-         * other services under the system's control that it inconsiders important.
+         * other services under the system's control that it considers important.
          */
         public static final int IMPORTANCE_VISIBLE = 200;
 
@@ -3646,9 +3648,9 @@
         public static final int IMPORTANCE_CANT_SAVE_STATE = 350;
 
         /**
-         * Constant for {@link #importance}: This process process contains
-         * cached code that is expendable, not actively running any app components
-         * we care about.
+         * Constant for {@link #importance}: This process contains cached code
+         * that is expendable, not actively running any app components we care
+         * about.
          */
         public static final int IMPORTANCE_CACHED = 400;
 
@@ -4052,10 +4054,28 @@
         }
     }
 
+    private final ArrayList<AppStartInfoCallbackWrapper> mAppStartInfoCallbacks =
+            new ArrayList<>();
+    @Nullable
+    private IApplicationStartInfoCompleteListener mAppStartInfoCompleteListener = null;
+
+    private static final class AppStartInfoCallbackWrapper {
+        @NonNull final Executor mExecutor;
+        @NonNull final Consumer<ApplicationStartInfo> mListener;
+
+        AppStartInfoCallbackWrapper(@NonNull final Executor executor,
+                @NonNull final Consumer<ApplicationStartInfo> listener) {
+            mExecutor = executor;
+            mListener = listener;
+        }
+    }
+
     /**
-     * Sets a callback to be notified when the {@link ApplicationStartInfo} records of this startup
+     * Adds a callback to be notified when the {@link ApplicationStartInfo} records of this startup
      * are complete.
      *
+     * <p class="note"> Note: callback will be removed automatically after being triggered.</p>
+     *
      * <p class="note"> Note: callback will not wait for {@link Activity#reportFullyDrawn} to occur.
      * Timestamp for fully drawn may be added after callback occurs. Set callback after invoking
      * {@link Activity#reportFullyDrawn} if timestamp for fully drawn is required.</p>
@@ -4073,33 +4093,77 @@
      * @throws IllegalArgumentException if executor or listener are null.
      */
     @FlaggedApi(Flags.FLAG_APP_START_INFO)
-    public void setApplicationStartInfoCompletionListener(@NonNull final Executor executor,
+    public void addApplicationStartInfoCompletionListener(@NonNull final Executor executor,
             @NonNull final Consumer<ApplicationStartInfo> listener) {
         Preconditions.checkNotNull(executor, "executor cannot be null");
         Preconditions.checkNotNull(listener, "listener cannot be null");
-        IApplicationStartInfoCompleteListener callback =
-                new IApplicationStartInfoCompleteListener.Stub() {
-            @Override
-            public void onApplicationStartInfoComplete(ApplicationStartInfo applicationStartInfo) {
-                executor.execute(() -> listener.accept(applicationStartInfo));
+        synchronized (mAppStartInfoCallbacks) {
+            for (int i = 0; i < mAppStartInfoCallbacks.size(); i++) {
+                if (listener.equals(mAppStartInfoCallbacks.get(i).mListener)) {
+                    return;
+                }
             }
-        };
-        try {
-            getService().setApplicationStartInfoCompleteListener(callback, mContext.getUserId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+            if (mAppStartInfoCompleteListener == null) {
+                mAppStartInfoCompleteListener = new IApplicationStartInfoCompleteListener.Stub() {
+                    @Override
+                    public void onApplicationStartInfoComplete(
+                            ApplicationStartInfo applicationStartInfo) {
+                        synchronized (mAppStartInfoCallbacks) {
+                            for (int i = 0; i < mAppStartInfoCallbacks.size(); i++) {
+                                final AppStartInfoCallbackWrapper callback =
+                                        mAppStartInfoCallbacks.get(i);
+                                callback.mExecutor.execute(() -> callback.mListener.accept(
+                                        applicationStartInfo));
+                            }
+                            mAppStartInfoCallbacks.clear();
+                            mAppStartInfoCompleteListener = null;
+                        }
+                    }
+                };
+                boolean succeeded = false;
+                try {
+                    getService().addApplicationStartInfoCompleteListener(
+                            mAppStartInfoCompleteListener, mContext.getUserId());
+                    succeeded = true;
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+                if (succeeded) {
+                    mAppStartInfoCallbacks.add(new AppStartInfoCallbackWrapper(executor, listener));
+                } else {
+                    mAppStartInfoCompleteListener = null;
+                    mAppStartInfoCallbacks.clear();
+                }
+            } else {
+                mAppStartInfoCallbacks.add(new AppStartInfoCallbackWrapper(executor, listener));
+            }
         }
     }
 
     /**
-     * Removes the callback set by {@link #setApplicationStartInfoCompletionListener} if there is one.
+     * Removes the provided callback set by {@link #addApplicationStartInfoCompletionListener}.
      */
     @FlaggedApi(Flags.FLAG_APP_START_INFO)
-    public void clearApplicationStartInfoCompletionListener() {
-        try {
-            getService().clearApplicationStartInfoCompleteListener(mContext.getUserId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+    public void removeApplicationStartInfoCompletionListener(
+            @NonNull final Consumer<ApplicationStartInfo> listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        synchronized (mAppStartInfoCallbacks) {
+            for (int i = 0; i < mAppStartInfoCallbacks.size(); i++) {
+                final AppStartInfoCallbackWrapper callback = mAppStartInfoCallbacks.get(i);
+                if (listener.equals(callback.mListener)) {
+                    mAppStartInfoCallbacks.remove(i);
+                    break;
+                }
+            }
+            if (mAppStartInfoCompleteListener != null && mAppStartInfoCallbacks.isEmpty()) {
+                try {
+                    getService().removeApplicationStartInfoCompleteListener(
+                            mAppStartInfoCompleteListener, mContext.getUserId());
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+                mAppStartInfoCompleteListener = null;
+            }
         }
     }
 
@@ -4340,7 +4404,7 @@
     }
 
     /**
-     * Start monitoring changes to the importance of uids running in the system.
+     * Start monitoring changes to the importance of all uids running in the system.
      * @param listener The listener callback that will receive change reports.
      * @param importanceCutpoint The level of importance in which the caller is interested
      * in differences.  For example, if {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}
@@ -4351,6 +4415,10 @@
      * <p>The caller must hold the {@link android.Manifest.permission#PACKAGE_USAGE_STATS}
      * permission to use this feature.</p>
      *
+     * <p>Calling this API with the same instance of {@code listener} without
+     * unregistering with {@link #removeOnUidImportanceListener} before it will result in
+     * an {@link IllegalArgumentException}.</p>
+     *
      * @throws IllegalArgumentException If the listener is already registered.
      * @throws SecurityException If the caller does not hold
      * {@link android.Manifest.permission#PACKAGE_USAGE_STATS}.
@@ -4360,17 +4428,52 @@
     @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
     public void addOnUidImportanceListener(OnUidImportanceListener listener,
             @RunningAppProcessInfo.Importance int importanceCutpoint) {
-        synchronized (this) {
+        addOnUidImportanceListenerInternal(listener, importanceCutpoint, null /* uids */);
+    }
+
+    /**
+     * Start monitoring changes to the importance of given uids running in the system.
+     *
+     * @param listener The listener callback that will receive change reports.
+     * @param importanceCutpoint The level of importance in which the caller is interested
+     * in differences.  For example, if {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}
+     * is used here, you will receive a call each time a uids importance transitions between
+     * being <= {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE} and
+     * > {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}.
+     * @param uids The UIDs that this listener is interested with. A {@code null} value means
+     * all UIDs will be monitored by this listener, this will be equivalent to the
+     * {@link #addOnUidImportanceListener(OnUidImportanceListener, int)} in this case.
+     *
+     * <p>Calling this API with the same instance of {@code listener} without
+     * unregistering with {@link #removeOnUidImportanceListener} before it will result in
+     * an {@link IllegalArgumentException}.</p>
+     *
+     * @throws IllegalArgumentException If the listener is already registered.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_UID_IMPORTANCE_LISTENER_FOR_UIDS)
+    @SystemApi
+    @SuppressLint("SamShouldBeLast")
+    @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+    public void addOnUidImportanceListener(@NonNull OnUidImportanceListener listener,
+            @RunningAppProcessInfo.Importance int importanceCutpoint, @Nullable int[] uids) {
+        addOnUidImportanceListenerInternal(listener, importanceCutpoint, uids);
+    }
+
+    @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+    private void addOnUidImportanceListenerInternal(@NonNull OnUidImportanceListener listener,
+            @RunningAppProcessInfo.Importance int importanceCutpoint, @Nullable int[] uids) {
+        synchronized (mImportanceListeners) {
             if (mImportanceListeners.containsKey(listener)) {
                 throw new IllegalArgumentException("Listener already registered: " + listener);
             }
             // TODO: implement the cut point in the system process to avoid IPCs.
             MyUidObserver observer = new MyUidObserver(listener, mContext);
             try {
-                getService().registerUidObserver(observer,
+                getService().registerUidObserverForUids(observer,
                         UID_OBSERVER_PROCSTATE | UID_OBSERVER_GONE,
                         RunningAppProcessInfo.importanceToProcState(importanceCutpoint),
-                        mContext.getOpPackageName());
+                        mContext.getOpPackageName(), uids);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -4388,7 +4491,7 @@
     @SystemApi
     @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
     public void removeOnUidImportanceListener(OnUidImportanceListener listener) {
-        synchronized (this) {
+        synchronized (mImportanceListeners) {
             MyUidObserver observer = mImportanceListeners.remove(listener);
             if (observer == null) {
                 throw new IllegalArgumentException("Listener not registered: " + listener);
diff --git a/core/java/android/app/ActivityOptions.aidl b/core/java/android/app/ActivityOptions.aidl
index bd5cd88..2d4a85f 100644
--- a/core/java/android/app/ActivityOptions.aidl
+++ b/core/java/android/app/ActivityOptions.aidl
@@ -17,4 +17,7 @@
 package android.app;
 
 /** @hide */
-parcelable ActivityOptions.SceneTransitionInfo;
\ No newline at end of file
+parcelable ActivityOptions.SceneTransitionInfo;
+
+/** @hide */
+parcelable ActivityOptions.LaunchCookie;
\ No newline at end of file
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 8af7ed1..111895e 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -29,6 +29,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.ExitTransitionCoordinator.ActivityExitTransitionCallbacks;
@@ -41,6 +42,7 @@
 import android.graphics.Bitmap.Config;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -182,6 +184,12 @@
     public static final String KEY_ANIM_START_LISTENER = "android:activity.animStartListener";
 
     /**
+     * Callback for when animation is aborted.
+     * @hide
+     */
+    private static final String KEY_ANIM_ABORT_LISTENER = "android:activity.animAbortListener";
+
+    /**
      * Specific a theme for a splash screen window.
      * @hide
      */
@@ -457,6 +465,7 @@
     private int mHeight;
     private IRemoteCallback mAnimationStartedListener;
     private IRemoteCallback mAnimationFinishedListener;
+    private IRemoteCallback mAnimationAbortListener;
     private SceneTransitionInfo mSceneTransitionInfo;
     private PendingIntent mUsageTimeReport;
     private int mLaunchDisplayId = INVALID_DISPLAY;
@@ -714,6 +723,14 @@
     }
 
     /**
+     * Callback for finding out when the given animation is finished
+     * @hide
+     */
+    public void setOnAnimationFinishedListener(IRemoteCallback listener) {
+        mAnimationFinishedListener = listener;
+    }
+
+    /**
      * Callback for finding out when the given animation has drawn its last frame.
      * @hide
      */
@@ -726,6 +743,14 @@
     }
 
     /**
+     * Callback for finding out when the given animation is aborted
+     * @hide
+     */
+    public void setOnAnimationAbortListener(IRemoteCallback listener) {
+        mAnimationAbortListener = listener;
+    }
+
+    /**
      * Create an ActivityOptions specifying an animation where the new
      * activity is scaled from a small originating area of the screen to
      * its final full representation.
@@ -1325,6 +1350,8 @@
                 KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE,
                 MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
         mDisableStartingWindow = opts.getBoolean(KEY_DISABLE_STARTING_WINDOW);
+        mAnimationAbortListener = IRemoteCallback.Stub.asInterface(
+                opts.getBinder(KEY_ANIM_ABORT_LISTENER));
     }
 
     /**
@@ -1425,11 +1452,15 @@
 
     /** @hide */
     public void abort() {
-        if (mAnimationStartedListener != null) {
+        sendResultIgnoreErrors(mAnimationStartedListener, null);
+        sendResultIgnoreErrors(mAnimationAbortListener, null);
+    }
+
+    private void sendResultIgnoreErrors(IRemoteCallback callback, Bundle data) {
+        if (callback != null) {
             try {
-                mAnimationStartedListener.sendResult(null);
-            } catch (RemoteException e) {
-            }
+                callback.sendResult(data);
+            } catch (RemoteException e) { }
         }
     }
 
@@ -1921,6 +1952,111 @@
     }
 
     /**
+     * An opaque token to use with {@link #setLaunchCookie(LaunchCookie)}.
+     *
+     * @hide
+     */
+    @SuppressLint("UnflaggedApi")
+    @TestApi
+    public static final class LaunchCookie implements Parcelable {
+        /** @hide */
+        public final IBinder binder;
+
+        /** @hide */
+        @SuppressLint("UnflaggedApi")
+        @TestApi
+        public LaunchCookie() {
+            binder = new Binder();
+        }
+
+        /** @hide */
+        public LaunchCookie(@Nullable String descriptor) {
+            binder = new Binder(descriptor);
+        }
+
+        private LaunchCookie(Parcel in) {
+            this.binder = in.readStrongBinder();
+        }
+
+        /** @hide */
+        @SuppressLint("UnflaggedApi")
+        @TestApi
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        /** @hide */
+        @SuppressLint("UnflaggedApi")
+        @TestApi
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeStrongBinder(binder);
+        }
+
+        /** @hide */
+        public static LaunchCookie readFromParcel(@NonNull Parcel in) {
+            return new LaunchCookie(in);
+        }
+
+        /** @hide */
+        public static void writeToParcel(@Nullable LaunchCookie launchCookie, Parcel out) {
+            if (launchCookie != null) {
+                launchCookie.writeToParcel(out, 0);
+            } else {
+                out.writeStrongBinder(null);
+            }
+        }
+
+        /** @hide */
+        @SuppressLint("UnflaggedApi")
+        @TestApi
+        @NonNull
+        public static final Parcelable.Creator<LaunchCookie> CREATOR =
+                new Parcelable.Creator<LaunchCookie>() {
+
+                    @Override
+                    public LaunchCookie createFromParcel(Parcel source) {
+                        return new LaunchCookie(source);
+                    }
+
+                    @Override
+                    public LaunchCookie[] newArray(int size) {
+                        return new LaunchCookie[size];
+                    }
+                };
+
+        @Override
+        public boolean equals(@Nullable Object obj) {
+            if (obj instanceof LaunchCookie) {
+                LaunchCookie other = (LaunchCookie) obj;
+                return binder == other.binder;
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return binder.hashCode();
+        }
+    }
+
+    /**
+     * Sets a launch cookie that can be used to track the {@link Activity} and task that are
+     * launched as a result of this option. If the launched activity is a trampoline that starts
+     * another activity immediately, the cookie will be transferred to the next activity.
+     *
+     * @param launchCookie a developer specified identifier for a specific task.
+     *
+     * @hide
+     */
+    @SuppressLint("UnflaggedApi")
+    @TestApi
+    public void setLaunchCookie(@NonNull LaunchCookie launchCookie) {
+        setLaunchCookie(launchCookie.binder);
+    }
+
+    /**
      * Sets a launch cookie that can be used to track the activity and task that are launch as a
      * result of this option. If the launched activity is a trampoline that starts another activity
      * immediately, the cookie will be transferred to the next activity.
@@ -2076,12 +2212,7 @@
                 mCustomExitResId = otherOptions.mCustomExitResId;
                 mCustomBackgroundColor = otherOptions.mCustomBackgroundColor;
                 mThumbnail = null;
-                if (mAnimationStartedListener != null) {
-                    try {
-                        mAnimationStartedListener.sendResult(null);
-                    } catch (RemoteException e) {
-                    }
-                }
+                sendResultIgnoreErrors(mAnimationStartedListener, null);
                 mAnimationStartedListener = otherOptions.mAnimationStartedListener;
                 break;
             case ANIM_CUSTOM_IN_PLACE:
@@ -2092,12 +2223,7 @@
                 mStartY = otherOptions.mStartY;
                 mWidth = otherOptions.mWidth;
                 mHeight = otherOptions.mHeight;
-                if (mAnimationStartedListener != null) {
-                    try {
-                        mAnimationStartedListener.sendResult(null);
-                    } catch (RemoteException e) {
-                    }
-                }
+                sendResultIgnoreErrors(mAnimationStartedListener, null);
                 mAnimationStartedListener = null;
                 break;
             case ANIM_THUMBNAIL_SCALE_UP:
@@ -2109,12 +2235,7 @@
                 mStartY = otherOptions.mStartY;
                 mWidth = otherOptions.mWidth;
                 mHeight = otherOptions.mHeight;
-                if (mAnimationStartedListener != null) {
-                    try {
-                        mAnimationStartedListener.sendResult(null);
-                    } catch (RemoteException e) {
-                    }
-                }
+                sendResultIgnoreErrors(mAnimationStartedListener, null);
                 mAnimationStartedListener = otherOptions.mAnimationStartedListener;
                 break;
             case ANIM_SCENE_TRANSITION:
@@ -2131,6 +2252,9 @@
         mRemoteAnimationAdapter = otherOptions.mRemoteAnimationAdapter;
         mLaunchIntoPipParams = otherOptions.mLaunchIntoPipParams;
         mIsEligibleForLegacyPermissionPrompt = otherOptions.mIsEligibleForLegacyPermissionPrompt;
+
+        sendResultIgnoreErrors(mAnimationAbortListener, null);
+        mAnimationAbortListener = otherOptions.mAnimationAbortListener;
     }
 
     /**
@@ -2329,6 +2453,8 @@
         if (mDisableStartingWindow) {
             b.putBoolean(KEY_DISABLE_STARTING_WINDOW, mDisableStartingWindow);
         }
+        b.putBinder(KEY_ANIM_ABORT_LISTENER,
+                mAnimationAbortListener != null ? mAnimationAbortListener.asBinder() : null);
         return b;
     }
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 949e2ba..5d2a26e 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -319,6 +319,10 @@
     public static final int SERVICE_DONE_EXECUTING_START = 1;
     /** Type for IActivityManager.serviceDoneExecuting: done stopping (destroying) service */
     public static final int SERVICE_DONE_EXECUTING_STOP = 2;
+    /** Type for IActivityManager.serviceDoneExecuting: done with an onRebind call */
+    public static final int SERVICE_DONE_EXECUTING_REBIND = 3;
+    /** Type for IActivityManager.serviceDoneExecuting: done with an onUnbind call */
+    public static final int SERVICE_DONE_EXECUTING_UNBIND = 4;
 
     /** Use foreground GC policy (less pause time) and higher JIT weight. */
     private static final int VM_PROCESS_STATE_JANK_PERCEPTIBLE = 0;
@@ -4882,7 +4886,7 @@
             mServices.put(data.token, service);
             try {
                 ActivityManager.getService().serviceDoneExecuting(
-                        data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
+                        data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0, null);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -4913,7 +4917,7 @@
                     } else {
                         s.onRebind(data.intent);
                         ActivityManager.getService().serviceDoneExecuting(
-                                data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
+                                data.token, SERVICE_DONE_EXECUTING_REBIND, 0, 0, data.intent);
                     }
                 } catch (RemoteException ex) {
                     throw ex.rethrowFromSystemServer();
@@ -4943,7 +4947,7 @@
                                 data.token, data.intent, doRebind);
                     } else {
                         ActivityManager.getService().serviceDoneExecuting(
-                                data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
+                                data.token, SERVICE_DONE_EXECUTING_UNBIND, 0, 0, data.intent);
                     }
                 } catch (RemoteException ex) {
                     throw ex.rethrowFromSystemServer();
@@ -5057,7 +5061,7 @@
 
                 try {
                     ActivityManager.getService().serviceDoneExecuting(
-                            data.token, SERVICE_DONE_EXECUTING_START, data.startId, res);
+                            data.token, SERVICE_DONE_EXECUTING_START, data.startId, res, null);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
@@ -5089,7 +5093,7 @@
 
                 try {
                     ActivityManager.getService().serviceDoneExecuting(
-                            token, SERVICE_DONE_EXECUTING_STOP, 0, 0);
+                            token, SERVICE_DONE_EXECUTING_STOP, 0, 0, null);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
@@ -6792,6 +6796,7 @@
                             }
                         }
                         if (killApp) {
+                            // Keep in sync with "perhaps it was removed" case below.
                             mPackages.remove(packages[i]);
                             mResourcePackages.remove(packages[i]);
                         }
@@ -6834,23 +6839,24 @@
                                                 PackageManager.GET_SHARED_LIBRARY_FILES,
                                                 UserHandle.myUserId());
 
-                                if (mActivities.size() > 0) {
-                                    for (ActivityClientRecord ar : mActivities.values()) {
-                                        if (ar.activityInfo.applicationInfo.packageName
-                                                .equals(packageName)) {
-                                            ar.activityInfo.applicationInfo = aInfo;
-                                            ar.packageInfo = pkgInfo;
+                                if (aInfo != null) {
+                                    if (mActivities.size() > 0) {
+                                        for (ActivityClientRecord ar : mActivities.values()) {
+                                            if (ar.activityInfo.applicationInfo.packageName
+                                                    .equals(packageName)) {
+                                                ar.activityInfo.applicationInfo = aInfo;
+                                                ar.packageInfo = pkgInfo;
+                                            }
                                         }
                                     }
-                                }
 
-                                final String[] oldResDirs = { pkgInfo.getResDir() };
+                                    final String[] oldResDirs = {pkgInfo.getResDir()};
 
-                                final ArrayList<String> oldPaths = new ArrayList<>();
-                                LoadedApk.makePaths(this, pkgInfo.getApplicationInfo(), oldPaths);
-                                pkgInfo.updateApplicationInfo(aInfo, oldPaths);
+                                    final ArrayList<String> oldPaths = new ArrayList<>();
+                                    LoadedApk.makePaths(
+                                            this, pkgInfo.getApplicationInfo(), oldPaths);
+                                    pkgInfo.updateApplicationInfo(aInfo, oldPaths);
 
-                                synchronized (mResourcesManager) {
                                     // Update affected Resources objects to use new ResourcesImpl
                                     mResourcesManager.appendPendingAppInfoUpdate(oldResDirs,
                                             aInfo);
@@ -6858,6 +6864,12 @@
                                 }
                             } catch (RemoteException e) {
                             }
+                        } else {
+                            // No package, perhaps it was removed?
+                            Slog.e(TAG, "Package [" + packages[i] + "] reported as REPLACED,"
+                                    + " but missing application info. Assuming REMOVED.");
+                            mPackages.remove(packages[i]);
+                            mResourcePackages.remove(packages[i]);
                         }
                     }
                 }
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 1db1caf..00c4b0f 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1533,16 +1533,31 @@
     public static final int OP_RESERVED_FOR_TESTING = AppProtoEnums.APP_OP_RESERVED_FOR_TESTING;
 
     /**
-     * Rapid clearing of notifications by a notification listener, see b/289080543 for details
+     * Rapid clearing of notifications by a notification listener
      *
      * @hide
      */
+    // See b/289080543 for more details
     public static final int OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER =
             AppProtoEnums.APP_OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER;
 
+    /**
+     * See {@link #OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER}.
+     * @hide
+     */
+    public static final int OP_READ_SYSTEM_GRAMMATICAL_GENDER =
+            AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
+
+    /**
+     * Allows an app whose primary use case is to backup or sync content to run longer jobs.
+     *
+     * @hide
+     */
+    public static final int OP_RUN_BACKUP_JOBS = AppProtoEnums.APP_OP_RUN_BACKUP_JOBS;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 143;
+    public static final int _NUM_OP = 145;
 
     /**
      * All app ops represented as strings.
@@ -1692,6 +1707,7 @@
             OPSTR_ENABLE_MOBILE_DATA_BY_USER,
             OPSTR_RESERVED_FOR_TESTING,
             OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER,
+            OPSTR_RUN_BACKUP_JOBS,
     })
     public @interface AppOpString {}
 
@@ -2180,6 +2196,8 @@
      *
      * @hide
      */
+    @FlaggedApi(android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+    @SystemApi
     public static final String OPSTR_ACCESS_RESTRICTED_SETTINGS =
             "android:access_restricted_settings";
 
@@ -2364,15 +2382,31 @@
             "android:reserved_for_testing";
 
     /**
-     * Rapid clearing of notifications by a notification listener, see b/289080543 for details
+     * Rapid clearing of notifications by a notification listener
      *
      * @hide
      */
+    // See b/289080543 for more details
     @SystemApi
     @FlaggedApi(FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED)
     public static final String OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER =
             "android:rapid_clear_notifications_by_listener";
 
+    /**
+     * Allows an application to read the system grammatical gender.
+     *
+     * @hide
+     */
+    public static final String OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER =
+            "android:read_system_grammatical_gender";
+
+    /**
+     * Allows an app whose primary use case is to backup or sync content to run longer jobs.
+     *
+     * @hide
+     */
+    public static final String OPSTR_RUN_BACKUP_JOBS = "android:run_backup_jobs";
+
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
     /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2484,6 +2518,8 @@
             OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
             OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
             OP_MEDIA_ROUTING_CONTROL,
+            OP_READ_SYSTEM_GRAMMATICAL_GENDER,
+            OP_RUN_BACKUP_JOBS,
     };
 
     static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2936,6 +2972,13 @@
                 OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER,
                 "RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER")
                 .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_READ_SYSTEM_GRAMMATICAL_GENDER,
+                OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER, "READ_SYSTEM_GRAMMATICAL_GENDER")
+                // will make it an app-op permission in the future.
+                // .setPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER)
+                .build(),
+        new AppOpInfo.Builder(OP_RUN_BACKUP_JOBS, OPSTR_RUN_BACKUP_JOBS, "RUN_BACKUP_JOBS")
+                .setPermission(Manifest.permission.RUN_BACKUP_JOBS).build(),
     };
 
     // The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index 656feb0..c6712c0 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -26,11 +26,19 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.Xml;
 import android.util.proto.ProtoInputStream;
 import android.util.proto.ProtoOutputStream;
 import android.util.proto.WireTypeMismatchException;
 
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -39,12 +47,18 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Iterator;
 import java.util.Map;
-import java.util.Set;
+import java.util.Objects;
 
 /**
- * Provide information related to a processes startup.
+ * Describes information related to an application process's startup.
+ *
+ * <p>
+ * Many aspects concerning why and how an applications process was started are valuable for apps
+ * both for logging and for potential behavior changes. Reason for process start, start type,
+ * start times, throttling, and other useful diagnostic data can be obtained from
+ * {@link ApplicationStartInfo} records.
+ * </p>
  */
 @FlaggedApi(Flags.FLAG_APP_START_INFO)
 public final class ApplicationStartInfo implements Parcelable {
@@ -210,6 +224,11 @@
     private int mDefiningUid;
 
     /**
+     * @see #getPackageName
+     */
+    private String mPackageName;
+
+    /**
      * @see #getProcessName
      */
     private String mProcessName;
@@ -344,6 +363,14 @@
     }
 
     /**
+     * @see #getPackageName
+     * @hide
+     */
+    public void setPackageName(final String packageName) {
+        mPackageName = intern(packageName);
+    }
+
+    /**
      * @see #getProcessName
      * @hide
      */
@@ -386,7 +413,9 @@
      * @hide
      */
     public void setIntent(Intent startIntent) {
-        mStartIntent = startIntent;
+        if (startIntent != null) {
+            mStartIntent = startIntent.maybeStripForHistory();
+        }
     }
 
     /**
@@ -456,6 +485,15 @@
     }
 
     /**
+     * Name of first package running in this process;
+     *
+     * @hide
+     */
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
      * The actual process name it was running with.
      *
      * <p class="note"> Note: field will be set for any {@link #getStartupState} value.</p>
@@ -512,6 +550,8 @@
     /**
      * The intent used to launch the application.
      *
+     * <p class="note"> Note: Intent is stripped and does not include extras.</p>
+     *
      * <p class="note"> Note: field will be set for any {@link #getStartupState} value.</p>
      */
     @SuppressLint("IntentBuilderName")
@@ -550,15 +590,15 @@
         dest.writeInt(mRealUid);
         dest.writeInt(mPackageUid);
         dest.writeInt(mDefiningUid);
+        dest.writeString(mPackageName);
         dest.writeString(mProcessName);
         dest.writeInt(mReason);
-        dest.writeInt(mStartupTimestampsNs.size());
-        Set<Map.Entry<Integer, Long>> timestampEntrySet = mStartupTimestampsNs.entrySet();
-        Iterator<Map.Entry<Integer, Long>> iter = timestampEntrySet.iterator();
-        while (iter.hasNext()) {
-            Map.Entry<Integer, Long> entry = iter.next();
-            dest.writeInt(entry.getKey());
-            dest.writeLong(entry.getValue());
+        dest.writeInt(mStartupTimestampsNs == null ? 0 : mStartupTimestampsNs.size());
+        if (mStartupTimestampsNs != null) {
+            for (int i = 0; i < mStartupTimestampsNs.size(); i++) {
+                dest.writeInt(mStartupTimestampsNs.keyAt(i));
+                dest.writeLong(mStartupTimestampsNs.valueAt(i));
+            }
         }
         dest.writeInt(mStartType);
         dest.writeParcelable(mStartIntent, flags);
@@ -575,6 +615,7 @@
         mRealUid = other.mRealUid;
         mPackageUid = other.mPackageUid;
         mDefiningUid = other.mDefiningUid;
+        mPackageName = other.mPackageName;
         mProcessName = other.mProcessName;
         mReason = other.mReason;
         mStartupTimestampsNs = other.mStartupTimestampsNs;
@@ -589,6 +630,7 @@
         mRealUid = in.readInt();
         mPackageUid = in.readInt();
         mDefiningUid = in.readInt();
+        mPackageName = intern(in.readString());
         mProcessName = intern(in.readString());
         mReason = in.readInt();
         int starupTimestampCount = in.readInt();
@@ -620,6 +662,12 @@
                 }
             };
 
+    private static final String PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS = "timestamps";
+    private static final String PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP = "timestamp";
+    private static final String PROTO_SERIALIZER_ATTRIBUTE_KEY = "key";
+    private static final String PROTO_SERIALIZER_ATTRIBUTE_TS = "ts";
+    private static final String PROTO_SERIALIZER_ATTRIBUTE_INTENT = "intent";
+
     /**
      * Write to a protocol buffer output stream. Protocol buffer message definition at {@link
      * android.app.ApplicationStartInfoProto}
@@ -640,16 +688,36 @@
         if (mStartupTimestampsNs != null && mStartupTimestampsNs.size() > 0) {
             ByteArrayOutputStream timestampsBytes = new ByteArrayOutputStream();
             ObjectOutputStream timestampsOut = new ObjectOutputStream(timestampsBytes);
-            timestampsOut.writeObject(mStartupTimestampsNs);
+            TypedXmlSerializer serializer = Xml.resolveSerializer(timestampsOut);
+            serializer.startDocument(null, true);
+            serializer.startTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS);
+            for (int i = 0; i < mStartupTimestampsNs.size(); i++) {
+                serializer.startTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP);
+                serializer.attributeInt(null, PROTO_SERIALIZER_ATTRIBUTE_KEY,
+                        mStartupTimestampsNs.keyAt(i));
+                serializer.attributeLong(null, PROTO_SERIALIZER_ATTRIBUTE_TS,
+                        mStartupTimestampsNs.valueAt(i));
+                serializer.endTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP);
+            }
+            serializer.endTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS);
+            serializer.endDocument();
             proto.write(ApplicationStartInfoProto.STARTUP_TIMESTAMPS,
                     timestampsBytes.toByteArray());
+            timestampsOut.close();
         }
         proto.write(ApplicationStartInfoProto.START_TYPE, mStartType);
         if (mStartIntent != null) {
-            Parcel parcel = Parcel.obtain();
-            mStartIntent.writeToParcel(parcel, 0);
-            proto.write(ApplicationStartInfoProto.START_INTENT, parcel.marshall());
-            parcel.recycle();
+            ByteArrayOutputStream intentBytes = new ByteArrayOutputStream();
+            ObjectOutputStream intentOut = new ObjectOutputStream(intentBytes);
+            TypedXmlSerializer serializer = Xml.resolveSerializer(intentOut);
+            serializer.startDocument(null, true);
+            serializer.startTag(null, PROTO_SERIALIZER_ATTRIBUTE_INTENT);
+            mStartIntent.saveToXml(serializer);
+            serializer.endTag(null, PROTO_SERIALIZER_ATTRIBUTE_INTENT);
+            serializer.endDocument();
+            proto.write(ApplicationStartInfoProto.START_INTENT,
+                    intentBytes.toByteArray());
+            intentOut.close();
         }
         proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode);
         proto.end(token);
@@ -693,21 +761,40 @@
                     ByteArrayInputStream timestampsBytes = new ByteArrayInputStream(proto.readBytes(
                             ApplicationStartInfoProto.STARTUP_TIMESTAMPS));
                     ObjectInputStream timestampsIn = new ObjectInputStream(timestampsBytes);
-                    mStartupTimestampsNs = (ArrayMap<Integer, Long>) timestampsIn.readObject();
+                    mStartupTimestampsNs = new ArrayMap<Integer, Long>();
+                    try {
+                        TypedXmlPullParser parser = Xml.resolvePullParser(timestampsIn);
+                        XmlUtils.beginDocument(parser, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS);
+                        int depth = parser.getDepth();
+                        while (XmlUtils.nextElementWithin(parser, depth)) {
+                            if (PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP.equals(parser.getName())) {
+                                int key = parser.getAttributeInt(null,
+                                        PROTO_SERIALIZER_ATTRIBUTE_KEY);
+                                long ts = parser.getAttributeLong(null,
+                                        PROTO_SERIALIZER_ATTRIBUTE_TS);
+                                mStartupTimestampsNs.put(key, ts);
+                            }
+                        }
+                    } catch (XmlPullParserException e) {
+                        // Timestamps lost
+                    }
+                    timestampsIn.close();
                     break;
                 case (int) ApplicationStartInfoProto.START_TYPE:
                     mStartType = proto.readInt(ApplicationStartInfoProto.START_TYPE);
                     break;
                 case (int) ApplicationStartInfoProto.START_INTENT:
-                    byte[] startIntentBytes = proto.readBytes(
-                        ApplicationStartInfoProto.START_INTENT);
-                    if (startIntentBytes.length > 0) {
-                        Parcel parcel = Parcel.obtain();
-                        parcel.unmarshall(startIntentBytes, 0, startIntentBytes.length);
-                        parcel.setDataPosition(0);
-                        mStartIntent = Intent.CREATOR.createFromParcel(parcel);
-                        parcel.recycle();
+                    ByteArrayInputStream intentBytes = new ByteArrayInputStream(proto.readBytes(
+                            ApplicationStartInfoProto.START_INTENT));
+                    ObjectInputStream intentIn = new ObjectInputStream(intentBytes);
+                    try {
+                        TypedXmlPullParser parser = Xml.resolvePullParser(intentIn);
+                        XmlUtils.beginDocument(parser, PROTO_SERIALIZER_ATTRIBUTE_INTENT);
+                        mStartIntent = Intent.restoreFromXml(parser);
+                    } catch (XmlPullParserException e) {
+                        // Intent lost
                     }
+                    intentIn.close();
                     break;
                 case (int) ApplicationStartInfoProto.LAUNCH_MODE:
                     mLaunchMode = proto.readInt(ApplicationStartInfoProto.LAUNCH_MODE);
@@ -730,6 +817,7 @@
                 .append(" definingUid=").append(mDefiningUid)
                 .append(" user=").append(UserHandle.getUserId(mPackageUid))
                 .append('\n')
+                .append(" package=").append(mPackageName)
                 .append(" process=").append(mProcessName)
                 .append(" startupState=").append(mStartupState)
                 .append(" reason=").append(reasonToString(mReason))
@@ -740,13 +828,11 @@
             sb.append(" intent=").append(mStartIntent.toString())
                 .append('\n');
         }
-        if (mStartupTimestampsNs.size() > 0) {
+        if (mStartupTimestampsNs != null && mStartupTimestampsNs.size() > 0) {
             sb.append(" timestamps: ");
-            Set<Map.Entry<Integer, Long>> timestampEntrySet = mStartupTimestampsNs.entrySet();
-            Iterator<Map.Entry<Integer, Long>> iter = timestampEntrySet.iterator();
-            while (iter.hasNext()) {
-                Map.Entry<Integer, Long> entry = iter.next();
-                sb.append(entry.getKey()).append("=").append(entry.getValue()).append(" ");
+            for (int i = 0; i < mStartupTimestampsNs.size(); i++) {
+                sb.append(mStartupTimestampsNs.keyAt(i)).append("=").append(mStartupTimestampsNs
+                        .valueAt(i)).append(" ");
             }
             sb.append('\n');
         }
@@ -780,4 +866,35 @@
             default -> "";
         };
     }
+
+    /** @hide */
+    @Override
+    public boolean equals(@Nullable Object other) {
+        if (other == null || !(other instanceof ApplicationStartInfo)) {
+            return false;
+        }
+        final ApplicationStartInfo o = (ApplicationStartInfo) other;
+        return mPid == o.mPid && mRealUid == o.mRealUid && mPackageUid == o.mPackageUid
+            && mDefiningUid == o.mDefiningUid && mReason == o.mReason
+            && mStartupState == o.mStartupState && mStartType == o.mStartType
+            && mLaunchMode == o.mLaunchMode && TextUtils.equals(mProcessName, o.mProcessName)
+            && timestampsEquals(o);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPid, mRealUid, mPackageUid, mDefiningUid, mReason, mStartupState,
+                mStartType, mLaunchMode, mProcessName,
+                mStartupTimestampsNs);
+    }
+
+    private boolean timestampsEquals(@NonNull ApplicationStartInfo other) {
+        if (mStartupTimestampsNs == null && other.mStartupTimestampsNs == null) {
+            return true;
+        }
+        if (mStartupTimestampsNs == null || other.mStartupTimestampsNs == null) {
+            return false;
+        }
+        return mStartupTimestampsNs.equals(other.mStartupTimestampsNs);
+    }
 }
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index 5b354fc..d57a4e5 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -23,7 +23,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.TestApi;
 import android.app.NotificationManager.InterruptionFilter;
 import android.content.ComponentName;
 import android.net.Uri;
@@ -113,8 +112,8 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface Type {}
 
-    /** Used to track which rule variables have been modified by the user.
-     * Should be checked against the bitmask {@link #getUserModifiedFields()}.
+    /**
+     * Enum for the user-modifiable fields in this object.
      * @hide
      */
     @IntDef(flag = true, prefix = { "FIELD_" }, value = {
@@ -128,13 +127,11 @@
      * @hide
      */
     @FlaggedApi(Flags.FLAG_MODES_API)
-    @TestApi
     public static final int FIELD_NAME = 1 << 0;
     /**
      * @hide
      */
     @FlaggedApi(Flags.FLAG_MODES_API)
-    @TestApi
     public static final int FIELD_INTERRUPTION_FILTER = 1 << 1;
 
     private boolean enabled;
@@ -153,7 +150,6 @@
     private int mIconResId;
     private String mTriggerDescription;
     private boolean mAllowManualInvocation;
-    private @ModifiableField int mUserModifiedFields; // Bitwise representation
 
     /**
      * The maximum string length for any string contained in this automatic zen rule. This pertains
@@ -256,7 +252,6 @@
             mIconResId = source.readInt();
             mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH);
             mType = source.readInt();
-            mUserModifiedFields = source.readInt();
         }
     }
 
@@ -307,8 +302,7 @@
      * Returns whether this rule's name has been modified by the user.
      * @hide
      */
-    // TODO: b/310620812 - Replace with mUserModifiedFields & FIELD_NAME once
-    //  FLAG_MODES_API is inlined.
+    // TODO: b/310620812 - Consider removing completely. Seems not be used anywhere except tests.
     public boolean isModified() {
         return mModified;
     }
@@ -506,32 +500,6 @@
         return type;
     }
 
-    /**
-     * Gets the bitmask representing which fields are user modified. Bits are set using
-     * {@link ModifiableField}.
-     * @hide
-     */
-    @FlaggedApi(Flags.FLAG_MODES_API)
-    @TestApi
-    public @ModifiableField int getUserModifiedFields() {
-        return mUserModifiedFields;
-    }
-
-    /**
-     * Returns {@code true} if the {@link AutomaticZenRule} can be updated.
-     * When this returns {@code false}, calls to
-     * {@link NotificationManager#updateAutomaticZenRule(String, AutomaticZenRule)}) with this rule
-     * will ignore changes to user-configurable fields.
-     */
-    @FlaggedApi(Flags.FLAG_MODES_API)
-    public boolean canUpdate() {
-        // The rule is considered updateable if its bitmask has no user modifications, and
-        // the bitmasks of the policy and device effects have no modification.
-        return mUserModifiedFields == 0
-                && (mZenPolicy == null || mZenPolicy.getUserModifiedFields() == 0)
-                && (mDeviceEffects == null || mDeviceEffects.getUserModifiedFields() == 0);
-    }
-
     @Override
     public int describeContents() {
         return 0;
@@ -560,7 +528,6 @@
             dest.writeInt(mIconResId);
             dest.writeString(mTriggerDescription);
             dest.writeInt(mType);
-            dest.writeInt(mUserModifiedFields);
         }
     }
 
@@ -582,16 +549,14 @@
                     .append(",allowManualInvocation=").append(mAllowManualInvocation)
                     .append(",iconResId=").append(mIconResId)
                     .append(",triggerDescription=").append(mTriggerDescription)
-                    .append(",type=").append(mType)
-                    .append(",userModifiedFields=")
-                    .append(modifiedFieldsToString(mUserModifiedFields));
+                    .append(",type=").append(mType);
         }
 
         return sb.append(']').toString();
     }
 
-    @FlaggedApi(Flags.FLAG_MODES_API)
-    private String modifiedFieldsToString(int bitmask) {
+    /** @hide */
+    public static String fieldsToString(@ModifiableField int bitmask) {
         ArrayList<String> modified = new ArrayList<>();
         if ((bitmask & FIELD_NAME) != 0) {
             modified.add("FIELD_NAME");
@@ -623,8 +588,7 @@
                     && other.mAllowManualInvocation == mAllowManualInvocation
                     && other.mIconResId == mIconResId
                     && Objects.equals(other.mTriggerDescription, mTriggerDescription)
-                    && other.mType == mType
-                    && other.mUserModifiedFields == mUserModifiedFields;
+                    && other.mType == mType;
         }
         return finalEquals;
     }
@@ -634,8 +598,7 @@
         if (Flags.modesApi()) {
             return Objects.hash(enabled, name, interruptionFilter, conditionId, owner,
                     configurationActivity, mZenPolicy, mDeviceEffects, mModified, creationTime,
-                    mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType,
-                    mUserModifiedFields);
+                    mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType);
         }
         return Objects.hash(enabled, name, interruptionFilter, conditionId, owner,
                 configurationActivity, mZenPolicy, mModified, creationTime, mPkg);
@@ -704,7 +667,6 @@
         private boolean mAllowManualInvocation;
         private long mCreationTime;
         private String mPkg;
-        private @ModifiableField int mUserModifiedFields;
 
         public Builder(@NonNull AutomaticZenRule rule) {
             mName = rule.getName();
@@ -721,7 +683,6 @@
             mAllowManualInvocation = rule.isManualInvocationAllowed();
             mCreationTime = rule.getCreationTime();
             mPkg = rule.getPackageName();
-            mUserModifiedFields = rule.mUserModifiedFields;
         }
 
         public Builder(@NonNull String name, @NonNull Uri conditionId) {
@@ -848,19 +809,6 @@
             return this;
         }
 
-        /**
-         * Sets the bitmask representing which fields have been user-modified.
-         * This method should not be used outside of tests. The value of userModifiedFields
-         * should be set based on what values are changed when a rule is populated or updated..
-         * @hide
-         */
-        @FlaggedApi(Flags.FLAG_MODES_API)
-        @TestApi
-        public @NonNull Builder setUserModifiedFields(@ModifiableField int userModifiedFields) {
-            mUserModifiedFields = userModifiedFields;
-            return this;
-        }
-
         public @NonNull AutomaticZenRule build() {
             AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigurationActivity,
                     mConditionId, mPolicy, mInterruptionFilter, mEnabled);
@@ -871,7 +819,6 @@
             rule.mIconResId = mIconResId;
             rule.mAllowManualInvocation = mAllowManualInvocation;
             rule.setPackageName(mPkg);
-            rule.mUserModifiedFields = mUserModifiedFields;
 
             return rule;
         }
diff --git a/core/java/android/app/BackgroundInstallControlManager.java b/core/java/android/app/BackgroundInstallControlManager.java
new file mode 100644
index 0000000..664fceb
--- /dev/null
+++ b/core/java/android/app/BackgroundInstallControlManager.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES;
+import static android.annotation.SystemApi.Client.PRIVILEGED_APPS;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.content.pm.IBackgroundInstallControlService;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import java.util.List;
+
+/**
+ * BackgroundInstallControlManager client allows apps to query apps installed in background.
+ *
+ * <p>Any applications that was installed without an accompanying installer UI activity paired
+ * with recorded user interaction event is considered background installed. This is determined by
+ * analysis of user-activity logs.
+ *
+ * <p>Warning: BackgroundInstallControl should not be considered a definitive
+ * authority of identifying background installed applications. Consumers can use this as a
+ * supplementary signal, but must perform additional due diligence to confirm the install nature
+ * of the package.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_BIC_CLIENT)
+@SystemApi(client = PRIVILEGED_APPS)
+@SystemService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE)
+public final class BackgroundInstallControlManager {
+
+    private static final String TAG = "BackgroundInstallControlManager";
+    private static IBackgroundInstallControlService sService;
+    private final Context mContext;
+
+    BackgroundInstallControlManager(Context context) {
+        mContext = context;
+    }
+
+    private static IBackgroundInstallControlService getService() {
+        if (sService == null) {
+            sService =
+                    IBackgroundInstallControlService.Stub.asInterface(
+                            ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE));
+        }
+        return sService;
+    }
+
+    /**
+     * Returns a full list of {@link PackageInfo} of apps currently installed for the current user
+     * that are considered installed in the background.
+     *
+     * <p>Refer to top level doc {@link BackgroundInstallControlManager} for more details on
+     * background-installed applications.
+     * <p>
+     *
+     * @param flags - Flags will be used to call
+     * {@link PackageManager#getInstalledPackages(PackageInfoFlags)} to retrieve installed packages.
+     * @return A list of packages retrieved from {@link PackageManager} with non-background
+     * installed app filter applied.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_BIC_CLIENT)
+    @SystemApi
+    @RequiresPermission(GET_BACKGROUND_INSTALLED_PACKAGES)
+    public @NonNull List<PackageInfo> getBackgroundInstalledPackages(
+            @PackageManager.PackageInfoFlagsBits long flags) {
+        List<PackageInfo> backgroundInstalledPackages;
+        try {
+            return getService()
+                    .getBackgroundInstalledPackages(flags, mContext.getUserId())
+                    .getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index edeec77..b0f6c46 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -240,7 +240,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private final String mOpPackageName;
     private final @NonNull ContextParams mParams;
-    private final @NonNull AttributionSource mAttributionSource;
+    private @NonNull AttributionSource mAttributionSource;
 
     private final @NonNull ResourcesManager mResourcesManager;
     @UnsupportedAppUsage
@@ -2409,6 +2409,17 @@
         }
     }
 
+    @Override
+    public int checkContentUriPermissionFull(Uri uri, int pid, int uid, int modeFlags) {
+        try {
+            return ActivityManager.getService().checkContentUriPermissionFull(
+                    ContentProvider.getUriWithoutUserId(uri), pid, uid, modeFlags,
+                    resolveUserId(uri));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     @NonNull
     @Override
     public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid,
@@ -2619,10 +2630,9 @@
                 flags | CONTEXT_REGISTER_PACKAGE);
         if (pi != null) {
             ContextImpl c = new ContextImpl(this, mMainThread, pi, ContextParams.EMPTY,
-                    mAttributionSource.getAttributionTag(),
-                    mAttributionSource.getNext(),
-                    null, mToken, new UserHandle(UserHandle.getUserId(application.uid)),
-                    flags, null, null);
+                    mAttributionSource.getAttributionTag(), mAttributionSource.getNext(), null,
+                    mToken, new UserHandle(UserHandle.getUserId(application.uid)), flags, null,
+                    null, mDeviceId, mIsExplicitDeviceId);
 
             final int displayId = getDisplayId();
             final Integer overrideDisplayId = mForceDisplayOverrideInResources
@@ -2668,18 +2678,16 @@
             // The system resources are loaded in every application, so we can safely copy
             // the context without reloading Resources.
             return new ContextImpl(this, mMainThread, mPackageInfo, mParams,
-                    mAttributionSource.getAttributionTag(),
-                    mAttributionSource.getNext(),
-                    null, mToken, user, flags, null, null);
+                    mAttributionSource.getAttributionTag(), mAttributionSource.getNext(), null,
+                    mToken, user, flags, null, null, mDeviceId, mIsExplicitDeviceId);
         }
 
         LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),
                 flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());
         if (pi != null) {
             ContextImpl c = new ContextImpl(this, mMainThread, pi, mParams,
-                    mAttributionSource.getAttributionTag(),
-                    mAttributionSource.getNext(),
-                    null, mToken, user, flags, null, null);
+                    mAttributionSource.getAttributionTag(), mAttributionSource.getNext(), null,
+                    mToken, user, flags, null, null, mDeviceId, mIsExplicitDeviceId);
 
             final int displayId = getDisplayId();
             final Integer overrideDisplayId = mForceDisplayOverrideInResources
@@ -2718,9 +2726,8 @@
         final String[] paths = mPackageInfo.getSplitPaths(splitName);
 
         final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mParams,
-                mAttributionSource.getAttributionTag(),
-                mAttributionSource.getNext(),
-                splitName, mToken, mUser, mFlags, classLoader, null);
+                mAttributionSource.getAttributionTag(), mAttributionSource.getNext(), splitName,
+                mToken, mUser, mFlags, classLoader, null, mDeviceId, mIsExplicitDeviceId);
 
         context.setResources(ResourcesManager.getInstance().getResources(
                 mToken,
@@ -2754,9 +2761,9 @@
         }
 
         ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mParams,
-                mAttributionSource.getAttributionTag(),
-                mAttributionSource.getNext(),
-                mSplitName, mToken, mUser, mFlags, mClassLoader, null);
+                mAttributionSource.getAttributionTag(), mAttributionSource.getNext(), mSplitName,
+                mToken, mUser, mFlags, mClassLoader, null, mDeviceId,
+                mIsExplicitDeviceId);
         context.mIsConfigurationBasedContext = true;
 
         final int displayId = getDisplayId();
@@ -2775,9 +2782,8 @@
         }
 
         ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mParams,
-                mAttributionSource.getAttributionTag(),
-                mAttributionSource.getNext(),
-                mSplitName, mToken, mUser, mFlags, mClassLoader, null);
+                mAttributionSource.getAttributionTag(), mAttributionSource.getNext(), mSplitName,
+                mToken, mUser, mFlags, mClassLoader, null, mDeviceId, mIsExplicitDeviceId);
 
         final int displayId = display.getDisplayId();
 
@@ -2822,14 +2828,9 @@
             }
         }
 
-        ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mParams,
-                mAttributionSource.getAttributionTag(),
-                mAttributionSource.getNext(),
-                mSplitName, mToken, mUser, mFlags, mClassLoader, null);
-
-        context.mDeviceId = deviceId;
-        context.mIsExplicitDeviceId = true;
-        return context;
+        return new ContextImpl(this, mMainThread, mPackageInfo, mParams,
+                mAttributionSource.getAttributionTag(), mAttributionSource.getNext(), mSplitName,
+                mToken, mUser, mFlags, mClassLoader, null, deviceId, true);
     }
 
     @NonNull
@@ -2915,9 +2916,8 @@
     @UiContext
     ContextImpl createWindowContextBase(@NonNull IBinder token, int displayId) {
         ContextImpl baseContext = new ContextImpl(this, mMainThread, mPackageInfo, mParams,
-                mAttributionSource.getAttributionTag(),
-                mAttributionSource.getNext(),
-                mSplitName, token, mUser, mFlags, mClassLoader, null);
+                mAttributionSource.getAttributionTag(), mAttributionSource.getNext(), mSplitName,
+                token, mUser, mFlags, mClassLoader, null, mDeviceId, mIsExplicitDeviceId);
         // Window contexts receive configurations directly from the server and as such do not
         // need to override their display in ResourcesManager.
         baseContext.mForceDisplayOverrideInResources = false;
@@ -2968,7 +2968,8 @@
     public Context createContext(@NonNull ContextParams contextParams) {
         return new ContextImpl(this, mMainThread, mPackageInfo, contextParams,
                 contextParams.getAttributionTag(), contextParams.getNextAttributionSource(),
-                mSplitName, mToken, mUser, mFlags, mClassLoader, null);
+                mSplitName, mToken, mUser, mFlags, mClassLoader, null, mDeviceId,
+                mIsExplicitDeviceId);
     }
 
     @Override
@@ -2982,9 +2983,8 @@
         final int flags = (mFlags & ~Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE)
                 | Context.CONTEXT_DEVICE_PROTECTED_STORAGE;
         return new ContextImpl(this, mMainThread, mPackageInfo, mParams,
-                mAttributionSource.getAttributionTag(),
-                mAttributionSource.getNext(),
-                mSplitName, mToken, mUser, flags, mClassLoader, null);
+                mAttributionSource.getAttributionTag(), mAttributionSource.getNext(), mSplitName,
+                mToken, mUser, flags, mClassLoader, null, mDeviceId, mIsExplicitDeviceId);
     }
 
     @Override
@@ -2992,9 +2992,8 @@
         final int flags = (mFlags & ~Context.CONTEXT_DEVICE_PROTECTED_STORAGE)
                 | Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE;
         return new ContextImpl(this, mMainThread, mPackageInfo, mParams,
-                mAttributionSource.getAttributionTag(),
-                mAttributionSource.getNext(),
-                mSplitName, mToken, mUser, flags, mClassLoader, null);
+                mAttributionSource.getAttributionTag(), mAttributionSource.getNext(), mSplitName,
+                mToken, mUser, flags, mClassLoader, null, mDeviceId, mIsExplicitDeviceId);
     }
 
     @Override
@@ -3087,6 +3086,7 @@
             int deviceId = vdm.getDeviceIdForDisplayId(displayId);
             if (deviceId != mDeviceId) {
                 mDeviceId = deviceId;
+                mAttributionSource = mAttributionSource.withDeviceId(mDeviceId);
                 notifyOnDeviceChangedListeners(mDeviceId);
             }
         }
@@ -3287,7 +3287,8 @@
     static ContextImpl createSystemContext(ActivityThread mainThread) {
         LoadedApk packageInfo = new LoadedApk(mainThread);
         ContextImpl context = new ContextImpl(null, mainThread, packageInfo,
-                ContextParams.EMPTY, null, null, null, null, null, 0, null, null);
+                ContextParams.EMPTY, null, null, null, null, null, 0, null, null,
+                DEVICE_ID_DEFAULT, false);
         context.setResources(packageInfo.getResources());
         context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
                 context.mResourcesManager.getDisplayMetrics());
@@ -3322,7 +3323,8 @@
             String opPackageName) {
         if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
         ContextImpl context = new ContextImpl(null, mainThread, packageInfo,
-            ContextParams.EMPTY, null, null, null, null, null, 0, null, opPackageName);
+                ContextParams.EMPTY, null, null, null, null, null, 0, null, opPackageName,
+                DEVICE_ID_DEFAULT, false);
         context.setResources(packageInfo.getResources());
         context.mContextType = isSystemOrSystemUI(context) ? CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI
                 : CONTEXT_TYPE_NON_UI;
@@ -3360,7 +3362,7 @@
 
         ContextImpl context = new ContextImpl(null, mainThread, packageInfo, ContextParams.EMPTY,
                 attributionTag, null, activityInfo.splitName, activityToken, null, 0, classLoader,
-                null);
+                null, DEVICE_ID_DEFAULT, false);
         context.mContextType = CONTEXT_TYPE_ACTIVITY;
         context.mIsConfigurationBasedContext = true;
 
@@ -3396,7 +3398,8 @@
             @NonNull LoadedApk packageInfo, @NonNull ContextParams params,
             @Nullable String attributionTag, @Nullable AttributionSource nextAttributionSource,
             @Nullable String splitName, @Nullable IBinder token, @Nullable UserHandle user,
-            int flags, @Nullable ClassLoader classLoader, @Nullable String overrideOpPackageName) {
+            int flags, @Nullable ClassLoader classLoader, @Nullable String overrideOpPackageName,
+            int deviceId, boolean isExplicitDeviceId) {
         mOuterContext = this;
         // If creator didn't specify which storage to use, use the default
         // location for application.
@@ -3426,13 +3429,18 @@
 
         String opPackageName;
 
+        mDeviceId = deviceId;
+        mIsExplicitDeviceId = isExplicitDeviceId;
+
         if (container != null) {
             mBasePackageName = container.mBasePackageName;
             opPackageName = container.mOpPackageName;
             setResources(container.mResources);
             mDisplay = container.mDisplay;
-            mDeviceId = container.mDeviceId;
-            mIsExplicitDeviceId = container.mIsExplicitDeviceId;
+            if (!isExplicitDeviceId) {
+                mIsExplicitDeviceId = container.mIsExplicitDeviceId;
+                mDeviceId = container.mDeviceId;
+            }
             mForceDisplayOverrideInResources = container.mForceDisplayOverrideInResources;
             mIsConfigurationBasedContext = container.mIsConfigurationBasedContext;
             mContextType = container.mContextType;
@@ -3455,17 +3463,18 @@
         mOpPackageName = overrideOpPackageName != null ? overrideOpPackageName : opPackageName;
         mParams = Objects.requireNonNull(params);
         mAttributionSource = createAttributionSource(attributionTag, nextAttributionSource,
-                params.getRenouncedPermissions(), params.shouldRegisterAttributionSource());
+                params.getRenouncedPermissions(), params.shouldRegisterAttributionSource(), mDeviceId);
         mContentResolver = new ApplicationContentResolver(this, mainThread);
     }
 
     private @NonNull AttributionSource createAttributionSource(@Nullable String attributionTag,
             @Nullable AttributionSource nextAttributionSource,
-            @Nullable Set<String> renouncedPermissions, boolean shouldRegister) {
+            @Nullable Set<String> renouncedPermissions, boolean shouldRegister,
+            int deviceId) {
         AttributionSource attributionSource = new AttributionSource(Process.myUid(),
                 Process.myPid(), mOpPackageName, attributionTag,
                 (renouncedPermissions != null) ? renouncedPermissions.toArray(new String[0]) : null,
-                getDeviceId(), nextAttributionSource);
+                deviceId, nextAttributionSource);
         // If we want to access protected data on behalf of another app we need to
         // tell the OS that we opt in to participate in the attribution chain.
         if (nextAttributionSource != null || shouldRegister) {
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
index a55121a..483a6e1 100644
--- a/core/java/android/app/GrammaticalInflectionManager.java
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -114,7 +114,7 @@
         }
 
         try {
-            mService.setSystemWideGrammaticalGender(mContext.getUserId(), grammaticalGender);
+            mService.setSystemWideGrammaticalGender(grammaticalGender, mContext.getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -131,7 +131,8 @@
     @Configuration.GrammaticalGender
     public int getSystemGrammaticalGender() {
         try {
-            return mService.getSystemGrammaticalGender(mContext.getUserId());
+            return mService.getSystemGrammaticalGender(mContext.getAttributionSource(),
+                    mContext.getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/HomeVisibilityListener.java b/core/java/android/app/HomeVisibilityListener.java
index 1f5f2e4..5dd7ab0 100644
--- a/core/java/android/app/HomeVisibilityListener.java
+++ b/core/java/android/app/HomeVisibilityListener.java
@@ -69,6 +69,11 @@
     public HomeVisibilityListener() {
         mObserver = new android.app.IProcessObserver.Stub() {
             @Override
+            public void onProcessStarted(int pid, int processUid, int packageUid,
+                    String packageName, String processName) {
+            }
+
+            @Override
             public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) {
                 refreshHomeVisibility();
             }
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 7370fc3..5b044f6 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -119,7 +119,7 @@
 
     oneway void setShowWhenLocked(in IBinder token, boolean showWhenLocked);
     oneway void setInheritShowWhenLocked(in IBinder token, boolean setInheritShownWhenLocked);
-    oneway void setTurnScreenOn(in IBinder token, boolean turnScreenOn);
+    void setTurnScreenOn(in IBinder token, boolean turnScreenOn);
     oneway void setAllowCrossUidActivitySwitchFromBelow(in IBinder token, boolean allowed);
     oneway void reportActivityFullyDrawn(in IBinder token, boolean restoredFromBundle);
     oneway void overrideActivityTransition(IBinder token, boolean open, int enterAnim, int exitAnim,
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 260e985..b063d04 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -116,6 +116,7 @@
      * @throws RemoteException
      * @return Returns A binder token identifying the UidObserver registration.
      */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
     IBinder registerUidObserverForUids(in IUidObserver observer, int which, int cutpoint,
             String callingPackage, in int[] uids);
 
@@ -274,6 +275,7 @@
     int getProcessLimit();
     int checkUriPermission(in Uri uri, int pid, int uid, int mode, int userId,
             in IBinder callerToken);
+    int checkContentUriPermissionFull(in Uri uri, int pid, int uid, int mode, int userId);
     int[] checkUriPermissions(in List<Uri> uris, int pid, int uid, int mode, int userId,
                 in IBinder callerToken);
     void grantUriPermission(in IApplicationThread caller, in String targetPkg, in Uri uri,
@@ -292,7 +294,8 @@
     @UnsupportedAppUsage
     ParceledListSlice getRecentTasks(int maxNum, int flags, int userId);
     @UnsupportedAppUsage
-    oneway void serviceDoneExecuting(in IBinder token, int type, int startId, int res);
+    oneway void serviceDoneExecuting(in IBinder token, int type, int startId, int res,
+            in Intent intent);
     /** @deprecated  Use {@link #getIntentSenderWithFeature} instead */
     @UnsupportedAppUsage(maxTargetSdk=29, publicAlternatives="Use {@link PendingIntent#getIntentSender()} instead")
     IIntentSender getIntentSender(int type, in String packageName, in IBinder token,
@@ -715,7 +718,7 @@
      * @param listener    A listener to for the callback upon completion of startup data collection.
      * @param userId      The userId in the multi-user environment.
      */
-    void setApplicationStartInfoCompleteListener(IApplicationStartInfoCompleteListener listener,
+    void addApplicationStartInfoCompleteListener(IApplicationStartInfoCompleteListener listener,
             int userId);
 
 
@@ -724,7 +727,8 @@
      *
      * @param userId      The userId in the multi-user environment.
      */
-    void clearApplicationStartInfoCompleteListener(int userId);
+    void removeApplicationStartInfoCompleteListener(IApplicationStartInfoCompleteListener listener,
+            int userId);
 
 
     /**
diff --git a/core/java/android/app/IGrammaticalInflectionManager.aidl b/core/java/android/app/IGrammaticalInflectionManager.aidl
index 48a4841..86f2e91 100644
--- a/core/java/android/app/IGrammaticalInflectionManager.aidl
+++ b/core/java/android/app/IGrammaticalInflectionManager.aidl
@@ -1,5 +1,22 @@
+/**
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package android.app;
 
+import android.content.AttributionSource;
 
 /**
  * Internal interface used to control app-specific gender.
@@ -20,10 +37,10 @@
      /**
       * Sets the grammatical gender to system.
       */
-     void setSystemWideGrammaticalGender(int userId, int gender);
+     void setSystemWideGrammaticalGender(int gender, int userId);
 
      /**
       * Gets the grammatical gender from system.
       */
-     int getSystemGrammaticalGender(int userId);
+     int getSystemGrammaticalGender(in AttributionSource attributionSource, int userId);
  }
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index c3adbc3..578105f 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -38,6 +38,7 @@
 import android.service.notification.INotificationListener;
 import android.service.notification.NotificationListenerFilter;
 import android.service.notification.StatusBarNotification;
+import android.service.notification.ZenPolicy;
 import android.app.AutomaticZenRule;
 import android.service.notification.ZenModeConfig;
 
@@ -213,6 +214,7 @@
     boolean isNotificationPolicyAccessGrantedForPackage(String pkg);
     void setNotificationPolicyAccessGranted(String pkg, boolean granted);
     void setNotificationPolicyAccessGrantedForUser(String pkg, int userId, boolean granted);
+    ZenPolicy getDefaultZenPolicy();
     AutomaticZenRule getAutomaticZenRule(String id);
     Map<String, AutomaticZenRule> getAutomaticZenRules();
     // TODO: b/310620812 - Remove getZenRules() when MODES_API is inlined.
diff --git a/core/java/android/app/IProcessObserver.aidl b/core/java/android/app/IProcessObserver.aidl
index 7be3620..5c5e72c 100644
--- a/core/java/android/app/IProcessObserver.aidl
+++ b/core/java/android/app/IProcessObserver.aidl
@@ -18,6 +18,17 @@
 
 /** {@hide} */
 oneway interface IProcessObserver {
+    /**
+     * Invoked when an app process starts up.
+     *
+     * @param pid The pid of the process.
+     * @param processUid The UID associated with the process.
+     * @param packageUid The UID associated with the package.
+     * @param packageName The name of the package.
+     * @param processName The name of the process.
+     */
+    void onProcessStarted(int pid, int processUid, int packageUid,
+                          @utf8InCpp String packageName, @utf8InCpp String processName);
     void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities);
     void onForegroundServicesChanged(int pid, int uid, int serviceTypes);
     void onProcessDied(int pid, int uid);
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index 60b34cd..3b83024 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -95,6 +95,34 @@
     int getNightModeCustomType();
 
     /**
+     * Overlays current Night Mode value.
+     * {@code attentionModeThemeOverlayType}.
+     *
+     * @param attentionModeThemeOverlayType
+     * @hide
+     */
+    @EnforcePermission("MODIFY_DAY_NIGHT_MODE")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)")
+    void setAttentionModeThemeOverlay(int attentionModeThemeOverlayType);
+
+
+    /**
+     * Returns current Attention Mode overlay type.
+     * <p>
+     * returns
+     *  <ul>
+     *    <li>{@link #MODE_ATTENTION_OFF}</li>
+     *    <li>{@link #MODE_ATTENTION_NIGHT}</li>
+     *    <li>{@link #MODE_ATTENTION_DAY}</li>
+     *  </ul>
+     * </p>
+     * @hide
+     */
+    @EnforcePermission("MODIFY_DAY_NIGHT_MODE")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)")
+    int getAttentionModeThemeOverlay();
+
+    /**
      * Sets the dark mode for the given application. This setting is persisted and will override the
      * system configuration for this application.
      *   1 - notnight mode
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index d7d6546..5acb9b5 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Bundle;
@@ -26,6 +27,8 @@
 import android.content.ComponentName;
 import android.app.WallpaperColors;
 
+import java.util.List;
+
 /** @hide */
 interface IWallpaperManager {
 
@@ -39,15 +42,21 @@
      *   FLAG_SET_SYSTEM
      *   FLAG_SET_LOCK
      *
-     * A 'null' cropHint rectangle is explicitly permitted as a sentinel for "whatever
-     * the source image's bounding rect is."
+     * 'screenOrientations' and 'crops' define how the wallpaper will be positioned for
+     * different screen orientations. If some screen orientations are missing, crops for these
+     * orientations will be added by the system.
+     *
+     * If 'screenOrientations' is null, 'crops' can be null or a singleton list. The system will
+     * fit the provided crop (or the whole image, if 'crops' is 'null') for the current device
+     * orientation, and add crops for the missing orientations.
      *
      * The completion callback's "onWallpaperChanged()" method is invoked when the
      * new wallpaper content is ready to display.
      */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.SET_WALLPAPER)")
     ParcelFileDescriptor setWallpaper(String name, in String callingPackage,
-            in Rect cropHint, boolean allowBackup, out Bundle extras, int which,
-            IWallpaperManagerCallback completion, int userId);
+            in int[] screenOrientations, in List<Rect> crops, boolean allowBackup,
+            out Bundle extras, int which, IWallpaperManagerCallback completion, int userId);
 
     /**
      * Set the live wallpaper.
@@ -78,6 +87,30 @@
             boolean getCropped);
 
     /**
+     * For a given user and a list of display sizes, get a list of Rect representing the
+     * area of the current wallpaper that is displayed for each display size.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL)")
+    @SuppressWarnings(value={"untyped-collection"})
+    List getBitmapCrops(in List<Point> displaySizes, int which, boolean originalBitmap, int userId);
+
+    /**
+     * Return how a bitmap of a given size would be cropped for a given list of display sizes when
+     * set with the given suggested crops.
+     * @hide
+     */
+    @SuppressWarnings(value={"untyped-collection"})
+    List getFutureBitmapCrops(in Point bitmapSize, in List<Point> displaySizes,
+            in int[] screenOrientations, in List<Rect> crops);
+
+    /**
+     * Return how a bitmap of a given size would be cropped when set with the given suggested crops.
+     * @hide
+     */
+    @SuppressWarnings(value={"untyped-collection"})
+    Rect getBitmapCrop(in Point bitmapSize, in int[] screenOrientations, in List<Rect> crops);
+
+    /**
      * Retrieve the given user's current wallpaper ID of the given kind.
      */
     int getWallpaperIdForUser(int which, int userId);
@@ -245,11 +278,4 @@
      * @hide
      */
     boolean isStaticWallpaper(int which);
-
-    /**
-     * Temporary method for project b/270726737.
-     * Return true if the wallpaper supports different crops for different display dimensions.
-     * @hide
-     */
-     boolean isMultiCropEnabled();
 }
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 2162e3a..68512b8 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -79,6 +79,7 @@
  * implementation is described to the system through an AndroidManifest.xml's
  * &lt;instrumentation&gt; tag.
  */
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
 public class Instrumentation {
 
     /**
@@ -132,6 +133,7 @@
     private UiAutomation mUiAutomation;
     private final Object mAnimationCompleteLock = new Object();
 
+    @android.ravenwood.annotation.RavenwoodKeep
     public Instrumentation() {
     }
 
@@ -142,6 +144,7 @@
      * reflection, but it will serve as noticeable discouragement from
      * doing such a thing.
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     private void checkInstrumenting(String method) {
         // Check if we have an instrumentation context, as init should only get called by
         // the system in startup processes that are being instrumented.
@@ -151,6 +154,11 @@
         }
     }
 
+    private void checkInstrumenting$ravenwood(String method) {
+        // At the moment, Ravenwood doesn't attach a Context, but we're only ever
+        // running code as part of tests, so we continue quietly
+    }
+
     /**
      * Returns if it is being called in an instrumentation environment.
      *
@@ -2504,6 +2512,7 @@
      * Takes control of the execution of messages on the specified looper until
      * {@link TestLooperManager#release} is called.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public TestLooperManager acquireLooperManager(Looper looper) {
         checkInstrumenting("acquireLooperManager");
         return new TestLooperManager(looper);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 476232c..a81ad3c 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5487,6 +5487,15 @@
             return mColors;
         }
 
+        /**
+         * @param isHeader If the notification is a notification header
+         * @return An instance of mColors after resolving the palette
+         */
+        private Colors getColors(boolean isHeader) {
+            mColors.resolvePalette(mContext, mN.color, !isHeader && mN.isColorized(), mInNightMode);
+            return mColors;
+        }
+
         private void updateBackgroundColor(RemoteViews contentView,
                 StandardTemplateParams p) {
             if (isBackgroundColorized(p)) {
@@ -5652,7 +5661,7 @@
                 pillColor = Colors.flattenAlpha(
                         getColors(p).getTertiaryFixedDimAccentColor(), bgColor);
                 textColor = Colors.flattenAlpha(
-                        getColors(p).getOnTertiaryAccentTextColor(), pillColor);
+                        getColors(p).getOnTertiaryFixedAccentTextColor(), pillColor);
             }
             contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor);
             contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
@@ -6618,6 +6627,23 @@
             return getColors(p).getContrastColor();
         }
 
+        /**
+         * Gets the foreground color of the small icon.  If the notification is colorized, this
+         * is the primary text color, otherwise it's the contrast-adjusted app-provided color.
+         * @hide
+         */
+        public @ColorInt int getSmallIconColor(boolean isHeader) {
+            return getColors(/* isHeader = */ isHeader).getContrastColor();
+        }
+
+        /**
+         * Gets the background color of the notification.
+         * @hide
+         */
+        public @ColorInt int getBackgroundColor(boolean isHeader) {
+            return getColors(/* isHeader = */ isHeader).getBackgroundColor();
+        }
+
         /** @return the theme's accent color for colored UI elements. */
         private @ColorInt int getPrimaryAccentColor(StandardTemplateParams p) {
             return getColors(p).getPrimaryAccentColor();
@@ -8532,6 +8558,8 @@
             boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT;
             boolean isHeaderless = !isConversationLayout && isCollapsed;
 
+            //TODO (b/217799515): ensure mConversationTitle always returns the correct
+            // conversationTitle, probably set mConversationTitle = conversationTitle after this
             CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
                     ? super.mBigContentTitle
                     : mConversationTitle;
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 0b6e24c..366b45b 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1750,6 +1750,20 @@
             @NonNull ComponentName listener, boolean granted) {
         setNotificationListenerAccessGranted(listener, granted, true);
     }
+    /**
+     * Gets the device-default notification policy as a ZenPolicy.
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public @NonNull ZenPolicy getDefaultZenPolicy() {
+        INotificationManager service = getService();
+        try {
+            return service.getDefaultZenPolicy();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 
     /**
      * For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above, the
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 47d19ed..3b5bba2 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -57,6 +57,7 @@
 
 # GrammaticalInflectionManager
 per-file *GrammaticalInflection* = file:/services/core/java/com/android/server/grammaticalinflection/OWNERS
+per-file grammatical_inflection_manager.aconfig = file:/services/core/java/com/android/server/grammaticalinflection/OWNERS
 
 # KeyguardManager
 per-file KeyguardManager.java = file:/services/core/java/com/android/server/locksettings/OWNERS
@@ -89,8 +90,8 @@
 per-file pinner-client.aconfig = file:/core/java/android/app/pinner/OWNERS
 
 # BackgroundInstallControlManager
-per-file BackgroundInstallControlManager.java = file:/services/core/java/com/android/server/pm/OWNERS
-per-file background_install_control_manager.aconfig = file:/services/core/java/com/android/server/pm/OWNERS
+per-file BackgroundInstallControlManager.java = file:/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
+per-file background_install_control_manager.aconfig = file:/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
 
 # ResourcesManager
 per-file ResourcesManager.java = file:RESOURCES_OWNERS
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 0261f0a..1ac08ac 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -179,6 +179,14 @@
     @Overridable
     public static final long BLOCK_MUTABLE_IMPLICIT_PENDING_INTENT = 236704164L;
 
+    /**
+     * Validate options passed in as bundle.
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final long PENDING_INTENT_OPTIONS_CHECK = 320664730L;
+
     /** @hide */
     @IntDef(flag = true,
             value = {
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 9cf732a..397b63f 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -31,6 +31,7 @@
 import android.app.blob.BlobStoreManagerFrameworkInitializer;
 import android.app.contentsuggestions.ContentSuggestionsManager;
 import android.app.contentsuggestions.IContentSuggestionsManager;
+import android.app.ecm.EnhancedConfirmationFrameworkInitializer;
 import android.app.job.JobSchedulerFrameworkInitializer;
 import android.app.people.PeopleManager;
 import android.app.prediction.AppPredictionManager;
@@ -209,6 +210,7 @@
 import android.permission.PermissionManager;
 import android.print.IPrintManager;
 import android.print.PrintManager;
+import android.provider.ContactKeysManager;
 import android.safetycenter.SafetyCenterFrameworkInitializer;
 import android.scheduling.SchedulingFrameworkInitializer;
 import android.security.FileIntegrityManager;
@@ -1603,6 +1605,18 @@
                     }
                 });
 
+        registerService(Context.CONTACT_KEYS_SERVICE, ContactKeysManager.class,
+                new CachedServiceFetcher<ContactKeysManager>() {
+                    @Override
+                    public ContactKeysManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        if (!android.provider.Flags.userKeys()) {
+                            throw new ServiceNotFoundException(
+                                    "ContactKeysManager is not supported");
+                        }
+                        return new ContactKeysManager(ctx);
+                    }});
+
         sInitializing = true;
         try {
             // Note: the following functions need to be @SystemApis, once they become mainline
@@ -1631,6 +1645,9 @@
             OnDevicePersonalizationFrameworkInitializer.registerServiceWrappers();
             DeviceLockFrameworkInitializer.registerServiceWrappers();
             VirtualizationFrameworkInitializer.registerServiceWrappers();
+            if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()) {
+                EnhancedConfirmationFrameworkInitializer.registerServiceWrappers();
+            }
         } finally {
             // If any of the above code throws, we're in a pretty bad shape and the process
             // will likely crash, but we'll reset it just in case there's an exception handler...
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 0ccb9cd..a271328 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -265,6 +266,60 @@
      */
     public static final int MODE_NIGHT_YES = 2;
 
+    /** @hide */
+    @IntDef(prefix = { "MODE_ATTENTION_THEME_OVERLAY_" }, value = {
+            MODE_ATTENTION_THEME_OVERLAY_OFF,
+            MODE_ATTENTION_THEME_OVERLAY_NIGHT,
+            MODE_ATTENTION_THEME_OVERLAY_DAY
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AttentionModeThemeOverlayType {}
+
+    /** @hide */
+    @IntDef(prefix = { "MODE_ATTENTION_THEME_OVERLAY_" }, value = {
+            MODE_ATTENTION_THEME_OVERLAY_OFF,
+            MODE_ATTENTION_THEME_OVERLAY_NIGHT,
+            MODE_ATTENTION_THEME_OVERLAY_DAY,
+            MODE_ATTENTION_THEME_OVERLAY_UNKNOWN
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AttentionModeThemeOverlayReturnType {}
+
+    /**
+     * Constant for {@link #setAttentionModeThemeOverlay(int)} (int)} and {@link
+     * #getAttentionModeThemeOverlay()}: Keeps night mode as set by {@link #setNightMode(int)}.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    @TestApi
+    public static final int MODE_ATTENTION_THEME_OVERLAY_OFF = 1000;
+
+    /**
+     * Constant for {@link #setAttentionModeThemeOverlay(int)} (int)} and {@link
+     * #getAttentionModeThemeOverlay()}: Maintains night mode always on.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    @TestApi
+    public static final int MODE_ATTENTION_THEME_OVERLAY_NIGHT = 1001;
+
+    /**
+     * Constant for {@link #setAttentionModeThemeOverlay(int)} (int)} and {@link
+     * #getAttentionModeThemeOverlay()}: Maintains night mode always off (Light).
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    @TestApi
+    public static final int MODE_ATTENTION_THEME_OVERLAY_DAY = 1002;
+
+    /**
+     * Constant for {@link #getAttentionModeThemeOverlay()}: Error communication with server.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    @TestApi
+    public static final int MODE_ATTENTION_THEME_OVERLAY_UNKNOWN = -1;
+
     /**
      * Granular types for {@link #setNightModeCustomType(int)}
      * @hide
@@ -733,6 +788,55 @@
     }
 
     /**
+     * Overlays current Attention mode Night Mode overlay.
+     * {@code attentionModeThemeOverlayType}.
+     *
+     * @throws IllegalArgumentException if passed an unsupported type to
+     *                                  {@code AttentionModeThemeOverlayType}.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+    public void setAttentionModeThemeOverlay(
+            @AttentionModeThemeOverlayType int attentionModeThemeOverlayType) {
+        if (sGlobals != null) {
+            try {
+                sGlobals.mService.setAttentionModeThemeOverlay(attentionModeThemeOverlayType);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Returns the currently configured Attention Mode theme overlay.
+     * <p>
+     * May be one of:
+     *   <ul>
+     *     <li>{@link #MODE_ATTENTION_THEME_OVERLAY_OFF}</li>
+     *     <li>{@link #MODE_ATTENTION_THEME_OVERLAY_NIGHT}</li>
+     *     <li>{@link #MODE_ATTENTION_THEME_OVERLAY_DAY}</li>
+     *     <li>{@link #MODE_ATTENTION_THEME_OVERLAY_UNKNOWN}</li>
+     *   </ul>
+     * </p>
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+    public @AttentionModeThemeOverlayReturnType int getAttentionModeThemeOverlay() {
+        if (sGlobals != null) {
+            try {
+                return sGlobals.mService.getAttentionModeThemeOverlay();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return MODE_ATTENTION_THEME_OVERLAY_UNKNOWN;
+    }
+
+    /**
      * Sets and persist the night mode for this application.
      * <p>
      * The mode can be one of:
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 62db90f..0116ca2 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -18,11 +18,14 @@
 
 import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE;
 import static android.Manifest.permission.READ_WALLPAPER_INTERNAL;
+import static android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
 
+import static com.android.window.flags.Flags.FLAG_MULTI_CROP;
 import static com.android.window.flags.Flags.multiCrop;
 
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -58,6 +61,7 @@
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
+import android.graphics.Point;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
@@ -84,6 +88,7 @@
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Pair;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.WindowManagerGlobal;
 
@@ -104,6 +109,7 @@
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -255,6 +261,13 @@
     public static final String COMMAND_GOING_TO_SLEEP = "android.wallpaper.goingtosleep";
 
     /**
+     * Command for {@link #sendWallpaperCommand}: reported when a physical display switch event
+     * happens, e.g. fold and unfold.
+     * @hide
+     */
+    public static final String COMMAND_DISPLAY_SWITCH = "android.wallpaper.displayswitch";
+
+    /**
      * Command for {@link #sendWallpaperCommand}: reported when the wallpaper that was already
      * set is re-applied by the user.
      * @hide
@@ -289,6 +302,79 @@
     public static final String EXTRA_FROM_FOREGROUND_APP =
             "android.service.wallpaper.extra.FROM_FOREGROUND_APP";
 
+    /**
+     * The different screen orientations. {@link #getOrientation} provides their exact definition.
+     * This is only used internally by the framework and the WallpaperBackupAgent.
+     * @hide
+     */
+    @IntDef(value = {
+            ORIENTATION_UNKNOWN,
+            PORTRAIT,
+            LANDSCAPE,
+            SQUARE_PORTRAIT,
+            SQUARE_LANDSCAPE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ScreenOrientation {}
+
+    /**
+     * @hide
+     */
+    public static final int ORIENTATION_UNKNOWN = -1;
+
+    /**
+     * Portrait orientation of most screens
+     * @hide
+     */
+    public static final int PORTRAIT = 0;
+
+    /**
+     * Landscape orientation of most screens
+     * @hide
+     */
+    public static final int LANDSCAPE = 1;
+
+    /**
+     * Portrait orientation with similar width and height (e.g. the inner screen of a foldable)
+     * @hide
+     */
+    public static final int SQUARE_PORTRAIT = 2;
+
+    /**
+     * Landscape orientation with similar width and height (e.g. the inner screen of a foldable)
+     * @hide
+     */
+    public static final int SQUARE_LANDSCAPE = 3;
+
+    /**
+     * Converts a (width, height) screen size to a {@link ScreenOrientation}.
+     * @param screenSize the dimensions of a screen
+     * @return the corresponding {@link ScreenOrientation}.
+     * @hide
+     */
+    public static @ScreenOrientation int getOrientation(Point screenSize) {
+        float ratio = ((float) screenSize.x) / screenSize.y;
+        // ratios between 3/4 and 4/3 are considered square
+        return ratio >= 4 / 3f ? LANDSCAPE
+                : ratio > 1f ? SQUARE_LANDSCAPE
+                : ratio > 3 / 4f ? SQUARE_PORTRAIT
+                : PORTRAIT;
+    }
+
+    /**
+     * Get the 90° rotation of a given orientation
+     * @hide
+     */
+    public static @ScreenOrientation int getRotatedOrientation(@ScreenOrientation int orientation) {
+        switch (orientation) {
+            case PORTRAIT: return LANDSCAPE;
+            case LANDSCAPE: return PORTRAIT;
+            case SQUARE_PORTRAIT: return SQUARE_LANDSCAPE;
+            case SQUARE_LANDSCAPE: return SQUARE_PORTRAIT;
+            default: return ORIENTATION_UNKNOWN;
+        }
+    }
+
     // flags for which kind of wallpaper to act on
 
     /** @hide */
@@ -867,15 +953,8 @@
      * @hide
      */
     public static boolean isMultiCropEnabled() {
-        if (sGlobals == null) {
-            sIsMultiCropEnabled = multiCrop();
-        }
         if (sIsMultiCropEnabled == null) {
-            try {
-                sIsMultiCropEnabled = sGlobals.mService.isMultiCropEnabled();
-            } catch (RemoteException e) {
-                e.rethrowFromSystemServer();
-            }
+            sIsMultiCropEnabled = multiCrop();
         }
         return sIsMultiCropEnabled;
     }
@@ -1502,6 +1581,99 @@
     }
 
     /**
+     * For the current user, given a list of display sizes, return a list of rectangles representing
+     * the area of the current wallpaper that would be shown for each of these sizes.
+     *
+     * @param displaySizes the display sizes.
+     * @param which wallpaper type. Must be either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
+     * @param originalBitmap If true, return areas relative to the original bitmap.
+     *                   If false, return areas relative to the cropped bitmap.
+     * @return A List of Rect where the Rect is within the cropped/original bitmap, and corresponds
+     *          to what is displayed. The Rect may have a larger width/height ratio than the screen
+     *          due to parallax. Return {@code null} if the wallpaper is not an ImageWallpaper.
+     *          Also return {@code null} when called with which={@link #FLAG_LOCK} if there is a
+     *          shared home + lock wallpaper.
+     * @hide
+     */
+    @FlaggedApi(FLAG_MULTI_CROP)
+    @RequiresPermission(READ_WALLPAPER_INTERNAL)
+    @Nullable
+    public List<Rect> getBitmapCrops(@NonNull List<Point> displaySizes,
+            @SetWallpaperFlags int which, boolean originalBitmap) {
+        checkExactlyOneWallpaperFlagSet(which);
+        try {
+            return sGlobals.mService.getBitmapCrops(displaySizes, which, originalBitmap,
+                    mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * For preview purposes.
+     * Return how a bitmap of a given size would be cropped for a given list of display sizes, if
+     * it was set as wallpaper via {@link #setBitmapWithCrops(Bitmap, Map, boolean, int)} or
+     * {@link #setStreamWithCrops(InputStream, Map, boolean, int)}.
+     *
+     * @return A List of Rect where the Rect is within the bitmap, and corresponds to what is
+     *          displayed for each display size. The Rect may have a larger width/height ratio than
+     *          the display due to parallax.
+     * @hide
+     */
+    @FlaggedApi(FLAG_MULTI_CROP)
+    @Nullable
+    public List<Rect> getBitmapCrops(@NonNull Point bitmapSize, @NonNull List<Point> displaySizes,
+            @Nullable Map<Point, Rect> cropHints) {
+        try {
+            if (cropHints == null) cropHints = Map.of();
+            Set<Map.Entry<Point, Rect>> entries = cropHints.entrySet();
+            int[] screenOrientations = entries.stream().mapToInt(entry ->
+                    getOrientation(entry.getKey())).toArray();
+            List<Rect> crops = entries.stream().map(Map.Entry::getValue).toList();
+            return sGlobals.mService.getFutureBitmapCrops(bitmapSize, displaySizes,
+                    screenOrientations, crops);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * For preview purposes.
+     * Compute the wallpaper colors of the given bitmap, if it was set as wallpaper via
+     * {@link #setBitmapWithCrops(Bitmap, Map, boolean, int)} or
+     * {@link #setStreamWithCrops(InputStream, Map, boolean, int)}.
+     *  Return {@code null} if an error occurred and the colors could not be computed.
+     *
+     * @hide
+     */
+    @FlaggedApi(FLAG_MULTI_CROP)
+    @RequiresPermission(SET_WALLPAPER_DIM_AMOUNT)
+    @Nullable
+    public WallpaperColors getWallpaperColors(@NonNull Bitmap bitmap,
+            @Nullable Map<Point, Rect> cropHints) {
+        if (sGlobals.mService == null) {
+            Log.w(TAG, "WallpaperService not running");
+            throw new RuntimeException(new DeadSystemException());
+        }
+        try {
+            if (cropHints == null) cropHints = Map.of();
+            Set<Map.Entry<Point, Rect>> entries = cropHints.entrySet();
+            int[] screenOrientations = entries.stream().mapToInt(entry ->
+                    getOrientation(entry.getKey())).toArray();
+            List<Rect> crops = entries.stream().map(Map.Entry::getValue).toList();
+            Point bitmapSize = new Point(bitmap.getWidth(), bitmap.getHeight());
+            Rect crop = sGlobals.mService.getBitmapCrop(bitmapSize, screenOrientations, crops);
+            float dimAmount = getWallpaperDimAmount();
+            Bitmap croppedBitmap = Bitmap.createBitmap(
+                    bitmap, crop.left, crop.top, crop.width(), crop.height());
+            WallpaperColors result = WallpaperColors.fromBitmap(croppedBitmap, dimAmount);
+            return result;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * <strong> Important note: </strong>
      * <ul>
      *     <li>Up to version S, this method requires the
@@ -1727,15 +1899,22 @@
 
     /**
      * Returns the information about the home screen wallpaper if its current wallpaper is a live
-     * wallpaper component. Otherwise, if the wallpaper is a static image, this returns null.
+     * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the
+     * caller doesn't have the appropriate permissions, this returns {@code null}.
      *
      * <p>
-     * In order to use this, apps should declare a {@code <queries>} tag with the action
-     * {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
+     * Before Android U, this method requires the
+     * {@link android.Manifest.permission#QUERY_ALL_PACKAGES} permission.
+     * </p>
+     *
+     * <p>
+     * Starting from Android U, in order to use this, apps should declare a {@code <queries>} tag
+     * with the action {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
      * this method will return {@code null} if the caller doesn't otherwise have
      * <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package.
      * </p>
      */
+    @RequiresPermission(value = "QUERY_ALL_PACKAGES", conditional = true)
     public WallpaperInfo getWallpaperInfo() {
         return getWallpaperInfoForUser(mContext.getUserId());
     }
@@ -1752,19 +1931,14 @@
     }
 
     /**
-     * Returns the information about the home screen wallpaper if its current wallpaper is a live
-     * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the
+     * Returns the information about the designated wallpaper if its current wallpaper is a live
+     * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if
      * the caller doesn't have the appropriate permissions, this returns {@code null}.
      *
      * <p>
-     * Before Android U, this method requires the
-     * {@link android.Manifest.permission#QUERY_ALL_PACKAGES} permission.
-     * </p>
-     *
-     * <p>
-     * Starting from Android U, In order to use this, apps should declare a {@code <queries>} tag
-     * with the action {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
-     * this method will return {@code null} if the caller doesn't otherwise have
+     * In order to use this, apps should declare a {@code <queries>} tag with the action
+     * {@code "android.service.wallpaper.WallpaperService"}. Otherwise, this method will return
+     * {@code null} if the caller doesn't otherwise have
      * <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package.
      * </p>
      *
@@ -1780,7 +1954,7 @@
     /**
      * Returns the information about the designated wallpaper if its current wallpaper is a live
      * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the
-     * the caller doesn't have the appropriate permissions, this returns {@code null}.
+     * caller doesn't have the appropriate permissions, this returns {@code null}.
      *
      * <p>
      * In order to use this, apps should declare a {@code <queries>} tag
@@ -1971,7 +2145,7 @@
             /* Set the wallpaper to the default values */
             ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(
                     "res:" + resources.getResourceName(resid),
-                    mContext.getOpPackageName(), null, false, result, which, completion,
+                    mContext.getOpPackageName(), null, null, false, result, which, completion,
                     mContext.getUserId());
             if (fd != null) {
                 FileOutputStream fos = null;
@@ -2089,6 +2263,11 @@
     public int setBitmap(Bitmap fullImage, Rect visibleCropHint,
             boolean allowBackup, @SetWallpaperFlags int which, int userId)
             throws IOException {
+        if (multiCrop()) {
+            SparseArray<Rect> cropMap = new SparseArray<>();
+            if (visibleCropHint != null) cropMap.put(ORIENTATION_UNKNOWN, visibleCropHint);
+            return setBitmapWithCrops(fullImage, cropMap, allowBackup, which, userId);
+        }
         validateRect(visibleCropHint);
         if (sGlobals.mService == null) {
             Log.w(TAG, "WallpaperService not running");
@@ -2096,9 +2275,69 @@
         }
         final Bundle result = new Bundle();
         final WallpaperSetCompletion completion = new WallpaperSetCompletion();
+        final List<Rect> crops = visibleCropHint == null ? null : List.of(visibleCropHint);
         try {
             ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
-                    mContext.getOpPackageName(), visibleCropHint, allowBackup,
+                    mContext.getOpPackageName(), null, crops, allowBackup, result, which,
+                    completion, userId);
+            if (fd != null) {
+                FileOutputStream fos = null;
+                try {
+                    fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+                    fullImage.compress(Bitmap.CompressFormat.PNG, 90, fos);
+                    fos.close();
+                    completion.waitForCompletion();
+                } finally {
+                    IoUtils.closeQuietly(fos);
+                }
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
+    }
+
+    /**
+     * Version of setBitmap that defines how the wallpaper will be positioned for different
+     * display sizes.
+     * Requires permission {@link android.Manifest.permission#SET_WALLPAPER}.
+     * @param cropHints map from screen dimensions to a sub-region of the image to display for those
+     *                  dimensions. The {@code Rect} sub-region may have a larger width/height ratio
+     *                  than the screen dimensions to apply a horizontal parallax effect. If the
+     *                  map is empty or some entries are missing, the system will apply a default
+     *                  strategy to position the wallpaper for any unspecified screen dimensions.
+     * @hide
+     */
+    @FlaggedApi(FLAG_MULTI_CROP)
+    @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
+    public int setBitmapWithCrops(@Nullable Bitmap fullImage, @NonNull Map<Point, Rect> cropHints,
+            boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+        SparseArray<Rect> crops = new SparseArray<>();
+        cropHints.forEach((k, v) -> crops.put(getOrientation(k), v));
+        return setBitmapWithCrops(fullImage, crops, allowBackup, which, mContext.getUserId());
+    }
+
+    @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
+    private int setBitmapWithCrops(@Nullable Bitmap fullImage, @NonNull SparseArray<Rect> cropHints,
+            boolean allowBackup, @SetWallpaperFlags int which, int userId) throws IOException {
+        if (sGlobals.mService == null) {
+            Log.w(TAG, "WallpaperService not running");
+            throw new RuntimeException(new DeadSystemException());
+        }
+        int size = cropHints.size();
+        int[] screenOrientations = new int[size];
+        List<Rect> crops = new ArrayList<>(size);
+        for (int i = 0; i < size; i++) {
+            screenOrientations[i] = cropHints.keyAt(i);
+            Rect cropHint = cropHints.valueAt(i);
+            validateRect(cropHint);
+            crops.add(cropHint);
+        }
+        final Bundle result = new Bundle();
+        final WallpaperSetCompletion completion = new WallpaperSetCompletion();
+        try {
+            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
+                    mContext.getOpPackageName(), screenOrientations, crops, allowBackup,
                     result, which, completion, userId);
             if (fd != null) {
                 FileOutputStream fos = null;
@@ -2214,6 +2453,11 @@
     public int setStream(InputStream bitmapData, Rect visibleCropHint,
             boolean allowBackup, @SetWallpaperFlags int which)
                     throws IOException {
+        if (multiCrop()) {
+            SparseArray<Rect> cropMap = new SparseArray<>();
+            if (visibleCropHint != null) cropMap.put(ORIENTATION_UNKNOWN, visibleCropHint);
+            return setStreamWithCrops(bitmapData, cropMap, allowBackup, which);
+        }
         validateRect(visibleCropHint);
         if (sGlobals.mService == null) {
             Log.w(TAG, "WallpaperService not running");
@@ -2221,10 +2465,11 @@
         }
         final Bundle result = new Bundle();
         final WallpaperSetCompletion completion = new WallpaperSetCompletion();
+        final List<Rect> crops = visibleCropHint == null ? null : List.of(visibleCropHint);
         try {
             ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
-                    mContext.getOpPackageName(), visibleCropHint, allowBackup,
-                    result, which, completion, mContext.getUserId());
+                    mContext.getOpPackageName(), null, crops, allowBackup, result, which,
+                    completion, mContext.getUserId());
             if (fd != null) {
                 FileOutputStream fos = null;
                 try {
@@ -2244,6 +2489,75 @@
     }
 
     /**
+     * Version of setStream that defines how the wallpaper will be positioned for different
+     * display sizes.
+     * Requires permission {@link android.Manifest.permission#SET_WALLPAPER}.
+     * @param cropHints map from screen dimensions to a sub-region of the image to display for those
+     *                  dimensions. The {@code Rect} sub-region may have a larger width/height ratio
+     *                  than the screen dimensions to apply a horizontal parallax effect. If the
+     *                  map is empty or some entries are missing, the system will apply a default
+     *                  strategy to position the wallpaper for any unspecified screen dimensions.
+     * @hide
+     */
+    @FlaggedApi(FLAG_MULTI_CROP)
+    @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
+    public int setStreamWithCrops(InputStream bitmapData, @NonNull Map<Point, Rect> cropHints,
+            boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+        SparseArray<Rect> crops = new SparseArray<>();
+        cropHints.forEach((k, v) -> crops.put(getOrientation(k), v));
+        return setStreamWithCrops(bitmapData, crops, allowBackup, which);
+    }
+
+    /**
+     * Similar to {@link #setStreamWithCrops(InputStream, Map, boolean, int)}, but using
+     * {@link ScreenOrientation} as keys of the cropHints map. Used for backup & restore, since
+     * WallpaperBackupAgent stores orientations rather than the exact display size.
+     * Requires permission {@link android.Manifest.permission#SET_WALLPAPER}.
+     * @param cropHints map from {@link ScreenOrientation} to a sub-region of the image to display
+     *                  for that screen orientation.
+     * @hide
+     */
+    @FlaggedApi(FLAG_MULTI_CROP)
+    @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
+    public int setStreamWithCrops(InputStream bitmapData, @NonNull SparseArray<Rect> cropHints,
+            boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+        if (sGlobals.mService == null) {
+            Log.w(TAG, "WallpaperService not running");
+            throw new RuntimeException(new DeadSystemException());
+        }
+        int size = cropHints.size();
+        int[] screenOrientations = new int[size];
+        List<Rect> crops = new ArrayList<>(size);
+        for (int i = 0; i < size; i++) {
+            screenOrientations[i] = cropHints.keyAt(i);
+            Rect cropHint = cropHints.valueAt(i);
+            validateRect(cropHint);
+            crops.add(cropHint);
+        }
+        final Bundle result = new Bundle();
+        final WallpaperSetCompletion completion = new WallpaperSetCompletion();
+        try {
+            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
+                    mContext.getOpPackageName(), screenOrientations, crops, allowBackup,
+                    result, which, completion, mContext.getUserId());
+            if (fd != null) {
+                FileOutputStream fos = null;
+                try {
+                    fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+                    copyStreamToWallpaperFile(bitmapData, fos);
+                    fos.close();
+                    completion.waitForCompletion();
+                } finally {
+                    IoUtils.closeQuietly(fos);
+                }
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
+    }
+
+    /**
      * Return whether any users are currently set to use the wallpaper
      * with the given resource ID.  That is, their wallpaper has been
      * set through {@link #setResource(int)} with the same resource id.
@@ -2499,7 +2813,7 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT)
+    @RequiresPermission(SET_WALLPAPER_DIM_AMOUNT)
     public void setWallpaperDimAmount(@FloatRange (from = 0f, to = 1f) float dimAmount) {
         if (sGlobals.mService == null) {
             Log.w(TAG, "WallpaperService not running");
@@ -2519,7 +2833,7 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT)
+    @RequiresPermission(SET_WALLPAPER_DIM_AMOUNT)
     public @FloatRange (from = 0f, to = 1f) float getWallpaperDimAmount() {
         if (sGlobals.mService == null) {
             Log.w(TAG, "WallpaperService not running");
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index b303ea6..c0b299b 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -13,3 +13,17 @@
      description: "API to get importance of UID that's binding to the caller"
      bug: "292533010"
 }
+
+flag {
+    namespace: "backstage_power"
+    name: "app_restrictions_api"
+    description: "API to track and query restrictions applied to apps"
+    bug: "320150834"
+}
+
+flag {
+     namespace: "backstage_power"
+     name: "uid_importance_listener_for_uids"
+     description: "API to add OnUidImportanceListener with targetted UIDs"
+     bug: "286258140"
+}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 5c42b0e..86d0125 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -53,6 +53,7 @@
 import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
 import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+import static android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED;
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
@@ -61,6 +62,7 @@
 import android.annotation.BroadcastBehavior;
 import android.annotation.CallbackExecutor;
 import android.annotation.ColorInt;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -4092,6 +4094,29 @@
         return MTE_NOT_CONTROLLED_BY_POLICY;
     }
 
+    /** Indicates that content protection is not controlled by policy, allowing user to choose. */
+    @FlaggedApi(FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+    public static final int CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY = 0;
+
+    /** Indicates that content protection is controlled and disabled by a policy. */
+    @FlaggedApi(FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+    public static final int CONTENT_PROTECTION_DISABLED = 1;
+
+    /** Indicates that content protection is controlled and enabled by a policy. */
+    @FlaggedApi(FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+    public static final int CONTENT_PROTECTION_ENABLED = 2;
+
+    /** @hide */
+    @IntDef(
+            prefix = {"CONTENT_PROTECTION_"},
+            value = {
+                CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY,
+                CONTENT_PROTECTION_DISABLED,
+                CONTENT_PROTECTION_ENABLED,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ContentProtectionPolicy {}
+
     /**
      * This object is a single place to tack on invalidation and disable calls.  All
      * binder caches in this class derive from this Config, so all can be invalidated or
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 35ce102..b3ecd92 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -55,3 +55,10 @@
   description: "Guards a bugfix that ends the credential input flow if the managed user has not stopped."
   bug: "293441361"
 }
+
+flag {
+    name: "default_sms_personal_app_suspension_fix_enabled"
+    namespace: "enterprise"
+    description: "Exempt the default sms app of the context user for suspension when calling setPersonalAppsSuspended"
+    bug: "309183330"
+}
diff --git a/core/java/android/app/ambient_context.aconfig b/core/java/android/app/ambient_context.aconfig
new file mode 100644
index 0000000..3f73da2
--- /dev/null
+++ b/core/java/android/app/ambient_context.aconfig
@@ -0,0 +1,8 @@
+package: "android.app"
+
+flag {
+  namespace: "biometrics_integration"
+  name: "ambient_heart_rate"
+  description: "Feature flag for adding heart rate api to ambient context."
+  bug: "318309481"
+}
diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.java b/core/java/android/app/ambientcontext/AmbientContextEvent.java
index b5c66ff..5ab7991 100644
--- a/core/java/android/app/ambientcontext/AmbientContextEvent.java
+++ b/core/java/android/app/ambientcontext/AmbientContextEvent.java
@@ -16,7 +16,9 @@
 
 package android.app.ambientcontext;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcelable;
@@ -68,6 +70,14 @@
     public static final int EVENT_BACK_DOUBLE_TAP = 3;
 
     /**
+     * The integer indicating a heart rate measurement was done.
+     *
+     * @see #getRatePerMinute
+     */
+    @Event @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE)
+    public static final int EVENT_HEART_RATE = 4;
+
+    /**
      * Integer indicating the start of wearable vendor defined events that can be detected.
      * These depend on the vendor implementation.
      */
@@ -79,12 +89,19 @@
      */
     public static final String KEY_VENDOR_WEARABLE_EVENT_NAME = "wearable_event_name";
 
+    /**
+     * Default value for {@link #getRatePerMinute}. Indicates that the rate of the event is unknown.
+     */
+    @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE)
+    public static final int RATE_PER_MINUTE_UNKNOWN = -1;
+
     /** @hide */
     @IntDef(prefix = { "EVENT_" }, value = {
             EVENT_UNKNOWN,
             EVENT_COUGH,
             EVENT_SNORE,
             EVENT_BACK_DOUBLE_TAP,
+            EVENT_HEART_RATE,
             EVENT_VENDOR_WEARABLE_START,
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -170,6 +187,16 @@
         return new PersistableBundle();
     }
 
+    /**
+     * Rate per minute of the event during the start to end time.
+     *
+     * @return the rate per minute, or {@link #RATE_PER_MINUTE_UNKNOWN} if the rate is unknown.
+     */
+    private final @IntRange(from = -1) int mRatePerMinute;
+    private static int defaultRatePerMinute() {
+        return RATE_PER_MINUTE_UNKNOWN;
+    }
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -179,6 +206,8 @@
     //
     // To regenerate run:
     // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java
+    // then manually add @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE) back to flagged
+    // APIs.
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -191,6 +220,7 @@
         EVENT_COUGH,
         EVENT_SNORE,
         EVENT_BACK_DOUBLE_TAP,
+        EVENT_HEART_RATE,
         EVENT_VENDOR_WEARABLE_START
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -209,6 +239,8 @@
                     return "EVENT_SNORE";
             case EVENT_BACK_DOUBLE_TAP:
                     return "EVENT_BACK_DOUBLE_TAP";
+            case EVENT_HEART_RATE:
+                    return "EVENT_HEART_RATE";
             case EVENT_VENDOR_WEARABLE_START:
                     return "EVENT_VENDOR_WEARABLE_START";
             default: return Integer.toHexString(value);
@@ -255,7 +287,8 @@
             @NonNull Instant endTime,
             @LevelValue int confidenceLevel,
             @LevelValue int densityLevel,
-            @NonNull PersistableBundle vendorData) {
+            @NonNull PersistableBundle vendorData,
+            @IntRange(from = -1) int ratePerMinute) {
         this.mEventType = eventType;
         com.android.internal.util.AnnotationValidations.validate(
                 EventCode.class, null, mEventType);
@@ -274,6 +307,10 @@
         this.mVendorData = vendorData;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mVendorData);
+        this.mRatePerMinute = ratePerMinute;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mRatePerMinute,
+                "from", -1);
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -330,6 +367,17 @@
         return mVendorData;
     }
 
+    /**
+     * Rate per minute of the event during the start to end time.
+     *
+     * @return the rate per minute, or {@link #RATE_PER_MINUTE_UNKNOWN} if the rate is unknown.
+     */
+    @DataClass.Generated.Member
+    @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE)
+    public @IntRange(from = -1) int getRatePerMinute() {
+        return mRatePerMinute;
+    }
+
     @Override
     @DataClass.Generated.Member
     public String toString() {
@@ -342,7 +390,8 @@
                 "endTime = " + mEndTime + ", " +
                 "confidenceLevel = " + mConfidenceLevel + ", " +
                 "densityLevel = " + mDensityLevel + ", " +
-                "vendorData = " + mVendorData +
+                "vendorData = " + mVendorData + ", " +
+                "ratePerMinute = " + mRatePerMinute +
         " }";
     }
 
@@ -380,6 +429,7 @@
         dest.writeInt(mConfidenceLevel);
         dest.writeInt(mDensityLevel);
         dest.writeTypedObject(mVendorData, flags);
+        dest.writeInt(mRatePerMinute);
     }
 
     @Override
@@ -399,6 +449,7 @@
         int confidenceLevel = in.readInt();
         int densityLevel = in.readInt();
         PersistableBundle vendorData = (PersistableBundle) in.readTypedObject(PersistableBundle.CREATOR);
+        int ratePerMinute = in.readInt();
 
         this.mEventType = eventType;
         com.android.internal.util.AnnotationValidations.validate(
@@ -418,6 +469,10 @@
         this.mVendorData = vendorData;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mVendorData);
+        this.mRatePerMinute = ratePerMinute;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mRatePerMinute,
+                "from", -1);
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -449,6 +504,7 @@
         private @LevelValue int mConfidenceLevel;
         private @LevelValue int mDensityLevel;
         private @NonNull PersistableBundle mVendorData;
+        private @IntRange(from = -1) int mRatePerMinute;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -525,10 +581,22 @@
             return this;
         }
 
+        /**
+         * Rate per minute of the event during the start to end time.
+         */
+        @DataClass.Generated.Member
+        @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE)
+        public @NonNull Builder setRatePerMinute(@IntRange(from = -1) int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x40;
+            mRatePerMinute = value;
+            return this;
+        }
+
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull AmbientContextEvent build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x40; // Mark builder used
+            mBuilderFieldsSet |= 0x80; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mEventType = defaultEventType();
@@ -548,18 +616,22 @@
             if ((mBuilderFieldsSet & 0x20) == 0) {
                 mVendorData = defaultVendorData();
             }
+            if ((mBuilderFieldsSet & 0x40) == 0) {
+                mRatePerMinute = defaultRatePerMinute();
+            }
             AmbientContextEvent o = new AmbientContextEvent(
                     mEventType,
                     mStartTime,
                     mEndTime,
                     mConfidenceLevel,
                     mDensityLevel,
-                    mVendorData);
+                    mVendorData,
+                    mRatePerMinute);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x40) != 0) {
+            if ((mBuilderFieldsSet & 0x80) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -567,10 +639,10 @@
     }
 
     @DataClass.Generated(
-            time = 1671217108067L,
+            time = 1705575046107L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java",
-            inputSignatures = "public static final  int EVENT_UNKNOWN\npublic static final  int EVENT_COUGH\npublic static final  int EVENT_SNORE\npublic static final  int EVENT_BACK_DOUBLE_TAP\npublic static final  int EVENT_VENDOR_WEARABLE_START\npublic static final  java.lang.String KEY_VENDOR_WEARABLE_EVENT_NAME\npublic static final  int LEVEL_UNKNOWN\npublic static final  int LEVEL_LOW\npublic static final  int LEVEL_MEDIUM_LOW\npublic static final  int LEVEL_MEDIUM\npublic static final  int LEVEL_MEDIUM_HIGH\npublic static final  int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate final @android.annotation.NonNull android.os.PersistableBundle mVendorData\nprivate static  int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultDensityLevel()\nprivate static  android.os.PersistableBundle defaultVendorData()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+            inputSignatures = "public static final  int EVENT_UNKNOWN\npublic static final  int EVENT_COUGH\npublic static final  int EVENT_SNORE\npublic static final  int EVENT_BACK_DOUBLE_TAP\npublic static final @android.app.ambientcontext.AmbientContextEvent.Event @android.annotation.FlaggedApi int EVENT_HEART_RATE\npublic static final  int EVENT_VENDOR_WEARABLE_START\npublic static final  java.lang.String KEY_VENDOR_WEARABLE_EVENT_NAME\npublic static final @android.annotation.FlaggedApi int RATE_PER_MINUTE_UNKNOWN\npublic static final  int LEVEL_UNKNOWN\npublic static final  int LEVEL_LOW\npublic static final  int LEVEL_MEDIUM_LOW\npublic static final  int LEVEL_MEDIUM\npublic static final  int LEVEL_MEDIUM_HIGH\npublic static final  int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate final @android.annotation.NonNull android.os.PersistableBundle mVendorData\nprivate final @android.annotation.IntRange int mRatePerMinute\nprivate static  int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultDensityLevel()\nprivate static  android.os.PersistableBundle defaultVendorData()\nprivate static  int defaultRatePerMinute()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/app/background_install_control_manager.aconfig b/core/java/android/app/background_install_control_manager.aconfig
new file mode 100644
index 0000000..029b93a
--- /dev/null
+++ b/core/java/android/app/background_install_control_manager.aconfig
@@ -0,0 +1,9 @@
+package: "android.app"
+
+flag {
+     namespace: "background_install_control"
+     name: "bic_client"
+     description: "System API for background install control."
+     is_fixed_read_only: true
+     bug: "287507984"
+}
diff --git a/core/java/android/app/backup/BackupHelperWithLogger.java b/core/java/android/app/backup/BackupHelperWithLogger.java
new file mode 100644
index 0000000..1a59a53
--- /dev/null
+++ b/core/java/android/app/backup/BackupHelperWithLogger.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.backup;
+
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Utility class for writing BackupHelpers with added logging capabilities.
+ * Used for passing a logger object to Helper in key shared backup agents
+ *
+ * @hide
+ */
+public abstract class BackupHelperWithLogger implements BackupHelper {
+    private BackupRestoreEventLogger mLogger;
+    private boolean mIsLoggerSet = false;
+
+    public abstract void writeNewStateDescription(ParcelFileDescriptor newState);
+
+    public abstract void restoreEntity(BackupDataInputStream data);
+
+    public abstract void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+            ParcelFileDescriptor newState);
+
+    /**
+     * Gets the logger so that the backuphelper can log success/error for each datatype handled
+     */
+    public BackupRestoreEventLogger getLogger() {
+        return mLogger;
+    }
+
+    /**
+     * Allow the shared backup agent to pass a logger to each of its backup helper
+     */
+    public void setLogger(BackupRestoreEventLogger logger) {
+        mLogger = logger;
+        mIsLoggerSet = true;
+    }
+
+    /**
+     * Allow the helper to check if its shared backup agent has passed a logger
+     */
+    public boolean isLoggerSet() {
+        return mIsLoggerSet;
+    }
+}
diff --git a/core/java/android/app/backup/BlobBackupHelper.java b/core/java/android/app/backup/BlobBackupHelper.java
index 82d0a94c..a55ff48 100644
--- a/core/java/android/app/backup/BlobBackupHelper.java
+++ b/core/java/android/app/backup/BlobBackupHelper.java
@@ -39,7 +39,7 @@
  *
  * @hide
  */
-public abstract class BlobBackupHelper implements BackupHelper {
+public abstract class BlobBackupHelper extends BackupHelperWithLogger {
     private static final String TAG = "BlobBackupHelper";
     private static final boolean DEBUG = false;
 
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index 6371871..a41cb1f 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -239,7 +239,7 @@
                 Log.e(TAG, "Unable to create/open file " + outFile.getPath(), e);
             }
 
-            byte[] buffer = new byte[32 * 1024];
+            byte[] buffer = new byte[64 * 1024];
             final long origSize = size;
             FileInputStream in = new FileInputStream(data.getFileDescriptor());
             while (size > 0) {
diff --git a/core/java/android/app/grammatical_inflection_manager.aconfig b/core/java/android/app/grammatical_inflection_manager.aconfig
index 989ce61..68d12ba 100644
--- a/core/java/android/app/grammatical_inflection_manager.aconfig
+++ b/core/java/android/app/grammatical_inflection_manager.aconfig
@@ -2,7 +2,7 @@
 
 flag {
     name: "system_terms_of_address_enabled"
-    namespace: "grammatical_gender"
+    namespace: "globalintl"
     description: "Feature flag for System Terms of Address"
     bug: "297798866"
 }
diff --git a/core/java/android/app/usage/UsageEventsQuery.java b/core/java/android/app/usage/UsageEventsQuery.java
index 3cd2923..c0f13ca 100644
--- a/core/java/android/app/usage/UsageEventsQuery.java
+++ b/core/java/android/app/usage/UsageEventsQuery.java
@@ -24,6 +24,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.ArraySet;
 
 import com.android.internal.util.ArrayUtils;
@@ -43,12 +44,14 @@
     private final @CurrentTimeMillisLong long mEndTimeMillis;
     private final @Event.EventType int[] mEventTypes;
     private final @UserIdInt int mUserId;
+    private final String[] mPackageNames;
 
     private UsageEventsQuery(@NonNull Builder builder) {
         mBeginTimeMillis = builder.mBeginTimeMillis;
         mEndTimeMillis = builder.mEndTimeMillis;
         mEventTypes = ArrayUtils.convertToIntArray(builder.mEventTypes);
         mUserId = builder.mUserId;
+        mPackageNames = builder.mPackageNames.toArray(new String[builder.mPackageNames.size()]);
     }
 
     private UsageEventsQuery(Parcel in) {
@@ -58,6 +61,9 @@
         mEventTypes = new int[eventTypesLength];
         in.readIntArray(mEventTypes);
         mUserId = in.readInt();
+        int packageNamesLength = in.readInt();
+        mPackageNames = new String[packageNamesLength];
+        in.readStringArray(mPackageNames);
     }
 
     /**
@@ -77,19 +83,17 @@
     }
 
     /**
-     * Returns the set of usage event types for the query.
-     * <em>Note: An empty set indicates query for all usage events. </em>
+     * Retrieves the usage event types for the query.
+     * <p>Note that an empty array indicates querying all usage event types, and it may
+     * cause additional system overhead when calling
+     * {@link UsageStatsManager#queryEvents(UsageEventsQuery)}. Apps are encouraged to
+     * provide a list of event types via {@link Builder#setEventTypes(int...)}</p>
+     *
+     * @return an array contains the usage event types that was previously set using
+     *         {@link Builder#setEventTypes(int...)} or an empty array if no value has been set.
      */
-    public @NonNull Set<Integer> getEventTypes() {
-        if (ArrayUtils.isEmpty(mEventTypes)) {
-            return Collections.emptySet();
-        }
-
-        HashSet<Integer> eventTypeSet = new HashSet<>();
-        for (int eventType : mEventTypes) {
-            eventTypeSet.add(eventType);
-        }
-        return eventTypeSet;
+    public @NonNull @Event.EventType int[] getEventTypes() {
+        return Arrays.copyOf(mEventTypes, mEventTypes.length);
     }
 
     /** @hide */
@@ -97,6 +101,28 @@
         return mUserId;
     }
 
+    /**
+     * Retrieves a {@code Set} of package names for the query.
+     * <p>Note that an empty set indicates querying usage events for all packages, and
+     * it may cause additional system overhead when calling
+     * {@link UsageStatsManager#queryEvents(UsageEventsQuery)}. Apps are encouraged to
+     * provide a list of package names via {@link Builder#setPackageNames(String...)}</p>
+     *
+     * @return a {@code Set} contains the package names that was previously set through
+     *         {@link Builder#setPackageNames(String...)} or an empty set if no value has been set.
+     */
+    public @NonNull Set<String> getPackageNames() {
+        if (ArrayUtils.isEmpty(mPackageNames)) {
+            return Collections.emptySet();
+        }
+
+        final HashSet<String> pkgNameSet = new HashSet<>();
+        for (String pkgName: mPackageNames) {
+            pkgNameSet.add(pkgName);
+        }
+        return pkgNameSet;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -109,6 +135,8 @@
         dest.writeInt(mEventTypes.length);
         dest.writeIntArray(mEventTypes);
         dest.writeInt(mUserId);
+        dest.writeInt(mPackageNames.length);
+        dest.writeStringArray(mPackageNames);
     }
 
     @NonNull
@@ -125,11 +153,6 @@
                 }
             };
 
-    /** @hide */
-    public int[] getEventTypeFilter() {
-        return Arrays.copyOf(mEventTypes, mEventTypes.length);
-    }
-
     /**
      * Builder for UsageEventsQuery.
      */
@@ -138,6 +161,7 @@
         private final @CurrentTimeMillisLong long mEndTimeMillis;
         private final ArraySet<Integer> mEventTypes = new ArraySet<>();
         private @UserIdInt int mUserId = UserHandle.USER_NULL;
+        private final ArraySet<String> mPackageNames = new ArraySet<>();
 
         /**
          * Constructor that specifies the period for which to return events.
@@ -166,12 +190,25 @@
         }
 
         /**
-         * Specifies the list of usage event types to be included in the query.
-         * @param eventTypes List of the usage event types. See {@link UsageEvents.Event}
+         * Sets the list of usage event types to be included in the query.
          *
-         * @throws llegalArgumentException if the event type is not valid.
+         * <p>Note: </p> An empty array will be returned by
+         * {@link UsageEventsQuery#getEventTypes()} without calling this method, which indicates
+         * querying for all event types. Apps are encouraged to provide a list of event types.
+         * Only the matching types supplied will be used to query.
+         *
+         * @param eventTypes the array of the usage event types. See {@link UsageEvents.Event}.
+         * @throws NullPointerException if {@code eventTypes} is {@code null} or empty.
+         * @throws IllegalArgumentException if any of event types are invalid.
+         * @see UsageEventsQuery#getEventTypes()
+         * @see UsageStatsManager#queryEvents(UsageEventsQuery)
          */
-        public @NonNull Builder addEventTypes(@NonNull @Event.EventType int... eventTypes) {
+        public @NonNull Builder setEventTypes(@NonNull @Event.EventType int... eventTypes) {
+            if (eventTypes == null || eventTypes.length == 0) {
+                throw new NullPointerException("eventTypes is null or empty");
+            }
+
+            mEventTypes.clear();
             for (int i = 0; i < eventTypes.length; i++) {
                 final int eventType = eventTypes[i];
                 if (eventType < Event.NONE || eventType > Event.MAX_EVENT_TYPE) {
@@ -191,5 +228,33 @@
             mUserId = userId;
             return this;
         }
+
+        /**
+         * Sets the list of package names to be included in the query.
+         *
+         * <p>Note: </p> An empty {@code Set} will be returned by
+         * {@link UsageEventsQuery#getPackageNames()} without calling this method, which indicates
+         * querying usage events for all packages. Apps are encouraged to provide a list of package
+         * names. Only the matching names supplied will be used to query.
+         *
+         * @param pkgNames the array of the package names, each package name should be a non-empty
+         *                 string, {@code null} or empty string("") is omitted.
+         * @see UsageEventsQuery#getPackageNames()
+         * @see UsageStatsManager#queryEvents(UsageEventsQuery)
+         * @throws NullPointerException if {@code pkgNames} is {@code null} or empty.
+         */
+        public @NonNull Builder setPackageNames(@NonNull String... pkgNames) {
+            if (pkgNames == null || pkgNames.length == 0) {
+                throw new NullPointerException("pkgNames is null or empty");
+            }
+            mPackageNames.clear();
+            for (int i = 0; i < pkgNames.length; i++) {
+                if (!TextUtils.isEmpty(pkgNames[i])) {
+                    mPackageNames.add(pkgNames[i]);
+                }
+            }
+
+            return this;
+        }
     }
 }
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 85d223d..8df913a 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -602,14 +602,15 @@
 
     /**
      * Query for events with specific UsageEventsQuery object.
+     *
      * <em>Note: if the user's device is not in an unlocked state (as defined by
      * {@link UserManager#isUserUnlocked()}), then {@code null} will be returned.</em>
      *
      * @param query The query object used to specify the query parameters.
-     * @return A {@link UsageEvents}.
+     * @return A {@link UsageEvents} which contains the events matching the query parameters.
      */
     @FlaggedApi(Flags.FLAG_FILTER_BASED_EVENT_QUERY_API)
-    @NonNull
+    @Nullable
     @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
     public UsageEvents queryEvents(@NonNull UsageEventsQuery query) {
         try {
diff --git a/core/java/android/app/wearable/OWNERS b/core/java/android/app/wearable/OWNERS
index 073e2d7..497eaf0 100644
--- a/core/java/android/app/wearable/OWNERS
+++ b/core/java/android/app/wearable/OWNERS
@@ -1,3 +1,5 @@
 charliewang@google.com
+hackz@google.com
 oni@google.com
+tomchan@google.com
 volnov@google.com
\ No newline at end of file
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index ec181da..1f19f81 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -907,7 +907,10 @@
 
     private InteractionHandler getHandler(InteractionHandler handler) {
         return (view, pendingIntent, response) -> {
-            AppWidgetManager.getInstance(mContext).noteAppWidgetTapped(mAppWidgetId);
+            AppWidgetManager manager = AppWidgetManager.getInstance(mContext);
+            if (manager != null) {
+                manager.noteAppWidgetTapped(mAppWidgetId);
+            }
             if (handler != null) {
                 return handler.onInteraction(view, pendingIntent, response);
             } else {
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 4cf9fca..6204edc 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -19,6 +19,7 @@
 import static android.appwidget.flags.Flags.remoteAdapterConversion;
 
 import android.annotation.BroadcastBehavior;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
@@ -30,6 +31,7 @@
 import android.annotation.UserIdInt;
 import android.app.IServiceConnection;
 import android.app.PendingIntent;
+import android.appwidget.flags.Flags;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
@@ -1415,6 +1417,89 @@
         }
     }
 
+    /**
+     * Set a preview for this widget. This preview will be used instead of the provider's {@link
+     * AppWidgetProviderInfo#previewLayout previewLayout} or {@link
+     * AppWidgetProviderInfo#previewImage previewImage} for previewing the widget in the widget
+     * picker and pin app widget flow.
+     *
+     * @param provider The {@link ComponentName} for the {@link android.content.BroadcastReceiver
+     *    BroadcastReceiver} provider for the AppWidget you intend to provide a preview for.
+     * @param widgetCategories The categories that this preview should be used for. This can be a
+     *    single category or combination of categories. If multiple categories are specified,
+     *    then this preview will be used for each of those categories. For example, if you
+     *    set a preview for WIDGET_CATEGORY_HOME_SCREEN | WIDGET_CATEGORY_KEYGUARD, the preview will
+     *    be used when picking widgets for the home screen and keyguard.
+     *
+     *    <p>Note: You should only use the widget categories that the provider supports, as defined
+     *    in {@link AppWidgetProviderInfo#widgetCategory}.
+     * @param preview This preview will be used for previewing the provider when picking widgets for
+     *    the selected categories.
+     *
+     * @see AppWidgetProviderInfo#WIDGET_CATEGORY_HOME_SCREEN
+     * @see AppWidgetProviderInfo#WIDGET_CATEGORY_KEYGUARD
+     * @see AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX
+     */
+    @FlaggedApi(Flags.FLAG_GENERATED_PREVIEWS)
+    public void setWidgetPreview(@NonNull ComponentName provider,
+            @AppWidgetProviderInfo.CategoryFlags int widgetCategories,
+            @NonNull RemoteViews preview) {
+        try {
+            mService.setWidgetPreview(provider, widgetCategories, preview);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the RemoteViews previews for this widget.
+     *
+     * @param provider The {@link ComponentName} for the {@link android.content.BroadcastReceiver
+     *    BroadcastReceiver} provider for the AppWidget you intend to get a preview for.
+     * @param profile The profile in which the provider resides. Passing null is equivalent
+     *        to querying for only the calling user.
+     * @param widgetCategory The widget category for which you want to display previews. This should
+     *    be a single category. If a combination of categories is provided, this function will
+     *    return a preview that matches at least one of the categories.
+     *
+     * @return The widget preview for the selected category, if available.
+     * @see AppWidgetProviderInfo#generatedPreviewCategories
+     */
+    @Nullable
+    @FlaggedApi(Flags.FLAG_GENERATED_PREVIEWS)
+    public RemoteViews getWidgetPreview(@NonNull ComponentName provider,
+            @Nullable UserHandle profile, @AppWidgetProviderInfo.CategoryFlags int widgetCategory) {
+        try {
+            if (profile == null) {
+                profile = mContext.getUser();
+            }
+            return mService.getWidgetPreview(mPackageName, provider, profile.getIdentifier(),
+                    widgetCategory);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Remove this provider's preview for the specified widget categories. If the provider does not
+     * have a preview for the specified widget category, this is a no-op.
+     *
+     * @param provider The AppWidgetProvider to remove previews for.
+     * @param widgetCategories The categories of the preview to remove. For example, removing the
+     *    preview for WIDGET_CATEGORY_HOME_SCREEN | WIDGET_CATEGORY_KEYGUARD will remove the
+     *    previews for both categories.
+     */
+    @FlaggedApi(Flags.FLAG_GENERATED_PREVIEWS)
+    public void removeWidgetPreview(@NonNull ComponentName provider,
+            @AppWidgetProviderInfo.CategoryFlags int widgetCategories) {
+        try {
+            mService.removeWidgetPreview(provider, widgetCategories);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
     @UiThread
     private static @NonNull Executor createUpdateExecutorIfNull() {
         if (sUpdateExecutor == null) {
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index e56e53a..1a80cac2 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -16,6 +16,10 @@
 
 package android.appwidget;
 
+import static android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS;
+import static android.appwidget.flags.Flags.generatedPreviews;
+
+import android.annotation.FlaggedApi;
 import android.annotation.IdRes;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -358,6 +362,20 @@
     /** @hide */
     public boolean isExtendedFromAppWidgetProvider;
 
+    /**
+     * Flags indicating the widget categories for which generated previews are available.
+     * These correspond to the previews set by this provider with
+     * {@link AppWidgetManager#setWidgetPreview}.
+     *
+     * @see #WIDGET_CATEGORY_HOME_SCREEN
+     * @see #WIDGET_CATEGORY_KEYGUARD
+     * @see #WIDGET_CATEGORY_SEARCHBOX
+     * @see AppWidgetManager#getWidgetPreview
+     */
+    @FlaggedApi(FLAG_GENERATED_PREVIEWS)
+    @SuppressLint("MutableBareField")
+    public int generatedPreviewCategories;
+
     public AppWidgetProviderInfo() {
 
     }
@@ -391,6 +409,9 @@
         this.widgetFeatures = in.readInt();
         this.descriptionRes = in.readInt();
         this.isExtendedFromAppWidgetProvider = in.readBoolean();
+        if (generatedPreviews()) {
+            generatedPreviewCategories = in.readInt();
+        }
     }
 
     /**
@@ -515,6 +536,9 @@
         out.writeInt(this.widgetFeatures);
         out.writeInt(this.descriptionRes);
         out.writeBoolean(this.isExtendedFromAppWidgetProvider);
+        if (generatedPreviews()) {
+            out.writeInt(this.generatedPreviewCategories);
+        }
     }
 
     @Override
@@ -545,6 +569,9 @@
         that.widgetFeatures = this.widgetFeatures;
         that.descriptionRes = this.descriptionRes;
         that.isExtendedFromAppWidgetProvider = this.isExtendedFromAppWidgetProvider;
+        if (generatedPreviews()) {
+            that.generatedPreviewCategories = this.generatedPreviewCategories;
+        }
         return that;
     }
 
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 879656a..d743992 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -76,6 +76,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 /**
@@ -909,34 +910,20 @@
     }
 
     /**
-     * Listener for any changes to the list of attached transports.
-     *
-     * @see com.android.server.companion.transport.Transport
-     *
-     * @hide
-     */
-    public interface OnTransportsChangedListener {
-        /**
-         * Invoked when a transport is attached or detached.
-         *
-         * @param associations all the associations which have connected transports.
-         */
-        void onTransportsChanged(@NonNull List<AssociationInfo> associations);
-    }
-
-    /**
      * Adds a listener for any changes to the list of attached transports.
-     * {@link OnTransportsChangedListener#onTransportsChanged(List)} will be triggered with a list
-     * of existing transports when a transport is detached or a new transport is attached.
+     * Registered listener will be triggered with a list of existing transports when a transport
+     * is detached or a new transport is attached.
      *
+     * @param executor The executor which will be used to invoke the listener.
+     * @param listener Called when a transport is attached or detached. Contains the updated list of
+     *                 associations which have connected transports.
      * @see com.android.server.companion.transport.Transport
-     *
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void addOnTransportsChangedListener(
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull OnTransportsChangedListener listener) {
+            @NonNull Consumer<List<AssociationInfo>> listener) {
         final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy(
                 executor, listener);
         try {
@@ -955,7 +942,7 @@
      */
     @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void removeOnTransportsChangedListener(
-            @NonNull OnTransportsChangedListener listener) {
+            @NonNull Consumer<List<AssociationInfo>> listener) {
         final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy(
                 null, listener);
         try {
@@ -983,28 +970,18 @@
     }
 
     /**
-     * Listener that triggers a callback when a message is received through a connected transport.
+     * Adds a listener that triggers when messages of given type are received.
      *
-     * @see #addOnMessageReceivedListener(Executor, int, OnMessageReceivedListener)
-     *
-     * @hide
-     */
-    public interface OnMessageReceivedListener {
-        /**
-         * Called when a message is received.
-         */
-        void onMessageReceived(int associationId, @NonNull byte[] data);
-    }
-
-    /**
-     * Adds a listener to trigger callbacks when messages of given type are received.
-     *
+     * @param executor The executor which will be used to invoke the listener.
+     * @param messageType Message type to be subscribed to.
+     * @param listener Called when a message is received. Contains the association ID of the message
+     *                 sender and the message payload as a byte array.
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void addOnMessageReceivedListener(
             @NonNull @CallbackExecutor Executor executor, int messageType,
-            @NonNull OnMessageReceivedListener listener) {
+            @NonNull BiConsumer<Integer, byte[]> listener) {
         final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy(
                 executor, listener);
         try {
@@ -1021,7 +998,7 @@
      */
     @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void removeOnMessageReceivedListener(int messageType,
-            @NonNull OnMessageReceivedListener listener) {
+            @NonNull BiConsumer<Integer, byte[]> listener) {
         final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy(
                 null, listener);
         try {
@@ -1061,6 +1038,7 @@
         }
     }
 
+    // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
     /**
      * Register to receive callbacks whenever the associated device comes in and out of range.
      *
@@ -1117,7 +1095,7 @@
                             callingUid, callingPid);
         }
     }
-
+    // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
     /**
      * Unregister for receiving callbacks whenever the associated device comes in and out of range.
      *
@@ -1160,6 +1138,64 @@
     }
 
     /**
+     * Register to receive callbacks whenever the associated device comes in and out of range.
+     *
+     * <p>The app doesn't need to remain running in order to receive its callbacks.</p>
+     *
+     * <p>Calling app must check for feature presence of
+     * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} before calling this API.</p>
+     *
+     * <p>For Bluetooth LE devices, this is based on scanning for device with the given address.
+     * The system will scan for the device when Bluetooth is ON or Bluetooth scanning is ON.</p>
+     *
+     * <p>For Bluetooth classic devices this is triggered when the device connects/disconnects.</p>
+     *
+     * <p>WiFi devices are not supported.</p>
+     *
+     * <p>If a Bluetooth LE device wants to use a rotating mac address, it is recommended to use
+     * Resolvable Private Address, and ensure the device is bonded to the phone so that android OS
+     * is able to resolve the address.</p>
+     *
+     * @param request A request for setting the types of device for observing device presence.
+     *
+     * @see ObservingDevicePresenceRequest.Builder
+     * @see CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)
+     */
+    @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
+    @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
+    public void startObservingDevicePresence(@NonNull ObservingDevicePresenceRequest request) {
+        Objects.requireNonNull(request, "request cannot be null");
+
+        try {
+            mService.startObservingDevicePresence(
+                    request, mContext.getOpPackageName(), mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregister for receiving callbacks whenever the associated device comes in and out of range.
+     *
+     * Calling app must check for feature presence of
+     * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} before calling this API.
+     *
+     * @param request A request for setting the types of device for observing device presence.
+     */
+    @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
+    @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
+    public void stopObservingDevicePresence(@NonNull ObservingDevicePresenceRequest request) {
+        Objects.requireNonNull(request, "request cannot be null");
+
+        try {
+            mService.stopObservingDevicePresence(
+                    request, mContext.getOpPackageName(), mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Dispatch a message to system for processing. It should only be called by
      * {@link CompanionDeviceService#dispatchMessageToSystem(int, int, byte[])}
      *
@@ -1596,34 +1632,34 @@
     private static class OnTransportsChangedListenerProxy
             extends IOnTransportsChangedListener.Stub {
         private final Executor mExecutor;
-        private final OnTransportsChangedListener mListener;
+        private final Consumer<List<AssociationInfo>> mListener;
 
         private OnTransportsChangedListenerProxy(Executor executor,
-                OnTransportsChangedListener listener) {
+                Consumer<List<AssociationInfo>> listener) {
             mExecutor = executor;
             mListener = listener;
         }
 
         @Override
         public void onTransportsChanged(@NonNull List<AssociationInfo> associations) {
-            mExecutor.execute(() -> mListener.onTransportsChanged(associations));
+            mExecutor.execute(() -> mListener.accept(associations));
         }
     }
 
     private static class OnMessageReceivedListenerProxy
             extends IOnMessageReceivedListener.Stub {
         private final Executor mExecutor;
-        private final OnMessageReceivedListener mListener;
+        private final BiConsumer<Integer, byte[]> mListener;
 
         private OnMessageReceivedListenerProxy(Executor executor,
-                OnMessageReceivedListener listener) {
+                BiConsumer<Integer, byte[]> listener) {
             mExecutor = executor;
             mListener = listener;
         }
 
         @Override
         public void onMessageReceived(int associationId, byte[] data) {
-            mExecutor.execute(() -> mListener.onMessageReceived(associationId, data));
+            mExecutor.execute(() -> mListener.accept(associationId, data));
         }
     }
 
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index 4d0267c..5ad2348 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -18,7 +18,6 @@
 package android.companion;
 
 import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -33,8 +32,6 @@
 
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -123,62 +120,6 @@
      */
     public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
 
-    /** @hide */
-    @IntDef(prefix = {"DEVICE_EVENT"}, value = {
-            DEVICE_EVENT_BLE_APPEARED,
-            DEVICE_EVENT_BLE_DISAPPEARED,
-            DEVICE_EVENT_BT_CONNECTED,
-            DEVICE_EVENT_BT_DISCONNECTED,
-            DEVICE_EVENT_SELF_MANAGED_APPEARED,
-            DEVICE_EVENT_SELF_MANAGED_DISAPPEARED
-    })
-
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface DeviceEvent {}
-
-    /**
-     * Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback
-     * with this event if the device comes into BLE range.
-     */
-    @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
-    public static final int DEVICE_EVENT_BLE_APPEARED = 0;
-
-    /**
-     * Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback
-     * with this event if the device is no longer in BLE range.
-     */
-    @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
-    public static final int DEVICE_EVENT_BLE_DISAPPEARED = 1;
-
-    /**
-     * Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback
-     * with this event when the bluetooth device is connected.
-     */
-    @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
-    public static final int DEVICE_EVENT_BT_CONNECTED = 2;
-
-    /**
-     * Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback
-     * with this event if the bluetooth device is disconnected.
-     */
-    @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
-    public static final int DEVICE_EVENT_BT_DISCONNECTED = 3;
-
-    /**
-     * A companion app for a self-managed device will receive the callback
-     * {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has appeared on its
-     * own.
-     */
-    @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
-    public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4;
-
-    /**
-     * A companion app for a self-managed device will receive the callback
-     * {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has disappeared on
-     * its own.
-     */
-    @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
-    public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5;
 
     private final Stub mRemote = new Stub();
 
@@ -306,6 +247,7 @@
                 .detachSystemDataTransport(associationId);
     }
 
+    // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
     /**
      * Called by system whenever a device associated with this app is connected.
      *
@@ -318,6 +260,7 @@
         }
     }
 
+    // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
     /**
      * Called by system whenever a device associated with this app is disconnected.
      *
@@ -331,27 +274,13 @@
     }
 
     /**
-     *  Called by the system during device events.
+     * Called by the system during device events.
      *
-     *  <p>E.g. Event {@link #DEVICE_EVENT_BLE_APPEARED} will be called when the associated
-     *  companion device comes into BLE range.
-     *  <p>Event {@link #DEVICE_EVENT_BLE_DISAPPEARED} will be called when the associated
-     *  companion device is no longer in BLE range.
-     *  <p> Event {@link #DEVICE_EVENT_BT_CONNECTED} will be called when the associated
-     *  companion device is connected.
-     *  <p>Event {@link #DEVICE_EVENT_BT_DISCONNECTED} will be called when the associated
-     *  companion device is disconnected.
-     *  Note that app must receive {@link #DEVICE_EVENT_BLE_APPEARED} first before
-     *  {@link #DEVICE_EVENT_BLE_DISAPPEARED} and {@link #DEVICE_EVENT_BT_CONNECTED}
-     *  before {@link #DEVICE_EVENT_BT_DISCONNECTED}.
-     *
-     * @param associationInfo A record for the companion device.
-     * @param event Associated companion device's event.
+     * @see CompanionDeviceManager#startObservingDevicePresence(ObservingDevicePresenceRequest)
      */
     @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
     @MainThread
-    public void onDeviceEvent(@NonNull AssociationInfo associationInfo,
-            @DeviceEvent int event) {
+    public void onDevicePresenceEvent(@NonNull DevicePresenceEvent event) {
         // Do nothing. Companion apps can override this function.
     }
 
@@ -390,9 +319,10 @@
         }
 
         @Override
-        public void onDeviceEvent(AssociationInfo associationInfo, int event) {
-            mMainHandler.postAtFrontOfQueue(
-                    () -> mService.onDeviceEvent(associationInfo, event));
+        public void onDevicePresenceEvent(DevicePresenceEvent event) {
+            if (Flags.devicePresence()) {
+                mMainHandler.postAtFrontOfQueue(() -> mService.onDevicePresenceEvent(event));
+            }
         }
     }
 }
diff --git a/core/java/android/companion/DevicePresenceEvent.aidl b/core/java/android/companion/DevicePresenceEvent.aidl
new file mode 100644
index 0000000..1521574
--- /dev/null
+++ b/core/java/android/companion/DevicePresenceEvent.aidl
@@ -0,0 +1,19 @@
+ /*
+  * Copyright (C) 2024 The Android Open Source Project
+  *
+  * Licensed under the Apache License, Version 2.0 (the "License");
+  * you may not use this file except in compliance with the License.
+  * You may obtain a copy of the License at
+  *
+  *      http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS,
+  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  * See the License for the specific language governing permissions and
+  * limitations under the License.
+  */
+
+ package android.companion;
+
+ parcelable DevicePresenceEvent;
diff --git a/core/java/android/companion/DevicePresenceEvent.java b/core/java/android/companion/DevicePresenceEvent.java
new file mode 100644
index 0000000..30439a5
--- /dev/null
+++ b/core/java/android/companion/DevicePresenceEvent.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Event for observing device presence.
+ *
+ * @see CompanionDeviceManager#startObservingDevicePresence(ObservingDevicePresenceRequest)
+ * @see ObservingDevicePresenceRequest.Builder#setUuid(ParcelUuid)
+ * @see ObservingDevicePresenceRequest.Builder#setAssociationId(int)
+ */
+@FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
+public final class DevicePresenceEvent implements Parcelable {
+
+    /** @hide */
+    @IntDef(prefix = {"EVENT"}, value = {
+            EVENT_BLE_APPEARED,
+            EVENT_BLE_DISAPPEARED,
+            EVENT_BT_CONNECTED,
+            EVENT_BT_DISCONNECTED,
+            EVENT_SELF_MANAGED_APPEARED,
+            EVENT_SELF_MANAGED_DISAPPEARED
+    })
+
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Event {}
+
+    /**
+     * Indicate observing device presence base on the ParcelUuid but not association id.
+     */
+    public static final int NO_ASSOCIATION = -1;
+
+    /**
+     * Companion app receives
+     * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} callback
+     * with this event if the device comes into BLE range.
+     */
+    public static final int EVENT_BLE_APPEARED = 0;
+
+    /**
+     * Companion app receives
+     * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} callback
+     * with this event if the device is no longer in BLE range.
+     */
+    public static final int EVENT_BLE_DISAPPEARED = 1;
+
+    /**
+     * Companion app receives
+     * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} callback
+     * with this event when the bluetooth device is connected.
+     */
+    public static final int EVENT_BT_CONNECTED = 2;
+
+    /**
+     * Companion app receives
+     * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} callback
+     * with this event if the bluetooth device is disconnected.
+     */
+    public static final int EVENT_BT_DISCONNECTED = 3;
+
+    /**
+     * A companion app for a self-managed device will receive the callback
+     * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)}
+     * if it reports that a device has appeared on its
+     * own.
+     */
+    public static final int EVENT_SELF_MANAGED_APPEARED = 4;
+
+    /**
+     * A companion app for a self-managed device will receive the callback
+     * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} if it reports
+     * that a device has disappeared on its own.
+     */
+    public static final int EVENT_SELF_MANAGED_DISAPPEARED = 5;
+    private final int mAssociationId;
+    private final int mEvent;
+    @Nullable
+    private final ParcelUuid mUuid;
+
+    private static final int PARCEL_UUID_NULL = 0;
+
+    private static final int PARCEL_UUID_NOT_NULL = 1;
+
+    /**
+     * Create a new DevicePresenceEvent.
+     */
+    public DevicePresenceEvent(
+            int associationId, @Event int event, @Nullable ParcelUuid uuid) {
+        mAssociationId = associationId;
+        mEvent = event;
+        mUuid = uuid;
+    }
+
+    /**
+     * @return The association id has been used to observe device presence.
+     *
+     * Caller will receive the valid association id if only if using
+     * {@link ObservingDevicePresenceRequest.Builder#setAssociationId(int)}, otherwise
+     * return {@link #NO_ASSOCIATION}.
+     *
+     * @see ObservingDevicePresenceRequest.Builder#setAssociationId(int)
+     */
+    public int getAssociationId() {
+        return mAssociationId;
+    }
+
+    /**
+     * @return Associated companion device's event.
+     */
+    public int getEvent() {
+        return mEvent;
+    }
+
+    /**
+     * @return The ParcelUuid has been used to observe device presence.
+     *
+     * Caller will receive the ParcelUuid if only if using
+     * {@link ObservingDevicePresenceRequest.Builder#setUuid(ParcelUuid)}, otherwise return null.
+     *
+     * @see ObservingDevicePresenceRequest.Builder#setUuid(ParcelUuid)
+     */
+
+    @Nullable
+    public ParcelUuid getUuid() {
+        return mUuid;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mAssociationId);
+        dest.writeInt(mEvent);
+        if (mUuid == null) {
+            // Write 0 to the parcel to indicate the ParcelUuid is null.
+            dest.writeInt(PARCEL_UUID_NULL);
+        } else {
+            dest.writeInt(PARCEL_UUID_NOT_NULL);
+            mUuid.writeToParcel(dest, flags);
+        }
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (!(o instanceof DevicePresenceEvent that)) return false;
+
+        return Objects.equals(mUuid, that.mUuid)
+                && mAssociationId == that.mAssociationId
+                && mEvent == that.mEvent;
+    }
+
+    @Override
+    public String toString() {
+        return "ObservingDevicePresenceResult { "
+                + "Association Id= " + mAssociationId + ","
+                + "ParcelUuid= " + mUuid + ","
+                + "Event= " + mEvent + "}";
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAssociationId, mEvent, mUuid);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<DevicePresenceEvent> CREATOR =
+            new Parcelable.Creator<DevicePresenceEvent>() {
+                @Override
+                public DevicePresenceEvent[] newArray(int size) {
+                    return new DevicePresenceEvent[size];
+                }
+
+                @Override
+                public DevicePresenceEvent createFromParcel(@NonNull Parcel in) {
+                    return new DevicePresenceEvent(in);
+                }
+            };
+
+    private DevicePresenceEvent(@NonNull Parcel in) {
+        mAssociationId = in.readInt();
+        mEvent = in.readInt();
+        if (in.readInt() == PARCEL_UUID_NULL) {
+            mUuid = null;
+        } else {
+            mUuid = ParcelUuid.CREATOR.createFromParcel(in);
+        }
+    }
+}
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 22689f3..57d59e5 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -24,8 +24,11 @@
 import android.companion.ISystemDataTransferCallback;
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
+import android.companion.ObservingDevicePresenceRequest;
 import android.companion.datatransfer.PermissionSyncRequest;
 import android.content.ComponentName;
+import android.os.ParcelUuid;
+
 
 /**
  * Interface for communication with the core companion device manager service.
@@ -132,4 +135,10 @@
     byte[] getBackupPayload(int userId);
 
     void applyRestoredPayload(in byte[] payload, int userId);
+
+    @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
+    void startObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
+
+    @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
+    void stopObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
 }
diff --git a/core/java/android/companion/ICompanionDeviceService.aidl b/core/java/android/companion/ICompanionDeviceService.aidl
index 2a311bf..f5401d2 100644
--- a/core/java/android/companion/ICompanionDeviceService.aidl
+++ b/core/java/android/companion/ICompanionDeviceService.aidl
@@ -17,10 +17,12 @@
 package android.companion;
 
 import android.companion.AssociationInfo;
+import android.companion.DevicePresenceEvent;
+import android.os.ParcelUuid;
 
 /** @hide */
 oneway interface ICompanionDeviceService {
     void onDeviceAppeared(in AssociationInfo associationInfo);
     void onDeviceDisappeared(in AssociationInfo associationInfo);
-    void onDeviceEvent(in AssociationInfo associationInfo, int state);
+    void onDevicePresenceEvent(in DevicePresenceEvent event);
 }
diff --git a/core/java/android/companion/ObservingDevicePresenceRequest.aidl b/core/java/android/companion/ObservingDevicePresenceRequest.aidl
new file mode 100644
index 0000000..fed0607
--- /dev/null
+++ b/core/java/android/companion/ObservingDevicePresenceRequest.aidl
@@ -0,0 +1,19 @@
+ /*
+  * Copyright (C) 2024 The Android Open Source Project
+  *
+  * Licensed under the Apache License, Version 2.0 (the "License");
+  * you may not use this file except in compliance with the License.
+  * You may obtain a copy of the License at
+  *
+  *      http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS,
+  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  * See the License for the specific language governing permissions and
+  * limitations under the License.
+  */
+
+ package android.companion;
+
+ parcelable ObservingDevicePresenceRequest;
\ No newline at end of file
diff --git a/core/java/android/companion/ObservingDevicePresenceRequest.java b/core/java/android/companion/ObservingDevicePresenceRequest.java
new file mode 100644
index 0000000..f1d594e
--- /dev/null
+++ b/core/java/android/companion/ObservingDevicePresenceRequest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+import android.provider.OneTimeUseBuilder;
+
+import java.util.Objects;
+
+/**
+ * A request for setting the types of device for observing device presence.
+ *
+ * <p>Only supports association id or ParcelUuid and calling app must declare uses-permission
+ * {@link android.Manifest.permission#REQUEST_OBSERVE_DEVICE_UUID_PRESENCE} if using
+ * {@link Builder#setUuid(ParcelUuid)}.</p>
+ *
+ * Calling apps must use either ObservingDevicePresenceRequest.Builder#setUuid(ParcelUuid) or
+ * ObservingDevicePresenceRequest.Builder#setAssociationId(int), but not both.
+ *
+ * @see Builder#setUuid(ParcelUuid)
+ * @see Builder#setAssociationId(int)
+ * @see CompanionDeviceManager#startObservingDevicePresence(ObservingDevicePresenceRequest)
+ */
+@FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
+public final class ObservingDevicePresenceRequest implements Parcelable {
+    private final int mAssociationId;
+    @Nullable private final ParcelUuid mUuid;
+
+    private static final int PARCEL_UUID_NULL = 0;
+
+    private static final int PARCEL_UUID_NOT_NULL = 1;
+
+    private ObservingDevicePresenceRequest(int associationId, ParcelUuid uuid) {
+        mAssociationId = associationId;
+        mUuid = uuid;
+    }
+
+    private ObservingDevicePresenceRequest(@NonNull Parcel in) {
+        mAssociationId = in.readInt();
+        if (in.readInt() == PARCEL_UUID_NULL) {
+            mUuid = null;
+        } else {
+            mUuid = ParcelUuid.CREATOR.createFromParcel(in);
+        }
+    }
+
+    /**
+     * @return the association id for observing device presence. It will return
+     * {@link DevicePresenceEvent#NO_ASSOCIATION} if using
+     * {@link Builder#setUuid(ParcelUuid)}.
+     */
+    public int getAssociationId() {
+        return mAssociationId;
+    }
+
+    /**
+     * @return the ParcelUuid for observing device presence.
+     */
+    @Nullable
+    public ParcelUuid getUuid() {
+        return mUuid;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mAssociationId);
+        if (mUuid == null) {
+            // Write 0 to the parcel to indicate the ParcelUuid is null.
+            dest.writeInt(PARCEL_UUID_NULL);
+        } else {
+            dest.writeInt(PARCEL_UUID_NOT_NULL);
+            mUuid.writeToParcel(dest, flags);
+        }
+
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<ObservingDevicePresenceRequest> CREATOR =
+            new Parcelable.Creator<ObservingDevicePresenceRequest>() {
+                @Override
+                public ObservingDevicePresenceRequest[] newArray(int size) {
+                    return new ObservingDevicePresenceRequest[size];
+                }
+
+                @Override
+                public ObservingDevicePresenceRequest createFromParcel(@NonNull Parcel in) {
+                    return new ObservingDevicePresenceRequest(in);
+                }
+            };
+
+    @Override
+    public String toString() {
+        return "ObservingDevicePresenceRequest { "
+                + "Association Id= " + mAssociationId + ","
+                + "ParcelUuid= " + mUuid + "}";
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ObservingDevicePresenceRequest that)) return false;
+
+        return Objects.equals(mUuid, that.mUuid) && mAssociationId == that.mAssociationId;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAssociationId, mUuid);
+    }
+
+    /**
+     * A builder for {@link ObservingDevicePresenceRequest}
+     */
+    public static final class Builder extends OneTimeUseBuilder<ObservingDevicePresenceRequest> {
+        // Initial the association id to {@link DevicePresenceEvent.NO_ASSOCIATION}
+        // to indicate the value is not set yet.
+        private int mAssociationId = DevicePresenceEvent.NO_ASSOCIATION;
+        private ParcelUuid mUuid;
+
+        public Builder() {}
+
+        /**
+         * Set the association id to be observed for device presence.
+         *
+         * <p>The provided device must be {@link CompanionDeviceManager#associate associated}
+         * with the calling app before calling this method if using this API.
+         *
+         * Caller must implement a single {@link CompanionDeviceService} which will be bound to and
+         * receive callbacks to
+         * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)}.</p>
+         *
+         * <p>Calling apps must use either {@link #setUuid(ParcelUuid)}
+         * or this API, but not both.</p>
+         *
+         * @param associationId The association id for observing device presence.
+         */
+        @NonNull
+        public Builder setAssociationId(int associationId) {
+            checkNotUsed();
+            this.mAssociationId = associationId;
+            return this;
+        }
+
+        /**
+         * Set the ParcelUuid to be observed for device presence.
+         *
+         * <p>It does not require to create the association before calling this API.
+         * This only supports classic Bluetooth scan and caller must implement
+         * a single {@link CompanionDeviceService} which will be bound to and receive callbacks to
+         * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)}.</p>
+         *
+         * <p>The Uuid should be matching one of the ParcelUuid form
+         * {@link android.bluetooth.BluetoothDevice#getUuids()}</p>
+         *
+         * <p>Calling apps must use either this API or {@link #setAssociationId(int)},
+         * but not both.</p>
+         *
+         * @param uuid The ParcelUuid for observing device presence.
+         */
+        @NonNull
+        @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE)
+        public Builder setUuid(@NonNull ParcelUuid uuid) {
+            checkNotUsed();
+            this.mUuid = uuid;
+            return this;
+        }
+
+        @NonNull
+        @Override
+        public ObservingDevicePresenceRequest build() {
+            markUsed();
+            if (mUuid != null && mAssociationId != DevicePresenceEvent.NO_ASSOCIATION) {
+                throw new IllegalStateException("Cannot observe device presence based on "
+                        + "both ParcelUuid and association ID. Choose one or the other.");
+            } else if (mUuid == null && mAssociationId <= 0) {
+                throw new IllegalStateException("Must provide either a ParcelUuid or "
+                        + "a valid association ID to observe device presence.");
+            }
+
+            return new ObservingDevicePresenceRequest(mAssociationId, mUuid);
+        }
+    }
+}
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index 9e410b8..d634b64 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -33,4 +33,4 @@
     namespace: "companion"
     description: "Expose perm sync user consent API"
     bug: "309528663"
-}
\ No newline at end of file
+}
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 12229b1..6eab363 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -35,6 +35,9 @@
 import android.hardware.input.VirtualMouseConfig;
 import android.hardware.input.VirtualMouseRelativeEvent;
 import android.hardware.input.VirtualMouseScrollEvent;
+import android.hardware.input.VirtualStylusButtonEvent;
+import android.hardware.input.VirtualStylusConfig;
+import android.hardware.input.VirtualStylusMotionEvent;
 import android.hardware.input.VirtualTouchEvent;
 import android.hardware.input.VirtualTouchscreenConfig;
 import android.hardware.input.VirtualNavigationTouchpadConfig;
@@ -144,6 +147,12 @@
     void createVirtualNavigationTouchpad(in VirtualNavigationTouchpadConfig config, IBinder token);
 
     /**
+     * Creates a new stylus and registers it with the input framework with the given token.
+     */
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+    void createVirtualStylus(in VirtualStylusConfig config, IBinder token);
+
+    /**
      * Removes the input device corresponding to the given token from the framework.
      */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
@@ -156,32 +165,32 @@
     int getInputDeviceId(IBinder token);
 
     /**
-    * Injects a key event to the virtual dpad corresponding to the given token.
-    */
+     * Injects a key event to the virtual dpad corresponding to the given token.
+     */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendDpadKeyEvent(IBinder token, in VirtualKeyEvent event);
 
     /**
-    * Injects a key event to the virtual keyboard corresponding to the given token.
-    */
+     * Injects a key event to the virtual keyboard corresponding to the given token.
+     */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendKeyEvent(IBinder token, in VirtualKeyEvent event);
 
     /**
-    * Injects a button event to the virtual mouse corresponding to the given token.
-    */
+     * Injects a button event to the virtual mouse corresponding to the given token.
+     */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendButtonEvent(IBinder token, in VirtualMouseButtonEvent event);
 
     /**
-    * Injects a relative event to the virtual mouse corresponding to the given token.
-    */
+     * Injects a relative event to the virtual mouse corresponding to the given token.
+     */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendRelativeEvent(IBinder token, in VirtualMouseRelativeEvent event);
 
     /**
-    * Injects a scroll event to the virtual mouse corresponding to the given token.
-    */
+     * Injects a scroll event to the virtual mouse corresponding to the given token.
+     */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendScrollEvent(IBinder token, in VirtualMouseScrollEvent event);
 
@@ -192,6 +201,18 @@
     boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event);
 
     /**
+     * Injects a motion event from the virtual stylus input device corresponding to the given token.
+     */
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+    boolean sendStylusMotionEvent(IBinder token, in VirtualStylusMotionEvent event);
+
+    /**
+     * Injects a button event from the virtual stylus input device corresponding to the given token.
+     */
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+    boolean sendStylusButtonEvent(IBinder token, in VirtualStylusButtonEvent event);
+
+    /**
      * Returns all virtual sensors created for this device.
      */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 0493312..83e18ec 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -78,6 +78,11 @@
     int getDeviceIdForDisplayId(int displayId);
 
     /**
+     * Returns the display name corresponding to the given persistent device ID, if any.
+     */
+    CharSequence getDisplayNameForPersistentDeviceId(in String persistentDeviceId);
+
+    /**
      * Checks whether the passed {@code deviceId} is a valid virtual device ID or not.
      * {@link VirtualDeviceManager#DEVICE_ID_DEFAULT} is not valid as it is the ID of the default
      * device which is not a virtual device. {@code deviceId} must correspond to a virtual device
@@ -128,4 +133,10 @@
      * device.
      */
     boolean isVirtualDeviceOwnedMirrorDisplay(int displayId);
+
+    /**
+     * Returns all current persistent device IDs, including the ones for which no virtual device
+     * exists, as long as one may have existed or can be created.
+     */
+    List<String> getAllPersistentDeviceIds();
 }
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index 2abeeee..c1e443d 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -42,6 +42,8 @@
 import android.hardware.input.VirtualMouseConfig;
 import android.hardware.input.VirtualNavigationTouchpad;
 import android.hardware.input.VirtualNavigationTouchpadConfig;
+import android.hardware.input.VirtualStylus;
+import android.hardware.input.VirtualStylusConfig;
 import android.hardware.input.VirtualTouchscreen;
 import android.hardware.input.VirtualTouchscreenConfig;
 import android.media.AudioManager;
@@ -316,6 +318,19 @@
         }
     }
 
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    @NonNull
+    VirtualStylus createVirtualStylus(@NonNull VirtualStylusConfig config) {
+        try {
+            final IBinder token = new Binder(
+                    "android.hardware.input.VirtualStylus:" + config.getInputDeviceName());
+            mVirtualDevice.createVirtualStylus(config, token);
+            return new VirtualStylus(config, mVirtualDevice, token);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     @NonNull
     VirtualNavigationTouchpad createVirtualNavigationTouchpad(
             @NonNull VirtualNavigationTouchpadConfig config) {
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index eef60f1..90d251b 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -55,6 +55,8 @@
 import android.hardware.input.VirtualMouseConfig;
 import android.hardware.input.VirtualNavigationTouchpad;
 import android.hardware.input.VirtualNavigationTouchpadConfig;
+import android.hardware.input.VirtualStylus;
+import android.hardware.input.VirtualStylusConfig;
 import android.hardware.input.VirtualTouchscreen;
 import android.hardware.input.VirtualTouchscreenConfig;
 import android.media.AudioManager;
@@ -359,6 +361,34 @@
     }
 
     /**
+     * Get the display name for a given persistent device ID.
+     *
+     * <p>This will work even if currently there is no valid virtual device with the given
+     * persistent ID, as long as such a device has been created or can be created.</p>
+     *
+     * @return the display name associated with the given persistent device ID, or {@code null} if
+     *     the persistent ID is invalid or does not correspond to a virtual device.
+     *
+     * @hide
+     */
+    // TODO(b/315481938): Link @see VirtualDevice#getPersistentDeviceId()
+    @FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API)
+    @SystemApi
+    @Nullable
+    public CharSequence getDisplayNameForPersistentDeviceId(@NonNull String persistentDeviceId) {
+        if (mService == null) {
+            Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service.");
+            return null;
+        }
+        try {
+            return mService.getDisplayNameForPersistentDeviceId(
+                    Objects.requireNonNull(persistentDeviceId));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Checks whether the passed {@code deviceId} is a valid virtual device ID or not.
      * {@link Context#DEVICE_ID_DEFAULT} is not valid as it is the ID of the default
      * device which is not a virtual device. {@code deviceId} must correspond to a virtual device
@@ -859,6 +889,19 @@
         }
 
         /**
+         * Creates a virtual stylus.
+         *
+         * @param config the touchscreen configurations for the virtual stylus.
+         */
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+        @NonNull
+        @FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
+        public VirtualStylus createVirtualStylus(
+                @NonNull VirtualStylusConfig config) {
+            return mVirtualDeviceInternal.createVirtualStylus(config);
+        }
+
+        /**
          * Creates a VirtualAudioDevice, capable of recording audio emanating from this device,
          * or injecting audio from another device.
          *
@@ -886,11 +929,14 @@
         }
 
         /**
-         * Creates a new virtual camera. If a virtual camera was already created, it will be closed.
+         * Creates a new virtual camera with the given {@link VirtualCameraConfig}. A virtual device
+         * can create a virtual camera only if it has
+         * {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM} as its
+         * {@link VirtualDeviceParams#POLICY_TYPE_CAMERA}.
          *
-         * @param config camera config.
-         * @return newly created camera;
-         * @hide
+         * @param config camera configuration.
+         * @return newly created camera.
+         * @see VirtualDeviceParams#POLICY_TYPE_CAMERA
          */
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         @NonNull
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 0253ddd..f9a9da1 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -159,7 +159,7 @@
      * @hide
      */
     @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS, POLICY_TYPE_AUDIO,
-            POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY})
+            POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY, POLICY_TYPE_CAMERA})
     @Retention(RetentionPolicy.SOURCE)
     @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
     public @interface PolicyType {}
@@ -246,6 +246,23 @@
     @FlaggedApi(Flags.FLAG_CROSS_DEVICE_CLIPBOARD)
     public static final int POLICY_TYPE_CLIPBOARD = 4;
 
+    /**
+     * Tells the camera framework how to handle camera requests for the front and back cameras from
+     * contexts associated with this virtual device.
+     *
+     * <ul>
+     *     <li>{@link #DEVICE_POLICY_DEFAULT}: Returns the front and back cameras of the default
+     *     device.
+     *     <li>{@link #DEVICE_POLICY_CUSTOM}: Returns the front and back cameras cameras of the
+     *     virtual device. Note that if the virtual device did not create any virtual cameras,
+     *     then no front and back cameras will be available.
+     * </ul>
+     *
+     * @see Context#getDeviceId
+     */
+    @FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
+    public static final int POLICY_TYPE_CAMERA = 5;
+
     private final int mLockState;
     @NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
     @NavigationPolicy
@@ -1153,6 +1170,10 @@
                 mDevicePolicies.delete(POLICY_TYPE_CLIPBOARD);
             }
 
+            if (!Flags.virtualCamera()) {
+                mDevicePolicies.delete(POLICY_TYPE_CAMERA);
+            }
+
             if ((mAudioPlaybackSessionId != AUDIO_SESSION_ID_GENERATE
                     || mAudioRecordingSessionId != AUDIO_SESSION_ID_GENERATE)
                     && mDevicePolicies.get(POLICY_TYPE_AUDIO, DEVICE_POLICY_DEFAULT)
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl b/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl
index 44942d6..f4f69b5 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl
+++ b/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.companion.virtual.camera;
 
-import android.companion.virtual.camera.VirtualCameraStreamConfig;
 import android.view.Surface;
 
 /**
@@ -30,38 +30,36 @@
      * Called when one of the requested stream has been configured by the virtual camera service and
      * is ready to receive data onto its {@link Surface}
      *
-     * @param streamId     The id of the configured stream
-     * @param surface      The surface to write data into for this stream
-     * @param streamConfig The image data configuration for this stream
+     * @param streamId The id of the configured stream
+     * @param surface The surface to write data into for this stream
+     * @param width The width of the surface
+     * @param height The height of the surface
+     * @param format The pixel format of the surface
      */
-    oneway void onStreamConfigured(
-            int streamId,
-            in Surface surface,
-            in VirtualCameraStreamConfig streamConfig);
+    oneway void onStreamConfigured(int streamId, in Surface surface, int width, int height,
+            int format);
 
     /**
      * The client application is requesting a camera frame for the given streamId and frameId.
      *
      * <p>The virtual camera needs to write the frame data in the {@link Surface} corresponding to
-     * this stream that was provided during the {@link #onStreamConfigured(int, Surface,
-     * VirtualCameraStreamConfig)} call.
+     * this stream that was provided during the
+     * {@link #onStreamConfigured(int, Surface, int, int, int)} call.
      *
      * @param streamId The streamId for which the frame is requested. This corresponds to the
-     *     streamId that was given in {@link #onStreamConfigured(int, Surface,
-     *     VirtualCameraStreamConfig)}
+     *     streamId that was given in {@link #onStreamConfigured(int, Surface, int, int, int)}
      * @param frameId The frameId that is being requested. Each request will have a different
      *     frameId, that will be increasing for each call with a particular streamId.
      */
     oneway void onProcessCaptureRequest(int streamId, long frameId);
 
     /**
-     * The stream previously configured when {@link #onStreamConfigured(int, Surface,
-     * VirtualCameraStreamConfig)} was called is now being closed and associated resources can be
-     * freed. The Surface was disposed on the client side and should not be used anymore by the
-     * virtual camera owner.
+     * The stream previously configured when
+     * {@link #onStreamConfigured(int, Surface, int, int, int)} was called is now being closed and
+     * associated resources can be freed. The Surface was disposed on the client side and should not
+     * be used anymore by the virtual camera owner.
      *
      * @param streamId The id of the stream that was closed.
      */
     oneway void onStreamClosed(int streamId);
-
 }
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
index 5b658b8..c894de4 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
@@ -17,9 +17,11 @@
 package android.companion.virtual.camera;
 
 import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.companion.virtual.flags.Flags;
+import android.graphics.ImageFormat;
 import android.view.Surface;
 
 import java.util.concurrent.Executor;
@@ -41,33 +43,33 @@
      *
      * @param streamId The id of the configured stream
      * @param surface The surface to write data into for this stream
-     * @param streamConfig The image data configuration for this stream
+     * @param width The width of the surface
+     * @param height The height of the surface
+     * @param format The {@link ImageFormat} of the surface
      */
-    void onStreamConfigured(
-            int streamId,
-            @NonNull Surface surface,
-            @NonNull VirtualCameraStreamConfig streamConfig);
+    void onStreamConfigured(int streamId, @NonNull Surface surface,
+            @IntRange(from = 1) int width, @IntRange(from = 1) int height,
+            @ImageFormat.Format int format);
 
     /**
      * The client application is requesting a camera frame for the given streamId and frameId.
      *
      * <p>The virtual camera needs to write the frame data in the {@link Surface} corresponding to
-     * this stream that was provided during the {@link #onStreamConfigured(int, Surface,
-     * VirtualCameraStreamConfig)} call.
+     * this stream that was provided during the
+     * {@link #onStreamConfigured(int, Surface, int, int, int)} call.
      *
      * @param streamId The streamId for which the frame is requested. This corresponds to the
-     *     streamId that was given in {@link #onStreamConfigured(int, Surface,
-     *     VirtualCameraStreamConfig)}
+     *     streamId that was given in {@link #onStreamConfigured(int, Surface, int, int, int)}
      * @param frameId The frameId that is being requested. Each request will have a different
      *     frameId, that will be increasing for each call with a particular streamId.
      */
     default void onProcessCaptureRequest(int streamId, long frameId) {}
 
     /**
-     * The stream previously configured when {@link #onStreamConfigured(int, Surface,
-     * VirtualCameraStreamConfig)} was called is now being closed and associated resources can be
-     * freed. The Surface corresponding to that streamId was disposed on the client side and should
-     * not be used anymore by the virtual camera owner
+     * The stream previously configured when
+     * {@link #onStreamConfigured(int, Surface, int, int, int)} was called is now being closed and
+     * associated resources can be freed. The Surface corresponding to that streamId was disposed on
+     * the client side and should not be used anymore by the virtual camera owner.
      *
      * @param streamId The id of the stream that was closed.
      */
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.aidl b/core/java/android/companion/virtual/camera/VirtualCameraConfig.aidl
index 88c27a5..5ceb4d1 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraConfig.aidl
+++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.companion.virtual.camera;
 
 /** @hide */
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
index 59fe9a1..06a0f5c 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
@@ -19,16 +19,23 @@
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
+import android.companion.virtual.VirtualDevice;
 import android.companion.virtual.flags.Flags;
 import android.graphics.ImageFormat;
+import android.graphics.PixelFormat;
+import android.hardware.camera2.CameraMetadata;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArraySet;
 import android.view.Surface;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Set;
 import java.util.concurrent.Executor;
 
@@ -43,16 +50,57 @@
 @FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
 public final class VirtualCameraConfig implements Parcelable {
 
+    private static final int LENS_FACING_UNKNOWN = -1;
+
+    /**
+     * Sensor orientation of {@code 0} degrees.
+     * @see #getSensorOrientation
+     */
+    public static final int SENSOR_ORIENTATION_0 = 0;
+    /**
+     * Sensor orientation of {@code 90} degrees.
+     * @see #getSensorOrientation
+     */
+    public static final int SENSOR_ORIENTATION_90 = 90;
+    /**
+     * Sensor orientation of {@code 180} degrees.
+     * @see #getSensorOrientation
+     */
+    public static final int SENSOR_ORIENTATION_180 = 180;
+    /**
+     * Sensor orientation of {@code 270} degrees.
+     * @see #getSensorOrientation
+     */
+    public static final int SENSOR_ORIENTATION_270 = 270;
+    /** @hide */
+    @IntDef(prefix = {"SENSOR_ORIENTATION_"}, value = {
+            SENSOR_ORIENTATION_0,
+            SENSOR_ORIENTATION_90,
+            SENSOR_ORIENTATION_180,
+            SENSOR_ORIENTATION_270
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SensorOrientation {}
+
     private final String mName;
     private final Set<VirtualCameraStreamConfig> mStreamConfigurations;
     private final IVirtualCameraCallback mCallback;
+    @SensorOrientation
+    private final int mSensorOrientation;
+    private final int mLensFacing;
 
     private VirtualCameraConfig(
             @NonNull String name,
             @NonNull Set<VirtualCameraStreamConfig> streamConfigurations,
             @NonNull Executor executor,
-            @NonNull VirtualCameraCallback callback) {
+            @NonNull VirtualCameraCallback callback,
+            @SensorOrientation int sensorOrientation,
+            int lensFacing) {
         mName = requireNonNull(name, "Missing name");
+        if (lensFacing == LENS_FACING_UNKNOWN) {
+            throw new IllegalArgumentException("Lens facing must be set");
+        }
+        mLensFacing = lensFacing;
         mStreamConfigurations =
                 Set.copyOf(requireNonNull(streamConfigurations, "Missing stream configurations"));
         if (mStreamConfigurations.isEmpty()) {
@@ -63,6 +111,7 @@
                 new VirtualCameraCallbackInternal(
                         requireNonNull(callback, "Missing callback"),
                         requireNonNull(executor, "Missing callback executor"));
+        mSensorOrientation = sensorOrientation;
     }
 
     private VirtualCameraConfig(@NonNull Parcel in) {
@@ -73,6 +122,8 @@
                         in.readParcelableArray(
                                 VirtualCameraStreamConfig.class.getClassLoader(),
                                 VirtualCameraStreamConfig.class));
+        mSensorOrientation = in.readInt();
+        mLensFacing = in.readInt();
     }
 
     @Override
@@ -86,6 +137,8 @@
         dest.writeStrongInterface(mCallback);
         dest.writeParcelableArray(
                 mStreamConfigurations.toArray(new VirtualCameraStreamConfig[0]), flags);
+        dest.writeInt(mSensorOrientation);
+        dest.writeInt(mLensFacing);
     }
 
     /**
@@ -100,7 +153,7 @@
      * Returns an unmodifiable set of the stream configurations added to this {@link
      * VirtualCameraConfig}.
      *
-     * @see VirtualCameraConfig.Builder#addStreamConfig(int, int, int)
+     * @see VirtualCameraConfig.Builder#addStreamConfig(int, int, int, int)
      */
     @NonNull
     public Set<VirtualCameraStreamConfig> getStreamConfigs() {
@@ -118,51 +171,141 @@
     }
 
     /**
+     * Returns the sensor orientation of this stream, which represents the clockwise angle (in
+     * degrees) through which the output image needs to be rotated to be upright on the device
+     * screen in its native orientation. Returns {@link #SENSOR_ORIENTATION_0} if omitted.
+     */
+    @SensorOrientation
+    public int getSensorOrientation() {
+        return mSensorOrientation;
+    }
+
+    /**
+     * Returns the direction that the virtual camera faces relative to the virtual device's screen.
+     *
+     * @see Builder#setLensFacing(int)
+     */
+    public int getLensFacing() {
+        return mLensFacing;
+    }
+
+    /**
      * Builder for {@link VirtualCameraConfig}.
      *
      * <p>To build an instance of {@link VirtualCameraConfig} the following conditions must be met:
-     * <li>At least one stream must be added with {@link #addStreamConfig(int, int, int)}.
+     * <li>At least one stream must be added with {@link #addStreamConfig(int, int, int, int)}.
      * <li>A callback must be set with {@link #setVirtualCameraCallback(Executor,
      *     VirtualCameraCallback)}
-     * <li>A camera name must be set with {@link #setName(String)}
+     * <li>A lens facing must be set with {@link #setLensFacing(int)}
      */
     @FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
     public static final class Builder {
 
-        private String mName;
+        private final String mName;
         private final ArraySet<VirtualCameraStreamConfig> mStreamConfigurations = new ArraySet<>();
         private Executor mCallbackExecutor;
         private VirtualCameraCallback mCallback;
+        private int mSensorOrientation = SENSOR_ORIENTATION_0;
+        private int mLensFacing = LENS_FACING_UNKNOWN;
 
         /**
-         * Set the name of the virtual camera instance.
+         * Creates a new instance of {@link Builder}.
+         *
+         * @param name The name of the {@link VirtualCamera}.
          */
-        @NonNull
-        public Builder setName(@NonNull String name) {
-            mName = requireNonNull(name, "Display name cannot be null");
-            return this;
+        public Builder(@NonNull String name) {
+            mName = requireNonNull(name, "Name cannot be null");
         }
 
         /**
-         * Add an available stream configuration fot this {@link VirtualCamera}.
+         * Adds a supported input stream configuration for this {@link VirtualCamera}.
          *
          * <p>At least one {@link VirtualCameraStreamConfig} must be added.
          *
          * @param width The width of the stream.
          * @param height The height of the stream.
-         * @param format The {@link ImageFormat} of the stream.
+         * @param format The input format of the stream. Supported formats are
+         *               {@link ImageFormat#YUV_420_888} and {@link PixelFormat#RGBA_8888}.
+         * @param maximumFramesPerSecond The maximum frame rate (in frames per second) for the
+         *                               stream.
          *
-         * @throws IllegalArgumentException if invalid format or dimensions are passed.
+         * @throws IllegalArgumentException if invalid dimensions, format or frame rate are passed.
          */
         @NonNull
-        public Builder addStreamConfig(int width, int height, @ImageFormat.Format int format) {
-            if (width <= 0 || height <= 0) {
-                throw new IllegalArgumentException("Invalid dimensions passed for stream config");
+        public Builder addStreamConfig(
+                @IntRange(from = 1) int width,
+                @IntRange(from = 1) int height,
+                @ImageFormat.Format int format,
+                @IntRange(from = 1) int maximumFramesPerSecond) {
+            // TODO(b/310857519): Check dimension upper limits based on the maximum texture size
+            // supported by the current device, instead of hardcoded limits.
+            if (width <= 0 || width > VirtualCameraStreamConfig.DIMENSION_UPPER_LIMIT) {
+                throw new IllegalArgumentException(
+                        "Invalid width passed for stream config: " + width
+                                + ", must be between 1 and "
+                                + VirtualCameraStreamConfig.DIMENSION_UPPER_LIMIT);
             }
-            if (!ImageFormat.isPublicFormat(format)) {
-                throw new IllegalArgumentException("Invalid format passed for stream config");
+            if (height <= 0 || height > VirtualCameraStreamConfig.DIMENSION_UPPER_LIMIT) {
+                throw new IllegalArgumentException(
+                        "Invalid height passed for stream config: " + height
+                                + ", must be between 1 and "
+                                + VirtualCameraStreamConfig.DIMENSION_UPPER_LIMIT);
             }
-            mStreamConfigurations.add(new VirtualCameraStreamConfig(width, height, format));
+            if (!isFormatSupported(format)) {
+                throw new IllegalArgumentException(
+                        "Invalid format passed for stream config: " + format);
+            }
+            if (maximumFramesPerSecond <= 0
+                    || maximumFramesPerSecond > VirtualCameraStreamConfig.MAX_FPS_UPPER_LIMIT) {
+                throw new IllegalArgumentException(
+                        "Invalid maximumFramesPerSecond, must be greater than 0 and less than "
+                                + VirtualCameraStreamConfig.MAX_FPS_UPPER_LIMIT);
+            }
+            mStreamConfigurations.add(new VirtualCameraStreamConfig(width, height, format,
+                    maximumFramesPerSecond));
+            return this;
+        }
+
+        /**
+         * Sets the sensor orientation of the virtual camera. This field is optional and can be
+         * omitted (defaults to {@link #SENSOR_ORIENTATION_0}).
+         *
+         * @param sensorOrientation The sensor orientation of the camera, which represents the
+         *                          clockwise angle (in degrees) through which the output image
+         *                          needs to be rotated to be upright on the device screen in its
+         *                          native orientation.
+         */
+        @NonNull
+        public Builder setSensorOrientation(@SensorOrientation int sensorOrientation) {
+            if (sensorOrientation != SENSOR_ORIENTATION_0
+                    && sensorOrientation != SENSOR_ORIENTATION_90
+                    && sensorOrientation != SENSOR_ORIENTATION_180
+                    && sensorOrientation != SENSOR_ORIENTATION_270) {
+                throw new IllegalArgumentException(
+                        "Invalid sensor orientation: " + sensorOrientation);
+            }
+            mSensorOrientation = sensorOrientation;
+            return this;
+        }
+
+        /**
+         * Sets the lens facing direction of the virtual camera, can be either
+         * {@link CameraMetadata#LENS_FACING_FRONT} or {@link CameraMetadata#LENS_FACING_BACK}.
+         *
+         * <p>A {@link VirtualDevice} can have at most one {@link VirtualCamera} with
+         * {@link CameraMetadata#LENS_FACING_FRONT} and at most one {@link VirtualCamera} with
+         * {@link CameraMetadata#LENS_FACING_BACK}.
+         *
+         * @param lensFacing The direction that the virtual camera faces relative to the device's
+         *                   screen.
+         */
+        @NonNull
+        public Builder setLensFacing(int lensFacing) {
+            if (lensFacing != CameraMetadata.LENS_FACING_BACK
+                    && lensFacing != CameraMetadata.LENS_FACING_FRONT) {
+                throw new IllegalArgumentException("Unsupported lens facing: " + lensFacing);
+            }
+            mLensFacing = lensFacing;
             return this;
         }
 
@@ -189,11 +332,13 @@
          * Builds a new instance of {@link VirtualCameraConfig}
          *
          * @throws NullPointerException if some required parameters are missing.
+         * @throws IllegalArgumentException if any parameter is invalid.
          */
         @NonNull
         public VirtualCameraConfig build() {
             return new VirtualCameraConfig(
-                    mName, mStreamConfigurations, mCallbackExecutor, mCallback);
+                    mName, mStreamConfigurations, mCallbackExecutor, mCallback, mSensorOrientation,
+                    mLensFacing);
         }
     }
 
@@ -208,9 +353,10 @@
         }
 
         @Override
-        public void onStreamConfigured(
-                int streamId, Surface surface, VirtualCameraStreamConfig streamConfig) {
-            mExecutor.execute(() -> mCallback.onStreamConfigured(streamId, surface, streamConfig));
+        public void onStreamConfigured(int streamId, Surface surface, int width, int height,
+                int format) {
+            mExecutor.execute(() -> mCallback.onStreamConfigured(streamId, surface, width, height,
+                    format));
         }
 
         @Override
@@ -237,4 +383,11 @@
                     return new VirtualCameraConfig[size];
                 }
             };
+
+    private static boolean isFormatSupported(@ImageFormat.Format int format) {
+        return switch (format) {
+            case ImageFormat.YUV_420_888, PixelFormat.RGBA_8888 -> true;
+            default -> false;
+        };
+    }
 }
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
deleted file mode 100644
index ce92b6d..0000000
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.companion.virtual.camera;
-
-/**
- * The configuration of a single virtual camera stream.
- * @hide
- */
-parcelable VirtualCameraStreamConfig;
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java
index 1272f16..00a814e 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.companion.virtual.camera;
 
 import android.annotation.FlaggedApi;
@@ -24,6 +25,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.Objects;
 
 /**
@@ -34,32 +37,47 @@
 @SystemApi
 @FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
 public final class VirtualCameraStreamConfig implements Parcelable {
+    // TODO(b/310857519): Check if we should increase the fps upper limit in future.
+    static final int MAX_FPS_UPPER_LIMIT = 60;
+    // This is the minimum guaranteed upper bound of texture size supported by all devices.
+    // Keep this in sync with kMaxTextureSize from services/camera/virtualcamera/util/Util.cc
+    // TODO(b/310857519): Remove this once we add support for fetching the maximum texture size
+    // supported by the current device.
+    static final int DIMENSION_UPPER_LIMIT = 2048;
 
     private final int mWidth;
     private final int mHeight;
     private final int mFormat;
+    private final int mMaxFps;
 
     /**
      * Construct a new instance of {@link VirtualCameraStreamConfig} initialized with the provided
-     * width, height and {@link ImageFormat}
+     * width, height and {@link ImageFormat}.
      *
      * @param width The width of the stream.
      * @param height The height of the stream.
      * @param format The {@link ImageFormat} of the stream.
+     * @param maxFps The maximum frame rate (in frames per second) for the stream.
+     *
+     * @hide
      */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public VirtualCameraStreamConfig(
             @IntRange(from = 1) int width,
             @IntRange(from = 1) int height,
-            @ImageFormat.Format int format) {
+            @ImageFormat.Format int format,
+            @IntRange(from = 1) int maxFps) {
         this.mWidth = width;
         this.mHeight = height;
         this.mFormat = format;
+        this.mMaxFps = maxFps;
     }
 
     private VirtualCameraStreamConfig(@NonNull Parcel in) {
         mWidth = in.readInt();
         mHeight = in.readInt();
         mFormat = in.readInt();
+        mMaxFps = in.readInt();
     }
 
     @Override
@@ -72,6 +90,7 @@
         dest.writeInt(mWidth);
         dest.writeInt(mHeight);
         dest.writeInt(mFormat);
+        dest.writeInt(mMaxFps);
     }
 
     @NonNull
@@ -105,12 +124,13 @@
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         VirtualCameraStreamConfig that = (VirtualCameraStreamConfig) o;
-        return mWidth == that.mWidth && mHeight == that.mHeight && mFormat == that.mFormat;
+        return mWidth == that.mWidth && mHeight == that.mHeight && mFormat == that.mFormat
+                && mMaxFps == that.mMaxFps;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mWidth, mHeight, mFormat);
+        return Objects.hash(mWidth, mHeight, mFormat, mMaxFps);
     }
 
     /** Returns the {@link ImageFormat} of this stream. */
@@ -118,4 +138,10 @@
     public int getFormat() {
         return mFormat;
     }
+
+    /** Returns the maximum frame rate (in frames per second) of this stream. */
+    @IntRange(from = 1)
+    public int getMaximumFramesPerSecond() {
+        return mMaxFps;
+    }
 }
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index ce2490b..588e4fc 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -100,3 +100,10 @@
   description: "Enable interactive screen mirroring using Virtual Devices"
   bug: "292212199"
 }
+
+flag {
+  name: "virtual_stylus"
+  namespace: "virtual_devices"
+  description: "Enable virtual stylus input"
+  bug: "304829446"
+}
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index a1357c9..bde562d 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -231,7 +231,7 @@
     }
 
     /** @hide */
-    public AttributionSource withToken(@NonNull Binder token) {
+    public AttributionSource withToken(@NonNull IBinder token) {
         return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
                 token, mAttributionSourceState.renouncedPermissions, getDeviceId(), getNext());
     }
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index c7a75ed..e9b94c9 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -41,6 +41,7 @@
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.database.SQLException;
+import android.multiuser.Flags;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Binder;
@@ -146,6 +147,7 @@
     private boolean mExported;
     private boolean mNoPerms;
     private boolean mSingleUser;
+    private boolean mSystemUserOnly;
     private SparseBooleanArray mUsersRedirectedToOwnerForMedia = new SparseBooleanArray();
 
     private ThreadLocal<AttributionSource> mCallingAttributionSource;
@@ -377,7 +379,9 @@
                             != PermissionChecker.PERMISSION_GRANTED
                             && getContext().checkUriPermission(userUri, Binder.getCallingPid(),
                             callingUid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
-                            != PackageManager.PERMISSION_GRANTED) {
+                            != PackageManager.PERMISSION_GRANTED
+                            && !deniedAccessSystemUserOnlyProvider(callingUserId,
+                            mSystemUserOnly)) {
                         FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION,
                                 enumCheckUriPermission,
                                 callingUid, uri.getAuthority(), type);
@@ -865,6 +869,10 @@
     boolean checkUser(int pid, int uid, Context context) {
         final int callingUserId = UserHandle.getUserId(uid);
 
+        if (deniedAccessSystemUserOnlyProvider(callingUserId, mSystemUserOnly)) {
+            return false;
+        }
+
         if (callingUserId == context.getUserId() || mSingleUser) {
             return true;
         }
@@ -987,6 +995,9 @@
 
         // last chance, check against any uri grants
         final int callingUserId = UserHandle.getUserId(uid);
+        if (deniedAccessSystemUserOnlyProvider(callingUserId, mSystemUserOnly)) {
+            return PermissionChecker.PERMISSION_HARD_DENIED;
+        }
         final Uri userUri = (mSingleUser && !UserHandle.isSameUser(mMyUid, uid))
                 ? maybeAddUserId(uri, callingUserId) : uri;
         if (context.checkUriPermission(userUri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
@@ -2623,6 +2634,7 @@
                 setPathPermissions(info.pathPermissions);
                 mExported = info.exported;
                 mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0;
+                mSystemUserOnly = (info.flags & ProviderInfo.FLAG_SYSTEM_USER_ONLY) != 0;
                 setAuthorities(info.authority);
             }
             if (Build.IS_DEBUGGABLE) {
@@ -2756,6 +2768,11 @@
         String auth = uri.getAuthority();
         if (!mSingleUser) {
             int userId = getUserIdFromAuthority(auth, UserHandle.USER_CURRENT);
+            if (deniedAccessSystemUserOnlyProvider(mContext.getUserId(),
+                    mSystemUserOnly)) {
+                throw new SecurityException("Trying to query a SYSTEM user only content"
+                        + " provider from user:" + mContext.getUserId());
+            }
             if (userId != UserHandle.USER_CURRENT
                     && userId != mContext.getUserId()
                     // Since userId specified in content uri, the provider userId would be
@@ -2929,4 +2946,16 @@
             Trace.traceBegin(traceTag, methodName + subInfo);
         }
     }
+    /**
+     * Return true if access to content provider is denied because it's a SYSTEM user only
+     * provider and the calling user is not the SYSTEM user.
+     *
+     * @param callingUserId UserId of the caller accessing the content provider.
+     * @param systemUserOnly true when the content provider is only available for the SYSTEM user.
+     */
+    private static boolean deniedAccessSystemUserOnlyProvider(int callingUserId,
+            boolean systemUserOnly) {
+        return Flags.enableSystemUserOnlyForServicesAndProviders()
+                && (callingUserId != UserHandle.USER_SYSTEM && systemUserOnly);
+    }
 }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index fa76e39..b8d7543 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -16,6 +16,8 @@
 
 package android.content;
 
+import static android.content.flags.Flags.FLAG_ENABLE_BIND_PACKAGE_ISOLATED_PROCESS;
+
 import android.annotation.AttrRes;
 import android.annotation.CallbackExecutor;
 import android.annotation.CheckResult;
@@ -296,6 +298,7 @@
             BIND_ALLOW_ACTIVITY_STARTS,
             BIND_INCLUDE_CAPABILITIES,
             BIND_SHARED_ISOLATED_PROCESS,
+            BIND_PACKAGE_ISOLATED_PROCESS,
             BIND_EXTERNAL_SERVICE
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -318,6 +321,7 @@
             BIND_ALLOW_ACTIVITY_STARTS,
             BIND_INCLUDE_CAPABILITIES,
             BIND_SHARED_ISOLATED_PROCESS,
+            BIND_PACKAGE_ISOLATED_PROCESS,
             // Intentionally not include BIND_EXTERNAL_SERVICE, because it'd cause sign-extension.
             // This would allow Android Studio to show a warning, if someone tries to use
             // BIND_EXTERNAL_SERVICE BindServiceFlags.
@@ -511,6 +515,26 @@
      */
     public static final int BIND_SHARED_ISOLATED_PROCESS = 0x00002000;
 
+    /**
+     * Flag for {@link #bindIsolatedService}: Bind the service into a shared isolated process,
+     * but only with other isolated services from the same package that declare the same process
+     * name.
+     *
+     * <p>Specifying this flag allows multiple isolated services defined in the same package to be
+     * running in a single shared isolated process. This shared isolated process must be specified
+     * since this flag will not work with the default application process.
+     *
+     * <p>This flag is different from {@link #BIND_SHARED_ISOLATED_PROCESS} since it only
+     * allows binding services from the same package in the same shared isolated process. This also
+     * means the shared package isolated process is global, and not scoped to each potential
+     * calling app.
+     *
+     * <p>The shared isolated process instance is identified by the "android:process" attribute
+     * defined by the service. This flag cannot be used without this attribute set.
+     */
+    @FlaggedApi(FLAG_ENABLE_BIND_PACKAGE_ISOLATED_PROCESS)
+    public static final int BIND_PACKAGE_ISOLATED_PROCESS = 1 << 14;
+
     /***********    Public flags above this line ***********/
     /***********    Hidden flags below this line ***********/
 
@@ -3633,8 +3657,8 @@
      * On Android {@link android.os.Build.VERSION_CODES#S} and later,
      * if the application is in a state where the service
      * can not be started (such as not in the foreground in a state when services are allowed),
-     * {@link android.app.BackgroundServiceStartNotAllowedException} is thrown
-     * This excemption extends {@link IllegalStateException}, so apps can
+     * {@link android.app.BackgroundServiceStartNotAllowedException} is thrown.
+     * This exception extends {@link IllegalStateException}, so apps can
      * use {@code catch (IllegalStateException)} to catch both.
      *
      * @see #startForegroundService(Intent)
@@ -4218,6 +4242,8 @@
             VIRTUALIZATION_SERVICE,
             GRAMMATICAL_INFLECTION_SERVICE,
             SECURITY_STATE_SERVICE,
+           //@hide: ECM_ENHANCED_CONFIRMATION_SERVICE,
+            CONTACT_KEYS_SERVICE,
 
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -5054,6 +5080,8 @@
      * @see #getSystemService
      * @see android.hardware.face.FaceManager
      */
+    @FlaggedApi(android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION)
+    @SystemApi
     public static final String FACE_SERVICE = "face";
 
     /**
@@ -6503,6 +6531,28 @@
     public static final String SECURITY_STATE_SERVICE = "security_state";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve an
+     * {@link android.app.ecm.EnhancedConfirmationManager}.
+     *
+     * @see #getSystemService(String)
+     * @see android.app.ecm.EnhancedConfirmationManager
+     * @hide
+     */
+    @FlaggedApi(android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+    @SystemApi
+    public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.provider.ContactKeysManager} to managing contact keys.
+     *
+     * @see #getSystemService(String)
+     * @see android.provider.ContactKeysManager
+     */
+    @FlaggedApi(android.provider.Flags.FLAG_USER_KEYS)
+    public static final String CONTACT_KEYS_SERVICE = "contact_keys";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
@@ -6734,7 +6784,7 @@
             @Intent.AccessUriMode int modeFlags);
 
     /**
-     * Determine whether a particular process and user ID has been granted
+     * Determine whether a particular process and uid has been granted
      * permission to access a specific URI.  This only checks for permissions
      * that have been explicitly granted -- if the given process/uid has
      * more general access to the URI's content provider then this check will
@@ -6758,7 +6808,38 @@
             @Intent.AccessUriMode int modeFlags);
 
     /**
-     * Determine whether a particular process and user ID has been granted
+     * Determine whether a particular process and uid has been granted
+     * permission to access a specific content URI.
+     *
+     * <p>Unlike {@link #checkUriPermission(Uri, int, int, int)}, this method
+     * checks for general access to the URI's content provider, as well as
+     * explicitly granted permissions.</p>
+     *
+     * <p>Note, this check will throw an {@link IllegalArgumentException}
+     * for non-content URIs.</p>
+     *
+     * @param uri The content uri that is being checked.
+     * @param pid (Optional) The process ID being checked against. If the
+     * pid is unknown, pass -1.
+     * @param uid The UID being checked against.  A uid of 0 is the root
+     * user, which will pass every permission check.
+     * @param modeFlags The access modes to check.
+     *
+     * @return {@link PackageManager#PERMISSION_GRANTED} if the given
+     * pid/uid is allowed to access that uri, or
+     * {@link PackageManager#PERMISSION_DENIED} if it is not.
+     *
+     * @see #checkUriPermission(Uri, int, int, int)
+     */
+    @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
+    @PackageManager.PermissionResult
+    public int checkContentUriPermissionFull(@NonNull Uri uri, int pid, int uid,
+            @Intent.AccessUriMode int modeFlags) {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
+     * Determine whether a particular process and uid has been granted
      * permission to access a list of URIs.  This only checks for permissions
      * that have been explicitly granted -- if the given process/uid has
      * more general access to the URI's content provider then this check will
@@ -6794,7 +6875,7 @@
             @Intent.AccessUriMode int modeFlags, IBinder callerToken);
 
     /**
-     * Determine whether the calling process and user ID has been
+     * Determine whether the calling process and uid has been
      * granted permission to access a specific URI.  This is basically
      * the same as calling {@link #checkUriPermission(Uri, int, int,
      * int)} with the pid and uid returned by {@link
@@ -6817,7 +6898,7 @@
     public abstract int checkCallingUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags);
 
     /**
-     * Determine whether the calling process and user ID has been
+     * Determine whether the calling process and uid has been
      * granted permission to access a list of URIs.  This is basically
      * the same as calling {@link #checkUriPermissions(List, int, int, int)}
      * with the pid and uid returned by {@link
@@ -6911,7 +6992,7 @@
             @Intent.AccessUriMode int modeFlags);
 
     /**
-     * If a particular process and user ID has not been granted
+     * If a particular process and uid has not been granted
      * permission to access a specific URI, throw {@link
      * SecurityException}.  This only checks for permissions that have
      * been explicitly granted -- if the given process/uid has more
@@ -6931,7 +7012,7 @@
             Uri uri, int pid, int uid, @Intent.AccessUriMode int modeFlags, String message);
 
     /**
-     * If the calling process and user ID has not been granted
+     * If the calling process and uid has not been granted
      * permission to access a specific URI, throw {@link
      * SecurityException}.  This is basically the same as calling
      * {@link #enforceUriPermission(Uri, int, int, int, String)} with
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 0a8029c..e0cf0a5 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -17,6 +17,7 @@
 package android.content;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -1003,6 +1004,12 @@
         return mBase.checkUriPermission(uri, pid, uid, modeFlags);
     }
 
+    @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
+    @Override
+    public int checkContentUriPermissionFull(@NonNull Uri uri, int pid, int uid, int modeFlags) {
+        return mBase.checkContentUriPermissionFull(uri, pid, uid, modeFlags);
+    }
+
     @NonNull
     @Override
     public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid,
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index ad3acd7..79af65a 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -16,11 +16,13 @@
 
 package android.content;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.Flags;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
@@ -175,6 +177,7 @@
     private static final String ACTION_STR = "action";
     private static final String AUTO_VERIFY_STR = "autoVerify";
     private static final String EXTRAS_STR = "extras";
+    private static final String URI_RELATIVE_FILTER_GROUP_STR = "uriRelativeFilterGroup";
 
     private static final int[] EMPTY_INT_ARRAY = new int[0];
     private static final long[] EMPTY_LONG_ARRAY = new long[0];
@@ -324,6 +327,7 @@
     private ArrayList<PatternMatcher> mDataSchemeSpecificParts = null;
     private ArrayList<AuthorityEntry> mDataAuthorities = null;
     private ArrayList<PatternMatcher> mDataPaths = null;
+    private ArrayList<UriRelativeFilterGroup> mUriRelativeFilterGroups = null;
     private ArrayList<String> mStaticDataTypes = null;
     private ArrayList<String> mDataTypes = null;
     private ArrayList<String> mMimeGroups = null;
@@ -520,6 +524,10 @@
         if (o.mDataPaths != null) {
             mDataPaths = new ArrayList<PatternMatcher>(o.mDataPaths);
         }
+        if (o.mUriRelativeFilterGroups != null) {
+            mUriRelativeFilterGroups =
+                    new ArrayList<UriRelativeFilterGroup>(o.mUriRelativeFilterGroups);
+        }
         if (o.mMimeGroups != null) {
             mMimeGroups = new ArrayList<String>(o.mMimeGroups);
         }
@@ -1563,6 +1571,63 @@
     }
 
     /**
+     * Add a new URI relative filter group to match against the Intent data.  The
+     * intent filter must include one or more schemes (via {@link #addDataScheme})
+     * <em>and</em> one or more authorities (via {@link #addDataAuthority}) for
+     * the group to be considered.
+     *
+     * <p>Groups will be matched in the order they were added and matching will only
+     * be done if no data paths match or if none are included. If both data paths and
+     * groups are not included, then only the scheme/authority must match.</p>
+     *
+     * @param group A {@link UriRelativeFilterGroup} to match the URI.
+     *
+     * @see UriRelativeFilterGroup
+     * @see #matchData
+     * @see #addDataScheme
+     * @see #addDataAuthority
+     */
+    @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
+    public final void addUriRelativeFilterGroup(@NonNull UriRelativeFilterGroup group) {
+        Objects.requireNonNull(group);
+        if (mUriRelativeFilterGroups == null) {
+            mUriRelativeFilterGroups = new ArrayList<>();
+        }
+        mUriRelativeFilterGroups.add(group);
+    }
+
+    /**
+     * Return the number of URI relative filter groups in the intent filter.
+     */
+    @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
+    public final int countUriRelativeFilterGroups() {
+        return mUriRelativeFilterGroups == null ? 0 : mUriRelativeFilterGroups.size();
+    }
+
+    /**
+     * Return a URI relative filter group in the intent filter.
+     *
+     * <p>Note: use of this method will result in a NullPointerException
+     * if no groups exists for this intent filter.</p>
+     *
+     * @param index index of the element to return
+     * @throws IndexOutOfBoundsException if index is out of range
+     */
+    @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
+    @NonNull
+    public final UriRelativeFilterGroup getUriRelativeFilterGroup(int index) {
+        return mUriRelativeFilterGroups.get(index);
+    }
+
+    /**
+     * Removes all existing URI relative filter groups in the intent filter.
+     */
+    @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
+    public final void clearUriRelativeFilterGroups() {
+        mUriRelativeFilterGroups = null;
+    }
+
+    /**
      * Match this intent filter against the given Intent data.  This ignores
      * the data scheme -- unlike {@link #matchData}, the authority will match
      * regardless of whether there is a matching scheme.
@@ -1677,12 +1742,24 @@
                     int authMatch = matchDataAuthority(data, wildcardSupported);
                     if (authMatch >= 0) {
                         final ArrayList<PatternMatcher> paths = mDataPaths;
-                        if (paths == null) {
-                            match = authMatch;
-                        } else if (hasDataPath(data.getPath(), wildcardSupported)) {
-                            match = MATCH_CATEGORY_PATH;
+                        final ArrayList<UriRelativeFilterGroup> groups = mUriRelativeFilterGroups;
+                        if (Flags.relativeReferenceIntentFilters()) {
+                            if (paths == null && groups == null) {
+                                match = authMatch;
+                            } else if (hasDataPath(data.getPath(), wildcardSupported)
+                                    || matchRelRefGroups(data)) {
+                                match = MATCH_CATEGORY_PATH;
+                            } else {
+                                return NO_MATCH_DATA;
+                            }
                         } else {
-                            return NO_MATCH_DATA;
+                            if (paths == null) {
+                                match = authMatch;
+                            } else if (hasDataPath(data.getPath(), wildcardSupported)) {
+                                match = MATCH_CATEGORY_PATH;
+                            } else {
+                                return NO_MATCH_DATA;
+                            }
                         }
                     } else {
                         return NO_MATCH_DATA;
@@ -1726,6 +1803,19 @@
         return match + MATCH_ADJUSTMENT_NORMAL;
     }
 
+    private boolean matchRelRefGroups(Uri data) {
+        if (mUriRelativeFilterGroups == null) {
+            return false;
+        }
+        for (int i = 0; i < mUriRelativeFilterGroups.size(); i++) {
+            UriRelativeFilterGroup group = mUriRelativeFilterGroups.get(i);
+            if (group.matchData(data)) {
+                return group.getAction() == UriRelativeFilterGroup.ACTION_ALLOW;
+            }
+        }
+        return false;
+    }
+
     /**
      * Add a new Intent category to match against.  The semantics of
      * categories is the opposite of actions -- an Intent includes the
@@ -2486,6 +2576,12 @@
             }
             serializer.endTag(null, EXTRAS_STR);
         }
+        if (Flags.relativeReferenceIntentFilters()) {
+            N = countUriRelativeFilterGroups();
+            for (int i = 0; i < N; i++) {
+                mUriRelativeFilterGroups.get(i).writeToXml(serializer);
+            }
+        }
     }
 
     /**
@@ -2614,6 +2710,9 @@
                 }
             } else if (tagName.equals(EXTRAS_STR)) {
                 mExtras = PersistableBundle.restoreFromXml(parser);
+            } else if (Flags.relativeReferenceIntentFilters()
+                    && URI_RELATIVE_FILTER_GROUP_STR.equals(tagName)) {
+                addUriRelativeFilterGroup(new UriRelativeFilterGroup(parser));
             } else {
                 Log.w("IntentFilter", "Unknown tag parsing IntentFilter: " + tagName);
             }
@@ -2680,6 +2779,12 @@
         if (mExtras != null) {
             mExtras.dumpDebug(proto, IntentFilterProto.EXTRAS);
         }
+        if (Flags.relativeReferenceIntentFilters() && mUriRelativeFilterGroups != null) {
+            Iterator<UriRelativeFilterGroup> it = mUriRelativeFilterGroups.iterator();
+            while (it.hasNext()) {
+                it.next().dumpDebug(proto, IntentFilterProto.URI_RELATIVE_FILTER_GROUPS);
+            }
+        }
         proto.end(token);
     }
 
@@ -2744,6 +2849,15 @@
                 du.println(sb.toString());
             }
         }
+        if (mUriRelativeFilterGroups != null) {
+            Iterator<UriRelativeFilterGroup> it = mUriRelativeFilterGroups.iterator();
+            while (it.hasNext()) {
+                sb.setLength(0);
+                sb.append(prefix); sb.append("UriRelativeFilterGroup: \"");
+                sb.append(it.next()); sb.append("\"");
+                du.println(sb.toString());
+            }
+        }
         if (mStaticDataTypes != null) {
             Iterator<String> it = mStaticDataTypes.iterator();
             while (it.hasNext()) {
@@ -2883,6 +2997,15 @@
         } else {
             dest.writeInt(0);
         }
+        if (Flags.relativeReferenceIntentFilters() && mUriRelativeFilterGroups != null) {
+            final int N = mUriRelativeFilterGroups.size();
+            dest.writeInt(N);
+            for (int i = 0; i < N; i++) {
+                mUriRelativeFilterGroups.get(i).writeToParcel(dest, flags);
+            }
+        } else {
+            dest.writeInt(0);
+        }
     }
 
     /**
@@ -2989,6 +3112,13 @@
         if (source.readInt() != 0) {
             mExtras = PersistableBundle.CREATOR.createFromParcel(source);
         }
+        N = source.readInt();
+        if (Flags.relativeReferenceIntentFilters() && N > 0) {
+            mUriRelativeFilterGroups = new ArrayList<UriRelativeFilterGroup>(N);
+            for (int i = 0; i < N; i++) {
+                mUriRelativeFilterGroups.add(new UriRelativeFilterGroup(source));
+            }
+        }
     }
 
     private boolean hasPartialTypes() {
diff --git a/core/java/android/content/OWNERS b/core/java/android/content/OWNERS
index 90c3d04..a37408b 100644
--- a/core/java/android/content/OWNERS
+++ b/core/java/android/content/OWNERS
@@ -4,6 +4,7 @@
 per-file *Content* = file:/services/core/java/com/android/server/am/OWNERS
 per-file *Sync* = file:/services/core/java/com/android/server/am/OWNERS
 per-file IntentFilter.java = file:/PACKAGE_MANAGER_OWNERS
+per-file UriRelativeFilter* = file:/PACKAGE_MANAGER_OWNERS
 per-file IntentFilter.java = file:/services/core/java/com/android/server/am/OWNERS
 per-file Intent.java = file:/INTENT_OWNERS
 per-file AutofillOptions* = file:/core/java/android/service/autofill/OWNERS
diff --git a/core/java/android/content/UriRelativeFilter.java b/core/java/android/content/UriRelativeFilter.java
new file mode 100644
index 0000000..9866cd0
--- /dev/null
+++ b/core/java/android/content/UriRelativeFilter.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.Flags;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.PatternMatcher;
+import android.util.proto.ProtoOutputStream;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A filter for matching Intent URI Data as part of a
+ * {@link UriRelativeFilterGroup}. A single filter can only be
+ * matched against either a URI path, query or fragment
+ */
+@FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
+public final class UriRelativeFilter {
+    private static final String FILTER_STR = "filter";
+    private static final String PART_STR = "part";
+    private static final String PATTERN_STR = "pattern";
+    static final String URI_RELATIVE_FILTER_STR = "uriRelativeFilter";
+
+    /**
+     * Value to indicate that the filter is to be applied to a URI path.
+     */
+    public static final int PATH = 0;
+    /**
+     * Value to indicate that the filter is to be applied to a URI query.
+     */
+    public static final int QUERY = 1;
+    /**
+     * Value to indicate that the filter is to be applied to a URI fragment.
+     */
+    public static final int FRAGMENT = 2;
+
+    /** @hide */
+    @IntDef(value = {
+            PATH,
+            QUERY,
+            FRAGMENT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UriPart {}
+
+    private final @UriPart int mUriPart;
+    private final @PatternMatcher.PatternType int mPatternType;
+    private final String mFilter;
+
+    /**
+     * Creates a new UriRelativeFilter.
+     *
+     * @param uriPart The URI part this filter operates on. Can be either a
+     *                {@link UriRelativeFilter#PATH}, {@link UriRelativeFilter#QUERY},
+     *                or {@link UriRelativeFilter#FRAGMENT}.
+     * @param patternType The pattern type of the filter. Can be either a
+     *                    {@link PatternMatcher#PATTERN_LITERAL},
+     *                    {@link PatternMatcher#PATTERN_PREFIX},
+*                         {@link PatternMatcher#PATTERN_SUFFIX},
+     *                    {@link PatternMatcher#PATTERN_SIMPLE_GLOB},
+     *                    or {@link PatternMatcher#PATTERN_ADVANCED_GLOB}.
+     * @param filter A literal or pattern string depedning on patterType
+     *               used to match a uriPart .
+     */
+    public UriRelativeFilter(
+            @UriPart int uriPart,
+            @PatternMatcher.PatternType int patternType,
+            @NonNull String filter) {
+        mUriPart = uriPart;
+        com.android.internal.util.AnnotationValidations.validate(
+                UriPart.class, null, mUriPart);
+        mPatternType = patternType;
+        com.android.internal.util.AnnotationValidations.validate(
+                PatternMatcher.PatternType.class, null, mPatternType);
+        mFilter = filter;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mFilter);
+    }
+
+    /**
+     * The URI part this filter operates on.
+     */
+    public @UriPart int getUriPart() {
+        return mUriPart;
+    }
+
+    /**
+     * The pattern type of the filter.
+     */
+    public @PatternMatcher.PatternType int getPatternType() {
+        return mPatternType;
+    }
+
+    /**
+     * The string used to filter the URI.
+     */
+    public @NonNull String getFilter() {
+        return mFilter;
+    }
+
+    /**
+     * Match this URI filter against an Intent's data. QUERY filters can
+     * match against any key value pair in the query string. PATH and
+     * FRAGMENT filters must match the entire string.
+     *
+     * @param data The full data string to match against, as supplied in
+     *             Intent.data.
+     *
+     * @return true if there is a match.
+     */
+    public boolean matchData(@NonNull Uri data) {
+        PatternMatcher pe = new PatternMatcher(mFilter, mPatternType);
+        switch (getUriPart()) {
+            case PATH:
+                return pe.match(data.getPath());
+            case QUERY:
+                return matchQuery(pe, data.getQuery());
+            case FRAGMENT:
+                return pe.match(data.getFragment());
+            default:
+                return false;
+        }
+    }
+
+    private boolean matchQuery(PatternMatcher pe, String query) {
+        if (query != null) {
+            String[] params = query.split("&");
+            if (params.length == 1) {
+                params = query.split(";");
+            }
+            for (int i = 0; i < params.length; i++) {
+                if (pe.match(params[i])) return true;
+            }
+        }
+        return false;
+    }
+
+    /** @hide */
+    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(UriRelativeFilterProto.URI_PART, mUriPart);
+        proto.write(UriRelativeFilterProto.PATTERN_TYPE, mPatternType);
+        proto.write(UriRelativeFilterProto.FILTER, mFilter);
+        proto.end(token);
+    }
+
+    /** @hide */
+    public void writeToXml(XmlSerializer serializer) throws IOException {
+        serializer.startTag(null, URI_RELATIVE_FILTER_STR);
+        serializer.attribute(null, PATTERN_STR, Integer.toString(mPatternType));
+        serializer.attribute(null, PART_STR, Integer.toString(mUriPart));
+        serializer.attribute(null, FILTER_STR, mFilter);
+        serializer.endTag(null, URI_RELATIVE_FILTER_STR);
+    }
+
+    private String uriPartToString() {
+        switch (mUriPart) {
+            case PATH:
+                return "PATH";
+            case QUERY:
+                return "QUERY";
+            case FRAGMENT:
+                return "FRAGMENT";
+            default:
+                return "UNKNOWN";
+        }
+    }
+
+    private String patternTypeToString() {
+        switch (mPatternType) {
+            case PatternMatcher.PATTERN_LITERAL:
+                return "LITERAL";
+            case PatternMatcher.PATTERN_PREFIX:
+                return "PREFIX";
+            case PatternMatcher.PATTERN_SIMPLE_GLOB:
+                return "GLOB";
+            case PatternMatcher.PATTERN_ADVANCED_GLOB:
+                return "ADVANCED_GLOB";
+            case PatternMatcher.PATTERN_SUFFIX:
+                return "SUFFIX";
+            default:
+                return "UNKNOWN";
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "UriRelativeFilter { "
+                + "uriPart = " + uriPartToString() + ", "
+                + "patternType = " + patternTypeToString() + ", "
+                + "filter = " + mFilter
+                + " }";
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        UriRelativeFilter that = (UriRelativeFilter) o;
+        return mUriPart == that.mUriPart
+                && mPatternType == that.mPatternType
+                && java.util.Objects.equals(mFilter, that.mFilter);
+    }
+
+    @Override
+    public int hashCode() {
+        int _hash = 1;
+        _hash = 31 * _hash + mUriPart;
+        _hash = 31 * _hash + mPatternType;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mFilter);
+        return _hash;
+    }
+
+    /** @hide */
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mUriPart);
+        dest.writeInt(mPatternType);
+        dest.writeString(mFilter);
+    }
+
+    /** @hide */
+    UriRelativeFilter(@NonNull android.os.Parcel in) {
+        mUriPart = in.readInt();
+        mPatternType = in.readInt();
+        mFilter = in.readString();
+    }
+
+    /** @hide */
+    public UriRelativeFilter(XmlPullParser parser) throws XmlPullParserException, IOException {
+        mUriPart = Integer.parseInt(parser.getAttributeValue(null, PART_STR));
+        mPatternType = Integer.parseInt(parser.getAttributeValue(null, PATTERN_STR));
+        mFilter = parser.getAttributeValue(null, FILTER_STR);
+    }
+}
diff --git a/core/java/android/content/UriRelativeFilterGroup.java b/core/java/android/content/UriRelativeFilterGroup.java
new file mode 100644
index 0000000..72c396a
--- /dev/null
+++ b/core/java/android/content/UriRelativeFilterGroup.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.pm.Flags;
+import android.net.Uri;
+import android.os.Parcel;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Objects;
+
+/**
+ * An intent data matching group based on a URI's relative reference which
+ * includes the path, query and fragment.  The group is only considered as
+ * matching if <em>all</em> UriRelativeFilters in the group match.  Each
+ * UriRelativeFilter defines a matching rule for a URI path, query or fragment.
+ * A group must contain one or more UriRelativeFilters to match but does not need to
+ * contain UriRelativeFilters for all existing parts of a URI to match.
+ *
+ * <p>For example, given a URI that contains path, query and fragment parts,
+ * a group containing only a path filter will match the URI if the path
+ * filter matches the URI path.  If the group contains a path and query
+ * filter, then the group will only match if both path and query filters
+ * match.  If a URI contains only a path with no query or fragment then a
+ * group can only match if it contains only a matching path filter. If the
+ * group also contained additional query or fragment filters then it will
+ * not match.</p>
+ */
+@FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
+public final class UriRelativeFilterGroup {
+    private static final String ALLOW_STR = "allow";
+    private static final String URI_RELATIVE_FILTER_GROUP_STR = "uriRelativeFilterGroup";
+
+    /**
+     * Value to indicate that the group match is allowed.
+     */
+    public static final int ACTION_ALLOW = 0;
+    /**
+     * Value to indicate that the group match is blocked.
+     */
+    public static final int ACTION_BLOCK = 1;
+
+    /** @hide */
+    @IntDef(value = {
+            ACTION_ALLOW,
+            ACTION_BLOCK
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Action {}
+
+    private final @Action int mAction;
+    private final ArraySet<UriRelativeFilter> mUriRelativeFilters = new ArraySet<>();
+
+    /**
+     * New UriRelativeFilterGroup that matches a Intent data.
+     *
+     * @param action Whether this matching group should be allowed or disallowed.
+     */
+    public UriRelativeFilterGroup(@Action int action) {
+        mAction = action;
+    }
+
+    /** @hide */
+    public UriRelativeFilterGroup(XmlPullParser parser) throws XmlPullParserException, IOException {
+        mAction = Integer.parseInt(parser.getAttributeValue(null, ALLOW_STR));
+
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG
+                || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG
+                    || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals(UriRelativeFilter.URI_RELATIVE_FILTER_STR)) {
+                addUriRelativeFilter(new UriRelativeFilter(parser));
+            } else {
+                Log.w("IntentFilter", "Unknown tag parsing IntentFilter: " + tagName);
+            }
+            XmlUtils.skipCurrentTag(parser);
+        }
+    }
+
+    /**
+     * Return {@link UriRelativeFilterGroup#ACTION_ALLOW} if a URI is allowed when matched
+     * and {@link UriRelativeFilterGroup#ACTION_BLOCK} if a URI is blacked when matched.
+     */
+    public @Action int getAction() {
+        return mAction;
+    }
+
+    /**
+     * Add a filter to the group.
+     */
+    public void addUriRelativeFilter(@NonNull UriRelativeFilter uriRelativeFilter) {
+        Objects.requireNonNull(uriRelativeFilter);
+        if (!CollectionUtils.contains(mUriRelativeFilters, uriRelativeFilter)) {
+            mUriRelativeFilters.add(uriRelativeFilter);
+        }
+    }
+
+    /**
+     * Returns a unmodifiable view of the UriRelativeFilters list in this group.
+     */
+    @NonNull
+    public Collection<UriRelativeFilter> getUriRelativeFilters() {
+        return Collections.unmodifiableCollection(mUriRelativeFilters);
+    }
+
+    /**
+     * Match all URI filter in this group against {@link Intent#getData()}.
+     *
+     * @param data The full data string to match against, as supplied in
+     *             Intent.data.
+     * @return true if all filters match.
+     */
+    public boolean matchData(@NonNull Uri data) {
+        if (mUriRelativeFilters.size() == 0) {
+            return false;
+        }
+        for (UriRelativeFilter filter : mUriRelativeFilters) {
+            if (!filter.matchData(data)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /** @hide */
+    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(UriRelativeFilterGroupProto.ACTION, mAction);
+        Iterator<UriRelativeFilter> it = mUriRelativeFilters.iterator();
+        while (it.hasNext()) {
+            it.next().dumpDebug(proto, UriRelativeFilterGroupProto.URI_RELATIVE_FILTERS);
+        }
+        proto.end(token);
+    }
+
+    /** @hide */
+    public void writeToXml(XmlSerializer serializer) throws IOException {
+        serializer.startTag(null, URI_RELATIVE_FILTER_GROUP_STR);
+        serializer.attribute(null, ALLOW_STR, Integer.toString(mAction));
+        Iterator<UriRelativeFilter> it = mUriRelativeFilters.iterator();
+        while (it.hasNext()) {
+            UriRelativeFilter filter = it.next();
+            filter.writeToXml(serializer);
+        }
+        serializer.endTag(null, URI_RELATIVE_FILTER_GROUP_STR);
+    }
+
+    @Override
+    public String toString() {
+        return "UriRelativeFilterGroup { allow = " + mAction
+                + ", uri_filters = " + mUriRelativeFilters + ",  }";
+    }
+
+    /** @hide */
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mAction);
+        final int n = mUriRelativeFilters.size();
+        if (n > 0) {
+            dest.writeInt(n);
+            Iterator<UriRelativeFilter> it = mUriRelativeFilters.iterator();
+            while (it.hasNext()) {
+                it.next().writeToParcel(dest, flags);
+            }
+        } else {
+            dest.writeInt(0);
+        }
+    }
+
+    /** @hide */
+    UriRelativeFilterGroup(@NonNull Parcel src) {
+        mAction = src.readInt();
+        final int n = src.readInt();
+        for (int i = 0; i < n; i++) {
+            mUriRelativeFilters.add(new UriRelativeFilter(src));
+        }
+    }
+}
diff --git a/core/java/android/content/flags/flags.aconfig b/core/java/android/content/flags/flags.aconfig
new file mode 100644
index 0000000..3445fb5
--- /dev/null
+++ b/core/java/android/content/flags/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.content.flags"
+
+flag {
+    name: "enable_bind_package_isolated_process"
+    namespace: "machine_learning"
+    description: "This flag enables the newly added flag for binding package-private isolated processes."
+    bug: "312706530"
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/ArchivedActivityInfo.java b/core/java/android/content/pm/ArchivedActivityInfo.java
index 1faa437..166d265 100644
--- a/core/java/android/content/pm/ArchivedActivityInfo.java
+++ b/core/java/android/content/pm/ArchivedActivityInfo.java
@@ -91,26 +91,31 @@
      * @hide
      */
     public static Bitmap drawableToBitmap(Drawable drawable, int iconSize) {
-        if (drawable instanceof BitmapDrawable) {
-            return ((BitmapDrawable) drawable).getBitmap();
-
-        }
-
         Bitmap bitmap;
-        if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
-            // Needed for drawables that are just a single color.
-            bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+        if (drawable instanceof BitmapDrawable) {
+            bitmap = ((BitmapDrawable) drawable).getBitmap();
         } else {
-            bitmap =
+            if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
+                // Needed for drawables that are just a single color.
+                bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+            } else {
+                bitmap =
                     Bitmap.createBitmap(
-                            drawable.getIntrinsicWidth(),
-                            drawable.getIntrinsicHeight(),
-                            Bitmap.Config.ARGB_8888);
+                        drawable.getIntrinsicWidth(),
+                        drawable.getIntrinsicHeight(),
+                        Bitmap.Config.ARGB_8888);
+            }
+
+            Canvas canvas = new Canvas(bitmap);
+            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+            drawable.draw(canvas);
         }
-        Canvas canvas = new Canvas(bitmap);
-        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
-        drawable.draw(canvas);
-        if (iconSize > 0 && bitmap.getWidth() > iconSize * 2 || bitmap.getHeight() > iconSize * 2) {
+        if (iconSize <= 0) {
+            return bitmap;
+        }
+
+        if (bitmap.getWidth() < iconSize || bitmap.getHeight() < iconSize
+                || bitmap.getWidth() > iconSize * 2 || bitmap.getHeight() > iconSize * 2) {
             var scaledBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, true);
             if (scaledBitmap != bitmap) {
                 bitmap.recycle();
@@ -235,7 +240,7 @@
     }
 
     @DataClass.Generated(
-            time = 1698789991876L,
+            time = 1705615445673L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/ArchivedActivityInfo.java",
             inputSignatures = "private @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate @android.annotation.NonNull android.content.ComponentName mComponentName\nprivate @android.annotation.Nullable android.graphics.drawable.Drawable mIcon\nprivate @android.annotation.Nullable android.graphics.drawable.Drawable mMonochromeIcon\n @android.annotation.NonNull android.content.pm.ArchivedActivityParcel getParcel()\npublic static  android.graphics.Bitmap drawableToBitmap(android.graphics.drawable.Drawable)\npublic static  android.graphics.Bitmap drawableToBitmap(android.graphics.drawable.Drawable,int)\npublic static  byte[] bytesFromBitmap(android.graphics.Bitmap)\nprivate static  android.graphics.drawable.Drawable drawableFromCompressedBitmap(byte[])\nclass ArchivedActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=false, genConstructor=false, genSetters=true)")
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 32ecb58..1f25fd0 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -80,7 +80,7 @@
             long timeout);
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES,android.Manifest.permission.REQUEST_DELETE_PACKAGES})")
-    void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle, int flags);
+    void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
     void requestUnarchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 43322641..c4bf18d 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -738,7 +738,7 @@
 
     /**
      * The set of error types that can be set for
-     * {@link #reportUnarchivalStatus(int, int, PendingIntent)}.
+     * {@link #reportUnarchivalState}.
      *
      * @hide
      */
@@ -2352,7 +2352,6 @@
      * communicated.
      *
      * @param statusReceiver Callback used to notify when the operation is completed.
-     * @param flags Flags for archiving. Can be 0 or {@link PackageManager#DELETE_SHOW_DIALOG}.
      * @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not
      *                                              available to the caller or isn't archived.
      */
@@ -2360,12 +2359,11 @@
             Manifest.permission.DELETE_PACKAGES,
             Manifest.permission.REQUEST_DELETE_PACKAGES})
     @FlaggedApi(Flags.FLAG_ARCHIVING)
-    public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver,
-            @DeleteFlags int flags)
+    public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver)
             throws PackageManager.NameNotFoundException {
         try {
             mInstaller.requestArchive(packageName, mInstallerPackageName, statusReceiver,
-                    new UserHandle(mUserId), flags);
+                    new UserHandle(mUserId));
         } catch (ParcelableException e) {
             e.maybeRethrow(PackageManager.NameNotFoundException.class);
         } catch (RemoteException e) {
@@ -2423,6 +2421,7 @@
      *                             facilitate the unarchival flow (e.g. user needs to log in).
      * @throws PackageManager.NameNotFoundException if no unarchival with {@code unarchiveId} exists
      */
+    // TODO(b/314960798) Remove old API once it's unused
     @RequiresPermission(anyOf = {
             Manifest.permission.INSTALL_PACKAGES,
             Manifest.permission.REQUEST_INSTALL_PACKAGES})
@@ -2440,6 +2439,30 @@
         }
     }
 
+    /**
+     * Reports the state of an unarchival to the system.
+     *
+     * @see UnarchivalState for the different state options.
+     * @throws PackageManager.NameNotFoundException if no unarchival with {@code unarchiveId} exists
+     */
+    @RequiresPermission(anyOf = {
+            Manifest.permission.INSTALL_PACKAGES,
+            Manifest.permission.REQUEST_INSTALL_PACKAGES})
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public void reportUnarchivalState(@NonNull UnarchivalState unarchivalState)
+            throws PackageManager.NameNotFoundException {
+        Objects.requireNonNull(unarchivalState);
+        try {
+            mInstaller.reportUnarchivalStatus(unarchivalState.getUnarchiveId(),
+                    unarchivalState.getStatus(), unarchivalState.getRequiredStorageBytes(),
+                    unarchivalState.getUserActionIntent(), new UserHandle(mUserId));
+        } catch (ParcelableException e) {
+            e.maybeRethrow(PackageManager.NameNotFoundException.class);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     // (b/239722738) This class serves as a bridge between the PackageLite class, which
     // is a hidden class, and the consumers of this class. (e.g. InstallInstalling.java)
     // This is a part of an effort to remove dependency on hidden APIs and use SystemAPIs or
@@ -2695,6 +2718,8 @@
         /** @hide */
         public long rollbackLifetimeMillis = 0;
         /** {@hide} */
+        public int rollbackImpactLevel = PackageManager.ROLLBACK_USER_IMPACT_LOW;
+        /** {@hide} */
         public boolean forceQueryableOverride;
         /** {@hide} */
         public int requireUserAction = USER_ACTION_UNSPECIFIED;
@@ -2751,6 +2776,7 @@
             }
             rollbackDataPolicy = source.readInt();
             rollbackLifetimeMillis = source.readLong();
+            rollbackImpactLevel = source.readInt();
             requireUserAction = source.readInt();
             packageSource = source.readInt();
             applicationEnabledSettingPersistent = source.readBoolean();
@@ -2785,6 +2811,7 @@
             ret.dataLoaderParams = dataLoaderParams;
             ret.rollbackDataPolicy = rollbackDataPolicy;
             ret.rollbackLifetimeMillis = rollbackLifetimeMillis;
+            ret.rollbackImpactLevel = rollbackImpactLevel;
             ret.requireUserAction = requireUserAction;
             ret.packageSource = packageSource;
             ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
@@ -3123,6 +3150,28 @@
         }
 
         /**
+         * rollbackImpactLevel is a measure of impact a rollback has on the user. This can take one
+         * of 3 values:
+         * <ul>
+         *     <li>{@link PackageManager#ROLLBACK_USER_IMPACT_LOW} (default)</li>
+         *     <li>{@link PackageManager#ROLLBACK_USER_IMPACT_HIGH} (1)</li>
+         *     <li>{@link PackageManager#ROLLBACK_USER_IMPACT_ONLY_MANUAL} (2)</li>
+         * </ul>
+         *
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
+        @FlaggedApi(Flags.FLAG_RECOVERABILITY_DETECTION)
+        public void setRollbackImpactLevel(@PackageManager.RollbackImpactLevel int impactLevel) {
+            if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) {
+                throw new IllegalArgumentException(
+                        "Can't set rollbackImpactLevel when rollback is not enabled");
+            }
+            rollbackImpactLevel = impactLevel;
+        }
+
+        /**
          * @deprecated use {@link #setRequestDowngrade(boolean)}.
          * {@hide}
          */
@@ -3495,6 +3544,7 @@
             pw.printPair("dataLoaderParams", dataLoaderParams);
             pw.printPair("rollbackDataPolicy", rollbackDataPolicy);
             pw.printPair("rollbackLifetimeMillis", rollbackLifetimeMillis);
+            pw.printPair("rollbackImpactLevel", rollbackImpactLevel);
             pw.printPair("applicationEnabledSettingPersistent",
                     applicationEnabledSettingPersistent);
             pw.printHexPair("developmentInstallFlags", developmentInstallFlags);
@@ -3538,6 +3588,7 @@
             }
             dest.writeInt(rollbackDataPolicy);
             dest.writeLong(rollbackLifetimeMillis);
+            dest.writeInt(rollbackImpactLevel);
             dest.writeInt(requireUserAction);
             dest.writeInt(packageSource);
             dest.writeBoolean(applicationEnabledSettingPersistent);
@@ -3736,6 +3787,9 @@
         public long rollbackLifetimeMillis;
 
         /** {@hide} */
+        public int rollbackImpactLevel;
+
+        /** {@hide} */
         public int requireUserAction;
 
         /** {@hide} */
@@ -3803,6 +3857,7 @@
             isPreapprovalRequested = source.readBoolean();
             rollbackDataPolicy = source.readInt();
             rollbackLifetimeMillis = source.readLong();
+            rollbackImpactLevel = source.readInt();
             createdMillis = source.readLong();
             requireUserAction = source.readInt();
             installerUid = source.readInt();
@@ -4440,6 +4495,7 @@
             dest.writeBoolean(isPreapprovalRequested);
             dest.writeInt(rollbackDataPolicy);
             dest.writeLong(rollbackLifetimeMillis);
+            dest.writeInt(rollbackImpactLevel);
             dest.writeLong(createdMillis);
             dest.writeInt(requireUserAction);
             dest.writeInt(installerUid);
@@ -4710,10 +4766,10 @@
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
                 inputSignatures = "private final @android.annotation.Nullable android.graphics.Bitmap mIcon\nprivate final @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate final @android.annotation.NonNull android.icu.util.ULocale mLocale\nprivate final @android.annotation.NonNull java.lang.String mPackageName\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.PackageInstaller.PreapprovalDetails> CREATOR\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\npublic @java.lang.Override int describeContents()\nclass PreapprovalDetails extends java.lang.Object implements [android.os.Parcelable]\nprivate @android.annotation.Nullable android.graphics.Bitmap mIcon\nprivate @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate @android.annotation.NonNull android.icu.util.ULocale mLocale\nprivate @android.annotation.NonNull java.lang.String mPackageName\nprivate  long mBuilderFieldsSet\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails.Builder setIcon(android.graphics.Bitmap)\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLabel(java.lang.CharSequence)\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLocale(android.icu.util.ULocale)\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails.Builder setPackageName(java.lang.String)\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails build()\nprivate  void checkNotUsed()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true)")
+
         @Deprecated
         private void __metadata() {}
 
-
         //@formatter:on
         // End of generated code
 
@@ -5104,13 +5160,188 @@
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
                 inputSignatures = "public static final @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE\nprivate final  boolean mDeviceIdleRequired\nprivate final  boolean mAppNotForegroundRequired\nprivate final  boolean mAppNotInteractingRequired\nprivate final  boolean mAppNotTopVisibleRequired\nprivate final  boolean mNotInCallRequired\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate  boolean mDeviceIdleRequired\nprivate  boolean mAppNotForegroundRequired\nprivate  boolean mAppNotInteractingRequired\nprivate  boolean mAppNotTopVisibleRequired\nprivate  boolean mNotInCallRequired\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setDeviceIdleRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotForegroundRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotInteractingRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotTopVisibleRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setNotInCallRequired()\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genEqualsHashCode=true)")
+
         @Deprecated
         private void __metadata() {}
 
-
         //@formatter:on
         // End of generated code
 
     }
 
+    /**
+     * Used to communicate the unarchival state in {@link #reportUnarchivalState}.
+     */
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public static final class UnarchivalState {
+
+        /**
+         * The caller is able to facilitate the unarchival for the given {@code unarchiveId}.
+         *
+         * @param unarchiveId the ID provided by the system as part of the intent.action.UNARCHIVE
+         *                    broadcast with EXTRA_UNARCHIVE_ID.
+         */
+        @NonNull
+        public static UnarchivalState createOkState(int unarchiveId) {
+            return new UnarchivalState(unarchiveId, UNARCHIVAL_OK, /* requiredStorageBytes= */ -1,
+                    /* userActionIntent= */ null);
+        }
+
+        /**
+         * User action is required before commencing with the unarchival for the given
+         * {@code unarchiveId}. E.g., this could be used if it's necessary for the user to sign-in
+         * first.
+         *
+         * @param unarchiveId      the ID provided by the system as part of the
+         *                         intent.action.UNARCHIVE
+         *                         broadcast with EXTRA_UNARCHIVE_ID.
+         * @param userActionIntent optional intent to start a follow up action required to
+         *                         facilitate the unarchival flow (e.g. user needs to log in).
+         */
+        @NonNull
+        public static UnarchivalState createUserActionRequiredState(int unarchiveId,
+                @NonNull PendingIntent userActionIntent) {
+            Objects.requireNonNull(userActionIntent);
+            return new UnarchivalState(unarchiveId, UNARCHIVAL_ERROR_USER_ACTION_NEEDED,
+                    /* requiredStorageBytes= */ -1, userActionIntent);
+        }
+
+        /**
+         * There is not enough storage to start the unarchival for the given {@code unarchiveId}.
+         *
+         * @param unarchiveId          the ID provided by the system as part of the
+         *                             intent.action.UNARCHIVE
+         *                             broadcast with EXTRA_UNARCHIVE_ID.
+         * @param requiredStorageBytes ff the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this
+         *                             field should be set to specify how many additional bytes of
+         *                             storage are required to unarchive the app.
+         * @param userActionIntent     can optionally be set to provide a custom storage-clearing
+         *                             action.
+         */
+        @NonNull
+        public static UnarchivalState createInsufficientStorageState(int unarchiveId,
+                long requiredStorageBytes, @Nullable PendingIntent userActionIntent) {
+            return new UnarchivalState(unarchiveId, UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE,
+                    requiredStorageBytes, userActionIntent);
+        }
+
+        /**
+         * The device has no data connectivity and unarchival cannot be started for the given
+         * {@code unarchiveId}.
+         *
+         * @param unarchiveId the ID provided by the system as part of the intent.action.UNARCHIVE
+         *                    broadcast with EXTRA_UNARCHIVE_ID.
+         */
+        @NonNull
+        public static UnarchivalState createNoConnectivityState(int unarchiveId) {
+            return new UnarchivalState(unarchiveId, UNARCHIVAL_ERROR_NO_CONNECTIVITY,
+                    /* requiredStorageBytes= */ -1,/* userActionIntent= */ null);
+        }
+
+        /**
+         * Generic error state for all cases that are not covered by other methods in this class.
+         *
+         * @param unarchiveId the ID provided by the system as part of the intent.action.UNARCHIVE
+         *                    broadcast with EXTRA_UNARCHIVE_ID.
+         */
+        @NonNull
+        public static UnarchivalState createGenericErrorState(int unarchiveId) {
+            return new UnarchivalState(unarchiveId, UNARCHIVAL_GENERIC_ERROR,
+                    /* requiredStorageBytes= */ -1,/* userActionIntent= */ null);
+        }
+
+
+        /**
+         * The ID provided by the system as part of the intent.action.UNARCHIVE broadcast with
+         * EXTRA_UNARCHIVE_ID.
+         */
+        private final int mUnarchiveId;
+
+        /** Used for the system to provide the user with necessary follow-up steps or errors. */
+        @UnarchivalStatus
+        private final int mStatus;
+
+        /**
+         * If the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this field should be set to specify
+         * how many additional bytes of storage are required to unarchive the app.
+         */
+        private final long mRequiredStorageBytes;
+
+        /**
+         * Optional intent to start a follow up action required to facilitate the unarchival flow
+         * (e.g., user needs to log in).
+         */
+        @Nullable
+        private final PendingIntent mUserActionIntent;
+
+        /**
+         * Creates a new UnarchivalState.
+         *
+         * @param unarchiveId          The ID provided by the system as part of the
+         *                             intent.action.UNARCHIVE broadcast with
+         *                             EXTRA_UNARCHIVE_ID.
+         * @param status               Used for the system to provide the user with necessary
+         *                             follow-up steps or errors.
+         * @param requiredStorageBytes If the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this
+         *                             field should be set to specify
+         *                             how many additional bytes of storage are required to
+         *                             unarchive the app.
+         * @param userActionIntent     Optional intent to start a follow up action required to
+         *                             facilitate the unarchival flow
+         *                             (e.g,. user needs to log in).
+         * @hide
+         */
+        private UnarchivalState(
+                int unarchiveId,
+                @UnarchivalStatus int status,
+                long requiredStorageBytes,
+                @Nullable PendingIntent userActionIntent) {
+            this.mUnarchiveId = unarchiveId;
+            this.mStatus = status;
+            com.android.internal.util.AnnotationValidations.validate(
+                    UnarchivalStatus.class, null, mStatus);
+            this.mRequiredStorageBytes = requiredStorageBytes;
+            this.mUserActionIntent = userActionIntent;
+        }
+
+        /**
+         * The ID provided by the system as part of the intent.action.UNARCHIVE broadcast with
+         * EXTRA_UNARCHIVE_ID.
+         *
+         * @hide
+         */
+        int getUnarchiveId() {
+            return mUnarchiveId;
+        }
+
+        /**
+         * Used for the system to provide the user with necessary follow-up steps or errors.
+         *
+         * @hide
+         */
+        @UnarchivalStatus int getStatus() {
+            return mStatus;
+        }
+
+        /**
+         * If the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this field should be set to specify
+         * how many additional bytes of storage are required to unarchive the app.
+         *
+         * @hide
+         */
+        long getRequiredStorageBytes() {
+            return mRequiredStorageBytes;
+        }
+
+        /**
+         * Optional intent to start a follow up action required to facilitate the unarchival flow
+         * (e.g. user needs to log in).
+         *
+         * @hide
+         */
+        @Nullable PendingIntent getUserActionIntent() {
+            return mUserActionIntent;
+        }
+    }
+
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8e5e825..8744eae 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -128,6 +128,7 @@
  * <a href="/training/basics/intents/package-visibility">manage package visibility</a>.
  * </p>
  */
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
 public abstract class PackageManager {
     private static final String TAG = "PackageManager";
 
@@ -1501,6 +1502,44 @@
     public static final int ROLLBACK_DATA_POLICY_RETAIN = 2;
 
     /** @hide */
+    @IntDef(prefix = {"ROLLBACK_USER_IMPACT_"}, value = {
+            ROLLBACK_USER_IMPACT_LOW,
+            ROLLBACK_USER_IMPACT_HIGH,
+            ROLLBACK_USER_IMPACT_ONLY_MANUAL,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RollbackImpactLevel {}
+
+    /**
+     * Rollback will be performed automatically in response to native crashes on startup or
+     * persistent service crashes. More suitable for apps that do not store any user data.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(android.content.pm.Flags.FLAG_RECOVERABILITY_DETECTION)
+    public static final int ROLLBACK_USER_IMPACT_LOW = 0;
+
+    /**
+     * Rollback will be performed automatically only when the device is found to be unrecoverable.
+     * More suitable for apps that store user data and have higher impact on user.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(android.content.pm.Flags.FLAG_RECOVERABILITY_DETECTION)
+    public static final int ROLLBACK_USER_IMPACT_HIGH = 1;
+
+    /**
+     * Rollback will not be performed automatically. It can be triggered externally.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(android.content.pm.Flags.FLAG_RECOVERABILITY_DETECTION)
+    public static final int ROLLBACK_USER_IMPACT_ONLY_MANUAL = 2;
+
+    /** @hide */
     @IntDef(flag = true, prefix = { "INSTALL_" }, value = {
             INSTALL_REPLACE_EXISTING,
             INSTALL_ALLOW_TEST,
@@ -2596,7 +2635,6 @@
             DELETE_SYSTEM_APP,
             DELETE_DONT_KILL_APP,
             DELETE_CHATTY,
-            DELETE_SHOW_DIALOG,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface DeleteFlags {}
@@ -2649,12 +2687,6 @@
     public static final int DELETE_ARCHIVE = 0x00000010;
 
     /**
-     * Show a confirmation dialog to the user when app is being deleted.
-     */
-    @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
-    public static final int DELETE_SHOW_DIALOG = 0x00000020;
-
-    /**
      * Flag parameter for {@link #deletePackage} to indicate that package deletion
      * should be chatty.
      *
@@ -5461,6 +5493,7 @@
      * application info.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static class Flags {
         final long mValue;
         protected Flags(long value) {
@@ -5475,6 +5508,7 @@
      * Specific flags used for retrieving package info. Example:
      * {@code PackageManager.getPackageInfo(packageName, PackageInfoFlags.of(0)}
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public final static class PackageInfoFlags extends Flags {
         private PackageInfoFlags(@PackageInfoFlagsBits long value) {
             super(value);
@@ -5488,6 +5522,7 @@
     /**
      * Specific flags used for retrieving application info.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public final static class ApplicationInfoFlags extends Flags {
         private ApplicationInfoFlags(@ApplicationInfoFlagsBits long value) {
             super(value);
@@ -5501,6 +5536,7 @@
     /**
      * Specific flags used for retrieving component info.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public final static class ComponentInfoFlags extends Flags {
         private ComponentInfoFlags(@ComponentInfoFlagsBits long value) {
             super(value);
@@ -5514,6 +5550,7 @@
     /**
      * Specific flags used for retrieving resolve info.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public final static class ResolveInfoFlags extends Flags {
         private ResolveInfoFlags(@ResolveInfoFlagsBits long value) {
             super(value);
diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java
index 9e553db..de33fa8 100644
--- a/core/java/android/content/pm/ProviderInfo.java
+++ b/core/java/android/content/pm/ProviderInfo.java
@@ -89,6 +89,15 @@
     public static final int FLAG_VISIBLE_TO_INSTANT_APP = 0x100000;
 
     /**
+     * Bit in {@link #flags}: If set, this provider will only be available
+     * for the system user.
+     * Set from the android.R.attr#systemUserOnly attribute.
+     * In Sync with {@link ActivityInfo#FLAG_SYSTEM_USER_ONLY}
+     * @hide
+     */
+    public static final int FLAG_SYSTEM_USER_ONLY = ActivityInfo.FLAG_SYSTEM_USER_ONLY;
+
+    /**
      * Bit in {@link #flags}: If set, a single instance of the provider will
      * run for all users on the device.  Set from the
      * {@link android.R.attr#singleUser} attribute.
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index ae46c027..2b378b1 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -101,6 +101,14 @@
     public static final int FLAG_VISIBLE_TO_INSTANT_APP = 0x100000;
 
     /**
+     * @hide Bit in {@link #flags}: If set, this service will only be available
+     * for the system user.
+     * Set from the android.R.attr#systemUserOnly attribute.
+     * In Sync with {@link ActivityInfo#FLAG_SYSTEM_USER_ONLY}
+     */
+    public static final int FLAG_SYSTEM_USER_ONLY = ActivityInfo.FLAG_SYSTEM_USER_ONLY;
+
+    /**
      * Bit in {@link #flags}: If set, a single instance of the service will
      * run for all users on the device.  Set from the
      * {@link android.R.attr#singleUser} attribute.
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 8fd78bd..3e9f260 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -52,6 +52,7 @@
  * @hide
  */
 @TestApi
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class UserInfo implements Parcelable {
 
     /**
@@ -438,6 +439,7 @@
     /**
      * @return true if this user can be switched to.
      **/
+    @android.ravenwood.annotation.RavenwoodThrow
     public boolean supportsSwitchTo() {
         if (partial || !isEnabled()) {
             // Don't support switching to disabled or partial users, which includes users with
@@ -455,6 +457,7 @@
      * @return true if user is of type {@link UserManager#USER_TYPE_SYSTEM_HEADLESS} and
      * {@link com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser} is true.
      */
+    @android.ravenwood.annotation.RavenwoodThrow
     private boolean canSwitchToHeadlessSystemUser() {
         return UserManager.USER_TYPE_SYSTEM_HEADLESS.equals(userType) && Resources.getSystem()
                 .getBoolean(com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser);
@@ -465,6 +468,7 @@
      * @deprecated Use {@link UserInfo#supportsSwitchTo} instead.
      */
     @Deprecated
+    @android.ravenwood.annotation.RavenwoodThrow
     public boolean supportsSwitchToByUser() {
         return supportsSwitchTo();
     }
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 57749d4..1d0e2db 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -16,6 +16,9 @@
 
 package android.content.pm;
 
+import static android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES;
+
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -73,7 +76,7 @@
 
     private static final String ATTR_CROSS_PROFILE_CONTENT_SHARING_STRATEGY =
             "crossProfileContentSharingStrategy";
-
+    private static final String ATTR_PROFILE_API_VISIBILITY = "profileApiVisibility";
     /** Index values of each property (to indicate whether they are present in this object). */
     @IntDef(prefix = "INDEX_", value = {
             INDEX_SHOW_IN_LAUNCHER,
@@ -93,6 +96,7 @@
             INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE,
             INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY,
             INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING,
+            INDEX_PROFILE_API_VISIBILITY
     })
     @Retention(RetentionPolicy.SOURCE)
     private @interface PropertyIndex {
@@ -114,6 +118,7 @@
     private static final int INDEX_SHOW_IN_SHARING_SURFACES = 14;
     private static final int INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY = 15;
     private static final int INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING = 16;
+    private static final int INDEX_PROFILE_API_VISIBILITY = 17;
     /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
     private long mPropertiesPresent = 0;
 
@@ -123,6 +128,7 @@
      * @hide
      */
     @IntDef(prefix = "SHOW_IN_LAUNCHER_", value = {
+            SHOW_IN_LAUNCHER_UNKNOWN,
             SHOW_IN_LAUNCHER_WITH_PARENT,
             SHOW_IN_LAUNCHER_SEPARATE,
             SHOW_IN_LAUNCHER_NO,
@@ -131,6 +137,13 @@
     public @interface ShowInLauncher {
     }
     /**
+     * Indicates that the show in launcher value for this profile is unknown or unsupported.
+     * @hide
+     */
+    @TestApi
+    @SuppressLint("UnflaggedApi") // b/306636213
+    public static final int SHOW_IN_LAUNCHER_UNKNOWN = -1;
+    /**
      * Suggests that the launcher should show this user's apps in the main tab.
      * That is, either this user is a full user, so its apps should be presented accordingly, or, if
      * this user is a profile, then its apps should be shown alongside its parent's apps.
@@ -157,6 +170,7 @@
      * @hide
      */
     @IntDef(prefix = "SHOW_IN_SETTINGS_", value = {
+            SHOW_IN_SETTINGS_UNKNOWN,
             SHOW_IN_SETTINGS_WITH_PARENT,
             SHOW_IN_SETTINGS_SEPARATE,
             SHOW_IN_SETTINGS_NO,
@@ -165,6 +179,12 @@
     public @interface ShowInSettings {
     }
     /**
+     * Indicates that the show in settings value for this profile is unknown or unsupported.
+     * @hide
+     */
+    @SuppressLint("UnflaggedApi") // b/306636213
+    public static final int SHOW_IN_SETTINGS_UNKNOWN = -1;
+    /**
      * Suggests that the Settings app should show this user's apps in the main tab.
      * That is, either this user is a full user, so its apps should be presented accordingly, or, if
      * this user is a profile, then its apps should be shown alongside its parent's apps.
@@ -309,6 +329,7 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "SHOW_IN_QUIET_MODE_",
             value = {
+                    SHOW_IN_QUIET_MODE_UNKNOWN,
                     SHOW_IN_QUIET_MODE_PAUSED,
                     SHOW_IN_QUIET_MODE_HIDDEN,
                     SHOW_IN_QUIET_MODE_DEFAULT,
@@ -318,6 +339,12 @@
     }
 
     /**
+     * Indicates that the show in quiet mode value for this profile is unknown.
+     */
+    @SuppressLint("UnflaggedApi") // b/306636213
+    public static final int SHOW_IN_QUIET_MODE_UNKNOWN = -1;
+
+    /**
      * Indicates that the profile should still be visible in quiet mode but should be shown as
      * paused (e.g. by greying out its icons).
      */
@@ -347,6 +374,7 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "SHOW_IN_SHARING_SURFACES_",
             value = {
+                    SHOW_IN_SHARING_SURFACES_UNKNOWN,
                     SHOW_IN_SHARING_SURFACES_SEPARATE,
                     SHOW_IN_SHARING_SURFACES_WITH_PARENT,
                     SHOW_IN_SHARING_SURFACES_NO,
@@ -356,6 +384,12 @@
     }
 
     /**
+     * Indicates that the show in launcher value for this profile is unknown or unsupported.
+     */
+    @SuppressLint("UnflaggedApi") // b/306636213
+    public static final int SHOW_IN_SHARING_SURFACES_UNKNOWN = SHOW_IN_LAUNCHER_UNKNOWN;
+
+    /**
      * Indicates that the profile data and apps should be shown in sharing surfaces intermixed with
      * parent user's data and apps.
      */
@@ -379,7 +413,8 @@
      *
      * @hide
      */
-    @IntDef(prefix = {"CROSS_PROFILE_CONTENT_SHARING_STRATEGY_"}, value = {
+    @IntDef(prefix = {"CROSS_PROFILE_CONTENT_SHARING_"}, value = {
+            CROSS_PROFILE_CONTENT_SHARING_UNKNOWN,
             CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION,
             CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT
     })
@@ -388,6 +423,13 @@
     }
 
     /**
+     * Signifies that cross-profile content sharing strategy, both to and from this profile, is
+     * unknown/unsupported.
+     */
+    @SuppressLint("UnflaggedApi") // b/306636213
+    public static final int CROSS_PROFILE_CONTENT_SHARING_UNKNOWN = -1;
+
+    /**
      * Signifies that cross-profile content sharing strategy, both to and from this profile, should
      * not be delegated to any other user/profile.
      * For ex:
@@ -413,6 +455,41 @@
     @SuppressLint("UnflaggedApi") // b/306636213
     public static final int CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT = 1;
 
+    /**
+     * Possible values for the profile visibility in public API surfaces. This indicates whether or
+     * not the information linked to the profile (userId, package names) should not be returned in
+     * API surfaces if a user is marked as hidden.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "PROFILE_API_VISIBILITY_",
+            value = {
+                    PROFILE_API_VISIBILITY_UNKNOWN,
+                    PROFILE_API_VISIBILITY_VISIBLE,
+                    PROFILE_API_VISIBILITY_HIDDEN,
+            }
+    )
+    public @interface ProfileApiVisibility {
+    }
+    /*
+    * The api visibility value for this profile user is undefined or unknown.
+     */
+    @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
+    public static final int PROFILE_API_VISIBILITY_UNKNOWN = -1;
+
+    /**
+     * Indicates that information about this profile user should be shown in API surfaces.
+     */
+    @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
+    public static final int PROFILE_API_VISIBILITY_VISIBLE = 0;
+
+    /**
+     * Indicates that information about this profile should be not be visible in API surfaces.
+     */
+    @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
+    public static final int PROFILE_API_VISIBILITY_HIDDEN = 1;
+
 
     /**
      * Creates a UserProperties (intended for the SystemServer) that stores a reference to the given
@@ -473,6 +550,9 @@
         setShowInQuietMode(orig.getShowInQuietMode());
         setShowInSharingSurfaces(orig.getShowInSharingSurfaces());
         setCrossProfileContentSharingStrategy(orig.getCrossProfileContentSharingStrategy());
+        if (android.multiuser.Flags.supportHidingProfiles()) {
+            setProfileApiVisibility(orig.getProfileApiVisibility());
+        }
     }
 
     /**
@@ -914,9 +994,31 @@
     }
     private @CrossProfileContentSharingStrategy int mCrossProfileContentSharingStrategy;
 
+    /**
+     * Returns the visibility of the profile user in API surfaces. Any information linked to the
+     * profile (userId, package names) should be hidden API surfaces if a user is marked as hidden.
+     */
+    @NonNull
+    @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
+    public @ProfileApiVisibility int getProfileApiVisibility() {
+        if (isPresent(INDEX_PROFILE_API_VISIBILITY)) return mProfileApiVisibility;
+        if (mDefaultProperties != null) return mDefaultProperties.mProfileApiVisibility;
+        throw new SecurityException("You don't have permission to query profileApiVisibility");
+    }
+    /** @hide */
+    @NonNull
+    @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
+    public void setProfileApiVisibility(@ProfileApiVisibility int profileApiVisibility) {
+        this.mProfileApiVisibility = profileApiVisibility;
+        setPresent(INDEX_PROFILE_API_VISIBILITY);
+    }
+    private @ProfileApiVisibility int mProfileApiVisibility;
 
     @Override
     public String toString() {
+        String profileApiVisibility =
+                android.multiuser.Flags.supportHidingProfiles() ? ", mProfileApiVisibility="
+                        + getProfileApiVisibility() : "";
         // Please print in increasing order of PropertyIndex.
         return "UserProperties{"
                 + "mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent)
@@ -940,6 +1042,7 @@
                 + ", mDeleteAppWithParent=" + getDeleteAppWithParent()
                 + ", mAlwaysVisible=" + getAlwaysVisible()
                 + ", mCrossProfileContentSharingStrategy=" + getCrossProfileContentSharingStrategy()
+                + profileApiVisibility
                 + "}";
     }
 
@@ -973,6 +1076,9 @@
         pw.println(prefix + "    mAlwaysVisible=" + getAlwaysVisible());
         pw.println(prefix + "    mCrossProfileContentSharingStrategy="
                 + getCrossProfileContentSharingStrategy());
+        if (android.multiuser.Flags.supportHidingProfiles()) {
+            pw.println(prefix + "    mProfileApiVisibility=" + getProfileApiVisibility());
+        }
     }
 
     /**
@@ -1056,6 +1162,12 @@
                     break;
                 case ATTR_CROSS_PROFILE_CONTENT_SHARING_STRATEGY:
                     setCrossProfileContentSharingStrategy(parser.getAttributeInt(i));
+                    break;
+                case ATTR_PROFILE_API_VISIBILITY:
+                    if (android.multiuser.Flags.supportHidingProfiles()) {
+                        setProfileApiVisibility(parser.getAttributeInt(i));
+                    }
+                    break;
                 default:
                     Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
             }
@@ -1138,6 +1250,12 @@
             serializer.attributeInt(null, ATTR_CROSS_PROFILE_CONTENT_SHARING_STRATEGY,
                     mCrossProfileContentSharingStrategy);
         }
+        if (isPresent(INDEX_PROFILE_API_VISIBILITY)) {
+            if (android.multiuser.Flags.supportHidingProfiles()) {
+                serializer.attributeInt(null, ATTR_PROFILE_API_VISIBILITY,
+                        mProfileApiVisibility);
+            }
+        }
     }
 
     // For use only with an object that has already had any permission-lacking fields stripped out.
@@ -1161,6 +1279,7 @@
         dest.writeBoolean(mDeleteAppWithParent);
         dest.writeBoolean(mAlwaysVisible);
         dest.writeInt(mCrossProfileContentSharingStrategy);
+        dest.writeInt(mProfileApiVisibility);
     }
 
     /**
@@ -1188,6 +1307,7 @@
         mDeleteAppWithParent = source.readBoolean();
         mAlwaysVisible = source.readBoolean();
         mCrossProfileContentSharingStrategy = source.readInt();
+        mProfileApiVisibility = source.readInt();
     }
 
     @Override
@@ -1237,6 +1357,7 @@
         private boolean mAlwaysVisible = false;
         private @CrossProfileContentSharingStrategy int mCrossProfileContentSharingStrategy =
                 CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION;
+        private @ProfileApiVisibility int mProfileApiVisibility = 0;
 
         /**
          * @hide
@@ -1391,6 +1512,17 @@
             return this;
         }
 
+        /**
+         * Sets the value for {@link #mProfileApiVisibility}
+         * @hide
+         */
+        @NonNull
+        @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
+        public Builder setProfileApiVisibility(@ProfileApiVisibility int profileApiVisibility){
+            mProfileApiVisibility = profileApiVisibility;
+            return this;
+        }
+
         /** Builds a UserProperties object with *all* values populated.
          * @hide
          */
@@ -1415,7 +1547,8 @@
                     mAllowStoppingUserWithDelayedLocking,
                     mDeleteAppWithParent,
                     mAlwaysVisible,
-                    mCrossProfileContentSharingStrategy);
+                    mCrossProfileContentSharingStrategy,
+                    mProfileApiVisibility);
         }
     } // end Builder
 
@@ -1436,7 +1569,8 @@
             boolean allowStoppingUserWithDelayedLocking,
             boolean deleteAppWithParent,
             boolean alwaysVisible,
-            @CrossProfileContentSharingStrategy int crossProfileContentSharingStrategy) {
+            @CrossProfileContentSharingStrategy int crossProfileContentSharingStrategy,
+            @ProfileApiVisibility int profileApiVisibility) {
         mDefaultProperties = null;
         setShowInLauncher(showInLauncher);
         setStartWithParent(startWithParent);
@@ -1456,5 +1590,8 @@
         setDeleteAppWithParent(deleteAppWithParent);
         setAlwaysVisible(alwaysVisible);
         setCrossProfileContentSharingStrategy(crossProfileContentSharingStrategy);
+        if (android.multiuser.Flags.supportHidingProfiles()) {
+            setProfileApiVisibility(profileApiVisibility);
+        }
     }
 }
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index a2cd3e1..caff457 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -110,6 +110,14 @@
 }
 
 flag {
+    name: "relative_reference_intent_filters"
+    namespace: "package_manager_service"
+    description: "Feature flag to enable relative reference intent filters"
+    bug: "307556883"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "fix_duplicated_flags"
     namespace: "package_manager_service"
     description: "Feature flag to fix duplicated PackageManager flag values"
@@ -146,3 +154,32 @@
     bug: "281848623"
 }
 
+flag {
+    name: "recoverability_detection"
+    namespace: "package_manager_service"
+    description: "Feature flag to enable recoverability detection feature. It includes GMS core rollback and improvements to rescue party."
+    bug: "291135724"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "fix_system_apps_first_install_time"
+    namespace: "package_manager_service"
+    description: "Feature flag to fix the first-install timestamps for system apps."
+    bug: "321258605"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "allow_sdk_sandbox_query_intent_activities"
+    namespace: "package_manager_service"
+    description: "Feature flag to allow the sandbox SDK to query intent activities of the client app."
+    bug: "295842134"
+}
+
+flag {
+    name: "emergency_install_permission"
+    namespace: "permissions"
+    description: "Feature flag to enable permission EMERGENCY_INSTALL_PACKAGES"
+    bug: "321080601"
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index c7797c7..efb8607 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -70,4 +70,41 @@
     namespace: "profile_experiences"
     description: "Add support for Private Space in resolver sheet"
     bug: "307515485"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "move_quiet_mode_operations_to_separate_thread"
+    namespace: "profile_experiences"
+    description: "Move the quiet mode operations, happening on a background thread today, to a separate thread."
+    bug: "320483504"
+}
+
+flag {
+    name: "enable_private_space_autolock_on_restarts"
+    namespace: "profile_experiences"
+    description: "Enable auto-locking private space on device restarts"
+    bug: "296993385"
+}
+
+flag {
+    name: "enable_system_user_only_for_services_and_providers"
+    namespace: "multiuser"
+    description: "Enable systemUserOnly manifest attribute for services and providers."
+    bug: "302354856"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "allow_private_profile_apis"
+    namespace: "profile_experiences"
+    description: "Enable only the API changes to support private space"
+    bug: "299069460"
+}
+
+flag {
+    name: "support_hiding_profiles"
+    namespace: "profile_experiences"
+    description: "Allow the use of a hide_profile property to hide some profiles behind a permission"
+    bug: "316362775"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/overlay/OverlayPaths.java b/core/java/android/content/pm/overlay/OverlayPaths.java
index a4db733..bd74b0b 100644
--- a/core/java/android/content/pm/overlay/OverlayPaths.java
+++ b/core/java/android/content/pm/overlay/OverlayPaths.java
@@ -49,6 +49,13 @@
     public static class Builder {
         final OverlayPaths mPaths = new OverlayPaths();
 
+        public Builder() {}
+
+        public Builder(@NonNull OverlayPaths base) {
+            mPaths.mResourceDirs.addAll(base.getResourceDirs());
+            mPaths.mOverlayPaths.addAll(base.getOverlayPaths());
+        }
+
         /**
          * Adds a non-APK path to the contents of {@link OverlayPaths#getOverlayPaths()}.
          */
diff --git a/core/java/android/content/res/Element.java b/core/java/android/content/res/Element.java
index 89f4985..6ff96f4 100644
--- a/core/java/android/content/res/Element.java
+++ b/core/java/android/content/res/Element.java
@@ -93,6 +93,7 @@
     protected static final String TAG_SUPPORTS_GL_TEXTURE = "supports-gl-texture";
     protected static final String TAG_SUPPORTS_INPUT = "supports-input";
     protected static final String TAG_SUPPORTS_SCREENS = "supports-screens";
+    protected static final String TAG_URI_RELATIVE_FILTER_GROUP = "uri-relative-filter-group";
     protected static final String TAG_USES_CONFIGURATION = "uses-configuration";
     protected static final String TAG_USES_FEATURE = "uses-feature";
     protected static final String TAG_USES_GL_TEXTURE = "uses-gl-texture";
@@ -106,6 +107,11 @@
 
     protected static final String TAG_ATTR_BACKUP_AGENT = "backupAgent";
     protected static final String TAG_ATTR_CATEGORY = "category";
+    protected static final String TAG_ATTR_FRAGMENT = "fragment";
+    protected static final String TAG_ATTR_FRAGMENT_ADVANCED_PATTERN = "fragmentAdvancedPattern";
+    protected static final String TAG_ATTR_FRAGMENT_PATTERN = "fragmentPattern";
+    protected static final String TAG_ATTR_FRAGMENT_PREFIX = "fragmentPrefix";
+    protected static final String TAG_ATTR_FRAGMENT_SUFFIX = "fragmentSuffix";
     protected static final String TAG_ATTR_HOST = "host";
     protected static final String TAG_ATTR_MANAGE_SPACE_ACTIVITY = "manageSpaceActivity";
     protected static final String TAG_ATTR_MIMETYPE = "mimeType";
@@ -122,6 +128,11 @@
     protected static final String TAG_ATTR_PERMISSION_GROUP = "permissionGroup";
     protected static final String TAG_ATTR_PORT = "port";
     protected static final String TAG_ATTR_PROCESS = "process";
+    protected static final String TAG_ATTR_QUERY = "query";
+    protected static final String TAG_ATTR_QUERY_ADVANCED_PATTERN = "queryAdvancedPattern";
+    protected static final String TAG_ATTR_QUERY_PATTERN = "queryPattern";
+    protected static final String TAG_ATTR_QUERY_PREFIX = "queryPrefix";
+    protected static final String TAG_ATTR_QUERY_SUFFIX = "querySuffix";
     protected static final String TAG_ATTR_READ_PERMISSION = "readPermission";
     protected static final String TAG_ATTR_REQUIRED_ACCOUNT_TYPE = "requiredAccountType";
     protected static final String TAG_ATTR_REQUIRED_SYSTEM_PROPERTY_NAME =
@@ -143,7 +154,7 @@
 
     // The length of mTagCounters corresponds to the number of tags defined in getCounterIdx. If new
     // tags are added then the size here should be increased to match.
-    private final TagCounter[] mTagCounters = new TagCounter[34];
+    private final TagCounter[] mTagCounters = new TagCounter[35];
 
     String mTag;
 
@@ -238,9 +249,11 @@
                 return 31;
             case TAG_INTENT:
                 return 32;
+            case TAG_URI_RELATIVE_FILTER_GROUP:
+                return 33;
             default:
                 // The size of the mTagCounters array should be equal to this value+1
-                return 33;
+                return 34;
         }
     }
 
@@ -276,6 +289,7 @@
             case TAG_SERVICE:
             case TAG_SUPPORTS_GL_TEXTURE:
             case TAG_SUPPORTS_SCREENS:
+            case TAG_URI_RELATIVE_FILTER_GROUP:
             case TAG_USES_CONFIGURATION:
             case TAG_USES_FEATURE:
             case TAG_USES_LIBRARY:
@@ -322,6 +336,7 @@
                 break;
             case TAG_INTENT:
             case TAG_INTENT_FILTER:
+                initializeCounter(TAG_URI_RELATIVE_FILTER_GROUP, 100);
                 initializeCounter(TAG_ACTION, 20000);
                 initializeCounter(TAG_CATEGORY, 40000);
                 initializeCounter(TAG_DATA, 40000);
@@ -354,6 +369,9 @@
                 initializeCounter(TAG_INTENT, 2000);
                 initializeCounter(TAG_PROVIDER, 8000);
                 break;
+            case TAG_URI_RELATIVE_FILTER_GROUP:
+                initializeCounter(TAG_DATA, 100);
+                break;
         }
     }
 
@@ -391,11 +409,21 @@
             case TAG_ATTR_VERSION_NAME:
             case TAG_ATTR_ZYGOTE_PRELOAD_NAME:
                 return MAX_ATTR_LEN_NAME;
+            case TAG_ATTR_FRAGMENT:
+            case TAG_ATTR_FRAGMENT_ADVANCED_PATTERN:
+            case TAG_ATTR_FRAGMENT_PATTERN:
+            case TAG_ATTR_FRAGMENT_PREFIX:
+            case TAG_ATTR_FRAGMENT_SUFFIX:
             case TAG_ATTR_PATH:
             case TAG_ATTR_PATH_ADVANCED_PATTERN:
             case TAG_ATTR_PATH_PATTERN:
             case TAG_ATTR_PATH_PREFIX:
             case TAG_ATTR_PATH_SUFFIX:
+            case TAG_ATTR_QUERY:
+            case TAG_ATTR_QUERY_ADVANCED_PATTERN:
+            case TAG_ATTR_QUERY_PATTERN:
+            case TAG_ATTR_QUERY_PREFIX:
+            case TAG_ATTR_QUERY_SUFFIX:
                 return MAX_ATTR_LEN_PATH;
             case TAG_ATTR_VALUE:
                 return MAX_ATTR_LEN_VALUE;
@@ -535,6 +563,16 @@
             case R.styleable.AndroidManifestData_pathPrefix:
             case R.styleable.AndroidManifestData_pathSuffix:
             case R.styleable.AndroidManifestData_pathAdvancedPattern:
+            case R.styleable.AndroidManifestData_query:
+            case R.styleable.AndroidManifestData_queryPattern:
+            case R.styleable.AndroidManifestData_queryPrefix:
+            case R.styleable.AndroidManifestData_querySuffix:
+            case R.styleable.AndroidManifestData_queryAdvancedPattern:
+            case R.styleable.AndroidManifestData_fragment:
+            case R.styleable.AndroidManifestData_fragmentPattern:
+            case R.styleable.AndroidManifestData_fragmentPrefix:
+            case R.styleable.AndroidManifestData_fragmentSuffix:
+            case R.styleable.AndroidManifestData_fragmentAdvancedPattern:
                 return MAX_ATTR_LEN_PATH;
             default:
                 return DEFAULT_MAX_STRING_ATTR_LENGTH;
diff --git a/core/java/android/content/res/FontScaleConverter.java b/core/java/android/content/res/FontScaleConverter.java
index 088949e..f4312a9 100644
--- a/core/java/android/content/res/FontScaleConverter.java
+++ b/core/java/android/content/res/FontScaleConverter.java
@@ -17,7 +17,9 @@
 package android.content.res;
 
 
+import android.annotation.AnyThread;
 import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
 
 /**
  * A converter for non-linear font scaling. Converts font sizes given in "sp" dimensions to a
@@ -40,4 +42,35 @@
      * Converts a dimension in "dp" back to "sp".
      */
     float convertDpToSp(float dp);
+
+    /**
+     * Returns true if non-linear font scaling curves would be in effect for the given scale, false
+     * if the scaling would follow a linear curve or for no scaling.
+     *
+     * <p>Example usage: {@code
+     * isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)}
+     */
+    @AnyThread
+    static boolean isNonLinearFontScalingActive(float fontScale) {
+        return FontScaleConverterFactory.isNonLinearFontScalingActive(fontScale);
+    }
+
+    /**
+     * Finds a matching FontScaleConverter for the given fontScale factor.
+     *
+     * Generally you shouldn't need this; you can use {@link
+     * android.util.TypedValue#applyDimension(int, float, DisplayMetrics)} directly and it will do
+     * the scaling conversion for you. Dimens and resources loaded from XML will also be
+     * automatically converted. But for UI frameworks or other situations where you need to do the
+     * conversion without an Android Context, you can use this method.
+     *
+     * @param fontScale the scale factor, usually from {@link Configuration#fontScale}.
+     *
+     * @return a converter for the given scale, or null if non-linear scaling should not be used.
+     */
+    @Nullable
+    @AnyThread
+    static FontScaleConverter forScale(float fontScale) {
+        return FontScaleConverterFactory.forScale(fontScale);
+    }
 }
diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java
index 5d31cc0..625d7cb 100644
--- a/core/java/android/content/res/FontScaleConverterFactory.java
+++ b/core/java/android/content/res/FontScaleConverterFactory.java
@@ -17,7 +17,6 @@
 package android.content.res;
 
 import android.annotation.AnyThread;
-import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.util.MathUtils;
@@ -32,8 +31,9 @@
  * android.util.TypedValue#applyDimension(int, float, DisplayMetrics)} directly and it will do the
  * scaling conversion for you. But for UI frameworks or other situations where you need to do the
  * conversion without an Android Context, you can use this class.
+ *
+ * @hide
  */
-@FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
 public class FontScaleConverterFactory {
     private static final float SCALE_KEY_MULTIPLIER = 100f;
 
@@ -58,6 +58,16 @@
         synchronized (LOOKUP_TABLES_WRITE_LOCK) {
             putInto(
                     sLookupTables,
+                    /* scaleKey= */ 1.1f,
+                    new FontScaleConverterImpl(
+                            /* fromSp= */
+                            new float[] {   8f,   10f,   12f,   14f,   18f,   20f,   24f,   30f,  100},
+                            /* toDp=   */
+                            new float[] { 8.8f,   11f, 13.2f, 15.6f, 19.2f, 21.2f, 24.8f,   30f,  100})
+            );
+
+            putInto(
+                    sLookupTables,
                     /* scaleKey= */ 1.15f,
                     new FontScaleConverterImpl(
                             /* fromSp= */
@@ -124,7 +134,6 @@
      * <p>Example usage:
      * <code>isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)</code>
      */
-    @FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
     @AnyThread
     public static boolean isNonLinearFontScalingActive(float fontScale) {
         return fontScale >= sMinScaleBeforeCurvesApplied;
@@ -137,7 +146,6 @@
      *
      * @return a converter for the given scale, or null if non-linear scaling should not be used.
      */
-    @FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
     @Nullable
     @AnyThread
     public static FontScaleConverter forScale(float fontScale) {
diff --git a/core/java/android/content/rollback/RollbackInfo.java b/core/java/android/content/rollback/RollbackInfo.java
index a363718..d128055 100644
--- a/core/java/android/content/rollback/RollbackInfo.java
+++ b/core/java/android/content/rollback/RollbackInfo.java
@@ -16,8 +16,12 @@
 
 package android.content.rollback;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.pm.Flags;
+import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -25,17 +29,14 @@
 import java.util.List;
 
 /**
- * Information about a set of packages that can be, or already have been
- * rolled back together.
+ * Information about a set of packages that can be, or already have been rolled back together.
  *
  * @hide
  */
 @SystemApi
 public final class RollbackInfo implements Parcelable {
 
-    /**
-     * A unique identifier for the rollback.
-     */
+    /** A unique identifier for the rollback. */
     private final int mRollbackId;
 
     private final List<PackageRollbackInfo> mPackages;
@@ -44,15 +45,39 @@
 
     private final boolean mIsStaged;
     private int mCommittedSessionId;
+    private int mRollbackImpactLevel;
 
     /** @hide */
-    public RollbackInfo(int rollbackId, List<PackageRollbackInfo> packages, boolean isStaged,
-            List<VersionedPackage> causePackages,  int committedSessionId) {
+    public RollbackInfo(
+            int rollbackId,
+            List<PackageRollbackInfo> packages,
+            boolean isStaged,
+            List<VersionedPackage> causePackages,
+            int committedSessionId,
+            @PackageManager.RollbackImpactLevel int rollbackImpactLevel) {
         this.mRollbackId = rollbackId;
         this.mPackages = packages;
         this.mIsStaged = isStaged;
         this.mCausePackages = causePackages;
         this.mCommittedSessionId = committedSessionId;
+        this.mRollbackImpactLevel = rollbackImpactLevel;
+    }
+
+    /** @hide */
+    public RollbackInfo(
+            int rollbackId,
+            List<PackageRollbackInfo> packages,
+            boolean isStaged,
+            List<VersionedPackage> causePackages,
+            int committedSessionId) {
+        // If impact level is not set default to 0
+        this(
+                rollbackId,
+                packages,
+                isStaged,
+                causePackages,
+                committedSessionId,
+                PackageManager.ROLLBACK_USER_IMPACT_LOW);
     }
 
     private RollbackInfo(Parcel in) {
@@ -61,34 +86,28 @@
         mIsStaged = in.readBoolean();
         mCausePackages = in.createTypedArrayList(VersionedPackage.CREATOR);
         mCommittedSessionId = in.readInt();
+        mRollbackImpactLevel = in.readInt();
     }
 
-    /**
-     * Returns a unique identifier for this rollback.
-     */
+    /** Returns a unique identifier for this rollback. */
     public int getRollbackId() {
         return mRollbackId;
     }
 
-    /**
-     * Returns the list of package that are rolled back.
-     */
+    /** Returns the list of package that are rolled back. */
     @NonNull
     public List<PackageRollbackInfo> getPackages() {
         return mPackages;
     }
 
-    /**
-     * Returns true if this rollback requires reboot to take effect after
-     * being committed.
-     */
+    /** Returns true if this rollback requires reboot to take effect after being committed. */
     public boolean isStaged() {
         return mIsStaged;
     }
 
     /**
-     * Returns the session ID for the committed rollback for staged rollbacks.
-     * Only applicable for rollbacks that have been committed.
+     * Returns the session ID for the committed rollback for staged rollbacks. Only applicable for
+     * rollbacks that have been committed.
      */
     public int getCommittedSessionId() {
         return mCommittedSessionId;
@@ -96,6 +115,7 @@
 
     /**
      * Sets the session ID for the committed rollback for staged rollbacks.
+     *
      * @hide
      */
     public void setCommittedSessionId(int sessionId) {
@@ -103,15 +123,40 @@
     }
 
     /**
-     * Gets the list of package versions that motivated this rollback.
-     * As provided to {@link #commitRollback} when the rollback was committed.
-     * This is only applicable for rollbacks that have been committed.
+     * Gets the list of package versions that motivated this rollback. As provided to {@link
+     * #commitRollback} when the rollback was committed. This is only applicable for rollbacks that
+     * have been committed.
      */
     @NonNull
     public List<VersionedPackage> getCausePackages() {
         return mCausePackages;
     }
 
+    /**
+     * Get rollback impact level. Refer {@link
+     * android.content.pm.PackageInstaller.SessionParams#setRollbackImpactLevel(int)} for more info
+     * on impact level.
+     *
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(Flags.FLAG_RECOVERABILITY_DETECTION)
+    public @PackageManager.RollbackImpactLevel int getRollbackImpactLevel() {
+        return mRollbackImpactLevel;
+    }
+
+    /**
+     * Set rollback impact level. Refer {@link
+     * android.content.pm.PackageInstaller.SessionParams#setRollbackImpactLevel(int)} for more info
+     * on impact level.
+     *
+     * @hide
+     */
+    public void setRollbackImpactLevel(
+            @PackageManager.RollbackImpactLevel int rollbackImpactLevel) {
+        mRollbackImpactLevel = rollbackImpactLevel;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -124,16 +169,17 @@
         out.writeBoolean(mIsStaged);
         out.writeTypedList(mCausePackages);
         out.writeInt(mCommittedSessionId);
+        out.writeInt(mRollbackImpactLevel);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<RollbackInfo> CREATOR =
             new Parcelable.Creator<RollbackInfo>() {
-        public RollbackInfo createFromParcel(Parcel in) {
-            return new RollbackInfo(in);
-        }
+                public RollbackInfo createFromParcel(Parcel in) {
+                    return new RollbackInfo(in);
+                }
 
-        public RollbackInfo[] newArray(int size) {
-            return new RollbackInfo[size];
-        }
-    };
+                public RollbackInfo[] newArray(int size) {
+                    return new RollbackInfo[size];
+                }
+            };
 }
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 47ee76e..2e63664 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -32,13 +32,14 @@
 import android.content.Context;
 import android.content.IntentSender;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.CancellationSignal;
+import android.os.IBinder;
 import android.os.ICancellationSignal;
 import android.os.OutcomeReceiver;
 import android.os.RemoteException;
 import android.provider.DeviceConfig;
 import android.util.Log;
-import android.view.autofill.IAutoFillManagerClient;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -58,6 +59,9 @@
 @SystemService(Context.CREDENTIAL_SERVICE)
 public final class CredentialManager {
     private static final String TAG = "CredentialManager";
+    private static final Bundle OPTIONS_SENDER_BAL_OPTIN = ActivityOptions.makeBasic()
+            .setPendingIntentBackgroundActivityStartMode(
+                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle();
 
     /** @hide */
     @IntDef(
@@ -138,7 +142,7 @@
             @CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<GetCandidateCredentialsResponse,
                     GetCandidateCredentialsException> callback,
-            @NonNull IAutoFillManagerClient clientCallback
+            @NonNull IBinder clientCallback
     ) {
         requireNonNull(request, "request must not be null");
         requireNonNull(callingPackage, "callingPackage must not be null");
@@ -757,9 +761,7 @@
         public void onPendingIntent(PendingIntent pendingIntent) {
             try {
                 mContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0,
-                        ActivityOptions.makeBasic()
-                            .setPendingIntentBackgroundActivityStartMode(
-                                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle());
+                        OPTIONS_SENDER_BAL_OPTIN);
             } catch (IntentSender.SendIntentException e) {
                 Log.e(
                         TAG,
@@ -817,7 +819,8 @@
         @Override
         public void onPendingIntent(PendingIntent pendingIntent) {
             try {
-                mContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+                mContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0,
+                        OPTIONS_SENDER_BAL_OPTIN);
             } catch (IntentSender.SendIntentException e) {
                 Log.e(
                         TAG,
diff --git a/core/java/android/credentials/GetCandidateCredentialsException.java b/core/java/android/credentials/GetCandidateCredentialsException.java
index 40650d0..0ac5f6c 100644
--- a/core/java/android/credentials/GetCandidateCredentialsException.java
+++ b/core/java/android/credentials/GetCandidateCredentialsException.java
@@ -46,6 +46,17 @@
             "android.credentials.GetCandidateCredentialsException.TYPE_NO_CREDENTIAL";
 
     @NonNull
+    public static final String TYPE_USER_CANCELED =
+            "android.credentials.GetCredentialException.TYPE_USER_CANCELED";
+    /**
+     * The error type value for when the given operation failed due to internal interruption.
+     * Retrying the same operation should fix the error.
+     */
+    @NonNull
+    public static final String TYPE_INTERRUPTED =
+            "android.credentials.GetCredentialException.TYPE_INTERRUPTED";
+
+    @NonNull
     private final String mType;
 
     /** Returns the specific exception type. */
diff --git a/core/java/android/credentials/GetCandidateCredentialsResponse.java b/core/java/android/credentials/GetCandidateCredentialsResponse.java
index 530fead..73361ad 100644
--- a/core/java/android/credentials/GetCandidateCredentialsResponse.java
+++ b/core/java/android/credentials/GetCandidateCredentialsResponse.java
@@ -19,7 +19,7 @@
 import android.annotation.Hide;
 import android.annotation.NonNull;
 import android.app.PendingIntent;
-import android.credentials.ui.GetCredentialProviderData;
+import android.credentials.selection.GetCredentialProviderData;
 import android.os.Parcel;
 import android.os.Parcelable;
 
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index 726bc97..d4a2d97 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -22,7 +22,6 @@
 import android.credentials.ClearCredentialStateRequest;
 import android.credentials.CreateCredentialRequest;
 import android.credentials.GetCandidateCredentialsRequest;
-import android.view.autofill.IAutoFillManagerClient;
 import android.credentials.GetCredentialRequest;
 import android.credentials.RegisterCredentialDescriptionRequest;
 import android.credentials.UnregisterCredentialDescriptionRequest;
@@ -33,6 +32,7 @@
 import android.credentials.IPrepareGetCredentialCallback;
 import android.credentials.ISetEnabledProvidersCallback;
 import android.content.ComponentName;
+import android.os.IBinder;
 import android.os.ICancellationSignal;
 
 /**
@@ -48,7 +48,7 @@
 
     @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
 
-    @nullable ICancellationSignal getCandidateCredentials(in GetCredentialRequest request, in IGetCandidateCredentialsCallback callback, in IAutoFillManagerClient clientCallback, String callingPackage);
+    @nullable ICancellationSignal getCandidateCredentials(in GetCredentialRequest request, in IGetCandidateCredentialsCallback callback, in IBinder clientCallback, String callingPackage);
 
     @nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
 
diff --git a/core/java/android/credentials/PrepareGetCredentialResponse.java b/core/java/android/credentials/PrepareGetCredentialResponse.java
index 212f571..75d671b 100644
--- a/core/java/android/credentials/PrepareGetCredentialResponse.java
+++ b/core/java/android/credentials/PrepareGetCredentialResponse.java
@@ -22,9 +22,11 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.app.ActivityOptions;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.IntentSender;
+import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.OutcomeReceiver;
 import android.util.Log;
@@ -41,6 +43,10 @@
  */
 public final class PrepareGetCredentialResponse {
 
+    private static final Bundle OPTIONS_SENDER_BAL_OPTIN = ActivityOptions.makeBasic()
+            .setPendingIntentBackgroundActivityStartMode(
+                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle();
+
     /**
      * A handle that represents a pending get-credential operation. Pass this handle to {@link
      * CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal,
@@ -80,7 +86,8 @@
                 @Override
                 public void onPendingIntent(PendingIntent pendingIntent) {
                     try {
-                        context.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+                        context.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0,
+                                OPTIONS_SENDER_BAL_OPTIN);
                     } catch (IntentSender.SendIntentException e) {
                         Log.e(TAG, "startIntentSender() failed for intent for show()", e);
                         executor.execute(() -> callback.onError(
@@ -101,7 +108,8 @@
             });
 
             try {
-                context.startIntentSender(mPendingIntent.getIntentSender(), null, 0, 0, 0);
+                context.startIntentSender(mPendingIntent.getIntentSender(), null, 0, 0, 0,
+                        OPTIONS_SENDER_BAL_OPTIN);
             } catch (IntentSender.SendIntentException e) {
                 Log.e(TAG, "startIntentSender() failed for intent for show()", e);
                 executor.execute(() -> callback.onError(
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index f876eeb..90cd471 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -33,4 +33,18 @@
     name: "new_settings_ui"
     description: "Enables new settings UI for VIC"
     bug: "315209085"
+}
+
+flag {
+    namespace: "credential_manager"
+    name: "selector_ui_improvements_enabled"
+    description: "Enables Credential Selector UI improvements for VIC"
+    bug: "319448437"
+}
+
+flag {
+    namespace: "credential_manager"
+    name: "configurable_selector_ui_enabled"
+    description: "Enables OEM configurable Credential Selector UI"
+    bug: "319448437"
 }
\ No newline at end of file
diff --git a/core/java/android/credentials/selection/AuthenticationEntry.java b/core/java/android/credentials/selection/AuthenticationEntry.java
new file mode 100644
index 0000000..54589e1
--- /dev/null
+++ b/core/java/android/credentials/selection/AuthenticationEntry.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.selection;
+
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.app.slice.Slice;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An authentication entry.
+ *
+ * Applicable only for credential retrieval flow, authentication entries are a special type of
+ * entries that require the user to unlock the given provider before its credential options can
+ * be fully rendered.
+ *
+ * @hide
+ */
+@TestApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+public final class AuthenticationEntry implements Parcelable {
+    @NonNull
+    private final String mKey;
+    @NonNull
+    private final String mSubkey;
+    @NonNull
+    private final @Status int mStatus;
+    @Nullable
+    private Intent mFrameworkExtrasIntent;
+    @NonNull
+    private final Slice mSlice;
+
+    /** @hide **/
+    @IntDef(prefix = {"STATUS_"}, value = {
+            STATUS_LOCKED,
+            STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT,
+            STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Status {
+    }
+
+    /** This entry is still locked, as initially supplied by the provider. */
+    public static final int STATUS_LOCKED = 0;
+    /**
+     * This entry was unlocked but didn't contain any credential. Meanwhile, "less recent" means
+     * there is another such entry that was unlocked more recently.
+     */
+    public static final int STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT = 1;
+    /**
+     * This is the most recent entry that was unlocked but didn't contain any credential.
+     *
+     * There will be at most one authentication entry with this status.
+     */
+    public static final int STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT = 2;
+
+    private AuthenticationEntry(@NonNull Parcel in) {
+        mKey = in.readString8();
+        mSubkey = in.readString8();
+        mStatus = in.readInt();
+        mSlice = in.readTypedObject(Slice.CREATOR);
+        mFrameworkExtrasIntent = in.readTypedObject(Intent.CREATOR);
+
+        AnnotationValidations.validate(NonNull.class, null, mKey);
+        AnnotationValidations.validate(NonNull.class, null, mSubkey);
+        AnnotationValidations.validate(NonNull.class, null, mSlice);
+    }
+
+    /**
+     * Constructor to be used for an entry that does not require further activities
+     * to be invoked when selected.
+     */
+    // TODO(b/322065508): remove this constructor.
+    public AuthenticationEntry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
+            @Status int status) {
+        mKey = key;
+        mSubkey = subkey;
+        mSlice = slice;
+        mStatus = status;
+    }
+
+    /** Constructor to be used for an entry that requires a pending intent to be invoked
+     * when clicked.
+     */
+    public AuthenticationEntry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
+            @Status int status, @NonNull Intent intent) {
+        this(key, subkey, slice, status);
+        mFrameworkExtrasIntent = intent;
+    }
+
+    /**
+     * Returns the identifier of this entry that's unique within the context of the
+     * CredentialManager request.
+     */
+    @NonNull
+    public String getKey() {
+        return mKey;
+    }
+
+    /**
+     * Returns the sub-identifier of this entry that's unique within the context of the {@code key}.
+     */
+    @NonNull
+    public String getSubkey() {
+        return mSubkey;
+    }
+
+    /** Returns the Slice to be rendered. */
+    @NonNull
+    public Slice getSlice() {
+        return mSlice;
+    }
+
+    /** Returns the entry status, depending on which the entry will be rendered differently. */
+    @NonNull
+    @Status
+    public int getStatus() {
+        return mStatus;
+    }
+
+    /**
+     * Returns the framework intent to be filled in when launching this entry's provider
+     * PendingIntent.
+     */
+    @Nullable
+    @SuppressLint("IntentBuilderName") // Not building a new intent.
+    public Intent getFrameworkExtrasIntent() {
+        return mFrameworkExtrasIntent;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mKey);
+        dest.writeString8(mSubkey);
+        dest.writeInt(mStatus);
+        dest.writeTypedObject(mSlice, flags);
+        dest.writeTypedObject(mFrameworkExtrasIntent, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<AuthenticationEntry> CREATOR = new Creator<>() {
+        @Override
+        public AuthenticationEntry createFromParcel(@NonNull Parcel in) {
+            return new AuthenticationEntry(in);
+        }
+
+        @Override
+        public AuthenticationEntry[] newArray(int size) {
+            return new AuthenticationEntry[size];
+        }
+    };
+}
diff --git a/core/java/android/credentials/selection/BaseDialogResult.java b/core/java/android/credentials/selection/BaseDialogResult.java
new file mode 100644
index 0000000..d4a73c3
--- /dev/null
+++ b/core/java/android/credentials/selection/BaseDialogResult.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.selection;
+
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Base dialog result data.
+ *
+ * Returned for simple use cases like cancellation. Can also be subclassed when more information
+ * is needed, e.g. {@link UserSelectionDialogResult}.
+ *
+ * @hide
+ */
+@TestApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+@SuppressLint("ParcelNotFinal") // Test API only. This is never intended to be officially exposed.
+// Instead proper final wrapper classes are defined (e.g. {@code FailureDialogResult}).
+public class BaseDialogResult implements Parcelable {
+    /** Parses and returns a BaseDialogResult from the given resultData. */
+    @Nullable
+    public static BaseDialogResult fromResultData(@NonNull Bundle resultData) {
+        return resultData.getParcelable(EXTRA_BASE_RESULT, BaseDialogResult.class);
+    }
+
+    /**
+     * Used for the UX to construct the {@code resultData Bundle} to send via the {@code
+     * ResultReceiver}.
+     */
+    public static void addToBundle(@NonNull BaseDialogResult result, @NonNull Bundle bundle) {
+        bundle.putParcelable(EXTRA_BASE_RESULT, result);
+    }
+
+    /**
+     * The intent extra key for the {@code BaseDialogResult} object when the credential
+     * selector activity finishes.
+     */
+    private static final String EXTRA_BASE_RESULT =
+            "android.credentials.selection.extra.BASE_RESULT";
+
+    /** @hide **/
+    @IntDef(prefix = {"RESULT_CODE_"}, value = {
+            RESULT_CODE_DIALOG_USER_CANCELED,
+            RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS,
+            RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION,
+            RESULT_CODE_DATA_PARSING_FAILURE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ResultCode {
+    }
+
+    /** User intentionally canceled the dialog. */
+    public static final int RESULT_CODE_DIALOG_USER_CANCELED = 0;
+    /**
+     * The UI was stopped since the user has chosen to navigate to the Settings UI to reconfigure
+     * their providers.
+     */
+    public static final int RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS = 1;
+    /**
+     * User made a selection and the dialog finished. The user selection result is in the
+     * {@code resultData}.
+     */
+    public static final int RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION = 2;
+    /**
+     * The UI was canceled because it failed to parse the incoming data.
+     */
+    public static final int RESULT_CODE_DATA_PARSING_FAILURE = 3;
+
+    @Nullable
+    @Deprecated
+    private final IBinder mRequestToken;
+
+    public BaseDialogResult(@Nullable IBinder requestToken) {
+        mRequestToken = requestToken;
+    }
+
+    /**
+     * Returns the unique identifier for the request that launched the operation.
+     *
+     * @deprecated do not use
+     */
+    @Nullable
+    @Deprecated
+    public IBinder getRequestToken() {
+        return mRequestToken;
+    }
+
+    @SuppressLint("ParcelConstructor") // Test API only. This is never intended to be officially
+    // exposed. Instead proper final wrapper classes are defined (e.g. {@code FailureDialogResult}).
+    protected BaseDialogResult(@NonNull Parcel in) {
+        IBinder requestToken = in.readStrongBinder();
+        mRequestToken = requestToken;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStrongBinder(mRequestToken);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<BaseDialogResult> CREATOR =
+            new Creator<BaseDialogResult>() {
+                @Override
+                public BaseDialogResult createFromParcel(@NonNull Parcel in) {
+                    return new BaseDialogResult(in);
+                }
+
+                @Override
+                public BaseDialogResult[] newArray(int size) {
+                    return new BaseDialogResult[size];
+                }
+            };
+}
diff --git a/core/java/android/credentials/selection/CancelUiRequest.java b/core/java/android/credentials/selection/CancelUiRequest.java
new file mode 100644
index 0000000..fca0e2a
--- /dev/null
+++ b/core/java/android/credentials/selection/CancelUiRequest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.selection;
+
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * A request to cancel the ongoing UI matching the identifier token in this request.
+ *
+ * @hide
+ */
+public final class CancelUiRequest implements Parcelable {
+
+    /**
+     * The intent extra key for the {@code CancelUiRequest} object when launching the UX
+     * activities.
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String EXTRA_CANCEL_UI_REQUEST =
+            "android.credentials.selection.extra.CANCEL_UI_REQUEST";
+
+    @NonNull
+    private final IBinder mToken;
+
+    private final boolean mShouldShowCancellationUi;
+
+    @NonNull
+    private final String mAppPackageName;
+
+    /** Returns the request token matching the user request that should be cancelled. */
+    @NonNull
+    public IBinder getToken() {
+        return mToken;
+    }
+
+    /**
+     * Returns the app package name invoking this request, that can be used to derive display
+     * metadata (e.g. "Cancelled by `App Name`").
+     */
+    @NonNull
+    public String getAppPackageName() {
+        return mAppPackageName;
+    }
+
+    /**
+     * Returns whether the UI should render a cancellation UI upon the request. If false, the UI
+     * will be silently cancelled.
+     */
+    public boolean shouldShowCancellationUi() {
+        return mShouldShowCancellationUi;
+    }
+
+    /** Constructs a {@link CancelUiRequest}. */
+    public CancelUiRequest(@NonNull IBinder token, boolean shouldShowCancellationUi,
+            @NonNull String appPackageName) {
+        mToken = token;
+        mShouldShowCancellationUi = shouldShowCancellationUi;
+        mAppPackageName = appPackageName;
+    }
+
+    private CancelUiRequest(@NonNull Parcel in) {
+        mToken = in.readStrongBinder();
+        AnnotationValidations.validate(NonNull.class, null, mToken);
+        mShouldShowCancellationUi = in.readBoolean();
+        mAppPackageName = in.readString8();
+        AnnotationValidations.validate(NonNull.class, null, mAppPackageName);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStrongBinder(mToken);
+        dest.writeBoolean(mShouldShowCancellationUi);
+        dest.writeString8(mAppPackageName);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Creator<CancelUiRequest> CREATOR = new Creator<>() {
+        @Override
+        public CancelUiRequest createFromParcel(@NonNull Parcel in) {
+            return new CancelUiRequest(in);
+        }
+
+        @Override
+        public CancelUiRequest[] newArray(int size) {
+            return new CancelUiRequest[size];
+        }
+    };
+}
diff --git a/core/java/android/credentials/selection/Constants.java b/core/java/android/credentials/selection/Constants.java
new file mode 100644
index 0000000..7e6c781
--- /dev/null
+++ b/core/java/android/credentials/selection/Constants.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.selection;
+
+/**
+ * Constants for the ui protocol that doesn't fit into other individual data structures.
+ *
+ * @hide
+ */
+public class Constants {
+
+    /**
+     * The intent extra key for the {@code ResultReceiver} object when launching the UX activities.
+     */
+    public static final String EXTRA_RESULT_RECEIVER =
+            "android.credentials.selection.extra.RESULT_RECEIVER";
+
+    /**
+     * The intent extra key for indicating whether the bottom sheet should be started directly
+     * on the 'All Options' screen.
+     */
+    public static final String EXTRA_REQ_FOR_ALL_OPTIONS =
+            "android.credentials.selection.extra.REQ_FOR_ALL_OPTIONS";
+
+    private Constants() {}
+}
diff --git a/core/java/android/credentials/selection/CreateCredentialProviderData.java b/core/java/android/credentials/selection/CreateCredentialProviderData.java
new file mode 100644
index 0000000..fc80ea8
--- /dev/null
+++ b/core/java/android/credentials/selection/CreateCredentialProviderData.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.selection;
+
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Per-provider metadata and entries for the create-credential flow.
+ *
+ * @hide
+ */
+@TestApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+public final class CreateCredentialProviderData extends ProviderData implements Parcelable {
+    @NonNull
+    private final List<Entry> mSaveEntries;
+    @Nullable
+    private final Entry mRemoteEntry;
+
+    public CreateCredentialProviderData(
+            @NonNull String providerFlattenedComponentName, @NonNull List<Entry> saveEntries,
+            @Nullable Entry remoteEntry) {
+        super(providerFlattenedComponentName);
+        mSaveEntries = new ArrayList<>(saveEntries);
+        mRemoteEntry = remoteEntry;
+    }
+
+    /**
+     * Converts the instance to a {@link CreateCredentialProviderInfo}.
+     *
+     * @hide
+     */
+    @NonNull
+    public CreateCredentialProviderInfo toCreateCredentialProviderInfo() {
+        return new CreateCredentialProviderInfo(
+                getProviderFlattenedComponentName(), mSaveEntries, mRemoteEntry);
+    }
+
+    @NonNull
+    public List<Entry> getSaveEntries() {
+        return mSaveEntries;
+    }
+
+    @Nullable
+    public Entry getRemoteEntry() {
+        return mRemoteEntry;
+    }
+
+    private CreateCredentialProviderData(@NonNull Parcel in) {
+        super(in);
+
+        List<Entry> credentialEntries = new ArrayList<>();
+        in.readTypedList(credentialEntries, Entry.CREATOR);
+        mSaveEntries = credentialEntries;
+        AnnotationValidations.validate(NonNull.class, null, mSaveEntries);
+
+        Entry remoteEntry = in.readTypedObject(Entry.CREATOR);
+        mRemoteEntry = remoteEntry;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeTypedList(mSaveEntries);
+        dest.writeTypedObject(mRemoteEntry, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Creator<CreateCredentialProviderData> CREATOR =
+            new Creator<>() {
+                @Override
+                public CreateCredentialProviderData createFromParcel(@NonNull Parcel in) {
+                    return new CreateCredentialProviderData(in);
+                }
+
+                @Override
+                public CreateCredentialProviderData[] newArray(int size) {
+                    return new CreateCredentialProviderData[size];
+                }
+            };
+
+    /**
+     * Builder for {@link CreateCredentialProviderData}.
+     *
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+    public static final class Builder {
+        @NonNull private String mProviderFlattenedComponentName;
+        @NonNull private List<Entry> mSaveEntries = new ArrayList<>();
+        @Nullable private Entry mRemoteEntry = null;
+
+        /** Constructor with required properties. */
+        public Builder(@NonNull String providerFlattenedComponentName) {
+            mProviderFlattenedComponentName = providerFlattenedComponentName;
+        }
+
+        /** Sets the list of save credential entries to be displayed to the user. */
+        @NonNull
+        public Builder setSaveEntries(@NonNull List<Entry> credentialEntries) {
+            mSaveEntries = credentialEntries;
+            return this;
+        }
+
+        /** Sets the remote entry of the provider. */
+        @NonNull
+        public Builder setRemoteEntry(@Nullable Entry remoteEntry) {
+            mRemoteEntry = remoteEntry;
+            return this;
+        }
+
+        /** Builds a {@link CreateCredentialProviderData}. */
+        @NonNull
+        public CreateCredentialProviderData build() {
+            return new CreateCredentialProviderData(mProviderFlattenedComponentName,
+                    mSaveEntries, mRemoteEntry);
+        }
+    }
+}
diff --git a/core/java/android/credentials/selection/CreateCredentialProviderInfo.java b/core/java/android/credentials/selection/CreateCredentialProviderInfo.java
new file mode 100644
index 0000000..78b9fd44
--- /dev/null
+++ b/core/java/android/credentials/selection/CreateCredentialProviderInfo.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.selection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Information pertaining to a specific provider during the given create-credential flow.
+ *
+ * This includes provider metadata and its credential creation options for display purposes.
+ *
+ * @hide
+ */
+public final class CreateCredentialProviderInfo {
+
+    @NonNull
+    private final String mProviderName;
+
+    @NonNull
+    private final List<Entry> mSaveEntries;
+    @Nullable
+    private final Entry mRemoteEntry;
+
+    CreateCredentialProviderInfo(
+            @NonNull String providerName, @NonNull List<Entry> saveEntries,
+            @Nullable Entry remoteEntry) {
+        mProviderName = Preconditions.checkStringNotEmpty(providerName);
+        mSaveEntries = new ArrayList<>(saveEntries);
+        mRemoteEntry = remoteEntry;
+    }
+
+    /** Returns the fully-qualified provider (component or package) name. */
+    @NonNull
+    public String getProviderName() {
+        return mProviderName;
+    }
+
+    /** Returns all the options this provider has, to which the credential can be saved. */
+    @NonNull
+    public List<Entry> getSaveEntries() {
+        return mSaveEntries;
+    }
+
+    /**
+     * Returns the remote credential saving option, if any.
+     *
+     * Notice that only one system configured provider can set this option, and when set, it means
+     * that the system service has already validated the provider's eligibility.
+     */
+    @Nullable
+    public Entry getRemoteEntry() {
+        return mRemoteEntry;
+    }
+
+    /**
+     * Builder for {@link CreateCredentialProviderInfo}.
+     *
+     * @hide
+     */
+    public static final class Builder {
+        @NonNull
+        private String mProviderName;
+        @NonNull
+        private List<Entry> mSaveEntries = new ArrayList<>();
+        @Nullable
+        private Entry mRemoteEntry = null;
+
+        /** Constructor with required properties. */
+        public Builder(@NonNull String providerName) {
+            mProviderName = Preconditions.checkStringNotEmpty(providerName);
+        }
+
+        /** Sets the list of options for credential saving to be displayed to the user. */
+        @NonNull
+        public Builder setSaveEntries(@NonNull List<Entry> credentialEntries) {
+            mSaveEntries = credentialEntries;
+            return this;
+        }
+
+        /** Sets the remote entry of the provider. */
+        @NonNull
+        public Builder setRemoteEntry(@Nullable Entry remoteEntry) {
+            mRemoteEntry = remoteEntry;
+            return this;
+        }
+
+        /** Builds a {@link CreateCredentialProviderInfo}. */
+        @NonNull
+        public CreateCredentialProviderInfo build() {
+            return new CreateCredentialProviderInfo(mProviderName, mSaveEntries, mRemoteEntry);
+        }
+    }
+}
diff --git a/core/java/android/credentials/selection/DisabledProviderData.java b/core/java/android/credentials/selection/DisabledProviderData.java
new file mode 100644
index 0000000..b6f6ad4
--- /dev/null
+++ b/core/java/android/credentials/selection/DisabledProviderData.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.selection;
+
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Metadata of a disabled provider.
+ *
+ * @hide
+ */
+@TestApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+public final class DisabledProviderData extends ProviderData implements Parcelable {
+
+    public DisabledProviderData(
+            @NonNull String providerFlattenedComponentName) {
+        super(providerFlattenedComponentName);
+    }
+
+    /**
+     * Converts the instance to a {@link DisabledProviderInfo}.
+     *
+     * @hide
+     */
+    @NonNull
+    public DisabledProviderInfo toDisabledProviderInfo() {
+        return new DisabledProviderInfo(getProviderFlattenedComponentName());
+    }
+
+    private DisabledProviderData(@NonNull Parcel in) {
+        super(in);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<DisabledProviderData> CREATOR = new Creator<>() {
+                @Override
+                public DisabledProviderData createFromParcel(@NonNull Parcel in) {
+                    return new DisabledProviderData(in);
+                }
+
+                @Override
+                public DisabledProviderData[] newArray(int size) {
+                    return new DisabledProviderData[size];
+                }
+    };
+}
diff --git a/core/java/android/credentials/selection/DisabledProviderInfo.java b/core/java/android/credentials/selection/DisabledProviderInfo.java
new file mode 100644
index 0000000..7d7dbc2
--- /dev/null
+++ b/core/java/android/credentials/selection/DisabledProviderInfo.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.selection;
+
+import android.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Information pertaining to a specific provider that is disabled from the user settings.
+ *
+ * Currently, disabled provider data is only propagated in the create-credential flow.
+ *
+ * @hide
+ */
+public final class DisabledProviderInfo {
+
+    @NonNull
+    private final String mProviderName;
+
+    /**
+     * Constructs a {@link DisabledProviderInfo}.
+     *
+     * @throws IllegalArgumentException if {@code providerName} is empty
+     */
+    public DisabledProviderInfo(
+            @NonNull String providerName) {
+        mProviderName = Preconditions.checkStringNotEmpty(providerName);
+    }
+
+    /** Returns the fully-qualified provider (component or package) name. */
+    @NonNull
+    public String getProviderName() {
+        return mProviderName;
+    }
+}
diff --git a/core/java/android/credentials/selection/Entry.java b/core/java/android/credentials/selection/Entry.java
new file mode 100644
index 0000000..bcf4ee3
--- /dev/null
+++ b/core/java/android/credentials/selection/Entry.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.selection;
+
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.app.PendingIntent;
+import android.app.slice.Slice;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * A credential, create, or action entry to be rendered.
+ *
+ * @hide
+ */
+@TestApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+public final class Entry implements Parcelable {
+    @NonNull
+    private final String mKey;
+    @NonNull
+    private final String mSubkey;
+    @Nullable
+    private PendingIntent mPendingIntent;
+    @Nullable
+    private Intent mFrameworkExtrasIntent;
+
+    @NonNull
+    private final Slice mSlice;
+
+    private Entry(@NonNull Parcel in) {
+        String key = in.readString8();
+        String subkey = in.readString8();
+        Slice slice = in.readTypedObject(Slice.CREATOR);
+
+        mKey = key;
+        AnnotationValidations.validate(NonNull.class, null, mKey);
+        mSubkey = subkey;
+        AnnotationValidations.validate(NonNull.class, null, mSubkey);
+        mSlice = slice;
+        AnnotationValidations.validate(NonNull.class, null, mSlice);
+        mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+        mFrameworkExtrasIntent = in.readTypedObject(Intent.CREATOR);
+    }
+
+    /**
+     * Constructor to be used for an entry that does not require further activities
+     * to be invoked when selected.
+     */
+    // TODO(b/322065508): deprecate this constructor.
+    public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice) {
+        mKey = key;
+        mSubkey = subkey;
+        mSlice = slice;
+    }
+
+    /**
+     * Constructor to be used for an entry that requires a pending intent to be invoked
+     * when clicked.
+     */
+    public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
+            @NonNull Intent intent) {
+        this(key, subkey, slice);
+        mFrameworkExtrasIntent = intent;
+    }
+
+    /**
+     * Returns the identifier of this entry that's unique within the context of the
+     * CredentialManager
+     * request.
+     *
+     * Generally used when sending the user selection result back to the system service.
+     */
+    @NonNull
+    public String getKey() {
+        return mKey;
+    }
+
+    /**
+     * Returns the sub-identifier of this entry that's unique within the context of the {@code key}.
+     *
+     * Generally used when sending the user selection result back to the system service.
+     */
+    @NonNull
+    public String getSubkey() {
+        return mSubkey;
+    }
+
+    /** Returns the Slice to be rendered. */
+    @NonNull
+    public Slice getSlice() {
+        return mSlice;
+    }
+
+    /**
+     * Returns the provider PendingIntent to launch once this entry is selected.
+     */
+    // TODO(b/322065508): deprecate this bit.
+    @Nullable
+    public PendingIntent getPendingIntent() {
+        return mPendingIntent;
+    }
+
+    /**
+     * Returns the framework fill in intent to add to the provider PendingIntent to launch, once
+     * this entry is selected.
+     */
+    @Nullable
+    @SuppressLint("IntentBuilderName") // Not building a new intent.
+    public Intent getFrameworkExtrasIntent() {
+        return mFrameworkExtrasIntent;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mKey);
+        dest.writeString8(mSubkey);
+        dest.writeTypedObject(mSlice, flags);
+        dest.writeTypedObject(mPendingIntent, flags);
+        dest.writeTypedObject(mFrameworkExtrasIntent, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<Entry> CREATOR = new Creator<>() {
+        @Override
+        public Entry createFromParcel(@NonNull Parcel in) {
+            return new Entry(in);
+        }
+
+        @Override
+        public Entry[] newArray(int size) {
+            return new Entry[size];
+        }
+    };
+}
diff --git a/core/java/android/credentials/selection/FailureDialogResult.java b/core/java/android/credentials/selection/FailureDialogResult.java
new file mode 100644
index 0000000..218aa46
--- /dev/null
+++ b/core/java/android/credentials/selection/FailureDialogResult.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.selection;
+
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Result data when the selector UI has encountered a failure.
+ *
+ * @hide
+ */
+@TestApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+public final class FailureDialogResult extends BaseDialogResult implements Parcelable {
+    /** Parses and returns a UserSelectionDialogResult from the given resultData. */
+    @Nullable
+    public static FailureDialogResult fromResultData(@NonNull Bundle resultData) {
+        return resultData.getParcelable(
+                EXTRA_FAILURE_RESULT, FailureDialogResult.class);
+    }
+
+    /**
+     * Used for the UX to construct the {@code resultData Bundle} to send via the {@code
+     * ResultReceiver}.
+     */
+    public static void addToBundle(
+            @NonNull FailureDialogResult result, @NonNull Bundle bundle) {
+        bundle.putParcelable(EXTRA_FAILURE_RESULT, result);
+    }
+
+    /**
+     * The intent extra key for the {@code UserSelectionDialogResult} object when the credential
+     * selector activity finishes.
+     */
+    private static final String EXTRA_FAILURE_RESULT =
+            "android.credentials.selection.extra.FAILURE_RESULT";
+
+    @Nullable
+    private final String mErrorMessage;
+
+    public FailureDialogResult(@Nullable IBinder requestToken, @Nullable String errorMessage) {
+        super(requestToken);
+        mErrorMessage = errorMessage;
+    }
+
+    /** Returns provider package name whose entry was selected by the user. */
+    @Nullable
+    public String getErrorMessage() {
+        return mErrorMessage;
+    }
+
+    private FailureDialogResult(@NonNull Parcel in) {
+        super(in);
+        mErrorMessage = in.readString8();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeString8(mErrorMessage);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<FailureDialogResult> CREATOR =
+            new Creator<>() {
+                @Override
+                public FailureDialogResult createFromParcel(@NonNull Parcel in) {
+                    return new FailureDialogResult(in);
+                }
+
+                @Override
+                public FailureDialogResult[] newArray(int size) {
+                    return new FailureDialogResult[size];
+                }
+            };
+}
diff --git a/core/java/android/credentials/selection/FailureResult.java b/core/java/android/credentials/selection/FailureResult.java
new file mode 100644
index 0000000..93ba671
--- /dev/null
+++ b/core/java/android/credentials/selection/FailureResult.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.selection;
+
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Failure or cancellation result encountered during a UI flow.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+public final class FailureResult {
+    @Nullable
+    private final String mErrorMessage;
+    @NonNull
+    private final int mErrorCode;
+
+    /** @hide **/
+    @IntDef(prefix = {"ERROR_CODE_"}, value = {
+            ERROR_CODE_DIALOG_CANCELED_BY_USER,
+            ERROR_CODE_CANCELED_AND_LAUNCHED_SETTINGS,
+            ERROR_CODE_UI_FAILURE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ErrorCode {
+    }
+
+    /**
+     * The UI was stopped due to a failure, e.g. because it failed to parse the incoming data,
+     * or it encountered an irrecoverable internal issue.
+     */
+    public static final int ERROR_CODE_UI_FAILURE = 0;
+    /** The user intentionally canceled the dialog. */
+    public static final int ERROR_CODE_DIALOG_CANCELED_BY_USER = 1;
+    /**
+     * The UI was stopped since the user has chosen to navigate to the Settings UI to reconfigure
+     * their providers.
+     */
+    public static final int ERROR_CODE_CANCELED_AND_LAUNCHED_SETTINGS = 2;
+
+    /**
+     * Constructs a {@link FailureResult}.
+     *
+     * @throws IllegalArgumentException if {@code providerId} is empty
+     */
+    public FailureResult(@ErrorCode int errorCode, @Nullable String errorMessage) {
+        mErrorCode = errorCode;
+        mErrorMessage = errorMessage;
+    }
+
+    /** Returns the error code. */
+    @ErrorCode
+    public int getErrorCode() {
+        return mErrorCode;
+    }
+
+    /** Returns the error message. */
+    @Nullable
+    public String getErrorMessage() {
+        return mErrorMessage;
+    }
+
+    FailureDialogResult toFailureDialogResult() {
+        return new FailureDialogResult(/*requestToken=*/null, mErrorMessage);
+    }
+
+    int errorCodeToResultCode() {
+        switch (mErrorCode) {
+            case ERROR_CODE_DIALOG_CANCELED_BY_USER:
+                return BaseDialogResult.RESULT_CODE_DIALOG_USER_CANCELED;
+            case ERROR_CODE_CANCELED_AND_LAUNCHED_SETTINGS:
+                return BaseDialogResult.RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS;
+            default:
+                return BaseDialogResult.RESULT_CODE_DATA_PARSING_FAILURE;
+        }
+    }
+}
diff --git a/core/java/android/credentials/selection/GetCredentialProviderData.java b/core/java/android/credentials/selection/GetCredentialProviderData.java
new file mode 100644
index 0000000..2d09f60
--- /dev/null
+++ b/core/java/android/credentials/selection/GetCredentialProviderData.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.selection;
+
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Per-provider metadata and entries for the get-credential flow.
+ *
+ * @hide
+ */
+@TestApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+public final class GetCredentialProviderData extends ProviderData implements Parcelable {
+    @NonNull
+    private final List<Entry> mCredentialEntries;
+    @NonNull
+    private final List<Entry> mActionChips;
+    @NonNull
+    private final List<AuthenticationEntry> mAuthenticationEntries;
+    @Nullable
+    private final Entry mRemoteEntry;
+
+    public GetCredentialProviderData(
+            @NonNull String providerFlattenedComponentName, @NonNull List<Entry> credentialEntries,
+            @NonNull List<Entry> actionChips,
+            @NonNull List<AuthenticationEntry> authenticationEntries,
+            @Nullable Entry remoteEntry) {
+        super(providerFlattenedComponentName);
+        mCredentialEntries = new ArrayList<>(credentialEntries);
+        mActionChips = new ArrayList<>(actionChips);
+        mAuthenticationEntries = new ArrayList<>(authenticationEntries);
+        mRemoteEntry = remoteEntry;
+    }
+
+    /**
+     * Converts the instance to a {@link GetCredentialProviderInfo}.
+     *
+     * @hide
+     */
+    @NonNull
+    public GetCredentialProviderInfo toGetCredentialProviderInfo() {
+        return new GetCredentialProviderInfo(getProviderFlattenedComponentName(),
+                mCredentialEntries, mActionChips, mAuthenticationEntries, mRemoteEntry);
+    }
+
+    @NonNull
+    public List<Entry> getCredentialEntries() {
+        return mCredentialEntries;
+    }
+
+    @NonNull
+    public List<Entry> getActionChips() {
+        return mActionChips;
+    }
+
+    @NonNull
+    public List<AuthenticationEntry> getAuthenticationEntries() {
+        return mAuthenticationEntries;
+    }
+
+    @Nullable
+    public Entry getRemoteEntry() {
+        return mRemoteEntry;
+    }
+
+    private GetCredentialProviderData(@NonNull Parcel in) {
+        super(in);
+
+        List<Entry> credentialEntries = new ArrayList<>();
+        in.readTypedList(credentialEntries, Entry.CREATOR);
+        mCredentialEntries = credentialEntries;
+        AnnotationValidations.validate(NonNull.class, null, mCredentialEntries);
+
+        List<Entry> actionChips = new ArrayList<>();
+        in.readTypedList(actionChips, Entry.CREATOR);
+        mActionChips = actionChips;
+        AnnotationValidations.validate(NonNull.class, null, mActionChips);
+
+        List<AuthenticationEntry> authenticationEntries = new ArrayList<>();
+        in.readTypedList(authenticationEntries, AuthenticationEntry.CREATOR);
+        mAuthenticationEntries = authenticationEntries;
+        AnnotationValidations.validate(NonNull.class, null, mAuthenticationEntries);
+
+        Entry remoteEntry = in.readTypedObject(Entry.CREATOR);
+        mRemoteEntry = remoteEntry;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeTypedList(mCredentialEntries);
+        dest.writeTypedList(mActionChips);
+        dest.writeTypedList(mAuthenticationEntries);
+        dest.writeTypedObject(mRemoteEntry, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<GetCredentialProviderData> CREATOR =
+            new Creator<GetCredentialProviderData>() {
+                @Override
+                public GetCredentialProviderData createFromParcel(@NonNull Parcel in) {
+                    return new GetCredentialProviderData(in);
+                }
+
+                @Override
+                public GetCredentialProviderData[] newArray(int size) {
+                    return new GetCredentialProviderData[size];
+                }
+            };
+
+    /**
+     * Builder for {@link GetCredentialProviderData}.
+     *
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+    public static final class Builder {
+        @NonNull
+        private String mProviderFlattenedComponentName;
+        @NonNull
+        private List<Entry> mCredentialEntries = new ArrayList<>();
+        @NonNull
+        private List<Entry> mActionChips = new ArrayList<>();
+        @NonNull
+        private List<AuthenticationEntry> mAuthenticationEntries = new ArrayList<>();
+        @Nullable
+        private Entry mRemoteEntry = null;
+
+        /** Constructor with required properties. */
+        public Builder(@NonNull String providerFlattenedComponentName) {
+            mProviderFlattenedComponentName = providerFlattenedComponentName;
+        }
+
+        /** Sets the list of save / get credential entries to be displayed to the user. */
+        @NonNull
+        public Builder setCredentialEntries(@NonNull List<Entry> credentialEntries) {
+            mCredentialEntries = credentialEntries;
+            return this;
+        }
+
+        /** Sets the list of action chips to be displayed to the user. */
+        @NonNull
+        public Builder setActionChips(@NonNull List<Entry> actionChips) {
+            mActionChips = actionChips;
+            return this;
+        }
+
+        /** Sets the authentication entry to be displayed to the user. */
+        @NonNull
+        public Builder setAuthenticationEntries(
+                @NonNull List<AuthenticationEntry> authenticationEntry) {
+            mAuthenticationEntries = authenticationEntry;
+            return this;
+        }
+
+        /** Sets the remote entry to be displayed to the user. */
+        @NonNull
+        public Builder setRemoteEntry(@Nullable Entry remoteEntry) {
+            mRemoteEntry = remoteEntry;
+            return this;
+        }
+
+        /** Builds a {@link GetCredentialProviderData}. */
+        @NonNull
+        public GetCredentialProviderData build() {
+            return new GetCredentialProviderData(mProviderFlattenedComponentName,
+                    mCredentialEntries, mActionChips, mAuthenticationEntries, mRemoteEntry);
+        }
+    }
+}
diff --git a/core/java/android/credentials/selection/GetCredentialProviderInfo.java b/core/java/android/credentials/selection/GetCredentialProviderInfo.java
new file mode 100644
index 0000000..db0fb84
--- /dev/null
+++ b/core/java/android/credentials/selection/GetCredentialProviderInfo.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.selection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Information pertaining to a specific provider during the given create-credential flow.
+ *
+ * This includes provider metadata and its credential creation options for display purposes.
+ *
+ * @hide
+ */
+public final class GetCredentialProviderInfo {
+
+    @NonNull
+    private final String mProviderName;
+
+    @NonNull
+    private final List<Entry> mCredentialEntries;
+    @NonNull
+    private final List<Entry> mActionChips;
+    @NonNull
+    private final List<AuthenticationEntry> mAuthenticationEntries;
+    @Nullable
+    private final Entry mRemoteEntry;
+
+    GetCredentialProviderInfo(
+            @NonNull String providerName, @NonNull List<Entry> credentialEntries,
+            @NonNull List<Entry> actionChips,
+            @NonNull List<AuthenticationEntry> authenticationEntries,
+            @Nullable Entry remoteEntry) {
+        mProviderName = Preconditions.checkStringNotEmpty(providerName);
+        mCredentialEntries = new ArrayList<>(credentialEntries);
+        mActionChips = new ArrayList<>(actionChips);
+        mAuthenticationEntries = new ArrayList<>(authenticationEntries);
+        mRemoteEntry = remoteEntry;
+    }
+
+    /** Returns the fully-qualified provider (component or package) name. */
+    @NonNull
+    public String getProviderName() {
+        return mProviderName;
+    }
+
+    /** Returns the display information for all the candidate credentials this provider has. */
+    @NonNull
+    public List<Entry> getCredentialEntries() {
+        return mCredentialEntries;
+    }
+
+    /**
+     * Returns a list of actions defined by the provider that intent into the provider's app for
+     * specific user actions, each of which should eventually lead to an actual credential.
+     */
+    @NonNull
+    public List<Entry> getActionChips() {
+        return mActionChips;
+    }
+
+    /**
+     * Returns a list of authentication actions that each intents into a provider authentication
+     * activity.
+     *
+     * When the authentication activity succeeds, the provider will return a list of actual
+     * credential candidates to render. However, the UI should not attempt to parse the result
+     * itself, but rather send the result back to the system service, which will then process the
+     * new candidates and relaunch the UI with updated display data.
+     */
+    @NonNull
+    public List<AuthenticationEntry> getAuthenticationEntries() {
+        return mAuthenticationEntries;
+    }
+
+    /**
+     * Returns the remote credential retrieval option, if any.
+     *
+     * Notice that only one system configured provider can set this option, and when set, it means
+     * that the system service has already validated the provider's eligibility.
+     */
+    @Nullable
+    public Entry getRemoteEntry() {
+        return mRemoteEntry;
+    }
+
+    /**
+     * Builder for {@link GetCredentialProviderInfo}.
+     *
+     * @hide
+     */
+    public static final class Builder {
+        @NonNull
+        private String mProviderName;
+        @NonNull
+        private List<Entry> mCredentialEntries = new ArrayList<>();
+        @NonNull
+        private List<Entry> mActionChips = new ArrayList<>();
+        @NonNull
+        private List<AuthenticationEntry> mAuthenticationEntries = new ArrayList<>();
+        @Nullable
+        private Entry mRemoteEntry = null;
+
+        /**
+         * Constructs a {@link GetCredentialProviderInfo.Builder}.
+         *
+         * @throws IllegalArgumentException if {@code providerName} is null or empty
+         */
+        public Builder(@NonNull String providerName) {
+            mProviderName = Preconditions.checkStringNotEmpty(providerName);
+        }
+
+        /** Sets the list of credential candidates to be displayed to the user. */
+        @NonNull
+        public Builder setCredentialEntries(@NonNull List<Entry> credentialEntries) {
+            mCredentialEntries = credentialEntries;
+            return this;
+        }
+
+        /** Sets the list of action chips to be displayed to the user. */
+        @NonNull
+        public Builder setActionChips(@NonNull List<Entry> actionChips) {
+            mActionChips = actionChips;
+            return this;
+        }
+
+        /** Sets the authentication entry to be displayed to the user. */
+        @NonNull
+        public Builder setAuthenticationEntries(
+                @NonNull List<AuthenticationEntry> authenticationEntry) {
+            mAuthenticationEntries = authenticationEntry;
+            return this;
+        }
+
+        /** Sets the remote entry to be displayed to the user. */
+        @NonNull
+        public Builder setRemoteEntry(@Nullable Entry remoteEntry) {
+            mRemoteEntry = remoteEntry;
+            return this;
+        }
+
+        /** Builds a {@link GetCredentialProviderInfo}. */
+        @NonNull
+        public GetCredentialProviderInfo build() {
+            return new GetCredentialProviderInfo(mProviderName,
+                    mCredentialEntries, mActionChips, mAuthenticationEntries, mRemoteEntry);
+        }
+    }
+}
diff --git a/core/java/android/credentials/selection/IntentFactory.java b/core/java/android/credentials/selection/IntentFactory.java
new file mode 100644
index 0000000..c3a09ae
--- /dev/null
+++ b/core/java/android/credentials/selection/IntentFactory.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.selection;
+
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.ResultReceiver;
+
+import java.util.ArrayList;
+
+/**
+ * Helpers for generating the intents and related extras parameters to launch the UI activities.
+ *
+ * @hide
+ */
+@TestApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+public class IntentFactory {
+
+    /**
+     * Generate a new launch intent to the Credential Selector UI.
+     *
+     * @hide
+     */
+    @NonNull
+    public static Intent createCredentialSelectorIntent(
+            @NonNull RequestInfo requestInfo,
+            @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
+            @NonNull
+            ArrayList<ProviderData> enabledProviderDataList,
+            @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
+            @NonNull
+            ArrayList<DisabledProviderData> disabledProviderDataList,
+            @NonNull ResultReceiver resultReceiver,
+            boolean isRequestForAllOptions) {
+
+        Intent intent = createCredentialSelectorIntent(requestInfo, enabledProviderDataList,
+                disabledProviderDataList, resultReceiver);
+        intent.putExtra(Constants.EXTRA_REQ_FOR_ALL_OPTIONS, isRequestForAllOptions);
+
+        return intent;
+    }
+
+    /** Generate a new launch intent to the Credential Selector UI. */
+    @NonNull
+    public static Intent createCredentialSelectorIntent(
+            @NonNull RequestInfo requestInfo,
+            @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
+                    @NonNull
+                    ArrayList<ProviderData> enabledProviderDataList,
+            @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
+                    @NonNull
+                    ArrayList<DisabledProviderData> disabledProviderDataList,
+            @NonNull ResultReceiver resultReceiver) {
+        Intent intent = new Intent();
+        ComponentName componentName =
+                ComponentName.unflattenFromString(
+                        Resources.getSystem()
+                                .getString(
+                                        com.android.internal.R.string
+                                                .config_credentialManagerDialogComponent));
+        intent.setComponent(componentName);
+
+        intent.putParcelableArrayListExtra(
+                ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList);
+        intent.putParcelableArrayListExtra(
+                ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList);
+        intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo);
+        intent.putExtra(
+                Constants.EXTRA_RESULT_RECEIVER, toIpcFriendlyResultReceiver(resultReceiver));
+
+        return intent;
+    }
+
+    /**
+     * Creates an Intent that cancels any UI matching the given request token id.
+     *
+     * @hide
+     */
+    @NonNull
+    public static Intent createCancelUiIntent(@NonNull IBinder requestToken,
+            boolean shouldShowCancellationUi, @NonNull String appPackageName) {
+        Intent intent = new Intent();
+        ComponentName componentName =
+                ComponentName.unflattenFromString(
+                        Resources.getSystem()
+                                .getString(
+                                        com.android.internal.R.string
+                                                .config_credentialManagerDialogComponent));
+        intent.setComponent(componentName);
+        intent.putExtra(CancelUiRequest.EXTRA_CANCEL_UI_REQUEST,
+                new CancelUiRequest(requestToken, shouldShowCancellationUi, appPackageName));
+        return intent;
+    }
+
+    /**
+     * Convert an instance of a "locally-defined" ResultReceiver to an instance of {@link
+     * android.os.ResultReceiver} itself, which the receiving process will be able to unmarshall.
+     */
+    private static <T extends ResultReceiver> ResultReceiver toIpcFriendlyResultReceiver(
+            T resultReceiver) {
+        final Parcel parcel = Parcel.obtain();
+        resultReceiver.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        return ipcFriendly;
+    }
+
+    private IntentFactory() {}
+}
diff --git a/core/java/android/credentials/selection/IntentHelper.java b/core/java/android/credentials/selection/IntentHelper.java
new file mode 100644
index 0000000..6bcd05a
--- /dev/null
+++ b/core/java/android/credentials/selection/IntentHelper.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.selection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.os.ResultReceiver;
+
+import java.util.List;
+
+/**
+ * Utilities for parsing the intent data used to launch the UI activity.
+ *
+ * @hide
+ */
+public final class IntentHelper {
+    /**
+     * Attempts to extract a {@link CancelUiRequest} from the given intent; returns null
+     * if not found.
+     */
+    @Nullable
+    public static CancelUiRequest extractCancelUiRequest(@NonNull Intent intent) {
+        return intent.getParcelableExtra(CancelUiRequest.EXTRA_CANCEL_UI_REQUEST,
+                CancelUiRequest.class);
+    }
+
+    /**
+     * Attempts to extract a {@link RequestInfo} from the given intent; returns null
+     * if not found.
+     */
+    @Nullable
+    public static RequestInfo extractRequestInfo(@NonNull Intent intent) {
+        return intent.getParcelableExtra(RequestInfo.EXTRA_REQUEST_INFO,
+                RequestInfo.class);
+    }
+
+    /**
+     * Attempts to extract the list of {@link GetCredentialProviderInfo} from the given intent;
+     * returns null if not found.
+     */
+    @Nullable
+    @SuppressLint("NullableCollection") // To be consistent with the nullable Intent extra APIs
+    // and the other APIs in this class.
+    public static List<GetCredentialProviderInfo> extractGetCredentialProviderDataList(
+            @NonNull Intent intent) {
+        List<GetCredentialProviderData> providerList = intent.getParcelableArrayListExtra(
+                ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
+                GetCredentialProviderData.class);
+        return providerList == null ? null : providerList.stream().map(
+                GetCredentialProviderData::toGetCredentialProviderInfo).toList();
+    }
+
+    /**
+     * Attempts to extract the list of {@link CreateCredentialProviderInfo} from the given intent;
+     * returns null if not found.
+     */
+    @Nullable
+    @SuppressLint("NullableCollection") // To be consistent with the nullable Intent extra APIs
+    // and the other APIs in this class.
+    public static List<CreateCredentialProviderInfo> extractCreateCredentialProviderDataList(
+            @NonNull Intent intent) {
+        List<CreateCredentialProviderData> providerList = intent.getParcelableArrayListExtra(
+                ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
+                CreateCredentialProviderData.class);
+        return providerList == null ? null : providerList.stream().map(
+                CreateCredentialProviderData::toCreateCredentialProviderInfo).toList();
+    }
+
+    /**
+     * Attempts to extract a {@link android.os.ResultReceiver} from the given intent, which should
+     * be used to send back UI results; returns null if not found.
+     */
+    @Nullable
+    public static ResultReceiver extractResultReceiver(@NonNull Intent intent) {
+        return intent.getParcelableExtra(Constants.EXTRA_RESULT_RECEIVER,
+                ResultReceiver.class);
+    }
+
+    private IntentHelper() {
+    }
+}
diff --git a/core/java/android/credentials/selection/ProviderData.java b/core/java/android/credentials/selection/ProviderData.java
new file mode 100644
index 0000000..e7a7d77
--- /dev/null
+++ b/core/java/android/credentials/selection/ProviderData.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.selection;
+
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * Super class for data structures that hold metadata and credential entries for a single provider.
+ *
+ * @hide
+ */
+@TestApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+@SuppressLint({"ParcelCreator", "ParcelNotFinal"})
+public abstract class ProviderData implements Parcelable {
+
+    /**
+     * The intent extra key for the list of {@code ProviderData} from active providers when
+     * launching the UX activities.
+     */
+    public static final String EXTRA_ENABLED_PROVIDER_DATA_LIST =
+            "android.credentials.selection.extra.ENABLED_PROVIDER_DATA_LIST";
+    /**
+     * The intent extra key for the list of {@code ProviderData} from disabled providers when
+     * launching the UX activities.
+     */
+    public static final String EXTRA_DISABLED_PROVIDER_DATA_LIST =
+            "android.credentials.selection.extra.DISABLED_PROVIDER_DATA_LIST";
+
+    @NonNull
+    private final String mProviderFlattenedComponentName;
+
+    public ProviderData(
+            @NonNull String providerFlattenedComponentName) {
+        mProviderFlattenedComponentName = providerFlattenedComponentName;
+    }
+
+    /**
+     * Returns provider component name.
+     * It also serves as the unique identifier for this provider.
+     */
+    @NonNull
+    public String getProviderFlattenedComponentName() {
+        return mProviderFlattenedComponentName;
+    }
+
+    @SuppressLint("ParcelConstructor") // Test API only. This is never intended to be officially
+    // exposed. Instead proper final wrapper classes are defined (e.g.
+    // {@code GetCredentialProviderInfo}).
+    protected ProviderData(@NonNull Parcel in) {
+        String providerFlattenedComponentName = in.readString8();
+        mProviderFlattenedComponentName = providerFlattenedComponentName;
+        AnnotationValidations.validate(NonNull.class, null, mProviderFlattenedComponentName);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mProviderFlattenedComponentName);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/credentials/selection/ProviderPendingIntentResponse.java b/core/java/android/credentials/selection/ProviderPendingIntentResponse.java
new file mode 100644
index 0000000..281f34a
--- /dev/null
+++ b/core/java/android/credentials/selection/ProviderPendingIntentResponse.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.selection;
+
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ResultReceiver;
+
+/**
+ * Result of launching a provider's PendingIntent associated with an {@link Entry} after it is
+ * selected by the user.
+ *
+ * The provider sets the credential creation / retrieval result through
+ * {@link android.app.Activity#setResult(int, Intent)}, which is then directly propagated back
+ * through this data structure.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+public final class ProviderPendingIntentResponse implements Parcelable {
+    private final int mResultCode;
+    @Nullable
+    private final Intent mResultData;
+
+    /**
+     * Constructs a {@link ProviderPendingIntentResponse}.
+     *
+     * When a user makes a selection, you should launch the associated provider PendingIntent,
+     * and expect the provider activity to complete and set
+     * {@link android.app.Activity#setResult(int, Intent)}. You should then immediately pass back
+     * the provider activity result code and data to the system service using this data class,
+     * via the {@link ResultHelper#sendUserSelectionResult(ResultReceiver, UserSelectionResult)}
+     * API.
+     *
+     * @param resultCode the resultCode returned from the provider activity
+     * @param resultData the result data returned from the provider activity; only set to null if
+     *                   the provider result (a provider would set it via
+     *                   {@link android.app.Activity#setResult(int, Intent)}) your UI received
+     *                   was actually null
+     */
+    public ProviderPendingIntentResponse(int resultCode, @Nullable Intent resultData) {
+        mResultCode = resultCode;
+        mResultData = resultData;
+    }
+
+    private ProviderPendingIntentResponse(@NonNull Parcel in) {
+        mResultCode = in.readInt();
+        mResultData = in.readTypedObject(Intent.CREATOR);
+    }
+
+    public static final @NonNull Creator<ProviderPendingIntentResponse> CREATOR =
+            new Creator<>() {
+                @Override
+                public ProviderPendingIntentResponse createFromParcel(@NonNull Parcel in) {
+                    return new ProviderPendingIntentResponse(in);
+                }
+
+                @Override
+                public ProviderPendingIntentResponse[] newArray(int size) {
+                    return new ProviderPendingIntentResponse[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mResultCode);
+        dest.writeTypedObject(mResultData, flags);
+    }
+
+    /**
+     * Returns the result code associated with this provider PendingIntent activity result, i.e.
+     * the {@code resultCode} that the provider activity has set using the
+     * {@link android.app.Activity#setResult(int, Intent)} API.
+     */
+    public int getResultCode() {
+        return mResultCode;
+    }
+
+    /**
+     * Returns the result data associated with this provider PendingIntent activity result, i.e.
+     * the {@code data} that the provider activity has set using the
+     * {@link android.app.Activity#setResult(int, Intent)} API.
+     *
+     * Notice that this value can be null if the provider UI result (a provider would set it via
+     * {@link android.app.Activity#setResult(int, Intent)}) that your UI received was actually null,
+     * which indicates an implementation error on the provider side. The system service will
+     * gracefully handle this by passing back an API exception (
+     * {@link android.credentials.GetCredentialException} or
+     * {@link android.credentials.CreateCredentialException}).
+     */
+    @SuppressLint("IntentBuilderName") // Not building a new intent.
+    @Nullable
+    public Intent getResultData() {
+        return mResultData;
+    }
+}
diff --git a/core/java/android/credentials/selection/RequestInfo.java b/core/java/android/credentials/selection/RequestInfo.java
new file mode 100644
index 0000000..7d6ea7e
--- /dev/null
+++ b/core/java/android/credentials/selection/RequestInfo.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.selection;
+
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.annotation.TestApi;
+import android.credentials.CreateCredentialRequest;
+import android.credentials.GetCredentialRequest;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Contains information about the request that initiated this UX flow.
+ *
+ * @hide
+ */
+@TestApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+public final class RequestInfo implements Parcelable {
+
+    /**
+     * The intent extra key for the {@code RequestInfo} object when launching the UX
+     * activities.
+     */
+    @NonNull
+    public static final String EXTRA_REQUEST_INFO =
+            "android.credentials.selection.extra.REQUEST_INFO";
+
+    /**
+     * Type value for any request that does not require UI.
+     */
+    @NonNull
+    public static final String TYPE_UNDEFINED = "android.credentials.selection.TYPE_UNDEFINED";
+    /**
+     * Type value for a getCredential request.
+     */
+    @NonNull
+    public static final String TYPE_GET = "android.credentials.selection.TYPE_GET";
+    /**
+     * Type value for a getCredential request that utilizes the credential registry.
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String TYPE_GET_VIA_REGISTRY =
+            "android.credentials.selection.TYPE_GET_VIA_REGISTRY";
+    /**
+     * Type value for a createCredential request.
+     */
+    @NonNull
+    public static final String TYPE_CREATE = "android.credentials.selection.TYPE_CREATE";
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef(value = {TYPE_GET, TYPE_CREATE})
+    public @interface RequestType {
+    }
+
+    @NonNull
+    private final IBinder mToken;
+
+    @Nullable
+    private final CreateCredentialRequest mCreateCredentialRequest;
+
+    @NonNull
+    private final List<String> mDefaultProviderIds;
+
+    @NonNull
+    private final List<String> mRegistryProviderIds;
+
+    @Nullable
+    private final GetCredentialRequest mGetCredentialRequest;
+
+    @NonNull
+    @RequestType
+    private final String mType;
+
+    @NonNull
+    private final String mAppPackageName;
+
+    private final boolean mHasPermissionToOverrideDefault;
+
+    /** Creates new {@code RequestInfo} for a create-credential flow. */
+    @NonNull
+    public static RequestInfo newCreateRequestInfo(
+            @NonNull IBinder token, @NonNull CreateCredentialRequest createCredentialRequest,
+            @NonNull String appPackageName) {
+        return new RequestInfo(
+                token, TYPE_CREATE, appPackageName, createCredentialRequest, null,
+                /*hasPermissionToOverrideDefault=*/ false,
+                /*defaultProviderIds=*/ new ArrayList<>());
+    }
+
+    /** Creates new {@code RequestInfo} for a create-credential flow. */
+    @NonNull
+    public static RequestInfo newCreateRequestInfo(
+            @NonNull IBinder token, @NonNull CreateCredentialRequest createCredentialRequest,
+            @NonNull String appPackageName, boolean hasPermissionToOverrideDefault,
+            @NonNull List<String> defaultProviderIds) {
+        return new RequestInfo(
+                token, TYPE_CREATE, appPackageName, createCredentialRequest, null,
+                hasPermissionToOverrideDefault, defaultProviderIds);
+    }
+
+    /** Creates new {@code RequestInfo} for a get-credential flow. */
+    @NonNull
+    public static RequestInfo newGetRequestInfo(
+            @NonNull IBinder token, @NonNull GetCredentialRequest getCredentialRequest,
+            @NonNull String appPackageName, boolean hasPermissionToOverrideDefault) {
+        return new RequestInfo(
+                token, TYPE_GET, appPackageName, null, getCredentialRequest,
+                hasPermissionToOverrideDefault,
+                /*defaultProviderIds=*/ new ArrayList<>());
+    }
+
+    /** Creates new {@code RequestInfo} for a get-credential flow. */
+    @NonNull
+    public static RequestInfo newGetRequestInfo(
+            @NonNull IBinder token, @NonNull GetCredentialRequest getCredentialRequest,
+            @NonNull String appPackageName) {
+        return new RequestInfo(
+                token, TYPE_GET, appPackageName, null, getCredentialRequest,
+                /*hasPermissionToOverrideDefault=*/ false,
+                /*defaultProviderIds=*/ new ArrayList<>());
+    }
+
+
+    /** Returns whether the calling package has the permission. */
+    public boolean hasPermissionToOverrideDefault() {
+        return mHasPermissionToOverrideDefault;
+    }
+
+    /** Returns the request token matching the user request. */
+    @NonNull
+    public IBinder getToken() {
+        return mToken;
+    }
+
+    /** Returns the request type. */
+    @NonNull
+    @RequestType
+    public String getType() {
+        return mType;
+    }
+
+    /** Returns the display name of the app that made this request. */
+    @NonNull
+    public String getAppPackageName() {
+        return mAppPackageName;
+    }
+
+    /**
+     * Returns the non-null CreateCredentialRequest when the type of the request is {@link
+     * #TYPE_CREATE}, or null otherwise.
+     */
+    @Nullable
+    public CreateCredentialRequest getCreateCredentialRequest() {
+        return mCreateCredentialRequest;
+    }
+
+    /**
+     * Returns default provider identifiers (component or package name) configured from the user
+     * settings.
+     *
+     * Will only be possibly non-empty for the create use case. Not meaningful for the sign-in use
+     * case.
+     */
+    @NonNull
+    public List<String> getDefaultProviderIds() {
+        return mDefaultProviderIds;
+    }
+
+    /**
+     * Returns provider identifiers (component or package name) that have been validated to provide
+     * registry entries.
+     */
+    @NonNull
+    public List<String> getRegistryProviderIds() {
+        return mRegistryProviderIds;
+    }
+
+    /**
+     * Returns the non-null GetCredentialRequest when the type of the request is {@link
+     * #TYPE_GET}, or null otherwise.
+     */
+    @Nullable
+    public GetCredentialRequest getGetCredentialRequest() {
+        return mGetCredentialRequest;
+    }
+
+    private RequestInfo(@NonNull IBinder token, @NonNull @RequestType String type,
+            @NonNull String appPackageName,
+            @Nullable CreateCredentialRequest createCredentialRequest,
+            @Nullable GetCredentialRequest getCredentialRequest,
+            boolean hasPermissionToOverrideDefault,
+            @NonNull List<String> defaultProviderIds) {
+        mToken = token;
+        mType = type;
+        mAppPackageName = appPackageName;
+        mCreateCredentialRequest = createCredentialRequest;
+        mGetCredentialRequest = getCredentialRequest;
+        mHasPermissionToOverrideDefault = hasPermissionToOverrideDefault;
+        mDefaultProviderIds = defaultProviderIds == null ? new ArrayList<>() : defaultProviderIds;
+        mRegistryProviderIds = new ArrayList<>();
+    }
+
+    private RequestInfo(@NonNull Parcel in) {
+        IBinder token = in.readStrongBinder();
+        String type = in.readString8();
+        String appPackageName = in.readString8();
+        CreateCredentialRequest createCredentialRequest =
+                in.readTypedObject(CreateCredentialRequest.CREATOR);
+        GetCredentialRequest getCredentialRequest =
+                in.readTypedObject(GetCredentialRequest.CREATOR);
+
+        mToken = token;
+        AnnotationValidations.validate(NonNull.class, null, mToken);
+        mType = type;
+        AnnotationValidations.validate(NonNull.class, null, mType);
+        mAppPackageName = appPackageName;
+        AnnotationValidations.validate(NonNull.class, null, mAppPackageName);
+        mCreateCredentialRequest = createCredentialRequest;
+        mGetCredentialRequest = getCredentialRequest;
+        mHasPermissionToOverrideDefault = in.readBoolean();
+        mDefaultProviderIds = in.createStringArrayList();
+        mRegistryProviderIds = in.createStringArrayList();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStrongBinder(mToken);
+        dest.writeString8(mType);
+        dest.writeString8(mAppPackageName);
+        dest.writeTypedObject(mCreateCredentialRequest, flags);
+        dest.writeTypedObject(mGetCredentialRequest, flags);
+        dest.writeBoolean(mHasPermissionToOverrideDefault);
+        dest.writeStringList(mDefaultProviderIds);
+        dest.writeStringList(mRegistryProviderIds);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Creator<RequestInfo> CREATOR = new Creator<>() {
+        @Override
+        public RequestInfo createFromParcel(@NonNull Parcel in) {
+            return new RequestInfo(in);
+        }
+
+        @Override
+        public RequestInfo[] newArray(int size) {
+            return new RequestInfo[size];
+        }
+    };
+}
diff --git a/core/java/android/credentials/selection/ResultHelper.java b/core/java/android/credentials/selection/ResultHelper.java
new file mode 100644
index 0000000..d6347b0
--- /dev/null
+++ b/core/java/android/credentials/selection/ResultHelper.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.selection;
+
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+
+/**
+ * Utilities for sending the UI results back to the system service.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+public final class ResultHelper {
+    /**
+     * Sends the {@code failureResult} that caused the UI to stop back to the CredentialManager
+     * service.
+     *
+     * @param resultReceiver the ResultReceiver sent from the system service, that can be extracted
+     *                      from the launch intent via
+     *                      {@link IntentHelper#extractResultReceiver(Intent)}
+     */
+    public static void sendFailureResult(@NonNull ResultReceiver resultReceiver,
+            @NonNull FailureResult failureResult) {
+        FailureDialogResult result = failureResult.toFailureDialogResult();
+        Bundle resultData = new Bundle();
+        FailureDialogResult.addToBundle(result, resultData);
+        resultReceiver.send(failureResult.errorCodeToResultCode(),
+                resultData);
+    }
+
+    /**
+     * Sends the completed {@code userSelectionResult} back to the CredentialManager service.
+     *
+     * @param resultReceiver the ResultReceiver sent from the system service, that can be extracted
+     *                       from the launch intent via
+     *                       {@link IntentHelper#extractResultReceiver(Intent)}
+     */
+    public static void sendUserSelectionResult(@NonNull ResultReceiver resultReceiver,
+            @NonNull UserSelectionResult userSelectionResult) {
+        UserSelectionDialogResult result = userSelectionResult.toUserSelectionDialogResult();
+        Bundle resultData = new Bundle();
+        UserSelectionDialogResult.addToBundle(result, resultData);
+        resultReceiver.send(BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION,
+                resultData);
+    }
+
+    private ResultHelper() {}
+}
diff --git a/core/java/android/credentials/selection/UserSelectionDialogResult.java b/core/java/android/credentials/selection/UserSelectionDialogResult.java
new file mode 100644
index 0000000..50d5aa3
--- /dev/null
+++ b/core/java/android/credentials/selection/UserSelectionDialogResult.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.selection;
+
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * Result data matching {@link BaseDialogResult#RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION}.
+ *
+ * @hide
+ */
+@TestApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+public final class UserSelectionDialogResult extends BaseDialogResult implements Parcelable {
+    /** Parses and returns a UserSelectionDialogResult from the given resultData. */
+    @Nullable
+    public static UserSelectionDialogResult fromResultData(@NonNull Bundle resultData) {
+        return resultData.getParcelable(
+                EXTRA_USER_SELECTION_RESULT, UserSelectionDialogResult.class);
+    }
+
+    /**
+     * Used for the UX to construct the {@code resultData Bundle} to send via the {@code
+     * ResultReceiver}.
+     */
+    public static void addToBundle(
+            @NonNull UserSelectionDialogResult result, @NonNull Bundle bundle) {
+        bundle.putParcelable(EXTRA_USER_SELECTION_RESULT, result);
+    }
+
+    /**
+     * The intent extra key for the {@code UserSelectionDialogResult} object when the credential
+     * selector activity finishes.
+     */
+    private static final String EXTRA_USER_SELECTION_RESULT =
+            "android.credentials.selection.extra.USER_SELECTION_RESULT";
+
+    @NonNull
+    private final String mProviderId;
+    @NonNull
+    private final String mEntryKey;
+    @NonNull
+    private final String mEntrySubkey;
+    @Nullable
+    private ProviderPendingIntentResponse mProviderPendingIntentResponse;
+
+    public UserSelectionDialogResult(
+            @Nullable IBinder requestToken, @NonNull String providerId,
+            @NonNull String entryKey, @NonNull String entrySubkey) {
+        super(requestToken);
+        mProviderId = providerId;
+        mEntryKey = entryKey;
+        mEntrySubkey = entrySubkey;
+    }
+
+    public UserSelectionDialogResult(
+            @Nullable IBinder requestToken, @NonNull String providerId,
+            @NonNull String entryKey, @NonNull String entrySubkey,
+            @Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
+        super(requestToken);
+        mProviderId = providerId;
+        mEntryKey = entryKey;
+        mEntrySubkey = entrySubkey;
+        mProviderPendingIntentResponse = providerPendingIntentResponse;
+    }
+
+    /** Returns provider package name whose entry was selected by the user. */
+    @NonNull
+    public String getProviderId() {
+        return mProviderId;
+    }
+
+    /** Returns the key of the visual entry that the user selected. */
+    @NonNull
+    public String getEntryKey() {
+        return mEntryKey;
+    }
+
+    /** Returns the subkey of the visual entry that the user selected. */
+    @NonNull
+    public String getEntrySubkey() {
+        return mEntrySubkey;
+    }
+
+    /** Returns the pending intent response from the provider. */
+    @Nullable
+    public ProviderPendingIntentResponse getPendingIntentProviderResponse() {
+        return mProviderPendingIntentResponse;
+    }
+
+    private UserSelectionDialogResult(@NonNull Parcel in) {
+        super(in);
+        String providerId = in.readString8();
+        String entryKey = in.readString8();
+        String entrySubkey = in.readString8();
+
+        mProviderId = providerId;
+        AnnotationValidations.validate(NonNull.class, null, mProviderId);
+        mEntryKey = entryKey;
+        AnnotationValidations.validate(NonNull.class, null, mEntryKey);
+        mEntrySubkey = entrySubkey;
+        AnnotationValidations.validate(NonNull.class, null, mEntrySubkey);
+        mProviderPendingIntentResponse = in.readTypedObject(ProviderPendingIntentResponse.CREATOR);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeString8(mProviderId);
+        dest.writeString8(mEntryKey);
+        dest.writeString8(mEntrySubkey);
+        dest.writeTypedObject(mProviderPendingIntentResponse, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<UserSelectionDialogResult> CREATOR =
+            new Creator<UserSelectionDialogResult>() {
+                @Override
+                public UserSelectionDialogResult createFromParcel(@NonNull Parcel in) {
+                    return new UserSelectionDialogResult(in);
+                }
+
+                @Override
+                public UserSelectionDialogResult[] newArray(int size) {
+                    return new UserSelectionDialogResult[size];
+                }
+            };
+}
diff --git a/core/java/android/credentials/selection/UserSelectionResult.java b/core/java/android/credentials/selection/UserSelectionResult.java
new file mode 100644
index 0000000..235a5d5
--- /dev/null
+++ b/core/java/android/credentials/selection/UserSelectionResult.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.selection;
+
+import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Result sent back from the UI after the user chose an option and completed the following
+ * transaction launched through the provider PendingIntent associated with that option.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+public final class UserSelectionResult {
+    @NonNull
+    private final String mProviderId;
+    @NonNull
+    private final String mEntryKey;
+    @NonNull
+    private final String mEntrySubkey;
+    @Nullable
+    private ProviderPendingIntentResponse mProviderPendingIntentResponse;
+
+    /**
+     * Constructs a {@link UserSelectionResult}.
+     *
+     * @param providerId the provider identifier (component name or package name) whose entry was
+     *                  selected by the user; the value should map to the
+     *                  {@link GetCredentialProviderInfo#getProviderName()} that provided this entry
+     * @param entryKey the identifier of this selected entry, i.e. the selected entry's
+     *                 {@link Entry#getKey()}
+     * @param entrySubkey the sub-identifier of this selected entry, i.e. the selected entry's
+     *                    {@link Entry#getSubkey()}
+     * @param providerPendingIntentResponse the provider activity result of launching the provider
+     *                                      PendingIntent associated with this selection; or null
+     *                                      if the associated selection didn't have an associated
+     *                                      provider PendingIntent
+     * @throws IllegalArgumentException if {@code providerId}, {@code entryKey}, or
+     *                                  {@code entrySubkey} is empty
+     */
+
+    public UserSelectionResult(@NonNull String providerId,
+            @NonNull String entryKey, @NonNull String entrySubkey,
+            @Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
+        mProviderId = Preconditions.checkStringNotEmpty(providerId);
+        mEntryKey = Preconditions.checkStringNotEmpty(entryKey);
+        mEntrySubkey = Preconditions.checkStringNotEmpty(entrySubkey);
+        mProviderPendingIntentResponse = providerPendingIntentResponse;
+    }
+
+    /**
+     * Returns the provider identifier (component name or package name) whose entry was selected by
+     * the user.
+     */
+    @NonNull
+    public String getProviderId() {
+        return mProviderId;
+    }
+
+    /** Returns the identifier of the visual entry that the user selected. */
+    @NonNull
+    public String getEntryKey() {
+        return mEntryKey;
+    }
+
+    /** Returns the sub-identifier of the visual entry that the user selected. */
+    @NonNull
+    public String getEntrySubkey() {
+        return mEntrySubkey;
+    }
+
+    /** Returns the pending intent response from the provider. */
+    @Nullable
+    public ProviderPendingIntentResponse getPendingIntentProviderResponse() {
+        return mProviderPendingIntentResponse;
+    }
+
+    @NonNull
+    UserSelectionDialogResult toUserSelectionDialogResult() {
+        return new UserSelectionDialogResult(/*requestToken=*/null, mProviderId, mEntryKey,
+                mEntrySubkey, mProviderPendingIntentResponse);
+    }
+}
diff --git a/core/java/android/credentials/ui/AuthenticationEntry.java b/core/java/android/credentials/ui/AuthenticationEntry.java
deleted file mode 100644
index b1a382c..0000000
--- a/core/java/android/credentials/ui/AuthenticationEntry.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.credentials.ui;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.annotation.TestApi;
-import android.app.slice.Slice;
-import android.content.Intent;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.AnnotationValidations;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * An authentication entry.
- *
- * @hide
- */
-@TestApi
-public final class AuthenticationEntry implements Parcelable {
-    @NonNull private final String mKey;
-    @NonNull private final String mSubkey;
-    @NonNull private final @Status int mStatus;
-    @Nullable private Intent mFrameworkExtrasIntent;
-    @NonNull private final Slice mSlice;
-
-    /** @hide **/
-    @IntDef(prefix = {"STATUS_"}, value = {
-            STATUS_LOCKED,
-            STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT,
-            STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface Status {}
-
-    /** This entry is still locked, as initially supplied by the provider. */
-    public static final int STATUS_LOCKED = 0;
-    /** This entry was unlocked but didn't contain any credential. Meanwhile, "less recent" means
-     *  there is another such entry that was unlocked more recently. */
-    public static final int STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT = 1;
-    /** This is the most recent entry that was unlocked but didn't contain any credential.
-     *  There should be at most one authentication entry with this status. */
-    public static final int STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT = 2;
-
-    private AuthenticationEntry(@NonNull Parcel in) {
-        mKey = in.readString8();
-        mSubkey = in.readString8();
-        mStatus = in.readInt();
-        mSlice = in.readTypedObject(Slice.CREATOR);
-        mFrameworkExtrasIntent = in.readTypedObject(Intent.CREATOR);
-
-        AnnotationValidations.validate(NonNull.class, null, mKey);
-        AnnotationValidations.validate(NonNull.class, null, mSubkey);
-        AnnotationValidations.validate(NonNull.class, null, mSlice);
-    }
-
-    /** Constructor to be used for an entry that does not require further activities
-     * to be invoked when selected.
-     */
-    public AuthenticationEntry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
-            @Status int status) {
-        mKey = key;
-        mSubkey = subkey;
-        mSlice = slice;
-        mStatus = status;
-    }
-
-    /** Constructor to be used for an entry that requires a pending intent to be invoked
-     * when clicked.
-     */
-    public AuthenticationEntry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
-            @Status int status, @NonNull Intent intent) {
-        this(key, subkey, slice, status);
-        mFrameworkExtrasIntent = intent;
-    }
-
-    /**
-    * Returns the identifier of this entry that's unique within the context of the CredentialManager
-    * request.
-    */
-    @NonNull
-    public String getKey() {
-        return mKey;
-    }
-
-    /**
-     * Returns the sub-identifier of this entry that's unique within the context of the {@code key}.
-     */
-    @NonNull
-    public String getSubkey() {
-        return mSubkey;
-    }
-
-    /**
-    * Returns the Slice to be rendered.
-    */
-    @NonNull
-    public Slice getSlice() {
-        return mSlice;
-    }
-
-    /**
-     * Returns the entry status.
-     */
-    @NonNull
-    @Status
-    public int getStatus() {
-        return mStatus;
-    }
-
-    @Nullable
-    @SuppressLint("IntentBuilderName") // Not building a new intent.
-    public Intent getFrameworkExtrasIntent() {
-        return mFrameworkExtrasIntent;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeString8(mKey);
-        dest.writeString8(mSubkey);
-        dest.writeInt(mStatus);
-        dest.writeTypedObject(mSlice, flags);
-        dest.writeTypedObject(mFrameworkExtrasIntent, flags);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    public static final @NonNull Creator<AuthenticationEntry> CREATOR = new Creator<>() {
-        @Override
-        public AuthenticationEntry createFromParcel(@NonNull Parcel in) {
-            return new AuthenticationEntry(in);
-        }
-
-        @Override
-        public AuthenticationEntry[] newArray(int size) {
-            return new AuthenticationEntry[size];
-        }
-    };
-}
diff --git a/core/java/android/credentials/ui/BaseDialogResult.java b/core/java/android/credentials/ui/BaseDialogResult.java
deleted file mode 100644
index e8cf5ab..0000000
--- a/core/java/android/credentials/ui/BaseDialogResult.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.credentials.ui;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.AnnotationValidations;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Base dialog result data.
- *
- * Returned for simple use cases like cancellation. Can also be subclassed when more information
- * is needed, e.g. {@link UserSelectionDialogResult}.
- *
- * @hide
- */
-public class BaseDialogResult implements Parcelable {
-    /** Parses and returns a BaseDialogResult from the given resultData. */
-    @Nullable
-    public static BaseDialogResult fromResultData(@NonNull Bundle resultData) {
-        return resultData.getParcelable(EXTRA_BASE_RESULT, BaseDialogResult.class);
-    }
-
-    /**
-     * Used for the UX to construct the {@code resultData Bundle} to send via the {@code
-     *  ResultReceiver}.
-     */
-    public static void addToBundle(@NonNull BaseDialogResult result, @NonNull Bundle bundle) {
-        bundle.putParcelable(EXTRA_BASE_RESULT, result);
-    }
-
-    /**
-     * The intent extra key for the {@code BaseDialogResult} object when the credential
-     * selector activity finishes.
-     */
-    private static final String EXTRA_BASE_RESULT = "android.credentials.ui.extra.BASE_RESULT";
-
-    /** @hide **/
-    @IntDef(prefix = {"RESULT_CODE_"}, value = {
-            RESULT_CODE_DIALOG_USER_CANCELED,
-            RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS,
-            RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION,
-            RESULT_CODE_DATA_PARSING_FAILURE,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ResultCode {}
-
-    /** User intentionally canceled the dialog. */
-    public static final int RESULT_CODE_DIALOG_USER_CANCELED = 0;
-    /**
-     * The user has consented to switching to a new default provider. The provider info is in the
-     * {@code resultData}.
-     */
-    public static final int RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS = 1;
-    /**
-     * User made a selection and the dialog finished. The user selection result is in the
-     * {@code resultData}.
-     */
-    public static final int RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION = 2;
-    /**
-     * The UI was canceled because it failed to parse the incoming data.
-     */
-    public static final int RESULT_CODE_DATA_PARSING_FAILURE = 3;
-
-    @Nullable
-    private final IBinder mRequestToken;
-
-    public BaseDialogResult(@Nullable IBinder requestToken) {
-        mRequestToken = requestToken;
-    }
-
-    /** Returns the unique identifier for the request that launched the operation. */
-    @Nullable
-    public IBinder getRequestToken() {
-        return mRequestToken;
-    }
-
-    protected BaseDialogResult(@NonNull Parcel in) {
-        IBinder requestToken = in.readStrongBinder();
-        mRequestToken = requestToken;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeStrongBinder(mRequestToken);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    public static final @NonNull Creator<BaseDialogResult> CREATOR =
-            new Creator<BaseDialogResult>() {
-        @Override
-        public BaseDialogResult createFromParcel(@NonNull Parcel in) {
-            return new BaseDialogResult(in);
-        }
-
-        @Override
-        public BaseDialogResult[] newArray(int size) {
-            return new BaseDialogResult[size];
-        }
-    };
-}
diff --git a/core/java/android/credentials/ui/CancelUiRequest.java b/core/java/android/credentials/ui/CancelUiRequest.java
deleted file mode 100644
index d4c249e..0000000
--- a/core/java/android/credentials/ui/CancelUiRequest.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.credentials.ui;
-
-import android.annotation.NonNull;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.AnnotationValidations;
-
-/**
- * A request to cancel any ongoing UI matching this request.
- *
- * @hide
- */
-public final class CancelUiRequest implements Parcelable {
-
-    /**
-     * The intent extra key for the {@code CancelUiRequest} object when launching the UX
-     * activities.
-     */
-    @NonNull public static final String EXTRA_CANCEL_UI_REQUEST =
-            "android.credentials.ui.extra.EXTRA_CANCEL_UI_REQUEST";
-
-    @NonNull
-    private final IBinder mToken;
-
-    private final boolean mShouldShowCancellationUi;
-
-    @NonNull
-    private final String mAppPackageName;
-
-    /** Returns the request token matching the user request that should be cancelled. */
-    @NonNull
-    public IBinder getToken() {
-        return mToken;
-    }
-
-    @NonNull
-    public String getAppPackageName() {
-        return mAppPackageName;
-    }
-
-    /**
-     * Returns whether the UI should render a cancellation UI upon the request. If false, the UI
-     * will be silently cancelled.
-     */
-    public boolean shouldShowCancellationUi() {
-        return mShouldShowCancellationUi;
-    }
-
-    public CancelUiRequest(@NonNull IBinder token, boolean shouldShowCancellationUi,
-            @NonNull String appPackageName) {
-        mToken = token;
-        mShouldShowCancellationUi = shouldShowCancellationUi;
-        mAppPackageName = appPackageName;
-    }
-
-    private CancelUiRequest(@NonNull Parcel in) {
-        mToken = in.readStrongBinder();
-        AnnotationValidations.validate(NonNull.class, null, mToken);
-        mShouldShowCancellationUi = in.readBoolean();
-        mAppPackageName = in.readString8();
-        AnnotationValidations.validate(NonNull.class, null, mAppPackageName);
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeStrongBinder(mToken);
-        dest.writeBoolean(mShouldShowCancellationUi);
-        dest.writeString8(mAppPackageName);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @NonNull public static final Creator<CancelUiRequest> CREATOR = new Creator<>() {
-        @Override
-        public CancelUiRequest createFromParcel(@NonNull Parcel in) {
-            return new CancelUiRequest(in);
-        }
-
-        @Override
-        public CancelUiRequest[] newArray(int size) {
-            return new CancelUiRequest[size];
-        }
-    };
-}
diff --git a/core/java/android/credentials/ui/Constants.java b/core/java/android/credentials/ui/Constants.java
deleted file mode 100644
index 37f850b..0000000
--- a/core/java/android/credentials/ui/Constants.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.credentials.ui;
-
-/**
- * Constants for the ui protocol that doesn't fit into other individual data structures.
- *
- * @hide
- */
-public class Constants {
-
-    /**
-     * The intent extra key for the {@code ResultReceiver} object when launching the UX activities.
-     */
-    public static final String EXTRA_RESULT_RECEIVER =
-            "android.credentials.ui.extra.RESULT_RECEIVER";
-
-    /**
-     * The intent extra key for indicating whether the bottom sheet should be started directly
-     * on the 'All Options' screen.
-     */
-    public static final String EXTRA_REQ_FOR_ALL_OPTIONS =
-            "android.credentials.ui.extra.REQ_FOR_ALL_OPTIONS";
-
-    /** The intent action for when the enabled Credential Manager providers has been updated. */
-    public static final String CREDMAN_ENABLED_PROVIDERS_UPDATED =
-            "android.credentials.ui.action.CREDMAN_ENABLED_PROVIDERS_UPDATED";
-}
diff --git a/core/java/android/credentials/ui/CreateCredentialProviderData.java b/core/java/android/credentials/ui/CreateCredentialProviderData.java
deleted file mode 100644
index 2508d8e..0000000
--- a/core/java/android/credentials/ui/CreateCredentialProviderData.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.credentials.ui;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.TestApi;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.AnnotationValidations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Per-provider metadata and entries for the create-credential flow.
- *
- * @hide
- */
-@TestApi
-public final class CreateCredentialProviderData extends ProviderData implements Parcelable {
-    @NonNull
-    private final List<Entry> mSaveEntries;
-    @Nullable
-    private final Entry mRemoteEntry;
-
-    public CreateCredentialProviderData(
-            @NonNull String providerFlattenedComponentName, @NonNull List<Entry> saveEntries,
-            @Nullable Entry remoteEntry) {
-        super(providerFlattenedComponentName);
-        mSaveEntries = new ArrayList<>(saveEntries);
-        mRemoteEntry = remoteEntry;
-    }
-
-    @NonNull
-    public List<Entry> getSaveEntries() {
-        return mSaveEntries;
-    }
-
-    @Nullable
-    public Entry getRemoteEntry() {
-        return mRemoteEntry;
-    }
-
-    private CreateCredentialProviderData(@NonNull Parcel in) {
-        super(in);
-
-        List<Entry> credentialEntries = new ArrayList<>();
-        in.readTypedList(credentialEntries, Entry.CREATOR);
-        mSaveEntries = credentialEntries;
-        AnnotationValidations.validate(NonNull.class, null, mSaveEntries);
-
-        Entry remoteEntry = in.readTypedObject(Entry.CREATOR);
-        mRemoteEntry = remoteEntry;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        super.writeToParcel(dest, flags);
-        dest.writeTypedList(mSaveEntries);
-        dest.writeTypedObject(mRemoteEntry, flags);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @NonNull
-    public static final Creator<CreateCredentialProviderData> CREATOR =
-            new Creator<>() {
-                @Override
-                public CreateCredentialProviderData createFromParcel(@NonNull Parcel in) {
-                    return new CreateCredentialProviderData(in);
-                }
-
-                @Override
-                public CreateCredentialProviderData[] newArray(int size) {
-                    return new CreateCredentialProviderData[size];
-                }
-            };
-
-    /**
-     * Builder for {@link CreateCredentialProviderData}.
-     *
-     * @hide
-     */
-    @TestApi
-    public static final class Builder {
-        @NonNull private String mProviderFlattenedComponentName;
-        @NonNull private List<Entry> mSaveEntries = new ArrayList<>();
-        @Nullable private Entry mRemoteEntry = null;
-
-        /** Constructor with required properties. */
-        public Builder(@NonNull String providerFlattenedComponentName) {
-            mProviderFlattenedComponentName = providerFlattenedComponentName;
-        }
-
-        /** Sets the list of save credential entries to be displayed to the user. */
-        @NonNull
-        public Builder setSaveEntries(@NonNull List<Entry> credentialEntries) {
-            mSaveEntries = credentialEntries;
-            return this;
-        }
-
-        /** Sets the remote entry of the provider. */
-        @NonNull
-        public Builder setRemoteEntry(@Nullable Entry remoteEntry) {
-            mRemoteEntry = remoteEntry;
-            return this;
-        }
-
-        /** Builds a {@link CreateCredentialProviderData}. */
-        @NonNull
-        public CreateCredentialProviderData build() {
-            return new CreateCredentialProviderData(mProviderFlattenedComponentName,
-                    mSaveEntries, mRemoteEntry);
-        }
-    }
-}
diff --git a/core/java/android/credentials/ui/DisabledProviderData.java b/core/java/android/credentials/ui/DisabledProviderData.java
deleted file mode 100644
index c266fd5..0000000
--- a/core/java/android/credentials/ui/DisabledProviderData.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.credentials.ui;
-
-import android.annotation.NonNull;
-import android.annotation.TestApi;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Metadata of a disabled provider.
- *
- * @hide
- */
-@TestApi
-public final class DisabledProviderData extends ProviderData implements Parcelable {
-
-    public DisabledProviderData(
-            @NonNull String providerFlattenedComponentName) {
-        super(providerFlattenedComponentName);
-    }
-
-    private DisabledProviderData(@NonNull Parcel in) {
-        super(in);
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        super.writeToParcel(dest, flags);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    public static final @NonNull Creator<DisabledProviderData> CREATOR = new Creator<>() {
-                @Override
-                public DisabledProviderData createFromParcel(@NonNull Parcel in) {
-                    return new DisabledProviderData(in);
-                }
-
-                @Override
-                public DisabledProviderData[] newArray(int size) {
-                    return new DisabledProviderData[size];
-                }
-    };
-}
diff --git a/core/java/android/credentials/ui/Entry.java b/core/java/android/credentials/ui/Entry.java
deleted file mode 100644
index 55f2a3e..0000000
--- a/core/java/android/credentials/ui/Entry.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.credentials.ui;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.annotation.TestApi;
-import android.app.PendingIntent;
-import android.app.slice.Slice;
-import android.content.Intent;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.AnnotationValidations;
-
-/**
- * A credential, create, or action entry to be rendered.
- *
- * @hide
- */
-@TestApi
-public final class Entry implements Parcelable {
-    @NonNull private final String mKey;
-    @NonNull private final String mSubkey;
-    @Nullable private PendingIntent mPendingIntent;
-    @Nullable private Intent mFrameworkExtrasIntent;
-
-    @NonNull
-    private final Slice mSlice;
-
-    private Entry(@NonNull Parcel in) {
-        String key = in.readString8();
-        String subkey = in.readString8();
-        Slice slice = in.readTypedObject(Slice.CREATOR);
-
-        mKey = key;
-        AnnotationValidations.validate(NonNull.class, null, mKey);
-        mSubkey = subkey;
-        AnnotationValidations.validate(NonNull.class, null, mSubkey);
-        mSlice = slice;
-        AnnotationValidations.validate(NonNull.class, null, mSlice);
-        mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
-        mFrameworkExtrasIntent = in.readTypedObject(Intent.CREATOR);
-    }
-
-    /** Constructor to be used for an entry that does not require further activities
-     * to be invoked when selected.
-     */
-    public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice) {
-        mKey = key;
-        mSubkey = subkey;
-        mSlice = slice;
-    }
-
-    /** Constructor to be used for an entry that requires a pending intent to be invoked
-     * when clicked.
-     */
-    public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
-            @NonNull Intent intent) {
-        this(key, subkey, slice);
-        mFrameworkExtrasIntent = intent;
-    }
-
-    /**
-    * Returns the identifier of this entry that's unique within the context of the CredentialManager
-    * request.
-    */
-    @NonNull
-    public String getKey() {
-        return mKey;
-    }
-
-    /**
-     * Returns the sub-identifier of this entry that's unique within the context of the {@code key}.
-     */
-    @NonNull
-    public String getSubkey() {
-        return mSubkey;
-    }
-
-    /**
-    * Returns the Slice to be rendered.
-    */
-    @NonNull
-    public Slice getSlice() {
-        return mSlice;
-    }
-
-    @Nullable
-    public PendingIntent getPendingIntent() {
-        return mPendingIntent;
-    }
-
-    @Nullable
-    @SuppressLint("IntentBuilderName") // Not building a new intent.
-    public Intent getFrameworkExtrasIntent() {
-        return mFrameworkExtrasIntent;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeString8(mKey);
-        dest.writeString8(mSubkey);
-        dest.writeTypedObject(mSlice, flags);
-        dest.writeTypedObject(mPendingIntent, flags);
-        dest.writeTypedObject(mFrameworkExtrasIntent, flags);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    public static final @NonNull Creator<Entry> CREATOR = new Creator<Entry>() {
-        @Override
-        public Entry createFromParcel(@NonNull Parcel in) {
-            return new Entry(in);
-        }
-
-        @Override
-        public Entry[] newArray(int size) {
-            return new Entry[size];
-        }
-    };
-}
diff --git a/core/java/android/credentials/ui/GetCredentialProviderData.java b/core/java/android/credentials/ui/GetCredentialProviderData.java
deleted file mode 100644
index 181475c..0000000
--- a/core/java/android/credentials/ui/GetCredentialProviderData.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.credentials.ui;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.TestApi;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.AnnotationValidations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Per-provider metadata and entries for the get-credential flow.
- *
- * @hide
- */
-@TestApi
-public final class GetCredentialProviderData extends ProviderData implements Parcelable {
-    @NonNull
-    private final List<Entry> mCredentialEntries;
-    @NonNull
-    private final List<Entry> mActionChips;
-    @NonNull
-    private final List<AuthenticationEntry> mAuthenticationEntries;
-    @Nullable
-    private final Entry mRemoteEntry;
-
-    public GetCredentialProviderData(
-            @NonNull String providerFlattenedComponentName, @NonNull List<Entry> credentialEntries,
-            @NonNull List<Entry> actionChips,
-            @NonNull List<AuthenticationEntry> authenticationEntries,
-            @Nullable Entry remoteEntry) {
-        super(providerFlattenedComponentName);
-        mCredentialEntries = new ArrayList<>(credentialEntries);
-        mActionChips = new ArrayList<>(actionChips);
-        mAuthenticationEntries = new ArrayList<>(authenticationEntries);
-        mRemoteEntry = remoteEntry;
-    }
-
-    @NonNull
-    public List<Entry> getCredentialEntries() {
-        return mCredentialEntries;
-    }
-
-    @NonNull
-    public List<Entry> getActionChips() {
-        return mActionChips;
-    }
-
-    @NonNull
-    public List<AuthenticationEntry> getAuthenticationEntries() {
-        return mAuthenticationEntries;
-    }
-
-    @Nullable
-    public Entry getRemoteEntry() {
-        return mRemoteEntry;
-    }
-
-    private GetCredentialProviderData(@NonNull Parcel in) {
-        super(in);
-
-        List<Entry> credentialEntries = new ArrayList<>();
-        in.readTypedList(credentialEntries, Entry.CREATOR);
-        mCredentialEntries = credentialEntries;
-        AnnotationValidations.validate(NonNull.class, null, mCredentialEntries);
-
-        List<Entry> actionChips  = new ArrayList<>();
-        in.readTypedList(actionChips, Entry.CREATOR);
-        mActionChips = actionChips;
-        AnnotationValidations.validate(NonNull.class, null, mActionChips);
-
-        List<AuthenticationEntry> authenticationEntries  = new ArrayList<>();
-        in.readTypedList(authenticationEntries, AuthenticationEntry.CREATOR);
-        mAuthenticationEntries = authenticationEntries;
-        AnnotationValidations.validate(NonNull.class, null, mAuthenticationEntries);
-
-        Entry remoteEntry = in.readTypedObject(Entry.CREATOR);
-        mRemoteEntry = remoteEntry;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        super.writeToParcel(dest, flags);
-        dest.writeTypedList(mCredentialEntries);
-        dest.writeTypedList(mActionChips);
-        dest.writeTypedList(mAuthenticationEntries);
-        dest.writeTypedObject(mRemoteEntry, flags);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    public static final @NonNull Creator<GetCredentialProviderData> CREATOR =
-            new Creator<GetCredentialProviderData>() {
-        @Override
-        public GetCredentialProviderData createFromParcel(@NonNull Parcel in) {
-            return new GetCredentialProviderData(in);
-        }
-
-        @Override
-        public GetCredentialProviderData[] newArray(int size) {
-            return new GetCredentialProviderData[size];
-        }
-    };
-
-    /**
-     * Builder for {@link GetCredentialProviderData}.
-     *
-     * @hide
-     */
-    @TestApi
-    public static final class Builder {
-        @NonNull private String mProviderFlattenedComponentName;
-        @NonNull private List<Entry> mCredentialEntries = new ArrayList<>();
-        @NonNull private List<Entry> mActionChips = new ArrayList<>();
-        @NonNull private List<AuthenticationEntry> mAuthenticationEntries = new ArrayList<>();
-        @Nullable private Entry mRemoteEntry = null;
-
-        /** Constructor with required properties. */
-        public Builder(@NonNull String providerFlattenedComponentName) {
-            mProviderFlattenedComponentName = providerFlattenedComponentName;
-        }
-
-        /** Sets the list of save / get credential entries to be displayed to the user. */
-        @NonNull
-        public Builder setCredentialEntries(@NonNull List<Entry> credentialEntries) {
-            mCredentialEntries = credentialEntries;
-            return this;
-        }
-
-        /** Sets the list of action chips to be displayed to the user. */
-        @NonNull
-        public Builder setActionChips(@NonNull List<Entry> actionChips) {
-            mActionChips = actionChips;
-            return this;
-        }
-
-        /** Sets the authentication entry to be displayed to the user. */
-        @NonNull
-        public Builder setAuthenticationEntries(
-                @NonNull List<AuthenticationEntry> authenticationEntry) {
-            mAuthenticationEntries = authenticationEntry;
-            return this;
-        }
-
-        /** Sets the remote entry to be displayed to the user. */
-        @NonNull
-        public Builder setRemoteEntry(@Nullable Entry remoteEntry) {
-            mRemoteEntry = remoteEntry;
-            return this;
-        }
-
-        /** Builds a {@link GetCredentialProviderData}. */
-        @NonNull
-        public GetCredentialProviderData build() {
-            return new GetCredentialProviderData(mProviderFlattenedComponentName,
-                    mCredentialEntries, mActionChips, mAuthenticationEntries, mRemoteEntry);
-        }
-    }
-}
diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java
deleted file mode 100644
index 49321d5..0000000
--- a/core/java/android/credentials/ui/IntentFactory.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.credentials.ui;
-
-import android.annotation.NonNull;
-import android.annotation.SuppressLint;
-import android.annotation.TestApi;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.ResultReceiver;
-
-import java.util.ArrayList;
-
-/**
- * Helpers for generating the intents and related extras parameters to launch the UI activities.
- *
- * @hide
- */
-@TestApi
-public class IntentFactory {
-
-    /**
-     * Generate a new launch intent to the Credential Selector UI.
-     *
-     * @hide
-     */
-    @NonNull
-    public static Intent createCredentialSelectorIntent(
-            @NonNull RequestInfo requestInfo,
-            @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
-            @NonNull
-            ArrayList<ProviderData> enabledProviderDataList,
-            @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
-            @NonNull
-            ArrayList<DisabledProviderData> disabledProviderDataList,
-            @NonNull ResultReceiver resultReceiver,
-            boolean isRequestForAllOptions) {
-
-        Intent intent = createCredentialSelectorIntent(requestInfo, enabledProviderDataList,
-                disabledProviderDataList, resultReceiver);
-        intent.putExtra(Constants.EXTRA_REQ_FOR_ALL_OPTIONS, isRequestForAllOptions);
-
-        return intent;
-    }
-
-    /** Generate a new launch intent to the Credential Selector UI. */
-    @NonNull
-    public static Intent createCredentialSelectorIntent(
-            @NonNull RequestInfo requestInfo,
-            @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
-                    @NonNull
-                    ArrayList<ProviderData> enabledProviderDataList,
-            @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
-                    @NonNull
-                    ArrayList<DisabledProviderData> disabledProviderDataList,
-            @NonNull ResultReceiver resultReceiver) {
-        Intent intent = new Intent();
-        ComponentName componentName =
-                ComponentName.unflattenFromString(
-                        Resources.getSystem()
-                                .getString(
-                                        com.android.internal.R.string
-                                                .config_credentialManagerDialogComponent));
-        intent.setComponent(componentName);
-
-        intent.putParcelableArrayListExtra(
-                ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList);
-        intent.putParcelableArrayListExtra(
-                ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList);
-        intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo);
-        intent.putExtra(
-                Constants.EXTRA_RESULT_RECEIVER, toIpcFriendlyResultReceiver(resultReceiver));
-
-        return intent;
-    }
-
-    /**
-     * Creates an Intent that cancels any UI matching the given request token id.
-     *
-     * @hide
-     */
-    @NonNull
-    public static Intent createCancelUiIntent(@NonNull IBinder requestToken,
-            boolean shouldShowCancellationUi, @NonNull String appPackageName) {
-        Intent intent = new Intent();
-        ComponentName componentName =
-                ComponentName.unflattenFromString(
-                        Resources.getSystem()
-                                .getString(
-                                        com.android.internal.R.string
-                                                .config_credentialManagerDialogComponent));
-        intent.setComponent(componentName);
-        intent.putExtra(CancelUiRequest.EXTRA_CANCEL_UI_REQUEST,
-                new CancelUiRequest(requestToken, shouldShowCancellationUi, appPackageName));
-        return intent;
-    }
-
-    /**
-     * Notify the UI that providers have been enabled/disabled.
-     *
-     * @hide
-     */
-    @NonNull
-    public static Intent createProviderUpdateIntent() {
-        Intent intent = new Intent();
-        ComponentName componentName =
-                ComponentName.unflattenFromString(
-                        Resources.getSystem()
-                                .getString(
-                                        com.android.internal.R.string
-                                                .config_credentialManagerReceiverComponent));
-        intent.setComponent(componentName);
-        intent.setAction(Constants.CREDMAN_ENABLED_PROVIDERS_UPDATED);
-        return intent;
-    }
-
-    /**
-     * Convert an instance of a "locally-defined" ResultReceiver to an instance of {@link
-     * android.os.ResultReceiver} itself, which the receiving process will be able to unmarshall.
-     */
-    private static <T extends ResultReceiver> ResultReceiver toIpcFriendlyResultReceiver(
-            T resultReceiver) {
-        final Parcel parcel = Parcel.obtain();
-        resultReceiver.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-
-        final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
-        parcel.recycle();
-
-        return ipcFriendly;
-    }
-
-    private IntentFactory() {}
-}
diff --git a/core/java/android/credentials/ui/ProviderData.java b/core/java/android/credentials/ui/ProviderData.java
deleted file mode 100644
index 1e5aa24..0000000
--- a/core/java/android/credentials/ui/ProviderData.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.credentials.ui;
-
-import android.annotation.NonNull;
-import android.annotation.SuppressLint;
-import android.annotation.TestApi;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.AnnotationValidations;
-
-/**
- * Super class for data structures that hold metadata and credential entries for a single provider.
- *
- * @hide
- */
-@TestApi
-@SuppressLint({"ParcelCreator", "ParcelNotFinal"})
-public abstract class ProviderData implements Parcelable {
-
-    /**
-     * The intent extra key for the list of {@code ProviderData} from active providers when
-     * launching the UX activities.
-     */
-    public static final String EXTRA_ENABLED_PROVIDER_DATA_LIST =
-            "android.credentials.ui.extra.ENABLED_PROVIDER_DATA_LIST";
-    /**
-     * The intent extra key for the list of {@code ProviderData} from disabled providers when
-     * launching the UX activities.
-     */
-    public static final String EXTRA_DISABLED_PROVIDER_DATA_LIST =
-            "android.credentials.ui.extra.DISABLED_PROVIDER_DATA_LIST";
-
-    @NonNull
-    private final String mProviderFlattenedComponentName;
-
-    public ProviderData(
-            @NonNull String providerFlattenedComponentName) {
-        mProviderFlattenedComponentName = providerFlattenedComponentName;
-    }
-
-    /**
-     * Returns provider component name.
-     * It also serves as the unique identifier for this provider.
-     */
-    @NonNull
-    public String getProviderFlattenedComponentName() {
-        return mProviderFlattenedComponentName;
-    }
-
-    protected ProviderData(@NonNull Parcel in) {
-        String providerFlattenedComponentName = in.readString8();
-        mProviderFlattenedComponentName = providerFlattenedComponentName;
-        AnnotationValidations.validate(NonNull.class, null, mProviderFlattenedComponentName);
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeString8(mProviderFlattenedComponentName);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-}
diff --git a/core/java/android/credentials/ui/ProviderDialogResult.java b/core/java/android/credentials/ui/ProviderDialogResult.java
deleted file mode 100644
index 53f1864..0000000
--- a/core/java/android/credentials/ui/ProviderDialogResult.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.credentials.ui;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.AnnotationValidations;
-
-/**
- * Result data matching {@link BaseDialogResult#RESULT_CODE_PROVIDER_ENABLED}, or {@link
- * BaseDialogResult#RESULT_CODE_DEFAULT_PROVIDER_CHANGED}.
- *
- * @hide
- */
-public final class ProviderDialogResult extends BaseDialogResult implements Parcelable {
-    /** Parses and returns a ProviderDialogResult from the given resultData. */
-    @Nullable
-    public static ProviderDialogResult fromResultData(@NonNull Bundle resultData) {
-        return resultData.getParcelable(EXTRA_PROVIDER_RESULT, ProviderDialogResult.class);
-    }
-
-    /**
-     * Used for the UX to construct the {@code resultData Bundle} to send via the {@code
-     *  ResultReceiver}.
-     */
-    public static void addToBundle(
-            @NonNull ProviderDialogResult result, @NonNull Bundle bundle) {
-        bundle.putParcelable(EXTRA_PROVIDER_RESULT, result);
-    }
-
-    /**
-     * The intent extra key for the {@code ProviderDialogResult} object when the credential
-     * selector activity finishes.
-     */
-    private static final String EXTRA_PROVIDER_RESULT =
-            "android.credentials.ui.extra.PROVIDER_RESULT";
-
-    @NonNull
-    private final String mProviderId;
-
-    public ProviderDialogResult(@NonNull IBinder requestToken, @NonNull String providerId) {
-        super(requestToken);
-        mProviderId = providerId;
-    }
-
-    @NonNull
-    public String getProviderId() {
-        return mProviderId;
-    }
-
-    protected ProviderDialogResult(@NonNull Parcel in) {
-        super(in);
-        String providerId = in.readString8();
-        mProviderId = providerId;
-        AnnotationValidations.validate(NonNull.class, null, mProviderId);
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        super.writeToParcel(dest, flags);
-        dest.writeString8(mProviderId);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    public static final @NonNull Creator<ProviderDialogResult> CREATOR =
-            new Creator<ProviderDialogResult>() {
-        @Override
-        public ProviderDialogResult createFromParcel(@NonNull Parcel in) {
-            return new ProviderDialogResult(in);
-        }
-
-        @Override
-        public ProviderDialogResult[] newArray(int size) {
-            return new ProviderDialogResult[size];
-        }
-    };
-}
diff --git a/core/java/android/credentials/ui/ProviderPendingIntentResponse.java b/core/java/android/credentials/ui/ProviderPendingIntentResponse.java
deleted file mode 100644
index 47936c4..0000000
--- a/core/java/android/credentials/ui/ProviderPendingIntentResponse.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.credentials.ui;
-
-import android.annotation.Nullable;
-import android.content.Intent;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import androidx.annotation.NonNull;
-
-/**
- * Response from a provider's pending intent
- *
- * @hide
- */
-public final class ProviderPendingIntentResponse implements Parcelable {
-    private final int mResultCode;
-    @Nullable
-    private final Intent mResultData;
-
-    public ProviderPendingIntentResponse(int resultCode, @Nullable Intent resultData) {
-        mResultCode = resultCode;
-        mResultData = resultData;
-    }
-
-    protected ProviderPendingIntentResponse(Parcel in) {
-        mResultCode = in.readInt();
-        mResultData = in.readTypedObject(Intent.CREATOR);
-    }
-
-    public static final Creator<ProviderPendingIntentResponse> CREATOR =
-            new Creator<ProviderPendingIntentResponse>() {
-                @Override
-                public ProviderPendingIntentResponse createFromParcel(Parcel in) {
-                    return new ProviderPendingIntentResponse(in);
-                }
-
-                @Override
-                public ProviderPendingIntentResponse[] newArray(int size) {
-                    return new ProviderPendingIntentResponse[size];
-                }
-            };
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(mResultCode);
-        dest.writeTypedObject(mResultData, flags);
-    }
-
-    /** Returns the result code associated with this pending intent activity result. */
-    public int getResultCode() {
-        return mResultCode;
-    }
-
-    /** Returns the result data associated with this pending intent activity result. */
-    @NonNull public Intent getResultData() {
-        return mResultData;
-    }
-}
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
deleted file mode 100644
index 4fedc83..0000000
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.credentials.ui;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.StringDef;
-import android.annotation.TestApi;
-import android.credentials.CreateCredentialRequest;
-import android.credentials.GetCredentialRequest;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.AnnotationValidations;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Contains information about the request that initiated this UX flow.
- *
- * @hide
- */
-@TestApi
-public final class RequestInfo implements Parcelable {
-
-    /**
-     * The intent extra key for the {@code RequestInfo} object when launching the UX
-     * activities.
-     */
-    @NonNull public static final String EXTRA_REQUEST_INFO =
-            "android.credentials.ui.extra.REQUEST_INFO";
-
-    /** Type value for any request that does not require UI. */
-    @NonNull public static final String TYPE_UNDEFINED = "android.credentials.ui.TYPE_UNDEFINED";
-    /** Type value for a getCredential request. */
-    @NonNull public static final String TYPE_GET = "android.credentials.ui.TYPE_GET";
-    /** Type value for a getCredential request that utilizes the credential registry.
-     *
-     * @hide
-     **/
-    @NonNull public static final String TYPE_GET_VIA_REGISTRY =
-            "android.credentials.ui.TYPE_GET_VIA_REGISTRY";
-    /** Type value for a createCredential request. */
-    @NonNull public static final String TYPE_CREATE = "android.credentials.ui.TYPE_CREATE";
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @StringDef(value = { TYPE_GET, TYPE_CREATE })
-    public @interface RequestType {}
-
-    @NonNull
-    private final IBinder mToken;
-
-    @Nullable
-    private final CreateCredentialRequest mCreateCredentialRequest;
-
-    @NonNull
-    private final List<String> mDefaultProviderIds;
-
-    @Nullable
-    private final GetCredentialRequest mGetCredentialRequest;
-
-    @NonNull
-    @RequestType
-    private final String mType;
-
-    @NonNull
-    private final String mAppPackageName;
-
-    private final boolean mHasPermissionToOverrideDefault;
-
-    /** Creates new {@code RequestInfo} for a create-credential flow. */
-    @NonNull
-    public static RequestInfo newCreateRequestInfo(
-            @NonNull IBinder token, @NonNull CreateCredentialRequest createCredentialRequest,
-            @NonNull String appPackageName) {
-        return new RequestInfo(
-                token, TYPE_CREATE, appPackageName, createCredentialRequest, null,
-                /*hasPermissionToOverrideDefault=*/ false,
-                /*defaultProviderIds=*/ new ArrayList<>());
-    }
-
-    /**
-     * Creates new {@code RequestInfo} for a create-credential flow.
-     *
-     * @hide
-     */
-    @NonNull
-    public static RequestInfo newCreateRequestInfo(
-            @NonNull IBinder token, @NonNull CreateCredentialRequest createCredentialRequest,
-            @NonNull String appPackageName, boolean hasPermissionToOverrideDefault,
-            @NonNull List<String> defaultProviderIds) {
-        return new RequestInfo(
-                token, TYPE_CREATE, appPackageName, createCredentialRequest, null,
-                hasPermissionToOverrideDefault, defaultProviderIds);
-    }
-
-    /**
-     * Creates new {@code RequestInfo} for a get-credential flow.
-     *
-     * @hide
-     */
-    @NonNull
-    public static RequestInfo newGetRequestInfo(
-            @NonNull IBinder token, @NonNull GetCredentialRequest getCredentialRequest,
-            @NonNull String appPackageName, boolean hasPermissionToOverrideDefault) {
-        return new RequestInfo(
-                token, TYPE_GET, appPackageName, null, getCredentialRequest,
-                hasPermissionToOverrideDefault,
-                /*defaultProviderIds=*/ new ArrayList<>());
-    }
-
-    /** Creates new {@code RequestInfo} for a get-credential flow. */
-    @NonNull
-    public static RequestInfo newGetRequestInfo(
-            @NonNull IBinder token, @NonNull GetCredentialRequest getCredentialRequest,
-            @NonNull String appPackageName) {
-        return new RequestInfo(
-                token, TYPE_GET, appPackageName, null, getCredentialRequest,
-                /*hasPermissionToOverrideDefault=*/ false,
-                /*defaultProviderIds=*/ new ArrayList<>());
-    }
-
-
-    /**
-     * Returns whether the calling package has the permission
-     *
-     * @hide
-     */
-    public boolean hasPermissionToOverrideDefault() {
-        return mHasPermissionToOverrideDefault;
-    }
-
-    /** Returns the request token matching the user request. */
-    @NonNull
-    public IBinder getToken() {
-        return mToken;
-    }
-
-    /** Returns the request type. */
-    @NonNull
-    @RequestType
-    public String getType() {
-        return mType;
-    }
-
-    /** Returns the display name of the app that made this request. */
-    @NonNull
-    public String getAppPackageName() {
-        return mAppPackageName;
-    }
-
-    /**
-     * Returns the non-null CreateCredentialRequest when the type of the request is {@link
-     * #TYPE_CREATE}, or null otherwise.
-     */
-    @Nullable
-    public CreateCredentialRequest getCreateCredentialRequest() {
-        return mCreateCredentialRequest;
-    }
-
-    /**
-     * Returns default provider identifier (flattened component name) configured from the user
-     * settings.
-     *
-     * Will only be possibly non-empty for the create use case. Not meaningful for the sign-in use
-     * case.
-     *
-     * @hide
-     */
-    @NonNull
-    public List<String> getDefaultProviderIds() {
-        return mDefaultProviderIds;
-    }
-
-    /**
-     * Returns the non-null GetCredentialRequest when the type of the request is {@link
-     * #TYPE_GET}, or null otherwise.
-     */
-    @Nullable
-    public GetCredentialRequest getGetCredentialRequest() {
-        return mGetCredentialRequest;
-    }
-
-    private RequestInfo(@NonNull IBinder token, @NonNull @RequestType String type,
-            @NonNull String appPackageName,
-            @Nullable CreateCredentialRequest createCredentialRequest,
-            @Nullable GetCredentialRequest getCredentialRequest,
-            boolean hasPermissionToOverrideDefault,
-            @NonNull List<String> defaultProviderIds) {
-        mToken = token;
-        mType = type;
-        mAppPackageName = appPackageName;
-        mCreateCredentialRequest = createCredentialRequest;
-        mGetCredentialRequest = getCredentialRequest;
-        mHasPermissionToOverrideDefault = hasPermissionToOverrideDefault;
-        mDefaultProviderIds = defaultProviderIds == null ? new ArrayList<>() : defaultProviderIds;
-    }
-
-    private RequestInfo(@NonNull Parcel in) {
-        IBinder token = in.readStrongBinder();
-        String type = in.readString8();
-        String appPackageName = in.readString8();
-        CreateCredentialRequest createCredentialRequest =
-                in.readTypedObject(CreateCredentialRequest.CREATOR);
-        GetCredentialRequest getCredentialRequest =
-                in.readTypedObject(GetCredentialRequest.CREATOR);
-
-        mToken = token;
-        AnnotationValidations.validate(NonNull.class, null, mToken);
-        mType = type;
-        AnnotationValidations.validate(NonNull.class, null, mType);
-        mAppPackageName = appPackageName;
-        AnnotationValidations.validate(NonNull.class, null, mAppPackageName);
-        mCreateCredentialRequest = createCredentialRequest;
-        mGetCredentialRequest = getCredentialRequest;
-        mHasPermissionToOverrideDefault = in.readBoolean();
-        mDefaultProviderIds = in.createStringArrayList();
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeStrongBinder(mToken);
-        dest.writeString8(mType);
-        dest.writeString8(mAppPackageName);
-        dest.writeTypedObject(mCreateCredentialRequest, flags);
-        dest.writeTypedObject(mGetCredentialRequest, flags);
-        dest.writeBoolean(mHasPermissionToOverrideDefault);
-        dest.writeStringList(mDefaultProviderIds);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @NonNull public static final Creator<RequestInfo> CREATOR = new Creator<>() {
-        @Override
-        public RequestInfo createFromParcel(@NonNull Parcel in) {
-            return new RequestInfo(in);
-        }
-
-        @Override
-        public RequestInfo[] newArray(int size) {
-            return new RequestInfo[size];
-        }
-    };
-}
diff --git a/core/java/android/credentials/ui/UserSelectionDialogResult.java b/core/java/android/credentials/ui/UserSelectionDialogResult.java
deleted file mode 100644
index 3089bf6..0000000
--- a/core/java/android/credentials/ui/UserSelectionDialogResult.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.credentials.ui;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.AnnotationValidations;
-
-/**
- * Result data matching {@link BaseDialogResult#RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION}.
- *
- * @hide
- */
-public final class UserSelectionDialogResult extends BaseDialogResult implements Parcelable {
-    /** Parses and returns a UserSelectionDialogResult from the given resultData. */
-    @Nullable
-    public static UserSelectionDialogResult fromResultData(@NonNull Bundle resultData) {
-        return resultData.getParcelable(
-            EXTRA_USER_SELECTION_RESULT, UserSelectionDialogResult.class);
-    }
-
-    /**
-     * Used for the UX to construct the {@code resultData Bundle} to send via the {@code
-     *  ResultReceiver}.
-     */
-    public static void addToBundle(
-            @NonNull UserSelectionDialogResult result, @NonNull Bundle bundle) {
-        bundle.putParcelable(EXTRA_USER_SELECTION_RESULT, result);
-    }
-
-    /**
-     * The intent extra key for the {@code UserSelectionDialogResult} object when the credential
-     * selector activity finishes.
-     */
-    private static final String EXTRA_USER_SELECTION_RESULT =
-            "android.credentials.ui.extra.USER_SELECTION_RESULT";
-
-    @NonNull private final String mProviderId;
-    @NonNull private final String mEntryKey;
-    @NonNull private final String mEntrySubkey;
-    @Nullable private ProviderPendingIntentResponse mProviderPendingIntentResponse;
-
-    public UserSelectionDialogResult(
-            @Nullable IBinder requestToken, @NonNull String providerId,
-            @NonNull String entryKey, @NonNull String entrySubkey) {
-        super(requestToken);
-        mProviderId = providerId;
-        mEntryKey = entryKey;
-        mEntrySubkey = entrySubkey;
-    }
-
-    public UserSelectionDialogResult(
-            @Nullable IBinder requestToken, @NonNull String providerId,
-            @NonNull String entryKey, @NonNull String entrySubkey,
-            @Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
-        super(requestToken);
-        mProviderId = providerId;
-        mEntryKey = entryKey;
-        mEntrySubkey = entrySubkey;
-        mProviderPendingIntentResponse = providerPendingIntentResponse;
-    }
-
-    /** Returns provider package name whose entry was selected by the user. */
-    @NonNull
-    public String getProviderId() {
-        return mProviderId;
-    }
-
-    /** Returns the key of the visual entry that the user selected. */
-    @NonNull
-    public String getEntryKey() {
-        return mEntryKey;
-    }
-
-    /** Returns the subkey of the visual entry that the user selected. */
-    @NonNull
-    public String getEntrySubkey() {
-        return mEntrySubkey;
-    }
-
-    /** Returns the pending intent response from the provider. */
-    @Nullable
-    public ProviderPendingIntentResponse getPendingIntentProviderResponse() {
-        return mProviderPendingIntentResponse;
-    }
-
-    protected UserSelectionDialogResult(@NonNull Parcel in) {
-        super(in);
-        String providerId = in.readString8();
-        String entryKey = in.readString8();
-        String entrySubkey = in.readString8();
-
-        mProviderId = providerId;
-        AnnotationValidations.validate(NonNull.class, null, mProviderId);
-        mEntryKey = entryKey;
-        AnnotationValidations.validate(NonNull.class, null, mEntryKey);
-        mEntrySubkey = entrySubkey;
-        AnnotationValidations.validate(NonNull.class, null, mEntrySubkey);
-        mProviderPendingIntentResponse = in.readTypedObject(ProviderPendingIntentResponse.CREATOR);
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        super.writeToParcel(dest, flags);
-        dest.writeString8(mProviderId);
-        dest.writeString8(mEntryKey);
-        dest.writeString8(mEntrySubkey);
-        dest.writeTypedObject(mProviderPendingIntentResponse, flags);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    public static final @NonNull Creator<UserSelectionDialogResult> CREATOR =
-            new Creator<UserSelectionDialogResult>() {
-        @Override
-        public UserSelectionDialogResult createFromParcel(@NonNull Parcel in) {
-            return new UserSelectionDialogResult(in);
-        }
-
-        @Override
-        public UserSelectionDialogResult[] newArray(int size) {
-            return new UserSelectionDialogResult[size];
-        }
-    };
-}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 8a4f678..35ae3c9 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -40,12 +40,15 @@
 import android.os.OperationCanceledException;
 import android.os.SystemProperties;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Printer;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
 import dalvik.annotation.optimization.NeverCompile;
@@ -103,8 +106,14 @@
     // Stores reference to all databases opened in the current process.
     // (The referent Object is not used at this time.)
     // INVARIANT: Guarded by sActiveDatabases.
+    @GuardedBy("sActiveDatabases")
     private static WeakHashMap<SQLiteDatabase, Object> sActiveDatabases = new WeakHashMap<>();
 
+    // Tracks which database files are currently open.  If a database file is opened more than
+    // once at any given moment, the associated databases are marked as "concurrent".
+    @GuardedBy("sActiveDatabases")
+    private static final OpenTracker sOpenTracker = new OpenTracker();
+
     // Thread-local for database sessions that belong to this database.
     // Each thread has its own database session.
     // INVARIANT: Immutable.
@@ -510,6 +519,7 @@
 
     private void dispose(boolean finalized) {
         final SQLiteConnectionPool pool;
+        final String path;
         synchronized (mLock) {
             if (mCloseGuardLocked != null) {
                 if (finalized) {
@@ -520,10 +530,12 @@
 
             pool = mConnectionPoolLocked;
             mConnectionPoolLocked = null;
+            path = isInMemoryDatabase() ? null : getPath();
         }
 
         if (!finalized) {
             synchronized (sActiveDatabases) {
+                sOpenTracker.close(path);
                 sActiveDatabases.remove(this);
             }
 
@@ -1132,6 +1144,74 @@
         }
     }
 
+    /**
+     * Track the number of times a database file has been opened.  There is a primary connection
+     * associated with every open database, and these can contend with each other, leading to
+     * unexpected SQLiteDatabaseLockedException exceptions.  The tracking here is only advisory:
+     * multiply-opened databases are logged but no other action is taken.
+     *
+     * This class is not thread-safe.
+     */
+    private static class OpenTracker {
+        // The list of currently-open databases.  This maps the database file to the number of
+        // currently-active opens.
+        private final ArrayMap<String, Integer> mOpens = new ArrayMap<>();
+
+        // The maximum number of concurrently open database paths that will be stored.  Once this
+        // many paths have been recorded, further paths are logged but not saved.
+        private static final int MAX_RECORDED_PATHS = 20;
+
+        // The list of databases that were ever concurrently opened.
+        private final ArraySet<String> mConcurrent = new ArraySet<>();
+
+        /** Return the canonical path.  On error, just return the input path. */
+        private static String normalize(String path) {
+            try {
+                return new File(path).toPath().toRealPath().toString();
+            } catch (Exception e) {
+                // If there is an IO or security exception, just continue, using the input path.
+                return path;
+            }
+        }
+
+        /** Return true if the path is currently open in another SQLiteDatabase instance. */
+        void open(@Nullable String path) {
+            if (path == null) return;
+            path = normalize(path);
+
+            Integer count = mOpens.get(path);
+            if (count == null || count == 0) {
+                mOpens.put(path, 1);
+                return;
+            } else {
+                mOpens.put(path, count + 1);
+                if (mConcurrent.size() < MAX_RECORDED_PATHS) {
+                    mConcurrent.add(path);
+                }
+                Log.w(TAG, "multiple primary connections on " + path);
+                return;
+            }
+        }
+
+        void close(@Nullable String path) {
+            if (path == null) return;
+            path = normalize(path);
+            Integer count = mOpens.get(path);
+            if (count == null || count <= 0) {
+                Log.e(TAG, "open database counting failure on " + path);
+            } else if (count == 1) {
+                // Implicitly set the count to zero, and make mOpens smaller.
+                mOpens.remove(path);
+            } else {
+                mOpens.put(path, count - 1);
+            }
+        }
+
+        ArraySet<String> getConcurrentDatabasePaths() {
+            return new ArraySet<>(mConcurrent);
+        }
+    }
+
     private void open() {
         try {
             try {
@@ -1153,14 +1233,17 @@
     }
 
     private void openInner() {
+        final String path;
         synchronized (mLock) {
             assert mConnectionPoolLocked == null;
             mConnectionPoolLocked = SQLiteConnectionPool.open(mConfigurationLocked);
             mCloseGuardLocked.open("close");
+            path = isInMemoryDatabase() ? null : getPath();
         }
 
         synchronized (sActiveDatabases) {
             sActiveDatabases.put(this, null);
+            sOpenTracker.open(path);
         }
     }
 
@@ -2345,6 +2428,17 @@
     }
 
     /**
+     * Return list of databases that have been concurrently opened.
+     * @hide
+     */
+    @VisibleForTesting
+    public static ArraySet<String> getConcurrentDatabasePaths() {
+        synchronized (sActiveDatabases) {
+            return sOpenTracker.getConcurrentDatabasePaths();
+        }
+    }
+
+    /**
      * Returns true if the new version code is greater than the current database version.
      *
      * @param newVersion The new version code.
@@ -2766,6 +2860,19 @@
                 dumpDatabaseDirectory(printer, new File(dir), isSystem);
             }
         }
+
+        // Dump concurrently-opened database files, if any
+        final ArraySet<String> concurrent;
+        synchronized (sActiveDatabases) {
+            concurrent = sOpenTracker.getConcurrentDatabasePaths();
+        }
+        if (concurrent.size() > 0) {
+            printer.println("");
+            printer.println("Concurrently opened database files");
+            for (String f : concurrent) {
+                printer.println("  " + f);
+            }
+        }
     }
 
     private static void dumpDatabaseDirectory(Printer pw, File dir, boolean isSystem) {
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index f5b3a7b..ce0f9f59 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -67,8 +67,8 @@
             S_UI8,
             YCBCR_P010,
             R_8,
-            R_16_UINT,
-            RG_1616_UINT,
+            R_16,
+            RG_1616,
             RGBA_10101010,
     })
     public @interface Format {
@@ -115,17 +115,19 @@
     @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
     public static final int R_8           = 0x38;
     /**
-     * Format: 16 bits red. Bits should be represented in unsigned integer, instead of the
-     * implicit unsigned normalized.
+     * Format: 16 bits red. When sampled on the GPU this is represented as an
+     * unsigned integer instead of implicit unsigned normalize.
+     * For more information see https://www.khronos.org/opengl/wiki/Normalized_Integer
      */
     @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
-    public static final int R_16_UINT     = 0x39;
+    public static final int R_16          = 0x39;
     /**
-     * Format: 16 bits each red, green. Bits should be represented in unsigned integer,
-     * instead of the implicit unsigned normalized.
+     * Format: 16 bits each red, green. When sampled on the GPU this is represented
+     * as an unsigned integer instead of implicit unsigned normalize.
+     * For more information see https://www.khronos.org/opengl/wiki/Normalized_Integer
      */
     @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
-    public static final int RG_1616_UINT  = 0x3a;
+    public static final int RG_1616       = 0x3a;
     /** Format: 10 bits each red, green, blue, alpha */
     @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
     public static final int RGBA_10101010 = 0x3b;
diff --git a/core/java/android/hardware/SyncFence.java b/core/java/android/hardware/SyncFence.java
index d6052cd..c2440fb 100644
--- a/core/java/android/hardware/SyncFence.java
+++ b/core/java/android/hardware/SyncFence.java
@@ -16,6 +16,7 @@
 
 package android.hardware;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.media.Image;
 import android.media.ImageWriter;
@@ -26,6 +27,8 @@
 import android.os.Parcelable;
 import android.os.SystemClock;
 
+import com.android.window.flags.Flags;
+
 import libcore.util.NativeAllocationRegistry;
 
 import java.io.FileDescriptor;
@@ -121,6 +124,19 @@
         }
     }
 
+    /**
+     * Creates a copy of the SyncFence from an existing one.
+     * Both fences must be closed() independently.
+     */
+    @FlaggedApi(Flags.FLAG_SDK_DESIRED_PRESENT_TIME)
+    public SyncFence(@NonNull SyncFence other) {
+        this(other.mNativePtr);
+
+        if (mNativePtr != 0) {
+            nIncRef(mNativePtr);
+        }
+    }
+
     private SyncFence() {
         mCloser = () -> {};
     }
@@ -312,4 +328,5 @@
     private static native int nGetFd(long nPtr);
     private static native boolean nWait(long nPtr, long timeout);
     private static native long nGetSignalTime(long nPtr);
+    private static native void nIncRef(long nPtr);
 }
diff --git a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
index 73ac333..d51e62e 100644
--- a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
+++ b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
@@ -33,4 +33,20 @@
      * Defines behavior in response to authentication stopping
      */
     void onAuthenticationStopped();
+
+    /**
+     * Defines behavior in response to a successful authentication
+     * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+     *                      authentication
+     * @param userId The user Id for the requested authentication
+     */
+    void onAuthenticationSucceeded(int requestReason, int userId);
+
+    /**
+     * Defines behavior in response to a failed authentication
+     * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+     *                      authentication
+     * @param userId The user Id for the requested authentication
+     */
+    void onAuthenticationFailed(int requestReason, int userId);
 }
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 8c1ea5f..bdaf9d7 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -16,14 +16,17 @@
 
 package android.hardware.biometrics;
 
+import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO;
 import static android.Manifest.permission.TEST_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.hardware.biometrics.BiometricManager.Authenticators;
 import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT;
 import static android.hardware.biometrics.Flags.FLAG_GET_OP_ID_CRYPTO_OBJECT;
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.DrawableRes;
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -32,6 +35,7 @@
 import android.annotation.TestApi;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.graphics.Bitmap;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.Binder;
@@ -159,6 +163,45 @@
         }
 
         /**
+         * Optional: Sets the drawable resource of the logo that will be shown on the prompt.
+         *
+         * <p> Note that using this method is not recommended in most scenarios because the calling
+         * application's icon will be used by default. Setting the logo is intended for large
+         * bundled applications that perform a wide range of functions and need to show distinct
+         * icons for each function.
+         *
+         * @param logoRes A drawable resource of the logo that will be shown on the prompt.
+         * @return This builder.
+         */
+        @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+        @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
+        @NonNull
+        public BiometricPrompt.Builder setLogoRes(@DrawableRes int logoRes) {
+            mPromptInfo.setLogoRes(logoRes);
+            return this;
+        }
+
+        /**
+         * Optional: Sets the bitmap drawable of the logo that will be shown on the prompt.
+         *
+         * <p> Note that using this method is not recommended in most scenarios because the calling
+         * application's icon will be used by default. Setting the logo is intended for large
+         * bundled applications that perform a wide range of functions and need to show distinct
+         * icons for each function.
+         *
+         * @param logoBitmap A bitmap drawable of the logo that will be shown on the prompt.
+         * @return This builder.
+         */
+        @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+        @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
+        @NonNull
+        public BiometricPrompt.Builder setLogoBitmap(@NonNull Bitmap logoBitmap) {
+            mPromptInfo.setLogoBitmap(logoBitmap);
+            return this;
+        }
+
+
+        /**
          * Required: Sets the title that will be shown on the prompt.
          * @param title The title to display.
          * @return This builder.
@@ -208,6 +251,12 @@
 
         /**
          * Optional: Sets a description that will be shown on the prompt.
+         *
+         * <p> Note that the description set by {@link Builder#setDescription(CharSequence)} will be
+         * overridden by {@link Builder#setContentView(PromptContentView)}. The view provided to
+         * {@link Builder#setContentView(PromptContentView)} will be used if both methods are
+         * called.
+         *
          * @param description The description to display.
          * @return This builder.
          */
@@ -218,6 +267,24 @@
         }
 
         /**
+         * Optional: Sets application customized content view that will be shown on the prompt.
+         *
+         * <p> Note that the description set by {@link Builder#setDescription(CharSequence)} will be
+         * overridden by {@link Builder#setContentView(PromptContentView)}. The view provided to
+         * {@link Builder#setContentView(PromptContentView)} will be used if both methods are
+         * called.
+         *
+         * @param view The customized view information.
+         * @return This builder.re
+         */
+        @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+        @NonNull
+        public BiometricPrompt.Builder setContentView(@NonNull PromptContentView view) {
+            mPromptInfo.setContentView(view);
+            return this;
+        }
+
+        /**
          * @param service
          * @return This builder.
          * @hide
@@ -651,6 +718,34 @@
     }
 
     /**
+     * Gets the drawable resource of the logo for the prompt, as set by
+     * {@link Builder#setLogoRes(int)}. Currently for system applications use only.
+     *
+     * @return The drawable resource of the logo, or -1 if the prompt has no logo resource set.
+     */
+    @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+    @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
+    @DrawableRes
+    public int getLogoRes() {
+        return mPromptInfo.getLogoRes();
+    }
+
+    /**
+     * Gets the logo bitmap for the prompt, as set by {@link Builder#setLogoBitmap(Bitmap)}.
+     * Currently for system applications use only.
+     *
+     * @return The logo bitmap of the prompt, or null if the prompt has no logo bitmap set.
+     */
+    @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+    @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
+    @Nullable
+    public Bitmap getLogoBitmap() {
+        return mPromptInfo.getLogoBitmap();
+    }
+
+
+
+    /**
      * Gets the title for the prompt, as set by {@link Builder#setTitle(CharSequence)}.
      * @return The title of the prompt, which is guaranteed to be non-null.
      */
@@ -698,6 +793,18 @@
     }
 
     /**
+     * Gets the content view for the prompt, as set by
+     * {@link Builder#setContentView(PromptContentView)}.
+     *
+     * @return The content view for the prompt, or null if the prompt has no content view.
+     */
+    @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+    @Nullable
+    public PromptContentView getContentView() {
+        return mPromptInfo.getContentView();
+    }
+
+    /**
      * Gets the negative button text for the prompt, as set by
      * {@link Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}.
      * @return The negative button text for the prompt, or null if no negative button text was set.
diff --git a/core/java/android/hardware/biometrics/IBiometricContextListener.aidl b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
index 6ac6581..2f58f51 100644
--- a/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
@@ -38,4 +38,8 @@
     // Called when the display state of the device changes.
     // Where `displayState` is defined in AuthenticateOptions.DisplayState
     void onDisplayStateChanged(int displayState);
+
+    // Called when the HAL ignoring touches state changes.
+    // When true, the HAL ignores touches on the sensor.
+    void onHardwareIgnoreTouchesChanged(boolean shouldIgnore);
 }
diff --git a/core/java/android/hardware/biometrics/PromptContentItem.java b/core/java/android/hardware/biometrics/PromptContentItem.java
new file mode 100644
index 0000000..c47b37a
--- /dev/null
+++ b/core/java/android/hardware/biometrics/PromptContentItem.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.biometrics;
+
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+
+import android.annotation.FlaggedApi;
+
+/**
+ * An item shown on {@link PromptContentView}.
+ */
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+public interface PromptContentItem {
+}
+
diff --git a/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java b/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java
new file mode 100644
index 0000000..25e5cca
--- /dev/null
+++ b/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.biometrics;
+
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A list item with bulleted text shown on {@link PromptVerticalListContentView}.
+ */
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+public final class PromptContentItemBulletedText implements PromptContentItemParcelable {
+    private final String mText;
+
+    /**
+     * A list item with bulleted text shown on {@link PromptVerticalListContentView}.
+     *
+     * @param text The text of this list item.
+     */
+    public PromptContentItemBulletedText(@NonNull String text) {
+        mText = text;
+    }
+
+    /**
+     * @hide
+     */
+    @NonNull
+    public String getText() {
+        return mText;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mText);
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    @NonNull
+    public static final Creator<PromptContentItemBulletedText> CREATOR = new Creator<>() {
+        @Override
+        public PromptContentItemBulletedText createFromParcel(Parcel in) {
+            return new PromptContentItemBulletedText(in.readString());
+        }
+
+        @Override
+        public PromptContentItemBulletedText[] newArray(int size) {
+            return new PromptContentItemBulletedText[size];
+        }
+    };
+}
diff --git a/core/java/android/hardware/biometrics/PromptContentItemParcelable.java b/core/java/android/hardware/biometrics/PromptContentItemParcelable.java
new file mode 100644
index 0000000..668912cf
--- /dev/null
+++ b/core/java/android/hardware/biometrics/PromptContentItemParcelable.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.biometrics;
+
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+
+import android.annotation.FlaggedApi;
+import android.os.Parcelable;
+
+/**
+ * A parcelable {@link PromptContentItem}.
+ */
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+sealed interface PromptContentItemParcelable extends PromptContentItem, Parcelable
+        permits PromptContentItemPlainText, PromptContentItemBulletedText {
+}
diff --git a/core/java/android/hardware/biometrics/PromptContentItemPlainText.java b/core/java/android/hardware/biometrics/PromptContentItemPlainText.java
new file mode 100644
index 0000000..7919256
--- /dev/null
+++ b/core/java/android/hardware/biometrics/PromptContentItemPlainText.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.biometrics;
+
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A list item with plain text shown on {@link PromptVerticalListContentView}.
+ */
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+public final class PromptContentItemPlainText implements PromptContentItemParcelable {
+    private final String mText;
+
+    /**
+     * A list item with plain text shown on {@link PromptVerticalListContentView}.
+     *
+     * @param text The text of this list item.
+     */
+    public PromptContentItemPlainText(@NonNull String text) {
+        mText = text;
+    }
+
+    /**
+     * @hide
+     */
+    @NonNull
+    public String getText() {
+        return mText;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mText);
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    @NonNull
+    public static final Creator<PromptContentItemPlainText> CREATOR = new Creator<>() {
+        @Override
+        public PromptContentItemPlainText createFromParcel(Parcel in) {
+            return new PromptContentItemPlainText(in.readString());
+        }
+
+        @Override
+        public PromptContentItemPlainText[] newArray(int size) {
+            return new PromptContentItemPlainText[size];
+        }
+    };
+}
diff --git a/core/java/android/hardware/biometrics/PromptContentView.java b/core/java/android/hardware/biometrics/PromptContentView.java
new file mode 100644
index 0000000..ff9313e
--- /dev/null
+++ b/core/java/android/hardware/biometrics/PromptContentView.java
@@ -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 android.hardware.biometrics;
+
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+
+import android.annotation.FlaggedApi;
+
+/**
+ * Contains the information of the template of content view for Biometric Prompt.
+ */
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+public interface PromptContentView {
+}
diff --git a/core/java/android/hardware/biometrics/PromptContentViewParcelable.java b/core/java/android/hardware/biometrics/PromptContentViewParcelable.java
new file mode 100644
index 0000000..43b965b
--- /dev/null
+++ b/core/java/android/hardware/biometrics/PromptContentViewParcelable.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.biometrics;
+
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+
+import android.annotation.FlaggedApi;
+import android.os.Parcelable;
+
+/**
+ * A parcelable {@link PromptContentView}.
+ */
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+sealed interface PromptContentViewParcelable extends PromptContentView, Parcelable
+        permits PromptVerticalListContentView {
+}
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 24cfd164..0f9cadc 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -16,8 +16,10 @@
 
 package android.hardware.biometrics;
 
+import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.Bitmap;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -30,11 +32,14 @@
  */
 public class PromptInfo implements Parcelable {
 
+    @DrawableRes private int mLogoRes = -1;
+    @Nullable private Bitmap mLogoBitmap;
     @NonNull private CharSequence mTitle;
     private boolean mUseDefaultTitle;
     @Nullable private CharSequence mSubtitle;
     private boolean mUseDefaultSubtitle;
     @Nullable private CharSequence mDescription;
+    @Nullable private PromptContentViewParcelable mContentView;
     @Nullable private CharSequence mDeviceCredentialTitle;
     @Nullable private CharSequence mDeviceCredentialSubtitle;
     @Nullable private CharSequence mDeviceCredentialDescription;
@@ -55,11 +60,15 @@
     }
 
     PromptInfo(Parcel in) {
+        mLogoRes = in.readInt();
+        mLogoBitmap = in.readTypedObject(Bitmap.CREATOR);
         mTitle = in.readCharSequence();
         mUseDefaultTitle = in.readBoolean();
         mSubtitle = in.readCharSequence();
         mUseDefaultSubtitle = in.readBoolean();
         mDescription = in.readCharSequence();
+        mContentView = in.readParcelable(PromptContentViewParcelable.class.getClassLoader(),
+                PromptContentViewParcelable.class);
         mDeviceCredentialTitle = in.readCharSequence();
         mDeviceCredentialSubtitle = in.readCharSequence();
         mDeviceCredentialDescription = in.readCharSequence();
@@ -95,11 +104,14 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mLogoRes);
+        dest.writeTypedObject(mLogoBitmap, 0);
         dest.writeCharSequence(mTitle);
         dest.writeBoolean(mUseDefaultTitle);
         dest.writeCharSequence(mSubtitle);
         dest.writeBoolean(mUseDefaultSubtitle);
         dest.writeCharSequence(mDescription);
+        dest.writeParcelable(mContentView, 0);
         dest.writeCharSequence(mDeviceCredentialTitle);
         dest.writeCharSequence(mDeviceCredentialSubtitle);
         dest.writeCharSequence(mDeviceCredentialDescription);
@@ -152,9 +164,30 @@
         }
         return false;
     }
+
+    /**
+     * Returns whether SET_BIOMETRIC_DIALOG_LOGO is contained.
+     */
+    public boolean containsSetLogoApiConfigurations() {
+        if (mLogoRes != -1) {
+            return true;
+        } else if (mLogoBitmap != null) {
+            return true;
+        }
+        return false;
+    }
     // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/BiometricPrompt.java)
 
     // Setters
+    public void setLogoRes(@DrawableRes int logoRes) {
+        mLogoRes = logoRes;
+        checkOnlyOneLogoSet();
+    }
+
+    public void setLogoBitmap(@NonNull Bitmap logoBitmap) {
+        mLogoBitmap = logoBitmap;
+        checkOnlyOneLogoSet();
+    }
 
     public void setTitle(CharSequence title) {
         mTitle = title;
@@ -176,6 +209,10 @@
         mDescription = description;
     }
 
+    public void setContentView(PromptContentView view) {
+        mContentView = (PromptContentViewParcelable) view;
+    }
+
     public void setDeviceCredentialTitle(CharSequence deviceCredentialTitle) {
         mDeviceCredentialTitle = deviceCredentialTitle;
     }
@@ -236,6 +273,14 @@
     }
 
     // Getters
+    @DrawableRes
+    public int getLogoRes() {
+        return mLogoRes;
+    }
+
+    public Bitmap getLogoBitmap() {
+        return mLogoBitmap;
+    }
 
     public CharSequence getTitle() {
         return mTitle;
@@ -257,6 +302,15 @@
         return mDescription;
     }
 
+    /**
+     * Gets the content view for the prompt.
+     *
+     * @return The content view for the prompt, or null if the prompt has no content view.
+     */
+    public PromptContentView getContentView() {
+        return mContentView;
+    }
+
     public CharSequence getDeviceCredentialTitle() {
         return mDeviceCredentialTitle;
     }
@@ -320,4 +374,11 @@
     public boolean isShowEmergencyCallButton() {
         return mShowEmergencyCallButton;
     }
+
+    private void checkOnlyOneLogoSet() {
+        if (mLogoRes != -1 && mLogoBitmap != null) {
+            throw new IllegalStateException(
+                    "Exclusively one of logo resource or logo bitmap can be set");
+        }
+    }
 }
diff --git a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
new file mode 100644
index 0000000..38d32dc
--- /dev/null
+++ b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.biometrics;
+
+import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Contains the information of the template of vertical list content view for Biometric Prompt. Note
+ * that there are limits on the item count and the number of characters allowed for each item's
+ * text.
+ * <p>
+ * Here's how you'd set a <code>PromptVerticalListContentView</code> on a Biometric Prompt:
+ * <pre class="prettyprint">
+ * BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(...)
+ *     .setTitle(...)
+ *     .setSubTitle(...)
+ *     .setContentView(new PromptVerticalListContentView.Builder()
+ *         .setDescription("test description")
+ *         .addListItem(new PromptContentItemPlainText("test item 1"))
+ *         .addListItem(new PromptContentItemPlainText("test item 2"))
+ *         .addListItem(new PromptContentItemBulletedText("test item 3"))
+ *         .build())
+ *     .build();
+ * </pre>
+ */
+@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+public final class PromptVerticalListContentView implements PromptContentViewParcelable {
+    private static final int MAX_ITEM_NUMBER = 20;
+    private static final int MAX_EACH_ITEM_CHARACTER_NUMBER = 640;
+    private final List<PromptContentItemParcelable> mContentList;
+    private final String mDescription;
+
+    private PromptVerticalListContentView(
+            @NonNull List<PromptContentItemParcelable> contentList,
+            @NonNull String description) {
+        mContentList = contentList;
+        mDescription = description;
+    }
+
+    private PromptVerticalListContentView(Parcel in) {
+        mContentList = in.readArrayList(
+                PromptContentItemParcelable.class.getClassLoader(),
+                PromptContentItemParcelable.class);
+        mDescription = in.readString();
+    }
+
+    /**
+     * Returns the maximum count of the list items.
+     */
+    public static int getMaxItemCount() {
+        return MAX_ITEM_NUMBER;
+    }
+
+    /**
+     * Returns the maximum number of characters allowed for each item's text.
+     */
+    public static int getMaxEachItemCharacterNumber() {
+        return MAX_EACH_ITEM_CHARACTER_NUMBER;
+    }
+
+    /**
+     * Gets the description for the content view, as set by
+     * {@link PromptVerticalListContentView.Builder#setDescription(String)}.
+     *
+     * @return The description for the content view, or null if the content view has no description.
+     */
+    @Nullable
+    public String getDescription() {
+        return mDescription;
+    }
+
+    /**
+     * Gets the list of items on the content view, as set by
+     * {@link PromptVerticalListContentView.Builder#addListItem(PromptContentItem)}.
+     *
+     * @return The item list on the content view.
+     */
+    @NonNull
+    public List<PromptContentItem> getListItems() {
+        return new ArrayList<>(mContentList);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) {
+        dest.writeList(mContentList);
+        dest.writeString(mDescription);
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    @NonNull
+    public static final Creator<PromptVerticalListContentView> CREATOR = new Creator<>() {
+        @Override
+        public PromptVerticalListContentView createFromParcel(Parcel in) {
+            return new PromptVerticalListContentView(in);
+        }
+
+        @Override
+        public PromptVerticalListContentView[] newArray(int size) {
+            return new PromptVerticalListContentView[size];
+        }
+    };
+
+
+    /**
+     * A builder that collects arguments to be shown on the vertical list view.
+     */
+    public static final class Builder {
+        private final List<PromptContentItemParcelable> mContentList = new ArrayList<>();
+        private String mDescription;
+
+        /**
+         * Optional: Sets a description that will be shown on the content view.
+         *
+         * @param description The description to display.
+         * @return This builder.
+         */
+        @NonNull
+        public Builder setDescription(@NonNull String description) {
+            mDescription = description;
+            return this;
+        }
+
+        /**
+         * Optional: Adds a list item in the current row. Maximum {@value MAX_ITEM_NUMBER} items in
+         * total. The maximum length for each item is {@value MAX_EACH_ITEM_CHARACTER_NUMBER}
+         * characters.
+         *
+         * @param listItem The list item view to display
+         * @return This builder.
+         */
+        @NonNull
+        public Builder addListItem(@NonNull PromptContentItem listItem) {
+            if (doesListItemExceedsCharLimit(listItem)) {
+                throw new IllegalStateException(
+                        "The character number of list item exceeds "
+                                + MAX_EACH_ITEM_CHARACTER_NUMBER);
+            }
+            mContentList.add((PromptContentItemParcelable) listItem);
+            return this;
+        }
+
+
+        /**
+         * Optional: Adds a list item in the current row. Maximum {@value MAX_ITEM_NUMBER} items in
+         * total. The maximum length for each item is {@value MAX_EACH_ITEM_CHARACTER_NUMBER}
+         * characters.
+         *
+         * @param listItem The list item view to display
+         * @param index The position at which to add the item
+         * @return This builder.
+         */
+        @NonNull
+        public Builder addListItem(@NonNull PromptContentItem listItem, int index) {
+            if (doesListItemExceedsCharLimit(listItem)) {
+                throw new IllegalStateException(
+                        "The character number of list item exceeds "
+                                + MAX_EACH_ITEM_CHARACTER_NUMBER);
+            }
+            mContentList.add(index, (PromptContentItemParcelable) listItem);
+            return this;
+        }
+
+        private boolean doesListItemExceedsCharLimit(PromptContentItem listItem) {
+            if (listItem instanceof PromptContentItemPlainText) {
+                return ((PromptContentItemPlainText) listItem).getText().length()
+                        > MAX_EACH_ITEM_CHARACTER_NUMBER;
+            } else if (listItem instanceof PromptContentItemBulletedText) {
+                return ((PromptContentItemBulletedText) listItem).getText().length()
+                        > MAX_EACH_ITEM_CHARACTER_NUMBER;
+            } else {
+                return false;
+            }
+        }
+
+
+        /**
+         * Creates a {@link PromptVerticalListContentView}.
+         *
+         * @return An instance of {@link PromptVerticalListContentView}.
+         */
+        @NonNull
+        public PromptVerticalListContentView build() {
+            if (mContentList.size() > MAX_ITEM_NUMBER) {
+                throw new IllegalStateException(
+                        "The number of list items exceeds " + MAX_ITEM_NUMBER);
+            }
+            return new PromptVerticalListContentView(mContentList, mDescription);
+        }
+    }
+}
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 375fdb5..8165d44 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -21,3 +21,17 @@
   bug: "307601768"
 }
 
+flag {
+  name: "custom_biometric_prompt"
+  namespace: "biometrics_framework"
+  description: "Feature flag for adding a custom content view API to BiometricPrompt.Builder."
+  bug: "302735104"
+}
+
+flag {
+  name: "face_background_authentication"
+  namespace: "biometrics_framework"
+  description: "Feature flag for allowing face background authentication with USE_BACKGROUND_FACE_AUTHENTICATION."
+  bug: "318584190"
+}
+
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 20b0932..1867a17 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -847,16 +847,18 @@
      * of the camera resolutions advertised by
      * {@link StreamConfigurationMap#getOutputSizes}.</p>
      *
-     * <p>Device-specific extensions currently support at most two
+     * <p>Device-specific extensions currently support at most three
      * multi-frame capture surface formats. ImageFormat.JPEG will be supported by all
-     * extensions and ImageFormat.YUV_420_888 may or may not be supported.</p>
+     * extensions while ImageFormat.YUV_420_888 and ImageFormat.JPEG_R may or may not be
+     * supported.</p>
      *
      * @param extension the extension type
      * @param format    device-specific extension output format
      * @return non-modifiable list of available sizes or an empty list if the format is not
      * supported.
-     * @throws IllegalArgumentException in case of format different from ImageFormat.JPEG /
-     *                                  ImageFormat.YUV_420_888; or unsupported extension.
+     * @throws IllegalArgumentException in case of format different from ImageFormat.JPEG,
+     *                                  ImageFormat.YUV_420_888, ImageFormat.JPEG_R; or
+     *                                  unsupported extension.
      */
     public @NonNull
     List<Size> getExtensionSupportedSizes(@Extension int extension, int format) {
@@ -940,14 +942,16 @@
      * @param format            device-specific extension output format
      * @return the range of estimated minimal and maximal capture latency in milliseconds
      * or null if no capture latency info can be provided
-     * @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG} /
-     *                                  {@link ImageFormat#YUV_420_888}; or unsupported extension.
+     * @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG},
+     *                                  {@link ImageFormat#YUV_420_888}, {@link ImageFormat#JPEG_R};
+     *                                  or unsupported extension.
      */
     public @Nullable Range<Long> getEstimatedCaptureLatencyRangeMillis(@Extension int extension,
             @NonNull Size captureOutputSize, @ImageFormat.Format int format) {
         switch (format) {
             case ImageFormat.YUV_420_888:
             case ImageFormat.JPEG:
+            case ImageFormat.JPEG_R:
                 //No op
                 break;
             default:
@@ -994,6 +998,10 @@
                     // specific and cannot be estimated accurately enough.
                     return  null;
                 }
+                if (format == ImageFormat.JPEG_R) {
+                    // JpegR/UltraHDR is not supported for basic extensions
+                    return null;
+                }
 
                 LatencyRange latencyRange = extenders.second.getEstimatedCaptureLatencyRange(sz);
                 if (latencyRange != null) {
diff --git a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
index 7c54a9b..509bcb8 100644
--- a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
+++ b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
@@ -26,6 +26,7 @@
     Surface surface;
     int imageFormat;
     int capacity;
+    long usage;
 
     const int TYPE_SURFACE = 0;
     const int TYPE_IMAGEREADER = 1;
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 98bc311..f6c8f36 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -1182,7 +1182,8 @@
                     return null;
                 }
                 ImageReader reader = ImageReader.newInstance(output.size.width,
-                        output.size.height, output.imageFormat, output.capacity);
+                        output.size.height, output.imageFormat, output.capacity,
+                        output.usage);
                 mReaderMap.put(output.outputId.id, reader);
                 return reader.getSurface();
             case CameraOutputConfig.TYPE_MULTIRES_IMAGEREADER:
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 3affb73..0cd1c8c 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -79,6 +79,8 @@
 import android.util.Range;
 import android.util.Size;
 
+import com.android.internal.camera.flags.Flags;
+
 import dalvik.annotation.optimization.FastNative;
 import dalvik.system.VMRuntime;
 
@@ -1795,49 +1797,57 @@
             return false;
         }
 
-        long[] tsArray = new long[samples.length];
-        float[] intrinsicsArray = new float[samples.length * 5];
-        for (int i = 0; i < samples.length; i++) {
-            tsArray[i] = samples[i].getTimestamp();
-            System.arraycopy(samples[i].getLensIntrinsics(), 0, intrinsicsArray, 5*i, 5);
+        if (Flags.concertMode()) {
+            long[] tsArray = new long[samples.length];
+            float[] intrinsicsArray = new float[samples.length * 5];
+            for (int i = 0; i < samples.length; i++) {
+                tsArray[i] = samples[i].getTimestampNanos();
+                System.arraycopy(samples[i].getLensIntrinsics(), 0, intrinsicsArray, 5 * i, 5);
 
+            }
+            setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES, intrinsicsArray);
+            setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS, tsArray);
+
+            return true;
+        } else {
+            return false;
         }
-        setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES, intrinsicsArray);
-        setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS, tsArray);
-
-        return true;
     }
 
     private LensIntrinsicsSample[] getLensIntrinsicSamples() {
-        long[] timestamps = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS);
-        float[] intrinsics = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES);
+        if (Flags.concertMode()) {
+            long[] timestamps = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS);
+            float[] intrinsics = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES);
 
-        if (timestamps == null) {
-            if (intrinsics != null) {
-                throw new AssertionError("timestamps is null but intrinsics is not");
+            if (timestamps == null) {
+                if (intrinsics != null) {
+                    throw new AssertionError("timestamps is null but intrinsics is not");
+                }
+
+                return null;
             }
 
+            if (intrinsics == null) {
+                throw new AssertionError("timestamps is not null but intrinsics is");
+            } else if ((intrinsics.length % 5) != 0) {
+                throw new AssertionError("intrinsics are not multiple of 5");
+            }
+
+            if ((intrinsics.length / 5) != timestamps.length) {
+                throw new AssertionError(String.format(
+                        "timestamps has %d entries but intrinsics has %d", timestamps.length,
+                        intrinsics.length / 5));
+            }
+
+            LensIntrinsicsSample[] samples = new LensIntrinsicsSample[timestamps.length];
+            for (int i = 0; i < timestamps.length; i++) {
+                float[] currentIntrinsic = Arrays.copyOfRange(intrinsics, 5 * i, 5 * i + 5);
+                samples[i] = new LensIntrinsicsSample(timestamps[i], currentIntrinsic);
+            }
+            return samples;
+        } else {
             return null;
         }
-
-        if (intrinsics == null) {
-            throw new AssertionError("timestamps is not null but intrinsics is");
-        } else if((intrinsics.length % 5) != 0) {
-            throw new AssertionError("intrinsics are not multiple of 5");
-        }
-
-        if ((intrinsics.length / 5) != timestamps.length) {
-            throw new AssertionError(String.format(
-                    "timestamps has %d entries but intrinsics has %d", timestamps.length,
-                    intrinsics.length / 5));
-        }
-
-        LensIntrinsicsSample[] samples = new LensIntrinsicsSample[timestamps.length];
-        for (int i = 0; i < timestamps.length; i++) {
-            float[] currentIntrinsic = Arrays.copyOfRange(intrinsics, 5*i, 5*i + 5);
-            samples[i] = new LensIntrinsicsSample(timestamps[i], currentIntrinsic);
-        }
-        return samples;
     }
 
     private Capability[] getExtendedSceneModeCapabilities() {
diff --git a/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java b/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java
index 575cbfa..9a4ec5c 100644
--- a/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java
+++ b/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java
@@ -37,16 +37,18 @@
      * Create a new {@link LensIntrinsicsSample}.
      *
      * <p>{@link LensIntrinsicsSample} contains the timestamp and the
-     * {@link CaptureResult#LENS_INTRINSIC_CALIBRATION} sample.
+     * {@link CaptureResult#LENS_INTRINSIC_CALIBRATION} sample.</p>
      *
-     * @param timestamp timestamp of the lens intrinsics sample.
-     * @param lensIntrinsics the lens intrinsic calibration for the sample.
+     * @param timestampNs timestamp in nanoseconds of the lens intrinsics sample. This uses the
+     *                  same time basis as {@link CaptureResult#SENSOR_TIMESTAMP}.
+     * @param lensIntrinsics the lens {@link CaptureResult#LENS_INTRINSIC_CALIBRATION intrinsic}
+     *                      calibration for the sample.
      *
      * @throws IllegalArgumentException if lensIntrinsics length is different from 5
      */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
-    public LensIntrinsicsSample(final long timestamp, @NonNull final float[] lensIntrinsics) {
-        mTimestampNs = timestamp;
+    public LensIntrinsicsSample(final long timestampNs, @NonNull final float[] lensIntrinsics) {
+        mTimestampNs = timestampNs;
         Preconditions.checkArgument(lensIntrinsics.length == 5);
         mLensIntrinsics = lensIntrinsics;
     }
@@ -54,18 +56,18 @@
     /**
      * Get the timestamp in nanoseconds.
      *
-     *<p>The timestamps are in the same timebase as and comparable to
+     *<p>The timestamps are in the same time basis as and comparable to
      *{@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp}.</p>
      *
      * @return a long value (guaranteed to be finite)
      */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
-    public long getTimestamp() {
+    public long getTimestampNanos() {
         return mTimestampNs;
     }
 
     /**
-     * Get the lens intrinsics calibration
+     * Get the lens {@link CaptureResult#LENS_INTRINSIC_CALIBRATION intrinsics} calibration
      *
      * @return a floating point value (guaranteed to be finite)
      * @see CaptureResult#LENS_INTRINSIC_CALIBRATION
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 134a510..8f0e0c9 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1831,15 +1831,6 @@
         String KEY_POWER_THROTTLING_DATA = "power_throttling_data";
 
         /**
-         * Key for new power controller feature flag. If enabled new DisplayPowerController will
-         * be used.
-         * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)}
-         * with {@link android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER} as the namespace.
-         * @hide
-         */
-        String KEY_NEW_POWER_CONTROLLER = "use_newly_structured_display_power_controller";
-
-        /**
          * Key for normal brightness mode controller feature flag.
          * It enables NormalBrightnessModeController.
          * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)}
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 02304b5..bae5e7f 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -18,18 +18,23 @@
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.MANAGE_BIOMETRIC;
+import static android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
+import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricStateListener;
 import android.hardware.biometrics.CryptoObject;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
@@ -37,9 +42,9 @@
 import android.os.CancellationSignal;
 import android.os.CancellationSignal.OnCancelListener;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
-import android.os.Looper;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.Trace;
@@ -49,15 +54,21 @@
 import android.view.Surface;
 
 import com.android.internal.R;
-import com.android.internal.os.SomeArgs;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * A class that coordinates access to the face authentication hardware.
+ *
+ * <p>Please use {@link BiometricPrompt} for face authentication unless the experience must be
+ * customized for unique system-level utilities, like the lock screen or ambient background usage.
+ *
  * @hide
  */
+@FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
+@SystemApi
 @SystemService(Context.FACE_SERVICE)
 public class FaceManager implements BiometricAuthenticator, BiometricFaceConstants {
 
@@ -88,81 +99,76 @@
     @Nullable private GenerateChallengeCallback mGenerateChallengeCallback;
     private CryptoObject mCryptoObject;
     private Face mRemovalFace;
-    private Handler mHandler;
+    private Executor mExecutor;
     private List<FaceSensorPropertiesInternal> mProps = new ArrayList<>();
 
     private final IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() {
 
         @Override // binder call
         public void onEnrollResult(Face face, int remaining) {
-            mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, face).sendToTarget();
+            mExecutor.execute(() -> sendEnrollResult(face, remaining));
         }
 
         @Override // binder call
         public void onAcquired(int acquireInfo, int vendorCode) {
-            mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode).sendToTarget();
+            mExecutor.execute(() -> sendAcquiredResult(acquireInfo, vendorCode));
         }
 
         @Override // binder call
         public void onAuthenticationSucceeded(Face face, int userId, boolean isStrongBiometric) {
-            mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId,
-                    isStrongBiometric ? 1 : 0, face).sendToTarget();
+            mExecutor.execute(() -> sendAuthenticatedSucceeded(face, userId, isStrongBiometric));
         }
 
         @Override // binder call
         public void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
-            mHandler.obtainMessage(MSG_FACE_DETECTED, sensorId, userId, isStrongBiometric)
-                    .sendToTarget();
+            mExecutor.execute(() -> sendFaceDetected(sensorId, userId, isStrongBiometric));
         }
 
         @Override // binder call
         public void onAuthenticationFailed() {
-            mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
+            mExecutor.execute(() -> sendAuthenticatedFailed());
         }
 
         @Override // binder call
         public void onError(int error, int vendorCode) {
-            mHandler.obtainMessage(MSG_ERROR, error, vendorCode).sendToTarget();
+            mExecutor.execute(() -> sendErrorResult(error, vendorCode));
         }
 
         @Override // binder call
         public void onRemoved(Face face, int remaining) {
-            mHandler.obtainMessage(MSG_REMOVED, remaining, 0, face).sendToTarget();
-            if (remaining == 0) {
-                Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                        Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0,
-                        UserHandle.USER_CURRENT);
-            }
+            mExecutor.execute(() -> {
+                sendRemovedResult(face, remaining);
+                if (remaining == 0) {
+                    Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                            Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0,
+                            UserHandle.USER_CURRENT);
+                }
+            });
         }
 
         @Override
         public void onFeatureSet(boolean success, int feature) {
-            mHandler.obtainMessage(MSG_SET_FEATURE_COMPLETED, feature, 0, success).sendToTarget();
+            mExecutor.execute(() -> sendSetFeatureCompleted(success, feature));
         }
 
         @Override
         public void onFeatureGet(boolean success, int[] features, boolean[] featureState) {
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = success;
-            args.arg2 = features;
-            args.arg3 = featureState;
-            mHandler.obtainMessage(MSG_GET_FEATURE_COMPLETED, args).sendToTarget();
+            mExecutor.execute(() -> sendGetFeatureCompleted(success, features, featureState));
         }
 
         @Override
         public void onChallengeGenerated(int sensorId, int userId, long challenge) {
-            mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, userId, challenge)
-                    .sendToTarget();
+            mExecutor.execute(() -> sendChallengeGenerated(sensorId, userId, challenge));
         }
 
         @Override
         public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
-            mHandler.obtainMessage(MSG_AUTHENTICATION_FRAME, frame).sendToTarget();
+            mExecutor.execute(() -> sendAuthenticationFrame(frame));
         }
 
         @Override
         public void onEnrollmentFrame(FaceEnrollFrame frame) {
-            mHandler.obtainMessage(MSG_ENROLLMENT_FRAME, frame).sendToTarget();
+            mExecutor.execute(() -> sendEnrollmentFrame(frame));
         }
     };
 
@@ -175,7 +181,7 @@
         if (mService == null) {
             Slog.v(TAG, "FaceAuthenticationManagerService was null");
         }
-        mHandler = new MyHandler(context);
+        mExecutor = context.getMainExecutor();
         if (context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
                 == PackageManager.PERMISSION_GRANTED) {
             addAuthenticatorsRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
@@ -189,18 +195,16 @@
     }
 
     /**
-     * Use the provided handler thread for events.
+     * Returns an {@link Executor} for the given {@link Handler} or the main {@link Executor} if
+     * {@code handler} is {@code null}.
      */
-    private void useHandler(Handler handler) {
-        if (handler != null) {
-            mHandler = new MyHandler(handler.getLooper());
-        } else if (mHandler.getLooper() != mContext.getMainLooper()) {
-            mHandler = new MyHandler(mContext.getMainLooper());
-        }
+    private @NonNull Executor createExecutorForHandlerIfNeeded(@Nullable Handler handler) {
+        return handler != null ? new HandlerExecutor(handler) : mContext.getMainExecutor();
     }
 
     /**
      * @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FaceAuthenticateOptions)}.
+     * @hide
      */
     @Deprecated
     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -212,17 +216,22 @@
     }
 
     /**
-     * Request authentication. This call operates the face recognition hardware and starts capturing images.
+     * Request authentication.
+     *
+     * <p>This call operates the face recognition hardware and starts capturing images.
      * It terminates when
      * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
      * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, at
      * which point the object is no longer valid. The operation can be canceled by using the
-     * provided cancel object.
+     * provided {@code cancel} object.
      *
-     * @param crypto   object associated with the call or null if none required
-     * @param cancel   an object that can be used to cancel authentication
+     * @param crypto   the cryptographic operations to use for authentication or {@code null} if
+     *                 none required
+     * @param cancel   an object that can be used to cancel authentication or {@code null} if not
+     *                 needed
      * @param callback an object to receive authentication events
-     * @param handler  an optional handler to handle callback events
+     * @param handler  an optional handler to handle callback events or {@code null} to obtain main
+     *                 {@link Executor} from {@link Context}
      * @param options  additional options to customize this request
      * @throws IllegalArgumentException if the crypto operation is not supported or is not backed
      *                                  by
@@ -235,6 +244,14 @@
     public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
             @NonNull AuthenticationCallback callback, @Nullable Handler handler,
             @NonNull FaceAuthenticateOptions options) {
+        authenticate(crypto, cancel, callback, createExecutorForHandlerIfNeeded(handler),
+                options, false /* allowBackgroundAuthentication */);
+    }
+
+    @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL, USE_BACKGROUND_FACE_AUTHENTICATION})
+    private void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
+            @NonNull AuthenticationCallback callback, @NonNull Executor executor,
+            @NonNull FaceAuthenticateOptions options, boolean allowBackgroundAuthentication) {
         if (callback == null) {
             throw new IllegalArgumentException("Must supply an authentication callback");
         }
@@ -249,13 +266,15 @@
 
         if (mService != null) {
             try {
-                useHandler(handler);
+                mExecutor = executor;
                 mAuthenticationCallback = callback;
                 mCryptoObject = crypto;
                 final long operationId = crypto != null ? crypto.getOpId() : 0;
                 Trace.beginSection("FaceManager#authenticate");
-                final long authId = mService.authenticate(
-                        mToken, operationId, mServiceReceiver, options);
+                final long authId = allowBackgroundAuthentication
+                        ? mService.authenticateInBackground(
+                                mToken, operationId, mServiceReceiver, options)
+                        : mService.authenticate(mToken, operationId, mServiceReceiver, options);
                 if (cancel != null) {
                     cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
                 }
@@ -273,6 +292,67 @@
     }
 
     /**
+     * Request background face authentication.
+     *
+     * <p>This call operates the face recognition hardware and starts capturing images.
+     * It terminates when
+     * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
+     * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationSucceeded(
+     * BiometricPrompt.AuthenticationResult)} is called, at which point the object is no longer
+     * valid. The operation can be canceled by using the provided cancel object.
+     *
+     * <p>See {@link BiometricPrompt#authenticate} for more details. Please use
+     * {@link BiometricPrompt} for face authentication unless the experience must be customized for
+     * unique system-level utilities, like the lock screen or ambient background usage.
+     *
+     * @param executor the specified {@link Executor} to handle callback events; if {@code null},
+     *                 the callback will be executed on the main {@link Executor}.
+     * @param crypto   the cryptographic operations to use for authentication or {@code null} if
+     *                 none required.
+     * @param cancel   an object that can be used to cancel authentication or {@code null} if not
+     *                 needed.
+     * @param callback an object to receive authentication events.
+     * @throws IllegalArgumentException if the crypto operation is not supported or is not backed
+     *                                  by
+     *                                  <a href="{@docRoot}training/articles/keystore.html">Android
+     *                                  Keystore facility</a>.
+     * @hide
+     */
+    @RequiresPermission(USE_BACKGROUND_FACE_AUTHENTICATION)
+    @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
+    @SystemApi
+    public void authenticateInBackground(@Nullable Executor executor,
+            @Nullable BiometricPrompt.CryptoObject crypto, @Nullable CancellationSignal cancel,
+            @NonNull BiometricPrompt.AuthenticationCallback callback) {
+        authenticate(crypto, cancel, new AuthenticationCallback() {
+                    @Override
+                    public void onAuthenticationError(int errorCode, CharSequence errString) {
+                        callback.onAuthenticationError(errorCode, errString);
+                    }
+
+                    @Override
+                    public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
+                        callback.onAuthenticationHelp(helpCode, helpString);
+                    }
+
+                    @Override
+                    public void onAuthenticationSucceeded(AuthenticationResult result) {
+                        callback.onAuthenticationSucceeded(
+                                new BiometricPrompt.AuthenticationResult(
+                                        crypto,
+                                        BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC));
+                    }
+
+                    @Override
+                    public void onAuthenticationFailed() {
+                        callback.onAuthenticationFailed();
+                    }
+                }, executor == null ? mContext.getMainExecutor() : executor,
+                new FaceAuthenticateOptions.Builder().build(),
+                true /* allowBackgroundAuthentication */);
+    }
+
+    /**
      * Uses the face hardware to detect for the presence of a face, without giving details about
      * accept/reject/lockout.
      * @hide
@@ -628,12 +708,14 @@
     }
 
     /**
-     * Determine if there is a face enrolled.
+     * Determine if there are enrolled {@link Face} templates.
      *
-     * @return true if a face is enrolled, false otherwise
+     * @return {@code true} if there are enrolled {@link Face} templates, {@code false} otherwise
      * @hide
      */
-    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL, USE_BACKGROUND_FACE_AUTHENTICATION})
+    @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
+    @SystemApi
     public boolean hasEnrolledTemplates() {
         return hasEnrolledTemplates(UserHandle.myUserId());
     }
@@ -798,7 +880,7 @@
                                             PowerManager.PARTIAL_WAKE_LOCK,
                                             "faceLockoutResetCallback");
                                     wakeLock.acquire();
-                                    mHandler.post(() -> {
+                                    mExecutor.execute(() -> {
                                         try {
                                             callback.onLockoutReset(sensorId);
                                         } finally {
@@ -1268,70 +1350,6 @@
         }
     }
 
-    private class MyHandler extends Handler {
-        private MyHandler(Context context) {
-            super(context.getMainLooper());
-        }
-
-        private MyHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(android.os.Message msg) {
-            Trace.beginSection("FaceManager#handleMessage: " + Integer.toString(msg.what));
-            switch (msg.what) {
-                case MSG_ENROLL_RESULT:
-                    sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */);
-                    break;
-                case MSG_ACQUIRED:
-                    sendAcquiredResult(msg.arg1 /* acquire info */, msg.arg2 /* vendorCode */);
-                    break;
-                case MSG_AUTHENTICATION_SUCCEEDED:
-                    sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */,
-                            msg.arg2 == 1 /* isStrongBiometric */);
-                    break;
-                case MSG_AUTHENTICATION_FAILED:
-                    sendAuthenticatedFailed();
-                    break;
-                case MSG_ERROR:
-                    sendErrorResult(msg.arg1 /* errMsgId */, msg.arg2 /* vendorCode */);
-                    break;
-                case MSG_REMOVED:
-                    sendRemovedResult((Face) msg.obj, msg.arg1 /* remaining */);
-                    break;
-                case MSG_SET_FEATURE_COMPLETED:
-                    sendSetFeatureCompleted((boolean) msg.obj /* success */,
-                            msg.arg1 /* feature */);
-                    break;
-                case MSG_GET_FEATURE_COMPLETED:
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    sendGetFeatureCompleted((boolean) args.arg1 /* success */,
-                            (int[]) args.arg2 /* features */,
-                            (boolean[]) args.arg3 /* featureState */);
-                    args.recycle();
-                    break;
-                case MSG_CHALLENGE_GENERATED:
-                    sendChallengeGenerated(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
-                            (long) msg.obj /* challenge */);
-                    break;
-                case MSG_FACE_DETECTED:
-                    sendFaceDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
-                            (boolean) msg.obj /* isStrongBiometric */);
-                    break;
-                case MSG_AUTHENTICATION_FRAME:
-                    sendAuthenticationFrame((FaceAuthenticationFrame) msg.obj /* frame */);
-                    break;
-                case MSG_ENROLLMENT_FRAME:
-                    sendEnrollmentFrame((FaceEnrollFrame) msg.obj /* frame */);
-                    break;
-                default:
-                    Slog.w(TAG, "Unknown message: " + msg.what);
-            }
-            Trace.endSection();
-        }
-    }
-
     private void sendSetFeatureCompleted(boolean success, int feature) {
         if (mSetFeatureCallback == null) {
             return;
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 0096877..8e234fa 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -15,6 +15,7 @@
  */
 package android.hardware.face;
 
+import android.hardware.biometrics.AuthenticationStateListener;
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IBiometricStateListener;
@@ -45,7 +46,7 @@
     byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer);
 
     // Retrieve static sensor properties for all face sensors
-    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+    @EnforcePermission(anyOf = {"USE_BIOMETRIC_INTERNAL", "USE_BACKGROUND_FACE_AUTHENTICATION"})
     List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(String opPackageName);
 
     // Retrieve static sensor properties for the specified sensor
@@ -57,6 +58,11 @@
     long authenticate(IBinder token, long operationId, IFaceServiceReceiver receiver,
             in FaceAuthenticateOptions options);
 
+    // Authenticate with a face. A requestId is returned that can be used to cancel this operation.
+    @EnforcePermission("USE_BACKGROUND_FACE_AUTHENTICATION")
+    long authenticateInBackground(IBinder token, long operationId, IFaceServiceReceiver receiver,
+            in FaceAuthenticateOptions options);
+
     // Uses the face hardware to detect for the presence of a face, without giving details
     // about accept/reject/lockout. A requestId is returned that can be used to cancel this
     // operation.
@@ -131,7 +137,7 @@
     void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName, long challenge);
 
     // Determine if a user has at least one enrolled face
-    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+    @EnforcePermission(anyOf = {"USE_BIOMETRIC_INTERNAL", "USE_BACKGROUND_FACE_AUTHENTICATION"})
     boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName);
 
     // Return the LockoutTracker status for the specified user
@@ -176,6 +182,14 @@
     // authenticators. The callback is automatically removed after it's invoked.
     void addAuthenticatorsRegisteredCallback(IFaceAuthenticatorsRegisteredCallback callback);
 
+    // Registers AuthenticationStateListener.
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+    void registerAuthenticationStateListener(AuthenticationStateListener listener);
+
+    // Unregisters AuthenticationStateListener.
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+    void unregisterAuthenticationStateListener(AuthenticationStateListener listener);
+
     // Registers BiometricStateListener.
     void registerBiometricStateListener(IBiometricStateListener listener);
 
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index d939532..fdbd319 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -17,6 +17,7 @@
 package android.hardware.input;
 
 import static com.android.hardware.input.Flags.keyboardA11yBounceKeysFlag;
+import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag;
 import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
 import static com.android.input.flags.Flags.enableInputFilterRustImpl;
 
@@ -68,6 +69,12 @@
      */
     public static final int MAX_ACCESSIBILITY_BOUNCE_KEYS_THRESHOLD_MILLIS = 5000;
 
+    /**
+     * The maximum allowed Accessibility slow keys threshold.
+     * @hide
+     */
+    public static final int MAX_ACCESSIBILITY_SLOW_KEYS_THRESHOLD_MILLIS = 5000;
+
     private InputSettings() {
     }
 
@@ -419,6 +426,86 @@
     }
 
     /**
+     * Whether Accessibility slow keys feature flags is enabled.
+     *
+     * <p>
+     * 'Slow keys' is an accessibility feature to aid users who have physical disabilities, that
+     * allows the user to specify the duration for which one must press-and-hold a key before the
+     * system accepts the keypress.
+     * </p>
+     *
+     * @hide
+     */
+    public static boolean isAccessibilitySlowKeysFeatureFlagEnabled() {
+        return keyboardA11ySlowKeysFlag() && enableInputFilterRustImpl();
+    }
+
+    /**
+     * Whether Accessibility slow keys is enabled.
+     *
+     * <p>
+     * 'Slow keys' is an accessibility feature to aid users who have physical disabilities, that
+     * allows the user to specify the duration for which one must press-and-hold a key before the
+     * system accepts the keypress.
+     * </p>
+     *
+     * @hide
+     */
+    public static boolean isAccessibilitySlowKeysEnabled(@NonNull Context context) {
+        return getAccessibilitySlowKeysThreshold(context) != 0;
+    }
+
+    /**
+     * Get Accessibility slow keys threshold duration in milliseconds.
+     *
+     * <p>
+     * 'Slow keys' is an accessibility feature to aid users who have physical disabilities, that
+     * allows the user to specify the duration for which one must press-and-hold a key before the
+     * system accepts the keypress.
+     * </p>
+     *
+     * @hide
+     */
+    public static int getAccessibilitySlowKeysThreshold(@NonNull Context context) {
+        if (!isAccessibilitySlowKeysFeatureFlagEnabled()) {
+            return 0;
+        }
+        return Settings.Secure.getIntForUser(context.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_SLOW_KEYS, 0, UserHandle.USER_CURRENT);
+    }
+
+    /**
+     * Set Accessibility slow keys threshold duration in milliseconds.
+     * @param thresholdTimeMillis time duration for which a key should be pressed to be registered
+     *                            in the system. The threshold must be between 0 and
+     *                            {@link MAX_ACCESSIBILITY_SLOW_KEYS_THRESHOLD_MILLIS}
+     *
+     * <p>
+     * 'Slow keys' is an accessibility feature to aid users who have physical disabilities, that
+     * allows the user to specify the duration for which one must press-and-hold a key before the
+     * system accepts the keypress.
+     * </p>
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+    public static void setAccessibilitySlowKeysThreshold(@NonNull Context context,
+            int thresholdTimeMillis) {
+        if (!isAccessibilitySlowKeysFeatureFlagEnabled()) {
+            return;
+        }
+        if (thresholdTimeMillis < 0
+                || thresholdTimeMillis > MAX_ACCESSIBILITY_SLOW_KEYS_THRESHOLD_MILLIS) {
+            throw new IllegalArgumentException(
+                    "Provided Slow keys threshold should be in range [0, "
+                            + MAX_ACCESSIBILITY_SLOW_KEYS_THRESHOLD_MILLIS + "]");
+        }
+        Settings.Secure.putIntForUser(context.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_SLOW_KEYS, thresholdTimeMillis,
+                UserHandle.USER_CURRENT);
+    }
+
+    /**
      * Whether Accessibility sticky keys feature is enabled.
      *
      * <p>
diff --git a/core/java/android/hardware/input/VirtualStylus.java b/core/java/android/hardware/input/VirtualStylus.java
new file mode 100644
index 0000000..c763f740
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualStylus.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.flags.Flags;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A virtual stylus which can be used to inject input into the framework that represents a stylus
+ * on a remote device.
+ *
+ * This registers an {@link android.view.InputDevice} that is interpreted like a
+ * physically-connected device and dispatches received events to it.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
+@SystemApi
+public class VirtualStylus extends VirtualInputDevice {
+    /** @hide */
+    public VirtualStylus(VirtualStylusConfig config, IVirtualDevice virtualDevice,
+            IBinder token) {
+        super(config, virtualDevice, token);
+    }
+
+    /**
+     * Sends a motion event to the system.
+     *
+     * @param event the event to send
+     */
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void sendMotionEvent(@NonNull VirtualStylusMotionEvent event) {
+        try {
+            if (!mVirtualDevice.sendStylusMotionEvent(mToken, event)) {
+                Log.w(TAG, "Failed to send motion event from virtual stylus "
+                        + mConfig.getInputDeviceName());
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sends a button event to the system.
+     *
+     * @param event the event to send
+     */
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void sendButtonEvent(@NonNull VirtualStylusButtonEvent event) {
+        try {
+            if (!mVirtualDevice.sendStylusButtonEvent(mToken, event)) {
+                Log.w(TAG, "Failed to send button event from virtual stylus "
+                        + mConfig.getInputDeviceName());
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/hardware/input/VirtualStylusButtonEvent.aidl b/core/java/android/hardware/input/VirtualStylusButtonEvent.aidl
new file mode 100644
index 0000000..7de32cc
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualStylusButtonEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+parcelable VirtualStylusButtonEvent;
diff --git a/core/java/android/hardware/input/VirtualStylusButtonEvent.java b/core/java/android/hardware/input/VirtualStylusButtonEvent.java
new file mode 100644
index 0000000..97a4cd0
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualStylusButtonEvent.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.companion.virtual.flags.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An event describing a stylus button click interaction originating from a remote device.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
+@SystemApi
+public final class VirtualStylusButtonEvent implements Parcelable {
+    /** @hide */
+    public static final int ACTION_UNKNOWN = -1;
+    /** Action indicating the stylus button has been pressed. */
+    public static final int ACTION_BUTTON_PRESS = MotionEvent.ACTION_BUTTON_PRESS;
+    /** Action indicating the stylus button has been released. */
+    public static final int ACTION_BUTTON_RELEASE = MotionEvent.ACTION_BUTTON_RELEASE;
+    /** @hide */
+    @IntDef(prefix = {"ACTION_"}, value = {
+            ACTION_UNKNOWN,
+            ACTION_BUTTON_PRESS,
+            ACTION_BUTTON_RELEASE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Action {}
+
+    /** @hide */
+    public static final int BUTTON_UNKNOWN = -1;
+    /** Action indicating the stylus button involved in this event is primary. */
+    public static final int BUTTON_PRIMARY = MotionEvent.BUTTON_STYLUS_PRIMARY;
+    /** Action indicating the stylus button involved in this event is secondary. */
+    public static final int BUTTON_SECONDARY = MotionEvent.BUTTON_STYLUS_SECONDARY;
+    /** @hide */
+    @IntDef(prefix = {"BUTTON_"}, value = {
+            BUTTON_UNKNOWN,
+            BUTTON_PRIMARY,
+            BUTTON_SECONDARY,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Button {}
+
+    @Action
+    private final int mAction;
+    @Button
+    private final int mButtonCode;
+    private final long mEventTimeNanos;
+
+    private VirtualStylusButtonEvent(@Action int action, @Button int buttonCode,
+            long eventTimeNanos) {
+        mAction = action;
+        mButtonCode = buttonCode;
+        mEventTimeNanos = eventTimeNanos;
+    }
+
+    private VirtualStylusButtonEvent(@NonNull Parcel parcel) {
+        mAction = parcel.readInt();
+        mButtonCode = parcel.readInt();
+        mEventTimeNanos = parcel.readLong();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) {
+        parcel.writeInt(mAction);
+        parcel.writeInt(mButtonCode);
+        parcel.writeLong(mEventTimeNanos);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Returns the button code associated with this event.
+     */
+    @Button
+    public int getButtonCode() {
+        return mButtonCode;
+    }
+
+    /**
+     * Returns the action associated with this event.
+     */
+    @Action
+    public int getAction() {
+        return mAction;
+    }
+
+    /**
+     * Returns the time this event occurred, in the {@link SystemClock#uptimeMillis()} time base but
+     * with nanosecond (instead of millisecond) precision.
+     *
+     * @see InputEvent#getEventTime()
+     */
+    public long getEventTimeNanos() {
+        return mEventTimeNanos;
+    }
+
+    /**
+     * Builder for {@link VirtualStylusButtonEvent}.
+     */
+    @FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
+    public static final class Builder {
+
+        @Action
+        private int mAction = ACTION_UNKNOWN;
+        @Button
+        private int mButtonCode = BUTTON_UNKNOWN;
+        private long mEventTimeNanos = 0L;
+
+        /**
+         * Creates a {@link VirtualStylusButtonEvent} object with the current builder configuration.
+         */
+        @NonNull
+        public VirtualStylusButtonEvent build() {
+            if (mAction == ACTION_UNKNOWN) {
+                throw new IllegalArgumentException(
+                        "Cannot build stylus button event with unset action");
+            }
+            if (mButtonCode == BUTTON_UNKNOWN) {
+                throw new IllegalArgumentException(
+                        "Cannot build stylus button event with unset button code");
+            }
+            return new VirtualStylusButtonEvent(mAction, mButtonCode, mEventTimeNanos);
+        }
+
+        /**
+         * Sets the button code of the event.
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        @NonNull
+        public Builder setButtonCode(@Button int buttonCode) {
+            if (buttonCode != BUTTON_PRIMARY && buttonCode != BUTTON_SECONDARY) {
+                throw new IllegalArgumentException(
+                        "Unsupported stylus button code : " + buttonCode);
+            }
+            mButtonCode = buttonCode;
+            return this;
+        }
+
+        /**
+         * Sets the action of the event.
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        @NonNull
+        public Builder setAction(@Action int action) {
+            if (action != ACTION_BUTTON_PRESS && action != ACTION_BUTTON_RELEASE) {
+                throw new IllegalArgumentException("Unsupported stylus button action : " + action);
+            }
+            mAction = action;
+            return this;
+        }
+
+        /**
+         * Sets the time (in nanoseconds) when this specific event was generated. This may be
+         * obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of
+         * millisecond), but can be different depending on the use case.
+         * This field is optional and can be omitted.
+         *
+         * @return this builder, to allow for chaining of calls
+         * @see InputEvent#getEventTime()
+         */
+        @NonNull
+        public Builder setEventTimeNanos(long eventTimeNanos) {
+            if (eventTimeNanos < 0L) {
+                throw new IllegalArgumentException("Event time cannot be negative");
+            }
+            this.mEventTimeNanos = eventTimeNanos;
+            return this;
+        }
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<VirtualStylusButtonEvent> CREATOR =
+            new Parcelable.Creator<>() {
+                public VirtualStylusButtonEvent createFromParcel(Parcel source) {
+                    return new VirtualStylusButtonEvent(source);
+                }
+
+                public VirtualStylusButtonEvent[] newArray(int size) {
+                    return new VirtualStylusButtonEvent[size];
+                }
+            };
+}
diff --git a/core/java/android/hardware/input/VirtualStylusConfig.aidl b/core/java/android/hardware/input/VirtualStylusConfig.aidl
new file mode 100644
index 0000000..a13eec2
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualStylusConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+parcelable VirtualStylusConfig;
diff --git a/core/java/android/hardware/input/VirtualStylusConfig.java b/core/java/android/hardware/input/VirtualStylusConfig.java
new file mode 100644
index 0000000..64cf1f5
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualStylusConfig.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.companion.virtual.flags.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Configurations to create a virtual stylus.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
+@SystemApi
+public final class VirtualStylusConfig extends VirtualTouchDeviceConfig implements Parcelable {
+
+    private VirtualStylusConfig(@NonNull Builder builder) {
+        super(builder);
+    }
+
+    private VirtualStylusConfig(@NonNull Parcel in) {
+        super(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+    }
+
+    @NonNull
+    public static final Creator<VirtualStylusConfig> CREATOR =
+            new Creator<>() {
+                @Override
+                public VirtualStylusConfig createFromParcel(Parcel in) {
+                    return new VirtualStylusConfig(in);
+                }
+
+                @Override
+                public VirtualStylusConfig[] newArray(int size) {
+                    return new VirtualStylusConfig[size];
+                }
+            };
+
+    /**
+     * Builder for creating a {@link VirtualStylusConfig}.
+     */
+    @FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
+    public static final class Builder extends VirtualTouchDeviceConfig.Builder<Builder> {
+
+        /**
+         * Creates a new instance for the given dimensions of the screen targeted by the
+         * {@link VirtualStylus}.
+         *
+         * <p>The dimensions are not pixels but in the screen's raw coordinate space. They do
+         * not necessarily have to correspond to the display size or aspect ratio. In this case the
+         * framework will handle the scaling appropriately.
+         *
+         * @param screenWidth The width of the targeted screen.
+         * @param screenHeight The height of the targeted screen.
+         */
+        public Builder(@IntRange(from = 1) int screenWidth,
+                @IntRange(from = 1) int screenHeight) {
+            super(screenWidth, screenHeight);
+        }
+
+        /**
+         * Builds the {@link VirtualStylusConfig} instance.
+         */
+        @NonNull
+        public VirtualStylusConfig build() {
+            return new VirtualStylusConfig(this);
+        }
+    }
+}
diff --git a/core/java/android/hardware/input/VirtualStylusMotionEvent.aidl b/core/java/android/hardware/input/VirtualStylusMotionEvent.aidl
new file mode 100644
index 0000000..42d14ab
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualStylusMotionEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+parcelable VirtualStylusMotionEvent;
diff --git a/core/java/android/hardware/input/VirtualStylusMotionEvent.java b/core/java/android/hardware/input/VirtualStylusMotionEvent.java
new file mode 100644
index 0000000..2ab76ae
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualStylusMotionEvent.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.companion.virtual.flags.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An event describing a stylus interaction originating from a remote device.
+ *
+ * The tool type, location and action are required; tilts and pressure are optional.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
+@SystemApi
+public final class VirtualStylusMotionEvent implements Parcelable {
+    private static final int TILT_MIN = -90;
+    private static final int TILT_MAX = 90;
+    private static final int PRESSURE_MIN = 0;
+    private static final int PRESSURE_MAX = 255;
+
+    /** @hide */
+    public static final int TOOL_TYPE_UNKNOWN = MotionEvent.TOOL_TYPE_UNKNOWN;
+    /** Tool type indicating that a stylus is the origin of the event. */
+    public static final int TOOL_TYPE_STYLUS = MotionEvent.TOOL_TYPE_STYLUS;
+    /** Tool type indicating that an eraser is the origin of the event. */
+    public static final int TOOL_TYPE_ERASER = MotionEvent.TOOL_TYPE_ERASER;
+    /** @hide */
+    @IntDef(prefix = { "TOOL_TYPE_" }, value = {
+            TOOL_TYPE_UNKNOWN,
+            TOOL_TYPE_STYLUS,
+            TOOL_TYPE_ERASER,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ToolType {}
+
+    /** @hide */
+    public static final int ACTION_UNKNOWN = -1;
+    /**
+     * Action indicating the stylus has been pressed down to the screen. ACTION_DOWN with pressure
+     * {@code 0} indicates that the stylus is hovering over the screen, and non-zero pressure
+     * indicates that the stylus is touching the screen.
+     */
+    public static final int ACTION_DOWN = MotionEvent.ACTION_DOWN;
+    /** Action indicating the stylus has been lifted from the screen. */
+    public static final int ACTION_UP = MotionEvent.ACTION_UP;
+    /** Action indicating the stylus has been moved along the screen. */
+    public static final int ACTION_MOVE = MotionEvent.ACTION_MOVE;
+    /** @hide */
+    @IntDef(prefix = { "ACTION_" }, value = {
+            ACTION_UNKNOWN,
+            ACTION_DOWN,
+            ACTION_UP,
+            ACTION_MOVE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Action {}
+
+    @ToolType
+    private final int mToolType;
+    @Action
+    private final int mAction;
+    private final int mX;
+    private final int mY;
+    private final int mPressure;
+    private final int mTiltX;
+    private final int mTiltY;
+    private final long mEventTimeNanos;
+
+    private VirtualStylusMotionEvent(@ToolType int toolType, @Action int action, int x, int y,
+            int pressure, int tiltX, int tiltY, long eventTimeNanos) {
+        mToolType = toolType;
+        mAction = action;
+        mX = x;
+        mY = y;
+        mPressure = pressure;
+        mTiltX = tiltX;
+        mTiltY = tiltY;
+        mEventTimeNanos = eventTimeNanos;
+    }
+
+    private VirtualStylusMotionEvent(@NonNull Parcel parcel) {
+        mToolType = parcel.readInt();
+        mAction = parcel.readInt();
+        mX = parcel.readInt();
+        mY = parcel.readInt();
+        mPressure = parcel.readInt();
+        mTiltX = parcel.readInt();
+        mTiltY = parcel.readInt();
+        mEventTimeNanos = parcel.readLong();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mToolType);
+        dest.writeInt(mAction);
+        dest.writeInt(mX);
+        dest.writeInt(mY);
+        dest.writeInt(mPressure);
+        dest.writeInt(mTiltX);
+        dest.writeInt(mTiltY);
+        dest.writeLong(mEventTimeNanos);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Returns the tool type associated with this event.
+     */
+    @ToolType
+    public int getToolType() {
+        return mToolType;
+    }
+
+    /**
+     * Returns the action associated with this event.
+     */
+    @Action
+    public int getAction() {
+        return mAction;
+    }
+
+    /**
+     * Returns the x-axis location associated with this event.
+     */
+    public int getX() {
+        return mX;
+    }
+
+    /**
+     * Returns the y-axis location associated with this event.
+     */
+    public int getY() {
+        return mY;
+    }
+
+    /**
+     * Returns the pressure associated with this event. {@code 0} pressure indicates that the stylus
+     * is hovering, otherwise the stylus is touching the screen. Returns {@code 255} if omitted.
+     */
+    public int getPressure() {
+        return mPressure;
+    }
+
+    /**
+     * Returns the plane angle (in degrees, in the range of [{@code -90}, {@code 90}]) between the
+     * y-z plane and the plane containing both the stylus axis and the y axis. A positive tiltX is
+     * to the right, in the direction of increasing x values. {@code 0} tilt indicates that the
+     * stylus is perpendicular to the x-axis. Returns {@code 0} if omitted.
+     *
+     * @see Builder#setTiltX
+     */
+    public int getTiltX() {
+        return mTiltX;
+    }
+
+    /**
+     * Returns the plane angle (in degrees, in the range of [{@code -90}, {@code 90}]) between the
+     * x-z plane and the plane containing both the stylus axis and the x axis. A positive tiltY is
+     * towards the user, in the direction of increasing y values. {@code 0} tilt indicates that the
+     * stylus is perpendicular to the y-axis. Returns {@code 0} if omitted.
+     *
+     * @see Builder#setTiltY
+     */
+    public int getTiltY() {
+        return mTiltY;
+    }
+
+    /**
+     * Returns the time this event occurred, in the {@link SystemClock#uptimeMillis()} time base but
+     * with nanosecond (instead of millisecond) precision.
+     *
+     * @see InputEvent#getEventTime()
+     */
+    public long getEventTimeNanos() {
+        return mEventTimeNanos;
+    }
+
+    /**
+     * Builder for {@link VirtualStylusMotionEvent}.
+     */
+    @FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
+    public static final class Builder {
+
+        @ToolType
+        private int mToolType = TOOL_TYPE_UNKNOWN;
+        @Action
+        private int mAction = ACTION_UNKNOWN;
+        private int mX = 0;
+        private int mY = 0;
+        private boolean mIsXSet = false;
+        private boolean mIsYSet = false;
+        private int mPressure = PRESSURE_MAX;
+        private int mTiltX = 0;
+        private int mTiltY = 0;
+        private long mEventTimeNanos = 0L;
+
+        /**
+         * Creates a {@link VirtualStylusMotionEvent} object with the current builder configuration.
+         *
+         * @throws IllegalArgumentException if one of the required arguments (action, tool type,
+         * x-axis location and y-axis location) is missing.
+         * {@link VirtualStylusMotionEvent} for a detailed explanation.
+         */
+        @NonNull
+        public VirtualStylusMotionEvent build() {
+            if (mToolType == TOOL_TYPE_UNKNOWN) {
+                throw new IllegalArgumentException(
+                        "Cannot build stylus motion event with unset tool type");
+            }
+            if (mAction == ACTION_UNKNOWN) {
+                throw new IllegalArgumentException(
+                        "Cannot build stylus motion event with unset action");
+            }
+            if (!mIsXSet) {
+                throw new IllegalArgumentException(
+                        "Cannot build stylus motion event with unset x-axis location");
+            }
+            if (!mIsYSet) {
+                throw new IllegalArgumentException(
+                        "Cannot build stylus motion event with unset y-axis location");
+            }
+            return new VirtualStylusMotionEvent(mToolType, mAction, mX, mY, mPressure, mTiltX,
+                    mTiltY, mEventTimeNanos);
+        }
+
+        /**
+         * Sets the tool type of the event.
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        @NonNull
+        public Builder setToolType(@ToolType int toolType) {
+            if (toolType != TOOL_TYPE_STYLUS && toolType != TOOL_TYPE_ERASER) {
+                throw new IllegalArgumentException("Unsupported stylus tool type: " + toolType);
+            }
+            mToolType = toolType;
+            return this;
+        }
+
+        /**
+         * Sets the action of the event.
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        @NonNull
+        public Builder setAction(@Action int action) {
+            if (action != ACTION_DOWN && action != ACTION_UP && action != ACTION_MOVE) {
+                throw new IllegalArgumentException("Unsupported stylus action : " + action);
+            }
+            mAction = action;
+            return this;
+        }
+
+        /**
+         * Sets the x-axis location of the event.
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        @NonNull
+        public Builder setX(int absX) {
+            mX = absX;
+            mIsXSet = true;
+            return this;
+        }
+
+        /**
+         * Sets the y-axis location of the event.
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        @NonNull
+        public Builder setY(int absY) {
+            mY = absY;
+            mIsYSet = true;
+            return this;
+        }
+
+        /**
+         * Sets the pressure of the event. {@code 0} pressure indicates that the stylus is hovering,
+         * otherwise the stylus is touching the screen. This field is optional and can be omitted
+         * (defaults to {@code 255}).
+         *
+         * @param pressure The pressure of the stylus.
+         *
+         * @throws IllegalArgumentException if the pressure is smaller than 0 or greater than 255.
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        @NonNull
+        public Builder setPressure(
+                @IntRange(from = PRESSURE_MIN, to = PRESSURE_MAX) int pressure) {
+            if (pressure < PRESSURE_MIN || pressure > PRESSURE_MAX) {
+                throw new IllegalArgumentException(
+                        "Pressure should be between " + PRESSURE_MIN + " and " + PRESSURE_MAX);
+            }
+            mPressure = pressure;
+            return this;
+        }
+
+        /**
+         * Sets the x-axis tilt of the event in degrees. {@code 0} tilt indicates that the stylus is
+         * perpendicular to the x-axis. This field is optional and can be omitted (defaults to
+         * {@code 0}). Both x-axis tilt and y-axis tilt are used to derive the tilt and orientation
+         * of the stylus, given by {@link MotionEvent#AXIS_TILT} and
+         * {@link MotionEvent#AXIS_ORIENTATION} respectively.
+         *
+         * @throws IllegalArgumentException if the tilt is smaller than -90 or greater than 90.
+         *
+         * @return this builder, to allow for chaining of calls
+         *
+         * @see VirtualStylusMotionEvent#getTiltX
+         * @see <a href="https://source.android.com/docs/core/interaction/input/touch-devices#orientation-and-tilt-fields">
+         *     Stylus tilt and orientation</a>
+         */
+        @NonNull
+        public Builder setTiltX(@IntRange(from = TILT_MIN, to = TILT_MAX) int tiltX) {
+            validateTilt(tiltX);
+            mTiltX = tiltX;
+            return this;
+        }
+
+        /**
+         * Sets the y-axis tilt of the event in degrees. {@code 0} tilt indicates that the stylus is
+         * perpendicular to the y-axis. This field is optional and can be omitted (defaults to
+         * {@code 0}). Both x-axis tilt and y-axis tilt are used to derive the tilt and orientation
+         * of the stylus, given by {@link MotionEvent#AXIS_TILT} and
+         * {@link MotionEvent#AXIS_ORIENTATION} respectively.
+         *
+         * @throws IllegalArgumentException if the tilt is smaller than -90 or greater than 90.
+         *
+         * @return this builder, to allow for chaining of calls
+         *
+         * @see VirtualStylusMotionEvent#getTiltY
+         * @see <a href="https://source.android.com/docs/core/interaction/input/touch-devices#orientation-and-tilt-fields">
+         *     Stylus tilt and orientation</a>
+         */
+        @NonNull
+        public Builder setTiltY(@IntRange(from = TILT_MIN, to = TILT_MAX) int tiltY) {
+            validateTilt(tiltY);
+            mTiltY = tiltY;
+            return this;
+        }
+
+        /**
+         * Sets the time (in nanoseconds) when this specific event was generated. This may be
+         * obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of
+         * millisecond), but can be different depending on the use case.
+         * This field is optional and can be omitted.
+         *
+         * @return this builder, to allow for chaining of calls
+         * @see InputEvent#getEventTime()
+         */
+        @NonNull
+        public Builder setEventTimeNanos(long eventTimeNanos) {
+            if (eventTimeNanos < 0L) {
+                throw new IllegalArgumentException("Event time cannot be negative");
+            }
+            mEventTimeNanos = eventTimeNanos;
+            return this;
+        }
+
+        private void validateTilt(int tilt) {
+            if (tilt < TILT_MIN || tilt > TILT_MAX) {
+                throw new IllegalArgumentException(
+                        "Tilt must be between " + TILT_MIN + " and " + TILT_MAX);
+            }
+        }
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<VirtualStylusMotionEvent> CREATOR =
+            new Parcelable.Creator<>() {
+                public VirtualStylusMotionEvent createFromParcel(Parcel source) {
+                    return new VirtualStylusMotionEvent(source);
+                }
+                public VirtualStylusMotionEvent[] newArray(int size) {
+                    return new VirtualStylusMotionEvent[size];
+                }
+            };
+}
diff --git a/core/java/android/hardware/input/VirtualTouchDeviceConfig.java b/core/java/android/hardware/input/VirtualTouchDeviceConfig.java
new file mode 100644
index 0000000..2e2cfab
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualTouchDeviceConfig.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.os.Parcel;
+
+/**
+ * Configurations to create a virtual touch-based device.
+ *
+ * @hide
+ */
+abstract class VirtualTouchDeviceConfig extends VirtualInputDeviceConfig {
+
+    /** The touch device width. */
+    private final int mWidth;
+    /** The touch device height. */
+    private final int mHeight;
+
+    VirtualTouchDeviceConfig(@NonNull Builder<? extends Builder<?>> builder) {
+        super(builder);
+        mWidth = builder.mWidth;
+        mHeight = builder.mHeight;
+    }
+
+    VirtualTouchDeviceConfig(@NonNull Parcel in) {
+        super(in);
+        mWidth = in.readInt();
+        mHeight = in.readInt();
+    }
+
+    /** Returns the touch device width. */
+    public int getWidth() {
+        return mWidth;
+    }
+
+    /** Returns the touch device height. */
+    public int getHeight() {
+        return mHeight;
+    }
+
+    @Override
+    void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(mWidth);
+        dest.writeInt(mHeight);
+    }
+
+    @Override
+    @NonNull
+    String additionalFieldsToString() {
+        return " width=" + mWidth + " height=" + mHeight;
+    }
+
+    /**
+     * Builder for creating a {@link VirtualTouchDeviceConfig}.
+     *
+     * @param <T> The subclass to be built.
+     */
+    abstract static class Builder<T extends Builder<T>>
+            extends VirtualInputDeviceConfig.Builder<T> {
+
+        private final int mWidth;
+        private final int mHeight;
+
+        /**
+         * Creates a new instance for the given dimensions of the touch device.
+         *
+         * <p>The dimensions are not pixels but in the screen's raw coordinate space. They do
+         * not necessarily have to correspond to the display size or aspect ratio. In this case the
+         * framework will handle the scaling appropriately.
+         *
+         * @param touchDeviceWidth The width of the touch device.
+         * @param touchDeviceHeight The height of the touch device.
+         */
+        Builder(@IntRange(from = 1) int touchDeviceWidth,
+                @IntRange(from = 1) int touchDeviceHeight) {
+            if (touchDeviceHeight <= 0 || touchDeviceWidth <= 0) {
+                throw new IllegalArgumentException(
+                        "Cannot create a virtual touch-based device, dimensions must be "
+                                + "positive. Got: (" + touchDeviceHeight + ", "
+                                + touchDeviceWidth + ")");
+            }
+            mHeight = touchDeviceHeight;
+            mWidth = touchDeviceWidth;
+        }
+    }
+}
diff --git a/core/java/android/hardware/input/VirtualTouchscreenConfig.java b/core/java/android/hardware/input/VirtualTouchscreenConfig.java
index 6308459..851cee6 100644
--- a/core/java/android/hardware/input/VirtualTouchscreenConfig.java
+++ b/core/java/android/hardware/input/VirtualTouchscreenConfig.java
@@ -23,38 +23,19 @@
 import android.os.Parcelable;
 
 /**
- * Configurations to create virtual touchscreen.
+ * Configurations to create a virtual touchscreen.
  *
  * @hide
  */
 @SystemApi
-public final class VirtualTouchscreenConfig extends VirtualInputDeviceConfig implements Parcelable {
-
-    /** The touchscreen width. */
-    private final int mWidth;
-    /** The touchscreen height. */
-    private final int mHeight;
+public final class VirtualTouchscreenConfig extends VirtualTouchDeviceConfig implements Parcelable {
 
     private VirtualTouchscreenConfig(@NonNull Builder builder) {
         super(builder);
-        mWidth = builder.mWidth;
-        mHeight = builder.mHeight;
     }
 
     private VirtualTouchscreenConfig(@NonNull Parcel in) {
         super(in);
-        mWidth = in.readInt();
-        mHeight = in.readInt();
-    }
-
-    /** Returns the touchscreen width. */
-    public int getWidth() {
-        return mWidth;
-    }
-
-    /** Returns the touchscreen height. */
-    public int getHeight() {
-        return mHeight;
     }
 
     @Override
@@ -65,19 +46,11 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
-        dest.writeInt(mWidth);
-        dest.writeInt(mHeight);
-    }
-
-    @Override
-    @NonNull
-    String additionalFieldsToString() {
-        return " width=" + mWidth + " height=" + mHeight;
     }
 
     @NonNull
     public static final Creator<VirtualTouchscreenConfig> CREATOR =
-            new Creator<VirtualTouchscreenConfig>() {
+            new Creator<>() {
                 @Override
                 public VirtualTouchscreenConfig createFromParcel(Parcel in) {
                     return new VirtualTouchscreenConfig(in);
@@ -92,9 +65,7 @@
     /**
      * Builder for creating a {@link VirtualTouchscreenConfig}.
      */
-    public static final class Builder extends VirtualInputDeviceConfig.Builder<Builder> {
-        private int mWidth;
-        private int mHeight;
+    public static final class Builder extends VirtualTouchDeviceConfig.Builder<Builder> {
 
         /**
          * Creates a new instance for the given dimensions of the {@link VirtualTouchscreen}.
@@ -108,14 +79,7 @@
          */
         public Builder(@IntRange(from = 1) int touchscreenWidth,
                 @IntRange(from = 1) int touchscreenHeight) {
-            if (touchscreenHeight <= 0 || touchscreenWidth <= 0) {
-                throw new IllegalArgumentException(
-                        "Cannot create a virtual touchscreen, touchscreen dimensions must be "
-                                + "positive. Got: (" + touchscreenHeight + ", "
-                                + touchscreenWidth + ")");
-            }
-            mHeight = touchscreenHeight;
-            mWidth = touchscreenWidth;
+            super(touchscreenWidth, touchscreenHeight);
         }
 
         /**
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 362fe78..0ed6569 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -29,4 +29,11 @@
     name: "pointer_coords_is_resampled_api"
     description: "Makes MotionEvent.PointerCoords#isResampled() a public API"
     bug: "298197511"
+}
+
+flag {
+    namespace: "input_native"
+    name: "keyboard_a11y_slow_keys_flag"
+    description: "Controls if the slow keys accessibility feature for physical keyboard is available to the user"
+    bug: "294546335"
 }
\ No newline at end of file
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index c5167db..a3a2a2e 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -304,11 +304,7 @@
      *
      * @param id primary identifier of a program to fetch
      * @return the program info, or null if there is no such program on the list
-     *
-     * @deprecated Use {@link #getProgramInfos(ProgramSelector.Identifier)} to get all programs
-     * with the given primary identifier
      */
-    @Deprecated
     public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) {
         Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries;
         synchronized (mLock) {
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 7e5c141..4c95e02 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -312,20 +312,14 @@
     public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10;
     /**
      * 1: AM, 2:FM
-     * @deprecated use {@link #IDENTIFIER_TYPE_DRMO_FREQUENCY} instead
      */
-    @Deprecated
     public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11;
     /**
      * 32bit primary identifier for SiriusXM Satellite Radio.
-     *
-     * @deprecated SiriusXM Satellite Radio is not supported
      */
     public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12;
     /**
      * 0-999 range
-     *
-     * @deprecated SiriusXM Satellite Radio is not supported
      */
     public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13;
     /**
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index f0f7e8a..41f21ef 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -166,12 +166,7 @@
      * analog handover state managed from the HAL implementation side.
      *
      * <p>Some radio technologies may not support this, i.e. DAB.
-     *
-     * @deprecated Use {@link #CONFIG_FORCE_ANALOG_FM} instead. If {@link #CONFIG_FORCE_ANALOG_FM}
-     * is supported in HAL, {@link RadioTuner#setConfigFlag} and {@link RadioTuner#isConfigFlagSet}
-     * with CONFIG_FORCE_ANALOG will set/get the value of {@link #CONFIG_FORCE_ANALOG_FM}.
      */
-    @Deprecated
     public static final int CONFIG_FORCE_ANALOG = 2;
     /**
      * Forces the digital playback for the supporting radio technology.
diff --git a/core/java/android/hardware/radio/TEST_MAPPING b/core/java/android/hardware/radio/TEST_MAPPING
new file mode 100644
index 0000000..ee4eeb6
--- /dev/null
+++ b/core/java/android/hardware/radio/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "imports": [
+    {
+      "path": "frameworks/base/core/tests/BroadcastRadioTests"
+    }
+  ]
+}
diff --git a/core/java/android/metrics/LogMaker.java b/core/java/android/metrics/LogMaker.java
index 8644d91..f65b713 100644
--- a/core/java/android/metrics/LogMaker.java
+++ b/core/java/android/metrics/LogMaker.java
@@ -32,6 +32,7 @@
  * @hide
  */
 @SystemApi
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class LogMaker {
     private static final String TAG = "LogBuilder";
 
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 365f913..594ec18 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import static android.app.ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
 import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
 import static android.app.ActivityManager.procStateToString;
 import static android.content.pm.PackageManager.GET_SIGNATURES;
@@ -170,6 +171,8 @@
     public static final String FIREWALL_CHAIN_NAME_RESTRICTED = "restricted";
     /** @hide */
     public static final String FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY = "low_power_standby";
+    /** @hide */
+    public static final String FIREWALL_CHAIN_NAME_BACKGROUND = "background";
 
     private static final boolean ALLOW_PLATFORM_APP_POLICY = true;
 
@@ -180,6 +183,9 @@
     /** @hide */
     public static final int TOP_THRESHOLD_STATE = ActivityManager.PROCESS_STATE_BOUND_TOP;
 
+    /** @hide */
+    public static final int BACKGROUND_THRESHOLD_STATE = ActivityManager.PROCESS_STATE_TOP_SLEEPING;
+
     /**
      * {@link Intent} extra that indicates which {@link NetworkTemplate} rule it
      * applies to.
@@ -264,6 +270,16 @@
      * @hide
      */
     public static final int ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST = 1 << 6;
+
+    /**
+     * Flag to indicate that the app is exempt from always-on background network restrictions.
+     * Note that this is explicitly different to the flag NOT_FOREGROUND which is used to grant
+     * shared exception to apps from power restrictions like doze, battery saver and app-standby.
+     *
+     * @hide
+     */
+    public static final int ALLOWED_REASON_NOT_IN_BACKGROUND = 1 << 7;
+
     /**
      * Flag to indicate that app is exempt from certain metered network restrictions because user
      * explicitly exempted it.
@@ -822,6 +838,21 @@
     }
 
     /**
+     * This is currently only used as an implementation detail for
+     * {@link com.android.server.net.NetworkPolicyManagerService}.
+     * Only put here to be together with other isProcStateAllowed* methods.
+     *
+     * @hide
+     */
+    public static boolean isProcStateAllowedNetworkWhileBackground(@Nullable UidState uidState) {
+        if (uidState == null) {
+            return false;
+        }
+        return uidState.procState < BACKGROUND_THRESHOLD_STATE
+                || (uidState.capability & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0;
+    }
+
+    /**
      * Returns true if {@param procState} is considered foreground and as such will be allowed
      * to access network when the device is in data saver mode. Otherwise, false.
      * @hide
diff --git a/core/java/android/net/thread/OWNERS b/core/java/android/net/thread/OWNERS
new file mode 100644
index 0000000..55c307b
--- /dev/null
+++ b/core/java/android/net/thread/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1203089
+
+include platform/packages/modules/ThreadNetwork:/OWNERS
diff --git a/core/java/android/net/thread/flags.aconfig b/core/java/android/net/thread/flags.aconfig
new file mode 100644
index 0000000..6e72f8e
--- /dev/null
+++ b/core/java/android/net/thread/flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.net.thread.flags"
+
+flag {
+    name: "thread_user_restriction_enabled"
+    namespace: "thread_network"
+    description: "Controls whether user restriction on thread networks is enabled"
+    bug: "307679182"
+}
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 70cf973..83b7eda 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -80,8 +80,6 @@
      * <p>The VCN will only migrate to a Carrier WiFi network that has a signal strength greater
      * than, or equal to this threshold.
      *
-     * <p>WARNING: The VCN does not listen for changes to this key made after VCN startup.
-     *
      * @hide
      */
     @NonNull
@@ -94,14 +92,39 @@
      * <p>If the VCN's selected Carrier WiFi network has a signal strength less than this threshold,
      * the VCN will attempt to migrate away from the Carrier WiFi network.
      *
-     * <p>WARNING: The VCN does not listen for changes to this key made after VCN startup.
-     *
      * @hide
      */
     @NonNull
     public static final String VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY =
             "vcn_network_selection_wifi_exit_rssi_threshold";
 
+    /**
+     * Key for the interval to poll IpSecTransformState for packet loss monitoring
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY =
+            "vcn_network_selection_poll_ipsec_state_interval_seconds";
+
+    /**
+     * Key for the threshold of IPSec packet loss rate
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY =
+            "vcn_network_selection_ipsec_packet_loss_percent_threshold";
+
+    /**
+     * Key for the list of timeouts in minute to stop penalizing an underlying network candidate
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY =
+            "vcn_network_selection_penalty_timeout_minutes_list";
+
     // TODO: Add separate signal strength thresholds for 2.4 GHz and 5GHz
 
     /**
@@ -115,6 +138,20 @@
             "vcn_restricted_transports";
 
     /**
+     * Key for number of seconds to wait before entering safe mode
+     *
+     * <p>A VcnGatewayConnection will enter safe mode when it takes over the configured timeout to
+     * enter {@link ConnectedState}.
+     *
+     * <p>Defaults to 30, unless overridden by carrier config
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY =
+            "vcn_safe_mode_timeout_seconds_key";
+
+    /**
      * Key for maximum number of parallel SAs for tunnel aggregation
      *
      * <p>If set to a value > 1, multiple tunnels will be set up, and inbound traffic will be
@@ -134,7 +171,11 @@
             new String[] {
                 VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY,
                 VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
+                VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY,
+                VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY,
+                VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY,
                 VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
+                VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY,
                 VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY,
             };
 
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index 6956916..7afd721 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -5,4 +5,18 @@
     namespace: "vcn"
     description: "Feature flag for safe mode configurability"
     bug: "276358140"
+}
+
+flag {
+    name: "safe_mode_timeout_config"
+    namespace: "vcn"
+    description: "Feature flag for adjustable safe mode timeout"
+    bug: "317406085"
+}
+
+flag{
+    name: "network_metric_monitor"
+    namespace: "vcn"
+    description: "Feature flag for enabling network metric monitor"
+    bug: "282996138"
 }
\ No newline at end of file
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
deleted file mode 100644
index 286cf28..0000000
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nfc;
-
-import android.app.PendingIntent;
-import android.content.IntentFilter;
-import android.nfc.NdefMessage;
-import android.nfc.Tag;
-import android.nfc.TechListParcel;
-import android.nfc.IAppCallback;
-import android.nfc.INfcAdapterExtras;
-import android.nfc.INfcControllerAlwaysOnListener;
-import android.nfc.INfcTag;
-import android.nfc.INfcCardEmulation;
-import android.nfc.INfcFCardEmulation;
-import android.nfc.INfcUnlockHandler;
-import android.nfc.ITagRemovedCallback;
-import android.nfc.INfcDta;
-import android.nfc.INfcWlcStateListener;
-import android.nfc.NfcAntennaInfo;
-import android.os.Bundle;
-import android.nfc.WlcLDeviceInfo;
-
-/**
- * @hide
- */
-interface INfcAdapter
-{
-    INfcTag getNfcTagInterface();
-    INfcCardEmulation getNfcCardEmulationInterface();
-    INfcFCardEmulation getNfcFCardEmulationInterface();
-    INfcAdapterExtras getNfcAdapterExtrasInterface(in String pkg);
-    INfcDta getNfcDtaInterface(in String pkg);
-    int getState();
-    boolean disable(boolean saveState);
-    boolean enable();
-    void pausePolling(int timeoutInMs);
-    void resumePolling();
-
-    void setForegroundDispatch(in PendingIntent intent,
-            in IntentFilter[] filters, in TechListParcel techLists);
-    void setAppCallback(in IAppCallback callback);
-
-    boolean ignore(int nativeHandle, int debounceMs, ITagRemovedCallback callback);
-
-    void dispatch(in Tag tag);
-
-    void setReaderMode (IBinder b, IAppCallback callback, int flags, in Bundle extras);
-
-    void addNfcUnlockHandler(INfcUnlockHandler unlockHandler, in int[] techList);
-    void removeNfcUnlockHandler(INfcUnlockHandler unlockHandler);
-
-    void verifyNfcPermission();
-    boolean isNfcSecureEnabled();
-    boolean deviceSupportsNfcSecure();
-    boolean setNfcSecure(boolean enable);
-    NfcAntennaInfo getNfcAntennaInfo();
-
-    boolean setControllerAlwaysOn(boolean value);
-    boolean isControllerAlwaysOn();
-    boolean isControllerAlwaysOnSupported();
-    void registerControllerAlwaysOnListener(in INfcControllerAlwaysOnListener listener);
-    void unregisterControllerAlwaysOnListener(in INfcControllerAlwaysOnListener listener);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
-    boolean isTagIntentAppPreferenceSupported();
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
-    Map getTagIntentAppPreferenceForUser(int userId);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
-    int setTagIntentAppPreferenceForUser(int userId, String pkg, boolean allow);
-
-    boolean isReaderOptionEnabled();
-    boolean isReaderOptionSupported();
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
-    boolean enableReaderOption(boolean enable);
-    boolean isObserveModeSupported();
-    boolean setObserveMode(boolean enabled);
-
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
-    boolean enableWlc(boolean enable);
-    boolean isWlcEnabled();
-    void registerWlcStateListener(in INfcWlcStateListener listener);
-    void unregisterWlcStateListener(in INfcWlcStateListener listener);
-    WlcLDeviceInfo getWlcLDeviceInfo();
-
-    void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags);
-}
diff --git a/core/java/android/nfc/INfcCardEmulation.aidl b/core/java/android/nfc/INfcCardEmulation.aidl
deleted file mode 100644
index f4b4604..0000000
--- a/core/java/android/nfc/INfcCardEmulation.aidl
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nfc;
-
-import android.content.ComponentName;
-import android.nfc.cardemulation.AidGroup;
-import android.nfc.cardemulation.ApduServiceInfo;
-import android.os.RemoteCallback;
-
-/**
- * @hide
- */
-interface INfcCardEmulation
-{
-    boolean isDefaultServiceForCategory(int userHandle, in ComponentName service, String category);
-    boolean isDefaultServiceForAid(int userHandle, in ComponentName service, String aid);
-    boolean setDefaultServiceForCategory(int userHandle, in ComponentName service, String category);
-    boolean setDefaultForNextTap(int userHandle, in ComponentName service);
-    boolean setServiceObserveModeDefault(int userId, in android.content.ComponentName service, boolean enable);
-    boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup);
-    boolean setOffHostForService(int userHandle, in ComponentName service, in String offHostSecureElement);
-    boolean unsetOffHostForService(int userHandle, in ComponentName service);
-    AidGroup getAidGroupForService(int userHandle, in ComponentName service, String category);
-    boolean removeAidGroupForService(int userHandle, in ComponentName service, String category);
-    List<ApduServiceInfo> getServices(int userHandle, in String category);
-    boolean setPreferredService(in ComponentName service);
-    boolean unsetPreferredService();
-    boolean supportsAidPrefixRegistration();
-    ApduServiceInfo getPreferredPaymentService(int userHandle);
-    boolean setServiceEnabledForCategoryOther(int userHandle, in ComponentName app, boolean status);
-    boolean isDefaultPaymentRegistered();
-
-    boolean overrideRoutingTable(int userHandle, String protocol, String technology);
-    boolean recoverRoutingTable(int userHandle);
-}
diff --git a/core/java/android/nfc/INfcWlcStateListener.aidl b/core/java/android/nfc/INfcWlcStateListener.aidl
deleted file mode 100644
index c2b7075..0000000
--- a/core/java/android/nfc/INfcWlcStateListener.aidl
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nfc;
-
-import android.nfc.WlcLDeviceInfo;
-/**
- * @hide
- */
-oneway interface INfcWlcStateListener {
-  /**
-   * Called whenever NFC WLC state changes
-   *
-   * @param wlcLDeviceInfo NFC wlc listener information
-   */
-  void onWlcStateChanged(in WlcLDeviceInfo wlcLDeviceInfo);
-}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
deleted file mode 100644
index 8219d2f..0000000
--- a/core/java/android/nfc/NfcAdapter.java
+++ /dev/null
@@ -1,2909 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nfc;
-
-import android.annotation.CallbackExecutor;
-import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
-import android.annotation.SdkConstant;
-import android.annotation.SdkConstant.SdkConstantType;
-import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
-import android.annotation.UserIdInt;
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Context;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.net.Uri;
-import android.nfc.tech.MifareClassic;
-import android.nfc.tech.Ndef;
-import android.nfc.tech.NfcA;
-import android.nfc.tech.NfcF;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-
-/**
- * Represents the local NFC adapter.
- * <p>
- * Use the helper {@link #getDefaultAdapter(Context)} to get the default NFC
- * adapter for this Android device.
- *
- * <div class="special reference">
- * <h3>Developer Guides</h3>
- * <p>For more information about using NFC, read the
- * <a href="{@docRoot}guide/topics/nfc/index.html">Near Field Communication</a> developer guide.</p>
- * <p>To perform basic file sharing between devices, read
- * <a href="{@docRoot}training/beam-files/index.html">Sharing Files with NFC</a>.
- * </div>
- */
-public final class NfcAdapter {
-    static final String TAG = "NFC";
-
-    private final NfcControllerAlwaysOnListener mControllerAlwaysOnListener;
-    private final NfcWlcStateListener mNfcWlcStateListener;
-
-    /**
-     * Intent to start an activity when a tag with NDEF payload is discovered.
-     *
-     * <p>The system inspects the first {@link NdefRecord} in the first {@link NdefMessage} and
-     * looks for a URI, SmartPoster, or MIME record. If a URI or SmartPoster record is found the
-     * intent will contain the URI in its data field. If a MIME record is found the intent will
-     * contain the MIME type in its type field. This allows activities to register
-     * {@link IntentFilter}s targeting specific content on tags. Activities should register the
-     * most specific intent filters possible to avoid the activity chooser dialog, which can
-     * disrupt the interaction with the tag as the user interacts with the screen.
-     *
-     * <p>If the tag has an NDEF payload this intent is started before
-     * {@link #ACTION_TECH_DISCOVERED}. If any activities respond to this intent neither
-     * {@link #ACTION_TECH_DISCOVERED} or {@link #ACTION_TAG_DISCOVERED} will be started.
-     *
-     * <p>The MIME type or data URI of this intent are normalized before dispatch -
-     * so that MIME, URI scheme and URI host are always lower-case.
-     */
-    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
-
-    /**
-     * Intent to start an activity when a tag is discovered and activities are registered for the
-     * specific technologies on the tag.
-     *
-     * <p>To receive this intent an activity must include an intent filter
-     * for this action and specify the desired tech types in a
-     * manifest <code>meta-data</code> entry. Here is an example manfiest entry:
-     * <pre>
-     * &lt;activity android:name=".nfc.TechFilter" android:label="NFC/TechFilter"&gt;
-     *     &lt;!-- Add a technology filter --&gt;
-     *     &lt;intent-filter&gt;
-     *         &lt;action android:name="android.nfc.action.TECH_DISCOVERED" /&gt;
-     *     &lt;/intent-filter&gt;
-     *
-     *     &lt;meta-data android:name="android.nfc.action.TECH_DISCOVERED"
-     *         android:resource="@xml/filter_nfc"
-     *     /&gt;
-     * &lt;/activity&gt;</pre>
-     *
-     * <p>The meta-data XML file should contain one or more <code>tech-list</code> entries
-     * each consisting or one or more <code>tech</code> entries. The <code>tech</code> entries refer
-     * to the qualified class name implementing the technology, for example "android.nfc.tech.NfcA".
-     *
-     * <p>A tag matches if any of the
-     * <code>tech-list</code> sets is a subset of {@link Tag#getTechList() Tag.getTechList()}. Each
-     * of the <code>tech-list</code>s is considered independently and the
-     * activity is considered a match is any single <code>tech-list</code> matches the tag that was
-     * discovered. This provides AND and OR semantics for filtering desired techs. Here is an
-     * example that will match any tag using {@link NfcF} or any tag using {@link NfcA},
-     * {@link MifareClassic}, and {@link Ndef}:
-     *
-     * <pre>
-     * &lt;resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"&gt;
-     *     &lt;!-- capture anything using NfcF --&gt;
-     *     &lt;tech-list&gt;
-     *         &lt;tech&gt;android.nfc.tech.NfcF&lt;/tech&gt;
-     *     &lt;/tech-list&gt;
-     *
-     *     &lt;!-- OR --&gt;
-     *
-     *     &lt;!-- capture all MIFARE Classics with NDEF payloads --&gt;
-     *     &lt;tech-list&gt;
-     *         &lt;tech&gt;android.nfc.tech.NfcA&lt;/tech&gt;
-     *         &lt;tech&gt;android.nfc.tech.MifareClassic&lt;/tech&gt;
-     *         &lt;tech&gt;android.nfc.tech.Ndef&lt;/tech&gt;
-     *     &lt;/tech-list&gt;
-     * &lt;/resources&gt;</pre>
-     *
-     * <p>This intent is started after {@link #ACTION_NDEF_DISCOVERED} and before
-     * {@link #ACTION_TAG_DISCOVERED}. If any activities respond to {@link #ACTION_NDEF_DISCOVERED}
-     * this intent will not be started. If any activities respond to this intent
-     * {@link #ACTION_TAG_DISCOVERED} will not be started.
-     */
-    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_TECH_DISCOVERED = "android.nfc.action.TECH_DISCOVERED";
-
-    /**
-     * Intent to start an activity when a tag is discovered.
-     *
-     * <p>This intent will not be started when a tag is discovered if any activities respond to
-     * {@link #ACTION_NDEF_DISCOVERED} or {@link #ACTION_TECH_DISCOVERED} for the current tag.
-     */
-    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
-
-    /**
-     * Broadcast Action: Intent to notify an application that a transaction event has occurred
-     * on the Secure Element.
-     *
-     * <p>This intent will only be sent if the application has requested permission for
-     * {@link android.Manifest.permission#NFC_TRANSACTION_EVENT} and if the application has the
-     * necessary access to Secure Element which witnessed the particular event.
-     */
-    @RequiresPermission(android.Manifest.permission.NFC_TRANSACTION_EVENT)
-    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_TRANSACTION_DETECTED =
-            "android.nfc.action.TRANSACTION_DETECTED";
-
-    /**
-     * Broadcast Action: Intent to notify if the preferred payment service changed.
-     *
-     * <p>This intent will only be sent to the application has requested permission for
-     * {@link android.Manifest.permission#NFC_PREFERRED_PAYMENT_INFO} and if the application
-     * has the necessary access to Secure Element which witnessed the particular event.
-     */
-    @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
-    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_PREFERRED_PAYMENT_CHANGED =
-            "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
-
-    /**
-     * Broadcast to only the activity that handles ACTION_TAG_DISCOVERED
-     * @hide
-     */
-    public static final String ACTION_TAG_LEFT_FIELD = "android.nfc.action.TAG_LOST";
-
-    /**
-     * Mandatory extra containing the {@link Tag} that was discovered for the
-     * {@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED}, and
-     * {@link #ACTION_TAG_DISCOVERED} intents.
-     */
-    public static final String EXTRA_TAG = "android.nfc.extra.TAG";
-
-    /**
-     * Extra containing an array of {@link NdefMessage} present on the discovered tag.<p>
-     * This extra is mandatory for {@link #ACTION_NDEF_DISCOVERED} intents,
-     * and optional for {@link #ACTION_TECH_DISCOVERED}, and
-     * {@link #ACTION_TAG_DISCOVERED} intents.<p>
-     * When this extra is present there will always be at least one
-     * {@link NdefMessage} element. Most NDEF tags have only one NDEF message,
-     * but we use an array for future compatibility.
-     */
-    public static final String EXTRA_NDEF_MESSAGES = "android.nfc.extra.NDEF_MESSAGES";
-
-    /**
-     * Optional extra containing a byte array containing the ID of the discovered tag for
-     * the {@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED}, and
-     * {@link #ACTION_TAG_DISCOVERED} intents.
-     */
-    public static final String EXTRA_ID = "android.nfc.extra.ID";
-
-    /**
-     * Broadcast Action: The state of the local NFC adapter has been
-     * changed.
-     * <p>For example, NFC has been turned on or off.
-     * <p>Always contains the extra field {@link #EXTRA_ADAPTER_STATE}
-     */
-    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_ADAPTER_STATE_CHANGED =
-            "android.nfc.action.ADAPTER_STATE_CHANGED";
-
-    /**
-     * Used as an int extra field in {@link #ACTION_ADAPTER_STATE_CHANGED}
-     * intents to request the current power state. Possible values are:
-     * {@link #STATE_OFF},
-     * {@link #STATE_TURNING_ON},
-     * {@link #STATE_ON},
-     * {@link #STATE_TURNING_OFF},
-     */
-    public static final String EXTRA_ADAPTER_STATE = "android.nfc.extra.ADAPTER_STATE";
-
-    /**
-     * Mandatory byte[] extra field in {@link #ACTION_TRANSACTION_DETECTED}
-     */
-    public static final String EXTRA_AID = "android.nfc.extra.AID";
-
-    /**
-     * Optional byte[] extra field in {@link #ACTION_TRANSACTION_DETECTED}
-     */
-    public static final String EXTRA_DATA = "android.nfc.extra.DATA";
-
-    /**
-     * Mandatory String extra field in {@link #ACTION_TRANSACTION_DETECTED}
-     * Indicates the Secure Element on which the transaction occurred.
-     * eSE1...eSEn for Embedded Secure Elements, SIM1...SIMn for UICC, etc.
-     */
-    public static final String EXTRA_SECURE_ELEMENT_NAME = "android.nfc.extra.SECURE_ELEMENT_NAME";
-
-    /**
-     * Mandatory String extra field in {@link #ACTION_PREFERRED_PAYMENT_CHANGED}
-     * Indicates the condition when trigger this event. Possible values are:
-     * {@link #PREFERRED_PAYMENT_LOADED},
-     * {@link #PREFERRED_PAYMENT_CHANGED},
-     * {@link #PREFERRED_PAYMENT_UPDATED},
-     */
-    public static final String EXTRA_PREFERRED_PAYMENT_CHANGED_REASON =
-            "android.nfc.extra.PREFERRED_PAYMENT_CHANGED_REASON";
-    /**
-     * Nfc is enabled and the preferred payment aids are registered.
-     */
-    public static final int PREFERRED_PAYMENT_LOADED = 1;
-    /**
-     * User selected another payment application as the preferred payment.
-     */
-    public static final int PREFERRED_PAYMENT_CHANGED = 2;
-    /**
-     * Current preferred payment has issued an update (registered/unregistered new aids or has been
-     * updated itself).
-     */
-    public static final int PREFERRED_PAYMENT_UPDATED = 3;
-
-    public static final int STATE_OFF = 1;
-    public static final int STATE_TURNING_ON = 2;
-    public static final int STATE_ON = 3;
-    public static final int STATE_TURNING_OFF = 4;
-
-    /**
-     * Possible states from {@link #getAdapterState}.
-     *
-     * @hide
-     */
-    @IntDef(prefix = { "STATE_" }, value = {
-            STATE_OFF,
-            STATE_TURNING_ON,
-            STATE_ON,
-            STATE_TURNING_OFF
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface AdapterState{}
-
-    /**
-     * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
-     * <p>
-     * Setting this flag enables polling for Nfc-A technology.
-     */
-    public static final int FLAG_READER_NFC_A = 0x1;
-
-    /**
-     * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
-     * <p>
-     * Setting this flag enables polling for Nfc-B technology.
-     */
-    public static final int FLAG_READER_NFC_B = 0x2;
-
-    /**
-     * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
-     * <p>
-     * Setting this flag enables polling for Nfc-F technology.
-     */
-    public static final int FLAG_READER_NFC_F = 0x4;
-
-    /**
-     * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
-     * <p>
-     * Setting this flag enables polling for Nfc-V (ISO15693) technology.
-     */
-    public static final int FLAG_READER_NFC_V = 0x8;
-
-    /**
-     * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
-     * <p>
-     * Setting this flag enables polling for NfcBarcode technology.
-     */
-    public static final int FLAG_READER_NFC_BARCODE = 0x10;
-
-    /** @hide */
-    @IntDef(flag = true, prefix = {"FLAG_READER_"}, value = {
-        FLAG_READER_KEEP,
-        FLAG_READER_DISABLE,
-        FLAG_READER_NFC_A,
-        FLAG_READER_NFC_B,
-        FLAG_READER_NFC_F,
-        FLAG_READER_NFC_V,
-        FLAG_READER_NFC_BARCODE
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface PollTechnology {}
-
-    /**
-     * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
-     * <p>
-     * Setting this flag allows the caller to prevent the
-     * platform from performing an NDEF check on the tags it
-     * finds.
-     */
-    public static final int FLAG_READER_SKIP_NDEF_CHECK = 0x80;
-
-    /**
-     * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
-     * <p>
-     * Setting this flag allows the caller to prevent the
-     * platform from playing sounds when it discovers a tag.
-     */
-    public static final int FLAG_READER_NO_PLATFORM_SOUNDS = 0x100;
-
-    /**
-     * Int Extra for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
-     * <p>
-     * Setting this integer extra allows the calling application to specify
-     * the delay that the platform will use for performing presence checks
-     * on any discovered tag.
-     */
-    public static final String EXTRA_READER_PRESENCE_CHECK_DELAY = "presence";
-
-    /**
-     * Flag for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
-     * <p>
-     * Setting this flag enables listening for Nfc-A technology.
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
-    public static final int FLAG_LISTEN_NFC_PASSIVE_A = 0x1;
-
-    /**
-     * Flag for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
-     * <p>
-     * Setting this flag enables listening for Nfc-B technology.
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
-    public static final int FLAG_LISTEN_NFC_PASSIVE_B = 1 << 1;
-
-    /**
-     * Flag for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
-     * <p>
-     * Setting this flag enables listening for Nfc-F technology.
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
-    public static final int FLAG_LISTEN_NFC_PASSIVE_F = 1 << 2;
-
-    /**
-     * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
-     * <p>
-     * Setting this flag disables listening.
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
-    public static final int FLAG_LISTEN_DISABLE = 0x0;
-
-    /**
-     * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
-     * <p>
-     * Setting this flag disables polling.
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
-    public static final int FLAG_READER_DISABLE = 0x0;
-
-    /**
-     * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
-     * <p>
-     * Setting this flag makes listening to use current flags.
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
-    public static final int FLAG_LISTEN_KEEP = -1;
-
-    /**
-     * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
-     * <p>
-     * Setting this flag makes polling to use current flags.
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
-    public static final int FLAG_READER_KEEP = -1;
-
-    /** @hide */
-    public static final int FLAG_USE_ALL_TECH = 0xff;
-
-    /** @hide */
-    @IntDef(flag = true, prefix = {"FLAG_LISTEN_"}, value = {
-        FLAG_LISTEN_KEEP,
-        FLAG_LISTEN_DISABLE,
-        FLAG_LISTEN_NFC_PASSIVE_A,
-        FLAG_LISTEN_NFC_PASSIVE_B,
-        FLAG_LISTEN_NFC_PASSIVE_F
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ListenTechnology {}
-
-    /**
-     * @hide
-     * @removed
-     */
-    @SystemApi
-    @UnsupportedAppUsage
-    public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 0x1;
-
-    /** @hide */
-    public static final String ACTION_HANDOVER_TRANSFER_STARTED =
-            "android.nfc.action.HANDOVER_TRANSFER_STARTED";
-
-    /** @hide */
-    public static final String ACTION_HANDOVER_TRANSFER_DONE =
-            "android.nfc.action.HANDOVER_TRANSFER_DONE";
-
-    /** @hide */
-    public static final String EXTRA_HANDOVER_TRANSFER_STATUS =
-            "android.nfc.extra.HANDOVER_TRANSFER_STATUS";
-
-    /** @hide */
-    public static final int HANDOVER_TRANSFER_STATUS_SUCCESS = 0;
-    /** @hide */
-    public static final int HANDOVER_TRANSFER_STATUS_FAILURE = 1;
-
-    /** @hide */
-    public static final String EXTRA_HANDOVER_TRANSFER_URI =
-            "android.nfc.extra.HANDOVER_TRANSFER_URI";
-
-    /**
-     * Broadcast Action: Notify possible NFC transaction blocked because device is locked.
-     * <p>An external NFC field detected when device locked and SecureNfc enabled.
-     * @hide
-     */
-    @SystemApi
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC =
-            "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
-
-    /**
-     * The requested app is correctly added to the Tag intent app preference.
-     *
-     * @see #setTagIntentAppPreferenceForUser(int userId, String pkg, boolean allow)
-     * @hide
-     */
-    @SystemApi
-    public static final int TAG_INTENT_APP_PREF_RESULT_SUCCESS = 0;
-
-    /**
-     * The requested app is not installed on the device.
-     *
-     * @see #setTagIntentAppPreferenceForUser(int userId, String pkg, boolean allow)
-     * @hide
-     */
-    @SystemApi
-    public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1;
-
-    /**
-     * The NfcService is not available.
-     *
-     * @see #setTagIntentAppPreferenceForUser(int userId, String pkg, boolean allow)
-     * @hide
-     */
-    @SystemApi
-    public static final int TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE = -2;
-
-    /**
-     * Possible response codes from {@link #setTagIntentAppPreferenceForUser}.
-     *
-     * @hide
-     */
-    @IntDef(prefix = { "TAG_INTENT_APP_PREF_RESULT" }, value = {
-            TAG_INTENT_APP_PREF_RESULT_SUCCESS,
-            TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND,
-            TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface TagIntentAppPreferenceResult {}
-
-    // Guarded by sLock
-    static boolean sIsInitialized = false;
-    static boolean sHasNfcFeature;
-    static boolean sHasCeFeature;
-    static boolean sHasNfcWlcFeature;
-
-    static Object sLock = new Object();
-
-    // Final after first constructor, except for
-    // attemptDeadServiceRecovery() when NFC crashes - we accept a best effort
-    // recovery
-    @UnsupportedAppUsage
-    static INfcAdapter sService;
-    static NfcServiceManager.ServiceRegisterer sServiceRegisterer;
-    static INfcTag sTagService;
-    static INfcCardEmulation sCardEmulationService;
-    static INfcFCardEmulation sNfcFCardEmulationService;
-
-    /**
-     * The NfcAdapter object for each application context.
-     * There is a 1-1 relationship between application context and
-     * NfcAdapter object.
-     */
-    static HashMap<Context, NfcAdapter> sNfcAdapters = new HashMap(); //guard by NfcAdapter.class
-
-    /**
-     * NfcAdapter used with a null context. This ctor was deprecated but we have
-     * to support it for backwards compatibility. New methods that require context
-     * might throw when called on the null-context NfcAdapter.
-     */
-    static NfcAdapter sNullContextNfcAdapter;  // protected by NfcAdapter.class
-
-    final NfcActivityManager mNfcActivityManager;
-    final Context mContext;
-    final HashMap<NfcUnlockHandler, INfcUnlockHandler> mNfcUnlockHandlers;
-    final Object mLock;
-
-    ITagRemovedCallback mTagRemovedListener; // protected by mLock
-
-    /**
-     * A callback to be invoked when the system finds a tag while the foreground activity is
-     * operating in reader mode.
-     * <p>Register your {@code ReaderCallback} implementation with {@link
-     * NfcAdapter#enableReaderMode} and disable it with {@link
-     * NfcAdapter#disableReaderMode}.
-     * @see NfcAdapter#enableReaderMode
-     */
-    public interface ReaderCallback {
-        public void onTagDiscovered(Tag tag);
-    }
-
-    /**
-     * A listener to be invoked when NFC controller always on state changes.
-     * <p>Register your {@code ControllerAlwaysOnListener} implementation with {@link
-     * NfcAdapter#registerControllerAlwaysOnListener} and disable it with {@link
-     * NfcAdapter#unregisterControllerAlwaysOnListener}.
-     * @see #registerControllerAlwaysOnListener
-     * @hide
-     */
-    @SystemApi
-    public interface ControllerAlwaysOnListener {
-        /**
-         * Called on NFC controller always on state changes
-         */
-        void onControllerAlwaysOnChanged(boolean isEnabled);
-    }
-
-    /**
-     * A callback to be invoked when the system successfully delivers your {@link NdefMessage}
-     * to another device.
-     * @deprecated this feature is removed. File sharing can work using other technology like
-     * Bluetooth.
-     */
-    @java.lang.Deprecated
-    public interface OnNdefPushCompleteCallback {
-        /**
-         * Called on successful NDEF push.
-         *
-         * <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
-         */
-        public void onNdefPushComplete(NfcEvent event);
-    }
-
-    /**
-     * 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 {@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 {@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
-     * Bluetooth.
-     */
-    @java.lang.Deprecated
-    public interface CreateNdefMessageCallback {
-        /**
-         * Called to provide a {@link NdefMessage} to push.
-         *
-         * <p>This callback is usually made on a binder thread (not the UI thread).
-         *
-         * <p>Called when this device is in range of another device
-         * that might support NDEF push. It allows the application to
-         * create the NDEF message only when it is required.
-         *
-         * <p>NDEF push cannot occur until this method returns, so do not
-         * block for too long.
-         *
-         * <p>The Android operating system will usually show a system UI
-         * on top of your activity during this time, so do not try to request
-         * input from the user to complete the callback, or provide custom NDEF
-         * push UI. The user probably will not see it.
-         *
-         * @param event {@link NfcEvent} with the {@link NfcEvent#nfcAdapter} field set
-         * @return NDEF message to push, or null to not provide a message
-         */
-        public NdefMessage createNdefMessage(NfcEvent event);
-    }
-
-
-     /**
-     * @deprecated this feature is removed. File sharing can work using other technology like
-     * Bluetooth.
-     */
-    @java.lang.Deprecated
-    public interface CreateBeamUrisCallback {
-        public Uri[] createBeamUris(NfcEvent event);
-    }
-
-    /**
-     * A callback that is invoked when a tag is removed from the field.
-     * @see NfcAdapter#ignore
-     */
-    public interface OnTagRemovedListener {
-        void onTagRemoved();
-    }
-
-    /**
-     * A callback to be invoked when an application has registered as a
-     * handler to unlock the device given an NFC tag at the lockscreen.
-     * @hide
-     */
-    @SystemApi
-    public interface NfcUnlockHandler {
-        /**
-         * Called at the lock screen to attempt to unlock the device with the given tag.
-         * @param tag the detected tag, to be used to unlock the device
-         * @return true if the device was successfully unlocked
-         */
-        public boolean onUnlockAttempted(Tag tag);
-    }
-
-    /**
-     * Return list of Secure Elements which support off host card emulation.
-     *
-     * @return List<String> containing secure elements on the device which supports
-     *                      off host card emulation. eSE for Embedded secure element,
-     *                      SIM for UICC and so on.
-     * @hide
-     */
-    public @NonNull List<String> getSupportedOffHostSecureElements() {
-        if (mContext == null) {
-            throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
-                    + " getSupportedOffHostSecureElements APIs");
-        }
-        List<String> offHostSE = new ArrayList<String>();
-        PackageManager pm = mContext.getPackageManager();
-        if (pm == null) {
-            Log.e(TAG, "Cannot get package manager, assuming no off-host CE feature");
-            return offHostSE;
-        }
-        if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC)) {
-            offHostSE.add("SIM");
-        }
-        if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE)) {
-            offHostSE.add("eSE");
-        }
-        return offHostSE;
-    }
-
-    private static void retrieveServiceRegisterer() {
-        if (sServiceRegisterer == null) {
-            NfcServiceManager manager = NfcFrameworkInitializer.getNfcServiceManager();
-            if (manager == null) {
-                Log.e(TAG, "NfcServiceManager is null");
-                throw new UnsupportedOperationException();
-            }
-            sServiceRegisterer = manager.getNfcManagerServiceRegisterer();
-        }
-    }
-
-    /**
-     * Returns the NfcAdapter for application context,
-     * or throws if NFC is not available.
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public static synchronized NfcAdapter getNfcAdapter(Context context) {
-        if (context == null) {
-            if (sNullContextNfcAdapter == null) {
-                sNullContextNfcAdapter = new NfcAdapter(null);
-            }
-            return sNullContextNfcAdapter;
-        }
-        if (!sIsInitialized) {
-            PackageManager pm;
-            pm = context.getPackageManager();
-            sHasNfcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC);
-            sHasCeFeature =
-                    pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)
-                    || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF)
-                    || pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC)
-                    || pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE);
-            sHasNfcWlcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC_CHARGING);
-            /* is this device meant to have NFC */
-            if (!sHasNfcFeature && !sHasCeFeature && !sHasNfcWlcFeature) {
-                Log.v(TAG, "this device does not have NFC support");
-                throw new UnsupportedOperationException();
-            }
-            retrieveServiceRegisterer();
-            sService = getServiceInterface();
-            if (sService == null) {
-                Log.e(TAG, "could not retrieve NFC service");
-                throw new UnsupportedOperationException();
-            }
-            if (sHasNfcFeature) {
-                try {
-                    sTagService = sService.getNfcTagInterface();
-                } catch (RemoteException e) {
-                    sTagService = null;
-                    Log.e(TAG, "could not retrieve NFC Tag service");
-                    throw new UnsupportedOperationException();
-                }
-            }
-            if (sHasCeFeature) {
-                try {
-                    sNfcFCardEmulationService = sService.getNfcFCardEmulationInterface();
-                } catch (RemoteException e) {
-                    sNfcFCardEmulationService = null;
-                    Log.e(TAG, "could not retrieve NFC-F card emulation service");
-                    throw new UnsupportedOperationException();
-                }
-                try {
-                    sCardEmulationService = sService.getNfcCardEmulationInterface();
-                } catch (RemoteException e) {
-                    sCardEmulationService = null;
-                    Log.e(TAG, "could not retrieve card emulation service");
-                    throw new UnsupportedOperationException();
-                }
-            }
-
-            sIsInitialized = true;
-        }
-        NfcAdapter adapter = sNfcAdapters.get(context);
-        if (adapter == null) {
-            adapter = new NfcAdapter(context);
-            sNfcAdapters.put(context, adapter);
-        }
-        return adapter;
-    }
-
-    /** get handle to NFC service interface */
-    private static INfcAdapter getServiceInterface() {
-        /* get a handle to NFC service */
-        IBinder b = sServiceRegisterer.get();
-        if (b == null) {
-            return null;
-        }
-        return INfcAdapter.Stub.asInterface(b);
-    }
-
-    /**
-     * Helper to get the default NFC Adapter.
-     * <p>
-     * Most Android devices will only have one NFC Adapter (NFC Controller).
-     * <p>
-     * This helper is the equivalent of:
-     * <pre>
-     * NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE);
-     * NfcAdapter adapter = manager.getDefaultAdapter();</pre>
-     * @param context the calling application's context
-     *
-     * @return the default NFC adapter, or null if no NFC adapter exists
-     */
-    public static NfcAdapter getDefaultAdapter(Context context) {
-        if (context == null) {
-            throw new IllegalArgumentException("context cannot be null");
-        }
-        context = context.getApplicationContext();
-        if (context == null) {
-            throw new IllegalArgumentException(
-                    "context not associated with any application (using a mock context?)");
-        }
-        retrieveServiceRegisterer();
-        if (sServiceRegisterer.tryGet() == null) {
-            if (sIsInitialized) {
-                synchronized (NfcAdapter.class) {
-                    /* Stale sService pointer */
-                    if (sIsInitialized) sIsInitialized = false;
-                }
-            }
-            return null;
-        }
-        /* Try to initialize the service */
-        NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE);
-        if (manager == null) {
-            // NFC not available
-            return null;
-        }
-        return manager.getDefaultAdapter();
-    }
-
-    /**
-     * Legacy NfcAdapter getter, always use {@link #getDefaultAdapter(Context)} instead.<p>
-     * This method was deprecated at API level 10 (Gingerbread MR1) because a context is required
-     * for many NFC API methods. Those methods will fail when called on an NfcAdapter
-     * object created from this method.<p>
-     * @deprecated use {@link #getDefaultAdapter(Context)}
-     * @hide
-     */
-    @Deprecated
-    @UnsupportedAppUsage
-    public static NfcAdapter getDefaultAdapter() {
-        // introduced in API version 9 (GB 2.3)
-        // deprecated in API version 10 (GB 2.3.3)
-        // removed from public API in version 16 (ICS MR2)
-        // should maintain as a hidden API for binary compatibility for a little longer
-        Log.w(TAG, "WARNING: NfcAdapter.getDefaultAdapter() is deprecated, use " +
-                "NfcAdapter.getDefaultAdapter(Context) instead", new Exception());
-
-        return NfcAdapter.getNfcAdapter(null);
-    }
-
-    NfcAdapter(Context context) {
-        mContext = context;
-        mNfcActivityManager = new NfcActivityManager(this);
-        mNfcUnlockHandlers = new HashMap<NfcUnlockHandler, INfcUnlockHandler>();
-        mTagRemovedListener = null;
-        mLock = new Object();
-        mControllerAlwaysOnListener = new NfcControllerAlwaysOnListener(getService());
-        mNfcWlcStateListener = new NfcWlcStateListener(getService());
-    }
-
-    /**
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public Context getContext() {
-        return mContext;
-    }
-
-    /**
-     * Returns the binder interface to the service.
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public INfcAdapter getService() {
-        isEnabled();  // NOP call to recover sService if it is stale
-        return sService;
-    }
-
-    /**
-     * Returns the binder interface to the tag service.
-     * @hide
-     */
-    public INfcTag getTagService() {
-        isEnabled();  // NOP call to recover sTagService if it is stale
-        return sTagService;
-    }
-
-    /**
-     * Returns the binder interface to the card emulation service.
-     * @hide
-     */
-    public INfcCardEmulation getCardEmulationService() {
-        isEnabled();
-        return sCardEmulationService;
-    }
-
-    /**
-     * Returns the binder interface to the NFC-F card emulation service.
-     * @hide
-     */
-    public INfcFCardEmulation getNfcFCardEmulationService() {
-        isEnabled();
-        return sNfcFCardEmulationService;
-    }
-
-    /**
-     * Returns the binder interface to the NFC-DTA test interface.
-     * @hide
-     */
-    public INfcDta getNfcDtaInterface() {
-        if (mContext == null) {
-            throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
-                    + " NFC extras APIs");
-        }
-        try {
-            return sService.getNfcDtaInterface(mContext.getPackageName());
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return null;
-            }
-            try {
-                return sService.getNfcDtaInterface(mContext.getPackageName());
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return null;
-        }
-    }
-
-    /**
-     * NFC service dead - attempt best effort recovery
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public void attemptDeadServiceRecovery(Exception e) {
-        Log.e(TAG, "NFC service dead - attempting to recover", e);
-        INfcAdapter service = getServiceInterface();
-        if (service == null) {
-            Log.e(TAG, "could not retrieve NFC service during service recovery");
-            // nothing more can be done now, sService is still stale, we'll hit
-            // this recovery path again later
-            return;
-        }
-        // assigning to sService is not thread-safe, but this is best-effort code
-        // and on a well-behaved system should never happen
-        sService = service;
-        if (sHasNfcFeature) {
-            try {
-                sTagService = service.getNfcTagInterface();
-            } catch (RemoteException ee) {
-                sTagService = null;
-                Log.e(TAG, "could not retrieve NFC tag service during service recovery");
-                // nothing more can be done now, sService is still stale, we'll hit
-                // this recovery path again later
-                return;
-            }
-        }
-
-        if (sHasCeFeature) {
-            try {
-                sCardEmulationService = service.getNfcCardEmulationInterface();
-            } catch (RemoteException ee) {
-                sCardEmulationService = null;
-                Log.e(TAG,
-                        "could not retrieve NFC card emulation service during service recovery");
-            }
-
-            try {
-                sNfcFCardEmulationService = service.getNfcFCardEmulationInterface();
-            } catch (RemoteException ee) {
-                sNfcFCardEmulationService = null;
-                Log.e(TAG,
-                        "could not retrieve NFC-F card emulation service during service recovery");
-            }
-        }
-
-        return;
-    }
-
-    private boolean isCardEmulationEnabled() {
-        if (sHasCeFeature) {
-            return (sCardEmulationService != null || sNfcFCardEmulationService != null);
-        }
-        return false;
-    }
-
-    private boolean isTagReadingEnabled() {
-        if (sHasNfcFeature) {
-            return sTagService != null;
-        }
-        return false;
-    }
-
-
-    /**
-     * Return true if this NFC Adapter has any features enabled.
-     *
-     * <p>If this method returns false, the NFC hardware is guaranteed not to
-     * generate or respond to any NFC communication over its NFC radio.
-     * <p>Applications can use this to check if NFC is enabled. Applications
-     * can request Settings UI allowing the user to toggle NFC using:
-     * <p><pre>startActivity(new Intent(Settings.ACTION_NFC_SETTINGS))</pre>
-     *
-     * @see android.provider.Settings#ACTION_NFC_SETTINGS
-     * @return true if this NFC Adapter has any features enabled
-     */
-    public boolean isEnabled() {
-        boolean serviceState = false;
-        try {
-            serviceState = sService.getState() == STATE_ON;
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                serviceState = sService.getState() == STATE_ON;
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-        }
-        return serviceState
-                && (isTagReadingEnabled() || isCardEmulationEnabled() || sHasNfcWlcFeature);
-    }
-
-    /**
-     * Return the state of this NFC Adapter.
-     *
-     * <p>Returns one of {@link #STATE_ON}, {@link #STATE_TURNING_ON},
-     * {@link #STATE_OFF}, {@link #STATE_TURNING_OFF}.
-     *
-     * <p>{@link #isEnabled()} is equivalent to
-     * <code>{@link #getAdapterState()} == {@link #STATE_ON}</code>
-     *
-     * @return the current state of this NFC adapter
-     *
-     * @hide
-     */
-    @SystemApi
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    public @AdapterState int getAdapterState() {
-        try {
-            return sService.getState();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return NfcAdapter.STATE_OFF;
-            }
-            try {
-                return sService.getState();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return NfcAdapter.STATE_OFF;
-        }
-    }
-
-    /**
-     * Enable NFC hardware.
-     *
-     * <p>This call is asynchronous. Listen for
-     * {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the
-     * operation is complete.
-     *
-     * <p>If this returns true, then either NFC is already on, or
-     * a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent
-     * to indicate a state transition. If this returns false, then
-     * there is some problem that prevents an attempt to turn
-     * NFC on (for example we are in airplane mode and NFC is not
-     * toggleable in airplane mode on this platform).
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    public boolean enable() {
-        try {
-            return sService.enable();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.enable();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Disable NFC hardware.
-     *
-     * <p>No NFC features will work after this call, and the hardware
-     * will not perform or respond to any NFC communication.
-     *
-     * <p>This call is asynchronous. Listen for
-     * {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the
-     * operation is complete.
-     *
-     * <p>If this returns true, then either NFC is already off, or
-     * a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent
-     * to indicate a state transition. If this returns false, then
-     * there is some problem that prevents an attempt to turn
-     * NFC off.
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    public boolean disable() {
-        try {
-            return sService.disable(true);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.disable(true);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Disable NFC hardware.
-     * @hide
-    */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    public boolean disable(boolean persist) {
-        try {
-            return sService.disable(persist);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.disable(persist);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Pauses polling for a {@code timeoutInMs} millis. If polling must be resumed before timeout,
-     * use {@link #resumePolling()}.
-     * @hide
-     */
-    public void pausePolling(int timeoutInMs) {
-        try {
-            sService.pausePolling(timeoutInMs);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-        }
-    }
-
-
-    /**
-     * Returns whether the device supports observer mode or not. When observe
-     * mode is enabled, the NFC hardware will listen for NFC readers, but not
-     * respond to them. When observe mode is disabled, the NFC hardware will
-     * resoond to the reader and proceed with the transaction.
-     * @return true if the mode is supported, false otherwise.
-     */
-    @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
-    public boolean isObserveModeSupported() {
-        try {
-            return sService.isObserveModeSupported();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            return false;
-        }
-    }
-
-   /**
-    * Disables observe mode to allow the transaction to proceed. See
-    * {@link #isObserveModeSupported()} for a description of observe mode and
-    * use {@link #disallowTransaction()} to enable observe mode and block
-    * transactions again.
-    *
-    * @return boolean indicating success or failure.
-    */
-    @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
-    public boolean allowTransaction() {
-        try {
-            return sService.setObserveMode(false);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            return false;
-        }
-    }
-
-    /**
-    * Signals that the transaction has completed and observe mode may be
-    * reenabled. See {@link #isObserveModeSupported()} for a description of
-    * observe mode and use {@link #allowTransaction()} to disable observe
-    * mode and allow transactions to proceed.
-    *
-    * @return boolean indicating success or failure.
-    */
-
-    @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
-    public boolean disallowTransaction() {
-        try {
-            return sService.setObserveMode(true);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            return false;
-        }
-    }
-
-    /**
-     * Resumes default polling for the current device state if polling is paused. Calling
-     * this while polling is not paused is a no-op.
-     *
-     * @hide
-     */
-    public void resumePolling() {
-        try {
-            sService.resumePolling();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-        }
-    }
-
-    /**
-     * Set one or more {@link Uri}s to send using Android Beam (TM). Every
-     * Uri you provide must have either scheme 'file' or scheme 'content'.
-     *
-     * <p>For the data provided through this method, Android Beam tries to
-     * switch to alternate transports such as Bluetooth to achieve a fast
-     * transfer speed. Hence this method is very suitable
-     * for transferring large files such as pictures or songs.
-     *
-     * <p>The receiving side will store the content of each Uri in
-     * a file and present a notification to the user to open the file
-     * with a {@link android.content.Intent} with action
-     * {@link android.content.Intent#ACTION_VIEW}.
-     * If multiple URIs are sent, the {@link android.content.Intent} will refer
-     * to the first of the stored files.
-     *
-     * <p>This method may be called at any time before {@link Activity#onDestroy},
-     * but the URI(s) are only made available for Android Beam when the
-     * specified activity(s) are in resumed (foreground) state. The recommended
-     * approach is to call this method during your Activity's
-     * {@link Activity#onCreate} - see sample
-     * code below. This method does not immediately perform any I/O or blocking work,
-     * so is safe to call on your main thread.
-     *
-     * <p>{@link #setBeamPushUris} and {@link #setBeamPushUrisCallback}
-     * have priority over both {@link #setNdefPushMessage} and
-     * {@link #setNdefPushMessageCallback}.
-     *
-     * <p>If {@link #setBeamPushUris} is called with a null Uri array,
-     * and/or {@link #setBeamPushUrisCallback} is called with a null callback,
-     * then the Uri push will be completely disabled for the specified activity(s).
-     *
-     * <p>Code example:
-     * <pre>
-     * protected void onCreate(Bundle savedInstanceState) {
-     *     super.onCreate(savedInstanceState);
-     *     NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
-     *     if (nfcAdapter == null) return;  // NFC not available on this device
-     *     nfcAdapter.setBeamPushUris(new Uri[] {uri1, uri2}, this);
-     * }</pre>
-     * And that is it. Only one call per activity is necessary. The Android
-     * OS will automatically release its references to the Uri(s) and the
-     * Activity object when it is destroyed if you follow this pattern.
-     *
-     * <p>If your Activity wants to dynamically supply Uri(s),
-     * then set a callback using {@link #setBeamPushUrisCallback} instead
-     * of using this method.
-     *
-     * <p class="note">Do not pass in an Activity that has already been through
-     * {@link Activity#onDestroy}. This is guaranteed if you call this API
-     * during {@link Activity#onCreate}.
-     *
-     * <p class="note">If this device does not support alternate transports
-     * such as Bluetooth or WiFI, calling this method does nothing.
-     *
-     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
-     *
-     * @param uris an array of Uri(s) to push over Android Beam
-     * @param activity activity for which the Uri(s) will be pushed
-     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @removed this feature is removed. File sharing can work using other technology like
-     * Bluetooth.
-     */
-    @java.lang.Deprecated
-    @UnsupportedAppUsage
-    public void setBeamPushUris(Uri[] uris, Activity activity) {
-        synchronized (sLock) {
-            if (!sHasNfcFeature) {
-                throw new UnsupportedOperationException();
-            }
-        }
-    }
-
-    /**
-     * Set a callback that will dynamically generate one or more {@link Uri}s
-     * to send using Android Beam (TM). Every Uri the callback provides
-     * must have either scheme 'file' or scheme 'content'.
-     *
-     * <p>For the data provided through this callback, Android Beam tries to
-     * switch to alternate transports such as Bluetooth to achieve a fast
-     * transfer speed. Hence this method is very suitable
-     * for transferring large files such as pictures or songs.
-     *
-     * <p>The receiving side will store the content of each Uri in
-     * a file and present a notification to the user to open the file
-     * with a {@link android.content.Intent} with action
-     * {@link android.content.Intent#ACTION_VIEW}.
-     * If multiple URIs are sent, the {@link android.content.Intent} will refer
-     * to the first of the stored files.
-     *
-     * <p>This method may be called at any time before {@link Activity#onDestroy},
-     * but the URI(s) are only made available for Android Beam when the
-     * specified activity(s) are in resumed (foreground) state. The recommended
-     * approach is to call this method during your Activity's
-     * {@link Activity#onCreate} - see sample
-     * code below. This method does not immediately perform any I/O or blocking work,
-     * so is safe to call on your main thread.
-     *
-     * <p>{@link #setBeamPushUris} and {@link #setBeamPushUrisCallback}
-     * have priority over both {@link #setNdefPushMessage} and
-     * {@link #setNdefPushMessageCallback}.
-     *
-     * <p>If {@link #setBeamPushUris} is called with a null Uri array,
-     * and/or {@link #setBeamPushUrisCallback} is called with a null callback,
-     * then the Uri push will be completely disabled for the specified activity(s).
-     *
-     * <p>Code example:
-     * <pre>
-     * protected void onCreate(Bundle savedInstanceState) {
-     *     super.onCreate(savedInstanceState);
-     *     NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
-     *     if (nfcAdapter == null) return;  // NFC not available on this device
-     *     nfcAdapter.setBeamPushUrisCallback(callback, this);
-     * }</pre>
-     * And that is it. Only one call per activity is necessary. The Android
-     * OS will automatically release its references to the Uri(s) and the
-     * Activity object when it is destroyed if you follow this pattern.
-     *
-     * <p class="note">Do not pass in an Activity that has already been through
-     * {@link Activity#onDestroy}. This is guaranteed if you call this API
-     * during {@link Activity#onCreate}.
-     *
-     * <p class="note">If this device does not support alternate transports
-     * such as Bluetooth or WiFI, calling this method does nothing.
-     *
-     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
-     *
-     * @param callback callback, or null to disable
-     * @param activity activity for which the Uri(s) will be pushed
-     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @removed this feature is removed. File sharing can work using other technology like
-     * Bluetooth.
-     */
-    @java.lang.Deprecated
-    @UnsupportedAppUsage
-    public void setBeamPushUrisCallback(CreateBeamUrisCallback callback, Activity activity) {
-        synchronized (sLock) {
-            if (!sHasNfcFeature) {
-                throw new UnsupportedOperationException();
-            }
-        }
-    }
-
-    /**
-     * Set a static {@link NdefMessage} to send using Android Beam (TM).
-     *
-     * <p>This method may be called at any time before {@link Activity#onDestroy},
-     * but the NDEF message is only made available for NDEF push when the
-     * specified activity(s) are in resumed (foreground) state. The recommended
-     * approach is to call this method during your Activity's
-     * {@link Activity#onCreate} - see sample
-     * code below. This method does not immediately perform any I/O or blocking work,
-     * so is safe to call on your main thread.
-     *
-     * <p>Only one NDEF message can be pushed by the currently resumed activity.
-     * If both {@link #setNdefPushMessage} and
-     * {@link #setNdefPushMessageCallback} are set, then
-     * the callback will take priority.
-     *
-     * <p>If neither {@link #setNdefPushMessage} or
-     * {@link #setNdefPushMessageCallback} have been called for your activity, then
-     * the Android OS may choose to send a default NDEF message on your behalf,
-     * such as a URI for your application.
-     *
-     * <p>If {@link #setNdefPushMessage} is called with a null NDEF message,
-     * and/or {@link #setNdefPushMessageCallback} is called with a null callback,
-     * then NDEF push will be completely disabled for the specified activity(s).
-     * This also disables any default NDEF message the Android OS would have
-     * otherwise sent on your behalf for those activity(s).
-     *
-     * <p>If you want to prevent the Android OS from sending default NDEF
-     * messages completely (for all activities), you can include a
-     * {@code <meta-data>} element inside the {@code <application>}
-     * element of your AndroidManifest.xml file, like this:
-     * <pre>
-     * &lt;application ...>
-     *     &lt;meta-data android:name="android.nfc.disable_beam_default"
-     *         android:value="true" />
-     * &lt;/application></pre>
-     *
-     * <p>The API allows for multiple activities to be specified at a time,
-     * but it is strongly recommended to just register one at a time,
-     * and to do so during the activity's {@link Activity#onCreate}. For example:
-     * <pre>
-     * protected void onCreate(Bundle savedInstanceState) {
-     *     super.onCreate(savedInstanceState);
-     *     NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
-     *     if (nfcAdapter == null) return;  // NFC not available on this device
-     *     nfcAdapter.setNdefPushMessage(ndefMessage, this);
-     * }</pre>
-     * And that is it. Only one call per activity is necessary. The Android
-     * OS will automatically release its references to the NDEF message and the
-     * Activity object when it is destroyed if you follow this pattern.
-     *
-     * <p>If your Activity wants to dynamically generate an NDEF message,
-     * then set a callback using {@link #setNdefPushMessageCallback} instead
-     * of a static message.
-     *
-     * <p class="note">Do not pass in an Activity that has already been through
-     * {@link Activity#onDestroy}. This is guaranteed if you call this API
-     * during {@link Activity#onCreate}.
-     *
-     * <p class="note">For sending large content such as pictures and songs,
-     * consider using {@link #setBeamPushUris}, which switches to alternate transports
-     * such as Bluetooth to achieve a fast transfer rate.
-     *
-     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
-     *
-     * @param message NDEF message to push over NFC, or null to disable
-     * @param activity activity for which the NDEF message will be pushed
-     * @param activities optional additional activities, however we strongly recommend
-     *        to only register one at a time, and to do so in that activity's
-     *        {@link Activity#onCreate}
-     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @removed this feature is removed. File sharing can work using other technology like
-     * Bluetooth.
-     */
-    @java.lang.Deprecated
-    @UnsupportedAppUsage
-    public void setNdefPushMessage(NdefMessage message, Activity activity,
-            Activity ... activities) {
-        synchronized (sLock) {
-            if (!sHasNfcFeature) {
-                throw new UnsupportedOperationException();
-            }
-        }
-    }
-
-    /**
-     * @hide
-     * @removed
-     */
-    @SystemApi
-    @UnsupportedAppUsage
-    public void setNdefPushMessage(NdefMessage message, Activity activity, int flags) {
-        synchronized (sLock) {
-            if (!sHasNfcFeature) {
-                throw new UnsupportedOperationException();
-            }
-        }
-    }
-
-    /**
-     * Set a callback that dynamically generates NDEF messages to send using Android Beam (TM).
-     *
-     * <p>This method may be called at any time before {@link Activity#onDestroy},
-     * but the NDEF message callback can only occur when the
-     * specified activity(s) are in resumed (foreground) state. The recommended
-     * approach is to call this method during your Activity's
-     * {@link Activity#onCreate} - see sample
-     * code below. This method does not immediately perform any I/O or blocking work,
-     * so is safe to call on your main thread.
-     *
-     * <p>Only one NDEF message can be pushed by the currently resumed activity.
-     * If both {@link #setNdefPushMessage} and
-     * {@link #setNdefPushMessageCallback} are set, then
-     * the callback will take priority.
-     *
-     * <p>If neither {@link #setNdefPushMessage} or
-     * {@link #setNdefPushMessageCallback} have been called for your activity, then
-     * the Android OS may choose to send a default NDEF message on your behalf,
-     * such as a URI for your application.
-     *
-     * <p>If {@link #setNdefPushMessage} is called with a null NDEF message,
-     * and/or {@link #setNdefPushMessageCallback} is called with a null callback,
-     * then NDEF push will be completely disabled for the specified activity(s).
-     * This also disables any default NDEF message the Android OS would have
-     * otherwise sent on your behalf for those activity(s).
-     *
-     * <p>If you want to prevent the Android OS from sending default NDEF
-     * messages completely (for all activities), you can include a
-     * {@code <meta-data>} element inside the {@code <application>}
-     * element of your AndroidManifest.xml file, like this:
-     * <pre>
-     * &lt;application ...>
-     *     &lt;meta-data android:name="android.nfc.disable_beam_default"
-     *         android:value="true" />
-     * &lt;/application></pre>
-     *
-     * <p>The API allows for multiple activities to be specified at a time,
-     * but it is strongly recommended to just register one at a time,
-     * and to do so during the activity's {@link Activity#onCreate}. For example:
-     * <pre>
-     * protected void onCreate(Bundle savedInstanceState) {
-     *     super.onCreate(savedInstanceState);
-     *     NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
-     *     if (nfcAdapter == null) return;  // NFC not available on this device
-     *     nfcAdapter.setNdefPushMessageCallback(callback, this);
-     * }</pre>
-     * And that is it. Only one call per activity is necessary. The Android
-     * OS will automatically release its references to the callback and the
-     * Activity object when it is destroyed if you follow this pattern.
-     *
-     * <p class="note">Do not pass in an Activity that has already been through
-     * {@link Activity#onDestroy}. This is guaranteed if you call this API
-     * during {@link Activity#onCreate}.
-     * <p class="note">For sending large content such as pictures and songs,
-     * consider using {@link #setBeamPushUris}, which switches to alternate transports
-     * such as Bluetooth to achieve a fast transfer rate.
-     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
-     *
-     * @param callback callback, or null to disable
-     * @param activity activity for which the NDEF message will be pushed
-     * @param activities optional additional activities, however we strongly recommend
-     *        to only register one at a time, and to do so in that activity's
-     *        {@link Activity#onCreate}
-     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @removed this feature is removed. File sharing can work using other technology like
-     * Bluetooth.
-     */
-    @java.lang.Deprecated
-    @UnsupportedAppUsage
-    public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity,
-            Activity ... activities) {
-        synchronized (sLock) {
-            if (!sHasNfcFeature) {
-                throw new UnsupportedOperationException();
-            }
-        }
-    }
-
-    /**
-     * Set a callback on successful Android Beam (TM).
-     *
-     * <p>This method may be called at any time before {@link Activity#onDestroy},
-     * but the callback can only occur when the
-     * specified activity(s) are in resumed (foreground) state. The recommended
-     * approach is to call this method during your Activity's
-     * {@link Activity#onCreate} - see sample
-     * code below. This method does not immediately perform any I/O or blocking work,
-     * so is safe to call on your main thread.
-     *
-     * <p>The API allows for multiple activities to be specified at a time,
-     * but it is strongly recommended to just register one at a time,
-     * and to do so during the activity's {@link Activity#onCreate}. For example:
-     * <pre>
-     * protected void onCreate(Bundle savedInstanceState) {
-     *     super.onCreate(savedInstanceState);
-     *     NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
-     *     if (nfcAdapter == null) return;  // NFC not available on this device
-     *     nfcAdapter.setOnNdefPushCompleteCallback(callback, this);
-     * }</pre>
-     * And that is it. Only one call per activity is necessary. The Android
-     * OS will automatically release its references to the callback and the
-     * Activity object when it is destroyed if you follow this pattern.
-     *
-     * <p class="note">Do not pass in an Activity that has already been through
-     * {@link Activity#onDestroy}. This is guaranteed if you call this API
-     * during {@link Activity#onCreate}.
-     *
-     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
-     *
-     * @param callback callback, or null to disable
-     * @param activity activity for which the NDEF message will be pushed
-     * @param activities optional additional activities, however we strongly recommend
-     *        to only register one at a time, and to do so in that activity's
-     *        {@link Activity#onCreate}
-     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @removed this feature is removed. File sharing can work using other technology like
-     * Bluetooth.
-     */
-    @java.lang.Deprecated
-    @UnsupportedAppUsage
-    public void setOnNdefPushCompleteCallback(OnNdefPushCompleteCallback callback,
-            Activity activity, Activity ... activities) {
-        synchronized (sLock) {
-            if (!sHasNfcFeature) {
-                throw new UnsupportedOperationException();
-            }
-        }
-    }
-
-    /**
-     * Enable foreground dispatch to the given Activity.
-     *
-     * <p>This will give priority to the foreground activity when
-     * dispatching a discovered {@link Tag} to an application.
-     *
-     * <p>If any IntentFilters are provided to this method they are used to match dispatch Intents
-     * for both the {@link NfcAdapter#ACTION_NDEF_DISCOVERED} and
-     * {@link NfcAdapter#ACTION_TAG_DISCOVERED}. Since {@link NfcAdapter#ACTION_TECH_DISCOVERED}
-     * relies on meta data outside of the IntentFilter matching for that dispatch Intent is handled
-     * by passing in the tech lists separately. Each first level entry in the tech list represents
-     * an array of technologies that must all be present to match. If any of the first level sets
-     * match then the dispatch is routed through the given PendingIntent. In other words, the second
-     * level is ANDed together and the first level entries are ORed together.
-     *
-     * <p>If you pass {@code null} for both the {@code filters} and {@code techLists} parameters
-     * that acts a wild card and will cause the foreground activity to receive all tags via the
-     * {@link NfcAdapter#ACTION_TAG_DISCOVERED} intent.
-     *
-     * <p>This method must be called from the main thread, and only when the activity is in the
-     * foreground (resumed). Also, activities must call {@link #disableForegroundDispatch} before
-     * the completion of their {@link Activity#onPause} callback to disable foreground dispatch
-     * after it has been enabled.
-     *
-     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
-     *
-     * @param activity the Activity to dispatch to
-     * @param intent the PendingIntent to start for the dispatch
-     * @param filters the IntentFilters to override dispatching for, or null to always dispatch
-     * @param techLists the tech lists used to perform matching for dispatching of the
-     *      {@link NfcAdapter#ACTION_TECH_DISCOVERED} intent
-     * @throws IllegalStateException if the Activity is not currently in the foreground
-     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     */
-    public void enableForegroundDispatch(Activity activity, PendingIntent intent,
-            IntentFilter[] filters, String[][] techLists) {
-        synchronized (sLock) {
-            if (!sHasNfcFeature) {
-                throw new UnsupportedOperationException();
-            }
-        }
-        if (activity == null || intent == null) {
-            throw new NullPointerException();
-        }
-        try {
-            TechListParcel parcel = null;
-            if (techLists != null && techLists.length > 0) {
-                parcel = new TechListParcel(techLists);
-            }
-            sService.setForegroundDispatch(intent, filters, parcel);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-        }
-    }
-
-    /**
-     * Disable foreground dispatch to the given activity.
-     *
-     * <p>After calling {@link #enableForegroundDispatch}, an activity
-     * must call this method before its {@link Activity#onPause} callback
-     * completes.
-     *
-     * <p>This method must be called from the main thread.
-     *
-     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
-     *
-     * @param activity the Activity to disable dispatch to
-     * @throws IllegalStateException if the Activity has already been paused
-     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     */
-    public void disableForegroundDispatch(Activity activity) {
-        synchronized (sLock) {
-            if (!sHasNfcFeature) {
-                throw new UnsupportedOperationException();
-            }
-        }
-        try {
-            sService.setForegroundDispatch(null, null, null);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-        }
-    }
-
-    /**
-     * Limit the NFC controller to reader mode while this Activity is in the foreground.
-     *
-     * <p>In this mode the NFC controller will only act as an NFC tag reader/writer,
-     * thus disabling any peer-to-peer (Android Beam) and card-emulation modes of
-     * the NFC adapter on this device.
-     *
-     * <p>Use {@link #FLAG_READER_SKIP_NDEF_CHECK} to prevent the platform from
-     * performing any NDEF checks in reader mode. Note that this will prevent the
-     * {@link Ndef} tag technology from being enumerated on the tag, and that
-     * NDEF-based tag dispatch will not be functional.
-     *
-     * <p>For interacting with tags that are emulated on another Android device
-     * using Android's host-based card-emulation, the recommended flags are
-     * {@link #FLAG_READER_NFC_A} and {@link #FLAG_READER_SKIP_NDEF_CHECK}.
-     *
-     * @param activity the Activity that requests the adapter to be in reader mode
-     * @param callback the callback to be called when a tag is discovered
-     * @param flags Flags indicating poll technologies and other optional parameters
-     * @param extras Additional extras for configuring reader mode.
-     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     */
-    public void enableReaderMode(Activity activity, ReaderCallback callback, int flags,
-            Bundle extras) {
-        synchronized (sLock) {
-            if (!sHasNfcFeature) {
-                throw new UnsupportedOperationException();
-            }
-        }
-        mNfcActivityManager.enableReaderMode(activity, callback, flags, extras);
-    }
-
-    /**
-     * Restore the NFC adapter to normal mode of operation: supporting
-     * peer-to-peer (Android Beam), card emulation, and polling for
-     * all supported tag technologies.
-     *
-     * @param activity the Activity that currently has reader mode enabled
-     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     */
-    public void disableReaderMode(Activity activity) {
-        synchronized (sLock) {
-            if (!sHasNfcFeature) {
-                throw new UnsupportedOperationException();
-            }
-        }
-        mNfcActivityManager.disableReaderMode(activity);
-    }
-
-    // Flags arguments to NFC adapter to enable/disable NFC
-    private static final int DISABLE_POLLING_FLAGS = 0x1000;
-    private static final int ENABLE_POLLING_FLAGS = 0x0000;
-
-    /**
-     * Privileged API to enable disable reader polling.
-     * Note: Use with caution! The app is responsible for ensuring that the polling state is
-     * returned to normal.
-     *
-     * @see #enableReaderMode(Activity, ReaderCallback, int, Bundle)  for more detailed
-     * documentation.
-     *
-     * @param enablePolling whether to enable or disable polling.
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    @SuppressLint("VisiblySynchronized")
-    public void setReaderMode(boolean enablePolling) {
-        synchronized (sLock) {
-            if (!sHasNfcFeature) {
-                throw new UnsupportedOperationException();
-            }
-        }
-        Binder token = new Binder();
-        int flags = enablePolling ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS;
-        try {
-            NfcAdapter.sService.setReaderMode(token, null, flags, null);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-        }
-    }
-
-    /**
-     * Set the NFC controller to enable specific poll/listen technologies,
-     * as specified in parameters, while this Activity is in the foreground.
-     *
-     * Use {@link #FLAG_READER_KEEP} to keep current polling technology.
-     * Use {@link #FLAG_LISTEN_KEEP} to keep current listenig technology.
-     * Use {@link #FLAG_READER_DISABLE} to disable polling.
-     * Use {@link #FLAG_LISTEN_DISABLE} to disable listening.
-     * Also refer to {@link #resetDiscoveryTechnology(Activity)} to restore these changes.
-     * </p>
-     * The pollTechnology, listenTechnology parameters can be one or several of below list.
-     * <pre>
-     *                    Poll                    Listen
-     *  Passive A         0x01   (NFC_A)           0x01  (NFC_PASSIVE_A)
-     *  Passive B         0x02   (NFC_B)           0x02  (NFC_PASSIVE_B)
-     *  Passive F         0x04   (NFC_F)           0x04  (NFC_PASSIVE_F)
-     *  ISO 15693         0x08   (NFC_V)             -
-     *  Kovio             0x10   (NFC_BARCODE)       -
-     * </pre>
-     * <p>Example usage in an Activity that requires to disable poll,
-     * keep current listen technologies:
-     * <pre>
-     * protected void onResume() {
-     *     mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
-     *     mNfcAdapter.setDiscoveryTechnology(this,
-     *         NfcAdapter.FLAG_READER_DISABLE, NfcAdapter.FLAG_LISTEN_KEEP);
-     * }</pre></p>
-     * @param activity The Activity that requests NFC controller to enable specific technologies.
-     * @param pollTechnology Flags indicating poll technologies.
-     * @param listenTechnology Flags indicating listen technologies.
-     * @throws UnsupportedOperationException if FEATURE_NFC,
-     * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF are unavailable.
-     */
-
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
-    public void setDiscoveryTechnology(@NonNull Activity activity,
-            @PollTechnology int pollTechnology, @ListenTechnology int listenTechnology) {
-        if (listenTechnology == FLAG_LISTEN_DISABLE) {
-            synchronized (sLock) {
-                if (!sHasNfcFeature) {
-                    throw new UnsupportedOperationException();
-                }
-            }
-            mNfcActivityManager.enableReaderMode(activity, null, pollTechnology, null);
-            return;
-        }
-        if (pollTechnology == FLAG_READER_DISABLE) {
-            synchronized (sLock) {
-                if (!sHasCeFeature) {
-                    throw new UnsupportedOperationException();
-                }
-            }
-        } else {
-            synchronized (sLock) {
-                if (!sHasNfcFeature || !sHasCeFeature) {
-                    throw new UnsupportedOperationException();
-                }
-            }
-        }
-        mNfcActivityManager.setDiscoveryTech(activity, pollTechnology, listenTechnology);
-    }
-
-    /**
-     * Restore the poll/listen technologies of NFC controller,
-     * which were changed by {@link #setDiscoveryTechnology(Activity , int , int)}
-     *
-     * @param activity The Activity that requests to changed technologies.
-     */
-
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
-    public void resetDiscoveryTechnology(@NonNull Activity activity) {
-        mNfcActivityManager.resetDiscoveryTech(activity);
-    }
-
-    /**
-     * Manually invoke Android Beam to share data.
-     *
-     * <p>The Android Beam animation is normally only shown when two NFC-capable
-     * devices come into range.
-     * By calling this method, an Activity can invoke the Beam animation directly
-     * even if no other NFC device is in range yet. The Beam animation will then
-     * prompt the user to tap another NFC-capable device to complete the data
-     * transfer.
-     *
-     * <p>The main advantage of using this method is that it avoids the need for the
-     * user to tap the screen to complete the transfer, as this method already
-     * establishes the direction of the transfer and the consent of the user to
-     * share data. Callers are responsible for making sure that the user has
-     * consented to sharing data on NFC tap.
-     *
-     * <p>Note that to use this method, the passed in Activity must have already
-     * set data to share over Beam by using method calls such as
-     * {@link #setNdefPushMessageCallback} or
-     * {@link #setBeamPushUrisCallback}.
-     *
-     * @param activity the current foreground Activity that has registered data to share
-     * @return whether the Beam animation was successfully invoked
-     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @removed this feature is removed. File sharing can work using other technology like
-     * Bluetooth.
-     */
-    @java.lang.Deprecated
-    @UnsupportedAppUsage
-    public boolean invokeBeam(Activity activity) {
-        synchronized (sLock) {
-            if (!sHasNfcFeature) {
-                throw new UnsupportedOperationException();
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Enable NDEF message push over NFC while this Activity is in the foreground.
-     *
-     * <p>You must explicitly call this method every time the activity is
-     * resumed, and you must call {@link #disableForegroundNdefPush} before
-     * your activity completes {@link Activity#onPause}.
-     *
-     * <p>Strongly recommend to use the new {@link #setNdefPushMessage}
-     * instead: it automatically hooks into your activity life-cycle,
-     * so you do not need to call enable/disable in your onResume/onPause.
-     *
-     * <p>For NDEF push to function properly the other NFC device must
-     * support either NFC Forum's SNEP (Simple Ndef Exchange Protocol), or
-     * Android's "com.android.npp" (Ndef Push Protocol). This was optional
-     * on Gingerbread level Android NFC devices, but SNEP is mandatory on
-     * Ice-Cream-Sandwich and beyond.
-     *
-     * <p>This method must be called from the main thread.
-     *
-     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
-     *
-     * @param activity foreground activity
-     * @param message a NDEF Message to push over NFC
-     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable
-     * @removed this feature is removed. File sharing can work using other technology like
-     * Bluetooth.
-     */
-    @Deprecated
-    @UnsupportedAppUsage
-    public void enableForegroundNdefPush(Activity activity, NdefMessage message) {
-        synchronized (sLock) {
-            if (!sHasNfcFeature) {
-                throw new UnsupportedOperationException();
-            }
-        }
-    }
-
-    /**
-     * Disable NDEF message push over P2P.
-     *
-     * <p>After calling {@link #enableForegroundNdefPush}, an activity
-     * must call this method before its {@link Activity#onPause} callback
-     * completes.
-     *
-     * <p>Strongly recommend to use the new {@link #setNdefPushMessage}
-     * instead: it automatically hooks into your activity life-cycle,
-     * so you do not need to call enable/disable in your onResume/onPause.
-     *
-     * <p>This method must be called from the main thread.
-     *
-     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
-     *
-     * @param activity the Foreground activity
-     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable
-     * @removed this feature is removed. File sharing can work using other technology like
-     * Bluetooth.
-     */
-    @Deprecated
-    @UnsupportedAppUsage
-    public void disableForegroundNdefPush(Activity activity) {
-        synchronized (sLock) {
-            if (!sHasNfcFeature) {
-                throw new UnsupportedOperationException();
-            }
-        }
-    }
-
-    /**
-     * Sets Secure NFC feature.
-     * <p>This API is for the Settings application.
-     * @return True if successful
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    public boolean enableSecureNfc(boolean enable) {
-        if (!sHasNfcFeature && !sHasCeFeature) {
-            throw new UnsupportedOperationException();
-        }
-        try {
-            return sService.setNfcSecure(enable);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.setNfcSecure(enable);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Checks if the device supports Secure NFC functionality.
-     *
-     * @return True if device supports Secure NFC, false otherwise
-     * @throws UnsupportedOperationException if FEATURE_NFC,
-     * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
-     * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
-     * are unavailable
-     */
-    public boolean isSecureNfcSupported() {
-        if (!sHasNfcFeature && !sHasCeFeature) {
-            throw new UnsupportedOperationException();
-        }
-        try {
-            return sService.deviceSupportsNfcSecure();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.deviceSupportsNfcSecure();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Returns information regarding Nfc antennas on the device
-     * such as their relative positioning on the device.
-     *
-     * @return Information on the nfc antenna(s) on the device.
-     * @throws UnsupportedOperationException if FEATURE_NFC,
-     * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
-     * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
-     * are unavailable
-     */
-    @Nullable
-    public NfcAntennaInfo getNfcAntennaInfo() {
-        if (!sHasNfcFeature && !sHasCeFeature) {
-            throw new UnsupportedOperationException();
-        }
-        try {
-            return sService.getNfcAntennaInfo();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return null;
-            }
-            try {
-                return sService.getNfcAntennaInfo();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return null;
-        }
-    }
-
-    /**
-     * Checks Secure NFC feature is enabled.
-     *
-     * @return True if Secure NFC is enabled, false otherwise
-     * @throws UnsupportedOperationException if FEATURE_NFC,
-     * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
-     * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
-     * are unavailable
-     * @throws UnsupportedOperationException if device doesn't support
-     *         Secure NFC functionality. {@link #isSecureNfcSupported}
-     */
-    public boolean isSecureNfcEnabled() {
-        if (!sHasNfcFeature && !sHasCeFeature) {
-            throw new UnsupportedOperationException();
-        }
-        try {
-            return sService.isNfcSecureEnabled();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.isNfcSecureEnabled();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Sets NFC Reader option feature.
-     * <p>This API is for the Settings application.
-     * @return True if successful
-     * @hide
-     */
-    @SystemApi
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_READER_OPTION)
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    public boolean enableReaderOption(boolean enable) {
-        if (!sHasNfcFeature) {
-            throw new UnsupportedOperationException();
-        }
-        try {
-            return sService.enableReaderOption(enable);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.enableReaderOption(enable);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Checks if the device supports NFC Reader option functionality.
-     *
-     * @return True if device supports NFC Reader option, false otherwise
-     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_READER_OPTION)
-    public boolean isReaderOptionSupported() {
-        if (!sHasNfcFeature) {
-            throw new UnsupportedOperationException();
-        }
-        try {
-            return sService.isReaderOptionSupported();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.isReaderOptionSupported();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Checks NFC Reader option feature is enabled.
-     *
-     * @return True if NFC Reader option  is enabled, false otherwise
-     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @throws UnsupportedOperationException if device doesn't support
-     *         NFC Reader option functionality. {@link #isReaderOptionSupported}
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_READER_OPTION)
-    public boolean isReaderOptionEnabled() {
-        if (!sHasNfcFeature) {
-            throw new UnsupportedOperationException();
-        }
-        try {
-            return sService.isReaderOptionEnabled();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.isReaderOptionEnabled();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Enable NDEF Push feature.
-     * <p>This API is for the Settings application.
-     * @hide
-     * @removed
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    @UnsupportedAppUsage
-    public boolean enableNdefPush() {
-        return false;
-    }
-
-    /**
-     * Disable NDEF Push feature.
-     * <p>This API is for the Settings application.
-     * @hide
-     * @removed
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    @UnsupportedAppUsage
-    public boolean disableNdefPush() {
-        return false;
-    }
-
-    /**
-     * Return true if the NDEF Push (Android Beam) feature is enabled.
-     * <p>This function will return true only if both NFC is enabled, and the
-     * NDEF Push feature is enabled.
-     * <p>Note that if NFC is enabled but NDEF Push is disabled then this
-     * device can still <i>receive</i> NDEF messages, it just cannot send them.
-     * <p>Applications cannot directly toggle the NDEF Push feature, but they
-     * can request Settings UI allowing the user to toggle NDEF Push using
-     * <code>startActivity(new Intent(Settings.ACTION_NFCSHARING_SETTINGS))</code>
-     * <p>Example usage in an Activity that requires NDEF Push:
-     * <p><pre>
-     * protected void onResume() {
-     *     super.onResume();
-     *     if (!nfcAdapter.isEnabled()) {
-     *         startActivity(new Intent(Settings.ACTION_NFC_SETTINGS));
-     *     } else if (!nfcAdapter.isNdefPushEnabled()) {
-     *         startActivity(new Intent(Settings.ACTION_NFCSHARING_SETTINGS));
-     *     }
-     * }</pre>
-     *
-     * @see android.provider.Settings#ACTION_NFCSHARING_SETTINGS
-     * @return true if NDEF Push feature is enabled
-     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
-     * @removed this feature is removed. File sharing can work using other technology like
-     * Bluetooth.
-     */
-    @java.lang.Deprecated
-    @UnsupportedAppUsage
-    public boolean isNdefPushEnabled() {
-        synchronized (sLock) {
-            if (!sHasNfcFeature) {
-                throw new UnsupportedOperationException();
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Signals that you are no longer interested in communicating with an NFC tag
-     * for as long as it remains in range.
-     *
-     * All future attempted communication to this tag will fail with {@link IOException}.
-     * The NFC controller will be put in a low-power polling mode, allowing the device
-     * to save power in cases where it's "attached" to a tag all the time (e.g. a tag in
-     * car dock).
-     *
-     * Additionally the debounceMs parameter allows you to specify for how long the tag needs
-     * to have gone out of range, before it will be dispatched again.
-     *
-     * Note: the NFC controller typically polls at a pretty slow interval (100 - 500 ms).
-     * This means that if the tag repeatedly goes in and out of range (for example, in
-     * case of a flaky connection), and the controller happens to poll every time the
-     * tag is out of range, it *will* re-dispatch the tag after debounceMs, despite the tag
-     * having been "in range" during the interval.
-     *
-     * Note 2: if a tag with another UID is detected after this API is called, its effect
-     * will be cancelled; if this tag shows up before the amount of time specified in
-     * debounceMs, it will be dispatched again.
-     *
-     * Note 3: some tags have a random UID, in which case this API won't work reliably.
-     *
-     * @param tag        the {@link android.nfc.Tag Tag} to ignore.
-     * @param debounceMs minimum amount of time the tag needs to be out of range before being
-     *                   dispatched again.
-     * @param tagRemovedListener listener to be called when the tag is removed from the field.
-     *                           Note that this will only be called if the tag has been out of range
-     *                           for at least debounceMs, or if another tag came into range before
-     *                           debounceMs. May be null in case you don't want a callback.
-     * @param handler the {@link android.os.Handler Handler} that will be used for delivering
-     *                the callback. if the handler is null, then the thread used for delivering
-     *                the callback is unspecified.
-     * @return false if the tag couldn't be found (or has already gone out of range), true otherwise
-     */
-    public boolean ignore(final Tag tag, int debounceMs,
-                          final OnTagRemovedListener tagRemovedListener, final Handler handler) {
-        ITagRemovedCallback.Stub iListener = null;
-        if (tagRemovedListener != null) {
-            iListener = new ITagRemovedCallback.Stub() {
-                @Override
-                public void onTagRemoved() throws RemoteException {
-                    if (handler != null) {
-                        handler.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                tagRemovedListener.onTagRemoved();
-                            }
-                        });
-                    } else {
-                        tagRemovedListener.onTagRemoved();
-                    }
-                    synchronized (mLock) {
-                        mTagRemovedListener = null;
-                    }
-                }
-            };
-        }
-        synchronized (mLock) {
-            mTagRemovedListener = iListener;
-        }
-        try {
-            return sService.ignore(tag.getServiceHandle(), debounceMs, iListener);
-        } catch (RemoteException e) {
-            return false;
-        }
-    }
-
-    /**
-     * Inject a mock NFC tag.<p>
-     * Used for testing purposes.
-     * <p class="note">Requires the
-     * {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission.
-     * @hide
-     */
-    public void dispatch(Tag tag) {
-        if (tag == null) {
-            throw new NullPointerException("tag cannot be null");
-        }
-        try {
-            sService.dispatch(tag);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-        }
-    }
-
-    /**
-     * Registers a new NFC unlock handler with the NFC service.
-     *
-     * <p />NFC unlock handlers are intended to unlock the keyguard in the presence of a trusted
-     * NFC device. The handler should return true if it successfully authenticates the user and
-     * unlocks the keyguard.
-     *
-     * <p /> The parameter {@code tagTechnologies} determines which Tag technologies will be polled for
-     * at the lockscreen. Polling for less tag technologies reduces latency, and so it is
-     * strongly recommended to only provide the Tag technologies that the handler is expected to
-     * receive. There must be at least one tag technology provided, otherwise the unlock handler
-     * is ignored.
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    public boolean addNfcUnlockHandler(final NfcUnlockHandler unlockHandler,
-                                       String[] tagTechnologies) {
-        synchronized (sLock) {
-            if (!sHasNfcFeature) {
-                throw new UnsupportedOperationException();
-            }
-        }
-        // If there are no tag technologies, don't bother adding unlock handler
-        if (tagTechnologies.length == 0) {
-            return false;
-        }
-
-        try {
-            synchronized (mLock) {
-                if (mNfcUnlockHandlers.containsKey(unlockHandler)) {
-                    // update the tag technologies
-                    sService.removeNfcUnlockHandler(mNfcUnlockHandlers.get(unlockHandler));
-                    mNfcUnlockHandlers.remove(unlockHandler);
-                }
-
-                INfcUnlockHandler.Stub iHandler = new INfcUnlockHandler.Stub() {
-                    @Override
-                    public boolean onUnlockAttempted(Tag tag) throws RemoteException {
-                        return unlockHandler.onUnlockAttempted(tag);
-                    }
-                };
-
-                sService.addNfcUnlockHandler(iHandler,
-                        Tag.getTechCodesFromStrings(tagTechnologies));
-                mNfcUnlockHandlers.put(unlockHandler, iHandler);
-            }
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            return false;
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Unable to register LockscreenDispatch", e);
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Removes a previously registered unlock handler. Also removes the tag technologies
-     * associated with the removed unlock handler.
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    public boolean removeNfcUnlockHandler(NfcUnlockHandler unlockHandler) {
-        synchronized (sLock) {
-            if (!sHasNfcFeature) {
-                throw new UnsupportedOperationException();
-            }
-        }
-        try {
-            synchronized (mLock) {
-                if (mNfcUnlockHandlers.containsKey(unlockHandler)) {
-                    sService.removeNfcUnlockHandler(mNfcUnlockHandlers.remove(unlockHandler));
-                }
-
-                return true;
-            }
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            return false;
-        }
-    }
-
-    /**
-     * @hide
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public INfcAdapterExtras getNfcAdapterExtrasInterface() {
-        if (mContext == null) {
-            throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
-                    + " NFC extras APIs");
-        }
-        try {
-            return sService.getNfcAdapterExtrasInterface(mContext.getPackageName());
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return null;
-            }
-            try {
-                return sService.getNfcAdapterExtrasInterface(mContext.getPackageName());
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return null;
-        }
-    }
-
-    void enforceResumed(Activity activity) {
-        if (!activity.isResumed()) {
-            throw new IllegalStateException("API cannot be called while activity is paused");
-        }
-    }
-
-    int getSdkVersion() {
-        if (mContext == null) {
-            return android.os.Build.VERSION_CODES.GINGERBREAD; // best guess
-        } else {
-            return mContext.getApplicationInfo().targetSdkVersion;
-        }
-    }
-
-    /**
-     * Sets NFC controller always on feature.
-     * <p>This API is for the NFCC internal state management. It allows to discriminate
-     * the controller function from the NFC function by keeping the NFC controller on without
-     * any NFC RF enabled if necessary.
-     * <p>This call is asynchronous. Register a listener {@link #ControllerAlwaysOnListener}
-     * by {@link #registerControllerAlwaysOnListener} to find out when the operation is
-     * complete.
-     * <p>If this returns true, then either NFCC always on state has been set based on the value,
-     * or a {@link ControllerAlwaysOnListener#onControllerAlwaysOnChanged(boolean)} will be invoked
-     * to indicate the state change.
-     * If this returns false, then there is some problem that prevents an attempt to turn NFCC
-     * always on.
-     * @param value if true the NFCC will be kept on (with no RF enabled if NFC adapter is
-     * disabled), if false the NFCC will follow completely the Nfc adapter state.
-     * @throws UnsupportedOperationException if FEATURE_NFC,
-     * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
-     * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
-     * are unavailable
-     * @return void
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
-    public boolean setControllerAlwaysOn(boolean value) {
-        if (!sHasNfcFeature && !sHasCeFeature) {
-            throw new UnsupportedOperationException();
-        }
-        try {
-            return sService.setControllerAlwaysOn(value);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.setControllerAlwaysOn(value);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Checks NFC controller always on feature is enabled.
-     *
-     * @return True if NFC controller always on is enabled, false otherwise
-     * @throws UnsupportedOperationException if FEATURE_NFC,
-     * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
-     * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
-     * are unavailable
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
-    public boolean isControllerAlwaysOn() {
-        try {
-            return sService.isControllerAlwaysOn();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.isControllerAlwaysOn();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Checks if the device supports NFC controller always on functionality.
-     *
-     * @return True if device supports NFC controller always on, false otherwise
-     * @throws UnsupportedOperationException if FEATURE_NFC,
-     * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
-     * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
-     * are unavailable
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
-    public boolean isControllerAlwaysOnSupported() {
-        if (!sHasNfcFeature && !sHasCeFeature) {
-            throw new UnsupportedOperationException();
-        }
-        try {
-            return sService.isControllerAlwaysOnSupported();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.isControllerAlwaysOnSupported();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Register a {@link ControllerAlwaysOnListener} to listen for NFC controller always on
-     * state changes
-     * <p>The provided listener will be invoked by the given {@link Executor}.
-     *
-     * @param executor an {@link Executor} to execute given listener
-     * @param listener user implementation of the {@link ControllerAlwaysOnListener}
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
-    public void registerControllerAlwaysOnListener(
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull ControllerAlwaysOnListener listener) {
-        mControllerAlwaysOnListener.register(executor, listener);
-    }
-
-    /**
-     * Unregister the specified {@link ControllerAlwaysOnListener}
-     * <p>The same {@link ControllerAlwaysOnListener} object used when calling
-     * {@link #registerControllerAlwaysOnListener(Executor, ControllerAlwaysOnListener)}
-     * must be used.
-     *
-     * <p>Listeners are automatically unregistered when application process goes away
-     *
-     * @param listener user implementation of the {@link ControllerAlwaysOnListener}
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
-    public void unregisterControllerAlwaysOnListener(
-            @NonNull ControllerAlwaysOnListener listener) {
-        mControllerAlwaysOnListener.unregister(listener);
-    }
-
-
-    /**
-     * Sets whether we dispatch NFC Tag intents to the package.
-     *
-     * <p>{@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED} or
-     * {@link #ACTION_TAG_DISCOVERED} will not be dispatched to an Activity if its package is
-     * disallowed.
-     * <p>An app is added to the preference list with the allowed flag set to {@code true}
-     * when a Tag intent is dispatched to the package for the first time. This API is called
-     * by settings to note that the user wants to change this default preference.
-     *
-     * @param userId the user to whom this package name will belong to
-     * @param pkg the full name (i.e. com.google.android.tag) of the package that will be added to
-     * the preference list
-     * @param allow {@code true} to allow dispatching Tag intents to the package's activity,
-     * {@code false} otherwise
-     * @return the {@link #TagIntentAppPreferenceResult} value
-     * @throws UnsupportedOperationException if {@link isTagIntentAppPreferenceSupported} returns
-     * {@code false}
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    @TagIntentAppPreferenceResult
-    public int setTagIntentAppPreferenceForUser(@UserIdInt int userId,
-                @NonNull String pkg, boolean allow) {
-        Objects.requireNonNull(pkg, "pkg cannot be null");
-        if (!isTagIntentAppPreferenceSupported()) {
-            Log.e(TAG, "TagIntentAppPreference is not supported");
-            throw new UnsupportedOperationException();
-        }
-        try {
-            return sService.setTagIntentAppPreferenceForUser(userId, pkg, allow);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            try {
-                return sService.setTagIntentAppPreferenceForUser(userId, pkg, allow);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE;
-        }
-    }
-
-
-    /**
-     * Get the Tag dispatch preference list of the UserId.
-     *
-     * <p>This returns a mapping of package names for this user id to whether we dispatch Tag
-     * intents to the package. {@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED} or
-     * {@link #ACTION_TAG_DISCOVERED} will not be dispatched to an Activity if its package is
-     * mapped to {@code false}.
-     * <p>There are three different possible cases:
-     * <p>A package not being in the preference list.
-     * It does not contain any Tag intent filters or the user never triggers a Tag detection that
-     * matches the intent filter of the package.
-     * <p>A package being mapped to {@code true}.
-     * When a package has been launched by a tag detection for the first time, the package name is
-     * put to the map and by default mapped to {@code true}. The package will receive Tag intents as
-     * usual.
-     * <p>A package being mapped to {@code false}.
-     * The user chooses to disable this package and it will not receive any Tag intents anymore.
-     *
-     * @param userId the user to whom this preference list will belong to
-     * @return a map of the UserId which indicates the mapping from package name to
-     * boolean(allow status), otherwise return an empty map
-     * @throws UnsupportedOperationException if {@link isTagIntentAppPreferenceSupported} returns
-     * {@code false}
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    @NonNull
-    public Map<String, Boolean> getTagIntentAppPreferenceForUser(@UserIdInt int userId) {
-        if (!isTagIntentAppPreferenceSupported()) {
-            Log.e(TAG, "TagIntentAppPreference is not supported");
-            throw new UnsupportedOperationException();
-        }
-        try {
-            Map<String, Boolean> result = (Map<String, Boolean>) sService
-                     .getTagIntentAppPreferenceForUser(userId);
-            return result;
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return Collections.emptyMap();
-            }
-            try {
-                Map<String, Boolean> result = (Map<String, Boolean>) sService
-                        .getTagIntentAppPreferenceForUser(userId);
-                return result;
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return Collections.emptyMap();
-        }
-    }
-
-    /**
-     * Checks if the device supports Tag application preference.
-     *
-     * @return {@code true} if the device supports Tag application preference, {@code false}
-     * otherwise
-     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    public boolean isTagIntentAppPreferenceSupported() {
-        if (!sHasNfcFeature) {
-            throw new UnsupportedOperationException();
-        }
-        try {
-            return sService.isTagIntentAppPreferenceSupported();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.isTagIntentAppPreferenceSupported();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Sets NFC charging feature.
-     * <p>This API is for the Settings application.
-     * @return True if successful
-     * @hide
-     */
-    @SystemApi
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    public boolean enableWlc(boolean enable) {
-        if (!sHasNfcWlcFeature) {
-            throw new UnsupportedOperationException();
-        }
-        try {
-            return sService.enableWlc(enable);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.enableWlc(enable);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Checks NFC charging feature is enabled.
-     *
-     * @return True if NFC charging is enabled, false otherwise
-     * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
-     * is unavailable
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
-    public boolean isWlcEnabled() {
-        if (!sHasNfcWlcFeature) {
-            throw new UnsupportedOperationException();
-        }
-        try {
-            return sService.isWlcEnabled();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return false;
-            }
-            try {
-                return sService.isWlcEnabled();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return false;
-        }
-    }
-
-    /**
-     * A listener to be invoked when NFC controller always on state changes.
-     * <p>Register your {@code ControllerAlwaysOnListener} implementation with {@link
-     * NfcAdapter#registerWlcStateListener} and disable it with {@link
-     * NfcAdapter#unregisterWlcStateListenerListener}.
-     * @see #registerWlcStateListener
-     * @hide
-     */
-    @SystemApi
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
-    public interface WlcStateListener {
-        /**
-         * Called on NFC WLC state changes
-         */
-        void onWlcStateChanged(@NonNull WlcLDeviceInfo wlcLDeviceInfo);
-    }
-
-    /**
-     * Register a {@link WlcStateListener} to listen for NFC WLC state changes
-     * <p>The provided listener will be invoked by the given {@link Executor}.
-     *
-     * @param executor an {@link Executor} to execute given listener
-     * @param listener user implementation of the {@link WlcStateListener}
-     * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
-     * is unavailable
-     *
-     * @hide
-     */
-    @SystemApi
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
-    public void registerWlcStateListener(
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull WlcStateListener listener) {
-        if (!sHasNfcWlcFeature) {
-            throw new UnsupportedOperationException();
-        }
-        mNfcWlcStateListener.register(executor, listener);
-    }
-
-    /**
-     * Unregister the specified {@link WlcStateListener}
-     * <p>The same {@link WlcStateListener} object used when calling
-     * {@link #registerWlcStateListener(Executor, WlcStateListener)}
-     * must be used.
-     *
-     * <p>Listeners are automatically unregistered when application process goes away
-     *
-     * @param listener user implementation of the {@link WlcStateListener}a
-     * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
-     * is unavailable
-     *
-     * @hide
-     */
-    @SystemApi
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
-    public void unregisterWlcStateListener(
-            @NonNull WlcStateListener listener) {
-        if (!sHasNfcWlcFeature) {
-            throw new UnsupportedOperationException();
-        }
-        mNfcWlcStateListener.unregister(listener);
-    }
-
-    /**
-     * Returns information on the NFC charging listener device
-     *
-     * @return Information on the NFC charging listener device
-     * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
-     * is unavailable
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
-    @Nullable
-    public WlcLDeviceInfo getWlcLDeviceInfo() {
-        if (!sHasNfcWlcFeature) {
-            throw new UnsupportedOperationException();
-        }
-        try {
-            return sService.getWlcLDeviceInfo();
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-            // Try one more time
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-                return null;
-            }
-            try {
-                return sService.getWlcLDeviceInfo();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover NFC Service.");
-            }
-            return null;
-        }
-    }
-}
diff --git a/core/java/android/nfc/NfcWlcStateListener.java b/core/java/android/nfc/NfcWlcStateListener.java
deleted file mode 100644
index 8d79310..0000000
--- a/core/java/android/nfc/NfcWlcStateListener.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nfc;
-
-import android.annotation.NonNull;
-import android.nfc.NfcAdapter.WlcStateListener;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executor;
-
-/**
- * @hide
- */
-public class NfcWlcStateListener extends INfcWlcStateListener.Stub {
-    private static final String TAG = NfcWlcStateListener.class.getSimpleName();
-
-    private final INfcAdapter mAdapter;
-
-    private final Map<WlcStateListener, Executor> mListenerMap = new HashMap<>();
-
-    private WlcLDeviceInfo mCurrentState = null;
-    private boolean mIsRegistered = false;
-
-    public NfcWlcStateListener(@NonNull INfcAdapter adapter) {
-        mAdapter = adapter;
-    }
-
-    /**
-     * Register a {@link WlcStateListener} with this
-     * {@link WlcStateListener}
-     *
-     * @param executor an {@link Executor} to execute given listener
-     * @param listener user implementation of the {@link WlcStateListener}
-     */
-    public void register(@NonNull Executor executor, @NonNull WlcStateListener listener) {
-        synchronized (this) {
-            if (mListenerMap.containsKey(listener)) {
-                return;
-            }
-
-            mListenerMap.put(listener, executor);
-
-            if (!mIsRegistered) {
-                try {
-                    mAdapter.registerWlcStateListener(this);
-                    mIsRegistered = true;
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Failed to register");
-                }
-            }
-        }
-    }
-
-    /**
-     * Unregister the specified {@link WlcStateListener}
-     *
-     * @param listener user implementation of the {@link WlcStateListener}
-     */
-    public void unregister(@NonNull WlcStateListener listener) {
-        synchronized (this) {
-            if (!mListenerMap.containsKey(listener)) {
-                return;
-            }
-
-            mListenerMap.remove(listener);
-
-            if (mListenerMap.isEmpty() && mIsRegistered) {
-                try {
-                    mAdapter.unregisterWlcStateListener(this);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Failed to unregister");
-                }
-                mIsRegistered = false;
-            }
-        }
-    }
-
-    private void sendCurrentState(@NonNull WlcStateListener listener) {
-        synchronized (this) {
-            Executor executor = mListenerMap.get(listener);
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                executor.execute(() -> listener.onWlcStateChanged(
-                        mCurrentState));
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-    }
-
-    @Override
-    public void onWlcStateChanged(@NonNull WlcLDeviceInfo wlcLDeviceInfo) {
-        synchronized (this) {
-            mCurrentState = wlcLDeviceInfo;
-
-            for (WlcStateListener cb : mListenerMap.keySet()) {
-                sendCurrentState(cb);
-            }
-        }
-    }
-}
-
diff --git a/core/java/android/nfc/OWNERS b/core/java/android/nfc/OWNERS
deleted file mode 100644
index 35e9713..0000000
--- a/core/java/android/nfc/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Bug component: 48448
-include platform/packages/apps/Nfc:/OWNERS
diff --git a/core/java/android/nfc/TEST_MAPPING b/core/java/android/nfc/TEST_MAPPING
deleted file mode 100644
index 5b5ea37..0000000
--- a/core/java/android/nfc/TEST_MAPPING
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "NfcManagerTests"
-    },
-    {
-      "name": "CtsNfcTestCases"
-    }
-  ]
-}
diff --git a/core/java/android/nfc/WlcLDeviceInfo.aidl b/core/java/android/nfc/WlcLDeviceInfo.aidl
deleted file mode 100644
index 33143fe..0000000
--- a/core/java/android/nfc/WlcLDeviceInfo.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nfc;
-
-parcelable WlcLDeviceInfo;
diff --git a/core/java/android/nfc/WlcLDeviceInfo.java b/core/java/android/nfc/WlcLDeviceInfo.java
deleted file mode 100644
index 016431e..0000000
--- a/core/java/android/nfc/WlcLDeviceInfo.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nfc;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Contains information of the nfc wireless charging listener device information.
- */
-@FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
-public final class WlcLDeviceInfo implements Parcelable {
-    public static final int DISCONNECTED = 1;
-
-    public static final int CONNECTED_CHARGING = 2;
-
-    public static final int CONNECTED_DISCHARGING = 3;
-
-    private double mProductId;
-    private double mTemperature;
-    private double mBatteryLevel;
-    private int mState;
-
-    public WlcLDeviceInfo(double productId, double temperature, double batteryLevel, int state) {
-        this.mProductId = productId;
-        this.mTemperature = temperature;
-        this.mBatteryLevel = batteryLevel;
-        this.mState = state;
-    }
-
-    /**
-     * ProductId of the WLC listener device.
-     */
-    public double getProductId() {
-        return mProductId;
-    }
-
-    /**
-     * Temperature of the WLC listener device.
-     */
-    public double getTemperature() {
-        return mTemperature;
-    }
-
-    /**
-     * BatteryLevel of the WLC listener device.
-     */
-    public double getBatteryLevel() {
-        return mBatteryLevel;
-    }
-
-    /**
-     * State of the WLC listener device.
-     */
-    public int getState() {
-        return mState;
-    }
-
-    private WlcLDeviceInfo(Parcel in) {
-        this.mProductId = in.readDouble();
-        this.mTemperature = in.readDouble();
-        this.mBatteryLevel = in.readDouble();
-        this.mState = in.readInt();
-    }
-
-    public static final @NonNull Parcelable.Creator<WlcLDeviceInfo> CREATOR =
-            new Parcelable.Creator<WlcLDeviceInfo>() {
-                @Override
-                public WlcLDeviceInfo createFromParcel(Parcel in) {
-                    return new WlcLDeviceInfo(in);
-                }
-
-                @Override
-                public WlcLDeviceInfo[] newArray(int size) {
-                    return new WlcLDeviceInfo[size];
-                }
-            };
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeDouble(mProductId);
-        dest.writeDouble(mTemperature);
-        dest.writeDouble(mBatteryLevel);
-        dest.writeDouble(mState);
-    }
-}
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
deleted file mode 100644
index 41dee3a..0000000
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ /dev/null
@@ -1,912 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**********************************************************************
- * This file is not a part of the NFC mainline module                 *
- * *******************************************************************/
-
-package android.nfc.cardemulation;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.compat.annotation.UnsupportedAppUsage;
-import android.content.ComponentName;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.graphics.drawable.Drawable;
-import android.nfc.Flags;
-import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
-import android.os.Parcelable;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Xml;
-import android.util.proto.ProtoOutputStream;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Pattern;
-
-/**
- * Class holding APDU service info.
- *
- * @hide
- */
-@SystemApi
-@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-public final class ApduServiceInfo implements Parcelable {
-    private static final String TAG = "ApduServiceInfo";
-
-    /**
-     * The service that implements this
-     */
-    private final ResolveInfo mService;
-
-    /**
-     * Description of the service
-     */
-    private final String mDescription;
-
-    /**
-     * Whether this service represents AIDs running on the host CPU
-     */
-    private final boolean mOnHost;
-
-    /**
-     * Offhost reader name.
-     * eg: SIM, eSE etc
-     */
-    private String mOffHostName;
-
-    /**
-     * Offhost reader name from manifest file.
-     * Used for resetOffHostSecureElement()
-     */
-    private final String mStaticOffHostName;
-
-    /**
-     * Mapping from category to static AID group
-     */
-    private final HashMap<String, AidGroup> mStaticAidGroups;
-
-    /**
-     * Mapping from category to dynamic AID group
-     */
-    private final HashMap<String, AidGroup> mDynamicAidGroups;
-
-    /**
-     * Whether this service should only be started when the device is unlocked.
-     */
-    private final boolean mRequiresDeviceUnlock;
-
-    /**
-     * Whether this service should only be started when the device is screen on.
-     */
-    private final boolean mRequiresDeviceScreenOn;
-
-    /**
-     * The id of the service banner specified in XML.
-     */
-    private final int mBannerResourceId;
-
-    /**
-     * The uid of the package the service belongs to
-     */
-    private final int mUid;
-
-    /**
-     * Settings Activity for this service
-     */
-    private final String mSettingsActivityName;
-
-    /**
-     * State of the service for CATEGORY_OTHER selection
-     */
-    private boolean mCategoryOtherServiceEnabled;
-
-    /**
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
-            ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups,
-            boolean requiresUnlock, int bannerResource, int uid,
-            String settingsActivityName, String offHost, String staticOffHost) {
-        this(info, onHost, description, staticAidGroups, dynamicAidGroups,
-                requiresUnlock, bannerResource, uid, settingsActivityName,
-                offHost, staticOffHost, false);
-    }
-
-    /**
-     * @hide
-     */
-    public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
-            ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups,
-            boolean requiresUnlock, int bannerResource, int uid,
-            String settingsActivityName, String offHost, String staticOffHost,
-            boolean isEnabled) {
-        this(info, onHost, description, staticAidGroups, dynamicAidGroups,
-                requiresUnlock, onHost ? true : false, bannerResource, uid,
-                settingsActivityName, offHost, staticOffHost, isEnabled);
-    }
-
-    /**
-     * @hide
-     */
-    public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
-            List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups,
-            boolean requiresUnlock, boolean requiresScreenOn, int bannerResource, int uid,
-            String settingsActivityName, String offHost, String staticOffHost, boolean isEnabled) {
-        this.mService = info;
-        this.mDescription = description;
-        this.mStaticAidGroups = new HashMap<String, AidGroup>();
-        this.mDynamicAidGroups = new HashMap<String, AidGroup>();
-        this.mOffHostName = offHost;
-        this.mStaticOffHostName = staticOffHost;
-        this.mOnHost = onHost;
-        this.mRequiresDeviceUnlock = requiresUnlock;
-        this.mRequiresDeviceScreenOn = requiresScreenOn;
-        for (AidGroup aidGroup : staticAidGroups) {
-            this.mStaticAidGroups.put(aidGroup.getCategory(), aidGroup);
-        }
-        for (AidGroup aidGroup : dynamicAidGroups) {
-            this.mDynamicAidGroups.put(aidGroup.getCategory(), aidGroup);
-        }
-        this.mBannerResourceId = bannerResource;
-        this.mUid = uid;
-        this.mSettingsActivityName = settingsActivityName;
-        this.mCategoryOtherServiceEnabled = isEnabled;
-
-    }
-
-    /**
-     * Creates a new ApduServiceInfo object.
-     *
-     * @param pm packageManager instance
-     * @param info app component info
-     * @param onHost whether service is on host or not (secure element)
-     * @throws XmlPullParserException If an error occurs parsing the element.
-     * @throws IOException If an error occurs reading the element.
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    public ApduServiceInfo(@NonNull PackageManager pm, @NonNull ResolveInfo info, boolean onHost)
-            throws XmlPullParserException, IOException {
-        ServiceInfo si = info.serviceInfo;
-        XmlResourceParser parser = null;
-        try {
-            if (onHost) {
-                parser = si.loadXmlMetaData(pm, HostApduService.SERVICE_META_DATA);
-                if (parser == null) {
-                    throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA +
-                            " meta-data");
-                }
-            } else {
-                parser = si.loadXmlMetaData(pm, OffHostApduService.SERVICE_META_DATA);
-                if (parser == null) {
-                    throw new XmlPullParserException("No " + OffHostApduService.SERVICE_META_DATA +
-                            " meta-data");
-                }
-            }
-
-            int eventType = parser.getEventType();
-            while (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_DOCUMENT) {
-                eventType = parser.next();
-            }
-
-            String tagName = parser.getName();
-            if (onHost && !"host-apdu-service".equals(tagName)) {
-                throw new XmlPullParserException(
-                        "Meta-data does not start with <host-apdu-service> tag");
-            } else if (!onHost && !"offhost-apdu-service".equals(tagName)) {
-                throw new XmlPullParserException(
-                        "Meta-data does not start with <offhost-apdu-service> tag");
-            }
-
-            Resources res = pm.getResourcesForApplication(si.applicationInfo);
-            AttributeSet attrs = Xml.asAttributeSet(parser);
-            if (onHost) {
-                TypedArray sa = res.obtainAttributes(attrs,
-                        com.android.internal.R.styleable.HostApduService);
-                mService = info;
-                mDescription = sa.getString(
-                        com.android.internal.R.styleable.HostApduService_description);
-                mRequiresDeviceUnlock = sa.getBoolean(
-                        com.android.internal.R.styleable.HostApduService_requireDeviceUnlock,
-                        false);
-                mRequiresDeviceScreenOn = sa.getBoolean(
-                        com.android.internal.R.styleable.HostApduService_requireDeviceScreenOn,
-                        true);
-                mBannerResourceId = sa.getResourceId(
-                        com.android.internal.R.styleable.HostApduService_apduServiceBanner, -1);
-                mSettingsActivityName = sa.getString(
-                        com.android.internal.R.styleable.HostApduService_settingsActivity);
-                mOffHostName = null;
-                mStaticOffHostName = mOffHostName;
-                sa.recycle();
-            } else {
-                TypedArray sa = res.obtainAttributes(attrs,
-                        com.android.internal.R.styleable.OffHostApduService);
-                mService = info;
-                mDescription = sa.getString(
-                        com.android.internal.R.styleable.OffHostApduService_description);
-                mRequiresDeviceUnlock = sa.getBoolean(
-                        com.android.internal.R.styleable.OffHostApduService_requireDeviceUnlock,
-                        false);
-                mRequiresDeviceScreenOn = sa.getBoolean(
-                        com.android.internal.R.styleable.OffHostApduService_requireDeviceScreenOn,
-                        false);
-                mBannerResourceId = sa.getResourceId(
-                        com.android.internal.R.styleable.OffHostApduService_apduServiceBanner, -1);
-                mSettingsActivityName = sa.getString(
-                        com.android.internal.R.styleable.HostApduService_settingsActivity);
-                mOffHostName = sa.getString(
-                        com.android.internal.R.styleable.OffHostApduService_secureElementName);
-                if (mOffHostName != null) {
-                    if (mOffHostName.equals("eSE")) {
-                        mOffHostName = "eSE1";
-                    } else if (mOffHostName.equals("SIM")) {
-                        mOffHostName = "SIM1";
-                    }
-                }
-                mStaticOffHostName = mOffHostName;
-                sa.recycle();
-            }
-
-            mStaticAidGroups = new HashMap<String, AidGroup>();
-            mDynamicAidGroups = new HashMap<String, AidGroup>();
-            mOnHost = onHost;
-
-            final int depth = parser.getDepth();
-            AidGroup currentGroup = null;
-
-            // Parsed values for the current AID group
-            while (((eventType = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
-                    && eventType != XmlPullParser.END_DOCUMENT) {
-                tagName = parser.getName();
-                if (eventType == XmlPullParser.START_TAG && "aid-group".equals(tagName) &&
-                        currentGroup == null) {
-                    final TypedArray groupAttrs = res.obtainAttributes(attrs,
-                            com.android.internal.R.styleable.AidGroup);
-                    // Get category of AID group
-                    String groupCategory = groupAttrs.getString(
-                            com.android.internal.R.styleable.AidGroup_category);
-                    String groupDescription = groupAttrs.getString(
-                            com.android.internal.R.styleable.AidGroup_description);
-                    if (!CardEmulation.CATEGORY_PAYMENT.equals(groupCategory)) {
-                        groupCategory = CardEmulation.CATEGORY_OTHER;
-                    }
-                    currentGroup = mStaticAidGroups.get(groupCategory);
-                    if (currentGroup != null) {
-                        if (!CardEmulation.CATEGORY_OTHER.equals(groupCategory)) {
-                            Log.e(TAG, "Not allowing multiple aid-groups in the " +
-                                    groupCategory + " category");
-                            currentGroup = null;
-                        }
-                    } else {
-                        currentGroup = new AidGroup(groupCategory, groupDescription);
-                    }
-                    groupAttrs.recycle();
-                } else if (eventType == XmlPullParser.END_TAG && "aid-group".equals(tagName) &&
-                        currentGroup != null) {
-                    if (currentGroup.getAids().size() > 0) {
-                        if (!mStaticAidGroups.containsKey(currentGroup.getCategory())) {
-                            mStaticAidGroups.put(currentGroup.getCategory(), currentGroup);
-                        }
-                    } else {
-                        Log.e(TAG, "Not adding <aid-group> with empty or invalid AIDs");
-                    }
-                    currentGroup = null;
-                } else if (eventType == XmlPullParser.START_TAG && "aid-filter".equals(tagName) &&
-                        currentGroup != null) {
-                    final TypedArray a = res.obtainAttributes(attrs,
-                            com.android.internal.R.styleable.AidFilter);
-                    String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
-                            toUpperCase();
-                    if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
-                        currentGroup.getAids().add(aid);
-                    } else {
-                        Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
-                    }
-                    a.recycle();
-                } else if (eventType == XmlPullParser.START_TAG &&
-                        "aid-prefix-filter".equals(tagName) && currentGroup != null) {
-                    final TypedArray a = res.obtainAttributes(attrs,
-                            com.android.internal.R.styleable.AidFilter);
-                    String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
-                            toUpperCase();
-                    // Add wildcard char to indicate prefix
-                    aid = aid.concat("*");
-                    if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
-                        currentGroup.getAids().add(aid);
-                    } else {
-                        Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
-                    }
-                    a.recycle();
-                } else if (eventType == XmlPullParser.START_TAG &&
-                        tagName.equals("aid-suffix-filter") && currentGroup != null) {
-                    final TypedArray a = res.obtainAttributes(attrs,
-                            com.android.internal.R.styleable.AidFilter);
-                    String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
-                            toUpperCase();
-                    // Add wildcard char to indicate suffix
-                    aid = aid.concat("#");
-                    if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
-                        currentGroup.getAids().add(aid);
-                    } else {
-                        Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
-                    }
-                    a.recycle();
-                }
-            }
-        } catch (NameNotFoundException e) {
-            throw new XmlPullParserException("Unable to create context for: " + si.packageName);
-        } finally {
-            if (parser != null) parser.close();
-        }
-        // Set uid
-        mUid = si.applicationInfo.uid;
-
-        mCategoryOtherServiceEnabled = true;    // support other category
-
-    }
-
-    /**
-     * Returns the app component corresponding to this APDU service.
-     *
-     * @return app component for this service
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    @NonNull
-    public ComponentName getComponent() {
-        return new ComponentName(mService.serviceInfo.packageName,
-                mService.serviceInfo.name);
-    }
-
-    /**
-     * Returns the offhost secure element name (if the service is offhost).
-     *
-     * @return offhost secure element name for offhost services
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    @Nullable
-    public String getOffHostSecureElement() {
-        return mOffHostName;
-    }
-
-    /**
-     * Returns a consolidated list of AIDs from the AID groups
-     * registered by this service. Note that if a service has both
-     * a static (manifest-based) AID group for a category and a dynamic
-     * AID group, only the dynamically registered AIDs will be returned
-     * for that category.
-     * @return List of AIDs registered by the service
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    @NonNull
-    public List<String> getAids() {
-        final ArrayList<String> aids = new ArrayList<String>();
-        for (AidGroup group : getAidGroups()) {
-            aids.addAll(group.getAids());
-        }
-        return aids;
-    }
-
-    /**
-     * Returns a consolidated list of AIDs with prefixes from the AID groups
-     * registered by this service. Note that if a service has both
-     * a static (manifest-based) AID group for a category and a dynamic
-     * AID group, only the dynamically registered AIDs will be returned
-     * for that category.
-     * @return List of prefix AIDs registered by the service
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    @NonNull
-    public List<String> getPrefixAids() {
-        final ArrayList<String> prefixAids = new ArrayList<String>();
-        for (AidGroup group : getAidGroups()) {
-            for (String aid : group.getAids()) {
-                if (aid.endsWith("*")) {
-                    prefixAids.add(aid);
-                }
-            }
-        }
-        return prefixAids;
-    }
-
-    /**
-     * Returns a consolidated list of AIDs with subsets from the AID groups
-     * registered by this service. Note that if a service has both
-     * a static (manifest-based) AID group for a category and a dynamic
-     * AID group, only the dynamically registered AIDs will be returned
-     * for that category.
-     * @return List of prefix AIDs registered by the service
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    @NonNull
-    public List<String> getSubsetAids() {
-        final ArrayList<String> subsetAids = new ArrayList<String>();
-        for (AidGroup group : getAidGroups()) {
-            for (String aid : group.getAids()) {
-                if (aid.endsWith("#")) {
-                    subsetAids.add(aid);
-                }
-            }
-        }
-        return subsetAids;
-    }
-
-    /**
-     * Returns the registered AID group for this category.
-     *
-     * @param category category name
-     * @return {@link AidGroup} instance for the provided category
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    @NonNull
-    public AidGroup getDynamicAidGroupForCategory(@NonNull String category) {
-        return mDynamicAidGroups.get(category);
-    }
-
-    /**
-     * Removes the registered AID group for this category.
-     *
-     * @param category category name
-     * @return {@code true} if an AID group existed
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    @NonNull
-    public boolean removeDynamicAidGroupForCategory(@NonNull String category) {
-        return (mDynamicAidGroups.remove(category) != null);
-    }
-
-    /**
-     * Returns a consolidated list of AID groups
-     * registered by this service. Note that if a service has both
-     * a static (manifest-based) AID group for a category and a dynamic
-     * AID group, only the dynamically registered AID group will be returned
-     * for that category.
-     * @return List of AIDs registered by the service
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    @NonNull
-    public List<AidGroup> getAidGroups() {
-        final ArrayList<AidGroup> groups = new ArrayList<AidGroup>();
-        for (Map.Entry<String, AidGroup> entry : mDynamicAidGroups.entrySet()) {
-            groups.add(entry.getValue());
-        }
-        for (Map.Entry<String, AidGroup> entry : mStaticAidGroups.entrySet()) {
-            if (!mDynamicAidGroups.containsKey(entry.getKey())) {
-                // Consolidate AID groups - don't return static ones
-                // if a dynamic group exists for the category.
-                groups.add(entry.getValue());
-            }
-        }
-        return groups;
-    }
-
-    /**
-     * Returns the category to which this service has attributed the AID that is passed in,
-     * or null if we don't know this AID.
-     * @param aid AID to lookup for
-     * @return category name corresponding to this AID
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    @NonNull
-    public String getCategoryForAid(@NonNull String aid) {
-        List<AidGroup> groups = getAidGroups();
-        for (AidGroup group : groups) {
-            if (group.getAids().contains(aid.toUpperCase())) {
-                return group.getCategory();
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Returns whether there is any AID group for this category.
-     * @param category category name
-     * @return {@code true} if an AID group exists
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    public boolean hasCategory(@NonNull String category) {
-        return (mStaticAidGroups.containsKey(category) || mDynamicAidGroups.containsKey(category));
-    }
-
-    /**
-     * Returns whether the service is on host or not.
-     * @return true if the service is on host (not secure element)
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    public boolean isOnHost() {
-        return mOnHost;
-    }
-
-    /**
-     * Returns whether the service requires device unlock.
-     * @return whether the service requires device unlock
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    public boolean requiresUnlock() {
-        return mRequiresDeviceUnlock;
-    }
-
-    /**
-     * Returns whether this service should only be started when the device is screen on.
-     * @return whether the service requires screen on
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    public boolean requiresScreenOn() {
-        return mRequiresDeviceScreenOn;
-    }
-
-    /**
-     * Returns description of service.
-     * @return user readable description of service
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    @NonNull
-    public String getDescription() {
-        return mDescription;
-    }
-
-    /**
-     * Returns uid of service.
-     * @return uid of the service
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    public int getUid() {
-        return mUid;
-    }
-
-    /**
-     * Add or replace an AID group to this service.
-     * @param aidGroup instance of aid group to set or replace
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    public void setDynamicAidGroup(@NonNull AidGroup aidGroup) {
-        mDynamicAidGroups.put(aidGroup.getCategory(), aidGroup);
-    }
-
-    /**
-     * Sets the off host Secure Element.
-     * @param  offHost  Secure Element to set. Only accept strings with prefix SIM or prefix eSE.
-     *                  Ref: GSMA TS.26 - NFC Handset Requirements
-     *                  TS26_NFC_REQ_069: For UICC, Secure Element Name SHALL be SIM[smartcard slot]
-     *                                    (e.g. SIM/SIM1, SIM2… SIMn).
-     *                  TS26_NFC_REQ_070: For embedded SE, Secure Element Name SHALL be eSE[number]
-     *                                    (e.g. eSE/eSE1, eSE2, etc.).
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    public void setOffHostSecureElement(@NonNull String offHost) {
-        mOffHostName = offHost;
-    }
-
-    /**
-     * Resets the off host Secure Element to statically defined
-     * by the service in the manifest file.
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    public void resetOffHostSecureElement() {
-        mOffHostName = mStaticOffHostName;
-    }
-
-    /**
-     * Load label for this service.
-     * @param pm packagemanager instance
-     * @return label name corresponding to service
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    @NonNull
-    public CharSequence loadLabel(@NonNull PackageManager pm) {
-        return mService.loadLabel(pm);
-    }
-
-    /**
-     * Load application label for this service.
-     * @param pm packagemanager instance
-     * @return app label name corresponding to service
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    @NonNull
-    public CharSequence loadAppLabel(@NonNull PackageManager pm) {
-        try {
-            return pm.getApplicationLabel(pm.getApplicationInfo(
-                    mService.resolvePackageName, PackageManager.GET_META_DATA));
-        } catch (PackageManager.NameNotFoundException e) {
-            return null;
-        }
-    }
-
-    /**
-     * Load application icon for this service.
-     * @param pm packagemanager instance
-     * @return app icon corresponding to service
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    @NonNull
-    public Drawable loadIcon(@NonNull PackageManager pm) {
-        return mService.loadIcon(pm);
-    }
-
-    /**
-     * Load application banner for this service.
-     * @param pm packagemanager instance
-     * @return app banner corresponding to service
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    @NonNull
-    public Drawable loadBanner(@NonNull PackageManager pm) {
-        Resources res;
-        try {
-            res = pm.getResourcesForApplication(mService.serviceInfo.packageName);
-            Drawable banner = res.getDrawable(mBannerResourceId);
-            return banner;
-        } catch (NotFoundException e) {
-            Log.e(TAG, "Could not load banner.");
-            return null;
-        } catch (NameNotFoundException e) {
-            Log.e(TAG, "Could not load banner.");
-            return null;
-        }
-    }
-
-    /**
-     * Load activity name for this service.
-     * @return activity name for this service
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    @NonNull
-    public String getSettingsActivityName() { return mSettingsActivityName; }
-
-    @Override
-    public String toString() {
-        StringBuilder out = new StringBuilder("ApduService: ");
-        out.append(getComponent());
-        out.append(", UID: " + mUid);
-        out.append(", description: " + mDescription);
-        out.append(", Static AID Groups: ");
-        for (AidGroup aidGroup : mStaticAidGroups.values()) {
-            out.append(aidGroup.toString());
-        }
-        out.append(", Dynamic AID Groups: ");
-        for (AidGroup aidGroup : mDynamicAidGroups.values()) {
-            out.append(aidGroup.toString());
-        }
-        return out.toString();
-    }
-
-    @Override
-    public boolean equals(@Nullable Object o) {
-        if (this == o) return true;
-        if (!(o instanceof ApduServiceInfo)) return false;
-        ApduServiceInfo thatService = (ApduServiceInfo) o;
-
-        return thatService.getComponent().equals(this.getComponent())
-                && thatService.getUid() == this.getUid();
-    }
-
-    @Override
-    public int hashCode() {
-        return getComponent().hashCode();
-    }
-
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        mService.writeToParcel(dest, flags);
-        dest.writeString(mDescription);
-        dest.writeInt(mOnHost ? 1 : 0);
-        dest.writeString(mOffHostName);
-        dest.writeString(mStaticOffHostName);
-        dest.writeInt(mStaticAidGroups.size());
-        if (mStaticAidGroups.size() > 0) {
-            dest.writeTypedList(new ArrayList<AidGroup>(mStaticAidGroups.values()));
-        }
-        dest.writeInt(mDynamicAidGroups.size());
-        if (mDynamicAidGroups.size() > 0) {
-            dest.writeTypedList(new ArrayList<AidGroup>(mDynamicAidGroups.values()));
-        }
-        dest.writeInt(mRequiresDeviceUnlock ? 1 : 0);
-        dest.writeInt(mRequiresDeviceScreenOn ? 1 : 0);
-        dest.writeInt(mBannerResourceId);
-        dest.writeInt(mUid);
-        dest.writeString(mSettingsActivityName);
-
-        dest.writeInt(mCategoryOtherServiceEnabled ? 1 : 0);
-    };
-
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    public static final @NonNull Parcelable.Creator<ApduServiceInfo> CREATOR =
-            new Parcelable.Creator<ApduServiceInfo>() {
-                @Override
-                public ApduServiceInfo createFromParcel(Parcel source) {
-                    ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source);
-                    String description = source.readString();
-                    boolean onHost = source.readInt() != 0;
-                    String offHostName = source.readString();
-                    String staticOffHostName = source.readString();
-                    ArrayList<AidGroup> staticAidGroups = new ArrayList<AidGroup>();
-                    int numStaticGroups = source.readInt();
-                    if (numStaticGroups > 0) {
-                        source.readTypedList(staticAidGroups, AidGroup.CREATOR);
-                    }
-                    ArrayList<AidGroup> dynamicAidGroups = new ArrayList<AidGroup>();
-                    int numDynamicGroups = source.readInt();
-                    if (numDynamicGroups > 0) {
-                        source.readTypedList(dynamicAidGroups, AidGroup.CREATOR);
-                    }
-                    boolean requiresUnlock = source.readInt() != 0;
-                    boolean requiresScreenOn = source.readInt() != 0;
-                    int bannerResource = source.readInt();
-                    int uid = source.readInt();
-                    String settingsActivityName = source.readString();
-                    boolean isEnabled = source.readInt() != 0;
-                    return new ApduServiceInfo(info, onHost, description, staticAidGroups,
-                            dynamicAidGroups, requiresUnlock, requiresScreenOn, bannerResource, uid,
-                            settingsActivityName, offHostName, staticOffHostName,
-                            isEnabled);
-                }
-
-                @Override
-                public ApduServiceInfo[] newArray(int size) {
-                    return new ApduServiceInfo[size];
-                }
-            };
-
-    /**
-     * Dump contents for debugging.
-     * @param fd parcelfiledescriptor instance
-     * @param pw printwriter instance
-     * @param args args for dumping
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    public void dump(@NonNull ParcelFileDescriptor fd, @NonNull PrintWriter pw,
-                     @NonNull String[] args) {
-        pw.println("    " + getComponent()
-                + " (Description: " + getDescription() + ")"
-                + " (UID: " + getUid() + ")");
-        if (mOnHost) {
-            pw.println("    On Host Service");
-        } else {
-            pw.println("    Off-host Service");
-            pw.println("        " + "Current off-host SE:" + mOffHostName
-                    + " static off-host SE:" + mStaticOffHostName);
-        }
-        pw.println("    Static AID groups:");
-        for (AidGroup group : mStaticAidGroups.values()) {
-            pw.println("        Category: " + group.getCategory()
-                    + "(enabled: " + mCategoryOtherServiceEnabled + ")");
-            for (String aid : group.getAids()) {
-                pw.println("            AID: " + aid);
-            }
-        }
-        pw.println("    Dynamic AID groups:");
-        for (AidGroup group : mDynamicAidGroups.values()) {
-            pw.println("        Category: " + group.getCategory()
-                    + "(enabled: " + mCategoryOtherServiceEnabled + ")");
-            for (String aid : group.getAids()) {
-                pw.println("            AID: " + aid);
-            }
-        }
-        pw.println("    Settings Activity: " + mSettingsActivityName);
-        pw.println("    Requires Device Unlock: " + mRequiresDeviceUnlock);
-        pw.println("    Requires Device ScreenOn: " + mRequiresDeviceScreenOn);
-    }
-
-
-    /**
-     * Enable or disable this CATEGORY_OTHER service.
-     *
-     * @param enabled true to indicate if user has enabled this service
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    public void setCategoryOtherServiceEnabled(boolean enabled) {
-        mCategoryOtherServiceEnabled = enabled;
-    }
-
-
-    /**
-     * Returns whether this CATEGORY_OTHER service is enabled or not.
-     *
-     * @return true to indicate if user has enabled this service
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    public boolean isCategoryOtherServiceEnabled() {
-        return mCategoryOtherServiceEnabled;
-    }
-
-    /**
-     * Dump debugging info as ApduServiceInfoProto.
-     *
-     * If the output belongs to a sub message, the caller is responsible for wrapping this function
-     * between {@link ProtoOutputStream#start(long)} and {@link ProtoOutputStream#end(long)}.
-     * See proto definition in frameworks/base/core/proto/android/nfc/apdu_service_info.proto
-     *
-     * @param proto the ProtoOutputStream to write to
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    public void dumpDebug(@NonNull ProtoOutputStream proto) {
-        getComponent().dumpDebug(proto, ApduServiceInfoProto.COMPONENT_NAME);
-        proto.write(ApduServiceInfoProto.DESCRIPTION, getDescription());
-        proto.write(ApduServiceInfoProto.ON_HOST, mOnHost);
-        if (!mOnHost) {
-            proto.write(ApduServiceInfoProto.OFF_HOST_NAME, mOffHostName);
-            proto.write(ApduServiceInfoProto.STATIC_OFF_HOST_NAME, mStaticOffHostName);
-        }
-        for (AidGroup group : mStaticAidGroups.values()) {
-            long token = proto.start(ApduServiceInfoProto.STATIC_AID_GROUPS);
-            group.dump(proto);
-            proto.end(token);
-        }
-        for (AidGroup group : mDynamicAidGroups.values()) {
-            long token = proto.start(ApduServiceInfoProto.STATIC_AID_GROUPS);
-            group.dump(proto);
-            proto.end(token);
-        }
-        proto.write(ApduServiceInfoProto.SETTINGS_ACTIVITY_NAME, mSettingsActivityName);
-    }
-
-    private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
-    /**
-     * Copied over from {@link CardEmulation#isValidAid(String)}
-     * @hide
-     */
-    private static boolean isValidAid(String aid) {
-        if (aid == null)
-            return false;
-
-        // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
-        if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
-            Log.e(TAG, "AID " + aid + " is not a valid AID.");
-            return false;
-        }
-
-        // If not a prefix/subset AID, the total length must be even (even # of AID chars)
-        if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
-            Log.e(TAG, "AID " + aid + " is not a valid AID.");
-            return false;
-        }
-
-        // Verify hex characters
-        if (!AID_PATTERN.matcher(aid).matches()) {
-            Log.e(TAG, "AID " + aid + " is not a valid AID.");
-            return false;
-        }
-
-        return true;
-    }
-}
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
deleted file mode 100644
index ad86d70..0000000
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ /dev/null
@@ -1,1087 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nfc.cardemulation;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
-import android.annotation.SdkConstant;
-import android.annotation.SdkConstant.SdkConstantType;
-import android.annotation.SystemApi;
-import android.annotation.UserIdInt;
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.nfc.Constants;
-import android.nfc.Flags;
-import android.nfc.INfcCardEmulation;
-import android.nfc.NfcAdapter;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
-import android.util.Log;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.regex.Pattern;
-
-/**
- * This class can be used to query the state of
- * NFC card emulation services.
- *
- * For a general introduction into NFC card emulation,
- * please read the <a href="{@docRoot}guide/topics/connectivity/nfc/hce.html">
- * NFC card emulation developer guide</a>.</p>
- *
- * <p class="note">Use of this class requires the
- * {@link PackageManager#FEATURE_NFC_HOST_CARD_EMULATION} to be present
- * on the device.
- */
-public final class CardEmulation {
-    private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
-    static final String TAG = "CardEmulation";
-
-    /**
-     * Activity action: ask the user to change the default
-     * card emulation service for a certain category. This will
-     * show a dialog that asks the user whether they want to
-     * replace the current default service with the service
-     * identified with the ComponentName specified in
-     * {@link #EXTRA_SERVICE_COMPONENT}, for the category
-     * specified in {@link #EXTRA_CATEGORY}. There is an optional
-     * extra field using {@link Intent#EXTRA_USER} to specify
-     * the {@link UserHandle} of the user that owns the app.
-     */
-    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_CHANGE_DEFAULT =
-            "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
-
-    /**
-     * The category extra for {@link #ACTION_CHANGE_DEFAULT}.
-     *
-     * @see #ACTION_CHANGE_DEFAULT
-     */
-    public static final String EXTRA_CATEGORY = "category";
-
-    /**
-     * The service {@link ComponentName} object passed in as an
-     * extra for {@link #ACTION_CHANGE_DEFAULT}.
-     *
-     * @see #ACTION_CHANGE_DEFAULT
-     */
-    public static final String EXTRA_SERVICE_COMPONENT = "component";
-
-    /**
-     * Category used for NFC payment services.
-     */
-    public static final String CATEGORY_PAYMENT = "payment";
-
-    /**
-     * Category that can be used for all other card emulation
-     * services.
-     */
-    public static final String CATEGORY_OTHER = "other";
-
-    /**
-     * Return value for {@link #getSelectionModeForCategory(String)}.
-     *
-     * <p>In this mode, the user has set a default service for this
-     *    category.
-     *
-     * <p>When using ISO-DEP card emulation with {@link HostApduService}
-     *    or {@link OffHostApduService}, if a remote NFC device selects
-     *    any of the Application IDs (AIDs)
-     *    that the default service has registered in this category,
-     *    that service will automatically be bound to to handle
-     *    the transaction.
-     */
-    public static final int SELECTION_MODE_PREFER_DEFAULT = 0;
-
-    /**
-     * Return value for {@link #getSelectionModeForCategory(String)}.
-     *
-     * <p>In this mode, when using ISO-DEP card emulation with {@link HostApduService}
-     *    or {@link OffHostApduService}, whenever an Application ID (AID) of this category
-     *    is selected, the user is asked which service they want to use to handle
-     *    the transaction, even if there is only one matching service.
-     */
-    public static final int SELECTION_MODE_ALWAYS_ASK = 1;
-
-    /**
-     * Return value for {@link #getSelectionModeForCategory(String)}.
-     *
-     * <p>In this mode, when using ISO-DEP card emulation with {@link HostApduService}
-     *    or {@link OffHostApduService}, the user will only be asked to select a service
-     *    if the Application ID (AID) selected by the reader has been registered by multiple
-     *    services. If there is only one service that has registered for the AID,
-     *    that service will be invoked directly.
-     */
-    public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2;
-
-    static boolean sIsInitialized = false;
-    static HashMap<Context, CardEmulation> sCardEmus = new HashMap<Context, CardEmulation>();
-    static INfcCardEmulation sService;
-
-    final Context mContext;
-
-    private CardEmulation(Context context, INfcCardEmulation service) {
-        mContext = context.getApplicationContext();
-        sService = service;
-    }
-
-    /**
-     * Helper to get an instance of this class.
-     *
-     * @param adapter A reference to an NfcAdapter object.
-     * @return
-     */
-    public static synchronized CardEmulation getInstance(NfcAdapter adapter) {
-        if (adapter == null) throw new NullPointerException("NfcAdapter is null");
-        Context context = adapter.getContext();
-        if (context == null) {
-            Log.e(TAG, "NfcAdapter context is null.");
-            throw new UnsupportedOperationException();
-        }
-        if (!sIsInitialized) {
-            PackageManager pm = context.getPackageManager();
-            if (pm == null) {
-                Log.e(TAG, "Cannot get PackageManager");
-                throw new UnsupportedOperationException();
-            }
-            if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
-                Log.e(TAG, "This device does not support card emulation");
-                throw new UnsupportedOperationException();
-            }
-            sIsInitialized = true;
-        }
-        CardEmulation manager = sCardEmus.get(context);
-        if (manager == null) {
-            // Get card emu service
-            INfcCardEmulation service = adapter.getCardEmulationService();
-            if (service == null) {
-                Log.e(TAG, "This device does not implement the INfcCardEmulation interface.");
-                throw new UnsupportedOperationException();
-            }
-            manager = new CardEmulation(context, service);
-            sCardEmus.put(context, manager);
-        }
-        return manager;
-    }
-
-    /**
-     * Allows an application to query whether a service is currently
-     * the default service to handle a card emulation category.
-     *
-     * <p>Note that if {@link #getSelectionModeForCategory(String)}
-     * returns {@link #SELECTION_MODE_ALWAYS_ASK} or {@link #SELECTION_MODE_ASK_IF_CONFLICT},
-     * this method will always return false. That is because in these
-     * selection modes a default can't be set at the category level. For categories where
-     * the selection mode is {@link #SELECTION_MODE_ALWAYS_ASK} or
-     * {@link #SELECTION_MODE_ASK_IF_CONFLICT}, use
-     * {@link #isDefaultServiceForAid(ComponentName, String)} to determine whether a service
-     * is the default for a specific AID.
-     *
-     * @param service The ComponentName of the service
-     * @param category The category
-     * @return whether service is currently the default service for the category.
-     *
-     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
-     */
-    public boolean isDefaultServiceForCategory(ComponentName service, String category) {
-        try {
-            return sService.isDefaultServiceForCategory(mContext.getUser().getIdentifier(),
-                    service, category);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.isDefaultServiceForCategory(mContext.getUser().getIdentifier(),
-                        service, category);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-        }
-    }
-
-    /**
-     *
-     * Allows an application to query whether a service is currently
-     * the default handler for a specified ISO7816-4 Application ID.
-     *
-     * @param service The ComponentName of the service
-     * @param aid The ISO7816-4 Application ID
-     * @return whether the service is the default handler for the specified AID
-     *
-     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
-     */
-    public boolean isDefaultServiceForAid(ComponentName service, String aid) {
-        try {
-            return sService.isDefaultServiceForAid(mContext.getUser().getIdentifier(),
-                    service, aid);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.isDefaultServiceForAid(mContext.getUser().getIdentifier(),
-                        service, aid);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
-    }
-
-    /**
-     * Returns whether the user has allowed AIDs registered in the
-     * specified category to be handled by a service that is preferred
-     * by the foreground application, instead of by a pre-configured default.
-     *
-     * Foreground applications can set such preferences using the
-     * {@link #setPreferredService(Activity, ComponentName)} method.
-     *
-     * @param category The category, e.g. {@link #CATEGORY_PAYMENT}
-     * @return whether AIDs in the category can be handled by a service
-     *         specified by the foreground app.
-     */
-    @SuppressWarnings("NonUserGetterCalled")
-    public boolean categoryAllowsForegroundPreference(String category) {
-        if (CATEGORY_PAYMENT.equals(category)) {
-            boolean preferForeground = false;
-            Context contextAsUser = mContext.createContextAsUser(
-                    UserHandle.of(UserHandle.myUserId()), 0);
-            try {
-                preferForeground = Settings.Secure.getInt(
-                        contextAsUser.getContentResolver(),
-                        Constants.SETTINGS_SECURE_NFC_PAYMENT_FOREGROUND) != 0;
-            } catch (SettingNotFoundException e) {
-            }
-            return preferForeground;
-        } else {
-            // Allowed for all other categories
-            return true;
-        }
-    }
-
-    /**
-     * Returns the service selection mode for the passed in category.
-     * Valid return values are:
-     * <p>{@link #SELECTION_MODE_PREFER_DEFAULT} the user has requested a default
-     *    service for this category, which will be preferred.
-     * <p>{@link #SELECTION_MODE_ALWAYS_ASK} the user has requested to be asked
-     *    every time what service they would like to use in this category.
-     * <p>{@link #SELECTION_MODE_ASK_IF_CONFLICT} the user will only be asked
-     *    to pick a service if there is a conflict.
-     * @param category The category, for example {@link #CATEGORY_PAYMENT}
-     * @return the selection mode for the passed in category
-     */
-    public int getSelectionModeForCategory(String category) {
-        if (CATEGORY_PAYMENT.equals(category)) {
-            boolean paymentRegistered = false;
-            try {
-                paymentRegistered = sService.isDefaultPaymentRegistered();
-            } catch (RemoteException e) {
-                recoverService();
-                if (sService == null) {
-                    Log.e(TAG, "Failed to recover CardEmulationService.");
-                    return SELECTION_MODE_ALWAYS_ASK;
-                }
-                try {
-                    paymentRegistered = sService.isDefaultPaymentRegistered();
-                } catch (RemoteException ee) {
-                    Log.e(TAG, "Failed to reach CardEmulationService.");
-                    return SELECTION_MODE_ALWAYS_ASK;
-                }
-            }
-            if (paymentRegistered) {
-                return SELECTION_MODE_PREFER_DEFAULT;
-            } else {
-                return SELECTION_MODE_ALWAYS_ASK;
-            }
-        } else {
-            return SELECTION_MODE_ASK_IF_CONFLICT;
-        }
-    }
-    /**
-     * Sets whether the system should default to observe mode or not when
-     * the service is in the foreground or the default payment service.
-     *
-     * @param service The component name of the service
-     * @param enable Whether the servic should default to observe mode or not
-     * @return whether the change was successful.
-     */
-    @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
-    public boolean setServiceObserveModeDefault(@NonNull ComponentName service, boolean enable) {
-        try {
-            return sService.setServiceObserveModeDefault(mContext.getUser().getIdentifier(),
-                    service, enable);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to reach CardEmulationService.");
-        }
-        return  false;
-    }
-
-    /**
-     * Registers a list of AIDs for a specific category for the
-     * specified service.
-     *
-     * <p>If a list of AIDs for that category was previously
-     * registered for this service (either statically
-     * through the manifest, or dynamically by using this API),
-     * that list of AIDs will be replaced with this one.
-     *
-     * <p>Note that you can only register AIDs for a service that
-     * is running under the same UID as the caller of this API. Typically
-     * this means you need to call this from the same
-     * package as the service itself, though UIDs can also
-     * be shared between packages using shared UIDs.
-     *
-     * @param service The component name of the service
-     * @param category The category of AIDs to be registered
-     * @param aids A list containing the AIDs to be registered
-     * @return whether the registration was successful.
-     */
-    public boolean registerAidsForService(ComponentName service, String category,
-            List<String> aids) {
-        AidGroup aidGroup = new AidGroup(aids, category);
-        try {
-            return sService.registerAidGroupForService(mContext.getUser().getIdentifier(),
-                    service, aidGroup);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.registerAidGroupForService(mContext.getUser().getIdentifier(),
-                        service, aidGroup);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
-    }
-
-    /**
-     * Unsets the off-host Secure Element for the given service.
-     *
-     * <p>Note that this will only remove Secure Element that was dynamically
-     * set using the {@link #setOffHostForService(ComponentName, String)}
-     * and resets it to a value that was statically assigned using manifest.
-     *
-     * <p>Note that you can only unset off-host SE for a service that
-     * is running under the same UID as the caller of this API. Typically
-     * this means you need to call this from the same
-     * package as the service itself, though UIDs can also
-     * be shared between packages using shared UIDs.
-     *
-     * @param service The component name of the service
-     * @return whether the registration was successful.
-     */
-    @RequiresPermission(android.Manifest.permission.NFC)
-    @NonNull
-    public boolean unsetOffHostForService(@NonNull ComponentName service) {
-        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
-        if (adapter == null) {
-            return false;
-        }
-
-        try {
-            return sService.unsetOffHostForService(mContext.getUser().getIdentifier(), service);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.unsetOffHostForService(mContext.getUser().getIdentifier(), service);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
-    }
-
-    /**
-     * Sets the off-host Secure Element for the given service.
-     *
-     * <p>If off-host SE was initially set (either statically
-     * through the manifest, or dynamically by using this API),
-     * it will be replaced with this one. All AIDs registered by
-     * this service will be re-routed to this Secure Element if
-     * successful. AIDs that was statically assigned using manifest
-     * will re-route to off-host SE that stated in manifest after NFC
-     * toggle.
-     *
-     * <p>Note that you can only set off-host SE for a service that
-     * is running under the same UID as the caller of this API. Typically
-     * this means you need to call this from the same
-     * package as the service itself, though UIDs can also
-     * be shared between packages using shared UIDs.
-     *
-     * <p>Registeration will be successful only if the Secure Element
-     * exists on the device.
-     *
-     * @param service The component name of the service
-     * @param offHostSecureElement Secure Element to register the AID to. Only accept strings with
-     *                             prefix SIM or prefix eSE.
-     *                             Ref: GSMA TS.26 - NFC Handset Requirements
-     *                             TS26_NFC_REQ_069: For UICC, Secure Element Name SHALL be
-     *                                               SIM[smartcard slot]
-     *                                               (e.g. SIM/SIM1, SIM2… SIMn).
-     *                             TS26_NFC_REQ_070: For embedded SE, Secure Element Name SHALL be
-     *                                               eSE[number]
-     *                                               (e.g. eSE/eSE1, eSE2, etc.).
-     * @return whether the registration was successful.
-     */
-    @RequiresPermission(android.Manifest.permission.NFC)
-    @NonNull
-    public boolean setOffHostForService(@NonNull ComponentName service,
-            @NonNull String offHostSecureElement) {
-        boolean validSecureElement = false;
-
-        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
-        if (adapter == null || offHostSecureElement == null) {
-            return false;
-        }
-
-        List<String> validSE = adapter.getSupportedOffHostSecureElements();
-        if ((offHostSecureElement.startsWith("eSE") && !validSE.contains("eSE"))
-                || (offHostSecureElement.startsWith("SIM") && !validSE.contains("SIM"))) {
-            return false;
-        }
-
-        if (!offHostSecureElement.startsWith("eSE") && !offHostSecureElement.startsWith("SIM")) {
-            return false;
-        }
-
-        if (offHostSecureElement.equals("eSE")) {
-            offHostSecureElement = "eSE1";
-        } else if (offHostSecureElement.equals("SIM")) {
-            offHostSecureElement = "SIM1";
-        }
-
-        try {
-            return sService.setOffHostForService(mContext.getUser().getIdentifier(), service,
-                offHostSecureElement);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.setOffHostForService(mContext.getUser().getIdentifier(), service,
-                        offHostSecureElement);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
-    }
-
-    /**
-     * Retrieves the currently registered AIDs for the specified
-     * category for a service.
-     *
-     * <p>Note that this will only return AIDs that were dynamically
-     * registered using {@link #registerAidsForService(ComponentName, String, List)}
-     * method. It will *not* return AIDs that were statically registered
-     * in the manifest.
-     *
-     * @param service The component name of the service
-     * @param category The category for which the AIDs were registered,
-     *                 e.g. {@link #CATEGORY_PAYMENT}
-     * @return The list of AIDs registered for this category, or null if it couldn't be found.
-     */
-    public List<String> getAidsForService(ComponentName service, String category) {
-        try {
-            AidGroup group =  sService.getAidGroupForService(mContext.getUser().getIdentifier(),
-                    service, category);
-            return (group != null ? group.getAids() : null);
-        } catch (RemoteException e) {
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return null;
-            }
-            try {
-                AidGroup group = sService.getAidGroupForService(mContext.getUser().getIdentifier(),
-                        service, category);
-                return (group != null ? group.getAids() : null);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return null;
-            }
-        }
-    }
-
-    /**
-     * Removes a previously registered list of AIDs for the specified category for the
-     * service provided.
-     *
-     * <p>Note that this will only remove AIDs that were dynamically
-     * registered using the {@link #registerAidsForService(ComponentName, String, List)}
-     * method. It will *not* remove AIDs that were statically registered in
-     * the manifest. If dynamically registered AIDs are removed using
-     * this method, and a statically registered AID group for the same category
-     * exists in the manifest, the static AID group will become active again.
-     *
-     * @param service The component name of the service
-     * @param category The category of the AIDs to be removed, e.g. {@link #CATEGORY_PAYMENT}
-     * @return whether the group was successfully removed.
-     */
-    public boolean removeAidsForService(ComponentName service, String category) {
-        try {
-            return sService.removeAidGroupForService(mContext.getUser().getIdentifier(), service,
-                    category);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.removeAidGroupForService(mContext.getUser().getIdentifier(),
-                        service, category);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
-    }
-
-    /**
-     * Allows a foreground application to specify which card emulation service
-     * should be preferred while a specific Activity is in the foreground.
-     *
-     * <p>The specified Activity must currently be in resumed state. A good
-     * paradigm is to call this method in your {@link Activity#onResume}, and to call
-     * {@link #unsetPreferredService(Activity)} in your {@link Activity#onPause}.
-     *
-     * <p>This method call will fail in two specific scenarios:
-     * <ul>
-     * <li> If the service registers one or more AIDs in the {@link #CATEGORY_PAYMENT}
-     * category, but the user has indicated that foreground apps are not allowed
-     * to override the default payment service.
-     * <li> If the service registers one or more AIDs in the {@link #CATEGORY_OTHER}
-     * category that are also handled by the default payment service, and the
-     * user has indicated that foreground apps are not allowed to override the
-     * default payment service.
-     * </ul>
-     *
-     * <p> Use {@link #categoryAllowsForegroundPreference(String)} to determine
-     * whether foreground apps can override the default payment service.
-     *
-     * <p>Note that this preference is not persisted by the OS, and hence must be
-     * called every time the Activity is resumed.
-     *
-     * @param activity The activity which prefers this service to be invoked
-     * @param service The service to be preferred while this activity is in the foreground
-     * @return whether the registration was successful
-     */
-    public boolean setPreferredService(Activity activity, ComponentName service) {
-        // Verify the activity is in the foreground before calling into NfcService
-        if (activity == null || service == null) {
-            throw new NullPointerException("activity or service or category is null");
-        }
-        try {
-            return sService.setPreferredService(service);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.setPreferredService(service);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
-    }
-
-    /**
-     * Unsets the preferred service for the specified Activity.
-     *
-     * <p>Note that the specified Activity must still be in resumed
-     * state at the time of this call. A good place to call this method
-     * is in your {@link Activity#onPause} implementation.
-     *
-     * @param activity The activity which the service was registered for
-     * @return true when successful
-     */
-    public boolean unsetPreferredService(Activity activity) {
-        if (activity == null) {
-            throw new NullPointerException("activity is null");
-        }
-        try {
-            return sService.unsetPreferredService();
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.unsetPreferredService();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
-    }
-
-    /**
-     * Some devices may allow an application to register all
-     * AIDs that starts with a certain prefix, e.g.
-     * "A000000004*" to register all MasterCard AIDs.
-     *
-     * Use this method to determine whether this device
-     * supports registering AID prefixes.
-     *
-     * @return whether AID prefix registering is supported on this device.
-     */
-    public boolean supportsAidPrefixRegistration() {
-        try {
-            return sService.supportsAidPrefixRegistration();
-        } catch (RemoteException e) {
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.supportsAidPrefixRegistration();
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
-    }
-
-    /**
-     * Retrieves the registered AIDs for the preferred payment service.
-     *
-     * @return The list of AIDs registered for this category, or null if it couldn't be found.
-     */
-    @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
-    @Nullable
-    public List<String> getAidsForPreferredPaymentService() {
-        try {
-            ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(
-                    mContext.getUser().getIdentifier());
-            return (serviceInfo != null ? serviceInfo.getAids() : null);
-        } catch (RemoteException e) {
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                throw e.rethrowFromSystemServer();
-            }
-            try {
-                ApduServiceInfo serviceInfo =
-                        sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
-                return (serviceInfo != null ? serviceInfo.getAids() : null);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                throw e.rethrowFromSystemServer();
-            }
-        }
-    }
-
-    /**
-     * Retrieves the route destination for the preferred payment service.
-     *
-     * @return The route destination secure element name of the preferred payment service.
-     *         HCE payment: "Host"
-     *         OffHost payment: 1. String with prefix SIM or prefix eSE string.
-     *                             Ref: GSMA TS.26 - NFC Handset Requirements
-     *                             TS26_NFC_REQ_069: For UICC, Secure Element Name SHALL be
-     *                                               SIM[smartcard slot]
-     *                                               (e.g. SIM/SIM1, SIM2… SIMn).
-     *                             TS26_NFC_REQ_070: For embedded SE, Secure Element Name SHALL be
-     *                                               eSE[number]
-     *                                               (e.g. eSE/eSE1, eSE2, etc.).
-     *                          2. "OffHost" if the payment service does not specify secure element
-     *                             name.
-     */
-    @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
-    @Nullable
-    public String getRouteDestinationForPreferredPaymentService() {
-        try {
-            ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(
-                    mContext.getUser().getIdentifier());
-            if (serviceInfo != null) {
-                if (!serviceInfo.isOnHost()) {
-                    return serviceInfo.getOffHostSecureElement() == null ?
-                            "OffHost" : serviceInfo.getOffHostSecureElement();
-                }
-                return "Host";
-            }
-            return null;
-        } catch (RemoteException e) {
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                throw e.rethrowFromSystemServer();
-            }
-            try {
-                ApduServiceInfo serviceInfo =
-                        sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
-                if (serviceInfo != null) {
-                    if (!serviceInfo.isOnHost()) {
-                        return serviceInfo.getOffHostSecureElement() == null ?
-                                "Offhost" : serviceInfo.getOffHostSecureElement();
-                    }
-                    return "Host";
-                }
-                return null;
-
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                throw e.rethrowFromSystemServer();
-            }
-        }
-    }
-
-    /**
-     * Returns a user-visible description of the preferred payment service.
-     *
-     * @return the preferred payment service description
-     */
-    @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
-    @Nullable
-    public CharSequence getDescriptionForPreferredPaymentService() {
-        try {
-            ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(
-                    mContext.getUser().getIdentifier());
-            return (serviceInfo != null ? serviceInfo.getDescription() : null);
-        } catch (RemoteException e) {
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                throw e.rethrowFromSystemServer();
-            }
-            try {
-                ApduServiceInfo serviceInfo =
-                        sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
-                return (serviceInfo != null ? serviceInfo.getDescription() : null);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                throw e.rethrowFromSystemServer();
-            }
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public boolean setDefaultServiceForCategory(ComponentName service, String category) {
-        try {
-            return sService.setDefaultServiceForCategory(mContext.getUser().getIdentifier(),
-                    service, category);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.setDefaultServiceForCategory(mContext.getUser().getIdentifier(),
-                        service, category);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public boolean setDefaultForNextTap(ComponentName service) {
-        try {
-            return sService.setDefaultForNextTap(mContext.getUser().getIdentifier(), service);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.setDefaultForNextTap(mContext.getUser().getIdentifier(), service);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public boolean setDefaultForNextTap(int userId, ComponentName service) {
-        try {
-            return sService.setDefaultForNextTap(userId, service);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.setDefaultForNextTap(userId, service);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public List<ApduServiceInfo> getServices(String category) {
-        try {
-            return sService.getServices(mContext.getUser().getIdentifier(), category);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return null;
-            }
-            try {
-                return sService.getServices(mContext.getUser().getIdentifier(), category);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return null;
-            }
-        }
-    }
-
-    /**
-     * Retrieves list of services registered of the provided category for the provided user.
-     *
-     * @param category Category string, one of {@link #CATEGORY_PAYMENT} or {@link #CATEGORY_OTHER}
-     * @param userId the user handle of the user whose information is being requested.
-     * @hide
-     */
-    @SystemApi
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
-    @NonNull
-    public List<ApduServiceInfo> getServices(@NonNull String category, @UserIdInt int userId) {
-        try {
-            return sService.getServices(userId, category);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return null;
-            }
-            try {
-                return sService.getServices(userId, category);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return null;
-            }
-        }
-    }
-
-    /**
-     * A valid AID according to ISO/IEC 7816-4:
-     * <ul>
-     * <li>Has >= 5 bytes and <=16 bytes (>=10 hex chars and <= 32 hex chars)
-     * <li>Consist of only hex characters
-     * <li>Additionally, we allow an asterisk at the end, to indicate
-     *     a prefix
-     * <li>Additinally we allow an (#) at symbol at the end, to indicate
-     *     a subset
-     * </ul>
-     *
-     * @hide
-     */
-    public static boolean isValidAid(String aid) {
-        if (aid == null)
-            return false;
-
-        // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
-        if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
-            Log.e(TAG, "AID " + aid + " is not a valid AID.");
-            return false;
-        }
-
-        // If not a prefix/subset AID, the total length must be even (even # of AID chars)
-        if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
-            Log.e(TAG, "AID " + aid + " is not a valid AID.");
-            return false;
-        }
-
-        // Verify hex characters
-        if (!AID_PATTERN.matcher(aid).matches()) {
-            Log.e(TAG, "AID " + aid + " is not a valid AID.");
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Allows to set or unset preferred service (category other) to avoid  AID Collision.
-     *
-     * @param service The ComponentName of the service
-     * @param status  true to enable, false to disable
-     * @return set service for the category and true if service is already set return false.
-     *
-     * @hide
-     */
-    public boolean setServiceEnabledForCategoryOther(ComponentName service, boolean status) {
-        if (service == null) {
-            throw new NullPointerException("activity or service or category is null");
-        }
-        int userId = mContext.getUser().getIdentifier();
-
-        try {
-            return sService.setServiceEnabledForCategoryOther(userId, service, status);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.setServiceEnabledForCategoryOther(userId, service, status);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
-    }
-
-     /**
-      * Setting NFC controller routing table, which includes Protocol Route and Technology Route,
-      * while this Activity is in the foreground.
-      *
-      * The parameter set to null can be used to keep current values for that entry.
-      * <p>
-      * Example usage in an Activity that requires to set proto route to "ESE" and keep tech route:
-      * <pre>
-      * protected void onResume() {
-      *     mNfcAdapter.overrideRoutingTable(this , "ESE" , null);
-      * }</pre>
-      * </p>
-      * Also activities must call this method when it goes to the background,
-      * with all parameters set to null.
-      * @param activity The Activity that requests NFC controller routing table to be changed.
-      * @param protocol ISO-DEP route destination, which can be "DH" or "UICC" or "ESE".
-      * @param technology Tech-A, Tech-B route destination, which can be "DH" or "UICC" or "ESE".
-      * @return true if operation is successful and false otherwise
-      *
-      * This is a high risk API and only included to support mainline effort
-      * @hide
-      */
-    public boolean overrideRoutingTable(Activity activity, String protocol, String technology) {
-        if (activity == null) {
-            throw new NullPointerException("activity or service or category is null");
-        }
-        if (!activity.isResumed()) {
-            throw new IllegalArgumentException("Activity must be resumed.");
-        }
-        try {
-            return sService.overrideRoutingTable(UserHandle.myUserId(), protocol, technology);
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.overrideRoutingTable(UserHandle.myUserId(), protocol, technology);
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
-    }
-
-    /**
-     * Restore the NFC controller routing table,
-     * which was changed by {@link #overrideRoutingTable(Activity, String, String)}
-     *
-     * @param activity The Activity that requested NFC controller routing table to be changed.
-     * @return true if operation is successful and false otherwise
-     *
-     * @hide
-     */
-    public boolean recoverRoutingTable(Activity activity) {
-        if (activity == null) {
-            throw new NullPointerException("activity is null");
-        }
-        if (!activity.isResumed()) {
-            throw new IllegalArgumentException("Activity must be resumed.");
-        }
-        try {
-            return sService.recoverRoutingTable(UserHandle.myUserId());
-        } catch (RemoteException e) {
-            // Try one more time
-            recoverService();
-            if (sService == null) {
-                Log.e(TAG, "Failed to recover CardEmulationService.");
-                return false;
-            }
-            try {
-                return sService.recoverRoutingTable(UserHandle.myUserId());
-            } catch (RemoteException ee) {
-                Log.e(TAG, "Failed to reach CardEmulationService.");
-                return false;
-            }
-        }
-    }
-
-    void recoverService() {
-        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
-        sService = adapter.getCardEmulationService();
-    }
-
-}
diff --git a/core/java/android/os/AggregateBatteryConsumer.java b/core/java/android/os/AggregateBatteryConsumer.java
index c5f5614..67e2195 100644
--- a/core/java/android/os/AggregateBatteryConsumer.java
+++ b/core/java/android/os/AggregateBatteryConsumer.java
@@ -33,6 +33,7 @@
  *
  * {@hide}
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class AggregateBatteryConsumer extends BatteryConsumer {
     static final int CONSUMER_TYPE_AGGREGATE = 0;
 
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 25fba60..b9bb059 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -17,9 +17,11 @@
 package android.os;
 
 import static android.os.Flags.FLAG_STATE_OF_HEALTH_PUBLIC;
+import static android.os.Flags.FLAG_BATTERY_PART_STATUS_API;
 
 import android.Manifest.permission;
 import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
@@ -236,6 +238,31 @@
     public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE =
                                             OsProtoEnums.CHARGING_POLICY_ADAPTIVE_LONGLIFE; // = 4
 
+    // values for "battery part status" property
+    /**
+     * Battery part status is not supported.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+    public static final int PART_STATUS_UNSUPPORTED = 0;
+
+    /**
+     * Battery is the original device battery.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+    public static final int PART_STATUS_ORIGINAL = 1;
+
+    /**
+     * Battery has been replaced.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+    public static final int PART_STATUS_REPLACED = 2;
+
     /** @hide */
     @SuppressLint("UnflaggedApi") // TestApi without associated feature.
     @TestApi
@@ -366,6 +393,32 @@
     @FlaggedApi(FLAG_STATE_OF_HEALTH_PUBLIC)
     public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10;
 
+    /**
+     * Battery part serial number.
+     *
+     * <p class="note">
+     * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+     *
+     * @hide
+     */
+    @RequiresPermission(permission.BATTERY_STATS)
+    @SystemApi
+    @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+    public static final int BATTERY_PROPERTY_SERIAL_NUMBER = 11;
+
+    /**
+     * Battery part status from a BATTERY_PART_STATUS_* value.
+     *
+     * <p class="note">
+     * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+     *
+     * @hide
+     */
+    @RequiresPermission(permission.BATTERY_STATS)
+    @SystemApi
+    @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+    public static final int BATTERY_PROPERTY_PART_STATUS = 12;
+
     private final Context mContext;
     private final IBatteryStats mBatteryStats;
     private final IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
@@ -431,6 +484,25 @@
     }
 
     /**
+     * Same as queryProperty, but for strings.
+     */
+    private String queryStringProperty(int id) {
+        if (mBatteryPropertiesRegistrar == null) {
+            return null;
+        }
+
+        try {
+            BatteryProperty prop = new BatteryProperty();
+            if (mBatteryPropertiesRegistrar.getProperty(id, prop) == 0) {
+                return prop.getString();
+            }
+            return null;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Return the value of a battery property of integer type.
      *
      * @param id identifier of the requested property
@@ -464,6 +536,21 @@
     }
 
     /**
+     * Return the value of a battery property of String type. If the
+     * platform does not provide the property queried, this value will
+     * be null.
+     *
+     * @param id identifier of the requested property.
+     *
+     * @return the property value, or null if not supported.
+     */
+    @Nullable
+    @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+    public String getStringProperty(int id) {
+        return queryStringProperty(id);
+    }
+
+    /**
      * Return true if the plugType given is wired
      * @param plugType {@link #BATTERY_PLUGGED_AC}, {@link #BATTERY_PLUGGED_USB},
      *        or {@link #BATTERY_PLUGGED_WIRELESS}
diff --git a/core/java/android/os/BatteryProperty.java b/core/java/android/os/BatteryProperty.java
index b40988a..464577f 100644
--- a/core/java/android/os/BatteryProperty.java
+++ b/core/java/android/os/BatteryProperty.java
@@ -28,12 +28,14 @@
  */
 public class BatteryProperty implements Parcelable {
     private long mValueLong;
+    private String mValueString;
 
     /**
      * @hide
      */
     public BatteryProperty() {
         mValueLong = Long.MIN_VALUE;
+        mValueString = null;
     }
 
     /**
@@ -46,14 +48,23 @@
     /**
      * @hide
      */
+    public String getString() {
+        return mValueString;
+    }
+
+    /**
+     * @hide
+     */
     public void setLong(long val) {
         mValueLong = val;
     }
 
-    /*
-     * Parcel read/write code must be kept in sync with
-     * frameworks/native/services/batteryservice/BatteryProperty.cpp
+    /**
+     * @hide
      */
+    public void setString(String val) {
+        mValueString = val;
+    }
 
     private BatteryProperty(Parcel p) {
         readFromParcel(p);
@@ -61,10 +72,12 @@
 
     public void readFromParcel(Parcel p) {
         mValueLong = p.readLong();
+        mValueString = p.readString8();
     }
 
     public void writeToParcel(Parcel p, int flags) {
         p.writeLong(mValueLong);
+        p.writeString8(mValueString);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<BatteryProperty> CREATOR
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 1a0ce70..b68b94d 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -37,6 +37,7 @@
 import android.service.batterystats.BatteryStatsServiceDumpHistoryProto;
 import android.service.batterystats.BatteryStatsServiceDumpProto;
 import android.telephony.CellSignalStrength;
+import android.telephony.ModemActivityInfo;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.text.format.DateFormat;
@@ -83,6 +84,7 @@
  * except where indicated otherwise.
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class BatteryStats {
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
@@ -463,6 +465,7 @@
     /**
      * State for keeping track of long counting information.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static abstract class LongCounter {
 
         /**
@@ -2724,12 +2727,6 @@
      */
     public abstract int getMobileRadioActiveUnknownCount(int which);
 
-    public static final int DATA_CONNECTION_OUT_OF_SERVICE = 0;
-    public static final int DATA_CONNECTION_EMERGENCY_SERVICE =
-            TelephonyManager.getAllNetworkTypes().length + 1;
-    public static final int DATA_CONNECTION_OTHER = DATA_CONNECTION_EMERGENCY_SERVICE + 1;
-
-
     static final String[] DATA_CONNECTION_NAMES = {
         "oos", "gprs", "edge", "umts", "cdma", "evdo_0", "evdo_A",
         "1xrtt", "hsdpa", "hsupa", "hspa", "iden", "evdo_b", "lte",
@@ -2737,9 +2734,28 @@
         "emngcy", "other"
     };
 
+    public static final int DATA_CONNECTION_OUT_OF_SERVICE = 0;
+    public static final int DATA_CONNECTION_EMERGENCY_SERVICE = getEmergencyNetworkConnectionType();
+    public static final int DATA_CONNECTION_OTHER = DATA_CONNECTION_EMERGENCY_SERVICE + 1;
+
     @UnsupportedAppUsage
     public static final int NUM_DATA_CONNECTION_TYPES = DATA_CONNECTION_OTHER + 1;
 
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static int getEmergencyNetworkConnectionType() {
+        int count = TelephonyManager.getAllNetworkTypes().length;
+        if (DATA_CONNECTION_NAMES.length != count + 3) {        // oos, emngcy, other
+            throw new IllegalStateException(
+                    "DATA_CONNECTION_NAMES length does not match network type count. "
+                    + "Expected: " + (count + 3) + ", actual:" + DATA_CONNECTION_NAMES.length);
+        }
+        return count + 1;
+    }
+
+    private static int getEmergencyNetworkConnectionType$ravenwood() {
+        return DATA_CONNECTION_NAMES.length - 2;
+    }
+
     /**
      * Returns the time in microseconds that the phone has been running with
      * the given data connection.
@@ -9015,4 +9031,44 @@
                 (lhs, rhs) -> Double.compare(rhs.millisecondsPerPacket, lhs.millisecondsPerPacket));
         return uidMobileRadioStats;
     }
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    @VisibleForTesting
+    protected static boolean isLowRamDevice() {
+        return ActivityManager.isLowRamDeviceStatic();
+    }
+
+    protected static boolean isLowRamDevice$ravenwood() {
+        return false;
+    }
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    @VisibleForTesting
+    protected static int getCellSignalStrengthLevelCount() {
+        return CellSignalStrength.getNumSignalStrengthLevels();
+    }
+
+    protected static int getCellSignalStrengthLevelCount$ravenwood() {
+        return 5;
+    }
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    @VisibleForTesting
+    protected static int getModemTxPowerLevelCount() {
+        return ModemActivityInfo.getNumTxPowerLevels();
+    }
+
+    protected static int getModemTxPowerLevelCount$ravenwood() {
+        return 5;
+    }
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    @VisibleForTesting
+    protected static boolean isKernelStatsAvailable() {
+        return true;
+    }
+
+    protected static boolean isKernelStatsAvailable$ravenwood() {
+        return false;
+    }
 }
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index 511f464..90d82e7 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -56,6 +56,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class BatteryUsageStats implements Parcelable, Closeable {
 
     /**
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 32840d4..203ef47 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -28,6 +28,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class BatteryUsageStatsQuery implements Parcelable {
 
     @NonNull
diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
index 8510084..f2ef185 100644
--- a/core/java/android/os/BugreportParams.java
+++ b/core/java/android/os/BugreportParams.java
@@ -21,6 +21,7 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.admin.flags.Flags;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -131,6 +132,7 @@
      */
     @TestApi
     @FlaggedApi(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
+    @UnsupportedAppUsage
     public static final int BUGREPORT_MODE_ONBOARDING = IDumpstate.BUGREPORT_MODE_ONBOARDING;
 
     /**
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 5871717..3977bdf 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -28,6 +28,7 @@
 import android.app.Application;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.sysprop.DeviceProperties;
 import android.sysprop.SocProperties;
 import android.sysprop.TelephonyProperties;
@@ -47,6 +48,7 @@
 /**
  * Information about the current build, extracted from system properties.
  */
+@RavenwoodKeepWholeClass
 public class Build {
     private static final String TAG = "Build";
 
@@ -307,7 +309,7 @@
          * compatibility.
          */
         final String[] abiList;
-        if (VMRuntime.getRuntime().is64Bit()) {
+        if (android.os.Process.is64Bit()) {
             abiList = SUPPORTED_64_BIT_ABIS;
         } else {
             abiList = SUPPORTED_32_BIT_ABIS;
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 3a32b2b..4dc32d5 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -25,6 +25,7 @@
 import static android.system.OsConstants.EINVAL;
 import static android.system.OsConstants.ENOSYS;
 import static android.system.OsConstants.F_OK;
+import static android.system.OsConstants.EIO;
 import static android.system.OsConstants.O_ACCMODE;
 import static android.system.OsConstants.O_APPEND;
 import static android.system.OsConstants.O_CREAT;
@@ -37,6 +38,7 @@
 import static android.system.OsConstants.SPLICE_F_MOVE;
 import static android.system.OsConstants.S_ISFIFO;
 import static android.system.OsConstants.S_ISREG;
+import static android.system.OsConstants.S_ISSOCK;
 import static android.system.OsConstants.W_OK;
 
 import android.annotation.NonNull;
@@ -474,6 +476,8 @@
                     }
                 } else if (S_ISFIFO(st_in.st_mode) || S_ISFIFO(st_out.st_mode)) {
                     return copyInternalSplice(in, out, count, signal, executor, listener);
+                } else if (S_ISSOCK(st_in.st_mode) || S_ISSOCK(st_out.st_mode)) {
+                    return copyInternalSpliceSocket(in, out, count, signal, executor, listener);
                 }
             } catch (ErrnoException e) {
                 throw e.rethrowAsIOException();
@@ -525,6 +529,86 @@
         }
         return progress;
     }
+    /**
+     * Requires one of input or output to be a socket file.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static long copyInternalSpliceSocket(FileDescriptor in, FileDescriptor out, long count,
+            CancellationSignal signal, Executor executor, ProgressListener listener)
+            throws ErrnoException {
+        long progress = 0;
+        long checkpoint = 0;
+        long countToRead = count;
+        long countInPipe = 0;
+        long t;
+
+        FileDescriptor[] pipes = Os.pipe();
+
+        while (countToRead > 0 || countInPipe > 0) {
+            if (countToRead > 0) {
+                t = Os.splice(in, null, pipes[1], null, Math.min(countToRead, COPY_CHECKPOINT_BYTES),
+                              SPLICE_F_MOVE | SPLICE_F_MORE);
+                if (t < 0) {
+                    // splice error
+                    Slog.e(TAG, "splice error, fdIn --> pipe, copy size:" + count +
+                           ", copied:" + progress +
+                           ", read:" + (count - countToRead) +
+                           ", in pipe:" + countInPipe);
+                    break;
+                } else if (t == 0) {
+                    // end of input, input count larger than real size
+                    Slog.w(TAG, "Reached the end of the input file. The size to be copied exceeds the actual size, copy size:" + count +
+                           ", copied:" + progress +
+                           ", read:" + (count - countToRead) +
+                           ", in pipe:" + countInPipe);
+                    countToRead = 0;
+                } else {
+                    countInPipe += t;
+                    countToRead -= t;
+                }
+            }
+
+            if (countInPipe > 0) {
+                t = Os.splice(pipes[0], null, out, null, Math.min(countInPipe, COPY_CHECKPOINT_BYTES),
+                              SPLICE_F_MOVE | SPLICE_F_MORE);
+                // The data is already in the pipeline, so the return value will not be zero.
+                // If it is 0, it means an error has occurred. So here use t<=0.
+                if (t <= 0) {
+                    Slog.e(TAG, "splice error, pipe --> fdOut, copy size:" + count +
+                           ", copied:" + progress +
+                           ", read:" + (count - countToRead) +
+                           ", in pipe: " + countInPipe);
+                    throw new ErrnoException("splice, pipe --> fdOut", EIO);
+                } else {
+                    progress += t;
+                    checkpoint += t;
+                    countInPipe -= t;
+                }
+            }
+
+            if (checkpoint >= COPY_CHECKPOINT_BYTES) {
+                if (signal != null) {
+                    signal.throwIfCanceled();
+                }
+                if (executor != null && listener != null) {
+                    final long progressSnapshot = progress;
+                    executor.execute(() -> {
+                        listener.onProgress(progressSnapshot);
+                    });
+                }
+                checkpoint = 0;
+            }
+        }
+        if (executor != null && listener != null) {
+            final long progressSnapshot = progress;
+            executor.execute(() -> {
+                listener.onProgress(progressSnapshot);
+            });
+        }
+        return progress;
+    }
 
     /**
      * Requires both input and output to be a regular file.
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index c60f949..fbec518 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -57,6 +57,7 @@
 
     @UnsupportedAppUsage
     Message mMessages;
+    private Message mLast;
     @UnsupportedAppUsage
     private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
     private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
@@ -66,6 +67,10 @@
     // Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
     private boolean mBlocked;
 
+    // Tracks the number of async message. We use this in enqueueMessage() to avoid searching the
+    // queue for async messages when inserting a message at the tail.
+    private int mAsyncMessageCount;
+
     // The next barrier token.
     // Barriers are indicated by messages with a null target whose arg1 field carries the token.
     @UnsupportedAppUsage
@@ -364,12 +369,21 @@
                         mBlocked = false;
                         if (prevMsg != null) {
                             prevMsg.next = msg.next;
+                            if (prevMsg.next == null) {
+                                mLast = prevMsg;
+                            }
                         } else {
                             mMessages = msg.next;
+                            if (msg.next == null) {
+                                mLast = null;
+                            }
                         }
                         msg.next = null;
                         if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                         msg.markInUse();
+                        if (msg.isAsynchronous()) {
+                            mAsyncMessageCount--;
+                        }
                         return msg;
                     }
                 } else {
@@ -492,6 +506,14 @@
             msg.when = when;
             msg.arg1 = token;
 
+            if (Flags.messageQueueTailTracking() && mLast != null && mLast.when <= when) {
+                /* Message goes to tail of list */
+                mLast.next = msg;
+                mLast = msg;
+                msg.next = null;
+                return token;
+            }
+
             Message prev = null;
             Message p = mMessages;
             if (when != 0) {
@@ -500,6 +522,12 @@
                     p = p.next;
                 }
             }
+
+            if (p == null) {
+                /* We reached the tail of the list, or list is empty. */
+                mLast = msg;
+            }
+
             if (prev != null) { // invariant: p == prev.next
                 msg.next = p;
                 prev.next = msg;
@@ -540,9 +568,15 @@
             final boolean needWake;
             if (prev != null) {
                 prev.next = p.next;
+                if (prev.next == null) {
+                    mLast = prev;
+                }
                 needWake = false;
             } else {
                 mMessages = p.next;
+                if (mMessages == null) {
+                    mLast = null;
+                }
                 needWake = mMessages == null || mMessages.target != null;
             }
             p.recycleUnchecked();
@@ -582,24 +616,77 @@
                 msg.next = p;
                 mMessages = msg;
                 needWake = mBlocked;
-            } else {
-                // Inserted within the middle of the queue.  Usually we don't have to wake
-                // up the event queue unless there is a barrier at the head of the queue
-                // and the message is the earliest asynchronous message in the queue.
-                needWake = mBlocked && p.target == null && msg.isAsynchronous();
-                Message prev;
-                for (;;) {
-                    prev = p;
-                    p = p.next;
-                    if (p == null || when < p.when) {
-                        break;
-                    }
-                    if (needWake && p.isAsynchronous()) {
-                        needWake = false;
-                    }
+                if (p == null) {
+                    mLast = mMessages;
                 }
-                msg.next = p; // invariant: p == prev.next
-                prev.next = msg;
+            } else {
+                // Message is to be inserted at tail or middle of queue. Usually we don't have to
+                // wake up the event queue unless there is a barrier at the head of the queue and
+                // the message is the earliest asynchronous message in the queue.
+                //
+                // For readability, we split this portion of the function into two blocks based on
+                // whether tail tracking is enabled. This has a minor implication for the case
+                // where tail tracking is disabled. See the comment below.
+                if (Flags.messageQueueTailTracking()) {
+                    needWake = mBlocked && p.target == null && msg.isAsynchronous()
+                        && mAsyncMessageCount == 0;
+                    if (when >= mLast.when) {
+                        msg.next = null;
+                        mLast.next = msg;
+                        mLast = msg;
+                    } else {
+                        // Inserted within the middle of the queue.
+                        Message prev;
+                        for (;;) {
+                            prev = p;
+                            p = p.next;
+                            if (p == null || when < p.when) {
+                                break;
+                            }
+                        }
+                        if (p == null) {
+                            /* Inserting at tail of queue */
+                            mLast = msg;
+                        }
+                        msg.next = p; // invariant: p == prev.next
+                        prev.next = msg;
+                    }
+                } else {
+                    needWake = mBlocked && p.target == null && msg.isAsynchronous();
+                    Message prev;
+                    for (;;) {
+                        prev = p;
+                        p = p.next;
+                        if (p == null || when < p.when) {
+                            break;
+                        }
+                        if (needWake && p.isAsynchronous()) {
+                            needWake = false;
+                        }
+                    }
+                    msg.next = p; // invariant: p == prev.next
+                    prev.next = msg;
+
+                    /*
+                     * If this block is executing then we have a build without tail tracking -
+                     * specifically: Flags.messageQueueTailTracking() == false. This is determined
+                     * at build time so the flag won't change on us during runtime.
+                     *
+                     * Since we don't want to pepper the code with extra checks, we only check
+                     * for tail tracking when we might use mLast. Otherwise, we continue to update
+                     * mLast as the tail of the list.
+                     *
+                     * In this case however we are not maintaining mLast correctly. Since we never
+                     * use it, this is fine. However, we run the risk of leaking a reference.
+                     * So set mLast to null in this case to avoid any Message leaks. The other
+                     * sites will never use the value so we are safe against null pointer derefs.
+                     */
+                    mLast = null;
+                }
+            }
+
+            if (msg.isAsynchronous()) {
+                mAsyncMessageCount++;
             }
 
             // We can assume mPtr != 0 because mQuitting is false.
@@ -692,10 +779,17 @@
                    && (object == null || p.obj == object)) {
                 Message n = p.next;
                 mMessages = n;
+                if (p.isAsynchronous()) {
+                    mAsyncMessageCount--;
+                }
                 p.recycleUnchecked();
                 p = n;
             }
 
+            if (p == null) {
+                mLast = mMessages;
+            }
+
             // Remove all messages after front.
             while (p != null) {
                 Message n = p.next;
@@ -703,8 +797,14 @@
                     if (n.target == h && n.what == what
                         && (object == null || n.obj == object)) {
                         Message nn = n.next;
+                        if (n.isAsynchronous()) {
+                            mAsyncMessageCount--;
+                        }
                         n.recycleUnchecked();
                         p.next = nn;
+                        if (p.next == null) {
+                            mLast = p;
+                        }
                         continue;
                     }
                 }
@@ -726,10 +826,17 @@
                    && (object == null || object.equals(p.obj))) {
                 Message n = p.next;
                 mMessages = n;
+                if (p.isAsynchronous()) {
+                    mAsyncMessageCount--;
+                }
                 p.recycleUnchecked();
                 p = n;
             }
 
+            if (p == null) {
+                mLast = mMessages;
+            }
+
             // Remove all messages after front.
             while (p != null) {
                 Message n = p.next;
@@ -737,8 +844,14 @@
                     if (n.target == h && n.what == what
                         && (object == null || object.equals(n.obj))) {
                         Message nn = n.next;
+                        if (n.isAsynchronous()) {
+                            mAsyncMessageCount--;
+                        }
                         n.recycleUnchecked();
                         p.next = nn;
+                        if (p.next == null) {
+                            mLast = p;
+                        }
                         continue;
                     }
                 }
@@ -760,10 +873,17 @@
                    && (object == null || p.obj == object)) {
                 Message n = p.next;
                 mMessages = n;
+                if (p.isAsynchronous()) {
+                    mAsyncMessageCount--;
+                }
                 p.recycleUnchecked();
                 p = n;
             }
 
+            if (p == null) {
+                mLast = mMessages;
+            }
+
             // Remove all messages after front.
             while (p != null) {
                 Message n = p.next;
@@ -771,8 +891,14 @@
                     if (n.target == h && n.callback == r
                         && (object == null || n.obj == object)) {
                         Message nn = n.next;
+                        if (n.isAsynchronous()) {
+                            mAsyncMessageCount--;
+                        }
                         n.recycleUnchecked();
                         p.next = nn;
+                        if (p.next == null) {
+                            mLast = p;
+                        }
                         continue;
                     }
                 }
@@ -794,10 +920,17 @@
                    && (object == null || object.equals(p.obj))) {
                 Message n = p.next;
                 mMessages = n;
+                if (p.isAsynchronous()) {
+                    mAsyncMessageCount--;
+                }
                 p.recycleUnchecked();
                 p = n;
             }
 
+            if (p == null) {
+                mLast = mMessages;
+            }
+
             // Remove all messages after front.
             while (p != null) {
                 Message n = p.next;
@@ -805,8 +938,14 @@
                     if (n.target == h && n.callback == r
                         && (object == null || object.equals(n.obj))) {
                         Message nn = n.next;
+                        if (n.isAsynchronous()) {
+                            mAsyncMessageCount--;
+                        }
                         n.recycleUnchecked();
                         p.next = nn;
+                        if (p.next == null) {
+                            mLast = p;
+                        }
                         continue;
                     }
                 }
@@ -829,18 +968,31 @@
                     && (object == null || p.obj == object)) {
                 Message n = p.next;
                 mMessages = n;
+                if (p.isAsynchronous()) {
+                    mAsyncMessageCount--;
+                }
                 p.recycleUnchecked();
                 p = n;
             }
 
+            if (p == null) {
+                mLast = mMessages;
+            }
+
             // Remove all messages after front.
             while (p != null) {
                 Message n = p.next;
                 if (n != null) {
                     if (n.target == h && (object == null || n.obj == object)) {
                         Message nn = n.next;
+                        if (n.isAsynchronous()) {
+                            mAsyncMessageCount--;
+                        }
                         n.recycleUnchecked();
                         p.next = nn;
+                        if (p.next == null) {
+                            mLast = p;
+                        }
                         continue;
                     }
                 }
@@ -862,18 +1014,31 @@
                     && (object == null || object.equals(p.obj))) {
                 Message n = p.next;
                 mMessages = n;
+                if (p.isAsynchronous()) {
+                    mAsyncMessageCount--;
+                }
                 p.recycleUnchecked();
                 p = n;
             }
 
+            if (p == null) {
+                mLast = mMessages;
+            }
+
             // Remove all messages after front.
             while (p != null) {
                 Message n = p.next;
                 if (n != null) {
                     if (n.target == h && (object == null || object.equals(n.obj))) {
                         Message nn = n.next;
+                        if (n.isAsynchronous()) {
+                            mAsyncMessageCount--;
+                        }
                         n.recycleUnchecked();
                         p.next = nn;
+                        if (p.next == null) {
+                            mLast = p;
+                        }
                         continue;
                     }
                 }
@@ -890,6 +1055,8 @@
             p = n;
         }
         mMessages = null;
+        mLast = null;
+        mAsyncMessageCount = 0;
     }
 
     private void removeAllFutureMessagesLocked() {
@@ -911,9 +1078,14 @@
                     p = n;
                 }
                 p.next = null;
+                mLast = p;
+
                 do {
                     p = n;
                     n = p.next;
+                    if (p.isAsynchronous()) {
+                        mAsyncMessageCount--;
+                    }
                     p.recycleUnchecked();
                 } while (n != null);
             }
diff --git a/core/java/android/os/PatternMatcher.java b/core/java/android/os/PatternMatcher.java
index b5425b4..79a2c59 100644
--- a/core/java/android/os/PatternMatcher.java
+++ b/core/java/android/os/PatternMatcher.java
@@ -16,9 +16,12 @@
 
 package android.os;
 
+import android.annotation.IntDef;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 
 /**
@@ -68,6 +71,17 @@
      */
     public static final int PATTERN_SUFFIX = 4;
 
+    /** @hide */
+    @IntDef(value = {
+            PATTERN_LITERAL,
+            PATTERN_PREFIX,
+            PATTERN_SIMPLE_GLOB,
+            PATTERN_ADVANCED_GLOB,
+            PATTERN_SUFFIX,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PatternType {}
+
     // token types for advanced matching
     private static final int TOKEN_TYPE_LITERAL = 0;
     private static final int TOKEN_TYPE_ANY = 1;
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index 37bde3d..e6bfcd7 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -183,13 +183,14 @@
 
         /** @hide */
         @Retention(RetentionPolicy.SOURCE)
-        @IntDef(prefix = {"CPU_LOAD_"}, value = {
+        @IntDef(prefix = {"CPU_LOAD_", "GPU_LOAD_"}, value = {
             CPU_LOAD_UP,
             CPU_LOAD_DOWN,
             CPU_LOAD_RESET,
             CPU_LOAD_RESUME,
             GPU_LOAD_UP,
-            GPU_LOAD_DOWN
+            GPU_LOAD_DOWN,
+            GPU_LOAD_RESET
         })
         public @interface Hint {}
 
@@ -204,7 +205,7 @@
         }
 
         /**
-         * Updates this session's target duration for each cycle of work.
+         * Updates this session's target total duration for each cycle of work.
          *
          * @param targetDurationNanos the new desired duration in nanoseconds
          */
diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java
index 02704f5..236194d 100644
--- a/core/java/android/os/PersistableBundle.java
+++ b/core/java/android/os/PersistableBundle.java
@@ -294,6 +294,43 @@
         XmlUtils.writeMapXml(mMap, out, this);
     }
 
+    /**
+     * Checks whether all keys and values are within the given character limit.
+     * Note: Maximum character limit of String that can be saved to XML as part of bundle is 65535.
+     * Otherwise IOException is thrown.
+     * @param limit length of String keys and values in the PersistableBundle, including nested
+     *                    PersistableBundles to check against.
+     *
+     * @hide
+     */
+    public boolean isBundleContentsWithinLengthLimit(int limit) {
+        unparcel();
+        if (mMap == null) {
+            return true;
+        }
+        for (int i = 0; i < mMap.size(); i++) {
+            if (mMap.keyAt(i) != null && mMap.keyAt(i).length() > limit) {
+                return false;
+            }
+            final Object value = mMap.valueAt(i);
+            if (value instanceof String && ((String) value).length() > limit) {
+                return false;
+            } else if (value instanceof String[]) {
+                String[] stringArray =  (String[]) value;
+                for (int j = 0; j < stringArray.length; j++) {
+                    if (stringArray[j] != null
+                            && stringArray[j].length() > limit) {
+                        return false;
+                    }
+                }
+            } else if (value instanceof PersistableBundle
+                    && !((PersistableBundle) value).isBundleContentsWithinLengthLimit(limit)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     /** @hide */
     static class MyReadMapCallback implements  XmlUtils.ReadMapCallback {
         @Override
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index 9c11ad4..164e265 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -40,6 +40,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 class PowerComponents {
     private final BatteryConsumer.BatteryConsumerData mData;
 
diff --git a/core/java/android/os/PowerMonitorReadings.java b/core/java/android/os/PowerMonitorReadings.java
index bb677d5..a0ab066 100644
--- a/core/java/android/os/PowerMonitorReadings.java
+++ b/core/java/android/os/PowerMonitorReadings.java
@@ -71,7 +71,7 @@
      */
     @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
     @ElapsedRealtimeLong
-    public long getTimestamp(@NonNull PowerMonitor powerMonitor) {
+    public long getTimestampMillis(@NonNull PowerMonitor powerMonitor) {
         int offset = Arrays.binarySearch(mPowerMonitors, powerMonitor, POWER_MONITOR_COMPARATOR);
         if (offset >= 0) {
             return mTimestampsMs[offset];
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index f952fcf..1f3a162 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -19,6 +19,7 @@
 import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
 
 import android.annotation.ElapsedRealtimeLong;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -37,6 +38,7 @@
 
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.Preconditions;
+import com.android.sdksandbox.flags.Flags;
 
 import dalvik.system.VMRuntime;
 
@@ -1016,11 +1018,30 @@
     @SystemApi(client = MODULE_LIBRARIES)
     @TestApi
     @android.ravenwood.annotation.RavenwoodKeep
+    // TODO(b/318651609): Deprecate once Process#getSdkSandboxUidForAppUid is rolled out to 100%
     public static final int toSdkSandboxUid(int uid) {
         return uid + (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID);
     }
 
     /**
+     * Returns the sdk sandbox uid corresponding to an app uid.
+     * @see android.app.sdksandbox.SdkSandboxManager
+     *
+     * @param uid the app uid
+     * @return the sdk sandbox uid for the given app uid
+     *
+     * @throws IllegalArgumentException if input is not an app uid
+     */
+    @FlaggedApi(Flags.FLAG_SDK_SANDBOX_UID_TO_APP_UID_API)
+    @android.ravenwood.annotation.RavenwoodKeep
+    public static final int getSdkSandboxUidForAppUid(int uid) {
+        if (!isApplicationUid(uid)) {
+            throw new IllegalArgumentException("Input UID is not an app UID");
+        }
+        return uid + (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID);
+    }
+
+    /**
      * Returns whether the current process is a sdk sandbox process.
      */
     @android.ravenwood.annotation.RavenwoodKeep
@@ -1568,7 +1589,15 @@
     @UnsupportedAppUsage
     public static final native long getPss(int pid);
 
-    /** @hide */
+    /**
+     * Gets the total Rss value for a given process, in bytes.
+     *
+     * @param pid the process to the Rss for
+     * @return an ordered array containing multiple values, they are:
+     *  [total_rss, file, anon, swap, shmem].
+     *  or NULL if the value cannot be determined
+     * @hide
+     */
     public static final native long[] getRss(int pid);
 
     /**
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index aa283a2..a818919 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -20,6 +20,8 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
+import android.ravenwood.annotation.RavenwoodNativeSubstitutionClass;
 import android.util.Log;
 import android.util.MutableInt;
 
@@ -36,6 +38,8 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Predicate;
 
 /**
  * Gives access to the system properties store.  The system properties
@@ -51,6 +55,8 @@
  * {@hide}
  */
 @SystemApi
+@RavenwoodKeepWholeClass
+@RavenwoodNativeSubstitutionClass("com.android.hoststubgen.nativesubstitution.SystemProperties_host")
 public class SystemProperties {
     private static final String TAG = "SystemProperties";
     private static final boolean TRACK_KEY_ACCESS = false;
@@ -94,6 +100,31 @@
         }
     }
 
+    /** @hide */
+    public static void init$ravenwood(Map<String, String> values,
+            Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate) {
+        native_init$ravenwood(values, keyReadablePredicate, keyWritablePredicate,
+                SystemProperties::callChangeCallbacks);
+        synchronized (sChangeCallbacks) {
+            sChangeCallbacks.clear();
+        }
+    }
+
+    /** @hide */
+    public static void reset$ravenwood() {
+        native_reset$ravenwood();
+        synchronized (sChangeCallbacks) {
+            sChangeCallbacks.clear();
+        }
+    }
+
+    // These native methods are currently only implemented by Ravenwood, as it's the only
+    // mechanism we have to jump to our RavenwoodNativeSubstitutionClass
+    private static native void native_init$ravenwood(Map<String, String> values,
+            Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate,
+            Runnable changeCallback);
+    private static native void native_reset$ravenwood();
+
     // The one-argument version of native_get used to be a regular native function. Nowadays,
     // we use the two-argument form of native_get all the time, but we can't just delete the
     // one-argument overload: apps use it via reflection, as the UnsupportedAppUsage annotation
diff --git a/core/java/android/os/TestLooperManager.java b/core/java/android/os/TestLooperManager.java
index 5e7549f..4b16c1d 100644
--- a/core/java/android/os/TestLooperManager.java
+++ b/core/java/android/os/TestLooperManager.java
@@ -28,6 +28,7 @@
  * The test code may use {@link #next()} to acquire messages that have been queued to this
  * {@link Looper}/{@link MessageQueue} and then {@link #execute} to run any that desires.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class TestLooperManager {
 
     private static final ArraySet<Looper> sHeldLoopers = new ArraySet<>();
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 5d7e04d..c0b4909 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -36,6 +36,7 @@
  * href="{@docRoot}tools/debugging/systrace.html">Analyzing Display and Performance
  * with Systrace</a>.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class Trace {
     /*
      * Writes trace events to the kernel trace buffer.  These trace events can be
@@ -123,10 +124,26 @@
 
     @UnsupportedAppUsage
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     private static native long nativeGetEnabledTags();
+    @android.ravenwood.annotation.RavenwoodReplace
     private static native void nativeSetAppTracingAllowed(boolean allowed);
+    @android.ravenwood.annotation.RavenwoodReplace
     private static native void nativeSetTracingEnabled(boolean allowed);
 
+    private static long nativeGetEnabledTags$ravenwood() {
+        // Tracing currently completely disabled under Ravenwood
+        return 0;
+    }
+
+    private static void nativeSetAppTracingAllowed$ravenwood(boolean allowed) {
+        // Tracing currently completely disabled under Ravenwood
+    }
+
+    private static void nativeSetTracingEnabled$ravenwood(boolean allowed) {
+        // Tracing currently completely disabled under Ravenwood
+    }
+
     @FastNative
     private static native void nativeTraceCounter(long tag, String name, long value);
     @FastNative
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index 3eea94e..53af838 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -37,6 +37,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class UidBatteryConsumer extends BatteryConsumer {
 
     static final int CONSUMER_TYPE_UID = 1;
@@ -223,6 +224,7 @@
     /**
      * Builder for UidBatteryConsumer.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static final class Builder extends BaseBuilder<Builder> {
         private static final String PACKAGE_NAME_UNINITIALIZED = "";
         private final BatteryStats.Uid mBatteryStatsUid;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index c280d13..d6df8d9 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -88,6 +88,7 @@
  * See {@link DevicePolicyManager#ACTION_PROVISION_MANAGED_PROFILE} for more on managed profiles.
  */
 @SystemService(Context.USER_SERVICE)
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
 public class UserManager {
 
     private static final String TAG = "UserManager";
@@ -106,6 +107,21 @@
     /** Whether the device is in headless system user mode; null until cached. */
     private static Boolean sIsHeadlessSystemUser = null;
 
+    /** Maximum length of username.
+     * @hide
+     */
+    public static final int MAX_USER_NAME_LENGTH = 100;
+
+    /** Maximum length of user property String value.
+     * @hide
+     */
+    public static final int MAX_ACCOUNT_STRING_LENGTH = 500;
+
+    /** Maximum length of account options String values.
+     * @hide
+     */
+    public static final int MAX_ACCOUNT_OPTIONS_LENGTH = 1000;
+
     /**
      * User type representing a {@link UserHandle#USER_SYSTEM system} user that is a human user.
      * This type of user cannot be created; it can only pre-exist on first boot.
@@ -1882,6 +1898,30 @@
             "no_near_field_communication_radio";
 
     /**
+     * This user restriction specifies if Thread network is disallowed on the device. If Thread
+     * network is disallowed it cannot be turned on via Settings.
+     *
+     * <p>This restriction can only be set by a device owner or a profile owner of an
+     * organization-owned managed profile on the parent profile.
+     * In both cases, the restriction applies globally on the device and will turn off the
+     * Thread network radio if it's currently on and prevent the radio from being turned
+     * on in the future.
+     *
+     * <p> <a href="https://www.threadgroup.org">Thread</a> is a low-power and low-latency wireless
+     * mesh networking protocol built on IPv6.
+     *
+     * <p>Default is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled")
+    public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
+
+    /**
      * List of key values that can be passed into the various user restriction related methods
      * in {@link UserManager} & {@link DevicePolicyManager}.
      * Note: This is slightly different from the real set of user restrictions listed in {@link
@@ -1967,6 +2007,7 @@
             DISALLOW_ULTRA_WIDEBAND_RADIO,
             DISALLOW_GRANT_ADMIN,
             DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
+            DISALLOW_THREAD_NETWORK,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface UserRestrictionKey {}
@@ -2906,6 +2947,7 @@
      * {@link UserManager#USER_TYPE_PROFILE_MANAGED managed profile}.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isUserTypeManagedProfile(@Nullable String userType) {
         return USER_TYPE_PROFILE_MANAGED.equals(userType);
     }
@@ -2914,6 +2956,7 @@
      * Returns whether the user type is a {@link UserManager#USER_TYPE_FULL_GUEST guest user}.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isUserTypeGuest(@Nullable String userType) {
         return USER_TYPE_FULL_GUEST.equals(userType);
     }
@@ -2923,6 +2966,7 @@
      * {@link UserManager#USER_TYPE_FULL_RESTRICTED restricted user}.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isUserTypeRestricted(@Nullable String userType) {
         return USER_TYPE_FULL_RESTRICTED.equals(userType);
     }
@@ -2931,6 +2975,7 @@
      * Returns whether the user type is a {@link UserManager#USER_TYPE_FULL_DEMO demo user}.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isUserTypeDemo(@Nullable String userType) {
         return USER_TYPE_FULL_DEMO.equals(userType);
     }
@@ -2939,6 +2984,7 @@
      * Returns whether the user type is a {@link UserManager#USER_TYPE_PROFILE_CLONE clone user}.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isUserTypeCloneProfile(@Nullable String userType) {
         return USER_TYPE_PROFILE_CLONE.equals(userType);
     }
@@ -2948,6 +2994,7 @@
      * {@link UserManager#USER_TYPE_PROFILE_COMMUNAL communal profile}.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isUserTypeCommunalProfile(@Nullable String userType) {
         return USER_TYPE_PROFILE_COMMUNAL.equals(userType);
     }
@@ -2958,6 +3005,7 @@
      *
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isUserTypePrivateProfile(@Nullable String userType) {
         return USER_TYPE_PROFILE_PRIVATE.equals(userType);
     }
@@ -4423,15 +4471,15 @@
      * This API should only be called if the current user is an {@link #isAdminUser() admin} user,
      * as otherwise the returned intent will not be able to create a user.
      *
-     * @param userName Optional name to assign to the user.
+     * @param userName Optional name to assign to the user. Character limit is 100.
      * @param accountName Optional account name that will be used by the setup wizard to initialize
-     *                    the user.
+     *                    the user. Character limit is 500.
      * @param accountType Optional account type for the account to be created. This is required
-     *                    if the account name is specified.
+     *                    if the account name is specified. Character limit is 500.
      * @param accountOptions Optional bundle of data to be passed in during account creation in the
      *                       new user via {@link AccountManager#addAccount(String, String, String[],
      *                       Bundle, android.app.Activity, android.accounts.AccountManagerCallback,
-     *                       Handler)}.
+     *                       Handler)}. Character limit is 1000.
      * @return An Intent that can be launched from an Activity.
      * @see #USER_CREATION_FAILED_NOT_PERMITTED
      * @see #USER_CREATION_FAILED_NO_MORE_USERS
diff --git a/core/java/android/os/VintfObject.java b/core/java/android/os/VintfObject.java
index 4fc5131..5056557 100644
--- a/core/java/android/os/VintfObject.java
+++ b/core/java/android/os/VintfObject.java
@@ -31,6 +31,10 @@
 
     private static final String LOG_TAG = "VintfObject";
 
+    static {
+        System.loadLibrary("vintf_jni");
+    }
+
     /**
      * Slurps all device information (both manifests and both matrices)
      * and report them.
diff --git a/core/java/android/os/VintfRuntimeInfo.java b/core/java/android/os/VintfRuntimeInfo.java
index f17039b..e729063 100644
--- a/core/java/android/os/VintfRuntimeInfo.java
+++ b/core/java/android/os/VintfRuntimeInfo.java
@@ -28,6 +28,10 @@
 
     private VintfRuntimeInfo() {}
 
+    static {
+        System.loadLibrary("vintf_jni");
+    }
+
     /**
      * @return /sys/fs/selinux/policyvers, via security_policyvers() native call
      *
diff --git a/core/java/android/os/WorkDuration.java b/core/java/android/os/WorkDuration.java
index 4fdc34f..2ebcd83 100644
--- a/core/java/android/os/WorkDuration.java
+++ b/core/java/android/os/WorkDuration.java
@@ -26,7 +26,7 @@
  * in each component, see
  * {@link PerformanceHintManager.Session#reportActualWorkDuration(WorkDuration)}.
  *
- * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+ * All timings should be in {@link SystemClock#uptimeNanos()} and measured in wall time.
  */
 @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
 public final class WorkDuration implements Parcelable {
@@ -50,17 +50,9 @@
 
     public WorkDuration() {}
 
-    public WorkDuration(long workPeriodStartTimestampNanos,
-                      long actualTotalDurationNanos,
-                      long actualCpuDurationNanos,
-                      long actualGpuDurationNanos) {
-        mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos;
-        mActualTotalDurationNanos = actualTotalDurationNanos;
-        mActualCpuDurationNanos = actualCpuDurationNanos;
-        mActualGpuDurationNanos = actualGpuDurationNanos;
-    }
-
     /**
+     * Constructor for testing.
+     *
      * @hide
      */
     public WorkDuration(long workPeriodStartTimestampNanos,
@@ -86,7 +78,7 @@
     /**
      * Sets the work period start timestamp in nanoseconds.
      *
-     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+     * All timings should be in {@link SystemClock#uptimeNanos()}.
      */
     public void setWorkPeriodStartTimestampNanos(long workPeriodStartTimestampNanos) {
         if (workPeriodStartTimestampNanos <= 0) {
@@ -99,7 +91,7 @@
     /**
      * Sets the actual total duration in nanoseconds.
      *
-     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+     * All timings should be in {@link SystemClock#uptimeNanos()}.
      */
     public void setActualTotalDurationNanos(long actualTotalDurationNanos) {
         if (actualTotalDurationNanos <= 0) {
@@ -111,7 +103,7 @@
     /**
      * Sets the actual CPU duration in nanoseconds.
      *
-     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+     * All timings should be in {@link SystemClock#uptimeNanos()}.
      */
     public void setActualCpuDurationNanos(long actualCpuDurationNanos) {
         if (actualCpuDurationNanos <= 0) {
@@ -123,7 +115,7 @@
     /**
      * Sets the actual GPU duration in nanoseconds.
      *
-     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+     * All timings should be in {@link SystemClock#uptimeNanos()}.
      */
     public void setActualGpuDurationNanos(long actualGpuDurationNanos) {
         if (actualGpuDurationNanos < 0) {
@@ -135,7 +127,7 @@
     /**
      * Returns the work period start timestamp based in nanoseconds.
      *
-     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+     * All timings should be in {@link SystemClock#uptimeNanos()}.
      */
     public long getWorkPeriodStartTimestampNanos() {
         return mWorkPeriodStartTimestampNanos;
@@ -144,7 +136,7 @@
     /**
      * Returns the actual total duration in nanoseconds.
      *
-     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+     * All timings should be in {@link SystemClock#uptimeNanos()}.
      */
     public long getActualTotalDurationNanos() {
         return mActualTotalDurationNanos;
@@ -153,7 +145,7 @@
     /**
      * Returns the actual CPU duration in nanoseconds.
      *
-     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+     * All timings should be in {@link SystemClock#uptimeNanos()}.
      */
     public long getActualCpuDurationNanos() {
         return mActualCpuDurationNanos;
@@ -162,7 +154,7 @@
     /**
      * Returns the actual GPU duration in nanoseconds.
      *
-     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+     * All timings should be in {@link SystemClock#uptimeNanos()}.
      */
     public long getActualGpuDurationNanos() {
         return mActualGpuDurationNanos;
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index c14810b..f3496e7 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -425,6 +425,8 @@
                 throw new ZygoteStartFailedEx("Embedded newlines not allowed");
             } else if (arg.indexOf('\r') >= 0) {
                 throw new ZygoteStartFailedEx("Embedded carriage returns not allowed");
+            } else if (arg.indexOf('\u0000') >= 0) {
+                throw new ZygoteStartFailedEx("Embedded nulls not allowed");
             }
         }
 
@@ -965,6 +967,14 @@
             return true;
         }
 
+        for (/* NonNull */ String s : mApiDenylistExemptions) {
+            // indexOf() is intrinsified and faster than contains().
+            if (s.indexOf('\n') >= 0 || s.indexOf('\r') >= 0 || s.indexOf('\u0000') >= 0) {
+                Slog.e(LOG_TAG, "Failed to set API denylist exemptions: Bad character");
+                mApiDenylistExemptions = Collections.emptyList();
+                return false;
+            }
+        }
         try {
             state.mZygoteOutputWriter.write(Integer.toString(mApiDenylistExemptions.size() + 1));
             state.mZygoteOutputWriter.newLine();
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index d646146..82518bf 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -98,3 +98,19 @@
     description: "Guards StrictMode APIs for detecting restricted network access."
     bug: "317250784"
 }
+
+flag {
+    name: "message_queue_tail_tracking"
+    namespace: "system_performance"
+    description: "track tail of message queue."
+    bug: "305311707"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "battery_part_status_api"
+    namespace: "phoenix"
+    description: "Feature flag for adding Health HAL v3 APIs."
+    is_fixed_read_only: true
+    bug: "309792384"
+}
diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java
index 2d53341..322a8e6 100644
--- a/core/java/android/os/health/SystemHealthManager.java
+++ b/core/java/android/os/health/SystemHealthManager.java
@@ -25,8 +25,8 @@
 import android.os.BatteryStats;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.IPowerStatsService;
+import android.os.OutcomeReceiver;
 import android.os.PowerMonitor;
 import android.os.PowerMonitorReadings;
 import android.os.Process;
@@ -39,6 +39,7 @@
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.List;
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
@@ -161,12 +162,12 @@
      * (on-device power rail monitor) rails and modeled energy consumers.  If ODPM is unsupported
      * on this device this method delivers an empty list.
      *
-     * @param handler  optional Handler to deliver the callback. If not supplied, the callback
+     * @param executor optional Handler to deliver the callback. If not supplied, the callback
      *                 may be invoked on an arbitrary thread.
      * @param onResult callback for the result
      */
     @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
-    public void getSupportedPowerMonitors(@Nullable Handler handler,
+    public void getSupportedPowerMonitors(@Nullable Executor executor,
             @NonNull Consumer<List<PowerMonitor>> onResult) {
         final List<PowerMonitor> result;
         synchronized (mPowerMonitorsLock) {
@@ -180,15 +181,15 @@
             }
         }
         if (result != null) {
-            if (handler != null) {
-                handler.post(() -> onResult.accept(result));
+            if (executor != null) {
+                executor.execute(() -> onResult.accept(result));
             } else {
                 onResult.accept(result);
             }
             return;
         }
         try {
-            mPowerStats.getSupportedPowerMonitors(new ResultReceiver(handler) {
+            mPowerStats.getSupportedPowerMonitors(new ResultReceiver(null) {
                 @Override
                 protected void onReceiveResult(int resultCode, Bundle resultData) {
                     PowerMonitor[] array = resultData.getParcelableArray(
@@ -197,7 +198,11 @@
                     synchronized (mPowerMonitorsLock) {
                         mPowerMonitorsInfo = result;
                     }
-                    onResult.accept(result);
+                    if (executor != null) {
+                        executor.execute(()-> onResult.accept(result));
+                    } else {
+                        onResult.accept(result);
+                    }
                 }
             });
         } catch (RemoteException e) {
@@ -213,17 +218,22 @@
      * monitors.
      *
      * @param powerMonitors power monitors to be retrieved.
-     * @param handler       optional Handler to deliver the callbacks. If not supplied, the callback
-     *                      may be invoked on an arbitrary thread.
-     * @param onSuccess     callback for the result
-     * @param onError       callback invoked in case of an error
+     * @param executor      optional Executor to deliver the callbacks. If not supplied, the
+     *                      callback may be invoked on an arbitrary thread.
+     * @param onResult      callback for the result
      */
     @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
     public void getPowerMonitorReadings(@NonNull List<PowerMonitor> powerMonitors,
-            @Nullable Handler handler, @NonNull Consumer<PowerMonitorReadings> onSuccess,
-            @NonNull Consumer<RuntimeException> onError) {
+            @Nullable Executor executor,
+            @NonNull OutcomeReceiver<PowerMonitorReadings, RuntimeException> onResult) {
         if (mPowerStats == null) {
-            onError.accept(new IllegalArgumentException("Unsupported power monitor"));
+            IllegalArgumentException error =
+                    new IllegalArgumentException("Unsupported power monitor");
+            if (executor != null) {
+                executor.execute(() -> onResult.onError(error));
+            } else {
+                onResult.onError(error);
+            }
             return;
         }
 
@@ -235,18 +245,31 @@
             indices[i] = powerMonitorsArray[i].index;
         }
         try {
-            mPowerStats.getPowerMonitorReadings(indices, new ResultReceiver(handler) {
+            mPowerStats.getPowerMonitorReadings(indices, new ResultReceiver(null) {
                 @Override
                 protected void onReceiveResult(int resultCode, Bundle resultData) {
                     if (resultCode == IPowerStatsService.RESULT_SUCCESS) {
-                        onSuccess.accept(new PowerMonitorReadings(powerMonitorsArray,
+                        PowerMonitorReadings result = new PowerMonitorReadings(powerMonitorsArray,
                                 resultData.getLongArray(IPowerStatsService.KEY_ENERGY),
-                                resultData.getLongArray(IPowerStatsService.KEY_TIMESTAMPS)));
-                    } else if (resultCode == IPowerStatsService.RESULT_UNSUPPORTED_POWER_MONITOR) {
-                        onError.accept(new IllegalArgumentException("Unsupported power monitor"));
+                                resultData.getLongArray(IPowerStatsService.KEY_TIMESTAMPS));
+                        if (executor != null) {
+                            executor.execute(() -> onResult.onResult(result));
+                        } else {
+                            onResult.onResult(result);
+                        }
                     } else {
-                        onError.accept(new IllegalStateException(
-                                "Unrecognized result code " + resultCode));
+                        RuntimeException error;
+                        if (resultCode == IPowerStatsService.RESULT_UNSUPPORTED_POWER_MONITOR) {
+                            error = new IllegalArgumentException("Unsupported power monitor");
+                        } else {
+                            error = new IllegalStateException(
+                                    "Unrecognized result code " + resultCode);
+                        }
+                        if (executor != null) {
+                            executor.execute(() -> onResult.onError(error));
+                        } else {
+                            onResult.onError(error);
+                        }
                     }
                 }
             });
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 3ecf74e..54ed73c 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -134,16 +134,16 @@
     @EnforcePermission("MOUNT_UNMOUNT_FILESYSTEMS")
     void setDebugFlags(int flags, int mask) = 60;
     @EnforcePermission("STORAGE_INTERNAL")
-    void createUserStorageKeys(int userId, int serialNumber, boolean ephemeral) = 61;
+    void createUserStorageKeys(int userId, boolean ephemeral) = 61;
     @EnforcePermission("STORAGE_INTERNAL")
     void destroyUserStorageKeys(int userId) = 62;
     @EnforcePermission("STORAGE_INTERNAL")
-    void unlockCeStorage(int userId, int serialNumber, in byte[] secret) = 63;
+    void unlockCeStorage(int userId, in byte[] secret) = 63;
     @EnforcePermission("STORAGE_INTERNAL")
     void lockCeStorage(int userId) = 64;
     boolean isCeStorageUnlocked(int userId) = 65;
     @EnforcePermission("STORAGE_INTERNAL")
-    void prepareUserStorage(in String volumeUuid, int userId, int serialNumber, int flags) = 66;
+    void prepareUserStorage(in String volumeUuid, int userId, int flags) = 66;
     @EnforcePermission("STORAGE_INTERNAL")
     void destroyUserStorage(in String volumeUuid, int userId, int flags) = 67;
     @EnforcePermission("STORAGE_INTERNAL")
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 78a12f7..9587db1 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1602,14 +1602,13 @@
      * This is only intended to be called by UserManagerService, as part of creating a user.
      *
      * @param userId ID of the user
-     * @param serialNumber serial number of the user
      * @param ephemeral whether the user is ephemeral
      * @throws RuntimeException on error.  The user's keys already existing is considered an error.
      * @hide
      */
-    public void createUserStorageKeys(int userId, int serialNumber, boolean ephemeral) {
+    public void createUserStorageKeys(int userId, boolean ephemeral) {
         try {
-            mStorageManager.createUserStorageKeys(userId, serialNumber, ephemeral);
+            mStorageManager.createUserStorageKeys(userId, ephemeral);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1653,9 +1652,9 @@
     }
 
     /** {@hide} */
-    public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) {
+    public void prepareUserStorage(String volumeUuid, int userId, int flags) {
         try {
-            mStorageManager.prepareUserStorage(volumeUuid, userId, serialNumber, flags);
+            mStorageManager.prepareUserStorage(volumeUuid, userId, flags);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index 8961846..6995ea8 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -193,7 +193,7 @@
      * @see com.android.server.pm.Installer#createFsveritySetupAuthToken()
      */
     public abstract IInstalld.IFsveritySetupAuthToken createFsveritySetupAuthToken(
-            ParcelFileDescriptor authFd, int appUid, @UserIdInt int userId) throws IOException;
+            ParcelFileDescriptor authFd, int uid) throws IOException;
 
     /**
      * A proxy call to the corresponding method in Installer.
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 7cecfdc..471f95b 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -92,7 +92,7 @@
 
     boolean isAutoRevokeExempted(String packageName, int userId);
 
-    void registerAttributionSource(in AttributionSourceState source);
+    IBinder registerAttributionSource(in AttributionSourceState source);
 
     boolean isRegisteredAttributionSource(in AttributionSourceState source);
 
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 91adc37..4af6e3a 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -23,6 +23,7 @@
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
 import static android.os.Build.VERSION_CODES.S;
+import static android.permission.flags.Flags.serverSideAttributionRegistration;
 
 import android.Manifest;
 import android.annotation.CheckResult;
@@ -59,6 +60,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
@@ -1464,13 +1466,19 @@
         // We use a shared static token for sources that are not registered since the token's
         // only used for process death detection. If we are about to use the source for security
         // enforcement we need to replace the binder with a unique one.
-        final AttributionSource registeredSource = source.withToken(new Binder());
         try {
-            mPermissionManager.registerAttributionSource(registeredSource.asState());
+            if (serverSideAttributionRegistration()) {
+                IBinder newToken = mPermissionManager.registerAttributionSource(source.asState());
+                return source.withToken(newToken);
+            } else {
+                AttributionSource registeredSource = source.withToken(new Binder());
+                mPermissionManager.registerAttributionSource(registeredSource.asState());
+                return registeredSource;
+            }
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
-        return registeredSource;
+        return source;
     }
 
     /**
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index fae7cb3..8c70501 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -45,7 +45,8 @@
 }
 
 flag {
-    name: "enhanced_confirmation_mode_apis"
+    name: "enhanced_confirmation_mode_apis_enabled"
+    is_fixed_read_only: true
     namespace: "permissions"
     description: "enable enhanced confirmation mode apis"
     bug: "310220212"
@@ -71,3 +72,25 @@
     description: "default retail demo role holder"
     bug: "274132354"
 }
+
+flag {
+    name: "server_side_attribution_registration"
+    namespace: "permissions"
+    description: "controls whether the binder representing an AttributionSource is created in the system server, or client process"
+    bug: "310953959"
+}
+
+flag {
+    name: "wallet_role_enabled"
+    namespace: "wallet_integration"
+    description: "This flag is used to enabled the Wallet Role for all users on the device"
+    bug: "283989236"
+}
+
+flag {
+  name: "signature_permission_allowlist_enabled"
+  is_fixed_read_only: true
+  namespace: "permissions"
+  description: "Enable signature permission allowlist"
+  bug: "308573169"
+}
diff --git a/core/java/android/provider/ContactKeysManager.java b/core/java/android/provider/ContactKeysManager.java
new file mode 100644
index 0000000..ecde699
--- /dev/null
+++ b/core/java/android/provider/ContactKeysManager.java
@@ -0,0 +1,1129 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * ContactKeysManager provides the access to the E2EE contact keys provider.
+ * It manages two types of keys - {@link ContactKey} of other users' and the owner's keys -
+ * {@link SelfKey}.
+ * <ul>
+ * <li>
+ * For {@link ContactKey} this API allows the insert/update, removal, changing of the
+ * verification state, retrieving the keys (either created by or visible to the caller app)
+ * operations.
+ * </li>
+ * <li>
+ * For {@link SelfKey} this API allows the insert/update, removal, retrieving the self keys
+ * (either created by or visible to the caller app) operations.
+ * </li>
+ * </ul>
+ * Keys are uniquely identified by:
+ * <ul>
+ * <li>
+ * ownerPackageName - package name of an app that the key belongs to.
+ * </li>
+ * <li>
+ * deviceId - an app-specified identifier for the device, defined by the app. Within that app,
+ * the deviceID should be unique among devices belonging to that user.
+ * </li>
+ * <li>
+ * accountId - the app-specified identifier for the account for which the contact key can be used.
+ * Usually a phone number.
+ * </li>
+ * </ul>
+ * Contact keys also use lookupKey which is an opaque value used to identify a contact in
+ * ContactsProvider.
+ */
+@FlaggedApi(Flags.FLAG_USER_KEYS)
+public class ContactKeysManager {
+    /**
+     * The authority for the contact keys provider.
+     * @hide
+     */
+    public static final String AUTHORITY = "com.android.contactkeys.contactkeysprovider";
+
+    /**
+     * A content:// style uri to the authority for the contact keys provider.
+     * @hide
+     */
+    @NonNull
+    public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
+
+    /**
+     * Maximum size of a contact key.
+     */
+    private static final int MAX_KEY_SIZE_BYTES = 5000;
+
+    /**
+     * Special value to distinguish a null array in a parcelable object.
+     */
+    private static final int ARRAY_IS_NULL = -1;
+
+    @NonNull
+    private final ContentResolver mContentResolver;
+
+    /** @hide */
+    public ContactKeysManager(@NonNull Context context) {
+        Objects.requireNonNull(context);
+        mContentResolver = context.getContentResolver();
+    }
+
+    /**
+     * Inserts a new entry into the contact keys table or updates one if it already exists.
+     * The inserted/updated contact key is owned by the caller app.
+     *
+     * @param lookupKey value that references the contact
+     * @param deviceId an app-specified identifier for the device
+     * @param accountId an app-specified identifier for the account
+     * @param keyValue the raw bytes for the key (max size is {@link #getMaxKeySizeBytes} bytes)
+     */
+    @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS)
+    public void updateOrInsertContactKey(@NonNull String lookupKey,
+            @NonNull String deviceId,
+            @NonNull String accountId,
+            @NonNull byte[] keyValue) {
+        validateKeyLength(keyValue);
+
+        Bundle extras = new Bundle();
+        extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putByteArray(ContactKeys.KEY_VALUE, Objects.requireNonNull(keyValue));
+
+        nullSafeCall(mContentResolver,
+                ContactKeys.UPDATE_OR_INSERT_CONTACT_KEY_METHOD, extras);
+    }
+
+    /**
+     * Retrieves a contact key entry given the lookup key, device ID, accountId and inferred
+     * caller package name.
+     *
+     * @param lookupKey the value that references the contact
+     * @param deviceId an app-specified identifier for the device
+     * @param accountId an app-specified identifier for the account
+     *
+     * @return a {@link ContactKey} object containing the contact key information,
+     * or null if no contact key is found.
+     */
+    @RequiresPermission(android.Manifest.permission.READ_CONTACTS)
+    @Nullable
+    public ContactKey getContactKey(
+            @NonNull String lookupKey,
+            @NonNull String deviceId,
+            @NonNull String accountId) {
+        Bundle extras = new Bundle();
+        extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+
+        Bundle response = nullSafeCall(mContentResolver,
+                ContactKeys.GET_CONTACT_KEY_METHOD, extras);
+
+        if (response == null) {
+            return null;
+        }
+        return response.getParcelable(ContactKeys.KEY_CONTACT_KEY, ContactKey.class);
+    }
+
+    /**
+     * Retrieves all contact key entries that belong to apps visible to the caller.
+     * The keys will be stripped of deviceId, timeUpdated and keyValue data.
+     *
+     * @param lookupKey the value that references the contact
+     *
+     * @return a list of {@link ContactKey} objects containing the contact key
+     * information, or an empty list if no keys are found.
+     */
+    @RequiresPermission(android.Manifest.permission.READ_CONTACTS)
+    @NonNull
+    public List<ContactKey> getAllContactKeys(@NonNull String lookupKey) {
+        Bundle extras = new Bundle();
+        extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+
+        Bundle response = nullSafeCall(mContentResolver,
+                ContactKeys.GET_ALL_CONTACT_KEYS_METHOD, extras);
+
+        if (response == null) {
+            return new ArrayList<>();
+        }
+        List<ContactKey> value = response.getParcelableArrayList(ContactKeys.KEY_CONTACT_KEYS,
+                ContactKey.class);
+        if (value == null) {
+            return new ArrayList<>();
+        }
+        return value;
+    }
+
+    /**
+     * Retrieves all contact key entries for a given lookupKey that belong to the caller app.
+     *
+     * @param lookupKey the value that references the contact
+     *
+     * @return a list of {@link ContactKey} objects containing the contact key
+     * information, or an empty list if no keys are found.
+     */
+    @RequiresPermission(android.Manifest.permission.READ_CONTACTS)
+    @NonNull
+    public List<ContactKey> getOwnerContactKeys(@NonNull String lookupKey) {
+        Bundle extras = new Bundle();
+        extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+
+        Bundle response = nullSafeCall(mContentResolver,
+                ContactKeys.GET_OWNER_CONTACT_KEYS_METHOD, extras);
+
+        if (response == null) {
+            return new ArrayList<>();
+        }
+        List<ContactKey> value = response.getParcelableArrayList(ContactKeys.KEY_CONTACT_KEYS,
+                ContactKey.class);
+        if (value == null) {
+            return new ArrayList<>();
+        }
+        return value;
+    }
+
+    /**
+     * Updates a contact key entry's local verification state that belongs to the caller app.
+     *
+     * @param lookupKey the value that references the contact
+     * @param deviceId an app-specified identifier for the device
+     * @param accountId an app-specified identifier for the account
+     * @param localVerificationState the new local verification state
+     *
+     * @return true if the entry was updated, false otherwise.
+     */
+    @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS)
+    public boolean updateContactKeyLocalVerificationState(@NonNull String lookupKey,
+            @NonNull String deviceId,
+            @NonNull String accountId,
+            @VerificationState int localVerificationState) {
+        validateVerificationState(localVerificationState);
+
+        Bundle extras = new Bundle();
+        extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putInt(ContactKeys.LOCAL_VERIFICATION_STATE, localVerificationState);
+
+        Bundle response = nullSafeCall(mContentResolver,
+                ContactKeys.UPDATE_CONTACT_KEY_LOCAL_VERIFICATION_STATE_METHOD, extras);
+
+        return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+    }
+
+    /**
+     * Updates a contact key entry's remote verification state that belongs to the caller app.
+     *
+     * @param lookupKey the value that references the contact
+     * @param deviceId an app-specified identifier for the device
+     * @param accountId an app-specified identifier for the account
+     * @param remoteVerificationState the new remote verification state
+     *
+     * @return true if the entry was updated, false otherwise.
+     */
+    @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS)
+    public boolean updateContactKeyRemoteVerificationState(@NonNull String lookupKey,
+            @NonNull String deviceId,
+            @NonNull String accountId,
+            @VerificationState int remoteVerificationState) {
+        validateVerificationState(remoteVerificationState);
+
+        Bundle extras = new Bundle();
+        extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+
+        Bundle response = nullSafeCall(mContentResolver,
+                ContactKeys.UPDATE_CONTACT_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+
+        return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+    }
+
+    private static void validateVerificationState(int verificationState) {
+        if (verificationState != UNVERIFIED
+                && verificationState != VERIFICATION_FAILED
+                && verificationState != VERIFIED) {
+            throw new IllegalArgumentException("Verification state value "
+                    + verificationState + " is not supported");
+        }
+    }
+
+    /**
+     * Removes a contact key entry that belongs to the caller app.
+     *
+     * @param lookupKey the value that references the contact
+     * @param deviceId an app-specified identifier for the device
+     * @param accountId an app-specified identifier for the account
+     *
+     * @return true if the entry was removed, false otherwise.
+     */
+    @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS)
+    public boolean removeContactKey(@NonNull String lookupKey,
+            @NonNull String deviceId,
+            @NonNull String accountId) {
+        Bundle extras = new Bundle();
+        extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+
+        Bundle response = nullSafeCall(mContentResolver,
+                ContactKeys.REMOVE_CONTACT_KEY_METHOD, extras);
+
+        return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+    }
+
+    /**
+     * Inserts a new entry into the self keys table or updates one if it already exists.
+
+     * @param deviceId an app-specified identifier for the device
+     * @param accountId an app-specified identifier for the account
+     * @param keyValue the raw bytes for the key (max size is {@link #getMaxKeySizeBytes} bytes)
+     *
+     * @return true if the entry was added or updated, false otherwise.
+     */
+    @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS)
+    public boolean updateOrInsertSelfKey(@NonNull String deviceId,
+            @NonNull String accountId,
+            @NonNull byte[] keyValue) {
+        validateKeyLength(keyValue);
+
+        Bundle extras = new Bundle();
+        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putByteArray(ContactKeys.KEY_VALUE, Objects.requireNonNull(keyValue));
+
+        Bundle response = nullSafeCall(mContentResolver,
+                ContactKeys.UPDATE_OR_INSERT_SELF_KEY_METHOD, extras);
+
+        return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+    }
+
+    private static void validateKeyLength(byte[] keyValue) {
+        Objects.requireNonNull(keyValue);
+        if (keyValue.length == 0 || keyValue.length > getMaxKeySizeBytes()) {
+            throw new IllegalArgumentException("Key value length is " + keyValue.length + "."
+                    + " Should be more than 0 and less than " + getMaxKeySizeBytes());
+        }
+    }
+
+    /**
+     * Updates a self key entry's remote verification state.
+     *
+     * @param deviceId an app-specified identifier for the device
+     * @param accountId an app-specified identifier for the account
+     * @param remoteVerificationState the new remote verification state
+     *
+     * @return true if the entry was updated, false otherwise.
+     */
+    @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS)
+    public boolean updateSelfKeyRemoteVerificationState(@NonNull String deviceId,
+            @NonNull String accountId,
+            @VerificationState int remoteVerificationState) {
+        validateVerificationState(remoteVerificationState);
+
+        Bundle extras = new Bundle();
+        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+        extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+
+        Bundle response = nullSafeCall(mContentResolver,
+                ContactKeys.UPDATE_SELF_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+
+        return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+    }
+
+    /**
+     * Maximum size of a contact key.
+     */
+    public static int getMaxKeySizeBytes() {
+        return MAX_KEY_SIZE_BYTES;
+    }
+
+    /**
+     * Returns a self key entry given the deviceId and the inferred package name of the caller.
+     *
+     * @param deviceId an app-specified identifier for the device
+     * @param accountId an app-specified identifier for the account
+     *
+     * @return a {@link SelfKey} object containing the self key information, or null if no self key
+     * is found.
+     */
+    @RequiresPermission(android.Manifest.permission.READ_CONTACTS)
+    @Nullable
+    public SelfKey getSelfKey(@NonNull String deviceId,
+            @NonNull String accountId) {
+        Bundle extras = new Bundle();
+        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+
+        Bundle response = nullSafeCall(mContentResolver,
+                ContactKeys.GET_SELF_KEY_METHOD, extras);
+
+        if (response == null) {
+            return null;
+        }
+        return response.getParcelable(ContactKeys.KEY_CONTACT_KEY, SelfKey.class);
+    }
+
+    /**
+     * Returns all self key entries that belong to apps visible to the caller.
+     * The keys will be stripped of deviceId, timeUpdated and keyValue data.
+     *
+     * @return a list of {@link SelfKey} objects containing the self key information, or
+     * an empty list if no keys are found.
+     */
+    @RequiresPermission(android.Manifest.permission.READ_CONTACTS)
+    @NonNull
+    public List<SelfKey> getAllSelfKeys() {
+        Bundle extras = new Bundle();
+
+        Bundle response = nullSafeCall(mContentResolver, ContactKeys.GET_ALL_SELF_KEYS_METHOD,
+                extras);
+
+        if (response == null) {
+            return new ArrayList<>();
+        }
+        List<SelfKey> value = response.getParcelableArrayList(ContactKeys.KEY_CONTACT_KEYS,
+                SelfKey.class);
+        if (value == null) {
+            return new ArrayList<>();
+        }
+        return value;
+    }
+
+    /**
+     * Returns all self key entries that are owned by the caller app.
+     *
+     * @return a list of {@link SelfKey} objects containing the self key information, or
+     * an empty list if no keys are found.
+     */
+    @RequiresPermission(android.Manifest.permission.READ_CONTACTS)
+    @NonNull
+    public List<SelfKey> getOwnerSelfKeys() {
+        Bundle extras = new Bundle();
+
+        Bundle response = nullSafeCall(mContentResolver, ContactKeys.GET_OWNER_SELF_KEYS_METHOD,
+                extras);
+
+        if (response == null) {
+            return new ArrayList<>();
+        }
+        List<SelfKey> value = response.getParcelableArrayList(ContactKeys.KEY_CONTACT_KEYS,
+                SelfKey.class);
+        if (value == null) {
+            return new ArrayList<>();
+        }
+        return value;
+    }
+
+    /**
+     * Removes a self key entry given the deviceId and the inferred package name of the caller.
+
+     * @param deviceId an app-specified identifier for the device
+     * @param accountId an app-specified identifier for the account
+     *
+     * @return true if the entry was removed, false otherwise.
+     */
+    @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS)
+    public boolean removeSelfKey(@NonNull String deviceId,
+            @NonNull String accountId) {
+        Bundle extras = new Bundle();
+        extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+        extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+
+        Bundle response = nullSafeCall(mContentResolver,
+                ContactKeys.REMOVE_SELF_KEY_METHOD, extras);
+
+        return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+    }
+
+    private Bundle nullSafeCall(@NonNull ContentResolver resolver, @NonNull String method,
+            @Nullable Bundle extras) {
+        try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY_URI)) {
+            return client.call(method, null, extras);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Possible values of verification state.
+     * @hide
+     */
+    @IntDef(prefix = {"VERIFICATION_STATE_"}, value = {
+            UNVERIFIED,
+            VERIFICATION_FAILED,
+            VERIFIED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface VerificationState {}
+
+    /**
+     * Unverified state of a contact E2EE key.
+     */
+    public static final int UNVERIFIED = 0;
+    /**
+     * Failed verification state of a contact E2EE key.
+     */
+    public static final int VERIFICATION_FAILED = 1;
+    /**
+     * Verified state of a contact E2EE key.
+     */
+    public static final int VERIFIED = 2;
+
+    /** @hide */
+    public static final class ContactKeys {
+
+        private ContactKeys() {}
+
+        /**
+         * <p>
+         * An opaque value that contains hints on how to find the contact if
+         * its row id changed as a result of a sync or aggregation.
+         * </p>
+         */
+        public static final String LOOKUP_KEY = "lookup";
+
+        /**
+         * <p>
+         * An app-specified identifier for the device for which the contact key can be used.
+         * </p>
+         */
+        public static final String DEVICE_ID = "device_id";
+
+        /**
+         * <p>
+         * An app-specified identifier for the account for which the contact key can be used.
+         * Usually a phone number.
+         * </p>
+         */
+        public static final String ACCOUNT_ID = "account_id";
+
+        /**
+         * <p>
+         * The display name for the contact.
+         * </p>
+         */
+        public static final String DISPLAY_NAME = "display_name";
+
+        /**
+         * <p>
+         * The phone number as the user entered it.
+         * </p>
+         */
+        public static final String PHONE_NUMBER = "number";
+
+        /**
+         * <p>
+         * The email address.
+         * </p>
+         */
+        public static final String EMAIL_ADDRESS = "address";
+
+        /**
+         * <p>
+         * Timestamp at which the key was updated.
+         * </p>
+         */
+        public static final String TIME_UPDATED = "time_updated";
+
+        /**
+         * <p>
+         * The raw bytes for the key.
+         * </p>
+         */
+        public static final String KEY_VALUE = "key_value";
+
+        /**
+         * <p>
+         * The package name of the package that created the key.
+         * </p>
+         */
+        public static final String OWNER_PACKAGE_NAME = "owner_package_name";
+
+        /**
+         * <p>
+         * Describes the local verification state for the key, for instance QR-code based
+         * verification.
+         * </p>
+         */
+        public static final String LOCAL_VERIFICATION_STATE = "local_verification_state";
+
+        /**
+         * <p>
+         * Describes the remote verification state for the key, for instance through a key
+         * transparency server.
+         * </p>
+         */
+        public static final String REMOTE_VERIFICATION_STATE = "remote_verification_state";
+
+        /**
+         * The method to invoke in order to add a new key for a contact.
+         */
+        public static final String UPDATE_OR_INSERT_CONTACT_KEY_METHOD = "updateOrInsertContactKey";
+
+        /**
+         * The method to invoke in order to retrieve key for a single contact.
+         */
+        public static final String GET_CONTACT_KEY_METHOD = "getContactKey";
+
+        /**
+         * The method to invoke in order to retrieve all contact keys.
+         */
+        public static final String GET_ALL_CONTACT_KEYS_METHOD = "getAllContactKeys";
+
+        /**
+         * The method to invoke in order to retrieve contact keys that belong to the caller.
+         */
+        public static final String GET_OWNER_CONTACT_KEYS_METHOD = "getOwnerContactKeys";
+
+        /**
+         * The method to invoke in order to update a contact key local verification state.
+         */
+        public static final String UPDATE_CONTACT_KEY_LOCAL_VERIFICATION_STATE_METHOD =
+                "updateContactKeyLocalVerificationState";
+
+        /**
+         * The method to invoke in order to update a contact key remote verification state.
+         */
+        public static final String UPDATE_CONTACT_KEY_REMOTE_VERIFICATION_STATE_METHOD =
+                "updateContactKeyRemoteVerificationState";
+
+        /**
+         * The method to invoke in order to remove a contact key.
+         */
+        public static final String REMOVE_CONTACT_KEY_METHOD = "removeContactKey";
+
+        /**
+         * The method to invoke in order to add a new self key.
+         */
+        public static final String UPDATE_OR_INSERT_SELF_KEY_METHOD = "updateOrInsertSelfKey";
+
+        /**
+         * The method to invoke in order to update a self key remote verification state.
+         */
+        public static final String UPDATE_SELF_KEY_REMOTE_VERIFICATION_STATE_METHOD =
+                "updateSelfKeyRemoteVerificationState";
+
+        /**
+         * The method to invoke in order to retrieve a self key.
+         */
+        public static final String GET_SELF_KEY_METHOD = "getSelfKey";
+
+        /**
+         * The method to invoke in order to retrieve all self keys.
+         */
+        public static final String GET_ALL_SELF_KEYS_METHOD = "getAllSelfKeys";
+
+        /**
+         * The method to invoke in order to retrieve self keys that belong to the caller.
+         */
+        public static final String GET_OWNER_SELF_KEYS_METHOD = "getOwnerSelfKeys";
+
+        /**
+         * The method to invoke in order to remove a new self key.
+         */
+        public static final String REMOVE_SELF_KEY_METHOD = "removeSelfKey";
+
+        /**
+         * Key in the incoming Bundle for all the contact keys.
+         */
+        public static final String KEY_CONTACT_KEYS = "key_contact_keys";
+
+        /**
+         * Key in the incoming Bundle for a single contact key.
+         */
+        public static final String KEY_CONTACT_KEY = "key_contact_key";
+
+        /**
+         * Key in the incoming Bundle for a number of modified rows.
+         */
+        public static final String KEY_UPDATED_ROWS = "key_updated_rows";
+    }
+
+    /**
+     * A parcelable class encapsulating other users' E2EE contact key.
+     */
+    public static final class ContactKey implements Parcelable {
+        /**
+         * An app-specified identifier for the device for which the contact key can be used.
+         */
+        private final String mDeviceId;
+
+        /**
+         * An app-specified identifier for the account for which the contact key can be used.
+         * Usually a phone number.
+         */
+        private final String mAccountId;
+
+        /**
+         * Owner application package name.
+         */
+        private final String mOwnerPackageName;
+
+        /**
+         * Timestamp at which the key was updated.
+         */
+        private final long mTimeUpdated;
+
+        /**
+         * The raw bytes for the key.
+         */
+        private final byte[] mKeyValue;
+
+        /**
+         * Describes the local verification state for the key, for instance QR-code based
+         * verification.
+         */
+        private final int mLocalVerificationState;
+
+        /**
+         * Describes the remote verification state for the key, for instance through a key
+         * transparency server.
+         */
+        private final int mRemoteVerificationState;
+
+        /**
+         * The display name for the contact.
+         */
+        private final String mDisplayName;
+
+        /**
+         * The phone number as the user entered it.
+         */
+        private final String mPhoneNumber;
+
+        /**
+         * The email address.
+         */
+        private final String mEmailAddress;
+
+        /**
+         * @hide
+         */
+        public ContactKey(@Nullable String deviceId, @NonNull String accountId,
+                @NonNull String ownerPackageName, long timeUpdated, @Nullable byte[] keyValue,
+                @VerificationState int localVerificationState,
+                @VerificationState int remoteVerificationState,
+                @Nullable String displayName,
+                @Nullable String phoneNumber, @Nullable String emailAddress) {
+            this.mDeviceId = deviceId;
+            this.mAccountId = accountId;
+            this.mOwnerPackageName = ownerPackageName;
+            this.mTimeUpdated = timeUpdated;
+            this.mKeyValue = keyValue == null ? null : Arrays.copyOf(keyValue, keyValue.length);
+            this.mLocalVerificationState = localVerificationState;
+            this.mRemoteVerificationState = remoteVerificationState;
+            this.mDisplayName = displayName;
+            this.mPhoneNumber = phoneNumber;
+            this.mEmailAddress = emailAddress;
+        }
+
+        /**
+         * Gets the app-specified identifier for the device for which the contact key can be used.
+         * Returns null if the app doesn't have the required visibility into the contact key.
+         *
+         * @return An app-specified identifier for the device.
+         */
+        @Nullable
+        public String getDeviceId() {
+            return mDeviceId;
+        }
+
+        /**
+         * Gets the app-specified identifier for the account for which the contact key can be used.
+         * Usually a phone number.
+         *
+         * @return An app-specified identifier for the account.
+         */
+        @NonNull
+        public String getAccountId() {
+            return mAccountId;
+        }
+
+        /**
+         * Gets the owner application package name.
+         *
+         * @return The owner application package name.
+         */
+        @NonNull
+        public String getOwnerPackageName() {
+            return mOwnerPackageName;
+        }
+
+        /**
+         * Gets the timestamp at which the key was updated. Returns -1 if the app doesn't have the
+         * required visibility into the contact key.
+         *
+         * @return The timestamp at which the key was updated in the System.currentTimeMillis()
+         * base.
+         */
+        public long getTimeUpdated() {
+            return mTimeUpdated;
+        }
+
+        /**
+         * Gets the raw bytes for the key.
+         * Returns null if the app doesn't have the required visibility into the contact key.
+         *
+         * @return A copy of the raw bytes for the key.
+         */
+        @Nullable
+        public byte[] getKeyValue() {
+            return mKeyValue == null ? null : Arrays.copyOf(mKeyValue, mKeyValue.length);
+        }
+
+        /**
+         * Gets the local verification state for the key, for instance QR-code based verification.
+         *
+         * @return The local verification state for the key.
+         */
+        public @VerificationState int getLocalVerificationState() {
+            return mLocalVerificationState;
+        }
+
+        /**
+         * Gets the remote verification state for the key, for instance through a key transparency
+         * server.
+         *
+         * @return The remote verification state for the key.
+         */
+        public @VerificationState int getRemoteVerificationState() {
+            return mRemoteVerificationState;
+        }
+
+        /**
+         * Gets the display name for the contact.
+         *
+         * @return The display name for the contact.
+         */
+        @Nullable
+        public String getDisplayName() {
+            return mDisplayName;
+        }
+
+        /**
+         * Gets the phone number as the user entered it.
+         *
+         * @return The phone number as the user entered it.
+         */
+        @Nullable
+        public String getPhoneNumber() {
+            return mPhoneNumber;
+        }
+
+        /**
+         * Gets the email address.
+         *
+         * @return The email address.
+         */
+        @Nullable
+        public String getEmailAddress() {
+            return mEmailAddress;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mDeviceId, mAccountId, mOwnerPackageName, mTimeUpdated,
+                    Arrays.hashCode(mKeyValue), mLocalVerificationState, mRemoteVerificationState,
+                    mDisplayName, mPhoneNumber, mEmailAddress);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null) return false;
+            if (obj == this) return true;
+
+            if (!(obj instanceof ContactKey toCompare)) {
+                return false;
+            }
+
+            return Objects.equals(mDeviceId, toCompare.mDeviceId)
+                    && Objects.equals(mAccountId, toCompare.mAccountId)
+                    && Objects.equals(mOwnerPackageName, toCompare.mOwnerPackageName)
+                    && mTimeUpdated == toCompare.mTimeUpdated
+                    && Arrays.equals(mKeyValue, toCompare.mKeyValue)
+                    && mLocalVerificationState == toCompare.mLocalVerificationState
+                    && mRemoteVerificationState == toCompare.mRemoteVerificationState
+                    && Objects.equals(mDisplayName, toCompare.mDisplayName)
+                    && Objects.equals(mPhoneNumber, toCompare.mPhoneNumber)
+                    && Objects.equals(mEmailAddress, toCompare.mEmailAddress);
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeString8(mDeviceId);
+            dest.writeString8(mAccountId);
+            dest.writeString8(mOwnerPackageName);
+            dest.writeLong(mTimeUpdated);
+            dest.writeInt(mKeyValue != null ? mKeyValue.length : ARRAY_IS_NULL);
+            if (mKeyValue != null) {
+                dest.writeByteArray(mKeyValue);
+            }
+            dest.writeInt(mLocalVerificationState);
+            dest.writeInt(mRemoteVerificationState);
+            dest.writeString8(mDisplayName);
+            dest.writeString8(mPhoneNumber);
+            dest.writeString8(mEmailAddress);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @NonNull
+        public static final Creator<ContactKey> CREATOR =
+                new Creator<>() {
+                    @Override
+                    public ContactKey createFromParcel(Parcel source) {
+                        String deviceId = source.readString8();
+                        String accountId = source.readString8();
+                        String ownerPackageName = source.readString8();
+                        long timeUpdated = source.readLong();
+                        int keyValueLength = source.readInt();
+                        byte[] keyValue;
+                        if (keyValueLength > 0) {
+                            keyValue = new byte[keyValueLength];
+                            source.readByteArray(keyValue);
+                        } else {
+                            keyValue = null;
+                        }
+                        int localVerificationState = source.readInt();
+                        int remoteVerificationState = source.readInt();
+                        String displayName = source.readString8();
+                        String number = source.readString8();
+                        String address = source.readString8();
+                        return new ContactKey(deviceId, accountId, ownerPackageName,
+                                timeUpdated, keyValue, localVerificationState,
+                                remoteVerificationState, displayName, number, address);
+                    }
+
+                    @Override
+                    public ContactKey[] newArray(int size) {
+                        return new ContactKey[size];
+                    }
+                };
+    }
+
+    /**
+     * A parcelable class encapsulating self E2EE contact key.
+     */
+    public static final class SelfKey implements Parcelable {
+        /**
+         * An app-specified identifier for the device for which the contact key can be used.
+         */
+        private final String mDeviceId;
+
+        /**
+         * An app-specified identifier for the account for which the contact key can be used.
+         * Usually a phone number.
+         */
+        private final String mAccountId;
+
+        /**
+         * Owner application package name.
+         */
+        private final String mOwnerPackageName;
+
+        /**
+         * Timestamp at which the key was updated.
+         */
+        private final long mTimeUpdated;
+
+        /**
+         * The raw bytes for the key.
+         */
+        private final byte[] mKeyValue;
+
+
+        /**
+         * Describes the remote verification state for the key, for instance through a key
+         * transparency server.
+         */
+        private final int mRemoteVerificationState;
+
+        /**
+         * @hide
+         */
+        public SelfKey(@Nullable String deviceId, @NonNull String accountId,
+                @NonNull String ownerPackageName, long timeUpdated, @Nullable byte[] keyValue,
+                @VerificationState int remoteVerificationState) {
+            this.mDeviceId = deviceId;
+            this.mAccountId = accountId;
+            this.mOwnerPackageName = ownerPackageName;
+            this.mTimeUpdated = timeUpdated;
+            this.mKeyValue = keyValue == null ? null : Arrays.copyOf(keyValue, keyValue.length);
+            this.mRemoteVerificationState = remoteVerificationState;
+        }
+
+        /**
+         * Gets the app-specified identifier for the device for which the contact key can be used.
+         * Returns null if the app doesn't have the required visibility into the contact key.
+         *
+         * @return An app-specified identifier for the device.
+         */
+        @Nullable
+        public String getDeviceId() {
+            return mDeviceId;
+        }
+
+        /**
+         * Gets the app-specified identifier for the account for which the contact key can be used.
+         * Usually a phone number.
+         *
+         * @return An app-specified identifier for the device.
+         */
+        @NonNull
+        public String getAccountId() {
+            return mAccountId;
+        }
+
+        /**
+         * Gets the owner application package name.
+         *
+         * @return The owner application package name.
+         */
+        @NonNull
+        public String getOwnerPackageName() {
+            return mOwnerPackageName;
+        }
+
+        /**
+         * Gets the timestamp at which the key was updated. Returns -1 if the app doesn't have the
+         * required visibility into the contact key.
+         *
+         * @return The timestamp at which the key was updated in the System.currentTimeMillis()
+         * base.
+         */
+        public long getTimeUpdated() {
+            return mTimeUpdated;
+        }
+
+        /**
+         * Gets the raw bytes for the key.
+         * Returns null if the app doesn't have the required visibility into the contact key.
+         *
+         * @return A copy of the raw bytes for the key.
+         */
+        @Nullable
+        public byte[] getKeyValue() {
+            return mKeyValue == null ? null : Arrays.copyOf(mKeyValue, mKeyValue.length);
+        }
+
+        /**
+         * Gets the remote verification state for the key, for instance through a key transparency
+         * server.
+         *
+         * @return The remote verification state for the key.
+         */
+        public @VerificationState int getRemoteVerificationState() {
+            return mRemoteVerificationState;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mDeviceId, mAccountId, mOwnerPackageName, mTimeUpdated,
+                    Arrays.hashCode(mKeyValue), mRemoteVerificationState);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null) return false;
+            if (obj == this) return true;
+
+            if (!(obj instanceof SelfKey toCompare)) {
+                return false;
+            }
+
+            return Objects.equals(mDeviceId, toCompare.mDeviceId)
+                    && Objects.equals(mAccountId, toCompare.mAccountId)
+                    && Objects.equals(mOwnerPackageName, toCompare.mOwnerPackageName)
+                    && mTimeUpdated == toCompare.mTimeUpdated
+                    && Arrays.equals(mKeyValue, toCompare.mKeyValue)
+                    && mRemoteVerificationState == toCompare.mRemoteVerificationState;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeString8(mDeviceId);
+            dest.writeString8(mAccountId);
+            dest.writeString8(mOwnerPackageName);
+            dest.writeLong(mTimeUpdated);
+            dest.writeInt(mKeyValue != null ? mKeyValue.length : ARRAY_IS_NULL);
+            if (mKeyValue != null) {
+                dest.writeByteArray(mKeyValue);
+            }
+            dest.writeInt(mRemoteVerificationState);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @NonNull
+        public static final Creator<SelfKey> CREATOR =
+                new Creator<>() {
+                    @Override
+                    public SelfKey createFromParcel(Parcel source) {
+                        String deviceId = source.readString8();
+                        String accountId = source.readString8();
+                        String ownerPackageName = source.readString8();
+                        long timeUpdated = source.readLong();
+                        int keyValueLength = source.readInt();
+                        byte[] keyValue;
+                        if (keyValueLength > 0) {
+                            keyValue = new byte[keyValueLength];
+                            source.readByteArray(keyValue);
+                        } else {
+                            keyValue = null;
+                        }
+                        int remoteVerificationState = source.readInt();
+                        return new SelfKey(deviceId, accountId, ownerPackageName,
+                                timeUpdated, keyValue, remoteVerificationState);
+                    }
+
+                    @Override
+                    public SelfKey[] newArray(int size) {
+                        return new SelfKey[size];
+                    }
+                };
+    }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e8da0d9..e238080 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -138,6 +138,20 @@
     public static final String ACTION_SETTINGS = "android.settings.SETTINGS";
 
     /**
+     * Activity Action: Show settings to provide guide about carrier satellite messaging.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    public static final String ACTION_SATELLITE_SETTING = "android.settings.SATELLITE_SETTING";
+
+    /**
      * Activity Action: Show settings to allow configuration of APNs.
      * <p>
      * Input: Nothing.
@@ -430,6 +444,18 @@
             "android.settings.ACCESSIBILITY_DETAILS_SETTINGS";
 
     /**
+     * Activity Action: Show settings to allow configuration of an accessibility
+     * shortcut belonging to an accessibility feature or features.
+     * <p>
+     * Input: ":settings:show_fragment_args" must contain "targets" denoting the services to edit.
+     * <p>
+     * Output: Nothing.
+     * @hide
+     **/
+    public static final String ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS =
+            "android.settings.ACCESSIBILITY_SHORTCUT_SETTINGS";
+
+    /**
      * Activity Action: Show settings to allow configuration of accessibility color and motion.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
@@ -1931,9 +1957,7 @@
      * A matching Activity will only be found if
      * {@link NotificationManager#areAutomaticZenRulesUserManaged()} is true.
      * <p>
-     * Input: Intent's data URI set with an application name, using the "package" schema (like
-     * "package:com.my.app").
-     * Input: The id of the rule, provided in {@link #EXTRA_AUTOMATIC_ZEN_RULE_ID}.
+     * Input: The id of the rule, provided in the {@link #EXTRA_AUTOMATIC_ZEN_RULE_ID} extra.
      * <p>
      * Output: Nothing.
      */
@@ -3178,15 +3202,7 @@
         }
 
         public void destroy() {
-            try {
-                // If this process is the system server process, mArray is the same object as
-                // the memory int array kept inside SettingsProvider, so skipping the close()
-                if (!Settings.isInSystemServer() && !mArray.isClosed()) {
-                    mArray.close();
-                }
-            } catch (IOException e) {
-                Log.e(TAG, "Error closing backing array", e);
-            }
+            maybeCloseGenerationArray(mArray);
         }
 
         @Override
@@ -3199,6 +3215,21 @@
         }
     }
 
+    private static void maybeCloseGenerationArray(@Nullable MemoryIntArray array) {
+        if (array == null) {
+            return;
+        }
+        try {
+            // If this process is the system server process, the MemoryIntArray received from Parcel
+            // is the same object as the one kept inside SettingsProvider, so skipping the close().
+            if (!Settings.isInSystemServer() && !array.isClosed()) {
+                array.close();
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Error closing the generation tracking array", e);
+        }
+    }
+
     private static final class ContentProviderHolder {
         private final Object mLock = new Object();
 
@@ -3238,9 +3269,17 @@
 
         private static final String NAME_EQ_PLACEHOLDER = "name=?";
 
+        // Cached values of queried settings.
+        // Key is the setting's name, value is the setting's value.
         // Must synchronize on 'this' to access mValues and mValuesVersion.
         private final ArrayMap<String, String> mValues = new ArrayMap<>();
 
+        // Cached values for queried prefixes.
+        // Key is the prefix, value is all of the settings under the prefix, mapped from a setting's
+        // name to a setting's value. The name string doesn't include the prefix.
+        // Must synchronize on 'this' to access.
+        private final ArrayMap<String, ArrayMap<String, String>> mPrefixToValues = new ArrayMap<>();
+
         private final Uri mUri;
         @UnsupportedAppUsage
         private final ContentProviderHolder mProviderHolder;
@@ -3498,6 +3537,8 @@
                                         mGenerationTrackers.put(name, new GenerationTracker(name,
                                                 array, index, generation,
                                                 mGenerationTrackerErrorHandler));
+                                    } else {
+                                        maybeCloseGenerationArray(array);
                                     }
                                 }
                                 if (mGenerationTrackers.get(name) != null
@@ -3585,15 +3626,13 @@
                     || applicationInfo.isSignedWithPlatformKey();
         }
 
-        private ArrayMap<String, String> getStringsForPrefixStripPrefix(
-                ContentResolver cr, String prefix, String[] names) {
+        private Map<String, String> getStringsForPrefixStripPrefix(
+                ContentResolver cr, String prefix, List<String> names) {
             String namespace = prefix.substring(0, prefix.length() - 1);
             ArrayMap<String, String> keyValues = new ArrayMap<>();
             int substringLength = prefix.length();
-
             int currentGeneration = -1;
             boolean needsGenerationTracker = false;
-
             synchronized (NameValueCache.this) {
                 final GenerationTracker generationTracker = mGenerationTrackers.get(prefix);
                 if (generationTracker != null) {
@@ -3607,40 +3646,24 @@
                         // generation tracker and request a new one
                         generationTracker.destroy();
                         mGenerationTrackers.remove(prefix);
-                        for (int i = mValues.size() - 1; i >= 0; i--) {
-                            String key = mValues.keyAt(i);
-                            if (key.startsWith(prefix)) {
-                                mValues.remove(key);
-                            }
-                        }
+                        mPrefixToValues.remove(prefix);
                         needsGenerationTracker = true;
                     } else {
-                        boolean prefixCached = mValues.containsKey(prefix);
-                        if (prefixCached) {
-                            if (DEBUG) {
-                                Log.i(TAG, "Cache hit for prefix:" + prefix);
-                            }
-                            if (names.length > 0) {
+                        final ArrayMap<String, String> cachedSettings = mPrefixToValues.get(prefix);
+                        if (cachedSettings != null) {
+                            if (!names.isEmpty()) {
                                 for (String name : names) {
-                                    String value = mValues.get(name);
-                                    if (value != null) {
+                                    // The cache can contain "null" values, need to use containsKey.
+                                    if (cachedSettings.containsKey(name)) {
                                         keyValues.put(
-                                                name.substring(substringLength),
-                                                value);
+                                                name,
+                                                cachedSettings.get(name));
                                     }
                                 }
                             } else {
-                                for (int i = 0; i < mValues.size(); ++i) {
-                                    String key = mValues.keyAt(i);
-                                    // Explicitly exclude the prefix as it is only there to
-                                    // signal that the prefix has been cached.
-                                    if (key.startsWith(prefix) && !key.equals(prefix)) {
-                                        String value = mValues.valueAt(i);
-                                        keyValues.put(
-                                                key.substring(substringLength),
-                                                value);
-                                    }
-                                }
+                                keyValues.putAll(cachedSettings);
+                                // Remove the hack added for the legacy behavior.
+                                keyValues.remove("");
                             }
                             return keyValues;
                         }
@@ -3650,7 +3673,6 @@
                     needsGenerationTracker = true;
                 }
             }
-
             if (mCallListCommand == null) {
                 // No list command specified, return empty map
                 return keyValues;
@@ -3695,20 +3717,23 @@
                 }
 
                 // All flags for the namespace
-                Map<String, String> flagsToValues =
+                HashMap<String, String> flagsToValues =
                         (HashMap) b.getSerializable(Settings.NameValueTable.VALUE, java.util.HashMap.class);
+                if (flagsToValues == null) {
+                    return keyValues;
+                }
                 // Only the flags requested by the caller
-                if (names.length > 0) {
+                if (!names.isEmpty()) {
                     for (String name : names) {
-                        String value = flagsToValues.get(name);
-                        if (value != null) {
+                        // flagsToValues can contain "null" values, need to use containsKey.
+                        final String key = Config.createCompositeName(namespace, name);
+                        if (flagsToValues.containsKey(key)) {
                             keyValues.put(
-                                    name.substring(substringLength),
-                                    value);
+                                    name,
+                                    flagsToValues.get(key));
                         }
                     }
                 } else {
-                    keyValues.ensureCapacity(keyValues.size() + flagsToValues.size());
                     for (Map.Entry<String, String> flag : flagsToValues.entrySet()) {
                         keyValues.put(
                                 flag.getKey().substring(substringLength),
@@ -3735,6 +3760,8 @@
                                     new GenerationTracker(prefix, array, index, generation,
                                             mGenerationTrackerErrorHandler));
                             currentGeneration = generation;
+                        } else {
+                            maybeCloseGenerationArray(array);
                         }
                     }
                     if (mGenerationTrackers.get(prefix) != null && currentGeneration
@@ -3742,10 +3769,18 @@
                         if (DEBUG) {
                             Log.i(TAG, "Updating cache for prefix:" + prefix);
                         }
-                        // cache the complete list of flags for the namespace
-                        mValues.putAll(flagsToValues);
-                        // Adding the prefix as a signal that the prefix is cached.
-                        mValues.put(prefix, null);
+                        // Cache the complete list of flags for the namespace for bulk queries.
+                        // In this cached list, the setting's name doesn't include the prefix.
+                        ArrayMap<String, String> namesToValues =
+                                new ArrayMap<>(flagsToValues.size() + 1);
+                        for (Map.Entry<String, String> flag : flagsToValues.entrySet()) {
+                            namesToValues.put(
+                                    flag.getKey().substring(substringLength),
+                                    flag.getValue());
+                        }
+                        // Legacy behavior, we return <"", null> when queried with name = ""
+                        namesToValues.put("", null);
+                        mPrefixToValues.put(prefix, namesToValues);
                     }
                 }
                 return keyValues;
@@ -4490,10 +4525,11 @@
         /** @hide */
         public static void adjustConfigurationForUser(ContentResolver cr, Configuration outConfig,
                 int userHandle, boolean updateSettingsIfEmpty) {
+            final float defaultFontScale = getDefaultFontScale(cr, userHandle);
             outConfig.fontScale = Settings.System.getFloatForUser(
-                    cr, FONT_SCALE, DEFAULT_FONT_SCALE, userHandle);
+                    cr, FONT_SCALE, defaultFontScale, userHandle);
             if (outConfig.fontScale < 0) {
-                outConfig.fontScale = DEFAULT_FONT_SCALE;
+                outConfig.fontScale = defaultFontScale;
             }
             outConfig.fontWeightAdjustment = Settings.Secure.getIntForUser(
                     cr, Settings.Secure.FONT_WEIGHT_ADJUSTMENT, DEFAULT_FONT_WEIGHT, userHandle);
@@ -4518,6 +4554,12 @@
             }
         }
 
+        private static float getDefaultFontScale(ContentResolver cr, int userHandle) {
+            return com.android.window.flags.Flags.configurableFontScaleDefault()
+                    ? Settings.System.getFloatForUser(cr, DEFAULT_DEVICE_FONT_SCALE,
+                    DEFAULT_FONT_SCALE, userHandle) : DEFAULT_FONT_SCALE;
+        }
+
         /**
          * @hide Erase the fields in the Configuration that should be applied
          * by the settings.
@@ -4884,6 +4926,16 @@
         public static final String FONT_SCALE = "font_scale";
 
         /**
+         * Default scaling factor for fonts for the specific device, float.
+         * The value is read from the {@link R.dimen.def_device_font_scale}
+         * configuration property.
+         *
+         * @hide
+         */
+        @Readable
+        public static final String DEFAULT_DEVICE_FONT_SCALE = "device_font_scale";
+
+        /**
          * The serialized system locale value.
          *
          * Do not use this value directory.
@@ -6222,6 +6274,7 @@
             PRIVATE_SETTINGS.add(CAMERA_FLASH_NOTIFICATION);
             PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION);
             PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION_COLOR);
+            PRIVATE_SETTINGS.add(DEFAULT_DEVICE_FONT_SCALE);
         }
 
         /**
@@ -7312,6 +7365,28 @@
                 "bluetooth_le_broadcast_app_source_name";
 
         /**
+         * This is used by LocalBluetoothLeBroadcast to downgrade the broadcast quality to improve
+         * compatibility.
+         *
+         * <ul>
+         *   <li>0 = false
+         *   <li>1 = true
+         * </ul>
+         *
+         * @hide
+         */
+        public static final String BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY =
+                "bluetooth_le_broadcast_improve_compatibility";
+
+        /**
+         * This is used by LocalBluetoothLeBroadcast to store the fallback active device address.
+         *
+         * @hide
+         */
+        public static final String BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_DEVICE_ADDRESS =
+                "bluetooth_le_broadcast_fallback_active_device_address";
+
+        /**
          * Ringtone routing value for hearing aid. It routes ringtone to hearing aid or device
          * speaker.
          * <ul>
@@ -7836,6 +7911,17 @@
         public static final String ACCESSIBILITY_BOUNCE_KEYS = "accessibility_bounce_keys";
 
         /**
+         * Whether to enable slow keys for Physical Keyboard accessibility.
+         *
+         * If set to non-zero value, any key press on physical keyboard needs to be pressed and
+         * held for the provided threshold duration (in milliseconds) to be registered in the
+         * system.
+         *
+         * @hide
+         */
+        public static final String ACCESSIBILITY_SLOW_KEYS = "accessibility_slow_keys";
+
+        /**
          * Whether to enable sticky keys for Physical Keyboard accessibility.
          *
          * This is a boolean value that determines if Sticky keys feature is enabled.
@@ -10131,7 +10217,9 @@
         public static final int HUB_MODE_TUTORIAL_STARTED = 1;
 
         /**
-         * Indicates that the user has completed the hub mode tutorial.
+         * Any value greater than or equal to this value is considered that the user has
+         * completed the hub mode tutorial.
+         *
          * One of the possible states for {@link #HUB_MODE_TUTORIAL_STATE}.
          *
          * @hide
@@ -10150,16 +10238,23 @@
 
         /**
          * Defines the user's current state of navigating through the hub mode tutorial.
-         * The possible states are defined in {@link HubModeTutorialState}.
+         * Some possible states are defined in {@link HubModeTutorialState}.
          *
+         * Any value greater than or equal to {@link HUB_MODE_TUTORIAL_COMPLETED} indicates that
+         * the user has completed that version of the hub mode tutorial. And tutorial may be
+         * shown again when a new version becomes available.
          * @hide
          */
         public static final String HUB_MODE_TUTORIAL_STATE = "hub_mode_tutorial_state";
 
         /**
          * The default NFC payment component
+         *
+         * @deprecated please use {@link android.app.role.RoleManager#getRoleHolders(String)}
+         * with {@link android.app.role.RoleManager#ROLE_WALLET} parameter.
          * @hide
          */
+        @Deprecated
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         public static final String NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component";
 
@@ -12176,6 +12271,36 @@
          */
         public static final String HIDE_PRIVATESPACE_ENTRY_POINT = "hide_privatespace_entry_point";
 
+        /** @hide */
+        public static final int PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK = 0;
+        /** @hide */
+        public static final int PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY = 1;
+        /** @hide */
+        public static final int PRIVATE_SPACE_AUTO_LOCK_NEVER = 2;
+
+        /**
+         * The different auto lock options for private space.
+         *
+         * @hide
+         */
+        @IntDef(prefix = {"PRIVATE_SPACE_AUTO_LOCK_"}, value = {
+                PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK,
+                PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY,
+                PRIVATE_SPACE_AUTO_LOCK_NEVER,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface PrivateSpaceAutoLockOption {
+        }
+
+
+        /**
+         *  Store auto lock value for private space.
+         *  The possible values are defined in {@link PrivateSpaceAutoLockOption}.
+         *
+         * @hide
+         */
+        public static final String PRIVATE_SPACE_AUTO_LOCK = "private_space_auto_lock";
+
         /**
          * These entries are considered common between the personal and the managed profile,
          * since the managed profile doesn't get to change them.
@@ -12192,6 +12317,8 @@
             CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE);
             CLONE_TO_MANAGED_PROFILE.add(SHOW_IME_WITH_HARD_KEYBOARD);
             CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_BOUNCE_KEYS);
+            CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_SLOW_KEYS);
+            CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_STICKY_KEYS);
             CLONE_TO_MANAGED_PROFILE.add(NOTIFICATION_BUBBLES);
             CLONE_TO_MANAGED_PROFILE.add(NOTIFICATION_HISTORY_ENABLED);
         }
@@ -13442,6 +13569,16 @@
         @Readable
         public static final String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update";
 
+
+        /**
+         * Whether to boot with 16K page size compatible kernel
+         * 1 = Boot with 16K kernel
+         * 0 = Boot with 4K kernel (default)
+         * @hide
+         */
+        @Readable
+        public static final String ENABLE_16K_PAGES = "enable_16k_pages";
+
         /** Timeout for package verification.
         * @hide */
         @Readable
@@ -19865,16 +20002,9 @@
         @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
         public static Map<String, String> getStrings(@NonNull ContentResolver resolver,
                 @NonNull String namespace, @NonNull List<String> names) {
-            String[] compositeNames = new String[names.size()];
-            for (int i = 0, size = names.size(); i < size; ++i) {
-                compositeNames[i] = createCompositeName(namespace, names.get(i));
-            }
-
             String prefix = createPrefix(namespace);
 
-            ArrayMap<String, String> keyValues = sNameValueCache.getStringsForPrefixStripPrefix(
-                    resolver, prefix, compositeNames);
-            return keyValues;
+            return sNameValueCache.getStringsForPrefixStripPrefix(resolver, prefix, names);
         }
 
         /**
@@ -20196,7 +20326,7 @@
             }
         }
 
-        private static String createCompositeName(@NonNull String namespace, @NonNull String name) {
+        static String createCompositeName(@NonNull String namespace, @NonNull String name) {
             Preconditions.checkNotNull(namespace);
             Preconditions.checkNotNull(name);
             var sb = new StringBuilder(namespace.length() + 1 + name.length());
diff --git a/core/java/android/provider/TEST_MAPPING b/core/java/android/provider/TEST_MAPPING
index 8b4a99e..d5ac7a7 100644
--- a/core/java/android/provider/TEST_MAPPING
+++ b/core/java/android/provider/TEST_MAPPING
@@ -28,5 +28,10 @@
                 }
             ]
         }
+    ],
+    "postsubmit": [
+        {
+            "name": "CtsDeviceConfigTestCases"
+        }
     ]
 }
diff --git a/core/java/android/provider/flags.aconfig b/core/java/android/provider/flags.aconfig
index 3dd7692..0f12b13 100644
--- a/core/java/android/provider/flags.aconfig
+++ b/core/java/android/provider/flags.aconfig
@@ -6,3 +6,10 @@
     description: "Enable Settings.System.resetToDefault APIs."
     bug: "279083734"
 }
+
+flag {
+    name: "user_keys"
+    namespace: "privacy_infra_policy"
+    description: "This flag controls new E2EE contact keys API"
+    bug: "290696572"
+}
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 30524a1..43163b3 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -53,8 +53,15 @@
 
 flag {
     name: "frp_enforcement"
-    namespace: "android_hw_security"
+    namespace: "hardware_backed_security"
     description: "This flag controls whether PDB enforces FRP"
     bug: "290312729"
     is_fixed_read_only: true
 }
+
+flag {
+  name: "report_primary_auth_attempts"
+  namespace: "biometrics"
+  description: "Report primary auth attempts from LockSettingsService"
+  bug: "285053096"
+}
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index fe6c4a4..0bae459 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -27,3 +27,17 @@
     description: "Enables the content URI permission APIs"
     bug: "293467489"
 }
+
+flag {
+    name: "enforce_intent_filter_match"
+    namespace: "responsible_apis"
+    description: "Make delivered intents match components' intent filters"
+    bug: "293560872"
+}
+
+flag {
+    name: "block_null_action_intents"
+    namespace: "responsible_apis"
+    description: "Do not allow intents without an action to match any intent filters"
+    bug: "293560872"
+}
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 5ad2502..298bdb8 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -622,6 +622,15 @@
                     new FillCallback(callback, request.getId())));
         }
 
+        @Override
+        public void onConvertCredentialRequest(
+                @NonNull ConvertCredentialRequest convertCredentialRequest,
+                @NonNull IConvertCredentialCallback convertCredentialCallback) {
+            mHandler.sendMessage(obtainMessage(
+                    AutofillService::onConvertCredentialRequest,
+                    AutofillService.this, convertCredentialRequest,
+                    new ConvertCredentialCallback(convertCredentialCallback)));
+        }
 
         @Override
         public void onFillCredentialRequest(FillRequest request, IFillCallback callback,
@@ -707,7 +716,19 @@
      */
     public void onFillCredentialRequest(@NonNull FillRequest request,
             @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback,
-            IAutoFillManagerClient autofillClientCallback) {}
+            @NonNull IAutoFillManagerClient autofillClientCallback) {}
+
+    /**
+     * Called by the Android system to convert a credential manager response to a dataset
+     *
+     * @param convertCredentialRequest the request that has the original credential manager response
+     * @param convertCredentialCallback callback used to notify the result of the request.
+     *
+     * @hide
+     */
+    public void onConvertCredentialRequest(
+            @NonNull ConvertCredentialRequest convertCredentialRequest,
+            @NonNull ConvertCredentialCallback convertCredentialCallback){}
 
     /**
      * Called when the user requests the service to save the contents of a screen.
diff --git a/core/java/android/service/autofill/ConvertCredentialCallback.java b/core/java/android/service/autofill/ConvertCredentialCallback.java
new file mode 100644
index 0000000..a39f011
--- /dev/null
+++ b/core/java/android/service/autofill/ConvertCredentialCallback.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+
+/**
+ * <p><code>ConvertCredentialCallback</code> handles convertCredentialResponse from Autofill
+ * Service.
+ *
+ * @hide
+ */
+public final class ConvertCredentialCallback {
+
+    private static final String TAG = "ConvertCredentialCallback";
+
+    private final IConvertCredentialCallback mCallback;
+
+    /** @hide */
+    public ConvertCredentialCallback(IConvertCredentialCallback callback) {
+        mCallback = callback;
+    }
+
+    /**
+     * Notifies the Android System that a convertCredentialRequest was fulfilled by the service.
+     *
+     * @param convertCredentialResponse the result
+     */
+    public void onSuccess(@NonNull ConvertCredentialResponse convertCredentialResponse) {
+        try {
+            mCallback.onSuccess(convertCredentialResponse);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Notifies the Android System that a convert credential request has failed
+     *
+     * @param message the error message
+     */
+    public void onFailure(@Nullable CharSequence message) {
+        try {
+            mCallback.onFailure(message);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+}
diff --git a/core/java/android/service/autofill/ConvertCredentialRequest.aidl b/core/java/android/service/autofill/ConvertCredentialRequest.aidl
new file mode 100644
index 0000000..79681e2
--- /dev/null
+++ b/core/java/android/service/autofill/ConvertCredentialRequest.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+parcelable ConvertCredentialRequest;
\ No newline at end of file
diff --git a/core/java/android/service/autofill/ConvertCredentialRequest.java b/core/java/android/service/autofill/ConvertCredentialRequest.java
new file mode 100644
index 0000000..d2d7556
--- /dev/null
+++ b/core/java/android/service/autofill/ConvertCredentialRequest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import android.annotation.NonNull;
+import android.credentials.GetCredentialResponse;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+
+/**
+ * This class represents a request to an autofill service to convert the credential manager response
+ * to a dataset.
+ *
+ * @hide
+ */
+@DataClass(
+        genToString = true,
+        genHiddenConstructor = true,
+        genHiddenConstDefs = true)
+public final class ConvertCredentialRequest implements Parcelable {
+    private final @NonNull GetCredentialResponse mGetCredentialResponse;
+    private final @NonNull Bundle mClientState;
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/ConvertCredentialRequest.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new ConvertCredentialRequest.
+     *
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public ConvertCredentialRequest(
+            @NonNull GetCredentialResponse getCredentialResponse,
+            @NonNull Bundle clientState) {
+        this.mGetCredentialResponse = getCredentialResponse;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mGetCredentialResponse);
+        this.mClientState = clientState;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mClientState);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull GetCredentialResponse getGetCredentialResponse() {
+        return mGetCredentialResponse;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull Bundle getClientState() {
+        return mClientState;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "ConvertCredentialRequest { " +
+                "getCredentialResponse = " + mGetCredentialResponse + ", " +
+                "clientState = " + mClientState +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeTypedObject(mGetCredentialResponse, flags);
+        dest.writeBundle(mClientState);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ ConvertCredentialRequest(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        GetCredentialResponse getCredentialResponse = (GetCredentialResponse) in.readTypedObject(GetCredentialResponse.CREATOR);
+        Bundle clientState = in.readBundle();
+
+        this.mGetCredentialResponse = getCredentialResponse;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mGetCredentialResponse);
+        this.mClientState = clientState;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mClientState);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<ConvertCredentialRequest> CREATOR
+            = new Parcelable.Creator<ConvertCredentialRequest>() {
+        @Override
+        public ConvertCredentialRequest[] newArray(int size) {
+            return new ConvertCredentialRequest[size];
+        }
+
+        @Override
+        public ConvertCredentialRequest createFromParcel(@NonNull Parcel in) {
+            return new ConvertCredentialRequest(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1706132305002L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/service/autofill/ConvertCredentialRequest.java",
+            inputSignatures = "private final @android.annotation.NonNull android.credentials.GetCredentialResponse mGetCredentialResponse\nprivate final @android.annotation.NonNull android.os.Bundle mClientState\nclass ConvertCredentialRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/service/autofill/ConvertCredentialResponse.aidl b/core/java/android/service/autofill/ConvertCredentialResponse.aidl
new file mode 100644
index 0000000..98ac6f6
--- /dev/null
+++ b/core/java/android/service/autofill/ConvertCredentialResponse.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+parcelable ConvertCredentialResponse;
\ No newline at end of file
diff --git a/core/java/android/service/autofill/ConvertCredentialResponse.java b/core/java/android/service/autofill/ConvertCredentialResponse.java
new file mode 100644
index 0000000..5da4f63
--- /dev/null
+++ b/core/java/android/service/autofill/ConvertCredentialResponse.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Response for a {@Link ConvertCredentialRequest}
+ *
+ * @hide
+ */
+@DataClass(
+        genToString = true,
+        genHiddenConstructor = true,
+        genHiddenConstDefs = true)
+public final class ConvertCredentialResponse implements Parcelable {
+    private final @NonNull Dataset mDataset;
+    private final @Nullable Bundle mClientState;
+
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/ConvertCredentialResponse.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new ConvertCredentialResponse.
+     *
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public ConvertCredentialResponse(
+            @NonNull Dataset dataset,
+            @Nullable Bundle clientState) {
+        this.mDataset = dataset;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mDataset);
+        this.mClientState = clientState;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull Dataset getDataset() {
+        return mDataset;
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable Bundle getClientState() {
+        return mClientState;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "ConvertCredentialResponse { " +
+                "dataset = " + mDataset + ", " +
+                "clientState = " + mClientState +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mClientState != null) flg |= 0x2;
+        dest.writeByte(flg);
+        dest.writeTypedObject(mDataset, flags);
+        if (mClientState != null) dest.writeBundle(mClientState);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ ConvertCredentialResponse(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        Dataset dataset = (Dataset) in.readTypedObject(Dataset.CREATOR);
+        Bundle clientState = (flg & 0x2) == 0 ? null : in.readBundle();
+
+        this.mDataset = dataset;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mDataset);
+        this.mClientState = clientState;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<ConvertCredentialResponse> CREATOR
+            = new Parcelable.Creator<ConvertCredentialResponse>() {
+        @Override
+        public ConvertCredentialResponse[] newArray(int size) {
+            return new ConvertCredentialResponse[size];
+        }
+
+        @Override
+        public ConvertCredentialResponse createFromParcel(@NonNull Parcel in) {
+            return new ConvertCredentialResponse(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1706132669373L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/service/autofill/ConvertCredentialResponse.java",
+            inputSignatures = "private final @android.annotation.NonNull android.service.autofill.Dataset mDataset\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nclass ConvertCredentialResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index 5d58120..98dda10 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -309,12 +309,19 @@
         /** The autofill suggestion is shown as a dialog presentation. */
         public static final int UI_TYPE_DIALOG = 3;
 
+        /**
+         *  The autofill suggestion is shown os a credman bottom sheet
+         *  @hide
+         */
+        public static final int UI_TYPE_CREDMAN_BOTTOM_SHEET = 4;
+
         /** @hide */
         @IntDef(prefix = { "UI_TYPE_" }, value = {
                 UI_TYPE_UNKNOWN,
                 UI_TYPE_MENU,
                 UI_TYPE_INLINE,
-                UI_TYPE_DIALOG
+                UI_TYPE_DIALOG,
+                UI_TYPE_CREDMAN_BOTTOM_SHEET
         })
         @Retention(RetentionPolicy.SOURCE)
         public @interface UiType {}
@@ -755,6 +762,8 @@
                     return "UI_TYPE_INLINE";
                 case UI_TYPE_DIALOG:
                     return "UI_TYPE_FILL_DIALOG";
+                case UI_TYPE_CREDMAN_BOTTOM_SHEET:
+                    return "UI_TYPE_CREDMAN_BOTTOM_SHEET";
                 default:
                     return "UI_TYPE_UNKNOWN";
             }
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 7ea74d3..09ec933 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -28,6 +28,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.app.Activity;
+import android.app.PendingIntent;
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.ParceledListSlice;
@@ -116,6 +117,7 @@
     private final boolean mShowFillDialogIcon;
     private final boolean mShowSaveDialogIcon;
     private final @Nullable FieldClassification[] mDetectedFieldTypes;
+    private final @Nullable PendingIntent mDialogPendingIntent;
 
     /**
     * Creates a shollow copy of the provided FillResponse.
@@ -150,7 +152,8 @@
                 r.mServiceDisplayNameResourceId,
                 r.mShowFillDialogIcon,
                 r.mShowSaveDialogIcon,
-                r.mDetectedFieldTypes);
+                r.mDetectedFieldTypes,
+                r.mDialogPendingIntent);
     }
 
     private FillResponse(ParceledListSlice<Dataset> datasets, SaveInfo saveInfo, Bundle clientState,
@@ -163,7 +166,7 @@
             int[] cancelIds, boolean supportsInlineSuggestions, int iconResourceId,
             int serviceDisplayNameResourceId, boolean showFillDialogIcon,
             boolean showSaveDialogIcon,
-            FieldClassification[] detectedFieldTypes) {
+            FieldClassification[] detectedFieldTypes, PendingIntent dialogPendingIntent) {
         mDatasets = datasets;
         mSaveInfo = saveInfo;
         mClientState = clientState;
@@ -190,6 +193,7 @@
         mShowFillDialogIcon = showFillDialogIcon;
         mShowSaveDialogIcon = showSaveDialogIcon;
         mDetectedFieldTypes = detectedFieldTypes;
+        mDialogPendingIntent = dialogPendingIntent;
     }
 
     private FillResponse(@NonNull Builder builder) {
@@ -219,6 +223,7 @@
         mShowFillDialogIcon = builder.mShowFillDialogIcon;
         mShowSaveDialogIcon = builder.mShowSaveDialogIcon;
         mDetectedFieldTypes = builder.mDetectedFieldTypes;
+        mDialogPendingIntent = builder.mDialogPendingIntent;
     }
 
     /** @hide */
@@ -399,6 +404,7 @@
         private boolean mShowFillDialogIcon = true;
         private boolean mShowSaveDialogIcon = true;
         private FieldClassification[] mDetectedFieldTypes;
+        private PendingIntent mDialogPendingIntent;
 
         /**
          * Adds a new {@link FieldClassification} to this response, to
@@ -1079,6 +1085,24 @@
         }
 
         /**
+         * Sets credential dialog pending intent. Framework will use the intent to launch the
+         * selector UI. A replacement for previous fill bottom sheet.
+         *
+         * @throws IllegalStateException if {@link #build()} was already called.
+         * @throws NullPointerException if {@code pendingIntent} is {@code null}.
+         *
+         * @hide
+         */
+        @NonNull
+        public Builder setDialogPendingIntent(@NonNull PendingIntent pendingIntent) {
+            throwIfDestroyed();
+            Preconditions.checkNotNull(pendingIntent,
+                    "can't pass a null object to setDialogPendingIntent");
+            mDialogPendingIntent = pendingIntent;
+            return this;
+        }
+
+        /**
          * Builds a new {@link FillResponse} instance.
          *
          * @throws IllegalStateException if any of the following conditions occur:
@@ -1187,6 +1211,9 @@
         if (mAuthentication != null) {
             builder.append(", hasAuthentication");
         }
+        if (mDialogPendingIntent != null) {
+            builder.append(", hasDialogPendingIntent");
+        }
         if (mAuthenticationIds != null) {
             builder.append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds));
         }
@@ -1232,6 +1259,7 @@
         parcel.writeParcelable(mInlineTooltipPresentation, flags);
         parcel.writeParcelable(mDialogPresentation, flags);
         parcel.writeParcelable(mDialogHeader, flags);
+        parcel.writeParcelable(mDialogPendingIntent, flags);
         parcel.writeParcelableArray(mFillDialogTriggerIds, flags);
         parcel.writeParcelable(mHeader, flags);
         parcel.writeParcelable(mFooter, flags);
@@ -1282,6 +1310,11 @@
             if (dialogHeader != null) {
                 builder.setDialogHeader(dialogHeader);
             }
+            final PendingIntent dialogPendingIntent = parcel.readParcelable(null,
+                    PendingIntent.class);
+            if (dialogPendingIntent != null) {
+                builder.setDialogPendingIntent(dialogPendingIntent);
+            }
             final AutofillId[] triggerIds = parcel.readParcelableArray(null, AutofillId.class);
             if (triggerIds != null) {
                 builder.setFillDialogTriggerIds(triggerIds);
diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl
index 03ead32..2c2feae 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -16,6 +16,8 @@
 
 package android.service.autofill;
 
+import android.service.autofill.ConvertCredentialRequest;
+import android.service.autofill.IConvertCredentialCallback;
 import android.service.autofill.FillRequest;
 import android.service.autofill.IFillCallback;
 import android.service.autofill.ISaveCallback;
@@ -32,7 +34,8 @@
     void onConnectedStateChanged(boolean connected);
     void onFillRequest(in FillRequest request, in IFillCallback callback);
     void onFillCredentialRequest(in FillRequest request, in IFillCallback callback,
-    in IAutoFillManagerClient client);
+        in IAutoFillManagerClient client);
     void onSaveRequest(in SaveRequest request, in ISaveCallback callback);
     void onSavedPasswordCountRequest(in IResultReceiver receiver);
+    void onConvertCredentialRequest(in ConvertCredentialRequest convertCredentialRequest, in IConvertCredentialCallback convertCredentialCallback);
 }
diff --git a/core/java/android/service/autofill/IConvertCredentialCallback.aidl b/core/java/android/service/autofill/IConvertCredentialCallback.aidl
new file mode 100644
index 0000000..9dfc294
--- /dev/null
+++ b/core/java/android/service/autofill/IConvertCredentialCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import android.os.ICancellationSignal;
+
+import android.service.autofill.ConvertCredentialResponse;
+
+/**
+ * Interface to receive the result of a convert credential request
+ *
+ * @hide
+ */
+oneway interface IConvertCredentialCallback {
+    void onSuccess(in ConvertCredentialResponse convertCredentialResponse);
+    void onFailure(CharSequence message);
+}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 7d9c0a3..2d657c2 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -445,6 +445,19 @@
     }
 
     /**
+     * Retrieves the current {@link android.app.Activity} associated with the dream.
+     * This method behaves similarly to calling {@link android.app.Activity#getActivity()}.
+     *
+     * @return The current activity, or null if the dream is not associated with an activity
+     * or not started.
+     *
+     * @hide
+     */
+    public Activity getActivity() {
+        return mActivity;
+    }
+
+    /**
      * Inflates a layout resource and set it to be the content view for this Dream.
      * Behaves similarly to {@link android.app.Activity#setContentView(int)}.
      *
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 92c516c..7658af5 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -42,6 +42,7 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
+import android.os.BadParcelableException;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -1056,7 +1057,7 @@
             ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface()
                     .getActiveNotificationsFromListener(mWrapper, keys, trim);
             return cleanUpNotificationList(parceledList);
-        } catch (android.os.RemoteException ex) {
+        } catch (android.os.RemoteException | BadParcelableException ex) {
             Log.v(TAG, "Unable to contact notification manager", ex);
         }
         return null;
diff --git a/core/java/android/service/notification/ZenDeviceEffects.java b/core/java/android/service/notification/ZenDeviceEffects.java
index 03ebae5..90049e6 100644
--- a/core/java/android/service/notification/ZenDeviceEffects.java
+++ b/core/java/android/service/notification/ZenDeviceEffects.java
@@ -20,7 +20,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.TestApi;
 import android.app.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -37,8 +36,8 @@
 @FlaggedApi(Flags.FLAG_MODES_API)
 public final class ZenDeviceEffects implements Parcelable {
 
-    /** Used to track which rule variables have been modified by the user.
-     * Should be checked against the bitmask {@link #getUserModifiedFields()}.
+    /**
+     * Enum for the user-modifiable fields in this object.
      * @hide
      */
     @IntDef(flag = true, prefix = { "FIELD_" }, value = {
@@ -59,52 +58,42 @@
     /**
      * @hide
      */
-    @TestApi
     public static final int FIELD_GRAYSCALE = 1 << 0;
     /**
      * @hide
      */
-    @TestApi
     public static final int FIELD_SUPPRESS_AMBIENT_DISPLAY = 1 << 1;
     /**
      * @hide
      */
-    @TestApi
     public static final int FIELD_DIM_WALLPAPER = 1 << 2;
     /**
      * @hide
      */
-    @TestApi
     public static final int FIELD_NIGHT_MODE = 1 << 3;
     /**
      * @hide
      */
-    @TestApi
     public static final int FIELD_DISABLE_AUTO_BRIGHTNESS = 1 << 4;
     /**
      * @hide
      */
-    @TestApi
     public static final int FIELD_DISABLE_TAP_TO_WAKE = 1 << 5;
     /**
      * @hide
      */
-    @TestApi
     public static final int FIELD_DISABLE_TILT_TO_WAKE = 1 << 6;
     /**
      * @hide
      */
-    @TestApi
     public static final int FIELD_DISABLE_TOUCH = 1 << 7;
     /**
      * @hide
      */
-    @TestApi
     public static final int FIELD_MINIMIZE_RADIO_USAGE = 1 << 8;
     /**
      * @hide
      */
-    @TestApi
     public static final int FIELD_MAXIMIZE_DOZE = 1 << 9;
 
     private final boolean mGrayscale;
@@ -119,13 +108,10 @@
     private final boolean mMinimizeRadioUsage;
     private final boolean mMaximizeDoze;
 
-    private final @ModifiableField int mUserModifiedFields; // Bitwise representation
-
     private ZenDeviceEffects(boolean grayscale, boolean suppressAmbientDisplay,
             boolean dimWallpaper, boolean nightMode, boolean disableAutoBrightness,
             boolean disableTapToWake, boolean disableTiltToWake, boolean disableTouch,
-            boolean minimizeRadioUsage, boolean maximizeDoze,
-            @ModifiableField int userModifiedFields) {
+            boolean minimizeRadioUsage, boolean maximizeDoze) {
         mGrayscale = grayscale;
         mSuppressAmbientDisplay = suppressAmbientDisplay;
         mDimWallpaper = dimWallpaper;
@@ -136,7 +122,6 @@
         mDisableTouch = disableTouch;
         mMinimizeRadioUsage = minimizeRadioUsage;
         mMaximizeDoze = maximizeDoze;
-        mUserModifiedFields = userModifiedFields;
     }
 
     @Override
@@ -153,15 +138,14 @@
                 && this.mDisableTiltToWake == that.mDisableTiltToWake
                 && this.mDisableTouch == that.mDisableTouch
                 && this.mMinimizeRadioUsage == that.mMinimizeRadioUsage
-                && this.mMaximizeDoze == that.mMaximizeDoze
-                && this.mUserModifiedFields == that.mUserModifiedFields;
+                && this.mMaximizeDoze == that.mMaximizeDoze;
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mGrayscale, mSuppressAmbientDisplay, mDimWallpaper, mNightMode,
                 mDisableAutoBrightness, mDisableTapToWake, mDisableTiltToWake, mDisableTouch,
-                mMinimizeRadioUsage, mMaximizeDoze, mUserModifiedFields);
+                mMinimizeRadioUsage, mMaximizeDoze);
     }
 
     @Override
@@ -177,11 +161,11 @@
         if (mDisableTouch) effects.add("disableTouch");
         if (mMinimizeRadioUsage) effects.add("minimizeRadioUsage");
         if (mMaximizeDoze) effects.add("maximizeDoze");
-        return "[" + String.join(", ", effects) + "]"
-                + " userModifiedFields: " + modifiedFieldsToString(mUserModifiedFields);
+        return "[" + String.join(", ", effects) + "]";
     }
 
-    private String modifiedFieldsToString(int bitmask) {
+    /** @hide */
+    public static String fieldsToString(@ModifiableField int bitmask) {
         ArrayList<String> modified = new ArrayList<>();
         if ((bitmask & FIELD_GRAYSCALE) != 0) {
             modified.add("FIELD_GRAYSCALE");
@@ -312,7 +296,7 @@
             return new ZenDeviceEffects(in.readBoolean(),
                     in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(),
                     in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(),
-                    in.readBoolean(), in.readInt());
+                    in.readBoolean());
         }
 
         @Override
@@ -321,16 +305,6 @@
         }
     };
 
-    /**
-     * Gets the bitmask representing which fields are user modified. Bits are set using
-     * {@link ModifiableField}.
-     * @hide
-     */
-    @TestApi
-    public @ModifiableField int getUserModifiedFields() {
-        return mUserModifiedFields;
-    }
-
     @Override
     public int describeContents() {
         return 0;
@@ -348,7 +322,6 @@
         dest.writeBoolean(mDisableTouch);
         dest.writeBoolean(mMinimizeRadioUsage);
         dest.writeBoolean(mMaximizeDoze);
-        dest.writeInt(mUserModifiedFields);
     }
 
     /** Builder class for {@link ZenDeviceEffects} objects. */
@@ -365,7 +338,6 @@
         private boolean mDisableTouch;
         private boolean mMinimizeRadioUsage;
         private boolean mMaximizeDoze;
-        private @ModifiableField int mUserModifiedFields;
 
         /**
          * Instantiates a new {@link ZenPolicy.Builder} with all effects set to default (disabled).
@@ -388,7 +360,6 @@
             mDisableTouch = zenDeviceEffects.shouldDisableTouch();
             mMinimizeRadioUsage = zenDeviceEffects.shouldMinimizeRadioUsage();
             mMaximizeDoze = zenDeviceEffects.shouldMaximizeDoze();
-            mUserModifiedFields = zenDeviceEffects.mUserModifiedFields;
         }
 
         /**
@@ -510,24 +481,13 @@
             return this;
         }
 
-        /**
-         * Sets the bitmask representing which fields are user modified. See the FIELD_ constants.
-         * @hide
-         */
-        @TestApi
-        @NonNull
-        public Builder setUserModifiedFields(@ModifiableField int userModifiedFields) {
-            mUserModifiedFields = userModifiedFields;
-            return this;
-        }
-
         /** Builds a {@link ZenDeviceEffects} object based on the builder's state. */
         @NonNull
         public ZenDeviceEffects build() {
             return new ZenDeviceEffects(mGrayscale,
                     mSuppressAmbientDisplay, mDimWallpaper, mNightMode, mDisableAutoBrightness,
                     mDisableTapToWake, mDisableTiltToWake, mDisableTouch, mMinimizeRadioUsage,
-                    mMaximizeDoze, mUserModifiedFields);
+                    mMaximizeDoze);
         }
     }
 }
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 45a0c20..d4a5356 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -64,6 +64,7 @@
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.time.Instant;
 import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Date;
@@ -184,7 +185,13 @@
             SUPPRESSED_EFFECT_SCREEN_OFF | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT
                     | SUPPRESSED_EFFECT_LIGHTS | SUPPRESSED_EFFECT_PEEK | SUPPRESSED_EFFECT_AMBIENT;
 
-    public static final int XML_VERSION = 8;
+    // ZenModeConfig XML versions distinguishing key changes.
+    public static final int XML_VERSION_ZEN_UPGRADE = 8;
+    public static final int XML_VERSION_MODES_API = 11;
+
+    // TODO: b/310620812 - Update XML_VERSION and update default_zen_config.xml accordingly when
+    //       modes_api is inlined.
+    private static final int XML_VERSION = 10;
     public static final String ZEN_TAG = "zen";
     private static final String ZEN_ATT_VERSION = "version";
     private static final String ZEN_ATT_USER = "user";
@@ -204,8 +211,8 @@
     private static final String ALLOW_ATT_SCREEN_ON = "visualScreenOn";
     private static final String ALLOW_ATT_CONV = "convos";
     private static final String ALLOW_ATT_CONV_FROM = "convosFrom";
-    private static final String ALLOW_ATT_CHANNELS = "channels";
-    private static final String USER_MODIFIED_FIELDS = "policyUserModifiedFields";
+    private static final String ALLOW_ATT_CHANNELS = "priorityChannels";
+    private static final String POLICY_USER_MODIFIED_FIELDS = "policyUserModifiedFields";
     private static final String DISALLOW_TAG = "disallow";
     private static final String DISALLOW_ATT_VISUAL_EFFECTS = "visualEffects";
     private static final String STATE_TAG = "state";
@@ -233,6 +240,7 @@
 
     private static final String MANUAL_TAG = "manual";
     private static final String AUTOMATIC_TAG = "automatic";
+    private static final String AUTOMATIC_DELETED_TAG = "deleted";
 
     private static final String RULE_ATT_ID = "ruleId";
     private static final String RULE_ATT_ENABLED = "enabled";
@@ -251,6 +259,7 @@
     private static final String RULE_ATT_USER_MODIFIED_FIELDS = "userModifiedFields";
     private static final String RULE_ATT_ICON = "rule_icon";
     private static final String RULE_ATT_TRIGGER_DESC = "triggerDesc";
+    private static final String RULE_ATT_DELETION_INSTANT = "deletionInstant";
 
     private static final String DEVICE_EFFECT_DISPLAY_GRAYSCALE = "zdeDisplayGrayscale";
     private static final String DEVICE_EFFECT_SUPPRESS_AMBIENT_DISPLAY =
@@ -292,6 +301,10 @@
     @UnsupportedAppUsage
     public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>();
 
+    // Note: Map is *pkg|conditionId* (see deletedRuleKey()) -> ZenRule,
+    // unlike automaticRules (which is id -> rule).
+    public final ArrayMap<String, ZenRule> deletedRules = new ArrayMap<>();
+
     @UnsupportedAppUsage
     public ZenModeConfig() {
     }
@@ -306,15 +319,9 @@
         allowMessagesFrom = source.readInt();
         user = source.readInt();
         manualRule = source.readParcelable(null, ZenRule.class);
-        final int len = source.readInt();
-        if (len > 0) {
-            final String[] ids = new String[len];
-            final ZenRule[] rules = new ZenRule[len];
-            source.readStringArray(ids);
-            source.readTypedArray(rules, ZenRule.CREATOR);
-            for (int i = 0; i < len; i++) {
-                automaticRules.put(ids[i], rules[i]);
-            }
+        readRulesFromParcel(automaticRules, source);
+        if (Flags.modesApi()) {
+            readRulesFromParcel(deletedRules, source);
         }
         allowAlarms = source.readInt() == 1;
         allowMedia = source.readInt() == 1;
@@ -328,6 +335,19 @@
         }
     }
 
+    private static void readRulesFromParcel(ArrayMap<String, ZenRule> ruleMap, Parcel source) {
+        final int len = source.readInt();
+        if (len > 0) {
+            final String[] ids = new String[len];
+            final ZenRule[] rules = new ZenRule[len];
+            source.readStringArray(ids);
+            source.readTypedArray(rules, ZenRule.CREATOR);
+            for (int i = 0; i < len; i++) {
+                ruleMap.put(ids[i], rules[i]);
+            }
+        }
+    }
+
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(allowCalls ? 1 : 0);
@@ -339,19 +359,9 @@
         dest.writeInt(allowMessagesFrom);
         dest.writeInt(user);
         dest.writeParcelable(manualRule, 0);
-        if (!automaticRules.isEmpty()) {
-            final int len = automaticRules.size();
-            final String[] ids = new String[len];
-            final ZenRule[] rules = new ZenRule[len];
-            for (int i = 0; i < len; i++) {
-                ids[i] = automaticRules.keyAt(i);
-                rules[i] = automaticRules.valueAt(i);
-            }
-            dest.writeInt(len);
-            dest.writeStringArray(ids);
-            dest.writeTypedArray(rules, 0);
-        } else {
-            dest.writeInt(0);
+        writeRulesToParcel(automaticRules, dest);
+        if (Flags.modesApi()) {
+            writeRulesToParcel(deletedRules, dest);
         }
         dest.writeInt(allowAlarms ? 1 : 0);
         dest.writeInt(allowMedia ? 1 : 0);
@@ -365,6 +375,23 @@
         }
     }
 
+    private static void writeRulesToParcel(ArrayMap<String, ZenRule> ruleMap, Parcel dest) {
+        if (!ruleMap.isEmpty()) {
+            final int len = ruleMap.size();
+            final String[] ids = new String[len];
+            final ZenRule[] rules = new ZenRule[len];
+            for (int i = 0; i < len; i++) {
+                ids[i] = ruleMap.keyAt(i);
+                rules[i] = ruleMap.valueAt(i);
+            }
+            dest.writeInt(len);
+            dest.writeStringArray(ids);
+            dest.writeTypedArray(rules, 0);
+        } else {
+            dest.writeInt(0);
+        }
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
@@ -389,23 +416,26 @@
         } else {
             sb.append(",areChannelsBypassingDnd=").append(areChannelsBypassingDnd);
         }
-        return sb.append(",\nautomaticRules=").append(rulesToString())
-                .append(",\nmanualRule=").append(manualRule)
-                .append(']').toString();
+        sb.append(",\nautomaticRules=").append(rulesToString(automaticRules))
+                .append(",\nmanualRule=").append(manualRule);
+        if (Flags.modesApi()) {
+            sb.append(",\ndeletedRules=").append(rulesToString(deletedRules));
+        }
+        return sb.append(']').toString();
     }
 
-    private String rulesToString() {
-        if (automaticRules.isEmpty()) {
+    private static String rulesToString(ArrayMap<String, ZenRule> ruleList) {
+        if (ruleList.isEmpty()) {
             return "{}";
         }
 
-        StringBuilder buffer = new StringBuilder(automaticRules.size() * 28);
+        StringBuilder buffer = new StringBuilder(ruleList.size() * 28);
         buffer.append("{\n");
-        for (int i = 0; i < automaticRules.size(); i++) {
+        for (int i = 0; i < ruleList.size(); i++) {
             if (i > 0) {
                 buffer.append(",\n");
             }
-            Object value = automaticRules.valueAt(i);
+            Object value = ruleList.valueAt(i);
             buffer.append(value);
         }
         buffer.append('}');
@@ -487,7 +517,9 @@
                 && other.allowConversations == allowConversations
                 && other.allowConversationsFrom == allowConversationsFrom;
         if (Flags.modesApi()) {
-            return eq && other.allowPriorityChannels == allowPriorityChannels;
+            return eq
+                    && Objects.equals(other.deletedRules, deletedRules)
+                    && other.allowPriorityChannels == allowPriorityChannels;
         }
         return eq;
     }
@@ -560,6 +592,10 @@
         }
     }
 
+    public static int getCurrentXmlVersion() {
+        return Flags.modesApi() ? XML_VERSION_MODES_API : XML_VERSION;
+    }
+
     public static ZenModeConfig readXml(TypedXmlPullParser parser)
             throws XmlPullParserException, IOException {
         int type = parser.getEventType();
@@ -567,7 +603,7 @@
         String tag = parser.getName();
         if (!ZEN_TAG.equals(tag)) return null;
         final ZenModeConfig rt = new ZenModeConfig();
-        rt.version = safeInt(parser, ZEN_ATT_VERSION, XML_VERSION);
+        rt.version = safeInt(parser, ZEN_ATT_VERSION, getCurrentXmlVersion());
         rt.user = safeInt(parser, ZEN_ATT_USER, rt.user);
         boolean readSuppressedEffects = false;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
@@ -644,12 +680,20 @@
                             DEFAULT_SUPPRESSED_VISUAL_EFFECTS);
                 } else if (MANUAL_TAG.equals(tag)) {
                     rt.manualRule = readRuleXml(parser);
-                } else if (AUTOMATIC_TAG.equals(tag)) {
+                } else if (AUTOMATIC_TAG.equals(tag)
+                        || (Flags.modesApi() && AUTOMATIC_DELETED_TAG.equals(tag))) {
                     final String id = parser.getAttributeValue(null, RULE_ATT_ID);
                     final ZenRule automaticRule = readRuleXml(parser);
                     if (id != null && automaticRule != null) {
                         automaticRule.id = id;
-                        rt.automaticRules.put(id, automaticRule);
+                        if (Flags.modesApi() && AUTOMATIC_DELETED_TAG.equals(tag)) {
+                            String deletedRuleKey = deletedRuleKey(automaticRule);
+                            if (deletedRuleKey != null) {
+                                rt.deletedRules.put(deletedRuleKey, automaticRule);
+                            }
+                        } else if (AUTOMATIC_TAG.equals(tag)) {
+                            rt.automaticRules.put(id, automaticRule);
+                        }
                     }
                 } else if (STATE_TAG.equals(tag)) {
                     rt.areChannelsBypassingDnd = safeBoolean(parser,
@@ -660,16 +704,29 @@
         throw new IllegalStateException("Failed to reach END_DOCUMENT");
     }
 
+    /** Generates the map key used for a {@link ZenRule} in {@link #deletedRules}. */
+    @Nullable
+    public static String deletedRuleKey(ZenRule rule) {
+        if (rule.pkg != null && rule.conditionId != null) {
+            return rule.pkg + "|" + rule.conditionId.toString();
+        } else {
+            return null;
+        }
+    }
+
     /**
      * Writes XML of current ZenModeConfig
      * @param out serializer
-     * @param version uses XML_VERSION if version is null
+     * @param version uses the current XML version if version is null
      * @throws IOException
      */
-    public void writeXml(TypedXmlSerializer out, Integer version) throws IOException {
+
+    public void writeXml(TypedXmlSerializer out, Integer version, boolean forBackup)
+            throws IOException {
+        int xmlVersion = getCurrentXmlVersion();
         out.startTag(null, ZEN_TAG);
         out.attribute(null, ZEN_ATT_VERSION, version == null
-                ? Integer.toString(XML_VERSION) : Integer.toString(version));
+                ? Integer.toString(xmlVersion) : Integer.toString(version));
         out.attributeInt(null, ZEN_ATT_USER, user);
         out.startTag(null, ALLOW_TAG);
         out.attributeBoolean(null, ALLOW_ATT_CALLS, allowCalls);
@@ -707,6 +764,15 @@
             writeRuleXml(automaticRule, out);
             out.endTag(null, AUTOMATIC_TAG);
         }
+        if (Flags.modesApi() && !forBackup) {
+            for (int i = 0; i < deletedRules.size(); i++) {
+                final ZenRule deletedRule = deletedRules.valueAt(i);
+                out.startTag(null, AUTOMATIC_DELETED_TAG);
+                out.attribute(null, RULE_ATT_ID, deletedRule.id);
+                writeRuleXml(deletedRule, out);
+                out.endTag(null, AUTOMATIC_DELETED_TAG);
+            }
+        }
 
         out.startTag(null, STATE_TAG);
         out.attributeBoolean(null, STATE_ATT_CHANNELS_BYPASSING_DND, areChannelsBypassingDnd);
@@ -752,6 +818,14 @@
             rt.triggerDescription = parser.getAttributeValue(null, RULE_ATT_TRIGGER_DESC);
             rt.type = safeInt(parser, RULE_ATT_TYPE, AutomaticZenRule.TYPE_UNKNOWN);
             rt.userModifiedFields = safeInt(parser, RULE_ATT_USER_MODIFIED_FIELDS, 0);
+            rt.zenPolicyUserModifiedFields = safeInt(parser, POLICY_USER_MODIFIED_FIELDS, 0);
+            rt.zenDeviceEffectsUserModifiedFields = safeInt(parser,
+                    DEVICE_EFFECT_USER_MODIFIED_FIELDS, 0);
+            Long deletionInstant = tryParseLong(
+                    parser.getAttributeValue(null, RULE_ATT_DELETION_INSTANT), null);
+            if (deletionInstant != null) {
+                rt.deletionInstant = Instant.ofEpochMilli(deletionInstant);
+            }
         }
         return rt;
     }
@@ -799,6 +873,13 @@
             }
             out.attributeInt(null, RULE_ATT_TYPE, rule.type);
             out.attributeInt(null, RULE_ATT_USER_MODIFIED_FIELDS, rule.userModifiedFields);
+            out.attributeInt(null, POLICY_USER_MODIFIED_FIELDS, rule.zenPolicyUserModifiedFields);
+            out.attributeInt(null, DEVICE_EFFECT_USER_MODIFIED_FIELDS,
+                    rule.zenDeviceEffectsUserModifiedFields);
+            if (rule.deletionInstant != null) {
+                out.attributeLong(null, RULE_ATT_DELETION_INSTANT,
+                        rule.deletionInstant.toEpochMilli());
+            }
         }
     }
 
@@ -856,12 +937,11 @@
         final int events = safeInt(parser, ALLOW_ATT_EVENTS, ZenPolicy.STATE_UNSET);
         final int reminders = safeInt(parser, ALLOW_ATT_REMINDERS, ZenPolicy.STATE_UNSET);
         if (Flags.modesApi()) {
-            final int channels = safeInt(parser, ALLOW_ATT_CHANNELS, ZenPolicy.CHANNEL_TYPE_UNSET);
-            if (channels != ZenPolicy.CHANNEL_TYPE_UNSET) {
-                builder.allowChannels(channels);
+            final int channels = safeInt(parser, ALLOW_ATT_CHANNELS, ZenPolicy.STATE_UNSET);
+            if (channels != ZenPolicy.STATE_UNSET) {
+                builder.allowPriorityChannels(channels == ZenPolicy.STATE_ALLOW);
                 policySet = true;
             }
-            builder.setUserModifiedFields(safeInt(parser, USER_MODIFIED_FIELDS, 0));
         }
 
         if (calls != ZenPolicy.PEOPLE_TYPE_UNSET) {
@@ -973,8 +1053,7 @@
                 out);
 
         if (Flags.modesApi()) {
-            writeZenPolicyState(ALLOW_ATT_CHANNELS, policy.getAllowedChannels(), out);
-            out.attributeInt(null, USER_MODIFIED_FIELDS, policy.getUserModifiedFields());
+            writeZenPolicyState(ALLOW_ATT_CHANNELS, policy.getPriorityChannelsAllowed(), out);
         }
     }
 
@@ -990,7 +1069,7 @@
                 out.attributeInt(null, attr, val);
             }
         } else if (Flags.modesApi() && Objects.equals(attr, ALLOW_ATT_CHANNELS)) {
-            if (val != ZenPolicy.CHANNEL_TYPE_UNSET) {
+            if (val != ZenPolicy.STATE_UNSET) {
                 out.attributeInt(null, attr, val);
             }
         } else {
@@ -1020,7 +1099,6 @@
                 .setShouldMinimizeRadioUsage(
                         safeBoolean(parser, DEVICE_EFFECT_MINIMIZE_RADIO_USAGE, false))
                 .setShouldMaximizeDoze(safeBoolean(parser, DEVICE_EFFECT_MAXIMIZE_DOZE, false))
-                .setUserModifiedFields(safeInt(parser, DEVICE_EFFECT_USER_MODIFIED_FIELDS, 0))
                 .build();
 
         return deviceEffects.hasEffects() ? deviceEffects : null;
@@ -1045,8 +1123,6 @@
         writeBooleanIfTrue(out, DEVICE_EFFECT_MINIMIZE_RADIO_USAGE,
                 deviceEffects.shouldMinimizeRadioUsage());
         writeBooleanIfTrue(out, DEVICE_EFFECT_MAXIMIZE_DOZE, deviceEffects.shouldMaximizeDoze());
-        out.attributeInt(null, DEVICE_EFFECT_USER_MODIFIED_FIELDS,
-                deviceEffects.getUserModifiedFields());
     }
 
     private static void writeBooleanIfTrue(TypedXmlSerializer out, String att, boolean value)
@@ -1175,8 +1251,7 @@
         }
 
         if (Flags.modesApi()) {
-            builder.allowChannels(allowPriorityChannels ? ZenPolicy.CHANNEL_TYPE_PRIORITY
-                    : ZenPolicy.CHANNEL_TYPE_NONE);
+            builder.allowPriorityChannels(allowPriorityChannels);
         }
         return builder.build();
     }
@@ -1306,7 +1381,7 @@
         int state = defaultPolicy.state;
         if (Flags.modesApi()) {
             state = Policy.policyState(defaultPolicy.hasPriorityChannels(),
-                    getAllowPriorityChannelsWithDefault(zenPolicy.getAllowedChannels(),
+                    ZenPolicy.stateToBoolean(zenPolicy.getPriorityChannelsAllowed(),
                             DEFAULT_ALLOW_PRIORITY_CHANNELS));
         }
 
@@ -1349,24 +1424,6 @@
     }
 
     /**
-     * Gets whether priority channels are permitted by this channel type, with the specified
-     * default if the value is unset. This effectively converts the channel enum to a boolean,
-     * where "true" indicates priority channels are allowed to break through and "false" means
-     * they are not.
-     */
-    public static boolean getAllowPriorityChannelsWithDefault(
-            @ZenPolicy.ChannelType int channelType, boolean defaultAllowChannels) {
-        switch (channelType) {
-            case ZenPolicy.CHANNEL_TYPE_PRIORITY:
-                return true;
-            case ZenPolicy.CHANNEL_TYPE_NONE:
-                return false;
-            default:
-                return defaultAllowChannels;
-        }
-    }
-
-    /**
      * Maps NotificationManager.Policy senders type to ZenPolicy.PeopleType
      */
     public static @ZenPolicy.PeopleType int getZenPolicySenders(int senders) {
@@ -1997,7 +2054,10 @@
         public String triggerDescription;
         public String iconResName;
         public boolean allowManualInvocation;
-        public int userModifiedFields;
+        @AutomaticZenRule.ModifiableField public int userModifiedFields;
+        @ZenPolicy.ModifiableField public int zenPolicyUserModifiedFields;
+        @ZenDeviceEffects.ModifiableField public int zenDeviceEffectsUserModifiedFields;
+        @Nullable public Instant deletionInstant; // Only set on deleted rules.
 
         public ZenRule() { }
 
@@ -2031,19 +2091,30 @@
                 triggerDescription = source.readString();
                 type = source.readInt();
                 userModifiedFields = source.readInt();
+                zenPolicyUserModifiedFields = source.readInt();
+                zenDeviceEffectsUserModifiedFields = source.readInt();
+                if (source.readInt() == 1) {
+                    deletionInstant = Instant.ofEpochMilli(source.readLong());
+                }
             }
         }
 
         /**
-         * @see AutomaticZenRule#canUpdate()
+         * Whether this ZenRule can be updated by an app. In general, rules that have been
+         * customized by the user cannot be further updated by an app, with some exceptions:
+         * <ul>
+         *     <li>Non user-configurable fields, like type, icon, configurationActivity, etc.
+         *     <li>Name, if the name was not specifically modified by the user (to support language
+         *          switches).
+         * </ul>
          */
         @FlaggedApi(Flags.FLAG_MODES_API)
         public boolean canBeUpdatedByApp() {
             // The rule is considered updateable if its bitmask has no user modifications, and
             // the bitmasks of the policy and device effects have no modification.
             return userModifiedFields == 0
-                    && (zenPolicy == null || zenPolicy.getUserModifiedFields() == 0)
-                    && (zenDeviceEffects == null || zenDeviceEffects.getUserModifiedFields() == 0);
+                    && zenPolicyUserModifiedFields == 0
+                    && zenDeviceEffectsUserModifiedFields == 0;
         }
 
         @Override
@@ -2091,6 +2162,14 @@
                 dest.writeString(triggerDescription);
                 dest.writeInt(type);
                 dest.writeInt(userModifiedFields);
+                dest.writeInt(zenPolicyUserModifiedFields);
+                dest.writeInt(zenDeviceEffectsUserModifiedFields);
+                if (deletionInstant != null) {
+                    dest.writeInt(1);
+                    dest.writeLong(deletionInstant.toEpochMilli());
+                } else {
+                    dest.writeInt(0);
+                }
             }
         }
 
@@ -2119,8 +2198,23 @@
                         .append(",allowManualInvocation=").append(allowManualInvocation)
                         .append(",iconResName=").append(iconResName)
                         .append(",triggerDescription=").append(triggerDescription)
-                        .append(",type=").append(type)
-                        .append(",userModifiedFields=").append(userModifiedFields);
+                        .append(",type=").append(type);
+                if (userModifiedFields != 0) {
+                    sb.append(",userModifiedFields=")
+                            .append(AutomaticZenRule.fieldsToString(userModifiedFields));
+                }
+                if (zenPolicyUserModifiedFields != 0) {
+                    sb.append(",zenPolicyUserModifiedFields=")
+                            .append(ZenPolicy.fieldsToString(zenPolicyUserModifiedFields));
+                }
+                if (zenDeviceEffectsUserModifiedFields != 0) {
+                    sb.append(",zenDeviceEffectsUserModifiedFields=")
+                            .append(ZenDeviceEffects.fieldsToString(
+                                    zenDeviceEffectsUserModifiedFields));
+                }
+                if (deletionInstant != null) {
+                    sb.append(",deletionInstant=").append(deletionInstant);
+                }
             }
 
             return sb.append(']').toString();
@@ -2180,7 +2274,11 @@
                         && Objects.equals(other.iconResName, iconResName)
                         && Objects.equals(other.triggerDescription, triggerDescription)
                         && other.type == type
-                        && other.userModifiedFields == userModifiedFields;
+                        && other.userModifiedFields == userModifiedFields
+                        && other.zenPolicyUserModifiedFields == zenPolicyUserModifiedFields
+                        && other.zenDeviceEffectsUserModifiedFields
+                            == zenDeviceEffectsUserModifiedFields
+                        && Objects.equals(other.deletionInstant, deletionInstant);
             }
 
             return finalEquals;
@@ -2192,12 +2290,25 @@
                 return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
                         component, configurationActivity, pkg, id, enabler, zenPolicy,
                         zenDeviceEffects, modified, allowManualInvocation, iconResName,
-                        triggerDescription, type, userModifiedFields);
+                        triggerDescription, type, userModifiedFields, zenPolicyUserModifiedFields,
+                        zenDeviceEffectsUserModifiedFields, deletionInstant);
             }
             return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
                     component, configurationActivity, pkg, id, enabler, zenPolicy, modified);
         }
 
+        /** Returns a deep copy of the {@link ZenRule}. */
+        public ZenRule copy() {
+            final Parcel parcel = Parcel.obtain();
+            try {
+                writeToParcel(parcel, 0);
+                parcel.setDataPosition(0);
+                return new ZenRule(parcel);
+            } finally {
+                parcel.recycle();
+            }
+        }
+
         public boolean isAutomaticActive() {
             return enabled && !snoozing && getPkg() != null && isTrueOrUnknown();
         }
diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java
index 8902368..91ef11c 100644
--- a/core/java/android/service/notification/ZenModeDiff.java
+++ b/core/java/android/service/notification/ZenModeDiff.java
@@ -30,6 +30,11 @@
 /**
  * ZenModeDiff is a utility class meant to encapsulate the diff between ZenModeConfigs and their
  * subcomponents (automatic and manual ZenRules).
+ *
+ * <p>Note that this class is intended to detect <em>meaningful</em> differences, so objects that
+ * are not identical (as per their {@code equals()} implementation) can still produce an empty diff
+ * if only "metadata" fields are updated.
+ *
  * @hide
  */
 public class ZenModeDiff {
@@ -467,7 +472,6 @@
         public static final String FIELD_ICON_RES = "iconResName";
         public static final String FIELD_TRIGGER_DESCRIPTION = "triggerDescription";
         public static final String FIELD_TYPE = "type";
-        public static final String FIELD_USER_MODIFIED_FIELDS = "userModifiedFields";
         // NOTE: new field strings must match the variable names in ZenModeConfig.ZenRule
 
         // Special field to track whether this rule became active or inactive
@@ -563,10 +567,6 @@
                 if (!Objects.equals(from.iconResName, to.iconResName)) {
                     addField(FIELD_ICON_RES, new FieldDiff<>(from.iconResName, to.iconResName));
                 }
-                if (from.userModifiedFields != to.userModifiedFields) {
-                    addField(FIELD_USER_MODIFIED_FIELDS,
-                            new FieldDiff<>(from.userModifiedFields, to.userModifiedFields));
-                }
             }
         }
 
diff --git a/core/java/android/service/notification/ZenPolicy.aidl b/core/java/android/service/notification/ZenPolicy.aidl
new file mode 100644
index 0000000..b56f5c6
--- /dev/null
+++ b/core/java/android/service/notification/ZenPolicy.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.notification;
+
+parcelable ZenPolicy;
\ No newline at end of file
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index 8477eb7..786d768 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -45,8 +45,8 @@
  */
 public final class ZenPolicy implements Parcelable {
 
-    /** Used to track which rule variables have been modified by the user.
-     * Should be checked against the bitmask {@link #getUserModifiedFields()}.
+    /**
+     * Enum for the user-modifiable fields in this object.
      * @hide
      */
     @IntDef(flag = true, prefix = { "FIELD_" }, value = {
@@ -76,7 +76,6 @@
      * the same time.
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_MESSAGES = 1 << 0;
     /**
@@ -84,7 +83,6 @@
      * the same time.
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_CALLS = 1 << 1;
     /**
@@ -92,13 +90,11 @@
      * set at the same time.
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_CONVERSATIONS = 1 << 2;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_ALLOW_CHANNELS = 1 << 3;
     /**
@@ -109,73 +105,61 @@
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_PRIORITY_CATEGORY_EVENTS = 1 << 5;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS = 1 << 6;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_PRIORITY_CATEGORY_ALARMS = 1 << 7;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_PRIORITY_CATEGORY_MEDIA = 1 << 8;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_PRIORITY_CATEGORY_SYSTEM = 1 << 9;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT = 1 << 10;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_VISUAL_EFFECT_LIGHTS = 1 << 11;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_VISUAL_EFFECT_PEEK = 1 << 12;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_VISUAL_EFFECT_STATUS_BAR = 1 << 13;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_VISUAL_EFFECT_BADGE = 1 << 14;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_VISUAL_EFFECT_AMBIENT = 1 << 15;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_VISUAL_EFFECT_NOTIFICATION_LIST = 1 << 16;
 
@@ -184,8 +168,8 @@
     private @PeopleType int mPriorityMessages = PEOPLE_TYPE_UNSET;
     private @PeopleType int mPriorityCalls = PEOPLE_TYPE_UNSET;
     private @ConversationSenders int mConversationSenders = CONVERSATION_SENDERS_UNSET;
-    private @ChannelType int mAllowChannels = CHANNEL_TYPE_UNSET;
-    private final @ModifiableField int mUserModifiedFields; // Bitwise representation
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    private @ChannelType int mAllowChannels = CHANNEL_POLICY_UNSET;
 
     /** @hide */
     @IntDef(prefix = { "PRIORITY_CATEGORY_" }, value = {
@@ -354,56 +338,52 @@
      */
     public static final int STATE_DISALLOW = 2;
 
-    /** @hide */
-    @IntDef(prefix = { "CHANNEL_TYPE_" }, value = {
-            CHANNEL_TYPE_UNSET,
-            CHANNEL_TYPE_PRIORITY,
-            CHANNEL_TYPE_NONE,
+    @IntDef(prefix = { "CHANNEL_POLICY_" }, value = {
+            CHANNEL_POLICY_UNSET,
+            CHANNEL_POLICY_PRIORITY,
+            CHANNEL_POLICY_NONE,
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface ChannelType {}
+    private @interface ChannelType {}
 
     /**
      * Indicates no explicit setting for which channels may bypass DND when this policy is active.
-     * Defaults to {@link #CHANNEL_TYPE_PRIORITY}.
+     * Defaults to {@link #CHANNEL_POLICY_PRIORITY}.
      */
     @FlaggedApi(Flags.FLAG_MODES_API)
-    public static final int CHANNEL_TYPE_UNSET = 0;
+    private static final int CHANNEL_POLICY_UNSET = 0;
 
     /**
      * Indicates that channels marked as {@link NotificationChannel#canBypassDnd()} can bypass DND
      * when this policy is active.
      */
     @FlaggedApi(Flags.FLAG_MODES_API)
-    public static final int CHANNEL_TYPE_PRIORITY = 1;
+    private static final int CHANNEL_POLICY_PRIORITY = 1;
 
     /**
      * Indicates that no channels can bypass DND when this policy is active, even those marked as
      * {@link NotificationChannel#canBypassDnd()}.
      */
     @FlaggedApi(Flags.FLAG_MODES_API)
-    public static final int CHANNEL_TYPE_NONE = 2;
+    private static final int CHANNEL_POLICY_NONE = 2;
 
     /** @hide */
     public ZenPolicy() {
         mPriorityCategories = new ArrayList<>(Collections.nCopies(NUM_PRIORITY_CATEGORIES, 0));
         mVisualEffects = new ArrayList<>(Collections.nCopies(NUM_VISUAL_EFFECTS, 0));
-        mUserModifiedFields = 0;
     }
 
     /** @hide */
     @FlaggedApi(Flags.FLAG_MODES_API)
     public ZenPolicy(List<Integer> priorityCategories, List<Integer> visualEffects,
                      @PeopleType int priorityMessages, @PeopleType int priorityCalls,
-                     @ConversationSenders int conversationSenders, @ChannelType int allowChannels,
-                     @ModifiableField int userModifiedFields) {
+                     @ConversationSenders int conversationSenders, @ChannelType int allowChannels) {
         mPriorityCategories = priorityCategories;
         mVisualEffects = visualEffects;
         mPriorityMessages = priorityMessages;
         mPriorityCalls = priorityCalls;
         mConversationSenders = conversationSenders;
         mAllowChannels = allowChannels;
-        mUserModifiedFields = userModifiedFields;
     }
 
     /**
@@ -584,16 +564,21 @@
     }
 
     /**
-     * Which types of {@link NotificationChannel channels} this policy allows to bypass DND. When
-     * this value is {@link #CHANNEL_TYPE_PRIORITY priority} channels, any channel with
-     * canBypassDnd() may bypass DND; when it is {@link #CHANNEL_TYPE_NONE none}, even channels
-     * with canBypassDnd() will be intercepted.
-     * @return {@link #CHANNEL_TYPE_UNSET}, {@link #CHANNEL_TYPE_PRIORITY}, or
-     *   {@link #CHANNEL_TYPE_NONE}
+     * Whether this policy allows {@link NotificationChannel channels} marked as
+     * {@link NotificationChannel#canBypassDnd()} to bypass DND. If {@link #STATE_ALLOW}, these
+     * channels may bypass; if {@link #STATE_DISALLOW}, then even notifications from channels
+     * with {@link NotificationChannel#canBypassDnd()} will be intercepted.
      */
     @FlaggedApi(Flags.FLAG_MODES_API)
-    public @ChannelType int getAllowedChannels() {
-        return mAllowChannels;
+    public @State int getPriorityChannelsAllowed() {
+        switch (mAllowChannels) {
+            case CHANNEL_POLICY_PRIORITY:
+                return STATE_ALLOW;
+            case CHANNEL_POLICY_NONE:
+                return STATE_DISALLOW;
+            default:
+                return STATE_UNSET;
+        }
     }
 
     /**
@@ -628,8 +613,6 @@
      * is not set, it is (@link STATE_UNSET} and will not change the current set policy.
      */
     public static final class Builder {
-        private @ModifiableField int mUserModifiedFields;
-
         private ZenPolicy mZenPolicy;
 
         public Builder() {
@@ -644,9 +627,6 @@
         public Builder(@Nullable ZenPolicy policy) {
             if (policy != null) {
                 mZenPolicy = policy.copy();
-                if (Flags.modesApi()) {
-                    mUserModifiedFields = policy.mUserModifiedFields;
-                }
             } else {
                 mZenPolicy = new ZenPolicy();
             }
@@ -657,11 +637,10 @@
          */
         public @NonNull ZenPolicy build() {
             if (Flags.modesApi()) {
-                return new ZenPolicy(new ArrayList<Integer>(mZenPolicy.mPriorityCategories),
-                        new ArrayList<Integer>(mZenPolicy.mVisualEffects),
+                return new ZenPolicy(new ArrayList<>(mZenPolicy.mPriorityCategories),
+                        new ArrayList<>(mZenPolicy.mVisualEffects),
                         mZenPolicy.mPriorityMessages, mZenPolicy.mPriorityCalls,
-                        mZenPolicy.mConversationSenders, mZenPolicy.mAllowChannels,
-                        mUserModifiedFields);
+                        mZenPolicy.mConversationSenders, mZenPolicy.mAllowChannels);
             } else {
                 return mZenPolicy.copy();
             }
@@ -1016,32 +995,10 @@
          * Set whether priority channels are permitted to break through DND.
          */
         @FlaggedApi(Flags.FLAG_MODES_API)
-        public @NonNull Builder allowChannels(@ChannelType int channelType) {
-            mZenPolicy.mAllowChannels = channelType;
+        public @NonNull Builder allowPriorityChannels(boolean allow) {
+            mZenPolicy.mAllowChannels = allow ? CHANNEL_POLICY_PRIORITY : CHANNEL_POLICY_NONE;
             return this;
         }
-
-        /**
-         * Sets the user modified fields bitmask.
-         * @hide
-         */
-        @TestApi
-        @FlaggedApi(Flags.FLAG_MODES_API)
-        public @NonNull Builder setUserModifiedFields(@ModifiableField int userModifiedFields) {
-            mUserModifiedFields = userModifiedFields;
-            return this;
-        }
-    }
-
-    /**
-     Gets the bitmask representing which fields are user modified. Bits are set using
-     * {@link ModifiableField}.
-     * @hide
-     */
-    @TestApi
-    @FlaggedApi(Flags.FLAG_MODES_API)
-    public @ModifiableField int getUserModifiedFields() {
-        return mUserModifiedFields;
     }
 
     @Override
@@ -1058,7 +1015,6 @@
         dest.writeInt(mConversationSenders);
         if (Flags.modesApi()) {
             dest.writeInt(mAllowChannels);
-            dest.writeInt(mUserModifiedFields);
         }
     }
 
@@ -1074,7 +1030,7 @@
                                 trimList(source.readArrayList(Integer.class.getClassLoader(),
                                         Integer.class), NUM_VISUAL_EFFECTS),
                                 source.readInt(), source.readInt(), source.readInt(),
-                                source.readInt(), source.readInt()
+                                source.readInt()
                         );
                     } else {
                         policy = new ZenPolicy();
@@ -1109,14 +1065,12 @@
                         conversationTypeToString(mConversationSenders));
         if (Flags.modesApi()) {
             sb.append(", allowChannels=").append(channelTypeToString(mAllowChannels));
-            sb.append(", userModifiedFields=")
-                    .append(modifiedFieldsToString(mUserModifiedFields));
         }
         return sb.append('}').toString();
     }
 
-    @FlaggedApi(Flags.FLAG_MODES_API)
-    private String modifiedFieldsToString(@ModifiableField int bitmask) {
+    /** @hide */
+    public static String fieldsToString(@ModifiableField int bitmask) {
         ArrayList<String> modified = new ArrayList<>();
         if ((bitmask & FIELD_MESSAGES) != 0) {
             modified.add("FIELD_MESSAGES");
@@ -1305,11 +1259,11 @@
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static String channelTypeToString(@ChannelType int channelType) {
         switch (channelType) {
-            case CHANNEL_TYPE_UNSET:
+            case CHANNEL_POLICY_UNSET:
                 return "unset";
-            case CHANNEL_TYPE_PRIORITY:
+            case CHANNEL_POLICY_PRIORITY:
                 return "priority";
-            case CHANNEL_TYPE_NONE:
+            case CHANNEL_POLICY_NONE:
                 return "none";
         }
         return "invalidChannelType{" + channelType + "}";
@@ -1327,8 +1281,7 @@
                 && other.mPriorityMessages == mPriorityMessages
                 && other.mConversationSenders == mConversationSenders;
         if (Flags.modesApi()) {
-            return eq && other.mAllowChannels == mAllowChannels
-                    && other.mUserModifiedFields == mUserModifiedFields;
+            return eq && other.mAllowChannels == mAllowChannels;
         }
         return eq;
     }
@@ -1337,7 +1290,7 @@
     public int hashCode() {
         if (Flags.modesApi()) {
             return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls,
-                    mPriorityMessages, mConversationSenders, mAllowChannels, mUserModifiedFields);
+                    mPriorityMessages, mConversationSenders, mAllowChannels);
         }
         return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls, mPriorityMessages,
                 mConversationSenders);
@@ -1389,11 +1342,11 @@
     }
 
     /** @hide */
-    public boolean isCategoryAllowed(@PriorityCategory int category, boolean defaultVal) {
-        switch (getZenPolicyPriorityCategoryState(category)) {
-            case ZenPolicy.STATE_ALLOW:
+    public static boolean stateToBoolean(@State int state, boolean defaultVal) {
+        switch (state) {
+            case STATE_ALLOW:
                 return true;
-            case ZenPolicy.STATE_DISALLOW:
+            case STATE_DISALLOW:
                 return false;
             default:
                 return defaultVal;
@@ -1401,15 +1354,13 @@
     }
 
     /** @hide */
+    public boolean isCategoryAllowed(@PriorityCategory int category, boolean defaultVal) {
+        return stateToBoolean(getZenPolicyPriorityCategoryState(category), defaultVal);
+    }
+
+    /** @hide */
     public boolean isVisualEffectAllowed(@VisualEffect int effect, boolean defaultVal) {
-        switch (getZenPolicyVisualEffectState(effect)) {
-            case ZenPolicy.STATE_ALLOW:
-                return true;
-            case ZenPolicy.STATE_DISALLOW:
-                return false;
-            default:
-                return defaultVal;
-        }
+        return stateToBoolean(getZenPolicyVisualEffectState(effect), defaultVal);
     }
 
     /**
@@ -1463,14 +1414,62 @@
         // apply allowed channels
         if (Flags.modesApi()) {
             // if no channels are allowed, can't newly allow them
-            if (mAllowChannels != CHANNEL_TYPE_NONE
-                    && policyToApply.mAllowChannels != CHANNEL_TYPE_UNSET) {
+            if (mAllowChannels != CHANNEL_POLICY_NONE
+                    && policyToApply.mAllowChannels != CHANNEL_POLICY_UNSET) {
                 mAllowChannels = policyToApply.mAllowChannels;
             }
         }
     }
 
     /**
+     * Overwrites any policy values in this ZenPolicy with set values from newPolicy and
+     * returns a copy of the resulting ZenPolicy.
+     * Unlike apply(), values set in newPolicy will always be kept over pre-existing
+     * fields. Any values in newPolicy that are not set keep their currently set values.
+     *
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public @NonNull ZenPolicy overwrittenWith(@Nullable ZenPolicy newPolicy) {
+        ZenPolicy result = this.copy();
+
+        if (newPolicy == null) {
+            return result;
+        }
+
+        // set priority categories
+        for (int category = 0; category < mPriorityCategories.size(); category++) {
+            @State int newState = newPolicy.mPriorityCategories.get(category);
+            if (newState != STATE_UNSET) {
+                result.mPriorityCategories.set(category, newState);
+
+                if (category == PRIORITY_CATEGORY_MESSAGES) {
+                    result.mPriorityMessages = newPolicy.mPriorityMessages;
+                } else if (category == PRIORITY_CATEGORY_CALLS) {
+                    result.mPriorityCalls = newPolicy.mPriorityCalls;
+                } else if (category == PRIORITY_CATEGORY_CONVERSATIONS) {
+                    result.mConversationSenders = newPolicy.mConversationSenders;
+                }
+            }
+        }
+
+        // set visual effects
+        for (int visualEffect = 0; visualEffect < mVisualEffects.size(); visualEffect++) {
+            if (newPolicy.mVisualEffects.get(visualEffect) != STATE_UNSET) {
+                result.mVisualEffects.set(visualEffect, newPolicy.mVisualEffects.get(visualEffect));
+            }
+        }
+
+        // set allowed channels
+        if (newPolicy.mAllowChannels != CHANNEL_POLICY_UNSET) {
+            result.mAllowChannels = newPolicy.mAllowChannels;
+        }
+
+        return result;
+    }
+
+    /**
      * @hide
      */
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
@@ -1530,7 +1529,7 @@
         proto.write(DNDPolicyProto.ALLOW_CONVERSATIONS_FROM, getPriorityConversationSenders());
 
         if (Flags.modesApi()) {
-            proto.write(DNDPolicyProto.ALLOW_CHANNELS, getAllowedChannels());
+            proto.write(DNDPolicyProto.ALLOW_CHANNELS, getPriorityChannelsAllowed());
         }
 
         proto.flush();
diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig
index a2ade6a..3008b8d 100644
--- a/core/java/android/service/notification/flags.aconfig
+++ b/core/java/android/service/notification/flags.aconfig
@@ -21,3 +21,11 @@
   description: "This flag controls the redacting of sensitive notifications from untrusted NotificationListenerServices"
   bug: "306271190"
 }
+
+flag {
+  name: "callstyle_callback_api"
+  namespace: "systemui"
+  description: "Guards the new CallStyleNotificationEventsCallback"
+  bug: "305095040"
+  is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
index ce38bb8..e6d8fd0 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
@@ -19,6 +19,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.role.RoleManager;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -30,6 +31,7 @@
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
+import android.os.Binder;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.AttributeSet;
@@ -69,12 +71,17 @@
 
     @Nullable
     static QuickAccessWalletServiceInfo tryCreate(@NonNull Context context) {
-        ComponentName defaultPaymentApp = getDefaultPaymentApp(context);
-        if (defaultPaymentApp == null) {
-            return null;
+        String defaultAppPackageName = getDefaultWalletApp(context);
+
+        if (defaultAppPackageName == null) {
+            ComponentName defaultPaymentApp = getDefaultPaymentApp(context);
+            if (defaultPaymentApp == null) {
+                return null;
+            }
+            defaultAppPackageName = defaultPaymentApp.getPackageName();
         }
 
-        ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultPaymentApp.getPackageName());
+        ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultAppPackageName);
         if (serviceInfo == null) {
             return null;
         }
@@ -92,6 +99,20 @@
         return new QuickAccessWalletServiceInfo(serviceInfo, metadata, tileServiceMetadata);
     }
 
+    private static String getDefaultWalletApp(Context context) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            RoleManager roleManager = context.getSystemService(RoleManager.class);
+            if (roleManager.isRoleAvailable(RoleManager.ROLE_WALLET)) {
+                List<String> roleHolders = roleManager.getRoleHolders(RoleManager.ROLE_WALLET);
+                return roleHolders.isEmpty() ? null : roleHolders.get(0);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return null;
+    }
+
     private static ComponentName getDefaultPaymentApp(Context context) {
         ContentResolver cr = context.getContentResolver();
         String comp = Settings.Secure.getString(cr, Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT);
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index adc54f5..f2bdbf6 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -325,7 +325,7 @@
             Slog.v(TAG, "BinderCallback#onQueryDetected");
             Binder.withCleanCallingIdentity(() -> {
                 synchronized (mLock) {
-                    mCallback.onQueryDetected(partialQuery);
+                    mExecutor.execute(()->mCallback.onQueryDetected(partialQuery));
                 }
             });
         }
@@ -335,7 +335,7 @@
             Slog.v(TAG, "BinderCallback#onQueryFinished");
             Binder.withCleanCallingIdentity(() -> {
                 synchronized (mLock) {
-                    mCallback.onQueryFinished();
+                    mExecutor.execute(()->mCallback.onQueryFinished());
                 }
             });
         }
@@ -345,7 +345,7 @@
             Slog.v(TAG, "BinderCallback#onQueryRejected");
             Binder.withCleanCallingIdentity(() -> {
                 synchronized (mLock) {
-                    mCallback.onQueryRejected();
+                    mExecutor.execute(()->mCallback.onQueryRejected());
                 }
             });
         }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 1a2be15..bbda068 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -16,6 +16,7 @@
 
 package android.service.wallpaper;
 
+import static android.app.WallpaperManager.COMMAND_DISPLAY_SWITCH;
 import static android.app.WallpaperManager.COMMAND_FREEZE;
 import static android.app.WallpaperManager.COMMAND_UNFREEZE;
 import static android.app.WallpaperManager.SetWallpaperFlags;
@@ -153,6 +154,7 @@
     static final boolean DEBUG = false;
     static final float MIN_PAGE_ALLOWED_MARGIN = .05f;
     private static final int MIN_BITMAP_SCREENSHOT_WIDTH = 64;
+    private static final long PRESERVE_VISIBLE_TIMEOUT_MS = 1000;
     private static final long DEFAULT_UPDATE_SCREENSHOT_DURATION = 60 * 1000; //Once per minute
     private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
             new RectF(0, 0, 1, 1);
@@ -165,6 +167,7 @@
 
     private static final int MSG_UPDATE_SURFACE = 10000;
     private static final int MSG_VISIBILITY_CHANGED = 10010;
+    private static final int MSG_REFRESH_VISIBILITY = 10011;
     private static final int MSG_WALLPAPER_OFFSETS = 10020;
     private static final int MSG_WALLPAPER_COMMAND = 10025;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -248,6 +251,11 @@
          */
         private boolean mIsScreenTurningOn;
         boolean mReportedVisible;
+        /**
+         * This is used with {@link #PRESERVE_VISIBLE_TIMEOUT_MS} to avoid intermediate visibility
+         * changes if the display may be toggled in a short time, e.g. display switch.
+         */
+        boolean mPreserveVisible;
         boolean mDestroyed;
         // Set to true after receiving WallpaperManager#COMMAND_FREEZE. It's reset back to false
         // after receiving WallpaperManager#COMMAND_UNFREEZE. COMMAND_FREEZE is fully applied once
@@ -263,7 +271,6 @@
         boolean mDrawingAllowed;
         boolean mOffsetsChanged;
         boolean mFixedSizeAllowed;
-        boolean mShouldDim;
         // Whether the wallpaper should be dimmed by default (when no additional dimming is applied)
         // based on its color hints
         boolean mShouldDimByDefault;
@@ -340,9 +347,11 @@
         private Display mDisplay;
         private Context mDisplayContext;
         private int mDisplayState;
-        private float mWallpaperDimAmount = 0.05f;
+
+        private float mCustomDimAmount = 0f;
+        private float mWallpaperDimAmount = 0f;
         private float mPreviousWallpaperDimAmount = mWallpaperDimAmount;
-        private float mDefaultDimAmount = mWallpaperDimAmount;
+        private float mDefaultDimAmount = 0.05f;
 
         SurfaceControl mSurfaceControl = new SurfaceControl();
         SurfaceControl mBbqSurfaceControl;
@@ -978,11 +987,8 @@
             mShouldDimByDefault = ((colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0
                     && (colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) == 0);
 
-            // If default dimming value changes and no additional dimming is applied
-            if (mShouldDimByDefault != mShouldDim && mWallpaperDimAmount == 0f) {
-                mShouldDim = mShouldDimByDefault;
-                updateSurfaceDimming();
-            }
+            // Recompute dim in case it changed compared to the previous WallpaperService
+            updateWallpaperDimming(mCustomDimAmount);
         }
 
         /**
@@ -991,28 +997,21 @@
          * @param dimAmount Float amount between [0.0, 1.0] to dim the wallpaper.
          */
         private void updateWallpaperDimming(float dimAmount) {
-            if (dimAmount == mWallpaperDimAmount) {
-                return;
-            }
+            mCustomDimAmount = Math.min(1f, dimAmount);
 
-            // Custom dim amount cannot be less than the default dim amount.
-            mWallpaperDimAmount = Math.max(mDefaultDimAmount, dimAmount);
-            // If dim amount is 0f (additional dimming is removed), then the wallpaper should dim
-            // based on its default wallpaper color hints.
-            mShouldDim = dimAmount != 0f || mShouldDimByDefault;
-            updateSurfaceDimming();
-        }
+            // If default dim is enabled, the actual dim amount is at least the default dim amount
+            mWallpaperDimAmount = (!mShouldDimByDefault) ? mCustomDimAmount
+                    : Math.max(mDefaultDimAmount, mCustomDimAmount);
 
-        private void updateSurfaceDimming() {
-            if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null) {
+            if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null
+                    || mWallpaperDimAmount == mPreviousWallpaperDimAmount) {
                 return;
             }
 
             SurfaceControl.Transaction surfaceControlTransaction = new SurfaceControl.Transaction();
             // TODO: apply the dimming to preview as well once surface transparency works in
             // preview mode.
-            if ((!isPreview() && mShouldDim)
-                    || mPreviousWallpaperDimAmount != mWallpaperDimAmount) {
+            if (!isPreview()) {
                 Log.v(TAG, "Setting wallpaper dimming: " + mWallpaperDimAmount);
 
                 // Animate dimming to gradually change the wallpaper alpha from the previous
@@ -1084,6 +1083,9 @@
             if (pendingCount != 0) {
                 out.print(prefix); out.print("mPendingResizeCount="); out.println(pendingCount);
             }
+            if (mPreserveVisible) {
+                out.print(prefix); out.print("mPreserveVisible=true");
+            }
             synchronized (mLock) {
                 out.print(prefix); out.print("mPendingXOffset="); out.print(mPendingXOffset);
                         out.print(" mPendingXOffset="); out.println(mPendingXOffset);
@@ -1534,8 +1536,6 @@
                     .createWindowContext(TYPE_WALLPAPER, null /* options */);
             mDefaultDimAmount = mDisplayContext.getResources().getFloat(
                     com.android.internal.R.dimen.config_wallpaperDimAmount);
-            mWallpaperDimAmount = mDefaultDimAmount;
-            mPreviousWallpaperDimAmount = mWallpaperDimAmount;
             mDisplayState = mDisplay.getCommittedState();
             mMergedConfiguration.setOverrideConfiguration(
                     mDisplayContext.getResources().getConfiguration());
@@ -1643,7 +1643,8 @@
                                 ? false
                                 : mIWallpaperEngine.mInfo.supportsAmbientMode();
                 // Report visibility only if display is fully on or wallpaper supports ambient mode.
-                boolean visible = mVisible && (displayFullyOn || supportsAmbientMode);
+                final boolean visible = (mVisible && (displayFullyOn || supportsAmbientMode))
+                        || mPreserveVisible;
                 if (DEBUG) {
                     Log.v(
                             TAG,
@@ -2080,6 +2081,9 @@
             if (!mDestroyed) {
                 if (COMMAND_FREEZE.equals(cmd.action) || COMMAND_UNFREEZE.equals(cmd.action)) {
                     updateFrozenState(/* frozenRequested= */ !COMMAND_UNFREEZE.equals(cmd.action));
+                } else if (COMMAND_DISPLAY_SWITCH.equals(cmd.action)) {
+                    handleDisplaySwitch(cmd.z == 1 /* startToSwitch */);
+                    return;
                 }
                 result = onCommand(cmd.action, cmd.x, cmd.y, cmd.z,
                         cmd.extras, cmd.sync);
@@ -2095,6 +2099,23 @@
             }
         }
 
+        private void handleDisplaySwitch(boolean startToSwitch) {
+            if (startToSwitch && mReportedVisible) {
+                // The display may be off/on in a short time when the display is switching.
+                // Keep the visible state until onScreenTurnedOn or !startToSwitch is received, so
+                // the rendering thread can be active to redraw in time when receiving size change.
+                mPreserveVisible = true;
+                mCaller.removeMessages(MSG_REFRESH_VISIBILITY);
+                mCaller.sendMessageDelayed(mCaller.obtainMessage(MSG_REFRESH_VISIBILITY),
+                        PRESERVE_VISIBLE_TIMEOUT_MS);
+            } else if (!startToSwitch && mPreserveVisible) {
+                // The switch is finished, so restore to actual visibility.
+                mPreserveVisible = false;
+                mCaller.removeMessages(MSG_REFRESH_VISIBILITY);
+                reportVisibility(false /* forceReport */);
+            }
+        }
+
         private void updateFrozenState(boolean frozenRequested) {
             if (mIWallpaperEngine.mInfo == null
                     // Procees the unfreeze command in case the wallaper became static while
@@ -2638,6 +2659,10 @@
                             + ": " + message.arg1);
                     mEngine.doVisibilityChanged(message.arg1 != 0);
                     break;
+                case MSG_REFRESH_VISIBILITY:
+                    mEngine.mPreserveVisible = false;
+                    mEngine.reportVisibility(false /* forceReport */);
+                    break;
                 case MSG_UPDATE_SCREEN_TURNING_ON:
                     if (DEBUG) {
                         Log.v(TAG,
diff --git a/core/java/android/service/wearable/OWNERS b/core/java/android/service/wearable/OWNERS
index 073e2d7..eca48b7 100644
--- a/core/java/android/service/wearable/OWNERS
+++ b/core/java/android/service/wearable/OWNERS
@@ -1,3 +1 @@
-charliewang@google.com
-oni@google.com
-volnov@google.com
\ No newline at end of file
+include /core/java/android/app/wearable/OWNERS
\ No newline at end of file
diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java
index 118d028..1ca7ac7 100644
--- a/core/java/android/speech/RecognizerIntent.java
+++ b/core/java/android/speech/RecognizerIntent.java
@@ -16,6 +16,9 @@
 
 package android.speech;
 
+import static android.speech.flags.Flags.FLAG_MULTILANG_EXTRA_LAUNCH;
+
+import android.annotation.FlaggedApi;
 import android.app.Activity;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
@@ -653,4 +656,30 @@
      */
     public static final String EXTRA_LANGUAGE_SWITCH_ALLOWED_LANGUAGES =
             "android.speech.extra.LANGUAGE_SWITCH_ALLOWED_LANGUAGES";
+
+    /**
+     * Optional integer to use for {@link #EXTRA_ENABLE_LANGUAGE_SWITCH}. If set, the language
+     * switch will be deactivated when LANGUAGE_SWITCH_MAX_SWITCHES reached.
+     *
+     * <p> Depending on the recognizer implementation, this flag may have no effect.
+     *
+     * @see #EXTRA_ENABLE_LANGUAGE_SWITCH
+     */
+    @FlaggedApi(FLAG_MULTILANG_EXTRA_LAUNCH)
+    public static final String EXTRA_LANGUAGE_SWITCH_MAX_SWITCHES =
+            "android.speech.extra.LANGUAGE_SWITCH_MAX_SWITCHES";
+
+    /**
+     * Optional integer to use for {@link #EXTRA_ENABLE_LANGUAGE_SWITCH}. If set, the language
+     * switch will only be activated for this value of ms of audio since the START_OF_SPEECH. This
+     * could provide a more stable recognition result when the language switch is only required in
+     * the beginning of the session.
+     *
+     * <p> Depending on the recognizer implementation, this flag may have no effect.
+     *
+     * @see #EXTRA_ENABLE_LANGUAGE_SWITCH
+     */
+    @FlaggedApi(FLAG_MULTILANG_EXTRA_LAUNCH)
+    public static final String EXTRA_LANGUAGE_SWITCH_INITIAL_ACTIVE_DURATION_TIME_MILLIS =
+            "android.speech.extra.LANGUAGE_SWITCH_INITIAL_ACTIVE_DURATION_TIME_MILLIS";
 }
diff --git a/core/java/android/speech/flags/speech_flags.aconfig b/core/java/android/speech/flags/speech_flags.aconfig
new file mode 100644
index 0000000..fd80127
--- /dev/null
+++ b/core/java/android/speech/flags/speech_flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.speech.flags"
+
+flag {
+    name: "multilang_extra_launch"
+    namespace: "machine_learning"
+    description: "Feature flag for adding new extra for multi-lang feature"
+    bug: "312489931"
+}
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index cf1156d..fb57921 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -1681,6 +1681,10 @@
                 @EmergencyCallbackModeStopReason int reason) {
             // not support. Can't override. Use TelephonyCallback.
         }
+
+        public final void onSimultaneousCallingStateChanged(int[] subIds) {
+            // not supported on the deprecated interface - Use TelephonyCallback instead
+        }
     }
 
     private void log(String s) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 19bcf28..dc6a035 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
@@ -33,15 +34,19 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IPhoneStateListener;
+import com.android.internal.telephony.flags.Flags;
 
 import dalvik.system.VMRuntime;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
 
 /**
  * A callback class for monitoring changes in specific telephony states
@@ -627,6 +632,18 @@
     public static final int EVENT_EMERGENCY_CALLBACK_MODE_CHANGED = 40;
 
     /**
+     * Event for listening to changes in simultaneous cellular calling subscriptions.
+     *
+     * @see SimultaneousCellularCallingSupportListener
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @SystemApi
+    public static final int EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED = 41;
+
+    /**
      * @hide
      */
     @IntDef(prefix = {"EVENT_"}, value = {
@@ -669,7 +686,8 @@
             EVENT_LINK_CAPACITY_ESTIMATE_CHANGED,
             EVENT_TRIGGER_NOTIFY_ANBR,
             EVENT_MEDIA_QUALITY_STATUS_CHANGED,
-            EVENT_EMERGENCY_CALLBACK_MODE_CHANGED
+            EVENT_EMERGENCY_CALLBACK_MODE_CHANGED,
+            EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface TelephonyEvent {
@@ -1373,6 +1391,44 @@
     }
 
     /**
+     * Interface for listening to changes in the simultaneous cellular calling state for active
+     * cellular subscriptions.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+    @SystemApi
+    public interface SimultaneousCellularCallingSupportListener {
+        /**
+         * Notify the Listener that the subscriptions available for simultaneous <b>cellular</b>
+         * calling have changed.
+         * <p>
+         * If we have an ongoing <b>cellular</b> call on one subscription in this Set, a
+         * simultaneous incoming or outgoing <b>cellular</b> call is possible on any of the
+         * subscriptions in this Set. On a traditional Dual Sim Dual Standby device, simultaneous
+         * calling is not possible between subscriptions, where on a Dual Sim Dual Active device,
+         * simultaneous calling may be possible between subscriptions in certain network conditions.
+         * <p>
+         * Note: This listener only tracks the capability of the modem to perform simultaneous
+         * cellular calls and does not track the simultaneous calling state of scenarios based on
+         * multiple IMS registration over multiple transports (WiFi/Internet calling).
+         * <p>
+         * Note: This listener fires for all changes to cellular calling subscriptions independent
+         * of which subscription it is registered on.
+         *
+         * @param simultaneousCallingSubscriptionIds The Set of subscription IDs that support
+         * simultaneous calling. If there is an ongoing call on a subscription in this Set, then a
+         * simultaneous incoming or outgoing call is only possible for other subscriptions in this
+         * Set. If there is an ongoing call on a subscription that is not in this Set, then
+         * simultaneous calling is not possible at the current time.
+         *
+         */
+        @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+        void onSimultaneousCellularCallingSubscriptionsChanged(
+                @NonNull Set<Integer> simultaneousCallingSubscriptionIds);
+    }
+
+    /**
      * Interface for call attributes listener.
      *
      * @hide
@@ -1976,6 +2032,17 @@
                                     allowedNetworkType)));
         }
 
+        public void onSimultaneousCallingStateChanged(int[] subIds) {
+            SimultaneousCellularCallingSupportListener listener =
+                    (SimultaneousCellularCallingSupportListener) mTelephonyCallbackWeakRef.get();
+            if (listener == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(
+                            () -> listener.onSimultaneousCellularCallingSubscriptionsChanged(
+                                    Arrays.stream(subIds).boxed().collect(Collectors.toSet()))));
+        }
+
         public void onLinkCapacityEstimateChanged(
                 List<LinkCapacityEstimate> linkCapacityEstimateList) {
             LinkCapacityEstimateChangedListener listener =
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 886727e..0a813a3 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -16,9 +16,11 @@
 package android.telephony;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.compat.Compatibility;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
@@ -36,6 +38,7 @@
 import android.telephony.Annotation.RadioPowerState;
 import android.telephony.Annotation.SimActivationState;
 import android.telephony.Annotation.SrvccState;
+import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.TelephonyManager.CarrierPrivilegesCallback;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsCallSession;
@@ -50,6 +53,7 @@
 import com.android.internal.telephony.ICarrierPrivilegesCallback;
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 import com.android.internal.telephony.ITelephonyRegistry;
+import com.android.server.telecom.flags.Flags;
 
 import java.lang.ref.WeakReference;
 import java.util.Arrays;
@@ -66,11 +70,13 @@
  * or {@link PhoneCapability} changed. This might trigger callback from applications side through
  * {@link android.telephony.PhoneStateListener}
  *
- * TODO: limit API access to only carrier apps with certain permissions or apps running on
+ * Limit API access to only carrier apps with certain permissions or apps running on
  * privileged UID.
  *
  * @hide
  */
+@SystemApi
+@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
 public class TelephonyRegistryManager {
 
     private static final String TAG = "TelephonyRegistryManager";
@@ -111,15 +117,17 @@
     }
 
     /**
-     * Register for changes to the list of active {@link SubscriptionInfo} records or to the
-     * individual records themselves. When a change occurs the onSubscriptionsChanged method of
-     * the listener will be invoked immediately if there has been a notification. The
-     * onSubscriptionChanged method will also be triggered once initially when calling this
-     * function.
+     * Register for changes to the list of {@link SubscriptionInfo} records or to the
+     * individual records (active or inactive) themselves. When a change occurs, the
+     * {@link OnSubscriptionsChangedListener#onSubscriptionsChanged()} method of
+     * the listener will be invoked immediately. The
+     * {@link OnSubscriptionsChangedListener#onSubscriptionsChanged()} method will also be invoked
+     * once initially when calling this method.
      *
-     * @param listener an instance of {@link SubscriptionManager.OnSubscriptionsChangedListener}
-     *                 with onSubscriptionsChanged overridden.
+     * @param listener an instance of {@link OnSubscriptionsChangedListener} with
+     * {@link OnSubscriptionsChangedListener#onSubscriptionsChanged()} overridden.
      * @param executor the executor that will execute callbacks.
+     * @hide
      */
     public void addOnSubscriptionsChangedListener(
             @NonNull SubscriptionManager.OnSubscriptionsChangedListener listener,
@@ -155,6 +163,7 @@
      * invoke the listener fails.
      *
      * @param listener that is to be unregistered.
+     * @hide
      */
     public void removeOnSubscriptionsChangedListener(
             @NonNull SubscriptionManager.OnSubscriptionsChangedListener listener) {
@@ -180,6 +189,7 @@
      * {@link SubscriptionManager.OnOpportunisticSubscriptionsChangedListener} with
      *                 onOpportunisticSubscriptionsChanged overridden.
      * @param executor an Executor that will execute callbacks.
+     * @hide
      */
     public void addOnOpportunisticSubscriptionsChangedListener(
             @NonNull SubscriptionManager.OnOpportunisticSubscriptionsChangedListener listener,
@@ -221,6 +231,7 @@
      * listener fails.
      *
      * @param listener that is to be unregistered.
+     * @hide
      */
     public void removeOnOpportunisticSubscriptionsChangedListener(
             @NonNull SubscriptionManager.OnOpportunisticSubscriptionsChangedListener listener) {
@@ -252,6 +263,7 @@
      * @param listener Listener providing callback
      * @param events Events
      * @param notifyNow Whether to notify instantly
+     * @hide
      */
     public void listenFromListener(int subId, @NonNull boolean renounceFineLocationAccess,
             @NonNull boolean renounceCoarseLocationAccess, @NonNull String pkg,
@@ -318,6 +330,7 @@
      * @param active Whether the carrier network change is or shortly will be
      * active. Set this value to true to begin showing alternative UI and false to stop.
      * @see TelephonyManager#hasCarrierPrivileges
+     * @hide
      */
     public void notifyCarrierNetworkChange(boolean active) {
         try {
@@ -344,6 +357,7 @@
      * @param active whether the carrier network change is or shortly will be active. Set this value
      *              to true to begin showing alternative UI and false to stop.
      * @see TelephonyManager#hasCarrierPrivileges
+     * @hide
      */
     public void notifyCarrierNetworkChange(int subscriptionId, boolean active) {
         try {
@@ -362,6 +376,7 @@
      * @param subId for which call state changed.
      * @param state latest call state. e.g, offhook, ringing
      * @param incomingNumber incoming phone number.
+     * @hide
      */
     public void notifyCallStateChanged(int slotIndex, int subId, @CallState int state,
             @Nullable String incomingNumber) {
@@ -380,6 +395,7 @@
      * @param incomingNumber incoming phone number.
      * @hide
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void notifyCallStateChangedForAllSubscriptions(@CallState int state,
             @Nullable String incomingNumber) {
@@ -424,6 +440,7 @@
      * subId is invalid.
      * @param subId for which the service state changed.
      * @param state service state e.g, in service, out of service or roaming status.
+     * @hide
      */
     public void notifyServiceStateChanged(int slotIndex, int subId, @NonNull ServiceState state) {
         try {
@@ -441,6 +458,7 @@
      * subId is invalid.
      * @param subId for which the signalstrength changed.
      * @param signalStrength e.g, signalstrength level {@see SignalStrength#getLevel()}
+     * @hide
      */
     public void notifySignalStrengthChanged(int slotIndex, int subId,
             @NonNull SignalStrength signalStrength) {
@@ -461,6 +479,7 @@
      * @param subId for which message waiting indicator changed.
      * @param msgWaitingInd {@code true} indicates there is message-waiting indicator, {@code false}
      * otherwise.
+     * @hide
      */
     public void notifyMessageWaitingChanged(int slotIndex, int subId, boolean msgWaitingInd) {
         try {
@@ -477,6 +496,7 @@
      * @param subId for which call forwarding status changed.
      * @param callForwardInd {@code true} indicates there is call forwarding, {@code false}
      * otherwise.
+     * @hide
      */
     public void notifyCallForwardingChanged(int subId, boolean callForwardInd) {
         try {
@@ -493,6 +513,7 @@
      * @param subId for which data activity state changed.
      * @param dataActivityType indicates the latest data activity type e.g. {@link
      * TelephonyManager#DATA_ACTIVITY_IN}
+     * @hide
      */
     public void notifyDataActivityChanged(int subId, @DataActivityType int dataActivityType) {
         try {
@@ -511,6 +532,7 @@
      * @param subId for which data activity state changed.
      * @param dataActivityType indicates the latest data activity type e.g. {@link
      * TelephonyManager#DATA_ACTIVITY_IN}
+     * @hide
      */
     public void notifyDataActivityChanged(int slotIndex, int subId,
             @DataActivityType int dataActivityType) {
@@ -532,6 +554,7 @@
      *
      * @see PreciseDataConnectionState
      * @see TelephonyManager#DATA_DISCONNECTED
+     * @hide
      */
     public void notifyDataConnectionForSubscriber(int slotIndex, int subId,
             @NonNull PreciseDataConnectionState preciseState) {
@@ -552,6 +575,7 @@
      * @param subId for which call quality state changed.
      * @param callQuality Information about call quality e.g, call quality level
      * @param networkType associated with this data connection. e.g, LTE
+     * @hide
      */
     public void notifyCallQualityChanged(int slotIndex, int subId, @NonNull CallQuality callQuality,
         @NetworkType int networkType) {
@@ -573,6 +597,7 @@
      * {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_JITTER_INT}
      *
      * @param status media quality status
+     * @hide
      */
     public void notifyMediaQualityStatusChanged(
             int slotIndex, int subId, @NonNull MediaQualityStatus status) {
@@ -590,6 +615,7 @@
      * @param slotIndex for which emergency number list changed. Can be derived from subId except
      * when subId is invalid.
      * @param subId for which emergency number list changed.
+     * @hide
      */
     public void notifyEmergencyNumberList( int slotIndex, int subId) {
         try {
@@ -602,14 +628,16 @@
 
     /**
      * Notify outgoing emergency call.
-     * @param phoneId Sender phone ID.
+     * @param simSlotIndex Sender phone ID.
      * @param subId Sender subscription ID.
      * @param emergencyNumber Emergency number.
+     * @hide
      */
-    public void notifyOutgoingEmergencyCall(int phoneId, int subId,
+    @SystemApi
+    public void notifyOutgoingEmergencyCall(int simSlotIndex, int subId,
             @NonNull EmergencyNumber emergencyNumber) {
         try {
-            sRegistry.notifyOutgoingEmergencyCall(phoneId, subId, emergencyNumber);
+            sRegistry.notifyOutgoingEmergencyCall(simSlotIndex, subId, emergencyNumber);
         } catch (RemoteException ex) {
             // system process is dead
             throw ex.rethrowFromSystemServer();
@@ -621,6 +649,7 @@
      * @param phoneId Sender phone ID.
      * @param subId Sender subscription ID.
      * @param emergencyNumber Emergency number.
+     * @hide
      */
     public void notifyOutgoingEmergencySms(int phoneId, int subId,
             @NonNull EmergencyNumber emergencyNumber) {
@@ -639,6 +668,7 @@
      * subId is invalid.
      * @param subId for which radio power state changed.
      * @param radioPowerState the current modem radio state.
+     * @hide
      */
     public void notifyRadioPowerStateChanged(int slotIndex, int subId,
             @RadioPowerState int radioPowerState) {
@@ -654,6 +684,7 @@
      * Notify {@link PhoneCapability} changed.
      *
      * @param phoneCapability the capability of the modem group.
+     * @hide
      */
     public void notifyPhoneCapabilityChanged(@NonNull PhoneCapability phoneCapability) {
         try {
@@ -685,6 +716,7 @@
      * when subId is invalid.
      * @param subId for which data activation state changed.
      * @param activationState sim activation state e.g, activated.
+     * @hide
      */
     public void notifyDataActivationStateChanged(int slotIndex, int subId,
             @SimActivationState int activationState) {
@@ -705,6 +737,7 @@
      * subId is invalid.
      * @param subId for which voice activation state changed.
      * @param activationState sim activation state e.g, activated.
+     * @hide
      */
     public void notifyVoiceActivationStateChanged(int slotIndex, int subId,
             @SimActivationState int activationState) {
@@ -725,6 +758,7 @@
      * when subId is invalid.
      * @param subId for which mobile data state has changed.
      * @param state {@code true} indicates mobile data is enabled/on. {@code false} otherwise.
+     * @hide
      */
     public void notifyUserMobileDataStateChanged(int slotIndex, int subId, boolean state) {
         try {
@@ -743,6 +777,7 @@
      * when the device is in emergency-only mode.
      * @param subscriptionId Subscription id for which display network info has changed.
      * @param telephonyDisplayInfo The display info.
+     * @hide
      */
     public void notifyDisplayInfoChanged(int slotIndex, int subscriptionId,
             @NonNull TelephonyDisplayInfo telephonyDisplayInfo) {
@@ -759,6 +794,7 @@
      *
      * @param subId for which ims call disconnect.
      * @param imsReasonInfo the reason for ims call disconnect.
+     * @hide
      */
     public void notifyImsDisconnectCause(int subId, @NonNull ImsReasonInfo imsReasonInfo) {
         try {
@@ -775,6 +811,7 @@
      *
      * @param subId for which srvcc state changed.
      * @param state srvcc state
+     * @hide
      */
     public void notifySrvccStateChanged(int subId, @SrvccState int state) {
         try {
@@ -798,6 +835,7 @@
      * @param imsServiceTypes Array of IMS call service type for ringing, foreground &
      *                        background calls.
      * @param imsCallTypes Array of IMS call type for ringing, foreground & background calls.
+     * @hide
      */
     public void notifyPreciseCallState(int slotIndex, int subId,
             @Annotation.PreciseCallStates int[] callStates, String[] imsCallIds,
@@ -822,6 +860,7 @@
      * @param cause {@link DisconnectCause} for the disconnected call.
      * @param preciseCause {@link android.telephony.PreciseDisconnectCause} for the disconnected
      * call.
+     * @hide
      */
     public void notifyDisconnectCause(int slotIndex, int subId, @DisconnectCauses int cause,
             @PreciseDisconnectCauses int preciseCause) {
@@ -838,6 +877,7 @@
      *
      * <p>To be compatible with {@link TelephonyRegistry}, use {@link CellIdentity} which is
      * parcelable, and convert to CellLocation in client code.
+     * @hide
      */
     public void notifyCellLocation(int subId, @NonNull CellIdentity cellLocation) {
         try {
@@ -854,6 +894,7 @@
      *
      * @param subId for which cellinfo changed.
      * @param cellInfo A list of cellInfo associated with the given subscription.
+     * @hide
      */
     public void notifyCellInfoChanged(int subId, @NonNull List<CellInfo> cellInfo) {
         try {
@@ -866,6 +907,7 @@
     /**
      * Notify that the active data subscription ID has changed.
      * @param activeDataSubId The new subscription ID for active data
+     * @hide
      */
     public void notifyActiveDataSubIdChanged(int activeDataSubId) {
         try {
@@ -896,6 +938,7 @@
      *        For UMTS, if a combined attach succeeds for PS only, then the GMM cause code shall be
      *        included as an additionalCauseCode. For LTE (ESM), cause codes are in
      *        TS 24.301 9.9.4.4. Integer.MAX_VALUE if this value is unused.
+     * @hide
      */
     public void notifyRegistrationFailed(int slotIndex, int subId,
             @NonNull CellIdentity cellIdentity, @NonNull String chosenPlmn,
@@ -914,6 +957,7 @@
      * @param slotIndex for the phone object that got updated barring info.
      * @param subId for which the BarringInfo changed.
      * @param barringInfo updated BarringInfo.
+     * @hide
      */
     public void notifyBarringInfoChanged(
             int slotIndex, int subId, @NonNull BarringInfo barringInfo) {
@@ -931,6 +975,7 @@
      * @param slotIndex for which physical channel configs changed.
      * @param subId the subId
      * @param configs a list of {@link PhysicalChannelConfig}, the configs of physical channel.
+     * @hide
      */
     public void notifyPhysicalChannelConfigForSubscriber(int slotIndex, int subId,
             List<PhysicalChannelConfig> configs) {
@@ -948,6 +993,7 @@
      * @param enabled True if data is enabled, otherwise disabled.
      * @param reason Reason for data enabled/disabled. See {@code REASON_*} in
      * {@link TelephonyManager}.
+     * @hide
      */
     public void notifyDataEnabled(int slotIndex, int subId, boolean enabled,
             @TelephonyManager.DataEnabledReason int reason) {
@@ -966,6 +1012,7 @@
      * @param subId for which allowed network types changed.
      * @param reason an allowed network type reasons.
      * @param allowedNetworkType an allowed network type bitmask value.
+     * @hide
      */
     public void notifyAllowedNetworkTypesChanged(int slotIndex, int subId,
             int reason, long allowedNetworkType) {
@@ -983,6 +1030,7 @@
      * @param slotIndex for the phone object that gets the updated link capacity estimate
      * @param subId for subscription that gets the updated link capacity estimate
      * @param linkCapacityEstimateList a list of {@link  LinkCapacityEstimate}
+     * @hide
      */
     public void notifyLinkCapacityEstimateChanged(int slotIndex, int subId,
             List<LinkCapacityEstimate> linkCapacityEstimateList) {
@@ -994,6 +1042,29 @@
         }
     }
 
+    /**
+     * Notify external listeners that the subscriptions supporting simultaneous cellular calling
+     * have changed.
+     * @param subIds The new set of subIds supporting simultaneous cellular calling.
+     * @hide
+     */
+    public void notifySimultaneousCellularCallingSubscriptionsChanged(
+            @NonNull Set<Integer> subIds) {
+        try {
+            sRegistry.notifySimultaneousCellularCallingSubscriptionsChanged(
+                    subIds.stream().mapToInt(i -> i).toArray());
+        } catch (RemoteException ex) {
+            // system server crash
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Processes potential event changes from the provided {@link TelephonyCallback}.
+     *
+     * @param telephonyCallback callback for monitoring callback changes to the telephony state.
+     * @hide
+     */
     public @NonNull Set<Integer> getEventsFromCallback(
             @NonNull TelephonyCallback telephonyCallback) {
         Set<Integer> eventList = new ArraySet<>();
@@ -1135,7 +1206,11 @@
             eventList.add(TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED);
         }
 
-
+        if (telephonyCallback
+                instanceof TelephonyCallback.SimultaneousCellularCallingSupportListener) {
+            eventList.add(
+                    TelephonyCallback.EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED);
+        }
         return eventList;
     }
 
@@ -1300,6 +1375,7 @@
      * may encounter an {@link IllegalStateException} when trying to register more callbacks.
      *
      * @param callback The {@link TelephonyCallback} object to register.
+     * @hide
      */
     public void registerTelephonyCallback(boolean renounceFineLocationAccess,
             boolean renounceCoarseLocationAccess,
@@ -1319,6 +1395,7 @@
      * Unregister an existing {@link TelephonyCallback}.
      *
      * @param callback The {@link TelephonyCallback} object to unregister.
+     * @hide
      */
     public void unregisterTelephonyCallback(int subId, String pkgName, String attributionTag,
             @NonNull TelephonyCallback callback, boolean notifyNow) {
@@ -1379,6 +1456,7 @@
      * @param logicalSlotIndex The SIM slot to listen on
      * @param executor The executor where {@code listener} will be invoked
      * @param callback The callback to register
+     * @hide
      */
     public void addCarrierPrivilegesCallback(
             int logicalSlotIndex,
@@ -1413,6 +1491,7 @@
      * Unregisters a {@link CarrierPrivilegesCallback}.
      *
      * @param callback The callback to unregister
+     * @hide
      */
     public void removeCarrierPrivilegesCallback(@NonNull CarrierPrivilegesCallback callback) {
         if (callback == null) {
@@ -1438,6 +1517,7 @@
      * @param logicalSlotIndex The SIM slot the change occurred on
      * @param privilegedPackageNames The updated set of packages names with carrier privileges
      * @param privilegedUids The updated set of UIDs with carrier privileges
+     * @hide
      */
     public void notifyCarrierPrivilegesChanged(
             int logicalSlotIndex,
@@ -1463,6 +1543,7 @@
      * @param logicalSlotIndex the SIM slot the change occurred on
      * @param packageName the package name of the changed {@link CarrierService}
      * @param uid the UID of the changed {@link CarrierService}
+     * @hide
      */
     public void notifyCarrierServiceChanged(int logicalSlotIndex, @Nullable String packageName,
             int uid) {
@@ -1479,6 +1560,7 @@
      *
      * @param executor The executor on which the callback will be executed.
      * @param listener The CarrierConfigChangeListener to be registered with.
+     * @hide
      */
     public void addCarrierConfigChangedListener(
             @NonNull @CallbackExecutor Executor executor,
@@ -1519,6 +1601,7 @@
      * Unregister to stop the notification when carrier configurations changed.
      *
      * @param listener The CarrierConfigChangeListener to be unregistered with.
+     * @hide
      */
     public void removeCarrierConfigChangedListener(
             @NonNull CarrierConfigManager.CarrierConfigChangeListener listener) {
@@ -1548,6 +1631,7 @@
      *                          {@link TelephonyManager#UNKNOWN_CARRIER_ID}.
      * @param specificCarrierId The optional specific carrier Id, may be {@link
      *                          TelephonyManager#UNKNOWN_CARRIER_ID}.
+     * @hide
      */
     public void notifyCarrierConfigChanged(int slotIndex, int subId, int carrierId,
             int specificCarrierId) {
@@ -1569,6 +1653,7 @@
      * @param subId Sender subscription ID.
      * @param type for callback mode entry.
      *             See {@link TelephonyManager.EmergencyCallbackModeType}.
+     * @hide
      */
     public void notifyCallBackModeStarted(int phoneId, int subId,
             @TelephonyManager.EmergencyCallbackModeType int type) {
@@ -1589,6 +1674,7 @@
      *             See {@link TelephonyManager.EmergencyCallbackModeType}.
      * @param reason for changing callback mode.
      *             See {@link TelephonyManager.EmergencyCallbackModeStopReason}.
+     * @hide
      */
     public void notifyCallbackModeStopped(int phoneId, int subId,
             @TelephonyManager.EmergencyCallbackModeType int type,
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 4c81888..a6d3bb4 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -454,7 +454,7 @@
             line.set(paint, source, 0, source.length(), Layout.DIR_LEFT_TO_RIGHT,
                     Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
                     mEllipsizedStart, mEllipsizedStart + mEllipsizedCount, useFallbackLineSpacing);
-            mMax = (int) Math.ceil(line.metrics(null, null, false));
+            mMax = (int) Math.ceil(line.metrics(null, null, false, null));
             TextLine.recycle(line);
         }
 
@@ -603,7 +603,7 @@
                 0 /* ellipsisStart, 0 since text has not been ellipsized at this point */,
                 0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */,
                 useFallbackLineSpacing);
-        fm.width = (int) Math.ceil(line.metrics(fm, fm.mDrawingBounds, false));
+        fm.width = (int) Math.ceil(line.metrics(fm, fm.mDrawingBounds, false, null));
         TextLine.recycle(line);
 
         return fm;
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 7b9cb6a..9286049 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -40,6 +40,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.GrowingArrayUtils;
+import com.android.text.flags.Flags;
 
 import java.lang.ref.WeakReference;
 
@@ -1276,8 +1277,21 @@
         }
 
         public void onSpanRemoved(Spannable s, Object o, int start, int end) {
-            if (o instanceof UpdateLayout)
-                transformAndReflow(s, start, end);
+            if (o instanceof UpdateLayout) {
+                if (Flags.insertModeCrashWhenDelete()) {
+                    final DynamicLayout dynamicLayout = mLayout.get();
+                    if (dynamicLayout != null && dynamicLayout.mDisplay instanceof OffsetMapping) {
+                        // It's possible that a Span is removed when the text covering it is
+                        // deleted, in this case, the original start and end of the span might be
+                        // OOB. So it'll reflow the entire string instead.
+                        reflow(s, 0, 0, s.length());
+                    } else {
+                        reflow(s, start, end - start, end - start);
+                    }
+                } else {
+                    transformAndReflow(s, start, end);
+                }
+            }
         }
 
         public void onSpanChanged(Spannable s, Object o, int start, int end, int nstart, int nend) {
@@ -1287,8 +1301,21 @@
                     // instead of causing an exception
                     start = 0;
                 }
-                transformAndReflow(s, start, end);
-                transformAndReflow(s, nstart, nend);
+                if (Flags.insertModeCrashWhenDelete()) {
+                    final DynamicLayout dynamicLayout = mLayout.get();
+                    if (dynamicLayout != null && dynamicLayout.mDisplay instanceof OffsetMapping) {
+                        // When text is changed, it'll also trigger onSpanChanged. In this case we
+                        // can't determine the updated range in the transformed text. So it'll
+                        // reflow the entire range instead.
+                        reflow(s, 0, 0, s.length());
+                    } else {
+                        reflow(s, start, end - start, end - start);
+                        reflow(s, nstart, nend - nstart, nend - nstart);
+                    }
+                } else {
+                    transformAndReflow(s, start, end);
+                    transformAndReflow(s, nstart, nend);
+                }
             }
         }
 
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index c9906cc..1ea80f1 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -18,6 +18,7 @@
 
 import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
 import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
+import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
 
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
@@ -50,8 +51,10 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.text.BreakIterator;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
 
 /**
  * A base class that manages text layout in visual elements on
@@ -149,7 +152,8 @@
     /** @hide */
     @IntDef(prefix = { "JUSTIFICATION_MODE_" }, value = {
             LineBreaker.JUSTIFICATION_MODE_NONE,
-            LineBreaker.JUSTIFICATION_MODE_INTER_WORD
+            LineBreaker.JUSTIFICATION_MODE_INTER_WORD,
+            LineBreaker.JUSTIFICATION_MODE_INTER_CHARACTER,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface JustificationMode {}
@@ -165,6 +169,13 @@
     public static final int JUSTIFICATION_MODE_INTER_WORD =
             LineBreaker.JUSTIFICATION_MODE_INTER_WORD;
 
+    /**
+     * Value for justification mode indicating the text is justified by stretching letter spacing.
+     */
+    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    public static final int JUSTIFICATION_MODE_INTER_CHARACTER =
+            LineBreaker.JUSTIFICATION_MODE_INTER_CHARACTER;
+
     /*
      * Line spacing multiplier for default line spacing.
      */
@@ -669,7 +680,8 @@
             int start = previousLineEnd;
             previousLineEnd = getLineStart(lineNum + 1);
             final boolean justify = isJustificationRequired(lineNum);
-            int end = getLineVisibleEnd(lineNum, start, previousLineEnd);
+            int end = getLineVisibleEnd(lineNum, start, previousLineEnd,
+                    true /* trailingSpaceAtLastLineIsVisible */);
             paint.setStartHyphenEdit(getStartHyphenEdit(lineNum));
             paint.setEndHyphenEdit(getEndHyphenEdit(lineNum));
 
@@ -805,7 +817,7 @@
                         getEllipsisStart(lineNum) + getEllipsisCount(lineNum),
                         isFallbackLineSpacingEnabled());
                 if (justify) {
-                    tl.justify(right - left - indentWidth);
+                    tl.justify(mJustificationMode, right - left - indentWidth);
                 }
                 tl.draw(canvas, x, ltop, lbaseline, lbottom);
             }
@@ -1054,9 +1066,9 @@
                     getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line),
                     isFallbackLineSpacingEnabled());
             if (isJustificationRequired(line)) {
-                tl.justify(getJustifyWidth(line));
+                tl.justify(mJustificationMode, getJustifyWidth(line));
             }
-            tl.metrics(null, rectF, false);
+            tl.metrics(null, rectF, false, null);
 
             float lineLeft = rectF.left;
             float lineRight = rectF.right;
@@ -1456,7 +1468,7 @@
         tl.set(mPaint, mText, start, end, dir, directions, hasTab, tabStops,
                 getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line),
                 isFallbackLineSpacingEnabled());
-        float wid = tl.measure(offset - start, trailing, null, null);
+        float wid = tl.measure(offset - start, trailing, null, null, null);
         TextLine.recycle(tl);
 
         if (clamped && wid > mWidth) {
@@ -1790,14 +1802,71 @@
                 getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line),
                 isFallbackLineSpacingEnabled());
         if (isJustificationRequired(line)) {
-            tl.justify(getJustifyWidth(line));
+            tl.justify(mJustificationMode, getJustifyWidth(line));
         }
-        final float width = tl.metrics(null, null, mUseBoundsForWidth);
+        final float width = tl.metrics(null, null, mUseBoundsForWidth, null);
         TextLine.recycle(tl);
         return width;
     }
 
     /**
+     * Returns the number of letter spacing unit in the line.
+     *
+     * <p>
+     * This API returns a number of letters that is a target of letter spacing. The letter spacing
+     * won't be added to the middle of the characters that are needed to be treated as a single,
+     * e.g., ligatured or conjunct form. Note that this value is different from the number of]
+     * grapheme clusters that is calculated by {@link BreakIterator#getCharacterInstance(Locale)}.
+     * For example, if the "fi" is ligatured, the ligatured form is treated as single uni and letter
+     * spacing is not added, but it has two separate grapheme cluster.
+     *
+     * <p>
+     * This value is used for calculating the letter spacing amount for the justification because
+     * the letter spacing is applied between clusters. For example, if extra {@code W} pixels needed
+     * to be filled by letter spacing, the amount of letter spacing to be applied is
+     * {@code W}/(letter spacing unit count - 1) px.
+     *
+     * @param line the index of the line
+     * @param includeTrailingWhitespace whether to include trailing whitespace
+     * @return the number of cluster count in the line.
+     */
+    @IntRange(from = 0)
+    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    public int getLineLetterSpacingUnitCount(@IntRange(from = 0) int line,
+            boolean includeTrailingWhitespace) {
+        final int start = getLineStart(line);
+        final int end = includeTrailingWhitespace ? getLineEnd(line)
+                : getLineVisibleEnd(line, getLineStart(line), getLineStart(line + 1),
+                        false  // trailingSpaceAtLastLineIsVisible: Treating trailing whitespaces at
+                               // the last line as a invisible chars for single line justification.
+                );
+
+        final Directions directions = getLineDirections(line);
+        // Returned directions can actually be null
+        if (directions == null) {
+            return 0;
+        }
+        final int dir = getParagraphDirection(line);
+
+        final TextLine tl = TextLine.obtain();
+        final TextPaint paint = mWorkPaint;
+        paint.set(mPaint);
+        paint.setStartHyphenEdit(getStartHyphenEdit(line));
+        paint.setEndHyphenEdit(getEndHyphenEdit(line));
+        tl.set(paint, mText, start, end, dir, directions,
+                false, null, // tab width is not used for cluster counting.
+                getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line),
+                isFallbackLineSpacingEnabled());
+        if (mLineInfo == null) {
+            mLineInfo = new TextLine.LineInfo();
+        }
+        mLineInfo.setClusterCount(0);
+        tl.metrics(null, null, mUseBoundsForWidth, mLineInfo);
+        TextLine.recycle(tl);
+        return mLineInfo.getClusterCount();
+    }
+
+    /**
      * Returns the signed horizontal extent of the specified line, excluding
      * leading margin.  If full is false, excludes trailing whitespace.
      * @param line the index of the line
@@ -1821,9 +1890,9 @@
                 getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line),
                 isFallbackLineSpacingEnabled());
         if (isJustificationRequired(line)) {
-            tl.justify(getJustifyWidth(line));
+            tl.justify(mJustificationMode, getJustifyWidth(line));
         }
-        final float width = tl.metrics(null, null, mUseBoundsForWidth);
+        final float width = tl.metrics(null, null, mUseBoundsForWidth, null);
         TextLine.recycle(tl);
         return width;
     }
@@ -2432,14 +2501,21 @@
      * is not counted) on the specified line.
      */
     public int getLineVisibleEnd(int line) {
-        return getLineVisibleEnd(line, getLineStart(line), getLineStart(line+1));
+        return getLineVisibleEnd(line, getLineStart(line), getLineStart(line + 1),
+                true /* trailingSpaceAtLastLineIsVisible */);
     }
 
-    private int getLineVisibleEnd(int line, int start, int end) {
+    private int getLineVisibleEnd(int line, int start, int end,
+            boolean trailingSpaceAtLastLineIsVisible) {
         CharSequence text = mText;
         char ch;
-        if (line == getLineCount() - 1) {
-            return end;
+
+        // Historically, trailing spaces at the last line is counted as visible. However, this
+        // doesn't work well for justification.
+        if (trailingSpaceAtLastLineIsVisible) {
+            if (line == getLineCount() - 1) {
+                return end;
+            }
         }
 
         for (; end > start; end--) {
@@ -2939,7 +3015,7 @@
             tl.set(paint, text, start, end, dir, directions, hasTabs, tabStops,
                     0 /* ellipsisStart */, 0 /* ellipsisEnd */,
                     false /* use fallback line spacing. unused */);
-            return margin + Math.abs(tl.metrics(null, null, useBoundsForWidth));
+            return margin + Math.abs(tl.metrics(null, null, useBoundsForWidth, null));
         } finally {
             TextLine.recycle(tl);
             if (mt != null) {
@@ -3337,6 +3413,8 @@
     private boolean mUseBoundsForWidth;
     private @Nullable Paint.FontMetrics mMinimumFontMetrics;
 
+    private TextLine.LineInfo mLineInfo = null;
+
     /** @hide */
     @IntDef(prefix = { "DIR_" }, value = {
             DIR_LEFT_TO_RIGHT,
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index ac5eb3c..b268c2e 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -671,9 +671,18 @@
         if (mLtrWithoutBidi) {
             // If the whole text is LTR direction, just apply whole region.
             if (builder == null) {
-                mWholeWidth += paint.getTextRunAdvances(
-                        mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */,
-                        mWidths.getRawArray(), start);
+                // For the compatibility reasons, the letter spacing should not be dropped at the
+                // left and right edge.
+                int oldFlag = paint.getFlags();
+                paint.setFlags(paint.getFlags()
+                        | (Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE));
+                try {
+                    mWholeWidth += paint.getTextRunAdvances(
+                            mCopiedBuffer, start, end - start, start, end - start,
+                            false /* isRtl */, mWidths.getRawArray(), start);
+                } finally {
+                    paint.setFlags(oldFlag);
+                }
             } else {
                 builder.appendStyleRun(paint, config, end - start, false /* isRtl */);
             }
@@ -690,9 +699,16 @@
                     final boolean isRtl = (level & 0x1) != 0;
                     if (builder == null) {
                         final int levelLength = levelEnd - levelStart;
-                        mWholeWidth += paint.getTextRunAdvances(
-                                mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
-                                isRtl, mWidths.getRawArray(), levelStart);
+                        int oldFlag = paint.getFlags();
+                        paint.setFlags(paint.getFlags()
+                                | (Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE));
+                        try {
+                            mWholeWidth += paint.getTextRunAdvances(
+                                    mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
+                                    isRtl, mWidths.getRawArray(), levelStart);
+                        } finally {
+                            paint.setFlags(oldFlag);
+                        }
                     } else {
                         builder.appendStyleRun(paint, config, levelEnd - levelStart, isRtl);
                     }
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 2105420b..99bd2ff 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -30,6 +30,7 @@
 import android.graphics.text.LineBreakConfig;
 import android.graphics.text.LineBreaker;
 import android.os.Build;
+import android.os.Trace;
 import android.text.style.LeadingMarginSpan;
 import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
 import android.text.style.LineHeightSpan;
@@ -534,7 +535,12 @@
             if (recycle == null) {
                 recycle = new StaticLayout();
             }
-            recycle.generate(this, mIncludePad, trackpadding);
+            Trace.beginSection("Generating StaticLayout For DynamicLayout");
+            try {
+                recycle.generate(this, mIncludePad, trackpadding);
+            } finally {
+                Trace.endSection();
+            }
             return recycle;
         }
 
@@ -689,7 +695,12 @@
         mLeftIndents = b.mLeftIndents;
         mRightIndents = b.mRightIndents;
 
-        generate(b, b.mIncludePad, trackPadding);
+        Trace.beginSection("Constructing StaticLayout");
+        try {
+            generate(b, b.mIncludePad, trackPadding);
+        } finally {
+            Trace.endSection();
+        }
     }
 
     private static int getBaseHyphenationFrequency(int frequency) {
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index f9abec0..224e5d8 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -76,6 +76,21 @@
     private RectF mTmpRectForPaintAPI;
     private Rect mTmpRectForPrecompute;
 
+    // Recycling object for Paint APIs. Do not use outside getRunAdvances method.
+    private Paint.RunInfo mRunInfo;
+
+    public static final class LineInfo {
+        private int mClusterCount;
+
+        public int getClusterCount() {
+            return mClusterCount;
+        }
+
+        public void setClusterCount(int clusterCount) {
+            mClusterCount = clusterCount;
+        }
+    };
+
     private boolean mUseFallbackExtent = false;
 
     // The start and end of a potentially existing ellipsis on this text line.
@@ -85,9 +100,25 @@
 
     // Additional width of whitespace for justification. This value is per whitespace, thus
     // the line width will increase by mAddedWidthForJustify x (number of stretchable whitespaces).
-    private float mAddedWidthForJustify;
+    private float mAddedWordSpacingInPx;
+    private float mAddedLetterSpacingInPx;
     private boolean mIsJustifying;
 
+    @VisibleForTesting
+    public float getAddedWordSpacingInPx() {
+        return mAddedWordSpacingInPx;
+    }
+
+    @VisibleForTesting
+    public float getAddedLetterSpacingInPx() {
+        return mAddedLetterSpacingInPx;
+    }
+
+    @VisibleForTesting
+    public boolean isJustifying() {
+        return mIsJustifying;
+    }
+
     private final TextPaint mWorkPaint = new TextPaint();
     private final TextPaint mActivePaint = new TextPaint();
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -244,7 +275,7 @@
             }
         }
         mTabs = tabStops;
-        mAddedWidthForJustify = 0;
+        mAddedWordSpacingInPx = 0;
         mIsJustifying = false;
 
         mEllipsisStart = ellipsisStart != ellipsisEnd ? ellipsisStart : 0;
@@ -259,23 +290,137 @@
      * Justify the line to the given width.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public void justify(float justifyWidth) {
+    public void justify(@Layout.JustificationMode int justificationMode, float justifyWidth) {
         int end = mLen;
         while (end > 0 && isLineEndSpace(mText.charAt(mStart + end - 1))) {
             end--;
         }
-        final int spaces = countStretchableSpaces(0, end);
-        if (spaces == 0) {
-            // There are no stretchable spaces, so we can't help the justification by adding any
-            // width.
-            return;
+        if (justificationMode == Layout.JUSTIFICATION_MODE_INTER_WORD) {
+            float width = Math.abs(measure(end, false, null, null, null));
+            final int spaces = countStretchableSpaces(0, end);
+            if (spaces == 0) {
+                // There are no stretchable spaces, so we can't help the justification by adding any
+                // width.
+                return;
+            }
+            mAddedWordSpacingInPx = (justifyWidth - width) / spaces;
+            mAddedLetterSpacingInPx = 0;
+        } else {  // justificationMode == Layout.JUSTIFICATION_MODE_INTER_CHARACTER
+            LineInfo lineInfo = new LineInfo();
+            float width = Math.abs(measure(end, false, null, null, lineInfo));
+
+            int lettersCount = lineInfo.getClusterCount();
+            if (lettersCount < 2) {
+                return;
+            }
+            mAddedLetterSpacingInPx = (justifyWidth - width) / (lettersCount - 1);
+            if (mAddedLetterSpacingInPx > 0.03) {
+                // If the letter spacing is more than 0.03em, the ligatures are automatically
+                // disabled, so re-calculate everything without ligatures.
+                final String oldFontFeatures = mPaint.getFontFeatureSettings();
+                mPaint.setFontFeatureSettings(oldFontFeatures + ", \"liga\" off, \"cliga\" off");
+                width = Math.abs(measure(end, false, null, null, lineInfo));
+                lettersCount = lineInfo.getClusterCount();
+                mAddedLetterSpacingInPx = (justifyWidth - width) / (lettersCount - 1);
+                mPaint.setFontFeatureSettings(oldFontFeatures);
+            }
+            mAddedWordSpacingInPx = 0;
         }
-        final float width = Math.abs(measure(end, false, null, null));
-        mAddedWidthForJustify = (justifyWidth - width) / spaces;
         mIsJustifying = true;
     }
 
     /**
+     * Returns the run flag of at the given BiDi run.
+     *
+     * @param bidiRunIndex a BiDi run index.
+     * @return a run flag of the given BiDi run.
+     */
+    @VisibleForTesting
+    public static int calculateRunFlag(int bidiRunIndex, int bidiRunCount, int lineDirection) {
+        if (bidiRunCount == 1) {
+            // Easy case. If there is only single run, it is most left and most right run.
+            return Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+        }
+        if (bidiRunIndex != 0 && bidiRunIndex != (bidiRunCount - 1)) {
+            // Easy case. If the given run is the middle of the line, it is not the most left or
+            // the most right run.
+            return 0;
+        }
+
+        int runFlag = 0;
+        // For the historical reasons, the BiDi implementation of Android works differently
+        // from the Java BiDi APIs. The mDirections holds the BiDi runs in visual order, but
+        // it is reversed order if the paragraph direction is RTL. So, the first BiDi run of
+        // mDirections is located the most left of the line if the paragraph direction is LTR.
+        // If the paragraph direction is RTL, the first BiDi run is located the most right of
+        // the line.
+        if (bidiRunIndex == 0) {
+            if (lineDirection == Layout.DIR_LEFT_TO_RIGHT) {
+                runFlag |= Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+            } else {
+                runFlag |= Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+            }
+        }
+        if (bidiRunIndex == (bidiRunCount - 1)) {
+            if (lineDirection == Layout.DIR_LEFT_TO_RIGHT) {
+                runFlag |= Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+            } else {
+                runFlag |= Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+            }
+        }
+        return runFlag;
+    }
+
+    /**
+     * Resolve the runFlag for the inline span range.
+     *
+     * @param runFlag the runFlag of the current BiDi run.
+     * @param isRtlRun true for RTL run, false for LTR run.
+     * @param runStart the inclusive BiDi run start offset.
+     * @param runEnd the exclusive BiDi run end offset.
+     * @param spanStart the inclusive span start offset.
+     * @param spanEnd the exclusive span end offset.
+     * @return the resolved runFlag.
+     */
+    @VisibleForTesting
+    public static int resolveRunFlagForSubSequence(int runFlag, boolean isRtlRun, int runStart,
+            int runEnd, int spanStart, int spanEnd) {
+        if (runFlag == 0) {
+            // Easy case. If the run is in the middle of the line, any inline span is also in the
+            // middle of the line.
+            return 0;
+        }
+        int localRunFlag = runFlag;
+        if ((runFlag & Paint.TEXT_RUN_FLAG_LEFT_EDGE) != 0) {
+            if (isRtlRun) {
+                if (spanEnd != runEnd) {
+                    // In the RTL context, the last run is the most left run.
+                    localRunFlag &= ~Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+                }
+            } else {  // LTR
+                if (spanStart != runStart) {
+                    // In the LTR context, the first run is the most left run.
+                    localRunFlag &= ~Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+                }
+            }
+        }
+        if ((runFlag & Paint.TEXT_RUN_FLAG_RIGHT_EDGE) != 0) {
+            if (isRtlRun) {
+                if (spanStart != runStart) {
+                    // In the RTL context, the start of the run is the most right run.
+                    localRunFlag &= ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+                }
+            } else {  // LTR
+                if (spanEnd != runEnd) {
+                    // In the LTR context, the last run is the most right position.
+                    localRunFlag &= ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+                }
+            }
+        }
+        return localRunFlag;
+    }
+
+    /**
      * Renders the TextLine.
      *
      * @param c the canvas to render on
@@ -293,11 +438,13 @@
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
+            final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
+
             int segStart = runStart;
             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
                 if (j == runLimit || charAt(j) == TAB_CHAR) {
                     h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom,
-                            runIndex != (runCount - 1) || j != mLen);
+                            runIndex != (runCount - 1) || j != mLen, runFlag);
 
                     if (j != runLimit) {  // charAt(j) == TAB_CHAR
                         h = mDir * nextTab(h * mDir);
@@ -315,10 +462,12 @@
      * @param drawBounds output parameter for drawing bounding box. optional.
      * @param returnDrawWidth true for returning width of the bounding box, false for returning
      *                       total advances.
+     * @param lineInfo an optional output parameter for filling line information.
      * @return the signed width of the line
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public float metrics(FontMetricsInt fmi, @Nullable RectF drawBounds, boolean returnDrawWidth) {
+    public float metrics(FontMetricsInt fmi, @Nullable RectF drawBounds, boolean returnDrawWidth,
+            @Nullable LineInfo lineInfo) {
         if (returnDrawWidth) {
             if (drawBounds == null) {
                 if (mTmpRectForMeasure == null) {
@@ -327,7 +476,7 @@
                 drawBounds = mTmpRectForMeasure;
             }
             drawBounds.setEmpty();
-            float w = measure(mLen, false, fmi, drawBounds);
+            float w = measure(mLen, false, fmi, drawBounds, lineInfo);
             float boundsWidth = drawBounds.width();
             if (Math.abs(w) > boundsWidth) {
                 return w;
@@ -337,7 +486,7 @@
                 return Math.signum(w) * boundsWidth;
             }
         } else {
-            return measure(mLen, false, fmi, drawBounds);
+            return measure(mLen, false, fmi, drawBounds, lineInfo);
         }
     }
 
@@ -354,11 +503,12 @@
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
+            final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
             int segStart = runStart;
             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
                 if (j == runLimit || charAt(j) == TAB_CHAR) {
                     horizontal += shapeRun(consumer, segStart, j, runIsRtl, x + horizontal,
-                            runIndex != (runCount - 1) || j != mLen);
+                            runIndex != (runCount - 1) || j != mLen, runFlag);
 
                     if (j != runLimit) {  // charAt(j) == TAB_CHAR
                         horizontal = mDir * nextTab(horizontal * mDir);
@@ -407,27 +557,33 @@
      *                 the edge of the preceding run's edge. See example above.
      * @param fmi receives metrics information about the requested character, can be null
      * @param drawBounds output parameter for drawing bounding box. optional.
+     * @param lineInfo an optional output parameter for filling line information.
      * @return the signed graphical offset from the leading margin to the requested character edge.
      *         The positive value means the offset is right from the leading edge. The negative
      *         value means the offset is left from the leading edge.
      */
     public float measure(@IntRange(from = 0) int offset, boolean trailing,
-            @NonNull FontMetricsInt fmi, @Nullable RectF drawBounds) {
+            @NonNull FontMetricsInt fmi, @Nullable RectF drawBounds, @Nullable LineInfo lineInfo) {
         if (offset > mLen) {
             throw new IndexOutOfBoundsException(
                     "offset(" + offset + ") should be less than line limit(" + mLen + ")");
         }
+        if (lineInfo != null) {
+            lineInfo.setClusterCount(0);
+        }
         final int target = trailing ? offset - 1 : offset;
         if (target < 0) {
             return 0;
         }
 
         float h = 0;
-        for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+        final int runCount = mDirections.getRunCount();
+        for (int runIndex = 0; runIndex < runCount; runIndex++) {
             final int runStart = mDirections.getRunStart(runIndex);
             if (runStart > mLen) break;
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+            final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
 
             int segStart = runStart;
             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
@@ -437,16 +593,16 @@
 
                     if (targetIsInThisSegment && sameDirection) {
                         return h + measureRun(segStart, offset, j, runIsRtl, fmi, drawBounds, null,
-                                0, h);
+                                0, h, lineInfo, runFlag);
                     }
 
                     final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi, drawBounds,
-                            null, 0, h);
+                            null, 0, h, lineInfo, runFlag);
                     h += sameDirection ? segmentWidth : -segmentWidth;
 
                     if (targetIsInThisSegment) {
                         return h + measureRun(segStart, offset, j, runIsRtl, null, null,  null, 0,
-                                h);
+                                h, lineInfo, runFlag);
                     }
 
                     if (j != runLimit) {  // charAt(j) == TAB_CHAR
@@ -525,19 +681,21 @@
                     + "result, needed: " + mLen + " had: " + advances.length);
         }
         float h = 0;
-        for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+        final int runCount = mDirections.getRunCount();
+        for (int runIndex = 0; runIndex < runCount; runIndex++) {
             final int runStart = mDirections.getRunStart(runIndex);
             if (runStart > mLen) break;
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+            final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
 
             int segStart = runStart;
             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
                 if (j == runLimit || charAt(j) == TAB_CHAR) {
                     final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
-
                     final float segmentWidth =
-                            measureRun(segStart, j, j, runIsRtl, null, null, advances, segStart, 0);
+                            measureRun(segStart, j, j, runIsRtl, null, null, advances, segStart, 0,
+                                    null, runFlag);
 
                     final float oldh = h;
                     h += sameDirection ? segmentWidth : -segmentWidth;
@@ -578,7 +736,7 @@
     }
 
     /**
-     * @see #measure(int, boolean, FontMetricsInt, RectF)
+     * @see #measure(int, boolean, FontMetricsInt, RectF, LineInfo)
      * @return The measure results for all possible offsets
      */
     @VisibleForTesting
@@ -589,11 +747,13 @@
         }
 
         float horizontal = 0;
-        for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+        final int runCount = mDirections.getRunCount();
+        for (int runIndex = 0; runIndex < runCount; runIndex++) {
             final int runStart = mDirections.getRunStart(runIndex);
             if (runStart > mLen) break;
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+            final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
 
             int segStart = runStart;
             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; ++j) {
@@ -610,7 +770,7 @@
                     final float previousSegEndHorizontal = measurement[segStart];
                     final float width =
                             measureRun(segStart, j, j, runIsRtl, fmi, null, measurement, segStart,
-                                    0);
+                                    0, null, runFlag);
                     horizontal += sameDirection ? width : -width;
 
                     float currHorizontal = sameDirection ? oldHorizontal : horizontal;
@@ -667,22 +827,24 @@
      * @param y the baseline
      * @param bottom the bottom of the line
      * @param needWidth true if the width value is required.
+     * @param runFlag the run flag to be applied for this run.
      * @return the signed width of the run, based on the paragraph direction.
      * Only valid if needWidth is true.
      */
     private float drawRun(Canvas c, int start,
             int limit, boolean runIsRtl, float x, int top, int y, int bottom,
-            boolean needWidth) {
+            boolean needWidth, int runFlag) {
 
         if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
-            float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0);
+            float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null,
+                    runFlag);
             handleRun(start, limit, limit, runIsRtl, c, null, x + w, top,
-                    y, bottom, null, null, false, null, 0);
+                    y, bottom, null, null, false, null, 0, null, runFlag);
             return w;
         }
 
         return handleRun(start, limit, limit, runIsRtl, c, null, x, top,
-                y, bottom, null, null, needWidth, null, 0);
+                y, bottom, null, null, needWidth, null, 0, null, runFlag);
     }
 
     /**
@@ -698,19 +860,22 @@
      * @param advances receives the advance information about the requested run, can be null.
      * @param advancesIndex the start index to fill in the advance information.
      * @param x horizontal offset of the run.
+     * @param lineInfo an optional output parameter for filling line information.
+     * @param runFlag the run flag to be applied for this run.
      * @return the signed width from the start of the run to the leading edge
      * of the character at offset, based on the run (not paragraph) direction
      */
     private float measureRun(int start, int offset, int limit, boolean runIsRtl,
             @Nullable FontMetricsInt fmi, @Nullable RectF drawBounds, @Nullable float[] advances,
-            int advancesIndex, float x) {
+            int advancesIndex, float x, @Nullable LineInfo lineInfo, int runFlag) {
         if (drawBounds != null && (mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
-            float w = -measureRun(start, offset, limit, runIsRtl, null, null, null, 0, 0);
+            float w = -measureRun(start, offset, limit, runIsRtl, null, null, null, 0, 0, null,
+                    runFlag);
             return handleRun(start, offset, limit, runIsRtl, null, null, x + w, 0, 0, 0, fmi,
-                    drawBounds, true, advances, advancesIndex);
+                    drawBounds, true, advances, advancesIndex, lineInfo, runFlag);
         }
         return handleRun(start, offset, limit, runIsRtl, null, null, x, 0, 0, 0, fmi, drawBounds,
-                true, advances, advancesIndex);
+                true, advances, advancesIndex, lineInfo, runFlag);
     }
 
     /**
@@ -722,21 +887,23 @@
      * @param runIsRtl true if the run is right-to-left
      * @param x the position of the run that is closest to the leading margin
      * @param needWidth true if the width value is required.
+     * @param runFlag the run flag to be applied for this run.
      * @return the signed width of the run, based on the paragraph direction.
      * Only valid if needWidth is true.
      */
     private float shapeRun(TextShaper.GlyphsConsumer consumer, int start,
-            int limit, boolean runIsRtl, float x, boolean needWidth) {
+            int limit, boolean runIsRtl, float x, boolean needWidth, int runFlag) {
 
         if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
-            float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0);
+            float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null,
+                    runFlag);
             handleRun(start, limit, limit, runIsRtl, null, consumer, x + w, 0, 0, 0, null, null,
-                    false, null, 0);
+                    false, null, 0, null, runFlag);
             return w;
         }
 
         return handleRun(start, limit, limit, runIsRtl, null, consumer, x, 0, 0, 0, null, null,
-                needWidth, null, 0);
+                needWidth, null, 0, null, runFlag);
     }
 
 
@@ -951,7 +1118,8 @@
         TextPaint wp = mWorkPaint;
         wp.set(mPaint);
         if (mIsJustifying) {
-            wp.setWordSpacing(mAddedWidthForJustify);
+            wp.setWordSpacing(mAddedWordSpacingInPx);
+            wp.setLetterSpacing(mAddedLetterSpacingInPx / wp.getTextSize());  // Convert to Em
         }
 
         int spanStart = runStart;
@@ -1077,16 +1245,35 @@
 
     private float getRunAdvance(TextPaint wp, int start, int end, int contextStart, int contextEnd,
             boolean runIsRtl, int offset, @Nullable float[] advances, int advancesIndex,
-            RectF drawingBounds) {
+            RectF drawingBounds, @Nullable LineInfo lineInfo) {
+        if (lineInfo != null) {
+            if (mRunInfo == null) {
+                mRunInfo = new Paint.RunInfo();
+            }
+            mRunInfo.setClusterCount(0);
+        } else {
+            mRunInfo = null;
+        }
         if (mCharsValid) {
-            return wp.getRunCharacterAdvance(mChars, start, end, contextStart, contextEnd,
-                    runIsRtl, offset, advances, advancesIndex, drawingBounds);
+            float r = wp.getRunCharacterAdvance(mChars, start, end, contextStart, contextEnd,
+                    runIsRtl, offset, advances, advancesIndex, drawingBounds, mRunInfo);
+            if (lineInfo != null) {
+                lineInfo.setClusterCount(lineInfo.getClusterCount() + mRunInfo.getClusterCount());
+            }
+            return r;
         } else {
             final int delta = mStart;
-            if (mComputed == null || advances != null) {
-                return wp.getRunCharacterAdvance(mText, delta + start, delta + end,
+            // TODO: Add cluster information to the PrecomputedText for better performance of
+            // justification.
+            if (mComputed == null || advances != null || lineInfo != null) {
+                float r = wp.getRunCharacterAdvance(mText, delta + start, delta + end,
                         delta + contextStart, delta + contextEnd, runIsRtl,
-                        delta + offset, advances, advancesIndex, drawingBounds);
+                        delta + offset, advances, advancesIndex, drawingBounds, mRunInfo);
+                if (lineInfo != null) {
+                    lineInfo.setClusterCount(
+                            lineInfo.getClusterCount() + mRunInfo.getClusterCount());
+                }
+                return r;
             } else {
                 if (drawingBounds != null) {
                     if (mTmpRectForPrecompute == null) {
@@ -1120,6 +1307,8 @@
      * @param decorations the list of locations and paremeters for drawing decorations
      * @param advances receives the advance information about the requested run, can be null.
      * @param advancesIndex the start index to fill in the advance information.
+     * @param lineInfo an optional output parameter for filling line information.
+     * @param runFlag the run flag to be applied for this run.
      * @return the signed width of the run based on the run direction; only
      * valid if needWidth is true
      */
@@ -1128,10 +1317,11 @@
             Canvas c, TextShaper.GlyphsConsumer consumer, float x, int top, int y, int bottom,
             FontMetricsInt fmi, RectF drawBounds, boolean needWidth, int offset,
             @Nullable ArrayList<DecorationInfo> decorations,
-            @Nullable float[] advances, int advancesIndex) {
-
+            @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo,
+            int runFlag) {
         if (mIsJustifying) {
-            wp.setWordSpacing(mAddedWidthForJustify);
+            wp.setWordSpacing(mAddedWordSpacingInPx);
+            wp.setLetterSpacing(mAddedLetterSpacingInPx / wp.getTextSize());  // Convert to Em
         }
         // Get metrics first (even for empty strings or "0" width runs)
         if (drawBounds != null && fmi == null) {
@@ -1147,7 +1337,16 @@
         }
 
         float totalWidth = 0;
-
+        if ((runFlag & Paint.TEXT_RUN_FLAG_LEFT_EDGE) == Paint.TEXT_RUN_FLAG_LEFT_EDGE) {
+            wp.setFlags(wp.getFlags() | Paint.TEXT_RUN_FLAG_LEFT_EDGE);
+        } else {
+            wp.setFlags(wp.getFlags() & ~Paint.TEXT_RUN_FLAG_LEFT_EDGE);
+        }
+        if ((runFlag & Paint.TEXT_RUN_FLAG_RIGHT_EDGE) == Paint.TEXT_RUN_FLAG_RIGHT_EDGE) {
+            wp.setFlags(wp.getFlags() | Paint.TEXT_RUN_FLAG_RIGHT_EDGE);
+        } else {
+            wp.setFlags(wp.getFlags() & ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE);
+        }
         final int numDecorations = decorations == null ? 0 : decorations.size();
         if (needWidth || ((c != null || consumer != null) && (wp.bgColor != 0
                 || numDecorations != 0 || runIsRtl))) {
@@ -1155,7 +1354,8 @@
                 mTmpRectForPaintAPI = new RectF();
             }
             totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset,
-                    advances, advancesIndex, drawBounds == null ? null : mTmpRectForPaintAPI);
+                    advances, advancesIndex, drawBounds == null ? null : mTmpRectForPaintAPI,
+                    lineInfo);
             if (drawBounds != null) {
                 if (runIsRtl) {
                     mTmpRectForPaintAPI.offset(x - totalWidth, 0);
@@ -1206,9 +1406,9 @@
                     final int decorationStart = Math.max(info.start, start);
                     final int decorationEnd = Math.min(info.end, offset);
                     float decorationStartAdvance = getRunAdvance(wp, start, end, contextStart,
-                            contextEnd, runIsRtl, decorationStart, null, 0, null);
+                            contextEnd, runIsRtl, decorationStart, null, 0, null, null);
                     float decorationEndAdvance = getRunAdvance(wp, start, end, contextStart,
-                            contextEnd, runIsRtl, decorationEnd, null, 0, null);
+                            contextEnd, runIsRtl, decorationEnd, null, 0, null, null);
                     final float decorationXLeft, decorationXRight;
                     if (runIsRtl) {
                         decorationXLeft = rightX - decorationEndAdvance;
@@ -1377,6 +1577,8 @@
      * @param needWidth true if the width is required
      * @param advances receives the advance information about the requested run, can be null.
      * @param advancesIndex the start index to fill in the advance information.
+     * @param lineInfo an optional output parameter for filling line information.
+     * @param runFlag the run flag to be applied for this run.
      * @return the signed width of the run based on the run direction; only
      * valid if needWidth is true
      */
@@ -1384,7 +1586,8 @@
             int limit, boolean runIsRtl, Canvas c,
             TextShaper.GlyphsConsumer consumer, float x, int top, int y,
             int bottom, FontMetricsInt fmi, RectF drawBounds, boolean needWidth,
-            @Nullable float[] advances, int advancesIndex) {
+            @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo,
+            int runFlag) {
 
         if (measureLimit < start || measureLimit > limit) {
             throw new IndexOutOfBoundsException("measureLimit (" + measureLimit + ") is out of "
@@ -1431,7 +1634,7 @@
             wp.setEndHyphenEdit(adjustEndHyphenEdit(limit, wp.getEndHyphenEdit()));
             return handleText(wp, start, limit, start, limit, runIsRtl, c, consumer, x, top,
                     y, bottom, fmi, drawBounds, needWidth, measureLimit, null, advances,
-                    advancesIndex);
+                    advancesIndex, lineInfo, runFlag);
         }
 
         // Shaping needs to take into account context up to metric boundaries,
@@ -1512,6 +1715,9 @@
                     // and use.
                     activePaint.set(wp);
                 } else if (!equalAttributes(wp, activePaint)) {
+                    final int spanRunFlag = resolveRunFlagForSubSequence(
+                            runFlag, runIsRtl, start, measureLimit, activeStart, activeEnd);
+
                     // The style of the present chunk of text is substantially different from the
                     // style of the previous chunk. We need to handle the active piece of text
                     // and restart with the present chunk.
@@ -1523,7 +1729,7 @@
                             consumer, x, top, y, bottom, fmi, drawBounds,
                             needWidth || activeEnd < measureLimit,
                             Math.min(activeEnd, mlimit), mDecorations,
-                            advances, advancesIndex + activeStart - start);
+                            advances, advancesIndex + activeStart - start, lineInfo, spanRunFlag);
 
                     activeStart = j;
                     activePaint.set(wp);
@@ -1543,6 +1749,9 @@
                     mDecorations.add(copy);
                 }
             }
+
+            final int spanRunFlag = resolveRunFlagForSubSequence(
+                    runFlag, runIsRtl, start, measureLimit, activeStart, activeEnd);
             // Handle the final piece of text.
             activePaint.setStartHyphenEdit(
                     adjustStartHyphenEdit(activeStart, mPaint.getStartHyphenEdit()));
@@ -1551,7 +1760,7 @@
             x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, consumer, x,
                     top, y, bottom, fmi, drawBounds, needWidth || activeEnd < measureLimit,
                     Math.min(activeEnd, mlimit), mDecorations,
-                    advances, advancesIndex + activeStart - start);
+                    advances, advancesIndex + activeStart - start, lineInfo, spanRunFlag);
         }
 
         return x - originalX;
@@ -1572,7 +1781,6 @@
      */
     private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
             int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
-
         if (mCharsValid) {
             int count = end - start;
             int contextCount = contextEnd - contextStart;
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index a1885ae..6e45fea 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -82,3 +82,17 @@
   description: "A feature flag that implement inter character justification."
   bug: "283193133"
 }
+
+flag {
+  name: "escape_clears_focus"
+  namespace: "text"
+  description: "Feature flag for clearing focus when the escape key is pressed."
+  bug: "312921137"
+}
+
+flag {
+  name: "insert_mode_crash_when_delete"
+  namespace: "text"
+  description: "A feature flag for fixing the crash while delete text in insert mode."
+  bug: "314254153"
+}
diff --git a/core/java/android/tracing/perfetto/CreateIncrementalStateArgs.java b/core/java/android/tracing/perfetto/CreateIncrementalStateArgs.java
new file mode 100644
index 0000000..819cc8c
--- /dev/null
+++ b/core/java/android/tracing/perfetto/CreateIncrementalStateArgs.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tracing.perfetto;
+
+import android.annotation.Nullable;
+
+/**
+ * @hide
+ * @param <DataSourceInstanceType> The type of datasource instance this state applied to.
+ */
+public class CreateIncrementalStateArgs<DataSourceInstanceType extends DataSourceInstance> {
+    private final DataSource<DataSourceInstanceType, Object, Object> mDataSource;
+    private final int mInstanceIndex;
+
+    CreateIncrementalStateArgs(DataSource dataSource, int instanceIndex) {
+        this.mDataSource = dataSource;
+        this.mInstanceIndex = instanceIndex;
+    }
+
+    /**
+     * Gets the datasource instance for this state with a lock.
+     * releaseDataSourceInstanceLocked must be called before this can be called again.
+     * @return The data source instance for this state.
+     *         Null if the datasource instance no longer exists.
+     */
+    public @Nullable DataSourceInstanceType getDataSourceInstanceLocked() {
+        return mDataSource.getDataSourceInstanceLocked(mInstanceIndex);
+    }
+}
diff --git a/core/java/android/tracing/perfetto/CreateTlsStateArgs.java b/core/java/android/tracing/perfetto/CreateTlsStateArgs.java
new file mode 100644
index 0000000..3fad2d1
--- /dev/null
+++ b/core/java/android/tracing/perfetto/CreateTlsStateArgs.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tracing.perfetto;
+
+import android.annotation.Nullable;
+
+/**
+ * @hide
+ * @param <DataSourceInstanceType> The type of datasource instance this state applied to.
+ */
+public class CreateTlsStateArgs<DataSourceInstanceType extends DataSourceInstance> {
+    private final DataSource<DataSourceInstanceType, Object, Object> mDataSource;
+    private final int mInstanceIndex;
+
+    CreateTlsStateArgs(DataSource dataSource, int instanceIndex) {
+        this.mDataSource = dataSource;
+        this.mInstanceIndex = instanceIndex;
+    }
+
+    /**
+     * Gets the datasource instance for this state with a lock.
+     * releaseDataSourceInstanceLocked must be called before this can be called again.
+     * @return The data source instance for this state.
+     *         Null if the datasource instance no longer exists.
+     */
+    public @Nullable DataSourceInstanceType getDataSourceInstanceLocked() {
+        return mDataSource.getDataSourceInstanceLocked(mInstanceIndex);
+    }
+}
diff --git a/core/java/android/tracing/perfetto/DataSource.java b/core/java/android/tracing/perfetto/DataSource.java
new file mode 100644
index 0000000..4e08aee
--- /dev/null
+++ b/core/java/android/tracing/perfetto/DataSource.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tracing.perfetto;
+
+import android.util.proto.ProtoInputStream;
+
+/**
+ * Templated base class meant to be derived by embedders to create a custom data
+ * source.
+ *
+ * @param <DataSourceInstanceType> The type for the DataSource instances that will be created from
+ *                                 this DataSource type.
+ * @param <TlsStateType> The type of the custom TLS state, if any is used.
+ * @param <IncrementalStateType> The type of the custom incremental state, if any is used.
+ *
+ * @hide
+ */
+public abstract class DataSource<DataSourceInstanceType extends DataSourceInstance,
+        TlsStateType, IncrementalStateType> {
+    protected final long mNativeObj;
+
+    public final String name;
+
+    /**
+     * A function implemented by each datasource to create a new data source instance.
+     *
+     * @param configStream A ProtoInputStream to read the tracing instance's config.
+     * @return A new data source instance setup with the provided config.
+     */
+    public abstract DataSourceInstanceType createInstance(
+            ProtoInputStream configStream, int instanceIndex);
+
+    /**
+     * Constructor for datasource base class.
+     *
+     * @param name The fully qualified name of the datasource.
+     */
+    public DataSource(String name) {
+        this.name = name;
+        this.mNativeObj = nativeCreate(this, name);
+    }
+
+    /**
+     * The main tracing method. Tracing code should call this passing a lambda as
+     * argument, with the following signature: void(TraceContext).
+     * <p>
+     * The lambda will be called synchronously (i.e., always before trace()
+     * returns) only if tracing is enabled and the data source has been enabled in
+     * the tracing config.
+     * <p>
+     * The lambda can be called more than once per trace() call, in the case of
+     * concurrent tracing sessions (or even if the data source is instantiated
+     * twice within the same trace config).
+     *
+     * @param fun The tracing lambda that will be called with the tracing contexts of each active
+     *            tracing instance.
+     */
+    public final void trace(
+            TraceFunction<DataSourceInstanceType, TlsStateType, IncrementalStateType> fun) {
+        nativeTrace(mNativeObj, fun);
+    }
+
+    /**
+     * Flush any trace data from this datasource that has not yet been flushed.
+     */
+    public final void flush() {
+        nativeFlushAll(mNativeObj);
+    }
+
+    /**
+     * Override this method to create a custom TlsState object for your DataSource. A new instance
+     * will be created per trace instance per thread.
+     *
+     * NOTE: Should only be called from native side.
+     */
+    protected TlsStateType createTlsState(CreateTlsStateArgs<DataSourceInstanceType> args) {
+        return null;
+    }
+
+    /**
+     * Override this method to create and use a custom IncrementalState object for your DataSource.
+     *
+     * NOTE: Should only be called from native side.
+     */
+    protected IncrementalStateType createIncrementalState(
+            CreateIncrementalStateArgs<DataSourceInstanceType> args) {
+        return null;
+    }
+
+    /**
+     * Registers the data source on all tracing backends, including ones that
+     * connect after the registration. Doing so enables the data source to receive
+     * Setup/Start/Stop notifications and makes the trace() method work when
+     * tracing is enabled and the data source is selected.
+     * <p>
+     * NOTE: Once registered, we cannot unregister the data source. Therefore, we should avoid
+     * creating and registering data source where not strictly required. This is a fundamental
+     * limitation of Perfetto itself.
+     *
+     * @param params Params to initialize the datasource with.
+     */
+    public void register(DataSourceParams params) {
+        nativeRegisterDataSource(this.mNativeObj, params.bufferExhaustedPolicy);
+    }
+
+    /**
+     * Gets the datasource instance with a specified index.
+     * IMPORTANT: releaseDataSourceInstance must be called after using the datasource instance.
+     * @param instanceIndex The index of the datasource to lock and get.
+     * @return The DataSourceInstance at index instanceIndex.
+     *         Null if the datasource instance at the requested index doesn't exist.
+     */
+    public DataSourceInstanceType getDataSourceInstanceLocked(int instanceIndex) {
+        return (DataSourceInstanceType) nativeGetPerfettoInstanceLocked(mNativeObj, instanceIndex);
+    }
+
+    /**
+     * Unlock the datasource at the specified index.
+     * @param instanceIndex The index of the datasource to unlock.
+     */
+    protected void releaseDataSourceInstance(int instanceIndex) {
+        nativeReleasePerfettoInstanceLocked(mNativeObj, instanceIndex);
+    }
+
+    /**
+     * Called from native side when a new tracing instance starts.
+     *
+     * @param rawConfig byte array of the PerfettoConfig encoded proto.
+     * @return A new Java DataSourceInstance object.
+     */
+    private DataSourceInstanceType createInstance(byte[] rawConfig, int instanceIndex) {
+        final ProtoInputStream inputStream = new ProtoInputStream(rawConfig);
+        return this.createInstance(inputStream, instanceIndex);
+    }
+
+    private static native void nativeRegisterDataSource(
+            long dataSourcePtr, int bufferExhaustedPolicy);
+
+    private static native long nativeCreate(DataSource thiz, String name);
+    private static native void nativeTrace(
+            long nativeDataSourcePointer, TraceFunction traceFunction);
+    private static native void nativeFlushAll(long nativeDataSourcePointer);
+    private static native long nativeGetFinalizer();
+
+    private static native DataSourceInstance nativeGetPerfettoInstanceLocked(
+            long dataSourcePtr, int dsInstanceIdx);
+    private static native void nativeReleasePerfettoInstanceLocked(
+            long dataSourcePtr, int dsInstanceIdx);
+}
diff --git a/core/java/android/tracing/perfetto/DataSourceInstance.java b/core/java/android/tracing/perfetto/DataSourceInstance.java
new file mode 100644
index 0000000..4994501
--- /dev/null
+++ b/core/java/android/tracing/perfetto/DataSourceInstance.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tracing.perfetto;
+
+/**
+ * @hide
+ */
+public abstract class DataSourceInstance implements AutoCloseable {
+    private final DataSource mDataSource;
+    private final int mInstanceIndex;
+
+    public DataSourceInstance(DataSource dataSource, int instanceIndex) {
+        this.mDataSource = dataSource;
+        this.mInstanceIndex = instanceIndex;
+    }
+
+    /**
+     * Executed when the tracing instance starts running.
+     * <p>
+     * NOTE: This callback executes on the Perfetto internal thread and is blocking.
+     *       Anything that is run in this callback should execute quickly.
+     *
+     * @param args Start arguments.
+     */
+    protected void onStart(StartCallbackArguments args) {}
+
+    /**
+     * Executed when a flush is triggered.
+     * <p>
+     * NOTE: This callback executes on the Perfetto internal thread and is blocking.
+     *       Anything that is run in this callback should execute quickly.
+     * @param args Flush arguments.
+     */
+    protected void onFlush(FlushCallbackArguments args) {}
+
+    /**
+     * Executed when the tracing instance is stopped.
+     * <p>
+     * NOTE: This callback executes on the Perfetto internal thread and is blocking.
+     *       Anything that is run in this callback should execute quickly.
+     * @param args Stop arguments.
+     */
+    protected void onStop(StopCallbackArguments args) {}
+
+    @Override
+    public final void close() {
+        this.release();
+    }
+
+    /**
+     * Release the lock on the datasource once you are finished using it.
+     * Only required to be called when instance was retrieved with
+     * `DataSource#getDataSourceInstanceLocked`.
+     */
+    public final void release() {
+        mDataSource.releaseDataSourceInstance(mInstanceIndex);
+    }
+}
diff --git a/core/java/android/tracing/perfetto/DataSourceParams.java b/core/java/android/tracing/perfetto/DataSourceParams.java
new file mode 100644
index 0000000..6cd04e3
--- /dev/null
+++ b/core/java/android/tracing/perfetto/DataSourceParams.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tracing.perfetto;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * DataSource Parameters
+ *
+ * @hide
+ */
+public class DataSourceParams {
+    /**
+     * @hide
+     */
+    @IntDef(value = {
+        PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP,
+        PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PerfettoDsBufferExhausted {}
+
+    // If the data source runs out of space when trying to acquire a new chunk,
+    // it will drop data.
+    public static final int PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP = 0;
+
+    // If the data source runs out of space when trying to acquire a new chunk,
+    // it will stall, retry and eventually abort if a free chunk is not acquired
+    // after a while.
+    public static final int PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT = 1;
+
+    public static DataSourceParams DEFAULTS =
+            new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP);
+
+    public DataSourceParams(@PerfettoDsBufferExhausted int bufferExhaustedPolicy) {
+        this.bufferExhaustedPolicy = bufferExhaustedPolicy;
+    }
+
+    public final @PerfettoDsBufferExhausted int bufferExhaustedPolicy;
+}
diff --git a/core/java/android/tracing/perfetto/FlushCallbackArguments.java b/core/java/android/tracing/perfetto/FlushCallbackArguments.java
new file mode 100644
index 0000000..ecf6aee
--- /dev/null
+++ b/core/java/android/tracing/perfetto/FlushCallbackArguments.java
@@ -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 android.tracing.perfetto;
+
+/**
+ * @hide
+ */
+public class FlushCallbackArguments {
+}
diff --git a/core/java/android/tracing/perfetto/InitArguments.java b/core/java/android/tracing/perfetto/InitArguments.java
new file mode 100644
index 0000000..da8c273
--- /dev/null
+++ b/core/java/android/tracing/perfetto/InitArguments.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tracing.perfetto;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @hide
+ */
+public class InitArguments {
+    public final @PerfettoBackend int backends;
+
+    /**
+     * @hide
+     */
+    @IntDef(value = {
+            PERFETTO_BACKEND_IN_PROCESS,
+            PERFETTO_BACKEND_SYSTEM,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PerfettoBackend {}
+
+    // The in-process tracing backend. Keeps trace buffers in the process memory.
+    public static final int PERFETTO_BACKEND_IN_PROCESS = (1 << 0);
+
+    // The system tracing backend. Connects to the system tracing service (e.g.
+    // on Linux/Android/Mac uses a named UNIX socket).
+    public static final int PERFETTO_BACKEND_SYSTEM = (1 << 1);
+
+    public static InitArguments DEFAULTS = new InitArguments(PERFETTO_BACKEND_SYSTEM);
+
+    public static InitArguments TESTING = new InitArguments(PERFETTO_BACKEND_IN_PROCESS);
+
+    public InitArguments(@PerfettoBackend int backends) {
+        this.backends = backends;
+    }
+}
diff --git a/core/java/android/tracing/perfetto/Producer.java b/core/java/android/tracing/perfetto/Producer.java
new file mode 100644
index 0000000..a1b3eb7
--- /dev/null
+++ b/core/java/android/tracing/perfetto/Producer.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tracing.perfetto;
+
+/**
+ * @hide
+ */
+public class Producer {
+
+    /**
+     * Initializes the global Perfetto producer.
+     *
+     * @param args arguments on how to initialize the Perfetto producer.
+     */
+    public static void init(InitArguments args) {
+        nativePerfettoProducerInit(args.backends);
+    }
+
+    private static native void nativePerfettoProducerInit(int backends);
+}
diff --git a/core/java/android/tracing/perfetto/StartCallbackArguments.java b/core/java/android/tracing/perfetto/StartCallbackArguments.java
new file mode 100644
index 0000000..9739d27
--- /dev/null
+++ b/core/java/android/tracing/perfetto/StartCallbackArguments.java
@@ -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 android.tracing.perfetto;
+
+/**
+ * @hide
+ */
+public class StartCallbackArguments {
+}
diff --git a/core/java/android/tracing/perfetto/StopCallbackArguments.java b/core/java/android/tracing/perfetto/StopCallbackArguments.java
new file mode 100644
index 0000000..0cd1a18
--- /dev/null
+++ b/core/java/android/tracing/perfetto/StopCallbackArguments.java
@@ -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 android.tracing.perfetto;
+
+/**
+ * @hide
+ */
+public class StopCallbackArguments {
+}
diff --git a/core/java/android/tracing/perfetto/TraceFunction.java b/core/java/android/tracing/perfetto/TraceFunction.java
new file mode 100644
index 0000000..62941df
--- /dev/null
+++ b/core/java/android/tracing/perfetto/TraceFunction.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tracing.perfetto;
+
+import java.io.IOException;
+
+/**
+ * The interface for the trace function called from native on a trace call with a context.
+ *
+ * @param <DataSourceInstanceType> The type of DataSource this tracing context is for.
+ * @param <TlsStateType> The type of the custom TLS state, if any is used.
+ * @param <IncrementalStateType> The type of the custom incremental state, if any is used.
+ *
+ * @hide
+ */
+public interface TraceFunction<DataSourceInstanceType extends DataSourceInstance,
+        TlsStateType, IncrementalStateType> {
+
+    /**
+     * This function will be called synchronously (i.e., always before trace() returns) only if
+     * tracing is enabled and the data source has been enabled in the tracing config.
+     * It can be called more than once per trace() call, in the case of concurrent tracing sessions
+     * (or even if the data source is instantiated twice within the same trace config).
+     *
+     * @param ctx the tracing context to trace for in the trace function.
+     */
+    void trace(TracingContext<DataSourceInstanceType, TlsStateType, IncrementalStateType> ctx)
+            throws IOException;
+}
diff --git a/core/java/android/tracing/perfetto/TracingContext.java b/core/java/android/tracing/perfetto/TracingContext.java
new file mode 100644
index 0000000..0bce26e
--- /dev/null
+++ b/core/java/android/tracing/perfetto/TracingContext.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tracing.perfetto;
+
+import android.util.proto.ProtoOutputStream;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Argument passed to the lambda function passed to Trace().
+ *
+ * @param <DataSourceInstanceType> The type of the datasource this tracing context is for.
+ * @param <TlsStateType> The type of the custom TLS state, if any is used.
+ * @param <IncrementalStateType> The type of the custom incremental state, if any is used.
+ *
+ * @hide
+ */
+public class TracingContext<DataSourceInstanceType extends DataSourceInstance,
+        TlsStateType, IncrementalStateType> {
+
+    private final long mContextPtr;
+    private final TlsStateType mTlsState;
+    private final IncrementalStateType mIncrementalState;
+    private final List<ProtoOutputStream> mTracePackets = new ArrayList<>();
+
+    // Should only be created from the native side.
+    private TracingContext(long contextPtr, TlsStateType tlsState,
+            IncrementalStateType incrementalState) {
+        this.mContextPtr = contextPtr;
+        this.mTlsState = tlsState;
+        this.mIncrementalState = incrementalState;
+    }
+
+    /**
+     * Creates a new output stream to be used to write a trace packet to. The output stream will be
+     * encoded to the proto binary representation when the callback trace function finishes and
+     * send over to the native side to be included in the proto buffer.
+     *
+     * @return A proto output stream to write a trace packet proto to
+     */
+    public ProtoOutputStream newTracePacket() {
+        final ProtoOutputStream os = new ProtoOutputStream(0);
+        mTracePackets.add(os);
+        return os;
+    }
+
+    /**
+     * Forces a commit of the thread-local tracing data written so far to the
+     * service. This is almost never required (tracing data is periodically
+     * committed as trace pages are filled up) and has a non-negligible
+     * performance hit (requires an IPC + refresh of the current thread-local
+     * chunk). The only case when this should be used is when handling OnStop()
+     * asynchronously, to ensure sure that the data is committed before the
+     * Stop timeout expires.
+     */
+    public void flush() {
+        nativeFlush(this, mContextPtr);
+    }
+
+    /**
+     * Can optionally be used to store custom per-sequence
+     * session data, which is not reset when incremental state is cleared
+     * (e.g. configuration options).
+     *
+     * @return The TlsState instance for the tracing thread and instance.
+     */
+    public TlsStateType getCustomTlsState() {
+        return this.mTlsState;
+    }
+
+    /**
+     * Can optionally be used store custom per-sequence
+     * incremental data (e.g., interning tables).
+     *
+     * @return The current IncrementalState object instance.
+     */
+    public IncrementalStateType getIncrementalState() {
+        return this.mIncrementalState;
+    }
+
+    // Called from native to get trace packets
+    private byte[][] getAndClearAllPendingTracePackets() {
+        byte[][] res = new byte[mTracePackets.size()][];
+        for (int i = 0; i < mTracePackets.size(); i++) {
+            ProtoOutputStream tracePacket = mTracePackets.get(i);
+            res[i] = tracePacket.getBytes();
+        }
+
+        mTracePackets.clear();
+        return res;
+    }
+
+    // private static native void nativeFlush(long nativeDataSourcePointer);
+    private static native void nativeFlush(TracingContext thiz, long ctxPointer);
+}
diff --git a/core/java/android/tracing/transition/TransitionDataSource.java b/core/java/android/tracing/transition/TransitionDataSource.java
new file mode 100644
index 0000000..baece75
--- /dev/null
+++ b/core/java/android/tracing/transition/TransitionDataSource.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tracing.transition;
+
+import android.tracing.perfetto.CreateTlsStateArgs;
+import android.tracing.perfetto.DataSource;
+import android.tracing.perfetto.DataSourceInstance;
+import android.tracing.perfetto.FlushCallbackArguments;
+import android.tracing.perfetto.StartCallbackArguments;
+import android.tracing.perfetto.StopCallbackArguments;
+import android.util.proto.ProtoInputStream;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @hide
+ */
+public class TransitionDataSource
+        extends DataSource<DataSourceInstance, TransitionDataSource.TlsState, Void> {
+    public static String DATA_SOURCE_NAME = "com.android.wm.shell.transition";
+
+    private final Runnable mOnStartStaticCallback;
+    private final Runnable mOnFlushStaticCallback;
+    private final Runnable mOnStopStaticCallback;
+
+    public TransitionDataSource(Runnable onStart, Runnable onFlush, Runnable onStop) {
+        super(DATA_SOURCE_NAME);
+        this.mOnStartStaticCallback = onStart;
+        this.mOnFlushStaticCallback = onFlush;
+        this.mOnStopStaticCallback = onStop;
+    }
+
+    @Override
+    protected TlsState createTlsState(CreateTlsStateArgs<DataSourceInstance> args) {
+        return new TlsState();
+    }
+
+    public class TlsState {
+        public final Map<String, Integer> handlerMapping = new HashMap<>();
+    }
+
+    @Override
+    public DataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) {
+        return new DataSourceInstance(this, instanceIndex) {
+            @Override
+            protected void onStart(StartCallbackArguments args) {
+                mOnStartStaticCallback.run();
+            }
+
+            @Override
+            protected void onFlush(FlushCallbackArguments args) {
+                mOnFlushStaticCallback.run();
+            }
+
+            @Override
+            protected void onStop(StopCallbackArguments args) {
+                mOnStopStaticCallback.run();
+            }
+        };
+    }
+}
diff --git a/core/java/android/util/Singleton.java b/core/java/android/util/Singleton.java
index 92646b4..d27bef9 100644
--- a/core/java/android/util/Singleton.java
+++ b/core/java/android/util/Singleton.java
@@ -25,6 +25,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public abstract class Singleton<T> {
 
     @UnsupportedAppUsage
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index f28574e..27c509a 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -197,9 +197,28 @@
      * Transfer the currently in progress touch gesture from the host to the requested
      * {@link SurfaceControlViewHost.SurfacePackage}. This requires that the
      * SurfaceControlViewHost was created with the current host's inputToken.
+     * <p>
+     * When the touch is transferred, the window currently receiving touch gets an ACTION_CANCEL
+     * and does not receive any further input events for this gesture.
+     * <p>
+     * The transferred-to window receives an ACTION_DOWN event and then the remainder of the
+     * input events for this gesture. It does not receive any of the previous events of this gesture
+     * that the originating window received.
+     * <p>
+     * The "transferTouch" API only works for the current gesture. When a new gesture arrives,
+     * input dispatcher will do a new round of hit testing. So, if the "host" window is still the
+     * first thing that's being touched, then it will receive the new gesture again. It will
+     * again be up to the host to transfer this new gesture to the embedded.
+     * <p>
+     * Once the transferred-to window receives the gesture, it can choose to give up this gesture
+     * and send it to another window that it's linked to (it can't be an arbitrary window for
+     * security reasons) using the same transferTouch API. Only the window currently receiving
+     * touch is allowed to transfer the gesture.
      *
      * @param surfacePackage The SurfacePackage to transfer the gesture to.
      * @return Whether the touch stream was transferred.
+     * @see SurfaceControlViewHost#transferTouchGestureToHost() for the reverse to transfer touch
+     * gesture from the embedded to the host.
      */
     @FlaggedApi(Flags.FLAG_TRANSFER_GESTURE_TO_EMBEDDED)
     default boolean transferHostTouchGestureToEmbedded(
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index a74cbe4..f0e673b 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -378,6 +378,13 @@
     }
 
     /**
+     * @hide
+     */
+    public Looper getLooper() {
+        return mLooper;
+    }
+
+    /**
      * The amount of time, in milliseconds, between each frame of the animation.
      * <p>
      * This is a requested time that the animation will attempt to honor, but the actual delay
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 1908c64c..fbadef3 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -2079,6 +2079,7 @@
      *
      * @see Display#getSupportedModes()
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static final class Mode implements Parcelable {
         /**
          * @hide
@@ -2467,6 +2468,7 @@
      * <p>You can get an instance for a given {@link Display} object with
      * {@link Display#getHdrCapabilities getHdrCapabilities()}.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static final class HdrCapabilities implements Parcelable {
         /**
          * Invalid luminance value.
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 981911e..5654bc1 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -51,6 +51,7 @@
  * Describes the characteristics of a particular logical display.
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class DisplayInfo implements Parcelable {
     /**
      * The surface flinger layer stack associated with this logical display.
diff --git a/core/java/android/view/EventLogTags.logtags b/core/java/android/view/EventLogTags.logtags
index cc0b18a..f1cd671 100644
--- a/core/java/android/view/EventLogTags.logtags
+++ b/core/java/android/view/EventLogTags.logtags
@@ -59,5 +59,10 @@
 # Enqueue Input Event
 62002 view_enqueue_input_event (eventType|3),(action|3)
 
+# following other view events defined in system/logging/logcat/event.logtags
+# ViewRoot Draw Events
+60004 viewroot_draw_event (window|3),(event|3)
+
+
 # NOTE - the range 1000000-2000000 is reserved for partners and others who
 # want to define their own log tags without conflicting with the core platform.
\ No newline at end of file
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 36b74e3..7903050 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -69,12 +69,13 @@
 import android.view.displayhash.DisplayHash;
 import android.view.displayhash.VerifiedDisplayHash;
 import android.window.AddToSurfaceSyncGroupResult;
+import android.window.IScreenRecordingCallback;
 import android.window.ISurfaceSyncGroupCompletedListener;
 import android.window.ITaskFpsCallback;
-import android.window.ScreenCapture;
-import android.window.WindowContextInfo;
 import android.window.ITrustedPresentationListener;
+import android.window.ScreenCapture;
 import android.window.TrustedPresentationThresholds;
+import android.window.WindowContextInfo;
 
 /**
  * System private interface to the window manager.
@@ -1083,4 +1084,8 @@
 
 
     void unregisterTrustedPresentationListener(in ITrustedPresentationListener listener, int id);
+
+    boolean registerScreenRecordingCallback(IScreenRecordingCallback callback);
+
+    void unregisterScreenRecordingCallback(IScreenRecordingCallback callback);
 }
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 9bf43a3..d2c25cd 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -235,7 +235,7 @@
      */
     oneway void setWallpaperDisplayOffset(IBinder windowToken, int x, int y);
 
-    Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
+    oneway void sendWallpaperCommand(IBinder window, String action, int x, int y,
             int z, in Bundle extras, boolean sync);
 
     @UnsupportedAppUsage
@@ -370,4 +370,14 @@
     boolean transferEmbeddedTouchFocusToHost(IWindow embeddedWindow);
 
     boolean transferHostTouchGestureToEmbedded(IWindow hostWindow, IBinder transferTouchToken);
+
+    /**
+     * Moves the focus to the adjacent window if there is one in the given direction. This can only
+     * move the focus to the window in the same leaf task.
+     *
+     * @param fromWindow The calling window that the focus is moved from.
+     * @param direction The {@link android.view.View.FocusDirection} that the new focus should go.
+     * @return {@code true} if the focus changes. Otherwise, {@code false}.
+     */
+    boolean moveFocusToAdjacentWindow(IWindow fromWindow, int direction);
 }
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 17a3a12..7f1e037 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -34,11 +34,11 @@
 import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
 import static android.view.InsetsController.LayoutInsetsDuringAnimation;
 import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ISIDE_BOTTOM;
-import static android.view.InsetsState.ISIDE_FLOATING;
-import static android.view.InsetsState.ISIDE_LEFT;
-import static android.view.InsetsState.ISIDE_RIGHT;
-import static android.view.InsetsState.ISIDE_TOP;
+import static android.view.InsetsSource.SIDE_BOTTOM;
+import static android.view.InsetsSource.SIDE_NONE;
+import static android.view.InsetsSource.SIDE_LEFT;
+import static android.view.InsetsSource.SIDE_RIGHT;
+import static android.view.InsetsSource.SIDE_TOP;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -60,7 +60,7 @@
 import android.util.SparseIntArray;
 import android.util.SparseSetArray;
 import android.util.proto.ProtoOutputStream;
-import android.view.InsetsState.InternalInsetsSide;
+import android.view.InsetsSource.InternalInsetsSide;
 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimation.Bounds;
@@ -142,7 +142,7 @@
             if (mHasZeroInsetsIme) {
                 // IME has shownInsets of ZERO, and can't map to a side by default.
                 // Map zero insets IME to bottom, making it a special case of bottom insets.
-                idSideMap.put(ID_IME, ISIDE_BOTTOM);
+                idSideMap.put(ID_IME, SIDE_BOTTOM);
             }
             buildSideControlsMap(idSideMap, mSideControlsMap, controls);
         } else {
@@ -286,10 +286,10 @@
         }
         final Insets offset = Insets.subtract(mShownInsets, mPendingInsets);
         final ArrayList<SurfaceParams> params = new ArrayList<>();
-        updateLeashesForSide(ISIDE_LEFT, offset.left, params, outState, mPendingAlpha);
-        updateLeashesForSide(ISIDE_TOP, offset.top, params, outState, mPendingAlpha);
-        updateLeashesForSide(ISIDE_RIGHT, offset.right, params, outState, mPendingAlpha);
-        updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, params, outState, mPendingAlpha);
+        updateLeashesForSide(SIDE_LEFT, offset.left, params, outState, mPendingAlpha);
+        updateLeashesForSide(SIDE_TOP, offset.top, params, outState, mPendingAlpha);
+        updateLeashesForSide(SIDE_RIGHT, offset.right, params, outState, mPendingAlpha);
+        updateLeashesForSide(SIDE_BOTTOM, offset.bottom, params, outState, mPendingAlpha);
 
         mController.applySurfaceParams(params.toArray(new SurfaceParams[params.size()]));
         mCurrentInsets = mPendingInsets;
@@ -499,19 +499,19 @@
         final float surfaceOffset = mTranslator != null
                 ? mTranslator.translateLengthInAppWindowToScreen(offset) : offset;
         switch (side) {
-            case ISIDE_LEFT:
+            case SIDE_LEFT:
                 m.postTranslate(-surfaceOffset, 0);
                 frame.offset(-offset, 0);
                 break;
-            case ISIDE_TOP:
+            case SIDE_TOP:
                 m.postTranslate(0, -surfaceOffset);
                 frame.offset(0, -offset);
                 break;
-            case ISIDE_RIGHT:
+            case SIDE_RIGHT:
                 m.postTranslate(surfaceOffset, 0);
                 frame.offset(offset, 0);
                 break;
-            case ISIDE_BOTTOM:
+            case SIDE_BOTTOM:
                 m.postTranslate(0, surfaceOffset);
                 frame.offset(0, offset);
                 break;
@@ -543,9 +543,10 @@
                 // control may be null if it got revoked.
                 continue;
             }
-            @InternalInsetsSide int side = InsetsState.getInsetSide(control.getInsetsHint());
-            if (side == ISIDE_FLOATING && control.getType() == WindowInsets.Type.ime()) {
-                side = ISIDE_BOTTOM;
+            @InternalInsetsSide int side = InsetsSource.getInsetSide(control.getInsetsHint());
+            if (side == SIDE_NONE && control.getType() == WindowInsets.Type.ime()) {
+                // IME might not provide insets when it is fullscreen or floating.
+                side = SIDE_BOTTOM;
             }
             sideControlsMap.add(side, control);
         }
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 0927d45..bc33d5e 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import static android.view.InsetsSourceProto.FRAME;
+import static android.view.InsetsSourceProto.TYPE;
 import static android.view.InsetsSourceProto.TYPE_NUMBER;
 import static android.view.InsetsSourceProto.VISIBLE;
 import static android.view.InsetsSourceProto.VISIBLE_FRAME;
@@ -46,6 +47,24 @@
  */
 public class InsetsSource implements Parcelable {
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "SIDE_", value = {
+            SIDE_NONE,
+            SIDE_LEFT,
+            SIDE_TOP,
+            SIDE_RIGHT,
+            SIDE_BOTTOM,
+            SIDE_UNKNOWN
+    })
+    public @interface InternalInsetsSide {}
+
+    static final int SIDE_NONE = 0;
+    static final int SIDE_LEFT = 1;
+    static final int SIDE_TOP = 2;
+    static final int SIDE_RIGHT = 3;
+    static final int SIDE_BOTTOM = 4;
+    static final int SIDE_UNKNOWN = 5;
+
     /** The insets source ID of IME */
     public static final int ID_IME = createId(null, 0, ime());
 
@@ -101,6 +120,12 @@
 
     private boolean mVisible;
 
+    /**
+     * Used to decide which side of the relative frame should receive insets when the frame fully
+     * covers the relative frame.
+     */
+    private @InternalInsetsSide int mSideHint = SIDE_NONE;
+
     private final Rect mTmpFrame = new Rect();
 
     public InsetsSource(int id, @InsetsType int type) {
@@ -119,6 +144,7 @@
                 ? new Rect(other.mVisibleFrame)
                 : null;
         mFlags = other.mFlags;
+        mSideHint = other.mSideHint;
     }
 
     public void set(InsetsSource other) {
@@ -128,6 +154,7 @@
                 ? new Rect(other.mVisibleFrame)
                 : null;
         mFlags = other.mFlags;
+        mSideHint = other.mSideHint;
     }
 
     public InsetsSource setFrame(int left, int top, int right, int bottom) {
@@ -160,6 +187,18 @@
         return this;
     }
 
+    /**
+     * Updates the side hint which is used to decide which side of the relative frame should receive
+     * insets when the frame fully covers the relative frame.
+     *
+     * @param bounds A rectangle which contains the frame. It will be used to calculate the hint.
+     */
+    public InsetsSource updateSideHint(Rect bounds) {
+        mSideHint = getInsetSide(
+                calculateInsets(bounds, mFrame, true /* ignoreVisibility */));
+        return this;
+    }
+
     public int getId() {
         return mId;
     }
@@ -236,8 +275,21 @@
             return Insets.of(0, 0, 0, mTmpFrame.height());
         }
 
-        // Intersecting at top/bottom
-        if (mTmpFrame.width() == relativeFrame.width()) {
+        if (mTmpFrame.equals(relativeFrame)) {
+            // Covering all sides
+            switch (mSideHint) {
+                default:
+                case SIDE_LEFT:
+                    return Insets.of(mTmpFrame.width(), 0, 0, 0);
+                case SIDE_TOP:
+                    return Insets.of(0, mTmpFrame.height(), 0, 0);
+                case SIDE_RIGHT:
+                    return Insets.of(0, 0, mTmpFrame.width(), 0);
+                case SIDE_BOTTOM:
+                    return Insets.of(0, 0, 0, mTmpFrame.height());
+            }
+        } else if (mTmpFrame.width() == relativeFrame.width()) {
+            // Intersecting at top/bottom
             if (mTmpFrame.top == relativeFrame.top) {
                 return Insets.of(0, mTmpFrame.height(), 0, 0);
             } else if (mTmpFrame.bottom == relativeFrame.bottom) {
@@ -249,9 +301,8 @@
             if (mTmpFrame.top == 0) {
                 return Insets.of(0, mTmpFrame.height(), 0, 0);
             }
-        }
-        // Intersecting at left/right
-        else if (mTmpFrame.height() == relativeFrame.height()) {
+        } else if (mTmpFrame.height() == relativeFrame.height()) {
+            // Intersecting at left/right
             if (mTmpFrame.left == relativeFrame.left) {
                 return Insets.of(mTmpFrame.width(), 0, 0, 0);
             } else if (mTmpFrame.right == relativeFrame.right) {
@@ -283,6 +334,46 @@
     }
 
     /**
+     * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b
+     * is set in order that this method returns a meaningful result.
+     */
+    static @InternalInsetsSide int getInsetSide(Insets insets) {
+        if (Insets.NONE.equals(insets)) {
+            return SIDE_NONE;
+        }
+        if (insets.left != 0) {
+            return SIDE_LEFT;
+        }
+        if (insets.top != 0) {
+            return SIDE_TOP;
+        }
+        if (insets.right != 0) {
+            return SIDE_RIGHT;
+        }
+        if (insets.bottom != 0) {
+            return SIDE_BOTTOM;
+        }
+        return SIDE_UNKNOWN;
+    }
+
+    static String sideToString(@InternalInsetsSide int side) {
+        switch (side) {
+            case SIDE_NONE:
+                return "NONE";
+            case SIDE_LEFT:
+                return "LEFT";
+            case SIDE_TOP:
+                return "TOP";
+            case SIDE_RIGHT:
+                return "RIGHT";
+            case SIDE_BOTTOM:
+                return "BOTTOM";
+            default:
+                return "UNKNOWN:" + side;
+        }
+    }
+
+    /**
      * Creates an identifier of an {@link InsetsSource}.
      *
      * @param owner An object owned by the owner. Only the owner can modify its own sources.
@@ -331,7 +422,7 @@
     }
 
     public static String flagsToString(@Flags int flags) {
-        final StringJoiner joiner = new StringJoiner(" ");
+        final StringJoiner joiner = new StringJoiner("|");
         if ((flags & FLAG_SUPPRESS_SCRIM) != 0) {
             joiner.add("SUPPRESS_SCRIM");
         }
@@ -352,6 +443,10 @@
      */
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
+        if (!android.os.Flags.androidOsBuildVanillaIceCream()) {
+            // Deprecated since V.
+            proto.write(TYPE, WindowInsets.Type.toString(mType));
+        }
         mFrame.dumpDebug(proto, FRAME);
         if (mVisibleFrame != null) {
             mVisibleFrame.dumpDebug(proto, VISIBLE_FRAME);
@@ -371,6 +466,7 @@
         }
         pw.print(" visible="); pw.print(mVisible);
         pw.print(" flags="); pw.print(flagsToString(mFlags));
+        pw.print(" sideHint="); pw.print(sideToString(mSideHint));
         pw.println();
     }
 
@@ -393,6 +489,7 @@
         if (mType != that.mType) return false;
         if (mVisible != that.mVisible) return false;
         if (mFlags != that.mFlags) return false;
+        if (mSideHint != that.mSideHint) return false;
         if (excludeInvisibleImeFrames && !mVisible && mType == WindowInsets.Type.ime()) return true;
         if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
         return mFrame.equals(that.mFrame);
@@ -400,7 +497,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags);
+        return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags, mSideHint);
     }
 
     public InsetsSource(Parcel in) {
@@ -414,6 +511,7 @@
         }
         mVisible = in.readBoolean();
         mFlags = in.readInt();
+        mSideHint = in.readInt();
     }
 
     @Override
@@ -434,6 +532,7 @@
         }
         dest.writeBoolean(mVisible);
         dest.writeInt(mFlags);
+        dest.writeInt(mSideHint);
     }
 
     @Override
@@ -442,7 +541,8 @@
                 + " mType=" + WindowInsets.Type.toString(mType)
                 + " mFrame=" + mFrame.toShortString()
                 + " mVisible=" + mVisible
-                + " mFlags=[" + flagsToString(mFlags) + "]"
+                + " mFlags=" + flagsToString(mFlags)
+                + " mSideHint=" + sideToString(mSideHint)
                 + "}";
     }
 
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 59e0932..c88da9e 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -37,7 +37,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.WindowConfiguration.ActivityType;
@@ -48,6 +47,7 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
+import android.view.InsetsSource.InternalInsetsSide;
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
@@ -55,8 +55,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 import java.util.StringJoiner;
 
@@ -66,23 +64,6 @@
  */
 public class InsetsState implements Parcelable {
 
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = "ISIDE", value = {
-            ISIDE_LEFT,
-            ISIDE_TOP,
-            ISIDE_RIGHT,
-            ISIDE_BOTTOM,
-            ISIDE_FLOATING,
-            ISIDE_UNKNOWN
-    })
-    public @interface InternalInsetsSide {}
-    static final int ISIDE_LEFT = 0;
-    static final int ISIDE_TOP = 1;
-    static final int ISIDE_RIGHT = 2;
-    static final int ISIDE_BOTTOM = 3;
-    static final int ISIDE_FLOATING = 4;
-    static final int ISIDE_UNKNOWN = 5;
-
     private final SparseArray<InsetsSource> mSources;
 
     /**
@@ -398,37 +379,14 @@
         }
 
         if (idSideMap != null) {
-            @InternalInsetsSide int insetSide = getInsetSide(insets);
-            if (insetSide != ISIDE_UNKNOWN) {
+            @InternalInsetsSide int insetSide = InsetsSource.getInsetSide(insets);
+            if (insetSide != InsetsSource.SIDE_UNKNOWN) {
                 idSideMap.put(source.getId(), insetSide);
             }
         }
     }
 
     /**
-     * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b
-     * is set in order that this method returns a meaningful result.
-     */
-    static @InternalInsetsSide int getInsetSide(Insets insets) {
-        if (Insets.NONE.equals(insets)) {
-            return ISIDE_FLOATING;
-        }
-        if (insets.left != 0) {
-            return ISIDE_LEFT;
-        }
-        if (insets.top != 0) {
-            return ISIDE_TOP;
-        }
-        if (insets.right != 0) {
-            return ISIDE_RIGHT;
-        }
-        if (insets.bottom != 0) {
-            return ISIDE_BOTTOM;
-        }
-        return ISIDE_UNKNOWN;
-    }
-
-    /**
      * Gets the source mapped from the ID, or creates one if no such mapping has been made.
      */
     public InsetsSource getOrCreateSource(int id, int type) {
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index 7800c28..9b21b76 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -32,7 +32,6 @@
 import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
-import android.hardware.display.DisplayManager;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -156,14 +155,12 @@
      */
     public static final int TYPE_DEFAULT = TYPE_ARROW;
 
-    private static final PointerIcon gNullIcon = new PointerIcon(TYPE_NULL);
-    private static final SparseArray<SparseArray<PointerIcon>> gSystemIconsByDisplay =
-            new SparseArray<SparseArray<PointerIcon>>();
-    private static boolean sUseLargeIcons = false;
+    // A cache of the system icons used by the app, used to avoid creating a new PointerIcon object
+    // every time we need to resolve the icon (i.e. on each input event).
+    private static final SparseArray<PointerIcon> SYSTEM_ICONS = new SparseArray<>();
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private final int mType;
-    private int mSystemIconResourceId;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private Bitmap mBitmap;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -177,44 +174,12 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private int mDurationPerFrame;
 
-    /**
-     * Listener for displays lifecycle.
-     * @hide
-     */
-    private static DisplayManager.DisplayListener sDisplayListener;
-
     private PointerIcon(int type) {
         mType = type;
     }
 
     /**
-     * Gets a special pointer icon that has no bitmap.
-     *
-     * @return The null pointer icon.
-     *
-     * @see #TYPE_NULL
-     * @hide
-     */
-    public static @NonNull PointerIcon getNullIcon() {
-        return gNullIcon;
-    }
-
-    /**
-     * Gets the default pointer icon.
-     *
-     * @param context The context.
-     * @return The default pointer icon.
-     *
-     * @throws IllegalArgumentException if context is null.
-     * @hide
-     */
-    public static @NonNull PointerIcon getDefaultIcon(@NonNull Context context) {
-        return getSystemIcon(context, TYPE_DEFAULT);
-    }
-
-    /**
      * Gets a system pointer icon for the given type.
-     * If typeis not recognized, returns the default pointer icon.
      *
      * @param context The context.
      * @param type The pointer icon type.
@@ -223,32 +188,42 @@
      * @throws IllegalArgumentException if context is null.
      */
     public static @NonNull PointerIcon getSystemIcon(@NonNull Context context, int type) {
-        // TODO(b/293587049): Pointer Icon Refactor: There is no need to load the system
-        // icon resource into memory outside of system server. Remove the need to load
-        // resources when getting a system icon.
         if (context == null) {
+            // We no longer use the context to resolve the system icon resource here, because the
+            // system will use its own context to do the type-to-resource resolution and cache it
+            // for use across different apps. Therefore, apps cannot customize the resource of a
+            // system icon. To avoid changing the public API, we keep the context parameter
+            // requirement.
             throw new IllegalArgumentException("context must not be null");
         }
+        return getSystemIcon(type);
+    }
 
-        if (type == TYPE_NULL) {
-            return gNullIcon;
+    private static @NonNull PointerIcon getSystemIcon(int type) {
+        if (type == TYPE_CUSTOM) {
+            throw new IllegalArgumentException("cannot get system icon for TYPE_CUSTOM");
+        }
+        PointerIcon icon = SYSTEM_ICONS.get(type);
+        if (icon == null) {
+            icon = new PointerIcon(type);
+            SYSTEM_ICONS.put(type, icon);
+        }
+        return icon;
+    }
+
+    /**
+     * Get a system icon with its resources loaded.
+     * This should only be used by the system for drawing icons to the screen.
+     * @hide
+     */
+    public static @NonNull PointerIcon getLoadedSystemIcon(@NonNull Context context, int type,
+            boolean useLargeIcons) {
+        if (type == TYPE_NOT_SPECIFIED) {
+            throw new IllegalStateException("Cannot load icon for type TYPE_NOT_SPECIFIED");
         }
 
-        if (sDisplayListener == null) {
-            registerDisplayListener(context);
-        }
-
-        final int displayId = context.getDisplayId();
-        SparseArray<PointerIcon> systemIcons = gSystemIconsByDisplay.get(displayId);
-        if (systemIcons == null) {
-            systemIcons = new SparseArray<>();
-            gSystemIconsByDisplay.put(displayId, systemIcons);
-        }
-
-        PointerIcon icon = systemIcons.get(type);
-        // Reload if not in the same display.
-        if (icon != null) {
-            return icon;
+        if (type == TYPE_CUSTOM) {
+            throw new IllegalArgumentException("Custom icons must be loaded when they're created");
         }
 
         int typeIndex = getSystemIconTypeIndex(type);
@@ -256,8 +231,9 @@
             typeIndex = getSystemIconTypeIndex(TYPE_DEFAULT);
         }
 
-        int defStyle = sUseLargeIcons ?
-                com.android.internal.R.style.LargePointer : com.android.internal.R.style.Pointer;
+        final int defStyle = useLargeIcons
+                ? com.android.internal.R.style.LargePointer
+                : com.android.internal.R.style.Pointer;
         TypedArray a = context.obtainStyledAttributes(null,
                 com.android.internal.R.styleable.Pointer,
                 0, defStyle);
@@ -266,26 +242,19 @@
 
         if (resourceId == -1) {
             Log.w(TAG, "Missing theme resources for pointer icon type " + type);
-            return type == TYPE_DEFAULT ? gNullIcon : getSystemIcon(context, TYPE_DEFAULT);
+            return type == TYPE_DEFAULT
+                    ? getSystemIcon(TYPE_NULL)
+                    : getLoadedSystemIcon(context, TYPE_DEFAULT, useLargeIcons);
         }
 
-        icon = new PointerIcon(type);
-        if ((resourceId & 0xff000000) == 0x01000000) {
-            icon.mSystemIconResourceId = resourceId;
-        } else {
-            icon.loadResource(context, context.getResources(), resourceId);
-        }
-        systemIcons.append(type, icon);
+        final PointerIcon icon = new PointerIcon(type);
+        icon.loadResource(context, context.getResources(), resourceId);
         return icon;
     }
 
-    /**
-     * Updates wheter accessibility large icons are used or not.
-     * @hide
-     */
-    public static void setUseLargeIcons(boolean use) {
-        sUseLargeIcons = use;
-        gSystemIconsByDisplay.clear();
+    private boolean isLoaded() {
+        return mBitmap != null && mHotSpotX >= 0 && mHotSpotX < mBitmap.getWidth() && mHotSpotY >= 0
+                && mHotSpotY < mBitmap.getHeight();
     }
 
     /**
@@ -346,78 +315,53 @@
         return icon;
     }
 
-    /**
-     * Loads the bitmap and hotspot information for a pointer icon, if it is not already loaded.
-     * Returns a pointer icon (not necessarily the same instance) with the information filled in.
-     *
-     * @param context The context.
-     * @return The loaded pointer icon.
-     *
-     * @throws IllegalArgumentException if context is null.
-     * @hide
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    public @NonNull PointerIcon load(@NonNull Context context) {
-        if (context == null) {
-            throw new IllegalArgumentException("context must not be null");
-        }
-
-        if (mSystemIconResourceId == 0 || mBitmap != null) {
-            return this;
-        }
-
-        PointerIcon result = new PointerIcon(mType);
-        result.mSystemIconResourceId = mSystemIconResourceId;
-        result.loadResource(context, context.getResources(), mSystemIconResourceId);
-        return result;
-    }
-
     /** @hide */
     public int getType() {
         return mType;
     }
 
-    public static final @NonNull Parcelable.Creator<PointerIcon> CREATOR
-            = new Parcelable.Creator<PointerIcon>() {
-        public PointerIcon createFromParcel(Parcel in) {
-            int type = in.readInt();
-            if (type == TYPE_NULL) {
-                return getNullIcon();
-            }
+    public static final @NonNull Parcelable.Creator<PointerIcon> CREATOR =
+            new Parcelable.Creator<>() {
+                @Override
+                public PointerIcon createFromParcel(Parcel in) {
+                    final int type = in.readInt();
+                    if (type != TYPE_CUSTOM) {
+                        return getSystemIcon(type);
+                    }
+                    final PointerIcon icon =
+                            PointerIcon.create(
+                                    Bitmap.CREATOR.createFromParcel(in),
+                                    in.readFloat(),
+                                    in.readFloat());
+                    return icon;
+                }
 
-            int systemIconResourceId = in.readInt();
-            if (systemIconResourceId != 0) {
-                PointerIcon icon = new PointerIcon(type);
-                icon.mSystemIconResourceId = systemIconResourceId;
-                return icon;
-            }
+                @Override
+                public PointerIcon[] newArray(int size) {
+                    return new PointerIcon[size];
+                }
+            };
 
-            Bitmap bitmap = Bitmap.CREATOR.createFromParcel(in);
-            float hotSpotX = in.readFloat();
-            float hotSpotY = in.readFloat();
-            return PointerIcon.create(bitmap, hotSpotX, hotSpotY);
-        }
-
-        public PointerIcon[] newArray(int size) {
-            return new PointerIcon[size];
-        }
-    };
-
+    @Override
     public int describeContents() {
         return 0;
     }
 
+    @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(mType);
-
-        if (mType != TYPE_NULL) {
-            out.writeInt(mSystemIconResourceId);
-            if (mSystemIconResourceId == 0) {
-                mBitmap.writeToParcel(out, flags);
-                out.writeFloat(mHotSpotX);
-                out.writeFloat(mHotSpotY);
-            }
+        if (mType != TYPE_CUSTOM) {
+            // When parceling a non-custom icon type, do not write the icon bitmap into the parcel
+            // because it can be re-loaded from resources after un-parceling.
+            return;
         }
+
+        if (!isLoaded()) {
+            throw new IllegalStateException("Custom icon should be loaded upon creation");
+        }
+        mBitmap.writeToParcel(out, flags);
+        out.writeFloat(mHotSpotX);
+        out.writeFloat(mHotSpotY);
     }
 
     @Override
@@ -431,14 +375,13 @@
         }
 
         PointerIcon otherIcon = (PointerIcon) other;
-        if (mType != otherIcon.mType
-                || mSystemIconResourceId != otherIcon.mSystemIconResourceId) {
+        if (mType != otherIcon.mType) {
             return false;
         }
 
-        if (mSystemIconResourceId == 0 && (mBitmap != otherIcon.mBitmap
+        if (mBitmap != otherIcon.mBitmap
                 || mHotSpotX != otherIcon.mHotSpotX
-                || mHotSpotY != otherIcon.mHotSpotY)) {
+                || mHotSpotY != otherIcon.mHotSpotY) {
             return false;
         }
 
@@ -545,13 +488,13 @@
         mBitmap = bitmap;
         mHotSpotX = hotSpotX;
         mHotSpotY = hotSpotY;
+        assert isLoaded();
     }
 
     @Override
     public String toString() {
         return "PointerIcon{type=" + typeToString(mType)
-                + ", hotspotX=" + mHotSpotX + ", hotspotY=" + mHotSpotY
-                + ", systemIconResourceId=" + mSystemIconResourceId + "}";
+                + ", hotspotX=" + mHotSpotX + ", hotspotY=" + mHotSpotY + "}";
     }
 
     private static void validateHotSpot(Bitmap bitmap, float hotSpotX, float hotSpotY) {
@@ -623,31 +566,6 @@
     }
 
     /**
-     * Manage system icon cache handled by display lifecycle.
-     * @param context The context.
-     */
-    private static void registerDisplayListener(@NonNull Context context) {
-        sDisplayListener = new DisplayManager.DisplayListener() {
-            @Override
-            public void onDisplayAdded(int displayId) {
-            }
-
-            @Override
-            public void onDisplayRemoved(int displayId) {
-                gSystemIconsByDisplay.remove(displayId);
-            }
-
-            @Override
-            public void onDisplayChanged(int displayId) {
-                gSystemIconsByDisplay.remove(displayId);
-            }
-        };
-
-        DisplayManager displayManager = context.getSystemService(DisplayManager.class);
-        displayManager.registerDisplayListener(sDisplayListener, null /* handler */);
-    }
-
-    /**
      * Convert type constant to string.
      * @hide
      */
@@ -680,6 +598,7 @@
             case TYPE_ZOOM_OUT: return "ZOOM_OUT";
             case TYPE_GRAB: return "GRAB";
             case TYPE_GRABBING: return "GRABBING";
+            case TYPE_HANDWRITING: return "HANDWRITING";
             default: return Integer.toString(type);
         }
     }
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 7850554..6c6e8b2 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -39,8 +39,10 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Log;
+import android.view.flags.Flags;
 
 import dalvik.system.CloseGuard;
+import dalvik.system.VMRuntime;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -101,6 +103,10 @@
             long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy);
     private static native void nativeDestroy(long nativeObject);
 
+    // 5MB is a wild guess for what the average surface should be. On most new phones, a full-screen
+    // surface is about 9MB... but not all surfaces are screen size. This should be a nice balance.
+    private static final long SURFACE_NATIVE_ALLOCATION_SIZE_BYTES = 5_000_000;
+
     public static final @android.annotation.NonNull Parcelable.Creator<Surface> CREATOR =
             new Parcelable.Creator<Surface>() {
         @Override
@@ -331,6 +337,7 @@
      */
     @UnsupportedAppUsage
     public Surface() {
+        registerNativeMemoryUsage();
     }
 
     /**
@@ -343,6 +350,7 @@
      */
     public Surface(@NonNull SurfaceControl from) {
         copyFrom(from);
+        registerNativeMemoryUsage();
     }
 
     /**
@@ -370,6 +378,7 @@
             mName = surfaceTexture.toString();
             setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture));
         }
+        registerNativeMemoryUsage();
     }
 
     /* called from android_view_Surface_createFromIGraphicBufferProducer() */
@@ -378,6 +387,7 @@
         synchronized (mLock) {
             setNativeObjectLocked(nativeObject);
         }
+        registerNativeMemoryUsage();
     }
 
     @Override
@@ -389,6 +399,7 @@
             release();
         } finally {
             super.finalize();
+            freeNativeMemoryUsage();
         }
     }
 
@@ -1062,8 +1073,7 @@
             if (error == -EINVAL) {
                 throw new IllegalArgumentException("Invalid argument to Surface.setFrameRate()");
             } else if (error != 0) {
-                throw new RuntimeException("Failed to set frame rate on Surface. Native error: "
-                        + error);
+                Log.e(TAG, "Failed to set frame rate on Surface. Native error: " + error);
             }
         }
     }
@@ -1243,4 +1253,16 @@
             return mIsWideColorGamut;
         }
     }
+
+    private static void registerNativeMemoryUsage() {
+        if (Flags.enableSurfaceNativeAllocRegistrationRo()) {
+            VMRuntime.getRuntime().registerNativeAllocation(SURFACE_NATIVE_ALLOCATION_SIZE_BYTES);
+        }
+    }
+
+    private static void freeNativeMemoryUsage() {
+        if (Flags.enableSurfaceNativeAllocRegistrationRo()) {
+            VMRuntime.getRuntime().registerNativeFree(SURFACE_NATIVE_ALLOCATION_SIZE_BYTES);
+        }
+    }
 }
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index b957b31..3ed0385 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -28,6 +28,7 @@
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -71,6 +72,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.VirtualRefBasePtr;
+import com.android.window.flags.Flags;
 
 import dalvik.system.CloseGuard;
 
@@ -277,6 +279,8 @@
     private static native int nativeGetLayerId(long nativeObject);
     private static native void nativeAddTransactionCommittedListener(long nativeObject,
             TransactionCommittedListener listener);
+    private static native void nativeAddTransactionCompletedListener(long nativeObject,
+            Consumer<TransactionStats> listener);
     private static native void nativeSanitize(long transactionObject, int pid, int uid);
     private static native void nativeSetDestinationFrame(long transactionObj, long nativeObject,
             int l, int t, int r, int b);
@@ -290,6 +294,10 @@
     private static native void nativeClearTrustedPresentationCallback(long transactionObj,
             long nativeObject);
     private static native StalledTransactionInfo nativeGetStalledTransactionInfo(int pid);
+    private static native void nativeSetDesiredPresentTimeNanos(long transactionObj,
+                                                                long desiredPresentTimeNanos);
+    private static native void nativeSetFrameTimeline(long transactionObj,
+                                                           long vsyncId);
 
     /**
      * Transforms that can be applied to buffers as they are displayed to a window.
@@ -2550,6 +2558,50 @@
     }
 
     /**
+     * Transaction stats given to the listener registered in
+     * {@link SurfaceControl.Transaction#addTransactionCompletedListener}
+     */
+    @FlaggedApi(Flags.FLAG_SDK_DESIRED_PRESENT_TIME)
+    public static final class TransactionStats {
+        private long mLatchTimeNanos;
+        private SyncFence mSyncFence;
+
+        // called from native
+        private TransactionStats(long latchTimeNanos, long presentFencePtr) {
+            mLatchTimeNanos = latchTimeNanos;
+            mSyncFence = new SyncFence(presentFencePtr);
+        }
+
+        /**
+         * Close the TransactionStats. Called by the framework when the listener returns.
+         * @hide
+         */
+        public void close() {
+            mSyncFence.close();
+        }
+
+        /**
+         * Returns the timestamp (in CLOCK_MONOTONIC) of when the frame was latched by the
+         * framework and queued for presentation.
+         */
+        @FlaggedApi(Flags.FLAG_SDK_DESIRED_PRESENT_TIME)
+        public long getLatchTimeNanos() {
+            return mLatchTimeNanos;
+        }
+
+        /**
+         * Returns a new SyncFence that signals when the transaction has been presented.
+         * The caller takes ownership of the fence and is responsible for closing
+         * it by calling {@link SyncFence#close}.
+         * If a device does not support present fences, an empty fence will be returned.
+         */
+        @FlaggedApi(Flags.FLAG_SDK_DESIRED_PRESENT_TIME)
+        public @NonNull SyncFence getPresentFence() {
+            return new SyncFence(mSyncFence);
+        }
+    };
+
+    /**
      * Threshold values that are sent with
      * {@link Transaction#setTrustedPresentationCallback(SurfaceControl,
      * TrustedPresentationThresholds, Executor, Consumer)}
@@ -4185,12 +4237,35 @@
         }
 
         /**
-         * Sets the frame timeline vsync id received from choreographer
-         * {@link Choreographer#getVsyncId()} that corresponds to the transaction submitted on that
-         * surface control.
+         * Sets the frame timeline to use in SurfaceFlinger.
          *
-         * @hide
+         * A frame timeline should be chosen based on the frame deadline the application
+         * can meet when rendering the frame and the application's desired presentation time.
+         * By setting a frame timeline, SurfaceFlinger tries to present the frame at the
+         * corresponding expected presentation time.
+         *
+         * To receive frame timelines, a callback must be posted to Choreographer using
+         * {@link Choreographer#postVsyncCallback} The vsyncId can then be extracted from the
+         * {@link Choreographer.FrameTimeline#getVsyncId}.
+         *
+         * @param vsyncId The vsync ID received from Choreographer, setting the frame's
+         *                presentation target to the corresponding expected presentation time
+         *                and deadline from the frame to be rendered. A stale or invalid value
+         *                will be ignored.
+         *
          */
+        @FlaggedApi(Flags.FLAG_SDK_DESIRED_PRESENT_TIME)
+        @NonNull
+        public Transaction setFrameTimeline(long vsyncId) {
+            if (!Flags.sdkDesiredPresentTime()) {
+                Log.w(TAG, "addTransactionCompletedListener was called but flag is disabled");
+                return this;
+            }
+            nativeSetFrameTimelineVsync(mNativeObject, vsyncId);
+            return this;
+        }
+
+        /** @hide */
         @NonNull
         public Transaction setFrameTimelineVsync(long frameTimelineVsyncId) {
             nativeSetFrameTimelineVsync(mNativeObject, frameTimelineVsyncId);
@@ -4207,6 +4282,9 @@
          * to avoid dropping frames (overwriting transactions), and unable to use timestamps (Which
          * provide a more efficient solution), then this method provides a method to pace your
          * transaction application.
+         * The listener is invoked once the transaction is applied, and never again. Multiple
+         * listeners can be added to the same transaction, however the order the listeners will
+         * be called is not guaranteed.
          *
          * @param executor The executor that the callback should be invoked on.
          * @param listener The callback that will be invoked when the transaction has been
@@ -4223,6 +4301,33 @@
         }
 
         /**
+         * Request to add a TransactionCompletedListener.
+         *
+         * The listener is invoked when transaction is presented, and never again. Multiple
+         * listeners can be added to the same transaction, however the order the listeners will
+         * be called is not guaranteed.
+         *
+         * @param executor The executor that the callback should be invoked on.
+         * @param listener The callback that will be invoked when the transaction has been
+         *                 completed.
+         */
+        @FlaggedApi(Flags.FLAG_SDK_DESIRED_PRESENT_TIME)
+        @NonNull
+        public Transaction addTransactionCompletedListener(
+                @NonNull @CallbackExecutor Executor executor,
+                @NonNull Consumer<TransactionStats> listener) {
+
+            if (!Flags.sdkDesiredPresentTime()) {
+                Log.w(TAG, "addTransactionCompletedListener was called but flag is disabled");
+                return this;
+            }
+            Consumer<TransactionStats> listenerInner = stats -> executor.execute(
+                                    () -> listener.andThen(TransactionStats::close).accept(stats));
+            nativeAddTransactionCompletedListener(mNativeObject, listenerInner);
+            return this;
+        }
+
+        /**
          * Sets a callback to receive feedback about the presentation of a {@link SurfaceControl}.
          * When the {@link SurfaceControl} is presented according to the passed in
          * {@link TrustedPresentationThresholds}, it is said to "enter the state", and receives the
@@ -4321,6 +4426,30 @@
         }
 
         /**
+         * Specifies a desiredPresentTimeNanos for the transaction. The framework will try to
+         * present the transaction at or after the time specified.
+         *
+         * Transactions will not be presented until all of their acquire fences have signaled even
+         * if the app requests an earlier present time.
+         *
+         * If an earlier transaction has a desired present time of x, and a later transaction has
+         * a desired present time that is before x, the later transaction will not preempt the
+         * earlier transaction.
+         *
+         * @param desiredPresentTimeNanos The desired time (in CLOCK_MONOTONIC) for the transaction.
+         * @return This transaction
+         */
+        @FlaggedApi(Flags.FLAG_SDK_DESIRED_PRESENT_TIME)
+        @NonNull
+        public Transaction setDesiredPresentTimeNanos(long desiredPresentTimeNanos) {
+            if (!Flags.sdkDesiredPresentTime()) {
+                Log.w(TAG, "addTransactionCompletedListener was called but flag is disabled");
+                return this;
+            }
+            nativeSetDesiredPresentTimeNanos(mNativeObject, desiredPresentTimeNanos);
+            return this;
+        }
+        /**
          * Writes the transaction to parcel, clearing the transaction as if it had been applied so
          * it can be used to store future transactions. It's the responsibility of the parcel
          * reader to apply the original transaction.
diff --git a/core/java/android/view/SurfaceControlInputReceiver.java b/core/java/android/view/SurfaceControlInputReceiver.java
new file mode 100644
index 0000000..81e4448
--- /dev/null
+++ b/core/java/android/view/SurfaceControlInputReceiver.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import com.android.window.flags.Flags;
+
+/**
+ * Provides a mechanism for a SurfaceControl to receive input events.
+ */
+@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
+public interface SurfaceControlInputReceiver {
+    /**
+     * When input events are batched, this is called at most once per frame. When non batched, this
+     * is called immediately for the input event.
+     *
+     * @param event The input event that was received. This input event object will become invalid
+     *              and recycled after this method is invoked. If there is need to persist this
+     *              object beyond the scope of this method, the overriding code should make a copy
+     *              of this object. For example, using
+     *              {@link MotionEvent#obtain(MotionEvent other)} or
+     *              {@link KeyEvent#KeyEvent(KeyEvent)} }
+     * @return true if the event was handled, false otherwise.
+     */
+    boolean onInputEvent(@NonNull InputEvent event);
+
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2b99e1e..1b22fda 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5546,11 +5546,11 @@
     @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
     public static final float REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE = -1;
     @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
-    public static final float REQUESTED_FRAME_RATE_CATEGORY_LOW = -30;
+    public static final float REQUESTED_FRAME_RATE_CATEGORY_LOW = -2;
     @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
-    public static final float REQUESTED_FRAME_RATE_CATEGORY_NORMAL = -60;
+    public static final float REQUESTED_FRAME_RATE_CATEGORY_NORMAL = -3;
     @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
-    public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -120;
+    public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -4;
 
     /**
      * Simple constructor to use when creating a view from code.
@@ -15015,6 +15015,7 @@
     }
 
     /** @hide */
+    @Nullable
     View getSelfOrParentImportantForA11y() {
         if (isImportantForAccessibility()) return this;
         ViewParent parent = getParentForAccessibility();
@@ -28491,6 +28492,7 @@
                 surface.destroy();
             }
             session.kill();
+            surfaceControl.release();
         }
     }
 
@@ -33170,6 +33172,10 @@
     public void setFrameContentVelocity(float pixelsPerSecond) {
         if (viewVelocityApi()) {
             mFrameContentVelocity = Math.abs(pixelsPerSecond);
+
+            if (sToolkitMetricsForFrameRateDecisionFlagValue) {
+                Trace.setCounter("Set frame velocity", (long) mFrameContentVelocity);
+            }
         }
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 350876c..7bc832e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS;
 import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED;
 import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND;
@@ -86,6 +87,7 @@
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
+import static android.view.accessibility.Flags.fixMergedContentChangeEvent;
 import static android.view.accessibility.Flags.forceInvertColor;
 import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
@@ -284,6 +286,7 @@
     private static final boolean DEBUG_TOUCH_NAVIGATION = false || LOCAL_LOGV;
     private static final boolean DEBUG_BLAST = false || LOCAL_LOGV;
     private static final int LOGTAG_INPUT_FOCUS = 62001;
+    private static final int LOGTAG_VIEWROOT_DRAW_EVENT = 60004;
 
     /**
      * Set to false if we do not want to use the multi threaded renderer even though
@@ -4748,13 +4751,7 @@
     }
 
     private void reportDrawFinished(@Nullable Transaction t, int seqId) {
-        if (DEBUG_BLAST) {
-            Log.d(mTag, "reportDrawFinished");
-        }
-
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-            Trace.instant(Trace.TRACE_TAG_VIEW, "reportDrawFinished " + mTag + " seqId=" + seqId);
-        }
+        logAndTrace("reportDrawFinished seqId=" + seqId);
         try {
             mWindowSession.finishDrawing(mWindow, t, seqId);
         } catch (RemoteException e) {
@@ -7236,7 +7233,7 @@
         }
 
         private boolean performFocusNavigation(KeyEvent event) {
-            int direction = 0;
+            @FocusDirection int direction = 0;
             switch (event.getKeyCode()) {
                 case KeyEvent.KEYCODE_DPAD_LEFT:
                     if (event.hasNoModifiers()) {
@@ -7288,6 +7285,8 @@
                                             isFastScrolling));
                             return true;
                         }
+                    } else if (moveFocusToAdjacentWindow(direction)) {
+                        return true;
                     }
 
                     // Give the focused view a last chance to handle the dpad key.
@@ -7297,12 +7296,26 @@
                 } else {
                     if (mView.restoreDefaultFocus()) {
                         return true;
+                    } else if (moveFocusToAdjacentWindow(direction)) {
+                        return true;
                     }
                 }
             }
             return false;
         }
 
+        private boolean moveFocusToAdjacentWindow(@FocusDirection int direction) {
+            if (getConfiguration().windowConfiguration.getWindowingMode()
+                    != WINDOWING_MODE_MULTI_WINDOW) {
+                return false;
+            }
+            try {
+                return mWindowSession.moveFocusToAdjacentWindow(mWindow, direction);
+            } catch (RemoteException e) {
+                return false;
+            }
+        }
+
         private boolean performKeyboardGroupNavigation(int direction) {
             final View focused = mView.findFocus();
             if (focused == null && mView.restoreDefaultFocus()) {
@@ -11492,6 +11505,15 @@
                 event.setContentChangeTypes(mChangeTypes);
                 if (mAction.isPresent()) event.setAction(mAction.getAsInt());
                 if (AccessibilityEvent.DEBUG_ORIGIN) event.originStackTrace = mOrigin;
+
+                if (fixMergedContentChangeEvent()) {
+                    if ((mChangeTypes & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0) {
+                        final View importantParent = source.getSelfOrParentImportantForA11y();
+                        if (importantParent != null) {
+                            source = importantParent;
+                        }
+                    }
+                }
                 source.sendAccessibilityEventUnchecked(event);
             } else {
                 mLastEventTimeMillis = 0;
@@ -11526,14 +11548,29 @@
             }
 
             if (mSource != null) {
-                // If there is no common predecessor, then mSource points to
-                // a removed view, hence in this case always prefer the source.
-                View predecessor = getCommonPredecessor(mSource, source);
-                if (predecessor != null) {
-                    predecessor = predecessor.getSelfOrParentImportantForA11y();
+                if (fixMergedContentChangeEvent()) {
+                    View newSource = getCommonPredecessor(mSource, source);
+                    if (newSource == null) {
+                        // If there is no common predecessor, then mSource points to
+                        // a removed view, hence in this case always prefer the source.
+                        newSource = source;
+                    }
+
+                    mChangeTypes |= changeType;
+                    if (mSource != newSource) {
+                        mChangeTypes |= AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
+                        mSource = newSource;
+                    }
+                } else {
+                    // If there is no common predecessor, then mSource points to
+                    // a removed view, hence in this case always prefer the source.
+                    View predecessor = getCommonPredecessor(mSource, source);
+                    if (predecessor != null) {
+                        predecessor = predecessor.getSelfOrParentImportantForA11y();
+                    }
+                    mSource = (predecessor != null) ? predecessor : source;
+                    mChangeTypes |= changeType;
                 }
-                mSource = (predecessor != null) ? predecessor : source;
-                mChangeTypes |= changeType;
 
                 final int performingAction = mAccessibilityManager.getPerformingAction();
                 if (performingAction != 0) {
@@ -11954,7 +11991,7 @@
                         Runnable timeoutRunnable = () -> Log.e(mTag,
                                 "Failed to submit the sync transaction after 4s. Likely to ANR "
                                         + "soon");
-                        mHandler.postDelayed(timeoutRunnable, 4L * Build.HW_TIMEOUT_MULTIPLIER);
+                        mHandler.postDelayed(timeoutRunnable, 4000L * Build.HW_TIMEOUT_MULTIPLIER);
                         transaction.addTransactionCommittedListener(mSimpleExecutor,
                                 () -> mHandler.removeCallbacks(timeoutRunnable));
                         surfaceSyncGroup.addTransaction(transaction);
@@ -12152,7 +12189,10 @@
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
             Trace.instant(Trace.TRACE_TAG_VIEW, mTag + "-" + msg);
         }
-        Log.d(mTag, msg);
+        if (DEBUG_BLAST) {
+            Log.d(mTag, msg);
+        }
+        EventLog.writeEvent(LOGTAG_VIEWROOT_DRAW_EVENT, mTag, msg);
     }
 
     private void setPreferredFrameRateCategory(int preferredFrameRateCategory) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index d8fa415..427d053 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -109,6 +109,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemProperties;
@@ -1358,9 +1359,8 @@
      *     android:value="false"/&gt;
      * &lt;/application&gt;
      * </pre>
-     * @hide
      */
-    // TODO(b/294227289): Make this public API
+    @FlaggedApi(Flags.FLAG_APP_COMPAT_PROPERTIES_API)
     String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE =
             "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE";
 
@@ -1402,9 +1402,8 @@
      *     android:value="false"/&gt;
      * &lt;/application&gt;
      * </pre>
-     * @hide
      */
-    // TODO(b/294227289): Make this public API
+    @FlaggedApi(Flags.FLAG_APP_COMPAT_PROPERTIES_API)
     String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE =
             "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE";
 
@@ -6015,4 +6014,99 @@
     default void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
         throw new UnsupportedOperationException();
     }
+
+    /**
+     * Registers a {@link SurfaceControlInputReceiver} for a {@link SurfaceControl} that will
+     * receive batched input event. For those events that are batched, the invocation will happen
+     * once per {@link Choreographer} frame, and other input events will be delivered immediately.
+     * This is different from
+     * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper,
+     * SurfaceControlInputReceiver)} in that the input events are received batched. The caller must
+     * invoke {@link #unregisterSurfaceControlInputReceiver(SurfaceControl)} to clean up the
+     * resources when no longer needing to use the {@link SurfaceControlInputReceiver}
+     *
+     * @param displayId      The display that the SurfaceControl will be placed on. Input will
+     *                       only work
+     *                       if SurfaceControl is on that display and that display was touched.
+     * @param surfaceControl The SurfaceControl to register the InputChannel for
+     * @param hostToken      The host token to link the InputChannel for. This is primarily for ANRs
+     *                       to ensure the host receives the ANR if any issues with touch on the
+     *                       InputChannel
+     * @param choreographer  The Choreographer used for batching. This should match the rendering
+     *                       Choreographer.
+     * @param receiver       The SurfaceControlInputReceiver that will receive the input events
+     */
+    @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
+    default void registerBatchedSurfaceControlInputReceiver(int displayId,
+            @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
+            @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) {
+        throw new UnsupportedOperationException(
+                "registerBatchedSurfaceControlInputReceiver is not implemented");
+    }
+
+    /**
+     * Registers a {@link SurfaceControlInputReceiver} for a {@link SurfaceControl} that will
+     * receive every input event. This is different than calling @link
+     * #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Choreographer,
+     * SurfaceControlInputReceiver)} in that the input events are received unbatched. The caller
+     * must invoke {@link #unregisterSurfaceControlInputReceiver(SurfaceControl)} to clean up the
+     * resources when no longer needing to use the {@link SurfaceControlInputReceiver}
+     *
+     * @param displayId      The display that the SurfaceControl will be placed on. Input will only
+     *                       work if SurfaceControl is on that display and that display was
+     *                       touched.
+     * @param hostToken      The host token to link the InputChannel for. This is primarily for ANRs
+     *                       to ensure the host receives the ANR if any issues with touch on the
+     *                       InputChannel
+     * @param surfaceControl The SurfaceControl to register the InputChannel for
+     * @param looper         The looper to use when invoking callbacks.
+     * @param receiver       The SurfaceControlInputReceiver that will receive the input events
+     **/
+    @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
+    default void registerUnbatchedSurfaceControlInputReceiver(int displayId,
+            @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
+            @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) {
+        throw new UnsupportedOperationException(
+                "registerUnbatchedSurfaceControlInputReceiver is not implemented");
+    }
+
+    /**
+     * Unregisters and cleans up the registered {@link SurfaceControlInputReceiver} for the
+     * specified token.
+     * <p>
+     * Must be called on the same {@link Looper} thread to which was passed to the
+     * {@link #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl,
+     * Choreographer,
+     * SurfaceControlInputReceiver)} or
+     * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper,
+     * SurfaceControlInputReceiver)}
+     *
+     * @param surfaceControl The SurfaceControl to remove and unregister the input channel for.
+     */
+    @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
+    default void unregisterSurfaceControlInputReceiver(@NonNull SurfaceControl surfaceControl) {
+        throw new UnsupportedOperationException(
+                "unregisterSurfaceControlInputReceiver is not implemented");
+    }
+
+    /**
+     * Returns the input client token for the {@link SurfaceControl}. This will only return non null
+     * if the SurfaceControl was registered for input via
+     * { #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Choreographer,
+     * SurfaceControlInputReceiver)} or
+     * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper,
+     * SurfaceControlInputReceiver)}.
+     * <p>
+     * This is helpful for testing to ensure the test waits for the layer to be registered with
+     * SurfaceFlinger and Input before proceeding with the test.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
+    @TestApi
+    @Nullable
+    default IBinder getSurfaceControlInputClientToken(@NonNull SurfaceControl surfaceControl) {
+        throw new UnsupportedOperationException(
+                "getSurfaceControlInputClientToken is not implemented");
+    }
 }
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index f1e4061..c49fce5 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -24,8 +26,10 @@
 import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
 import android.graphics.HardwareRenderer;
+import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
@@ -34,10 +38,12 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
+import android.util.SparseArray;
 import android.view.inputmethod.InputMethodManager;
 import android.window.ITrustedPresentationListener;
 import android.window.TrustedPresentationThresholds;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.FastPrintWriter;
 
 import java.io.FileDescriptor;
@@ -151,6 +157,10 @@
     private final TrustedPresentationListener mTrustedPresentationListener =
             new TrustedPresentationListener();
 
+    @GuardedBy("mSurfaceControlInputReceivers")
+    private final SparseArray<SurfaceControlInputReceiverInfo>
+            mSurfaceControlInputReceivers = new SparseArray<>();
+
     private WindowManagerGlobal() {
     }
 
@@ -808,6 +818,97 @@
         mTrustedPresentationListener.removeListener(listener);
     }
 
+    void registerBatchedSurfaceControlInputReceiver(int displayId,
+            @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
+            @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) {
+        IBinder clientToken = new Binder();
+        InputChannel inputChannel = new InputChannel();
+        try {
+            WindowManagerGlobal.getWindowSession().grantInputChannel(displayId, surfaceControl,
+                    clientToken, hostToken, 0, 0, TYPE_APPLICATION, 0, null, null,
+                    surfaceControl.getName(), inputChannel);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to create input channel", e);
+            e.rethrowAsRuntimeException();
+        }
+
+        synchronized (mSurfaceControlInputReceivers) {
+            mSurfaceControlInputReceivers.put(surfaceControl.getLayerId(),
+                    new SurfaceControlInputReceiverInfo(clientToken,
+                            new BatchedInputEventReceiver(inputChannel, choreographer.getLooper(),
+                                    choreographer) {
+                                @Override
+                                public void onInputEvent(InputEvent event) {
+                                    boolean handled = receiver.onInputEvent(event);
+                                    finishInputEvent(event, handled);
+                                }
+                            }));
+        }
+    }
+
+    void registerUnbatchedSurfaceControlInputReceiver(
+            int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
+            @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) {
+        IBinder clientToken = new Binder();
+        InputChannel inputChannel = new InputChannel();
+        try {
+            WindowManagerGlobal.getWindowSession().grantInputChannel(displayId, surfaceControl,
+                    clientToken, hostToken, 0, 0, TYPE_APPLICATION, 0, null, null,
+                    surfaceControl.getName(), inputChannel);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to create input channel", e);
+            e.rethrowAsRuntimeException();
+        }
+
+        synchronized (mSurfaceControlInputReceivers) {
+            mSurfaceControlInputReceivers.put(surfaceControl.getLayerId(),
+                    new SurfaceControlInputReceiverInfo(clientToken,
+                            new InputEventReceiver(inputChannel, looper) {
+                                @Override
+                                public void onInputEvent(InputEvent event) {
+                                    boolean handled = receiver.onInputEvent(event);
+                                    finishInputEvent(event, handled);
+                                }
+                            }));
+        }
+    }
+
+    void unregisterSurfaceControlInputReceiver(SurfaceControl surfaceControl) {
+        SurfaceControlInputReceiverInfo surfaceControlInputReceiverInfo;
+        synchronized (mSurfaceControlInputReceivers) {
+            surfaceControlInputReceiverInfo = mSurfaceControlInputReceivers.removeReturnOld(
+                    surfaceControl.getLayerId());
+        }
+
+        if (surfaceControlInputReceiverInfo == null) {
+            Log.w(TAG, "No registered input event receiver with sc: " + surfaceControl);
+            return;
+        }
+        try {
+            WindowManagerGlobal.getWindowSession().remove(
+                    surfaceControlInputReceiverInfo.mClientToken);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to remove input channel", e);
+            e.rethrowAsRuntimeException();
+        }
+
+        surfaceControlInputReceiverInfo.mInputEventReceiver.dispose();
+    }
+
+    IBinder getSurfaceControlInputClientToken(SurfaceControl surfaceControl) {
+        SurfaceControlInputReceiverInfo surfaceControlInputReceiverInfo;
+        synchronized (mSurfaceControlInputReceivers) {
+            surfaceControlInputReceiverInfo = mSurfaceControlInputReceivers.get(
+                    surfaceControl.getLayerId());
+        }
+
+        if (surfaceControlInputReceiverInfo == null) {
+            Log.w(TAG, "No registered input event receiver with sc: " + surfaceControl);
+            return null;
+        }
+        return surfaceControlInputReceiverInfo.mClientToken;
+    }
+
     private final class TrustedPresentationListener extends
             ITrustedPresentationListener.Stub {
         private static int sId = 0;
@@ -900,6 +1001,17 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    private static class SurfaceControlInputReceiverInfo {
+        final IBinder mClientToken;
+        final InputEventReceiver mInputEventReceiver;
+
+        private SurfaceControlInputReceiverInfo(IBinder clientToken,
+                InputEventReceiver inputEventReceiver) {
+            mClientToken = clientToken;
+            mInputEventReceiver = inputEventReceiver;
+        }
+    }
 }
 
 final class WindowLeaked extends AndroidRuntimeException {
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index b4b1fde..41d181c 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -32,6 +32,7 @@
 import android.graphics.Region;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.StrictMode;
 import android.util.Log;
@@ -520,6 +521,32 @@
     @Override
     public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
         mGlobal.unregisterTrustedPresentationListener(listener);
+    }
 
+    @Override
+    public void registerBatchedSurfaceControlInputReceiver(int displayId,
+            @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
+            @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) {
+        mGlobal.registerBatchedSurfaceControlInputReceiver(displayId, hostToken,
+                surfaceControl, choreographer, receiver);
+    }
+
+    @Override
+    public void registerUnbatchedSurfaceControlInputReceiver(
+            int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
+            @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) {
+        mGlobal.registerUnbatchedSurfaceControlInputReceiver(displayId, hostToken,
+                surfaceControl, looper, receiver);
+    }
+
+    @Override
+    public void unregisterSurfaceControlInputReceiver(@NonNull SurfaceControl surfaceControl) {
+        mGlobal.unregisterSurfaceControlInputReceiver(surfaceControl);
+    }
+
+    @Override
+    @Nullable
+    public IBinder getSurfaceControlInputClientToken(@NonNull SurfaceControl surfaceControl) {
+        return mGlobal.getSurfaceControlInputClientToken(surfaceControl);
     }
 }
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index d6ac562..c4d18c6 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -30,6 +30,7 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.MergedConfiguration;
+import android.view.View.FocusDirection;
 import android.view.WindowInsets.Type.InsetsType;
 import android.window.ClientWindowFrames;
 import android.window.OnBackInvokedCallbackInfo;
@@ -533,9 +534,8 @@
     }
 
     @Override
-    public android.os.Bundle sendWallpaperCommand(android.os.IBinder window,
+    public void sendWallpaperCommand(android.os.IBinder window,
             java.lang.String action, int x, int y, int z, android.os.Bundle extras, boolean sync) {
-        return null;
     }
 
     @Override
@@ -665,6 +665,13 @@
         return false;
     }
 
+    @Override
+    public boolean moveFocusToAdjacentWindow(IWindow fromWindow, @FocusDirection int direction) {
+        Log.e(TAG, "Received request to moveFocusToAdjacentWindow on"
+                + " WindowlessWindowManager. We shouldn't get here!");
+        return false;
+    }
+
     void setParentInterface(@Nullable ISurfaceControlViewHostParent parentInterface) {
         IBinder oldInterface = mParentInterface == null ? null : mParentInterface.asBinder();
         IBinder newInterface = parentInterface == null ? null : parentInterface.asBinder();
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 49d2ceb..7782fd7 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -66,6 +66,7 @@
 import android.view.accessibility.AccessibilityEvent.EventType;
 
 import com.android.internal.R;
+import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IntPair;
 
@@ -155,22 +156,6 @@
     public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON =
             "com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON";
 
-    /**
-     * Used as an int value for accessibility chooser activity to represent the accessibility button
-     * shortcut type.
-     *
-     * @hide
-     */
-    public static final int ACCESSIBILITY_BUTTON = 0;
-
-    /**
-     * Used as an int value for accessibility chooser activity to represent hardware key shortcut,
-     * such as volume key button.
-     *
-     * @hide
-     */
-    public static final int ACCESSIBILITY_SHORTCUT_KEY = 1;
-
     /** @hide */
     public static final int FLASH_REASON_CALL = 1;
 
@@ -184,32 +169,6 @@
     public static final int FLASH_REASON_PREVIEW = 4;
 
     /**
-     * Annotations for the shortcut type.
-     * <p>Note: Keep in sync with {@link #SHORTCUT_TYPES}.</p>
-     * @hide
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(value = {
-            // LINT.IfChange(shortcut_type_intdef)
-            ACCESSIBILITY_BUTTON,
-            ACCESSIBILITY_SHORTCUT_KEY
-            // LINT.ThenChange(:shortcut_type_array)
-    })
-    public @interface ShortcutType {}
-
-    /**
-     * Used for iterating through {@link ShortcutType}.
-     * <p>Note: Keep in sync with {@link ShortcutType}.</p>
-     * @hide
-     */
-    public static final int[] SHORTCUT_TYPES = {
-            // LINT.IfChange(shortcut_type_array)
-            ACCESSIBILITY_BUTTON,
-            ACCESSIBILITY_SHORTCUT_KEY,
-            // LINT.ThenChange(:shortcut_type_intdef)
-    };
-
-    /**
      * Annotations for content flag of UI.
      * @hide
      */
@@ -1785,7 +1744,8 @@
     @TestApi
     @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
     @NonNull
-    public List<String> getAccessibilityShortcutTargets(@ShortcutType int shortcutType) {
+    public List<String> getAccessibilityShortcutTargets(
+            @ShortcutConstants.UserShortcutType int shortcutType) {
         final IAccessibilityManager service;
         synchronized (mLock) {
             service = getServiceLocked();
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 48f8f1b..a11ac7c 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -53,6 +53,13 @@
 
 flag {
     namespace: "accessibility"
+    name: "fix_merged_content_change_event"
+    description: "Fixes event type and source of content change event merged in ViewRootImpl"
+    bug: "277305460"
+}
+
+flag {
+    namespace: "accessibility"
     name: "flash_notification_system_api"
     description: "Makes flash notification APIs as system APIs for calling from mainline module"
     bug: "303131332"
@@ -87,6 +94,13 @@
 }
 
 flag {
+    name: "skip_accessibility_warning_dialog_for_trusted_services"
+    namespace: "accessibility"
+    description: "Skips showing the accessibility warning dialog for trusted services."
+    bug: "303511250"
+}
+
+flag {
     namespace: "accessibility"
     name: "update_always_on_a11y_service"
     description: "Updates the Always-On A11yService state when the user changes the enablement of the shortcut."
@@ -99,3 +113,10 @@
     description: "Feature flag for system pinch zoom gesture detector and related opt-out apis"
     bug: "283323770"
 }
+
+flag {
+    name: "support_system_pinch_zoom_opt_out_apis"
+    namespace: "accessibility"
+    description: "Feature flag for declaring system pinch zoom opt-out apis"
+    bug: "315089687"
+}
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index 70d8abe..57a3b76 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -109,6 +109,40 @@
     }
 
     /**
+     * Locks AnimationUtils{@link #currentAnimationTimeMillis()} to a fixed value for the current
+     * thread. This is used by {@link android.view.Choreographer} to ensure that all accesses
+     * during a vsync update are synchronized to the timestamp of the vsync.
+     *
+     * It is also exposed to tests to allow for rapid, flake-free headless testing.
+     *
+     * Must be followed by a call to {@link #unlockAnimationClock()} to allow time to
+     * progress. Failing to do this will result in stuck animations, scrolls, and flings.
+     *
+     * Note that time is not allowed to "rewind" and must perpetually flow forward. So the
+     * lock may fail if the time is in the past from a previously returned value, however
+     * time will be frozen for the duration of the lock. The clock is a thread-local, so
+     * ensure that {@link #lockAnimationClock(long)}, {@link #unlockAnimationClock()}, and
+     * {@link #currentAnimationTimeMillis()} are all called on the same thread.
+     *
+     * This is also not reference counted in any way. Any call to {@link #unlockAnimationClock()}
+     * will unlock the clock for everyone on the same thread. It is therefore recommended
+     * for tests to use their own thread to ensure that there is no collision with any existing
+     * {@link android.view.Choreographer} instance.
+     *
+     * Have to add the method back because of b/307888459.
+     * Remove this method once the lockAnimationClock(long, long) change
+     * is landed to aosp/android14-tests-dev branch.
+     *
+     * @hide
+     */
+    @TestApi
+    public static void lockAnimationClock(long vsyncMillis) {
+        AnimationState state = sAnimationState.get();
+        state.animationClockLocked = true;
+        state.currentVsyncTimeMillis = vsyncMillis;
+    }
+
+    /**
      * Frees the time lock set in place by {@link #lockAnimationClock(long)}. Must be called
      * to allow the animation clock to self-update.
      *
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index dbeffc8..559ccfea7 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -298,6 +298,15 @@
             "android.view.autofill.extra.AUGMENTED_AUTOFILL_CLIENT";
 
     /**
+     * Internal extra used to pass the fill request id in client state of
+     * {@link ConvertCredentialResponse}
+     *
+     * @hide
+     */
+    public static final String EXTRA_AUTOFILL_REQUEST_ID =
+            "android.view.autofill.extra.AUTOFILL_REQUEST_ID";
+
+    /**
      * Autofill Hint to indicate that it can match any field.
      *
      * @hide
diff --git a/core/java/android/view/autofill/OWNERS b/core/java/android/view/autofill/OWNERS
index 37c6f5b..898947a 100644
--- a/core/java/android/view/autofill/OWNERS
+++ b/core/java/android/view/autofill/OWNERS
@@ -4,6 +4,7 @@
 haoranzhang@google.com
 skxu@google.com
 yunicorn@google.com
+reemabajwa@google.com
 
 # Bug component: 543785 = per-file *Augmented*
 per-file *Augmented* = wangqi@google.com
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java b/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java
index bf1d31c..fbb66d1 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java
@@ -149,9 +149,12 @@
      *
      * Because it is not guaranteed that the events will be enqueued from a single thread, the
      * implementation must be thread-safe to prevent unexpected behaviour.
+     *
+     * @hide
      */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     @NonNull
-    private final ConcurrentLinkedQueue<ContentCaptureEvent> mEventProcessQueue;
+    public final ConcurrentLinkedQueue<ContentCaptureEvent> mEventProcessQueue;
 
     /**
      * List of events held to be sent to the {@link ContentCaptureService} as a batch.
@@ -908,7 +911,7 @@
      * clear the buffer events then starting sending out current event.
      */
     private void enqueueEvent(@NonNull final ContentCaptureEvent event, boolean forceFlush) {
-        if (forceFlush) {
+        if (forceFlush || mEventProcessQueue.size() >= mManager.mOptions.maxBufferSize - 1) {
             // The buffer events are cleared in the same thread first to prevent new events
             // being added during the time of context switch. This would disrupt the sequence
             // of events.
diff --git a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
index 2a3008a..5d3153c 100644
--- a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
+++ b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
@@ -34,3 +34,10 @@
     description: "If true, an appop is logged when a notification is rapidly cleared by a notification listener."
     bug: "289080543"
 }
+
+flag {
+    name: "manage_device_policy_enabled"
+    namespace: "content_protection"
+    description: "If true, the APIs to manage content protection device policy will be enabled."
+    bug: "319477846"
+}
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index a74b06a..1dd99ba 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -1,6 +1,22 @@
 package: "android.view.flags"
 
 flag {
+     name: "enable_surface_native_alloc_registration"
+     namespace: "toolkit"
+     description: "Feature flag for registering surfaces with the VM for faster cleanup"
+     bug: "306193257"
+}
+
+flag {
+    name: "enable_surface_native_alloc_registration_ro"
+    namespace: "toolkit"
+    description: "Feature flag for registering surfaces with the VM for faster"
+      " cleanup. Fixed readonly version."
+    bug: "306193257"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "enable_use_measure_cache_during_force_layout"
     namespace: "toolkit"
     description: "Enables using the measure cache during a view force layout from the second "
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index bb7677d6..ccc5dbb 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -47,3 +47,10 @@
     is_fixed_read_only: true
 }
 
+flag {
+    name: "use_zero_jank_proxy"
+    namespace: "input_method"
+    description: "Feature flag for using a proxy that uses async calls to achieve zero jank for IMMS calls."
+    bug: "293640003"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index ef50045..1d2f653 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -16,6 +16,9 @@
 
 package android.view.textclassifier;
 
+import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS;
+
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -108,6 +111,9 @@
     String TYPE_DATE_TIME = "datetime";
     /** Flight number in IATA format. */
     String TYPE_FLIGHT_NUMBER = "flight";
+    /** One-time login codes */
+    @FlaggedApi(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
+    String TYPE_OTP_CODE = "otp_code";
     /**
      * Word that users may be interested to look up for meaning.
      * @hide
@@ -126,7 +132,8 @@
             TYPE_DATE,
             TYPE_DATE_TIME,
             TYPE_FLIGHT_NUMBER,
-            TYPE_DICTIONARY
+            TYPE_DICTIONARY,
+            TYPE_OTP_CODE
     })
     @interface EntityType {}
 
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index 828ec26..c6271d2 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -16,20 +16,40 @@
 
 package android.webkit;
 
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.net.ParseException;
 import android.net.Uri;
 import android.net.WebAddress;
+import android.os.Build;
 import android.util.Log;
 
 import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.nio.charset.Charset;
 import java.util.Locale;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 public final class URLUtil {
 
+    /**
+     * This feature enables parsing of Content-Disposition headers that conform to RFC 6266. In
+     * particular, this enables parsing of {@code filename*} values which can use a different
+     * character encoding.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    @FlaggedApi(android.os.Flags.FLAG_ANDROID_OS_BUILD_VANILLA_ICE_CREAM)
+    public static final long PARSE_CONTENT_DISPOSITION_USING_RFC_6266 = 319400769L;
+
     private static final String LOGTAG = "webkit";
     private static final boolean TRACE = false;
 
@@ -293,21 +313,58 @@
 
     /**
      * Guesses canonical filename that a download would have, using the URL and contentDisposition.
-     * File extension, if not defined, is added based on the mimetype
+     *
+     * <p>File extension, if not defined, is added based on the mimetype.
+     *
+     * <p>The {@code contentDisposition} argument will be treated differently depending on
+     * targetSdkVersion.
+     *
+     * <ul>
+     *   <li>For targetSDK versions &lt; {@code VANILLA_ICE_CREAM} it will be parsed based on RFC
+     *       2616.
+     *   <li>For targetSDK versions &gt;= {@code VANILLA_ICE_CREAM} it will be parsed based on RFC
+     *       6266.
+     * </ul>
+     *
+     * In practice, this means that from {@code VANILLA_ICE_CREAM}, this method will be able to
+     * parse {@code filename*} directives in the {@code contentDisposition} string.
+     *
+     * <p>The function also changed in the following ways in {@code VANILLA_ICE_CREAM}:
+     *
+     * <ul>
+     *   <li>If the suggested file type extension doesn't match the passed {@code mimeType}, the
+     *       method will append the appropriate extension instead of replacing the current
+     *       extension.
+     *   <li>If the suggested file name contains a path separator ({@code "/"}), the method will
+     *       replace this with the underscore character ({@code "_"}) instead of splitting the
+     *       result and only using the last part.
+     * </ul>
      *
      * @param url Url to the content
      * @param contentDisposition Content-Disposition HTTP header or {@code null}
      * @param mimeType Mime-type of the content or {@code null}
      * @return suggested filename
      */
-    public static final String guessFileName(
+    public static String guessFileName(
+            String url, @Nullable String contentDisposition, @Nullable String mimeType) {
+        if (android.os.Flags.androidOsBuildVanillaIceCream()) {
+            if (Compatibility.isChangeEnabled(PARSE_CONTENT_DISPOSITION_USING_RFC_6266)) {
+                return guessFileNameRfc6266(url, contentDisposition, mimeType);
+            }
+        }
+
+        return guessFileNameRfc2616(url, contentDisposition, mimeType);
+    }
+
+    /** Legacy implementation of guessFileName, based on RFC 2616. */
+    private static String guessFileNameRfc2616(
             String url, @Nullable String contentDisposition, @Nullable String mimeType) {
         String filename = null;
         String extension = null;
 
         // If we couldn't do anything with the hint, move toward the content disposition
         if (contentDisposition != null) {
-            filename = parseContentDisposition(contentDisposition);
+            filename = parseContentDispositionRfc2616(contentDisposition);
             if (filename != null) {
                 int index = filename.lastIndexOf('/') + 1;
                 if (index > 0) {
@@ -384,6 +441,128 @@
         return filename + extension;
     }
 
+    /**
+     * Guesses canonical filename that a download would have, using the URL and contentDisposition.
+     * Uses RFC 6266 for parsing the contentDisposition header value.
+     */
+    @NonNull
+    private static String guessFileNameRfc6266(
+            @NonNull String url, @Nullable String contentDisposition, @Nullable String mimeType) {
+        String filename = getFilenameSuggestion(url, contentDisposition);
+        // Split filename between base and extension
+        // Add an extension if filename does not have one
+        String extensionFromMimeType = suggestExtensionFromMimeType(mimeType);
+
+        if (filename.indexOf('.') < 0) {
+            // Filename does not have an extension, use the suggested one.
+            return filename + extensionFromMimeType;
+        }
+
+        // Filename already contains at least one dot.
+        // Compare the last segment of the extension against the mime type.
+        // If there's a mismatch, add the suggested extension instead.
+        if (mimeType != null && extensionDifferentFromMimeType(filename, mimeType)) {
+            return filename + extensionFromMimeType;
+        }
+        return filename;
+    }
+
+    /**
+     * Get the suggested file name from the {@code contentDisposition} or {@code url}. Will ensure
+     * that the filename contains no path separators by replacing them with the {@code "_"}
+     * character.
+     */
+    @NonNull
+    private static String getFilenameSuggestion(String url, @Nullable String contentDisposition) {
+        // First attempt to parse the Content-Disposition header if available
+        if (contentDisposition != null) {
+            String filename = getFilenameFromContentDispositionRfc6266(contentDisposition);
+            if (filename != null) {
+                return replacePathSeparators(filename);
+            }
+        }
+
+        // Try to generate a filename based on the URL.
+        if (url != null) {
+            Uri parsedUri = Uri.parse(url);
+            String lastPathSegment = parsedUri.getLastPathSegment();
+            if (lastPathSegment != null) {
+                return replacePathSeparators(lastPathSegment);
+            }
+        }
+
+        // Finally, if couldn't get filename from URI, get a generic filename.
+        return "downloadfile";
+    }
+
+    /**
+     * Replace all instances of {@code "/"} with {@code "_"} to avoid filenames that navigate the
+     * path.
+     */
+    @NonNull
+    private static String replacePathSeparators(@NonNull String raw) {
+        return raw.replaceAll("/", "_");
+    }
+
+    /**
+     * Check if the {@code filename} has an extension that is different from the expected one based
+     * on the {@code mimeType}.
+     */
+    private static boolean extensionDifferentFromMimeType(
+            @NonNull String filename, @NonNull String mimeType) {
+        int lastDotIndex = filename.lastIndexOf('.');
+        String typeFromExt =
+                MimeTypeMap.getSingleton()
+                        .getMimeTypeFromExtension(filename.substring(lastDotIndex + 1));
+        return typeFromExt != null && !typeFromExt.equalsIgnoreCase(mimeType);
+    }
+
+    /**
+     * Get a candidate file extension (including the {@code .}) for the given mimeType. will return
+     * {@code ".bin"} if {@code mimeType} is {@code null}
+     *
+     * @param mimeType Reported mimetype
+     * @return A file extension, including the {@code .}
+     */
+    @NonNull
+    private static String suggestExtensionFromMimeType(@Nullable String mimeType) {
+        if (mimeType == null) {
+            return ".bin";
+        }
+        String extensionFromMimeType =
+                MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
+        if (extensionFromMimeType != null) {
+            return "." + extensionFromMimeType;
+        }
+        if (mimeType.equalsIgnoreCase("text/html")) {
+            return ".html";
+        } else if (mimeType.toLowerCase(Locale.ROOT).startsWith("text/")) {
+            return ".txt";
+        } else {
+            return ".bin";
+        }
+    }
+
+    /**
+     * Parse the Content-Disposition HTTP Header.
+     *
+     * <p>Behavior depends on targetSdkVersion.
+     *
+     * <ul>
+     *   <li>For targetSDK versions &lt; {@code VANILLA_ICE_CREAM} it will parse based on RFC 2616.
+     *   <li>For targetSDK versions &gt;= {@code VANILLA_ICE_CREAM} it will parse based on RFC 6266.
+     * </ul>
+     */
+    @UnsupportedAppUsage
+    static String parseContentDisposition(String contentDisposition) {
+        if (android.os.Flags.androidOsBuildVanillaIceCream()) {
+            if (Compatibility.isChangeEnabled(PARSE_CONTENT_DISPOSITION_USING_RFC_6266)) {
+                return getFilenameFromContentDispositionRfc6266(contentDisposition);
+            }
+        }
+        return parseContentDispositionRfc2616(contentDisposition);
+    }
+
     /** Regex used to parse content-disposition headers */
     private static final Pattern CONTENT_DISPOSITION_PATTERN =
             Pattern.compile(
@@ -391,15 +570,14 @@
                     Pattern.CASE_INSENSITIVE);
 
     /**
-     * Parse the Content-Disposition HTTP Header. The format of the header is defined here:
-     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This header provides a filename for
-     * content that is going to be downloaded to the file system. We only support the attachment
-     * type. Note that RFC 2616 specifies the filename value must be double-quoted. Unfortunately
-     * some servers do not quote the value so to maintain consistent behaviour with other browsers,
-     * we allow unquoted values too.
+     * Parse the Content-Disposition HTTP Header. The format of the header is defined here: <a
+     * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html">rfc2616 Section 19</a>. This
+     * header provides a filename for content that is going to be downloaded to the file system. We
+     * only support the attachment type. Note that RFC 2616 specifies the filename value must be
+     * double-quoted. Unfortunately some servers do not quote the value so to maintain consistent
+     * behaviour with other browsers, we allow unquoted values too.
      */
-    @UnsupportedAppUsage
-    static String parseContentDisposition(String contentDisposition) {
+    private static String parseContentDispositionRfc2616(String contentDisposition) {
         try {
             Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition);
             if (m.find()) {
@@ -410,4 +588,136 @@
         }
         return null;
     }
+
+    /**
+     * Pattern for parsing individual content disposition key-value pairs.
+     *
+     * <p>The pattern will attempt to parse the value as either single-, double-, or unquoted. For
+     * the single- and double-quoted options, the pattern allows escaped quotes as part of the
+     * value, as per <a href="https://datatracker.ietf.org/doc/html/rfc2616#section-2.2">rfc2616
+     * section-2.2</a>
+     */
+    @SuppressWarnings("RegExpRepeatedSpace") // Spaces are only for readability.
+    private static final Pattern DISPOSITION_PATTERN =
+            Pattern.compile(
+                    """
+                            \\s*(\\S+?) # Group 1: parameter name
+                            \\s*=\\s* # Match equals sign
+                            (?: # non-capturing group of options
+                               '( (?: [^'\\\\] | \\\\. )* )' # Group 2: single-quoted
+                             | "( (?: [^"\\\\] | \\\\. )*  )" # Group 3: double-quoted
+                             | ( [^'"][^;\\s]* ) # Group 4: un-quoted parameter
+                            )\\s*;? # Optional end semicolon""",
+                    Pattern.COMMENTS);
+
+    /**
+     * Extract filename from a {@code Content-Disposition} header value.
+     *
+     * <p>This method implements the parsing defined in <a
+     * href="https://datatracker.ietf.org/doc/html/rfc6266">RFC 6266</a>, supporting both the {@code
+     * filename} and {@code filename*} disposition parameters. If the passed header value has the
+     * {@code "inline"} disposition type, this method will return {@code null} to indicate that a
+     * download was not intended.
+     *
+     * <p>If both {@code filename*} and {@code filename} is present, the former will be returned, as
+     * per the RFC. Invalid encoded values will be ignored.
+     *
+     * @param contentDisposition Value of {@code Content-Disposition} header.
+     * @return The filename suggested by the header or {@code null} if no filename could be parsed
+     *     from the header value.
+     */
+    @Nullable
+    private static String getFilenameFromContentDispositionRfc6266(
+            @NonNull String contentDisposition) {
+        String[] parts = contentDisposition.trim().split(";", 2);
+        if (parts.length < 2) {
+            // Need at least 2 parts, the `disposition-type` and at least one `disposition-parm`.
+            return null;
+        }
+        String dispositionType = parts[0].trim();
+        if ("inline".equalsIgnoreCase(dispositionType)) {
+            // "inline" should not result in a download.
+            // Unknown disposition types should be handles as "attachment"
+            // https://datatracker.ietf.org/doc/html/rfc6266#section-4.2
+            return null;
+        }
+        String dispositionParameters = parts[1];
+        Matcher matcher = DISPOSITION_PATTERN.matcher(dispositionParameters);
+        String filename = null;
+        String filenameExt = null;
+        while (matcher.find()) {
+            String parameter = matcher.group(1);
+            String value;
+            if (matcher.group(2) != null) {
+                value = removeSlashEscapes(matcher.group(2)); // Value was single-quoted
+            } else if (matcher.group(3) != null) {
+                value = removeSlashEscapes(matcher.group(3)); // Value was double-quoted
+            } else {
+                value = matcher.group(4); // Value was un-quoted
+            }
+
+            if (parameter == null || value == null) {
+                continue;
+            }
+
+            if ("filename*".equalsIgnoreCase(parameter)) {
+                filenameExt = parseExtValueString(value);
+            } else if ("filename".equalsIgnoreCase(parameter)) {
+                filename = value;
+            }
+        }
+
+        // RFC 6266 dictates the filenameExt should be preferred if present.
+        if (filenameExt != null) {
+            return filenameExt;
+        }
+        return filename;
+    }
+
+    /** Replace escapes of the \X form with X. */
+    private static String removeSlashEscapes(String raw) {
+        if (raw == null) {
+            return null;
+        }
+        return raw.replaceAll("\\\\(.)", "$1");
+    }
+
+    /**
+     * Parse an extended value string which can be percent-encoded. Return {@code} null if unable to
+     * parse the string.
+     */
+    private static String parseExtValueString(String raw) {
+        String[] parts = raw.split("'", 3);
+        if (parts.length < 3) {
+            return null;
+        }
+
+        String encoding = parts[0];
+        // Intentionally ignore parts[1] (language).
+        String valueChars = parts[2];
+
+        try {
+            // The URLDecoder force-decodes + as " "
+            // so preemptively replace all values with the encoded value to preserve them.
+            Charset charset = Charset.forName(encoding);
+            String valueWithEncodedPlus = encodePlusCharacters(valueChars, charset);
+            return URLDecoder.decode(valueWithEncodedPlus, charset);
+        } catch (RuntimeException ignored) {
+            return null; // Ignoring an un-parsable value is within spec.
+        }
+    }
+
+    /**
+     * Replace all instances of {@code "+"} with the percent-encoded equivalent for the given {@code
+     * charset}.
+     */
+    @NonNull
+    private static String encodePlusCharacters(@NonNull String valueChars, Charset charset) {
+        StringBuilder sb = new StringBuilder();
+        for (byte b : charset.encode("+").array()) {
+            // Formatting a byte is not possible with TextUtils.formatSimple
+            sb.append(String.format("%02x", b));
+        }
+        return valueChars.replaceAll("\\+", sb.toString());
+    }
 }
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index d12eda3..14c5348 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1203,11 +1203,7 @@
      * changes to this setting after that point.
      *
      * @param flag {@code true} if the WebView should use the database storage API
-     * @deprecated WebSQL is deprecated and this method will become a no-op on all
-     * Android versions once support is removed in Chromium. See
-     * https://developer.chrome.com/blog/deprecating-web-sql for more information.
      */
-    @Deprecated
     public abstract void setDatabaseEnabled(boolean flag);
 
     /**
@@ -1240,11 +1236,7 @@
      *
      * @return {@code true} if the database storage API is enabled
      * @see #setDatabaseEnabled
-     * @deprecated WebSQL is deprecated and this method will become a no-op on all
-     * Android versions once support is removed in Chromium. See
-     * https://developer.chrome.com/blog/deprecating-web-sql for more information.
      */
-    @Deprecated
     public abstract boolean getDatabaseEnabled();
 
     /**
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index 2dfeae3..80aad60 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -65,6 +65,14 @@
      * {@code true} causes the current WebView to abort loading the URL, while returning
      * {@code false} causes the WebView to continue loading the URL as usual.
      *
+     * <p>This callback is not called for all page navigations. In particular, this is not called
+     * for navigations which the app initiated with {@code loadUrl()}: this callback would not serve
+     * a purpose in this case, because the app already knows about the navigation. This callback
+     * lets the app know about navigations initiated by the web page (such as navigations initiated
+     * by JavaScript code), by the user (such as when the user taps on a link), or by an HTTP
+     * redirect (ex. if {@code loadUrl("foo.com")} redirects to {@code "bar.com"} because of HTTP
+     * 301).
+     *
      * <p class="note"><b>Note:</b> Do not call {@link WebView#loadUrl(String)} with the request's
      * URL and then return {@code true}. This unnecessarily cancels the current load and starts a
      * new load with the same URL. The correct way to continue loading a given URL is to simply
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 0ef37d1..53b047a 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -16,6 +16,8 @@
 
 package android.webkit;
 
+import static android.webkit.Flags.updateServiceV2;
+
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.UptimeMillisLong;
@@ -33,6 +35,7 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.text.TextUtils;
 import android.util.AndroidRuntimeException;
 import android.util.ArraySet;
 import android.util.Log;
@@ -410,6 +413,21 @@
         }
     }
 
+    // Returns whether the given package is enabled.
+    // This state can be changed by the user from Settings->Apps
+    private static boolean isEnabledPackage(PackageInfo packageInfo) {
+        if (packageInfo == null) return false;
+        return packageInfo.applicationInfo.enabled;
+    }
+
+    // Return {@code true} if the package is installed and not hidden
+    private static boolean isInstalledPackage(PackageInfo packageInfo) {
+        if (packageInfo == null) return false;
+        return (((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0)
+                && ((packageInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HIDDEN)
+                        == 0));
+    }
+
     @UnsupportedAppUsage
     private static Context getWebViewContextAndSetProvider() throws MissingWebViewPackageException {
         Application initialApplication = AppGlobals.getInitialApplication();
@@ -456,6 +474,21 @@
                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
             }
 
+            if (updateServiceV2() && !isInstalledPackage(newPackageInfo)) {
+                throw new MissingWebViewPackageException(
+                        TextUtils.formatSimple(
+                                "Current WebView Package (%s) is not installed for the current "
+                                + "user",
+                                newPackageInfo.packageName));
+            }
+
+            if (updateServiceV2() && !isEnabledPackage(newPackageInfo)) {
+                throw new MissingWebViewPackageException(
+                        TextUtils.formatSimple(
+                                "Current WebView Package (%s) is not enabled for the current user",
+                                newPackageInfo.packageName));
+            }
+
             // Validate the newly fetched package info, throws MissingWebViewPackageException on
             // failure
             verifyPackageInfo(response.packageInfo, newPackageInfo);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index ddcfb40..57d268c 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -148,6 +148,7 @@
 import com.android.internal.util.GrowingArrayUtils;
 import com.android.internal.util.Preconditions;
 import com.android.internal.view.FloatingActionMode;
+import com.android.text.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -2343,6 +2344,13 @@
      */
     void invalidateTextDisplayList(Layout layout, int start, int end) {
         if (mTextRenderNodes != null && layout instanceof DynamicLayout) {
+            if (Flags.insertModeCrashWhenDelete()
+                    && mTextView.isOffsetMappingAvailable()) {
+                // Text is transformed with an OffsetMapping, and we can't know the changed range
+                // on the transformed text. Invalidate the all display lists instead.
+                invalidateTextDisplayList();
+                return;
+            }
             final int startTransformed =
                     mTextView.originalToTransformed(start, OffsetMapping.MAP_STRATEGY_CHARACTER);
             final int endTransformed =
diff --git a/core/java/android/widget/RemoteCanvas.java b/core/java/android/widget/RemoteCanvas.java
new file mode 100644
index 0000000..9a0898c
--- /dev/null
+++ b/core/java/android/widget/RemoteCanvas.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.widget;
+
+import static android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL;
+
+import android.annotation.AttrRes;
+import android.annotation.FlaggedApi;
+import android.annotation.StyleRes;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.function.IntConsumer;
+
+/**
+ * {@link RemoteCanvas} is designed to support arbitrary protocols between two processes using
+ * {@link RemoteViews.DrawInstructions}. Upon instantiation in the host process,
+ * {@link RemoteCanvas#setDrawInstructions(RemoteViews.DrawInstructions)} is called so that the
+ * host process can render the {@link RemoteViews.DrawInstructions} from the provider process
+ * accordingly.
+ *
+ * @hide
+ */
+@FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+public class RemoteCanvas extends View {
+
+    private static final String TAG = "RemoteCanvas";
+
+    @Nullable
+    private SparseArray<Runnable> mCallbacks;
+
+    private final IntConsumer mOnClickHandler = (viewId) -> {
+        if (mCallbacks == null) {
+            Log.w(TAG, "Cannot find callback for " + viewId
+                    + ", in fact there were no callbacks from this RemoteViews at all.");
+            return;
+        }
+        final Runnable cb = getCallbacks().get(viewId);
+        if (cb != null) {
+            cb.run();
+        } else {
+            Log.w(TAG, "Cannot find callback for " + viewId);
+        }
+    };
+
+    RemoteCanvas(@NonNull Context context) {
+        super(context);
+    }
+
+    RemoteCanvas(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    RemoteCanvas(@NonNull Context context, @Nullable AttributeSet attrs,
+                 @AttrRes int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    RemoteCanvas(@NonNull Context context, @Nullable AttributeSet attrs,
+                 @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    /**
+     * Setter method for the {@link RemoteViews.DrawInstructions} from the provider process for
+     * the host process to render accordingly.
+     *
+     * @param instructions {@link RemoteViews.DrawInstructions} from the provider process.
+     */
+    void setDrawInstructions(@NonNull final RemoteViews.DrawInstructions instructions) {
+        setTag(instructions);
+        // TODO: handle draw instructions
+        // TODO: attach mOnClickHandler
+    }
+
+    /**
+     * Adds a callback function to a clickable area in the RemoteCanvas.
+     *
+     * @param viewId the viewId of the clickable area
+     * @param cb the callback function to be triggered when clicked
+     */
+    void addOnClickHandler(final int viewId, @NonNull final Runnable cb) {
+        getCallbacks().set(viewId, cb);
+    }
+
+    /**
+     * Returns all callbacks added to the RemoteCanvas through
+     * {@link #addOnClickHandler(int, Runnable)}.
+     */
+    @VisibleForTesting
+    public SparseArray<Runnable> getCallbacks() {
+        if (mCallbacks == null) {
+            mCallbacks = new SparseArray<>();
+        }
+        return mCallbacks;
+    }
+}
diff --git a/core/java/android/widget/RemoteViews.aidl b/core/java/android/widget/RemoteViews.aidl
index 6a5fc03..19a5f25 100644
--- a/core/java/android/widget/RemoteViews.aidl
+++ b/core/java/android/widget/RemoteViews.aidl
@@ -18,3 +18,4 @@
 
 parcelable RemoteViews;
 parcelable RemoteViews.RemoteCollectionItems;
+parcelable RemoteViews.DrawInstructions;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 0d499a1..0654add 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -16,6 +16,8 @@
 
 package android.widget;
 
+import static android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL;
+import static android.appwidget.flags.Flags.drawDataParcel;
 import static android.appwidget.flags.Flags.remoteAdapterConversion;
 import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
 
@@ -243,6 +245,7 @@
     private static final int ATTRIBUTE_REFLECTION_ACTION_TAG = 32;
     private static final int SET_REMOTE_ADAPTER_TAG = 33;
     private static final int SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG = 34;
+    private static final int SET_DRAW_INSTRUCTION_TAG = 35;
 
     /** @hide **/
     @IntDef(prefix = "MARGIN_", value = {
@@ -442,6 +445,19 @@
     @Nullable
     private LayoutInflater.Factory2 mLayoutInflaterFactory2;
 
+    /**
+     * Indicates whether this {@link RemoteViews} was instantiated with a {@link DrawInstructions}
+     * object. {@link DrawInstructions} serves as an alternative protocol for the host process
+     * to render.
+     */
+    private boolean mHasDrawInstructions;
+
+    @Nullable
+    private SparseArray<PendingIntent> mPendingIntentTemplate;
+
+    @Nullable
+    private SparseArray<Intent> mFillInIntent;
+
     private static final InteractionHandler DEFAULT_INTERACTION_HANDLER =
             (view, pendingIntent, response) ->
                     startPendingIntent(view, pendingIntent, response.getLaunchOptions(view));
@@ -1463,6 +1479,11 @@
 
         @Override
         public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
+            if (hasDrawInstructions() && root instanceof RemoteCanvas target) {
+                target.addOnClickHandler(mViewId, () ->
+                        mResponse.handleViewInteraction(root, params.handler));
+                return;
+            }
             final View target = root.findViewById(mViewId);
             if (target == null) return;
 
@@ -3851,6 +3872,45 @@
         }
     }
 
+    private static class SetDrawInstructionAction extends Action {
+
+        @Nullable
+        private final DrawInstructions mInstructions;
+
+        SetDrawInstructionAction(@NonNull final DrawInstructions instructions) {
+            mInstructions = instructions;
+        }
+
+        SetDrawInstructionAction(@NonNull final Parcel in) {
+            if (drawDataParcel()) {
+                mInstructions = DrawInstructions.readFromParcel(in);
+            } else {
+                mInstructions = null;
+            }
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            if (drawDataParcel()) {
+                DrawInstructions.writeToParcel(mInstructions, dest, flags);
+            }
+        }
+
+        @Override
+        public void apply(View root, ViewGroup rootParent, ActionApplyParams params)
+                throws ActionException {
+            if (drawDataParcel() && mInstructions != null
+                    && root instanceof RemoteCanvas remoteCanvas) {
+                remoteCanvas.setDrawInstructions(mInstructions);
+            }
+        }
+
+        @Override
+        public int getActionTag() {
+            return SET_DRAW_INSTRUCTION_TAG;
+        }
+    }
+
     /**
      * Create a new RemoteViews object that will display the views contained
      * in the specified layout file.
@@ -4080,6 +4140,7 @@
         mClassCookies = src.mClassCookies;
         mIdealSize = src.mIdealSize;
         mProviderInstanceId = src.mProviderInstanceId;
+        mHasDrawInstructions = src.mHasDrawInstructions;
 
         if (src.hasLandscapeAndPortraitLayouts()) {
             mLandscape = createInitializedFrom(src.mLandscape, hierarchyRoot);
@@ -4114,12 +4175,26 @@
     /**
      * Reads a RemoteViews object from a parcel.
      *
-     * @param parcel
+     * @param parcel the parcel object
      */
     public RemoteViews(Parcel parcel) {
         this(parcel, /* rootData= */ null, /* info= */ null, /* depth= */ 0);
     }
 
+    /**
+     * Instantiates a RemoteViews object using {@link DrawInstructions}, which serves as an
+     * alternative to XML layout. {@link DrawInstructions} objects contains the instructions which
+     * can be interpreted and rendered accordingly in the host process.
+     *
+     * @param drawInstructions The {@link DrawInstructions} object
+     */
+    @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+    public RemoteViews(@NonNull final DrawInstructions drawInstructions) {
+        Objects.requireNonNull(drawInstructions);
+        mHasDrawInstructions = true;
+        addAction(new SetDrawInstructionAction(drawInstructions));
+    }
+
     private RemoteViews(@NonNull Parcel parcel, @Nullable HierarchyRootData rootData,
             @Nullable ApplicationInfo info, int depth) {
         if (depth > MAX_NESTED_VIEWS
@@ -4178,6 +4253,7 @@
         }
         mApplyFlags = parcel.readInt();
         mProviderInstanceId = parcel.readLong();
+        mHasDrawInstructions = parcel.readBoolean();
 
         // Ensure that all descendants have their caches set up recursively.
         if (mIsRoot) {
@@ -4254,6 +4330,8 @@
                 return new AttributeReflectionAction(parcel);
             case SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG:
                 return new SetOnStylusHandwritingResponse(parcel);
+            case SET_DRAW_INSTRUCTION_TAG:
+                return new SetDrawInstructionAction(parcel);
             default:
                 throw new ActionException("Tag " + tag + " not found");
         }
@@ -4747,7 +4825,12 @@
      *          by a child of viewId and executed when that child is clicked
      */
     public void setPendingIntentTemplate(@IdRes int viewId, PendingIntent pendingIntentTemplate) {
-        addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate));
+        if (hasDrawInstructions()) {
+            getPendingIntentTemplate().set(viewId, pendingIntentTemplate);
+            tryAddRemoteResponse(viewId);
+        } else {
+            addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate));
+        }
     }
 
     /**
@@ -4768,7 +4851,12 @@
      *        in order to determine the on-click behavior of the view specified by viewId
      */
     public void setOnClickFillInIntent(@IdRes int viewId, Intent fillInIntent) {
-        setOnClickResponse(viewId, RemoteResponse.fromFillInIntent(fillInIntent));
+        if (hasDrawInstructions()) {
+            getFillInIntent().set(viewId, fillInIntent);
+            tryAddRemoteResponse(viewId);
+        } else {
+            setOnClickResponse(viewId, RemoteResponse.fromFillInIntent(fillInIntent));
+        }
     }
 
     /**
@@ -5791,6 +5879,10 @@
         }
     }
 
+    private boolean hasDrawInstructions() {
+        return mHasDrawInstructions;
+    }
+
     private RemoteViews getRemoteViewsToApply(Context context) {
         if (hasLandscapeAndPortraitLayouts()) {
             int orientation = context.getResources().getConfiguration().orientation;
@@ -5973,6 +6065,10 @@
         if (applyThemeResId != 0) {
             inflationContext = new ContextThemeWrapper(inflationContext, applyThemeResId);
         }
+        // If the RemoteViews contains draw instructions, just use it instead.
+        if (rv.hasDrawInstructions()) {
+            return new RemoteCanvas(inflationContext);
+        }
         LayoutInflater inflater = LayoutInflater.from(context);
 
         // Clone inflater so we load resources from correct context and
@@ -6236,7 +6332,7 @@
 
     /** @hide */
     public boolean canRecycleView(@Nullable View v) {
-        if (v == null) {
+        if (v == null || hasDrawInstructions()) {
             return false;
         }
         Integer previousLayoutId = (Integer) v.getTag(R.id.widget_frame);
@@ -6388,6 +6484,32 @@
         return context;
     }
 
+    @NonNull
+    private SparseArray<PendingIntent> getPendingIntentTemplate() {
+        if (mPendingIntentTemplate == null) {
+            mPendingIntentTemplate = new SparseArray<>();
+        }
+        return mPendingIntentTemplate;
+    }
+
+    @NonNull
+    private SparseArray<Intent> getFillInIntent() {
+        if (mFillInIntent == null) {
+            mFillInIntent = new SparseArray<>();
+        }
+        return mFillInIntent;
+    }
+
+    private void tryAddRemoteResponse(final int viewId) {
+        final PendingIntent pendingIntent = getPendingIntentTemplate().get(viewId);
+        final Intent intent = getFillInIntent().get(viewId);
+        if (pendingIntent != null && intent != null) {
+            addAction(new SetOnClickResponse(viewId,
+                    RemoteResponse.fromPendingIntentTemplateAndFillInIntent(
+                            pendingIntent, intent)));
+        }
+    }
+
     /**
      * Utility class to hold all the options when applying the remote views
      * @hide
@@ -6624,6 +6746,7 @@
         }
         dest.writeInt(mApplyFlags);
         dest.writeLong(mProviderInstanceId);
+        dest.writeBoolean(mHasDrawInstructions);
 
         dest.restoreAllowSquashing(prevSquashingAllowed);
     }
@@ -6926,6 +7049,14 @@
             return response;
         }
 
+        private static RemoteResponse fromPendingIntentTemplateAndFillInIntent(
+                @NonNull final PendingIntent pendingIntent, @NonNull final Intent intent) {
+            RemoteResponse response = new RemoteResponse();
+            response.mPendingIntent = pendingIntent;
+            response.mFillIntent = intent;
+            return response;
+        }
+
         /**
          * Adds a shared element to be transferred as part of the transition between Activities
          * using cross-Activity scene animations. The position of the first element will be used as
@@ -6964,8 +7095,8 @@
 
         private void writeToParcel(Parcel dest, int flags) {
             PendingIntent.writePendingIntentOrNullToParcel(mPendingIntent, dest);
-            if (mPendingIntent == null) {
-                // Only write the intent if pending intent is null
+            dest.writeBoolean((mFillIntent != null));
+            if (mFillIntent != null) {
                 dest.writeTypedObject(mFillIntent, flags);
             }
             dest.writeInt(mInteractionType);
@@ -6975,9 +7106,7 @@
 
         private void readFromParcel(Parcel parcel) {
             mPendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
-            if (mPendingIntent == null) {
-                mFillIntent = parcel.readTypedObject(Intent.CREATOR);
-            }
+            mFillIntent = parcel.readBoolean() ? parcel.readTypedObject(Intent.CREATOR) : null;
             mInteractionType = parcel.readInt();
             int[] viewIds = parcel.createIntArray();
             mViewIds = viewIds == null ? null : IntArray.wrap(viewIds);
@@ -7054,7 +7183,7 @@
 
         /** @hide */
         public Pair<Intent, ActivityOptions> getLaunchOptions(View view) {
-            Intent intent = mPendingIntent != null ? new Intent() : new Intent(mFillIntent);
+            Intent intent = mFillIntent == null ? new Intent() : new Intent(mFillIntent);
             intent.setSourceBounds(getSourceBounds(view));
 
             if (view instanceof CompoundButton
@@ -7413,6 +7542,98 @@
     }
 
     /**
+     * A data parcel that carries the instructions to draw the RemoteViews, as an alternative to
+     * XML layout.
+     */
+    @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+    public static final class DrawInstructions {
+
+        @NonNull
+        private final List<byte[]> mInstructions;
+
+        private DrawInstructions() {
+            throw new UnsupportedOperationException(
+                    "DrawInstructions cannot be instantiate without instructions");
+        }
+
+        private DrawInstructions(@NonNull List<byte[]> instructions) {
+            // Create and retain an immutable copy of given instructions.
+            mInstructions = new ArrayList<>(instructions.size());
+            for (byte[] instruction : instructions) {
+                final int len = instruction.length;
+                final byte[] target = new byte[len];
+                System.arraycopy(instruction, 0, target, 0, len);
+                mInstructions.add(target);
+            }
+        }
+
+        @Nullable
+        private static DrawInstructions readFromParcel(@NonNull final Parcel in) {
+            int size = in.readInt();
+            if (size == -1) {
+                return null;
+            }
+            byte[] instruction;
+            final List<byte[]> instructions = new ArrayList<>(size);
+            for (int i = 0; i < size; i++) {
+                instruction = new byte[in.readInt()];
+                in.readByteArray(instruction);
+                instructions.add(instruction);
+            }
+            return new DrawInstructions(instructions);
+        }
+        private static void writeToParcel(@Nullable final DrawInstructions drawInstructions,
+                @NonNull final Parcel dest, final int flags) {
+            if (drawInstructions == null) {
+                dest.writeInt(-1);
+                return;
+            }
+            final List<byte[]> instructions = drawInstructions.mInstructions;
+            dest.writeInt(instructions.size());
+            for (byte[] instruction : instructions) {
+                dest.writeInt(instruction.length);
+                dest.writeByteArray(instruction);
+            }
+        }
+
+        /**
+         * Append additional instructions to this {@link DrawInstructions} object.
+         */
+        @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+        public void appendInstructions(@NonNull final byte[] instructions) {
+            mInstructions.add(instructions);
+        }
+
+        /**
+         * Builder class for {@link DrawInstructions} objects.
+         */
+        @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+        public static final class Builder {
+
+            private final List<byte[]> mInstructions;
+
+            /**
+             * Constructor.
+             *
+             * @param instructions Information to draw the RemoteViews.
+             */
+            @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+            public Builder(@NonNull final List<byte[]> instructions) {
+                mInstructions = new ArrayList<>(instructions);
+            }
+
+            /**
+             * Creates a {@link DrawInstructions} instance.
+             */
+            @NonNull
+            @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
+            public DrawInstructions build() {
+                return new DrawInstructions(mInstructions);
+            }
+        }
+    }
+
+    /**
      * Get the ID of the top-level view of the XML layout, if set using
      * {@link RemoteViews#RemoteViews(String, int, int)}.
      */
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 6da6a64..e812f85 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9589,6 +9589,23 @@
                 }
                 break;
 
+            case KeyEvent.KEYCODE_ESCAPE:
+                if (com.android.text.flags.Flags.escapeClearsFocus() && event.hasNoModifiers()) {
+                    if (mEditor != null && mEditor.getTextActionMode() != null) {
+                        stopTextActionMode();
+                        return KEY_EVENT_HANDLED;
+                    }
+                    if (hasFocus()) {
+                        clearFocus();
+                        InputMethodManager imm = getInputMethodManager();
+                        if (imm != null) {
+                            imm.hideSoftInputFromView(this, 0);
+                        }
+                        return KEY_EVENT_HANDLED;
+                    }
+                }
+                break;
+
             case KeyEvent.KEYCODE_CUT:
                 if (event.hasNoModifiers() && canCut()) {
                     if (onTextContextMenuItem(ID_CUT)) {
diff --git a/core/java/android/window/IScreenRecordingCallback.aidl b/core/java/android/window/IScreenRecordingCallback.aidl
new file mode 100644
index 0000000..560ee75
--- /dev/null
+++ b/core/java/android/window/IScreenRecordingCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+/**
+ * @hide
+ */
+oneway interface IScreenRecordingCallback {
+    void onScreenRecordingStateChanged(boolean visibleInScreenRecording);
+}
diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java
index befb002..5446428 100644
--- a/core/java/android/window/ScreenCapture.java
+++ b/core/java/android/window/ScreenCapture.java
@@ -30,6 +30,8 @@
 import android.util.Log;
 import android.view.SurfaceControl;
 
+import com.android.window.flags.Flags;
+
 import libcore.util.NativeAllocationRegistry;
 
 import java.util.concurrent.CountDownLatch;
@@ -48,7 +50,7 @@
     private static native int nativeCaptureDisplay(DisplayCaptureArgs captureArgs,
             long captureListener);
     private static native int nativeCaptureLayers(LayerCaptureArgs captureArgs,
-            long captureListener);
+            long captureListener, boolean sync);
     private static native long nativeCreateScreenCaptureListener(
             ObjIntConsumer<ScreenshotHardwareBuffer> consumer);
     private static native void nativeWriteListenerToParcel(long nativeObject, Parcel out);
@@ -134,7 +136,8 @@
      */
     public static ScreenshotHardwareBuffer captureLayers(LayerCaptureArgs captureArgs) {
         SynchronousScreenCaptureListener syncScreenCapture = createSyncCaptureListener();
-        int status = captureLayers(captureArgs, syncScreenCapture);
+        int status = nativeCaptureLayers(captureArgs, syncScreenCapture.mNativeObject,
+                Flags.syncScreenCapture());
         if (status != 0) {
             return null;
         }
@@ -171,7 +174,7 @@
      */
     public static int captureLayers(@NonNull LayerCaptureArgs captureArgs,
             @NonNull ScreenCaptureListener captureListener) {
-        return nativeCaptureLayers(captureArgs, captureListener.mNativeObject);
+        return nativeCaptureLayers(captureArgs, captureListener.mNativeObject, false /* sync */);
     }
 
     /**
@@ -674,7 +677,7 @@
      * This listener can only be used for a single call to capture content call.
      */
     public static class ScreenCaptureListener implements Parcelable {
-        private final long mNativeObject;
+        final long mNativeObject;
         private static final NativeAllocationRegistry sRegistry =
                 NativeAllocationRegistry.createMalloced(
                         ScreenCaptureListener.class.getClassLoader(), getNativeListenerFinalizer());
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index bdaad2b..473b814 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -47,6 +47,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
+import android.view.WindowManager;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
@@ -337,7 +338,14 @@
                         "SplashScreenView");
                 ImageView imageView = new ImageView(viewContext);
                 imageView.setBackground(mIconDrawable);
-                viewHost.setView(imageView, mIconSize, mIconSize);
+                final int windowFlag = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+                final WindowManager.LayoutParams lp =
+                        new WindowManager.LayoutParams(mIconSize, mIconSize,
+                                WindowManager.LayoutParams.TYPE_APPLICATION, windowFlag,
+                                PixelFormat.TRANSPARENT);
+                viewHost.setView(imageView, lp);
                 SurfaceControlViewHost.SurfacePackage surfacePackage = viewHost.getSurfacePackage();
                 surfaceView.setChildSurfacePackage(surfacePackage);
                 view.mSurfacePackage = surfacePackage;
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
index 5dbf328..93297e6 100644
--- a/core/java/android/window/TaskFragmentCreationParams.java
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -94,11 +94,18 @@
     @Nullable
     private final IBinder mPairedActivityToken;
 
+    /**
+     * If {@code true}, transitions are allowed even if the TaskFragment is empty. If
+     * {@code false}, transitions will wait until the TaskFragment becomes non-empty or other
+     * conditions are met. Default to {@code false}.
+     */
+    private final boolean mAllowTransitionWhenEmpty;
+
     private TaskFragmentCreationParams(
             @NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
             @NonNull IBinder ownerToken, @NonNull Rect initialRelativeBounds,
             @WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken,
-            @Nullable IBinder pairedActivityToken) {
+            @Nullable IBinder pairedActivityToken, boolean allowTransitionWhenEmpty) {
         if (pairedPrimaryFragmentToken != null && pairedActivityToken != null) {
             throw new IllegalArgumentException("pairedPrimaryFragmentToken and"
                     + " pairedActivityToken should not be set at the same time.");
@@ -110,6 +117,7 @@
         mWindowingMode = windowingMode;
         mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken;
         mPairedActivityToken = pairedActivityToken;
+        mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
     }
 
     @NonNull
@@ -155,6 +163,11 @@
         return mPairedActivityToken;
     }
 
+    /** @hide */
+    public boolean getAllowTransitionWhenEmpty() {
+        return mAllowTransitionWhenEmpty;
+    }
+
     private TaskFragmentCreationParams(Parcel in) {
         mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
         mFragmentToken = in.readStrongBinder();
@@ -163,6 +176,7 @@
         mWindowingMode = in.readInt();
         mPairedPrimaryFragmentToken = in.readStrongBinder();
         mPairedActivityToken = in.readStrongBinder();
+        mAllowTransitionWhenEmpty = in.readBoolean();
     }
 
     /** @hide */
@@ -175,6 +189,7 @@
         dest.writeInt(mWindowingMode);
         dest.writeStrongBinder(mPairedPrimaryFragmentToken);
         dest.writeStrongBinder(mPairedActivityToken);
+        dest.writeBoolean(mAllowTransitionWhenEmpty);
     }
 
     @NonNull
@@ -201,6 +216,7 @@
                 + " windowingMode=" + mWindowingMode
                 + " pairedFragmentToken=" + mPairedPrimaryFragmentToken
                 + " pairedActivityToken=" + mPairedActivityToken
+                + " allowTransitionWhenEmpty=" + mAllowTransitionWhenEmpty
                 + "}";
     }
 
@@ -234,6 +250,8 @@
         @Nullable
         private IBinder mPairedActivityToken;
 
+        private boolean mAllowTransitionWhenEmpty;
+
         public Builder(@NonNull TaskFragmentOrganizerToken organizer,
                 @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
             mOrganizer = organizer;
@@ -298,12 +316,26 @@
             return this;
         }
 
+        /**
+         * Sets whether transitions are allowed when the TaskFragment is empty. If {@code true},
+         * transitions are allowed when the TaskFragment is empty. If {@code false}, transitions
+         * will wait until the TaskFragment becomes non-empty or other conditions are met. Default
+         * to {@code false}.
+         *
+         * @hide
+         */
+        @NonNull
+        public Builder setAllowTransitionWhenEmpty(boolean allowTransitionWhenEmpty) {
+            mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
+            return this;
+        }
+
         /** Constructs the options to create TaskFragment with. */
         @NonNull
         public TaskFragmentCreationParams build() {
             return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
                     mInitialRelativeBounds, mWindowingMode, mPairedPrimaryFragmentToken,
-                    mPairedActivityToken);
+                    mPairedActivityToken, mAllowTransitionWhenEmpty);
         }
     }
 }
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index acc6a74..7b8cdff 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -125,6 +125,16 @@
      */
     public static final int OP_TYPE_SET_DIM_ON_TASK = 16;
 
+    /**
+     * Sets this TaskFragment to move to bottom of the Task if any of the activities below it is
+     * launched in a mode requiring clear top.
+     *
+     * This is only allowed for system organizers. See
+     * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer(
+     * ITaskFragmentOrganizer, boolean)}
+     */
+    public static final int OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH = 17;
+
     @IntDef(prefix = { "OP_TYPE_" }, value = {
             OP_TYPE_UNKNOWN,
             OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -144,6 +154,7 @@
             OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE,
             OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE,
             OP_TYPE_SET_DIM_ON_TASK,
+            OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface OperationType {}
@@ -173,12 +184,14 @@
 
     private final boolean mDimOnTask;
 
+    private final boolean mMoveToBottomIfClearWhenLaunch;
+
     private TaskFragmentOperation(@OperationType int opType,
             @Nullable TaskFragmentCreationParams taskFragmentCreationParams,
             @Nullable IBinder activityToken, @Nullable Intent activityIntent,
             @Nullable Bundle bundle, @Nullable IBinder secondaryFragmentToken,
             @Nullable TaskFragmentAnimationParams animationParams,
-            boolean isolatedNav, boolean dimOnTask) {
+            boolean isolatedNav, boolean dimOnTask, boolean moveToBottomIfClearWhenLaunch) {
         mOpType = opType;
         mTaskFragmentCreationParams = taskFragmentCreationParams;
         mActivityToken = activityToken;
@@ -188,6 +201,7 @@
         mAnimationParams = animationParams;
         mIsolatedNav = isolatedNav;
         mDimOnTask = dimOnTask;
+        mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch;
     }
 
     private TaskFragmentOperation(Parcel in) {
@@ -200,6 +214,7 @@
         mAnimationParams = in.readTypedObject(TaskFragmentAnimationParams.CREATOR);
         mIsolatedNav = in.readBoolean();
         mDimOnTask = in.readBoolean();
+        mMoveToBottomIfClearWhenLaunch = in.readBoolean();
     }
 
     @Override
@@ -213,6 +228,7 @@
         dest.writeTypedObject(mAnimationParams, flags);
         dest.writeBoolean(mIsolatedNav);
         dest.writeBoolean(mDimOnTask);
+        dest.writeBoolean(mMoveToBottomIfClearWhenLaunch);
     }
 
     @NonNull
@@ -300,6 +316,14 @@
         return mDimOnTask;
     }
 
+    /**
+     * Returns whether the TaskFragment should move to bottom of task when any activity below it
+     * is launched in clear top mode.
+     */
+    public boolean isMoveToBottomIfClearWhenLaunch() {
+        return mMoveToBottomIfClearWhenLaunch;
+    }
+
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();
@@ -324,6 +348,7 @@
         }
         sb.append(", isolatedNav=").append(mIsolatedNav);
         sb.append(", dimOnTask=").append(mDimOnTask);
+        sb.append(", moveToBottomIfClearWhenLaunch=").append(mMoveToBottomIfClearWhenLaunch);
 
         sb.append('}');
         return sb.toString();
@@ -332,7 +357,8 @@
     @Override
     public int hashCode() {
         return Objects.hash(mOpType, mTaskFragmentCreationParams, mActivityToken, mActivityIntent,
-                mBundle, mSecondaryFragmentToken, mAnimationParams, mIsolatedNav, mDimOnTask);
+                mBundle, mSecondaryFragmentToken, mAnimationParams, mIsolatedNav, mDimOnTask,
+                mMoveToBottomIfClearWhenLaunch);
     }
 
     @Override
@@ -349,7 +375,8 @@
                 && Objects.equals(mSecondaryFragmentToken, other.mSecondaryFragmentToken)
                 && Objects.equals(mAnimationParams, other.mAnimationParams)
                 && mIsolatedNav == other.mIsolatedNav
-                && mDimOnTask == other.mDimOnTask;
+                && mDimOnTask == other.mDimOnTask
+                && mMoveToBottomIfClearWhenLaunch == other.mMoveToBottomIfClearWhenLaunch;
     }
 
     @Override
@@ -385,6 +412,8 @@
 
         private boolean mDimOnTask;
 
+        private boolean mMoveToBottomIfClearWhenLaunch;
+
         /**
          * @param opType the {@link OperationType} of this {@link TaskFragmentOperation}.
          */
@@ -466,13 +495,23 @@
         }
 
         /**
+         * Sets whether the TaskFragment should move to bottom of task when any activity below it
+         * is launched in clear top mode.
+         */
+        @NonNull
+        public Builder setMoveToBottomIfClearWhenLaunch(boolean moveToBottomIfClearWhenLaunch) {
+            mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch;
+            return this;
+        }
+
+        /**
          * Constructs the {@link TaskFragmentOperation}.
          */
         @NonNull
         public TaskFragmentOperation build() {
             return new TaskFragmentOperation(mOpType, mTaskFragmentCreationParams, mActivityToken,
                     mActivityIntent, mBundle, mSecondaryFragmentToken, mAnimationParams,
-                    mIsolatedNav, mDimOnTask);
+                    mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch);
         }
     }
 }
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index bceb872..feae173 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -421,8 +421,11 @@
         final String perChangeLineStart = shouldPrettyPrint ? "\n" + innerPrefix : "";
         StringBuilder sb = new StringBuilder();
         sb.append("{id=").append(mDebugId).append(" t=").append(transitTypeToString(mType))
-                .append(" f=0x").append(Integer.toHexString(mFlags)).append(" trk=").append(mTrack)
-                .append(" r=[");
+                .append(" f=0x").append(Integer.toHexString(mFlags)).append(" trk=").append(mTrack);
+        if (mOptions != null) {
+            sb.append(" opt=").append(mOptions);
+        }
+        sb.append(" r=[");
         for (int i = 0; i < mRoots.size(); ++i) {
             if (i > 0) {
                 sb.append(',');
@@ -1211,21 +1214,31 @@
 
         @NonNull
         private static String typeToString(int mode) {
-            switch(mode) {
-                case ANIM_CUSTOM: return "ANIM_CUSTOM";
-                case ANIM_CLIP_REVEAL: return "ANIM_CLIP_REVEAL";
-                case ANIM_SCALE_UP: return "ANIM_SCALE_UP";
-                case ANIM_THUMBNAIL_SCALE_UP: return "ANIM_THUMBNAIL_SCALE_UP";
-                case ANIM_THUMBNAIL_SCALE_DOWN: return "ANIM_THUMBNAIL_SCALE_DOWN";
-                case ANIM_OPEN_CROSS_PROFILE_APPS: return "ANIM_OPEN_CROSS_PROFILE_APPS";
-                default: return "<unknown:" + mode + ">";
-            }
+            return switch (mode) {
+                case ANIM_CUSTOM -> "CUSTOM";
+                case ANIM_SCALE_UP -> "SCALE_UP";
+                case ANIM_THUMBNAIL_SCALE_UP -> "THUMBNAIL_SCALE_UP";
+                case ANIM_THUMBNAIL_SCALE_DOWN -> "THUMBNAIL_SCALE_DOWN";
+                case ANIM_SCENE_TRANSITION -> "SCENE_TRANSITION";
+                case ANIM_CLIP_REVEAL -> "CLIP_REVEAL";
+                case ANIM_OPEN_CROSS_PROFILE_APPS -> "OPEN_CROSS_PROFILE_APPS";
+                case ANIM_FROM_STYLE -> "FROM_STYLE";
+                default -> "<" + mode + ">";
+            };
         }
 
         @Override
         public String toString() {
-            return "{ AnimationOptions type= " + typeToString(mType) + " package=" + mPackageName
-                    + " override=" + mOverrideTaskTransition + " b=" + mTransitionBounds + "}";
+            final StringBuilder sb = new StringBuilder(32);
+            sb.append("{t=").append(typeToString(mType));
+            if (mOverrideTaskTransition) {
+                sb.append(" overrideTask=true");
+            }
+            if (!mTransitionBounds.isEmpty()) {
+                sb.append(" bounds=").append(mTransitionBounds);
+            }
+            sb.append('}');
+            return sb.toString();
         }
 
         /** Customized activity transition. */
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 86804c6..65075ae 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -226,9 +226,6 @@
             setTopOnBackInvokedCallback(null);
         }
 
-        // We should also stop running animations since all callbacks have been removed.
-        // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
-        Handler.getMain().post(mProgressAnimator::reset);
         mAllCallbacks.clear();
         mOnBackInvokedCallbacks.clear();
     }
@@ -442,8 +439,7 @@
 
         return WindowOnBackInvokedDispatcher
                 .isOnBackInvokedCallbackEnabled(activityInfo, applicationInfo,
-                        () -> originalContext.obtainStyledAttributes(
-                                new int[] {android.R.attr.windowSwipeToDismiss}), true);
+                        () -> originalContext);
     }
 
     @Override
@@ -501,7 +497,7 @@
      */
     public static boolean isOnBackInvokedCallbackEnabled(@Nullable ActivityInfo activityInfo,
             @NonNull ApplicationInfo applicationInfo,
-            @NonNull Supplier<TypedArray> windowAttrSupplier, boolean recycleTypedArray) {
+            @NonNull Supplier<Context> contextSupplier) {
         // new back is enabled if the feature flag is enabled AND the app does not explicitly
         // request legacy back.
         if (!ENABLE_PREDICTIVE_BACK) {
@@ -547,15 +543,15 @@
             //    setTrigger(true)
             // Use the original context to resolve the styled attribute so that they stay
             // true to the window.
-            TypedArray windowAttr = windowAttrSupplier.get();
+            final Context context = contextSupplier.get();
             boolean windowSwipeToDismiss = true;
-            if (windowAttr != null) {
-                if (windowAttr.getIndexCount() > 0) {
-                    windowSwipeToDismiss = windowAttr.getBoolean(0, true);
+            if (context != null) {
+                final TypedArray array = context.obtainStyledAttributes(
+                            new int[]{android.R.attr.windowSwipeToDismiss});
+                if (array.getIndexCount() > 0) {
+                    windowSwipeToDismiss = array.getBoolean(0, true);
                 }
-                if (recycleTypedArray) {
-                    windowAttr.recycle();
-                }
+                array.recycle();
             }
 
             if (DEBUG) {
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index c20b278..7f5331b 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -167,6 +167,11 @@
                     + ", reported config=" + currentConfig
                     + ", updated config=" + newConfig);
         }
+        // Update display first. In case callers want to obtain display information(
+        // ex: DisplayMetrics) in #onConfigurationChanged callback.
+        if (displayChanged) {
+            context.updateDisplay(newDisplayId);
+        }
         if (shouldUpdateResources) {
             // TODO(ag/9789103): update resource manager logic to track non-activity tokens
             mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId);
@@ -195,9 +200,6 @@
                 }
             }
         }
-        if (displayChanged) {
-            context.updateDisplay(newDisplayId);
-        }
     }
 
     /**
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 4b5595f..1de77f6 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -43,3 +43,26 @@
   description: "Whether the API PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT is available"
   bug: "310816437"
 }
+
+flag {
+  name: "allow_hide_scm_button"
+  namespace: "large_screen_experiences_app_compat"
+  description: "Whether we should allow hiding the size compat restart button"
+  bug: "318840081"
+}
+
+flag {
+  name: "configurable_font_scale_default"
+  namespace: "large_screen_experiences_app_compat"
+  description: "Whether the font_scale is read from a device dependent configuration file"
+  bug: "319808237"
+  is_fixed_read_only: true
+}
+
+flag {
+  name: "camera_compat_for_freeform"
+  namespace: "large_screen_experiences_app_compat"
+  description: "Whether to apply Camera Compat treatment to fixed-orientation apps in freeform windowing mode"
+  bug: "314952133"
+  is_fixed_read_only: true
+}
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index ad0e9a4..51890ec 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -8,6 +8,13 @@
 }
 
 flag {
+    name: "bal_require_opt_in_same_uid"
+    namespace: "responsible_apis"
+    description: "Require the PendingIntent creator/sender to opt in if it is the same UID"
+    bug: "296478951"
+}
+
+flag {
     name: "bal_dont_bring_existing_background_task_stack_to_fg"
     namespace: "responsible_apis"
     description: "When starting a PendingIntent with ONLY creator privileges, don't bring the existing task stack to foreground"
@@ -29,13 +36,6 @@
 }
 
 flag {
-    name: "bal_return_correct_code_if_caller_is_persistent_system_process"
-    namespace: "responsible_apis"
-    description: "Split visibility check and return a better status code in case of system process."
-    bug: "171459802"
-}
-
-flag {
     name: "bal_improve_real_caller_visibility_check"
     namespace: "responsible_apis"
     description: "Prevent a task to restart based on a visible window during task switch."
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 56df493..069affb 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -63,4 +63,36 @@
     description: "Enable trustedPresentationListener on windows public API"
     is_fixed_read_only: true
     bug: "278027319"
-}
\ No newline at end of file
+}
+
+flag {
+    namespace: "window_surfaces"
+    name: "sdk_desired_present_time"
+    description: "Feature flag for the new SDK API to set desired present time"
+    is_fixed_read_only: true
+    bug: "295038072"
+}
+
+flag {
+    namespace: "window_surfaces"
+    name: "surface_control_input_receiver"
+    description: "Enable public API to register an InputReceiver for a SurfaceControl"
+    is_fixed_read_only: true
+    bug: "278757236"
+}
+
+flag {
+    namespace: "window_surfaces"
+    name: "screen_recording_callbacks"
+    description: "Enable screen recording callbacks public API"
+    is_fixed_read_only: true
+    bug: "304574518"
+}
+
+flag {
+    namespace: "window_surfaces"
+    name: "sync_screen_capture"
+    description: "Create a screen capture API that blocks in SurfaceFlinger"
+    is_fixed_read_only: true
+    bug: "321263247"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index f2bce9c..f234637 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -38,14 +38,6 @@
 }
 
 flag {
-  name: "draw_magnifier_border_outside_wmlock"
-  namespace: "windowing_frontend"
-  description: "Avoid holding WM locks for a long time when executing lockCanvas"
-  bug: "316075123"
-  is_fixed_read_only: true
-}
-
-flag {
   name: "introduce_smoother_dimmer"
   namespace: "windowing_frontend"
   description: "Refactor dim to fix flickers"
@@ -69,11 +61,10 @@
 }
 
 flag {
-    name: "predictive_back_system_animations"
+    name: "predictive_back_system_anims"
     namespace: "systemui"
     description: "Predictive back for system animations"
-    bug: "309545085"
-    is_fixed_read_only: true
+    bug: "320510464"
 }
 
 flag {
@@ -90,4 +81,12 @@
     description: "Feature flag to enable a multi-instance system ui component property."
     bug: "262864589"
     is_fixed_read_only: true
+}
+
+flag {
+  name: "insets_decoupled_configuration"
+  namespace: "windowing_frontend"
+  description: "Configuration decoupled from insets"
+  bug: "151861875"
+  is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index f743ab7..4a6e8d7 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -42,8 +42,23 @@
 
 flag {
     namespace: "windowing_sdk"
+    name: "untrusted_embedding_any_app_permission"
+    description: "Feature flag to enable the permission to embed any app in untrusted mode."
+    bug: "289199433"
+    is_fixed_read_only: true
+}
+
+flag {
+    namespace: "windowing_sdk"
     name: "activity_window_info_flag"
     description: "To dispatch ActivityWindowInfo through ClientTransaction"
     bug: "287582673"
     is_fixed_read_only: true
+}
+
+flag {
+    namespace: "windowing_sdk"
+    name: "embedded_activity_back_nav_flag"
+    description: "Refines embedded activity back navigation behavior"
+    bug: "240575809"
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index de0f070..b4395a7 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -18,7 +18,6 @@
 
 import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 
 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
 import static com.android.internal.os.RoSystemProperties.SUPPORT_ONE_HANDED_MODE;
@@ -329,7 +328,8 @@
     }
 
     private AlertDialog createShortcutWarningDialog(int userId) {
-        List<AccessibilityTarget> targets = getTargets(mContext, ACCESSIBILITY_SHORTCUT_KEY);
+        List<AccessibilityTarget> targets = getTargets(mContext,
+                ShortcutConstants.UserShortcutType.HARDWARE);
         if (targets.size() == 0) {
             return null;
         }
@@ -541,7 +541,7 @@
     private ComponentName getShortcutTargetComponentName() {
         final List<String> shortcutTargets = mFrameworkObjectProvider
                 .getAccessibilityManagerInstance(mContext)
-                .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY);
+                .getAccessibilityShortcutTargets(ShortcutConstants.UserShortcutType.HARDWARE);
         if (shortcutTargets.size() != 1) {
             return null;
         }
diff --git a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
index 7ec8838..353e182 100644
--- a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
+++ b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
@@ -44,19 +44,27 @@
      * choose accessibility shortcut as preferred shortcut.
      * {@code TRIPLETAP} for displaying specifying magnification to be toggled via quickly
      * tapping screen 3 times as preferred shortcut.
+     * {@code TWO_FINGERS_TRIPLE_TAP} for displaying specifying magnification to be toggled via
+     * quickly tapping screen 3 times with two fingers as preferred shortcut.
      */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({
-            UserShortcutType.DEFAULT,
-            UserShortcutType.SOFTWARE,
-            UserShortcutType.HARDWARE,
-            UserShortcutType.TRIPLETAP,
-    })
+    @IntDef(
+            flag = true,
+            value = {
+                    UserShortcutType.DEFAULT,
+                    UserShortcutType.SOFTWARE,
+                    UserShortcutType.HARDWARE,
+                    UserShortcutType.TRIPLETAP,
+                    UserShortcutType.TWO_FINGERS_TRIPLE_TAP,
+            })
     public @interface UserShortcutType {
         int DEFAULT = 0;
-        int SOFTWARE = 1; // 1 << 0
-        int HARDWARE = 2; // 1 << 1
-        int TRIPLETAP = 4; // 1 << 2
+        // LINT.IfChange(shortcut_type_intdef)
+        int SOFTWARE = 1;
+        int HARDWARE = 1 << 1;
+        int TRIPLETAP = 1 << 2;
+        int TWO_FINGERS_TRIPLE_TAP = 1 << 3;
+        // LINT.ThenChange(:shortcut_type_array)
     }
 
     /**
@@ -64,9 +72,12 @@
      * non-default IntDef types.
      */
     public static final int[] USER_SHORTCUT_TYPES = {
+            // LINT.IfChange(shortcut_type_array)
             UserShortcutType.SOFTWARE,
             UserShortcutType.HARDWARE,
-            UserShortcutType.TRIPLETAP
+            UserShortcutType.TRIPLETAP,
+            UserShortcutType.TWO_FINGERS_TRIPLE_TAP,
+            // LINT.ThenChange(:shortcut_type_intdef)
     };
 
 
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java
index 063154d..33048dc 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java
@@ -17,14 +17,13 @@
 package com.android.internal.accessibility.dialog;
 
 import static com.android.internal.accessibility.util.ShortcutUtils.convertToKey;
-import static com.android.internal.accessibility.util.ShortcutUtils.convertToUserType;
 import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained;
 
 import android.accessibilityservice.AccessibilityShortcutInfo;
 import android.annotation.NonNull;
 import android.content.Context;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
 
+import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
 import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
 
@@ -33,7 +32,8 @@
  */
 class AccessibilityActivityTarget extends AccessibilityTarget {
 
-    AccessibilityActivityTarget(Context context, @ShortcutType int shortcutType,
+    AccessibilityActivityTarget(Context context,
+            @ShortcutConstants.UserShortcutType int shortcutType,
             @NonNull AccessibilityShortcutInfo shortcutInfo) {
         super(context,
                 shortcutType,
@@ -44,7 +44,7 @@
                 shortcutInfo.getActivityInfo().applicationInfo.uid,
                 shortcutInfo.getActivityInfo().loadLabel(context.getPackageManager()),
                 shortcutInfo.getActivityInfo().loadIcon(context.getPackageManager()),
-                convertToKey(convertToUserType(shortcutType)));
+                convertToKey(shortcutType));
     }
 
     @Override
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
index 7eb09e5..e084ebd 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
@@ -17,7 +17,6 @@
 package com.android.internal.accessibility.dialog;
 
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
@@ -36,6 +35,7 @@
 import android.widget.TextView;
 
 import com.android.internal.R;
+import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.widget.ResolverDrawerLayout;
 
 import java.util.ArrayList;
@@ -85,7 +85,7 @@
             prompt.setVisibility(View.VISIBLE);
         }
 
-        mTargets.addAll(getTargets(this, ACCESSIBILITY_BUTTON));
+        mTargets.addAll(getTargets(this, ShortcutConstants.UserShortcutType.SOFTWARE));
 
         final GridView gridview = findViewById(R.id.accessibility_button_chooser_grid);
         gridview.setAdapter(new ButtonTargetAdapter(mTargets));
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
index 2b6913c..7406da4 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
@@ -17,14 +17,13 @@
 package com.android.internal.accessibility.dialog;
 
 import static com.android.internal.accessibility.util.ShortcutUtils.convertToKey;
-import static com.android.internal.accessibility.util.ShortcutUtils.convertToUserType;
 import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.NonNull;
 import android.content.Context;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
 
+import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
 import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
 
@@ -36,7 +35,9 @@
 
     private final AccessibilityServiceInfo mAccessibilityServiceInfo;
 
-    AccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
+    AccessibilityServiceTarget(
+            Context context,
+            @ShortcutConstants.UserShortcutType int shortcutType,
             @AccessibilityFragmentType int fragmentType,
             @NonNull AccessibilityServiceInfo serviceInfo) {
         super(context,
@@ -48,7 +49,7 @@
                 serviceInfo.getResolveInfo().serviceInfo.applicationInfo.uid,
                 serviceInfo.getResolveInfo().loadLabel(context.getPackageManager()),
                 serviceInfo.getResolveInfo().loadIcon(context.getPackageManager()),
-                convertToKey(convertToUserType(shortcutType)));
+                convertToKey(shortcutType));
         mAccessibilityServiceInfo = serviceInfo;
     }
 
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index 2e80b7e..8e2ec1b 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -15,10 +15,6 @@
  */
 package com.android.internal.accessibility.dialog;
 
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
-import static android.view.accessibility.AccessibilityManager.ShortcutType;
-
 import static com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.createEnableDialogContentView;
 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getInstalledTargets;
@@ -42,6 +38,7 @@
 import android.widget.AdapterView;
 
 import com.android.internal.R;
+import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
@@ -52,8 +49,8 @@
  * activity or allowlisting feature for volume key shortcut.
  */
 public class AccessibilityShortcutChooserActivity extends Activity {
-    @ShortcutType
-    private final int mShortcutType = ACCESSIBILITY_SHORTCUT_KEY;
+    @ShortcutConstants.UserShortcutType
+    private final int mShortcutType = ShortcutConstants.UserShortcutType.HARDWARE;
     private static final String KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE =
             "accessibility_shortcut_menu_mode";
     private final List<AccessibilityTarget> mTargets = new ArrayList<>();
@@ -246,7 +243,7 @@
                 mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT;
         final int selectDialogTitleId = R.string.accessibility_select_shortcut_menu_title;
         final int editDialogTitleId =
-                mShortcutType == ACCESSIBILITY_BUTTON
+                mShortcutType == ShortcutConstants.UserShortcutType.SOFTWARE
                         ? R.string.accessibility_edit_shortcut_menu_button_title
                         : R.string.accessibility_edit_shortcut_menu_volume_title;
 
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
index 652cb52..4ab1ee9 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
@@ -16,10 +16,6 @@
 
 package com.android.internal.accessibility.dialog;
 
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
-
-import static com.android.internal.accessibility.util.ShortcutUtils.convertToUserType;
 import static com.android.internal.accessibility.util.ShortcutUtils.optInValueToSettings;
 import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
 
@@ -30,7 +26,6 @@
 import android.graphics.drawable.Drawable;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
@@ -47,7 +42,7 @@
 public abstract class AccessibilityTarget implements TargetOperations, OnTargetSelectedListener,
         OnTargetCheckedChangeListener {
     private Context mContext;
-    @ShortcutType
+    @ShortcutConstants.UserShortcutType
     private int mShortcutType;
     @AccessibilityFragmentType
     private int mFragmentType;
@@ -61,7 +56,8 @@
     private CharSequence mStateDescription;
 
     @VisibleForTesting
-    public AccessibilityTarget(Context context, @ShortcutType int shortcutType,
+    public AccessibilityTarget(
+            Context context, @ShortcutConstants.UserShortcutType int shortcutType,
             @AccessibilityFragmentType int fragmentType, boolean isShortcutSwitched, String id,
             int uid, CharSequence label, Drawable icon, String key) {
         mContext = context;
@@ -99,10 +95,10 @@
         final AccessibilityManager am =
                 getContext().getSystemService(AccessibilityManager.class);
         switch (getShortcutType()) {
-            case ACCESSIBILITY_BUTTON:
+            case ShortcutConstants.UserShortcutType.SOFTWARE:
                 am.notifyAccessibilityButtonClicked(getContext().getDisplayId(), getId());
                 return;
-            case ACCESSIBILITY_SHORTCUT_KEY:
+            case ShortcutConstants.UserShortcutType.HARDWARE:
                 am.performAccessibilityShortcut(getId());
                 return;
             default:
@@ -114,9 +110,9 @@
     public void onCheckedChanged(boolean isChecked) {
         setShortcutEnabled(isChecked);
         if (isChecked) {
-            optInValueToSettings(getContext(), convertToUserType(getShortcutType()), getId());
+            optInValueToSettings(getContext(), getShortcutType(), getId());
         } else {
-            optOutValueFromSettings(getContext(), convertToUserType(getShortcutType()), getId());
+            optOutValueFromSettings(getContext(), getShortcutType(), getId());
         }
     }
 
@@ -142,7 +138,7 @@
         return mContext;
     }
 
-    public @ShortcutType int getShortcutType() {
+    public @ShortcutConstants.UserShortcutType int getShortcutType() {
         return mShortcutType;
     }
 
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index 51a5ddf..bd63e23 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -16,8 +16,6 @@
 
 package com.android.internal.accessibility.dialog;
 
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
-
 import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
 import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
 import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
@@ -41,12 +39,12 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
 import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.TextView;
 
 import com.android.internal.R;
+import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
 
 import java.util.ArrayList;
@@ -70,8 +68,9 @@
      * @return The list of {@link AccessibilityTarget}.
      * @hide
      */
-    public static List<AccessibilityTarget> getTargets(Context context,
-            @ShortcutType int shortcutType) {
+    public static List<AccessibilityTarget> getTargets(
+            Context context,
+            @ShortcutConstants.UserShortcutType int shortcutType) {
         // List all accessibility target
         final List<AccessibilityTarget> installedTargets = getInstalledTargets(context,
                 shortcutType);
@@ -113,7 +112,7 @@
      * @return The list of {@link AccessibilityTarget}.
      */
     static List<AccessibilityTarget> getInstalledTargets(Context context,
-            @ShortcutType int shortcutType) {
+            @ShortcutConstants.UserShortcutType int shortcutType) {
         final List<AccessibilityTarget> targets = new ArrayList<>();
         targets.addAll(getAccessibilityFilteredTargets(context, shortcutType));
         targets.addAll(getAllowListingFeatureTargets(context, shortcutType));
@@ -122,7 +121,7 @@
     }
 
     private static List<AccessibilityTarget> getAccessibilityFilteredTargets(Context context,
-            @ShortcutType int shortcutType) {
+            @ShortcutConstants.UserShortcutType int shortcutType) {
         final List<AccessibilityTarget> serviceTargets =
                 getAccessibilityServiceTargets(context, shortcutType);
         final List<AccessibilityTarget> activityTargets =
@@ -155,7 +154,7 @@
     }
 
     private static List<AccessibilityTarget> getAccessibilityServiceTargets(Context context,
-            @ShortcutType int shortcutType) {
+            @ShortcutConstants.UserShortcutType int shortcutType) {
         final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
                 Context.ACCESSIBILITY_SERVICE);
         final List<AccessibilityServiceInfo> installedServices =
@@ -171,7 +170,7 @@
             final boolean hasRequestAccessibilityButtonFlag =
                     (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
             if ((targetSdk <= Build.VERSION_CODES.Q) && !hasRequestAccessibilityButtonFlag
-                    && (shortcutType == ACCESSIBILITY_BUTTON)) {
+                    && (shortcutType == ShortcutConstants.UserShortcutType.SOFTWARE)) {
                 continue;
             }
 
@@ -182,7 +181,7 @@
     }
 
     private static List<AccessibilityTarget> getAccessibilityActivityTargets(Context context,
-            @ShortcutType int shortcutType) {
+            @ShortcutConstants.UserShortcutType int shortcutType) {
         final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
                 Context.ACCESSIBILITY_SERVICE);
         final List<AccessibilityShortcutInfo> installedServices =
@@ -201,7 +200,7 @@
     }
 
     private static List<AccessibilityTarget> getAllowListingFeatureTargets(Context context,
-            @ShortcutType int shortcutType) {
+            @ShortcutConstants.UserShortcutType int shortcutType) {
         final List<AccessibilityTarget> targets = new ArrayList<>();
         final int uid = context.getApplicationInfo().uid;
 
@@ -281,8 +280,10 @@
         return targets;
     }
 
-    private static AccessibilityTarget createAccessibilityServiceTarget(Context context,
-            @ShortcutType int shortcutType, @NonNull AccessibilityServiceInfo info) {
+    private static AccessibilityTarget createAccessibilityServiceTarget(
+            Context context,
+            @ShortcutConstants.UserShortcutType int shortcutType,
+            @NonNull AccessibilityServiceInfo info) {
         switch (getAccessibilityServiceFragmentType(info)) {
             case AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE:
                 return new VolumeShortcutToggleAccessibilityServiceTarget(context, shortcutType,
diff --git a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java
index 1bc8b84..641a9f1 100644
--- a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java
@@ -16,9 +16,6 @@
 
 package com.android.internal.accessibility.dialog;
 
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
-
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
 import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
 import static com.android.internal.accessibility.util.ShortcutUtils.isComponentIdExistingInSettings;
@@ -28,7 +25,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.UserHandle;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
 import android.view.accessibility.Flags;
 
 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
@@ -45,7 +41,7 @@
 public class InvisibleToggleAccessibilityServiceTarget extends AccessibilityServiceTarget {
 
     public InvisibleToggleAccessibilityServiceTarget(
-            Context context, @ShortcutType int shortcutType,
+            Context context, @UserShortcutType int shortcutType,
             @NonNull AccessibilityServiceInfo serviceInfo) {
         super(context,
                 shortcutType,
@@ -72,10 +68,10 @@
 
     private boolean isComponentIdExistingInOtherShortcut() {
         switch (getShortcutType()) {
-            case ACCESSIBILITY_BUTTON:
+            case UserShortcutType.SOFTWARE:
                 return isComponentIdExistingInSettings(getContext(), UserShortcutType.HARDWARE,
                         getId());
-            case ACCESSIBILITY_SHORTCUT_KEY:
+            case UserShortcutType.HARDWARE:
                 return isComponentIdExistingInSettings(getContext(), UserShortcutType.SOFTWARE,
                         getId());
             default:
diff --git a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
index c22f17d..2204c0b 100644
--- a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
@@ -18,8 +18,8 @@
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
 
+import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
 
 /**
@@ -28,7 +28,8 @@
  */
 class InvisibleToggleAllowListingFeatureTarget extends AccessibilityTarget {
 
-    InvisibleToggleAllowListingFeatureTarget(Context context, @ShortcutType int shortcutType,
+    InvisibleToggleAllowListingFeatureTarget(Context context,
+            @ShortcutConstants.UserShortcutType int shortcutType,
             boolean isShortcutSwitched, String id, int uid, CharSequence label, Drawable icon,
             String key) {
         super(context, shortcutType, AccessibilityFragmentType.INVISIBLE_TOGGLE, isShortcutSwitched,
diff --git a/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
index a4ffef6..a6ef73e 100644
--- a/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
@@ -22,9 +22,9 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.view.View;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import com.android.internal.R;
+import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
 import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
 import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
@@ -45,7 +45,8 @@
         float DISABLED = 0.5f;
     }
 
-    ToggleAccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
+    ToggleAccessibilityServiceTarget(Context context,
+            @ShortcutConstants.UserShortcutType int shortcutType,
             @NonNull AccessibilityServiceInfo serviceInfo) {
         super(context,
                 shortcutType,
diff --git a/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java b/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
index 11e668f..2a9c555 100644
--- a/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
@@ -21,9 +21,9 @@
 import android.graphics.drawable.Drawable;
 import android.provider.Settings;
 import android.view.View;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import com.android.internal.R;
+import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
 import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
 import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
@@ -34,7 +34,8 @@
  */
 class ToggleAllowListingFeatureTarget extends AccessibilityTarget {
 
-    ToggleAllowListingFeatureTarget(Context context, @ShortcutType int shortcutType,
+    ToggleAllowListingFeatureTarget(Context context,
+            @ShortcutConstants.UserShortcutType int shortcutType,
             boolean isShortcutSwitched, String id, int uid, CharSequence label, Drawable icon,
             String key) {
         super(context, shortcutType, AccessibilityFragmentType.TOGGLE, isShortcutSwitched, id,
diff --git a/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
index 04f5061..4926e72 100644
--- a/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
@@ -16,9 +16,6 @@
 
 package com.android.internal.accessibility.dialog;
 
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
-
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
 import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
 import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
@@ -27,7 +24,6 @@
 import android.annotation.NonNull;
 import android.content.ComponentName;
 import android.content.Context;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
 import android.widget.Toast;
 
 import com.android.internal.R;
@@ -39,7 +35,8 @@
  */
 class VolumeShortcutToggleAccessibilityServiceTarget extends AccessibilityServiceTarget {
 
-    VolumeShortcutToggleAccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
+    VolumeShortcutToggleAccessibilityServiceTarget(Context context,
+            @UserShortcutType int shortcutType,
             @NonNull AccessibilityServiceInfo serviceInfo) {
         super(context,
                 shortcutType,
@@ -50,10 +47,10 @@
     @Override
     public void onCheckedChanged(boolean isChecked) {
         switch (getShortcutType()) {
-            case ACCESSIBILITY_BUTTON:
+            case UserShortcutType.SOFTWARE:
                 onCheckedFromAccessibilityButton(isChecked);
                 return;
-            case ACCESSIBILITY_SHORTCUT_KEY:
+            case UserShortcutType.HARDWARE:
                 super.onCheckedChanged(isChecked);
                 return;
             default:
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
index 6b074a6..1e4bcf2 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
@@ -21,8 +21,6 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SERVICE_STATUS__DISABLED;
@@ -47,9 +45,8 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.provider.Settings;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
 
+import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.util.FrameworkStatsLog;
 
 /** Methods for logging accessibility states. */
@@ -71,15 +68,15 @@
 
     /**
      * Logs accessibility feature name that is assigned to the given {@code shortcutType}.
-     * Calls this when clicking the shortcut {@link AccessibilityManager#ACCESSIBILITY_BUTTON} or
-     * {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY}.
+     * Calls this when clicking the shortcut {@link ShortcutConstants.UserShortcutType.SOFTWARE} or
+     * {@link ShortcutConstants.UserShortcutType.HARDWARE}.
      *
      * @param context context used to retrieve the {@link Settings} provider
      * @param componentName component name of the accessibility feature
      * @param shortcutType  accessibility shortcut type
      */
     public static void logAccessibilityShortcutActivated(Context context,
-            ComponentName componentName, @ShortcutType int shortcutType) {
+            ComponentName componentName, @ShortcutConstants.UserShortcutType int shortcutType) {
         logAccessibilityShortcutActivatedInternal(componentName,
                 convertToLoggingShortcutType(context, shortcutType), UNKNOWN_STATUS);
     }
@@ -87,8 +84,8 @@
     /**
      * Logs accessibility feature name that is assigned to the given {@code shortcutType} and the
      * {@code serviceEnabled} status.
-     * Calls this when clicking the shortcut {@link AccessibilityManager#ACCESSIBILITY_BUTTON}
-     * or {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY}.
+     * Calls this when clicking the shortcut {@link ShortcutConstants.UserShortcutType.SOFTWARE}
+     * or {@link ShortcutConstants.UserShortcutType.HARDWARE}.
      *
      * @param context context used to retrieve the {@link Settings} provider
      * @param componentName  component name of the accessibility feature
@@ -96,7 +93,8 @@
      * @param serviceEnabled {@code true} if the service is enabled
      */
     public static void logAccessibilityShortcutActivated(Context context,
-            ComponentName componentName, @ShortcutType int shortcutType, boolean serviceEnabled) {
+            ComponentName componentName, @ShortcutConstants.UserShortcutType int shortcutType,
+            boolean serviceEnabled) {
         logAccessibilityShortcutActivatedInternal(componentName,
                 convertToLoggingShortcutType(context, shortcutType),
                 convertToLoggingServiceStatus(serviceEnabled));
@@ -235,9 +233,9 @@
     }
 
     private static int convertToLoggingShortcutType(Context context,
-            @ShortcutType int shortcutType) {
+            @ShortcutConstants.UserShortcutType int shortcutType) {
         switch (shortcutType) {
-            case ACCESSIBILITY_BUTTON:
+            case ShortcutConstants.UserShortcutType.SOFTWARE:
                 if (isAccessibilityFloatingMenuEnabled(context)) {
                     return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU;
                 } else if (isAccessibilityGestureEnabled(context)) {
@@ -245,7 +243,7 @@
                 } else {
                     return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON;
                 }
-            case ACCESSIBILITY_SHORTCUT_KEY:
+            case ShortcutConstants.UserShortcutType.HARDWARE:
                 return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY;
         }
         return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
index 3fd3030..276c5c4 100644
--- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
+++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
@@ -16,9 +16,6 @@
 
 package com.android.internal.accessibility.util;
 
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
-
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
 import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType.INVISIBLE_TOGGLE;
 import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
@@ -33,7 +30,6 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import java.util.Collections;
 import java.util.List;
@@ -144,7 +140,7 @@
      * @param componentId The component id that need to be checked.
      * @return {@code true} if a component id is contained.
      */
-    public static boolean isShortcutContained(Context context, @ShortcutType int shortcutType,
+    public static boolean isShortcutContained(Context context, @UserShortcutType int shortcutType,
             @NonNull String componentId) {
         final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
                 Context.ACCESSIBILITY_SERVICE);
@@ -166,6 +162,8 @@
                 return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
             case UserShortcutType.TRIPLETAP:
                 return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
+            case UserShortcutType.TWO_FINGERS_TRIPLE_TAP:
+                return Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED;
             default:
                 throw new IllegalArgumentException(
                         "Unsupported user shortcut type: " + type);
@@ -173,24 +171,6 @@
     }
 
     /**
-     * Converts {@link ShortcutType} to {@link UserShortcutType}.
-     *
-     * @param type The shortcut type.
-     * @return Mapping type from {@link UserShortcutType}.
-     */
-    public static @UserShortcutType int convertToUserType(@ShortcutType int type) {
-        switch (type) {
-            case ACCESSIBILITY_BUTTON:
-                return UserShortcutType.SOFTWARE;
-            case ACCESSIBILITY_SHORTCUT_KEY:
-                return UserShortcutType.HARDWARE;
-            default:
-                throw new IllegalArgumentException(
-                        "Unsupported shortcut type:" + type);
-        }
-    }
-
-    /**
      * Updates an accessibility state if the accessibility service is a Always-On a11y service,
      * a.k.a. AccessibilityServices that has FLAG_REQUEST_ACCESSIBILITY_BUTTON
      * <p>
@@ -255,12 +235,13 @@
     public static Set<String> getShortcutTargetsFromSettings(
             Context context, @UserShortcutType int shortcutType, int userId) {
         final String targetKey = convertToKey(shortcutType);
-        if (Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED.equals(targetKey)) {
+        if (Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED.equals(targetKey)
+                || Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED
+                .equals(targetKey)) {
             boolean magnificationEnabled = Settings.Secure.getIntForUser(
                     context.getContentResolver(), targetKey, /* def= */ 0, userId) == 1;
             return magnificationEnabled ? Set.of(MAGNIFICATION_CONTROLLER_NAME)
                     : Collections.emptySet();
-
         } else {
             final String targetString = Settings.Secure.getStringForUser(
                     context.getContentResolver(), targetKey, userId);
diff --git a/core/java/com/android/internal/app/ConfirmUserCreationActivity.java b/core/java/com/android/internal/app/ConfirmUserCreationActivity.java
index 0a28997..b4e8749 100644
--- a/core/java/com/android/internal/app/ConfirmUserCreationActivity.java
+++ b/core/java/com/android/internal/app/ConfirmUserCreationActivity.java
@@ -116,6 +116,14 @@
         if (cantCreateUser) {
             setResult(UserManager.USER_CREATION_FAILED_NOT_PERMITTED);
             return null;
+        } else if (!(isUserPropertyWithinLimit(mUserName, UserManager.MAX_USER_NAME_LENGTH)
+                && isUserPropertyWithinLimit(mAccountName, UserManager.MAX_ACCOUNT_STRING_LENGTH)
+                && isUserPropertyWithinLimit(mAccountType, UserManager.MAX_ACCOUNT_STRING_LENGTH))
+                || (mAccountOptions != null && !mAccountOptions.isBundleContentsWithinLengthLimit(
+                UserManager.MAX_ACCOUNT_OPTIONS_LENGTH))) {
+            setResult(UserManager.USER_CREATION_FAILED_NOT_PERMITTED);
+            Log.i(TAG, "User properties must not exceed their character limits");
+            return null;
         } else if (cantCreateAnyMoreUsers) {
             setResult(UserManager.USER_CREATION_FAILED_NO_MORE_USERS);
             return null;
@@ -144,4 +152,8 @@
         }
         finish();
     }
+
+    private boolean isUserPropertyWithinLimit(String property, int limit) {
+        return property == null || property.length() <= limit;
+    }
 }
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index 4fe9aea..85bdbb9 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -80,5 +80,11 @@
             in Bundle extras, in IntentSender resultIntent);
     boolean isRequestPinAppWidgetSupported();
     oneway void noteAppWidgetTapped(in String callingPackage, in int appWidgetId);
+    void setWidgetPreview(in ComponentName providerComponent, in int widgetCategories,
+            in RemoteViews preview);
+    @nullable RemoteViews getWidgetPreview(in String callingPackage,
+            in ComponentName providerComponent, in int profileId, in int widgetCategory);
+    void removeWidgetPreview(in ComponentName providerComponent, in int widgetCategories);
+
 }
 
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index eeea17b..90ca95a 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -71,7 +71,7 @@
                 "persist.debug.sysui.notification.notif_cooldown_t1", 60000);
         /** Value used by polite notif. feature */
         public static final Flag NOTIF_COOLDOWN_T2 = devFlag(
-                "persist.debug.sysui.notification.notif_cooldown_t2", 5000);
+                "persist.debug.sysui.notification.notif_cooldown_t2", 10000);
         /** Value used by polite notif. feature */
         public static final Flag NOTIF_VOLUME1 = devFlag(
                 "persist.debug.sysui.notification.notif_volume1", 30);
@@ -81,6 +81,10 @@
         public static final Flag NOTIF_COOLDOWN_COUNTER_RESET = devFlag(
                 "persist.debug.sysui.notification.notif_cooldown_counter_reset", 10);
 
+        /** Value used by polite notif. feature */
+        public static final Flag NOTIF_AVALANCHE_TIMEOUT = devFlag(
+                "persist.debug.sysui.notification.notif_avalanche_timeout", 120_000);
+
         /** b/303716154: For debugging only: use short bitmap duration. */
         public static final Flag DEBUG_SHORT_BITMAP_DURATION = devFlag(
                 "persist.sysui.notification.debug_short_bitmap_duration");
diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index 37aaa72..0068490 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -47,6 +47,7 @@
  * (new) system for storing the brightness. It has methods to convert between the two and also
  * observes for when one of the settings is changed and syncs this with the other.
  */
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
 public class BrightnessSynchronizer {
     private static final String TAG = "BrightnessSynchronizer";
 
@@ -282,6 +283,7 @@
      * @param b second float to compare
      * @return whether the two values are within a small enough tolerance value
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean floatEquals(float a, float b) {
         if (a == b) {
             return true;
diff --git a/core/java/com/android/internal/display/RefreshRateSettingsUtils.java b/core/java/com/android/internal/display/RefreshRateSettingsUtils.java
index e55c641..fab8984 100644
--- a/core/java/com/android/internal/display/RefreshRateSettingsUtils.java
+++ b/core/java/com/android/internal/display/RefreshRateSettingsUtils.java
@@ -33,13 +33,14 @@
     /**
      * Find the highest refresh rate among all the modes of the default display.
      *
+     * This method will acquire DisplayManager.mLock, so calling it while holding other locks
+     * should be done with care.
      * @param context The context
      * @return The highest refresh rate
      */
     public static float findHighestRefreshRateForDefaultDisplay(Context context) {
         final DisplayManager dm = context.getSystemService(DisplayManager.class);
         final Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
-
         if (display == null) {
             Log.w(TAG, "No valid default display device");
             return DEFAULT_REFRESH_RATE;
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 96740c5..7b3565b 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -121,10 +121,11 @@
     public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = 85;
     public static final int CUJ_PREDICTIVE_BACK_HOME = 86;
     public static final int CUJ_LAUNCHER_SEARCH_QSB_OPEN = 87;
+    public static final int CUJ_BACK_PANEL_ARROW = 88;
 
     // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
     @VisibleForTesting
-    static final int LAST_CUJ = CUJ_LAUNCHER_SEARCH_QSB_OPEN;
+    static final int LAST_CUJ = CUJ_BACK_PANEL_ARROW;
 
     /** @hide */
     @IntDef({
@@ -207,6 +208,7 @@
             CUJ_PREDICTIVE_BACK_CROSS_TASK,
             CUJ_PREDICTIVE_BACK_HOME,
             CUJ_LAUNCHER_SEARCH_QSB_OPEN,
+            CUJ_BACK_PANEL_ARROW,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
@@ -298,8 +300,8 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_TASK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_SEARCH_QSB_OPEN] =
-            FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_SEARCH_QSB_OPEN;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_SEARCH_QSB_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_SEARCH_QSB_OPEN;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BACK_PANEL_ARROW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BACK_PANEL_ARROW;
     }
 
     private Cuj() {
@@ -474,6 +476,8 @@
                 return "PREDICTIVE_BACK_HOME";
             case CUJ_LAUNCHER_SEARCH_QSB_OPEN:
                 return "LAUNCHER_SEARCH_QSB_OPEN";
+            case CUJ_BACK_PANEL_ARROW:
+                return "BACK_PANEL_ARROW";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
index f3f16a0..d9cac12 100644
--- a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
+++ b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
@@ -28,6 +28,7 @@
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Trace;
+import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.view.WindowCallbacks;
@@ -52,6 +53,7 @@
  * @hide
  */
 class InteractionMonitorDebugOverlay implements WindowCallbacks {
+    private static final String TAG = "InteractionMonitorDebug";
     private static final int REASON_STILL_RUNNING = -1000;
     private final Object mLock;
     // Sparse array where the key in the CUJ and the value is the session status, or null if
@@ -77,7 +79,7 @@
         mDebugPaint.setAntiAlias(false);
         mDebugFontMetrics = new Paint.FontMetrics();
         final Context context = ActivityThread.currentApplication();
-        mPackageName = context.getPackageName();
+        mPackageName = context == null ? "null" : context.getPackageName();
     }
 
     @UiThread
@@ -153,8 +155,14 @@
                           SparseArray<InteractionJankMonitor.RunningTracker> runningTrackers) {
         synchronized (mLock) {
             mRunningCujs.put(removedCuj, reason);
+            boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
+            if (isLoggable) {
+                String cujName = Cuj.getNameOfCuj(removedCuj);
+                Log.d(TAG, cujName + (reason == REASON_END_NORMAL ? " ended" : " cancelled"));
+            }
             // If REASON_STILL_RUNNING is not in mRunningCujs, then all CUJs have ended
             if (mRunningCujs.indexOfValue(REASON_STILL_RUNNING) < 0) {
+                if (isLoggable) Log.d(TAG, "All CUJs ended");
                 mRunningCujs.clear();
                 dispose();
             } else {
@@ -186,6 +194,10 @@
 
     @UiThread
     void onTrackerAdded(@Cuj.CujType int addedCuj, InteractionJankMonitor.RunningTracker tracker) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            String cujName = Cuj.getNameOfCuj(addedCuj);
+            Log.d(TAG, cujName + " started");
+        }
         synchronized (mLock) {
             // Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ
             // is still running
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index e58f4f0..88aa89a 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -34,6 +34,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class MetricsLogger {
     // define metric categories in frameworks/base/proto/src/metrics_constants.proto.
     // mirror changes in native version at system/core/libmetricslogger/metrics_logger.cpp
diff --git a/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java b/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java
index 6786427..df8bf31 100644
--- a/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java
+++ b/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java
@@ -12,6 +12,7 @@
  *
  * @hide.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class FakeMetricsLogger extends MetricsLogger {
     private Queue<LogMaker> logs = new LinkedList<>();
 
diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
index e303890..6787ddc 100644
--- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
+++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
@@ -27,6 +27,7 @@
  *
  * @hide.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class UiEventLoggerFake implements UiEventLogger {
     /**
      * Immutable data class used to record fake log events.
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 1330e16..aa60cc9 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -490,7 +490,7 @@
      * Returns true if this instance only supports reading history.
      */
     public boolean isReadOnly() {
-        return mActiveFile == null || mHistoryDir == null;
+        return !mMutable || mActiveFile == null || mHistoryDir == null;
     }
 
     /**
@@ -508,6 +508,13 @@
      * create next history file.
      */
     public void startNextFile(long elapsedRealtimeMs) {
+        synchronized (this) {
+            startNextFileLocked(elapsedRealtimeMs);
+        }
+    }
+
+    @GuardedBy("this")
+    private void startNextFileLocked(long elapsedRealtimeMs) {
         if (mMaxHistoryFiles == 0) {
             Slog.wtf(TAG, "mMaxHistoryFiles should not be zero when writing history");
             return;
@@ -548,10 +555,7 @@
         }
 
         mWrittenPowerStatsDescriptors.clear();
-
-        synchronized (this) {
-            cleanupLocked();
-        }
+        cleanupLocked();
     }
 
     @GuardedBy("this")
@@ -599,27 +603,31 @@
      * number 0 again.
      */
     public void reset() {
-        if (DEBUG) Slog.i(TAG, "********** CLEARING HISTORY!");
-        for (BatteryHistoryFile file : mHistoryFiles) {
-            file.atomicFile.delete();
+        synchronized (this) {
+            if (DEBUG) Slog.i(TAG, "********** CLEARING HISTORY!");
+            for (BatteryHistoryFile file : mHistoryFiles) {
+                file.atomicFile.delete();
+            }
+            mHistoryFiles.clear();
+
+            BatteryHistoryFile name = makeBatteryHistoryFile();
+            mHistoryFiles.add(name);
+            setActiveFile(name);
+
+            initHistoryBuffer();
         }
-        mHistoryFiles.clear();
-
-        BatteryHistoryFile name = makeBatteryHistoryFile();
-        mHistoryFiles.add(name);
-        setActiveFile(name);
-
-        initHistoryBuffer();
     }
 
     /**
      * Returns the monotonic clock time when the available battery history collection started.
      */
     public long getStartTime() {
-        if (!mHistoryFiles.isEmpty()) {
-            return mHistoryFiles.get(0).monotonicTimeMs;
-        } else {
-            return mHistoryBufferStartTime;
+        synchronized (this) {
+            if (!mHistoryFiles.isEmpty()) {
+                return mHistoryFiles.get(0).monotonicTimeMs;
+            } else {
+                return mHistoryBufferStartTime;
+            }
         }
     }
 
@@ -633,11 +641,14 @@
      */
     @NonNull
     public BatteryStatsHistoryIterator iterate(long startTimeMs, long endTimeMs) {
+        if (mMutable) {
+            return copy().iterate(startTimeMs, endTimeMs);
+        }
+
         mCurrentFileIndex = 0;
         mCurrentParcel = null;
         mCurrentParcelEnd = 0;
         mParcelIndex = 0;
-        mMutable = false;
         if (mWritableHistory != null) {
             synchronized (mWritableHistory) {
                 mWritableHistory.setCleanupEnabledLocked(false);
@@ -650,14 +661,11 @@
      * Finish iterating history files and history buffer.
      */
     void iteratorFinished() {
-        // setDataPosition so mHistoryBuffer Parcel can be written.
         mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
         if (mWritableHistory != null) {
             synchronized (mWritableHistory) {
                 mWritableHistory.setCleanupEnabledLocked(true);
             }
-        } else {
-            mMutable = true;
         }
     }
 
@@ -671,6 +679,8 @@
      */
     @Nullable
     public Parcel getNextParcel(long startTimeMs, long endTimeMs) {
+        checkImmutable();
+
         // First iterate through all records in current parcel.
         if (mCurrentParcel != null) {
             if (mCurrentParcel.dataPosition() < mCurrentParcelEnd) {
@@ -754,6 +764,12 @@
         return mCurrentParcel;
     }
 
+    private void checkImmutable() {
+        if (mMutable) {
+            throw new IllegalStateException("Iterating over a mutable battery history");
+        }
+    }
+
     /**
      * Read history file into a parcel.
      *
@@ -863,8 +879,10 @@
      * @param out the output parcel
      */
     public void writeToParcel(Parcel out) {
-        writeHistoryBuffer(out);
-        writeToParcel(out, false /* useBlobs */);
+        synchronized (this) {
+            writeHistoryBuffer(out);
+            writeToParcel(out, false /* useBlobs */);
+        }
     }
 
     /**
@@ -874,8 +892,10 @@
      * @param out the output parcel
      */
     public void writeToBatteryUsageStatsParcel(Parcel out) {
-        out.writeBlob(mHistoryBuffer.marshall());
-        writeToParcel(out, true /* useBlobs */);
+        synchronized (this) {
+            out.writeBlob(mHistoryBuffer.marshall());
+            writeToParcel(out, true /* useBlobs */);
+        }
     }
 
     private void writeToParcel(Parcel out, boolean useBlobs) {
@@ -977,11 +997,16 @@
     /**
      * @return true if there is more than 100MB free disk space left.
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     private boolean hasFreeDiskSpace() {
         final StatFs stats = new StatFs(mHistoryDir.getAbsolutePath());
         return stats.getAvailableBytes() > MIN_FREE_SPACE;
     }
 
+    private boolean hasFreeDiskSpace$ravenwood() {
+        return true;
+    }
+
     @VisibleForTesting
     public List<String> getFilesNames() {
         List<String> names = new ArrayList<>();
@@ -1017,14 +1042,18 @@
      * Enables/disables recording of history.  When disabled, all "record*" calls are a no-op.
      */
     public void setHistoryRecordingEnabled(boolean enabled) {
-        mRecordingHistory = enabled;
+        synchronized (this) {
+            mRecordingHistory = enabled;
+        }
     }
 
     /**
      * Returns true if history recording is enabled.
      */
     public boolean isRecordingHistory() {
-        return mRecordingHistory;
+        synchronized (this) {
+            return mRecordingHistory;
+        }
     }
 
     /**
@@ -1032,8 +1061,10 @@
      */
     @VisibleForTesting
     public void forceRecordAllHistory() {
-        mHaveBatteryLevel = true;
-        mRecordingHistory = true;
+        synchronized (this) {
+            mHaveBatteryLevel = true;
+            mRecordingHistory = true;
+        }
     }
 
     /**
@@ -1041,37 +1072,43 @@
      */
     public void startRecordingHistory(final long elapsedRealtimeMs, final long uptimeMs,
             boolean reset) {
-        mRecordingHistory = true;
-        mHistoryCur.currentTime = mClock.currentTimeMillis();
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur,
-                reset ? HistoryItem.CMD_RESET : HistoryItem.CMD_CURRENT_TIME);
-        mHistoryCur.currentTime = 0;
+        synchronized (this) {
+            mRecordingHistory = true;
+            mHistoryCur.currentTime = mClock.currentTimeMillis();
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur,
+                    reset ? HistoryItem.CMD_RESET : HistoryItem.CMD_CURRENT_TIME);
+            mHistoryCur.currentTime = 0;
+        }
     }
 
     /**
      * Prepares to continue recording after restoring previous history from persistent storage.
      */
     public void continueRecordingHistory() {
-        if (mHistoryBuffer.dataPosition() <= 0 && mHistoryFiles.size() <= 1) {
-            return;
-        }
+        synchronized (this) {
+            if (mHistoryBuffer.dataPosition() <= 0 && mHistoryFiles.size() <= 1) {
+                return;
+            }
 
-        mRecordingHistory = true;
-        final long elapsedRealtimeMs = mClock.elapsedRealtime();
-        final long uptimeMs = mClock.uptimeMillis();
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur, HistoryItem.CMD_START);
-        startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
+            mRecordingHistory = true;
+            final long elapsedRealtimeMs = mClock.elapsedRealtime();
+            final long uptimeMs = mClock.uptimeMillis();
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur, HistoryItem.CMD_START);
+            startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
+        }
     }
 
     /**
      * Notes the current battery state to be reflected in the next written history item.
      */
     public void setBatteryState(boolean charging, int status, int level, int chargeUah) {
-        mHaveBatteryLevel = true;
-        setChargingState(charging);
-        mHistoryCur.batteryStatus = (byte) status;
-        mHistoryCur.batteryLevel = (byte) level;
-        mHistoryCur.batteryChargeUah = chargeUah;
+        synchronized (this) {
+            mHaveBatteryLevel = true;
+            setChargingState(charging);
+            mHistoryCur.batteryStatus = (byte) status;
+            mHistoryCur.batteryLevel = (byte) level;
+            mHistoryCur.batteryChargeUah = chargeUah;
+        }
     }
 
     /**
@@ -1079,24 +1116,28 @@
      */
     public void setBatteryState(int status, int level, int health, int plugType, int temperature,
             int voltageMv, int chargeUah) {
-        mHaveBatteryLevel = true;
-        mHistoryCur.batteryStatus = (byte) status;
-        mHistoryCur.batteryLevel = (byte) level;
-        mHistoryCur.batteryHealth = (byte) health;
-        mHistoryCur.batteryPlugType = (byte) plugType;
-        mHistoryCur.batteryTemperature = (short) temperature;
-        mHistoryCur.batteryVoltage = (char) voltageMv;
-        mHistoryCur.batteryChargeUah = chargeUah;
+        synchronized (this) {
+            mHaveBatteryLevel = true;
+            mHistoryCur.batteryStatus = (byte) status;
+            mHistoryCur.batteryLevel = (byte) level;
+            mHistoryCur.batteryHealth = (byte) health;
+            mHistoryCur.batteryPlugType = (byte) plugType;
+            mHistoryCur.batteryTemperature = (short) temperature;
+            mHistoryCur.batteryVoltage = (char) voltageMv;
+            mHistoryCur.batteryChargeUah = chargeUah;
+        }
     }
 
     /**
      * Notes the current power plugged-in state to be reflected in the next written history item.
      */
     public void setPluggedInState(boolean pluggedIn) {
-        if (pluggedIn) {
-            mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
-        } else {
-            mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+        synchronized (this) {
+            if (pluggedIn) {
+                mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+            } else {
+                mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+            }
         }
     }
 
@@ -1104,10 +1145,12 @@
      * Notes the current battery charging state to be reflected in the next written history item.
      */
     public void setChargingState(boolean charging) {
-        if (charging) {
-            mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG;
-        } else {
-            mHistoryCur.states2 &= ~HistoryItem.STATE2_CHARGING_FLAG;
+        synchronized (this) {
+            if (charging) {
+                mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG;
+            } else {
+                mHistoryCur.states2 &= ~HistoryItem.STATE2_CHARGING_FLAG;
+            }
         }
     }
 
@@ -1116,38 +1159,44 @@
      */
     public void recordEvent(long elapsedRealtimeMs, long uptimeMs, int code, String name,
             int uid) {
-        mHistoryCur.eventCode = code;
-        mHistoryCur.eventTag = mHistoryCur.localEventTag;
-        mHistoryCur.eventTag.string = name;
-        mHistoryCur.eventTag.uid = uid;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        synchronized (this) {
+            mHistoryCur.eventCode = code;
+            mHistoryCur.eventTag = mHistoryCur.localEventTag;
+            mHistoryCur.eventTag.string = name;
+            mHistoryCur.eventTag.uid = uid;
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        }
     }
 
     /**
      * Records a time change event.
      */
     public void recordCurrentTimeChange(long elapsedRealtimeMs, long uptimeMs, long currentTimeMs) {
-        if (!mRecordingHistory) {
-            return;
-        }
+        synchronized (this) {
+            if (!mRecordingHistory) {
+                return;
+            }
 
-        mHistoryCur.currentTime = currentTimeMs;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur,
-                HistoryItem.CMD_CURRENT_TIME);
-        mHistoryCur.currentTime = 0;
+            mHistoryCur.currentTime = currentTimeMs;
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur,
+                    HistoryItem.CMD_CURRENT_TIME);
+            mHistoryCur.currentTime = 0;
+        }
     }
 
     /**
      * Records a system shutdown event.
      */
     public void recordShutdownEvent(long elapsedRealtimeMs, long uptimeMs, long currentTimeMs) {
-        if (!mRecordingHistory) {
-            return;
-        }
+        synchronized (this) {
+            if (!mRecordingHistory) {
+                return;
+            }
 
-        mHistoryCur.currentTime = currentTimeMs;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur, HistoryItem.CMD_SHUTDOWN);
-        mHistoryCur.currentTime = 0;
+            mHistoryCur.currentTime = currentTimeMs;
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur, HistoryItem.CMD_SHUTDOWN);
+            mHistoryCur.currentTime = 0;
+        }
     }
 
     /**
@@ -1155,13 +1204,15 @@
      */
     public void recordBatteryState(long elapsedRealtimeMs, long uptimeMs, int batteryLevel,
             boolean isPlugged) {
-        mHistoryCur.batteryLevel = (byte) batteryLevel;
-        setPluggedInState(isPlugged);
-        if (DEBUG) {
-            Slog.v(TAG, "Battery unplugged to: "
-                    + Integer.toHexString(mHistoryCur.states));
+        synchronized (this) {
+            mHistoryCur.batteryLevel = (byte) batteryLevel;
+            setPluggedInState(isPlugged);
+            if (DEBUG) {
+                Slog.v(TAG, "Battery unplugged to: "
+                        + Integer.toHexString(mHistoryCur.states));
+            }
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
         }
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
     }
 
     /**
@@ -1169,9 +1220,11 @@
      */
     public void recordPowerStats(long elapsedRealtimeMs, long uptimeMs,
             PowerStats powerStats) {
-        mHistoryCur.powerStats = powerStats;
-        mHistoryCur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        synchronized (this) {
+            mHistoryCur.powerStats = powerStats;
+            mHistoryCur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        }
     }
 
     /**
@@ -1179,11 +1232,13 @@
      */
     public void recordProcessStateChange(long elapsedRealtimeMs, long uptimeMs,
             int uid, @BatteryConsumer.ProcessState int processState) {
-        mHistoryCur.processStateChange = mHistoryCur.localProcessStateChange;
-        mHistoryCur.processStateChange.uid = uid;
-        mHistoryCur.processStateChange.processState = processState;
-        mHistoryCur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        synchronized (this) {
+            mHistoryCur.processStateChange = mHistoryCur.localProcessStateChange;
+            mHistoryCur.processStateChange.uid = uid;
+            mHistoryCur.processStateChange.processState = processState;
+            mHistoryCur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        }
     }
 
     /**
@@ -1192,8 +1247,10 @@
      */
     public void recordWifiConsumedCharge(long elapsedRealtimeMs, long uptimeMs,
             double monitoredRailChargeMah) {
-        mHistoryCur.wifiRailChargeMah += monitoredRailChargeMah;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        synchronized (this) {
+            mHistoryCur.wifiRailChargeMah += monitoredRailChargeMah;
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        }
     }
 
     /**
@@ -1201,10 +1258,12 @@
      */
     public void recordWakelockStartEvent(long elapsedRealtimeMs, long uptimeMs, String historyName,
             int uid) {
-        mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
-        mHistoryCur.wakelockTag.string = historyName;
-        mHistoryCur.wakelockTag.uid = uid;
-        recordStateStartEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.STATE_WAKE_LOCK_FLAG);
+        synchronized (this) {
+            mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+            mHistoryCur.wakelockTag.string = historyName;
+            mHistoryCur.wakelockTag.uid = uid;
+            recordStateStartEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.STATE_WAKE_LOCK_FLAG);
+        }
     }
 
     /**
@@ -1212,18 +1271,20 @@
      */
     public boolean maybeUpdateWakelockTag(long elapsedRealtimeMs, long uptimeMs, String historyName,
             int uid) {
-        if (mHistoryLastWritten.cmd != HistoryItem.CMD_UPDATE) {
-            return false;
+        synchronized (this) {
+            if (mHistoryLastWritten.cmd != HistoryItem.CMD_UPDATE) {
+                return false;
+            }
+            if (mHistoryLastWritten.wakelockTag != null) {
+                // We'll try to update the last tag.
+                mHistoryLastWritten.wakelockTag = null;
+                mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+                mHistoryCur.wakelockTag.string = historyName;
+                mHistoryCur.wakelockTag.uid = uid;
+                writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+            }
+            return true;
         }
-        if (mHistoryLastWritten.wakelockTag != null) {
-            // We'll try to update the last tag.
-            mHistoryLastWritten.wakelockTag = null;
-            mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
-            mHistoryCur.wakelockTag.string = historyName;
-            mHistoryCur.wakelockTag.uid = uid;
-            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
-        }
-        return true;
     }
 
     /**
@@ -1231,26 +1292,32 @@
      */
     public void recordWakelockStopEvent(long elapsedRealtimeMs, long uptimeMs, String historyName,
             int uid) {
-        mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
-        mHistoryCur.wakelockTag.string = historyName != null ? historyName : "";
-        mHistoryCur.wakelockTag.uid = uid;
-        recordStateStopEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.STATE_WAKE_LOCK_FLAG);
+        synchronized (this) {
+            mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+            mHistoryCur.wakelockTag.string = historyName != null ? historyName : "";
+            mHistoryCur.wakelockTag.uid = uid;
+            recordStateStopEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.STATE_WAKE_LOCK_FLAG);
+        }
     }
 
     /**
      * Records an event when some state flag changes to true.
      */
     public void recordStateStartEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
-        mHistoryCur.states |= stateFlags;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        synchronized (this) {
+            mHistoryCur.states |= stateFlags;
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        }
     }
 
     /**
      * Records an event when some state flag changes to false.
      */
     public void recordStateStopEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
-        mHistoryCur.states &= ~stateFlags;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        synchronized (this) {
+            mHistoryCur.states &= ~stateFlags;
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        }
     }
 
     /**
@@ -1258,34 +1325,42 @@
      */
     public void recordStateChangeEvent(long elapsedRealtimeMs, long uptimeMs, int stateStartFlags,
             int stateStopFlags) {
-        mHistoryCur.states = (mHistoryCur.states | stateStartFlags) & ~stateStopFlags;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        synchronized (this) {
+            mHistoryCur.states = (mHistoryCur.states | stateStartFlags) & ~stateStopFlags;
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        }
     }
 
     /**
      * Records an event when some state2 flag changes to true.
      */
     public void recordState2StartEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
-        mHistoryCur.states2 |= stateFlags;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        synchronized (this) {
+            mHistoryCur.states2 |= stateFlags;
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        }
     }
 
     /**
      * Records an event when some state2 flag changes to false.
      */
     public void recordState2StopEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
-        mHistoryCur.states2 &= ~stateFlags;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        synchronized (this) {
+            mHistoryCur.states2 &= ~stateFlags;
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        }
     }
 
     /**
      * Records an wakeup event.
      */
     public void recordWakeupEvent(long elapsedRealtimeMs, long uptimeMs, String reason) {
-        mHistoryCur.wakeReasonTag = mHistoryCur.localWakeReasonTag;
-        mHistoryCur.wakeReasonTag.string = reason;
-        mHistoryCur.wakeReasonTag.uid = 0;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        synchronized (this) {
+            mHistoryCur.wakeReasonTag = mHistoryCur.localWakeReasonTag;
+            mHistoryCur.wakeReasonTag.string = reason;
+            mHistoryCur.wakeReasonTag.uid = 0;
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        }
     }
 
     /**
@@ -1293,10 +1368,12 @@
      */
     public void recordScreenBrightnessEvent(long elapsedRealtimeMs, long uptimeMs,
             int brightnessBin) {
-        mHistoryCur.states = setBitField(mHistoryCur.states, brightnessBin,
-                HistoryItem.STATE_BRIGHTNESS_SHIFT,
-                HistoryItem.STATE_BRIGHTNESS_MASK);
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        synchronized (this) {
+            mHistoryCur.states = setBitField(mHistoryCur.states, brightnessBin,
+                    HistoryItem.STATE_BRIGHTNESS_SHIFT,
+                    HistoryItem.STATE_BRIGHTNESS_MASK);
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        }
     }
 
     /**
@@ -1304,20 +1381,24 @@
      */
     public void recordGpsSignalQualityEvent(long elapsedRealtimeMs, long uptimeMs,
             int signalLevel) {
-        mHistoryCur.states2 = setBitField(mHistoryCur.states2, signalLevel,
-                HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT,
-                HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK);
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        synchronized (this) {
+            mHistoryCur.states2 = setBitField(mHistoryCur.states2, signalLevel,
+                    HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT,
+                    HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK);
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        }
     }
 
     /**
      * Records a device idle mode change event.
      */
     public void recordDeviceIdleEvent(long elapsedRealtimeMs, long uptimeMs, int mode) {
-        mHistoryCur.states2 = setBitField(mHistoryCur.states2, mode,
-                HistoryItem.STATE2_DEVICE_IDLE_SHIFT,
-                HistoryItem.STATE2_DEVICE_IDLE_MASK);
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        synchronized (this) {
+            mHistoryCur.states2 = setBitField(mHistoryCur.states2, mode,
+                    HistoryItem.STATE2_DEVICE_IDLE_SHIFT,
+                    HistoryItem.STATE2_DEVICE_IDLE_MASK);
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        }
     }
 
     /**
@@ -1325,20 +1406,22 @@
      */
     public void recordPhoneStateChangeEvent(long elapsedRealtimeMs, long uptimeMs, int addStateFlag,
             int removeStateFlag, int state, int signalStrength) {
-        mHistoryCur.states = (mHistoryCur.states | addStateFlag) & ~removeStateFlag;
-        if (state != -1) {
-            mHistoryCur.states =
-                    setBitField(mHistoryCur.states, state,
-                            HistoryItem.STATE_PHONE_STATE_SHIFT,
-                            HistoryItem.STATE_PHONE_STATE_MASK);
+        synchronized (this) {
+            mHistoryCur.states = (mHistoryCur.states | addStateFlag) & ~removeStateFlag;
+            if (state != -1) {
+                mHistoryCur.states =
+                        setBitField(mHistoryCur.states, state,
+                                HistoryItem.STATE_PHONE_STATE_SHIFT,
+                                HistoryItem.STATE_PHONE_STATE_MASK);
+            }
+            if (signalStrength != -1) {
+                mHistoryCur.states =
+                        setBitField(mHistoryCur.states, signalStrength,
+                                HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT,
+                                HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK);
+            }
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
         }
-        if (signalStrength != -1) {
-            mHistoryCur.states =
-                    setBitField(mHistoryCur.states, signalStrength,
-                            HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT,
-                            HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK);
-        }
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
     }
 
     /**
@@ -1346,10 +1429,12 @@
      */
     public void recordDataConnectionTypeChangeEvent(long elapsedRealtimeMs, long uptimeMs,
             int dataConnectionType) {
-        mHistoryCur.states = setBitField(mHistoryCur.states, dataConnectionType,
-                HistoryItem.STATE_DATA_CONNECTION_SHIFT,
-                HistoryItem.STATE_DATA_CONNECTION_MASK);
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        synchronized (this) {
+            mHistoryCur.states = setBitField(mHistoryCur.states, dataConnectionType,
+                    HistoryItem.STATE_DATA_CONNECTION_SHIFT,
+                    HistoryItem.STATE_DATA_CONNECTION_MASK);
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        }
     }
 
     /**
@@ -1357,10 +1442,12 @@
      */
     public void recordNrStateChangeEvent(long elapsedRealtimeMs, long uptimeMs,
             int nrState) {
-        mHistoryCur.states2 = setBitField(mHistoryCur.states2, nrState,
-                HistoryItem.STATE2_NR_STATE_SHIFT,
-                HistoryItem.STATE2_NR_STATE_MASK);
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        synchronized (this) {
+            mHistoryCur.states2 = setBitField(mHistoryCur.states2, nrState,
+                    HistoryItem.STATE2_NR_STATE_SHIFT,
+                    HistoryItem.STATE2_NR_STATE_MASK);
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        }
     }
 
     /**
@@ -1368,11 +1455,13 @@
      */
     public void recordWifiSupplicantStateChangeEvent(long elapsedRealtimeMs, long uptimeMs,
             int supplState) {
-        mHistoryCur.states2 =
-                setBitField(mHistoryCur.states2, supplState,
-                        HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT,
-                        HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK);
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        synchronized (this) {
+            mHistoryCur.states2 =
+                    setBitField(mHistoryCur.states2, supplState,
+                            HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT,
+                            HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK);
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        }
     }
 
     /**
@@ -1380,11 +1469,13 @@
      */
     public void recordWifiSignalStrengthChangeEvent(long elapsedRealtimeMs, long uptimeMs,
             int strengthBin) {
-        mHistoryCur.states2 =
-                setBitField(mHistoryCur.states2, strengthBin,
-                        HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT,
-                        HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK);
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        synchronized (this) {
+            mHistoryCur.states2 =
+                    setBitField(mHistoryCur.states2, strengthBin,
+                            HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT,
+                            HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK);
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+        }
     }
 
     /**
@@ -1441,25 +1532,30 @@
      * Writes the current history item to history.
      */
     public void writeHistoryItem(long elapsedRealtimeMs, long uptimeMs) {
-        if (mTrackRunningHistoryElapsedRealtimeMs != 0) {
-            final long diffElapsedMs = elapsedRealtimeMs - mTrackRunningHistoryElapsedRealtimeMs;
-            final long diffUptimeMs = uptimeMs - mTrackRunningHistoryUptimeMs;
-            if (diffUptimeMs < (diffElapsedMs - 20)) {
-                final long wakeElapsedTimeMs = elapsedRealtimeMs - (diffElapsedMs - diffUptimeMs);
-                mHistoryAddTmp.setTo(mHistoryLastWritten);
-                mHistoryAddTmp.wakelockTag = null;
-                mHistoryAddTmp.wakeReasonTag = null;
-                mHistoryAddTmp.eventCode = HistoryItem.EVENT_NONE;
-                mHistoryAddTmp.states &= ~HistoryItem.STATE_CPU_RUNNING_FLAG;
-                writeHistoryItem(wakeElapsedTimeMs, uptimeMs, mHistoryAddTmp);
+        synchronized (this) {
+            if (mTrackRunningHistoryElapsedRealtimeMs != 0) {
+                final long diffElapsedMs =
+                        elapsedRealtimeMs - mTrackRunningHistoryElapsedRealtimeMs;
+                final long diffUptimeMs = uptimeMs - mTrackRunningHistoryUptimeMs;
+                if (diffUptimeMs < (diffElapsedMs - 20)) {
+                    final long wakeElapsedTimeMs =
+                            elapsedRealtimeMs - (diffElapsedMs - diffUptimeMs);
+                    mHistoryAddTmp.setTo(mHistoryLastWritten);
+                    mHistoryAddTmp.wakelockTag = null;
+                    mHistoryAddTmp.wakeReasonTag = null;
+                    mHistoryAddTmp.eventCode = HistoryItem.EVENT_NONE;
+                    mHistoryAddTmp.states &= ~HistoryItem.STATE_CPU_RUNNING_FLAG;
+                    writeHistoryItem(wakeElapsedTimeMs, uptimeMs, mHistoryAddTmp);
+                }
             }
+            mHistoryCur.states |= HistoryItem.STATE_CPU_RUNNING_FLAG;
+            mTrackRunningHistoryElapsedRealtimeMs = elapsedRealtimeMs;
+            mTrackRunningHistoryUptimeMs = uptimeMs;
+            writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur);
         }
-        mHistoryCur.states |= HistoryItem.STATE_CPU_RUNNING_FLAG;
-        mTrackRunningHistoryElapsedRealtimeMs = elapsedRealtimeMs;
-        mTrackRunningHistoryUptimeMs = uptimeMs;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur);
     }
 
+    @GuardedBy("this")
     private void writeHistoryItem(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
         if (mTracer != null && mTracer.tracingEnabled()) {
             recordTraceEvents(cur.eventCode, cur.eventTag);
@@ -1586,6 +1682,7 @@
         writeHistoryItem(elapsedRealtimeMs, uptimeMs, cur, HistoryItem.CMD_UPDATE);
     }
 
+    @GuardedBy("this")
     private void writeHistoryItem(long elapsedRealtimeMs,
             @SuppressWarnings("UnusedVariable") long uptimeMs, HistoryItem cur, byte cmd) {
         if (!mMutable) {
@@ -1696,7 +1793,8 @@
     /**
      * Writes the delta between the previous and current history items into history buffer.
      */
-    public void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) {
+    @GuardedBy("this")
+    private void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) {
         if (last == null || cur.cmd != HistoryItem.CMD_UPDATE) {
             dest.writeInt(BatteryStatsHistory.DELTA_TIME_ABS);
             cur.writeToParcel(dest, 0);
@@ -1916,6 +2014,7 @@
      * while writing the current history buffer, the method returns
      * <code>(index | TAG_FIRST_OCCURRENCE_FLAG)</code>
      */
+    @GuardedBy("this")
     private int writeHistoryTag(HistoryTag tag) {
         if (tag.string == null) {
             Slog.wtfStack(TAG, "writeHistoryTag called with null name");
@@ -1959,33 +2058,37 @@
      * Don't allow any more batching in to the current history event.
      */
     public void commitCurrentHistoryBatchLocked() {
-        mHistoryLastWritten.cmd = HistoryItem.CMD_NULL;
+        synchronized (this) {
+            mHistoryLastWritten.cmd = HistoryItem.CMD_NULL;
+        }
     }
 
     /**
      * Saves the accumulated history buffer in the active file, see {@link #getActiveFile()} .
      */
     public void writeHistory() {
-        if (isReadOnly()) {
-            Slog.w(TAG, "writeHistory: this instance instance is read-only");
-            return;
-        }
-
-        // Save the monotonic time first, so that even if the history write below fails,
-        // we still wouldn't end up with overlapping history timelines.
-        mMonotonicClock.write();
-
-        Parcel p = Parcel.obtain();
-        try {
-            final long start = SystemClock.uptimeMillis();
-            writeHistoryBuffer(p);
-            if (DEBUG) {
-                Slog.d(TAG, "writeHistoryBuffer duration ms:"
-                        + (SystemClock.uptimeMillis() - start) + " bytes:" + p.dataSize());
+        synchronized (this) {
+            if (isReadOnly()) {
+                Slog.w(TAG, "writeHistory: this instance instance is read-only");
+                return;
             }
-            writeParcelToFileLocked(p, mActiveFile);
-        } finally {
-            p.recycle();
+
+            // Save the monotonic time first, so that even if the history write below fails,
+            // we still wouldn't end up with overlapping history timelines.
+            mMonotonicClock.write();
+
+            Parcel p = Parcel.obtain();
+            try {
+                final long start = SystemClock.uptimeMillis();
+                writeHistoryBuffer(p);
+                if (DEBUG) {
+                    Slog.d(TAG, "writeHistoryBuffer duration ms:"
+                            + (SystemClock.uptimeMillis() - start) + " bytes:" + p.dataSize());
+                }
+                writeParcelToFileLocked(p, mActiveFile);
+            } finally {
+                p.recycle();
+            }
         }
     }
 
@@ -1993,35 +2096,38 @@
      * Reads history buffer from a persisted Parcel.
      */
     public void readHistoryBuffer(Parcel in) throws ParcelFormatException {
-        final int version = in.readInt();
-        if (version != BatteryStatsHistory.VERSION) {
-            Slog.w("BatteryStats", "readHistoryBuffer: version got " + version
-                    + ", expected " + BatteryStatsHistory.VERSION + "; erasing old stats");
-            return;
-        }
-
-        mHistoryBufferStartTime = in.readLong();
-        mHistoryBuffer.setDataSize(0);
-        mHistoryBuffer.setDataPosition(0);
-
-        int bufSize = in.readInt();
-        int curPos = in.dataPosition();
-        if (bufSize >= (mMaxHistoryBufferSize * 100)) {
-            throw new ParcelFormatException(
-                    "File corrupt: history data buffer too large " + bufSize);
-        } else if ((bufSize & ~3) != bufSize) {
-            throw new ParcelFormatException(
-                    "File corrupt: history data buffer not aligned " + bufSize);
-        } else {
-            if (DEBUG) {
-                Slog.i(TAG, "***************** READING NEW HISTORY: " + bufSize
-                        + " bytes at " + curPos);
+        synchronized (this) {
+            final int version = in.readInt();
+            if (version != BatteryStatsHistory.VERSION) {
+                Slog.w("BatteryStats", "readHistoryBuffer: version got " + version
+                        + ", expected " + BatteryStatsHistory.VERSION + "; erasing old stats");
+                return;
             }
-            mHistoryBuffer.appendFrom(in, curPos, bufSize);
-            in.setDataPosition(curPos + bufSize);
+
+            mHistoryBufferStartTime = in.readLong();
+            mHistoryBuffer.setDataSize(0);
+            mHistoryBuffer.setDataPosition(0);
+
+            int bufSize = in.readInt();
+            int curPos = in.dataPosition();
+            if (bufSize >= (mMaxHistoryBufferSize * 100)) {
+                throw new ParcelFormatException(
+                        "File corrupt: history data buffer too large " + bufSize);
+            } else if ((bufSize & ~3) != bufSize) {
+                throw new ParcelFormatException(
+                        "File corrupt: history data buffer not aligned " + bufSize);
+            } else {
+                if (DEBUG) {
+                    Slog.i(TAG, "***************** READING NEW HISTORY: " + bufSize
+                            + " bytes at " + curPos);
+                }
+                mHistoryBuffer.appendFrom(in, curPos, bufSize);
+                in.setDataPosition(curPos + bufSize);
+            }
         }
     }
 
+    @GuardedBy("this")
     private void writeHistoryBuffer(Parcel out) {
         out.writeInt(BatteryStatsHistory.VERSION);
         out.writeLong(mHistoryBufferStartTime);
@@ -2033,6 +2139,7 @@
         out.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
     }
 
+    @GuardedBy("this")
     private void writeParcelToFileLocked(Parcel p, AtomicFile file) {
         FileOutputStream fos = null;
         mWriteLock.lock();
@@ -2061,34 +2168,43 @@
      * Returns the total number of history tags in the tag pool.
      */
     public int getHistoryStringPoolSize() {
-        return mHistoryTagPool.size();
+        synchronized (this) {
+            return mHistoryTagPool.size();
+        }
     }
 
     /**
      * Returns the total number of bytes occupied by the history tag pool.
      */
     public int getHistoryStringPoolBytes() {
-        return mNumHistoryTagChars;
+        synchronized (this) {
+            return mNumHistoryTagChars;
+        }
     }
 
     /**
      * Returns the string held by the requested history tag.
      */
     public String getHistoryTagPoolString(int index) {
-        ensureHistoryTagArray();
-        HistoryTag historyTag = mHistoryTags.get(index);
-        return historyTag != null ? historyTag.string : null;
+        synchronized (this) {
+            ensureHistoryTagArray();
+            HistoryTag historyTag = mHistoryTags.get(index);
+            return historyTag != null ? historyTag.string : null;
+        }
     }
 
     /**
      * Returns the UID held by the requested history tag.
      */
     public int getHistoryTagPoolUid(int index) {
-        ensureHistoryTagArray();
-        HistoryTag historyTag = mHistoryTags.get(index);
-        return historyTag != null ? historyTag.uid : Process.INVALID_UID;
+        synchronized (this) {
+            ensureHistoryTagArray();
+            HistoryTag historyTag = mHistoryTags.get(index);
+            return historyTag != null ? historyTag.uid : Process.INVALID_UID;
+        }
     }
 
+    @GuardedBy("this")
     private void ensureHistoryTagArray() {
         if (mHistoryTags != null) {
             return;
diff --git a/core/java/com/android/internal/os/MonotonicClock.java b/core/java/com/android/internal/os/MonotonicClock.java
index c3bcfa6..eca6f58 100644
--- a/core/java/com/android/internal/os/MonotonicClock.java
+++ b/core/java/com/android/internal/os/MonotonicClock.java
@@ -17,11 +17,11 @@
 package com.android.internal.os;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.util.AtomicFile;
 import android.util.Log;
 import android.util.Xml;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
@@ -49,20 +49,27 @@
 
     private final AtomicFile mFile;
     private final Clock mClock;
-    private long mTimeshift;
+    private final long mTimeshift;
 
     public static final long UNDEFINED = -1;
 
     public MonotonicClock(File file) {
-        mFile = new AtomicFile(file);
-        mClock = Clock.SYSTEM_CLOCK;
-        read();
+        this (file, Clock.SYSTEM_CLOCK.elapsedRealtime(), Clock.SYSTEM_CLOCK);
     }
 
     public MonotonicClock(long monotonicTime, @NonNull Clock clock) {
+        this(null, monotonicTime, clock);
+    }
+
+    public MonotonicClock(@Nullable File file, long monotonicTime, @NonNull Clock clock) {
         mClock = clock;
-        mFile = null;
-        mTimeshift = monotonicTime - mClock.elapsedRealtime();
+        if (file != null) {
+            mFile = new AtomicFile(file);
+            mTimeshift = read(monotonicTime - mClock.elapsedRealtime());
+        } else {
+            mFile = null;
+            mTimeshift = monotonicTime - mClock.elapsedRealtime();
+        }
     }
 
     /**
@@ -81,15 +88,16 @@
         return mTimeshift + elapsedRealtimeMs;
     }
 
-    private void read() {
+    private long read(long defaultTimeshift) {
         if (!mFile.exists()) {
-            return;
+            return defaultTimeshift;
         }
 
         try {
-            readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser());
+            return readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser());
         } catch (IOException e) {
             Log.e(TAG, "Cannot load monotonic clock from " + mFile.getBaseFile(), e);
+            return defaultTimeshift;
         }
     }
 
@@ -102,18 +110,21 @@
             return;
         }
 
-        try (FileOutputStream out = mFile.startWrite()) {
+        FileOutputStream out = null;
+        try  {
+            out = mFile.startWrite();
             writeXml(out, Xml.newBinarySerializer());
+            mFile.finishWrite(out);
         } catch (IOException e) {
             Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e);
+            mFile.failWrite(out);
         }
     }
 
     /**
      * Parses an XML file containing the persistent state of the monotonic clock.
      */
-    @VisibleForTesting
-    public void readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException {
+    private long readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException {
         long savedTimeshift = 0;
         try {
             parser.setInput(inputStream, StandardCharsets.UTF_8.name());
@@ -128,14 +139,13 @@
         } catch (XmlPullParserException e) {
             throw new IOException(e);
         }
-        mTimeshift = savedTimeshift - mClock.elapsedRealtime();
+        return savedTimeshift - mClock.elapsedRealtime();
     }
 
     /**
      * Creates an XML file containing the persistent state of the monotonic clock.
      */
-    @VisibleForTesting
-    public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
+    private void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
         serializer.setOutput(out, StandardCharsets.UTF_8.name());
         serializer.startDocument(null, true);
         serializer.startTag(null, XML_TAG_MONOTONIC_TIME);
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index 2298cbd..ab982f5 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -50,6 +50,7 @@
  * Customize the XML file for different devices.
  * [hidden]
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class PowerProfile {
 
     public static final String TAG = "PowerProfile";
@@ -321,6 +322,13 @@
     private int mCpuPowerBracketCount;
 
     @VisibleForTesting
+    public PowerProfile() {
+        synchronized (sLock) {
+            initLocked();
+        }
+    }
+
+    @VisibleForTesting
     @UnsupportedAppUsage
     public PowerProfile(Context context) {
         this(context, false);
@@ -358,6 +366,10 @@
         if (sPowerItemMap.size() == 0 && sPowerArrayMap.size() == 0) {
             readPowerValuesFromXml(context, xmlId);
         }
+        initLocked();
+    }
+
+    private void initLocked() {
         initCpuClusters();
         initCpuScalingPolicies();
         initCpuPowerBrackets();
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java
index e3bfb38..e419d13 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java
@@ -38,7 +38,7 @@
 public class ParsedAttributionImpl implements ParsedAttribution, Parcelable {
 
     /** Maximum amount of attributions per package */
-    static final int MAX_NUM_ATTRIBUTIONS = 10000;
+    static final int MAX_NUM_ATTRIBUTIONS = 400;
 
     /** Tag of the attribution */
     private @NonNull String tag;
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
index c6683cf..05728ee 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
@@ -18,9 +18,13 @@
 
 import static com.android.internal.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.UriRelativeFilter;
+import android.content.UriRelativeFilterGroup;
+import android.content.pm.Flags;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.res.Resources;
@@ -132,6 +136,11 @@
                 case "data":
                     result = parseData(intentInfo, res, parser, allowGlobs, input);
                     break;
+                case "uri-relative-filter-group":
+                    if (Flags.relativeReferenceIntentFilters()) {
+                        result = parseRelRefGroup(intentInfo, pkg, res, parser, allowGlobs, input);
+                        break;
+                    }
                 default:
                     result = ParsingUtils.unknownTag("<intent-filter>", pkg, parser, input);
                     break;
@@ -163,6 +172,197 @@
     }
 
     @NonNull
+    @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
+    private static ParseResult<ParsedIntentInfo> parseRelRefGroup(ParsedIntentInfo intentInfo,
+            ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean allowGlobs,
+            ParseInput input) throws XmlPullParserException, IOException {
+        IntentFilter intentFilter = intentInfo.getIntentFilter();
+        TypedArray sa = res.obtainAttributes(parser,
+                R.styleable.AndroidManifestUriRelativeFilterGroup);
+        UriRelativeFilterGroup group;
+        try {
+            int action = UriRelativeFilterGroup.ACTION_ALLOW;
+            if (!sa.getBoolean(R.styleable.AndroidManifestUriRelativeFilterGroup_allow, true)) {
+                action = UriRelativeFilterGroup.ACTION_BLOCK;
+            }
+            group = new UriRelativeFilterGroup(action);
+        } finally {
+            sa.recycle();
+        }
+        final int depth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG
+                || parser.getDepth() > depth)) {
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            final ParseResult result;
+            String nodeName = parser.getName();
+            switch (nodeName) {
+                case "data":
+                    result = parseRelRefGroupData(group, res, parser, allowGlobs, input);
+                    break;
+                default:
+                    result = ParsingUtils.unknownTag("<uri-relative-filter-group>",
+                            pkg, parser, input);
+                    break;
+            }
+
+            if (result.isError()) {
+                return input.error(result);
+            }
+        }
+
+        if (group.getUriRelativeFilters().size() > 0) {
+            intentFilter.addUriRelativeFilterGroup(group);
+        }
+        return input.success(null);
+    }
+
+    @NonNull
+    @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
+    private static ParseResult<ParsedIntentInfo> parseRelRefGroupData(UriRelativeFilterGroup group,
+            Resources res, XmlResourceParser parser, boolean allowGlobs, ParseInput input) {
+        TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestData);
+        try {
+            String str = sa.getNonConfigurationString(
+                    R.styleable.AndroidManifestData_path, 0);
+            if (str != null) {
+                group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH,
+                        PatternMatcher.PATTERN_LITERAL, str));
+            }
+
+            str = sa.getNonConfigurationString(
+                    R.styleable.AndroidManifestData_pathPrefix, 0);
+            if (str != null) {
+                group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH,
+                        PatternMatcher.PATTERN_PREFIX, str));
+            }
+
+            str = sa.getNonConfigurationString(
+                    R.styleable.AndroidManifestData_pathPattern, 0);
+            if (str != null) {
+                if (!allowGlobs) {
+                    return input.error(
+                            "pathPattern not allowed here; path must be literal");
+                }
+                group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH,
+                        PatternMatcher.PATTERN_SIMPLE_GLOB, str));
+            }
+
+            str = sa.getNonConfigurationString(
+                    R.styleable.AndroidManifestData_pathAdvancedPattern, 0);
+            if (str != null) {
+                if (!allowGlobs) {
+                    return input.error(
+                            "pathAdvancedPattern not allowed here; path must be literal");
+                }
+                group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH,
+                        PatternMatcher.PATTERN_ADVANCED_GLOB, str));
+            }
+
+            str = sa.getNonConfigurationString(
+                    R.styleable.AndroidManifestData_pathSuffix, 0);
+            if (str != null) {
+                group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH,
+                        PatternMatcher.PATTERN_SUFFIX, str));
+            }
+
+            str = sa.getNonConfigurationString(
+                    R.styleable.AndroidManifestData_fragment, 0);
+            if (str != null) {
+                group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT,
+                        PatternMatcher.PATTERN_LITERAL, str));
+            }
+
+            str = sa.getNonConfigurationString(
+                    R.styleable.AndroidManifestData_fragmentPrefix, 0);
+            if (str != null) {
+                group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT,
+                        PatternMatcher.PATTERN_PREFIX, str));
+            }
+
+            str = sa.getNonConfigurationString(
+                    R.styleable.AndroidManifestData_fragmentPattern, 0);
+            if (str != null) {
+                if (!allowGlobs) {
+                    return input.error(
+                            "fragmentPattern not allowed here; fragment must be literal");
+                }
+                group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT,
+                        PatternMatcher.PATTERN_SIMPLE_GLOB, str));
+            }
+
+            str = sa.getNonConfigurationString(
+                    R.styleable.AndroidManifestData_fragmentAdvancedPattern, 0);
+            if (str != null) {
+                if (!allowGlobs) {
+                    return input.error(
+                            "fragmentAdvancedPattern not allowed here; fragment must be literal");
+                }
+                group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT,
+                        PatternMatcher.PATTERN_ADVANCED_GLOB, str));
+            }
+
+            str = sa.getNonConfigurationString(
+                    R.styleable.AndroidManifestData_fragmentSuffix, 0);
+            if (str != null) {
+                group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT,
+                        PatternMatcher.PATTERN_SUFFIX, str));
+            }
+
+            str = sa.getNonConfigurationString(
+                    R.styleable.AndroidManifestData_query, 0);
+            if (str != null) {
+                group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY,
+                        PatternMatcher.PATTERN_LITERAL, str));
+            }
+
+            str = sa.getNonConfigurationString(
+                    R.styleable.AndroidManifestData_queryPrefix, 0);
+            if (str != null) {
+                group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY,
+                        PatternMatcher.PATTERN_PREFIX, str));
+            }
+
+            str = sa.getNonConfigurationString(
+                    R.styleable.AndroidManifestData_queryPattern, 0);
+            if (str != null) {
+                if (!allowGlobs) {
+                    return input.error(
+                            "queryPattern not allowed here; query must be literal");
+                }
+                group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY,
+                        PatternMatcher.PATTERN_SIMPLE_GLOB, str));
+            }
+
+            str = sa.getNonConfigurationString(
+                    R.styleable.AndroidManifestData_queryAdvancedPattern, 0);
+            if (str != null) {
+                if (!allowGlobs) {
+                    return input.error(
+                            "queryAdvancedPattern not allowed here; query must be literal");
+                }
+                group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY,
+                        PatternMatcher.PATTERN_ADVANCED_GLOB, str));
+            }
+
+            str = sa.getNonConfigurationString(
+                    R.styleable.AndroidManifestData_querySuffix, 0);
+            if (str != null) {
+                group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY,
+                        PatternMatcher.PATTERN_SUFFIX, str));
+            }
+
+            return input.success(null);
+        } finally {
+            sa.recycle();
+        }
+    }
+
+    @NonNull
     private static ParseResult<ParsedIntentInfo> parseData(ParsedIntentInfo intentInfo,
             Resources resources, XmlResourceParser parser, boolean allowGlobs, ParseInput input) {
         IntentFilter intentFilter = intentInfo.getIntentFilter();
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
index 5d82d04..12aff1c 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
@@ -29,6 +29,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.multiuser.Flags;
 import android.os.Build;
 import android.os.PatternMatcher;
 import android.util.Slog;
@@ -126,6 +127,10 @@
                     .setFlags(provider.getFlags() | flag(ProviderInfo.FLAG_SINGLE_USER,
                             R.styleable.AndroidManifestProvider_singleUser, sa));
 
+            if (Flags.enableSystemUserOnlyForServicesAndProviders()) {
+                provider.setFlags(provider.getFlags() | flag(ProviderInfo.FLAG_SYSTEM_USER_ONLY,
+                        R.styleable.AndroidManifestProvider_systemUserOnly, sa));
+            }
             visibleToEphemeral = sa.getBoolean(
                     R.styleable.AndroidManifestProvider_visibleToInstantApps, false);
             if (visibleToEphemeral) {
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
index a1dd19a3..4ac542f8 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
@@ -29,6 +29,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.multiuser.Flags;
 import android.os.Build;
 
 import com.android.internal.R;
@@ -105,6 +106,11 @@
                             | flag(ServiceInfo.FLAG_SINGLE_USER,
                             R.styleable.AndroidManifestService_singleUser, sa)));
 
+            if (Flags.enableSystemUserOnlyForServicesAndProviders()) {
+                service.setFlags(service.getFlags() | flag(ServiceInfo.FLAG_SYSTEM_USER_ONLY,
+                        R.styleable.AndroidManifestService_systemUserOnly, sa));
+            }
+
             visibleToEphemeral = sa.getBoolean(
                     R.styleable.AndroidManifestService_visibleToInstantApps, false);
             if (visibleToEphemeral) {
diff --git a/core/java/com/android/internal/policy/SystemBarUtils.java b/core/java/com/android/internal/policy/SystemBarUtils.java
index 7a1ac07..efa3697 100644
--- a/core/java/com/android/internal/policy/SystemBarUtils.java
+++ b/core/java/com/android/internal/policy/SystemBarUtils.java
@@ -19,8 +19,9 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Insets;
-import android.util.RotationUtils;
+import android.view.Display;
 import android.view.DisplayCutout;
+import android.view.DisplayInfo;
 import android.view.Surface;
 
 import com.android.internal.R;
@@ -56,21 +57,21 @@
      */
     public static int getStatusBarHeightForRotation(
             Context context, @Surface.Rotation int targetRot) {
-        final int rotation = context.getDisplay().getRotation();
-        final DisplayCutout cutout = context.getDisplay().getCutout();
-
-        Insets insets = cutout == null ? Insets.NONE : Insets.of(cutout.getSafeInsets());
-        Insets waterfallInsets = cutout == null ? Insets.NONE : cutout.getWaterfallInsets();
-        // rotate insets to target rotation if needed.
-        if (rotation != targetRot) {
-            if (!insets.equals(Insets.NONE)) {
-                insets = RotationUtils.rotateInsets(
-                        insets, RotationUtils.deltaRotation(rotation, targetRot));
-            }
-            if (!waterfallInsets.equals(Insets.NONE)) {
-                waterfallInsets = RotationUtils.rotateInsets(
-                        waterfallInsets, RotationUtils.deltaRotation(rotation, targetRot));
-            }
+        final Display display = context.getDisplay();
+        final int rotation = display.getRotation();
+        final DisplayCutout cutout = display.getCutout();
+        DisplayInfo info = new DisplayInfo();
+        display.getDisplayInfo(info);
+        Insets insets;
+        Insets waterfallInsets;
+        if (cutout == null) {
+            insets = Insets.NONE;
+            waterfallInsets = Insets.NONE;
+        } else {
+            DisplayCutout rotated =
+                    cutout.getRotated(info.logicalWidth, info.logicalHeight, rotation, targetRot);
+            insets = Insets.of(rotated.getSafeInsets());
+            waterfallInsets = rotated.getWaterfallInsets();
         }
         final int defaultSize =
                 context.getResources().getDimensionPixelSize(R.dimen.status_bar_height_default);
diff --git a/core/java/com/android/internal/power/ModemPowerProfile.java b/core/java/com/android/internal/power/ModemPowerProfile.java
index a555ae3..b15c10e 100644
--- a/core/java/com/android/internal/power/ModemPowerProfile.java
+++ b/core/java/com/android/internal/power/ModemPowerProfile.java
@@ -39,6 +39,7 @@
 /**
  * ModemPowerProfile for handling the modem element in the power_profile.xml
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ModemPowerProfile {
     private static final String TAG = "ModemPowerProfile";
 
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 03cfd4f..969f95d 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -80,4 +80,5 @@
     void onMediaQualityStatusChanged(in MediaQualityStatus mediaQualityStatus);
     void onCallBackModeStarted(int type);
     void onCallBackModeStopped(int type, int reason);
+    void onSimultaneousCallingStateChanged(in int[] subIds);
 }
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index aab2242..0203ea4 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -104,6 +104,7 @@
     void notifyAllowedNetworkTypesChanged(in int phoneId, in int subId, in int reason, in long allowedNetworkType);
     void notifyLinkCapacityEstimateChanged(in int phoneId, in int subId,
             in List<LinkCapacityEstimate> linkCapacityEstimateList);
+    void notifySimultaneousCellularCallingSubscriptionsChanged(in int[] subIds);
 
     void addCarrierPrivilegesCallback(
             int phoneId, ICarrierPrivilegesCallback callback, String pkg, String featureId);
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index b87027c..4191936 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -120,8 +120,9 @@
     public static @NonNull <I, O> List<O> map(@Nullable List<I> cur,
             Function<? super I, ? extends O> f) {
         if (isEmpty(cur)) return Collections.emptyList();
-        final ArrayList<O> result = new ArrayList<>();
-        for (int i = 0; i < cur.size(); i++) {
+        final int size = cur.size();
+        final ArrayList<O> result = new ArrayList<>(size);
+        for (int i = 0; i < size; i++) {
             result.add(f.apply(cur.get(i)));
         }
         return result;
@@ -133,7 +134,7 @@
     public static @NonNull <I, O> Set<O> map(@Nullable Set<I> cur,
             Function<? super I, ? extends O> f) {
         if (isEmpty(cur)) return emptySet();
-        ArraySet<O> result = new ArraySet<>();
+        ArraySet<O> result = new ArraySet<>(cur.size());
         if (cur instanceof ArraySet) {
             ArraySet<I> arraySet = (ArraySet<I>) cur;
             int size = arraySet.size();
@@ -163,7 +164,7 @@
             Function<? super I, ? extends O> f) {
         if (isEmpty(cur)) return Collections.emptyList();
         List<O> result = null;
-        for (int i = 0; i < cur.size(); i++) {
+        for (int i = 0, size = cur.size(); i < size; i++) {
             O transformed = f.apply(cur.get(i));
             if (transformed != null) {
                 result = add(result, transformed);
@@ -239,7 +240,7 @@
     public static @NonNull <T> List<T> filter(@Nullable List<?> list, Class<T> c) {
         if (isEmpty(list)) return Collections.emptyList();
         ArrayList<T> result = null;
-        for (int i = 0; i < list.size(); i++) {
+        for (int i = 0, size = list.size(); i < size; i++) {
             final Object item = list.get(i);
             if (c.isInstance(item)) {
                 result = ArrayUtils.add(result, (T) item);
@@ -273,7 +274,7 @@
     public static @Nullable <T> T find(@Nullable List<T> items,
             java.util.function.Predicate<T> predicate) {
         if (isEmpty(items)) return null;
-        for (int i = 0; i < items.size(); i++) {
+        for (int i = 0, size = items.size(); i < size; i++) {
             final T item = items.get(i);
             if (predicate.test(item)) return item;
         }
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 58ee2b2..13efaf0 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -26,6 +26,8 @@
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FACE_WAKE_AND_UNLOCK;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FINGERPRINT_WAKE_AND_UNLOCK;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK;
@@ -234,6 +236,19 @@
      */
     public static final int ACTION_BACK_SYSTEM_ANIMATION = 25;
 
+    /**
+     * Time notifications spent in hidden state for performance reasons. We might temporary
+     * hide notifications after display size changes (e.g. fold/unfold of a foldable device)
+     * and measure them while they are hidden to unblock rendering of the rest of the UI.
+     */
+    public static final int ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE = 26;
+
+    /**
+     * The same as {@link ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE} but tracks time only
+     * when the notifications are hidden and when the shade is open or keyguard is visible.
+     */
+    public static final int ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN = 27;
+
     private static final int[] ACTIONS_ALL = {
         ACTION_EXPAND_PANEL,
         ACTION_TOGGLE_RECENTS,
@@ -261,6 +276,8 @@
         ACTION_NOTIFICATION_BIG_PICTURE_LOADED,
         ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME,
         ACTION_BACK_SYSTEM_ANIMATION,
+        ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE,
+        ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN,
     };
 
     /** @hide */
@@ -291,6 +308,8 @@
         ACTION_NOTIFICATION_BIG_PICTURE_LOADED,
         ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME,
         ACTION_BACK_SYSTEM_ANIMATION,
+        ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE,
+        ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Action {
@@ -324,6 +343,8 @@
             UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATION_BIG_PICTURE_LOADED,
             UIACTION_LATENCY_REPORTED__ACTION__ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME,
             UIACTION_LATENCY_REPORTED__ACTION__ACTION_BACK_SYSTEM_ANIMATION,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE,
+            UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN,
     };
 
     private final Object mLock = new Object();
@@ -514,6 +535,10 @@
                 return "ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME";
             case UIACTION_LATENCY_REPORTED__ACTION__ACTION_BACK_SYSTEM_ANIMATION:
                 return "ACTION_BACK_SYSTEM_ANIMATION";
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE:
+                return "ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE";
+            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN:
+                return "ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN";
             default:
                 throw new IllegalArgumentException("Invalid action");
         }
diff --git a/core/java/com/android/internal/util/RingBuffer.java b/core/java/com/android/internal/util/RingBuffer.java
index 0488659..342ba1b6 100644
--- a/core/java/com/android/internal/util/RingBuffer.java
+++ b/core/java/com/android/internal/util/RingBuffer.java
@@ -19,7 +19,10 @@
 import static com.android.internal.util.Preconditions.checkArgumentPositive;
 
 import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
 import java.util.Arrays;
+import java.util.function.IntFunction;
+import java.util.function.Supplier;
 
 /**
  * A simple ring buffer structure with bounded capacity backed by an array.
@@ -30,16 +33,35 @@
 @android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class RingBuffer<T> {
 
+    private final Supplier<T> mNewItem;
     // Array for storing events.
     private final T[] mBuffer;
     // Cursor keeping track of the logical end of the array. This cursor never
     // wraps and instead keeps track of the total number of append() operations.
     private long mCursor = 0;
 
+    /**
+     * @deprecated This uses reflection to create new instances.
+     *             Use {@link #RingBuffer(Supplier, IntFunction, int)}} instead.
+     */
+    @Deprecated
     public RingBuffer(Class<T> c, int capacity) {
+        this(() -> (T) createNewItem(c), cap -> (T[]) Array.newInstance(c, cap), capacity);
+    }
+
+    private static Object createNewItem(Class c) {
+        try {
+            return c.getDeclaredConstructor().newInstance();
+        } catch (IllegalAccessException | InstantiationException | NoSuchMethodException
+                 | InvocationTargetException e) {
+            return null;
+        }
+    }
+
+    public RingBuffer(Supplier<T> newItem, IntFunction<T[]> newBacking, int capacity) {
         checkArgumentPositive(capacity, "A RingBuffer cannot have 0 capacity");
-        // Java cannot create generic arrays without a runtime hint.
-        mBuffer = (T[]) Array.newInstance(c, capacity);
+        mBuffer = newBacking.apply(capacity);
+        mNewItem = newItem;
     }
 
     public int size() {
@@ -69,22 +91,11 @@
     public T getNextSlot() {
         final int nextSlotIdx = indexOf(mCursor++);
         if (mBuffer[nextSlotIdx] == null) {
-            mBuffer[nextSlotIdx] = createNewItem();
+            mBuffer[nextSlotIdx] = mNewItem.get();
         }
         return mBuffer[nextSlotIdx];
     }
 
-    /**
-     * @return a new object of type <T> or null if a new object could not be created.
-     */
-    protected T createNewItem() {
-        try {
-            return (T) mBuffer.getClass().getComponentType().newInstance();
-        } catch (IllegalAccessException | InstantiationException e) {
-            return null;
-        }
-    }
-
     public T[] toArray() {
         // Only generic way to create a T[] from another T[]
         T[] out = Arrays.copyOf(mBuffer, size(), (Class<T[]>) mBuffer.getClass());
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 42be784..a8d0d37 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -105,6 +105,9 @@
     private int mConversationIconTopPaddingExpandedGroup;
     private int mConversationIconTopPadding;
     private int mExpandedGroupMessagePadding;
+    // TODO (b/217799515) Currently, mConversationText shows the conversation title, the actual
+    //  conversation text is inside of mMessagingLinearLayout, which is misleading, we should rename
+    //  this to mConversationTitleView
     private TextView mConversationText;
     private View mConversationIconBadge;
     private CachingIconView mConversationIconBadgeBg;
@@ -125,6 +128,11 @@
     private int mNotificationBackgroundColor;
     private CharSequence mFallbackChatName;
     private CharSequence mFallbackGroupChatName;
+    //TODO (b/217799515) Currently, Notification.MessagingStyle, ConversationLayout, and
+    // HybridConversationNotificationView, each has their own definition of "ConversationTitle".
+    // What make things worse is that the term of "ConversationTitle" often confuses with
+    // "ConversationText".
+    // We need to unify them or differentiate the namings.
     private CharSequence mConversationTitle;
     private int mMessageSpacingStandard;
     private int mMessageSpacingGroup;
@@ -160,12 +168,12 @@
     }
 
     public ConversationLayout(@NonNull Context context, @Nullable AttributeSet attrs,
-            @AttrRes int defStyleAttr) {
+                              @AttrRes int defStyleAttr) {
         super(context, attrs, defStyleAttr);
     }
 
     public ConversationLayout(@NonNull Context context, @Nullable AttributeSet attrs,
-            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+                              @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
     }
 
@@ -297,13 +305,17 @@
         mNameReplacement = nameReplacement;
     }
 
-    /** Sets this conversation as "important", adding some additional UI treatment. */
+    /**
+     * Sets this conversation as "important", adding some additional UI treatment.
+     */
     @RemotableViewMethod
     public void setIsImportantConversation(boolean isImportantConversation) {
         setIsImportantConversation(isImportantConversation, false);
     }
 
-    /** @hide **/
+    /**
+     * @hide
+     **/
     public void setIsImportantConversation(boolean isImportantConversation, boolean animate) {
         mImportantConversation = isImportantConversation;
         mImportanceRingView.setVisibility(isImportantConversation && mIcon.getVisibility() != GONE
@@ -386,6 +398,7 @@
 
     /**
      * Set conversation data
+     *
      * @param extras Bundle contains conversation data
      */
     @RemotableViewMethod(asyncImpl = "setDataAsync")
@@ -427,6 +440,7 @@
      * RemotableViewMethod's asyncImpl of {@link #setData(Bundle)}.
      * This should be called on a background thread, and returns a Runnable which is then must be
      * called on the main thread to complete the operation and set text.
+     *
      * @param extras Bundle contains conversation data
      * @hide
      */
@@ -449,6 +463,7 @@
 
     /**
      * enable/disable precomputed text usage
+     *
      * @hide
      */
     public void setPrecomputedTextEnabled(boolean precomputedTextEnabled) {
@@ -466,7 +481,9 @@
         mImageResolver = resolver;
     }
 
-    /** @hide */
+    /**
+     * @hide
+     */
     public void setUnreadCount(int unreadCount) {
         mExpandButton.setNumber(unreadCount);
     }
@@ -795,6 +812,10 @@
         mConversationTitle = conversationTitle != null ? conversationTitle.toString() : null;
     }
 
+    // TODO (b/217799515) getConversationTitle is not consistent with setConversationTitle
+    //  if you call getConversationTitle() immediately after setConversationTitle(), the result
+    //  will not correctly reflect the new change without calling updateConversationLayout, for
+    //  example.
     public CharSequence getConversationTitle() {
         return mConversationText.getText();
     }
@@ -914,7 +935,7 @@
     }
 
     private void createGroupViews(List<List<MessagingMessage>> groups,
-            List<Person> senders, boolean showSpinner) {
+                                  List<Person> senders, boolean showSpinner) {
         mGroups.clear();
         for (int groupIndex = 0; groupIndex < groups.size(); groupIndex++) {
             List<MessagingMessage> group = groups.get(groupIndex);
@@ -963,8 +984,8 @@
     }
 
     private void findGroups(List<MessagingMessage> historicMessages,
-            List<MessagingMessage> messages, List<List<MessagingMessage>> groups,
-            List<Person> senders) {
+                            List<MessagingMessage> messages, List<List<MessagingMessage>> groups,
+                            List<Person> senders) {
         CharSequence currentSenderKey = null;
         List<MessagingMessage> currentGroup = null;
         int histSize = historicMessages.size();
diff --git a/core/java/com/android/internal/widget/ILockSettingsStateListener.aidl b/core/java/com/android/internal/widget/ILockSettingsStateListener.aidl
new file mode 100644
index 0000000..25e3003
--- /dev/null
+++ b/core/java/com/android/internal/widget/ILockSettingsStateListener.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+/**
+ * Callback interface between LockSettingService and other system services to be notified about the
+ * state of primary authentication (i.e. PIN/pattern/password).
+ * @hide
+ */
+oneway interface ILockSettingsStateListener {
+    /**
+     * Defines behavior in response to a successful authentication
+     * @param userId The user Id for the requested authentication
+     */
+    void onAuthenticationSucceeded(int userId);
+
+    /**
+     * Defines behavior in response to a failed authentication
+     * @param userId The user Id for the requested authentication
+     */
+    void onAuthenticationFailed(int userId);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index 0704cb8..5da6435 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -18,9 +18,11 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.os.Build;
 import android.os.Trace;
 import android.text.BoringLayout;
 import android.text.Layout;
+import android.text.PrecomputedText;
 import android.text.StaticLayout;
 import android.text.TextUtils;
 import android.text.method.TransformationMethod;
@@ -48,6 +50,10 @@
     private int mLayoutMaxLines = -1;
     private int mImageEndMargin;
 
+    private int mStaticLayoutCreationCountInOnMeasure = 0;
+
+    private static final boolean TRACE_ONMEASURE = Build.isDebuggable();
+
     public ImageFloatingTextView(Context context) {
         this(context, null);
     }
@@ -71,7 +77,10 @@
     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
             Layout.Alignment alignment, boolean shouldEllipsize,
             TextUtils.TruncateAt effectiveEllipsize, boolean useSaved) {
-        Trace.beginSection("ImageFloatingTextView#makeSingleLayout");
+        if (TRACE_ONMEASURE) {
+            Trace.beginSection("ImageFloatingTextView#makeSingleLayout");
+            mStaticLayoutCreationCountInOnMeasure++;
+        }
         TransformationMethod transformationMethod = getTransformationMethod();
         CharSequence text = getText();
         if (transformationMethod != null) {
@@ -79,7 +88,7 @@
         }
         text = text == null ? "" : text;
         StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, text.length(),
-                getPaint(), wantWidth)
+                        getPaint(), wantWidth)
                 .setAlignment(alignment)
                 .setTextDirection(getTextDirectionHeuristic())
                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
@@ -115,7 +124,10 @@
         }
 
         final StaticLayout result = builder.build();
-        Trace.endSection();
+        if (TRACE_ONMEASURE) {
+            trackMaxLines();
+            Trace.endSection();
+        }
         return result;
     }
 
@@ -141,7 +153,10 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        Trace.beginSection("ImageFloatingTextView#onMeasure");
+        if (TRACE_ONMEASURE) {
+            Trace.beginSection("ImageFloatingTextView#onMeasure");
+        }
+        mStaticLayoutCreationCountInOnMeasure = 0;
         int availableHeight = MeasureSpec.getSize(heightMeasureSpec) - mPaddingTop - mPaddingBottom;
         if (getLayout() != null && getLayout().getHeight() != availableHeight) {
             // We've been measured before and the new size is different than before, lets make sure
@@ -168,7 +183,12 @@
                 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
             }
         }
-        Trace.endSection();
+
+
+        if (TRACE_ONMEASURE) {
+            trackParameters();
+            Trace.endSection();
+        }
     }
 
     @Override
@@ -216,4 +236,37 @@
             requestLayout();
         }
     }
+
+    private void trackParameters() {
+        if (!TRACE_ONMEASURE) {
+            return;
+        }
+        Trace.setCounter("ImageFloatingView#staticLayoutCreationCount",
+                mStaticLayoutCreationCountInOnMeasure);
+        Trace.setCounter("ImageFloatingView#isPrecomputedText",
+                isTextAPrecomputedText());
+    }
+    /**
+     * @return 1 if {@link TextView#getText()} is PrecomputedText, else 0
+     */
+    private int isTextAPrecomputedText() {
+        final CharSequence text = getText();
+        if (text == null) {
+            return 0;
+        }
+
+        if (text instanceof PrecomputedText) {
+            return 1;
+        }
+
+        return 0;
+    }
+
+    private void trackMaxLines() {
+        if (!TRACE_ONMEASURE) {
+            return;
+        }
+
+        Trace.setCounter("ImageFloatingView#layoutMaxLines", mLayoutMaxLines);
+    }
 }
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index bf8e613..b5b3a48 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -333,11 +333,17 @@
 
     @UnsupportedAppUsage
     public LockPatternUtils(Context context) {
+        this(context, null);
+    }
+
+    @VisibleForTesting
+    public LockPatternUtils(Context context, ILockSettings lockSettings) {
         mContext = context;
         mContentResolver = context.getContentResolver();
 
         Looper looper = Looper.myLooper();
         mHandler = looper != null ? new Handler(looper) : null;
+        mLockSettingsService = lockSettings;
     }
 
     @UnsupportedAppUsage
@@ -1384,8 +1390,8 @@
     }
 
     public boolean isUserInLockdown(int userId) {
-        return getStrongAuthForUser(userId)
-                == StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+        return (getStrongAuthForUser(userId)
+                & StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN) != 0;
     }
 
     private static class WrappedCallback extends ICheckCredentialProgressCallback.Stub {
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index 8114e1f..627e877 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -166,4 +166,16 @@
      * Refreshes pending strong auth timeout with the latest admin requirement set by device policy.
      */
     public abstract void refreshStrongAuthTimeout(int userId);
+
+    /**
+     * Register a LockSettingsStateListener
+     * @param listener The listener to be registered
+     */
+    public abstract void registerLockSettingsStateListener(ILockSettingsStateListener listener);
+
+    /**
+     * Unregister a LockSettingsStateListener
+     * @param listener The listener to be unregistered
+     */
+    public abstract void unregisterLockSettingsStateListener(ILockSettingsStateListener listener);
 }
diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java
index c06f5f7..e07acac 100644
--- a/core/java/com/android/internal/widget/MessagingLinearLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java
@@ -21,6 +21,8 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.os.Build;
+import android.os.Trace;
 import android.util.AttributeSet;
 import android.view.RemotableViewMethod;
 import android.view.View;
@@ -45,6 +47,8 @@
 
     private int mMaxDisplayedLines = Integer.MAX_VALUE;
 
+    private static final boolean TRACE_ONMEASURE = Build.isDebuggable();
+
     public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
 
@@ -67,6 +71,10 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (TRACE_ONMEASURE) {
+            Trace.beginSection("MessagingLinearLayout#onMeasure");
+            trackMeasureSpecs(widthMeasureSpec, heightMeasureSpec);
+        }
         // This is essentially a bottom-up linear layout that only adds children that fit entirely
         // up to a maximum height.
         int targetHeight = MeasureSpec.getSize(heightMeasureSpec);
@@ -177,6 +185,9 @@
                 resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
                         widthMeasureSpec),
                 Math.max(getSuggestedMinimumHeight(), totalHeight));
+        if (TRACE_ONMEASURE) {
+            Trace.endSection();
+        }
     }
 
     @Override
@@ -240,6 +251,25 @@
         }
     }
 
+    private void trackMeasureSpecs(int widthMeasureSpec, int heightMeasureSpec) {
+        if (!TRACE_ONMEASURE) {
+            return;
+        }
+
+        final int availableWidth = MeasureSpec.getSize(widthMeasureSpec);
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int availableHeight = MeasureSpec.getSize(heightMeasureSpec);
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        Trace.setCounter("MessagingLinearLayout#onMeasure_widthMeasureSpecSize",
+                availableWidth);
+        Trace.setCounter("MessagingLinearLayout#onMeasure_widthMeasureSpecMode",
+                widthMode);
+        Trace.setCounter("MessagingLinearLayout#onMeasure_heightMeasureSpecSize",
+                availableHeight);
+        Trace.setCounter("MessagingLinearLayout#onMeasure_heightMeasureSpecMode",
+                heightMode);
+    }
+
     @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
diff --git a/core/java/com/android/internal/widget/PeopleHelper.java b/core/java/com/android/internal/widget/PeopleHelper.java
index 85cedc3..3f5b4a0 100644
--- a/core/java/com/android/internal/widget/PeopleHelper.java
+++ b/core/java/com/android/internal/widget/PeopleHelper.java
@@ -22,6 +22,8 @@
 import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.Person;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -222,6 +224,72 @@
     }
 
     /**
+     * A class that represents a map from unique sender names in the groups to the string 1- or
+     * 2-character prefix strings for the names. This class uses the String value of the
+     * CharSequence Names as the key.
+     */
+    public class NameToPrefixMap {
+        Map<String, String> mMap;
+        NameToPrefixMap(Map<String, String> map) {
+            this.mMap = map;
+        }
+
+        /**
+         * @param name the name
+         * @return the prefix of the given name
+         */
+        public String getPrefix(CharSequence name) {
+            return mMap.get(name.toString());
+        }
+    }
+
+    /**
+     * Same functionality as mapUniqueNamesToPrefix, but takes list-represented message groups as
+     * the input. This method is better when inflating MessagingGroup from the UI thread is not
+     * an option.
+     * @param groups message groups represented by lists. A message group is some consecutive
+     *               messages (>=3) from the same sender in a conversation.
+     */
+    public NameToPrefixMap mapUniqueNamesToPrefixWithGroupList(
+            List<List<Notification.MessagingStyle.Message>> groups) {
+        // Map of unique names to their prefix
+        ArrayMap<String, String> uniqueNames = new ArrayMap<>();
+        // Map of single-character string prefix to the only name which uses it, or null if multiple
+        ArrayMap<String, CharSequence> uniqueCharacters = new ArrayMap<>();
+        for (int i = 0; i < groups.size(); i++) {
+            List<Notification.MessagingStyle.Message> group = groups.get(i);
+            if (group.isEmpty()) continue;
+            Person sender = group.get(0).getSenderPerson();
+            if (sender == null) continue;
+            CharSequence senderName = sender.getName();
+            if (sender.getIcon() != null || TextUtils.isEmpty(senderName)) {
+                continue;
+            }
+            String senderNameString = senderName.toString();
+            if (!uniqueNames.containsKey(senderNameString)) {
+                String charPrefix = findNamePrefix(senderName, null);
+                if (charPrefix == null) {
+                    continue;
+                }
+                if (uniqueCharacters.containsKey(charPrefix)) {
+                    // this character was already used, lets make it more unique. We first need to
+                    // resolve the existing character if it exists
+                    CharSequence existingName = uniqueCharacters.get(charPrefix);
+                    if (existingName != null) {
+                        uniqueNames.put(existingName.toString(), findNameSplit(existingName));
+                        uniqueCharacters.put(charPrefix, null);
+                    }
+                    uniqueNames.put(senderNameString, findNameSplit(senderName));
+                } else {
+                    uniqueNames.put(senderNameString, charPrefix);
+                    uniqueCharacters.put(charPrefix, senderName);
+                }
+            }
+        }
+        return new NameToPrefixMap(uniqueNames);
+    }
+
+    /**
      * Update whether the groups can hide the sender if they are first
      * (happens only for 1:1 conversations where the given title matches the sender's name)
      */
diff --git a/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java b/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java
index ce9ab82..2ff6225 100644
--- a/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java
+++ b/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java
@@ -21,6 +21,7 @@
 import android.app.backup.BackupDataInputStream;
 import android.app.backup.BackupDataOutput;
 import android.app.backup.BackupHelper;
+import android.app.backup.BackupHelperWithLogger;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.SyncAdapterType;
@@ -56,7 +57,7 @@
  * sync settings are backed up as a JSON object containing all the necessary information for
  * restoring the sync settings later.
  */
-public class AccountSyncSettingsBackupHelper implements BackupHelper {
+public class AccountSyncSettingsBackupHelper extends BackupHelperWithLogger {
 
     private static final String TAG = "AccountSyncSettingsBackupHelper";
     private static final boolean DEBUG = false;
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 2a744e3..656cc3e 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -188,8 +188,6 @@
                 "android_os_SharedMemory.cpp",
                 "android_os_storage_StorageManager.cpp",
                 "android_os_UEventObserver.cpp",
-                "android_os_VintfObject.cpp",
-                "android_os_VintfRuntimeInfo.cpp",
                 "android_os_incremental_IncrementalManager.cpp",
                 "android_net_LocalSocketImpl.cpp",
                 "android_service_DataLoaderService.cpp",
@@ -269,6 +267,9 @@
                 "android_window_WindowInfosListener.cpp",
                 "android_window_ScreenCapture.cpp",
                 "jni_common.cpp",
+                "android_tracing_PerfettoDataSource.cpp",
+                "android_tracing_PerfettoDataSourceInstance.cpp",
+                "android_tracing_PerfettoProducer.cpp",
             ],
 
             static_libs: [
@@ -277,11 +278,13 @@
                 "libdmabufinfo",
                 "libgif",
                 "libgui_window_info_static",
+                "libkernelconfigs",
                 "libseccomp_policy",
                 "libgrallocusage",
                 "libscrypt_static",
                 "libstatssocket_lazy",
                 "libskia",
+                "libperfetto_client_experimental",
             ],
 
             shared_libs: [
@@ -346,7 +349,6 @@
                 "libnativeloader_lazy",
                 "libmemunreachable",
                 "libhidlbase",
-                "libvintf",
                 "libnativedisplay",
                 "libnativewindow",
                 "libdl",
@@ -355,6 +357,7 @@
                 "server_configurable_flags",
                 "libimage_io",
                 "libultrahdr",
+                "libperfetto_c",
             ],
             export_shared_lib_headers: [
                 // our headers include libnativewindow's public headers
@@ -453,8 +456,25 @@
                 // (e.g. gDefaultServiceManager)
                 "libbinder",
                 "libhidlbase", // libhwbinder is in here
-                "libvintf",
             ],
         },
     },
 }
+
+cc_library_shared {
+    name: "libvintf_jni",
+
+    cpp_std: "gnu++20",
+
+    srcs: [
+        "android_os_VintfObject.cpp",
+        "android_os_VintfRuntimeInfo.cpp",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libnativehelper",
+        "libvintf",
+    ],
+}
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 17aad43..aa63f4f 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -152,8 +152,6 @@
 extern int register_android_os_Parcel(JNIEnv* env);
 extern int register_android_os_PerformanceHintManager(JNIEnv* env);
 extern int register_android_os_SELinux(JNIEnv* env);
-extern int register_android_os_VintfObject(JNIEnv *env);
-extern int register_android_os_VintfRuntimeInfo(JNIEnv *env);
 extern int register_android_os_storage_StorageManager(JNIEnv* env);
 extern int register_android_os_SystemProperties(JNIEnv *env);
 extern int register_android_os_SystemClock(JNIEnv* env);
@@ -220,6 +218,9 @@
 extern int register_android_window_WindowInfosListener(JNIEnv* env);
 extern int register_android_window_ScreenCapture(JNIEnv* env);
 extern int register_jni_common(JNIEnv* env);
+extern int register_android_tracing_PerfettoDataSource(JNIEnv* env);
+extern int register_android_tracing_PerfettoDataSourceInstance(JNIEnv* env);
+extern int register_android_tracing_PerfettoProducer(JNIEnv* env);
 
 // Namespace for Android Runtime flags applied during boot time.
 static const char* RUNTIME_NATIVE_BOOT_NAMESPACE = "runtime_native_boot";
@@ -1542,8 +1543,6 @@
         REG_JNI(register_android_os_NativeHandle),
         REG_JNI(register_android_os_ServiceManager),
         REG_JNI(register_android_os_storage_StorageManager),
-        REG_JNI(register_android_os_VintfObject),
-        REG_JNI(register_android_os_VintfRuntimeInfo),
         REG_JNI(register_android_service_DataLoaderService),
         REG_JNI(register_android_view_DisplayEventReceiver),
         REG_JNI(register_android_view_Surface),
@@ -1675,6 +1674,10 @@
         REG_JNI(register_android_window_WindowInfosListener),
         REG_JNI(register_android_window_ScreenCapture),
         REG_JNI(register_jni_common),
+
+        REG_JNI(register_android_tracing_PerfettoDataSource),
+        REG_JNI(register_android_tracing_PerfettoDataSourceInstance),
+        REG_JNI(register_android_tracing_PerfettoProducer),
 };
 
 /*
diff --git a/core/jni/android_hardware_OverlayProperties.cpp b/core/jni/android_hardware_OverlayProperties.cpp
index 5b95ee7..f6fe3dd 100644
--- a/core/jni/android_hardware_OverlayProperties.cpp
+++ b/core/jni/android_hardware_OverlayProperties.cpp
@@ -61,13 +61,12 @@
                           static_cast<int32_t>(HAL_PIXEL_FORMAT_RGBA_FP16)) !=
                         i.pixelFormats.end() &&
                 std::find(i.standards.begin(), i.standards.end(),
-                          static_cast<int32_t>(HAL_DATASPACE_STANDARD_BT2020)) !=
+                          static_cast<int32_t>(HAL_DATASPACE_STANDARD_BT709)) !=
                         i.standards.end() &&
                 std::find(i.transfers.begin(), i.transfers.end(),
-                          static_cast<int32_t>(HAL_DATASPACE_TRANSFER_ST2084)) !=
-                        i.transfers.end() &&
+                          static_cast<int32_t>(HAL_DATASPACE_TRANSFER_SRGB)) != i.transfers.end() &&
                 std::find(i.ranges.begin(), i.ranges.end(),
-                          static_cast<int32_t>(HAL_DATASPACE_RANGE_FULL)) != i.ranges.end()) {
+                          static_cast<int32_t>(HAL_DATASPACE_RANGE_EXTENDED)) != i.ranges.end()) {
                 return true;
             }
         }
diff --git a/core/jni/android_hardware_SyncFence.cpp b/core/jni/android_hardware_SyncFence.cpp
index b996653..6e94616 100644
--- a/core/jni/android_hardware_SyncFence.cpp
+++ b/core/jni/android_hardware_SyncFence.cpp
@@ -66,6 +66,10 @@
     return fromJlong<Fence>(jPtr)->getSignalTime();
 }
 
+static void SyncFence_incRef(JNIEnv*, jobject, jlong jPtr) {
+    fromJlong<Fence>(jPtr)->incStrong((void*)SyncFence_incRef);
+}
+
 // ----------------------------------------------------------------------------
 // JNI Glue
 // ----------------------------------------------------------------------------
@@ -80,6 +84,7 @@
         { "nGetFd", "(J)I", (void*) SyncFence_getFd },
         { "nWait",  "(JJ)Z", (void*) SyncFence_wait },
         { "nGetSignalTime", "(J)J", (void*) SyncFence_getSignalTime },
+        { "nIncRef", "(J)V", (void*) SyncFence_incRef },
 };
 // clang-format on
 
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index de1ce4e..1504a00 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -52,7 +52,7 @@
 #include <memunreachable/memunreachable.h>
 #include <android-base/strings.h>
 #include "android_os_Debug.h"
-#include <vintf/VintfObject.h>
+#include <vintf/KernelConfigs.h>
 
 namespace android
 {
@@ -1004,10 +1004,9 @@
     } cfg_state = CONFIG_UNKNOWN;
 
     if (cfg_state == CONFIG_UNKNOWN) {
-        auto runtime_info = vintf::VintfObject::GetInstance()->getRuntimeInfo(
-                vintf::RuntimeInfo::FetchFlag::CONFIG_GZ);
-        CHECK(runtime_info != nullptr) << "Kernel configs cannot be fetched. b/151092221";
-        const std::map<std::string, std::string>& configs = runtime_info->kernelConfigs();
+        std::map<std::string, std::string> configs;
+        const status_t result = android::kernelconfigs::LoadKernelConfigs(&configs);
+        CHECK(result == OK) << "Kernel configs could not be fetched. b/151092221";
         std::map<std::string, std::string>::const_iterator it = configs.find("CONFIG_VMAP_STACK");
         cfg_state = (it != configs.end() && it->second == "y") ? CONFIG_SET : CONFIG_UNSET;
     }
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index 477bd09..734b5f4 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -39,7 +39,6 @@
 #include <hwbinder/ProcessState.h>
 #include <nativehelper/ScopedLocalRef.h>
 #include <nativehelper/ScopedUtfChars.h>
-#include <vintf/parse_string.h>
 #include <utils/misc.h>
 
 #include "core_jni_helpers.h"
diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp
index b651711..ce4a337 100644
--- a/core/jni/android_os_VintfObject.cpp
+++ b/core/jni/android_os_VintfObject.cpp
@@ -17,16 +17,14 @@
 #define LOG_TAG "VintfObject"
 //#define LOG_NDEBUG 0
 #include <android-base/logging.h>
-
-#include <vector>
-#include <string>
-
-#include <nativehelper/JNIHelp.h>
 #include <vintf/VintfObject.h>
 #include <vintf/parse_string.h>
 #include <vintf/parse_xml.h>
 
-#include "core_jni_helpers.h"
+#include <vector>
+#include <string>
+
+#include "jni_wrappers.h"
 
 static jclass gString;
 static jclass gHashMapClazz;
@@ -94,10 +92,13 @@
     return toJavaStringArray(env, cStrings);
 }
 
-static jint android_os_VintfObject_verifyBuildAtBoot(JNIEnv* env, jclass) {
+static jint android_os_VintfObject_verifyBuildAtBoot(JNIEnv*, jclass) {
     std::string error;
+    // Use temporary VintfObject, not the shared instance, to release memory
+    // after check.
     int32_t status =
-            VintfObject::GetInstance()
+            VintfObject::Builder()
+                    .build()
                     ->checkCompatibility(&error, ENABLE_ALL_CHECKS.disableAvb().disableKernel());
     if (status)
         LOG(WARNING) << "VintfObject.verifyBuildAtBoot() returns " << status << ": " << error;
@@ -201,4 +202,23 @@
             NELEM(gVintfObjectMethods));
 }
 
-};
+extern int register_android_os_VintfRuntimeInfo(JNIEnv* env);
+
+} // namespace android
+
+jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
+    JNIEnv* env = NULL;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6)) {
+        return JNI_ERR;
+    }
+
+    if (android::register_android_os_VintfObject(env) < 0) {
+        return JNI_ERR;
+    }
+
+    if (android::register_android_os_VintfRuntimeInfo(env) < 0) {
+        return JNI_ERR;
+    }
+
+    return JNI_VERSION_1_6;
+}
diff --git a/core/jni/android_os_VintfRuntimeInfo.cpp b/core/jni/android_os_VintfRuntimeInfo.cpp
index b0271b9..7c2f588 100644
--- a/core/jni/android_os_VintfRuntimeInfo.cpp
+++ b/core/jni/android_os_VintfRuntimeInfo.cpp
@@ -17,23 +17,22 @@
 #define LOG_TAG "VintfRuntimeInfo"
 //#define LOG_NDEBUG 0
 
-#include <nativehelper/JNIHelp.h>
 #include <vintf/VintfObject.h>
 #include <vintf/parse_string.h>
 #include <vintf/parse_xml.h>
 
-#include "core_jni_helpers.h"
+#include "jni_wrappers.h"
 
 namespace android {
 
 using vintf::RuntimeInfo;
 using vintf::VintfObject;
 
-#define MAP_STRING_METHOD(javaMethod, cppString, flags)                                  \
-    static jstring android_os_VintfRuntimeInfo_##javaMethod(JNIEnv* env, jclass clazz) { \
-        std::shared_ptr<const RuntimeInfo> info = VintfObject::GetRuntimeInfo(flags);    \
-        if (info == nullptr) return nullptr;                                             \
-        return env->NewStringUTF((cppString).c_str());                                   \
+#define MAP_STRING_METHOD(javaMethod, cppString, flags)                               \
+    static jstring android_os_VintfRuntimeInfo_##javaMethod(JNIEnv* env, jclass) {    \
+        std::shared_ptr<const RuntimeInfo> info = VintfObject::GetRuntimeInfo(flags); \
+        if (info == nullptr) return nullptr;                                          \
+        return env->NewStringUTF((cppString).c_str());                                \
     }
 
 MAP_STRING_METHOD(getCpuInfo, info->cpuInfo(), RuntimeInfo::FetchFlag::CPU_INFO);
@@ -49,9 +48,7 @@
 MAP_STRING_METHOD(getBootVbmetaAvbVersion, vintf::to_string(info->bootVbmetaAvbVersion()),
                   RuntimeInfo::FetchFlag::AVB);
 
-
-static jlong android_os_VintfRuntimeInfo_getKernelSepolicyVersion(JNIEnv *env, jclass clazz)
-{
+static jlong android_os_VintfRuntimeInfo_getKernelSepolicyVersion(JNIEnv*, jclass) {
     std::shared_ptr<const RuntimeInfo> info =
             VintfObject::GetRuntimeInfo(RuntimeInfo::FetchFlag::POLICYVERS);
     if (info == nullptr) return 0;
diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp
new file mode 100644
index 0000000..d710698
--- /dev/null
+++ b/core/jni/android_tracing_PerfettoDataSource.cpp
@@ -0,0 +1,437 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Perfetto"
+
+#include "android_tracing_PerfettoDataSource.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <perfetto/public/data_source.h>
+#include <perfetto/public/producer.h>
+#include <perfetto/public/protos/trace/test_event.pzc.h>
+#include <perfetto/public/protos/trace/trace_packet.pzc.h>
+#include <perfetto/tracing.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <sstream>
+#include <thread>
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+static struct {
+    jclass clazz;
+    jmethodID createInstance;
+    jmethodID createTlsState;
+    jmethodID createIncrementalState;
+} gPerfettoDataSourceClassInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID init;
+    jmethodID getAndClearAllPendingTracePackets;
+} gTracingContextClassInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID init;
+} gCreateTlsStateArgsClassInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID init;
+} gCreateIncrementalStateArgsClassInfo;
+
+static JavaVM* gVm;
+
+struct TlsState {
+    jobject jobj;
+};
+
+struct IncrementalState {
+    jobject jobj;
+};
+
+static void traceAllPendingPackets(JNIEnv* env, jobject jCtx, PerfettoDsTracerIterator* ctx) {
+    jobjectArray packets =
+            (jobjectArray)env
+                    ->CallObjectMethod(jCtx,
+                                       gTracingContextClassInfo.getAndClearAllPendingTracePackets);
+    if (env->ExceptionOccurred()) {
+        env->ExceptionDescribe();
+        env->ExceptionClear();
+
+        LOG_ALWAYS_FATAL("Failed to call java context finalize method");
+    }
+
+    int packets_count = env->GetArrayLength(packets);
+    for (int i = 0; i < packets_count; i++) {
+        jbyteArray packet_proto_buffer = (jbyteArray)env->GetObjectArrayElement(packets, i);
+
+        jbyte* raw_proto_buffer = env->GetByteArrayElements(packet_proto_buffer, 0);
+        int buffer_size = env->GetArrayLength(packet_proto_buffer);
+
+        struct PerfettoDsRootTracePacket trace_packet;
+        PerfettoDsTracerPacketBegin(ctx, &trace_packet);
+        PerfettoPbMsgAppendBytes(&trace_packet.msg.msg, (const uint8_t*)raw_proto_buffer,
+                                 buffer_size);
+        PerfettoDsTracerPacketEnd(ctx, &trace_packet);
+    }
+}
+
+PerfettoDataSource::PerfettoDataSource(JNIEnv* env, jobject javaDataSource,
+                                       std::string dataSourceName)
+      : dataSourceName(std::move(dataSourceName)),
+        mJavaDataSource(env->NewGlobalRef(javaDataSource)) {}
+
+jobject PerfettoDataSource::newInstance(JNIEnv* env, void* ds_config, size_t ds_config_size,
+                                        PerfettoDsInstanceIndex inst_id) {
+    jbyteArray configArray = env->NewByteArray(ds_config_size);
+
+    void* temp = env->GetPrimitiveArrayCritical((jarray)configArray, 0);
+    memcpy(temp, ds_config, ds_config_size);
+    env->ReleasePrimitiveArrayCritical(configArray, temp, 0);
+
+    jobject instance =
+            env->CallObjectMethod(mJavaDataSource, gPerfettoDataSourceClassInfo.createInstance,
+                                  configArray, inst_id);
+
+    if (env->ExceptionCheck()) {
+        LOGE_EX(env);
+        env->ExceptionClear();
+        LOG_ALWAYS_FATAL("Failed to create new Java Perfetto datasource instance");
+    }
+
+    return instance;
+}
+
+jobject PerfettoDataSource::createTlsStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id) {
+    ScopedLocalRef<jobject> args(env,
+                                 env->NewObject(gCreateTlsStateArgsClassInfo.clazz,
+                                                gCreateTlsStateArgsClassInfo.init, mJavaDataSource,
+                                                inst_id));
+
+    ScopedLocalRef<jobject> tslState(env,
+                                     env->CallObjectMethod(mJavaDataSource,
+                                                           gPerfettoDataSourceClassInfo
+                                                                   .createTlsState,
+                                                           args.get()));
+
+    if (env->ExceptionCheck()) {
+        LOGE_EX(env);
+        env->ExceptionClear();
+        LOG_ALWAYS_FATAL("Failed to create new Java Perfetto incremental state");
+    }
+
+    return env->NewGlobalRef(tslState.get());
+}
+
+jobject PerfettoDataSource::createIncrementalStateGlobalRef(JNIEnv* env,
+                                                            PerfettoDsInstanceIndex inst_id) {
+    ScopedLocalRef<jobject> args(env,
+                                 env->NewObject(gCreateIncrementalStateArgsClassInfo.clazz,
+                                                gCreateIncrementalStateArgsClassInfo.init,
+                                                mJavaDataSource, inst_id));
+
+    ScopedLocalRef<jobject> incrementalState(env,
+                                             env->CallObjectMethod(mJavaDataSource,
+                                                                   gPerfettoDataSourceClassInfo
+                                                                           .createIncrementalState,
+                                                                   args.get()));
+
+    if (env->ExceptionCheck()) {
+        LOGE_EX(env);
+        env->ExceptionClear();
+        LOG_ALWAYS_FATAL("Failed to create Java Perfetto incremental state");
+    }
+
+    return env->NewGlobalRef(incrementalState.get());
+}
+
+void PerfettoDataSource::trace(JNIEnv* env, jobject traceFunction) {
+    PERFETTO_DS_TRACE(dataSource, ctx) {
+        TlsState* tls_state =
+                reinterpret_cast<TlsState*>(PerfettoDsGetCustomTls(&dataSource, &ctx));
+        IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(
+                PerfettoDsGetIncrementalState(&dataSource, &ctx));
+
+        ScopedLocalRef<jobject> jCtx(env,
+                                     env->NewObject(gTracingContextClassInfo.clazz,
+                                                    gTracingContextClassInfo.init, &ctx,
+                                                    tls_state->jobj, incr_state->jobj));
+
+        jclass objclass = env->GetObjectClass(traceFunction);
+        jmethodID method =
+                env->GetMethodID(objclass, "trace", "(Landroid/tracing/perfetto/TracingContext;)V");
+        if (method == 0) {
+            LOG_ALWAYS_FATAL("Failed to get method id");
+        }
+
+        env->ExceptionClear();
+
+        env->CallVoidMethod(traceFunction, method, jCtx.get());
+        if (env->ExceptionOccurred()) {
+            env->ExceptionDescribe();
+            env->ExceptionClear();
+            LOG_ALWAYS_FATAL("Failed to call java trace method");
+        }
+
+        traceAllPendingPackets(env, jCtx.get(), &ctx);
+    }
+}
+
+void PerfettoDataSource::flushAll() {
+    PERFETTO_DS_TRACE(dataSource, ctx) {
+        PerfettoDsTracerFlush(&ctx, nullptr, nullptr);
+    }
+}
+
+PerfettoDataSource::~PerfettoDataSource() {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    env->DeleteWeakGlobalRef(mJavaDataSource);
+}
+
+jlong nativeCreate(JNIEnv* env, jclass clazz, jobject javaDataSource, jstring name) {
+    const char* nativeString = env->GetStringUTFChars(name, 0);
+    PerfettoDataSource* dataSource = new PerfettoDataSource(env, javaDataSource, nativeString);
+    env->ReleaseStringUTFChars(name, nativeString);
+
+    dataSource->incStrong((void*)nativeCreate);
+
+    return reinterpret_cast<jlong>(dataSource);
+}
+
+void nativeDestroy(void* ptr) {
+    PerfettoDataSource* dataSource = reinterpret_cast<PerfettoDataSource*>(ptr);
+    dataSource->decStrong((void*)nativeCreate);
+}
+
+static jlong nativeGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&nativeDestroy));
+}
+
+void nativeTrace(JNIEnv* env, jclass clazz, jlong dataSourcePtr, jobject traceFunctionInterface) {
+    sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+
+    datasource->trace(env, traceFunctionInterface);
+}
+
+void nativeFlush(JNIEnv* env, jclass clazz, jobject jCtx, jlong ctxPtr) {
+    auto* ctx = reinterpret_cast<struct PerfettoDsTracerIterator*>(ctxPtr);
+    traceAllPendingPackets(env, jCtx, ctx);
+    PerfettoDsTracerFlush(ctx, nullptr, nullptr);
+}
+
+void nativeFlushAll(JNIEnv* env, jclass clazz, jlong ptr) {
+    sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(ptr);
+    datasource->flushAll();
+}
+
+void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
+                              int buffer_exhausted_policy) {
+    sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(datasource_ptr);
+
+    struct PerfettoDsParams params = PerfettoDsParamsDefault();
+    params.buffer_exhausted_policy = (PerfettoDsBufferExhaustedPolicy)buffer_exhausted_policy;
+
+    params.user_arg = reinterpret_cast<void*>(datasource.get());
+
+    params.on_setup_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex inst_id,
+                            void* ds_config, size_t ds_config_size, void* user_arg,
+                            struct PerfettoDsOnSetupArgs*) -> void* {
+        JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+        auto* datasource = reinterpret_cast<PerfettoDataSource*>(user_arg);
+
+        ScopedLocalRef<jobject> java_data_source_instance(env,
+                                                          datasource->newInstance(env, ds_config,
+                                                                                  ds_config_size,
+                                                                                  inst_id));
+
+        auto* datasource_instance =
+                new PerfettoDataSourceInstance(env, java_data_source_instance.get(), inst_id);
+
+        return static_cast<void*>(datasource_instance);
+    };
+
+    params.on_create_tls_cb = [](struct PerfettoDsImpl* ds_impl, PerfettoDsInstanceIndex inst_id,
+                                 struct PerfettoDsTracerImpl* tracer, void* user_arg) -> void* {
+        JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+        auto* datasource = reinterpret_cast<PerfettoDataSource*>(user_arg);
+
+        jobject java_tls_state = datasource->createTlsStateGlobalRef(env, inst_id);
+
+        auto* tls_state = new TlsState(java_tls_state);
+
+        return static_cast<void*>(tls_state);
+    };
+
+    params.on_delete_tls_cb = [](void* ptr) {
+        JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+        TlsState* tls_state = reinterpret_cast<TlsState*>(ptr);
+        env->DeleteGlobalRef(tls_state->jobj);
+        delete tls_state;
+    };
+
+    params.on_create_incr_cb = [](struct PerfettoDsImpl* ds_impl, PerfettoDsInstanceIndex inst_id,
+                                  struct PerfettoDsTracerImpl* tracer, void* user_arg) -> void* {
+        JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+        auto* datasource = reinterpret_cast<PerfettoDataSource*>(user_arg);
+        jobject java_incr_state = datasource->createIncrementalStateGlobalRef(env, inst_id);
+
+        auto* incr_state = new IncrementalState(java_incr_state);
+        return static_cast<void*>(incr_state);
+    };
+
+    params.on_delete_incr_cb = [](void* ptr) {
+        JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+        IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(ptr);
+        env->DeleteGlobalRef(incr_state->jobj);
+        delete incr_state;
+    };
+
+    params.on_start_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex, void*, void* inst_ctx,
+                            struct PerfettoDsOnStartArgs*) {
+        JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+        auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+        datasource_instance->onStart(env);
+    };
+
+    params.on_flush_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex, void*, void* inst_ctx,
+                            struct PerfettoDsOnFlushArgs*) {
+        JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+        auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+        datasource_instance->onFlush(env);
+    };
+
+    params.on_stop_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex inst_id, void* user_arg,
+                           void* inst_ctx, struct PerfettoDsOnStopArgs*) {
+        JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+
+        auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+        datasource_instance->onStop(env);
+    };
+
+    params.on_destroy_cb = [](struct PerfettoDsImpl* ds_impl, void* user_arg,
+                              void* inst_ctx) -> void {
+        auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+        delete datasource_instance;
+    };
+
+    PerfettoDsRegister(&datasource->dataSource, datasource->dataSourceName.c_str(), params);
+}
+
+jobject nativeGetPerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr,
+                                        PerfettoDsInstanceIndex instance_idx) {
+    sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+    auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(
+            PerfettoDsImplGetInstanceLocked(datasource->dataSource.impl, instance_idx));
+
+    if (datasource_instance == nullptr) {
+        // datasource instance doesn't exist
+        return nullptr;
+    }
+
+    return datasource_instance->GetJavaDataSourceInstance();
+}
+
+void nativeReleasePerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr,
+                                         PerfettoDsInstanceIndex instance_idx) {
+    sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+    PerfettoDsImplReleaseInstanceLocked(datasource->dataSource.impl, instance_idx);
+}
+
+const JNINativeMethod gMethods[] = {
+        /* name, signature, funcPtr */
+        {"nativeCreate", "(Landroid/tracing/perfetto/DataSource;Ljava/lang/String;)J",
+         (void*)nativeCreate},
+        {"nativeTrace", "(JLandroid/tracing/perfetto/TraceFunction;)V", (void*)nativeTrace},
+        {"nativeFlushAll", "(J)V", (void*)nativeFlushAll},
+        {"nativeGetFinalizer", "()J", (void*)nativeGetFinalizer},
+        {"nativeRegisterDataSource", "(JI)V", (void*)nativeRegisterDataSource},
+        {"nativeGetPerfettoInstanceLocked", "(JI)Landroid/tracing/perfetto/DataSourceInstance;",
+         (void*)nativeGetPerfettoInstanceLocked},
+        {"nativeReleasePerfettoInstanceLocked", "(JI)V",
+         (void*)nativeReleasePerfettoInstanceLocked},
+};
+
+const JNINativeMethod gMethodsTracingContext[] = {
+        /* name, signature, funcPtr */
+        {"nativeFlush", "(Landroid/tracing/perfetto/TracingContext;J)V", (void*)nativeFlush},
+};
+
+int register_android_tracing_PerfettoDataSource(JNIEnv* env) {
+    int res = jniRegisterNativeMethods(env, "android/tracing/perfetto/DataSource", gMethods,
+                                       NELEM(gMethods));
+
+    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+
+    res = jniRegisterNativeMethods(env, "android/tracing/perfetto/TracingContext",
+                                   gMethodsTracingContext, NELEM(gMethodsTracingContext));
+
+    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+
+    if (env->GetJavaVM(&gVm) != JNI_OK) {
+        LOG_ALWAYS_FATAL("Failed to get JavaVM from JNIEnv: %p", env);
+    }
+
+    jclass clazz = env->FindClass("android/tracing/perfetto/DataSource");
+    gPerfettoDataSourceClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+    gPerfettoDataSourceClassInfo.createInstance =
+            env->GetMethodID(gPerfettoDataSourceClassInfo.clazz, "createInstance",
+                             "([BI)Landroid/tracing/perfetto/DataSourceInstance;");
+    gPerfettoDataSourceClassInfo.createTlsState =
+            env->GetMethodID(gPerfettoDataSourceClassInfo.clazz, "createTlsState",
+                             "(Landroid/tracing/perfetto/CreateTlsStateArgs;)Ljava/lang/Object;");
+    gPerfettoDataSourceClassInfo.createIncrementalState =
+            env->GetMethodID(gPerfettoDataSourceClassInfo.clazz, "createIncrementalState",
+                             "(Landroid/tracing/perfetto/CreateIncrementalStateArgs;)Ljava/lang/"
+                             "Object;");
+
+    clazz = env->FindClass("android/tracing/perfetto/TracingContext");
+    gTracingContextClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+    gTracingContextClassInfo.init = env->GetMethodID(gTracingContextClassInfo.clazz, "<init>",
+                                                     "(JLjava/lang/Object;Ljava/lang/Object;)V");
+    gTracingContextClassInfo.getAndClearAllPendingTracePackets =
+            env->GetMethodID(gTracingContextClassInfo.clazz, "getAndClearAllPendingTracePackets",
+                             "()[[B");
+
+    clazz = env->FindClass("android/tracing/perfetto/CreateTlsStateArgs");
+    gCreateTlsStateArgsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+    gCreateTlsStateArgsClassInfo.init =
+            env->GetMethodID(gCreateTlsStateArgsClassInfo.clazz, "<init>",
+                             "(Landroid/tracing/perfetto/DataSource;I)V");
+
+    clazz = env->FindClass("android/tracing/perfetto/CreateIncrementalStateArgs");
+    gCreateIncrementalStateArgsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+    gCreateIncrementalStateArgsClassInfo.init =
+            env->GetMethodID(gCreateIncrementalStateArgsClassInfo.clazz, "<init>",
+                             "(Landroid/tracing/perfetto/DataSource;I)V");
+
+    return 0;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/android_tracing_PerfettoDataSource.h b/core/jni/android_tracing_PerfettoDataSource.h
new file mode 100644
index 0000000..4ddf1d8
--- /dev/null
+++ b/core/jni/android_tracing_PerfettoDataSource.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Perfetto"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <perfetto/public/data_source.h>
+#include <perfetto/public/producer.h>
+#include <perfetto/public/protos/trace/test_event.pzc.h>
+#include <perfetto/public/protos/trace/trace_packet.pzc.h>
+#include <perfetto/tracing.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <sstream>
+#include <thread>
+
+#include "android_tracing_PerfettoDataSourceInstance.h"
+#include "core_jni_helpers.h"
+
+namespace android {
+
+class PerfettoDataSource : public virtual RefBase {
+public:
+    const std::string dataSourceName;
+    struct PerfettoDs dataSource = PERFETTO_DS_INIT();
+
+    PerfettoDataSource(JNIEnv* env, jobject java_data_source, std::string data_source_name);
+    ~PerfettoDataSource();
+
+    jobject newInstance(JNIEnv* env, void* ds_config, size_t ds_config_size,
+                        PerfettoDsInstanceIndex inst_id);
+
+    jobject createTlsStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id);
+    jobject createIncrementalStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id);
+    void trace(JNIEnv* env, jobject trace_function);
+    void flushAll();
+
+private:
+    jobject mJavaDataSource;
+    std::map<PerfettoDsInstanceIndex, PerfettoDataSourceInstance*> mInstances;
+};
+
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/android_tracing_PerfettoDataSourceInstance.cpp b/core/jni/android_tracing_PerfettoDataSourceInstance.cpp
new file mode 100644
index 0000000..e659bf1
--- /dev/null
+++ b/core/jni/android_tracing_PerfettoDataSourceInstance.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Perfetto"
+
+#include "android_tracing_PerfettoDataSourceInstance.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <perfetto/public/data_source.h>
+#include <perfetto/public/producer.h>
+#include <perfetto/public/protos/trace/test_event.pzc.h>
+#include <perfetto/public/protos/trace/trace_packet.pzc.h>
+#include <perfetto/tracing.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <sstream>
+#include <thread>
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+static struct {
+    jclass clazz;
+    jmethodID init;
+} gStartCallbackArgumentsClassInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID init;
+} gFlushCallbackArgumentsClassInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID init;
+} gStopCallbackArgumentsClassInfo;
+
+static JavaVM* gVm;
+
+void callJavaMethodWithArgsObject(JNIEnv* env, jobject classRef, jmethodID method, jobject args) {
+    ScopedLocalRef<jobject> localClassRef(env, env->NewLocalRef(classRef));
+
+    if (localClassRef == nullptr) {
+        ALOGE("Weak reference went out of scope");
+        return;
+    }
+
+    env->CallVoidMethod(localClassRef.get(), method, args);
+
+    if (env->ExceptionCheck()) {
+        env->ExceptionDescribe();
+        LOGE_EX(env);
+        env->ExceptionClear();
+    }
+}
+
+PerfettoDataSourceInstance::PerfettoDataSourceInstance(JNIEnv* env, jobject javaDataSourceInstance,
+                                                       PerfettoDsInstanceIndex inst_idx)
+      : inst_idx(inst_idx), mJavaDataSourceInstance(env->NewGlobalRef(javaDataSourceInstance)) {}
+
+PerfettoDataSourceInstance::~PerfettoDataSourceInstance() {
+    JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
+    env->DeleteGlobalRef(mJavaDataSourceInstance);
+}
+
+void PerfettoDataSourceInstance::onStart(JNIEnv* env) {
+    ScopedLocalRef<jobject> args(env,
+                                 env->NewObject(gStartCallbackArgumentsClassInfo.clazz,
+                                                gStartCallbackArgumentsClassInfo.init));
+    jclass cls = env->GetObjectClass(mJavaDataSourceInstance);
+    jmethodID mid = env->GetMethodID(cls, "onStart",
+                                     "(Landroid/tracing/perfetto/StartCallbackArguments;)V");
+
+    callJavaMethodWithArgsObject(env, mJavaDataSourceInstance, mid, args.get());
+}
+
+void PerfettoDataSourceInstance::onFlush(JNIEnv* env) {
+    ScopedLocalRef<jobject> args(env,
+                                 env->NewObject(gFlushCallbackArgumentsClassInfo.clazz,
+                                                gFlushCallbackArgumentsClassInfo.init));
+    jclass cls = env->GetObjectClass(mJavaDataSourceInstance);
+    jmethodID mid = env->GetMethodID(cls, "onFlush",
+                                     "(Landroid/tracing/perfetto/FlushCallbackArguments;)V");
+
+    callJavaMethodWithArgsObject(env, mJavaDataSourceInstance, mid, args.get());
+}
+
+void PerfettoDataSourceInstance::onStop(JNIEnv* env) {
+    ScopedLocalRef<jobject> args(env,
+                                 env->NewObject(gStopCallbackArgumentsClassInfo.clazz,
+                                                gStopCallbackArgumentsClassInfo.init));
+    jclass cls = env->GetObjectClass(mJavaDataSourceInstance);
+    jmethodID mid =
+            env->GetMethodID(cls, "onStop", "(Landroid/tracing/perfetto/StopCallbackArguments;)V");
+
+    callJavaMethodWithArgsObject(env, mJavaDataSourceInstance, mid, args.get());
+}
+
+int register_android_tracing_PerfettoDataSourceInstance(JNIEnv* env) {
+    if (env->GetJavaVM(&gVm) != JNI_OK) {
+        LOG_ALWAYS_FATAL("Failed to get JavaVM from JNIEnv: %p", env);
+    }
+
+    jclass clazz = env->FindClass("android/tracing/perfetto/StartCallbackArguments");
+    gStartCallbackArgumentsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+    gStartCallbackArgumentsClassInfo.init =
+            env->GetMethodID(gStartCallbackArgumentsClassInfo.clazz, "<init>", "()V");
+
+    clazz = env->FindClass("android/tracing/perfetto/FlushCallbackArguments");
+    gFlushCallbackArgumentsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+    gFlushCallbackArgumentsClassInfo.init =
+            env->GetMethodID(gFlushCallbackArgumentsClassInfo.clazz, "<init>", "()V");
+
+    clazz = env->FindClass("android/tracing/perfetto/StopCallbackArguments");
+    gStopCallbackArgumentsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+    gStopCallbackArgumentsClassInfo.init =
+            env->GetMethodID(gStopCallbackArgumentsClassInfo.clazz, "<init>", "()V");
+
+    return 0;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/android_tracing_PerfettoDataSourceInstance.h b/core/jni/android_tracing_PerfettoDataSourceInstance.h
new file mode 100644
index 0000000..d577655
--- /dev/null
+++ b/core/jni/android_tracing_PerfettoDataSourceInstance.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Perfetto"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <perfetto/public/data_source.h>
+#include <perfetto/public/producer.h>
+#include <perfetto/public/protos/trace/test_event.pzc.h>
+#include <perfetto/public/protos/trace/trace_packet.pzc.h>
+#include <perfetto/tracing.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <sstream>
+#include <thread>
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+class PerfettoDataSourceInstance {
+public:
+    PerfettoDataSourceInstance(JNIEnv* env, jobject javaDataSourceInstance,
+                               PerfettoDsInstanceIndex inst_idx);
+    ~PerfettoDataSourceInstance();
+
+    void onStart(JNIEnv* env);
+    void onFlush(JNIEnv* env);
+    void onStop(JNIEnv* env);
+
+    jobject GetJavaDataSourceInstance() {
+        return mJavaDataSourceInstance;
+    }
+
+    PerfettoDsInstanceIndex getIndex() {
+        return inst_idx;
+    }
+
+private:
+    PerfettoDsInstanceIndex inst_idx;
+    jobject mJavaDataSourceInstance;
+};
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/android_tracing_PerfettoProducer.cpp b/core/jni/android_tracing_PerfettoProducer.cpp
new file mode 100644
index 0000000..ce72f58
--- /dev/null
+++ b/core/jni/android_tracing_PerfettoProducer.cpp
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Perfetto"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <perfetto/public/data_source.h>
+#include <perfetto/public/producer.h>
+#include <perfetto/public/protos/trace/test_event.pzc.h>
+#include <perfetto/public/protos/trace/trace_packet.pzc.h>
+#include <perfetto/tracing.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <sstream>
+#include <thread>
+
+#include "android_tracing_PerfettoDataSource.h"
+#include "core_jni_helpers.h"
+
+namespace android {
+
+void perfettoProducerInit(JNIEnv* env, jclass clazz, int backends) {
+    struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT();
+    args.backends = (PerfettoBackendTypes)backends;
+    PerfettoProducerInit(args);
+}
+
+const JNINativeMethod gMethods[] = {
+        /* name, signature, funcPtr */
+        {"nativePerfettoProducerInit", "(I)V", (void*)perfettoProducerInit},
+};
+
+int register_android_tracing_PerfettoProducer(JNIEnv* env) {
+    int res = jniRegisterNativeMethods(env, "android/tracing/perfetto/Producer", gMethods,
+                                       NELEM(gMethods));
+
+    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+
+    return 0;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 7af69f2..d2e58bb 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -28,6 +28,7 @@
 #include <meminfo/sysmeminfo.h>
 #include <processgroup/processgroup.h>
 #include <processgroup/sched_policy.h>
+#include <android-base/logging.h>
 #include <android-base/unique_fd.h>
 
 #include <algorithm>
@@ -232,6 +233,31 @@
     }
 }
 
+// Look up the user ID of a process in /proc/${pid}/status. The Uid: line is present in
+// /proc/${pid}/status since at least kernel v2.5.
+static int uid_from_pid(int pid)
+{
+    int uid = -1;
+    std::array<char, 64> path;
+    int res = snprintf(path.data(), path.size(), "/proc/%d/status", pid);
+    if (res < 0 || res >= static_cast<int>(path.size())) {
+        DCHECK(false);
+        return uid;
+    }
+    FILE* f = fopen(path.data(), "r");
+    if (!f) {
+        return uid;
+    }
+    char line[256];
+    while (fgets(line, sizeof(line), f)) {
+        if (sscanf(line, "Uid: %d", &uid) == 1) {
+            break;
+        }
+    }
+    fclose(f);
+    return uid;
+}
+
 void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jint grp)
 {
     ALOGV("%s pid=%d grp=%" PRId32, __func__, pid, grp);
@@ -275,7 +301,12 @@
         }
     }
 
-    if (!SetProcessProfilesCached(0, pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}))
+    const int uid = uid_from_pid(pid);
+    if (uid < 0) {
+        signalExceptionForGroupError(env, ESRCH, pid);
+        return;
+    }
+    if (!SetProcessProfilesCached(uid, pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}))
         signalExceptionForGroupError(env, errno ? errno : EPERM, pid);
 }
 
@@ -1134,12 +1165,11 @@
 
 static jlongArray android_os_Process_getRss(JNIEnv* env, jobject clazz, jint pid)
 {
-    // total, file, anon, swap
-    jlong rss[4] = {0, 0, 0, 0};
+    // total, file, anon, swap, shmem
+    jlong rss[5] = {0, 0, 0, 0, 0};
     std::string status_path =
             android::base::StringPrintf("/proc/%d/status", pid);
     UniqueFile file = MakeUniqueFile(status_path.c_str(), "re");
-
     char line[256];
     while (file != nullptr && fgets(line, sizeof(line), file.get())) {
         jlong v;
@@ -1151,17 +1181,18 @@
             rss[2] = v;
         } else if ( sscanf(line, "VmSwap: %" SCNd64 " kB", &v) == 1) {
             rss[3] = v;
+        } else if ( sscanf(line, "RssShmem: %" SCNd64 " kB", &v) == 1) {
+            rss[4] = v;
         }
     }
 
-    jlongArray rssArray = env->NewLongArray(4);
+    jlongArray rssArray = env->NewLongArray(5);
     if (rssArray == NULL) {
         jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
         return NULL;
     }
 
-    env->SetLongArrayRegion(rssArray, 0, 4, rss);
-
+    env->SetLongArrayRegion(rssArray, 0, 5, rss);
     return rssArray;
 }
 
diff --git a/core/jni/android_view_PointerIcon.cpp b/core/jni/android_view_PointerIcon.cpp
index d57ec15..c6a3b52 100644
--- a/core/jni/android_view_PointerIcon.cpp
+++ b/core/jni/android_view_PointerIcon.cpp
@@ -18,12 +18,12 @@
 
 #include "android_view_PointerIcon.h"
 
+#include <android-base/logging.h>
 #include <android/graphics/bitmap.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedLocalRef.h>
-#include <utils/Log.h>
 
 #include "core_jni_helpers.h"
 
@@ -37,90 +37,41 @@
     jfieldID mHotSpotY;
     jfieldID mBitmapFrames;
     jfieldID mDurationPerFrame;
-    jmethodID getSystemIcon;
-    jmethodID load;
 } gPointerIconClassInfo;
 
 
 // --- Global Functions ---
 
-jobject android_view_PointerIcon_getSystemIcon(JNIEnv* env, jobject contextObj,
-                                               PointerIconStyle style) {
-    jobject pointerIconObj = env->CallStaticObjectMethod(gPointerIconClassInfo.clazz,
-            gPointerIconClassInfo.getSystemIcon, contextObj, style);
-    if (env->ExceptionCheck()) {
-        ALOGW("An exception occurred while getting a pointer icon with style %d.", style);
-        LOGW_EX(env);
-        env->ExceptionClear();
-        return NULL;
-    }
-    return pointerIconObj;
-}
-
-status_t android_view_PointerIcon_load(JNIEnv* env, jobject pointerIconObj, jobject contextObj,
-        PointerIcon* outPointerIcon) {
-    outPointerIcon->reset();
-
+PointerIcon android_view_PointerIcon_toNative(JNIEnv* env, jobject pointerIconObj) {
     if (!pointerIconObj) {
-        return OK;
+        LOG(FATAL) << __func__ << ": pointerIconObj is null";
     }
-
-    ScopedLocalRef<jobject> loadedPointerIconObj(env, env->CallObjectMethod(pointerIconObj,
-            gPointerIconClassInfo.load, contextObj));
-    if (env->ExceptionCheck() || !loadedPointerIconObj.get()) {
-        ALOGW("An exception occurred while loading a pointer icon.");
-        LOGW_EX(env);
-        env->ExceptionClear();
-        return UNKNOWN_ERROR;
-    }
-    return android_view_PointerIcon_getLoadedIcon(env, loadedPointerIconObj.get(), outPointerIcon);
-}
-
-status_t android_view_PointerIcon_getLoadedIcon(JNIEnv* env, jobject pointerIconObj,
-        PointerIcon* outPointerIcon) {
-    if (!pointerIconObj) {
-        return BAD_VALUE;
-    }
-    outPointerIcon->style = static_cast<PointerIconStyle>(
+    PointerIcon icon;
+    icon.style = static_cast<PointerIconStyle>(
             env->GetIntField(pointerIconObj, gPointerIconClassInfo.mType));
-    outPointerIcon->hotSpotX = env->GetFloatField(pointerIconObj, gPointerIconClassInfo.mHotSpotX);
-    outPointerIcon->hotSpotY = env->GetFloatField(pointerIconObj, gPointerIconClassInfo.mHotSpotY);
+    icon.hotSpotX = env->GetFloatField(pointerIconObj, gPointerIconClassInfo.mHotSpotX);
+    icon.hotSpotY = env->GetFloatField(pointerIconObj, gPointerIconClassInfo.mHotSpotY);
 
     ScopedLocalRef<jobject> bitmapObj(
             env, env->GetObjectField(pointerIconObj, gPointerIconClassInfo.mBitmap));
     if (bitmapObj.get()) {
-        outPointerIcon->bitmap = graphics::Bitmap(env, bitmapObj.get());
+        icon.bitmap = graphics::Bitmap(env, bitmapObj.get());
     }
 
     ScopedLocalRef<jobjectArray> bitmapFramesObj(env, reinterpret_cast<jobjectArray>(
             env->GetObjectField(pointerIconObj, gPointerIconClassInfo.mBitmapFrames)));
     if (bitmapFramesObj.get()) {
-        outPointerIcon->durationPerFrame = env->GetIntField(
-                pointerIconObj, gPointerIconClassInfo.mDurationPerFrame);
+        icon.durationPerFrame =
+                env->GetIntField(pointerIconObj, gPointerIconClassInfo.mDurationPerFrame);
         jsize size = env->GetArrayLength(bitmapFramesObj.get());
-        outPointerIcon->bitmapFrames.resize(size);
+        icon.bitmapFrames.resize(size);
         for (jsize i = 0; i < size; ++i) {
             ScopedLocalRef<jobject> bitmapObj(env, env->GetObjectArrayElement(bitmapFramesObj.get(), i));
-            outPointerIcon->bitmapFrames[i] = graphics::Bitmap(env, bitmapObj.get());
+            icon.bitmapFrames[i] = graphics::Bitmap(env, bitmapObj.get());
         }
     }
 
-    return OK;
-}
-
-status_t android_view_PointerIcon_loadSystemIcon(JNIEnv* env, jobject contextObj,
-                                                 PointerIconStyle style,
-                                                 PointerIcon* outPointerIcon) {
-    jobject pointerIconObj = android_view_PointerIcon_getSystemIcon(env, contextObj, style);
-    if (!pointerIconObj) {
-        outPointerIcon->reset();
-        return UNKNOWN_ERROR;
-    }
-
-    status_t status = android_view_PointerIcon_load(env, pointerIconObj,
-            contextObj, outPointerIcon);
-    env->DeleteLocalRef(pointerIconObj);
-    return status;
+    return icon;
 }
 
 // --- JNI Registration ---
@@ -147,12 +98,6 @@
     gPointerIconClassInfo.mDurationPerFrame = GetFieldIDOrDie(env, gPointerIconClassInfo.clazz,
             "mDurationPerFrame", "I");
 
-    gPointerIconClassInfo.getSystemIcon = GetStaticMethodIDOrDie(env, gPointerIconClassInfo.clazz,
-            "getSystemIcon", "(Landroid/content/Context;I)Landroid/view/PointerIcon;");
-
-    gPointerIconClassInfo.load = GetMethodIDOrDie(env, gPointerIconClassInfo.clazz,
-            "load", "(Landroid/content/Context;)Landroid/view/PointerIcon;");
-
     return 0;
 }
 
diff --git a/core/jni/android_view_PointerIcon.h b/core/jni/android_view_PointerIcon.h
index f3eaad3..ee446fb 100644
--- a/core/jni/android_view_PointerIcon.h
+++ b/core/jni/android_view_PointerIcon.h
@@ -52,24 +52,12 @@
     }
 };
 
-/* Gets a system pointer icon with the specified style. */
-extern jobject android_view_PointerIcon_getSystemIcon(JNIEnv* env, jobject contextObj,
-                                                      PointerIconStyle style);
-
-/* Loads the bitmap associated with a pointer icon.
- * If pointerIconObj is NULL, returns OK and a pointer icon with POINTER_ICON_STYLE_NULL. */
-extern status_t android_view_PointerIcon_load(JNIEnv* env, jobject pointerIconObj,
-                                              jobject contextObj, PointerIcon* outPointerIcon);
-
-/* Obtain the data of pointerIconObj and put to outPointerIcon. */
-extern status_t android_view_PointerIcon_getLoadedIcon(JNIEnv* env, jobject pointerIconObj,
-                                                       PointerIcon* outPointerIcon);
-
-/* Loads the bitmap associated with a pointer icon by style.
- * If pointerIconObj is NULL, returns OK and a pointer icon with POINTER_ICON_STYLE_NULL. */
-extern status_t android_view_PointerIcon_loadSystemIcon(JNIEnv* env, jobject contextObj,
-                                                        PointerIconStyle style,
-                                                        PointerIcon* outPointerIcon);
+/*
+ * Obtain the data of the Java pointerIconObj into a native PointerIcon.
+ *
+ * The pointerIconObj must not be null.
+ */
+PointerIcon android_view_PointerIcon_toNative(JNIEnv* env, jobject pointerIconObj);
 
 } // namespace android
 
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index db42246..98f409a 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -231,6 +231,16 @@
 
 static struct {
     jclass clazz;
+    jmethodID accept;
+} gConsumerClassInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID ctor;
+} gTransactionStatsClassInfo;
+
+static struct {
+    jclass clazz;
     jmethodID ctor;
     jfieldID format;
     jfieldID alphaInterpretation;
@@ -317,6 +327,54 @@
     }
 };
 
+class TransactionCompletedListenerWrapper {
+public:
+    explicit TransactionCompletedListenerWrapper(JNIEnv* env, jobject object) {
+        env->GetJavaVM(&mVm);
+        mTransactionCompletedListenerObject = env->NewGlobalRef(object);
+        LOG_ALWAYS_FATAL_IF(!mTransactionCompletedListenerObject, "Failed to make global ref");
+    }
+
+    ~TransactionCompletedListenerWrapper() {
+        getenv()->DeleteGlobalRef(mTransactionCompletedListenerObject);
+    }
+
+    void callback(nsecs_t latchTime, const sp<Fence>& presentFence,
+                  const std::vector<SurfaceControlStats>& /*stats*/) {
+        JNIEnv* env = getenv();
+        // Adding a strong reference for java SyncFence
+        if (presentFence) {
+            presentFence->incStrong(0);
+        }
+
+        jobject stats =
+                env->NewObject(gTransactionStatsClassInfo.clazz, gTransactionStatsClassInfo.ctor,
+                               latchTime, presentFence.get());
+        env->CallVoidMethod(mTransactionCompletedListenerObject, gConsumerClassInfo.accept, stats);
+        env->DeleteLocalRef(stats);
+        DieIfException(env, "Uncaught exception in TransactionCompletedListener.");
+    }
+
+    static void transactionCallbackThunk(void* context, nsecs_t latchTime,
+                                         const sp<Fence>& presentFence,
+                                         const std::vector<SurfaceControlStats>& stats) {
+        TransactionCompletedListenerWrapper* listener =
+                reinterpret_cast<TransactionCompletedListenerWrapper*>(context);
+        listener->callback(latchTime, presentFence, stats);
+        delete listener;
+    }
+
+private:
+    jobject mTransactionCompletedListenerObject;
+    JavaVM* mVm;
+
+    JNIEnv* getenv() {
+        JNIEnv* env;
+        mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
+        return env;
+    }
+};
+
 class WindowInfosReportedListenerWrapper : public gui::BnWindowInfosReportedListener {
 public:
     explicit WindowInfosReportedListenerWrapper(JNIEnv* env, jobject listener) {
@@ -1879,10 +1937,16 @@
 
     FrameTimelineInfo ftInfo;
     ftInfo.vsyncId = frameTimelineVsyncId;
-    ftInfo.inputEventId = android::os::IInputConstants::INVALID_INPUT_EVENT_ID;
     transaction->setFrameTimelineInfo(ftInfo);
 }
 
+static void nativeSetDesiredPresentTimeNanos(JNIEnv* env, jclass clazz, jlong transactionObj,
+                                             jlong desiredPresentTimeNanos) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+    transaction->setDesiredPresentTime(desiredPresentTimeNanos);
+}
+
 static void nativeAddTransactionCommittedListener(JNIEnv* env, jclass clazz, jlong transactionObj,
                                                   jobject transactionCommittedListenerObject) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -1894,6 +1958,17 @@
                                                  context);
 }
 
+static void nativeAddTransactionCompletedListener(JNIEnv* env, jclass clazz, jlong transactionObj,
+                                                  jobject transactionCompletedListenerObject) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+    void* context =
+            new TransactionCompletedListenerWrapper(env, transactionCompletedListenerObject);
+    transaction->addTransactionCompletedCallback(TransactionCompletedListenerWrapper::
+                                                         transactionCallbackThunk,
+                                                 context);
+}
+
 static void nativeSetTrustedPresentationCallback(JNIEnv* env, jclass clazz, jlong transactionObj,
                                                  jlong nativeObject,
                                                  jlong trustedPresentationCallbackObject,
@@ -2318,6 +2393,8 @@
             (void*)nativeSurfaceFlushJankData },
     {"nativeAddTransactionCommittedListener", "(JLandroid/view/SurfaceControl$TransactionCommittedListener;)V",
             (void*) nativeAddTransactionCommittedListener },
+    {"nativeAddTransactionCompletedListener", "(JLjava/util/function/Consumer;)V",
+            (void*) nativeAddTransactionCompletedListener },
     {"nativeSetTrustedPresentationCallback", "(JJJLandroid/view/SurfaceControl$TrustedPresentationThresholds;)V",
             (void*) nativeSetTrustedPresentationCallback },
     {"nativeClearTrustedPresentationCallback", "(JJ)V",
@@ -2337,6 +2414,8 @@
     {"getNativeTrustedPresentationCallbackFinalizer", "()J", (void*)getNativeTrustedPresentationCallbackFinalizer },
     {"nativeGetStalledTransactionInfo", "(I)Landroid/gui/StalledTransactionInfo;",
             (void*) nativeGetStalledTransactionInfo },
+    {"nativeSetDesiredPresentTimeNanos", "(JJ)V",
+            (void*) nativeSetDesiredPresentTimeNanos },
         // clang-format on
 };
 
@@ -2539,6 +2618,16 @@
     gTransactionCommittedListenerClassInfo.onTransactionCommitted =
             GetMethodIDOrDie(env, transactionCommittedListenerClazz, "onTransactionCommitted",
                              "()V");
+    jclass consumerClazz = FindClassOrDie(env, "java/util/function/Consumer");
+    gConsumerClassInfo.clazz = MakeGlobalRefOrDie(env, consumerClazz);
+    gConsumerClassInfo.accept =
+            GetMethodIDOrDie(env, consumerClazz, "accept", "(Ljava/lang/Object;)V");
+
+    jclass transactionStatsClazz =
+            FindClassOrDie(env, "android/view/SurfaceControl$TransactionStats");
+    gTransactionStatsClassInfo.clazz = MakeGlobalRefOrDie(env, transactionStatsClazz);
+    gTransactionStatsClassInfo.ctor =
+            GetMethodIDOrDie(env, gTransactionStatsClassInfo.clazz, "<init>", "(JJ)V");
 
     jclass displayDecorationSupportClazz =
             FindClassOrDie(env, "android/hardware/graphics/common/DisplayDecorationSupport");
diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp
index 6e903b3..1031542 100644
--- a/core/jni/android_window_ScreenCapture.cpp
+++ b/core/jni/android_window_ScreenCapture.cpp
@@ -211,7 +211,7 @@
 }
 
 static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject,
-                                jlong screenCaptureListenerObject) {
+                                jlong screenCaptureListenerObject, jboolean sync) {
     LayerCaptureArgs captureArgs;
     getCaptureArgs(env, layerCaptureArgsObject, captureArgs);
 
@@ -227,7 +227,7 @@
 
     sp<gui::IScreenCaptureListener> captureListener =
             reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject);
-    return ScreenshotClient::captureLayers(captureArgs, captureListener);
+    return ScreenshotClient::captureLayers(captureArgs, captureListener, sync);
 }
 
 static jlong nativeCreateScreenCaptureListener(JNIEnv* env, jclass clazz, jobject consumerObj) {
@@ -281,7 +281,7 @@
         // clang-format off
     {"nativeCaptureDisplay", "(Landroid/window/ScreenCapture$DisplayCaptureArgs;J)I",
             (void*)nativeCaptureDisplay },
-    {"nativeCaptureLayers",  "(Landroid/window/ScreenCapture$LayerCaptureArgs;J)I",
+    {"nativeCaptureLayers",  "(Landroid/window/ScreenCapture$LayerCaptureArgs;JZ)I",
             (void*)nativeCaptureLayers },
     {"nativeCreateScreenCaptureListener", "(Ljava/util/function/ObjIntConsumer;)J",
             (void*)nativeCreateScreenCaptureListener },
diff --git a/core/jni/core_jni_helpers.h b/core/jni/core_jni_helpers.h
index 210dc89..769fa72 100644
--- a/core/jni/core_jni_helpers.h
+++ b/core/jni/core_jni_helpers.h
@@ -22,6 +22,8 @@
 #include <nativehelper/scoped_utf_chars.h>
 #include <android_runtime/AndroidRuntime.h>
 
+#include "jni_wrappers.h"
+
 // Host targets (layoutlib) do not differentiate between regular and critical native methods,
 // and they need all the JNI methods to have JNIEnv* and jclass/jobject as their first two arguments.
 // The following macro allows to have those arguments when compiling for host while omitting them when
@@ -36,60 +38,6 @@
 
 namespace android {
 
-// Defines some helpful functions.
-
-static inline jclass FindClassOrDie(JNIEnv* env, const char* class_name) {
-    jclass clazz = env->FindClass(class_name);
-    LOG_ALWAYS_FATAL_IF(clazz == NULL, "Unable to find class %s", class_name);
-    return clazz;
-}
-
-static inline jfieldID GetFieldIDOrDie(JNIEnv* env, jclass clazz, const char* field_name,
-                                       const char* field_signature) {
-    jfieldID res = env->GetFieldID(clazz, field_name, field_signature);
-    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find field %s with signature %s", field_name,
-                        field_signature);
-    return res;
-}
-
-static inline jmethodID GetMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name,
-                                         const char* method_signature) {
-    jmethodID res = env->GetMethodID(clazz, method_name, method_signature);
-    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find method %s with signature %s", method_name,
-                        method_signature);
-    return res;
-}
-
-static inline jfieldID GetStaticFieldIDOrDie(JNIEnv* env, jclass clazz, const char* field_name,
-                                             const char* field_signature) {
-    jfieldID res = env->GetStaticFieldID(clazz, field_name, field_signature);
-    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static field %s with signature %s", field_name,
-                        field_signature);
-    return res;
-}
-
-static inline jmethodID GetStaticMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name,
-                                               const char* method_signature) {
-    jmethodID res = env->GetStaticMethodID(clazz, method_name, method_signature);
-    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static method %s with signature %s",
-                        method_name, method_signature);
-    return res;
-}
-
-template <typename T>
-static inline T MakeGlobalRefOrDie(JNIEnv* env, T in) {
-    jobject res = env->NewGlobalRef(in);
-    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to create global reference.");
-    return static_cast<T>(res);
-}
-
-static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
-                                       const JNINativeMethod* gMethods, int numMethods) {
-    int res = AndroidRuntime::registerNativeMethods(env, className, gMethods, numMethods);
-    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
-    return res;
-}
-
 /**
  * Returns the result of invoking java.lang.ref.Reference.get() on a Reference object.
  */
diff --git a/core/jni/jni_wrappers.h b/core/jni/jni_wrappers.h
new file mode 100644
index 0000000..3b29e30
--- /dev/null
+++ b/core/jni/jni_wrappers.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+// JNI wrappers for better logging
+
+#include <jni.h>
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+
+namespace android {
+
+static inline jclass FindClassOrDie(JNIEnv* env, const char* class_name) {
+    jclass clazz = env->FindClass(class_name);
+    LOG_ALWAYS_FATAL_IF(clazz == NULL, "Unable to find class %s", class_name);
+    return clazz;
+}
+
+static inline jfieldID GetFieldIDOrDie(JNIEnv* env, jclass clazz, const char* field_name,
+                                       const char* field_signature) {
+    jfieldID res = env->GetFieldID(clazz, field_name, field_signature);
+    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find field %s with signature %s", field_name,
+                        field_signature);
+    return res;
+}
+
+static inline jmethodID GetMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name,
+                                         const char* method_signature) {
+    jmethodID res = env->GetMethodID(clazz, method_name, method_signature);
+    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find method %s with signature %s", method_name,
+                        method_signature);
+    return res;
+}
+
+static inline jfieldID GetStaticFieldIDOrDie(JNIEnv* env, jclass clazz, const char* field_name,
+                                             const char* field_signature) {
+    jfieldID res = env->GetStaticFieldID(clazz, field_name, field_signature);
+    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static field %s with signature %s", field_name,
+                        field_signature);
+    return res;
+}
+
+static inline jmethodID GetStaticMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name,
+                                               const char* method_signature) {
+    jmethodID res = env->GetStaticMethodID(clazz, method_name, method_signature);
+    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static method %s with signature %s",
+                        method_name, method_signature);
+    return res;
+}
+
+template <typename T>
+static inline T MakeGlobalRefOrDie(JNIEnv* env, T in) {
+    jobject res = env->NewGlobalRef(in);
+    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to create global reference.");
+    return static_cast<T>(res);
+}
+
+static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
+                                       const JNINativeMethod* gMethods, int numMethods) {
+    int res = jniRegisterNativeMethods(env, className, gMethods, numMethods);
+    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+    return res;
+}
+
+} // namespace android
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index db391f7..a854e36 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -18,6 +18,7 @@
 per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS
 per-file android/hardware/sensorprivacy.proto = ntmyren@google.com,evanseverson@google.com
 per-file background_install_control.proto = wenhaowang@google.com,georgechan@google.com,billylau@google.com
+per-file android/content/intent.proto = file:/PACKAGE_MANAGER_OWNERS
 
 # Biometrics
 jaggies@google.com
@@ -31,5 +32,3 @@
 
 # Accessibility
 pweaver@google.com
-hongmingjin@google.com
-cbrower@google.com
diff --git a/core/proto/android/content/intent.proto b/core/proto/android/content/intent.proto
index 75e2908..1d1f88b 100644
--- a/core/proto/android/content/intent.proto
+++ b/core/proto/android/content/intent.proto
@@ -66,7 +66,7 @@
     optional string identifier = 13 [ (.android.privacy).dest = DEST_EXPLICIT ];
 }
 
-// Next Tag: 12
+// Next Tag: 14
 message IntentFilterProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
 
@@ -89,6 +89,7 @@
     optional bool get_auto_verify = 10;
     repeated string mime_groups = 11;
     optional android.os.PersistableBundleProto extras = 12;
+    repeated UriRelativeFilterGroupProto uri_relative_filter_groups = 13;
 }
 
 message AuthorityEntryProto {
@@ -98,3 +99,23 @@
     optional bool wild = 2;
     optional int32 port = 3;
 }
+
+message UriRelativeFilterGroupProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    enum Action {
+        ACTION_ALLOW = 0;
+        ACTION_BLOCK = 1;
+    }
+
+    optional Action action = 1;
+    repeated UriRelativeFilterProto uri_relative_filters = 2;
+}
+
+message UriRelativeFilterProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    required int32 uri_part = 1;
+    required int32 pattern_type = 2;
+    required string filter = 3;
+}
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 52e0124..c92435f 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -397,6 +397,8 @@
     optional bool should_override_min_aspect_ratio = 42;
     optional bool should_ignore_orientation_request_loop = 43;
     optional bool should_override_force_resize_app = 44;
+    optional bool should_enable_user_aspect_ratio_settings = 45;
+    optional bool is_user_fullscreen_override_enabled = 46;
 }
 
 /* represents WindowToken */
@@ -462,6 +464,7 @@
     repeated .android.graphics.RectProto keep_clear_areas = 45;
     repeated .android.graphics.RectProto unrestricted_keep_clear_areas = 46;
     repeated .android.view.InsetsSourceProto mergedLocalInsetsSources = 47;
+    optional int32 requested_visible_types = 48;
 }
 
 message IdentifierProto {
diff --git a/core/proto/android/service/notification.proto b/core/proto/android/service/notification.proto
index a2978be..ad36b1c 100644
--- a/core/proto/android/service/notification.proto
+++ b/core/proto/android/service/notification.proto
@@ -360,7 +360,7 @@
 
     optional ConversationType allow_conversations_from = 19;
 
-    optional ChannelType allow_channels = 20;
+    optional ChannelPolicy allow_channels = 20;
 }
 
 // Enum identifying the type of rule that changed; values set to match ones used in the
@@ -373,8 +373,8 @@
 
 // Enum used in DNDPolicyProto to indicate the type of channels permitted to
 // break through DND. Mirrors values in ZenPolicy.
-enum ChannelType {
-    CHANNEL_TYPE_UNSET = 0;
-    CHANNEL_TYPE_PRIORITY = 1;
-    CHANNEL_TYPE_NONE = 2;
+enum ChannelPolicy {
+    CHANNEL_POLICY_UNSET = 0;
+    CHANNEL_POLICY_PRIORITY = 1;
+    CHANNEL_POLICY_NONE = 2;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ef6caef..0171f58 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2956,6 +2956,16 @@
     <permission android:name="android.permission.MANAGE_SENSORS"
         android:protectionLevel="signature" />
 
+    <!-- Must be required by a DomainSelectionService to ensure that only the
+         system can bind to it.
+         <p>Protection level: signature
+         @SystemApi
+         @hide
+         @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service")
+    -->
+    <permission android:name="android.permission.BIND_DOMAIN_SELECTION_SERVICE"
+        android:protectionLevel="signature" />
+
     <!-- Must be required by an ImsService to ensure that only the
          system can bind to it.
          <p>Protection level: signature|privileged|vendorPrivileged
@@ -3042,6 +3052,17 @@
     <permission android:name="android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE"
         android:protectionLevel="internal|role" />
 
+    <!-- Used to provide the Telecom framework with access to the last known call ID.
+         <p>Protection level: signature
+         @SystemApi
+         @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")
+         @hide
+    -->
+    <permission android:name="android.permission.ACCESS_LAST_KNOWN_CELL_ID"
+        android:protectionLevel="signature"
+        android:label="@string/permlab_accessLastKnownCellId"
+        android:description="@string/permdesc_accessLastKnownCellId"/>
+
     <!-- ================================== -->
     <!-- Permissions for sdcard interaction -->
     <!-- ================================== -->
@@ -3160,7 +3181,7 @@
          types of interactions
          @hide -->
     <permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
-        android:protectionLevel="signature|installer|role" />
+        android:protectionLevel="signature|installer|module|role" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
 
     <!-- Allows interaction across profiles in the same profile group. -->
@@ -3548,6 +3569,13 @@
     <permission android:name="android.permission.MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION"
                 android:protectionLevel="internal|role" />
 
+    <!-- Allows an application to set policy related to <a
+    href="https://www.threadgroup.org">Thread</a> network.
+        @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled")
+    -->
+    <permission android:name="android.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK"
+                android:protectionLevel="internal|role" />
+
     <!-- Allows an application to set policy related to windows.
         <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
         required to call APIs protected by this permission on users different to the calling user.
@@ -3723,6 +3751,13 @@
     <permission android:name="android.permission.MANAGE_DEVICE_POLICY_DEVICE_IDENTIFIERS"
                 android:protectionLevel="internal|role" />
 
+    <!-- Allows an application to manage policy related to content protection.
+        <p>Protection level: internal|role
+        @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled")
+    -->
+    <permission android:name="android.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION"
+                android:protectionLevel="internal|role" />
+
     <!-- Allows an application to set device policies outside the current user
         that are critical for securing data within the current user.
         <p>Holding this permission allows the use of other held MANAGE_DEVICE_POLICY_*
@@ -3747,6 +3782,13 @@
     <permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL"
                 android:protectionLevel="internal|role" />
 
+    <!-- Allows an application to access EnhancedConfirmationManager.
+        @SystemApi
+        @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled")
+        @hide This is not a third-party API (intended for OEMs and system apps). -->
+    <permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES"
+                android:protectionLevel="signature|installer" />
+
     <!-- @SystemApi @hide Allows an application to set a device owner on retail demo devices.-->
     <permission android:name="android.permission.PROVISION_DEMO_DEVICE"
                 android:protectionLevel="signature|setup|knownSigner"
@@ -3793,6 +3835,18 @@
     <permission android:name="android.permission.ACTIVITY_EMBEDDING"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- Allows an application to embed any other apps in untrusted embedding mode without the need
+         for the embedded app to consent.
+         <p>For now, this permission is only granted to the Assistant application selected by
+         the user.
+         {@see https://developer.android.com/guide/topics/large-screens/activity-embedding#trust_model}
+         @SystemApi
+         @FlaggedApi("com.android.window.flags.untrusted_embedding_any_app_permission")
+         @hide
+        -->
+    <permission android:name="android.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE"
+                android:protectionLevel="internal|role" />
+
     <!-- Allows an application to start any activity, regardless of permission
          protection or exported state.
          @hide -->
@@ -4940,7 +4994,7 @@
          <p>Protection level: signature
     -->
     <permission android:name="android.permission.BIND_NFC_SERVICE"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|module" />
 
     <!-- Must be required by a {@link android.service.quickaccesswallet.QuickAccessWalletService}
          to ensure that only the system can bind to it.
@@ -5055,7 +5109,7 @@
          <p>Intended for use by ROLE_ASSISTANT and signature apps only.
     -->
     <permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE"
-                android:protectionLevel="signature|role"/>
+                android:protectionLevel="signature|module|role"/>
 
     <!-- Must be required by a {@link android.service.autofill.AutofillService},
          to ensure that only the system can bind to it.
@@ -5214,6 +5268,14 @@
     <permission android:name="android.permission.BIND_REMOTE_DISPLAY"
         android:protectionLevel="signature" />
 
+    <!-- Must be required by a android.media.tv.ad.TvAdService to ensure that only the system can
+         bind to it.
+         <p>Protection level: signature|privileged
+         @FlaggedApi("android.media.tv.flags.enable_ad_service_fw")
+    -->
+    <permission android:name="android.permission.BIND_TV_AD_SERVICE"
+                android:protectionLevel="signature|privileged" />
+
     <!-- Must be required by a {@link android.media.tv.TvInputService}
          to ensure that only the system can bind to it.
          <p>Protection level: signature|privileged
@@ -5665,7 +5727,8 @@
     <!-- @SystemApi Allows an application to manage the holders of a role.
          @hide -->
     <permission android:name="android.permission.MANAGE_ROLE_HOLDERS"
-                android:protectionLevel="signature|installer" />
+                android:protectionLevel="signature|installer|module" />
+    <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
 
     <!-- @SystemApi Allows an application to manage the holders of roles associated with default
          applications.
@@ -5685,7 +5748,7 @@
     <!-- @SystemApi Allows an application to observe role holder changes.
          @hide -->
     <permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"
-                android:protectionLevel="signature|installer" />
+                android:protectionLevel="signature|installer|module" />
 
     <!-- Allows an application to manage the companion devices.
          @hide -->
@@ -5700,6 +5763,14 @@
                 android:description="@string/permdesc_observeCompanionDevicePresence"
                 android:protectionLevel="normal" />
 
+    <!-- Allows an application to subscribe to notifications about the nearby devices' presence
+         status change base on the UUIDs.
+         <p>Not for use by third-party applications.</p>
+         @FlaggedApi("android.companion.flags.device_presence")
+    -->
+    <permission android:name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE"
+                android:protectionLevel="signature|privileged" />
+
     <!-- Allows an application to deliver companion messages to system
          -->
     <permission android:name="android.permission.DELIVER_COMPANION_MESSAGES"
@@ -6593,9 +6664,23 @@
     <permission android:name="android.permission.USE_BIOMETRIC_INTERNAL"
         android:protectionLevel="signature" />
 
+    <!-- Allows privileged apps to access the background face authentication.
+        @SystemApi
+        @FlaggedApi("android.hardware.biometrics.face_background_authentication")
+        @hide -->
+    <permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows the system to control the BiometricDialog (SystemUI). Reserved for the system. @hide -->
     <permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG"
-        android:protectionLevel="signature" />
+                android:protectionLevel="signature" />
+
+    <!-- Allows an application to set the BiometricDialog (SystemUI) logo .
+         <p>Not for use by third-party applications.
+         @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")
+    -->
+    <permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO"
+                android:protectionLevel="signature" />
 
     <!-- Allows an application to control keyguard.  Only allowed for system processes.
         @hide -->
@@ -7006,6 +7091,7 @@
         android:protectionLevel="signature" />
 
     <!-- @SystemApi Allows an application to access the smartspace service as a client.
+     @FlaggedApi(android.app.smartspace.flags.Flags.FLAG_ACCESS_SMARTSPACE)
      @hide  <p>Not for use by third-party applications.</p> -->
     <permission android:name="android.permission.ACCESS_SMARTSPACE"
         android:protectionLevel="signature|privileged|development" />
@@ -7741,6 +7827,16 @@
     <permission android:name="android.permission.RUN_USER_INITIATED_JOBS"
                 android:protectionLevel="normal"/>
 
+    <!-- @FlaggedApi("android.app.job.backup_jobs_exemption")
+         Gives applications whose <b>primary use case</b> is to backup or sync content increased
+         job execution allowance in order to complete the related work. The jobs must have a valid
+         content URI trigger and network constraint set.
+         <p>This is a special access permission that can be revoked by the system or the user.
+         <p>Protection level: signature|privileged|appop
+     -->
+    <permission android:name="android.permission.RUN_BACKUP_JOBS"
+                android:protectionLevel="signature|privileged|appop"/>
+
     <!-- Allows an app access to the installer provided app metadata.
         @SystemApi
         @hide
@@ -7888,6 +7984,36 @@
     <permission android:name="android.permission.RECEIVE_SENSITIVE_NOTIFICATIONS"
         android:protectionLevel="signature|role" />
 
+    <!-- @SystemApi
+         @FlaggedApi("android.app.bic_client")
+         Allows app to call BackgroundInstallControlManager API to retrieve silently installed apps
+         for all users on device.
+         <p>Apps with a BackgroundInstallControlManager client will not be able to call any API without
+         this permission.
+         <p>Protection level: signature|role
+         @hide
+     -->
+    <permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES"
+        android:protectionLevel="signature|role" />
+
+    <!-- @SystemApi Allows an application to read the system grammatical gender.
+         @FlaggedApi("android.app.system_terms_of_address_enabled")
+         <p>Protection level: signature|privileged
+         @hide
+    -->
+    <permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"
+                android:protectionLevel="signature|privileged"/>
+
+    <!-- @SystemApi
+        @FlaggedApi("android.content.pm.emergency_install_permission")
+        Allows each app store in the system image to designate another app in the system image to
+        update the app store
+        <p>Protection level: signature|privileged
+        @hide
+    -->
+    <permission android:name="android.permission.EMERGENCY_INSTALL_PACKAGES"
+                android:protectionLevel="signature|privileged"/>
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
@@ -8294,6 +8420,16 @@
             </intent-filter>
         </receiver>
 
+        <!-- Broadcast Receiver listens to sufficient verifier broadcast from Package Manager
+            when installing new SDK. Verification of SDK code during installation time is run
+            to determine compatibility with privacy sandbox restrictions. -->
+        <receiver android:name="com.android.server.sdksandbox.SdkSandboxVerifierReceiver"
+                 android:exported="false">
+            <intent-filter>
+                <action android:name="android.intent.action.PACKAGE_NEEDS_VERIFICATION"/>
+            </intent-filter>
+        </receiver>
+
         <service android:name="android.hardware.location.GeofenceHardwareService"
             android:permission="android.permission.LOCATION_HARDWARE"
             android:exported="false" />
@@ -8335,6 +8471,10 @@
                  android:permission="android.permission.BIND_JOB_SERVICE">
         </service>
 
+        <service android:name="com.android.server.selinux.SelinuxAuditLogsService"
+                 android:permission="android.permission.BIND_JOB_SERVICE">
+        </service>
+
         <service android:name="com.android.server.compos.IsolatedCompilationJobService"
                  android:permission="android.permission.BIND_JOB_SERVICE">
         </service>
diff --git a/core/res/res/color-night/notification_expand_button_state_tint.xml b/core/res/res/color-night/notification_expand_button_state_tint.xml
deleted file mode 100644
index a794d53..0000000
--- a/core/res/res/color-night/notification_expand_button_state_tint.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_pressed="true" android:color="@android:color/system_on_surface_dark" android:alpha="0.06"/>
-    <item android:state_hovered="true" android:color="@android:color/system_on_surface_dark" android:alpha="0.03"/>
-    <item android:color="@android:color/system_on_surface_dark" android:alpha="0.00"/>
-</selector>
\ No newline at end of file
diff --git a/core/res/res/color/notification_expand_button_state_tint.xml b/core/res/res/color/notification_expand_button_state_tint.xml
index 67b2c25..5a8594f 100644
--- a/core/res/res/color/notification_expand_button_state_tint.xml
+++ b/core/res/res/color/notification_expand_button_state_tint.xml
@@ -14,8 +14,11 @@
   ~ limitations under the License.
   -->
 
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_pressed="true" android:color="@android:color/system_on_surface_light" android:alpha="0.12"/>
-    <item android:state_hovered="true" android:color="@android:color/system_on_surface_light" android:alpha="0.08"/>
-    <item android:color="@android:color/system_on_surface_light" android:alpha="0.00"/>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:state_pressed="true" android:color="?androidprv:attr/materialColorOnPrimaryFixed"
+          android:alpha="0.15"/>
+    <item android:state_hovered="true" android:color="?androidprv:attr/materialColorOnPrimaryFixed"
+          android:alpha="0.11"/>
+    <item android:color="@color/transparent" />
 </selector>
\ No newline at end of file
diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index f3acab0..fe12f6e 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -1,5 +1,5 @@
 <!--
-Copyright (C) 2021 The Android Open Source Project
+Copyright (C) 2024 The Android Open Source Project
 
    Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
@@ -20,179 +20,103 @@
     android:viewportWidth="512"
     android:viewportHeight="512">
   <path
-      android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0">
+      android:pathData="M256.22,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98s-24.52,-54.37 -11.33,-77.21c13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21s-103.26,178.86 -120.65,208.98c-17.39,30.12 -34.83,48.42 -61.2,48.42Z"
+      android:strokeWidth="0">
     <aapt:attr name="android:fillColor">
       <gradient 
-          android:startX="256"
-          android:startY="21.81"
-          android:endX="256"
-          android:endY="350.42"
+          android:startX="56.22"
+          android:startY="256"
+          android:endX="456.22"
+          android:endY="256"
           android:type="linear">
         <item android:offset="0" android:color="#FF073042"/>
         <item android:offset="1" android:color="#FF073042"/>
       </gradient>
     </aapt:attr>
   </path>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="m195.27,187.64l2.25,-6.69c13.91,78.13 50.84,284.39 50.84,50.33 0,-0.97 0.72,-1.81 1.62,-1.81h12.69c0.9,0 1.62,0.83 1.62,1.8 -0.2,409.91 -69.03,-43.64 -69.03,-43.64Z"
-        android:fillColor="#3ddc84"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="m158.77,180.68l-33.17,57.45c-1.9,3.3 -0.77,7.52 2.53,9.42 3.3,1.9 7.52,0.77 9.42,-2.53l33.59,-58.17c54.27,24.33 116.34,24.33 170.61,0l33.59,58.17c1.97,3.26 6.21,4.3 9.47,2.33 3.17,-1.91 4.26,-5.99 2.47,-9.23l-33.16,-57.45c56.95,-30.97 95.91,-88.64 101.61,-156.76H57.17c5.7,68.12 44.65,125.79 101.61,156.76Z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M250.26,187.66L262.17,187.66A2.13,2.13 0,0 1,264.3 189.78L264.3,217.85A2.13,2.13 0,0 1,262.17 219.98L250.26,219.98A2.13,2.13 0,0 1,248.14 217.85L248.14,189.78A2.13,2.13 0,0 1,250.26 187.66z"
-        android:fillColor="#3ddc84"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M250.12,170.29L262.32,170.29A1.98,1.98 0,0 1,264.3 172.26L264.3,176.39A1.98,1.98 0,0 1,262.32 178.37L250.12,178.37A1.98,1.98 0,0 1,248.14 176.39L248.14,172.26A1.98,1.98 0,0 1,250.12 170.29z"
-        android:fillColor="#3ddc84"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M171.92,216.82h4.04v4.04h-4.04z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M188.8,275.73h4.04v4.04h-4.04z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M369.04,337.63h4.04v4.04h-4.04z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M285.93,252.22h4.04v4.04h-4.04z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M318.96,218.84h4.04v4.04h-4.04z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M294.05,288.55h4.04v4.04h-4.04z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M330.82,273.31h8.08v8.08h-8.08z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M220.14,238.94h8.08v8.08h-8.08z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M293.34,349.25h8.08v8.08h-8.08z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M161.05,254.24h8.08v8.08h-8.08z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M378.92,192h8.08v8.08h-8.08z"
-        android:fillColor="#fff"/>
-  </group>
-  <group>
-    <clip-path
-        android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
-    <path
-        android:pathData="M137.87,323.7h8.08v8.08h-8.08z"
-        android:fillColor="#fff"/>
-  </group>
   <path
-      android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"
-      android:strokeWidth="56.561"
-      android:fillColor="#00000000"
-      android:strokeColor="#f86734"/>
-  <path
-      android:pathData="m256.22,126.57c-6.69,0 -12.12,5.27 -12.12,11.77v14.52c0,1.25 1.02,2.27 2.27,2.27h0c1.25,0 2.27,-1.02 2.27,-2.27v-3.91c0,-2.51 2.04,-4.55 4.55,-4.55h6.06c2.51,0 4.55,2.04 4.55,4.55v3.91c0,1.25 1.02,2.27 2.27,2.27s2.27,-1.02 2.27,-2.27v-14.52c0,-6.5 -5.43,-11.77 -12.12,-11.77Z"
+      android:pathData="M198.85,168.57l2.03,-6.03c12.55,70.48 45.87,256.56 45.87,45.41 0,-0.88 0.65,-1.63 1.46,-1.63h11.45c0.81,0 1.46,0.75 1.46,1.63 -0.18,369.8 -62.28,-39.37 -62.28,-39.37Z"
+      android:strokeWidth="0"
       android:fillColor="#3ddc84"/>
   <path
-      android:pathData="m93.34,116.36l3.85,-4.36 29.64,9.76 -4.44,5.03 -6.23,-2.1 -7.86,8.91 2.86,5.92 -4.43,5.03 -13.39,-28.18ZM110.43,122.76l-8.86,-3.02 4.11,8.41 4.76,-5.39Z"
+      android:pathData="M186.69,167.97l-23.69,41.03c-1.36,2.36 -0.55,5.37 1.8,6.73 2.36,1.36 5.37,0.55 6.73,-1.8l23.99,-41.55c38.76,17.38 83.1,17.38 121.86,0l23.99,41.55c1.41,2.33 4.44,3.07 6.77,1.66 2.26,-1.37 3.04,-4.28 1.76,-6.59l-23.69,-41.03c40.68,-22.12 68.5,-63.31 72.57,-111.97H114.11c4.07,48.65 31.89,89.85 72.57,111.97Z"
+      android:strokeWidth="0"
       android:fillColor="#fff"/>
   <path
-      android:pathData="m153.62,100.85l-21.71,-6.2 10.38,14.38 -5.21,3.76 -16.78,-23.26 4.49,-3.24 21.65,6.19 -10.35,-14.35 5.24,-3.78 16.78,23.26 -4.49,3.24Z"
+      android:pathData="M248.46,168.59L259.2,168.59A1.92,1.92 0,0 1,261.12 170.5L261.12,195.83A1.92,1.92 0,0 1,259.2 197.75L248.46,197.75A1.92,1.92 0,0 1,246.54 195.83L246.54,170.5A1.92,1.92 0,0 1,248.46 168.59z"
+      android:strokeWidth="0"
+      android:fillColor="#3ddc84"/>
+  <path
+      android:pathData="M248.32,152.92L259.34,152.92A1.78,1.78 0,0 1,261.12 154.7L261.12,158.43A1.78,1.78 0,0 1,259.34 160.21L248.32,160.21A1.78,1.78 0,0 1,246.54 158.43L246.54,154.7A1.78,1.78 0,0 1,248.32 152.92z"
+      android:strokeWidth="0"
+      android:fillColor="#3ddc84"/>
+  <path
+      android:pathData="M159.03,176.91h4.04v4.04h-4.04z"
+      android:strokeWidth="0"
       android:fillColor="#fff"/>
   <path
-      android:pathData="m161.46,63.15l8.99,-3.84c7.43,-3.18 15.96,0.12 19.09,7.44 3.13,7.32 -0.38,15.76 -7.81,18.94l-8.99,3.84 -11.28,-26.38ZM179.41,80.26c4.46,-1.91 5.96,-6.72 4.15,-10.96 -1.81,-4.24 -6.33,-6.48 -10.79,-4.57l-3.08,1.32 6.64,15.53 3.08,-1.32Z"
+      android:pathData="M188.8,275.73h4.04v4.04h-4.04z"
+      android:strokeWidth="0"
       android:fillColor="#fff"/>
   <path
-      android:pathData="m204.23,47.57l11.1,-2.2c5.31,-1.05 9.47,2.08 10.4,6.76 0.72,3.65 -0.76,6.37 -4.07,8.34l12.4,10.44 -7.57,1.5 -11.65,-9.76 -1.03,0.2 2.3,11.61 -6.3,1.25 -5.57,-28.14ZM216.78,56.7c1.86,-0.37 3,-1.71 2.68,-3.33 -0.34,-1.7 -1.88,-2.43 -3.74,-2.06l-4.04,0.8 1.07,5.39 4.04,-0.8Z"
+      android:pathData="M373.41,158.93h4.04v4.04h-4.04z"
+      android:strokeWidth="0"
       android:fillColor="#fff"/>
   <path
-      android:pathData="m244.29,55.6c0.13,-8.16 6.86,-14.72 15.06,-14.58 8.16,0.13 14.72,6.9 14.58,15.06s-6.91,14.72 -15.06,14.58c-8.2,-0.13 -14.71,-6.9 -14.58,-15.06ZM267.44,55.98c0.08,-4.64 -3.54,-8.66 -8.18,-8.74 -4.68,-0.08 -8.42,3.82 -8.5,8.47 -0.08,4.65 3.54,8.66 8.22,8.74 4.64,0.08 8.39,-3.82 8.46,-8.47Z"
+      android:pathData="M112.1,129.34h4.04v4.04h-4.04z"
+      android:strokeWidth="0"
       android:fillColor="#fff"/>
   <path
-      android:pathData="m294.39,44.84l6.31,1.23 -5.49,28.16 -6.31,-1.23 5.49,-28.16Z"
+      android:pathData="M285.93,252.22h4.04v4.04h-4.04z"
+      android:strokeWidth="0"
       android:fillColor="#fff"/>
   <path
-      android:pathData="m321.94,51.41l9.14,3.48c7.55,2.88 11.39,11.17 8.56,18.61 -2.83,7.44 -11.22,11.07 -18.77,8.19l-9.14,-3.48 10.22,-26.8ZM322.96,76.19c4.53,1.73 8.95,-0.69 10.6,-5 1.64,-4.3 -0.05,-9.06 -4.58,-10.78l-3.13,-1.19 -6.01,15.78 3.13,1.19Z"
+      android:pathData="M318.96,218.84h4.04v4.04h-4.04z"
+      android:strokeWidth="0"
       android:fillColor="#fff"/>
   <path
-      android:pathData="m381.41,89.24l-4.21,-3.21 3.65,-4.78 9.06,6.91 -17.4,22.81 -4.85,-3.7 13.75,-18.02Z"
+      android:pathData="M294.05,288.55h4.04v4.04h-4.04z"
+      android:strokeWidth="0"
       android:fillColor="#fff"/>
   <path
-      android:pathData="m397.96,126.37l-9.56,-10.26 3.61,-3.36 22.8,-1.25 3.74,4.02 -12.35,11.51 2.51,2.69 -4.08,3.8 -2.51,-2.69 -4.55,4.24 -4.16,-4.46 4.55,-4.24ZM407.83,117.17l-10.28,0.58 4.49,4.82 5.79,-5.4Z"
+      android:pathData="M330.82,263.31h8.08v8.08h-8.08z"
+      android:strokeWidth="0"
       android:fillColor="#fff"/>
+  <path
+      android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
+      android:strokeWidth="0"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M224.18,216.82h8.08v8.08h-8.08z"
+      android:strokeWidth="0"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
+      android:strokeWidth="0"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M293.34,339.25h8.08v8.08h-8.08z"
+      android:strokeWidth="0"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M165.09,236.28h8.08v8.08h-8.08z"
+      android:strokeWidth="0"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M378.92,192h8.08v8.08h-8.08z"
+      android:strokeWidth="0"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M204.28,314.86h8.08v8.08h-8.08z"
+      android:strokeWidth="0"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M253.83,118.47c-6.04,0 -10.93,4.76 -10.93,10.62v13.1c0,1.13 0.92,2.05 2.05,2.05h0c1.13,0 2.05,-0.92 2.05,-2.05v-3.53c0,-2.27 1.84,-4.1 4.1,-4.1h5.47c2.27,0 4.1,1.84 4.1,4.1v3.53c0,1.13 0.92,2.05 2.05,2.05s2.05,-0.92 2.05,-2.05v-13.1c0,-5.86 -4.9,-10.62 -10.93,-10.62Z"
+      android:strokeWidth="0"
+      android:fillColor="#3ddc84"/>
+  <path
+      android:pathData="M256,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98 -17.39,-30.12 -24.52,-54.37 -11.33,-77.21 13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21 -17.39,30.12 -103.26,178.86 -120.65,208.98 -17.39,30.12 -34.83,48.42 -61.2,48.42Z"
+      android:strokeWidth="55"
+      android:fillColor="#00000000"
+      android:strokeColor="#f86733"/>
 </vector>
-
diff --git a/core/res/res/drawable/autofill_half_sheet_divider.xml b/core/res/res/drawable/autofill_half_sheet_divider.xml
new file mode 100644
index 0000000..1a96c7d
--- /dev/null
+++ b/core/res/res/drawable/autofill_half_sheet_divider.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copied from //frameworks/base/core/res/res/drawable/list_divider_material.xml. -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:tint="@color/foreground_material_light">
+  <solid android:color="#1f000000" />
+  <size
+      android:height="1dp"
+      android:width="1dp"/>
+</shape>
\ No newline at end of file
diff --git a/core/res/res/layout/autofill_save.xml b/core/res/res/layout/autofill_save.xml
index 27f8138..ddedca2 100644
--- a/core/res/res/layout/autofill_save.xml
+++ b/core/res/res/layout/autofill_save.xml
@@ -31,11 +31,11 @@
         android:gravity="center_horizontal"
         android:orientation="vertical">
         <ScrollView
+            android:id="@+id/autofill_sheet_scroll_view"
             android:layout_width="fill_parent"
             android:layout_height="0dp"
             android:fillViewport="true"
-            android:layout_weight="1"
-            android:layout_marginBottom="8dp">
+            android:layout_weight="1">
             <LinearLayout
                 android:layout_marginStart="@dimen/autofill_save_outer_margin"
                 android:layout_marginEnd="@dimen/autofill_save_outer_margin"
@@ -66,16 +66,25 @@
                     android:layout_height="wrap_content"
                     android:minHeight="0dp"
                     android:visibility="gone"/>
-
+                <View
+                    android:id="@+id/autofill_sheet_scroll_view_space"
+                    android:layout_width="match_parent"
+                    android:layout_height="16dp"/>
             </LinearLayout>
         </ScrollView>
 
+        <View
+            android:id="@+id/autofill_sheet_divider"
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            style="@style/AutofillHalfSheetDivider" />
+
         <com.android.internal.widget.ButtonBarLayout
             android:layout_width="match_parent"
             android:layout_height="48dp"
             android:layout_gravity="end"
             android:clipToPadding="false"
-            android:layout_marginTop="16dp"
+            android:layout_marginTop="8dp"
             android:layout_marginBottom="8dp"
             android:theme="@style/Theme.DeviceDefault.AutofillHalfScreenDialogButton"
             android:orientation="horizontal"
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 8281462..e864872 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Laat die program toe om die seluitsendingmodule te bind om seluitsendingboodskappe aan te stuur wanneer hulle ontvang word. Seluitsendingwaarskuwings word in sommige liggings gelewer om jou oor noodsituasies te waarsku. Kwaadwillige programme kan met die werkverrigting of werking van jou toestel inmeng wanneer \'n noodseluitsending ontvang word."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Bestuur voortgaande oproepe"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Stel \'n program in staat om besonderhede oor voortgaande oproepe op jou toestel te sien, en hierdie oproepe te beheer."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"lees seluitsending-boodskappe"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Laat die program toe om seluitsending-boodskappe te lees wat deur jou toestel ontvang word. Seluitsending-waarskuwings word in sommige plekke afgelewer om jou van noodsituasies te waarsku. Kwaadwillige programme mag inmeng met die prestasie of die werking van jou toestel wanneer \'n noodgeval se seluitsending ontvang word."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"lees ingetekende nuus"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Laat die app toe om die voorgronddienstipe “stelselvrystelling” te gebruik"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"gebruik voorgronddienstipe “lêerbestuur”"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Laat die app toe om die voorgronddienstipe “lêerbestuur” te gebruik"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"gebruik voorgronddienstipe “mediaverwerking”"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Laat die app toe om die voorgronddienstipe “mediaverwerking” te gebruik"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"gebruik voorgronddienstipe “spesiale gebruik”"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Laat die app toe om die voorgronddienstipe “spesiale gebruik” te gebruik"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"meet programberging-ruimte"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Gebruik skermslot"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Voer jou skermslot in om voort te gaan"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Druk ferm op die sensor"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Kan nie vingerafdruk herken nie. Probeer weer."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Vingerafdruk word nie herken nie. Probeer weer."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Maak vingerafdruksensor skoon en probeer weer"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Maak sensor skoon en probeer weer"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Druk ferm op sensor"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Kan nie jou gesig sien nie. Hou jou foon op oogvlak."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Te veel beweging. Hou foon stil."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Skryf jou gesig asseblief weer in."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Kan nie gesig herken nie. Probeer weer."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Gesig word nie herken nie. Probeer weer."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Verander die posisie van jou kop effens"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Kyk meer reguit na jou foon"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Kyk meer reguit na jou foon"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index ff7dee8..e91fe9d 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"የሕዋስ ስርጭት መልዕክቶች እንደመጡ ለማስተላለፍ መተግበሪያው ከሕዋስ ስርጭት ሞዱሉ ጋር እንዲተሳሰር ያስችለዋል። የሕዋስ ስርጭት ማንቂያዎች አስቸኳይ ሁኔታዎች ሲያጋጥሙ አንዳንድ አካባቢዎች ላይ የሚላኩ ናቸው። የሕዋስ ስርጭት ሲደርስ ተንኮል-አዘል መተግበሪያዎች በመሣሪያዎ አፈጻጸም ወይም አሰራር ላይ ጣልቃ ሊገቡ ይችላሉ።"</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"በመካሄድ ላይ ያሉ ጥሪዎችን አስተዳድር"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"አንድ መተግበሪያ በመካሄድ ላይ ስላሉ ጥሪዎች ዝርዝሮችን እንዲመለከት ያስችለዋል።"</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"የህዋስ ስርጭት መልዕክቶችን አንብብ"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"መሣሪያህ የህዋስ ስርጭት መልዕክቶች ሲቀበል መተግበሪያው እንዲያነበው ይፈቅድለታል። የህዋስ ስርጭት ማንቂያዎች አስቸኳይ ሁኔታዎች ሲያጋጥሙ አንዳንድ አካባቢዎች ላይ የሚላኩ ናቸው። የህዋስ ስርጭት ሲደርስ ተንኮል አዘል መተግበሪያዎች በመሣሪያህ አፈጻጸም ወይም አሰራር ላይ ጣልቃ ሊገቡ ይችላሉ።"</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"የምዝገባ መግቦች አንበብ"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"መተግበሪያው የፊት አገልግሎትን በ«systemExempted» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"የፊት አገልግሎትን በ«fileManagement» ዓይነት ማስሄድ"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"መተግበሪያው የፊት አገልግሎቶችን በ«fileManagement» ዓይነት እንዲጠቀም ያስችላል"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"የፊት ለፊት አገልግሎትን በ«mediaProcessing» ዓይነት ማስሄድ"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"መተግበሪያው የፊት ለፊት አገልግሎቶችን በ«mediaProcessing» ዓይነት እንዲጠቀም ያስችላል"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"የፊት አገልግሎትን በ«specialUse» ዓይነት ማስሄድ"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"መተግበሪያው የፊት አገልግሎትን በ«specialUse» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"የመተግበሪያ ማከማቻ ቦታ ለካ"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"የማያ ገፅ መቆለፊን ይጠቀሙ"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"ለመቀጠል የማያ ገፅ ቁልፍዎን ያስገቡ"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"ዳሳሹን በደንብ ይጫኑት"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"የጣት አሻራን መለየት አልተቻለም። እንደገና ይሞክሩ።"</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"የጣት አሻራ አልታወቀም። እንደገና ይሞክሩ።"</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"የጣት አሻራ ዳሳሽን ያጽዱ እና እንደገና ይሞክሩ"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"ዳሳሹን ያጽዱ እና እንደገና ይሞክሩ"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"ዳሳሹን ጠበቅ አድርገው ይጫኑት"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"የእርስዎን መልክ ማየት አይችልም። ስልክዎን በዓይን ትክክል ይያዙ።"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"ከልክ በላይ ብዙ እንቅስቃሴ። ስልኩን ቀጥ አድርገው ይያዙት።"</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"እባክዎ ፊትዎን እንደገና ያስመዝግቡ"</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"መልክን መለየት አልተቻለም። እንደገና ይሞክሩ።"</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"ፊቱ አልታወቀም። እንደገና ይሞክሩ።"</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"የጭንቅላትዎን ቦታ በትንሹ ይለዋውጡ"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"ስልክዎን ይበልጥ በቀጥታ ይመልከቱ"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"ስልክዎን ይበልጥ በቀጥታ ይመልከቱ"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 0cc49a5..ae1f2aa 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -374,6 +374,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"يسمح للتطبيق بالارتباط بوحدة البث الخلوي لإعادة توجيه رسائل البث الخلوي بينما يتم استقبالها. ويتم تسليم تنبيهات البث الخلوي في بعض المواقع لتحذيرك في حالات الطوارئ. ويمكن أن تؤثر التطبيقات الضارة على أداء الجهاز أو تشغيله عندما يتم تلقي بث خلوي في حالات الطوارئ."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"إدارة المكالمات الجارية"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"يسمح هذا الإذن للتطبيق بمعرفة تفاصيل المكالمات الجارية على جهازك والتحكّم فيها."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"قراءة رسائل بث الخلية"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"السماح للتطبيق بقراءة رسائل بث الخلية التي يتلقاها هذا الجهاز. يتم تسليم اشعارات بث الخلية في بعض المواقع لتحذيرك من حالات طارئة. يمكن أن تتداخل التطبيقات الضارة مع أداء أو تشغيل الجهاز عندما يتم تلقي بث خلية طارئ."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"قراءة الخلاصات المشتركة"</string>
@@ -438,6 +442,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"‏يسمح هذا الإذن للتطبيق بالاستفادة من الخدمات التي تعمل في المقدّمة ذات النوع \"systemExempted\"."</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"‏تشغيل الخدمة التي تعمل في المقدّمة ذات النوع \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"‏يسمح هذا الإذن للتطبيق بالاستفادة من الخدمات التي تعمل في المقدّمة ذات النوع \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"‏تشغيل الخدمة التي تعمل في المقدّمة ذات النوع \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"‏يسمح هذا الإذن للتطبيق بالاستفادة من الخدمات التي تعمل في المقدّمة ذات النوع \"mediaProcessing\"."</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"‏تشغيل الخدمة التي تعمل في المقدّمة ذات النوع \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"‏يسمح هذا الإذن للتطبيق بالاستفادة من الخدمات التي تعمل في المقدّمة ذات النوع \"specialUse\"."</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"قياس مساحة تخزين التطبيق"</string>
@@ -636,7 +642,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"استخدام قفل الشاشة"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"أدخِل قفل الشاشة للمتابعة"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"اضغط بقوة على أداة الاستشعار"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"يتعذّر التعرّف على بصمة الإصبع. يُرجى إعادة المحاولة."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"لم يتم التعرّف على بصمة الإصبع. يُرجى إعادة المحاولة."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"يُرجى تنظيف مستشعر بصمات الإصبع ثم إعادة المحاولة."</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"تنظيف المستشعر ثم إعادة المحاولة"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"اضغط بقوة على أداة الاستشعار"</string>
@@ -700,7 +706,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"ارفع هاتفك إلى مستوى العينَين لأنّه تتعذّر رؤية وجهك"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"حركة أكثر من اللازم. يُرجى حمل الهاتف بثبات."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"يُرجى إعادة تسجيل وجهك."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"يتعذّر التعرّف على الوجه. يُرجى إعادة المحاولة."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"يتعذّر التعرّف على الوجه. يُرجى إعادة المحاولة."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"غيِّر موضع رأسك قليلاً."</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"يُرجى النظر إلى هاتفك مباشرةً"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"يُرجى النظر إلى هاتفك مباشرةً"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index ddc2367..fd8082a 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"চেল সম্প্ৰচাৰ বাৰ্তাসমূহ লাভ কৰিলে সেইবোৰ ফৰৱাৰ্ড কৰিবলৈ এপ্‌টোক চেল সম্প্ৰচাৰ মডিউলটোৰ সৈতে সংযুক্ত হ\'বলৈ অনুমতি দিয়ে। আপোনাক জৰুৰীকালীন পৰিস্থিতিসমূহৰ বিষয়ে সতৰ্ক কৰিবলৈ কিছুমান অৱস্থানত চেল সম্প্ৰচাৰ সতৰ্কবাৰ্তাসমূহ ডেলিভাৰ কৰা হয়। কোনো জৰুৰীকালীন চেল সম্প্ৰচাৰ লাভ কৰিলে ক্ষতিকাৰক এপ্‌সমূহে আপোনাৰ ডিভাইচটোৰ কাৰ্যক্ষমতা অথবা কাৰ্যপ্ৰণালীত হস্তক্ষেপ কৰিব পাৰে।"</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"চলি থকা কলসমূহ পৰিচালনা কৰক"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"এটা এপক আপোনাৰ ডিভাইচত চলি থকা কলৰ সবিশেষ চাবলৈ আৰু এই কলসমূহ নিয়ন্ত্ৰণ কৰিবলৈ অনুমতি দিয়ে।"</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"চেল সম্প্ৰচাৰৰ বার্তাবোৰ পঢ়ক"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"আপোনাৰ ডিভাইচে লাভ কৰা চেল সম্প্ৰচাৰৰ বার্তাবোৰ পঢ়িবলৈ এপক অনুমতি দিয়ে। আপোনাক জৰুৰীকালীন পৰিস্থিতিবোৰত সর্তক কৰিবলৈ চেল সম্প্ৰচাৰৰ বার্তাবোৰ প্ৰেৰণ কৰা হয়। জৰুৰীকালীন চেল সম্প্ৰচাৰ লাভ কৰাৰ সময়ত আপোনাৰ ডিভাইচৰ কাৰ্যদক্ষতা বা কাৰ্যপ্ৰণালীত ক্ষতিকাৰক এপবোৰে হস্তক্ষেপ কৰিব পাৰে।"</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"আপুনি সদস্যভুক্ত হোৱা ফীডসমূহ পঢ়ক"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"এপ্‌টোক \"systemExempted\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ চলাওক"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"এপ্‌টোক \"fileManagement\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"\"mediaProcessing\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ চলাওক"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"এপ্‌টোক \"mediaProcessing\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ চলাওক"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"এপ্‌টোক \"specialUse\" সম্পৰ্কীয় অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"এপৰ ষ্ট’ৰেজৰ খালী ঠাই হিচাপ কৰক"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"স্ক্ৰীন ল\'ক ব্যৱহাৰ কৰক"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"অব্যাহত ৰাখিবলৈ আপোনাৰ স্ক্ৰীন লক দিয়ক"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"ছেন্সৰটোত ভালকৈ টিপক"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"ফিংগাৰপ্ৰিণ্ট চিনাক্ত কৰিব পৰা নাই। পুনৰ চেষ্টা কৰক।"</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"ফিংগাৰপ্ৰিণ্ট চিনাক্ত কৰিব পৰা নাই। পুনৰ চেষ্টা কৰক।"</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"ফিংগাৰপ্ৰিণ্ট ছেন্সৰটো মচি পুনৰ চেষ্টা কৰক"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"ছেন্সৰটো মচি পুনৰ চেষ্টা কৰক"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"ছেন্সৰটোত ভালকৈ টিপক"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"আপোনাৰ মুখাৱয়ব দেখা নাই। আপোনাৰ ফ’নটো চকুৰ স্তৰত ধৰি ৰাখক।"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"বেছি লৰচৰ কৰি আছে। ফ’নটো স্থিৰকৈ ধৰক।"</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"আপোনাৰ মুখমণ্ডল পুনৰ পঞ্জীয়ন কৰক।"</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"মুখাৱয়ব চিনিব নোৱাৰি। পুনৰ চেষ্টা কৰক।"</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"মুখাৱয়ব চিনাক্ত কৰিব পৰা নাই। পুনৰ চেষ্টা কৰক।"</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"আপোনাৰ মূৰটোৰ স্থান সামান্য সলনি কৰক"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"আপোনাৰ ফ’নটোলৈ আৰু পোনপটীয়াকৈ চাওক"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"আপোনাৰ ফ’নটোলৈ আৰু পোনপটীয়াকৈ চাওক"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 794e26a..a14725b 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Tətbiqə şəbəkə yayım mesajlarını əldə edildiyi anda yönləndirmək üçün şəbəkə yayımı moduluna bağlanmaq icazəsi verir. Şəbəkə yayımı bəzi məkanlarda olan fövqəladə hadisələrlə bağlı Sizi xəbərdar etmək üçün qəbul edilir. Zərərli tətbiqlər fövqəladə şəbəkə yayımı əldə edildiyi zaman cihazın performansına və əməliyyatına müdaxilə edə bilər."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Davam edən zəngləri idarə edin"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Tətbiqə cihazınızda davam edən zənglər haqqında məlumatları görmək və bu zəngləri idarə etmək imkanı verir."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"mobil yayım mesajlarını oxuyur"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Tətbiqə telefonunuz tərəfindən alınmış yayım mesajlarını oxuma icazəsi verir. Telefon yayımı bəzi məkanlarda olan fövqəladə hadisələrlə bağlı sizi xəbərdar etmək üçün qəbul edilir. Zərərli tətbiqlər təcili mobil yayım qəbul edildiyi zaman telefonunun performansına və əməliyyatına müdaxilə edə bilər."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"abunə olunmuş xəbərləri oxuyur"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Tətbiqə \"systemExempted\" növü olan ön fon xidmətlərini işlətmək icazəsi verir"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" növü olan ön fon xidmətlərini işə salmaq"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Tətbiqə \"fileManagement\" növü olan ön fon xidmətlərini işlətmək icazəsi verir"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"\"mediaProcessing\" növü ilə ön fon xidmətini işə salmaq"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Tətbiqə \"mediaProcessing\" növü ilə ön fon xidmətlərindən istifadə icazəsi verir"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" növü olan ön fon xidmətləri işlətmək"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Tətbiqə \"specialUse\" növü olan ön fon xidmətlərini işlətmək icazəsi verir"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"tətbiq saxlama yaddaşını ölçmək"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Ekran kilidindən istifadə edin"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Davam etmək üçün ekran kilidinizi daxil edin"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Sensora basıb saxlayın"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Barmaq izini tanımaq olmur. Yenidən cəhd edin."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Barmaq izi tanınmadı. Yenidən cəhd edin."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Barmaq izi sensorunu silib yenidən cəhd edin"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Sensoru silib yenidən cəhd edin"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Sensora basıb saxlayın"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Üzünüz görünmür. Telefonunuzu göz səviyyəsində saxlayın."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Cihaz stabil deyil. Telefonu tərpətməyin."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Üzünüzü yenidən qeydiyyatdan keçirin."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Üzü tanımaq olmur. Yenə cəhd edin."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Üz tanınmadı. Yenidən cəhd edin."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Başınızın yerini bir az dəyişdirin"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Telefonunuza düz baxın"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Telefonunuza düz baxın"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 8df8b87..91b5b045 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -371,6 +371,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Dozvoljava aplikaciji da se vezuje za modul poruka za mobilne uređaje na lokalitetu da bi prosleđivala poruke za mobilne uređaje na lokalitetu onako kako su primljene. Obaveštenja poruka za mobilne uređaje na lokalitetu se na nekim lokacijama primaju kao upozorenja na hitne slučajeve. Zlonamerne aplikacije mogu da utiču na performanse ili ometaju rad uređaja kada se primi poruka o hitnom slučaju za mobilne uređaje na lokalitetu."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Upravljanje odlaznim pozivima"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Omogućava aplikaciji da vidi detalje o odlaznim pozivima na uređaju i da kontroliše te pozive."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"čitanje poruka info servisa"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Omogućava aplikaciji da čita poruke info servisa koje uređaj prima. Upozorenja info servisa se na nekim lokacijama primaju kao upozorenja na hitne slučajeve. Zlonamerne aplikacije mogu da utiču na performanse ili ometaju funkcionisanje uređaja kada se primi poruka info servisa o hitnom slučaju."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"čitanje prijavljenih fidova"</string>
@@ -435,6 +439,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „systemExempted“"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"pokretanje usluge u prvom planu koja pripada tipu „fileManagement“"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „fileManagement“"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"pokretanje usluge u prvom planu koja pripada tipu „mediaProcessing“"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „mediaProcessing“"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"pokretanje usluge u prvom planu koja pripada tipu „specialUse“"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „specialUse“"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"merenje memorijskog prostora u aplikaciji"</string>
@@ -633,7 +639,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Koristite zaključavanje ekrana"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Upotrebite zaključavanje ekrana da biste nastavili"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Čvrsto pritisnite senzor"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Prepoznavanje otiska prsta nije uspelo. Probajte ponovo."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Otisak prsta nije prepoznat. Probajte ponovo."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Obrišite senzor za otisak prsta i probajte ponovo"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Obrišite senzor i probajte ponovo"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Čvrsto pritisnite senzor"</string>
@@ -697,7 +703,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Ne vidi se lice. Držite telefon u visini očiju."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Mnogo se pomerate. Držite telefon mirno."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Ponovo registrujte lice."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Lice nije prepoznato. Probajte ponovo."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Lice nije prepoznato. Probajte ponovo."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Malo pomerite glavu"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Gledajte pravo u telefon"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Gledajte pravo u telefon"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 13801d0..68b89f0 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -372,6 +372,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Дазваляе праграме звязвацца з модулем сотавай трансляцыі, каб пераадрасоўваць атрыманыя там паведамленні. Абвесткі сотавай трансляцыі дасылаюцца ў некаторыя месцы, каб папярэджваць вас пра надзвычайныя сітуацыі. Шкодныя праграмы могуць негатыўна ўплываць на прадукцыйнасць або працу прылады падчас атрымання паведамленняў сотавай трансляцыі пра надзвычайныя сітуацыі."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Кіраваць уваходнымі выклікамі"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Дазваляе праграме праглядаць на прыладзе падрабязныя звесткі пра ўваходныя выклікі і кіраваць імі."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"чытаць паведамленні базавай станцыі"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Дазваляе прыкладанню чытаць паведамленні базавай станцыі, атрыманыя прыладай. Папярэджанні базавай станцыі дасылаюцца ў некаторыя месцы, каб папярэдзіць вас аб надзвычайных сітуацыях. Шкоднасныя прыкладанні могуць уплываць на прадукцыйнасць ці працу прылады пры атрыманні паведамлення базавай станцыі аб надзвычайнай сітуацыі."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"чытаць падпісаныя каналы"</string>
@@ -436,6 +440,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Дазваляе праграме выкарыстоўваць актыўныя сэрвісы тыпу \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"запуск актыўнага сэрвісу тыпу \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Праграма зможа выкарыстоўваць актыўныя сэрвісы тыпу \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"запуск актыўнага сэрвісу тыпу \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Дазваляе праграме выкарыстоўваць актыўныя сэрвісы тыпу \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"запуск актыўнага сэрвісу тыпу \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Дазваляе праграме выкарыстоўваць актыўныя сэрвісы тыпу \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"вымерыць прастору для захоўвання прыкладання"</string>
@@ -634,7 +640,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Ужываць блакіроўку экрана"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Каб працягнуць, скарыстайце свой сродак блакіроўкі экрана"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Шчыльна прыкладзіце палец да сканера"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Не ўдалося распазнаць адбітак пальца. Паўтарыце спробу."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Адбітак пальца не распазнаны. Паўтарыце спробу."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Ачысціце сканер адбіткаў пальцаў і паўтарыце спробу"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Ачысціце сканер і паўтарыце спробу"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Шчыльна прыкладзіце палец да сканера"</string>
@@ -698,7 +704,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Не відаць твару. Трымайце тэлефон на ўзроўні вачэй."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Трымайце прыладу нерухома. Трымайце тэлефон роўна."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Паўтарыце рэгістрацыю твару."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Твар не распазнаны. Паўтарыце спробу."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Твар не распазнаны. Паўтарыце спробу."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Крыху змяніце паставу галавы"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Глядзіце прама на экран тэлефона"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Глядзіце прама на экран тэлефона"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index e0e9b09..e369102 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Разрешава на приложението да се обвърже с модула за клетъчно излъчване, за да препраща получените съобщения с клетъчно излъчване. Сигналите с клетъчно излъчване се получават на някои местоположения, за да ви предупредят за спешни случаи. Злонамерените приложения могат да възпрепятстват изпълнението или работата на устройството ви при получаване на такова спешно излъчване."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Управление на текущите обаждания"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Разрешава на приложението да вижда подробности за текущите обаждания на устройството ви и да ги управлява."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"четене на съобщения с клетъчно излъчване"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Разрешава на приложението да чете съобщения с клетъчно излъчване, получени от устройството ви. Сигналите с клетъчно излъчване се получават на някои местоположения, за да ви предупредят за спешни ситуации. Злонамерените приложения могат да възпрепятстват изпълнението или работата на устройството ви при получаване на такова спешно излъчване."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"четене на емисиите с абонамент"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Разрешава на приложението да се възползва от услуги на преден план от тип systemExempted"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"Изпълнение на услуги на преден план от тип fileManagement"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Разрешава на приложението да се възползва от услуги на преден план от тип fileManagement"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"изпълнение на услуги на преден план от тип mediaProcessing"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Разрешава на приложението да се възползва от услуги на преден план от тип mediaProcessing"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"изпълнение на услуги на преден план от тип specialUse"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Разрешава на приложението да се възползва от услуги на преден план от тип specialUse"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"измерване на ползваното от приложението място в хранилището"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Ползване на заключв. на екрана"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Въведете опцията си за заключване на екрана, за да продължите"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Натиснете добре върху сензора"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Отпечатъкът не може да бъде разпознат. Опитайте отново."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Отпечатъкът не е разпознат. Опитайте отново."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Почистете сензора за отпечатъци и опитайте отново"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Почистете сензора и опитайте отново"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Натиснете добре върху сензора"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Лицето не се вижда. Задръжте на нивото на очите."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Твърде много движение. Дръжте телефона неподвижно."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Моля, регистрирайте лицето си отново."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Лицето не е разпознато. Опитайте отново."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Лицето не е разпознато. Опитайте отново."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Леко променете позицията на главата си"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Гледайте директно към телефона си"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Гледайте директно към телефона си"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index cf8c64d..ecbc6d5 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"সেল ব্রডকাস্ট মেসেজ পেলে এটি সেল ব্রডকাস্ট মডিউলের সাথে তা যুক্ত করে যাতে সেই মেসেজ ফরওয়ার্ড করা যায়। আপনাকে জরুরি অবস্থা সম্পর্কে সাবধান করতে কিছু লোকেশনে সেল ব্রডকাস্ট অ্যালার্ট মেসেজ ডেলিভার করা হয়। জরুরি সেল ব্রডকাস্ট পাওয়া গেলে ক্ষতিকারক অ্যাপ আপনার ডিভাইসের পারফর্ম্যান্স ও অপারেশনে বাধা সৃষ্টি করতে পারে।"</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"অনগোয়িং কল ম্যানেজ করুন"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"আপনার ডিভাইসে অনগোয়িং কলের বিষয়ে বিবরণ দেখতে এবং এই কলগুলি কন্ট্রোল করার জন্য অ্যাপকে অনুমতি দেয়।"</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"সেল সম্প্রচার মেসেজ পড়ুন"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"আপনার ডিভাইস দ্বারা প্রাপ্ত সেল সম্প্রচার পড়তে অ্যাপ্লিকেশানটিকে অনুমতি দেয়৷ কয়েকটি স্থানে আপনাকে জরুরি অবস্থার জন্য সতর্ক করতে জরুরি সতর্কতাগুলি বিতরণ করা হয়৷ যখন একটি জরুরি সেল সম্প্রচার প্রাপ্ত হয় তখন ক্ষতিকারক অ্যাপ্লিকেশানগুলি আপনার ডিভাইসের কার্য সম্পাদনা বা কার্যকলাপে প্রতিবন্ধকতার সৃষ্টি করতে পারে৷"</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"গ্রাহক হিসেবে নেওয়া ফিডগুলি পড়ে"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"অ্যাপকে \"systemExempted\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা ব্যবহার করার অনুমতি দেয়"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা রান করা"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"অ্যাপকে \"fileManagement\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা ব্যবহার করার অনুমতি দেয়"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"\"mediaProcessing\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা রান করা"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"অ্যাপকে \"mediaProcessing\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা ব্যবহার করার অনুমতি দেয়"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা রান করান"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"অ্যাপকে \"specialUse\" সম্পর্কিত ফোরগ্রাউন্ড পরিষেবা ব্যবহার করার অনুমতি দেয়"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"অ্যাপ্লিকেশন সঞ্চয়স্থানের জায়গা পরিমাপ করে"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"স্ক্রিন লক ব্যবহার করুন"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"চালিয়ে যেতে আপনার স্ক্রিন লক ব্যবহার করুন"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"সেন্সরে জোরে প্রেস করুন"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"ফিঙ্গারপ্রিন্ট শনাক্ত করা যায়নি। আবার চেষ্টা করুন।"</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"ফিঙ্গারপ্রিন্ট শনাক্ত হয়নি। আবার চেষ্টা করুন।"</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"আঙ্গুলের ছাপের সেন্সর পরিষ্কার করে আবার চেষ্টা করুন"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"সেন্সর পরিষ্কার করে আবার চেষ্টা করুন"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"সেন্সরে জোরে প্রেস করুন"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"আপনার মুখ দেখা যাচ্ছে না। ফোন আপনার চোখের সোজাসুজি ধরুন।"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"খুব বেশি নড়ছে। ফোনটি যাতে না কাঁপে সেইভাবে ধরুন।"</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"আপনার মুখের ছবি আবার নথিভুক্ত করুন।"</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"মুখ শনাক্ত করা যাচ্ছে না। আবার চেষ্টা করুন।"</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"মুখ চেনা যায়নি। আবার চেষ্টা করুন।"</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"আপনার মাথার পজিশন সামান্য পরিবর্তন করুন"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"আপনার ফোনের দিকে একদম সোজাসুজি তাকান"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"আপনার ফোনের দিকে একদম সোজাসুজি তাকান"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index ef941e19f..5088911 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -371,6 +371,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Dopušta aplikaciji da se veže za modul info servisa kako bi prosljeđivala poruke info servisa. Upozorenja koja emitira info servis se isporučuju na nekim lokacijama kako bi vas upozorila na vanredne situacije. Zlonamjerne aplikacije mogu ometati performanse ili rad vašeg uređaja kada primite informacije o vanrednoj situaciji od info servisa."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Upravljanje pozivima u toku"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Omogućava aplikaciji da vidi detalje o pozivima u toku na vašem uređaju i da ih kontrolira."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"čitanje poruka info servisa"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Omogućava aplikaciji čitanje poruka info servisa koje je primio vaš uređaj. Upozorenja koja emitira info servis se isporučuju na nekim lokacijama kako bi vas upozorila na vanredne situacije. Zlonamjerne aplikacije mogu ometati performanse ili rad vašeg uređaja kada primite informaciju o vanrednoj situaciji od info servisa."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"čitanje sadržaja na koje ste pretplaćeni"</string>
@@ -435,6 +439,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Dozvoljava aplikaciji da koristi usluge u prvom planu s vrstom \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"pokreni uslugu u prvom planu s vrstom \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Dozvoljava aplikaciji da koristi usluge u prvom planu s vrstom \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"pokreni uslugu u prvom planu s vrstom \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Dozvoljava aplikaciji da koristi usluge u prvom planu s vrstom \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"pokreni uslugu u prvom planu s vrstom \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Dozvoljava aplikaciji da koristi usluge u prvom planu s vrstom \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"mjerenje prostora kojeg aplikacije zauzimaju u pohrani"</string>
@@ -633,7 +639,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Koristi zaključavanje ekrana"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Unesite zaključavanje ekrana da nastavite"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Čvrsto pritisnite senzor"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Otisak prsta nije prepoznat. Pokušajte ponovo."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Otisak prsta nije prepoznat. Pokušajte ponovo."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Očistite senzor za otisak prsta i pokušajte ponovo"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Očistite senzor i pokušajte ponovo"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Čvrsto pritisnite senzor"</string>
@@ -697,7 +703,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Lice se ne vidi. Držite telefon u visini očiju."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Previše pokreta. Držite telefon mirno."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Ponovo registrirajte lice."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Nije moguće prepoznati lice. Pokušajte ponovo."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Lice nije prepoznato. Pokušajte ponovo."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Malo pomjerite glavu"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Gledajte direktno u telefon"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Gledajte direktno u telefon"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index ec14677..1719d53 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -371,6 +371,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Permet que l\'aplicació es vinculi al mòdul de difusió mòbil per poder reenviar els missatges de difusió mòbil a mesura que es reben. Les alertes de difusió mòbil s\'entreguen en algunes ubicacions per alertar de situacions d\'emergència. És possible que les aplicacions malicioses interfereixin en el rendiment o en el funcionament del dispositiu quan es rebi una difusió mòbil d\'emergència."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Gestiona les trucades en curs"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Permet que una aplicació vegi els detalls sobre les trucades en curs al dispositiu i que controli aquestes trucades."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"llegir missatges de difusió mòbil"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Permet que l\'aplicació llegeixi missatges de difusió mòbil rebuts pel dispositiu. Les alertes de difusió mòbil s\'entreguen en algunes ubicacions per alertar de situacions d\'emergència. És possible que les aplicacions malicioses interfereixin en el rendiment o en el funcionament del dispositiu quan es rep una difusió mòbil d\'emergència."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"llegir els feeds als quals esteu subscrit"</string>
@@ -435,6 +439,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"executa serveis en primer pla amb el tipus \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"executar serveis en primer pla amb el tipus \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"executa serveis en primer pla amb el tipus \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permet que l\'aplicació utilitzi serveis en primer pla amb el tipus \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"mesura l\'espai d\'emmagatzematge d\'aplicacions"</string>
@@ -633,7 +639,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Utilitza el bloqueig de pantalla"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Introdueix el teu bloqueig de pantalla per continuar"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Prem el sensor de manera ferma"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"No es pot reconèixer l\'empremta digital. Torna-ho a provar."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"L\'empremta digital no s\'ha reconegut. Torna-ho a provar."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Neteja el sensor d\'empremtes digitals i torna-ho a provar"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Neteja el sensor i torna-ho a provar"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Prem el sensor de manera ferma"</string>
@@ -697,7 +703,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"No se\'t veu la cara. Mantén el telèfon a l\'altura dels ulls."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Massa moviment. Subjecta bé el telèfon."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Torna a registrar la teva cara."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"No podem reconèixer la cara. Torna-ho a provar."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"La cara no s\'ha reconegut. Torna-ho a provar."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Canvia lleugerament la posició del cap"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Mira més directament al telèfon"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Mira més directament al telèfon"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index e518de4..d50f4f8 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -372,6 +372,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Umožňuje aplikaci vytvořit vazbu s modulem informačních služeb za účelem přesměrovávání přijatých zpráv informačních služeb. Výstražná upozornění informačních služeb jsou v některých oblastech odesílána za účelem varování před výjimečnými událostmi. Škodlivé aplikace mohou narušit výkon či provoz vašeho zařízení během přijímání zpráv informačních služeb."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Správa probíhajících hovorů"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Umožňuje aplikaci zobrazit podrobnosti o probíhajících hovorech v zařízení a tyto hovory ovládat."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"čtení zpráv informačních služeb"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Umožňuje aplikaci číst zprávy informačních služeb přijaté ve vašem zařízení. Výstražná upozornění informačních služeb jsou v některých oblastech odesílána za účelem varování před výjimečnými událostmi. Škodlivé aplikace mohou narušit výkon či provoz vašeho zařízení během přijímání zpráv informačních služeb."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"čtení zdrojů přihlášených k odběru"</string>
@@ -436,6 +440,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Umožňuje aplikaci používat služby v popředí typu „systemExempted“"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"používat službu v popředí typu „fileManagement“"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Umožňuje aplikaci používat služby v popředí typu „fileManagement“"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"používat službu v popředí typu mediaProcessing"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Umožňuje aplikaci používat služby v popředí typu mediaProjection"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"používat službu v popředí typu „specialUse“"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Umožňuje aplikaci používat služby v popředí typu „specialUse“"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"výpočet místa pro ukládání aplikací"</string>
@@ -634,7 +640,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Použít zámek obrazovky"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Pokračujte zadáním zámku obrazovky"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Pevně zatlačte na snímač"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Otisk prstu se nepodařilo rozpoznat. Zkuste to znovu."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Otisk prstu nebyl rozpoznán. Zkuste to znovu."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Vyčistěte snímač otisků prstů a zkuste to znovu"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Vyčistěte senzor a zkuste to znovu"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Pevně přitiskněte prst na snímač"</string>
@@ -698,7 +704,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Obličej není vidět. Držte telefon v úrovni očí."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Příliš mnoho pohybu. Držte telefon nehybně."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Zaznamenejte obličej znovu."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Obličej se nepodařilo rozpoznat. Zkuste to znovu."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Obličej nebyl rozpoznán. Zkuste to znovu."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Mírně pohněte hlavou"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Dívejte se přímo na telefon"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Dívejte se přímo na telefon"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index d3f8550..e0fc3ca 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Tillader, at appen bindes til Cell Broadcast-modulet, så Cell Broadcast-meddelelser kan videresendes, når de modtages. I regioner sendes der Cell Broadcast-underretninger for at advare dig om nødsituationer. Ondsindede apps kan forstyrre effektiviteten eller driften af din enhed, når den modtager en Cell Broadcast-meddelelse om en nødsituation."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Administrere igangværende opkald"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Giver appen tilladelse til at se oplysninger om igangværende opkald på din enhed og styre disse opkald."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"læse Cell Broadcast-meddelelser"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Tillader, at appen læser Cell Broadcast-underretninger, der modtages af din enhed. I regioner sendes der Cell Broadcast-underretninger for at advare om nødsituationer. Ondsindede apps kan forstyrre ydelsen eller driften af ​din ​enhed, når der modtages en Cell Broadcast-meddelelse om en nødsituation."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"læse feeds, jeg abonnerer på"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Tillader, at appen benytter tjenester af typen \"systemExempted\" i forgrunden"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"kør tjenesten af typen \"fileManagement\" i forgrunden"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Tillader, at appen benytter tjenester af typen \"fileManagement\" i forgrunden"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"kør tjenesten af typen \"mediaProcessing\" i forgrunden"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Tillader, at appen benytter tjenester af typen \"mediaProcessing\" i forgrunden"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"kør tjenesten af typen \"specialUse\" i forgrunden"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Tillader, at appen benytter tjenester af typen \"specialUse\" i forgrunden"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"måle appens lagerplads"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Brug skærmlås"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Angiv din skærmlås for at fortsætte"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Hold fingeren på sensoren"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Fingeraftrykket kan ikke genkendes. Prøv igen."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Fingeraftrykket blev ikke genkendt. Prøv igen."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Rengør fingeraftrykssensoren, og prøv igen"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Rengør sensoren, og prøv igen"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Hold fingeren på sensoren"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Dit ansigt kan ikke registreres. Hold din telefon i øjenhøjde."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Der er for meget bevægelse. Hold telefonen stille."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Registrer dit ansigt igen."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Ansigtet kan ikke genkendes. Prøv igen."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Ansigtet blev ikke genkendt. Prøv igen."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Flyt dit hoved en smule"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Kig mere direkte på din telefon"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Kig mere direkte på din telefon"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 514d695..7f0dd1e 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Ermöglicht der App, sich mit dem Cell-Broadcast-Modul zu verbinden, um empfangene Cell-Broadcast-Nachrichten weiterzuleiten. Cell-Broadcast-Benachrichtigungen können in einigen Ländern oder Regionen gesendet werden, um dich bei Notfallsituationen zu warnen. Schädliche Apps können die Leistung oder den Betrieb deines Geräts beeinträchtigen, wenn eine Cell-Broadcast-Notfallbenachrichtigung eingeht."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Aktuelle Anrufe verwalten"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Ermöglicht einer App, Details zu aktuellen Anrufen auf deinem Gerät zu sehen und diese Anrufe zu verwalten."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"Cell Broadcast-Nachrichten lesen"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Ermöglicht der App, von deinem Gerät empfangene Cell Broadcast-Nachrichten zu lesen. Cell Broadcast-Benachrichtigungen werden an einigen Standorten gesendet, um dich über Notfallsituationen zu informieren. Schädliche Apps können die Leistung oder den Betrieb deines Geräts beeinträchtigen, wenn eine Cell Broadcast-Notfallbenachrichtigung eingeht."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"Abonnierte Feeds lesen"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Ermöglicht der App, Vordergrunddienste mit dem Typ „systemExempted“ zu verwenden"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"Dienste im Vordergrund mit dem Typ „fileManagement“ ausführen"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Ermöglicht der App, Dienste im Vordergrund mit dem Typ „fileManagement“ zu verwenden"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"Vordergrunddienst des Typs „mediaProcessing“ ausführen"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Ermöglicht der App, Vordergrunddienste des Typs „mediaProcessing“ zu verwenden"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"Vordergrunddienste mit dem Typ „specialUse“ ausführen"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Ermöglicht der App, Vordergrunddienste mit dem Typ „specialUse“ zu verwenden"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"Speicherplatz der App ermitteln"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Displaysperre verwenden"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Displaysperre eingeben, um fortzufahren"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Drücke fest auf den Sensor"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Fingerabdruck wurde nicht erkannt. Versuch es noch einmal."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Fingerabdruck nicht erkannt. Versuche es noch einmal."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Reinige den Fingerabdrucksensor und versuch es noch einmal"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Reinige den Sensor und versuche es noch einmal"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Drücke fest auf den Sensor"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Gesicht nicht erkannt. Smartphone auf Augenhöhe halten."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Zu viel Unruhe. Halte das Smartphone ruhig."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Bitte registriere dein Gesicht noch einmal."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Gesicht nicht erkannt. Versuche es noch einmal."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Gesicht nicht erkannt. Versuche es noch einmal."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Ändere die Position deines Kopfes leicht"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Sieh direkt auf dein Smartphone"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Sieh direkt auf dein Smartphone"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index a392c0f..366533c 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Επιτρέπει στην εφαρμογή να συνδέεται στη λειτουργική μονάδα εκπομπής κινητής τηλεφωνίας, προκειμένου να προωθεί τα μηνύματα εκπομπής κινητής τηλεφωνίας κατά τη λήψη τους. Οι ειδοποιήσεις εκπομπής κινητής τηλεφωνίας προβάλλονται σε ορισμένες τοποθεσίες, για να σας προειδοποιήσουν σχετικά με καταστάσεις έκτακτης ανάγκης. Οι κακόβουλες εφαρμογές μπορεί να επηρεάσουν την απόδοση ή τη λειτουργία της συσκευής σας κατά τη λήψη μιας εκπομπής κινητής τηλεφωνίας έκτακτης ανάγκης."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Διαχείριση κλήσεων σε εξέλιξη"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Επιτρέπει σε μια εφαρμογή να βλέπει λεπτομέρειες σχετικά με τις κλήσεις σε εξέλιξη στη συσκευή σας και να τις ελέγχει."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"διαβάζει μηνύματα που έχουν μεταδοθεί μέσω κινητού τηλεφώνου"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Επιτρέπει στην εφαρμογή την ανάγνωση μηνυμάτων που έχουν μεταδοθεί μέσω κινητού τηλεφώνου και έχουν ληφθεί από τη συσκευή σας. Ειδοποιήσεις που μεταδίδονται μέσω κινητού παραδίδονται σε ορισμένες τοποθεσίες για να σας προειδοποιήσουν για καταστάσεις έκτακτης ανάγκης. Κακόβουλες εφαρμογές ενδέχεται να παρεμποδίσουν την απόδοση ή τη λειτουργία της συσκευής σας κατά τη λήψη μετάδοσης μέσω κινητού σχετικά με μια επείγουσα κατάσταση."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"διαβάζει ροές δεδομένων στις οποίες έχετε εγγραφεί"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τις υπηρεσίες στο προσκήνιο με τον τύπο \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"εκτέλεση υπηρεσίας στο προσκήνιο με τον τύπο fileManagement"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τις υπηρεσίες στο προσκήνιο με τον τύπο fileManagement."</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"εκτέλεση υπηρεσίας στο προσκήνιο με τον τύπο \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τις υπηρεσίες στο προσκήνιο με τον τύπο \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"εκτέλεση υπηρεσίας στο προσκήνιο με τον τύπο \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Επιτρέπει στην εφαρμογή να χρησιμοποιεί τις υπηρεσίες στο προσκήνιο με τον τύπο \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"υπολογίζει τον αποθηκευτικό χώρο εφαρμογής"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Χρήση κλειδώματος οθόνης"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Χρησιμοποιήστε το κλείδωμα οθόνης για να συνεχίσετε"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Πιέστε σταθερά τον αισθητήρα"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"To δακτ. αποτύπωμα δεν αναγνωρίστηκε. Δοκιμάστε ξανά."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Το δακτυλικό αποτύπωμα δεν αναγνωρίστηκε. Δοκιμάστε ξανά."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Καθαρίστε τον αισθητήρα δακτυλικών αποτυπωμάτων και δοκιμάστε ξανά"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Καθαρίστε τον αισθητήρα και δοκιμάστε ξανά"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Πιέστε σταθερά τον αισθητήρα"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Κρατήστε το τηλέφωνο στο ύψος των ματιών σας."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Πάρα πολλή κίνηση. Κρατήστε σταθερό το τηλέφωνο."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Καταχωρίστε ξανά το πρόσωπό σας."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Το πρόσωπο δεν αναγνωρίζεται. Δοκιμάστε ξανά."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Το πρόσωπο δεν αναγνωρίστηκε. Δοκιμάστε ξανά."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Αλλάξτε ελαφρώς τη θέση του κεφαλιού σας"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Κοιτάξτε απευθείας το τηλέφωνό σας"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Κοιτάξτε απευθείας το τηλέφωνό σας"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 7498488..4e54710 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Allows the app to bind to the cell broadcast module in order to forward cell broadcast messages as they are received. Cell broadcast alerts are delivered in some locations to warn you of emergency situations. Malicious apps may interfere with the performance or operation of your device when an emergency cell broadcast is received."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Manage ongoing calls"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Allows an app to see details about ongoing calls on your device and to control these calls."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"read mobile broadcast messages"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Allows the app to read mobile broadcast messages received by your device. Cell broadcast alerts are delivered in some locations to warn you of emergency situations. Malicious apps may interfere with the performance or operation of your device when an emergency mobile broadcast is received."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"read subscribed feeds"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Allows the app to make use of foreground services with the type \'systemExempted\'"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"run foreground service with the type \'fileManagement\'"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Allows the app to make use of foreground services with the type \'fileManagement\'"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"run foreground service with the type \'mediaProcessing\'"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Allows the app to make use of foreground services with the type \'mediaProcessing\'"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"run foreground service with the type \'specialUse\'"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Allows the app to make use of foreground services with the type \'specialUse\'"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"measure app storage space"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Use screen lock"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Enter your screen lock to continue"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Press firmly on the sensor"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Can’t recognise fingerprint. Try again."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Fingerprint not recognised. Try again."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Clean fingerprint sensor and try again"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Clean sensor and try again"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Press firmly on the sensor"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Can’t see your face. Hold your phone at eye level."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Too much motion. Hold phone steady."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Please re-enroll your face."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Can’t recognise face. Try again."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Face not recognised. Try again."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Change the position of your head slightly"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Look more directly at your phone"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Look more directly at your phone"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index fd76ce5..4f3b52a 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -370,6 +370,8 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Allows the app to bind to the cell broadcast module in order to forward cell broadcast messages as they are received. Cell broadcast alerts are delivered in some locations to warn you of emergency situations. Malicious apps may interfere with the performance or operation of your device when an emergency cell broadcast is received."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Manage ongoing calls"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Allows an app to see details about ongoing calls on your device and to control these calls."</string>
+    <string name="permlab_accessLastKnownCellId" msgid="7638226620825665130">"Access last known cell identity."</string>
+    <string name="permdesc_accessLastKnownCellId" msgid="6664621339249308857">"Allows an app to access to the last known cell identity provided by telephony."</string>
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"read cell broadcast messages"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Allows the app to read cell broadcast messages received by your device. Cell broadcast alerts are delivered in some locations to warn you of emergency situations. Malicious apps may interfere with the performance or operation of your device when an emergency cell broadcast is received."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"read subscribed feeds"</string>
@@ -434,6 +436,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Allows the app to make use of foreground services with the type \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"run foreground service with the type \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Allows the app to make use of foreground services with the type \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"run foreground service with the type \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Allows the app to make use of foreground services with the type \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"run foreground service with the type \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Allows the app to make use of foreground services with the type \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"measure app storage space"</string>
@@ -632,7 +636,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Use screen lock"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Enter your screen lock to continue"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Press firmly on the sensor"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Can’t recognize fingerprint. Try again."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Fingerprint not recognized. Try again."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Clean fingerprint sensor and try again"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Clean sensor and try again"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Press firmly on the sensor"</string>
@@ -696,7 +700,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Can’t see your face. Hold your phone at eye level."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Too much motion. Hold phone steady."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Please re-enroll your face."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Can’t recognize face. Try again."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Face not recognized. Try again."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Change the position of your head slightly"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Look more directly at your phone"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Look more directly at your phone"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 1f2ccce..240421b 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Allows the app to bind to the cell broadcast module in order to forward cell broadcast messages as they are received. Cell broadcast alerts are delivered in some locations to warn you of emergency situations. Malicious apps may interfere with the performance or operation of your device when an emergency cell broadcast is received."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Manage ongoing calls"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Allows an app to see details about ongoing calls on your device and to control these calls."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"read mobile broadcast messages"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Allows the app to read mobile broadcast messages received by your device. Cell broadcast alerts are delivered in some locations to warn you of emergency situations. Malicious apps may interfere with the performance or operation of your device when an emergency mobile broadcast is received."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"read subscribed feeds"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Allows the app to make use of foreground services with the type \'systemExempted\'"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"run foreground service with the type \'fileManagement\'"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Allows the app to make use of foreground services with the type \'fileManagement\'"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"run foreground service with the type \'mediaProcessing\'"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Allows the app to make use of foreground services with the type \'mediaProcessing\'"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"run foreground service with the type \'specialUse\'"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Allows the app to make use of foreground services with the type \'specialUse\'"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"measure app storage space"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Use screen lock"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Enter your screen lock to continue"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Press firmly on the sensor"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Can’t recognise fingerprint. Try again."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Fingerprint not recognised. Try again."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Clean fingerprint sensor and try again"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Clean sensor and try again"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Press firmly on the sensor"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Can’t see your face. Hold your phone at eye level."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Too much motion. Hold phone steady."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Please re-enroll your face."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Can’t recognise face. Try again."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Face not recognised. Try again."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Change the position of your head slightly"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Look more directly at your phone"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Look more directly at your phone"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index d4fd01e..4b427bc 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Allows the app to bind to the cell broadcast module in order to forward cell broadcast messages as they are received. Cell broadcast alerts are delivered in some locations to warn you of emergency situations. Malicious apps may interfere with the performance or operation of your device when an emergency cell broadcast is received."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Manage ongoing calls"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Allows an app to see details about ongoing calls on your device and to control these calls."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"read mobile broadcast messages"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Allows the app to read mobile broadcast messages received by your device. Cell broadcast alerts are delivered in some locations to warn you of emergency situations. Malicious apps may interfere with the performance or operation of your device when an emergency mobile broadcast is received."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"read subscribed feeds"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Allows the app to make use of foreground services with the type \'systemExempted\'"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"run foreground service with the type \'fileManagement\'"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Allows the app to make use of foreground services with the type \'fileManagement\'"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"run foreground service with the type \'mediaProcessing\'"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Allows the app to make use of foreground services with the type \'mediaProcessing\'"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"run foreground service with the type \'specialUse\'"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Allows the app to make use of foreground services with the type \'specialUse\'"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"measure app storage space"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Use screen lock"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Enter your screen lock to continue"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Press firmly on the sensor"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Can’t recognise fingerprint. Try again."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Fingerprint not recognised. Try again."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Clean fingerprint sensor and try again"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Clean sensor and try again"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Press firmly on the sensor"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Can’t see your face. Hold your phone at eye level."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Too much motion. Hold phone steady."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Please re-enroll your face."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Can’t recognise face. Try again."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Face not recognised. Try again."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Change the position of your head slightly"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Look more directly at your phone"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Look more directly at your phone"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 8a89ebd..66c24b4 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -370,6 +370,8 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‎‎‎‏‎‏‏‎‎‏‏‎‎‎‎‏‏‎‏‎‎‎‏‏‏‏‎‎‎‎‏‏‎‎Allows the app to bind to the cell broadcast module in order to forward cell broadcast messages as they are received. Cell broadcast alerts are delivered in some locations to warn you of emergency situations. Malicious apps may interfere with the performance or operation of your device when an emergency cell broadcast is received.‎‏‎‎‏‎"</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‏‏‏‎‎‏‏‏‎‎‏‎‏‏‏‎‏‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‎‏‏‎‎‎‎‎‏‎‏‏‎‎‏‏‎‏‏‎‎‏‏‎‎Manage ongoing calls‎‏‎‎‏‎"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‏‎‎‎‎‎‎‏‎‎‏‎‏‎‎‎‎‎‏‏‏‎‏‎‎‏‎‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‏‎‏‎‎‎‎‎‎‏‎Allows an app to see details about ongoing calls on your device and to control these calls.‎‏‎‎‏‎"</string>
+    <string name="permlab_accessLastKnownCellId" msgid="7638226620825665130">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎‎‎‎‎‎‏‏‎‏‏‏‎‏‎‏‎‎‏‎‎‏‎‎‎‎‎‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‎‎Access last known cell identity.‎‏‎‎‏‎"</string>
+    <string name="permdesc_accessLastKnownCellId" msgid="6664621339249308857">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‏‏‏‏‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‎‏‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‏‏‎‏‎‎‏‎‏‏‏‎‎‏‎Allows an app to access to the last known cell identity provided by telephony.‎‏‎‎‏‎"</string>
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‎‏‏‎‎‎‎‎‎‏‎‎‏‏‎‎‏‏‏‎‏‏‎‏‏‏‏‎‏‏‎‏‎‎‏‎‏‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‎read cell broadcast messages‎‏‎‎‏‎"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‏‎‏‎‏‎‏‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‎‏‏‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‎Allows the app to read cell broadcast messages received by your device. Cell broadcast alerts are delivered in some locations to warn you of emergency situations. Malicious apps may interfere with the performance or operation of your device when an emergency cell broadcast is received.‎‏‎‎‏‎"</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‏‎‎‏‎‏‎‎‎‏‎‎‏‎‎‏‏‎‎‎‏‏‎‎‎‏‏‎‎‏‎‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‎‏‎read subscribed feeds‎‏‎‎‏‎"</string>
@@ -434,6 +436,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‎‏‎‏‏‏‎‎‎‏‏‎‏‎‏‏‎‎‎‏‎‎‏‎‏‎‎‏‎‎‏‏‏‎‏‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‏‎‎Allows the app to make use of foreground services with the type \"systemExempted\"‎‏‎‎‏‎"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‏‏‏‎‏‏‏‏‏‏‏‎‎‎‏‎‎‏‎‏‏‎‎‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‏‎‎run foreground service with the type \"fileManagement\"‎‏‎‎‏‎"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‎‏‏‎‎‏‏‎‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‏‎‏‏‏‎‏‏‎‏‏‏‏‏‏‎‎‏‏‎‎‎Allows the app to make use of foreground services with the type \"fileManagement\"‎‏‎‎‏‎"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‏‏‎‎‎‎‏‏‏‏‏‏‎‎‏‏‏‎‎‏‏‎‏‎‎‏‏‏‎‎‏‏‏‎‎‎‎‏‏‎‏‎‏‏‏‎‏‎‎‎‎run foreground service with the type \"mediaProcessing\"‎‏‎‎‏‎"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‏‎‏‎‎‏‏‏‏‏‎‎‏‏‏‎‎‎‎‏‏‏‎‎‎‎‏‏‏‏‏‎‎‎‎‎‎‏‎‏‏‏‎‎‎‏‎‎‎‎‎‎‎Allows the app to make use of foreground services with the type \"mediaProcessing\"‎‏‎‎‏‎"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‏‏‏‎‏‏‎‎‎‏‎‏‏‎‏‏‎‎‏‎‏‏‏‏‎‏‏‎‎‎‎‎‎‎‎‏‏‎‎‏‎‎‏‏‎‏‏‎‏‎‎run foreground service with the type \"specialUse\"‎‏‎‎‏‎"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‎‏‏‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎‏‎‎‏‎‏‏‏‎‏‎‎‏‎‎‎‎‏‏‏‎‏‏‏‏‏‎Allows the app to make use of foreground services with the type \"specialUse\"‎‏‎‎‏‎"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‎‎‏‏‎‏‎‏‏‎‏‎‏‎‎‎‏‎‏‎‎‎‎‎‎‏‏‎‎‏‎‎‏‎‏‏‎‎‎‏‎‏‏‎‏‎‎‏‎‏‏‏‎‎‎‏‎measure app storage space‎‏‎‎‏‎"</string>
@@ -632,7 +636,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‎‎‎Use screen lock‎‏‎‎‏‎"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‏‎‏‎‏‎‏‏‏‎‎‏‏‎‏‎‎‏‎‏‏‏‎‎‏‎‏‏‎‏‏‎‏‎‏‏‎‏‏‎‏‎‎‎‎‏‎‎‏‎‏‏‏‏‏‏‎Enter your screen lock to continue‎‏‎‎‏‎"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‎‎‎‏‎‎‏‎‏‏‏‏‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‎‏‏‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎‎‎Press firmly on the sensor‎‏‎‎‏‎"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‎‏‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‏‎‏‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎‏‏‎‎‎‏‎‏‎‎Can’t recognize fingerprint. Try again.‎‏‎‎‏‎"</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‏‏‎‎‏‎‏‎‏‎‏‎‏‎‏‏‎‎‏‏‎‏‏‎‏‎‏‎‏‏‎‏‎‎‏‏‏‎‏‏‏‎‏‏‏‎‎‎‎‏‏‏‏‎‎Fingerprint not recognized. Try again.‎‏‎‎‏‎"</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‏‎‎‏‎‏‎‏‏‎‏‎‎‏‎‎‎‏‏‎‎‏‏‏‎‏‎‏‏‎‏‏‎‏‏‎‎‏‏‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎Clean fingerprint sensor and try again‎‏‎‎‏‎"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‎‏‏‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‏‏‎‏‎‎‏‏‎‎‏‏‏‎‎‎‏‎‎‎‎‏‎Clean sensor and try again‎‏‎‎‏‎"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‎‎‏‏‎‎‏‎‏‏‏‎‎‏‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‏‏‎‏‏‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‎‎‎‎Press firmly on the sensor‎‏‎‎‏‎"</string>
@@ -696,7 +700,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‏‎‏‏‏‎‏‎‏‎‎‏‏‏‎‏‎‎‏‏‎‏‎‏‏‎‏‎‎‏‎‎‏‎‏‏‎‏‎‏‏‎‎‏‎‎‎‏‏‏‎‏‏‎‎Can’t see your face. Hold your phone at eye level.‎‏‎‎‏‎"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‎‏‎‏‏‎‎‏‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‏‎‎‎‎‎‏‏‏‎‎‏‎‏‏‎‏‎‏‏‎‎‏‏‎‎‎‎Too much motion. Hold phone steady.‎‏‎‎‏‎"</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‎‎‏‏‏‏‎‏‎‏‏‏‎‏‎‏‏‎‎‏‏‏‎‎‎‏‏‎‏‏‏‎‏‏‎‏‎‏‎‏‏‏‎‏‏‏‏‎‎‏‎‎Please re-enroll your face.‎‏‎‎‏‎"</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‏‏‎‎‎‏‏‏‎‏‎‎‏‎‎‏‎‏‏‎‏‏‎‏‏‏‎‏‎‏‏‏‎‎‏‏‏‎‏‎‏‏‎‎‏‎Can’t recognize face. Try again.‎‏‎‎‏‎"</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‏‏‎‎‎‏‎‏‏‏‎‎‏‎‏‏‏‏‎‏‏‏‎‏‎‎‎‏‏‎‎‎‎‎‏‎‏‏‏‎Face not recognized. Try again.‎‏‎‎‏‎"</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‏‏‎‎‏‏‏‏‎‎‏‎‎‏‎‏‏‎‏‏‏‎‎‏‏‏‎‎‎‏‎‏‎‎‎‎‏‏‎‎‏‏‏‎‏‏‏‏‏‏‎‎Change the position of your head slightly‎‏‎‎‏‎"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‎‏‏‎‎‎‎‎‏‎‏‏‎‎‏‎‏‎‏‏‏‎‎‎‏‎‎‏‏‏‎‎‎‏‏‎‏‎‎‎‏‎‎‏‏‏‏‏‎‏‎‎‎‎‎‎Look more directly at your phone‎‏‎‎‏‎"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‏‎‎‏‎‎‏‎‎‏‏‎‏‎‎‎‏‏‏‎‏‏‎‎‏‎‏‎‏‎‏‎‎‏‎‏‏‎‏‏‎‎‎‏‎‎‏‎‎‏‏‎‎‎Look more directly at your phone‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index f6117cd..0d57e4b 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -371,6 +371,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Permite que la app se vincule al módulo de emisión móvil para reenviar los mensajes de este tipo a medida que se reciben. En algunas ubicaciones, se envían alertas de emisión móvil para advertirte en situaciones de emergencia. Es posible que las apps maliciosas interfieran con el rendimiento o el funcionamiento de tu dispositivo cuando recibes una emisión móvil de emergencia."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Administrar llamadas en curso"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Permite que una app vea detalles sobre las llamadas en curso del dispositivo y las controle."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"Leer mensajes de difusión móvil"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Permite a la aplicación leer los mensajes de difusión móvil que recibe tu dispositivo. En algunas ubicaciones, las alertas de difusión móvil se envían para informar situaciones de emergencia. Las aplicaciones maliciosas pueden afectar el rendimiento o funcionamiento de tu dispositivo cuando se recibe un un mensaje de difusión móvil de emergencia."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"leer canales suscritos"</string>
@@ -435,6 +439,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite que la app use servicios en primer plano con el tipo \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"Ejecutar un servicio en primer plano con el tipo \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Permite que la app use servicios en primer plano con el tipo \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"Ejecutar un servicio en primer plano con el tipo \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Permite que la app use servicios en primer plano con el tipo \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"Ejecuta un servicio en primer plano con el tipo \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite que la app use servicios en primer plano con el tipo \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"medir el espacio de almacenamiento de la aplicación"</string>
@@ -633,7 +639,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Usar bloqueo de pantalla"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Ingresa tu bloqueo de pantalla para continuar"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Presiona el sensor con firmeza"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"No se reconoce la huella dactilar. Vuelve a intentarlo."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"No se reconoció la huella dactilar. Vuelve a intentarlo."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Limpia el sensor de huellas dactilares y vuelve a intentarlo"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Limpia el sensor y vuelve a intentarlo"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Presiona el sensor con firmeza"</string>
@@ -697,7 +703,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"No se te ve el rostro. Sostén el teléfono a la altura de los ojos."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Te estás moviendo demasiado. No muevas el teléfono"</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Vuelve a registrar tu rostro."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"No se reconoce el rostro. Vuelve a intentarlo."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"No se reconoció el rostro. Vuelve a intentarlo."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Cambia levemente la posición de la cabeza"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Mira directamente al teléfono"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Mira directamente al teléfono"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 406f879..79be741 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -371,6 +371,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Permite que la aplicación se vincule con el módulo de difusión móvil para reenviar los mensajes de ese tipo en cuanto se reciben. En ciertas ubicaciones se envían alertas de difusión móvil para avisar de situaciones de emergencia. Cuando se recibe una alerta de difusión móvil de emergencia, ciertas aplicaciones malintencionadas podrían interferir en el rendimiento o en el funcionamiento del dispositivo."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Gestionar llamadas en curso"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Permite que una aplicación consulte datos de llamadas que estén en curso en tu dispositivo y controle esas llamadas."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"leer mensajes de difusión móvil"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Permite que la aplicación lea mensajes de difusión móvil que haya recibido el dispositivo. Las alertas de difusión móvil se envían en algunas ubicaciones para avisar de situaciones de emergencia. Es posible que las aplicaciones malintencionadas interfieran en el rendimiento o en el funcionamiento del dispositivo si se recibe una alerta de difusión móvil de emergencia."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"leer feeds a los que está suscrito el usuario"</string>
@@ -435,6 +439,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite que la aplicación use servicios en primer plano con el tipo \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"ejecutar un servicio en primer plano con el tipo \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Permite que la aplicación use servicios en primer plano con el tipo \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"ejecutar un servicio en primer plano con el tipo \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Permite que la aplicación use servicios en primer plano con el tipo \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"ejecutar un servicio en primer plano con el tipo \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite que la aplicación use servicios en primer plano con el tipo \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"medir el espacio de almacenamiento de la aplicación"</string>
@@ -633,7 +639,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Usar bloqueo de pantalla"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Introduce tu bloqueo de pantalla para continuar"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Pulsa firmemente el sensor"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"No se puede reconocer la huella digital. Inténtalo de nuevo."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Huella digital no reconocida. Inténtalo de nuevo."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Limpia el sensor de huellas digitales e inténtalo de nuevo"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Limpia el sensor e inténtalo de nuevo"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Pulsa firmemente el sensor"</string>
@@ -697,7 +703,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"No se detecta tu cara. Sujeta el teléfono a la altura de los ojos."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"El teléfono se mueve demasiado. Mantenlo quieto."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Vuelve a registrar tu cara."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"No se reconoce la cara. Inténtalo de nuevo."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Cara no reconocida. Inténtalo de nuevo."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Cambia ligeramente la posición de tu cabeza"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Mira al teléfono de forma más directa"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Mira al teléfono de forma más directa"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index afcc618..a07c4cd 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Võimaldab rakendusel luua ühenduse kärjeteadete mooduliga, et saabunud kärjeteateid edasi saata. Kärjeteateid edastatakse mõnes asukohas eriolukorrast teavitamiseks. Pahatahtlikud rakendused võivad seadme toimivust või tööd eriolukorra kärjeteate vastuvõtmisel segada."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Käimasolevate kõnede haldamine"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Võimaldab rakendusel näha teie seadmes käimasolevate kõnede üksikasju ja neid kõnesid hallata."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"mobiilsidesõnumite lugemine"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Võimaldab rakendusel lugeda seadme vastu võetud mobiilsidesõnumeid. Mobiilsidemärguandeid edastatakse mõnes asukohas eriolukorrast teavitamiseks. Pahatahtlikud rakendused võivad segada seadme toimivust või tööd eriolukorra sõnumi vastuvõtmisel."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"loe tellitud kanaleid"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Lubab rakendusel kasutada esiplaanil olevaid teenuseid, mille tüüp on „systemExempted“."</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"sellise esiplaanil oleva teenuse käitamine, mille tüüp on „fileManagement“"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Lubab rakendusel kasutada esiplaanil olevaid teenuseid, mille tüüp on „fileManagement“"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"sellise esiplaanil oleva teenuse käitamine, mille tüüp on „mediaProcessing“"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Lubab rakendusel kasutada esiplaanil olevaid teenuseid, mille tüüp on „mediaProcessing“."</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"sellise esiplaanil oleva teenuse käitamine, mille tüüp on „specialUse“"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Lubab rakendusel kasutada esiplaanil olevaid teenuseid, mille tüüp on „specialUse“."</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"Rakenduse mäluruumi mõõtmine"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Ekraaniluku kasutamine"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Jätkamiseks sisestage oma ekraanilukk"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Vajutage kindlalt andurile"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Sõrmejälge ei õnnestu tuvastada. Proovige uuesti."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Sõrmejälge ei tuvastatud. Proovige uuesti."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Puhastage sõrmejäljeandur ja proovige uuesti"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Puhastage andur ja proovige uuesti"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Vajutage kindlalt andurile"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Teie nägu ei ole näha. Hoidke telefoni silmade kõrgusel."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Liiga palju liikumist. Hoidke telefoni paigal."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Registreerige oma nägu uuesti."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Nägu ei õnnestu tuvastada. Proovige uuesti."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Nägu ei tuvastatud. Proovige uuesti."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Muutke pisut oma pea asendit"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Vaadake otse telefoni"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Vaadake otse telefoni"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 2b9c555..b3f2ce2 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -347,51 +347,55 @@
     <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Pantaila-argazkiak atera ditzake."</string>
     <string name="dream_preview_title" msgid="5570751491996100804">"Aurrebista, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"desgaitu edo aldatu egoera-barra"</string>
-    <string name="permdesc_statusBar" msgid="5809162768651019642">"Egoera-barra desgaitzea edo sistema-ikonoak gehitzea edo kentzea baimentzen die aplikazioei."</string>
+    <string name="permdesc_statusBar" msgid="5809162768651019642">"Egoera-barra desgaitzeko edo sistema-ikonoak gehitzeko edo kentzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"bihurtu egoera-barra"</string>
-    <string name="permdesc_statusBarService" msgid="6652917399085712557">"Egoera-barra izateko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_statusBarService" msgid="6652917399085712557">"Egoera-barra izateko baimena ematen dio aplikazioari."</string>
     <string name="permlab_expandStatusBar" msgid="1184232794782141698">"zabaldu/tolestu egoera-barra"</string>
-    <string name="permdesc_expandStatusBar" msgid="7180756900448498536">"Egoera-barra zabaltzeko edo tolesteko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_expandStatusBar" msgid="7180756900448498536">"Egoera-barra zabaltzeko edo tolesteko baimena ematen dio aplikazioari."</string>
     <string name="permlab_fullScreenIntent" msgid="4310888199502509104">"blokeatutako gailu batean jakinarazpenak pantaila osoko jarduera gisa bistaratzea"</string>
-    <string name="permdesc_fullScreenIntent" msgid="1100721419406643997">"Blokeatutako gailu batean jakinarazpenak pantaila osoko jarduera gisa bistaratzeko baimena ematen die aplikazioei"</string>
+    <string name="permdesc_fullScreenIntent" msgid="1100721419406643997">"Blokeatutako gailu batean jakinarazpenak pantaila osoko jarduera gisa bistaratzeko baimena ematen dio aplikazioari"</string>
     <string name="permlab_install_shortcut" msgid="7451554307502256221">"Instalatu lasterbideak"</string>
     <string name="permdesc_install_shortcut" msgid="4476328467240212503">"Erabiltzaileak ezer egin gabe hasierako pantailan lasterbideak gehitzeko aukera ematen die aplikazioei."</string>
     <string name="permlab_uninstall_shortcut" msgid="295263654781900390">"desinstalatu lasterbideak"</string>
     <string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"Erabiltzaileak ezer egin gabe hasierako pantailako lasterbideak kentzeko aukera ematen die aplikazioei."</string>
     <string name="permlab_processOutgoingCalls" msgid="4075056020714266558">"birbideratu irteerako deiak"</string>
-    <string name="permdesc_processOutgoingCalls" msgid="7833149750590606334">"Irteerako deian markatutako zenbakia ikusteko baimena ematen die aplikazioei, deia beste zenbaki batera birbideratzeko edo deia bertan behera uzteko aukerarekin."</string>
+    <string name="permdesc_processOutgoingCalls" msgid="7833149750590606334">"Irteerako deian markatutako zenbakia ikusteko baimena ematen dio aplikazioari, deia beste zenbaki batera birbideratzeko edo deia bertan behera uzteko aukerarekin."</string>
     <string name="permlab_answerPhoneCalls" msgid="4131324833663725855">"erantzun telefono-deiak"</string>
-    <string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"Sarrerako deiak hartzea baimentzen die aplikazioei."</string>
+    <string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"Sarrerako deiak hartzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_receiveSms" msgid="505961632050451881">"jaso testu-mezuak (SMSak)"</string>
-    <string name="permdesc_receiveSms" msgid="1797345626687832285">"SMS mezuak jasotzeko eta prozesatzeko baimena ematen die aplikazioei. Horrela, aplikazioak gailura bidalitako mezuak kontrola eta ezaba ditzake zuri erakutsi gabe."</string>
+    <string name="permdesc_receiveSms" msgid="1797345626687832285">"SMS mezuak jasotzeko eta prozesatzeko baimena ematen dio aplikazioari. Horrela, aplikazioak gailura bidalitako mezuak kontrola eta ezaba ditzake zuri erakutsi gabe."</string>
     <string name="permlab_receiveMms" msgid="4000650116674380275">"jaso testu-mezuak (MMSak)"</string>
-    <string name="permdesc_receiveMms" msgid="958102423732219710">"MMS mezuak jasotzeko eta prozesatzeko baimena ematen die aplikazioei. Horrela, aplikazioak gailura bidalitako mezuak kontrola eta ezaba ditzake zuri erakutsi gabe."</string>
+    <string name="permdesc_receiveMms" msgid="958102423732219710">"MMS mezuak jasotzeko eta prozesatzeko baimena ematen dio aplikazioari. Horrela, aplikazioak gailura bidalitako mezuak kontrola eta ezaba ditzake zuri erakutsi gabe."</string>
     <string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"desbideratu sare mugikor bidezko igorpen-mezuak"</string>
-    <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Sare mugikor bidezko iragarpen-modulura lotzeko baimena ematen die aplikazioei, sare mugikor bidezko iragarpen-mezuak jaso ahala desbideratu ahal izateko. Sare mugikor bidezko iragarpen-alertak kokapen batzuetan entregatzen dira larrialdi-egoeren berri emateko. Sare mugikor bidezko larrialdi-iragarpenak jasotzean, asmo txarreko aplikazioek gailuaren errendimenduari edota funtzionamenduari eragin diezaiokete."</string>
+    <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Sare mugikor bidezko iragarpen-modulura lotzeko baimena ematen dio aplikazioari, sare mugikor bidezko iragarpen-mezuak jaso ahala desbideratu ahal izateko. Sare mugikor bidezko iragarpen-alertak kokapen batzuetan entregatzen dira larrialdi-egoeren berri emateko. Sare mugikor bidezko larrialdi-iragarpenak jasotzean, asmo txarreko aplikazioek gailuaren errendimenduari edota funtzionamenduari eragin diezaiokete."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Kudeatu abian dauden deiak"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Gailuak jasotzen dituen deiei buruzko xehetasunak ikusteko eta dei horiek kontrolatzeko baimena ematen die aplikazioei."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"irakurri sare mugikor bidezko igorpen-mezuak"</string>
-    <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Gailuak jasotako sare mugikor bidezko igorpen-mezuak irakurtzeko baimena ematen die aplikazioei. Sare mugikor bidezko igorpen-alertak kokapen batzuetan ematen dira larrialdi-egoeren berri emateko. Asmo txarreko aplikazioek gailuaren errendimendua edo funtzionamendua oztopa dezakete larrialdi-igorpen horietako bat jasotzen denean."</string>
+    <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Gailuak jasotako sare mugikor bidezko igorpen-mezuak irakurtzeko baimena ematen dio aplikazioari. Sare mugikor bidezko igorpen-alertak kokapen batzuetan ematen dira larrialdi-egoeren berri emateko. Asmo txarreko aplikazioek gailuaren errendimendua edo funtzionamendua oztopa dezakete larrialdi-igorpen horietako bat jasotzen denean."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"irakurri harpidetutako jarioak"</string>
-    <string name="permdesc_subscribedFeedsRead" msgid="6911349196661811865">"Une horretan sinkronizatutako jarioei buruzko xehetasunak lortzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_subscribedFeedsRead" msgid="6911349196661811865">"Une horretan sinkronizatutako jarioei buruzko xehetasunak lortzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_sendSms" msgid="7757368721742014252">"bidali eta ikusi SMS mezuak"</string>
-    <string name="permdesc_sendSms" msgid="6757089798435130769">"SMS mezuak bidaltzeko baimena ematen die aplikazioei. Horrela, ustekabeko gastuak eragin daitezke. Asmo txarreko aplikazioek erabil dezakete zuk berretsi gabeko mezuak bidalita gastuak eragiteko."</string>
+    <string name="permdesc_sendSms" msgid="6757089798435130769">"SMS mezuak bidaltzeko baimena ematen dio aplikazioari. Horrela, ustekabeko gastuak eragin daitezke. Asmo txarreko aplikazioek erabil dezakete zuk berretsi gabeko mezuak bidalita gastuak eragiteko."</string>
     <string name="permlab_readSms" msgid="5164176626258800297">"irakurri testu-mezuak (SMSak edo MMSak)"</string>
     <string name="permdesc_readSms" product="tablet" msgid="7912990447198112829">"Aplikazioak tabletan gordetako SMS mezu (testu-mezu) guztiak irakur ditzake."</string>
     <string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"Aplikazioek Android TV gailuan gordetako SMS (testu) mezu guztiak irakur ditzakete."</string>
     <string name="permdesc_readSms" product="default" msgid="774753371111699782">"Aplikazioak telefonoan gordetako SMS mezu (testu-mezu) guztiak irakur ditzake."</string>
     <string name="permlab_receiveWapPush" msgid="4223747702856929056">"jaso testu-mezuak (WAP bidezkoak)"</string>
-    <string name="permdesc_receiveWapPush" msgid="1638677888301778457">"WAP mezuak jasotzeko eta prozesatzeko baimena ematen die aplikazioei. Horrela, aplikazioak, besteak beste, gailura bidalitako mezuak kontrola eta ezaba ditzake zuri erakutsi gabe."</string>
+    <string name="permdesc_receiveWapPush" msgid="1638677888301778457">"WAP mezuak jasotzeko eta prozesatzeko baimena ematen dio aplikazioari. Horrela, aplikazioak, besteak beste, gailura bidalitako mezuak kontrola eta ezaba ditzake zuri erakutsi gabe."</string>
     <string name="permlab_getTasks" msgid="7460048811831750262">"eskuratu abian diren aplikazioak"</string>
-    <string name="permdesc_getTasks" msgid="7388138607018233726">"Une honetan edo duela gutxi exekutatutako zereginei buruzko informazioa lortzeko baimena ematen die aplikazioei. Horrela, aplikazioak gailuan erabiltzen ari diren aplikazioei buruzko informazioa ezagut dezake."</string>
+    <string name="permdesc_getTasks" msgid="7388138607018233726">"Une honetan edo duela gutxi exekutatutako zereginei buruzko informazioa lortzeko baimena ematen dio aplikazioari. Horrela, aplikazioak gailuan erabiltzen ari diren aplikazioei buruzko informazioa ezagut dezake."</string>
     <string name="permlab_manageProfileAndDeviceOwners" msgid="639849495253987493">"kudeatu profilen eta gailuen jabeak"</string>
     <string name="permdesc_manageProfileAndDeviceOwners" msgid="7304240671781989283">"Profilaren eta gailuaren jabeak zehazteko baimena ematen die aplikazioei."</string>
     <string name="permlab_reorderTasks" msgid="7598562301992923804">"ordenatu abian diren aplikazioak"</string>
-    <string name="permdesc_reorderTasks" msgid="8796089937352344183">"Zereginak aurreko eta atzeko planora eramateko baimena ematen die aplikazioei. Aplikazioak zuk ezer egin gabe egin dezake hori."</string>
+    <string name="permdesc_reorderTasks" msgid="8796089937352344183">"Zereginak aurreko eta atzeko planora eramateko baimena ematen dio aplikazioari. Aplikazioak zuk ezer egin gabe egin dezake hori."</string>
     <string name="permlab_enableCarMode" msgid="893019409519325311">"gaitu auto modua"</string>
-    <string name="permdesc_enableCarMode" msgid="56419168820473508">"Auto modua gaitzea baimentzen die aplikazioei."</string>
+    <string name="permdesc_enableCarMode" msgid="56419168820473508">"Auto modua gaitzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_killBackgroundProcesses" msgid="6559320515561928348">"itxi beste aplikazioak"</string>
-    <string name="permdesc_killBackgroundProcesses" msgid="2357013583055434685">"Beste aplikazioen atzeko planoko prozesuak amaitzeko baimena ematen die aplikazioei. Horrela, agian aplikazio batzuk exekutatzeari utziko zaio."</string>
+    <string name="permdesc_killBackgroundProcesses" msgid="2357013583055434685">"Beste aplikazioen atzeko planoko prozesuak amaitzeko baimena ematen dio aplikazioari. Horrela, agian aplikazio batzuk exekutatzeari utziko zaio."</string>
     <string name="permlab_systemAlertWindow" msgid="5757218350944719065">"agertu beste aplikazio batzuen gainean"</string>
     <string name="permdesc_systemAlertWindow" msgid="1145660714855738308">"Beste aplikazio batzuen edo pantailako beste zati batzuen gainean ager daiteke aplikazioa. Aplikazioaren funtzionamendu normala oztopa dezake eta beste aplikazio batzuen itxura alda dezake."</string>
     <string name="permlab_hideOverlayWindows" msgid="6382697828482271802">"ezkutatu gainjarritako aplikazioak"</string>
@@ -405,11 +409,11 @@
     <string name="permlab_use_exact_alarm" msgid="348045139777131552">"antolatu alarmak edo gertaera-abisuak"</string>
     <string name="permdesc_use_exact_alarm" msgid="7033761461886938912">"Aplikazioak hainbat ekintza programa ditzake; adibidez, alarmak eta abisuak, etorkizuneko une batean jakinarazpen bat jaso dezazun."</string>
     <string name="permlab_persistentActivity" msgid="464970041740567970">"izan aplikazioa beti abian"</string>
-    <string name="permdesc_persistentActivity" product="tablet" msgid="6055271149187369916">"Beren zati batzuk memoria modu iraunkorrean ezartzeko baimena ematen die aplikazioei. Horrela, beste aplikazioek erabilgarri duten memoria murritz daiteke eta tableta motel daiteke."</string>
-    <string name="permdesc_persistentActivity" product="tv" msgid="6800526387664131321">"Beren zati batzuk memorian modu iraunkorrean ezartzeko baimena ematen die aplikazioei. Ondorioz, beste aplikazioek memoria gutxiago izan lezakete erabilgarri, eta Android TV gailuak motelago funtziona lezake."</string>
-    <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Beren zati batzuk memoria modu iraunkorrean ezartzeko baimena ematen die aplikazioei. Horrela, beste aplikazioek erabilgarri duten memoria murritz daiteke eta telefonoa motel daiteke."</string>
+    <string name="permdesc_persistentActivity" product="tablet" msgid="6055271149187369916">"Bere zati batzuk memoria modu iraunkorrean ezartzeko baimena ematen dio aplikazioari. Horrela, beste aplikazioek erabilgarri duten memoria murritz daiteke eta tableta motel daiteke."</string>
+    <string name="permdesc_persistentActivity" product="tv" msgid="6800526387664131321">"Bere zati batzuk memorian modu iraunkorrean ezartzeko baimena ematen dio aplikazioari. Ondorioz, beste aplikazioek memoria gutxiago izan lezakete erabilgarri, eta Android TV gailuak motelago funtziona lezake."</string>
+    <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Beren zati batzuk memoria modu iraunkorrean ezartzeko baimena ematen dio aplikazioari. Horrela, beste aplikazioek erabilgarri duten memoria murritz daiteke eta telefonoa motel daiteke."</string>
     <string name="permlab_foregroundService" msgid="1768855976818467491">"abiarazi zerbitzuak aurreko planoan"</string>
-    <string name="permdesc_foregroundService" msgid="8720071450020922795">"Aurreko planoko zerbitzuak erabiltzea baimentzen dio aplikazioari."</string>
+    <string name="permdesc_foregroundService" msgid="8720071450020922795">"Aurreko planoko zerbitzuak erabiltzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"exekutatu aurreko planoko zerbitzu bat (camera motakoa)"</string>
     <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Aurreko planoko zerbitzuak (camera motakoak) erabiltzeko baimena ematen dio aplikazioari"</string>
     <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"exekutatu aurreko planoko zerbitzu bat (connectedDevice motakoa)"</string>
@@ -433,39 +437,41 @@
     <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"exekutatu aurreko planoko zerbitzu bat (systemExempted motakoa)"</string>
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Aurreko planoko zerbitzuak (systemExempted motakoak) erabiltzeko baimena ematen dio aplikazioari"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"exekutatu aurreko planoko zerbitzu bat (fileManagement motakoa)"</string>
-    <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Aurreko planoko zerbitzuak (fileManagement motakoak) erabiltzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Aurreko planoko zerbitzuak (fileManagement motakoak) erabiltzeko baimena ematen dio aplikazioari"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"aurreko planoko zerbitzu bat exekutatu (mediaProcessing motakoa)"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Aurreko planoko zerbitzuak (mediaProcessing motakoak) erabiltzeko baimena ematen dio aplikazioari"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"exekutatu aurreko planoko zerbitzu bat (specialUse motakoa)"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Aurreko planoko zerbitzuak (specialUse motakoak) erabiltzeko baimena ematen dio aplikazioari"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"neurtu aplikazioen biltegiratzeko tokia"</string>
-    <string name="permdesc_getPackageSize" msgid="742743530909966782">"Bere kodea, datuak eta cache-tamainak eskuratzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_getPackageSize" msgid="742743530909966782">"Bere kodea, datuak eta cache-tamainak eskuratzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"aldatu sistemaren ezarpenak"</string>
-    <string name="permdesc_writeSettings" msgid="8293047411196067188">"Sistemaren ezarpenen datuak aldatzeko baimena ematen die aplikazioei. Asmo txarreko aplikazioek sistemaren konfigurazioa hondatzeko erabil dezakete."</string>
+    <string name="permdesc_writeSettings" msgid="8293047411196067188">"Sistemaren ezarpenen datuak aldatzeko baimena ematen dio aplikazioari. Asmo txarreko aplikazioek sistemaren konfigurazioa hondatzeko erabil dezakete."</string>
     <string name="permlab_receiveBootCompleted" msgid="6643339400247325379">"exekutatu abiaraztean"</string>
-    <string name="permdesc_receiveBootCompleted" product="tablet" msgid="5565659082718177484">"Sistema berrabiarazi bezain laster abiarazteko baimena ematen die aplikazioei. Horrela, agian denbora gehiago beharko du tabletak abiarazteko, eta tabletaren funtzionamendu orokorra mantso daiteke, baimen hori duten aplikazioak beti abian egongo baitira."</string>
-    <string name="permdesc_receiveBootCompleted" product="tv" msgid="4900842256047614307">"Sistema abiarazi bezain laster beren burua abiarazteko baimena ematen die aplikazioei. Baliteke denbora gehiago behar izatea Android TV gailua abiarazteko eta aplikazioek gailua orokorrean mantsoago ibilarazteko baimena izatea, beti abian izango baita."</string>
-    <string name="permdesc_receiveBootCompleted" product="default" msgid="7912677044558690092">"Sistema berrabiarazi bezain laster abiarazteko baimena ematen die aplikazioei. Horrela, agian denbora gehiago beharko du telefonoak abiarazteko, eta telefonoaren funtzionamendu orokorra mantso daiteke, baimen hori duten aplikazioak beti abian egongo baitira."</string>
+    <string name="permdesc_receiveBootCompleted" product="tablet" msgid="5565659082718177484">"Sistema berrabiarazi bezain laster abiarazteko baimena ematen dio aplikazioari. Horrela, agian denbora gehiago beharko du tabletak abiarazteko, eta tabletaren funtzionamendu orokorra mantso daiteke, baimen hori duten aplikazioak beti abian egongo baitira."</string>
+    <string name="permdesc_receiveBootCompleted" product="tv" msgid="4900842256047614307">"Sistema abiarazi bezain laster bere burua abiarazteko baimena ematen dio aplikazioari. Baliteke denbora gehiago behar izatea Android TV gailua abiarazteko eta aplikazioak gailua orokorrean mantsoago ibilarazteko baimena izatea, beti abian izango baita."</string>
+    <string name="permdesc_receiveBootCompleted" product="default" msgid="7912677044558690092">"Sistema berrabiarazi bezain laster abiarazteko baimena ematen dio aplikazioari. Horrela, agian denbora gehiago beharko du telefonoak abiarazteko, eta telefonoaren funtzionamendu orokorra mantso daiteke, baimen hori duten aplikazioak beti abian egongo baitira."</string>
     <string name="permlab_broadcastSticky" msgid="4552241916400572230">"bidali igorpen erakargarria"</string>
-    <string name="permdesc_broadcastSticky" product="tablet" msgid="5058486069846384013">"Igorpen iraunkorrak emateko baimena ematen die; horiek igorpena amaitu ondoren mantentzen dira. Gehiegi erabiliz gero, tableta motel edo ezegonkor ibiliko da, memoria gehiago erabiliko delako."</string>
-    <string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Igorpen iraunkorrak egiteko baimena ematen die aplikazioei. Igorpena amaitu ondoren ere igortzen jarraitzen dute igorpen iraunkorrek. Gehiegi erabiliz gero, Android TV gailua motel edo ezegonkor ibiliko da, memoria gehiago erabiliko delako."</string>
-    <string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Igorpen iraunkorrak emateko baimena ematen die; horiek igorpena amaitu ondoren mantentzen dira. Gehiegi erabiliz gero, telefonoa motel edo ezegonkor ibiliko da, memoria gehiago erabiliko delako."</string>
+    <string name="permdesc_broadcastSticky" product="tablet" msgid="5058486069846384013">"Igorpen iraunkorrak emateko baimena ematen dio aplikazioari; horiek igorpena amaitu ondoren mantentzen dira. Gehiegi erabiliz gero, tableta motel edo ezegonkor ibiliko da, memoria gehiago erabiliko delako."</string>
+    <string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Igorpen iraunkorrak egiteko baimena ematen dio aplikazioari. Igorpena amaitu ondoren ere igortzen jarraitzen dute igorpen iraunkorrek. Gehiegi erabiliz gero, Android TV gailua motel edo ezegonkor ibiliko da, memoria gehiago erabiliko delako."</string>
+    <string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Igorpen iraunkorrak emateko baimena ematen dio aplikazioari; horiek igorpena amaitu ondoren mantentzen dira. Gehiegi erabiliz gero, telefonoa motel edo ezegonkor ibiliko da, memoria gehiago erabiliko delako."</string>
     <string name="permlab_readContacts" msgid="8776395111787429099">"irakurri kontaktuak"</string>
-    <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Tabletan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen die aplikazioei. Kontaktuak sortu dituzten tabletako kontuak ere atzitu ahalko dituzte aplikazioek. Horrek barnean hartuko ditu instalatutako aplikazioek sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzakete aplikazioek, eta baliteke asmo txarreko aplikazioek zuk jakin gabe partekatzea datu horiek."</string>
-    <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Android TV gailuan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen die aplikazioei. Kontaktuak sortu dituzten Android TV gailuko kontuak ere atzitu ahalko dituzte aplikazioek. Horrek barnean hartuko ditu instalatutako aplikazioek sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzakete aplikazioek, eta baliteke asmo txarreko aplikazioek zuk jakin gabe partekatzea datu horiek."</string>
-    <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Telefonoan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen die aplikazioei. Kontaktuak sortu dituzten telefonoko kontuak ere atzitu ahalko dituzte aplikazioek. Horrek barnean hartuko ditu instalatutako aplikazioek sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzakete aplikazioek, eta baliteke asmo txarreko aplikazioek zuk jakin gabe partekatzea datu horiek."</string>
+    <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Tabletan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten tabletako kontuak ere atzitu ahalko ditu aplikazioak. Horrek barnean hartuko ditu instalatutako aplikazioak sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzake aplikazioak, eta baliteke asmo txarreko aplikazioek zuk jakin gabe partekatzea datu horiek."</string>
+    <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Android TV gailuan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten Android TV gailuko kontuak ere atzitu ahalko ditu aplikazioak. Horrek barnean hartuko ditu instalatutako aplikazioak sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzake aplikazioak, eta baliteke asmo txarreko aplikazioek zuk jakin gabe partekatzea datu horiek."</string>
+    <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Telefonoan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten telefonoko kontuak ere atzitu ahalko ditu aplikazioak. Horrek barnean hartuko ditu instalatutako aplikazioak sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzake aplikazioak, eta baliteke asmo txarreko aplikazioek zuk jakin gabe partekatzea datu horiek."</string>
     <string name="permlab_writeContacts" msgid="8919430536404830430">"aldatu kontaktuak"</string>
-    <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Tabletan gordetako kontaktuei buruzko datuak aldatzeko baimena ematen die aplikazioei. Baimen horrekin, aplikazioek kontaktuen datuak ezaba ditzakete."</string>
-    <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Android TV gailuan gordetako kontaktuei buruzko datuak aldatzeko baimena ematen die aplikazioei. Baimen horrekin, aplikazioek kontaktuen datuak ezaba ditzakete."</string>
-    <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Telefonoan gordetako kontaktuei buruzko datuak aldatzeko baimena ematen die aplikazioei. Baimen horrekin, aplikazioek kontaktuen datuak ezaba ditzakete."</string>
+    <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Tabletan gordetako kontaktuei buruzko datuak aldatzeko baimena ematen dio aplikazioari. Baimen horrekin, aplikazioak kontaktuen datuak ezaba ditzake."</string>
+    <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Android TV gailuan gordetako kontaktuei buruzko datuak aldatzeko baimena ematen dio aplikazioari. Baimen horrekin, aplikazioak kontaktuen datuak ezaba ditzake."</string>
+    <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Telefonoan gordetako kontaktuei buruzko datuak aldatzeko baimena ematen dio aplikazioari. Baimen horrekin, aplikazioak kontaktuen datuak ezaba ditzake."</string>
     <string name="permlab_readCallLog" msgid="1739990210293505948">"irakurri deien erregistroa"</string>
     <string name="permdesc_readCallLog" msgid="8964770895425873433">"Aplikazioak deien historia irakur dezake."</string>
     <string name="permlab_writeCallLog" msgid="670292975137658895">"idatzi deien erregistroan"</string>
-    <string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"Tabletaren deien erregistroa aldatzeko baimena ematen die aplikazioei, sarrerako eta irteerako deiei buruzko datuak barne. Asmo txarreko aplikazioek deien erregistroa ezabatzeko edo aldatzeko erabil dezakete."</string>
-    <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"Android TV gailuko deien erregistroa aldatzeko baimena ematen die aplikazioei, sarrerako eta irteerako deiei buruzko datuak barne. Baliteke asmo txarreko aplikazioek deien erregistroa ezabatzea edo aldatzea."</string>
-    <string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"Telefonoaren deien erregistroa aldatzeko baimena ematen die aplikazioei, sarrerako eta irteerako deiei buruzko datuak barne. Asmo txarreko aplikazioek deien erregistroa ezabatzeko edo aldatzeko erabil dezakete."</string>
+    <string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"Tabletaren deien erregistroa aldatzeko baimena ematen dio aplikazioari, sarrerako eta irteerako deiei buruzko datuak barne. Asmo txarreko aplikazioek deien erregistroa ezabatzeko edo aldatzeko erabil dezakete."</string>
+    <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"Android TV gailuko deien erregistroa aldatzeko baimena ematen dio aplikazioari, sarrerako eta irteerako deiei buruzko datuak barne. Baliteke asmo txarreko aplikazioek deien erregistroa ezabatzea edo aldatzea."</string>
+    <string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"Telefonoaren deien erregistroa aldatzeko baimena ematen dio aplikazioari, sarrerako eta irteerako deiei buruzko datuak barne. Asmo txarreko aplikazioek deien erregistroa ezabatzeko edo aldatzeko erabil dezakete."</string>
     <string name="permlab_bodySensors" msgid="662918578601619569">"Atzitu gorputz-sentsoreen datuak (esaterako, bihotz-maiztasuna) aplikazioa erabili bitartean"</string>
-    <string name="permdesc_bodySensors" product="default" msgid="7652650410295512140">"Aplikazioak erabiltzen diren bitartean, gorputz-sentsoreen datuak (besteak beste, bihotz-maiztasuna, tenperatura eta odolean dagoen oxigenoaren ehunekoa) erabiltzeko baimena ematen die aplikazio horiei."</string>
+    <string name="permdesc_bodySensors" product="default" msgid="7652650410295512140">"Aplikazioa erabiltzen den bitartean, gorputz-sentsoreen datuak (besteak beste, bihotz-maiztasuna, tenperatura eta odolean dagoen oxigenoaren ehunekoa) erabiltzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_bodySensors_background" msgid="4912560779957760446">"Atzitu gorputz-sentsoreen datuak (adib., bihotz-maiztasunarenak) atzeko planoan"</string>
-    <string name="permdesc_bodySensors_background" product="default" msgid="8870726027557749417">"Aplikazioak atzeko planoan egon bitartean, gorputz-sentsoreen datuak (besteak beste, bihotz-maiztasuna, tenperatura eta odolean dagoen oxigenoaren ehunekoa) erabiltzeko baimena ematen die aplikazio horiei."</string>
+    <string name="permdesc_bodySensors_background" product="default" msgid="8870726027557749417">"Aplikazioa atzeko planoan egon bitartean, gorputz-sentsoreen datuak (besteak beste, bihotz-maiztasuna, tenperatura eta odolean dagoen oxigenoaren ehunekoa) erabiltzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_readCalendar" msgid="6408654259475396200">"irakurri egutegiko gertaerak eta xehetasunak"</string>
     <string name="permdesc_readCalendar" product="tablet" msgid="515452384059803326">"Aplikazioak tabletan gordetako egutegiko gertaerak irakur ditzake eta egutegiko datuak parteka eta gorde ditzake."</string>
     <string name="permdesc_readCalendar" product="tv" msgid="5811726712981647628">"Aplikazioak Android TV gailuan gordeta dituzun egutegiko gertaerak irakur ditzake, baita egutegiko datuak partekatu eta gorde ere."</string>
@@ -475,7 +481,7 @@
     <string name="permdesc_writeCalendar" product="tv" msgid="951246749004952706">"Android TV gailuan egutegiko gertaerak gehitzeko eta gehitutakoak kentzeko edo aldatzeko aukera dute aplikazioek. Gainera, egutegien jabeenak diruditen mezuak bidal ditzakete, edo gertaerak aldatu jabeei ezer esan gabe."</string>
     <string name="permdesc_writeCalendar" product="default" msgid="5416380074475634233">"Telefonoko gertaerak gehitzeko, kentzeko edo aldatzeko aukera du aplikazioak. Gainera, egutegien jabeenak diruditen mezuak bidal ditzake, eta gertaerak alda ditzake jabeei beraiei jakinarazi gabe."</string>
     <string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"atzitu kokapen-hornitzaileen komando gehigarriak"</string>
-    <string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Kokapen-hornitzailearen agindu gehigarriak erabiltzeko baimena ematen die aplikazioei. Horrela, agian aplikazioek GPSaren edo bestelako kokapenaren iturburuen funtzionamenduan eragina izan dezakete."</string>
+    <string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Kokapen-hornitzailearen agindu gehigarriak erabiltzeko baimena ematen dio aplikazioari. Horrela, agian aplikazioak GPSaren edo bestelako kokapenaren iturburuen funtzionamenduan eragina izan dezake."</string>
     <string name="permlab_accessFineLocation" msgid="6426318438195622966">"lortu kokapen zehatza aurreko planoan bakarrik"</string>
     <string name="permdesc_accessFineLocation" msgid="6732174080240016335">"Abian denean, aplikazioak kokapen zehatza lor dezake kokapen-zerbitzuen bidez. Aplikazioak kokapena lortu ahal izateko, kokapen-zerbitzuek aktibatuta egon behar dute gailuan. Agian bateria gehiago erabiliko du."</string>
     <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"atzitu gutxi gorabeherako kokapena aurreko planoan bakarrik"</string>
@@ -483,7 +489,7 @@
     <string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"atzitu kokapena atzeko planoan"</string>
     <string name="permdesc_accessBackgroundLocation" msgid="8264885066095638105">"Aplikazioak kokapena atzi dezake, baita aplikazioa erabiltzen ari ez zarenean ere."</string>
     <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"aldatu audio-ezarpenak"</string>
-    <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Audio-ezarpen orokorrak aldatzeko baimena ematen die aplikazioei; besteak beste, bolumena eta irteerarako zer bozgorailu erabiltzen den."</string>
+    <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Audio-ezarpen orokorrak aldatzeko baimena ematen dio aplikazioari; besteak beste, bolumena eta irteerarako zer bozgorailu erabiltzen den."</string>
     <string name="permlab_recordAudio" msgid="1208457423054219147">"grabatu audioa"</string>
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Aplikazioak abian den bitartean erabil dezake mikrofonoa audioa grabatzeko."</string>
     <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Audioa grabatu atzeko planoan."</string>
@@ -491,7 +497,7 @@
     <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"hauteman pantaila-argazkiak aplikazioen leihoetan"</string>
     <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Aplikazioa erabili bitartean pantaila-argazki bat ateratzen denean, jakinarazpen bat jasoko duzu aplikazioan."</string>
     <string name="permlab_sim_communication" msgid="176788115994050692">"bidali aginduak SIM txartelera"</string>
-    <string name="permdesc_sim_communication" msgid="4179799296415957960">"SIM txartelera aginduak bidaltzeko baimena ematen die aplikazioei. Oso arriskutsua da."</string>
+    <string name="permdesc_sim_communication" msgid="4179799296415957960">"SIM txartelera aginduak bidaltzeko baimena ematen dio aplikazioari. Oso arriskutsua da."</string>
     <string name="permlab_activityRecognition" msgid="1782303296053990884">"hauteman jarduera fisikoa"</string>
     <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Aplikazioak jarduera fisikoa hauteman dezake."</string>
     <string name="permlab_camera" msgid="6320282492904119413">"atera argazkiak eta grabatu bideoak"</string>
@@ -505,118 +511,118 @@
     <string name="permlab_cameraHeadlessSystemUser" msgid="680194666834500050">"Eman interfaze grafikorik gabeko sistema-erabiltzaile gisa kamera erabiltzeko baimena aplikazio edo zerbitzu bati."</string>
     <string name="permdesc_cameraHeadlessSystemUser" msgid="6963163319710996412">"Aplikazio honek interfaze grafikorik gabeko sistema-erabiltzaile gisa erabil dezake kamera."</string>
     <string name="permlab_vibrate" msgid="8596800035791962017">"kontrolatu dardara"</string>
-    <string name="permdesc_vibrate" msgid="8733343234582083721">"Bibragailua kontrolatzeko baimena ematen die aplikazioei."</string>
-    <string name="permdesc_vibrator_state" msgid="7050024956594170724">"Dardara-egoera erabiltzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_vibrate" msgid="8733343234582083721">"Bibragailua kontrolatzeko baimena ematen dio aplikazioari."</string>
+    <string name="permdesc_vibrator_state" msgid="7050024956594170724">"Dardara-egoera erabiltzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_callPhone" msgid="1798582257194643320">"deitu zuzenean telefono-zenbakietara"</string>
-    <string name="permdesc_callPhone" msgid="7892422187827695656">"Zuk ezer egin beharrik gabe, telefono-zenbakietara deitzeko baimena ematen die aplikazioei. Ondorioz, baliteke ustekabeko gastuak edo deiak eragitea. Kontuan izan aplikazioak ezingo duela deitu larrialdietarako zenbakietara. Zuk berretsi gabeko deiak eginda, asmo txarreko aplikazioek baimen hori erabil dezakete gastuak eragiteko edo operadore-kode jakin batzuk markatzeko, sarrerako deiak beste zenbaki batera automatikoki desbideratzeko asmoarekin."</string>
+    <string name="permdesc_callPhone" msgid="7892422187827695656">"Zuk ezer egin beharrik gabe, telefono-zenbakietara deitzeko baimena ematen dio aplikazioari. Ondorioz, baliteke ustekabeko gastuak edo deiak eragitea. Kontuan izan aplikazioak ezingo duela deitu larrialdietarako zenbakietara. Zuk berretsi gabeko deiak eginda, asmo txarreko aplikazioek baimen hori erabil dezakete gastuak eragiteko edo operadore-kode jakin batzuk markatzeko, sarrerako deiak beste zenbaki batera automatikoki desbideratzeko asmoarekin."</string>
     <string name="permlab_accessImsCallService" msgid="442192920714863782">"atzitu IMS dei-zerbitzua"</string>
-    <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Zuk ezer egin beharrik gabe deiak egiteko IMS zerbitzua erabiltzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Zuk ezer egin beharrik gabe deiak egiteko IMS zerbitzua erabiltzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"irakurri telefonoaren egoera eta identitatea"</string>
-    <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Gailuaren telefono-eginbideak erabiltzeko baimena ematen die aplikazioei. Baimen horrek aplikazioari telefono-zenbakia eta gailu IDak zein diren, deirik aktibo dagoen eta deia zer zenbakirekin konektatuta dagoen zehazteko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Gailuaren telefono-eginbideak erabiltzeko baimena ematen dio aplikazioari. Baimen horrek aplikazioari telefono-zenbakia eta gailu IDak zein diren, deirik aktibo dagoen eta deia zer zenbakirekin konektatuta dagoen zehazteko baimena ematen dio aplikazioari."</string>
     <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"irakurri oinarrizko egoera telefonikoa eta identitatea"</string>
     <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Gailuaren oinarrizko eginbide telefonikoak erabiltzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"bideratu deiak sistemaren bidez"</string>
-    <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Deiak sistemaren bidez bideratzea baimentzen die aplikazioei, deien zerbitzua ahal bezain ona izan dadin."</string>
+    <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Deiak sistemaren bidez bideratzeko baimena ematen dio aplikazioari, deien zerbitzua ahal bezain ona izan dadin."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"ikusi eta kontrolatu deiak sistemaren bidez."</string>
-    <string name="permdesc_callCompanionApp" msgid="8474168926184156261">"Gailuan abian diren deiak eta deion informazioa ikusi eta kontrolatzeko baimena ematen die aplikazioei; besteak beste, deien zenbakiak eta deien egoera."</string>
+    <string name="permdesc_callCompanionApp" msgid="8474168926184156261">"Gailuan abian diren deiak eta deion informazioa ikusi eta kontrolatzeko baimena ematen dio aplikazioari; besteak beste, deien zenbakiak eta deien egoera."</string>
     <string name="permlab_exemptFromAudioRecordRestrictions" msgid="1164725468350759486">"salbuetsi audioa grabatzeko murriztapenen aurrean"</string>
     <string name="permdesc_exemptFromAudioRecordRestrictions" msgid="2425117015896871976">"Salbuetsi aplikazioa audioa grabatzeko murriztapenen aurrean."</string>
     <string name="permlab_acceptHandover" msgid="2925523073573116523">"jarraitu beste aplikazio batean hasitako deia"</string>
-    <string name="permdesc_acceptHandovers" msgid="7129026180128626870">"Beste aplikazio batean hasitako dei batekin jarraitzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_acceptHandovers" msgid="7129026180128626870">"Beste aplikazio batean hasitako dei batekin jarraitzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_readPhoneNumbers" msgid="5668704794723365628">"irakurri telefono-zenbakiak"</string>
-    <string name="permdesc_readPhoneNumbers" msgid="7368652482818338871">"Gailuaren telefono-zenbakiak erabiltzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_readPhoneNumbers" msgid="7368652482818338871">"Gailuaren telefono-zenbakiak erabiltzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_wakeLock" product="automotive" msgid="1904736682319375676">"mantendu piztuta autoko pantaila"</string>
     <string name="permlab_wakeLock" product="tablet" msgid="1527660973931694000">"eragotzi tableta inaktibo ezartzea"</string>
     <string name="permlab_wakeLock" product="tv" msgid="2856941418123343518">"Android TV gailua inaktibo ezar dadin eragotzi"</string>
     <string name="permlab_wakeLock" product="default" msgid="569409726861695115">"eragotzi telefonoa inaktibo ezartzea"</string>
-    <string name="permdesc_wakeLock" product="automotive" msgid="5995045369683254571">"Autoko pantaila piztuta mantentzeko baimena ematen die aplikazioei."</string>
-    <string name="permdesc_wakeLock" product="tablet" msgid="2441742939101526277">"Tableta inaktibo ezartzea galaraztea baimentzen die aplikazioei."</string>
-    <string name="permdesc_wakeLock" product="tv" msgid="2329298966735118796">"Android TV gailua inaktibo ezartzea eragozteko baimena ematen die aplikazioei."</string>
-    <string name="permdesc_wakeLock" product="default" msgid="3689523792074007163">"Telefonoa inaktibo ezartzea galaraztea baimentzen die aplikazioei."</string>
+    <string name="permdesc_wakeLock" product="automotive" msgid="5995045369683254571">"Autoko pantaila piztuta mantentzeko baimena ematen dio aplikazioari."</string>
+    <string name="permdesc_wakeLock" product="tablet" msgid="2441742939101526277">"Tableta inaktibo ezartzea galarazteko baimena ematen dio aplikazioari."</string>
+    <string name="permdesc_wakeLock" product="tv" msgid="2329298966735118796">"Android TV gailua inaktibo ezartzea eragozteko baimena ematen dio aplikazioari."</string>
+    <string name="permdesc_wakeLock" product="default" msgid="3689523792074007163">"Telefonoa inaktibo ezartzea galarazteko baimena ematen dio aplikazioari."</string>
     <string name="permlab_transmitIr" msgid="8077196086358004010">"transmititu infragorriak"</string>
-    <string name="permdesc_transmitIr" product="tablet" msgid="5884738958581810253">"Tabletaren infragorri-igorlea erabiltzeko baimena ematen die aplikazioei."</string>
-    <string name="permdesc_transmitIr" product="tv" msgid="3278506969529173281">"Android TV gailuaren infragorri-igorlea erabiltzeko baimena ematen die aplikazioei."</string>
-    <string name="permdesc_transmitIr" product="default" msgid="8484193849295581808">"Telefonoaren infragorri-igorlea erabiltzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_transmitIr" product="tablet" msgid="5884738958581810253">"Tabletaren infragorri-igorlea erabiltzeko baimena ematen dio aplikazioari."</string>
+    <string name="permdesc_transmitIr" product="tv" msgid="3278506969529173281">"Android TV gailuaren infragorri-igorlea erabiltzeko baimena ematen dio aplikazioari."</string>
+    <string name="permdesc_transmitIr" product="default" msgid="8484193849295581808">"Telefonoaren infragorri-igorlea erabiltzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_setWallpaper" msgid="6959514622698794511">"ezarri horma-papera"</string>
-    <string name="permdesc_setWallpaper" msgid="2973996714129021397">"Sistemaren horma-papera aldatzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_setWallpaper" msgid="2973996714129021397">"Sistemaren horma-papera aldatzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_setWallpaperHints" msgid="1153485176642032714">"doitu horma-paperaren tamaina"</string>
-    <string name="permdesc_setWallpaperHints" msgid="6257053376990044668">"Sistemaren horma-paperaren tamainaren doitzeak ezartzea baimentzen die aplikazioei."</string>
+    <string name="permdesc_setWallpaperHints" msgid="6257053376990044668">"Sistemaren horma-paperaren tamainaren doitzeak ezartzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_setTimeZone" msgid="7922618798611542432">"ezarri ordu-zona"</string>
-    <string name="permdesc_setTimeZone" product="tablet" msgid="1788868809638682503">"Tabletaren ordu-zona aldatzeko baimena ematen die aplikazioei."</string>
-    <string name="permdesc_setTimeZone" product="tv" msgid="9069045914174455938">"Android TV gailuaren ordu-zona aldatzeko baimena ematen die aplikazioei."</string>
-    <string name="permdesc_setTimeZone" product="default" msgid="4611828585759488256">"Telefonoaren ordu-zona aldatzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_setTimeZone" product="tablet" msgid="1788868809638682503">"Tabletaren ordu-zona aldatzeko baimena ematen dio aplikazioari."</string>
+    <string name="permdesc_setTimeZone" product="tv" msgid="9069045914174455938">"Android TV gailuaren ordu-zona aldatzeko baimena ematen dio aplikazioari."</string>
+    <string name="permdesc_setTimeZone" product="default" msgid="4611828585759488256">"Telefonoaren ordu-zona aldatzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_getAccounts" msgid="5304317160463582791">"bilatu gailuko kontuak"</string>
-    <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"Tabletak ezagutzen dituen kontuen zerrenda lortzeko baimena ematen die aplikazioei. Instalatuta dauzkazun aplikazioek sortutako kontuak har daitezke barnean."</string>
-    <string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"Android TV gailuak ezagutzen dituen kontuen zerrenda lortzeko baimena ematen die aplikazioei. Kontu horien artean, instalatuta dauzkazun aplikazioek sortutako kontuak egon litezke."</string>
-    <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"Telefonoak ezagutzen dituen kontuen zerrenda lortzeko baimena ematen die aplikazioei. Instalatuta dauzkazun aplikazioek sortutako kontuak har daitezke barnean."</string>
+    <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"Tabletak ezagutzen dituen kontuen zerrenda lortzeko baimena ematen dio aplikazioari. Instalatuta dauzkazun aplikazioek sortutako kontuak har daitezke barnean."</string>
+    <string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"Android TV gailuak ezagutzen dituen kontuen zerrenda lortzeko baimena ematen dio aplikazioari. Kontu horien artean, instalatuta dauzkazun aplikazioek sortutako kontuak egon litezke."</string>
+    <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"Telefonoak ezagutzen dituen kontuen zerrenda lortzeko baimena ematen dio aplikazioari. Instalatuta dauzkazun aplikazioek sortutako kontuak har daitezke barnean."</string>
     <string name="permlab_accessNetworkState" msgid="2349126720783633918">"ikusi sareko konexioak"</string>
-    <string name="permdesc_accessNetworkState" msgid="4394564702881662849">"Sareko konexioei buruzko informazioa ikusteko baimena ematen die aplikazioei; adibidez, zer sare dauden eta zeintzuk dauden konektatuta."</string>
+    <string name="permdesc_accessNetworkState" msgid="4394564702881662849">"Sareko konexioei buruzko informazioa ikusteko baimena ematen dio aplikazioari; adibidez, zer sare dauden eta zeintzuk dauden konektatuta."</string>
     <string name="permlab_createNetworkSockets" msgid="3224420491603590541">"izan sarerako sarbide osoa"</string>
-    <string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"Sare-socketak sortzeko eta sare-protokolo pertsonalizatuak erabiltzeko baimena ematen die aplikazioei. Arakatzaileak eta beste aplikazio batzuek Internetera konektatzeko moduak eskaintzen dituzte, beraz, baimen hori ez da beharrezkoa datuak Internetera bidaltzeko."</string>
+    <string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"Sare-socketak sortzeko eta sare-protokolo pertsonalizatuak erabiltzeko baimena ematen dio aplikazioari. Arakatzaileak eta beste aplikazio batzuek Internetera konektatzeko moduak eskaintzen dituzte, beraz, baimen hori ez da beharrezkoa datuak Internetera bidaltzeko."</string>
     <string name="permlab_changeNetworkState" msgid="8945711637530425586">"aldatu sarearen konektagarritasuna"</string>
-    <string name="permdesc_changeNetworkState" msgid="649341947816898736">"Sarearen konexioaren egoera aldatzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_changeNetworkState" msgid="649341947816898736">"Sarearen konexioaren egoera aldatzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_changeTetherState" msgid="9079611809931863861">"aldatu telefono bidezko konektagarritasuna"</string>
-    <string name="permdesc_changeTetherState" msgid="3025129606422533085">"Partekatutako Interneterako konexioaren egoera aldatzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_changeTetherState" msgid="3025129606422533085">"Partekatutako Interneterako konexioaren egoera aldatzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_accessWifiState" msgid="5552488500317911052">"ikusi wifi-konexioak"</string>
-    <string name="permdesc_accessWifiState" msgid="6913641669259483363">"Wi-Fi sareei buruzko informazioa ikusteko baimena ematen die aplikazioei, adibidez, wifi-konexioa aktibatuta dagoen eta konektatutako Wi-Fi gailuen izenak zein diren."</string>
+    <string name="permdesc_accessWifiState" msgid="6913641669259483363">"Wifi-sareei buruzko informazioa ikusteko baimena ematen dio aplikazioari, adibidez, wifi-konexioa aktibatuta dagoen eta konektatutako wifi gailuen izenak zein diren."</string>
     <string name="permlab_changeWifiState" msgid="7947824109713181554">"konektatu wifira edo deskonektatu bertatik"</string>
-    <string name="permdesc_changeWifiState" msgid="7170350070554505384">"Wi-Fi sarbide-puntuetara konektatzeko edo haietatik deskonektatzeko baimena ematen die aplikazioei, baita Wi-Fi sareen gailu-konfigurazioari aldaketak egitekoa ere."</string>
+    <string name="permdesc_changeWifiState" msgid="7170350070554505384">"Wi-Fi sarbide-puntuetara konektatzeko edo haietatik deskonektatzeko baimena ematen dio aplikazioari, baita Wi-Fi sareen gailu-konfigurazioari aldaketak egitekoa ere."</string>
     <string name="permlab_changeWifiMulticastState" msgid="285626875870754696">"onartu Wi-Fi Multicast harrera"</string>
-    <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"Wi-Fi sarearen bidez gailu guztiei bidalitako paketeak jasotzeko baimena ematen die aplikazioei multidifusio-helbideak erabilita, ez tableta soilik. Multidifusiokoa ez den moduak baino bateria gehiago erabiltzen du."</string>
-    <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"Multidifusio-helbideak erabiliz wifi-sare bateko gailu guztiei (ez bakarrik Android TV gailuari) bidalitako paketeak jasotzeko baimena ematen die aplikazioei. Multidifusiokoa ez den moduak baino bateria gehiago erabiltzen du."</string>
-    <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"Wi-Fi sarearen bidez gailu guztiei bidalitako paketeak jasotzeko baimena ematen die aplikazioei multidifusio-helbideak erabilita, ez telefonoa soilik. Multidifusiokoa ez den moduak baino bateria gehiago erabiltzen du."</string>
+    <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"Wifi sarearen bidez gailu guztiei bidalitako paketeak jasotzeko baimena ematen dio aplikazioari multidifusio-helbideak erabilita, ez tableta soilik. Multidifusiokoa ez den moduak baino bateria gehiago erabiltzen du."</string>
+    <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"Multidifusio-helbideak erabiliz wifi-sare bateko gailu guztiei (ez bakarrik Android TV gailuari) bidalitako paketeak jasotzeko baimena ematen dio aplikazioari. Multidifusiokoa ez den moduak baino bateria gehiago erabiltzen du."</string>
+    <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"Wi-Fi sarearen bidez gailu guztiei bidalitako paketeak jasotzeko baimena ematen dio aplikazioari multidifusio-helbideak erabilita, ez telefonoa soilik. Multidifusiokoa ez den moduak baino bateria gehiago erabiltzen du."</string>
     <string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"atzitu Bluetootharen ezarpenak"</string>
-    <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"Tokiko Bluetooth tableta konfiguratzea eta urruneko gailuak detektatzea eta haiekin parekatzea baimentzen die aplikazioei."</string>
-    <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"Android TV gailuan Bluetootha konfiguratzeko eta urruneko gailuak hautemateko eta haiekin parekatzeko baimena ematen die aplikazioei."</string>
-    <string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"Tokiko Bluetooth telefonoa konfiguratzea eta urruneko gailuak detektatzea eta haiekin parekatzea baimentzen die aplikazioei."</string>
+    <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"Tokiko Bluetooth tableta konfiguratzeko eta urruneko gailuak detektatzeko eta haiekin parekatzeko baimena ematen dio aplikazioari."</string>
+    <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"Android TV gailuan Bluetootha konfiguratzeko eta urruneko gailuak hautemateko eta haiekin parekatzeko baimena ematen dio aplikazioari."</string>
+    <string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"Tokiko Bluetooth telefonoa konfiguratzea eta urruneko gailuak detektatzeko eta haiekin parekatzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_accessWimaxState" msgid="7029563339012437434">"WiMAX sarera konektatzea eta deskonektatzea"</string>
-    <string name="permdesc_accessWimaxState" msgid="5372734776802067708">"WiMAX gaituta dagoen zehazteko eta konektatutako WiMAX sareei buruzko informazioa ikusteko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_accessWimaxState" msgid="5372734776802067708">"WiMAX gaituta dagoen zehazteko eta konektatutako WiMAX sareei buruzko informazioa ikusteko baimena ematen dio aplikazioari."</string>
     <string name="permlab_changeWimaxState" msgid="6223305780806267462">"Aldatu WiMAX egoera"</string>
-    <string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"Tableta WiMAX sareetara konektatzeko edo haietatik deskonektatzeko baimena ematen die aplikazioei."</string>
-    <string name="permdesc_changeWimaxState" product="tv" msgid="5373274458799425276">"Android TV gailua WiMAX sareetara konektatzeko edo haietatik deskonektatzeko baimena ematen die aplikazioei."</string>
-    <string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"Telefonoa WiMAX sareetara konektatzeko edo haietatik deskonektatzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"Tableta WiMAX sareetara konektatzeko edo haietatik deskonektatzeko baimena ematen dio aplikazioari."</string>
+    <string name="permdesc_changeWimaxState" product="tv" msgid="5373274458799425276">"Android TV gailua WiMAX sareetara konektatzeko edo haietatik deskonektatzeko baimena ematen dio aplikazioari."</string>
+    <string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"Telefonoa WiMAX sareetara konektatzeko edo haietatik deskonektatzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_bluetooth" msgid="586333280736937209">"partekatu Bluetooth bidezko gailuekin"</string>
-    <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Tabletaren Bluetooth konfigurazioa ikusteko eta parekatutako gailuekin konexioak egiteko eta onartzeko baimena ematen die aplikazioei."</string>
-    <string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Android TV gailuaren Bluetooth bidezko konexioaren konfigurazioa ikusteko eta parekatutako gailuekin konexioak sortzeko eta onartzeko baimena ematen die aplikazioei."</string>
-    <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Telefonoaren Bluetooth konfigurazioa ikusteko eta parekatutako gailuekin konexioak egiteko eta onartzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Tabletaren Bluetooth konfigurazioa ikusteko eta parekatutako gailuekin konexioak egiteko eta onartzeko baimena ematen dio aplikazioari."</string>
+    <string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Android TV gailuaren Bluetooth bidezko konexioaren konfigurazioa ikusteko eta parekatutako gailuekin konexioak sortzeko eta onartzeko baimena ematen dio aplikazioari."</string>
+    <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Telefonoaren Bluetooth konfigurazioa ikusteko eta parekatutako gailuekin konexioak egiteko eta onartzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_bluetooth_scan" msgid="5402587142833124594">"inguruko Bluetooth bidezko gailuak hauteman eta haiekin parekatu"</string>
-    <string name="permdesc_bluetooth_scan" product="default" msgid="6540723536925289276">"Inguruko Bluetooth bidezko gailuak hautemateko eta haiekin parekatzeko baimena ematen die aplikazioei"</string>
+    <string name="permdesc_bluetooth_scan" product="default" msgid="6540723536925289276">"Inguruko Bluetooth bidezko gailuak hautemateko eta haiekin parekatzeko baimena ematen dio aplikazioari"</string>
     <string name="permlab_bluetooth_connect" msgid="6657463246355003528">"parekatutako Bluetooth bidezko gailuetara konektatu"</string>
-    <string name="permdesc_bluetooth_connect" product="default" msgid="4546016548795544617">"Parekatutako Bluetooth bidezko gailuetara konektatzeko baimena ematen die aplikazioei"</string>
+    <string name="permdesc_bluetooth_connect" product="default" msgid="4546016548795544617">"Parekatutako Bluetooth bidezko gailuetara konektatzeko baimena ematen dio aplikazioari"</string>
     <string name="permlab_bluetooth_advertise" msgid="2781147747928853177">"inguruko Bluetooth bidezko gailuetan informazioa iragarri"</string>
-    <string name="permdesc_bluetooth_advertise" product="default" msgid="6085174451034210183">"Inguruko Bluetooth bidezko gailuetan informazioa iragartzeko baimena ematen die aplikazioei"</string>
+    <string name="permdesc_bluetooth_advertise" product="default" msgid="6085174451034210183">"Inguruko Bluetooth bidezko gailuetan informazioa iragartzeko baimena ematen dio aplikazioari"</string>
     <string name="permlab_uwb_ranging" msgid="8141915781475770665">"banda ultrazabala darabilten inguruko gailuen arteko distantzia erlatiboa zehaztu"</string>
     <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"Banda ultrazabala darabilten inguruko gailuen arteko distantzia erlatiboa zehazteko baimena ematen dio aplikazioari"</string>
     <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"inguruko wifi-gailuekin interakzioan jardun"</string>
-    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Inguruko wifi-gailuetan iragartzeko, haiekin konektatzeko eta haien kokapena zehazteko baimena ematen die aplikazioei"</string>
+    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Inguruko wifi-gailuetan iragartzeko, haiekin konektatzeko eta haien kokapena zehazteko baimena ematen dio aplikazioari"</string>
     <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"NFC bidezko ordainketa-zerbitzu lehenetsiari buruzko informazioa"</string>
-    <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"NFC bidezko ordainketa-zerbitzu lehenetsiari buruzko informazioa jasotzeko baimena ematen die aplikazioei, hala nola erregistratutako laguntzaileak eta ibilbidearen helmuga."</string>
+    <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"NFC bidezko ordainketa-zerbitzu lehenetsiari buruzko informazioa jasotzeko baimena ematen dio aplikazioari, hala nola erregistratutako laguntzaileak eta ibilbidearen helmuga."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"kontrolatu Near Field Communication komunikazioa"</string>
-    <string name="permdesc_nfc" msgid="8352737680695296741">"Near Field Communication (NFC) etiketekin, txartelekin eta irakurgailuekin komunikatzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_nfc" msgid="8352737680695296741">"Near Field Communication (NFC) etiketekin, txartelekin eta irakurgailuekin komunikatzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"Elementu seguruetako transakzioen gertaerak"</string>
     <string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"Elementu seguru batean egiten ari diren transakzioei buruzko informazioa jasotzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_disableKeyguard" msgid="3605253559020928505">"desgaitu pantailaren blokeoa"</string>
-    <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Teklen blokeoa eta erlazionatutako pasahitz-segurtasuna desgaitzeko baimena ematen die aplikazioei. Adibidez, telefonoak teklen blokeoa desgaitzen du telefono-deiak jasotzen dituenean, eta berriro gaitzen du deiak amaitzean."</string>
+    <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Teklen blokeoa eta erlazionatutako pasahitz-segurtasuna desgaitzeko baimena ematen dio aplikazioari. Adibidez, telefonoak teklen blokeoa desgaitzen du telefono-deiak jasotzen dituenean, eta berriro gaitzen du deiak amaitzean."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"eskatu pantailaren blokeoa konplexua izatea"</string>
-    <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"Pantailaren blokeoaren konplexutasun-maila (handia, ertaina, txikia edo bat ere ez) jakiteko baimena ematen die aplikazioei. Informazio horrekin, pantailaren blokeoaren luzera-barruti edo mota posiblea ondoriozta liteke. Halaber, pantailaren blokeoa maila jakin batera igotzeko iradoki diezaiekete aplikazioek erabiltzaileei, baina horri ez ikusi egin eta aplikazioak erabiltzen jarraitzeko aukera dute erabiltzaileek. Kontuan izan pantailaren blokeoa ez dela gordetzen testu arrunt gisa; beraz, aplikazioek ez dute jakingo pasahitz zehatza zein den."</string>
+    <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"Pantailaren blokeoaren konplexutasun-maila (handia, ertaina, txikia edo bat ere ez) jakiteko baimena ematen dio aplikazioari. Informazio horrekin, pantailaren blokeoaren luzera-barruti edo mota posiblea ondoriozta liteke. Halaber, pantailaren blokeoa maila jakin batera igotzeko iradoki diezaiekete aplikazioek erabiltzaileei, baina horri ez ikusi egin eta aplikazioak erabiltzen jarraitzeko aukera dute erabiltzaileek. Kontuan izan pantailaren blokeoa ez dela gordetzen testu arrunt gisa; beraz, aplikazioek ez dute jakingo pasahitz zehatza zein den."</string>
     <string name="permlab_postNotification" msgid="4875401198597803658">"jakinarazpenak erakutsi"</string>
-    <string name="permdesc_postNotification" msgid="5974977162462877075">"Jakinarazpenak erakusteko baimena ematen die aplikazioei"</string>
+    <string name="permdesc_postNotification" msgid="5974977162462877075">"Jakinarazpenak erakusteko baimena ematen dio aplikazioari"</string>
     <string name="permlab_turnScreenOn" msgid="219344053664171492">"piztu pantaila"</string>
     <string name="permdesc_turnScreenOn" msgid="4394606875897601559">"Pantaila pizteko baimena ematen dio aplikazioari."</string>
     <string name="permlab_useBiometric" msgid="6314741124749633786">"erabili hardware biometrikoa"</string>
-    <string name="permdesc_useBiometric" msgid="7502858732677143410">"Autentifikatzeko hardware biometrikoa erabiltzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_useBiometric" msgid="7502858732677143410">"Autentifikatzeko hardware biometrikoa erabiltzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_manageFingerprint" msgid="7432667156322821178">"kudeatu hatz-marken hardwarea"</string>
-    <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"Aztarna digitalaren txantiloiak gehitzeko eta ezabatzeko metodoei dei egitea baimentzen die aplikazioei."</string>
+    <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"Aztarna digitalaren txantiloiak gehitzeko eta ezabatzeko metodoei dei egiteko baimena ematen dio aplikazioari."</string>
     <string name="permlab_useFingerprint" msgid="1001421069766751922">"erabili hatz-marken hardwarea"</string>
-    <string name="permdesc_useFingerprint" msgid="412463055059323742">"Autentifikatzeko hatz-marken hardwarea erabiltzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_useFingerprint" msgid="412463055059323742">"Autentifikatzeko hatz-marken hardwarea erabiltzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_audioWrite" msgid="8501705294265669405">"musika bilduma aldatu"</string>
-    <string name="permdesc_audioWrite" msgid="8057399517013412431">"Musika bilduma aldatzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_audioWrite" msgid="8057399517013412431">"Musika bilduma aldatzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_videoWrite" msgid="5940738769586451318">"bideo bilduma aldatu"</string>
-    <string name="permdesc_videoWrite" msgid="6124731210613317051">"Bideo bilduma aldatzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_videoWrite" msgid="6124731210613317051">"Bideo bilduma aldatzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_imagesWrite" msgid="1774555086984985578">"argazki bilduma aldatu"</string>
-    <string name="permdesc_imagesWrite" msgid="5195054463269193317">"Argazki bilduma aldatzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_imagesWrite" msgid="5195054463269193317">"Argazki bilduma aldatzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_mediaLocation" msgid="7368098373378598066">"multimedia-edukien bildumako kokapena irakurri"</string>
-    <string name="permdesc_mediaLocation" msgid="597912899423578138">"Multimedia-edukien bildumako kokapena irakurtzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_mediaLocation" msgid="597912899423578138">"Multimedia-edukien bildumako kokapena irakurtzeko baimena ematen dio aplikazioari."</string>
     <string name="biometric_app_setting_name" msgid="3339209978734534457">"Erabili sistema biometrikoak"</string>
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Erabili sistema biometrikoak edo pantailaren blokeoa"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Egiaztatu zeu zarela"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Erabili pantailaren blokeoa"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Aurrera egiteko, desblokeatu pantailaren blokeoa"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Sakatu irmo sentsorea"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Ezin da hauteman hatz-marka. Saiatu berriro."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Ez da ezagutu hatz-marka. Saiatu berriro."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Garbitu hatz-marken sentsorea eta saiatu berriro"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Garbitu sentsorea eta saiatu berriro"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Sakatu irmo sentsorea"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Ezin da hauteman aurpegia. Eutsi telefonoari begien parean."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Mugimendu gehiegi dago. Eutsi tinko telefonoari."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Erregistratu berriro aurpegia."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Ezin da hauteman aurpegia. Saiatu berriro."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Ez da ezagutu aurpegia. Saiatu berriro."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Aldatu buruaren posizioa apur bat"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Begiratu zuzenago telefonoari"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Begiratu zuzenago telefonoari"</string>
@@ -734,45 +740,45 @@
     <string name="face_error_vendor_unknown" msgid="7387005932083302070">"Arazo bat izan da. Saiatu berriro."</string>
     <string name="face_icon_content_description" msgid="465030547475916280">"Aurpegiaren ikonoa"</string>
     <string name="permlab_readSyncSettings" msgid="6250532864893156277">"irakurri sinkronizazio-ezarpenak"</string>
-    <string name="permdesc_readSyncSettings" msgid="1325658466358779298">"Kontu baten sinkronizazio-ezarpenak irakurtzeko baimena ematen die aplikazioei. Adibidez, Jendea aplikazioa konturen batekin sinkronizatuta dagoen zehatz dezake."</string>
+    <string name="permdesc_readSyncSettings" msgid="1325658466358779298">"Kontu baten sinkronizazio-ezarpenak irakurtzeko baimena ematen dio aplikazioari. Adibidez, Jendea aplikazioa konturen batekin sinkronizatuta dagoen zehatz dezake."</string>
     <string name="permlab_writeSyncSettings" msgid="6583154300780427399">"aktibatu eta desaktibatu sinkronizazioa"</string>
     <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"Kontu baten sinkronizazio-ezarpenak aldatzeko baimena ematen die aplikazioei. Adibidez, Jendea aplikazioa kontu batekin sinkronizatzeko erabil daiteke."</string>
     <string name="permlab_readSyncStats" msgid="3747407238320105332">"irakurri sinkronizazio-estatistikak"</string>
     <string name="permdesc_readSyncStats" msgid="3867809926567379434">"Kontu baten sinkronizazio-estatistikak irakurtzeko baimena ematen dio; besteak beste, sinkronizazio-gertaeren historia eta sinkronizatutako datu kopurua."</string>
     <string name="permlab_sdcardRead" msgid="5791467020950064920">"Irakurri biltegi partekatuko edukia"</string>
-    <string name="permdesc_sdcardRead" msgid="6872973242228240382">"Biltegi partekatuko edukia irakurtzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_sdcardRead" msgid="6872973242228240382">"Biltegi partekatuko edukia irakurtzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_readMediaAudio" msgid="8723513075731763810">"irakurri biltegi partekatuko audio-fitxategiak"</string>
-    <string name="permdesc_readMediaAudio" msgid="5299772574434619399">"Biltegi partekatuko audio-fitxategiak irakurtzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_readMediaAudio" msgid="5299772574434619399">"Biltegi partekatuko audio-fitxategiak irakurtzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_readMediaVideo" msgid="7768003311260655007">"irakurri biltegi partekatuko bideo-fitxategiak"</string>
-    <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Biltegi partekatuko bideo-fitxategiak irakurtzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Biltegi partekatuko bideo-fitxategiak irakurtzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_readMediaImages" msgid="4057590631020986789">"irakurri biltegi partekatuko irudi-fitxategiak"</string>
-    <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Biltegi partekatuko irudi-fitxategiak irakurtzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Biltegi partekatuko irudi-fitxategiak irakurtzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"irakurri biltegi partekatuko irudi- eta bideo-fitxategiak"</string>
-    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Biltegi partekatuko irudi- eta bideo-fitxategiak irakurtzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Biltegi partekatuko irudi- eta bideo-fitxategiak irakurtzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_sdcardWrite" msgid="4863021819671416668">"aldatu edo ezabatu biltegi partekatuko edukia"</string>
-    <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Biltegi partekatuko edukian idazteko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Biltegi partekatuko edukian idazteko baimena ematen dio aplikazioari."</string>
     <string name="permlab_use_sip" msgid="8250774565189337477">"egin/jaso SIP deiak"</string>
-    <string name="permdesc_use_sip" msgid="3590270893253204451">"SIP deiak egitea eta jasotzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_use_sip" msgid="3590270893253204451">"SIP deiak egiteko eta jasotzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_register_sim_subscription" msgid="1653054249287576161">"erregistratu telekomunikabideekiko SIM konexio berriak"</string>
-    <string name="permdesc_register_sim_subscription" msgid="4183858662792232464">"Telekomunikabideekiko SIM konexio berriak erregistratzea baimentzen die aplikazioei."</string>
+    <string name="permdesc_register_sim_subscription" msgid="4183858662792232464">"Telekomunikabideekiko SIM konexio berriak erregistratzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_register_call_provider" msgid="6135073566140050702">"erregistratu telekomunikabideekiko konexio berriak"</string>
-    <string name="permdesc_register_call_provider" msgid="4201429251459068613">"Telekomunikabideekiko konexio berriak erregistratzea baimentzen die aplikazioei."</string>
+    <string name="permdesc_register_call_provider" msgid="4201429251459068613">"Telekomunikabideekiko konexio berriak erregistratzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_connection_manager" msgid="3179365584691166915">"kudeatu telekomunikabideekiko konexioak"</string>
-    <string name="permdesc_connection_manager" msgid="1426093604238937733">"Telekomunikabideekiko konexioak kudeatzea baimentzen die aplikazioei."</string>
+    <string name="permdesc_connection_manager" msgid="1426093604238937733">"Telekomunikabideekiko konexioak kudeatzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_bind_incall_service" msgid="5990625112603493016">"erabili pantaila deiak abian direnean"</string>
-    <string name="permdesc_bind_incall_service" msgid="4124917526967765162">"Erabiltzaileak deiaren pantaila noiz eta nola ikusten duen kontrolatzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_bind_incall_service" msgid="4124917526967765162">"Erabiltzaileak deiaren pantaila noiz eta nola ikusten duen kontrolatzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_bind_connection_service" msgid="5409268245525024736">"jardun interakzioan telefono-zerbitzuekin"</string>
-    <string name="permdesc_bind_connection_service" msgid="6261796725253264518">"Deiak egiteko eta jasotzeko telefonia-zerbitzuekin interakzioan aritzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_bind_connection_service" msgid="6261796725253264518">"Deiak egiteko eta jasotzeko telefonia-zerbitzuekin interakzioan aritzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_control_incall_experience" msgid="6436863486094352987">"eskaini erabiltzaileentzako aukerak deiak abian direnean"</string>
-    <string name="permdesc_control_incall_experience" msgid="5896723643771737534">"Deiak abian direnean erabiltzeko aukera eskaintzea baimentzen die aplikazioei."</string>
+    <string name="permdesc_control_incall_experience" msgid="5896723643771737534">"Deiak abian direnean erabiltzeko aukera eskaintzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_readNetworkUsageHistory" msgid="8470402862501573795">"irakurri sare-erabileraren historia"</string>
-    <string name="permdesc_readNetworkUsageHistory" msgid="1112962304941637102">"Sare eta aplikazio jakin batzuen sare-erabileraren historia irakurtzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_readNetworkUsageHistory" msgid="1112962304941637102">"Sare eta aplikazio jakin batzuen sare-erabileraren historia irakurtzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_manageNetworkPolicy" msgid="6872549423152175378">"kudeatu sare-gidalerroak"</string>
-    <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"Sareko gidalerroak kudeatzea eta aplikazioetarako berariazko arauak definitzea baimentzen die aplikazioei."</string>
+    <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"Sareko gidalerroak kudeatzeko eta aplikazioetarako berariazko arauak definitzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_modifyNetworkAccounting" msgid="7448790834938749041">"aldatu sare-erabileraren kalkuluak"</string>
-    <string name="permdesc_modifyNetworkAccounting" msgid="5076042642247205390">"Aplikazioen sare-erabilera kalkulatzeko modua aldatzeko baimena ematen die aplikazioei. Aplikazio normalek ez lukete beharko."</string>
+    <string name="permdesc_modifyNetworkAccounting" msgid="5076042642247205390">"Aplikazioen sare-erabilera kalkulatzeko modua aldatzeko baimena ematen dio aplikazioari. Aplikazio normalek ez lukete beharko."</string>
     <string name="permlab_accessNotifications" msgid="7130360248191984741">"atzitu jakinarazpenak"</string>
-    <string name="permdesc_accessNotifications" msgid="761730149268789668">"Jakinarazpenak berreskuratu, aztertu eta garbitzeko baimena ematen die aplikazioei, beste aplikazioek argitaratutako jakinarazpenak barne."</string>
+    <string name="permdesc_accessNotifications" msgid="761730149268789668">"Jakinarazpenak berreskuratu, aztertu eta garbitzeko baimena ematen dio aplikazioari, beste aplikazioek argitaratutako jakinarazpenak barne."</string>
     <string name="permlab_bindNotificationListenerService" msgid="5848096702733262458">"lotu jakinarazpen-hautemaile bati"</string>
     <string name="permdesc_bindNotificationListenerService" msgid="4970553694467137126">"Jakinarazpen-hautemaile baten goi-mailako interfazera lotzeko aukera ematen dio titularrari. Aplikazio normalek ez dute baimen hau behar."</string>
     <string name="permlab_bindConditionProviderService" msgid="5245421224814878483">"lotu baldintza-hornitzaileen zerbitzuei"</string>
@@ -784,7 +790,7 @@
     <string name="permlab_accessNetworkConditions" msgid="1270732533356286514">"hauteman sarearen baldintzei buruzko behaketak"</string>
     <string name="permdesc_accessNetworkConditions" msgid="2959269186741956109">"Sareko baldintzak hautemateko aukera ematen die aplikazioei. Aplikazio normalek ez dute baimen hau behar."</string>
     <string name="permlab_setInputCalibration" msgid="932069700285223434">"Aldatu idazteko gailuaren kalibrazioa"</string>
-    <string name="permdesc_setInputCalibration" msgid="2937872391426631726">"Ukipen-pantailaren kalibrazio-parametroak aldatzeko baimena ematen die aplikazioei. Aplikazio normalek ez lukete beharko."</string>
+    <string name="permdesc_setInputCalibration" msgid="2937872391426631726">"Ukipen-pantailaren kalibrazio-parametroak aldatzeko baimena ematen dio aplikazioari. Aplikazio normalek ez lukete beharko."</string>
     <string name="permlab_accessDrmCertificates" msgid="6473765454472436597">"atzitu DRM ziurtagiriak"</string>
     <string name="permdesc_accessDrmCertificates" msgid="6983139753493781941">"DRM ziurtagiriak hornitzea eta erabiltzeko baimena ematen die aplikazioei. Aplikazio normalek ez lukete beharko."</string>
     <string name="permlab_handoverStatus" msgid="7620438488137057281">"Jaso Android Beam transferentzien egoera"</string>
@@ -796,7 +802,7 @@
     <string name="permlab_bindCarrierServices" msgid="2395596978626237474">"lotu operadorearen zerbitzuei"</string>
     <string name="permdesc_bindCarrierServices" msgid="9185614481967262900">"Operadorearen zerbitzuei lotzea baimentzen die titularrei. Aplikazio normalek ez dute baimen hau behar."</string>
     <string name="permlab_access_notification_policy" msgid="5524112842876975537">"atzitu ez molestatzeko modua"</string>
-    <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Ez molestatzeko moduaren konfigurazioa irakurtzeko eta bertan idazteko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Ez molestatzeko moduaren konfigurazioa irakurtzeko eta bertan idazteko baimena ematen dio aplikazioari."</string>
     <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"hasi ikusteko baimena erabiltzen"</string>
     <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Aplikazioaren baimena erabiltzen hasteko baimena ematen die titularrei. Aplikazio normalek ez lukete beharko."</string>
     <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"hasi baimenen inguruko erabakiak ikusten"</string>
@@ -804,7 +810,7 @@
     <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"hasi aplikazioaren eginbideak ikusten"</string>
     <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Aplikazio baten eginbideei buruzko informazioa ikusten hasteko baimena ematen die titularrei."</string>
     <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"atzitu sentsoreen datuen laginak abiadura handian"</string>
-    <string name="permdesc_highSamplingRateSensors" msgid="8430061978931155995">"Aplikazioak 200 Hz-tik gorako abiaduran hartu ahal izango ditu sentsoreen datuen laginak"</string>
+    <string name="permdesc_highSamplingRateSensors" msgid="8430061978931155995">"200 Hz-tik gorako abiaduran sentsoreen datuen laginak hartzeko baimena ematen dio aplikazioari"</string>
     <string name="permlab_updatePackagesWithoutUserAction" msgid="3363272609642618551">"eguneratu aplikazioa erabiltzaileak ezer egin gabe"</string>
     <string name="permdesc_updatePackagesWithoutUserAction" msgid="4567739631260526366">"Lehendik instalatu duen aplikazioa erabiltzaileak ezer egin gabe eguneratzeko baimena ematen dio titularrari"</string>
     <string name="policylab_limitPassword" msgid="4851829918814422199">"Ezarri pasahitzen arauak"</string>
@@ -1074,9 +1080,9 @@
     <string name="js_dialog_before_unload" msgid="7213364985774778744">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nZiur orritik irten nahi duzula?"</string>
     <string name="autofill_window_title" msgid="4379134104008111961">"Bete automatikoki <xliff:g id="SERVICENAME">%1$s</xliff:g> erabiliz"</string>
     <string name="permlab_setAlarm" msgid="1158001610254173567">"ezarri alarmak"</string>
-    <string name="permdesc_setAlarm" msgid="2185033720060109640">"Instalatutako alarma batean alarmak ezartzea baimentzen die aplikazioei. Alarma-aplikazio batzuek agian ez dute eginbide hori inplementatuko."</string>
+    <string name="permdesc_setAlarm" msgid="2185033720060109640">"Instalatutako alarma batean alarmak ezartzeko baimena ematen dio aplikazioari. Alarma-aplikazio batzuek agian ez dute eginbide hori inplementatuko."</string>
     <string name="permlab_addVoicemail" msgid="4770245808840814471">"gehitu erantzungailua"</string>
-    <string name="permdesc_addVoicemail" msgid="5470312139820074324">"Erantzungailuko sarrera-ontzian mezuak gehitzeko baimena ematen die aplikazioei."</string>
+    <string name="permdesc_addVoicemail" msgid="5470312139820074324">"Erantzungailuko sarrera-ontzian mezuak gehitzeko baimena ematen dio aplikazioari."</string>
     <string name="pasted_from_clipboard" msgid="7355790625710831847">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> aplikazioak arbeleko edukia itsatsi du"</string>
     <string name="more_item_label" msgid="7419249600215749115">"Gehiago"</string>
     <string name="prepend_shortcut_label" msgid="1743716737502867951">"Menua+"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index eb71a7d..7738e1c 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"به برنامه امکان می‌دهد به مدول پخش سلولی متصل شود تا پیام‌های پخش سلولی را به محض دریافت بازارسال کند. هشدارهای پخش سلولی در برخی از موقعیت‌های مکانی ارسال می‌شوند تا موقعیت‌های اضطراری را به شما اعلام کنند. وقتی پخش سلولی دریافت می‌شود، ممکن است برنامه‌های مخرب در عملکرد یا کارکرد دستگاه شما اختلال ایجاد کنند."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"مدیریت تماس‌های درحال انجام"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"به برنامه اجازه می‌دهد جزئیات تماس‌های درحال انجام در دستگاه را ببیند و این تماس‌ها را کنترل کند."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"خواندن پیام‌های پخش سلولی"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"‏به برنامه اجازه می‎دهد پیام‌های پخش سلولی دستگاه شما را بخواند. هشدارهای پخش سلولی در برخی از موقعیت‌های مکانی تحویل داده می‎شوند تا موقعیت‌های اضطراری را به شما اعلام کنند. وقتی پخش سلولی دریافت می‎شود، ممکن است برنامه‌های مخرب در عملکرد یا کارکرد دستگاه شما اختلال ایجاد کنند."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"خواندن فیدهای مشترک"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"به برنامه اجازه می‌دهد از سرویس‌های پیش‌نما از نوع «معافیت سیستم» استفاده کند"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"‏اجرای سرویس پیش‌نما از نوع «fileManagement»"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"‏به برنامه اجازه می‌دهد از سرویس‌های پیش‌نما از نوع «fileManagement» استفاده کند"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"‏اجرای سرویس پیش‌زمینه‌ای از نوع «mediaProcessing»"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"‏به برنامه اجازه می‌دهد از سرویس‌های پیش‌زمینه‌ای از نوع «mediaProcessing» استفاده کند"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"اجرای سرویس پیش‌نما از نوع «استفاده ویژه»"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"به برنامه اجازه می‌دهد از سرویس‌های پیش‌نما از نوع «استفاده ویژه» استفاده کند"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"اندازه‌گیری اندازه فضای ذخیره‌سازی برنامه"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"از قفل صفحه استفاده کنید"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"برای ادامه، قفل صفحه‌تان را وارد کنید"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"محکم روی حسگر فشار دهید"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"اثر انگشت شناسایی نشد. دوباره امتحان کنید."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"اثر انگشت شناسایی نشد. دوباره امتحان کنید."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"حسگر اثر انگشت را تمیز و دوباره امتحان کنید"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"حسگر را تمیز و دوباره امتحان کنید"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"محکم روی حسگر فشار دهید"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"چهره دیده نمی‌شود. تلفن را هم‌سطح چشمانتان نگه دارید."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"حرکت خیلی زیاد است. تلفن را ثابت نگه‌دارید."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"لطفاً چهره‌تان را مجدداً ثبت کنید."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"چهره شناسایی نشد. دوباره امتحان کنید."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"چهره شناسایی نشد. دوباره امتحان کنید."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"موقعیت سرتان را کمی تغییر دهید"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"مستقیم‌تر به تلفن نگاه کنید"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"مستقیم‌تر به تلفن نگاه کنید"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 8359bf6..96e0ad4 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Sallii sovelluksen sitoutua solulähetysmoduuliin lähettääkseen solulähetysviestejä edelleen sitä mukaa kun ne saapuvat. Solulähetysilmoitusten avulla ilmoitetaan hätätilanteista joissakin paikoissa. Haitalliset sovellukset voivat häiritä laitteen toimintaa laitteen vastaanottaessa hätätilanteeseen liittyvän solulähetysviestin."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Ylläpidä käynnissä olevia puheluita"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Sovellus voi nähdä tietoja laitteella käynnissä olevista puheluista ja ohjata näitä puheluita."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"lue tiedotteita"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Antaa sovelluksen lukea laitteesi vastaanottamia tiedotteita. Tiedotteiden avulla ilmoitetaan hätätilanteista joissakin paikoissa. Haitalliset sovellukset voivat häiritä laitteen toimintaa laitteen vastaanottaessa hätätiedotteen."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"lukea tilattuja syötteitä"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Sallii sovelluksen käyttää etualan palveluja, joiden tyyppi on \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"käyttää etualan palveluja, joiden tyyppi on \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Sallii sovelluksen käyttää etualan palveluja, joiden tyyppi on \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"käyttää etualan palveluja, joiden tyyppi on \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Sallii sovelluksen käyttää etualan palveluja, joiden tyyppi on \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"käyttää etualan palveluja, joiden tyyppi on \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Sallii sovelluksen käyttää etualan palveluja, joiden tyyppi on \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"sovellusten tallennustilan mittaaminen"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Käytä näytön lukitusta"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Jatka lisäämällä näytön lukituksen avaustapa"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Paina anturia voimakkaasti"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Sormenjälkeä ei voi tunnistaa. Yritä uudelleen."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Sormenjälkeä ei tunnistettu. Yritä uudelleen."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Puhdista sormenjälkitunnistin ja yritä uudelleen"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Puhdista anturi ja yritä uudelleen"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Paina tunnistinta voimakkaasti"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Kasvoja ei näy. Pidä puhelinta silmien korkeudella."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Laite liikkui liikaa. Pidä puhelin vakaana."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Rekisteröi kasvot uudelleen."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Kasvoja ei voi tunnistaa. Yritä uudelleen."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Kasvoja ei tunnistettu. Yritä uudelleen."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Liikuta päätä hieman"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Katso suoremmin puhelimeen"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Katso suoremmin puhelimeen"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 8842ae8..379dce09 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -371,6 +371,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Permet à l\'application d\'établir un lien avec le module de diffusion cellulaire afin de transférer les messages de diffusion cellulaire à mesure de leur réception. Dans certaines régions, des alertes de diffusion cellulaire sont envoyées afin de vous avertir de situations d\'urgence. Des applications malveillantes peuvent interférer avec les performances ou le fonctionnement de votre appareil lors de la réception d\'une alerte d\'urgence par diffusion cellulaire."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Gérer les appels en cours"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Autorise une application à afficher les renseignements concernant les appels en cours sur votre appareil et à les gérer."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"lire les messages de diffusion cellulaire"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Permet à l\'application de lire les messages de diffusion cellulaire que votre appareil reçoit. Dans certaines zones géographiques, des alertes vous sont envoyées afin de vous prévenir en cas de situation d\'urgence. Des applications malveillantes peuvent venir perturber les performances ou le fonctionnement de votre appareil lors de la réception d\'un message de diffusion cellulaire."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"lire les flux auxquels vous êtes abonné"</string>
@@ -435,6 +439,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Autoriser l\'application à utiliser les services d\'avant-plan avec le type « système exempté »"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"exécuter le service d\'avant-plan avec le type « fileManagement »"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Autorise l\'application à utiliser les services d\'avant-plan avec le type « fileManagement »"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"exécuter le service de premier plan avec le type « mediaProcessing »"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Autorise l\'application à utiliser les services de premier plan avec le type « mediaProcessing »"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"exécuter le service d\'avant-plan avec le type « usage spécial »"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Autoriser l\'application à utiliser les services d\'avant-plan avec le type « usage spécial »"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"évaluer l\'espace de stockage de l\'application"</string>
@@ -633,7 +639,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Utiliser le verrouillage de l\'écran"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Entrez votre verrouillage d\'écran pour continuer"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Appuyez fermement sur le capteur"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Empreinte digitale non reconnue. Réessayez."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Empreinte digitale non reconnue. Réessayez."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Nettoyez le capteur d\'empreintes digitales et réessayez"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Nettoyez le capteur et réessayez"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Appuyez fermement sur le capteur"</string>
@@ -697,7 +703,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Visage non détecté. Tenez votre téléphone à hauteur des yeux."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Trop de mouvement. Tenez le téléphone immobile."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Veuillez inscrire votre visage à nouveau."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Visage non reconnu. Réessayez."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Visage non reconnu. Réessayez."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Modifiez légèrement la position de votre tête"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Regardez droit dans le téléphone"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Regardez droit dans le téléphone"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 3b24230..455b2da 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -371,6 +371,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Autorise l\'application à établir une connexion avec le module de diffusion cellulaire afin de transférer les messages reçus via un canal de diffusion cellulaire. Des alertes de diffusion cellulaire sont générées dans certaines régions afin de vous avertir de situations d\'urgence. Des applications malveillantes peuvent interférer avec les performances ou le fonctionnement de votre appareil lors de la réception d\'une alerte d\'urgence par diffusion cellulaire."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Gérer les appels en cours"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Autorise une application à afficher les détails concernant les appels en cours sur votre appareil et à contrôler ces appels."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"lire les messages reçus via un canal de diffusion cellulaire"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Permet à l\'application de lire les messages que votre appareil reçoit via un canal de diffusion cellulaire. Dans certaines zones géographiques, des alertes vous sont envoyées afin de vous prévenir en cas de situation d\'urgence. Les applications malveillantes peuvent venir perturber les performances ou le fonctionnement de votre appareil lorsqu\'un message est reçu via un canal de diffusion cellulaire."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"lire les flux auxquels vous êtes abonné"</string>
@@ -435,6 +439,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Autorise l\'appli à utiliser les services de premier plan avec le type \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"exécuter un service de premier plan avec le type \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Autorise l\'appli à utiliser les services de premier plan avec le type \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"exécuter un service de premier plan avec le type \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Autorise l\'appli à utiliser les services de premier plan avec le type \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"exécuter un service de premier plan avec le type \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Autorise l\'appli à utiliser les services de premier plan avec le type \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"évaluer l\'espace de stockage de l\'application"</string>
@@ -633,7 +639,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Utiliser verrouillage écran"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Utilisez le verrouillage de l\'écran pour continuer"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Appuyez fermement sur le lecteur"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Impossible de reconnaître l\'empreinte digitale. Réessayez."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Empreinte digitale non reconnue. Réessayez."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Nettoyez le lecteur d\'empreinte digitale et réessayez"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Nettoyez le lecteur et réessayez"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Appuyez fermement sur le lecteur"</string>
@@ -697,7 +703,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Visage non détecté. Tenez votre téléphone à hauteur des yeux."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Trop de mouvement. Ne bougez pas le téléphone."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Veuillez enregistrer à nouveau votre visage."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Visage non reconnu. Réessayez."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Visage non reconnu. Réessayez."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Déplacez légèrement votre tête."</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Mettez-vous bien de face et regardez le téléphone"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Mettez-vous bien de face et regardez le téléphone"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 703ed7c..df08dd3 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Permite que a aplicación se vincule ao módulo de difusión móbil para reenviar as mensaxes deste tipo segundo se reciban. As alertas de difusión móbil envíanse nalgunhas localizacións para avisar de situacións de emerxencia. As aplicacións maliciosas poden interferir no rendemento ou funcionamento do teu dispositivo cando se reciba unha difusión móbil de emerxencia."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Xestionar as chamadas saíntes"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Permite que unha aplicación controle as chamadas saíntes do teu dispositivo e consulte os detalles sobre elas."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"ler mensaxes de difusión móbil"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Permite á aplicación ler mensaxes de difusión móbil recibidas polo teu dispositivo. As alertas de difusión móbil envíanse nalgunhas localizacións para avisar de situacións de emerxencia. É posible que aplicacións maliciosas afecten ao rendemento ou funcionamento do teu dispositivo cando se recibe unha difusión móbil de emerxencia."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"ler feeds subscritos"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite que a aplicación faga uso de servizos en primeiro plano co tipo \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"executar servizo en primeiro plano co tipo \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Permite que a aplicación faga uso de servizos en primeiro plano co tipo \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"executar servizo en primeiro plano co tipo \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Permite que a aplicación faga uso de servizos en primeiro plano co tipo \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"executar servizo en primeiro plano co tipo \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite que a aplicación faga uso de servizos en primeiro plano co tipo \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"medir o espazo de almacenamento da aplicación"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Usar credencial do dispositivo"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Desbloquea a pantalla para continuar"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Preme o sensor con firmeza"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Non se puido recoñecer a impresión dixital. Téntao de novo."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Non se recoñeceu a impresión dixital. Téntao de novo."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Limpa o sensor de impresión dixital e téntao de novo"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Limpa o sensor e téntao de novo"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Preme o sensor con firmeza"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Non se che ve a cara. Pon o teléfono diante dos ollos"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Demasiado movemento. Non movas o teléfono."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Volve rexistrar a túa cara."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Non se recoñeceu a cara. Téntao de novo."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Non se recoñeceu a cara. Téntao de novo."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Cambia lixeiramente a posición da cabeza"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Mira o teléfono de forma máis directa"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Mira o teléfono de forma máis directa"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 2e1f0e6..574c510 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"સેલ બ્રોડકાસ્ટ સંદેશા પ્રાપ્ત થાય કે તરત ફૉરવર્ડ કરવા માટે સેલ બ્રોડકાસ્ટ મૉડ્યૂલ સાથે પ્રતિબદ્ધ થવા બાબતે ઍપને મંજૂરી આપે છે. તમને કટોકટીની પરિસ્થિતિની ચેતવણી આપવા માટે સેલ બ્રોડકાસ્ટ અલર્ટ અમુક સ્થાનોમાં ડિલિવર કરવામાં આવે છે. કટોકટી અંગેનો સેલ બ્રોડકાસ્ટ પ્રાપ્ત થાય, ત્યારે દુર્ભાવનાપૂર્ણ ઍપ તમારા ડિવાઇસના કાર્યપ્રદર્શન અથવા ઑપરેશનમાં વિક્ષેપ પાડે તેમ બની શકે છે."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"ચાલી રહેલા કૉલ મેનેજ કરો"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"ઍપને તમારા ડિવાઇસ પર ચાલુ કૉલ વિશેની વિગતો જોવાની અને આ કૉલને નિયંત્રિત કરવાની મંજૂરી આપે છે."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"સેલ બ્રોડકાસ્ટ સંદેશા વાંચો"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"ઍપ તમારા ડિવાઇસ દ્વારા પ્રાપ્ત થયેલ સેલ બ્રોડકાસ્ટ સંદેશાને વાંચવાની મંજૂરી આપે છે. સેલ બ્રોડકાસ્ટ ચેતવણીઓ તમને ઇમર્જન્સીની સ્થિતિઓ અંગે ચેતવવા માટે કેટલાક સ્થાનોમાં વિતરિત થાય છે. જ્યારે ઇમર્જન્સીનો સેલ બ્રોડકાસ્ટ પ્રાપ્ત થાય ત્યારે દુર્ભાવનાપૂર્ણ ઍપ તમારા ડિવાઇસના કાર્યપ્રદર્શન અથવા ઓપરેશનમાં હસ્તક્ષેપ કરી શકે છે."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"સબ્સ્ક્રાઇબ કરેલ ફીડ્સ વાંચો"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"ઍપને \"systemExempted\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવાઓનો ઉપયોગ કરવાની મંજૂરી આપે છે"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવા ચલાવો"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"ઍપને \"fileManagement\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવાઓનો ઉપયોગ કરવાની મંજૂરી આપે છે"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"\"mediaProcessing\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવા ચલાવો"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"ઍપને \"mediaProcessing\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવાઓનો ઉપયોગ કરવાની મંજૂરી આપે છે"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવા ચલાવો"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"ઍપને \"specialUse\" પ્રકારની પરવાનગી વડે ફૉરગ્રાઉન્ડ સેવાઓનો ઉપયોગ કરવાની મંજૂરી આપે છે"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"ઍપ્લિકેશન સંગ્રહ સ્થાન માપો"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"સ્ક્રીન લૉકનો ઉપયોગ કરો"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"આગળ વધવા માટે તમારું સ્ક્રીન લૉક દાખલ કરો"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"સેન્સર પર જોરથી દબાવો"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"ફિંગરપ્રિન્ટ ઓળખી શકતા નથી. ફરી પ્રયાસ કરો."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"ફિંગરપ્રિન્ટ ઓળખી શકાઈ નથી. ફરી પ્રયાસ કરો."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"ફિંગરપ્રિન્ટ સેન્સર સાફ કરો અને ફરી પ્રયાસ કરો"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"સેન્સર સાફ કરો અને ફરી પ્રયાસ કરો"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"સેન્સર પર જોરથી દબાવો"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"તમારો ચહેરો દેખાતો નથી. તમારા ફોનને આંખના લેવલ પર પકડી રાખો."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"ડિવાઇસ અસ્થિર છે. ફોનને સ્થિર રાખો."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"કૃપા કરીને તમારા ચહેરાની ફરી નોંધણી કરાવો."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"ચહેરો ઓળખી શકતા નથી. ફરી પ્રયાસ કરો."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"ચહેરો ઓળખાયો નથી. ફરી પ્રયાસ કરો."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"તમારા માથાની સ્થિતિ સહેજ બદલો"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"વધારે પ્રમાણમાં સીધું તમારા ફોન તરફ જુઓ"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"વધારે પ્રમાણમાં સીધું તમારા ફોન તરફ જુઓ"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 336d998..2f08e2a 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"सेल ब्रॉडकास्ट (CBC) मैसेज आते ही उसे दूसरे नंबर पर भेजने के लिए, ऐप्लिकेशन को सेल ब्रॉडकास्ट (CBC) मॉड्यूल पर बाइंड करने की अनुमति देता है. कुछ जगहों में सेल ब्रॉडकास्ट (CBC) अलर्ट आपातकालीन स्थितियों के बारे में चेतावनी देने के लिए भेजा जाता है. नुकसान पहुंचाने वाले ऐप्लिकेशन, आपातकाल में सेल ब्रॉडकास्ट (CBC) मैसेज मिलने पर आपके डिवाइस के काम करते समय या इसके परफ़ॉर्मेंस में रुकावट पैदा कर सकते हैं."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"चल रहे कॉल प्रबंधित करें"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"इससे, ऐप्लिकेशन को आपके डिवाइस पर चल रहे कॉल की जानकारी देखने और उन्हें कंट्रोल करने की अनुमति मिलती है."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"सेल ब्रॉडकास्ट (CBC) मैसेज पढ़ें"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"ऐप को, वो सेल ब्रॉडकास्ट (CBC) मैसेज पढ़ने देता है जो आपके डिवाइस को मिले हैं. सेल ब्रॉडकास्ट (CBC) अलर्ट कुछ स्थानों (लोकेशन) पर आपको आपातकालीन स्‍थितियों की चेतावनी देने के लिए दिए जाते हैं. आपातकालीन सेल ब्रॉडकास्ट (CBC) मिलने पर, धोखा देने वाले ऐप आपके डिवाइस के परफ़ॉर्मेंस या कार्यवाही में दखल दे सकते हैं."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"सदस्यता वाली फ़ीड पढ़ें"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"इससे ऐप्लिकेशन, \"systemExempted\" टाइप वाली फ़ोरग्राउंड सेवाओं का इस्तेमाल कर पाता है"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" टाइप वाली फ़ोरग्राउंड सेवा को चलाने की अनुमति दें"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"इससे ऐप्लिकेशन को \"fileManagement\" टाइप वाली फ़ोरग्राउंड सेवाओं का इस्तेमाल करने की अनुमति मिलती है"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"\"mediaProcessing\" टाइप वाली फ़ोरग्राउंड सेवा को चलाने की अनुमति दें"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"इससे ऐप्लिकेशन, \"mediaProcessing\" टाइप वाली फ़ोरग्राउंड सेवाओं का इस्तेमाल कर पाता है"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" टाइप वाली फ़ोरग्राउंड सेवा को चलाने की अनुमति दें"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"इससे ऐप्लिकेशन, \"specialUse\" टाइप वाली फ़ोरग्राउंड सेवाओं का इस्तेमाल कर पाता है"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"पता करें कि ऐप मेमोरी में कितनी जगह है"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"स्क्रीन लॉक का क्रेडेंशियल इस्तेमाल करें"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"जारी रखने के लिए, अपने स्क्रीन लॉक की पुष्टि करें"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"सेंसर को उंगली से ज़ोर से दबाएं"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"फ़िंगरप्रिंट की पहचान नहीं की जा सकी. फिर से कोशिश करें."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"फ़िंगरप्रिंट की पहचान नहीं हो पाई. फिर से कोशिश करें."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"फ़िंगरप्रिंट सेंसर को साफ़ करके फिर से कोशिश करें"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"फ़िंगरप्रिंट सेंसर को साफ़ करके फिर से कोशिश करें"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"सेंसर को उंगली से दबाकर रखें"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"आपका चेहरा नहीं दिख रहा है. फ़ोन को अपनी आंखों की सीध में रखें."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"डिवाइस बहुत ज़्यादा हिल रहा है. फ़ोन को बिना हिलाएं पकड़ें."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"कृपया फिर से अपने चेहरे की पहचान कराएं."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"चेहरे की पहचान नहीं हुई. फिर से कोशिश करें."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"चेहरा नहीं पहचाना गया. फिर से कोशिश करें."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"अपने सिर की पोज़िशन को थोड़ा बदलें"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"अपने फ़ोन की तरफ़ बिलकुल सीधा देखें"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"अपने फ़ोन की तरफ़ बिलकुल सीधा देखें"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index ec5a77e..af3bd29 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -371,6 +371,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Omogućuje aplikaciji da se poveže s modulom za emitiranje na mobitele kako bi prosljeđivala poruke koje se emitiraju na mobitele po njihovom primitku. Upozorenja značajke emitiranja na mobitele dostavljaju se na nekim lokacijama kako bi upozorila korisnike na hitne situacije. Zlonamjerne aplikacije mogu ometati izvršavanje ili rad vašeg uređaja kada stigne hitno emitiranje na mobitele."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Upravljanje tekućim pozivima"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Omogućuje aplikaciji pregled pojedinosti o tekućim pozivima na uređaju i upravljanje tim pozivima."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"čitaj poruke koje se emitiraju unutar mobilne mreže"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Omogućuje aplikaciji čitanje poruka emitiranih unutar mobilne mreže koje prima vaš uređaj. Upozorenja koja se emitiraju na području mobilne mreže dostavljaju se na nekim lokacijama kako bi upozorila korisnike na hitne situacije. Zlonamjerne aplikacije mogu ometati izvršavanje ili rad vašeg uređaja kada stigne hitno upozorenje koje se emitira unutar mobilne mreže."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"čitanje pretplaćenih feedova"</string>
@@ -435,6 +439,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Omogućuje aplikaciji da iskoristi usluge u prednjem planu s vrstom \"izuzeo sustav\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"pokreni uslugu u prednjem planu s vrstom fileManagement"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Aplikaciji omogućuje da iskoristi usluge u prednjem planu s vrstom fileManagement"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"pokretanje usluge u prednjem planu s vrstom \"obrada medija\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Omogućuje aplikaciji da iskoristi usluge u prednjem planu s vrstom \"obrada medija\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"pokretanje usluge u prednjem planu s vrstom \"posebna upotreba\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Omogućuje aplikaciji da iskoristi usluge u prednjem planu s vrstom \"posebna upotreba\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"mjerenje prostora za pohranu aplikacije"</string>
@@ -633,7 +639,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Upotreba zaključavanja zaslona"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Upotrijebite zaključavanje zaslona da biste nastavili"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Čvrsto pritisnite senzor"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Prepoznavanje otiska prsta nije uspjelo. Pokušajte ponovo."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Otisak prsta nije prepoznat. Pokušajte ponovo."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Očistite senzor otiska prsta i pokušajte ponovno"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Očistite senzor i pokušajte ponovno"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Čvrsto pritisnite senzor"</string>
@@ -697,7 +703,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Vaše se lice ne vidi. Držite telefon u razini očiju."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Previše kretanja. Držite telefon mirno."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Ponovo registrirajte svoje lice."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Prepoznavanje lica nije uspjelo. Pokušajte ponovo."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Lice nije prepoznato. Pokušajte ponovo."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Malo pomaknite glavu"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Gledajte ravno u telefon"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Gledajte ravno u telefon"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 3bcd44b..882156c 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Az alkalmazás összekapcsolódhat a cellán belüli üzenetszórás moduljával, hogy az érkezésükkor továbbítani tudja a cellán belüli üzeneteket. Bizonyos helyeken figyelmeztető üzeneteket kaphat a cellán belül a vészhelyzetekről. A rosszindulatú alkalmazások vészhelyzeti cellaüzenet érkezésekor befolyásolhatják az eszköz teljesítményét és működését."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Folyamatban lévő hívások kezelése"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Engedélyezi az alkalmazásnak, hogy az eszközén folyamatban lévő hívások részleteihez hozzáférjen, és vezérelje ezeket a hívásokat."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"cellán belüli üzenetek olvasása"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Lehetővé teszi az alkalmazás számára az eszközre érkező cellán belüli üzenetek olvasását. Bizonyos helyeken figyelmeztető üzeneteket kaphat a cellán belül a vészhelyzetekről. A rosszindulatú alkalmazások befolyásolhatják az eszköz  teljesítményét vagy működését vészhelyzeti cellaüzenet érkezésekor."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"feliratkozott hírcsatornák olvasása"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Engedélyezi az alkalmazásnak az előtérben lévő szolgáltatások használatát a következő típussal: „systemExempted”"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"előtérben lévő szolgáltatás futtatása a következő típussal: „fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Lehetővé teszi az alkalmazásnak az előtérben futó szolgáltatások használatát a következő típussal: „fileManagement”"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"előtérben lévő szolgáltatás futtatása a következő típussal: „mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Engedélyezi az alkalmazásnak az előtérben lévő szolgáltatások használatát a következő típussal: „mediaProcessing”"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"előtérben lévő szolgáltatás futtatása a következő típussal: „specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Engedélyezi az alkalmazásnak az előtérben lévő szolgáltatások használatát a következő típussal: „specialUse”"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"alkalmazás-tárhely felmérése"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Képernyőzár használata"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"A folytatáshoz adja meg a képernyőzár hitelesítési adatait"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Nyomja meg határozottan az érzékelőt"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Az ujjlenyomat nem ismerhető fel. Próbálkozzon újra."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Ismeretlen ujjlenyomat. Próbálkozzon újra."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Tisztítsa meg az ujjlenyomat-érzékelőt, majd próbálja újra"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Tisztítsa meg az érzékelőt, majd próbálja újra"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Nyomja meg határozottan az érzékelőt"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Nem látszik az arca. Tartsa szemmagasságban a telefonját."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Túl sok a mozgás. Tartsa stabilan a telefont."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Rögzítsen újra képet az arcáról."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Az arc nem felismerhető. Próbálja újra."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Sikertelen arcfelismerés. Próbálkozzon újra."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Egy kicsit mozdítsa el a fejét"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Nézzen egyenesen a telefonjára"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Nézzen egyenesen a telefonjára"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 603d7c6..c85aeb8 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Թույլ է տալիս հավելվածին կապ հաստատել բջջային հեռարձակման մոդուլի հետ՝ բնակչությանը ծանուցող հաղորդագրությունները վերահասցեավորելու համար։ Որոշ երկրներում այս հաղորդագրություններն օգտագործվում են բնակչությանը արտակարգ իրավիճակների մասին զգուշացնելու համար: Վնասարար հավելվածները կարող են խանգարել ձեր սարքի աշխատանքին, որին ուղարկվում են այս հաղորդագրությունները:"</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Ընթացիկ զանգերի կառավարում"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Թույլ է տալիս հավելվածին ձեր սարքում տեսնել ընթացիկ զանգերի մասին տեղեկությունները և կառավարել այդ զանգերը։"</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"կարդալ բջջային զեկուցվող հաղորդագրությունները"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Թույլ է տալիս հավելվածին կարդալ ձեր սարքի կողմից ստացված բջջային հեռարձակվող հաղորդագրությունները: Բջջային հեռարձակվող զգուշացումները ուղարկվում են որոշ վայրերում` արտակարգ իրավիճակների մասին ձեզ զգուշացնելու համար: Վնասարար հավելվածները կարող են խանգարել ձեր սարքի արդյունավետությանը կամ շահագործմանը, երբ ստացվում է արտակարգ իրավիճակի մասին բջջային հաղորդում:"</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"կարդալ բաժանորդագրված հոսքերը"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Հավելվածին թույլ է տալիս օգտագործել systemExempted տեսակով ակտիվ ծառայությունները"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"fileManagement տեսակով ակտիվ ծառայությունների գործարկում"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Թույլատրում է հավելվածին օգտագործել fileManagement տեսակով ակտիվ ծառայությունները"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"mediaProcessing տեսակով ակտիվ ծառայությունների գործարկում"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Հավելվածին թույլ է տալիս օգտագործել mediaProcessing տեսակով ակտիվ ծառայությունները"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"գործարկել specialUse տեսակով ակտիվ ծառայությունները"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Հավելվածին թույլ է տալիս օգտագործել specialUse տեսակով ակտիվ ծառայությունները"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"չափել հավելվածի պահոցի տարածքը"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Էկրանի կողպում"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Շարունակելու համար ապակողպեք էկրանը"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Մատը ուժեղ սեղմեք սկաների վրա"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Մատնահետքը չի հաջողվում ճանաչել։ Նորից փորձեք։"</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Մատնահետքը չի ճանաչվել։ Նորից փորձեք։"</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Մաքրեք մատնահետքերի սկաները և նորից փորձեք"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Մաքրեք սկաները և նորից փորձեք"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Մատը ուժեղ սեղմեք սկաների վրա"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Դեմքը չի երևում։ Հեռախոսը պահեք աչքերի մա­­կարդակում։"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Շատ եք շարժում։ Հեռախոսն անշարժ պահեք։"</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Նորից փորձեք։"</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Դեմքը չի հաջողվում ճանաչել։ Նորից փորձեք։"</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Դեմքը չի ճանաչվել։ Նորից փորձեք։"</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Թեթևակի փոխեք գլխի դիրքը"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Նայեք ուղիղ էկրանին"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Նայեք ուղիղ էկրանին"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 7886280..2b76815 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Mengizinkan aplikasi mem-binding ke modul cell broadcast untuk meneruskan pesan cell broadcast saat pesan tersebut diterima. Notifikasi cell broadcast dikirim di beberapa lokasi untuk memperingatkan Anda tentang situasi darurat. Aplikasi berbahaya dapat mengganggu performa atau operasi perangkat saat cell broadcast darurat diterima."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Mengelola panggilan yang sedang berlangsung"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Mengizinkan aplikasi untuk melihat detail panggilan yang sedang berlangsung di perangkat dan mengontrolnya."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"membaca pesan siaran seluler"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Mengizinkan aplikasi membaca pesan siaran seluler yang diterima perangkat Anda. Notifikasi siaran seluler dikirimkan di beberapa lokasi untuk memperingatkan Anda tentang situasi darurat. Aplikasi berbahaya dapat mengganggu kinerja atau operasi perangkat Anda saat siaran seluler darurat diterima."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"baca feed langganan"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Mengizinkan aplikasi menggunakan layanan latar depan dengan jenis \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"menjalankan layanan latar depan dengan jenis \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Mengizinkan aplikasi menggunakan layanan latar depan dengan jenis \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"menjalankan layanan latar depan dengan jenis \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Mengizinkan aplikasi menggunakan layanan latar depan dengan jenis \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"menjalankan layanan latar depan dengan jenis \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Mengizinkan aplikasi menggunakan layanan latar depan dengan jenis \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"mengukur ruang penyimpanan aplikasi"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Gunakan kunci layar"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Masukkan kunci layar untuk melanjutkan"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Tekan sensor dengan kuat"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Tidak dapat mengenali sidik jari. Coba lagi."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Sidik jari tidak dikenali. Coba lagi."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Bersihkan sensor sidik jari lalu coba lagi"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Bersihkan sensor lalu coba lagi"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Tekan sensor dengan kuat"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Wajah tidak terlihat. Pegang ponsel sejajar mata."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Terlalu banyak gerakan. Stabilkan ponsel."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Daftarkan ulang wajah Anda."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Tidak dapat mengenali wajah. Coba lagi."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Wajah tidak dikenali. Coba lagi."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Ubah sedikit posisi kepala"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Lihat lebih lurus ke arah ponsel"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Lihat lebih lurus ke arah ponsel"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index ddf60c1..f8b4197 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Heimilar forritinu að bindast endurvarpseiningunni til að framsenda skilaboð frá endurvarpa þegar þau berast. Viðvaranir frá endurvarpa berast á tilteknum stöðum til að vara þig við neyðarástandi. Spilliforrit geta truflað afköst eða virkni tækisins þegar neyðarboð berast frá endurvarpa."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Stjórna símtölum sem eru í gangi"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Leyfir forriti að sjá upplýsingar um og stjórna símtölum sem eru í gangi í tækinu þínu."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"lesa skilaboð frá endurvarpa"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Leyfir forriti að lesa skilaboð frá endurvarpa sem tækið móttekur. Viðvaranir frá endurvarpa berast á tilteknum stöðum til að vara þig við neyðarástandi. Spilliforrit geta truflað afköst eða virkni tækisins þegar neyðarboð berast frá endurvarpa."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"lesa strauma í áskrift"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Leyfir forritinu að nota forgrunnsþjónustu af gerðinni „systemExempted“"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"keyra forgrunnsþjónustu af gerðinni „fileManagement“"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Leyfir forritinu að nota forgrunnsþjónustur af gerðinni „fileManagement“"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"Keyra forgrunnsþjónustu af gerðinni „mediaProcessing“"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Leyfir forritinu að nota forgrunnsþjónustu af gerðinni „mediaProcessing“"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"keyra forgrunnsþjónustu af gerðinni „specialUse“"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Leyfir forritinu að nota forgrunnsþjónustu af gerðinni „specialUse“"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"mæla geymslurými forrits"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Nota skjálás"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Sláðu inn skjálásinn þinn til að halda áfram"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Ýttu ákveðið á lesarann"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Fingrafar þekkist ekki. Reyndu aftur."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Ekki tókst að bera kennsl á fingrafar. Reyndu aftur."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Hreinsaðu fingrafaralesarann og reyndu aftur"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Hreinsaðu lesarann og reyndu aftur"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Ýttu ákveðið á lesarann"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Sé ekki andlitið á þér. Haltu símanum í augnhæð."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Of mikil hreyfing. Haltu símanum stöðugum."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Skráðu nafnið þitt aftur."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Andlit þekkist ekki. Reyndu aftur."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Ekki tókst að bera kennsl á andlit. Reyndu aftur."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Færðu höfuðið aðeins til"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Horfðu beint á símann"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Horfðu beint á símann"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 3877ac4..5a67476 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -371,6 +371,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Consente all\'app di essere associata al modulo di cell broadcast al fine di inoltrare i messaggi di cell broadcast man mano che arrivano. Gli avvisi cell broadcast vengono trasmessi in alcune località per avvertire di situazioni di emergenza. Le app dannose potrebbero interferire con le prestazioni o con il funzionamento del dispositivo quando si riceve un messaggio cell broadcast di emergenza."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Gestione delle chiamate in corso"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Consente a un\'app di accedere a dettagli relativi alle chiamate in corso sul tuo dispositivo e di controllare tali chiamate."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"lettura di messaggi cell broadcast"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Consente all\'applicazione di leggere i messaggi cell broadcast ricevuti dal dispositivo. Gli avvisi cell broadcast vengono trasmessi in alcune località per avvertire di eventuali situazioni di emergenza. Le app dannose potrebbero interferire con le prestazioni o con il funzionamento del dispositivo quando si riceve un messaggio cell broadcast di emergenza."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"lettura feed sottoscritti"</string>
@@ -435,6 +439,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Consente all\'app di usare i servizi in primo piano con il tipo \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"Eseguire servizi in primo piano con il tipo \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Consente all\'app di usare i servizi in primo piano con il tipo \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"Eseguire servizi in primo piano con il tipo \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Consente all\'app di usare i servizi in primo piano con il tipo \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"Esecuzione di servizi in primo piano con il tipo \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Consente all\'app di usare i servizi in primo piano con il tipo \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"calcolo spazio di archiviazione applicazioni"</string>
@@ -633,7 +639,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Usa il blocco schermo"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Inserisci il blocco schermo per continuare"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Premi con decisione sul sensore"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Impossibile riconoscere l\'impronta. Riprova."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Impronta non riconosciuta. Riprova."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Pulisci il sensore di impronte digitali e riprova"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Pulisci il sensore e riprova"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Premi con decisione sul sensore"</string>
@@ -697,7 +703,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Volto non visibile. Tieni lo smartphone all\'altezza degli occhi."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Troppo movimento. Tieni fermo il telefono."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Ripeti l\'acquisizione del volto."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Impossibile riconoscere il volto. Riprova."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Volto non riconosciuto. Riprova."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Cambia leggermente la posizione della testa"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Guarda dritto nello smartphone"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Guarda dritto nello smartphone"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index eb2f3b2..270568d 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -371,6 +371,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"מאפשרת לאפליקציה להתחייב למודול של השידור הסלולרי כדי להעביר הודעות של שידור סלולרי כשהן מתקבלות. התראות שידור סלולרי נשלחות במקומות מסוימים כדי להזהיר אותך מפני מצבי חירום. אפליקציות זדוניות עשויות להפריע לביצועים או לפעולה של המכשיר כאשר מתקבל שידור חירום סלולרי."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"ניהול שיחות שנערכות"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"לאפליקציה תהיה אפשרות לראות פרטים על שיחות שנערכות במכשיר ולשלוט בשיחות האלה."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"קריאת הודעות שידור סלולרי"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"מאפשרת לאפליקציה לקרוא הודעות שידור סלולרי שהתקבלו במכשיר שלך. התראות שידור סלולרי נשלחות במקומות מסוימים כדי להזהיר אותך מפני מצבי חירום. אפליקציות זדוניות עשויות להפריע לביצועים או לפעולה של המכשיר שלך כאשר מתקבל שידור חירום סלולרי."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"קריאת עדכוני מינויים"</string>
@@ -435,6 +439,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"‏ההרשאה הזו מאפשרת לאפליקציה להשתמש בשירותים שפועלים בחזית מסוג \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"‏הפעלת שירות שפועל בחזית מסוג \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"‏ההרשאה הזו מאפשרת לאפליקציה להתבסס על שירותים שפועלים בחזית מסוג \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"‏הפעלת שירות שפועל בחזית מסוג \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"‏ההרשאה הזו מאפשרת לאפליקציה להשתמש בשירותים שפועלים בחזית מסוג \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"‏הפעלת שירות שפועל בחזית מסוג \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"‏ההרשאה הזו מאפשרת לאפליקציה להשתמש בשירותים שפועלים בחזית מסוג \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"מדידת נפח האחסון של אפליקציות"</string>
@@ -633,7 +639,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"שימוש בנעילת מסך"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"יש לבטל את נעילת המסך כדי להמשיך"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"צריך ללחוץ לחיצה חזקה על החיישן"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"לא ניתן לזהות את טביעת האצבע. יש לנסות שוב."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"טביעת האצבע לא זוהתה. אפשר לנסות שוב."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"עליך לנקות את חיישן טביעות האצבע ולנסות שוב"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"עליך לנקות את החיישן ולנסות שוב"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"צריך ללחוץ חזק על החיישן"</string>
@@ -697,7 +703,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"לא רואים את הפנים שלך. יש להחזיק את הטלפון בגובה העיניים."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"יותר מדי תנועה. יש להחזיק את הטלפון בצורה יציבה."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"יש לסרוק שוב את הפנים."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"לא ניתן לזהות את הפנים. יש לנסות שוב."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"הפנים לא זוהו. אפשר לנסות שוב."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"צריך לשנות מעט את תנוחת הראש"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"צריך להביט ישירות בטלפון"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"צריך להביט ישירות בטלפון"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index a2d40af..98e2324 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"受信した緊急速報メールを転送するために、緊急速報メール モジュールにバインドすることをこのアプリに許可します。緊急速報メールは、緊急事態を警告する目的で一部の地域に配信されます。緊急速報メールの受信時に、悪意のあるアプリによってデバイスの動作や処理が妨害される恐れがあります。"</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"通話の管理"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"デバイスでの通話に関する詳細の参照と、通話の操作をアプリに許可します。"</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"緊急速報メール SMS の読み取り"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"デバイスで受信した緊急速報メール SMS の読み取りをアプリに許可します。緊急速報メールは、緊急事態を警告する目的で一部の地域に配信されます。緊急速報メールの受信時に、悪意のあるアプリによってデバイスの動作や処理が妨害される恐れがあります。"</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"登録したフィードの読み取り"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"タイプが「systemExempted」のフォアグラウンド サービスの使用をアプリに許可します"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"タイプが「fileManagement」のフォアグラウンド サービスの実行"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"タイプが「fileManagement」のフォアグラウンド サービスの使用をアプリに許可します"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"タイプが「mediaProcessing」のフォアグラウンド サービスの実行"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"タイプが「mediaProcessing」のフォアグラウンド サービスの使用をアプリに許可します"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"タイプが「specialUse」のフォアグラウンド サービスの実行"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"タイプが「specialUse」のフォアグラウンド サービスの使用をアプリに許可します"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"アプリのストレージ容量の計測"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"画面ロックの使用"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"続行するには画面ロックを入力してください"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"センサーにしっかりと押し当ててください"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"指紋を認識できません。もう一度お試しください。"</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"指紋を認識できません。もう一度お試しください。"</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"指紋認証センサーの汚れを取り除いて、もう一度お試しください"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"センサーの汚れを取り除いて、もう一度お試しください"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"センサーにしっかりと押し当ててください"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"顔を確認できません。スマートフォンを目の高さに合わせます。"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"あまり動かさないでください。安定させてください。"</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"顔を登録し直してください。"</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"顔を認識できません。もう一度お試しください。"</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"顔を認識できません。もう一度お試しください。"</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"顔の位置を少し変えてください"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"もっとまっすぐスマートフォンに顔を向けてください"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"もっとまっすぐスマートフォンに顔を向けてください"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index dee1f1f..f7ce159 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"საშუალებას აძლევს აპს, დაუკავშირდეს cell broadcast მოდულს იმისთვის, რომ გადაამისამართოს cell broadcast შეტყობინებები მათი მიღებისთანავე. Cell broadcast გაფრთხილებები მიეწოდება ზოგიერთ მდებარეობაზე საგანგებო სიტუაციების შესახებ გაფრთხილების მიზნით. საგანგებო cell broadcast-ის მიღების დროს, მავნე აპებმა შეიძლება ხელი შეუშალონ თქვენი მოწყობილობის ფუნქციონირებას ან ოპერაციებს."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"მიმდინარე ზარების მართვა"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"საშუალებას აძლევს აპს, ნახოს თქვენს მოწყობილობაზე მიმდინარე ზარების დეტალები და აკონტროლოს ეს ზარები."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"მასიური დაგზავნის შეტყობინებების წაკითხვა"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"აპს შეეძლება, წაიკითხოს თქვენს მოწყობილობაზე გამოგზავნილი ქსელის სამაუწყებლო შეტყობინებები. სამაუწყებლო გაფრთხილებები მოგეწოდებათ ზოგიერთ ადგილზე ექსტრემალური სიტუაციების შესახებ გასაფრთხილებლად. ქსელის გადაუდებელი შეტყონიბენის მიღების დროს მავნე აპებმა შეიძლება ხელი შეუშალონ თქვენი მოწყობილობის ფუნქციონირებას ან ოპერაციებს."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"გამოწერილი არხების წაკითხვა"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"ნებას რთავს აპს, გამოიყენოს პრიორიტეტული სერვისები ტიპის „გათავისუფლებულისისტემა“ შემთხვევაში"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"პრიორიტეტული სერვისის გაშვება ტიპის „ფაილებისმართვა“ შემთხვევაში"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"საშუალებას აძლევს აპს, გამოიყენოს პრიორიტეტული სერვისები ტიპის „ფაილისმართვა“ შემთხვევაში"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"პრიორიტეტული სერვისის გაშვება „mediaProcessing“ ტიპის შემთხვევაში"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"აპს რთავს ნებას, გამოიყენოს პრიორიტეტული სერვისები „mediaProcessing\" ტიპის შემთხვევაში"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"პრიორიტეტული სერვისის გაშვება ტიპის „სპეციალურიგამოყენება“ შემთხვევაში"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"ნებას რთავს აპს, გამოიყენოს პრიორიტეტული სერვისები ტიპის „სპეციალურიგამოყენება“ შემთხვევაში"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"აპის მეხსიერების სივრცის გაზომვა"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"გამოიყენეთ ეკრანის დაბლოკვა"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"გასაგრძელებლად შედით ეკრანის დაბლოკვაში"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"მაგრად დააჭირეთ სენსორს"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"თითის ანაბეჭდის ამოცნობა ვერ ხერხდება. ცადეთ ხელახლა."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"თითის ანაბეჭდის ამოცნობა ვერ მოხერხდა. ცადეთ ხელახლა."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"გაწმინდეთ თითის ანაბეჭდის სენსორი და ხელახლა ცადეთ"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"გაწმინდეთ სენსორი და ხელახლა ცადეთ"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"მაგრად დააჭირეთ სენსორს"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"სახე არ ჩანს. დაიჭირეთ ტელ. თვალის დონეზე."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"მეტისმეტად მოძრაობთ. მყარად დაიჭირეთ ტელეფონი."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"გთხოვთ, ხელახლა დაარეგისტრიროთ თქვენი სახე."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"სახის ამოცნობა ვერ ხერხდება. ცადეთ ხელახლა."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"სახის ამოცნობა ვერ მოხერხდა. ცადეთ ხელახლა."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"ოდნავ შეცვალეთ თავის პოზიცია"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"პირდაპირ უყურეთ ტელეფონს"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"პირდაპირ უყურეთ ტელეფონს"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 4cf61ac..c459954 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Ұялы таратылым хабарлары алынғаннан кейін, олардың бағытын өзгерту үшін қолданбаға ұялы таратылым модулімен байланыстыруға мүмкіндік береді. Ұялы таратылым ескертулері кей аймақтарда төтенше жағдайлар туралы хабарлау үшін беріледі. Төтенше жағдай туралы ұялы таратылым хабары алынғаннан кейін, зиянды қолданбалар құрылғы жұмысына кедергі келтіруі мүмкін."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Қазіргі қоңырауларды басқару"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Қолданбаға құрылғыдағы қазіргі қоңыраулар туралы мәліметтерді көруге және басқаруға мүмкіндік береді."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"ұялы хабар тарату хабарларын оқу"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Қолданбаға ұялы таратылым хабарларын оқу мүмкіндігін береді. Ұялы таратылым дабылдары кейбір аймақтарда төтенше жағдай туралы ескерту үшін қолданылады. Төтенше ұялы хабарлар келгенде залалды қолданбалар құрылғының жұмысына кедергі жасауы мүмкін."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"жазылған ағындарды оқу"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Қолданбаға \"systemExempted\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" түрі бар экрандық режимдегі қызметті іске қосу"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Қолданбаға \"fileManagement\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"\"mediaProcessing\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Қолданбаға \"mediaProcessing\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" түріне жататын экрандық режимдегі қызметті іске қосу"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Қолданбаға \"specialUse\" түріне жататын экрандық режимдегі қызметтерді пайдалануға рұқсат беріледі."</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"қолданба жадындағы бос орынды өлшеу"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Экран құлпын пайдалану"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Жалғастыру үшін экран құлпын енгізіңіз."</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Сканерді қатты басыңыз"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Саусақ ізін тану мүмкін емес. Қайталап көріңіз."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Саусақ ізі танылмады. Қайталап көріңіз."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Саусақ ізін оқу сканерін тазалап, әрекетті қайталаңыз."</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Сканерді тазалап, әрекетті қайталаңыз."</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Сканерді қатты басыңыз"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Бетіңіз көрінбей тұр. Телефонды көз деңгейінде ұстаңыз."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Қозғалыс тым көп. Телефонды қозғалтпаңыз."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Қайта тіркеліңіз."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Бет танылмады. Қайталап көріңіз."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Бет танылмады. Қайталап көріңіз."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Басыңыздың қалпын сәл өзгертіңіз."</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Телефонға тура қараңыз"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Телефонға тура қараңыз"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 7973f2a..364db0c 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"អនុញ្ញាតឱ្យ​កម្មវិធី​ភ្ជាប់ទៅ​ម៉ូឌុល​ការផ្សាយចល័ត ដើម្បីបញ្ជូន​សារផ្សាយ​ចល័តបន្ត នៅពេល​ទទួលបាន​សារទាំងនោះ។ ការជូនដំណឹងអំពី​ការផ្សាយចល័ត​ត្រូវបានបញ្ជូនទៅ​ទីតាំងមួយចំនួន ដើម្បីព្រមាន​អ្នក​អំពីស្ថានភាព​អាសន្ន។ កម្មវិធី​គ្រោះថ្នាក់​អាច​រំខាន​ដល់ដំណើរការ ឬប្រតិបត្តិការ​ឧបករណ៍​របស់អ្នក នៅពេល​ទទួលបានការផ្សាយ​ចល័តពេលមានអាសន្ន​។"</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"គ្រប់គ្រង​ការហៅទូរសព្ទដែល​កំពុងដំណើរការ"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"អនុញ្ញាតឱ្យ​កម្មវិធីមើលព័ត៌មានលម្អិត​អំពីការហៅទូរសព្ទ​ដែលកំពុងដំណើរការ​នៅលើឧបករណ៍​របស់អ្នក និងគ្រប់គ្រង​ការហៅទូរសព្ទទាំងនេះ។"</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"អាន​សារ​ប្រកាស​ចល័ត"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"ឲ្យ​កម្មវិធី​អាន​សារ​ប្រកាស​ការ​ហៅ​ដែល​ឧបករណ៍​របស់​​អ្នក​បាន​ទទួល។ ការ​ជូន​ដំណឹង​ប្រកាស​ចល័ត​ត្រូវ​បាន​បញ្ជូន​ទៅ​ទីតាំង​មួយ​ចំនួន ដើម្បី​ព្រមាន​អ្នក​អំពី​ស្ថានភាព​អាសន្ន។ កម្មវិធី​ព្យាបាទ​អាច​ជ្រៀតជ្រែក​ការ​អនុវត្ត ឬ​ប្រតិបត្តិការ​ឧបករណ៍​របស់​អ្នក​​ពេល​ទទួល​ការ​ប្រកាស​ចល័ត​ពេល​អាសន្ន។"</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"អាន​អត្ថបទ​ព័ត៌មាន​បាន​ជាវ"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"អនុញ្ញាតឱ្យកម្មវិធីប្រើប្រាស់សេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"ដំណើរការសេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"អនុញ្ញាតឱ្យកម្មវិធីប្រើប្រាស់សេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"ដំណើរការសេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"អនុញ្ញាតឱ្យកម្មវិធីប្រើប្រាស់សេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"ដំណើរការសេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"អនុញ្ញាតឱ្យកម្មវិធីប្រើប្រាស់សេវាកម្មផ្ទៃខាងមុខជាមួយនឹងប្រភេទ \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"វាស់​ទំហំ​ការ​ផ្ទុក​​កម្មវិធី"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"ប្រើ​ការ​ចាក់​សោ​អេក្រង់"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"បញ្ចូលការចាក់សោអេក្រង់របស់អ្នក ដើម្បីបន្ត"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"សង្កត់លើ​សេនស័រឱ្យណែន"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"មិនអាចសម្គាល់​ស្នាមម្រាមដៃបានទេ។ សូមព្យាយាមម្ដងទៀត។"</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"មិនស្គាល់ស្នាមម្រាមដៃទេ។ សូមព្យាយាមម្ដងទៀត។"</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"សម្អាត​ឧបករណ៍​ចាប់ស្នាមម្រាមដៃ រួចព្យាយាម​ម្ដងទៀត"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"សម្អាត​ឧបករណ៍​ចាប់សញ្ញា រួចព្យាយាម​ម្ដងទៀត"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"សង្កត់លើ​សេនស័រឱ្យណែន"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"មើលមិនឃើញ​មុខរបស់អ្នកទេ។ កាន់ទូរសព្ទរបស់អ្នក​ដាក់ត្រឹមភ្នែក។"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"មាន​ចលនា​ខ្លាំងពេក។ សូមកាន់​ទូរសព្ទ​ឱ្យនឹង។"</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"សូម​​ស្កេន​បញ្ចូល​មុខរបស់អ្នក​ម្ដងទៀត។"</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"មិនអាចសម្គាល់មុខបានទេ។ សូមព្យាយាមម្ដងទៀត។"</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"មិន​ស្គាល់​មុខទេ។ សូមព្យាយាមម្ដងទៀត។"</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"ប្ដូរទីតាំងក្បាល​របស់អ្នកតិចៗ"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"មើល​ទូរសព្ទ​របស់អ្នក​ឱ្យចំជាងនេះ"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"មើល​ទូរសព្ទ​របស់អ្នក​ឱ្យចំជាងនេះ"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 7e5e5b5..89e2361 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"ಸೆಲ್ ಪ್ರಸಾರವು ಸಂದೇಶಗಳನ್ನು ಸ್ವೀಕರಿಸಿದ ರೀತಿಯಲ್ಲಿಯೇ ಫಾರ್ವರ್ಡ್ ಮಾಡಲು, ಸೆಲ್ ಪ್ರಸಾರ ಮಾಡ್ಯುಲ್‌ ಅನ್ನು ಪ್ರತಿಬಂಧಿಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಕೆಲವು ಸ್ಥಳಗಳಲ್ಲಿ ತುರ್ತು ಸ್ಥಿತಿಗಳ ಕುರಿತು ನಿಮಗೆ ಎಚ್ಚರಿಸಲು ಸೆಲ್ ಪ್ರಸಾರದ ಎಚ್ಚರಿಕೆಗಳನ್ನು ಕಳುಹಿಸಲಾಗುತ್ತದೆ. ತುರ್ತು ಸೆಲ್‌ ಪ್ರಸಾರವನ್ನು ಸ್ವೀಕರಿಸಿದಾಗ ನಿಮ್ಮ ಸಾಧನದ ಕಾರ್ಯಾಚರಣೆ ಅಥವಾ ಕಾರ್ಯಕ್ಷಮತೆಗೆ ದುರುದ್ದೇಶಪೂರಿತ ಆ್ಯಪ್‌ಗಳು ಹಸ್ತಕ್ಷೇಪ ಮಾಡಬಹುದು."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"ಚಾಲ್ತಿಯಲ್ಲಿರುವ ಕರೆಗಳನ್ನು ನಿರ್ವಹಿಸಿ"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿನ ಚಾಲ್ತಿಯಲ್ಲಿರುವ ಕರೆಗಳ ಕುರಿತಾದ ವಿವರಗಳನ್ನು ನೋಡಲು ಮತ್ತು ಆ ಕರೆಗಳನ್ನು ನಿಯಂತ್ರಿಸಲು ಆ್ಯಪ್ ಒಂದಕ್ಕೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"ಸೆಲ್ ಪ್ರಸಾರದ ಸಂದೇಶಗಳನ್ನು ಓದಿರಿ"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"ನಿಮ್ಮ ಸಾಧನದಿಂದ ಸ್ವೀಕರಿಸಿದ ಸೆಲ್ ಪ್ರಸಾರ ಸಂದೇಶಗಳನ್ನು ರೀಡ್ ಮಾಡಲು ಅಪ್ಲಿಕೇಶನ್‌‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಸೆಲ್ ಪ್ರಸಾರ ಎಚ್ಚರಿಕೆಗಳನ್ನು ತುರ್ತು ಸಂದರ್ಭಗಳಲ್ಲಿ ನಿಮಗೆ ಎಚ್ಚರಿಸುವ ಸಲುವಾಗಿ ಕೆಲವು ಸ್ಥಳಗಳಲ್ಲಿ ವಿತರಿಸಲಾಗುತ್ತದೆ. ದುರುದ್ದೇಶಪೂರಿತ ಅಪ್ಲಿಕೇಶನ್‌‌‌ಗಳು ತುರ್ತು ಸೆಲ್ ಪ್ರಸಾರವನ್ನು ಸ್ವೀಕರಿಸುವಾಗ, ನಿಮ್ಮ ಸಾಧನದ ಕಾರ್ಯಕ್ಷಮತೆ ಇಲ್ಲವೇ ಕಾರ್ಯಾಚರಣೆಯಲ್ಲಿ ಹಸ್ತಕ್ಷೇಪ ಮಾಡಬಹುದು."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"ಚಂದಾದಾರ ಫೀಡ್‌ಗಳನ್ನು ಓದಿ"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಯನ್ನು ರನ್ ಮಾಡಿ"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"\"fileManagement\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"\"mediaProcessing\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಯನ್ನು ರನ್ ಮಾಡಿ"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"\"mediaProcessing\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಯನ್ನು ರನ್ ಮಾಡಿ"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"ಅಪ್ಲಿಕೇಶನ್‌ ಸಂಗ್ರಹ ಸ್ಥಳವನ್ನು ಅಳೆಯಿರಿ"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಬಳಸಿ"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"ಮುಂದುವರಿಯಲು ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಲಾಕ್‌ ಅನ್ನು ನಮೂದಿಸಿ"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"ಸೆನ್ಸರ್ ಮೇಲೆ ಗಟ್ಟಿಯಾಗಿ ಒತ್ತಿರಿ"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"ಫಿಂಗರ್‌ಪ್ರಿಂಟ್ ಅನ್ನು ಗುರುತಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"ಫಿಂಗರ್ ಪ್ರಿಂಟ್ ಅನ್ನು ಗುರುತಿಸಲಾಗಿಲ್ಲ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"ಫಿಂಗರ್‌ ಪ್ರಿಂಟ್ ಸೆನ್ಸರ್‌‌ ಸ್ವಚ್ಛಗೊಳಿಸಿ ಹಾಗೂ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"ಸೆನ್ಸರ್‌‌ ಸ್ವಚ್ಛಗೊಳಿಸಿ ಹಾಗೂ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"ಸೆನ್ಸರ್ ಮೇಲೆ ಗಟ್ಟಿಯಾಗಿ ಒತ್ತಿರಿ"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"ನಿಮ್ಮ ಮುಖ ಕಾಣಿಸುತ್ತಿಲ್ಲ. ನಿಮ್ಮ ಫೋನ್ ಅನ್ನು ಕಣ್ಣಿನ ನೇರಕ್ಕೆ ಹಿಡಿದುಕೊಳ್ಳಿ."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"ತುಂಬಾ ಅಲುಗಾಡುತ್ತಿದೆ ಫೋನ್ ಅನ್ನು ಸ್ಥಿರವಾಗಿ ಹಿಡಿಯಿರಿ."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"ನಿಮ್ಮ ಮುಖವನ್ನು ಮರುನೋಂದಣಿ ಮಾಡಿ."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"ಮುಖ ಗುರುತಿಸಲಾಗುತ್ತಿಲ್ಲ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"ಮುಖ ಗುರುತಿಸಲಾಗಿಲ್ಲ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"ನಿಮ್ಮ ತಲೆಯ ಸ್ಥಾನವನ್ನು ಸ್ವಲ್ಪ ಬದಲಾಯಿಸಿ"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"ನಿಮ್ಮ ಫೋನ್ ಅನ್ನು ನೇರವಾಗಿ ನೋಡಿ"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"ನಿಮ್ಮ ಫೋನ್ ಅನ್ನು ನೇರವಾಗಿ ನೋಡಿ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index cfc7730..a129c9bb 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"셀 브로드캐스트 메시지를 수신하자마자 전달하기 위해 앱이 셀 브로드캐스트 모듈에 연결하도록 허용합니다. 비상 상황임을 알리기 위해 일부 지역에서 셀 브로드캐스트 경고가 전달됩니다. 비상 셀 브로드캐스트를 수신할 때 악성 앱이 기기의 성능이나 작동을 방해할 수 있습니다."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"진행 중인 전화 관리"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"앱에서 기기의 진행 중인 전화에 관한 세부정보를 확인하고 전화를 제어하도록 허용합니다."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"셀 브로드캐스트 메시지 읽기"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"앱이 기기가 수신한 셀 브로드캐스트 메시지를 읽을 수 있도록 합니다. 비상 상황임을 알리기 위해 일부 지역에서 셀 브로드캐스트 경고가 전달됩니다. 비상 셀 브로드캐스트를 수신할 때 악성 앱이 기기의 성능이나 작동을 방해할 수 있습니다."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"가입된 피드 읽기"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"앱에서 \'systemExempted\' 유형의 포그라운드 서비스를 사용하도록 허용합니다."</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\'fileManagement\' 유형의 포그라운드 서비스 실행"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"앱에서 \'fileManagement\' 유형의 포그라운드 서비스를 사용하도록 허용합니다."</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"\'mediaProcessing\' 유형의 포그라운드 서비스 실행"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"앱에서 \'mediaProcessing\' 유형의 포그라운드 서비스를 사용하도록 허용합니다."</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\'specialUse\' 유형의 포그라운드 서비스 실행"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"앱에서 \'specialUse\' 유형의 포그라운드 서비스를 사용하도록 허용합니다."</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"앱 저장공간 계산"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"화면 잠금 사용"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"계속하려면 화면 잠금용 사용자 인증 정보를 입력하세요"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"센서를 세게 누르세요"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"지문을 인식할 수 없습니다. 다시 시도해 주세요."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"지문을 인식할 수 없습니다. 다시 시도해 주세요."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"지문 센서를 닦은 후 다시 시도해 보세요."</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"센서를 닦은 후 다시 시도해 보세요."</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"센서를 세게 누르세요"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"얼굴이 보이지 않습니다. 눈높이에 맞춰 휴대전화를 들어 주세요"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"너무 많이 움직였습니다. 휴대전화를 흔들리지 않게 잡으세요."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"얼굴을 다시 등록해 주세요."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"얼굴을 인식할 수 없습니다. 다시 시도해 주세요."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"얼굴을 인식할 수 없습니다. 다시 시도해 주세요."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"얼굴의 위치를 조금 변경해 주세요."</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"휴대전화를 좀 더 정면에서 바라보세요."</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"휴대전화를 좀 더 정면에서 바라보세요."</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index dcdfc80..b84dcf5 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Уюк жөнөтүүлөрүнүн билдирүүлөрү келген сайын башка номерге багыттап туруу үчүн колдонмого уюк жөнөтүүлөрүнүн модулу менен байланышууга уруксат берет. Шашылыш уюк жөнөтүүлөрү кээ бир жерлердеги өзгөчө кырдаалдар тууралуу сизге эскертүү үчүн жөнөтүлөт. Зыянкеч колдонмолор шашылыш уюк жөнөтүүлөрү кабыл алынганда түзмөктүн майнаптуулугуна же иштешине жолтоо болушу мүмкүн."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Учурдагы чалууларды башкаруу"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Колдонмого телефонуңузда аткарылып жаткан чалууларды көрүп, аларды көзөмөлдөөгө уруксат берет."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"уюктук берүү билдирүүлөрүн окуу"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Колдонмого түзмөгүңүз кабыл алган уюк берүүнүн билдирүүлөрүн окууга жол берет. Шашылыш эскертүү билдирүүлөрү кээ бир жерлердеги өзгөчө кырдаалдар тууралу сизди эскертүү үчүн жөнөтүлөт. Зыяндуу колдономолор шашылыш эскертүүлөр берилип жатканда, сиздин түзмөктүн иштешине жолтоо болушу мүмкүн."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"жазылган түрмөктөрдү окуу"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Колдонмолорго алдынкы пландагы \"systemExempted\" түрүндөгү кызматтарды колдонууга уруксат берет"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"алдынкы пландагы \"fileManagement\" түрүндөгү кызматты аткаруу"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Колдонмолорго алдынкы пландагы \"fileManagement\" түрүндөгү кызматтарды колдонууга уруксат берет"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"алдыңкы пландагы \"mediaProcessing\" түрүндөгү кызматты иштетүү"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Колдонмолорго алдыңкы пландагы \"mediaProcessing\" түрүндөгү кызматтарды колдонууга уруксат берет"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"алдынкы пландагы \"specialUse\" түрүндөгү кызматты аткаруу"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Колдонмолорго алдынкы пландагы \"specialUse\" түрүндөгү кызматтарды колдонууга уруксат берет"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"колдонмо сактагычынын мейкиндигин өлчөө"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Экран кулпусун колдонуу"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Улантуу үчүн экрандын кулпусун киргизиңиз"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Сенсорду катуу басыңыз"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Манжа изи таанылбай жатат. Кайра аракет кылыңыз."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Манжа изи таанылган жок. Кайра аракет кылыңыз."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Манжа изинин сенсорун тазалап, кайра аракет кылыңыз"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Сенсорду тазалап, кайра аракет кылыңыз"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Сенсорду катуу басыңыз"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Жүзүңүз көрүнбөй жатат. Телефонду маңдайыңызга кармаңыз."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Кыймылдап жибердиңиз. Телефонду түз кармаңыз."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Жүзүңүздү кайра таанытыңыз."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Жүз таанылбай жатат. Кайталаңыз."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Жүз таанылган жок. Кайра аракет кылыңыз."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Башыңызды бир аз буруңуз"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Телефонуңузду караңыз"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Телефонуңузду караңыз"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 49266a4..267f60d 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"ອະນຸຍາດໃຫ້ແອັບຜູກມັດກັບໂມດູນການກະຈາຍສັນຍານໂທລະສັບເພື່ອສົ່ງຕໍ່ຂໍ້ຄວາມການກະຈາຍສັນຍານໂທລະສັບເມື່ອໄດ້ຮັບມາ. ການເຕືອນການກະຈາຍສັນຍານໂທລະສັບແມ່ນຖືກຈັດສົ່ງໃນບາງສະຖານທີ່ເພື່ອເຕືອນທ່ານໃນກໍລະນີມີເຫດການສຸກເສີນເກີດຂຶ້ນ. ແອັບທີ່ເປັນອັນຕະລາຍອາດລົບກວນປະສິດທິພາບ ຫຼື ການເຮັດວຽກຂອງອຸປະກອນທ່ານເມື່ອໄດ້ຮັບການກະຈາຍສັນຍານໂທລະສັບສຸກເສີນ."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"ຈັດການສາຍໂທອອກ"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"ອະນຸຍາດໃຫ້ແອັບໃດໜຶ່ງເບິ່ງເຫັນລາຍລະອຽດກ່ຽວກັບສາຍໂທອອກຢູ່ອຸປະກອນຂອງທ່ານ ແລະ ເພື່ອຄວບຄຸມການໂທເຫຼົ່ານີ້."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"ອ່ານຂໍ້ຄວາມກະຈາຍສັນຍານຂອງເສົາສັນຍານ"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"ອະນຸຍາດໃຫ້ແອັບຯ ສາມາດອ່ານຂໍ້ຄວາມແຈ້ງເຕືອນເຫດສຸກເສີນ ທີ່ໄດ້ຮັບໂດຍອຸປະກອນຂອງທ່ານ. ການແຈ້ງເຕືອນສຸກເສີນທີ່ມີໃຫ້ບໍລິການໃນບາງພື້ນທີ່ ເພື່ອແຈ້ງເຕືອນໃຫ້ທ່ານຮູ້ເຖິງສະຖານະການສຸກເສີນ. ແອັບພລິເຄຊັນທີ່ເປັນອັນຕະລາຍອາດລົບກວນປະສິດທິພາບ ຫຼືການດຳເນີນງານຂອງອຸປະກອນຂອງທ່ານ ເມື່ອໄດ້ການຮັບແຈ້ງເຕືອນສຸກເສີນຈາກສະຖານີມືຖື."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"ອ່ານຂໍ້ມູນຟີດທີ່ສະໝັກໄວ້"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"ອະນຸຍາດໃຫ້ແອັບໃຊ້ປະໂຫຍດຈາກບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ໄດ້ຮັບການຍົກເວັ້ນຈາກລະບົບ\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"ເອີ້ນໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ການຈັດການໄຟລ໌\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"ອະນຸຍາດໃຫ້ແອັບໃຊ້ປະໂຫຍດຈາກບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ການຈັດການໄຟລ໌\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"ເອີ້ນໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"ອະນຸຍາດໃຫ້ແອັບໃຊ້ປະໂຫຍດຈາກບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"ເອີ້ນໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ການນຳໃຊ້ພິເສດ\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"ອະນຸຍາດໃຫ້ແອັບໃຊ້ປະໂຫຍດຈາກບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍມີປະເພດເປັນ \"ການນຳໃຊ້ພິເສດ\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"ກວດສອບພື້ນທີ່ຈັດເກັບຂໍ້ມູນແອັບຯ"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"ໃຊ້ການລັອກໜ້າຈໍ"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"ໃສ່ການລັອກໜ້າຈໍຂອງທ່ານເພື່ອສືບຕໍ່"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"ກົດຢູ່ເຊັນເຊີໃຫ້ແໜ້ນ"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"ບໍ່ສາມາດຈຳແນກລາຍນິ້ວມືໄດ້. ກະລຸນາລອງໃໝ່."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"ບໍ່ສາມາດຈຳແນກລາຍນິ້ວມືໄດ້. ລອງໃໝ່."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"ໃຫ້ອະນາໄມເຊັນ​ເຊີລາຍນິ້ວ​ມືແລ້ວລອງໃໝ່"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"ໃຫ້ອະນາໄມເຊັນ​ເຊີແລ້ວລອງໃໝ່"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"ກົດຢູ່ເຊັນເຊີໃຫ້ແໜ້ນ"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"ບໍ່ເຫັນໃບໜ້າຂອງທ່ານ. ຖືໂທລະສັບຂອງທ່ານໄວ້ໃນລະດັບສາຍຕາ."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"ເຄື່ອນໄຫວຫຼາຍເກີນໄປ. ກະລຸນາຖືໂທລະສັບໄວ້ຊື່ໆ."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"ກະລຸນາລົງທະບຽນອຸປະກອນຂອງທ່ານອີກເທື່ອໜຶ່ງ."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"ບໍ່ສາມາດຈຳແນກໃບໜ້າໄດ້. ກະລຸນາລອງໃໝ່."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"ບໍ່ສາມາດຈຳແນກໜ້າໄດ້. ລອງໃໝ່."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"ປ່ຽນຕຳແໜ່ງຂອງຫົວທ່ານເລັກນ້ອຍ"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"ເບິ່ງຊື່ໆໄປຫາໂທລະສັບຂອງທ່ານໃຫ້ຫຼາຍຂຶ້ນ"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"ເບິ່ງຊື່ໆໄປຫາໂທລະສັບຂອງທ່ານໃຫ້ຫຼາຍຂຶ້ນ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index aefda2e..4696722 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -372,6 +372,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Programai leidžiama susaistyti transliacijos mobiliuoju modulį, kad būtų galima persiųsti mobiliuoju transliuojamus pranešimus, kai jie gaunami. Transliacijos mobiliuoju įspėjimai pristatomi kai kuriose vietovėse, kad būtų galima įspėti apie kritines situacijas. Kai gaunamas mobiliuoju transliuojamas pranešimas apie kritinę situaciją, kenkėjiškos programos gali trukdyti įrenginiui veikti ar jį naudoti."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Tvarkyti vykstančius skambučius"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Programai leidžiama peržiūrėti išsamią informaciją apie vykstančius skambučius įrenginyje ir valdyti šiuos skambučius."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"skaityti mobiliuoju transliuojamus pranešimus"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Programai leidžiama skaityti mobiliuoju transliuojamus pranešimus, gaunamus jūsų įrenginyje. Mobiliuoju transliuojami įspėjimai pristatomi kai kuriose vietose, kad įspėtų apie kritines situacijas. Kai gaunamas  mobiliuoju transliuojamas pranešimas apie kritinę situaciją, kenkėjiškos programos gali trukdyti įrenginiui veikti ar jį naudoti."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"skaityti prenumeruojamus tiekimus"</string>
@@ -436,6 +440,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Programai leidžiama naudoti priekinio plano paslaugas, kurių tipas „systemExempted“"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"Priekinio plano paslaugos, kurios tipas „fileManagement“, vykdymas"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Leidžiama programai naudoti priekinio plano paslaugas, kurių tipas „fileManagement“"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"vykdyti priekinio plano paslaugą, kurios tipas „mediaProcessing“"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Programai leidžiama naudoti priekinio plano paslaugas, kurių tipas „mediaProcessing“"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"paleisti priekinio plano paslaugą, kurios tipas „specialUse“"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Programai leidžiama naudoti priekinio plano paslaugas, kurių tipas „specialUse“"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"matuoti programos atmintinės vietą"</string>
@@ -634,7 +640,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Naudoti ekrano užraktą"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Jei norite tęsti, įveskite ekrano užraktą"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Stipriai paspauskite jutiklį"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Nepavyko atpažinti kontrolinio kodo. Bandykite dar kartą."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Piršto atspaudas neatpažintas. Bandykite dar kartą."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Nuvalykite kontrolinio kodo jutiklį ir bandykite dar kartą"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Nuvalykite jutiklį ir bandykite dar kartą"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Tvirtai paspauskite jutiklį"</string>
@@ -698,7 +704,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Nepavyko pamatyti jūsų veido. Laikykite telefoną akių lygyje."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Įrenginys per daug judinamas. Nejudink. telefono."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Užregistruokite veidą iš naujo."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Veidas neatpažintas. Bandykite dar kartą."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Veidas neatpažintas. Bandykite dar kartą."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Kaskart šiek tiek pakeiskite galvos poziciją"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Žiūrėkite tiesiai į telefoną"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Žiūrėkite tiesiai į telefoną"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index f17f02f..79e82cc 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -371,6 +371,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Ļauj piesaistīt lietotni šūnu apraides modulim, lai pārsūtītu šūnu apraides ziņojumus, tiklīdz tie tiek saņemti. Šūnu apraides brīdinājumi tiek piegādāti noteiktās atrašanās vietās, lai brīdinātu jūs par ārkārtas situācijām. Ļaunprātīgas lietotnes var traucēt ierīces veiktspēju vai darbības, kad ir saņemts ārkārtas šūnas apraides ziņojums."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Aktīvo zvanu pārvaldība"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Ļauj lietotnei skatīt detalizētu informāciju par aktīvajiem zvaniem jūsu ierīcē, kā arī kontrolēt šos zvanus."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"šūnu apraides ziņojumu lasīšana"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Ļauj lietotnei lasīt ierīcē saņemtos šūnu apraides ziņojumus. Šūnu apraides brīdinājumi tiek piegādāti dažās atrašanās vietās, lai brīdinātu jūs par ārkārtas situācijām. Ļaunprātīgas lietotnes var traucēt ierīces veiktspēju vai darbības, kad ir saņemts ārkārtas šūnas apraides ziņojums."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"lasīt abonētās plūsmas"</string>
@@ -435,6 +439,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Ļauj lietotnei izmantot šāda veida priekšplāna pakalpojumus: systemExempted"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"izpildīt šāda veida priekšplāna pakalpojumu: fileManagement"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Ļauj lietotnei izmantot šāda veida priekšplāna pakalpojumus: fileManagement"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"izpildīt šāda veida priekšplāna pakalpojumu: mediaProcessing"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Ļauj lietotnei izmantot šāda veida priekšplāna pakalpojumus: mediaProcessing"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"izpildīt šāda veida priekšplāna pakalpojumu: specialUse"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Ļauj lietotnei izmantot šāda veida priekšplāna pakalpojumus: specialUse"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"noteikt vietas apjomu lietotnes atmiņā"</string>
@@ -633,7 +639,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Ekrāna bloķēšanas metodes izmantošana"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Lai turpinātu, ievadiet ekrāna bloķēšanas informāciju"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Stingri spiediet pirkstu pie sensora"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Nevar atpazīt pirksta nospiedumu. Mēģiniet vēlreiz."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Pirksta nospiedums netika atpazīts. Mēģiniet vēlreiz."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Notīriet pirkstu nospiedumu sensoru un mēģiniet vēlreiz"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Notīriet sensoru un mēģiniet vēlreiz"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Stingri spiediet pirkstu pie sensora"</string>
@@ -697,7 +703,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Seja nav redzama. Turiet tālruni acu līmenī."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Pārāk daudz kustību. Nekustīgi turiet tālruni."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Lūdzu, atkārtoti reģistrējiet savu seju."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Nevar atpazīt seju. Mēģiniet vēlreiz."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Seja netika atpazīta. Mēģiniet vēlreiz."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Nedaudz mainiet galvas pozīciju."</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Skatieties tieši uz tālruni"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Skatieties tieši uz tālruni"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index dbcf11f..82cb889d 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Дозволува апликацијата да се врзе со модулот за мобилен пренос за да проследува пораки за мобилен пренос штом ќе се примат. Предупредувањата за мобилно емитување се доставуваат на некои локации за да ве предупредат на итни ситуации. Злонамерните апликации може да пречат во ефикасноста или работењето на вашиот уред кога се прима емитување за итен случај."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Управување со тековни повици"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Дозволува апликацијата да гледа детали за тековните повици на уредот и да ги контролира овие повици."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"прочитај пораки за мобилно емитување"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Овозможува апликациите да ги читаат пораките за мобилно емитување што ги прима вашиот уред. Предупредувањата за мобилно емитување се доставуваат на некои локации, за да ве предупредат на итни ситуации. Злонамерните апликации може да пречат во ефикасноста или работењето на вашиот уред кога се прима емитување за итен случај."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"читај претплатени навестувања на содржина"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Дозволува апликацијата да ги користи во преден план услугите со типот „systemExempted“"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"Извршување услуга во преден план со типот „fileManagement“"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Дозволува апликацијата да ги користи услугите во преден план со типот „fileManagement“"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"извршување услуга во преден план со типот „mediaProcessing“"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Дозволува апликацијата да ги користи услугите во преден план со типот „mediaProcessing“"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"да извршува во преден план услуга со типот „specialUse“"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Дозволува апликацијата да ги користи во преден план услугите со типот „specialUse“"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"измери простор за складирање на апликацијата"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Користи заклучување екран"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Внесете го заклучувањето на екранот за да продолжите"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Цврсто притиснете на сензорот"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Не се препознава отпечатокот. Обидете се повторно."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Отпечатокот не е препознаен. Обидете се повторно."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Исчистете го сензорот за отпечатоци и обидете се повторно"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Исчистете го сензорот и обидете се повторно"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Цврсто притиснете на сензорот"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Не се гледа ликот. Држете го телефонот во висина на очите."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Премногу движење. Држете го телефонот стабилно."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Повторно регистрирајте го лицето."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Не се препознава ликот. Обидете се пак."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Ликот не е препознаен. Обидете се повторно."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Малку сменете ја положбата на главата"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Гледајте подиректно во телефонот"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Гледајте подиректно во телефонот"</string>
@@ -1961,7 +1967,7 @@
     <string name="locale_search_menu" msgid="6258090710176422934">"Пребарај"</string>
     <string name="app_suspended_title" msgid="888873445010322650">"Апликацијата не е достапна"</string>
     <string name="app_suspended_default_message" msgid="6451215678552004172">"Апликацијата <xliff:g id="APP_NAME_0">%1$s</xliff:g> не е достапна во моментов. Со ова управува <xliff:g id="APP_NAME_1">%2$s</xliff:g>."</string>
-    <string name="app_suspended_more_details" msgid="211260942831587014">"Дознај повеќе"</string>
+    <string name="app_suspended_more_details" msgid="211260942831587014">"Дознајте повеќе"</string>
     <string name="app_suspended_unsuspend_message" msgid="1665438589450555459">"Прекини ја паузата"</string>
     <string name="work_mode_off_title" msgid="6367463960165135829">"Да се актив. работните аплик.?"</string>
     <string name="work_mode_turn_on" msgid="5316648862401307800">"Прекини ја паузата"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 68c0749..2bea784 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"സ്വീകരിക്കുന്ന മുറയ്ക്ക് ബ്രോഡ്‌കാസ്‌റ്റ് സന്ദേശങ്ങൾ കൈമാറുന്നതിന് സെൽ ബ്രോഡ്‌കാസ്‌റ്റ് മോഡ്യൂളിലേക്ക് ബൈൻഡ് ചെയ്യാൻ ആപ്പിനെ അനുവദിക്കുന്നു. അടിയന്തര സാഹചര്യങ്ങളെ കുറിച്ച് നിങ്ങൾക്ക് മുന്നറിയിപ്പ് നൽകുന്നതിന് ചില ലൊക്കേഷനുകളിൽ സെൽ ബ്രോഡ്‌കാസ്‌റ്റ് അലേർട്ടുകൾ ഡെലിവറി ചെയ്യപ്പെടുന്നു. ഒരു അടിയന്തര സെൽ ബ്രോഡ്‌കാസ്റ്റ് ലഭിക്കുമ്പോൾ ക്ഷുദ്രകരമായ അപ്ലിക്കേഷനുകൾ നിങ്ങളുടെ ഉപകരണത്തിന്റെ പ്രകടനത്തെയോ പ്രവർത്തനത്തെയോ തടസപ്പെടുത്താനിടയുണ്ട്."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"നടന്നുകൊണ്ടിരിക്കുന്ന കോളുകൾ മാനേജ് ചെയ്യുക"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"നിങ്ങളുടെ ഉപകരണത്തിൽ നടന്നുകൊണ്ടിരിക്കുന്ന കോളുകളുടെ വിശദാംശങ്ങൾ കാണാനും ഈ കോളുകൾ നിയന്ത്രിക്കാനും ആപ്പിനെ അനുവദിക്കുന്നു."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"സെൽ പ്രക്ഷേപണ സന്ദേശങ്ങൾ റീഡുചെയ്യുക"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"നിങ്ങളുടെ ഉപകരണത്തിൽ ലഭിച്ച സെൽ പ്രക്ഷേപണ സന്ദേശങ്ങൾ റീഡുചെയ്യാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു. അടിയന്തര സാഹചര്യങ്ങളെക്കുറിച്ച് നിങ്ങൾക്ക് മുന്നറിയിപ്പ് നൽകാനായി ചില ലൊക്കേഷനുകളിൽ നൽകപ്പെടുന്നവയാണ് സെൽ പ്രക്ഷേപണ അലേർട്ടുകൾ. ഒരു അടിയന്തര സെൽ പ്രക്ഷേപണം ലഭിക്കുമ്പോൾ, ക്ഷുദ്രകരമായ അപ്ലിക്കേഷനുകൾ നിങ്ങളുടെ ഉപകരണത്തിന്റെ പ്രകടനമോ പ്രവർത്തനമോ തടസ്സപ്പെടുത്താനിടയുണ്ട്."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"സബ്‌സ്ക്രൈബ് ചെയ്‌ത ഫീഡുകൾ വായിക്കുക"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ പ്രയോജനപ്പെടുത്താൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" എന്ന തരത്തിലുള്ള ഫോർഗ്രൗണ്ട് സേവനം റൺ ചെയ്യുക"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"\"fileManagement\" എന്ന തരത്തിലുള്ള ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ പ്രയോജനപ്പെടുത്താൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"\"mediaProcessing\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനം റൺ ചെയ്യുക"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"\"mediaProcessing\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ പ്രയോജനപ്പെടുത്താൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനം റൺ ചെയ്യുക"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" എന്ന തരം ഉപയോഗിച്ച് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ പ്രയോജനപ്പെടുത്താൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"അപ്ലിക്കേഷൻ സംഭരണയിടം അളക്കുക"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"സ്‌ക്രീൻ ലോക്ക് ഉപയോഗിക്കുക"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"തുടരാൻ നിങ്ങളുടെ സ്‌ക്രീൻ ലോക്ക് നൽകുക"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"സെൻസറിൽ നന്നായി അമർത്തുക"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"ഫിംഗർപ്രിന്റ് തിരിച്ചറിയാനാകുന്നില്ല. വീണ്ടും ശ്രമിക്കുക."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"ഫിംഗർപ്രിന്റ് തിരിച്ചറിഞ്ഞിട്ടില്ല. വീണ്ടും ശ്രമിക്കൂ."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"ഫിംഗർപ്രിന്റ് സെൻസർ വൃത്തിയാക്കിയ ശേഷം വീണ്ടും ശ്രമിക്കുക"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"സെൻസർ വൃത്തിയാക്കിയ ശേഷം വീണ്ടും ശ്രമിക്കുക"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"സെൻസറിൽ നന്നായി അമർത്തുക"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"മുഖം കാണുന്നില്ല. ഫോൺ കണ്ണിന് നേരെ പിടിക്കുക."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"വളരെയധികം ചലനം. ഫോൺ അനക്കാതെ നേരെ പിടിക്കുക."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"നിങ്ങളുടെ മുഖം വീണ്ടും എൻറോൾ ചെയ്യുക."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"മുഖം തിരിച്ചറിയാനാകുന്നില്ല. വീണ്ടും ശ്രമിക്കൂ."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"മുഖം തിരിച്ചറിഞ്ഞിട്ടില്ല. വീണ്ടും ശ്രമിക്കൂ."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"നിങ്ങളുടെ തലയുടെ സ്ഥാനം ചെറുതായി മാറ്റുക"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"കൂടുതൽ കൃത്യമായി ഫോണിന് നേരെ നോക്കുക"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"കൂടുതൽ കൃത്യമായി ഫോണിന് നേരെ നോക്കുക"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 97858ef2..5d8f143 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Гар утсанд масс мессеж түгээх онцлогийн мессежийг хүлээн авах үед түүнийг шилжүүлэх зорилгоор аппад гар утсанд масс мессеж түгээх модультай холбогдохыг зөвшөөрөх Гар утсанд масс мессеж түгээх онцлогийн сэрэмжлүүлэг нь онцгой нөхцөл байдлын тухай танд анхааруулахын тулд зарим байршилд хүрдэг. Гар утсанд масс мессеж түгээх онцлогийн илгээх онцгой нөхцөл байдлын тухай мессежийг хүлээн авах үед хортой апп таны төхөөрөмжийн гүйцэтгэл эсвэл ажиллагаанд саад учруулж болзошгүй."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Үргэлжилж буй дуудлагыг удирдах"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Аппад таны төхөөрөмж дээрх үргэлжилж буй дуудлагын талаарх дэлгэрэнгүйг харах болон эдгээр дуудлагыг хянахыг зөвшөөрнө."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"үүрэн өргөн дамжууллын мессеж унших"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Апп нь таны төхөөрөмжийн хүлээн авсан үүрэн өргөн дамжуулах мессежийг унших боломжтой. Үүрэн өргөн дамжууллын мэдэгдэл нь яаралтай нөхцөл байдлыг анхааруулах зорилгоор зарим байршлуудад хүрдэг. Хортой апп нь яаралтай үүрэн өргөн дамжууллыг хүлээн авсан үед таны төхөөрөмжийн ажиллагаа болон чадамжид нөлөөлөх боломжтой."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"бүртгүүлсэн хангамжийг унших"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Аппад \"systemExempted\" төрөлтэй нүүрэн талын үйлчилгээнүүдийг ашиглахыг зөвшөөрнө"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"FileManagement\" төрөлтэй нүүрэн талын үйлчилгээг ажиллуулах"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Аппад \"fileManagement\" төрөлтэй нүүрэн талын үйлчилгээнүүдийг ашиглахыг зөвшөөрнө"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"\"mediaProcessing\" төрөлтэй нүүрэн талын үйлчилгээг ажиллуулах"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Аппад \"mediaProcessing\" төрөлтэй нүүрэн талын үйлчилгээнүүдийг ашиглахыг зөвшөөрнө"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"SpecialUse\" төрөлтэй нүүрэн талын үйлчилгээг ажиллуулах"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Аппад \"specialUse\" төрөлтэй нүүрэн талын үйлчилгээнүүдийг ашиглахыг зөвшөөрнө"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"апп сангийн хэмжээг хэмжих"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Дэлгэцийн түгжээг ашиглах"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Үргэлжлүүлэхийн тулд дэлгэцийн түгжээгээ оруулна уу"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Мэдрэгч дээр чанга дарна уу"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Хурууны хээг таних боломжгүй. Дахин оролдоно уу."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Хурууны хээг таньсангүй. Дахин оролдоно уу."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Хурууны хээ мэдрэгчийг цэвэрлээд, дахин оролдоно уу"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Мэдрэгчийг цэвэрлээд, дахин оролдоно уу"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Мэдрэгч дээр чанга дарна уу"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Таны царай харагдахгүй байна. Утсаа нүднийхээ түвшинд барина уу."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Хэт их хөдөлгөөнтэй байна. Утсаа хөдөлгөөнгүй барина уу."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Нүүрээ дахин бүртгүүлнэ үү."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Царайг танихгүй байна. Дахин оролдоно уу."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Царайг таньсангүй. Дахин оролдоно уу."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Толгойныхоо байрлалыг бага зэрэг өөрчилнө үү"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Утас руугаа аль болох эгц харна уу"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Утас руугаа аль болох эгц харна уу"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index cd553a4..6654a14 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"सेल प्रसारण मेसेज मिळाल्यानंतर ते फॉरवर्ड करण्यासाठी ॲपला सेल प्रसारण मॉड्यूलमध्ये प्रतिबद्ध करण्याची अनुमती देते. काही स्थानांमध्ये तुम्हाला आणीबाणीच्या परिस्थीतींची चेतावणी देण्यासाठी सेल प्रसारण सूचना वितरित केल्या जातात. दुर्भावनापूर्ण अ‍ॅप्स आणीबाणी सेल प्रसारण मिळवतात तेव्हा ती तुमच्या डिव्हाइसच्या परफॉर्मन्समध्ये किंवा कामामध्ये कदाचित व्यत्यय आणू शकतात."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"सुरू असलेले कॉल व्यवस्थापित करा"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"ॲपला तुमच्या डिव्हाइसवर सुरू असलेल्या कॉलचे तपशील पाहण्याची आणि या कॉलना नियंत्रित करण्याची अनुमती द्या."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"सेल प्रसारण मेसेज वाचा"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"आपल्या डिव्हाइसद्वारे प्राप्त केलेले सेल प्रसारण मेसेज वाचण्यासाठी अ‍ॅप ला अनुमती देते. काही स्थानांमध्ये तुम्हाला आणीबाणीच्या परिस्थितीची चेतावणी देण्यासाठी सेल प्रसारण सूचना वितरीत केल्या जातात. आणीबाणी सेल प्रसारण प्राप्त होते तेव्हा आपल्या डिव्हाइसच्या कार्यप्रदर्शनात किंवा कार्यात दुर्भावनापूर्ण अ‍ॅप्स व्यत्यय आणू शकतात."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"सदस्यत्व घेतलेली फीड वाचा"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" प्रकारासोबत अ‍ॅपला फोरग्राउंड सेवांचा वापर करण्याची अनुमती देते"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" प्रकारासोबत फोरग्राउंड सेवा रन करा"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"\"fileManagement\" प्रकारासोबत अ‍ॅपला फोरग्राउंड सेवांचा वापर करण्याची अनुमती देते"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"\"mediaProcessing\" प्रकारासह फोरग्राउंड सेवा रन करा"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"अ‍ॅपला \"mediaProcessing\" प्रकारासह फोरग्राउंड सेवा वापरण्याची अनुमती देते"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" प्रकारासोबत फोरग्राउंड सेवा रन करा"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" प्रकारासोबत अ‍ॅपला फोरग्राउंड सेवांचा वापर करण्याची अनुमती देते"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"अ‍ॅप संचयन स्थान मोजा"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"स्क्रीन लॉक वापरा"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"पुढे सुरू ठेवण्यासाठी तुमचे स्क्रीन लॉक एंटर करा"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"सेन्सरवर जोरात प्रेस करा"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"फिंगरप्रिंट ओळखता आली नाही. पुन्हा प्रयत्न करा."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"फिंगरप्रिंट ओळखता आली नाही. पुन्हा प्रयत्न करा."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"फिंगरप्रिंट सेन्सर स्वच्छ करा आणि पुन्हा प्रयत्न करा"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"सेन्सर स्वच्छ करा आणि पुन्हा प्रयत्न करा"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"सेन्सरवर जोरात प्रेस करा"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"तुमचा चेहरा दिसत नाही. तुमचा फोन डोळ्याच्या पातळीवर धरा."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"खूप हलत आहे. फोन स्थिर धरून ठेवा."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"कृपया तुमच्या चेहऱ्याची पुन्हा नोंदणी करा."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"चेहरा ओळखू शकत नाही. पुन्हा प्रयत्न करा."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"चेहरा ओळखता आला नाही. पुन्हा प्रयत्न करा."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"तुमच्या डोक्याचे स्थान किंचित बदला"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"तुमच्या फोनकडे आणखी थेट पहा"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"तुमच्या फोनकडे आणखी थेट पहा"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 90ffb21..d51dafe 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Membenarkan apl terikat pada modul siaran sel untuk mengirim semula mesej siaran sel apabila diterima. Makluman siaran sel dihantar di sesetengah lokasi untuk memberi amaran kepada anda tentang situasi kecemasan. Apl hasad boleh mengganggu prestasi atau operasi peranti anda apabila siaran sel kecemasan diterima."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Urus panggilan yang sedang berjalan"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Benarkan apl melihat butiran panggilan yang sedang berjalan pada peranti anda dan mengawal panggilan ini."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"baca mesej siaran sel"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Membolehkan apl membaca mesej siaran sel yang diterima oleh peranti anda. Isyarat siaran sel dihantar di beberapa lokasi untuk memberi amaran kepada anda tentang situasi kecemasan. Apl hasad boleh mengganggu prestasi atau operasi peranti anda apabila siaran sel kecemasan diterima."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"baca suapan langganan"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Membenarkan apl menggunakan perkhidmatan latar depan dengan jenis \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"jalankan perkhidmatan latar depan dengan jenis \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Benarkan apl menggunakan perkhidmatan latar depan dengan jenis \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"jalankan perkhidmatan latar depan dengan jenis \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Membenarkan apl menggunakan perkhidmatan latar depan dengan jenis \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"jalankan perkhidmatan latar depan dengan jenis \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Membenarkan apl menggunakan perkhidmatan latar depan dengan jenis \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"ukur ruang storan apl"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Gunakan kunci skrin"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Masukkan kunci skrin untuk teruskan"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Tekan penderia dengan kuat"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Tidak dapat mengecam cap jari. Cuba lagi."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Cap jari tidak dikenali. Cuba lagi."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Bersihkan penderia cap jari dan cuba lagi"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Bersihkan penderia dan cuba lagi"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Tekan penderia dengan kuat"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Wajah tidak kelihatan. Pegang telefon pada paras mata."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Terlalu bnyk gerakan. Pegang telefon dgn stabil."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Sila daftarkan semula wajah anda."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Tidak dapat mengecam wajah. Cuba lagi."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Wajah tidak dikenali. Cuba lagi."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Tukar sedikit kedudukan kepala anda"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Lihat lebih lurus pada telefon"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Lihat lebih lurus pada telefon"</string>
@@ -1016,7 +1022,7 @@
     <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"Lupa corak?"</string>
     <string name="lockscreen_glogin_forgot_pattern" msgid="9218940117797602518">"Buka kunci akaun"</string>
     <string name="lockscreen_glogin_too_many_attempts" msgid="3775904917743034195">"Terlalu banyak percubaan melukis corak"</string>
-    <string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"Untuk membuka kunci, log masuk dengan akaun Google anda."</string>
+    <string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"Untuk membuka kunci, log masuk dengan Google Account anda."</string>
     <string name="lockscreen_glogin_username_hint" msgid="6916101478673157045">"Nama Pengguna (E-mel)"</string>
     <string name="lockscreen_glogin_password_hint" msgid="3031027901286812848">"Kata laluan"</string>
     <string name="lockscreen_glogin_submit_button" msgid="3590556636347843733">"Log masuk"</string>
@@ -1667,7 +1673,7 @@
     <string name="kg_invalid_puk" msgid="4809502818518963344">"Masukkan semula kod PIN yang betul. Percubaan berulang akan melumpuhkan SIM secara kekal."</string>
     <string name="kg_invalid_confirm_pin_hint" product="default" msgid="4705368340409816254">"Kod PIN tidak sepadan"</string>
     <string name="kg_login_too_many_attempts" msgid="699292728290654121">"Terlalu banyak percubaan melukis corak"</string>
-    <string name="kg_login_instructions" msgid="3619844310339066827">"Untuk membuka kunci, log masuk dengan akaun Google anda."</string>
+    <string name="kg_login_instructions" msgid="3619844310339066827">"Untuk membuka kunci, log masuk dengan Google Account anda."</string>
     <string name="kg_login_username_hint" msgid="1765453775467133251">"Nama Pengguna (E-mel)"</string>
     <string name="kg_login_password_hint" msgid="3330530727273164402">"Kata laluan"</string>
     <string name="kg_login_submit_button" msgid="893611277617096870">"Log masuk"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index fa5efa6..a9726a5 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"စာတို ဖြန့်ဝေခြင်းစနစ် မော်ဂျူးကိုပေါင်းရန် အက်ပ်များအား ခွင့်ပြုသည်။ ၎င်းမှာ စာတို ဖြန့်ဝေခြင်းစနစ်သုံး မက်ဆေ့ဂျ်များကို လက်ခံရရှိသည့်အတိုင်း ထပ်ဆင့်ပို့ရန် ဖြစ်သည်။ အချို့တည်နေရာများတွင် သင့်အား အရေးပေါ်အခြေအနေများကို သတိပေးရန် စာတို ဖြန့်ဝေခြင်းစနစ်သုံး သတိပေးချက်များကို ပေးပို့သည်။ အရေးပေါ် စာတို ဖြန့်ဝေခြင်းကို ရရှိသည့်အခါ သံသယဖြစ်နိုင်ဖွယ်ရှိသည့် အက်ပ်များက သင့်စက်၏ စွမ်းဆောင်ရည်နှင့် အော်ပရေးရှင်းတို့ကို အနှောင့်အယှက်ပေးနိုင်သည်။"</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"လက်ရှိခေါ်ဆိုမှုများကို စီမံခြင်း"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"သင့်စက်ပစ္စည်းပေါ်ရှိ လက်ရှိခေါ်ဆိုမှုများအကြောင်း အသေးစိတ်များကို ကြည့်ရှုရန်နှင့် ဤခေါ်ဆိုမှုများကို ထိန်းချုပ်ရန် အက်ပ်အား ခွင့်ပြုသည်။"</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"စာတိုများ ဖြန့်ဝေခြင်းစနစ်အား ဖတ်ခြင်း"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"အပလီကေးရှင်းကို သင်၏ စက်ပစ္စည်းမှ လက်ခံရရှိသော အများလွှင့်ထုတ်ချက်များကို ဖတ်ရန် ခွင့်ပြုသည်။  အများလွှင့်ထုတ်ချက်များသည် အရေးပေါ်အခြေအနေများကို သင့်အား သတိပေးရန် အချို့ နေရာများတွင် ပို့ပေးသည်။ အရေးပေါ်သတိပေးချက် ထုတ်လွှင့်ချက်ကို လက်ခံရရှိချိန်တွင်အန္တရာယ် ဖြစ်စေနိုင်သော အပလီကေးရှင်းများသည် သင့်စက်ပစ္စည်း၏ လုပ်ငန်းလည်ပတ်မှုနှင့် စွမ်းဆောင်မှုကို ဝင်စွက်ဖက်နိုင်သည်။"</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"အမည်သွင်းထားသောဖိဖ့်များကို ဖတ်ခြင်း"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှုများအား အကျိုးရှိရှိ အသုံးပြုနိုင်ရန် အက်ပ်ကို ခွင့်ပြုသည်"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"“fileManagement” အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှု လုပ်ဆောင်ခြင်း"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"“\"fileManagement” အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှုများအား အကျိုးရှိရှိ အသုံးပြုနိုင်ရန် အက်ပ်ကို ခွင့်ပြုသည်"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"“mediaProcessing” အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှု လုပ်ဆောင်ခြင်း"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"“mediaProcessing” အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှုများအား အကျိုးရှိရှိ အသုံးပြုနိုင်ရန် အက်ပ်ကို ခွင့်ပြုသည်"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှု လုပ်ဆောင်ခြင်း"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" အမျိုးအစား မျက်နှာစာဝန်ဆောင်မှုများအား အကျိုးရှိရှိ အသုံးပြုနိုင်ရန် အက်ပ်ကို ခွင့်ပြုသည်"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"အက်ပ်သိုလ​ှောင်မှု နေရာကို တိုင်းထွာခြင်း"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"ဖန်သားပြင်လော့ခ်ချခြင်းကို သုံးခြင်း"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"ရှေ့ဆက်ရန် သင်၏ဖန်သားပြင် လော့ခ်ချခြင်းကို ထည့်ပါ"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"အာရုံခံကိရိယာပေါ်တွင် သေချာဖိပါ"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"လက်ဗွေကို မမှတ်မိပါ။ ထပ်စမ်းကြည့်ပါ။"</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"လက်ဗွေကို မသိရှိပါ။ ထပ်စမ်းကြည့်ပါ။"</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"လက်ဗွေ အာရုံခံကိရိယာကို သန့်ရှင်းပြီး ထပ်စမ်းကြည့်ပါ"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"အာရုံခံကိရိယာကို သန့်ရှင်းပြီး ထပ်စမ်းကြည့်ပါ"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"အာရုံခံကိရိယာပေါ်တွင် သေချာဖိပါ"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"သင့်မျက်နှာ မမြင်ရပါ။ ဖုန်းနှင့် မျက်စိ တစ်တန်းတည်းထားပါ။"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"လှုပ်လွန်းသည်။ ဖုန်းကို ငြိမ်ငြိမ်ကိုင်ပါ။"</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"သင့်မျက်နှာကို ပြန်စာရင်းသွင်းပါ။"</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"မျက်နှာကို မသိပါ။ ထပ်စမ်းကြည့်ပါ။"</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"မျက်နှာကို မသိရှိပါ။ ထပ်စမ်းကြည့်ပါ။"</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"ခေါင်းအနေအထားကို အနည်းငယ်ပြောင်းပါ"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"သင့်ဖုန်းကို တည့်တည့်ကြည့်ပါ"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"သင့်ဖုန်းကို တည့်တည့်ကြည့်ပါ"</string>
@@ -800,7 +806,7 @@
     <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"အစမြင်ကွင်း ခွင့်ပြုချက် အသုံးပြုမှု"</string>
     <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"အက်ပ်တစ်ခုအတွက် ခွင့်ပြုချက်စတင်အသုံးပြုမှုကို ကိုင်ဆောင်သူအား ခွင့်ပြုသည်။ ပုံမှန်အက်ပ်များအတွက် ဘယ်သောအခါမျှ မလိုအပ်ပါ။"</string>
     <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"ခွင့်ပြုသည့် ဆုံးဖြတ်ချက်များကို စတင်ကြည့်ခြင်း"</string>
-    <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ခွင့်ပြုထားသည့်အက်ပ်အား ခွင့်ပြုသည့်ဆုံးဖြတ်ချက်များကို ကြည့်နိုင်ရန်အတွက် စခရင်စတင်ရန် ခွင့်ပြုနိုင်သည်။ သာမန်အက်ပ်များအတွက် မည်သည့်အခါမျှ မလိုအပ်နိုင်ပါ။"</string>
+    <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ခွင့်ပြုထားသည့်အက်ပ်အား ခွင့်ပြုသည့်ဆုံးဖြတ်ချက်များကို ကြည့်နိုင်ရန်အတွက် စခရင်စတင်ရန် ခွင့်ပြုနိုင်သည်။ သာမန်အက်ပ်များအတွက် မည်သည့်အခါမှ မလိုအပ်နိုင်ပါ။"</string>
     <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"အက်ပ်ဝန်ဆောင်မှုများကို စတင်ကြည့်ခြင်း"</string>
     <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ဝန်ဆောင်မှုအချက်အလက်ကိုများကို ခွင့်ပြုချက်ရထားသည့် အက်ပ်အား စတင်ကြည့်နိုင်ရန် ခွင့်ပြုသည်။"</string>
     <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"နမူနာနှုန်းမြင့်သော အာရုံခံစနစ်ဒေတာကို သုံးပါ"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index f97e437..822218d1 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Tillat at denne appen binder seg til modulen for kringkastede meldinger for å videresende kringkastede meldinger når de mottas. Kringkastede varsler leveres noen steder for å advare deg om nødssituasjoner. Skadelige apper kan forstyrre ytelsen eller funksjonen til enheten din når en kringkastet nødmelding mottas."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Administrer pågående anrop"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Gir en app tillatelse til å se informasjon om pågående anrop på enheten og til å kontrollere disse anropene."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"lese kringkastede meldinger"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Tillater at appen kan lese kringkastede meldinger enheten din mottar. Kringkastede varsler leveres noen steder for å advare deg om nødssituasjoner. Skadelige apper kan forstyrre ytelsen eller funksjonen til enheten din når en kringkastet nødmelding mottas."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"lese abonnement på nyhetskilder"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Lar appen bruke forgrunnstjenester med typen «systemExempted»"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"kjør forgrunnstjeneste med typen «fileManagement»"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Lar appen bruke forgrunnstjenester med typen «fileManagement»"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"kjør forgrunnstjeneste med typen «mediaProcessing»"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Lar appen bruke forgrunnstjenester med typen «mediaProcessing»"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"kjøre forgrunnstjeneste med typen «specialUse»"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Lar appen bruke forgrunnstjenester med typen «specialUse»"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"måle lagringsplass for apper"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Bruk skjermlås"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Skriv inn skjermlåsen for å fortsette"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Trykk godt på sensoren"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Fingeravtrykket gjenkjennes ikke. Prøv på nytt."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Fingeravtrykket ble ikke gjenkjent. Prøv på nytt."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Rengjør fingeravtrykkssensoren og prøv igjen"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Rengjør sensoren og prøv igjen"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Trykk godt på sensoren"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Kan ikke se ansiktet ditt. Hold telefonen i øyehøyde."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"For mye bevegelse. Hold telefonen stødig."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Registrer ansiktet ditt på nytt."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Ansiktet gjenkjennes ikke. Prøv på nytt."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Ansiktet ble ikke gjenkjent. Prøv på nytt."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Endre hodeposisjonen litt"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Se mer direkte på telefonen"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Se mer direkte på telefonen"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 99dcae0..de85e5c 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"मोबाइल प्रसारणसम्बन्धी म्यासेजहरू प्राप्त हुनासाथै तिनीहरूलाई फर्वार्ड गर्नका लागि यसले एपलाई मोबाइल प्रसारण मोड्युलमा जोडिने अनुमति दिन्छ। तपाईंलाई कतिपय स्थानमा आपत्‌कालीन अवस्थाका बारेमा जानकारी दिनका लागि मोबाइल प्रसारणसम्बन्धी अलर्टहरू पठाइन्छ। हानिकारक एपहरूले आपत्‌कालीन मोबाइल प्रसारण प्राप्त हुँदा तपाईंको यन्त्रलाई कार्य सम्पादन गर्ने वा सञ्चालित हुने क्रममा हस्तक्षेप गर्न सक्छन्।"</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"जारी रहेका कलहरू व्यवस्थापन गर्न"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"तपाईं यो एपलाई अनुमति दिनुभयो यस एपले तपाईंको डिभाइसमा जारी रहेका कलसम्बन्धी विवरण हेर्न र ती कलहरू नियन्त्रण गर्न सक्छ।"</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"सेल प्रसारित म्यासेजहरू पढ्नुहोस्"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"तपाईंको उपकरणद्वारा प्राप्त सेल प्रसारण म्यासेजहरू एपलाई पढ्न अनुमति दिन्छ। सेल प्रसारण चेतावनीहरू केही स्थानहरूमा तपाईंलाई आपत्‌कालीन गतिविधिहरूको बारेमा सचेत गराउन गरिएका छन्। खराब एपहरूले एउटा आपत्‌कालीन सेल प्रसारण प्राप्त गर्दछ जब तपाईंको उपकरणको प्रदर्शन वा अपरेशनको साथ हस्तक्षेप गर्न सक्दछन्।"</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"सदस्य बनाइका फिडहरू पढ्नुहोस्"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"यसले एपलाई \"systemExempted\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिन्छ"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"यस प्रकारको \"fileManagement\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू चलाउने अनुमति"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"यसले यो एपलाई यस प्रकारको \"fileManagement\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिन्छ"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"यस प्रकारको \"mediaProcessing\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू चलाउने अनुमति"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"यसले एपलाई यस प्रकारको \"mediaProcessing\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिन्छ"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिनुहोस्"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"यसले एपलाई \"specialUse\" सँग सम्बन्धित फोरग्राउन्ड सेवाहरू प्रयोग गर्ने अनुमति दिन्छ"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"एप भण्डारण ठाउँको मापन गर्नुहोस्"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"स्क्रिन लक प्रयोग गर्नुहोस्"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"जारी राख्न आफ्नो स्क्रिन लक हाल्नुहोस्"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"सेन्सरमा बेसरी थिच्नुहोस्"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"फिंगरप्रिन्ट पहिचान गर्न सकिएन। फेरि प्रयास गर्नुहोस्।"</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"फिंगरप्रिन्ट पहिचान गर्न सकिएन। फेरि प्रयास गर्नुहोस्।"</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"फिंगरप्रिन्ट सेन्सर सफा गरेर फेरि प्रयास गर्नुहोस्"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"सेन्सर सफा गरेर फेरि प्रयास गर्नुहोस्"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"सेन्सरमा बेसरी थिच्नुहोस्"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"तपाईंको अनुहार देखिएन। तपाईंको फोन आफ्नो आँखाअघि राखी समात्नुहोस्।"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"अत्यधिक हल्लियो। फोन स्थिर राख्नुहोस्।"</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"कृपया आफ्नो अनुहार पुनः दर्ता गर्नुहोस्।"</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"अनुहार पहिचान गर्न सकिएन। फेरि प्रयास गर्नुहोस्।"</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"अनुहार पहिचान गर्न सकिएन। फेरि प्रयास गर्नुहोस्।"</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"आफ्नो टाउको थोरै यताउता सार्नुहोस्"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"आफ्नो फोनमा अझ सीधा हेर्नुहोस्"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"आफ्नो फोनमा अझ सीधा हेर्नुहोस्"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index f0d34a5..72a7e68 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Hiermee kan de app de module voor cell broadcasts binden om cell broadcast-berichten door te sturen als die worden ontvangen. Cell broadcast-waarschuwingen worden op bepaalde locaties verzonden om je te waarschuwen voor noodsituaties. Schadelijke apps kunnen de prestaties of verwerking van je apparaat verstoren als een bericht met een noodmelding wordt ontvangen."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Actieve gesprekken beheren"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Hiermee kan een app informatie over actieve gesprekken op je apparaat bekijken en deze gesprekken beheren."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"infodienstberichten lezen"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Toestaan dat de app infodienstberichten leest die worden ontvangen op je apparaat. Infodienstberichten worden verzonden naar bepaalde locaties om u te waarschjeen voor noodsituaties. Schadelijke apps kunnen de prestaties of verwerking van je apparaat verstoren wanneer een infodienstbericht wordt ontvangen."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"Geabonneerde feeds lezen"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Hiermee kan de app gebruikmaken van services op de voorgrond van het type \'systemExempted\'"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"service op de voorgrond van het type \'fileManagement\' uitvoeren"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Hiermee kan de app gebruikmaken van services op de voorgrond van het type \'fileManagement\'."</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"service op de voorgrond van het type \'mediaProcessing\' uitvoeren"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Hiermee kan de app gebruikmaken van services op de voorgrond van het type \'mediaProcessing\'"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"service op de voorgrond van het type \'specialUse\' uitvoeren"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Hiermee kan de app gebruikmaken van services op de voorgrond van het type \'specialUse\'"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"opslagruimte van app meten"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Schermvergrendeling gebruiken"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Voer je schermvergrendeling in om door te gaan"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Druk stevig op de sensor"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Vingerafdruk niet herkend. Probeer het opnieuw."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Vingerafdruk niet herkend. Probeer het opnieuw."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Reinig de vingerafdruksensor en probeer het opnieuw"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Reinig de sensor en probeer het opnieuw"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Druk stevig op de sensor"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Je gezicht is niet te zien. Houd je telefoon op ooghoogte."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Te veel beweging. Houd je telefoon stil."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Registreer je gezicht opnieuw."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Gezicht niet herkend. Probeer het opnieuw."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Gezicht niet herkend. Probeer het opnieuw."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Verander de positie van je hoofd een beetje"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Kijk goed recht naar je telefoon"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Kijk goed recht naar je telefoon"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index bfde6c9..1575fd8 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"ସେଲ୍ ପ୍ରସାରଣ ମେସେଜ୍ ପ୍ରାପ୍ତ ହେବା ପରେ ସେଗୁଡ଼ିକୁ ଫର୍‍ୱାର୍ଡ କରିବା ପାଇଁ ଆପ୍‍କୁ ସେଲ୍ ପ୍ରସାରଣ ମଡ୍ୟୁଲ୍ ସହିତ ସଂଯୁକ୍ତ କରିବାକୁ ଅନୁମତି ଦିଏ। ଜରୁରୀକାଳୀନ ପରିସ୍ଥିତିରେ ଆପଣଙ୍କୁ ଚେତାବନୀ ଦେବା ପାଇଁ କିଛି ଲୋକେସନ୍‍‍ରେ ସେଲ୍ ପ୍ରସାରଣ ଆଲର୍ଟ ବିତରଣ କରାଯାଇଥାଏ। ଏକ ଜରୁରୀକାଳୀନ ସେଲ୍ ପ୍ରସାରଣ ପ୍ରାପ୍ତ ହେବା ସମୟରେ କିଛି କ୍ଷତିକାରକ ଆପ୍ସ ହୁଏତ ଆପଣଙ୍କର ଡିଭାଇସ୍‍ର କାର୍ଯ୍ୟଦକ୍ଷତା କିମ୍ବା କାର୍ଯ୍ୟ ପ୍ରକ୍ରିୟାରେ ହସ୍ତକ୍ଷେପ କରିପାରେ।"</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"ଚାଲୁଥିବା କଲଗୁଡ଼ିକୁ ପରିଚାଳନା କରନ୍ତୁ"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"ଏକ ଆପକୁ ଆପଣଙ୍କ ଡିଭାଇସରେ ଚାଲୁଥିବା କଲଗୁଡ଼ିକର ବିବରଣୀ ଦେଖିବା ଓ ଏହି କଲଗୁଡ଼ିକୁ ନିୟନ୍ତ୍ରଣ କରିବା ପାଇଁ ଅନୁମତି ଦେଇଥାଏ।"</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"ସେଲ୍‍ ବ୍ରଡ୍‍କାଷ୍ଟ ମେସେଜ୍‍ ପଢ଼ନ୍ତୁ"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"ଆପଣଙ୍କ ଡିଭାଇସ୍‍ରେ ପ୍ରାପ୍ତ ହୋଇଥିବା ସେଲ୍‍ ବ୍ରଡ୍‍କାଷ୍ଟ ମେସେଜ୍‍ ପଢିବାକୁ ଆପ୍‍କୁ ଅନୁମତି ଦିଏ। ଜରୁରୀକାଳୀନ ଅବସ୍ଥା ବିଷୟରେ ଆପଣଙ୍କୁ ସତର୍କ କରାଇବାକୁ କିଛି ଲୋକେଶନ୍‍ରେ ସେଲ୍‍ ବ୍ରଡ୍‍କାଷ୍ଟ ସତର୍କ ଡେଲିଭର୍ କରାଯାଇଥାଏ। ଏକ ଜରୁରୀକାଳୀନ ସେଲ୍‍ ବ୍ରଡ୍‍କାଷ୍ଟ ପ୍ରାପ୍ତ ହେବାପରେ ହାନୀକାରକ ଆପ୍‍ ଆପଣଙ୍କ ଡିଭାଇସ୍‍ର କାର୍ଯ୍ୟକ୍ଷମତା କିମ୍ବା ସଞ୍ଚାଳନାରେ ବାଧା ପହଞ୍ଚାଇପାରନ୍ତି।"</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"ସବସ୍କ୍ରାଇବ୍ ହୋଇଥିବା ଫୀଡ୍‌କୁ ପଢ଼ନ୍ତୁ"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ଚଲାଏ"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"\"fileManagement\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"\"mediaProcessing\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ଚଲାଏ"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"\"mediaProcessing\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ଚଲାଏ"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" ପ୍ରକାର ସହ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"ଆପ୍‍ ଷ୍ଟୋରେଜ୍‍ ସ୍ଥାନର ମାପ କରନ୍ତୁ"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"ସ୍କ୍ରିନ୍ ଲକ୍ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"ଜାରି ରଖିବାକୁ ଆପଣଙ୍କ ସ୍କ୍ରିନ୍ ଲକ୍ ଏଣ୍ଟର୍ କରନ୍ତୁ"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"ସେନ୍ସର ଉପରେ ଦୃଢ଼ ଭାବେ ଦବାନ୍ତୁ"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"ଟିପଚିହ୍ନକୁ ଚିହ୍ନଟ କରାଯାଇପାରିବ ନାହିଁ। ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"ଟିପଚିହ୍ନ ଚିହ୍ନଟ କରାଯାଇନାହିଁ। ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"ଟିପଚିହ୍ନ ସେନ୍ସରକୁ ପରିଷ୍କାର କରି ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"ସେନ୍ସରକୁ ପରିଷ୍କାର କରି ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"ସେନ୍ସର ଉପରେ ଦୃଢ଼ ଭାବେ ଦବାନ୍ତୁ"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"ଆପଣଙ୍କ ଫେସ ଦେଖାଯାଉନାହିଁ। ଆପଣଙ୍କ ଫୋନକୁ ଆଖି ସିଧାରେ ଧରି ରଖନ୍ତୁ।"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"ଅତ୍ୟଧିକ ଅସ୍ଥିର। ଫୋନ୍‍କୁ ସ୍ଥିର ଭାବେ ଧରନ୍ତୁ।"</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"ଦୟାକରି ଆପଣଙ୍କର ମୁହଁ ପୁଣି-ଏନ୍‍ରୋଲ୍ କରନ୍ତୁ।"</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"ଫେସ ଚିହ୍ନଟ କରାଯାଇପାରିବ ନାହିଁ। ପୁଣି ଚେଷ୍ଟା କର।"</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"ଫେସ ଚିହ୍ନଟ କରାଯାଇନାହିଁ। ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"ଆପଣଙ୍କ ମୁଣ୍ଡର ସ୍ଥିତି ସାମାନ୍ୟ ବଦଳାନ୍ତୁ"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"ଆପଣଙ୍କ ଫୋନକୁ ସମ୍ପୂର୍ଣ୍ଣ ସିଧା ଦେଖନ୍ତୁ"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"ଆପଣଙ୍କ ଫୋନକୁ ସମ୍ପୂର୍ଣ୍ଣ ସିଧା ଦେଖନ୍ତୁ"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index d3901f7..01f4fe1 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -85,7 +85,7 @@
     <string name="NetworkPreferenceSwitchSummary" msgid="2086506181486324860">"ਤਰਜੀਹੀ ਨੈੱਟਵਰਕ ਨੂੰ ਬਦਲ ਕੇ ਦੇਖੋ। ਬਦਲਣ ਲਈ ਟੈਪ ਕਰੋ।"</string>
     <string name="EmergencyCallWarningTitle" msgid="1615688002899152860">"ਸੰਕਟਕਾਲੀਨ ਕਾਲਿੰਗ ਉਪਲਬਧ ਨਹੀਂ"</string>
     <string name="EmergencyCallWarningSummary" msgid="1194185880092805497">"ਵਾਈ-ਫਾਈ ਰਾਹੀਂ ਸੰਕਟਕਾਲੀਨ ਕਾਲਾਂ ਨਹੀਂ ਕਰ ਸਕਦੇ"</string>
-    <string name="notification_channel_network_alert" msgid="4788053066033851841">"ਸੁਚੇਤਨਾਵਾਂ"</string>
+    <string name="notification_channel_network_alert" msgid="4788053066033851841">"ਅਲਰਟ"</string>
     <string name="notification_channel_call_forward" msgid="8230490317314272406">"ਕਾਲ ਫਾਰਵਰਡਿੰਗ"</string>
     <string name="notification_channel_emergency_callback" msgid="54074839059123159">"ਸੰਕਟਕਾਲੀਨ ਕਾਲਬੈਕ ਮੋਡ"</string>
     <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"ਮੋਬਾਈਲ ਡਾਟੇ ਦੀ ਸਥਿਤੀ"</string>
@@ -279,11 +279,11 @@
     <string name="notification_channel_developer_important" msgid="7197281908918789589">"ਮਹੱਤਵਪੂਰਨ ਵਿਕਾਸਕਾਰ ਸੁਨੇਹੇ"</string>
     <string name="notification_channel_updates" msgid="7907863984825495278">"ਅੱਪਡੇਟ"</string>
     <string name="notification_channel_network_status" msgid="2127687368725272809">"ਨੈੱਟਵਰਕ ਅਵਸਥਾ"</string>
-    <string name="notification_channel_network_alerts" msgid="6312366315654526528">"ਨੈੱਟਵਰਕ ਸੁਚੇਤਨਾਵਾਂ"</string>
+    <string name="notification_channel_network_alerts" msgid="6312366315654526528">"ਨੈੱਟਵਰਕ ਅਲਰਟ"</string>
     <string name="notification_channel_network_available" msgid="6083697929214165169">"ਨੈੱਟਵਰਕ ਉਪਲਬਧ ਹੈ"</string>
     <string name="notification_channel_vpn" msgid="1628529026203808999">"VPN ਅਵਸਥਾ"</string>
-    <string name="notification_channel_device_admin" msgid="6384932669406095506">"ਤੁਹਾਡੇ ਆਈ.ਟੀ. ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਸੁਚੇਤਨਾਵਾਂ"</string>
-    <string name="notification_channel_alerts" msgid="5070241039583668427">"ਸੁਚੇਤਨਾਵਾਂ"</string>
+    <string name="notification_channel_device_admin" msgid="6384932669406095506">"ਤੁਹਾਡੇ ਆਈ.ਟੀ. ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਅਲਰਟ"</string>
+    <string name="notification_channel_alerts" msgid="5070241039583668427">"ਅਲਰਟ"</string>
     <string name="notification_channel_retail_mode" msgid="3732239154256431213">"ਪ੍ਰਚੂਨ ਸਟੋਰਾਂ ਲਈ ਡੈਮੋ"</string>
     <string name="notification_channel_usb" msgid="1528280969406244896">"USB ਕਨੈਕਸ਼ਨ"</string>
     <string name="notification_channel_heavy_weight_app" msgid="17455756500828043">"ਚੱਲ ਰਹੀ ਐਪ"</string>
@@ -367,9 +367,13 @@
     <string name="permlab_receiveMms" msgid="4000650116674380275">"ਲਿਖਤ ਸੁਨੇਹੇ (MMS) ਪ੍ਰਾਪਤ ਕਰੋ"</string>
     <string name="permdesc_receiveMms" msgid="958102423732219710">"ਐਪ ਨੂੰ MMS ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਕਰਨ ਅਤੇ ਉਹਨਾਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਸਦਾ ਮਤਲਬ ਹੈ ਕਿ ਐਪ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਤੇ ਭੇਜੇ ਗਏ ਸੁਨੇਹਿਆਂ ਨੂੰ ਤੁਹਾਨੂੰ ਦਿਖਾਏ ਬਿਨਾਂ ਨਿਰੀਖਣ ਕਰ ਸਕਦੀ ਹੈ ਜਾਂ ਮਿਟਾ ਸਕਦੀ ਹੈ।"</string>
     <string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਸੁਨੇਹਿਆਂ ਨੂੰ ਅੱਗੇ ਭੇਜੋ"</string>
-    <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"ਐਪ ਨੂੰ ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਸੁਨੇਹਿਆਂ ਦੇ ਪ੍ਰਾਪਤ ਹੁੰਦੇ ਹੀ ਉਹਨਾਂ ਨੂੰ ਅੱਗੇ ਭੇਜਣ ਲਈ ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਮਾਡਿਊਲ ਨਾਲ ਜੋੜਨ ਦੀ ਇਜਾਜ਼ਤ ਦਿੱਤੀ ਜਾਂਦੀ ਹੈ। ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਸੁਚੇਤਨਾਵਾਂ ਤੁਹਾਨੂੰ ਸੰਕਟਕਾਲੀ ਸਥਿਤੀਆਂ ਦੀ ਚਿਤਾਵਨੀ ਦੇਣ ਲਈ ਕੁਝ ਟਿਕਾਣਿਆਂ \'ਤੇ ਪ੍ਰਦਾਨ ਕੀਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ। ਭੈੜੀਆਂ ਐਪਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀ ਕਾਰਗੁਜ਼ਾਰੀ ਜਾਂ ਓਪਰੇਸ਼ਨ ਵਿੱਚ ਵਿਘਨ ਪਾ ਸਕਦੀਆਂ ਹਨ ਜਦੋਂ ਇੱਕ ਸੰਕਟਕਾਲੀ ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਪ੍ਰਾਪਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ।"</string>
+    <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"ਐਪ ਨੂੰ ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਸੁਨੇਹਿਆਂ ਦੇ ਪ੍ਰਾਪਤ ਹੁੰਦੇ ਹੀ ਉਨ੍ਹਾਂ ਨੂੰ ਅੱਗੇ ਭੇਜਣ ਲਈ ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਮਾਡਿਊਲ ਨਾਲ ਜੋੜਨ ਦੀ ਇਜਾਜ਼ਤ ਦਿੱਤੀ ਜਾਂਦੀ ਹੈ। ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਅਲਰਟ ਤੁਹਾਨੂੰ ਐਮਰਜੈਂਸੀ ਸਥਿਤੀਆਂ ਦੀ ਚਿਤਾਵਨੀ ਦੇਣ ਲਈ ਕੁਝ ਟਿਕਾਣਿਆਂ \'ਤੇ ਪ੍ਰਦਾਨ ਕੀਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ। ਭੈੜੀਆਂ ਐਪਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀ ਕਾਰਗੁਜ਼ਾਰੀ ਜਾਂ ਓਪਰੇਸ਼ਨ ਵਿੱਚ ਵਿਘਨ ਪਾ ਸਕਦੀਆਂ ਹਨ ਜਦੋਂ ਇੱਕ ਐਮਰਜੈਂਸੀ ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਪ੍ਰਾਪਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ।"</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"ਜਾਰੀ ਕਾਲਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"ਐਪ ਨੂੰ ਆਪਣੇ ਡੀਵਾਈਸ \'ਤੇ ਜਾਰੀ ਕਾਲਾਂ ਬਾਰੇ ਵੇਰਵੇ ਦੇਖਣ ਅਤੇ ਇਹਨਾਂ ਕਾਲਾਂ ਨੂੰ ਕੰਟਰੋਲ ਕਰਨ ਦਿਓ।"</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"ਸੈਲ ਪ੍ਰਸਾਰਨ ਸੁਨੇਹੇ ਪੜ੍ਹੋ"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"ਐਪ ਨੂੰ ਤੁਹਾਡੀ ਡੀਵਾਈਸ ਵੱਲੋਂ ਪ੍ਰਾਪਤ ਕੀਤੇ ਸੈੱਲ ਪ੍ਰਸਾਰਣ ਸੁਨੇਹੇ ਪੜ੍ਹਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਸੈੱਲ ਪ੍ਰਸਾਰਣ ਚਿਤਾਵਨੀਆਂ ਤੁਹਾਨੂੰ ਸੰਕਟਕਾਲੀਨ ਸਥਿਤੀਆਂ ਦੀ ਚਿਤਾਵਨੀ ਦੇਣ ਲਈ ਕੁਝ ਨਿਰਧਾਰਤ ਟਿਕਾਣਿਆਂ ਤੇ ਪ੍ਰਦਾਨ ਕੀਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ। ਖਰਾਬ ਐਪਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੇ ਪ੍ਰਦਰਸ਼ਨ ਜਾਂ ਓਪਰੇਸ਼ਨ ਵਿੱਚ ਵਿਘਨ ਪਾ ਸਕਦੀਆਂ ਹਨ ਜਦੋਂ ਇੱਕ ਸੰਕਟਕਾਲੀਨ ਸੈੱਲ ਪ੍ਰਸਾਰਣ ਪ੍ਰਾਪਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ।"</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"ਸਬਸਕ੍ਰਾਈਬ ਕੀਤੇ ਫੀਡਸ ਪੜ੍ਹੋ"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"ਐਪ ਨੂੰ \"systemExempted\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾ ਨੂੰ ਚਲਾਓ"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"ਐਪ ਨੂੰ \"fileManagement\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤਣ ਦੀ ਆਗਿਆ ਮਿਲਦੀ ਹੈ"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"\"mediaProcessing\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾ ਨੂੰ ਚਲਾਓ"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"ਐਪ ਨੂੰ \"mediaProcessing\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਵਰਤਣ ਦੀ ਆਗਿਆ ਮਿਲਦੀ ਹੈ"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾ ਨੂੰ ਚਲਾਉਂਦੀ ਹੈ"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"ਐਪ ਨੂੰ \"specialUse\" ਕਿਸਮ ਨਾਲ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"ਐਪ ਸਟੋਰੇਜ ਜਗ੍ਹਾ ਮਾਪੋ"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"ਸਕ੍ਰੀਨ ਲਾਕ ਦੀ ਵਰਤੋਂ ਕਰੋ"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"ਜਾਰੀ ਰੱਖਣ ਲਈ ਆਪਣਾ ਸਕ੍ਰੀਨ ਲਾਕ ਦਾਖਲ ਕਰੋ"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"ਸੈਂਸਰ ਨੂੰ ਜ਼ੋਰ ਨਾਲ ਦਬਾਓ"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"ਫਿੰਗਰਪ੍ਰਿੰਟ ਦੀ ਪਛਾਣ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"ਫਿੰਗਰਪ੍ਰਿੰਟ ਦੀ ਪਛਾਣ ਨਹੀਂ ਹੋਈ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"ਫਿੰਗਰਪ੍ਰਿੰਟ ਸੈਂਸਰ ਨੂੰ ਸਾਫ਼ ਕਰੋ ਅਤੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"ਸੈਂਸਰ ਨੂੰ ਸਾਫ਼ ਕਰੋ ਅਤੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"ਸੈਂਸਰ ਨੂੰ ਜ਼ੋਰ ਨਾਲ ਦਬਾਓ"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"ਤੁਹਾਡਾ ਚਿਹਰਾ ਨਹੀਂ ਦਿਸ ਰਿਹਾ। ਆਪਣੇ ਫ਼ੋਨ ਨੂੰ ਅੱਖਾਂ ਦੀ ਸੀਧ ਵਿੱਚ ਰੱਖੋ।"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"ਬਹੁਤ ਜ਼ਿਆਦਾ ਹਿਲਜੁਲ। ਫ਼ੋਨ ਨੂੰ ਸਥਿਰ ਰੱਖੋ।"</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"ਕਿਰਪਾ ਕਰਕੇ ਆਪਣਾ ਚਿਹਰਾ ਦੁਬਾਰਾ ਦਰਜ ਕਰੋ।"</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"ਚਿਹਰੇ ਦੀ ਪਛਾਣ ਨਹੀਂ ਹੋਈ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"ਚਿਹਰੇ ਦੀ ਪਛਾਣ ਨਹੀਂ ਹੋਈ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"ਆਪਣੇ ਸਿਰ ਨੂੰ ਥੋੜ੍ਹਾ ਹਿਲਾਓ"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"ਸਿੱਧਾ ਆਪਣੇ ਫ਼ੋਨ ਵੱਲ ਦੇਖੋ"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"ਸਿੱਧਾ ਆਪਣੇ ਫ਼ੋਨ ਵੱਲ ਦੇਖੋ"</string>
@@ -1922,7 +1928,7 @@
     <string name="stk_cc_ss_to_ss" msgid="132040645206514450">"ਨਵੀਂ SS ਬੇਨਤੀ ਵਿੱਚ ਬਦਲਿਆ ਗਿਆ"</string>
     <string name="notification_phishing_alert_content_description" msgid="494227305355958790">"ਫ਼ਿਸ਼ਿੰਗ ਸੰਬੰਧੀ ਅਲਰਟ"</string>
     <string name="notification_work_profile_content_description" msgid="5296477955677725799">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ"</string>
-    <string name="notification_alerted_content_description" msgid="6139691253611265992">"ਸੁਚੇਤਨਾਵਾਂ"</string>
+    <string name="notification_alerted_content_description" msgid="6139691253611265992">"ਅਲਰਟ"</string>
     <string name="notification_verified_content_description" msgid="6401483602782359391">"ਪੁਸ਼ਟੀਕਿਰਤ"</string>
     <string name="expand_button_content_description_collapsed" msgid="3873368935659010279">"ਵਿਸਤਾਰ ਕਰੋ"</string>
     <string name="expand_button_content_description_expanded" msgid="7484217944948667489">"ਸਮੇਟੋ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index e03679b..375053e 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -372,6 +372,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Zezwala aplikacji powiązać się z modułem komunikatów z sieci komórkowej, aby przekazywać je w momencie, w którym są otrzymywane. W niektórych lokalizacjach komunikaty alarmowe z sieci komórkowej są dostarczane, aby ostrzec Cię o sytuacjach zagrożenia. Złośliwe aplikacje mogą wpływać na działanie urządzenia lub zakłócać je po nadejściu komunikatu alarmowego z sieci komórkowej."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Zarządzaj połączeniami w toku"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Zezwala aplikacji zobaczyć detale dotyczące połączeń w toku na Twoim urządzeniu i na kontrolę tych połączeń."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"odczyt komunikatów z sieci komórkowej"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Zezwala aplikacji na odczyt komunikatów z sieci komórkowej odebranych na urządzeniu. Komunikaty alarmowe z sieci komórkowej są dostarczane w niektórych lokalizacjach w celu ostrzeżenia Cię o sytuacjach zagrożenia. Złośliwe aplikacje mogą wpływać na wydajność lub zakłócać działanie urządzenia po odebraniu komunikatu alarmowego z sieci komórkowej."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"czytanie subskrybowanych źródeł"</string>
@@ -436,6 +440,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „systemExempted”"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"Uruchamianie usług działających na pierwszym planie typu „fileManagement”"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „fileManagement”."</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"uruchamianie usług działających na pierwszym planie typu „mediaProcessing”"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „mediaProcessing”"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"uruchamianie usług działających na pierwszym planie typu „specialUse”"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Zezwala na wykorzystywanie przez aplikację usług działających na pierwszym planie typu „specialUse”"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"mierzenie rozmiaru pamięci aplikacji"</string>
@@ -634,7 +640,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Używaj blokady ekranu"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Użyj blokady ekranu, aby kontynuować"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Mocno naciśnij czujnik"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Nie rozpoznaję odcisku palca. Spróbuj ponownie."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Nie rozpoznano odcisku palca. Spróbuj ponownie."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Wyczyść czytnik linii papilarnych i spróbuj ponownie"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Wyczyść czujnik i spróbuj ponownie"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Mocno naciśnij czujnik"</string>
@@ -698,7 +704,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Nie widać twarzy – trzymaj telefon na wysokości oczu"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Telefon się porusza. Trzymaj go nieruchomo."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Zarejestruj swoją twarz ponownie."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Nie rozpoznaję twarzy. Spróbuj ponownie."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Nie rozpoznano twarzy. Spróbuj ponownie."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Lekko zmień położenie głowy"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Patrz prosto na telefon"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Patrz prosto na telefon"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 2888c5f..9bec567 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -371,6 +371,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Permite que o app se vincule ao módulo de transmissão celular para encaminhar mensagens de transmissão celular assim que elas forem recebidas. Alertas de transmissão celular são recebidos em alguns locais para avisar sobre situações de emergência. Apps maliciosos podem interferir no desempenho ou funcionamento do dispositivo quando uma transmissão celular de emergência é recebida."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Gerenciar chamadas em andamento"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Permite que um app veja detalhes sobre chamadas em andamento no seu dispositivo e as controle."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"ler mensagens de difusão celular"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Permite que o app leia mensagens de difusão celular recebidas por seu dispositivo. Alertas de difusão celular são recebidos em alguns locais para avisar você de situações de emergência. Apps maliciosos podem interferir no desempenho ou funcionamento de seu dispositivo quando uma difusão celular de emergência é recebida."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"ler feeds inscritos"</string>
@@ -435,6 +439,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite que o app use serviços em primeiro plano com o tipo \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"executar serviços em primeiro plano com o tipo \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Permite que o app use serviços em primeiro plano com o tipo \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"executar serviços em primeiro plano com o tipo \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Permite que o app use serviços em primeiro plano com o tipo \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"executar serviços em primeiro plano com o tipo \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite que o app use serviços em primeiro plano com o tipo \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"medir o espaço de armazenamento do app"</string>
@@ -633,7 +639,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Usar bloqueio de tela"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Insira seu bloqueio de tela para continuar"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Pressione o sensor com firmeza"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Impressão digital não reconhecida. Tente de novo."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Impressão digital não reconhecida. Tente de novo."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Limpe o sensor de impressão digital e tente novamente"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Limpe o sensor e tente novamente"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Pressione o sensor com firmeza"</string>
@@ -697,7 +703,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Rosto não detectado. Segure o smartphone na altura dos olhos."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Muito movimento. Não mova o smartphone."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Registre seu rosto novamente."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Não foi possível reconhecer o rosto. Tente de novo."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Rosto não reconhecido. Tente de novo."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Mude a posição da cabeça ligeiramente"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Olhe diretamente para o smartphone"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Olhe diretamente para o smartphone"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 7a3201f..b1be29f 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -371,6 +371,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Permite que a app se vincule ao módulo de difusão celular para encaminhar mensagens de difusão celular à medida que são recebidas. Os alertas de difusão celular são fornecidos em algumas localizações para avisar sobre situações de emergência. As aplicações maliciosas podem interferir com o desempenho ou funcionamento do seu dispositivo quando for recebida uma difusão celular de emergência."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Gerir chamadas em curso"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Permite que uma app veja detalhes acerca das chamadas em curso no seu dispositivo e controle essas chamadas."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"ler mensagens de transmissão celular"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Permite que a app leia mensagens de transmissão celular recebidas pelo seu dispositivo. Os alertas de transmissão celular são fornecidos em algumas localizações para avisá-lo sobre situações de emergência. As aplicações maliciosas podem interferir com o desempenho ou funcionamento do seu dispositivo quando for recebida uma transmissão celular de emergência."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"ler feeds subscritos"</string>
@@ -435,6 +439,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite que a app use serviços em primeiro plano com o tipo \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"executar o serviço em primeiro plano com o tipo \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Permite que a app use serviços em primeiro plano com o tipo \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"executar o serviço em primeiro plano com o tipo \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Permite que a app use serviços em primeiro plano com o tipo \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"executar o serviço em primeiro plano com o tipo \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite que a app use serviços em primeiro plano com o tipo \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"medir espaço de armazenamento da app"</string>
@@ -633,7 +639,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Usar o bloqueio de ecrã"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Introduza o bloqueio de ecrã para continuar"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Prima firmemente o sensor"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Impossível reconhecer impressão digital. Volte a tentar."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Impressão digital não reconhecida. Tente novamente."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Limpe o sensor de impressões digitais e tente novamente"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Limpe o sensor e tente novamente"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Prima firmemente o sensor"</string>
@@ -697,7 +703,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Rosto não detetado. Segure o telemóvel ao nível dos olhos"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Demasiado movimento. Mantenha o telemóvel firme."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Volte a inscrever o rosto."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Impossível reconhecer o rosto. Tente novamente."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Rosto não reconhecido. Tente novamente."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Altere ligeiramente a posição da sua cabeça"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Olhe mais diretamente para o telemóvel"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Olhe mais diretamente para o telemóvel"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 2888c5f..9bec567 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -371,6 +371,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Permite que o app se vincule ao módulo de transmissão celular para encaminhar mensagens de transmissão celular assim que elas forem recebidas. Alertas de transmissão celular são recebidos em alguns locais para avisar sobre situações de emergência. Apps maliciosos podem interferir no desempenho ou funcionamento do dispositivo quando uma transmissão celular de emergência é recebida."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Gerenciar chamadas em andamento"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Permite que um app veja detalhes sobre chamadas em andamento no seu dispositivo e as controle."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"ler mensagens de difusão celular"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Permite que o app leia mensagens de difusão celular recebidas por seu dispositivo. Alertas de difusão celular são recebidos em alguns locais para avisar você de situações de emergência. Apps maliciosos podem interferir no desempenho ou funcionamento de seu dispositivo quando uma difusão celular de emergência é recebida."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"ler feeds inscritos"</string>
@@ -435,6 +439,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite que o app use serviços em primeiro plano com o tipo \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"executar serviços em primeiro plano com o tipo \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Permite que o app use serviços em primeiro plano com o tipo \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"executar serviços em primeiro plano com o tipo \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Permite que o app use serviços em primeiro plano com o tipo \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"executar serviços em primeiro plano com o tipo \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite que o app use serviços em primeiro plano com o tipo \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"medir o espaço de armazenamento do app"</string>
@@ -633,7 +639,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Usar bloqueio de tela"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Insira seu bloqueio de tela para continuar"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Pressione o sensor com firmeza"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Impressão digital não reconhecida. Tente de novo."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Impressão digital não reconhecida. Tente de novo."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Limpe o sensor de impressão digital e tente novamente"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Limpe o sensor e tente novamente"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Pressione o sensor com firmeza"</string>
@@ -697,7 +703,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Rosto não detectado. Segure o smartphone na altura dos olhos."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Muito movimento. Não mova o smartphone."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Registre seu rosto novamente."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Não foi possível reconhecer o rosto. Tente de novo."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Rosto não reconhecido. Tente de novo."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Mude a posição da cabeça ligeiramente"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Olhe diretamente para o smartphone"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Olhe diretamente para o smartphone"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 082fbb2..b6029f7 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -371,6 +371,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Permite aplicației să se conecteze la modulul de transmisie celulară pentru a redirecționa mesajele cu transmisie celulară pe măsură ce le primește. Alertele cu transmisie celulară sunt difuzate în unele locații pentru a te avertiza cu privire la situațiile de urgență. Aplicațiile rău intenționate pot afecta performanța sau funcționarea dispozitivului când e primită o transmisie celulară de urgență."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Să gestioneze apelurile în desfășurare"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Permite unei aplicații să vadă detalii despre apelurile în desfășurare de pe dispozitiv și să gestioneze apelurile respective."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"citește mesajele cu transmisie celulară"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Permite aplicației să citească mesajele primite prin transmisie celulară de dispozitiv. Alertele cu transmisie celulară sunt difuzate în unele locații pentru a te avertiza cu privire la situațiile de urgență. Aplicațiile rău intenționate pot afecta performanța sau funcționarea dispozitivului când e primită o transmisie celulară de urgență."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"citire feeduri abonat"</string>
@@ -435,6 +439,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Permite aplicației să folosească serviciile în prim-plan cu tipul „systemExempted”"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"să folosească serviciile în prim-plan cu tipul „fileManagement”"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Permite aplicației să folosească serviciile în prim-plan cu tipul „fileManagement”"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"să folosească serviciile în prim-plan cu tipul „mediaProcessing”"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Permite aplicației să folosească serviciile în prim-plan cu tipul „mediaProcessing”"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"să folosească serviciile în prim-plan cu tipul „specialUse”"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Permite aplicației să folosească serviciile în prim-plan cu tipul „specialUse”"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"măsurare spațiu de stocare al aplicației"</string>
@@ -633,7 +639,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Folosește blocarea ecranului"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Introdu blocarea ecranului pentru a continua"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Apasă ferm pe senzor"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Amprenta nu a fost recunoscută. Încearcă din nou."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Amprenta nu a fost recunoscută. Încearcă din nou."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Curăță senzorul de amprentă și încearcă din nou"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Curăță senzorul și încearcă din nou"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Apasă ferm pe senzor"</string>
@@ -697,7 +703,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Nu ți se vede fața. Ține telefonul la nivelul ochilor."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Prea multă mișcare. Ține telefonul nemișcat."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Reînregistrează-ți chipul."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Chipul nu a fost recunoscut. Reîncearcă."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Fața nu a fost recunoscută. Încearcă din nou."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Schimbă ușor poziția capului"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Privește mai direct spre telefon"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Privește mai direct spre telefon"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 8a03a6b..7d3b869 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -372,6 +372,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Приложение сможет выполнить привязку к модулю оповещения населения, чтобы пересылать сообщения широковещательных SMS-служб сразу после их получения. В некоторых странах эти сообщения используются для информирования об экстренных ситуациях. Вредоносное ПО может помешать работе устройства, на которое поступают такие сообщения."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Управление текущими звонками"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Приложение сможет управлять текущими звонками на вашем устройстве, а также получит доступ к сведениям о них"</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"Читать сообщения массовой рассылки"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Приложение получит доступ к сообщениям широковещательных SMS-служб, которые в некоторых странах используются для информирования населения об экстренных ситуациях. Вредоносные программы могут помешать работе устройства, на которое поступают такие сообщения."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"Просмотр фидов пользователя"</string>
@@ -436,6 +440,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Разрешить приложению использовать активные службы с типом systemExempted"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"Запуск активных служб с типом fileManagement"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Приложение сможет использовать активные службы с типом fileManagement."</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"запускать активные службы с типом mediaProcessing"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Приложение сможет использовать активные службы с типом mediaProcessing."</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"запускать активные службы с типом specialUse"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Разрешить приложению использовать активные службы с типом specialUse"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"Вычисление объема памяти приложений"</string>
@@ -634,7 +640,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Использовать блокировку экрана"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Чтобы продолжить, разблокируйте экран."</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Плотно прижмите палец к сканеру"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Не удалось распознать отпечаток. Повторите попытку."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Отпечаток пальца не распознан. Повторите попытку."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Очистите сканер отпечатков пальцев и повторите попытку."</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Очистите сканер и повторите попытку."</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Плотно прижмите палец к сканеру."</string>
@@ -698,7 +704,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Вашего лица не видно. Держите телефон на уровне глаз"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Не перемещайте устройство. Держите его неподвижно."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Повторите попытку."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Не удалось распознать лицо. Повторите попытку."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Лицо не распознано. Повторите попытку."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Немного измените положение головы"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Смотрите прямо на телефон"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Смотрите прямо на телефон"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 89ee9f9..12cb28a 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"සෙල් විකාශන පණිවිඩ ලැබුණු විට ඒවා යොමු කිරීම සඳහා සෙල් විකාශන මොඩියුලයට බැඳීමට යෙදුමට ඉඩ දෙයි. හදිසි අවස්ථා පිළිබඳව ඔබට අනතුරු ඇඟවීම සඳහා සෙල් විකාශන ඇඟවීම් සමහර ස්ථානවල ලබා දෙනු ලැබේ. හදිසි සෙල් විකාශනයක් ලැබෙන අවස්ථාවකදී, අනිෂ්ට යෙදුම්වලින් ඔබගේ උපාංග කාර්ය සාධනයට හෝ මෙහෙයුමට බාධා සිදු විය හැකිය."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"සිදු වෙමින් පවතින ඇමතුම් කළමනාකරණය කරන්න"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"ඔබගේ උපාංගයේ සිදු වෙමින් පවතින ඇමතුම් පිළිබඳ විස්තර බැලීමට සහ මෙම ඇමතුම් පාලනය කිරීමට යෙදුමකට ඉඩ දෙයි."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"සෙල් ප්‍රචාරණ පණිවිඩ කියවීම"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"ඔබගේ උපාංගයට ලැබුණු සෙල් විකාශන පණිවිඩ කියවීමට යෙදුමට අවසර දෙන්න. ඔබට හදිසි අවස්ථාවන් පිළිබඳ අනතුරු ඇඟවීමට සෙල් විකාශන පණිවිඩ ඇතැම් ස්ථානවල සිට යවනු ලබයි. හදිසි සෙල් විකාශන ලැබෙන අවස්ථාවකදී, අනිෂ්ට යෙදුම් මඟින් ඔබගේ උපාංගයට කාර්ය සාධනයට හෝ ක්‍රියකරණයට බාධා සිදුවිය හැක."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"දායක වූ සංග්‍රහ කියවීම"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" වර්ගය සමග පෙරබිම් සේවා භාවිතා කිරීමට යෙදුමට ඉඩ දෙයි"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" වර්ගය සමග පෙරබිම් සේවාව ධාවනය කරන්න"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"\"fileManagement\" වර්ගය සමග පෙරබිම් සේවා භාවිතා කිරීමට යෙදුමට ඉඩ දෙයි"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"\"mediaProcessing\" වර්ගය සමග පෙරබිම් සේවාව ධාවනය කරන්න"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"\"mediaProcessing\" වර්ගය සමග පෙරබිම් සේවා භාවිතා කිරීමට යෙදුමට ඉඩ දෙයි"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" වර්ගය සමග පෙරබිම් සේවාව ධාවනය කරන්න"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" වර්ගය සමග පෙරබිම් සේවා භාවිතා කිරීමට යෙදුමට ඉඩ දෙයි"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"යෙදුම් ආචයනයේ ඉඩ ප්‍රමාණය මැනීම"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"තිර අගුල භාවිත කරන්න"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"ඉදිරියට යාමට ඔබගේ තිර අගුල ඇතුළත් කරන්න"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"සංවේදකය මත තදින් ඔබන්න"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"ඇඟිලි සලකුණ හඳුනා ගත නොහැක. නැවත උත්සාහ කරන්න."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"ඇඟිලි සලකුණ හඳුනා නොගැනිණි. නැවත උත්සාහ කරන්න."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"ඇඟිලි සලකුණු සංවේදකය පිරිසිදු කර නැවත උත්සාහ කරන්න"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"සංවේදකය පිරිසිදු කර නැවත උත්සාහ කරන්න"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"සංවේදකය මත තදින් ඔබන්න"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"ඔබගේ මුහුණ දැකිය නොහැකිය. ඔබගේ දුරකථනය ඇස් මට්ටමින් අල්ලා ගන්න."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"චලනය ඉතා වැඩියි. දුරකථනය ස්ථිරව අල්ලා සිටින්න."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"ඔබේ මුහුණ යළි ලියාපදිංචි කරන්න."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"මුහුණ හඳුනා ගත නොහැකිය. නැවත උත්සාහ කරන්න."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"මුහුණ හඳුනා නොගැනිණි. නැවත උත්සාහ කරන්න."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"ඔබගේ හිසෙහි පිහිටීම මදක් වෙනස් කරන්න"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"ඔබගේ දුරකථනය දෙස වඩාත් ඍජුව බලන්න"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"ඔබගේ දුරකථනය දෙස වඩාත් ඍජුව බලන්න"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index bbfefd1..e2161ab 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -372,6 +372,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Umožňuje aplikácii spojiť sa s modulom správ informačných služieb s cieľom preposielať prichádzajúce správy informačných služieb. Správy informačných služieb sa doručujú na určitých miestach a upozorňujú na tiesňové situácie. Škodlivé aplikácie môžu pri prijatí správy informačnej služby narušiť výkonnosť alebo prevádzku vášho zariadenia."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Správa prebiehajúcich hovorov"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Povolí aplikácii čítať podrobnosti o prebiehajúcich hovoroch v zariadení a ovládať ich."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"čítať správy informačných služieb"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Umožňuje aplikácii čítať správy informačných služieb prijaté vaším zariadením. Správy informačných služieb sa doručujú na určitých miestach a upozorňujú na tiesňové situácie. Škodlivé aplikácie môžu pri prijatí správy informačnej služby narušiť výkonnosť alebo prevádzku vášho zariadenia."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"čítať odoberané informačné kanály"</string>
@@ -436,6 +440,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Umožňuje aplikácii využívať služby na popredí s typom dataSync systemExempted"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"spúšťať službu na popredí s typom remoteMessaging"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Umožňuje aplikácii využívať služby na popredí s typom fileManagement"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"spúšťať službu na popredí s typom mediaProcessing"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Umožňuje aplikácii využívať služby na popredí s typom mediaProcessing"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"spustiť službu na popredí s typom specialUse"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Umožňuje aplikácii využívať služby na popredí s typom specialUse"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"zistiť veľkosť ukladacieho priestoru aplikácie"</string>
@@ -634,7 +640,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Použiť zámku obrazovky"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Pokračujte zadaním zámky obrazovky"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Pevne pritlačte prst na senzor"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Odtlačok prsta sa nedá rozpoznať. Skúste to znova."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Odtlačok prsta nebol rozpoznaný. Skúste to znova."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Vyčistite senzor odtlačkov prstov a skúste to znova"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Vyčistite senzor a skúste to znova"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Pevne pritlačte prst na senzor"</string>
@@ -698,7 +704,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Nie je vidieť vašu tvár. Držte telefón na úrovni očí."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Priveľa pohybu. Nehýbte telefónom."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Znova zaregistrujte svoju tvár."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Tvár sa nedá rozpoznať. Skúste to znova."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Tvár nebola rozpoznaná. Skúste to znova."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Trocha zmeňte pozíciu hlavy"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Pozrite sa na telefón priamejšie"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Pozrite sa na telefón priamejšie"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index e4e9a37..54cc786 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -372,6 +372,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Aplikaciji omogoča povezovanje z modulom za oddaje v celici, da posreduje sporočila oddaj v celici, takoj ko jih prejme. Na nekaterih lokacijah so opozorila oddaj v celici dostavljena, da vas opozorijo na izredne razmere. Zlonamerne aplikacije lahko vplivajo na delovanje naprave, ko prejme sporočilo oddaje v celici."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Upravljanje aktivnih klicev"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Aplikaciji dovoljuje ogled podrobnosti o aktivnih klicih v napravi in upravljanje s temi klici."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"branje sporočil oddaje v celici"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Omogoča aplikaciji branje sporočil oddaje v celici, ki jih prejme naprava. Opozorila oddaje v celici so dostavljena na nekaterih lokacijah, da vas opozorijo na izredne razmere. Zlonamerne aplikacije lahko vplivajo na delovanje naprave, ko dobi sporočilo oddaje v celici."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"branje naročenih virov"</string>
@@ -436,6 +440,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Aplikaciji dovoljuje, da uporablja storitve v ospredju vrste »systemExempted«."</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"izvajanje storitve v ospredju vrste »fileManagement«"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Aplikaciji dovoljuje, da uporablja storitve v ospredju vrste »fileManagement«."</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"izvajanje storitve v ospredju vrste »mediaProcessing«"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Aplikaciji dovoljuje, da uporablja storitve v ospredju vrste »mediaProcessing«"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"izvajanje storitve v ospredju vrste »specialUse«"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Aplikaciji dovoljuje, da uporablja storitve v ospredju vrste »specialUse«."</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"izračunavanje prostora za shranjevanje aplikacije"</string>
@@ -634,7 +640,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Uporaba odklepanja s poverilnico"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Odklenite zaslon, če želite nadaljevati."</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Prst dobro pridržite na tipalu"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Prstnega odtisa ni mogoče prepoznati. Poskusite znova."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Prstni odtis ni prepoznan. Poskusite znova."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Očistite tipalo prstnih odtisov in poskusite znova."</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Očistite tipalo in poskusite znova."</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Prst dobro pridržite na tipalu"</string>
@@ -698,7 +704,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Obraz ni viden. Držite telefon v višini oči."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Preveč se premikate. Držite telefon pri miru."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Znova registrirajte svoj obraz."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Obraza ni mogoče prepoznati. Poskusite znova."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Obraz ni prepoznan. Poskusite znova."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Nekoliko spremenite položaj glave."</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Glejte bolj naravnost v telefon"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Glejte bolj naravnost v telefon"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 288d8bf..3bd5158 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Lejon që aplikacioni të lidhet me modulin e transmetimit celular për t\'i transferuar mesazhet e transmetimit celular menjëherë kur merren. Sinjalizimet e transmetimit celular dërgohen në disa vendndodhje për të të paralajmëruar për situata urgjente. Aplikacionet keqdashëse mund të ndërhyjnë në cilësinë e funksionimit ose në veprimin e pajisjes sate kur merret një transmetim celular urgjent."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Menaxho telefonatat në vazhdim"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Lejon që një aplikacion të shikojë detaje rreth telefonatave në vazhdim dhe t\'i kontrollojë këto telefonata."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"lexo mesazhet e transmetimit të qelizës"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Lejon aplikacionin të lexojë mesazhet e transmetimit të qelizës, të marra nga pajisja jote. Alarmet e transmetimit të qelizës dërgohen në disa vendndodhje për të të paralajmëruar në situata urgjente. Aplikacionet keqdashëse mund të ndërhyjnë në veprimtarinë ose operacionin e pajisjes tënde kur merret një transmetim urgjent i qelizës."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"lexo informacione të abonuara"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Lejon që aplikacioni të përdorë shërbimet në plan të parë me llojin \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"ekzekuto shërbimin në plan të parë me llojin \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Lejon aplikacionin të përdorë shërbimet në plan të parë me llojin \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"të ekzekutojë shërbimin në plan të parë me llojin \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Lejon që aplikacioni të përdorë shërbimet në plan të parë me llojin \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"të ekzekutojë shërbimin në plan të parë me llojin \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Lejon që aplikacioni të përdorë shërbimet në plan të parë me llojin \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"mat hapësirën ruajtëse të aplikacionit"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Përdor kyçjen e ekranit"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Fut kyçjen e ekranit për të vazhduar"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Shtyp fort te sensori"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Nuk mund ta dallojë gjurmën e gishtit. Provo përsëri."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Gjurma e gishtit nuk njihet. Provo përsëri."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Pastro sensorin e gjurmës së gishtit dhe provo sërish"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Pastro sensorin dhe provo sërish"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Shtyp fort te sensori"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Fytyra s\'mund të shihet. Mbaje telefonin në nivelin e syve."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Ka shumë lëvizje. Mbaje telefonin të palëvizur."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Regjistroje përsëri fytyrën tënde."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Fytyra nuk mund të njihet. Provo sërish."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Fytyra nuk njihet. Provo përsëri."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Ndrysho pak pozicionin e kokës"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Shiko më drejtpërdrejt telefonin"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Shiko më drejtpërdrejt telefonin"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 479a1db..0ea7b6e 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -371,6 +371,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Дозвољава апликацији да се везује за модул порука за мобилне уређаје на локалитету да би прослеђивала поруке за мобилне уређаје на локалитету онако како су примљене. Обавештења порука за мобилне уређаје на локалитету се на неким локацијама примају као упозорења на хитне случајеве. Злонамерне апликације могу да утичу на перформансе или ометају рад уређаја када се прими порука о хитном случају за мобилне уређаје на локалитету."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Управљање одлазним позивима"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Омогућава апликацији да види детаље о одлазним позивима на уређају и да контролише те позиве."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"читање порука инфо сервиса"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Омогућава апликацији да чита поруке инфо сервиса које уређај прима. Упозорења инфо сервиса се на неким локацијама примају као упозорења на хитне случајеве. Злонамерне апликације могу да утичу на перформансе или ометају функционисање уређаја када се прими порука инфо сервиса о хитном случају."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"читање пријављених фидова"</string>
@@ -435,6 +439,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „systemExempted“"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"покретање услуге у првом плану која припада типу „fileManagement“"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „fileManagement“"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"покретање услуге у првом плану која припада типу „mediaProcessing“"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „mediaProcessing“"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"покретање услуге у првом плану која припада типу „specialUse“"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „specialUse“"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"мерење меморијског простора у апликацији"</string>
@@ -633,7 +639,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Користите закључавање екрана"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Употребите закључавање екрана да бисте наставили"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Чврсто притисните сензор"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Препознавање отиска прста није успело. Пробајте поново."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Отисак прста није препознат. Пробајте поново."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Обришите сензор за отисак прста и пробајте поново"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Обришите сензор и пробајте поново"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Чврсто притисните сензор"</string>
@@ -697,7 +703,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Не види се лице. Држите телефон у висини очију."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Много се померате. Држите телефон мирно."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Поново региструјте лице."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Лице није препознато. Пробајте поново."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Лице није препознато. Пробајте поново."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Мало померите главу"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Гледајте право у телефон"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Гледајте право у телефон"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index a30aa0a..12e24b6 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Tillåter att appen binds till cellsändningsmodulen så att massutskick via sms kan vidarebefordras vid mottagandet. I vissa områden används massutskick via sms för att varna om nödsituationer. Skadliga appar kan påverka enhetens prestanda eller funktioner när ett massutskick via sms tas emot."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Hantera pågående samtal"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Tillåter att en app får åtkomst till information om pågående samtal på enheten och kan kontrollera dessa samtal."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"läsa SMS-meddelanden"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Appen tillåts läsa SMS som skickas till din enhet. På vissa platser skickas SMS för att varna för nödsituationer. Skadliga appar kan påverka enhetens prestanda eller funktionalitet när du får ett meddelande om en nödsituation via SMS."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"läsa flöden som du prenumererar på"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Tillåter att appen använder förgrundstjänster av typen systemExempted"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"kör förgrundstjänst av typen fileManagement"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Tillåter att appen använder förgrundstjänster av typen fileManagement"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"kör förgrundstjänst av typen mediaProcessing"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Tillåter att appen använder förgrundstjänster av typen mediaProcessing"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"kör förgrundstjänst av typen specialUse"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Tillåter att appen använder förgrundstjänster av typen specialUse"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"mäta appens lagringsplats"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Använd skärmlåset"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Fortsätt med hjälp av ditt skärmlås"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Tryck hårt på sensorn"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Fingeravtrycket kändes inte igen. Försök igen."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Fingeravtrycket känns inte igen. Försök igen."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Rengör fingeravtryckssensorn och försök igen"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Rengör sensorn och försök igen"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Tryck hårt på sensorn"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Ansiktet syns inte. Håll telefonen i ögonhöjd."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"För mycket rörelse. Håll mobilen stilla."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Registrera ansiktet på nytt."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Ansiktet kändes inte igen. Försök igen."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Ansiktet känns inte igen. Försök igen."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Rör lite på huvudet"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Titta rakt på telefonen"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Titta rakt på telefonen"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 9a063fb..4a99945 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Huruhusu programu ipachikwe katika sehemu ya matangazo ya simu ili isambaze ujumbe wa matangazo ya simu unapopokewa. Arifa za matangazo ya simu huwasilishwa katika maeneo mengine ili kukuonya juu ya hali za dharura. Huenda programu hasidi zikatatiza utendaji au shughuli ya kifaa chako matangazo ya simu ya dharura yanapopokewa."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Dhibiti simu zinazoendelea"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Huruhusu programu kuangalia maelezo kuhusu simu zinazoendelea kwenye kifaa chako na kuzidhibiti."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"soma mawasiliano ya matangazo ya simu"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Huruhusu programu kusoma mawasiliano ya matangazo ya simu yaliyoingia kwenye kifaa chako. Arifa za matangazo ya simu huwasilishwa katika maeneo mengine ili kukuonya juu ya hali za dharura. Huenda programu hasidi zikatatiza utendajikazi au shughuli ya kifaa chako wakati matangazo ya simu ya dharura yameingia."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"kusoma mipasho kutoka vyanzo unavyofuatilia"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Huiruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"kutekeleza huduma yenye aina ya \"mediaProcessing\" inayoonekana kwenye skrini"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Huruhusu programu kutumia huduma zenye aina ya \"mediaProcessing\" zinazoonekana kwenye skrini"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"kutekeleza huduma inayoonekana kwenye skrini inayohusiana na \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Huruhusu programu itumie huduma zinazoonekana kwenye skrini zinazohusiana na \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"Pima nafasi ya hifadhi ya programu"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Tumia mbinu ya kufunga skrini"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Weka mbinu yako ya kufunga skrini ili uendelee"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Bonyeza kwa uthabiti kwenye kitambuzi"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Imeshindwa kutambua alama ya kidole. Jaribu tena."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Alama ya kidole haitambuliki. Jaribu tena."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Safisha kitambua alama ya kidole kisha ujaribu tena"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Safisha kitambuzi kisha ujaribu tena"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Bonyeza kwa nguvu kwenye kitambuzi"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Imeshindwa kuona uso wako. Shikilia simu ikilingana na macho."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Inatikisika sana. Ishike simu iwe thabiti."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Tafadhali sajili uso wako tena."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Imeshindwa kutambua uso. Jaribu tena."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Uso hautambuliki. Jaribu tena."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Badilisha nafasi ya kichwa chako kidogo"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Angalia simu yako moja kwa moja"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Angalia simu yako moja kwa moja"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 23402f7..20f12c4 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"செல் பிராட்காஸ்ட் மெசேஜ்களைப் பெறும்போதெல்லாம் அவற்றை முன்னனுப்பும் பொருட்டு, ஆப்ஸை செல் பிராட்காஸ்ட் மாடியூலோடு இணைக்கும். சில இடங்களில் அவசர சூழ்நிலைகளின் போது உங்களை எச்சரிக்க செல் பிராட்காஸ்ட் விழிப்பூட்டல்கள் அனுப்பப்படும். அவசரநிலை செல் பிராட்காஸ்ட்டைப் பெறும்போது, தீங்கிழைக்கும் ஆப்ஸ் உங்கள் சாதனத்தின் செயல்திறனுக்கோ செயல்பாட்டிற்கோ இடையூறு விளைவிக்கக்கூடும்."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"செயலில் உள்ள அழைப்புகளை நிர்வகித்தல்"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"உங்கள் சாதனத்தில், செயலில் உள்ள அழைப்புகள் குறித்த விவரங்களைப் பார்க்கவும் அந்த அழைப்புகளை நிர்வகிக்கவும் ஆப்ஸை அனுமதிக்கும்."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"செல் அலைபரப்புச் செய்திகளைப் படித்தல்"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"உங்கள் சாதனத்தில் பெறப்படும் செல் அலைபரப்புச் செய்திகளைப் படிப்பதற்குப் ஆப்ஸை அனுமதிக்கிறது. அவசரநிலை சூழ்நிலைகளை உங்களுக்கு எச்சரிக்கைச் செய்வதற்கு சில இடங்களில் செல் அலைபரப்பு விழிப்பூட்டல்கள் வழங்கப்படும். அவசரநிலை மொபைல் அலைபரப்புப் பெறப்படும்போது உங்கள் சாதனத்தின் செயல்திறன் அல்லது செயல்பாட்டுடன் தீங்கிழைக்கும் ஆப்ஸ் அதைத் தடுக்கலாம்."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"குழுசேர்ந்த ஊட்டங்களைப் படித்தல்"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"\"fileManagement\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"\"mediaProcessing\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"\"mediaProcessing\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" எனும் வகையைக் கொண்ட முன்புலச் சேவையை இயக்குதல்"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" எனும் வகையைக் கொண்ட முன்புலச் சேவைகளைப் பயன்படுத்த ஆப்ஸை அனுமதிக்கும்"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"ஆப்ஸ் சேமிப்பு இடத்தை அளவிடல்"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"திரைப் பூட்டைப் பயன்படுத்து"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"தொடர்வதற்கு உங்கள் திரைப் பூட்டை உள்ளிடுங்கள்"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"சென்சாரின் மீது நன்றாக அழுத்தவும்"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"கைரேகையை அடையாளம் காண முடியவில்லை. மீண்டும் முயலவும்."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"கைரேகையைக் கண்டறிய முடியவில்லை. மீண்டும் முயலவும்."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"கைரேகை சென்சாரைச் சுத்தம் செய்துவிட்டு மீண்டும் முயலவும்"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"சென்சாரைச் சுத்தம் செய்துவிட்டு மீண்டும் முயலவும்"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"சென்சாரின் மீது நன்றாக அழுத்தவும்"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"முகம் சரியாகத் தெரியவில்லை. மொபைலைக் கண்களுக்கு நேராகப் பிடிக்கவும்."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"அதிகமாக அசைகிறது. மொபைலை அசைக்காமல் பிடிக்கவும்."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"உங்கள் முகத்தை மீண்டும் பதிவுசெய்யுங்கள்."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"முகத்தை அடையாளம் காண இயலவில்லை. மீண்டும் முயலவும்."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"முகத்தைக் கண்டறிய முடியவில்லை. மீண்டும் முயலவும்."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"தலையின் நிலையைச் சிறிதளவு மாற்றவும்"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"உங்கள் மொபைலை நேராகப் பார்க்கவும்"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"உங்கள் மொபைலை நேராகப் பார்க்கவும்"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 30697b3..ec58281 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"సెల్ ప్రసార మెసేజ్‌లను స్వీకరించినప్పుడు, వాటిని ఫార్వర్డ్ చేయడానికి సెల్ ప్రసార మాడ్యూల్‌కు కట్టుబడి ఉండేందుకు యాప్‌ను అనుమతిస్తుంది. ఎమర్జెన్సీ పరిస్థితుల గురించి మిమ్మల్ని హెచ్చరించడానికి కొన్ని లొకేషన్లలో సెల్ ప్రసార అలర్ట్‌లు డెలివరీ చేయబడతాయి. ఎమర్జెన్సీ సెల్ ప్రసార అలర్ట్‌ను స్వీకరించినప్పుడు హానికరమైన యాప్‌లు మీ పరికరం పనితీరుకు లేదా నిర్వహణకు ఆటంకం కలిగించే అవకాశం ఉంది."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"కొనసాగుతున్న కాల్స్‌ను మేనేజ్ చేయండి"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"మీ పరికరంలో కొనసాగుతున్న కాల్స్‌ను చూడటానికి అలాగే వాటిని కంట్రోల్ చేయడానికి ఒక యాప్‌కు అనుమతిస్తోంది."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"సెల్ ప్రసార మెసేజ్‌లను చదవడం"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"మీ పరికరం స్వీకరించిన సెల్ ప్రసార మెసేజ్‌లను చదవడానికి యాప్‌ను అనుమతిస్తుంది. ఎమర్జెన్సీ పరిస్థితుల గురించి మిమ్మల్ని హెచ్చరించడానికి కొన్ని లొకేషన్లలో సెల్ ప్రసార అలర్ట్‌లు డెలివరీ చేయబడతాయి. ఎమర్జెన్సీ సెల్ ప్రసార అలర్ట్‌ను స్వీకరించినప్పుడు హానికరమైన యాప్‌లు మీ పరికరం పనితీరుకు లేదా నిర్వహణకు ఆటంకం కలిగించే అవకాశం ఉంది."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"చందా చేయబడిన ఫీడ్‌లను చదవడం"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"\"systemExempted\" అనే రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌లను ఉపయోగించడానికి యాప్‌ను అనుమతిస్తుంది"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌ను రన్ చేయండి"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"\"fileManagement\" అనే రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌లను ఉపయోగించుకోవడానికి యాప్‌ను అనుమతిస్తుంది"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"\"mediaProcessing\" రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌ను రన్ చేయండి"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"\"mediaProcessing\" అనే రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌లను ఉపయోగించుకోవడానికి యాప్‌ను అనుమతిస్తుంది"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌ను రన్ చేయండి"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" అనే రకంతో ఫోర్‌గ్రౌండ్ సర్వీస్‌లను ఉపయోగించడానికి యాప్‌ను అనుమతిస్తుంది"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"యాప్ స్టోరేజ్‌ స్థలాన్ని అంచనా వేయడం"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"స్క్రీన్ లాక్‌ను ఉపయోగించండి"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"కొనసాగించడానికి మీ స్క్రీన్ లాక్‌ను ఎంటర్ చేయండి"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"సెన్సార్ మీద గట్టిగా నొక్కండి"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"వేలిముద్రను గుర్తించడం సాధ్యపడదు. మళ్లీ ట్రై చేయండి."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"వేలిముద్ర గుర్తించబడలేదు. మళ్లీ ట్రై చేయండి."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"వేలిముద్ర సెన్సార్‌ను క్లీన్ చేసి, మళ్లీ ట్రై చేయండి"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"సెన్సార్‌ను క్లీన్ చేసి, మళ్లీ ట్రై చేయండి"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"సెన్సార్ మీద గట్టిగా నొక్కండి"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"మీ ముఖం కనిపించడం లేదు. మీ ఫోన్‌ను కళ్లకు ఎదురుగా పట్టుకోండి."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"బాగా కదుపుతున్నారు. ఫోన్‌ను స్థిరంగా పట్టుకోండి"</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"దయచేసి మీ ముఖాన్ని మళ్లీ నమోదు చేయండి."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"ముఖం గుర్తించబడలేదు. మళ్లీ ట్రై చేయండి."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"ముఖం గుర్తించబడలేదు. మళ్లీ ట్రై చేయండి."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"మీ తల స్థానాన్ని కొద్దిగా మార్చండి"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"మీ ఫోన్ వైపు మరింత నేరుగా చూడండి"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"మీ ఫోన్ వైపు మరింత నేరుగా చూడండి"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 66ed053..0da6af7 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"อนุญาตให้แอปเชื่อมโยงกับโมดูลการส่งข้อมูลเตือนภัยทางมือถือ (CB) เพื่อส่งต่อข้อความจากการส่งข้อมูลเตือนภัยทางมือถือ (CB) ทันทีที่ได้รับ ระบบจะส่งการแจ้งเตือนจากการส่งข้อมูลเตือนภัยทางมือถือ (CB) ในบางตำแหน่งเพื่อแจ้งเตือนคุณเกี่ยวกับสถานการณ์ฉุกเฉิน แอปที่เป็นอันตรายอาจรบกวนประสิทธิภาพหรือการทำงานของอุปกรณ์เมื่อได้รับการส่งข้อมูลเตือนภัยทางมือถือ (CB) ในกรณีฉุกเฉิน"</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"จัดการสายที่สนทนา"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"อนุญาตแอปเพื่อดูรายละเอียดเกี่ยวกับสายที่สนทนาอยู่บนโทรศัพท์และเพื่อควบคุมสายสนทนาเหล่านี้"</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"อ่านข้อความที่ได้รับจากสถานีมือถือ"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"อนุญาตให้แอปอ่านข้อความจากสถานีมือถือที่อุปกรณ์ได้รับ การแจ้งเตือนทางมือถือมีให้บริการในบางพื้นที่ โดยจะแจ้งเตือนคุณเกี่ยวกับสถานการณ์ฉุกเฉิน แอปที่เป็นอันตรายอาจเข้าแทรกแซงการทำงานของอุปกรณ์เมื่อได้รับข้อความแจ้งเตือนฉุกเฉิน"</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"อ่านฟีดข้อมูลที่สมัครไว้"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"อนุญาตให้แอปใช้ประโยชน์จากบริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"ได้รับการยกเว้นจากระบบ\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"เรียกใช้บริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"การจัดการไฟล์\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"อนุญาตให้แอปใช้ประโยชน์จากบริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"การจัดการไฟล์\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"เรียกใช้บริการที่ทำงานอยู่เบื้องหน้าซึ่งเป็นประเภท \"ประมวลผลสื่อ\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"อนุญาตให้แอปใช้ประโยชน์จากบริการที่ทำงานอยู่เบื้องหน้าซึ่งเป็นประเภท \"การประมวลผลสื่อ\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"เรียกใช้บริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"การใช้งานพิเศษ\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"อนุญาตให้แอปใช้ประโยชน์จากบริการที่ทำงานอยู่เบื้องหน้าโดยมีประเภทเป็น \"การใช้งานพิเศษ\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"วัดพื้นที่เก็บข้อมูลของแอปพลิเคชัน"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"ใช้การล็อกหน้าจอ"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"ป้อนข้อมูลการล็อกหน้าจอเพื่อดำเนินการต่อ"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"กดเซ็นเซอร์ให้แน่น"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"ไม่รู้จักลายนิ้วมือ โปรดลองอีกครั้ง"</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"ไม่รู้จักลายนิ้วมือ โปรดลองอีกครั้ง"</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"ทำความสะอาดเซ็นเซอร์ลายนิ้วมือแล้วลองอีกครั้ง"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"ทำความสะอาดเซ็นเซอร์แล้วลองอีกครั้ง"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"กดเซ็นเซอร์ให้แน่น"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"ไม่เห็นใบหน้า ถือโทรศัพท์ไว้ที่ระดับสายตา"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"มีการเคลื่อนไหวมากเกินไป ถือโทรศัพท์นิ่งๆ"</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"โปรดลงทะเบียนใบหน้าอีกครั้ง"</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"ไม่รู้จักใบหน้า โปรดลองอีกครั้ง"</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"ไม่รู้จักใบหน้า โปรดลองอีกครั้ง"</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"เปลี่ยนตำแหน่งของศีรษะเล็กน้อย"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"โปรดมองตรงมาที่โทรศัพท์"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"โปรดมองตรงมาที่โทรศัพท์"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index c83daa3..cf38312 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Nagbibigay-daan sa app na mag-bind sa module ng cell broadcast para makapagpasa ng mga mensahe ng cell broadcast pagkatanggap sa mga ito. Inihahatid ang mga alerto ng cell broadcast sa ilang lokasyon para balaan ka tungkol sa mga emergency na sitwasyon. Posibleng makasagabal ang mga nakakahamak na app sa performance o pagpapatakbo ng iyong device kapag nakatanggap ito ng emergency na cell broadcast."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Pamahalaan ang mga kasalukuyang tawag"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Pinapayagan ang app na makita ang mga detalye tungkol sa mga kasalukuyang tawag sa iyong device at kontrolin ang mga tawag na ito."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"basahin ang mga mensahe ng cell broadcast"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Binibigyang-daan ang app na magbasa ng mga mensahe ng cell broadcast na natanggap ng iyong device. Inihahatid ang mga alerto ng cell broadcast sa ilang lokasyon upang balaan ka tungkol sa mga emergency na sitwasyon. Maaaring makaabala ang nakakahamak na apps sa performance o pagpapatakbo ng iyong device kapag nakatanggap ng emergency na cell broadcast."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"magbasa ng mga na-subscribe na feed"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Nagbibigay-daan sa app na gamitin ang mga serbisyo sa foreground na may uring \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"Magpatakbo ng serbisyo sa foreground na may uring \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Nagbibigay-daan sa app na gamitin ang mga serbisyo sa foreground na may uring \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"magpagana ng serbisyo sa foreground na may uring \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Nagbibigay-daan sa app na gamitin ang mga serbisyo sa foreground na may uring \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"magpagana ng serbisyo sa foreground na may uring \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Nagbibigay-daan sa app na gamitin ang mga serbisyo sa foreground na may uring \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"sukatin ang espasyo ng storage ng app"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Gumamit ng lock ng screen"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Ilagay ang iyong lock ng screen para magpatuloy"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Pumindot nang madiin sa sensor"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Hindi makilala ang fingerprint. Subukan ulit."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Hindi nakilala ang fingerprint. Subukan ulit."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Linisin ang sensor para sa fingerprint at subukan ulit"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Linisin ang sensor at subukan ulit"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Pumindot nang madiin sa sensor"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Hindi makita ang mukha mo. Hawakan ang telepono kapantay ng mata."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Masyadong magalaw. Hawakang mabuti ang telepono."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Paki-enroll muli ang iyong mukha."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Hindi makilala ang mukha. Subukan ulit."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Hindi nakilala ang mukha. Subukan ulit."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Bahagyang baguhin ang posisyon ng iyong ulo"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Tumingin nang mas direkta sa iyong telepono"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Tumingin nang mas direkta sa iyong telepono"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index c2c8ec6..7751f62 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Uygulamanın hücre yayını mesajları geldiğinde bunları yönlendirmek için hücre yayını modülüne bağlanmasına izin verir. Hücre yayını uyarıları bazı konumlarda acil durumlar hakkında sizi uyarmak için kullanılır. Zararlı uygulamalar acil durum hücre yayını alındığında cihazınızın performansını ve çalışmasını olumsuz etkileyebilir."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Devam eden aramaları yönetme"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Uygulamanın, cihazınızda devam eden aramalarla ilgili bilgileri görüp kontrol etmesine izin verir."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"hücre yayını mesajlarını oku"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Uygulamaya, cihazınız tarafından alınan hücre yayını mesajlarını okuma izni verir. Hücre yayını uyarıları bazı yerlerde acil durumlar konusunda sizi uyarmak için gönderilir. Kötü amaçlı uygulamalar acil hücre yayını alındığında cihazınızın performansına ya da çalışmasına engel olabilir."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"abone olunan yayınları okuma"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Uygulamanın \"systemExempted\" türüyle ön plan hizmetlerini kullanmasına izin verir"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"\"fileManagement\" türündeki ön plan hizmetini çalıştırma"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Uygulamanın \"fileManagement\" türündeki ön plan hizmetlerini kullanmasına izin verir."</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"\"mediaProcessing\" türündeki ön plan hizmetini çalıştırma"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Uygulamanın \"mediaProcessing\" türüyle ön plan hizmetlerini kullanmasına izin verir"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" türüyle ön plan hizmetini çalıştırma"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Uygulamanın \"specialUse\" türüyle ön plan hizmetlerini kullanmasına izin verir"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"uygulama depolama alanını ölç"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Ekran kilidi kullan"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Devam etmek için ekran kilidinizi girin"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Sensöre sıkıca bastırın"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Parmak izi tanınamadı. Tekrar deneyin."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Parmak izi tanınmadı. Tekrar deneyin."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Parmak izi sensörünü temizleyip tekrar deneyin"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Sensörü temizleyip tekrar deneyin"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Sensöre sıkıca bastırın"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Yüzünüz görünmüyor. Telefonunuzu göz hizasında tutun."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Çok fazla hareket ediyorsunuz. Telefonu sabit tutun."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Lütfen yüzünüzü yeniden kaydedin."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Yüz tanınamadı. Tekrar deneyin."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Yüz tanınmadı. Tekrar deneyin."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Başınızın konumunu hafifçe değiştirin"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Telefonunuza daha doğrudan bakın"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Telefonunuza daha doğrudan bakın"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 00139a6..491956c 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -372,6 +372,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Дозволяє додатку зв\'язуватися з модулем Cell Broadcast, щоб переадресувати відповідні вхідні повідомлення. У деяких місцеположеннях сповіщення Cell Broadcast надсилаються для попередження про надзвичайні ситуації. Після повідомлення Cell Broadcast шкідливі додатки можуть перешкоджати роботі вашого пристрою."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Керування поточними дзвінками"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Дає змогу додатку переглядати поточні дзвінки на пристрої та керувати ними."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"читати широкомовні повідомлення мережі"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Дозволяє програмі читати широкомовні повідомлення мережі, отримані пристроєм. Широкомовні сповіщення мережі надсилаються в деяких країнах для попередження про надзвичайні ситуації. Шкідливі програми можуть втручатися у швидкодію чи роботу пристрою під час отримання широкомовного повідомлення мережі про надзвичайну ситуацію."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"читати підписані канали"</string>
@@ -436,6 +440,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Дозволяє додатку використовувати активні сервіси типу systemExempted"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"запускати активний сервіс типу fileManagement"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Дозволяє додатку використовувати активні сервіси типу fileManagement"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"запускати сервіс типу mediaProcessing в активному режимі"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Дозволяє додатку використовувати активні сервіси типу mediaProcessing"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"запускати сервіс типу specialUse в активному режимі"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Дозволяє додатку використовувати активні сервіси типу specialUse"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"визначати об’єм пам’яті програми"</string>
@@ -634,7 +640,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Доступ розблокуванням екрана"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Щоб продовжити, введіть дані для розблокування екрана"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Міцно притисніть палець до сканера"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Відбиток пальця не розпізнано. Повторіть спробу."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Відбиток пальця не розпізнано. Повторіть спробу."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Очистьте сканер відбитків пальців і повторіть спробу"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Очистьте сканер і повторіть спробу"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Міцно притисніть палець до сканера"</string>
@@ -698,7 +704,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Обличчя не видно. Утримуйте телефон на рівні очей."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Забагато рухів. Тримайте телефон нерухомо."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Повторно проскануйте обличчя."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Обличчя не розпізнано. Повторіть спробу."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Обличчя не розпізнано. Повторіть спробу."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Трохи змініть положення голови"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Дивіться на телефон прямо"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Дивіться на телефон прямо"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index dc3ca47..c9346ea 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"سیل کی نشریاتی پیغامات کے موصول ہوتے ہی فارورڈ کرنے کے لیے ایپ کو سیل کے نشریاتی ماڈیول میں پابندی لگانے کی اجازت دیں۔ سیل کی نشریاتی الرٹس آپ کو ایمرجنسی حالات سے مطلع کرنے کیلئے کچھ مقامات میں مہیا کی جاتی ہیں۔ نقصان دہ ایپس کوئی ایمرجنسی سیل براڈ کاسٹ موصول ہونے پر آپ کے آلے کی کارکردگی یا عمل میں مداخلت کر سکتی ہیں۔"</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"جاری کالز کا نظم کریں"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"اس سے ایپ کو آپ کے آلے پر جاری کالز کے بارے میں تفصیلات دیکھنے اور ان کالز کو کنٹرول کرنے کی اجازت ملتی ہے۔"</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"سیل کے نشریاتی پیغامات پڑھیں"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"ایپ کو آپ کے آلے کو موصولہ سیل کے نشریاتی پیغامات پڑھنے کی اجازت دیتا ہے۔ سیل کی نشریاتی الرٹس آپ کو ایمرجنسی حالات سے مطلع کرنے کیلئے کچھ مقامات میں مہیا کی جاتی ہیں۔ نقصان دہ ایپس کوئی ایمرجنسی سیل کا نشریہ موصول ہونے پر آپ کے آلے کی کارکردگی یا عمل میں خلل ڈال سکتی ہیں۔"</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"سبسکرائب کردہ فیڈز پڑھیں"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"‏ایپ کو \"systemExempted\" کی قسم کے ساتھ پیش منظر کی سروسز کے استعمال کی اجازت دیتی ہے"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"‏\"fileManagement\" کی قسم کے ساتھ پیش منظر کی سروس چلائیں"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"‏ایپ کو \"fileManagement\" کی قسم کے ساتھ پیش منظر کی سروسز کے استعمال کی اجازت دیتی ہے"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"‏\"mediaProcessing\" کی قسم کے ساتھ پیش منظر کی سروس چلائیں"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"‏ایپ کو \"mediaProcessing\" کی قسم کے ساتھ پیش منظر کی سروسز کے استعمال کی اجازت دیتی ہے"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"‏\"specialUse\" کی قسم کے ساتھ پیش منظر کی سروس چلائیں"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"‏ایپ کو \"specialUse\" کی قسم کے ساتھ پیش منظر کی سروسز کے استعمال کی اجازت دیتی ہے"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"ایپ اسٹوریج کی جگہ کی پیمائش کریں"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"اسکرین لاک استعمال کریں"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"جاری رکھنے کے لیے اپنا اسکرین لاک درج کریں"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"سینسر پر اچھی طرح دبائیں"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"فنگر پرنٹ کی شناخت نہیں کی جا سکی۔ دوبارہ کوشش کریں۔"</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"فنگر پرنٹ کی شناخت نہیں ہو سکی۔ دوبارہ کوشش کریں۔"</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"فنگر پرنٹ سینسر صاف کریں اور دوبارہ کوشش کریں"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"سینسر صاف کریں اور دوبارہ کوشش کریں"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"سینسر پر اچھی طرح دبائیں"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"آپ کا چہرہ دکھائی نہیں دے رہا۔ اپنے فون کو آنکھ کی سطح پر پکڑیں۔"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"کافی حرکت ہو رہی ہے۔ فون کو مضبوطی سے پکڑیں۔"</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"براہ کرم اپنے چہرے کو دوبارہ مندرج کریں۔"</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"چہرے کی شناخت نہیں ہو سکی۔ پھر کوشش کریں۔"</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"چہرے کی شناخت نہیں ہو سکی۔ دوبارہ کوشش کریں۔"</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"اپنے سر کی پوزیشن کو تھوڑا تبدیل کریں"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"اپنے فون کی طرف چہرے کو سیدھا رکھیں"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"اپنے فون کی طرف چہرے کو سیدھا رکھیں"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 23ef54c..8b52c36 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Qabul qilingan aholini ogohlantirish xabarlarini shu holicha uzatish uchun ilovani aholini ogohlantirish moduliga bogʻlash imkonini beradi. Ilovaga ayrim mamlakatlarda aholini favqulodda vaziyatlarda ogohlantirish uchun yuboriladigan tarqatma xabarlarni oʻqish uchun ruxsat beradi. Zararli dasturlar bunday xabarlar kelayotgan qurilmaning ishlashiga xalaqit qilishi mumkin."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Amaldagi chaqiruvlarni boshqarish"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Ilovaga qurilmangizdagi amaldagi chaqiruv tafsilotlarini koʻrish va uni boshqarish huquqini beradi."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"uyali tarmoq operatori xabarlarini o‘qish"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Ilovaga qurilmangiz tomonidan qabul qilingan uyali tarmoq operatori xabarlarini o‘qish uchun ruxsat beradi. Uyali tarmoq operatorining ogohlantiruvchi xabarlari ba’zi manzillarga favqulodda holatlar haqida ogohlantirish uchun jo‘natiladi. Zararli ilovalar uyali tarmoq orqali favqulodda xabar qabul qilinganda qurilmangizning ish faoliyati yoki amallariga xalaqit qilishi mumkin"</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"obunalarni o‘qish"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Ilovaga “systemExempted” turidagi faol xizmatlardan foydalanishga ruxsat beradi"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"“fileManagement” turidagi faol xizmatni ishga tushirish"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Ilovaga “fileManagement” turidagi faol xizmatlardan foydalanishga ruxsat beradi"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"“mediaProcessing” turidagi faol xizmatni ishga tushirish"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Ilovaga “mediaProcessing” turidagi faol xizmatlardan foydalanishga ruxsat beradi"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"“specialUse” turidagi faol xizmatni ishga tushirish"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Ilovaga “specialUse” turidagi faol xizmatlardan foydalanishga ruxsat beradi"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"ilovalar egallagan xotira joyini hisoblash"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Ekran qulfi"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Ekran qulfini kiritish bilan davom eting"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Sensorni mahkam bosing"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Bu barmoq izi notanish. Qayta urining."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Barmoq izi aniqlanmadi. Qayta urining."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Barmoq izi skanerini tozalang va qayta urining"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Sensorni tozalang va qayta urining"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Sensorni mahkam bosing"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Yuz koʻrinmayapti. Telefonni koʻz darajasida tuting."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Ortiqcha harakatlanmoqda. Qimirlatmasdan ushlang."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Yuzingizni qaytadan qayd qildiring."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Yuz aniqlanmadi. Qayta urining."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Yuz aniqlanmadi. Qayta urining."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Boshingiz holatini biroz oʻzgartiring"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Telefonga tik qarab turing"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Telefonga tik qarab turing"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 22d9f06..3149c18 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Cho phép ứng dụng liên kết với mô-đun truyền phát trên di động để chuyển tiếp tin nhắn truyền phát trên di động ngay khi nhận được. Ở một số vị trí, thông báo truyền phát trên di động sẽ được gửi nhằm cảnh báo cho bạn về các tình huống khẩn cấp. Các ứng dụng độc hại có thể ảnh hưởng đến hiệu suất hoặc hoạt động của thiết bị khi nhận được tin nhắn truyền phát trên di động lúc khẩn cấp."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Quản lý cuộc gọi đang diễn ra"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Cho phép một ứng dụng xem thông tin chi tiết về các cuộc gọi đang diễn ra trên thiết bị và kiểm soát các cuộc gọi này."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"đọc tin nhắn quảng bá"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Cho phép ứng dụng đọc tin nhắn quảng bá mà thiết bị của bạn nhận được. Tin nhắn quảng bá cảnh báo được gửi ở một số địa điểm nhằm cảnh báo cho bạn về các tình huống khẩn cấp. Các ứng dụng độc hại có thể gây ảnh hưởng đến hiệu suất hoặc hoạt động của thiết bị của bạn khi nhận được tin nhắn quảng bá khẩn cấp."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"đọc nguồn cấp dữ liệu đã đăng ký"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Cho phép ứng dụng dùng các dịch vụ trên nền trước thuộc loại \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"chạy dịch vụ trên nền trước thuộc loại \"fileManagement\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Cho phép ứng dụng dùng các dịch vụ trên nền trước thuộc loại \"fileManagement\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"chạy dịch vụ trên nền trước thuộc loại \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Cho phép ứng dụng dùng các dịch vụ trên nền trước thuộc loại \"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"chạy dịch vụ trên nền trước thuộc loại \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Cho phép ứng dụng dùng các dịch vụ trên nền trước thuộc loại \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"đo dung lượng lưu trữ ứng dụng"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Dùng phương thức khóa màn hình"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Hãy nhập phương thức khóa màn hình của bạn để tiếp tục"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Ấn mạnh lên cảm biến"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Không nhận dạng được vân tay. Hãy thử lại."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Không nhận dạng được vân tay. Hãy thử lại."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Hãy vệ sinh cảm biến vân tay rồi thử lại"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Vệ sinh cảm biến rồi thử lại"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Ấn mạnh lên cảm biến"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Không thấy khuôn mặt. Hãy cầm điện thoại ngang tầm mắt."</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Thiết bị di chuyển quá nhiều. Giữ yên thiết bị."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Vui lòng đăng ký lại khuôn mặt của bạn."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Không thể nhận dạng khuôn mặt. Hãy thử lại."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Không nhận dạng được khuôn mặt. Hãy thử lại."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Nghiêng đầu của bạn một chút"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Nhìn thẳng vào điện thoại"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Nhìn thẳng vào điện thoại"</string>
diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml
index af30532..31acd9a 100644
--- a/core/res/res/values-watch/config.xml
+++ b/core/res/res/values-watch/config.xml
@@ -90,4 +90,7 @@
          {@link MotionEvent#AXIS_SCROLL} generated by {@link InputDevice#SOURCE_ROTARY_ENCODER}
          devices. -->
     <bool name="config_viewRotaryEncoderHapticScrollFedbackEnabled">true</bool>
+
+    <!-- If this is true, allow wake from theater mode from motion. -->
+    <bool name="config_allowTheaterModeWakeFromMotion">true</bool>
 </resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index dec4705..168a50e 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"允许应用绑定到小区广播模块,以便及时转发收到的小区广播消息。小区广播消息是在某些地区发送的、用于发布紧急情况警告的提醒信息。恶意应用可能会在您的设备收到紧急小区广播时干扰设备的性能或操作。"</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"管理正在进行的通话"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"允许应用查看有关设备上正在进行的通话的详细信息并控制这些通话。"</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"读取小区广播消息"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"允许应用读取您的设备收到的小区广播消息。小区广播消息是在某些地区发送的、用于发布紧急情况警告的提醒信息。恶意应用可能会在您收到小区紧急广播时干扰您设备的性能或操作。"</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"读取订阅的供稿"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"允许该应用使用“systemExempted”类型的前台服务"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"运行“fileManagement”类型的前台服务"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"允许该应用使用“fileManagement”类型的前台服务"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"运行“mediaProcessing”类型的前台服务"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"允许该应用使用“mediaProcessing”类型的前台服务"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"运行“specialUse”类型的前台服务"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"允许该应用使用“specialUse”类型的前台服务"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"计算应用存储空间"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"使用屏幕锁定凭据"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"输入您的屏幕锁定凭据才能继续"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"请用力按住传感器"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"无法识别指纹,请重试。"</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"无法识别指纹。请重试。"</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"请清洁指纹传感器,然后重试"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"请清洁传感器,然后重试"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"请用力按住传感器"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"看不到您的脸,请将手机举到与眼睛齐平的位置。"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"摄像头过于晃动。请将手机拿稳。"</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"请重新注册您的面孔。"</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"无法识别人脸,请重试。"</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"无法识别面孔。请重试。"</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"请略微调整头部的位置"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"请尽量直视手机"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"请尽量直视手机"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 769c274..c6c0997 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"允許應用程式繫結至區域廣播模組,以在收到區域廣播訊息時轉寄訊息。在某些地點,系統會發出區域廣播通知,提示你有緊急狀況發生。惡意應用程式可能會在裝置收到緊急區域廣播時,干擾裝置的效能或運作。"</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"管理正在進行的通話"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"允許應用程式查看裝置上正在進行的通話詳情並控制通話。"</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"讀取區域廣播訊息"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"允許應用程式讀取你裝置接收的區域廣播訊息。某些地點會發出區域廣播警報,警告你發生緊急狀況。惡意應用程式可能會在裝置收到緊急區域廣播時,干擾裝置的性能或運作。"</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"讀取訂閱的資訊提供"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"允許應用程式配搭「systemExempted」類型使用前景服務"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"配搭「fileManagement」類型執行前景服務"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"允許應用程式配搭「fileManagement」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"執行「mediaProcessing」類型的前景服務"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"允許應用程式使用「mediaProcessing」類型的前景服務"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"配搭「specialUse」類型執行前景服務"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"允許應用程式配搭「specialUse」類型使用前景服務"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"測量應用程式儲存空間"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"使用螢幕鎖定"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"如要繼續操作,請輸入螢幕鎖定解鎖憑證"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"請用力按住感應器"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"無法辨識指紋,請再試一次。"</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"無法辨識指紋,請再試一次。"</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"請清潔指紋感應器,然後再試一次"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"請清潔感應器,然後再試一次"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"請用力按住感應器"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"看不到面孔,請將手機放在視線水平。"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"裝置不夠穩定。請拿穩手機。"</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"請重新註冊面孔。"</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"無法辨識面孔,請再試一次。"</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"無法辨識面孔,請再試一次。"</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"請稍為轉換頭部的位置"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"正面望向手機"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"正面望向手機"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 2c8c046..3aa79c4 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"允許應用程式繫結至區域廣播模組,以便轉送收到的區域廣播訊息。某些地點會發出區域廣播警示,警告你有緊急狀況發生。請注意,惡意應用程式可能會在裝置收到緊急區域廣播時,干擾裝置的效能或運作。"</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"管理進行中的通話"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"允許應用程式查看裝置上進行中通話的詳細資料,並控制這些通話。"</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"讀取區域廣播訊息"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"允許應用程式讀取你裝置收到的區域廣播訊息。某些地點會發出區域廣播警示,警告你有緊急狀況發生。請注意,惡意應用程式可能會在裝置收到緊急區域廣播時,干擾裝置的效能或運作。"</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"讀取訂閱資訊提供"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"允許應用程式搭配「systemExempted」類型使用前景服務"</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"搭配「fileManagement」類型執行前景服務"</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"允許應用程式搭配「fileManagement」類型使用前景服務"</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"執行「mediaProcessing」類型的前景服務"</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"允許應用程式使用「mediaProcessing」類型的前景服務"</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"搭配「specialUse」類型執行前景服務"</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"允許應用程式搭配「specialUse」類型使用前景服務"</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"測量應用程式儲存空間"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"使用螢幕鎖定功能"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"如要繼續操作,請輸入螢幕鎖定憑證"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"請確實按住感應器"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"無法辨識指紋,請再試一次。"</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"無法辨識指紋,請再試一次。"</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"請清潔指紋感應器,然後再試一次"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"清潔感應器,然後再試一次"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"請確實按住感應器"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"未偵測到你的臉,請將手機舉到與眼睛同高的位置。"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"鏡頭過度晃動,請拿穩手機。"</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"請重新註冊你的臉孔。"</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"無法辨識這張臉,請再試一次。"</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"無法辨識臉孔,請再試一次。"</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"請稍微改變頭部位置"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"請盡可能直視手機"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"請盡可能直視手機"</string>
@@ -1507,10 +1513,10 @@
     <string name="vpn_title_long" msgid="6834144390504619998">"<xliff:g id="APP">%s</xliff:g> 已啟用 VPN"</string>
     <string name="vpn_text" msgid="2275388920267251078">"輕觸一下即可管理網路。"</string>
     <string name="vpn_text_long" msgid="278540576806169831">"已連線至 <xliff:g id="SESSION">%s</xliff:g>,輕觸一下即可管理網路。"</string>
-    <string name="vpn_lockdown_connecting" msgid="6096725311950342607">"正在連線至永久連線的 VPN…"</string>
-    <string name="vpn_lockdown_connected" msgid="2853127976590658469">"已連線至永久連線的 VPN"</string>
-    <string name="vpn_lockdown_disconnected" msgid="5573611651300764955">"已中斷連線至永久連線的 VPN"</string>
-    <string name="vpn_lockdown_error" msgid="4453048646854247947">"無法連上永久連線的 VPN"</string>
+    <string name="vpn_lockdown_connecting" msgid="6096725311950342607">"正在連線至永久連線 VPN…"</string>
+    <string name="vpn_lockdown_connected" msgid="2853127976590658469">"已連線至永久連線 VPN"</string>
+    <string name="vpn_lockdown_disconnected" msgid="5573611651300764955">"已中斷連線至永久連線 VPN"</string>
+    <string name="vpn_lockdown_error" msgid="4453048646854247947">"無法連上永久連線 VPN"</string>
     <string name="vpn_lockdown_config" msgid="8331697329868252169">"變更網路或 VPN 設定"</string>
     <string name="upload_file" msgid="8651942222301634271">"選擇檔案"</string>
     <string name="no_file_chosen" msgid="4146295695162318057">"未選擇任何檔案"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 42f0b3f..c18b7d4 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -370,6 +370,10 @@
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Ivumela uhlelo lokusebenza ukuthi luboshezelwe kumojuli yokusakaza kweselula ukuze kudluliselwe imilayezo yokusakaza yeselula njengoba itholwa. Izexwayiso zokusakaza kweselula zilethwa kwezinye izindawo ukuze zikuxwayise ngezimo zesimo esiphuthumayo. Izinhlelo zokusebenza ezinobungozi zingaphazamisa ngokusebenza noma ukusetshenziswa kwedivayisi yakho uma ukusakaza kweselula kwesimo esiphuthumayo kwamukelwa."</string>
     <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Phatha amakholi aqhubekayo"</string>
     <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Ivumela uhlelo lokusebenza ukubona imininingwane emayelana namakholi aqhubekayo kudivayisi yakho nokulawula lamakholi."</string>
+    <!-- no translation found for permlab_accessLastKnownCellId (7638226620825665130) -->
+    <skip />
+    <!-- no translation found for permdesc_accessLastKnownCellId (6664621339249308857) -->
+    <skip />
     <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"funda imilayezo yokusakaza yeselula"</string>
     <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Ivumela uhlelo lokusebenza ukufunda imilayezo yokusakaza yeselula etholwe idivayisi yakho. Izaziso zokusakaza zeselula zilethwa kwezinye izindawo ukukuxwayisa ngezimo ezisheshayo. Izinhlelo zokusebenza ezingalungile zingaphazamisana nokusebenza noma umsebenzi wedivayisi yakho uma ukusakaza kweselula kwesimo esisheshayo kutholwa."</string>
     <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"funda izifunzo ezikhokhelwayo"</string>
@@ -434,6 +438,8 @@
     <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Kuvumela i-app ukusebenzisa amasevisi aphambili ngohlobo lokuthi \"systemExempted\""</string>
     <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"qalisa isevisi ephambili ngohlobo lokuthi \"Ikholi yefoni\""</string>
     <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Kuvumela i-app ukusebenzisa amasevisi aphambili ngohlobo lwe-\"systemExempted\""</string>
+    <string name="permlab_foregroundServiceMediaProcessing" msgid="3045295152245381864">"qalisa isevisi ephambili ngohlobo oluthi \"mediaProcessing\""</string>
+    <string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"Ivumela i-app ukusebenzisa amasevisi aphambili ngohlobo lwe-\"mediaProcessing\""</string>
     <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"qalisa isevisi ephambili ngohlobo lokuthi \"specialUse\""</string>
     <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Kuvumela i-app ukusebenzisa amasevisi aphambili ngohlobo lokuthi \"specialUse\""</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"linganisa isikhala sokugcina uhlelo lokusebenza"</string>
@@ -632,7 +638,7 @@
     <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Sebenzisa isikhiya sesikrini"</string>
     <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Faka ukukhiya isikrini kwakho ukuze uqhubeke"</string>
     <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Cindezela inzwa uqinise"</string>
-    <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Ayisazi isigxivizo somunwe. Zama futhi."</string>
+    <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Isigxivizo somunwe asaziwa. Zama futhi."</string>
     <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Hlanza inzwa yesigxivizo somunwe bese uzame futhi"</string>
     <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Hlanza inzwa bese uzame futhi"</string>
     <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Cindezela inzwa uqinise"</string>
@@ -696,7 +702,7 @@
     <string name="face_acquired_not_detected" msgid="1057966913397548150">"Ayikwazi ukubona ubuso bakho. Bamba ifoni yakho iqondane namehlo"</string>
     <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Ukunyakaza okuningi kakhulu. Bamba ifoni iqine."</string>
     <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Sicela uphinde ubhalise ubuso bakho."</string>
-    <string name="face_acquired_too_different" msgid="2520389515612972889">"Ayikwazi ukubona ubuso. Zama futhi."</string>
+    <string name="face_acquired_too_different" msgid="4505278456634706967">"Ubuso abaziwa. Zama futhi."</string>
     <string name="face_acquired_too_similar" msgid="8882920552674125694">"Shintsha indawo yekhanda lakho kancane"</string>
     <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Bheka ngqo kakhulu kufoni yakho"</string>
     <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Bheka ngqo kakhulu kufoni yakho"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5cfb1a3..2eb28eb 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4372,6 +4372,14 @@
         <attr name="name" />
     </declare-styleable>
 
+    <!-- Specify one or more <code>polling-loop-filter</code> elements inside a
+         <code>host-apdu-service</code> to indicate polling loop frames that
+         your service can handle. -->
+    <declare-styleable name="PollingLoopFilter">
+        <!-- The polling loop frame. This attribute is mandatory. -->
+        <attr name="name" />
+    </declare-styleable>
+
     <!-- Use <code>host-nfcf-service</code> as the root tag of the XML resource that
          describes an {@link android.nfc.cardemulation.HostNfcFService} service, which
          is referenced from its {@link android.nfc.cardemulation.HostNfcFService#SERVICE_META_DATA}
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 8fae6db..6884fc0 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -506,6 +506,12 @@
          receivers, and providers; it can not be used with activities. -->
     <attr name="singleUser" format="boolean" />
 
+    <!-- If set to true, only a single instance of this component will
+    run and be available for the SYSTEM user. Non SYSTEM users will not be
+    allowed to access the component if this flag is enabled.
+    This flag can be used with services, receivers, providers and activities. -->
+    <attr name="systemUserOnly" format="boolean" />
+
     <!-- Specify a specific process that the associated code is to run in.
          Use with the application tag (to supply a default process for all
          application components), or with the activity, receiver, service,
@@ -1813,6 +1819,12 @@
 
     <attr name="allowUpdateOwnership" format="boolean" />
 
+    <!-- This attribute can be applied to any tag in the manifest. The system uses its value to
+         determine whether the element (e.g., a permission) should be enabled or disabled
+         depending on the state of feature flags.
+         @hide @SystemApi @FlaggedApi("android.content.res.manifest_flagging") -->
+    <attr name="featureFlag" format="string" />
+
     <!-- The <code>manifest</code> tag is the root of an
          <code>AndroidManifest.xml</code> file,
          describing the contents of an Android package (.apk) file.  One
@@ -2859,6 +2871,7 @@
              Context.createAttributionContext() using the first attribution tag
              contained here. -->
         <attr name="attributionTags" />
+        <attr name="systemUserOnly" format="boolean" />
     </declare-styleable>
 
     <!-- Attributes that can be supplied in an AndroidManifest.xml
@@ -3017,6 +3030,7 @@
              ignored when the process is bound into a shared isolated process by a client.
         -->
         <attr name="allowSharedIsolatedProcess" format="boolean" />
+        <attr name="systemUserOnly" format="boolean" />
     </declare-styleable>
 
     <!-- @hide The <code>apex-system-service</code> tag declares an apex system service
@@ -3144,7 +3158,7 @@
         <attr name="uiOptions" />
         <attr name="parentActivityName" />
         <attr name="singleUser" />
-        <!-- @hide This broadcast receiver or activity will only receive broadcasts for the
+        <!-- This broadcast receiver or activity will only receive broadcasts for the
              system user-->
         <attr name="systemUserOnly" format="boolean" />
         <attr name="persistableMode" />
@@ -3424,6 +3438,20 @@
     <!-- Attributes that can be supplied in an AndroidManifest.xml
          <code>data</code> tag, a child of the
          {@link #AndroidManifestIntentFilter intent-filter} tag, describing
+         a group matching rule consisting of one or more
+         {@link #AndroidManifestData data} tags that must all match.  This
+         tag can be specified multiple times to create multiple groups that
+         will be matched in the order they are defined. -->
+    <declare-styleable name="AndroidManifestUriRelativeFilterGroup"
+        parent="AndroidManifestIntentFilter">
+        <!-- Specify if this group is allow rule or disallow rule.  If this
+             attribute is not specified then it is assumed to be true -->
+        <attr name="allow" format="boolean"/>
+    </declare-styleable>
+
+    <!-- Attributes that can be supplied in an AndroidManifest.xml
+         <code>data</code> tag, a child of the
+         {@link #AndroidManifestIntentFilter intent-filter} tag, describing
          the types of data that match.  This tag can be specified multiple
          times to supply multiple data options, as described in the
          {@link android.content.IntentFilter} class.  Note that all such
@@ -3431,7 +3459,8 @@
          <code>&lt;data android:scheme="myscheme" android:host="me.com" /&gt;</code>
          is equivalent to <code>&lt;data android:scheme="myscheme" /&gt;
          &lt;data android:host="me.com" /&gt;</code>. -->
-    <declare-styleable name="AndroidManifestData" parent="AndroidManifestIntentFilter">
+    <declare-styleable name="AndroidManifestData"
+        parent="AndroidManifestIntentFilter AndroidManifestUriRelativeFilterGroup">
         <!-- Specify a MIME type that is handled, as per
              {@link android.content.IntentFilter#addDataType
              IntentFilter.addDataType()}.
@@ -3535,6 +3564,70 @@
              IntentFilter.addDataPath()} with
              {@link android.os.PatternMatcher#PATTERN_SUFFIX}. -->
         <attr name="pathSuffix" />
+        <!-- Specify a URI query that must exactly match, as a
+             {@link android.content.UriRelativeFilter UriRelativeFilter} with
+             {@link android.os.PatternMatcher#PATTERN_LITERAL}. -->
+        <attr name="query" format="string" />
+        <!-- Specify a URI query that must be a prefix to match, as a
+             {@link android.content.UriRelativeFilter UriRelativeFilter} with
+             {@link android.os.PatternMatcher#PATTERN_PREFIX}. -->
+        <attr name="queryPrefix" format="string" />
+        <!-- Specify a URI query that matches a simple pattern, as a
+             {@link android.content.UriRelativeFilter UriRelativeFilter} with
+             {@link android.os.PatternMatcher#PATTERN_SIMPLE_GLOB}.
+             Note that because '\' is used as an escape character when
+             reading the string from XML (before it is parsed as a pattern),
+             you will need to double-escape: for example a literal "*" would
+             be written as "\\*" and a literal "\" would be written as
+             "\\\\".  This is basically the same as what you would need to
+             write if constructing the string in Java code. -->
+        <attr name="queryPattern" format="string" />
+        <!-- Specify a URI query that matches an advanced pattern, as a
+             {@link android.content.UriRelativeFilter UriRelativeFilter} with
+             {@link android.os.PatternMatcher#PATTERN_ADVANCED_GLOB}.
+             Note that because '\' is used as an escape character when
+             reading the string from XML (before it is parsed as a pattern),
+             you will need to double-escape: for example a literal "*" would
+             be written as "\\*" and a literal "\" would be written as
+             "\\\\".  This is basically the same as what you would need to
+             write if constructing the string in Java code. -->
+        <attr name="queryAdvancedPattern" format="string" />
+        <!-- Specify a URI query that must be a suffix to match, as a
+             {@link android.content.UriRelativeFilter UriRelativeFilter} with
+             {@link android.os.PatternMatcher#PATTERN_SUFFIX}. -->
+        <attr name="querySuffix" format="string" />
+        <!-- Specify a URI fragment that must exactly match, as a
+             {@link android.content.UriRelativeFilter UriRelativeFilter} with
+             {@link android.os.PatternMatcher#PATTERN_LITERAL}. -->
+        <attr name="fragment" format="string" />
+        <!-- Specify a URI fragment that must be a prefix to match, as a
+             {@link android.content.UriRelativeFilter UriRelativeFilter} with
+             {@link android.os.PatternMatcher#PATTERN_PREFIX}. -->
+        <attr name="fragmentPrefix" format="string" />
+        <!-- Specify a URI fragment that matches a simple pattern, as a
+             {@link android.content.UriRelativeFilter UriRelativeFilter} with
+             {@link android.os.PatternMatcher#PATTERN_SIMPLE_GLOB}.
+             Note that because '\' is used as an escape character when
+             reading the string from XML (before it is parsed as a pattern),
+             you will need to double-escape: for example a literal "*" would
+             be written as "\\*" and a literal "\" would be written as
+             "\\\\".  This is basically the same as what you would need to
+             write if constructing the string in Java code. -->
+        <attr name="fragmentPattern" format="string" />
+        <!-- Specify a URI fragment that matches an advanced pattern, as a
+             {@link android.content.UriRelativeFilter UriRelativeFilter} with
+             {@link android.os.PatternMatcher#PATTERN_ADVANCED_GLOB}.
+             Note that because '\' is used as an escape character when
+             reading the string from XML (before it is parsed as a pattern),
+             you will need to double-escape: for example a literal "*" would
+             be written as "\\*" and a literal "\" would be written as
+             "\\\\".  This is basically the same as what you would need to
+             write if constructing the string in Java code. -->
+        <attr name="fragmentAdvancedPattern" format="string" />
+        <!-- Specify a URI fragment that must be a suffix to match, as a
+             {@link android.content.UriRelativeFilter UriRelativeFilter} with
+             {@link android.os.PatternMatcher#PATTERN_SUFFIX}. -->
+        <attr name="fragmentSuffix" format="string" />
     </declare-styleable>
 
     <!-- Attributes that can be supplied in an AndroidManifest.xml
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 806be94..ebb0d34 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -57,6 +57,7 @@
         <item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_ethernet</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_oem_satellite</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_wifi</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_mobile</xliff:g></item>
@@ -102,6 +103,7 @@
     <string translatable="false" name="status_bar_call_strength">call_strength</string>
     <string translatable="false" name="status_bar_sensors_off">sensors_off</string>
     <string translatable="false" name="status_bar_screen_record">screen_record</string>
+    <string translatable="false" name="status_bar_oem_satellite">satellite</string>
 
     <!-- Flag indicating whether the surface flinger has limited
          alpha compositing functionality in hardware.  If set, the window
@@ -2349,6 +2351,8 @@
     <string name="config_defaultCallRedirection" translatable="false"></string>
     <!-- The name of the package that will hold the call screening role by default. -->
     <string name="config_defaultCallScreening" translatable="false"></string>
+    <!-- The name of the package that will hold the wallet role by default. -->
+    <string name="config_defaultWallet" translatable="false" />
     <!-- The name of the package that will hold the system gallery role. -->
     <string name="config_systemGallery" translatable="false">com.android.gallery3d</string>
     <!-- The names of the packages that will hold the automotive projection role. -->
@@ -4493,6 +4497,16 @@
     <!-- URI for default Accessibility notification sound when to enable accessibility shortcut. -->
     <string name="config_defaultAccessibilityNotificationSound" translatable="false"></string>
 
+    <!-- Array of component names, each flattened to a string, for accessibility services that
+         can be enabled by the user without showing a warning prompt. These services must be
+         preinstalled. -->
+    <string-array translatable="false" name="config_trustedAccessibilityServices">
+        <!--
+        <item>com.example.package.first/com.example.class.FirstService</item>
+        <item>com.example.package.second/com.example.class.SecondService</item>
+        -->
+    </string-array>
+
     <!-- Warning: This API can be dangerous when not implemented properly. In particular,
          escrow token must NOT be retrievable from device storage. In other words, either
          escrow token is not stored on device or its ciphertext is stored on device while
@@ -5332,20 +5346,19 @@
      and a second time clipped to the fill level to indicate charge -->
     <bool name="config_batterymeterDualTone">false</bool>
 
-    <!-- The default refresh rate for a given device. Change this value to set a higher default
-         refresh rate. If the hardware composer on the device supports display modes with a higher
-         refresh rate than the default value specified here, the framework may use those higher
-         refresh rate modes if an app chooses one by setting preferredDisplayModeId or calling
-         setFrameRate().
-         If a non-zero value is set for config_defaultPeakRefreshRate, then
-         config_defaultRefreshRate may be set to 0, in which case the value set for
-         config_defaultPeakRefreshRate will act as the default frame rate. -->
-    <integer name="config_defaultRefreshRate">60</integer>
+    <!-- The default refresh rate for a given device. This value is used to set the
+         global refresh rate vote, and when set to zero it has no effect on the vote.
+         If this value is non-zero but the hardware composer on the device supports
+         display modes with higher refresh rates, the framework may use those higher
+         refresh rate modes if an app chooses one by setting preferredDisplayModeId
+         or calling setFrameRate().-->
+    <integer name="config_defaultRefreshRate">0</integer>
 
-    <!-- The default peak refresh rate for a given device. Change this value if you want to prevent
-         the framework from using higher refresh rates, even if display modes with higher refresh
-         rates are available from hardware composer. Only has an effect if the value is
-         non-zero. -->
+    <!-- The default peak refresh rate for a given device. This value is used to set the
+         global peak refresh rate vote, and when set to zero it has no effect on the vote.
+         Change this value to non-zero if you want to prevent the framework from using higher
+         refresh rates, even if display modes with higher refresh rates are available from
+         hardware composer. -->
     <integer name="config_defaultPeakRefreshRate">0</integer>
 
     <!-- External display peak refresh rate for the given device. Change this value if you want to
@@ -6598,7 +6611,7 @@
     </string-array>
 
     <!-- Whether or not the monitoring on the apps' background battery drain is enabled -->
-    <bool name="config_bg_current_drain_monitor_enabled">true</bool>
+    <bool name="config_bg_current_drain_monitor_enabled">false</bool>
 
     <!-- The threshold of the background current drain (in percentage) to the restricted
          standby bucket.
@@ -6888,4 +6901,14 @@
     <!-- Defines suitability of the built-in speaker route.
          Refer to {@link MediaRoute2Info} to see supported values.  -->
     <integer name="config_mediaRouter_builtInSpeakerSuitability">0</integer>
+
+    <!-- Whether to show a percentage text next to the progressbar while preparing to update the
+         device -->
+    <bool name="config_showPercentageTextDuringRebootToUpdate">true</bool>
+
+    <!-- Defines the minimum interval (in ms) between two input-based user-activity poke events. -->
+    <integer name="config_minMillisBetweenInputUserActivityEvents">100</integer>
+
+    <!-- Name of the starting activity for DisplayCompat host. specific to automotive.-->
+    <string name="config_defaultDisplayCompatHostActivity" translatable="false"></string>
 </resources>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 5346454..d0216b30 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -192,8 +192,13 @@
 
     <!-- The identifier of the satellite's SIM profile. The identifier is composed of MCC and MNC
          of the satellite PLMN with the format "mccmnc". -->
-    <string name="config_satellite_sim_identifier" translatable="false"></string>
-    <java-symbol type="string" name="config_satellite_sim_identifier" />
+    <string name="config_satellite_sim_plmn_identifier" translatable="false"></string>
+    <java-symbol type="string" name="config_satellite_sim_plmn_identifier" />
+
+    <!-- The identifier for the satellite's SIM profile. The identifier is the service provider name
+    (spn) from the profile metadata. -->
+    <string name="config_satellite_sim_spn_identifier" translatable="false"></string>
+    <java-symbol type="string" name="config_satellite_sim_spn_identifier" />
 
     <!-- The app to which the emergency call will be handed over for OEM-enabled satellite
          messaging. The format of the config string is "package_name;class_name". -->
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 17bb86a..830e99c 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -119,6 +119,30 @@
     <public name="optional"/>
     <!-- @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") -->
     <public name="adServiceTypes" />
+    <!-- @hide @SystemApi @FlaggedApi("android.content.res.manifest_flagging") -->
+    <public name="featureFlag"/>
+    <!-- @FlaggedApi("android.multiuser.enable_system_user_only_for_services_and_providers") -->
+    <public name="systemUserOnly"/>
+    <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+    <public name="allow"/>
+    <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+    <public name="query"/>
+    <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+    <public name="queryPrefix"/>
+    <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+    <public name="queryPattern"/>
+    <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+    <public name="queryAdvancedPattern"/>
+    <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+    <public name="querySuffix"/>
+    <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+    <public name="fragmentPrefix"/>
+    <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+    <public name="fragmentPattern"/>
+    <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+    <public name="fragmentAdvancedPattern"/>
+    <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+    <public name="fragmentSuffix"/>
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01bc0000">
@@ -130,6 +154,8 @@
   <staging-public-group type="string" first-id="0x01ba0000">
     <!-- @hide @SystemApi @FlaggedApi("android.permission.flags.retail_demo_role_enabled") -->
     <public name="config_defaultRetailDemo" />
+    <!-- @hide @SystemApi @FlaggedApi("android.permission.flags.wallet_role_enabled") -->
+    <public name="config_defaultWallet" />
   </staging-public-group>
 
   <staging-public-group type="dimen" first-id="0x01b90000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 542e9d6..558bae7 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1075,6 +1075,12 @@
     <string name="permdesc_manageOngoingCalls">Allows an app to see details about ongoing calls
          on your device and to control these calls.</string>
 
+    <!-- Title for an application permission, listed so that the user can access the last known cell id provided by telephony. -->
+    <string name="permlab_accessLastKnownCellId">Access last known cell identity.</string>
+    <!-- Description on an application permission, listed so that the user can access the last known cell id provided by telephony -->
+    <string name="permdesc_accessLastKnownCellId">Allows an app to access to the last known
+        cell identity provided by telephony.</string>
+
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_readCellBroadcasts">read cell broadcast messages</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -1825,7 +1831,7 @@
     <!-- Message shown during fingerprint acquisision when the fingerprint cannot be recognized -->
     <string name="fingerprint_acquired_partial">Press firmly on the sensor</string>
     <!-- Message shown during fingerprint acquisision when the fingerprint cannot be recognized -->
-    <string name="fingerprint_acquired_insufficient">Can\u2019t recognize fingerprint. Try again.</string>
+    <string name="fingerprint_acquired_insufficient">Fingerprint not recognized. Try again.</string>
     <!-- Message shown during fingerprint acquisision when the fingerprint sensor needs cleaning -->
     <string name="fingerprint_acquired_imager_dirty">Clean fingerprint sensor and try again</string>
     <string name="fingerprint_acquired_imager_dirty_alt">Clean sensor and try again</string>
@@ -1959,7 +1965,7 @@
     <!-- Message shown during face acquisition when the sensor needs to be recalibrated [CHAR LIMIT=50] -->
     <string name="face_acquired_recalibrate">Please re-enroll your face.</string>
     <!-- Message shown during face enrollment when a different person's face is detected [CHAR LIMIT=50] -->
-    <string name="face_acquired_too_different">Can\u2019t recognize face. Try again.</string>
+    <string name="face_acquired_too_different">Face not recognized. Try again.</string>
     <!-- Message shown during face enrollment when the face is too similar to a previous acquisition [CHAR LIMIT=50] -->
     <string name="face_acquired_too_similar">Change the position of your head slightly</string>
     <!-- Message shown during acqusition when the user's face is turned too far left or right [CHAR LIMIT=50] -->
@@ -5301,6 +5307,12 @@
     <!-- Content description of the expand button icon in the notification when expanded.-->
     <string name="expand_button_content_description_expanded">Collapse</string>
 
+    <!-- A11y announcement when a view is collapsed. -->
+    <string name="content_description_collapsed">Collapsed</string>
+
+    <!-- A11y announcement when a view is expanded. -->
+    <string name="content_description_expanded">Expanded</string>
+
     <!-- Accessibility action description on the expand button. -->
     <string name="expand_action_accessibility">toggle expansion</string>
 
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 619ec31..22d028c 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1515,6 +1515,11 @@
         <item name="background">@drawable/btn_outlined</item>
     </style>
 
+    <!-- @hide Divider for Autofill half screen dialog -->
+    <style name="AutofillHalfSheetDivider">
+        <item name="android:background">@drawable/autofill_half_sheet_divider</item>
+    </style>
+
     <!-- @hide Autofill background for popup window (not for fullscreen) -->
     <style name="AutofillDatasetPicker">
         <item name="elevation">4dp</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d12ef2b..dc2f74b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3198,6 +3198,7 @@
   <java-symbol type="string" name="status_bar_camera" />
   <java-symbol type="string" name="status_bar_sensors_off" />
   <java-symbol type="string" name="status_bar_screen_record" />
+  <java-symbol type="string" name="status_bar_oem_satellite" />
 
   <!-- Locale picker -->
   <java-symbol type="id" name="locale_search_menu" />
@@ -3629,6 +3630,7 @@
   <java-symbol type="string" name="config_defaultAccessibilityService" />
   <java-symbol type="string" name="config_defaultAccessibilityNotificationSound" />
   <java-symbol type="string" name="accessibility_shortcut_spoken_feedback" />
+  <java-symbol type="array" name="config_trustedAccessibilityServices" />
 
   <java-symbol type="string" name="accessibility_select_shortcut_menu_title" />
   <java-symbol type="string" name="accessibility_edit_shortcut_menu_button_title" />
@@ -3702,6 +3704,10 @@
   <java-symbol type="id" name="autofill_dataset_list"/>
   <java-symbol type="id" name="autofill_dataset_picker"/>
   <java-symbol type="id" name="autofill_dataset_title" />
+  <java-symbol type="id" name="autofill_sheet_divider"/>
+  <java-symbol type="id" name="autofill_sheet_scroll_view"/>
+  <java-symbol type="id" name="autofill_sheet_scroll_view_space"/>
+
   <java-symbol type="id" name="autofill_save_custom_subtitle" />
   <java-symbol type="id" name="autofill_save_icon" />
   <java-symbol type="id" name="autofill_save_no" />
@@ -3820,6 +3826,9 @@
   <java-symbol type="string" name="expand_button_content_description_collapsed" />
   <java-symbol type="string" name="expand_button_content_description_expanded" />
 
+  <java-symbol type="string" name="content_description_collapsed" />
+  <java-symbol type="string" name="content_description_expanded" />
+
   <!-- Colon separated list of package names that should be granted Notification Listener access -->
   <java-symbol type="string" name="config_defaultListenerAccessPackages" />
 
@@ -5311,4 +5320,9 @@
 
   <!-- Android MediaRouter framework configs. -->
   <java-symbol type="integer" name="config_mediaRouter_builtInSpeakerSuitability" />
+
+  <!-- Shutdown thread config flags -->
+  <java-symbol type="bool" name="config_showPercentageTextDuringRebootToUpdate" />
+
+  <java-symbol type="integer" name="config_minMillisBetweenInputUserActivityEvents" />
 </resources>
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 3a2e50a..9bb2499 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -34,7 +34,7 @@
          http://smscoin.net/software/engine/WordPress/Paid+SMS-registration/ -->
 
     <!-- Arab Emirates -->
-    <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214" />
+    <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214|6253" />
 
     <!-- Albania: 5 digits, known short codes listed -->
     <shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" />
@@ -86,7 +86,7 @@
     <shortcode country="cn" premium="1066.*" free="1065.*" />
 
     <!-- Colombia: 1-6 digits (not confirmed) -->
-    <shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960|899948|87739|85517" />
+    <shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960|899948|87739|85517|491289" />
 
     <!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU -->
     <shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" />
@@ -104,6 +104,12 @@
     <!-- Denmark: see http://iprs.webspacecommerce.com/Denmark-Premium-Rate-Numbers -->
     <shortcode country="dk" pattern="\\d{4,5}" premium="1\\d{3}" free="116\\d{3}|4665" />
 
+    <!-- Dominican Republic: 1-6 digits (standard system default, not country specific) -->
+    <shortcode country="do" pattern="\\d{1,6}" free="912892" />
+
+    <!-- Ecuador: 1-6 digits (standard system default, not country specific) -->
+    <shortcode country="ec" pattern="\\d{1,6}" free="466453" />
+
     <!-- Estonia: short codes 3-5 digits starting with 1, plus premium 7 digit numbers starting with 90, plus EU.
          http://www.tja.ee/public/documents/Elektrooniline_side/Oigusaktid/ENG/Estonian_Numbering_Plan_annex_06_09_2010.mht -->
     <shortcode country="ee" pattern="1\\d{2,4}" premium="90\\d{5}|15330|1701[0-3]" free="116\\d{3}|95034" />
@@ -154,8 +160,8 @@
          http://www.comreg.ie/_fileupload/publications/ComReg1117.pdf -->
     <shortcode country="ie" pattern="\\d{5}" premium="5[3-9]\\d{3}" free="50\\d{3}|116\\d{3}" standard="5[12]\\d{3}" />
 
-    <!-- Israel: 4 digits, known premium codes listed -->
-    <shortcode country="il" pattern="\\d{4}" premium="4422|4545" />
+    <!-- Israel: 1-5 digits, known premium codes listed -->
+    <shortcode country="il" pattern="\\d{1,5}" premium="4422|4545" free="37477|6681" />
 
     <!-- Italy: 5 digits (premium=41xxx,42xxx), plus EU:
          https://www.itu.int/dms_pub/itu-t/oth/02/02/T020200006B0001PDFE.pdf -->
@@ -193,11 +199,14 @@
     <shortcode country="mk" pattern="\\d{1,6}" free="129005|122" />
 
     <!-- Mexico: 4-5 digits (not confirmed), known premium codes listed -->
-    <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453" />
+    <shortcode country="mx" pattern="\\d{4,6}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453|550346" />
 
     <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf -->
     <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288|66668" />
 
+    <!-- Namibia: 1-5 digits (standard system default, not country specific) -->
+    <shortcode country="na" pattern="\\d{1,5}" free="40005" />
+
     <!-- The Netherlands, 4 digits, known premium codes listed, plus EU -->
     <shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}|2223|6225|2223|1662" />
 
diff --git a/core/tests/BroadcastRadioTests/TEST_MAPPING b/core/tests/BroadcastRadioTests/TEST_MAPPING
new file mode 100644
index 0000000..b085a27
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "postsubmit": [
+    {
+      "name": "BroadcastRadioTests"
+    }
+  ]
+}
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java
index b1cf9c2..bb69fa4 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java
@@ -124,6 +124,17 @@
     }
 
     @Test
+    public void construct_withNullInSecondaryIdsForSelector() {
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+            new ProgramSelector(DAB_PROGRAM_TYPE, DAB_DMB_SID_EXT_IDENTIFIER_1,
+                    new ProgramSelector.Identifier[]{null}, /* vendorIds= */ null);
+        });
+
+        assertWithMessage("Exception for null secondary id")
+                .that(thrown).hasMessageThat().contains("secondaryIds list must not contain nulls");
+    }
+
+    @Test
     public void getProgramType() {
         ProgramSelector selector = getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null);
 
@@ -269,11 +280,11 @@
 
         ProgramSelector selector = ProgramSelector.createAmFmSelector(band, (int) AM_FREQUENCY);
 
-        assertWithMessage("Program type")
+        assertWithMessage("AM program type without subchannel")
                 .that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_AM);
-        assertWithMessage("Primary identifiers")
+        assertWithMessage("AM primary identifiers without subchannel")
                 .that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
-        assertWithMessage("Secondary identifiers")
+        assertWithMessage("AM secondary identifiers without subchannel")
                 .that(selector.getSecondaryIds()).isEmpty();
     }
 
@@ -285,15 +296,29 @@
         ProgramSelector selector = ProgramSelector.createAmFmSelector(
                 RadioManager.BAND_INVALID, (int) FM_FREQUENCY);
 
-        assertWithMessage("Program type")
+        assertWithMessage("FM program type without band and subchannel")
                 .that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_FM);
-        assertWithMessage("Primary identifiers")
+        assertWithMessage("FM primary identifiers without band and subchannel")
                 .that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
-        assertWithMessage("Secondary identifiers")
+        assertWithMessage("FM secondary identifiers without band and subchannel")
                 .that(selector.getSecondaryIds()).isEmpty();
     }
 
     @Test
+    public void createAmFmSelector_withValidFrequency() {
+        ProgramSelector.Identifier primaryIdExpected = new ProgramSelector.Identifier(
+                ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, AM_FREQUENCY);
+
+        ProgramSelector selector = ProgramSelector.createAmFmSelector(RadioManager.BAND_INVALID,
+                (int) AM_FREQUENCY);
+
+        assertWithMessage("AM program type with valid frequency")
+                .that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_AM);
+        assertWithMessage("AM primary identifiers with valid frequency")
+                .that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
+    }
+
+    @Test
     public void createAmFmSelector_withValidFrequencyAndSubChannel() {
         int band = RadioManager.BAND_AM_HD;
         int subChannel = 2;
@@ -307,15 +332,26 @@
         ProgramSelector selector = ProgramSelector.createAmFmSelector(band, (int) AM_FREQUENCY,
                 subChannel);
 
-        assertWithMessage("Program type")
+        assertWithMessage("AM program type with valid frequency and subchannel")
                 .that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_AM);
-        assertWithMessage("Primary identifiers")
+        assertWithMessage("AM primary identifiers with valid frequency and subchannel")
                 .that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
-        assertWithMessage("Secondary identifiers")
+        assertWithMessage("AM secondary identifiers with valid frequency and subchannel")
                 .that(selector.getSecondaryIds()).isEqualTo(secondaryIdExpected);
     }
 
     @Test
+    public void createAmFmSelector_withInvalidBand_throwsIllegalArgumentException() {
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+            ProgramSelector.createAmFmSelector(/* band= */ 1000, (int) AM_FREQUENCY);
+        });
+
+        assertWithMessage("Exception for using invalid band")
+                .that(thrown).hasMessageThat().contains(
+                        "Unknown band");
+    }
+
+    @Test
     public void createAmFmSelector_withInvalidFrequency_throwsIllegalArgumentException() {
         int invalidFrequency = 50000;
 
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
index 89464d1..4f9b269 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -169,6 +170,16 @@
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Test
+    public void constructor_withUnsupportedTypeForBandDescriptor_throwsException() {
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+                () -> new RadioManager.AmBandDescriptor(REGION, /* type= */ 100, AM_LOWER_LIMIT,
+                        AM_UPPER_LIMIT, AM_SPACING, STEREO_SUPPORTED));
+
+        assertWithMessage("Unsupported band type exception")
+                .that(thrown).hasMessageThat().contains("Unsupported band");
+    }
+
+    @Test
     public void getType_forBandDescriptor() {
         RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor();
 
@@ -363,6 +374,18 @@
     }
 
     @Test
+    public void equals_withAmBandDescriptorsAndOtherTypeObject() {
+        assertWithMessage("AM Band Descriptor")
+                .that(AM_BAND_DESCRIPTOR).isNotEqualTo(FM_BAND_DESCRIPTOR);
+    }
+
+    @Test
+    public void equals_withFmBandDescriptorsAndOtherTypeObject() {
+        assertWithMessage("FM Band Descriptor")
+                .that(FM_BAND_DESCRIPTOR).isNotEqualTo(AM_BAND_DESCRIPTOR);
+    }
+
+    @Test
     public void equals_withAmBandDescriptorsOfDifferentUpperLimits_returnsFalse() {
         RadioManager.AmBandDescriptor amBandDescriptorCompared =
                 new RadioManager.AmBandDescriptor(REGION, RadioManager.BAND_AM, AM_LOWER_LIMIT,
@@ -373,9 +396,83 @@
     }
 
     @Test
-    public void equals_withAmAndFmBandDescriptors_returnsFalse() {
-        assertWithMessage("AM Band Descriptor")
-                .that(AM_BAND_DESCRIPTOR).isNotEqualTo(FM_BAND_DESCRIPTOR);
+    public void equals_withAmBandDescriptorsOfDifferentStereoSupportValues() {
+        RadioManager.AmBandDescriptor amBandDescriptorCompared =
+                new RadioManager.AmBandDescriptor(REGION, RadioManager.BAND_AM, AM_LOWER_LIMIT,
+                        AM_UPPER_LIMIT, AM_SPACING, !STEREO_SUPPORTED);
+
+        assertWithMessage("AM Band Descriptor of different stereo support values")
+                .that(AM_BAND_DESCRIPTOR).isNotEqualTo(amBandDescriptorCompared);
+    }
+
+    @Test
+    public void equals_withFmBandDescriptorsOfDifferentSpacingValues() {
+        RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+                REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING * 2,
+                STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED);
+
+        assertWithMessage("FM Band Descriptors of different support limit values")
+                .that(FM_BAND_DESCRIPTOR).isNotEqualTo(fmBandDescriptorCompared);
+    }
+
+    @Test
+    public void equals_withFmBandConfigsOfDifferentLowerLimitValues() {
+        RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+                REGION + 1, RadioManager.BAND_AM_HD, AM_LOWER_LIMIT, AM_UPPER_LIMIT, AM_SPACING,
+                STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED);
+
+        assertWithMessage("FM Band Descriptors of different region values")
+                .that(FM_BAND_DESCRIPTOR).isNotEqualTo(fmBandDescriptorCompared);
+    }
+
+    @Test
+    public void equals_withFmBandDescriptorsOfDifferentStereoSupportValues() {
+        RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+                REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+                !STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED);
+
+        assertWithMessage("FM Band Descriptors of different stereo support values")
+                .that(fmBandDescriptorCompared).isNotEqualTo(FM_BAND_DESCRIPTOR);
+    }
+
+    @Test
+    public void equals_withFmBandDescriptorsOfDifferentRdsSupportValues() {
+        RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+                REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+                STEREO_SUPPORTED, !RDS_SUPPORTED, TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED);
+
+        assertWithMessage("FM Band Descriptors of different rds support values")
+                .that(fmBandDescriptorCompared).isNotEqualTo(FM_BAND_DESCRIPTOR);
+    }
+
+    @Test
+    public void equals_withFmBandDescriptorsOfDifferentTaSupportValues() {
+        RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+                REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+                STEREO_SUPPORTED, RDS_SUPPORTED, !TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED);
+
+        assertWithMessage("FM Band Descriptors of different ta support values")
+                .that(fmBandDescriptorCompared).isNotEqualTo(FM_BAND_DESCRIPTOR);
+    }
+
+    @Test
+    public void equals_withFmBandDescriptorsOfDifferentAfSupportValues() {
+        RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+                REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+                STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, !AF_SUPPORTED, EA_SUPPORTED);
+
+        assertWithMessage("FM Band Descriptors of different af support values")
+                .that(fmBandDescriptorCompared).isNotEqualTo(FM_BAND_DESCRIPTOR);
+    }
+
+    @Test
+    public void equals_withFmBandDescriptorsOfDifferentEaSupportValues() {
+        RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+                REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+                STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, AF_SUPPORTED, !EA_SUPPORTED);
+
+        assertWithMessage("FM Band Descriptors of different ea support values")
+                .that(fmBandDescriptorCompared).isNotEqualTo(FM_BAND_DESCRIPTOR);
     }
 
     @Test
@@ -587,18 +684,79 @@
     }
 
     @Test
-    public void equals_withFmBandConfigsOfDifferentAfs_returnsFalse() {
-        RadioManager.FmBandConfig.Builder builder = new RadioManager.FmBandConfig.Builder(
-                createFmBandDescriptor()).setStereo(STEREO_SUPPORTED).setRds(RDS_SUPPORTED)
-                .setTa(TA_SUPPORTED).setAf(!AF_SUPPORTED).setEa(EA_SUPPORTED);
-        RadioManager.FmBandConfig fmBandConfigFromBuilder = builder.build();
+    public void equals_withFmBandConfigsOfDifferentRegionValues() {
+        RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+                new RadioManager.FmBandDescriptor(REGION + 1, RadioManager.BAND_AM_HD,
+                        AM_LOWER_LIMIT, AM_UPPER_LIMIT, AM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED,
+                        TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED));
 
-        assertWithMessage("FM Band Config of different af value")
-                .that(FM_BAND_CONFIG).isNotEqualTo(fmBandConfigFromBuilder);
+        assertWithMessage("FM Band Config of different regions")
+                .that(FM_BAND_CONFIG).isNotEqualTo(fmBandConfigCompared);
     }
 
     @Test
-    public void equals_withFmAndAmBandConfigs_returnsFalse() {
+    public void equals_withFmBandConfigsOfDifferentStereoSupportValues() {
+        RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+                new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT,
+                        FM_UPPER_LIMIT, FM_SPACING, !STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED,
+                        AF_SUPPORTED, EA_SUPPORTED));
+
+        assertWithMessage("FM Band Config with different stereo support values")
+                .that(fmBandConfigCompared).isNotEqualTo(FM_BAND_CONFIG);
+    }
+
+    @Test
+    public void equals_withFmBandConfigsOfDifferentRdsSupportValues() {
+        RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+                new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT,
+                        FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, !RDS_SUPPORTED, TA_SUPPORTED,
+                        AF_SUPPORTED, EA_SUPPORTED));
+
+        assertWithMessage("FM Band Config with different RDS support values")
+                .that(fmBandConfigCompared).isNotEqualTo(FM_BAND_CONFIG);
+    }
+
+    @Test
+    public void equals_withFmBandConfigsOfDifferentTaSupportValues() {
+        RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+                new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT,
+                        FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED, !TA_SUPPORTED,
+                        AF_SUPPORTED, EA_SUPPORTED));
+
+        assertWithMessage("FM Band Configs with different ta values")
+                .that(fmBandConfigCompared).isNotEqualTo(FM_BAND_CONFIG);
+    }
+
+    @Test
+    public void equals_withFmBandConfigsOfDifferentAfSupportValues() {
+        RadioManager.FmBandConfig.Builder builder = new RadioManager.FmBandConfig.Builder(
+                createFmBandDescriptor()).setStereo(STEREO_SUPPORTED).setRds(RDS_SUPPORTED)
+                .setTa(TA_SUPPORTED).setAf(!AF_SUPPORTED).setEa(EA_SUPPORTED);
+        RadioManager.FmBandConfig fmBandConfigCompared = builder.build();
+
+        assertWithMessage("FM Band Config of different af support value")
+                .that(FM_BAND_CONFIG).isNotEqualTo(fmBandConfigCompared);
+    }
+
+    @Test
+    public void equals_withFmBandConfigsOfDifferentEaSupportValues() {
+        RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+                new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT,
+                        FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED,
+                        AF_SUPPORTED, !EA_SUPPORTED));
+
+        assertWithMessage("FM Band Configs with different ea support values")
+                .that(fmBandConfigCompared).isNotEqualTo(FM_BAND_CONFIG);
+    }
+
+    @Test
+    public void equals_withAmBandConfigsAndOtherTypeObject() {
+        assertWithMessage("AM Band Config")
+                .that(AM_BAND_CONFIG).isNotEqualTo(FM_BAND_CONFIG);
+    }
+
+    @Test
+    public void equals_withFmBandConfigsAndOtherTypeObject() {
         assertWithMessage("FM Band Config")
                 .that(FM_BAND_CONFIG).isNotEqualTo(AM_BAND_CONFIG);
     }
@@ -1143,6 +1301,18 @@
     }
 
     @Test
+    public void addAnnouncementListener_withListenerAddedBeforeAndCloseException_throws()
+            throws Exception {
+        createRadioManager();
+        Set<Integer> enableTypeSet = createAnnouncementTypeSet(EVENT_ANNOUNCEMENT_TYPE);
+        mRadioManager.addAnnouncementListener(enableTypeSet, mEventListener);
+        doThrow(new RemoteException()).when(mCloseHandleMock).close();
+
+        assertThrows(RuntimeException.class,
+                () -> mRadioManager.addAnnouncementListener(enableTypeSet, mEventListener));
+    }
+
+    @Test
     public void addAnnouncementListener_whenServiceDied_throwException() throws Exception {
         createRadioManager();
         String exceptionMessage = "service is dead";
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
index 7b9121e..fddfd39 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
@@ -16,14 +16,20 @@
 
 package android.hardware.radio;
 
-import static com.google.common.truth.Truth.assertWithMessage;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 
 import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
 
 import android.graphics.Bitmap;
 import android.os.Parcel;
 import android.platform.test.flag.junit.SetFlagsRule;
 
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
+
+import com.google.common.truth.Expect;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -34,7 +40,7 @@
 import java.util.Set;
 
 @RunWith(MockitoJUnitRunner.class)
-public final class RadioMetadataTest {
+public final class RadioMetadataTest extends ExtendedRadioMockitoTestCase {
 
     private static final int CREATOR_ARRAY_SIZE = 3;
     private static final int INT_KEY_VALUE = 1;
@@ -49,14 +55,21 @@
     private Bitmap mBitmapValue;
 
     @Rule
+    public final Expect mExpect = Expect.create();
+    @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
+    @Override
+    protected void initializeSession(StaticMockitoSessionBuilder builder) {
+        builder.spyStatic(Bitmap.class);
+    }
+
     @Test
     public void describeContents_forClock() {
         RadioMetadata.Clock clock = new RadioMetadata.Clock(TEST_UTC_SECOND_SINCE_EPOCH,
                 TEST_TIME_ZONE_OFFSET_MINUTES);
 
-        assertWithMessage("Describe contents for metadata clock")
+        mExpect.withMessage("Describe contents for metadata clock")
                 .that(clock.describeContents()).isEqualTo(0);
     }
 
@@ -64,7 +77,7 @@
     public void newArray_forClockCreator() {
         RadioMetadata.Clock[] clocks = RadioMetadata.Clock.CREATOR.newArray(CREATOR_ARRAY_SIZE);
 
-        assertWithMessage("Clock array size").that(clocks.length).isEqualTo(CREATOR_ARRAY_SIZE);
+        mExpect.withMessage("Clock array size").that(clocks.length).isEqualTo(CREATOR_ARRAY_SIZE);
     }
 
     @Test
@@ -77,9 +90,9 @@
         parcel.setDataPosition(0);
 
         RadioMetadata.Clock clockFromParcel = RadioMetadata.Clock.CREATOR.createFromParcel(parcel);
-        assertWithMessage("UTC second since epoch of metadata clock created from parcel")
+        mExpect.withMessage("UTC second since epoch of metadata clock created from parcel")
                 .that(clockFromParcel.getUtcEpochSeconds()).isEqualTo(TEST_UTC_SECOND_SINCE_EPOCH);
-        assertWithMessage("Time zone offset minutes of metadata clock created from parcel")
+        mExpect.withMessage("Time zone offset minutes of metadata clock created from parcel")
                 .that(clockFromParcel.getTimezoneOffsetMinutes())
                 .isEqualTo(TEST_TIME_ZONE_OFFSET_MINUTES);
     }
@@ -92,7 +105,7 @@
             mBuilder.putString(invalidStringKey, "value");
         });
 
-        assertWithMessage("Exception for putting illegal string-value key %s", invalidStringKey)
+        mExpect.withMessage("Exception for putting illegal string-value key %s", invalidStringKey)
                 .that(thrown).hasMessageThat()
                 .matches(".*" + invalidStringKey + ".*cannot.*String.*?");
     }
@@ -105,12 +118,25 @@
             mBuilder.putInt(invalidIntKey, INT_KEY_VALUE);
         });
 
-        assertWithMessage("Exception for putting illegal int-value for key %s", invalidIntKey)
+        mExpect.withMessage("Exception for putting illegal int-value for key %s", invalidIntKey)
                 .that(thrown).hasMessageThat()
                 .matches(".*" + invalidIntKey + ".*cannot.*int.*?");
     }
 
     @Test
+    public void putBitmap_withIllegalKey() {
+        String invalidIntKey = RadioMetadata.METADATA_KEY_GENRE;
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+            mBuilder.putBitmap(invalidIntKey, mBitmapValue);
+        });
+
+        mExpect.withMessage("Exception for putting illegal bitmap-value for key %s", invalidIntKey)
+                .that(thrown).hasMessageThat()
+                .matches(".*" + invalidIntKey + ".*cannot.*Bitmap.*?");
+    }
+
+    @Test
     public void putClock_withIllegalKey() {
         String invalidClockKey = RadioMetadata.METADATA_KEY_ALBUM;
 
@@ -119,7 +145,7 @@
                     /* timezoneOffsetMinutes= */ 1);
         });
 
-        assertWithMessage("Exception for putting illegal clock-value key %s", invalidClockKey)
+        mExpect.withMessage("Exception for putting illegal clock-value key %s", invalidClockKey)
                 .that(thrown).hasMessageThat()
                 .matches(".*" + invalidClockKey + ".*cannot.*Clock.*?");
     }
@@ -133,7 +159,7 @@
             mBuilder.putStringArray(invalidStringArrayKey, UFIDS_VALUE);
         });
 
-        assertWithMessage("Exception for putting illegal string-array-value for key %s",
+        mExpect.withMessage("Exception for putting illegal string-array-value for key %s",
                 invalidStringArrayKey).that(thrown).hasMessageThat()
                 .matches(".*" + invalidStringArrayKey + ".*cannot.*Array.*?");
     }
@@ -146,7 +172,7 @@
             mBuilder.putStringArray(/* key= */ null, UFIDS_VALUE);
         });
 
-        assertWithMessage("Exception for putting string-array with null key")
+        mExpect.withMessage("Exception for putting string-array with null key")
                 .that(thrown).hasMessageThat().contains("can not be null");
     }
 
@@ -158,7 +184,7 @@
             mBuilder.putStringArray(RadioMetadata.METADATA_KEY_UFIDS, /* value= */ null);
         });
 
-        assertWithMessage("Exception for putting null string-array")
+        mExpect.withMessage("Exception for putting null string-array")
                 .that(thrown).hasMessageThat().contains("can not be null");
     }
 
@@ -167,7 +193,7 @@
         String key = RadioMetadata.METADATA_KEY_RDS_PI;
         RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build();
 
-        assertWithMessage("Whether metadata contains %s in metadata", key)
+        mExpect.withMessage("Whether metadata contains %s in metadata", key)
                 .that(metadata.containsKey(key)).isTrue();
     }
 
@@ -176,7 +202,7 @@
         String key = RadioMetadata.METADATA_KEY_RDS_PI;
         RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build();
 
-        assertWithMessage("Whether metadata contains key %s not in metadata", key)
+        mExpect.withMessage("Whether metadata contains key %s not in metadata", key)
                 .that(metadata.containsKey(RadioMetadata.METADATA_KEY_ARTIST)).isFalse();
     }
 
@@ -185,7 +211,7 @@
         String key = RadioMetadata.METADATA_KEY_RDS_PI;
         RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build();
 
-        assertWithMessage("Int value for key %s in metadata", key)
+        mExpect.withMessage("Int value for key %s in metadata", key)
                 .that(metadata.getInt(key)).isEqualTo(INT_KEY_VALUE);
     }
 
@@ -195,7 +221,7 @@
         RadioMetadata metadata =
                 mBuilder.putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE).build();
 
-        assertWithMessage("Int value for key %s in metadata", key)
+        mExpect.withMessage("Int value for key %s in metadata", key)
                 .that(metadata.getInt(key)).isEqualTo(0);
     }
 
@@ -204,7 +230,7 @@
         String key = RadioMetadata.METADATA_KEY_ARTIST;
         RadioMetadata metadata = mBuilder.putString(key, ARTIST_KEY_VALUE).build();
 
-        assertWithMessage("String value for key %s in metadata", key)
+        mExpect.withMessage("String value for key %s in metadata", key)
                 .that(metadata.getString(key)).isEqualTo(ARTIST_KEY_VALUE);
     }
 
@@ -213,7 +239,7 @@
         String key = RadioMetadata.METADATA_KEY_ARTIST;
         RadioMetadata metadata = mBuilder.build();
 
-        assertWithMessage("String value for key %s not in metadata", key)
+        mExpect.withMessage("String value for key %s not in metadata", key)
                 .that(metadata.getString(key)).isNull();
     }
 
@@ -222,7 +248,7 @@
         String key = RadioMetadata.METADATA_KEY_ICON;
         RadioMetadata metadata = mBuilder.putBitmap(key, mBitmapValue).build();
 
-        assertWithMessage("Bitmap value for key %s in metadata", key)
+        mExpect.withMessage("Bitmap value for key %s in metadata", key)
                 .that(metadata.getBitmap(key)).isEqualTo(mBitmapValue);
     }
 
@@ -231,16 +257,26 @@
         String key = RadioMetadata.METADATA_KEY_ICON;
         RadioMetadata metadata = mBuilder.build();
 
-        assertWithMessage("Bitmap value for key %s not in metadata", key)
+        mExpect.withMessage("Bitmap value for key %s not in metadata", key)
                 .that(metadata.getBitmap(key)).isNull();
     }
 
     @Test
+    public void getBitmap_withIllegalKey() {
+        String illegalKey = RadioMetadata.METADATA_KEY_ARTIST;
+        RadioMetadata metadata = mBuilder.putInt(RadioMetadata.METADATA_KEY_ICON, INT_KEY_VALUE)
+                .build();
+
+        mExpect.withMessage("Bitmap id value with non-bitmap-type key %s", illegalKey)
+                .that(metadata.getBitmap(illegalKey)).isNull();
+    }
+
+    @Test
     public void getBitmapId_withKeyInMetadata() {
         String key = RadioMetadata.METADATA_KEY_ART;
         RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build();
 
-        assertWithMessage("Bitmap id value for key %s in metadata", key)
+        mExpect.withMessage("Bitmap id value for key %s in metadata", key)
                 .that(metadata.getBitmapId(key)).isEqualTo(INT_KEY_VALUE);
     }
 
@@ -249,11 +285,21 @@
         String key = RadioMetadata.METADATA_KEY_ART;
         RadioMetadata metadata = mBuilder.build();
 
-        assertWithMessage("Bitmap id value for key %s not in metadata", key)
+        mExpect.withMessage("Bitmap id value for key %s not in metadata", key)
                 .that(metadata.getBitmapId(key)).isEqualTo(0);
     }
 
     @Test
+    public void getBitmapId_withIllegalKey() {
+        String illegalKey = RadioMetadata.METADATA_KEY_ARTIST;
+        RadioMetadata metadata = mBuilder.putInt(RadioMetadata.METADATA_KEY_ART, INT_KEY_VALUE)
+                .build();
+
+        mExpect.withMessage("Bitmap id value with non-bitmap-id-type key %s", illegalKey)
+                .that(metadata.getBitmapId(illegalKey)).isEqualTo(0);
+    }
+
+    @Test
     public void getClock_withKeyInMetadata() {
         String key = RadioMetadata.METADATA_KEY_CLOCK;
         RadioMetadata metadata = mBuilder
@@ -262,10 +308,10 @@
 
         RadioMetadata.Clock clockExpected = metadata.getClock(key);
 
-        assertWithMessage("Number of seconds since epoch of value for key %s in metadata", key)
+        mExpect.withMessage("Number of seconds since epoch of value for key %s in metadata", key)
                 .that(clockExpected.getUtcEpochSeconds())
                 .isEqualTo(TEST_UTC_SECOND_SINCE_EPOCH);
-        assertWithMessage("Offset of timezone in minutes of value for key %s in metadata", key)
+        mExpect.withMessage("Offset of timezone in minutes of value for key %s in metadata", key)
                 .that(clockExpected.getTimezoneOffsetMinutes())
                 .isEqualTo(TEST_TIME_ZONE_OFFSET_MINUTES);
     }
@@ -275,17 +321,27 @@
         String key = RadioMetadata.METADATA_KEY_CLOCK;
         RadioMetadata metadata = mBuilder.build();
 
-        assertWithMessage("Clock value for key %s not in metadata", key)
+        mExpect.withMessage("Clock value for key %s not in metadata", key)
                 .that(metadata.getClock(key)).isNull();
     }
 
     @Test
+    public void getClock_withIllegalKey() {
+        String illegalKey = RadioMetadata.METADATA_KEY_ARTIST;
+        RadioMetadata metadata = mBuilder.putClock(RadioMetadata.METADATA_KEY_CLOCK,
+                        TEST_UTC_SECOND_SINCE_EPOCH, TEST_TIME_ZONE_OFFSET_MINUTES).build();
+
+        mExpect.withMessage("Clock value for non-clock-type key %s", illegalKey)
+                .that(metadata.getClock(illegalKey)).isNull();
+    }
+
+    @Test
     public void getStringArray_withKeyInMetadata() {
         mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
         String key = RadioMetadata.METADATA_KEY_UFIDS;
         RadioMetadata metadata = mBuilder.putStringArray(key, UFIDS_VALUE).build();
 
-        assertWithMessage("String-array value for key %s not in metadata", key)
+        mExpect.withMessage("String-array value for key %s not in metadata", key)
                 .that(metadata.getStringArray(key)).asList().isEqualTo(Arrays.asList(UFIDS_VALUE));
     }
 
@@ -299,7 +355,7 @@
             metadata.getStringArray(key);
         });
 
-        assertWithMessage("Exception for getting string array for string-array value for key %s "
+        mExpect.withMessage("Exception for getting string array for string-array value for key %s "
                 + "not in metadata", key).that(thrown).hasMessageThat().contains("not found");
     }
 
@@ -312,7 +368,7 @@
             metadata.getStringArray(/* key= */ null);
         });
 
-        assertWithMessage("Exception for getting string array with null key")
+        mExpect.withMessage("Exception for getting string array with null key")
                 .that(thrown).hasMessageThat().contains("can not be null");
     }
 
@@ -326,7 +382,7 @@
             metadata.getStringArray(invalidClockKey);
         });
 
-        assertWithMessage("Exception for getting string array for key %s not of string-array type",
+        mExpect.withMessage("Exception for getting string array for non-string-array type key %s",
                 invalidClockKey).that(thrown).hasMessageThat()
                 .contains("string array");
     }
@@ -338,7 +394,7 @@
                 .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE)
                 .build();
 
-        assertWithMessage("Size of fields in non-empty metadata")
+        mExpect.withMessage("Size of fields in non-empty metadata")
                 .that(metadata.size()).isEqualTo(2);
     }
 
@@ -346,7 +402,7 @@
     public void size_withEmptyMetadata() {
         RadioMetadata metadata = mBuilder.build();
 
-        assertWithMessage("Size of fields in empty metadata")
+        mExpect.withMessage("Size of fields in empty metadata")
                 .that(metadata.size()).isEqualTo(0);
     }
 
@@ -360,7 +416,7 @@
 
         Set<String> metadataSet = metadata.keySet();
 
-        assertWithMessage("Metadata set of non-empty metadata")
+        mExpect.withMessage("Metadata set of non-empty metadata")
                 .that(metadataSet).containsExactly(RadioMetadata.METADATA_KEY_ICON,
                         RadioMetadata.METADATA_KEY_RDS_PI, RadioMetadata.METADATA_KEY_ARTIST);
     }
@@ -371,7 +427,7 @@
 
         Set<String> metadataSet = metadata.keySet();
 
-        assertWithMessage("Metadata set of empty metadata")
+        mExpect.withMessage("Metadata set of empty metadata")
                 .that(metadataSet).isEmpty();
     }
 
@@ -380,7 +436,7 @@
         int nativeKey = 0;
         String key = RadioMetadata.getKeyFromNativeKey(nativeKey);
 
-        assertWithMessage("Key for native key %s", nativeKey)
+        mExpect.withMessage("Key for native key %s", nativeKey)
                 .that(key).isEqualTo(RadioMetadata.METADATA_KEY_RDS_PI);
     }
 
@@ -393,7 +449,7 @@
         RadioMetadata.Builder copyBuilder = new RadioMetadata.Builder(metadata);
         RadioMetadata metadataCopied = copyBuilder.build();
 
-        assertWithMessage("Metadata with the same contents")
+        mExpect.withMessage("Metadata with the same contents")
                 .that(metadataCopied).isEqualTo(metadata);
     }
 
@@ -401,14 +457,15 @@
     public void describeContents_forMetadata() {
         RadioMetadata metadata = mBuilder.build();
 
-        assertWithMessage("Metadata contents").that(metadata.describeContents()).isEqualTo(0);
+        mExpect.withMessage("Metadata contents").that(metadata.describeContents()).isEqualTo(0);
     }
 
     @Test
     public void newArray_forRadioMetadataCreator() {
         RadioMetadata[] metadataArray = RadioMetadata.CREATOR.newArray(CREATOR_ARRAY_SIZE);
 
-        assertWithMessage("Radio metadata array").that(metadataArray).hasLength(CREATOR_ARRAY_SIZE);
+        mExpect.withMessage("Radio metadata array").that(metadataArray)
+                .hasLength(CREATOR_ARRAY_SIZE);
     }
 
     @Test
@@ -423,7 +480,7 @@
         parcel.setDataPosition(0);
 
         RadioMetadata metadataFromParcel = RadioMetadata.CREATOR.createFromParcel(parcel);
-        assertWithMessage("Radio metadata created from parcel")
+        mExpect.withMessage("Radio metadata created from parcel")
                 .that(metadataFromParcel).isEqualTo(metadataExpected);
     }
 
@@ -441,7 +498,43 @@
         parcel.setDataPosition(0);
 
         RadioMetadata metadataFromParcel = RadioMetadata.CREATOR.createFromParcel(parcel);
-        assertWithMessage("Radio metadata created from parcel with string array type metadata")
+        mExpect.withMessage("Radio metadata created from parcel with string array type metadata")
                 .that(metadataFromParcel).isEqualTo(metadataExpected);
     }
+
+    @Test
+    public void build_withRadioMetadataMeetingSizeRequirement() {
+        int maxBitmapSize = 10;
+        doReturn(maxBitmapSize - 1).when(mBitmapValue).getHeight();
+        doReturn(maxBitmapSize - 1).when(mBitmapValue).getWidth();
+        RadioMetadata metadataSource = mBuilder.putBitmap(
+                RadioMetadata.METADATA_KEY_ICON, mBitmapValue).build();
+
+        RadioMetadata metadata = new RadioMetadata.Builder(metadataSource, maxBitmapSize).build();
+
+        mExpect.withMessage("Bitmap without resizing")
+                .that(metadata.getBitmap(RadioMetadata.METADATA_KEY_ICON)).isEqualTo(mBitmapValue);
+    }
+
+    @Test
+    public void build_withRadioMetadataAboveSizeRequirement() {
+        int maxBitmapSize = 10;
+        int bitmapHeight = 10;
+        int bitmapWidth = 20;
+        int bitmapWidthResized = maxBitmapSize;
+        int bitmapHeightResized  = (bitmapHeight * bitmapWidthResized) / bitmapWidth;
+        Bitmap bitmapResized = mock(Bitmap.class);
+        doReturn(bitmapHeight).when(mBitmapValue).getHeight();
+        doReturn(bitmapWidth).when(mBitmapValue).getWidth();
+        doReturn(bitmapResized).when(() -> Bitmap.createScaledBitmap(
+                mBitmapValue, bitmapWidthResized, bitmapHeightResized, /* filter= */ true));
+        RadioMetadata metadataSource = mBuilder.putBitmap(
+                RadioMetadata.METADATA_KEY_ICON, mBitmapValue).build();
+
+        RadioMetadata metadata = new RadioMetadata.Builder(metadataSource, maxBitmapSize).build();
+
+        mExpect.withMessage("Bitmap with resizing")
+                .that(metadata.getBitmap(RadioMetadata.METADATA_KEY_ICON))
+                .isEqualTo(bitmapResized);
+    }
 }
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
index 4841711..4cda26d 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
@@ -125,6 +125,16 @@
     }
 
     @Test
+    public void close_forTunerAdapterCalledTwice() throws Exception {
+        mRadioTuner.close();
+        verify(mTunerMock).close();
+
+        mRadioTuner.close();
+
+        verify(mTunerMock).close();
+    }
+
+    @Test
     public void setConfiguration_forTunerAdapter() throws Exception {
         int status = mRadioTuner.setConfiguration(TEST_BAND_CONFIG);
 
@@ -134,6 +144,12 @@
     }
 
     @Test
+    public void setConfiguration_withNull_fails() throws Exception {
+        assertWithMessage("Status for setting configuration with null")
+                .that(mRadioTuner.setConfiguration(null)).isEqualTo(RadioManager.STATUS_BAD_VALUE);
+    }
+
+    @Test
     public void setConfiguration_withInvalidParameters_fails() throws Exception {
         doThrow(new IllegalArgumentException()).when(mTunerMock).setConfiguration(any());
 
@@ -840,6 +856,15 @@
     }
 
     @Test
+    public void onTuneFailed_withDeadService() throws Exception {
+        mTunerCallback.onTuneFailed(RadioManager.STATUS_DEAD_OBJECT, FM_SELECTOR);
+
+        verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onTuneFailed(
+                RadioManager.STATUS_DEAD_OBJECT, FM_SELECTOR);
+        verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onError(RadioTuner.ERROR_SERVER_DIED);
+    }
+
+    @Test
     public void onProgramListChanged_forTunerCallbackAdapter() throws Exception {
         mTunerCallback.onProgramListChanged();
 
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/UniqueProgramIdentifierTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/UniqueProgramIdentifierTest.java
index b36367b..e68a0b8 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/UniqueProgramIdentifierTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/UniqueProgramIdentifierTest.java
@@ -16,6 +16,8 @@
 
 package android.hardware.radio;
 
+import static org.junit.Assert.assertThrows;
+
 import android.annotation.Nullable;
 import android.os.Parcel;
 
@@ -45,6 +47,24 @@
     public final Expect expect = Expect.create();
 
     @Test
+    public void requireCriticalSecondaryIds_forDab() {
+        expect.withMessage("Critical secondary Id required for DAB")
+                .that(UniqueProgramIdentifier.requireCriticalSecondaryIds(
+                        ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT)).isTrue();
+    }
+
+    @Test
+    public void constructor_withNullSelector() {
+        ProgramSelector nullSelector = null;
+
+        NullPointerException thrown = assertThrows(NullPointerException.class,
+                () -> new UniqueProgramIdentifier(nullSelector));
+
+        expect.withMessage("Null pointer exception for unique program identifier")
+                .that(thrown).hasMessageThat().contains("can not be null");
+    }
+
+    @Test
     public void getPrimaryId_forUniqueProgramIdentifier() {
         ProgramSelector dabSelector = getDabSelector(new ProgramSelector.Identifier[]{
                 DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null);
@@ -67,6 +87,27 @@
     }
 
     @Test
+    public void getCriticalSecondaryIds_forDabUniqueProgramIdentifierWithoutEnsemble() {
+        ProgramSelector dabSelector = getDabSelector(new ProgramSelector.Identifier[]{
+                DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null);
+        UniqueProgramIdentifier dabIdentifier = new UniqueProgramIdentifier(dabSelector);
+
+        expect.withMessage("Critical secondary ids of DAB unique identifier without ensemble")
+                .that(dabIdentifier.getCriticalSecondaryIds())
+                .containsExactly(DAB_FREQUENCY_IDENTIFIER);
+    }
+
+    @Test
+    public void getCriticalSecondaryIds_forDabUniqueProgramIdentifierWithoutSecondaryIds() {
+        ProgramSelector dabSelector = getDabSelector(new ProgramSelector.Identifier[]{},
+                /* vendorIds= */ null);
+        UniqueProgramIdentifier dabIdentifier = new UniqueProgramIdentifier(dabSelector);
+
+        expect.withMessage("Critical secondary ids of DAB unique identifier")
+                .that(dabIdentifier.getCriticalSecondaryIds()).isEmpty();
+    }
+
+    @Test
     public void getCriticalSecondaryIds_forFmUniqueProgramIdentifier() {
         UniqueProgramIdentifier fmUniqueIdentifier = new UniqueProgramIdentifier(
                 new ProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, FM_IDENTIFIER,
@@ -147,6 +188,19 @@
     }
 
     @Test
+    public void equals_withMissingSecondaryIdsForUniqueProgramIdentifier() {
+        ProgramSelector dabSelector1 = getDabSelector(new ProgramSelector.Identifier[]{
+                DAB_ENSEMBLE_IDENTIFIER}, /* vendorIds= */ null);
+        UniqueProgramIdentifier dabIdentifier1 = new UniqueProgramIdentifier(dabSelector1);
+        ProgramSelector dabSelector2 = getDabSelector(new ProgramSelector.Identifier[]{
+                DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null);
+        UniqueProgramIdentifier dabIdentifier2 = new UniqueProgramIdentifier(dabSelector2);
+
+        expect.withMessage("DAB unique identifier with missing secondary ids")
+                .that(dabIdentifier1).isNotEqualTo(dabIdentifier2);
+    }
+
+    @Test
     public void describeContents_forUniqueProgramIdentifier() {
         UniqueProgramIdentifier fmUniqueIdentifier = new UniqueProgramIdentifier(FM_IDENTIFIER);
 
diff --git a/core/tests/InputMethodCoreTests/Android.bp b/core/tests/InputMethodCoreTests/Android.bp
new file mode 100644
index 0000000..ac64625
--- /dev/null
+++ b/core/tests/InputMethodCoreTests/Android.bp
@@ -0,0 +1,66 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "InputMethodCoreTests",
+
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+        "src/**/I*.aidl",
+    ],
+
+    dxflags: ["--core-library"],
+
+    static_libs: [
+        "collector-device-lib-platform",
+        "android-common",
+        "frameworks-core-util-lib",
+        "androidx.core_core",
+        "androidx.core_core-ktx",
+        "androidx.test.ext.junit",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "flag-junit",
+        "junit-params",
+        "kotlin-test",
+        "mockito-target-minus-junit4",
+        "platform-test-annotations",
+        "platform-compat-test-rules",
+        "truth",
+        "print-test-util-lib",
+        "testng",
+        "device-time-shell-utils",
+        "testables",
+        "flag-junit",
+    ],
+
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+        "framework",
+        "ext",
+        "framework-res",
+    ],
+
+    sdk_version: "core_platform",
+    test_suites: [
+        "device-tests",
+        "automotive-tests",
+    ],
+
+    certificate: "platform",
+
+    resource_dirs: ["res"],
+
+    data: [
+        ":com.android.cts.helpers.aosp",
+    ],
+}
diff --git a/core/tests/InputMethodCoreTests/AndroidManifest.xml b/core/tests/InputMethodCoreTests/AndroidManifest.xml
new file mode 100644
index 0000000..8d00d0f
--- /dev/null
+++ b/core/tests/InputMethodCoreTests/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          android:installLocation="internalOnly"
+          package="com.android.frameworks.inputmethodcoretests"
+          android:sharedUserId="com.android.uid.test">
+
+    <application
+        android:supportsRtl="true"
+        android:enableOnBackInvokedCallback="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.frameworks.inputmethodcoretests"
+            android:label="InputMethod Core Tests" />
+</manifest>
diff --git a/core/tests/InputMethodCoreTests/AndroidTest.xml b/core/tests/InputMethodCoreTests/AndroidTest.xml
new file mode 100644
index 0000000..fa585d8
--- /dev/null
+++ b/core/tests/InputMethodCoreTests/AndroidTest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs InputMethod Core Tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="InputMethodCoreTests.apk" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <!-- TODO(b/254155965): Design a mechanism to finally remove this command. -->
+        <option name="run-command" value="settings put global device_config_sync_disabled 0" />
+    </target_preparer>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DeviceInteractionHelperInstaller" />
+
+    <option name="test-tag" value="InputMethodCoreTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.frameworks.inputmethodcoretests" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/core/tests/InputMethodCoreTests/OWNERS b/core/tests/InputMethodCoreTests/OWNERS
new file mode 100644
index 0000000..5deb2ce
--- /dev/null
+++ b/core/tests/InputMethodCoreTests/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/view/inputmethod/OWNERS
diff --git a/core/tests/coretests/res/xml/ime_meta.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta.xml
similarity index 100%
rename from core/tests/coretests/res/xml/ime_meta.xml
rename to core/tests/InputMethodCoreTests/res/xml/ime_meta.xml
diff --git a/core/tests/coretests/res/xml/ime_meta_inline_suggestions.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions.xml
similarity index 100%
rename from core/tests/coretests/res/xml/ime_meta_inline_suggestions.xml
rename to core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions.xml
diff --git a/core/tests/coretests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml
similarity index 100%
rename from core/tests/coretests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml
rename to core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml
diff --git a/core/tests/coretests/res/xml/ime_meta_sw_next.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_sw_next.xml
similarity index 100%
rename from core/tests/coretests/res/xml/ime_meta_sw_next.xml
rename to core/tests/InputMethodCoreTests/res/xml/ime_meta_sw_next.xml
diff --git a/core/tests/coretests/res/xml/ime_meta_virtual_device_only.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_virtual_device_only.xml
similarity index 100%
rename from core/tests/coretests/res/xml/ime_meta_virtual_device_only.xml
rename to core/tests/InputMethodCoreTests/res/xml/ime_meta_virtual_device_only.xml
diff --git a/core/tests/coretests/res/xml/ime_meta_vr_only.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_vr_only.xml
similarity index 100%
rename from core/tests/coretests/res/xml/ime_meta_vr_only.xml
rename to core/tests/InputMethodCoreTests/res/xml/ime_meta_vr_only.xml
diff --git a/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/BaseInputConnectionTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/BaseInputConnectionTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/CursorAnchorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/CursorAnchorInfoTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
new file mode 100644
index 0000000..a3f537e
--- /dev/null
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import android.annotation.XmlRes;
+import android.content.Context;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.inputmethodcoretests.R;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputMethodInfoTest {
+
+    @Rule
+    public SetFlagsRule mSetFlagsRule =
+            new SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
+
+    @Test
+    public void testEqualsAndHashCode() throws Exception {
+        final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta);
+        final InputMethodInfo clone = cloneViaParcel(imi);
+
+        assertThat(clone.equals(imi), is(true));
+        assertThat(clone.hashCode(), equalTo(imi.hashCode()));
+    }
+
+    @Test
+    public void testBooleanAttributes_DefaultValues() throws Exception {
+        final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta);
+
+        assertThat(imi.supportsSwitchingToNextInputMethod(), is(false));
+        assertThat(imi.isInlineSuggestionsEnabled(), is(false));
+        assertThat(imi.supportsInlineSuggestionsWithTouchExploration(), is(false));
+
+        final InputMethodInfo clone = cloneViaParcel(imi);
+
+        assertThat(clone.supportsSwitchingToNextInputMethod(), is(false));
+        assertThat(imi.isInlineSuggestionsEnabled(), is(false));
+        assertThat(imi.supportsInlineSuggestionsWithTouchExploration(), is(false));
+        assertThat(imi.supportsStylusHandwriting(), is(false));
+        assertThat(imi.createStylusHandwritingSettingsActivityIntent(), equalTo(null));
+    }
+
+    @Test
+    public void testSupportsSwitchingToNextInputMethod() throws Exception {
+        final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta_sw_next);
+
+        assertThat(imi.supportsSwitchingToNextInputMethod(), is(true));
+
+        final InputMethodInfo clone = cloneViaParcel(imi);
+
+        assertThat(clone.supportsSwitchingToNextInputMethod(), is(true));
+    }
+
+    @Test
+    public void testInlineSuggestionsEnabled() throws Exception {
+        final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta_inline_suggestions);
+
+        assertThat(imi.isInlineSuggestionsEnabled(), is(true));
+
+        final InputMethodInfo clone = cloneViaParcel(imi);
+
+        assertThat(clone.isInlineSuggestionsEnabled(), is(true));
+    }
+
+    @Test
+    public void testInlineSuggestionsEnabledWithTouchExploration() throws Exception {
+        final InputMethodInfo imi =
+                buildInputMethodForTest(R.xml.ime_meta_inline_suggestions_with_touch_exploration);
+
+        assertThat(imi.supportsInlineSuggestionsWithTouchExploration(), is(true));
+
+        final InputMethodInfo clone = cloneViaParcel(imi);
+
+        assertThat(clone.supportsInlineSuggestionsWithTouchExploration(), is(true));
+    }
+
+    @Test
+    public void testIsVrOnly() throws Exception {
+        final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta_vr_only);
+
+        assertThat(imi.isVrOnly(), is(true));
+
+        final InputMethodInfo clone = cloneViaParcel(imi);
+
+        assertThat(clone.isVrOnly(), is(true));
+    }
+
+    @Test
+    public void testIsVirtualDeviceOnly() throws Exception {
+        mSetFlagsRule.enableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_CUSTOM_IME);
+
+        final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta_virtual_device_only);
+
+        assertThat(imi.isVirtualDeviceOnly(), is(true));
+
+        final InputMethodInfo clone = cloneViaParcel(imi);
+
+        assertThat(clone.isVirtualDeviceOnly(), is(true));
+    }
+
+    private InputMethodInfo buildInputMethodForTest(final @XmlRes int metaDataRes)
+            throws Exception {
+        final Context context = InstrumentationRegistry.getContext();
+        final ServiceInfo serviceInfo = new ServiceInfo();
+        serviceInfo.applicationInfo = context.getApplicationInfo();
+        serviceInfo.packageName = context.getPackageName();
+        serviceInfo.name = "DummyImeForTest";
+        serviceInfo.metaData = new Bundle();
+        serviceInfo.metaData.putInt(InputMethod.SERVICE_META_DATA, metaDataRes);
+        final ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = serviceInfo;
+        return new InputMethodInfo(context, resolveInfo, null /* additionalSubtypesMap */);
+    }
+
+    private InputMethodInfo cloneViaParcel(final InputMethodInfo original) {
+        Parcel parcel = null;
+        try {
+            parcel = Parcel.obtain();
+            original.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            return InputMethodInfo.CREATOR.createFromParcel(parcel);
+        } finally {
+            if (parcel != null) {
+                parcel.recycle();
+            }
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodManagerTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/InputMethodManagerTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/SparseRectFArrayTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/SparseRectFArrayTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/SurroundingTextTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SurroundingTextTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/SurroundingTextTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/SurroundingTextTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/TextAppearanceInfoTest.java
similarity index 100%
rename from core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java
rename to core/tests/InputMethodCoreTests/src/android/view/inputmethod/TextAppearanceInfoTest.java
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt
similarity index 100%
rename from core/tests/coretests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt
rename to core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java
similarity index 100%
rename from core/tests/coretests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java
rename to core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodDebugTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java
similarity index 100%
rename from core/tests/coretests/src/com/android/internal/inputmethod/InputMethodDebugTest.java
rename to core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
similarity index 100%
rename from core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
rename to core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java
similarity index 100%
rename from core/tests/coretests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java
rename to core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index c058174..f476799 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -30,6 +30,8 @@
 
 android_test {
     name: "FrameworksCoreTests",
+    // FrameworksCoreTestsRavenwood references the .aapt.srcjar
+    use_resource_processor: false,
 
     srcs: [
         "src/**/*.java",
@@ -60,6 +62,7 @@
         "frameworks-core-util-lib",
         "mockwebserver",
         "guava",
+        "android.app.usage.flags-aconfig-java",
         "android.view.accessibility.flags-aconfig-java",
         "androidx.core_core",
         "androidx.core_core-ktx",
@@ -83,6 +86,10 @@
         "com.android.text.flags-aconfig-java",
         "flag-junit",
         "ravenwood-junit",
+        "perfetto_trace_java_protos",
+        "flickerlib-parsers",
+        "flickerlib-trace_processor_shell",
+        "mockito-target-extended-minus-junit4",
     ],
 
     libs: [
@@ -202,11 +209,15 @@
         "testng",
     ],
     srcs: [
+        "src/android/content/pm/PackageManagerTest.java",
+        "src/android/content/pm/UserInfoTest.java",
         "src/android/database/CursorWindowTest.java",
         "src/android/os/**/*.java",
+        "src/android/telephony/PinResultTest.java",
         "src/android/util/**/*.java",
+        "src/android/view/DisplayInfoTest.java",
+        "src/com/android/internal/logging/**/*.java",
         "src/com/android/internal/os/**/*.java",
-        "src/com/android/internal/os/LongArrayMultiStateCounterTest.java",
         "src/com/android/internal/util/**/*.java",
         "src/com/android/internal/power/EnergyConsumerStatsTest.java",
 
diff --git a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
index 43266a5..cb3f99c 100644
--- a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
+++ b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
@@ -17,6 +17,7 @@
 package android.animation;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.util.PollingCheck;
@@ -343,6 +344,20 @@
     }
 
     @Test
+    public void childAnimatorCancelsDuringUpdate_animatorSetIsEnded() throws Throwable {
+        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                animation.cancel();
+            }
+        });
+        mActivity.runOnUiThread(() -> {
+            mSet1.start();
+            assertFalse(mSet1.isRunning());
+        });
+    }
+
+    @Test
     public void reentrantStart() throws Throwable {
         CountDownLatch latch = new CountDownLatch(3);
         AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
diff --git a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
index 9d85b65..1925588 100644
--- a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
+++ b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
@@ -16,8 +16,6 @@
 
 package android.app;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.fail;
 
@@ -28,8 +26,6 @@
 import android.os.Parcel;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
-import android.service.notification.ZenDeviceEffects;
-import android.service.notification.ZenPolicy;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -230,66 +226,4 @@
 
         assertThrows(IllegalArgumentException.class, () -> builder.setType(100));
     }
-
-    @Test
-    @EnableFlags(Flags.FLAG_MODES_API)
-    public void testCanUpdate_nullPolicyAndDeviceEffects() {
-        AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("name",
-                Uri.parse("uri://short"));
-
-        AutomaticZenRule rule = builder.setUserModifiedFields(0)
-                .setZenPolicy(null)
-                .setDeviceEffects(null)
-                .build();
-
-        assertThat(rule.canUpdate()).isTrue();
-
-        rule = builder.setUserModifiedFields(1).build();
-        assertThat(rule.canUpdate()).isFalse();
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_MODES_API)
-    public void testCanUpdate_policyModified() {
-        ZenPolicy.Builder policyBuilder = new ZenPolicy.Builder().setUserModifiedFields(0);
-        ZenPolicy policy = policyBuilder.build();
-
-        AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("name",
-                Uri.parse("uri://short"));
-        AutomaticZenRule rule = builder.setUserModifiedFields(0)
-                .setZenPolicy(policy)
-                .setDeviceEffects(null).build();
-
-        // Newly created ZenPolicy is not user modified.
-        assertThat(policy.getUserModifiedFields()).isEqualTo(0);
-        assertThat(rule.canUpdate()).isTrue();
-
-        policy = policyBuilder.setUserModifiedFields(1).build();
-        assertThat(policy.getUserModifiedFields()).isEqualTo(1);
-        rule = builder.setZenPolicy(policy).build();
-        assertThat(rule.canUpdate()).isFalse();
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_MODES_API)
-    public void testCanUpdate_deviceEffectsModified() {
-        ZenDeviceEffects.Builder deviceEffectsBuilder =
-                new ZenDeviceEffects.Builder().setUserModifiedFields(0);
-        ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
-
-        AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("name",
-                Uri.parse("uri://short"));
-        AutomaticZenRule rule = builder.setUserModifiedFields(0)
-                .setZenPolicy(null)
-                .setDeviceEffects(deviceEffects).build();
-
-        // Newly created ZenDeviceEffects is not user modified.
-        assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(0);
-        assertThat(rule.canUpdate()).isTrue();
-
-        deviceEffects = deviceEffectsBuilder.setUserModifiedFields(1).build();
-        assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(1);
-        rule = builder.setDeviceEffects(deviceEffects).build();
-        assertThat(rule.canUpdate()).isFalse();
-    }
 }
diff --git a/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java b/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java
index 839b645..5516845 100644
--- a/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java
+++ b/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java
@@ -15,14 +15,20 @@
  */
 package android.app.usage;
 
+import static android.app.usage.Flags.FLAG_FILTER_BASED_EVENT_QUERY_API;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import android.app.usage.UsageEvents.Event;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -32,7 +38,12 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class UsageEventsQueryTest {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Test
+    @RequiresFlagsEnabled(FLAG_FILTER_BASED_EVENT_QUERY_API)
     public void testQueryDuration() {
         // Test with negative beginTimeMillis.
         long beginTimeMillis = -100;
@@ -97,6 +108,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(FLAG_FILTER_BASED_EVENT_QUERY_API)
     public void testQueryEventTypes() {
         Random rnd = new Random();
         UsageEventsQuery.Builder queryBuilder = new UsageEventsQuery.Builder(1000, 2000);
@@ -104,7 +116,7 @@
         // Test with invalid event type.
         int eventType = Event.NONE - 1;
         try {
-            queryBuilder.addEventTypes(eventType);
+            queryBuilder.setEventTypes(eventType);
             fail("Invalid event type: " + eventType);
         } catch (IllegalArgumentException e) {
             // Expected, fall through.
@@ -112,7 +124,7 @@
 
         eventType = Event.MAX_EVENT_TYPE + 1;
         try {
-            queryBuilder.addEventTypes(eventType);
+            queryBuilder.setEventTypes(eventType);
             fail("Invalid event type: " + eventType);
         } catch (IllegalArgumentException e) {
             // Expected, fall through.
@@ -121,14 +133,29 @@
         // Test with valid and duplicate event types.
         eventType = rnd.nextInt(Event.MAX_EVENT_TYPE + 1);
         try {
-            UsageEventsQuery query = queryBuilder.addEventTypes(eventType, eventType, eventType)
+            UsageEventsQuery query = queryBuilder.setEventTypes(eventType, eventType, eventType)
                     .build();
-            Set<Integer> eventTypeSet = query.getEventTypes();
-            assertEquals(eventTypeSet.size(), 1);
-            int type = eventTypeSet.iterator().next();
+            final int[] eventTypesArray = query.getEventTypes();
+            assertEquals(eventTypesArray.length, 1);
+            int type = eventTypesArray[0];
             assertEquals(type, eventType);
         } catch (IllegalArgumentException e) {
             fail("Valid event type: " + eventType);
         }
     }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_FILTER_BASED_EVENT_QUERY_API)
+    public void testQueryEventPackages() {
+        UsageEventsQuery.Builder queryBuilder = new UsageEventsQuery.Builder(1000, 2000);
+
+        // Test with duplicate package names and empty package name
+        final String pkgName = "test.package.name";
+        UsageEventsQuery query = queryBuilder.setPackageNames(pkgName, pkgName, "", pkgName)
+                .build();
+        Set<String> pkgNameSet = query.getPackageNames();
+        // Duplicated package names and empty package name will be ignored.
+        assertEquals(pkgNameSet.size(), 1);
+        assertEquals(pkgName, pkgNameSet.iterator().next());
+    }
 }
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTest.java b/core/tests/coretests/src/android/content/pm/PackageManagerTest.java
new file mode 100644
index 0000000..20421d1
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PackageManagerTest {
+    @Test
+    public void testPackageInfoFlags() throws Exception {
+        assertThat(PackageManager.PackageInfoFlags.of(42L).getValue()).isEqualTo(42L);
+    }
+
+    @Test
+    public void testApplicationInfoFlags() throws Exception {
+        assertThat(PackageManager.ApplicationInfoFlags.of(42L).getValue()).isEqualTo(42L);
+    }
+
+    @Test
+    public void testComponentInfoFlags() throws Exception {
+        assertThat(PackageManager.ComponentInfoFlags.of(42L).getValue()).isEqualTo(42L);
+    }
+
+    @Test
+    public void testResolveInfoFlags() throws Exception {
+        assertThat(PackageManager.ResolveInfoFlags.of(42L).getValue()).isEqualTo(42L);
+    }
+}
diff --git a/core/tests/coretests/src/android/content/pm/UserInfoTest.java b/core/tests/coretests/src/android/content/pm/UserInfoTest.java
new file mode 100644
index 0000000..af36dbb
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/UserInfoTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class UserInfoTest {
+    @Test
+    public void testSimple() throws Exception {
+        final UserInfo ui = new UserInfo(10, "Test", UserInfo.FLAG_GUEST);
+
+        assertThat(ui.getUserHandle()).isEqualTo(UserHandle.of(10));
+        assertThat(ui.name).isEqualTo("Test");
+
+        // Derived based on userType field
+        assertThat(ui.isManagedProfile()).isEqualTo(false);
+        assertThat(ui.isGuest()).isEqualTo(true);
+        assertThat(ui.isRestricted()).isEqualTo(false);
+        assertThat(ui.isDemo()).isEqualTo(false);
+        assertThat(ui.isCloneProfile()).isEqualTo(false);
+        assertThat(ui.isCommunalProfile()).isEqualTo(false);
+        assertThat(ui.isPrivateProfile()).isEqualTo(false);
+
+        // Derived based on flags field
+        assertThat(ui.isPrimary()).isEqualTo(false);
+        assertThat(ui.isAdmin()).isEqualTo(false);
+        assertThat(ui.isProfile()).isEqualTo(false);
+        assertThat(ui.isEnabled()).isEqualTo(true);
+        assertThat(ui.isQuietModeEnabled()).isEqualTo(false);
+        assertThat(ui.isEphemeral()).isEqualTo(false);
+        assertThat(ui.isForTesting()).isEqualTo(false);
+        assertThat(ui.isInitialized()).isEqualTo(false);
+        assertThat(ui.isFull()).isEqualTo(false);
+        assertThat(ui.isMain()).isEqualTo(false);
+
+        // Derived dynamically
+        assertThat(ui.canHaveProfile()).isEqualTo(false);
+    }
+
+    @Test
+    public void testDebug() throws Exception {
+        final UserInfo ui = new UserInfo(10, "Test", UserInfo.FLAG_GUEST);
+
+        assertThat(ui.toString()).isNotEmpty();
+        assertThat(ui.toFullString()).isNotEmpty();
+    }
+}
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index 1617eda..a2a5433 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -48,7 +48,7 @@
     @get:Rule
     val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
 
-    private lateinit var defaultLookupTables: SparseArray<FontScaleConverter>
+    private var defaultLookupTables: SparseArray<FontScaleConverter>? = null
 
     @Before
     fun setup() {
@@ -58,7 +58,9 @@
     @After
     fun teardown() {
         // Restore the default tables (since some tests will have added extras to the cache)
-        FontScaleConverterFactory.sLookupTables = defaultLookupTables
+        if (defaultLookupTables != null) {
+            FontScaleConverterFactory.sLookupTables = defaultLookupTables!!
+        }
     }
 
     @Test
@@ -143,7 +145,6 @@
     fun unnecessaryFontScalesReturnsNull() {
         assertThat(FontScaleConverterFactory.forScale(0F)).isNull()
         assertThat(FontScaleConverterFactory.forScale(1F)).isNull()
-        assertThat(FontScaleConverterFactory.forScale(1.1F)).isNull()
         assertThat(FontScaleConverterFactory.forScale(0.85F)).isNull()
     }
 
@@ -174,7 +175,7 @@
         assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(-1f)).isFalse()
         assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(0.85f)).isFalse()
         assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.02f)).isFalse()
-        assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.10f)).isFalse()
+        assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.10f)).isTrue()
         assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.15f)).isTrue()
         assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.1499999f))
                 .isTrue()
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index bd5f809..3ee565f 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -255,6 +255,7 @@
 
         final String query = "--comment\nSELECT count(*) from t1";
 
+        database.beginTransactionReadOnly();
         try {
             for (int i = count; i > 0; i--) {
                 ticker.arriveAndAwaitAdvance();
@@ -268,6 +269,7 @@
         } catch (Throwable t) {
             errors.add(t);
         } finally {
+            database.endTransaction();
             ticker.arriveAndDeregister();
         }
     }
@@ -401,4 +403,41 @@
         }
         assertFalse(allowed);
     }
+
+    /** Return true if the path is in the list of strings. */
+    private boolean isConcurrent(String path) throws Exception {
+        path = new File(path).toPath().toRealPath().toString();
+        return SQLiteDatabase.getConcurrentDatabasePaths().contains(path);
+    }
+
+    @Test
+    public void testDuplicateDatabases() throws Exception {
+        // The two database paths in this test are assumed not to have been opened earlier in this
+        // process.
+
+        // A database path that will be opened twice.
+        final String dbName = "never-used-db.db";
+        final File dbFile = mContext.getDatabasePath(dbName);
+        final String dbPath = dbFile.getPath();
+
+        // A database path that will be opened only once.
+        final String okName = "never-used-ok.db";
+        final File okFile = mContext.getDatabasePath(okName);
+        final String okPath = okFile.getPath();
+
+        SQLiteDatabase db1 = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
+        assertFalse(isConcurrent(dbPath));
+        SQLiteDatabase db2 = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
+        assertTrue(isConcurrent(dbPath));
+        db1.close();
+        assertTrue(isConcurrent(dbPath));
+        db2.close();
+        assertTrue(isConcurrent(dbPath));
+
+        SQLiteDatabase db3 = SQLiteDatabase.openOrCreateDatabase(okFile, null);
+        db3.close();
+        db3 = SQLiteDatabase.openOrCreateDatabase(okFile, null);
+        assertFalse(isConcurrent(okPath));
+        db3.close();
+    }
 }
diff --git a/core/tests/coretests/src/android/graphics/PaintTest.java b/core/tests/coretests/src/android/graphics/PaintTest.java
index bf56df1..0dec756 100644
--- a/core/tests/coretests/src/android/graphics/PaintTest.java
+++ b/core/tests/coretests/src/android/graphics/PaintTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertNotEquals;
 
 import android.test.InstrumentationTestCase;
+import android.text.TextUtils;
 
 import androidx.test.filters.SmallTest;
 
@@ -362,4 +363,44 @@
         //    = 30
         assertEquals(30.0f, p.getUnderlineThickness(), 0.5f);
     }
+
+    private int getClusterCount(Paint p, String text) {
+        Paint.RunInfo runInfo = new Paint.RunInfo();
+        p.getRunCharacterAdvance(text, 0, text.length(), 0, text.length(), false, 0, null, 0, null,
+                runInfo);
+        int ccByString = runInfo.getClusterCount();
+        runInfo.setClusterCount(0);
+        char[] buf = new char[text.length()];
+        TextUtils.getChars(text, 0, text.length(), buf, 0);
+        p.getRunCharacterAdvance(buf, 0, buf.length, 0, buf.length, false, 0, null, 0, null,
+                runInfo);
+        int ccByChars = runInfo.getClusterCount();
+        assertEquals(ccByChars, ccByString);
+        return ccByChars;
+    }
+
+    public void testCluster() {
+        final Paint p = new Paint();
+        p.setTextSize(100);
+
+        // Regular String
+        assertEquals(1, getClusterCount(p, "A"));
+        assertEquals(2, getClusterCount(p, "AB"));
+
+        // Ligature is in the same cluster
+        assertEquals(1, getClusterCount(p, "fi"));  // Ligature
+        p.setFontFeatureSettings("'liga' off");
+        assertEquals(2, getClusterCount(p, "fi"));  // Ligature is disabled
+        p.setFontFeatureSettings("");
+
+        // Combining character
+        assertEquals(1, getClusterCount(p, "\u0061\u0300"));  // A + COMBINING GRAVE ACCENT
+
+        // BiDi
+        final String rtlStr = "\u05D0\u05D1\u05D2";
+        final String ltrStr = "abc";
+        assertEquals(3, getClusterCount(p, rtlStr));
+        assertEquals(6, getClusterCount(p, rtlStr + ltrStr));
+        assertEquals(9, getClusterCount(p, ltrStr + rtlStr + ltrStr));
+    }
 }
diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
index b843ad7..d816d085 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
@@ -18,6 +18,7 @@
 
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS;
+import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -35,12 +36,15 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
+import android.hardware.biometrics.BiometricPrompt;
 import android.os.CancellationSignal;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
 
 import com.android.internal.R;
 
@@ -58,6 +62,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 @Presubmit
 @RunWith(MockitoJUnitRunner.class)
@@ -78,6 +83,8 @@
     @Mock
     private FaceManager.AuthenticationCallback mAuthCallback;
     @Mock
+    private BiometricPrompt.AuthenticationCallback mBioAuthCallback;
+    @Mock
     private FaceManager.EnrollmentCallback mEnrollmentCallback;
     @Mock
     private FaceManager.FaceDetectionCallback mFaceDetectionCallback;
@@ -91,13 +98,16 @@
     private TestLooper mLooper;
     private Handler mHandler;
     private FaceManager mFaceManager;
+    private Executor mExecutor;
 
     @Before
     public void setUp() throws Exception {
         mLooper = new TestLooper();
         mHandler = new Handler(mLooper.getLooper());
+        mExecutor = new HandlerExecutor(mHandler);
 
         when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
+        when(mContext.getMainExecutor()).thenReturn(mExecutor);
         when(mContext.getOpPackageName()).thenReturn(PACKAGE_NAME);
         when(mContext.getAttributionTag()).thenReturn(ATTRIBUTION_TAG);
         when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
@@ -159,6 +169,19 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(FLAG_FACE_BACKGROUND_AUTHENTICATION)
+    public void authenticateInBackground_errorWhenUnavailable() throws Exception {
+        when(mService.authenticateInBackground(any(), anyLong(), any(), any()))
+                .thenThrow(new RemoteException());
+
+        mFaceManager.authenticateInBackground(mExecutor, null, new CancellationSignal(),
+                mBioAuthCallback);
+        mLooper.dispatchAll();
+
+        verify(mBioAuthCallback).onAuthenticationError(eq(FACE_ERROR_HW_UNAVAILABLE), any());
+    }
+
+    @Test
     public void enrollment_errorWhenFaceEnrollmentExists() throws RemoteException {
         when(mResources.getInteger(R.integer.config_faceMaxTemplatesPerUser)).thenReturn(1);
         when(mService.getEnrolledFaces(anyInt(), anyInt(), anyString()))
diff --git a/core/tests/coretests/src/android/os/BuildTest.java b/core/tests/coretests/src/android/os/BuildTest.java
index 3162e6d..2a718ff 100644
--- a/core/tests/coretests/src/android/os/BuildTest.java
+++ b/core/tests/coretests/src/android/os/BuildTest.java
@@ -20,7 +20,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.platform.test.ravenwood.RavenwoodRule;
 
@@ -45,7 +44,8 @@
     public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
+                SetFlagsRule.DefaultInitValueType.NULL_DEFAULT);
 
     /**
      * Asserts that a String is non-null and non-empty.  If it is not,
@@ -70,7 +70,6 @@
      */
     @Test
     @SmallTest
-    @IgnoreUnderRavenwood(blockedBy = Build.class)
     public void testBuildFields() throws Exception {
         assertNotEmpty("ID", Build.ID);
         assertNotEmpty("DISPLAY", Build.DISPLAY);
diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java
index e7b5dff6..93c2e0e 100644
--- a/core/tests/coretests/src/android/os/BundleTest.java
+++ b/core/tests/coretests/src/android/os/BundleTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
@@ -121,6 +122,14 @@
     }
 
     @Test
+    public void testEmpty() throws Exception {
+        assertNotNull(Bundle.EMPTY);
+        assertEquals(0, Bundle.EMPTY.size());
+
+        new Bundle(Bundle.EMPTY);
+    }
+
+    @Test
     @IgnoreUnderRavenwood(blockedBy = ParcelFileDescriptor.class)
     public void testCreateFromParcel() throws Exception {
         boolean withFd;
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index 60500d5..78a2c1c 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -29,6 +29,7 @@
 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
 import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
 import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY;
+import static android.system.OsConstants.AF_INET;
 import static android.system.OsConstants.F_OK;
 import static android.system.OsConstants.O_APPEND;
 import static android.system.OsConstants.O_CREAT;
@@ -37,6 +38,7 @@
 import static android.system.OsConstants.O_TRUNC;
 import static android.system.OsConstants.O_WRONLY;
 import static android.system.OsConstants.R_OK;
+import static android.system.OsConstants.SOCK_STREAM;
 import static android.system.OsConstants.W_OK;
 import static android.system.OsConstants.X_OK;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
@@ -55,6 +57,7 @@
 import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.ravenwood.RavenwoodRule;
 import android.provider.DocumentsContract.Document;
+import android.system.Os;
 import android.util.DataUnit;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -67,6 +70,8 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
@@ -78,6 +83,7 @@
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Random;
+import java.net.InetSocketAddress;
 
 @RunWith(AndroidJUnit4.class)
 public class FileUtilsTest {
@@ -253,6 +259,84 @@
         assertArrayEquals(expected, actual);
     }
 
+    //TODO(ravenwood) Remove the _$noRavenwood suffix and add @RavenwoodIgnore instead
+    @Test
+    public void testCopy_SocketToFile_FileToSocket$noRavenwood() throws Exception {
+        for (int size : DATA_SIZES ) {
+            final File src = new File(mTarget, "src");
+            final File dest = new File(mTarget, "dest");
+            byte[] expected = new byte[size];
+            byte[] actual = new byte[size];
+            new Random().nextBytes(expected);
+
+            // write test data in to src file
+            writeFile(src, expected);
+
+            // start server, get data from client and save to dest file (socket --> file)
+            FileDescriptor srvSocketFd = Os.socket(AF_INET, SOCK_STREAM, 0);
+            Os.bind(srvSocketFd, new InetSocketAddress("localhost", 0));
+            Os.listen(srvSocketFd, 5);
+            InetSocketAddress localSocketAddress = (InetSocketAddress) Os.getsockname(srvSocketFd);
+
+            final Thread srv = new Thread(new Runnable() {
+                public void run() {
+                    try {
+                        InetSocketAddress peerAddress = new InetSocketAddress();
+                        FileDescriptor srvConnFd = Os.accept(srvSocketFd, peerAddress);
+
+                        // read file size
+                        byte[] rcvFileSizeByteArray = new byte[8];
+                        Os.read(srvConnFd, rcvFileSizeByteArray, 0, rcvFileSizeByteArray.length);
+                        long rcvFileSize = 0;
+                        for (int i = 0; i < 8; i++) {
+                            rcvFileSize <<= 8;
+                            rcvFileSize |= (rcvFileSizeByteArray[i] & 0xFF);
+                        }
+
+                        FileOutputStream fileOutputStream = new FileOutputStream(dest);
+                        // copy data from socket to file
+                        FileUtils.copy(srvConnFd, fileOutputStream.getFD(), rcvFileSize, null, null, null);
+
+                        fileOutputStream.close();
+                        Os.close(srvConnFd);
+                        Os.close(srvSocketFd);
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                }
+            });
+
+            srv.start();
+
+
+            // start client, get data from dest file and send to server (file --> socket)
+            FileDescriptor clientFd = Os.socket(AF_INET, SOCK_STREAM, 0);
+            Os.connect(clientFd, localSocketAddress.getAddress(), localSocketAddress.getPort());
+
+            FileInputStream fileInputStream = new FileInputStream(src);
+            long sndFileSize = src.length();
+            // send the file size to server
+            byte[] sndFileSizeByteArray = new byte[8];
+            for (int i = 7; i >= 0; i--) {
+                sndFileSizeByteArray[i] = (byte)(sndFileSize & 0xFF);
+                sndFileSize >>= 8;
+            }
+            Os.write(clientFd, sndFileSizeByteArray, 0, sndFileSizeByteArray.length);
+
+            // copy data from file to socket
+            FileUtils.copy(fileInputStream.getFD(), clientFd, src.length(), null, null, null);
+
+            fileInputStream.close();
+            Os.close(clientFd);
+
+            srv.join();
+
+            // read test data from dest file
+            actual = readFile(dest);
+            assertArrayEquals(expected, actual);
+        }
+    }
+
     @Test
     public void testIsFilenameSafe() throws Exception {
         assertTrue(FileUtils.isFilenameSafe(new File("foobar")));
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index 12a2844..a28bb69 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -195,9 +195,30 @@
         Session s = createSession();
         assumeNotNull(s);
         s.updateTargetWorkDuration(16);
-        s.reportActualWorkDuration(new WorkDuration(1, 12, 8, 6));
-        s.reportActualWorkDuration(new WorkDuration(1, 33, 14, 20));
-        s.reportActualWorkDuration(new WorkDuration(1, 14, 10, 6));
+        {
+            WorkDuration workDuration = new WorkDuration();
+            workDuration.setWorkPeriodStartTimestampNanos(1);
+            workDuration.setActualTotalDurationNanos(12);
+            workDuration.setActualCpuDurationNanos(8);
+            workDuration.setActualGpuDurationNanos(6);
+            s.reportActualWorkDuration(workDuration);
+        }
+        {
+            WorkDuration workDuration = new WorkDuration();
+            workDuration.setWorkPeriodStartTimestampNanos(1);
+            workDuration.setActualTotalDurationNanos(33);
+            workDuration.setActualCpuDurationNanos(14);
+            workDuration.setActualGpuDurationNanos(20);
+            s.reportActualWorkDuration(workDuration);
+        }
+        {
+            WorkDuration workDuration = new WorkDuration();
+            workDuration.setWorkPeriodStartTimestampNanos(1);
+            workDuration.setActualTotalDurationNanos(14);
+            workDuration.setActualCpuDurationNanos(10);
+            workDuration.setActualGpuDurationNanos(6);
+            s.reportActualWorkDuration(workDuration);
+        }
     }
 
     @Test
@@ -206,25 +227,25 @@
         assumeNotNull(s);
         s.updateTargetWorkDuration(16);
         assertThrows(IllegalArgumentException.class, () -> {
-            s.reportActualWorkDuration(new WorkDuration(-1, 12, 8, 6));
+            s.reportActualWorkDuration(new WorkDuration(-1, 12, 8, 6, 1));
         });
         assertThrows(IllegalArgumentException.class, () -> {
-            s.reportActualWorkDuration(new WorkDuration(0, 12, 8, 6));
+            s.reportActualWorkDuration(new WorkDuration(0, 12, 8, 6, 1));
         });
         assertThrows(IllegalArgumentException.class, () -> {
-            s.reportActualWorkDuration(new WorkDuration(1, -1, 8, 6));
+            s.reportActualWorkDuration(new WorkDuration(1, -1, 8, 6, 1));
         });
         assertThrows(IllegalArgumentException.class, () -> {
-            s.reportActualWorkDuration(new WorkDuration(1, 0, 8, 6));
+            s.reportActualWorkDuration(new WorkDuration(1, 0, 8, 6, 1));
         });
         assertThrows(IllegalArgumentException.class, () -> {
-            s.reportActualWorkDuration(new WorkDuration(1, 12, -1, 6));
+            s.reportActualWorkDuration(new WorkDuration(1, 12, -1, 6, 1));
         });
         assertThrows(IllegalArgumentException.class, () -> {
-            s.reportActualWorkDuration(new WorkDuration(1, 12, 0, 6));
+            s.reportActualWorkDuration(new WorkDuration(1, 12, 0, 6, 1));
         });
         assertThrows(IllegalArgumentException.class, () -> {
-            s.reportActualWorkDuration(new WorkDuration(1, 12, 8, -1));
+            s.reportActualWorkDuration(new WorkDuration(1, 12, 8, -1, 1));
         });
     }
 }
diff --git a/core/tests/coretests/src/android/os/TestLooperManagerTest.java b/core/tests/coretests/src/android/os/TestLooperManagerTest.java
new file mode 100644
index 0000000..5959444
--- /dev/null
+++ b/core/tests/coretests/src/android/os/TestLooperManagerTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class TestLooperManagerTest {
+    private static final String TAG = "TestLooperManagerTest";
+
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
+    @Test
+    public void testMainThread() throws Exception {
+        doTest(Looper.getMainLooper());
+    }
+
+    @Test
+    public void testCustomThread() throws Exception {
+        final HandlerThread thread = new HandlerThread(TAG);
+        thread.start();
+        doTest(thread.getLooper());
+    }
+
+    private void doTest(Looper looper) throws Exception {
+        final TestLooperManager tlm =
+                InstrumentationRegistry.getInstrumentation().acquireLooperManager(looper);
+
+        final Handler handler = new Handler(looper);
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        assertFalse(tlm.hasMessages(handler, null, 42));
+
+        handler.sendEmptyMessage(42);
+        handler.post(() -> {
+            latch.countDown();
+        });
+        assertTrue(tlm.hasMessages(handler, null, 42));
+        assertFalse(latch.await(100, TimeUnit.MILLISECONDS));
+
+        final Message first = tlm.next();
+        assertEquals(42, first.what);
+        assertNull(first.callback);
+        tlm.execute(first);
+        assertFalse(tlm.hasMessages(handler, null, 42));
+        assertFalse(latch.await(100, TimeUnit.MILLISECONDS));
+        tlm.recycle(first);
+
+        final Message second = tlm.next();
+        assertNotNull(second.callback);
+        tlm.execute(second);
+        assertFalse(tlm.hasMessages(handler, null, 42));
+        assertTrue(latch.await(100, TimeUnit.MILLISECONDS));
+        tlm.recycle(second);
+
+        tlm.release();
+    }
+}
diff --git a/core/tests/coretests/src/android/os/TraceTest.java b/core/tests/coretests/src/android/os/TraceTest.java
index 593833ec..b2c005f 100644
--- a/core/tests/coretests/src/android/os/TraceTest.java
+++ b/core/tests/coretests/src/android/os/TraceTest.java
@@ -34,7 +34,6 @@
  * while tracing on the emulator and then run traceview to view the trace.
  */
 @RunWith(AndroidJUnit4.class)
-@IgnoreUnderRavenwood(blockedBy = Trace.class)
 public class TraceTest {
     private static final String TAG = "TraceTest";
 
@@ -46,7 +45,51 @@
     private int gMethodCalls = 0;
 
     @Test
+    public void testEnableDisable() {
+        // Currently only verifying that we can invoke without crashing
+        Trace.setTracingEnabled(true, 0);
+        Trace.setTracingEnabled(false, 0);
+
+        Trace.setAppTracingAllowed(true);
+        Trace.setAppTracingAllowed(false);
+    }
+
+    @Test
+    public void testBeginEnd() {
+        // Currently only verifying that we can invoke without crashing
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG);
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+        Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+        Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+
+        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, TAG, 42);
+        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+
+        Trace.beginSection(TAG);
+        Trace.endSection();
+
+        Trace.beginAsyncSection(TAG, 42);
+        Trace.endAsyncSection(TAG, 42);
+    }
+
+    @Test
+    public void testCounter() {
+        // Currently only verifying that we can invoke without crashing
+        Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+        Trace.setCounter(TAG, 42);
+    }
+
+    @Test
+    public void testInstant() {
+        // Currently only verifying that we can invoke without crashing
+        Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG);
+        Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, TAG);
+    }
+
+    @Test
     public void testNullStrings() {
+        // Currently only verifying that we can invoke without crashing
         Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER, null, 42);
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, null);
 
@@ -62,6 +105,7 @@
 
     @Test
     @SmallTest
+    @IgnoreUnderRavenwood(blockedBy = Debug.class)
     public void testNativeTracingFromJava()
     {
         long start = System.currentTimeMillis();
@@ -82,6 +126,7 @@
     
     // This should not run in the automated suite.
     @Suppress
+    @IgnoreUnderRavenwood(blockedBy = Debug.class)
     public void disableTestNativeTracingFromC()
     {
         long start = System.currentTimeMillis();
@@ -97,6 +142,7 @@
     @Test
     @LargeTest
     @Suppress  // Failing.
+    @IgnoreUnderRavenwood(blockedBy = Debug.class)
     public void testMethodTracing()
     {
         long start = System.currentTimeMillis();
diff --git a/core/tests/coretests/src/android/os/WorkDurationUnitTest.java b/core/tests/coretests/src/android/os/WorkDurationUnitTest.java
new file mode 100644
index 0000000..c70da6e
--- /dev/null
+++ b/core/tests/coretests/src/android/os/WorkDurationUnitTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static org.junit.Assert.assertThrows;
+
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = WorkDuration.class)
+public class WorkDurationUnitTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    // Required for RequiresFlagsEnabled and RequiresFlagsDisabled annotations to take effect.
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isUnderRavenwood() ? null
+            : DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
+    public void testWorkDurationSetters_IllegalArgument() {
+        WorkDuration workDuration = new WorkDuration();
+        assertThrows(IllegalArgumentException.class, () -> {
+            workDuration.setWorkPeriodStartTimestampNanos(-1);
+        });
+        assertThrows(IllegalArgumentException.class, () -> {
+            workDuration.setWorkPeriodStartTimestampNanos(0);
+        });
+        assertThrows(IllegalArgumentException.class, () -> {
+            workDuration.setActualTotalDurationNanos(-1);
+        });
+        assertThrows(IllegalArgumentException.class, () -> {
+            workDuration.setActualTotalDurationNanos(0);
+        });
+        assertThrows(IllegalArgumentException.class, () -> {
+            workDuration.setActualCpuDurationNanos(-1);
+        });
+        assertThrows(IllegalArgumentException.class, () -> {
+            workDuration.setActualCpuDurationNanos(0);
+        });
+        assertThrows(IllegalArgumentException.class, () -> {
+            workDuration.setActualGpuDurationNanos(-1);
+        });
+    }
+}
diff --git a/core/tests/coretests/src/android/telephony/PinResultTest.java b/core/tests/coretests/src/android/telephony/PinResultTest.java
new file mode 100644
index 0000000..c260807
--- /dev/null
+++ b/core/tests/coretests/src/android/telephony/PinResultTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class PinResultTest {
+    @Test
+    public void testSimple() throws Exception {
+        final PinResult res = new PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 5);
+        assertThat(res.getResult()).isEqualTo(PinResult.PIN_RESULT_TYPE_SUCCESS);
+        assertThat(res.getAttemptsRemaining()).isEqualTo(5);
+    }
+}
diff --git a/core/tests/coretests/src/android/text/TextLineJustificationTest.kt b/core/tests/coretests/src/android/text/TextLineJustificationTest.kt
new file mode 100644
index 0000000..a525615
--- /dev/null
+++ b/core/tests/coretests/src/android/text/TextLineJustificationTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text
+
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TextLineJustificationTest {
+
+    @Rule
+    @JvmField
+    val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+    private val PAINT = TextPaint().apply {
+        textSize = 10f // make 1em = 10px
+    }
+
+    private fun makeTextLine(cs: CharSequence, paint: TextPaint) = TextLine.obtain().apply {
+        set(paint, cs, 0, cs.length, Layout.DIR_LEFT_TO_RIGHT, Layout.DIRS_ALL_LEFT_TO_RIGHT, false,
+                null, 0, 0, false)
+    }
+
+    private fun getClusterCount(cs: CharSequence, paint: TextPaint) = TextLine.LineInfo().apply {
+        makeTextLine(cs, paint).also {
+            it.metrics(null, null, false, this)
+            TextLine.recycle(it)
+        }
+    }.clusterCount
+
+    fun justifyTest_WithoutJustify() {
+        val line = "Hello, World."
+        val tl = makeTextLine(line, PAINT)
+
+        // Without calling justify method, justifying should be false and all added spaces should
+        // be zeros.
+        assertThat(tl.isJustifying).isFalse()
+        assertThat(tl.addedWordSpacingInPx).isEqualTo(0)
+        assertThat(tl.addedLetterSpacingInPx).isEqualTo(0)
+    }
+
+    @Test
+    fun justifyTest_IntrCharacter_Latin() {
+        val line = "Hello, World."
+        val clusterCount = getClusterCount(line, PAINT)
+        val originalWidth = Layout.getDesiredWidth(line, PAINT)
+        val extraWidth = 100f
+
+        val tl = makeTextLine(line, PAINT)
+        tl.justify(Layout.JUSTIFICATION_MODE_INTER_CHARACTER, originalWidth + extraWidth)
+
+        assertThat(tl.isJustifying).isTrue()
+        assertThat(tl.addedWordSpacingInPx).isEqualTo(0)
+        assertThat(tl.addedLetterSpacingInPx).isEqualTo(extraWidth / (clusterCount - 1))
+
+        TextLine.recycle(tl)
+    }
+
+    @Test
+    fun justifyTest_IntrCharacter_Japanese() {
+        val line = "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002"
+        val clusterCount = getClusterCount(line, PAINT)
+        val originalWidth = Layout.getDesiredWidth(line, PAINT)
+        val extraWidth = 100f
+
+        val tl = makeTextLine(line, PAINT)
+        tl.justify(Layout.JUSTIFICATION_MODE_INTER_CHARACTER, originalWidth + extraWidth)
+
+        assertThat(tl.isJustifying).isTrue()
+        assertThat(tl.addedWordSpacingInPx).isEqualTo(0)
+        assertThat(tl.addedLetterSpacingInPx).isEqualTo(extraWidth / (clusterCount - 1))
+
+        TextLine.recycle(tl)
+    }
+
+    @Test
+    fun justifyTest_IntrWord_Latin() {
+        val line = "Hello, World."
+        val originalWidth = Layout.getDesiredWidth(line, PAINT)
+        val extraWidth = 100f
+
+        val tl = makeTextLine(line, PAINT)
+        tl.justify(Layout.JUSTIFICATION_MODE_INTER_WORD, originalWidth + extraWidth)
+
+        assertThat(tl.isJustifying).isTrue()
+        // This text contains only one whitespace, so word spacing should be same to the extraWidth.
+        assertThat(tl.addedWordSpacingInPx).isEqualTo(extraWidth)
+        assertThat(tl.addedLetterSpacingInPx).isEqualTo(0)
+
+        TextLine.recycle(tl)
+    }
+
+    @Test
+    fun justifyTest_IntrWord_Japanese() {
+        val line = "\u672C\u65E5\u306F\u6674\u0020\u5929\u306A\u308A\u3002"
+        val originalWidth = Layout.getDesiredWidth(line, PAINT)
+        val extraWidth = 100f
+
+        val tl = makeTextLine(line, PAINT)
+        tl.justify(Layout.JUSTIFICATION_MODE_INTER_WORD, originalWidth + extraWidth)
+
+        assertThat(tl.isJustifying).isTrue()
+        // This text contains only one whitespace, so word spacing should be same to the extraWidth.
+        assertThat(tl.addedWordSpacingInPx).isEqualTo(extraWidth)
+        assertThat(tl.addedLetterSpacingInPx).isEqualTo(0)
+
+        TextLine.recycle(tl)
+    }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
new file mode 100644
index 0000000..27869bb
--- /dev/null
+++ b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text
+
+import android.graphics.Paint
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+const val LEFT_EDGE = Paint.TEXT_RUN_FLAG_LEFT_EDGE
+const val RIGHT_EDGE = Paint.TEXT_RUN_FLAG_RIGHT_EDGE
+const val MIDDLE_OF_LINE = 0
+const val WHOLE_LINE = LEFT_EDGE or RIGHT_EDGE
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TextLineLetterSpacingTest {
+
+    @Rule
+    @JvmField
+    val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+    @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @Test
+    fun calculateRunFlagTest() {
+        // Only one Bidi run
+        assertThat(TextLine.calculateRunFlag(0, 1, Layout.DIR_LEFT_TO_RIGHT))
+                .isEqualTo(WHOLE_LINE)
+        assertThat(TextLine.calculateRunFlag(0, 1, Layout.DIR_RIGHT_TO_LEFT))
+                .isEqualTo(WHOLE_LINE)
+
+        // Two BiDi Runs.
+        // If the layout is LTR, the first run is the left most run.
+        assertThat(TextLine.calculateRunFlag(0, 2, Layout.DIR_LEFT_TO_RIGHT))
+                .isEqualTo(LEFT_EDGE)
+        // If the layout is LTR, the last run is the right most run.
+        assertThat(TextLine.calculateRunFlag(1, 2, Layout.DIR_LEFT_TO_RIGHT))
+                .isEqualTo(RIGHT_EDGE)
+        // If the layout is RTL, the first run is the right most run.
+        assertThat(TextLine.calculateRunFlag(0, 2, Layout.DIR_RIGHT_TO_LEFT))
+                .isEqualTo(RIGHT_EDGE)
+        // If the layout is RTL, the last run is the left most run.
+        assertThat(TextLine.calculateRunFlag(1, 2, Layout.DIR_RIGHT_TO_LEFT))
+                .isEqualTo(LEFT_EDGE)
+
+        // Three BiDi Runs.
+        // If the layout is LTR, the first run is the left most run.
+        assertThat(TextLine.calculateRunFlag(0, 3, Layout.DIR_LEFT_TO_RIGHT))
+                .isEqualTo(LEFT_EDGE)
+        // Regardless of the context direction, the middle run must not have any flags.
+        assertThat(TextLine.calculateRunFlag(1, 3, Layout.DIR_LEFT_TO_RIGHT))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // If the layout is LTR, the last run is the right most run.
+        assertThat(TextLine.calculateRunFlag(2, 3, Layout.DIR_LEFT_TO_RIGHT))
+                .isEqualTo(RIGHT_EDGE)
+        // If the layout is RTL, the first run is the right most run.
+        assertThat(TextLine.calculateRunFlag(0, 3, Layout.DIR_RIGHT_TO_LEFT))
+                .isEqualTo(RIGHT_EDGE)
+        // Regardless of the context direction, the middle run must not have any flags.
+        assertThat(TextLine.calculateRunFlag(1, 3, Layout.DIR_RIGHT_TO_LEFT))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // If the layout is RTL, the last run is the left most run.
+        assertThat(TextLine.calculateRunFlag(2, 3, Layout.DIR_RIGHT_TO_LEFT))
+                .isEqualTo(LEFT_EDGE)
+    }
+
+    @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @Test
+    fun resolveRunFlagForSubSequenceTest() {
+        val runStart = 5
+        val runEnd = 15
+        // Regardless of the run directions, if the span covers entire Bidi run, the same runFlag
+        // should be returned.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, false, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(LEFT_EDGE)
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, true, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(LEFT_EDGE)
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, false, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(RIGHT_EDGE)
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, true, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(RIGHT_EDGE)
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, false, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(WHOLE_LINE)
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, true, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(WHOLE_LINE)
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, false, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(MIDDLE_OF_LINE)
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, true, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(MIDDLE_OF_LINE)
+
+
+
+        // Left edge of LTR text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, false, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(LEFT_EDGE)
+        // Left edge of RTL text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, true, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Right edge of LTR text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, false, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Right edge of RTL text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, true, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(RIGHT_EDGE)
+        // Whole line of LTR text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, false, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(LEFT_EDGE)
+        // Whole line of RTL text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, true, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(RIGHT_EDGE)
+        // Middle of LTR text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, false, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Middle of RTL text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, true, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+
+
+
+        // Left edge of LTR text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, false, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Left edge of RTL text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, true, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(LEFT_EDGE)
+        // Right edge of LTR text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, false, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(RIGHT_EDGE)
+        // Right edge of RTL text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, true, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Whole line of LTR text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, false, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(RIGHT_EDGE)
+        // Whole line of RTL text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, true, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(LEFT_EDGE)
+        // Middle of LTR text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, false, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Middle of RTL text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, true, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(MIDDLE_OF_LINE)
+
+
+
+        // Left edge of LTR text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Left edge of RTL text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Right edge of LTR text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Right edge of RTL text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Whole line of LTR text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Whole line of RTL text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Middle of LTR text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Middle of RTL text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+    }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/text/TextLineTest.java b/core/tests/coretests/src/android/text/TextLineTest.java
index 34842a0..8ae5669 100644
--- a/core/tests/coretests/src/android/text/TextLineTest.java
+++ b/core/tests/coretests/src/android/text/TextLineTest.java
@@ -50,11 +50,11 @@
         tl.set(paint, line, 0, line.length(), Layout.DIR_LEFT_TO_RIGHT,
                 Layout.DIRS_ALL_LEFT_TO_RIGHT, false /* hasTabs */, null /* tabStops */,
                 0, 0 /* no ellipsis */, false /* useFallbackLinespace */);
-        final float originalWidth = tl.metrics(null, null, false);
+        final float originalWidth = tl.metrics(null, null, false, null);
         final float expandedWidth = 2 * originalWidth;
 
-        tl.justify(expandedWidth);
-        final float newWidth = tl.metrics(null, null, false);
+        tl.justify(Layout.JUSTIFICATION_MODE_INTER_WORD, expandedWidth);
+        final float newWidth = tl.metrics(null, null, false, null);
         TextLine.recycle(tl);
         return Math.abs(newWidth - expandedWidth) < 0.5;
     }
@@ -128,7 +128,7 @@
     private void assertMeasurements(final TextLine tl, final int length, boolean trailing,
             final float[] expected) {
         for (int offset = 0; offset <= length; ++offset) {
-            assertEquals(expected[offset], tl.measure(offset, trailing, null, null), 0.0f);
+            assertEquals(expected[offset], tl.measure(offset, trailing, null, null, null), 0.0f);
         }
 
         final boolean[] trailings = new boolean[length + 1];
@@ -318,7 +318,7 @@
         tl.set(new TextPaint(), text, 0, text.length(), 1, Layout.DIRS_ALL_LEFT_TO_RIGHT,
                 false /* hasTabs */, null /* tabStops */, 9, 12,
                 false /* useFallbackLineSpacing */);
-        tl.measure(text.length(), false /* trailing */, null /* fmi */, null);
+        tl.measure(text.length(), false /* trailing */, null /* fmi */, null, null);
 
         assertFalse(span.mIsUsed);
     }
@@ -335,7 +335,7 @@
         tl.set(new TextPaint(), text, 0, text.length(), 1, Layout.DIRS_ALL_LEFT_TO_RIGHT,
                 false /* hasTabs */, null /* tabStops */, 9, 12,
                 false /* useFallbackLineSpacing */);
-        tl.measure(text.length(), false /* trailing */, null /* fmi */, null);
+        tl.measure(text.length(), false /* trailing */, null /* fmi */, null, null);
 
         assertTrue(span.mIsUsed);
     }
@@ -352,7 +352,7 @@
         tl.set(new TextPaint(), text, 0, text.length(), 1, Layout.DIRS_ALL_LEFT_TO_RIGHT,
                 false /* hasTabs */, null /* tabStops */, 9, 12,
                 false /* useFallbackLineSpacing */);
-        tl.measure(text.length(), false /* trailing */, null /* fmi */, null);
+        tl.measure(text.length(), false /* trailing */, null /* fmi */, null, null);
         assertTrue(span.mIsUsed);
     }
 
diff --git a/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
new file mode 100644
index 0000000..bd2f36f
--- /dev/null
+++ b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java
@@ -0,0 +1,664 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tracing.perfetto;
+
+import static android.internal.perfetto.protos.PerfettoTrace.TestEvent.PAYLOAD;
+import static android.internal.perfetto.protos.PerfettoTrace.TestEvent.TestPayload.SINGLE_INT;
+import static android.internal.perfetto.protos.PerfettoTrace.TracePacket.FOR_TESTING;
+
+import static java.io.File.createTempFile;
+import static java.nio.file.Files.createTempDirectory;
+
+import android.internal.perfetto.protos.PerfettoTrace;
+import android.tools.common.ScenarioBuilder;
+import android.tools.common.Tag;
+import android.tools.common.io.TraceType;
+import android.tools.device.traces.TraceConfig;
+import android.tools.device.traces.TraceConfigs;
+import android.tools.device.traces.io.ResultReader;
+import android.tools.device.traces.io.ResultWriter;
+import android.tools.device.traces.monitors.PerfettoTraceMonitor;
+import android.tools.device.traces.monitors.TraceMonitor;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.truth.Truth;
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import perfetto.protos.PerfettoConfig;
+import perfetto.protos.TracePacketOuterClass;
+
+@RunWith(AndroidJUnit4.class)
+public class DataSourceTest {
+    private final File mTracingDirectory = createTempDirectory("temp").toFile();
+
+    private final ResultWriter mWriter = new ResultWriter()
+            .forScenario(new ScenarioBuilder()
+                    .forClass(createTempFile("temp", "").getName()).build())
+            .withOutputDir(mTracingDirectory)
+            .setRunComplete();
+
+    private final TraceConfigs mTraceConfig = new TraceConfigs(
+            new TraceConfig(false, true, false),
+            new TraceConfig(false, true, false),
+            new TraceConfig(false, true, false),
+            new TraceConfig(false, true, false)
+    );
+
+    private static TestDataSource sTestDataSource;
+
+    private static TestDataSource.DataSourceInstanceProvider sInstanceProvider;
+    private static TestDataSource.TlsStateProvider sTlsStateProvider;
+    private static TestDataSource.IncrementalStateProvider sIncrementalStateProvider;
+
+    public DataSourceTest() throws IOException {}
+
+    @BeforeClass
+    public static void beforeAll() {
+        Producer.init(InitArguments.DEFAULTS);
+        setupProviders();
+        sTestDataSource = new TestDataSource(
+                (ds, idx, configStream) -> sInstanceProvider.provide(ds, idx, configStream),
+                args -> sTlsStateProvider.provide(args),
+                args -> sIncrementalStateProvider.provide(args));
+        sTestDataSource.register(DataSourceParams.DEFAULTS);
+    }
+
+    private static void setupProviders() {
+        sInstanceProvider = (ds, idx, configStream) ->
+                new TestDataSource.TestDataSourceInstance(ds, idx);
+        sTlsStateProvider = args -> new TestDataSource.TestTlsState();
+        sIncrementalStateProvider = args -> new TestDataSource.TestIncrementalState();
+    }
+
+    @Before
+    public void setup() {
+        setupProviders();
+    }
+
+    @Test
+    public void canTraceData() throws InvalidProtocolBufferException {
+        final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build()).build();
+
+        try {
+            traceMonitor.start();
+
+            sTestDataSource.trace((ctx) -> {
+                final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+                long forTestingToken = protoOutputStream.start(FOR_TESTING);
+                long payloadToken = protoOutputStream.start(PAYLOAD);
+                protoOutputStream.write(SINGLE_INT, 10);
+                protoOutputStream.end(payloadToken);
+                protoOutputStream.end(forTestingToken);
+            });
+        } finally {
+            traceMonitor.stop(mWriter);
+        }
+
+        final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+        final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+        assert rawProtoFromFile != null;
+        final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
+                .parseFrom(rawProtoFromFile);
+
+        Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+        final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+                .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+        final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
+                .filter(it -> it.getForTesting().getPayload().getSingleInt() == 10).toList();
+        Truth.assertThat(matchingPackets).hasSize(1);
+    }
+
+    @Test
+    public void canUseTlsStateForCustomState() {
+        final int expectedStateTestValue = 10;
+        final AtomicInteger actualStateTestValue = new AtomicInteger();
+
+        final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build()).build();
+
+        try {
+            traceMonitor.start();
+
+            sTestDataSource.trace((ctx) -> {
+                TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+                state.testStateValue = expectedStateTestValue;
+            });
+
+            sTestDataSource.trace((ctx) -> {
+                TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+                actualStateTestValue.set(state.testStateValue);
+            });
+        } finally {
+            traceMonitor.stop(mWriter);
+        }
+
+        Truth.assertThat(actualStateTestValue.get()).isEqualTo(expectedStateTestValue);
+    }
+
+    @Test
+    public void eachInstanceHasOwnTlsState() {
+        final int[] expectedStateTestValues = new int[] { 1, 2 };
+        final int[] actualStateTestValues = new int[] { 0, 0 };
+
+        final TraceMonitor traceMonitor1 = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build()).build();
+        final TraceMonitor traceMonitor2 = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build()).build();
+
+        try {
+            traceMonitor1.start();
+            try {
+                traceMonitor2.start();
+
+                AtomicInteger index = new AtomicInteger(0);
+                sTestDataSource.trace((ctx) -> {
+                    TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+                    state.testStateValue = expectedStateTestValues[index.getAndIncrement()];
+                });
+
+                index.set(0);
+                sTestDataSource.trace((ctx) -> {
+                    TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+                    actualStateTestValues[index.getAndIncrement()] = state.testStateValue;
+                });
+            } finally {
+                traceMonitor1.stop(mWriter);
+            }
+        } finally {
+            traceMonitor2.stop(mWriter);
+        }
+
+        Truth.assertThat(actualStateTestValues[0]).isEqualTo(expectedStateTestValues[0]);
+        Truth.assertThat(actualStateTestValues[1]).isEqualTo(expectedStateTestValues[1]);
+    }
+
+    @Test
+    public void eachThreadHasOwnTlsState() throws InterruptedException {
+        final int thread1ExpectedStateValue = 1;
+        final int thread2ExpectedStateValue = 2;
+
+        final AtomicInteger thread1ActualStateValue = new AtomicInteger();
+        final AtomicInteger thread2ActualStateValue = new AtomicInteger();
+
+        final CountDownLatch setUpLatch = new CountDownLatch(2);
+        final CountDownLatch setStateLatch = new CountDownLatch(2);
+        final CountDownLatch setOutStateLatch = new CountDownLatch(2);
+
+        final RunnableCreator createTask = (stateValue, stateOut) -> () -> {
+            Producer.init(InitArguments.DEFAULTS);
+
+            setUpLatch.countDown();
+
+            try {
+                setUpLatch.await(3, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+
+            sTestDataSource.trace((ctx) -> {
+                TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+                state.testStateValue = stateValue;
+                setStateLatch.countDown();
+            });
+
+            try {
+                setStateLatch.await(3, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+
+            sTestDataSource.trace((ctx) -> {
+                stateOut.set(ctx.getCustomTlsState().testStateValue);
+                setOutStateLatch.countDown();
+            });
+        };
+
+        final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build()).build();
+
+        try {
+            traceMonitor.start();
+
+            new Thread(
+                    createTask.create(thread1ExpectedStateValue, thread1ActualStateValue)).start();
+            new Thread(
+                    createTask.create(thread2ExpectedStateValue, thread2ActualStateValue)).start();
+
+            setOutStateLatch.await(3, TimeUnit.SECONDS);
+
+        } finally {
+            traceMonitor.stop(mWriter);
+        }
+
+        Truth.assertThat(thread1ActualStateValue.get()).isEqualTo(thread1ExpectedStateValue);
+        Truth.assertThat(thread2ActualStateValue.get()).isEqualTo(thread2ExpectedStateValue);
+    }
+
+    @Test
+    public void incrementalStateIsReset() throws InterruptedException {
+
+        final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build())
+                .setIncrementalTimeout(10)
+                .build();
+
+        final AtomicInteger testStateValue = new AtomicInteger();
+        try {
+            traceMonitor.start();
+
+            sTestDataSource.trace(ctx -> ctx.getIncrementalState().testStateValue = 1);
+
+            // Timeout to make sure the incremental state is cleared.
+            Thread.sleep(1000);
+
+            sTestDataSource.trace(ctx ->
+                    testStateValue.set(ctx.getIncrementalState().testStateValue));
+        } finally {
+            traceMonitor.stop(mWriter);
+        }
+
+        Truth.assertThat(testStateValue.get()).isNotEqualTo(1);
+    }
+
+    @Test
+    public void getInstanceConfigOnCreateInstance() throws IOException {
+        final int expectedDummyIntValue = 10;
+        AtomicReference<ProtoInputStream> configStream = new AtomicReference<>();
+        sInstanceProvider = (ds, idx, config) -> {
+            configStream.set(config);
+            return new TestDataSource.TestDataSourceInstance(ds, idx);
+        };
+
+        final TraceMonitor monitor = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name)
+                        .setForTesting(PerfettoConfig.TestConfig.newBuilder().setDummyFields(
+                                PerfettoConfig.TestConfig.DummyFields.newBuilder()
+                                        .setFieldInt32(expectedDummyIntValue)
+                                        .build())
+                                .build())
+                        .build())
+                .build();
+
+        try {
+            monitor.start();
+        } finally {
+            monitor.stop(mWriter);
+        }
+
+        int configDummyIntValue = 0;
+        while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            if (configStream.get().getFieldNumber()
+                    == (int) PerfettoTrace.DataSourceConfig.FOR_TESTING) {
+                final long forTestingToken = configStream.get()
+                        .start(PerfettoTrace.DataSourceConfig.FOR_TESTING);
+                while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                    if (configStream.get().getFieldNumber()
+                            == (int) PerfettoTrace.TestConfig.DUMMY_FIELDS) {
+                        final long dummyFieldsToken = configStream.get()
+                                .start(PerfettoTrace.TestConfig.DUMMY_FIELDS);
+                        while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                            if (configStream.get().getFieldNumber()
+                                    == (int) PerfettoTrace.TestConfig.DummyFields.FIELD_INT32) {
+                                int val = configStream.get().readInt(
+                                        PerfettoTrace.TestConfig.DummyFields.FIELD_INT32);
+                                if (val != 0) {
+                                    configDummyIntValue = val;
+                                    break;
+                                }
+                            }
+                        }
+                        configStream.get().end(dummyFieldsToken);
+                        break;
+                    }
+                }
+                configStream.get().end(forTestingToken);
+                break;
+            }
+        }
+
+        Truth.assertThat(configDummyIntValue).isEqualTo(expectedDummyIntValue);
+    }
+
+    @Test
+    public void multipleTraceInstances() throws IOException, InterruptedException {
+        final int instanceCount = 3;
+
+        final List<TraceMonitor> monitors = new ArrayList<>();
+        final List<ResultWriter> writers = new ArrayList<>();
+
+        for (int i = 0; i < instanceCount; i++) {
+            final ResultWriter writer = new ResultWriter()
+                    .forScenario(new ScenarioBuilder()
+                            .forClass(createTempFile("temp", "").getName()).build())
+                    .withOutputDir(mTracingDirectory)
+                    .setRunComplete();
+            writers.add(writer);
+        }
+
+        // Start at 1 because 0 is considered null value so payload will be ignored in that case
+        TestDataSource.TestTlsState.lastIndex = 1;
+
+        final AtomicInteger traceCallCount = new AtomicInteger();
+        final CountDownLatch latch = new CountDownLatch(instanceCount);
+
+        try {
+            // Start instances
+            for (int i = 0; i < instanceCount; i++) {
+                final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+                        .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                                .setName(sTestDataSource.name).build()).build();
+                monitors.add(traceMonitor);
+                traceMonitor.start();
+            }
+
+            // Trace the stateIndex of the tracing instance.
+            sTestDataSource.trace(ctx -> {
+                final int testIntValue = ctx.getCustomTlsState().stateIndex;
+                traceCallCount.incrementAndGet();
+
+                final ProtoOutputStream os = ctx.newTracePacket();
+                long forTestingToken = os.start(FOR_TESTING);
+                long payloadToken = os.start(PAYLOAD);
+                os.write(SINGLE_INT, testIntValue);
+                os.end(payloadToken);
+                os.end(forTestingToken);
+
+                latch.countDown();
+            });
+        } finally {
+            // Stop instances
+            for (int i = 0; i < instanceCount; i++) {
+                final TraceMonitor monitor = monitors.get(i);
+                final ResultWriter writer = writers.get(i);
+                monitor.stop(writer);
+            }
+        }
+
+        latch.await(3, TimeUnit.SECONDS);
+        Truth.assertThat(traceCallCount.get()).isEqualTo(instanceCount);
+
+        for (int i = 0; i < instanceCount; i++) {
+            final int expectedTracedValue = i + 1;
+            final ResultWriter writer = writers.get(i);
+            final ResultReader reader = new ResultReader(writer.write(), mTraceConfig);
+            final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+            assert rawProtoFromFile != null;
+            final perfetto.protos.TraceOuterClass.Trace trace =
+                    perfetto.protos.TraceOuterClass.Trace.parseFrom(rawProtoFromFile);
+
+            Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+            final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+                    .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+            Truth.assertWithMessage("One packet has for testing data")
+                    .that(tracePackets).hasSize(1);
+
+            final List<TracePacketOuterClass.TracePacket> matchingPackets =
+                    tracePackets.stream()
+                            .filter(it -> it.getForTesting().getPayload()
+                                    .getSingleInt() == expectedTracedValue).toList();
+            Truth.assertWithMessage(
+                            "One packet has testing data with a payload with the expected value")
+                    .that(matchingPackets).hasSize(1);
+        }
+    }
+
+    @Test
+    public void onStartCallbackTriggered() throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        final AtomicBoolean callbackCalled = new AtomicBoolean(false);
+        sInstanceProvider = (ds, idx, config) -> new TestDataSource.TestDataSourceInstance(
+                        ds,
+                        idx,
+                        (args) -> {
+                            callbackCalled.set(true);
+                            latch.countDown();
+                        },
+                        (args) -> {},
+                        (args) -> {}
+        );
+
+        final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build()).build();
+
+        Truth.assertThat(callbackCalled.get()).isFalse();
+        try {
+            traceMonitor.start();
+            latch.await(3, TimeUnit.SECONDS);
+            Truth.assertThat(callbackCalled.get()).isTrue();
+        } finally {
+            traceMonitor.stop(mWriter);
+        }
+    }
+
+    @Test
+    public void onFlushCallbackTriggered() throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final AtomicBoolean callbackCalled = new AtomicBoolean(false);
+        sInstanceProvider = (ds, idx, config) ->
+                new TestDataSource.TestDataSourceInstance(
+                        ds,
+                        idx,
+                        (args) -> {},
+                        (args) -> {
+                            callbackCalled.set(true);
+                            latch.countDown();
+                        },
+                        (args) -> {}
+                );
+
+        final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build()).build();
+
+        try {
+            traceMonitor.start();
+            Truth.assertThat(callbackCalled.get()).isFalse();
+            sTestDataSource.trace((ctx) -> {
+                final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+                long forTestingToken = protoOutputStream.start(FOR_TESTING);
+                long payloadToken = protoOutputStream.start(PAYLOAD);
+                protoOutputStream.write(SINGLE_INT, 10);
+                protoOutputStream.end(payloadToken);
+                protoOutputStream.end(forTestingToken);
+            });
+        } finally {
+            traceMonitor.stop(mWriter);
+        }
+
+        latch.await(3, TimeUnit.SECONDS);
+        Truth.assertThat(callbackCalled.get()).isTrue();
+    }
+
+    @Test
+    public void onStopCallbackTriggered() throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final AtomicBoolean callbackCalled = new AtomicBoolean(false);
+        sInstanceProvider = (ds, idx, config) ->
+                new TestDataSource.TestDataSourceInstance(
+                        ds,
+                        idx,
+                        (args) -> {},
+                        (args) -> {},
+                        (args) -> {
+                            callbackCalled.set(true);
+                            latch.countDown();
+                        }
+                );
+
+        final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build()).build();
+
+        try {
+            traceMonitor.start();
+            Truth.assertThat(callbackCalled.get()).isFalse();
+        } finally {
+            traceMonitor.stop(mWriter);
+        }
+
+        latch.await(3, TimeUnit.SECONDS);
+        Truth.assertThat(callbackCalled.get()).isTrue();
+    }
+
+    @Test
+    public void canUseDataSourceInstanceToCreateTlsState() throws InvalidProtocolBufferException {
+        final Object testObject = new Object();
+
+        sInstanceProvider = (ds, idx, configStream) -> {
+            final TestDataSource.TestDataSourceInstance dsInstance =
+                    new TestDataSource.TestDataSourceInstance(ds, idx);
+            dsInstance.testObject = testObject;
+            return dsInstance;
+        };
+
+        sTlsStateProvider = args -> {
+            final TestDataSource.TestTlsState tlsState = new TestDataSource.TestTlsState();
+
+            try (TestDataSource.TestDataSourceInstance dataSourceInstance =
+                         args.getDataSourceInstanceLocked()) {
+                if (dataSourceInstance != null) {
+                    tlsState.testStateValue = dataSourceInstance.testObject.hashCode();
+                }
+            }
+
+            return tlsState;
+        };
+
+        final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build()).build();
+
+        try {
+            traceMonitor.start();
+            sTestDataSource.trace((ctx) -> {
+                final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+                long forTestingToken = protoOutputStream.start(FOR_TESTING);
+                long payloadToken = protoOutputStream.start(PAYLOAD);
+                protoOutputStream.write(SINGLE_INT, ctx.getCustomTlsState().testStateValue);
+                protoOutputStream.end(payloadToken);
+                protoOutputStream.end(forTestingToken);
+            });
+        } finally {
+            traceMonitor.stop(mWriter);
+        }
+
+        final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+        final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+        assert rawProtoFromFile != null;
+        final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
+                .parseFrom(rawProtoFromFile);
+
+        Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+        final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+                .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+        final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
+                .filter(it -> it.getForTesting().getPayload().getSingleInt()
+                        == testObject.hashCode()).toList();
+        Truth.assertThat(matchingPackets).hasSize(1);
+    }
+
+    @Test
+    public void canUseDataSourceInstanceToCreateIncrementalState()
+            throws InvalidProtocolBufferException {
+        final Object testObject = new Object();
+
+        sInstanceProvider = (ds, idx, configStream) -> {
+            final TestDataSource.TestDataSourceInstance dsInstance =
+                    new TestDataSource.TestDataSourceInstance(ds, idx);
+            dsInstance.testObject = testObject;
+            return dsInstance;
+        };
+
+        sIncrementalStateProvider = args -> {
+            final TestDataSource.TestIncrementalState incrementalState =
+                    new TestDataSource.TestIncrementalState();
+
+            try (TestDataSource.TestDataSourceInstance dataSourceInstance =
+                    args.getDataSourceInstanceLocked()) {
+                if (dataSourceInstance != null) {
+                    incrementalState.testStateValue = dataSourceInstance.testObject.hashCode();
+                }
+            }
+
+            return incrementalState;
+        };
+
+        final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+                .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+                        .setName(sTestDataSource.name).build()).build();
+
+        try {
+            traceMonitor.start();
+            sTestDataSource.trace((ctx) -> {
+                final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+                long forTestingToken = protoOutputStream.start(FOR_TESTING);
+                long payloadToken = protoOutputStream.start(PAYLOAD);
+                protoOutputStream.write(SINGLE_INT, ctx.getIncrementalState().testStateValue);
+                protoOutputStream.end(payloadToken);
+                protoOutputStream.end(forTestingToken);
+            });
+        } finally {
+            traceMonitor.stop(mWriter);
+        }
+
+        final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+        final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+        assert rawProtoFromFile != null;
+        final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
+                .parseFrom(rawProtoFromFile);
+
+        Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+        final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+                .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+        final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
+                .filter(it -> it.getForTesting().getPayload().getSingleInt()
+                        == testObject.hashCode()).toList();
+        Truth.assertThat(matchingPackets).hasSize(1);
+    }
+
+    interface RunnableCreator {
+        Runnable create(int state, AtomicInteger stateOut);
+    }
+}
diff --git a/core/tests/coretests/src/android/tracing/perfetto/TestDataSource.java b/core/tests/coretests/src/android/tracing/perfetto/TestDataSource.java
new file mode 100644
index 0000000..d78f78b
--- /dev/null
+++ b/core/tests/coretests/src/android/tracing/perfetto/TestDataSource.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tracing.perfetto;
+
+import android.util.proto.ProtoInputStream;
+
+import java.util.UUID;
+import java.util.function.Consumer;
+
+public class TestDataSource extends DataSource<TestDataSource.TestDataSourceInstance,
+        TestDataSource.TestTlsState, TestDataSource.TestIncrementalState> {
+    private final DataSourceInstanceProvider mDataSourceInstanceProvider;
+    private final TlsStateProvider mTlsStateProvider;
+    private final IncrementalStateProvider mIncrementalStateProvider;
+
+    interface DataSourceInstanceProvider {
+        TestDataSourceInstance provide(
+                TestDataSource dataSource, int instanceIndex, ProtoInputStream configStream);
+    }
+
+    interface TlsStateProvider {
+        TestTlsState provide(CreateTlsStateArgs<TestDataSourceInstance> args);
+    }
+
+    interface IncrementalStateProvider {
+        TestIncrementalState provide(CreateIncrementalStateArgs<TestDataSourceInstance> args);
+    }
+
+    public TestDataSource() {
+        this((ds, idx, config) -> new TestDataSourceInstance(ds, idx),
+                args -> new TestTlsState(), args -> new TestIncrementalState());
+    }
+
+    public TestDataSource(
+            DataSourceInstanceProvider dataSourceInstanceProvider,
+            TlsStateProvider tlsStateProvider,
+            IncrementalStateProvider incrementalStateProvider
+    ) {
+        super("android.tracing.perfetto.TestDataSource#" + UUID.randomUUID().toString());
+        this.mDataSourceInstanceProvider = dataSourceInstanceProvider;
+        this.mTlsStateProvider = tlsStateProvider;
+        this.mIncrementalStateProvider = incrementalStateProvider;
+    }
+
+    @Override
+    public TestDataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) {
+        return mDataSourceInstanceProvider.provide(this, instanceIndex, configStream);
+    }
+
+    @Override
+    public TestTlsState createTlsState(CreateTlsStateArgs args) {
+        return mTlsStateProvider.provide(args);
+    }
+
+    @Override
+    public TestIncrementalState createIncrementalState(CreateIncrementalStateArgs args) {
+        return mIncrementalStateProvider.provide(args);
+    }
+
+    public static class TestTlsState {
+        public int testStateValue;
+        public int stateIndex = lastIndex++;
+
+        public static int lastIndex = 0;
+    }
+
+    public static class TestIncrementalState {
+        public int testStateValue;
+    }
+
+    public static class TestDataSourceInstance extends DataSourceInstance {
+        public Object testObject;
+        Consumer<StartCallbackArguments> mStartCallback;
+        Consumer<FlushCallbackArguments> mFlushCallback;
+        Consumer<StopCallbackArguments> mStopCallback;
+
+        public TestDataSourceInstance(DataSource dataSource, int instanceIndex) {
+            this(dataSource, instanceIndex, args -> {}, args -> {}, args -> {});
+        }
+
+        public TestDataSourceInstance(
+                DataSource dataSource,
+                int instanceIndex,
+                Consumer<StartCallbackArguments> startCallback,
+                Consumer<FlushCallbackArguments> flushCallback,
+                Consumer<StopCallbackArguments> stopCallback) {
+            super(dataSource, instanceIndex);
+            this.mStartCallback = startCallback;
+            this.mFlushCallback = flushCallback;
+            this.mStopCallback = stopCallback;
+        }
+
+        @Override
+        public void onStart(StartCallbackArguments args) {
+            this.mStartCallback.accept(args);
+        }
+
+        @Override
+        public void onFlush(FlushCallbackArguments args) {
+            this.mFlushCallback.accept(args);
+        }
+
+        @Override
+        public void onStop(StopCallbackArguments args) {
+            this.mStopCallback.accept(args);
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/util/SingletonTest.java b/core/tests/coretests/src/android/util/SingletonTest.java
new file mode 100644
index 0000000..8c5a963
--- /dev/null
+++ b/core/tests/coretests/src/android/util/SingletonTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SingletonTest {
+    @Test
+    public void testSimple() throws Exception {
+        final Singleton<Object> singleton = new Singleton<>() {
+            @Override
+            protected Object create() {
+                return new Object();
+            }
+        };
+
+        final Object first = singleton.get();
+        final Object second = singleton.get();
+        assertTrue(first == second);
+    }
+}
diff --git a/core/tests/coretests/src/android/view/DisplayInfoTest.java b/core/tests/coretests/src/android/view/DisplayInfoTest.java
index 803d38c..4c5b7e5 100644
--- a/core/tests/coretests/src/android/view/DisplayInfoTest.java
+++ b/core/tests/coretests/src/android/view/DisplayInfoTest.java
@@ -21,9 +21,12 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.platform.test.ravenwood.RavenwoodRule;
+
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -32,6 +35,9 @@
 public class DisplayInfoTest {
     private static final float FLOAT_EQUAL_DELTA = 0.0001f;
 
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     @Test
     public void testDefaultDisplayInfosAreEqual() {
         DisplayInfo displayInfo1 = new DisplayInfo();
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 906d84e..672875a 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -20,8 +20,8 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
 import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ISIDE_BOTTOM;
-import static android.view.InsetsState.ISIDE_TOP;
+import static android.view.InsetsSource.SIDE_BOTTOM;
+import static android.view.InsetsSource.SIDE_TOP;
 import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
 import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
 import static android.view.RoundedCorner.POSITION_TOP_LEFT;
@@ -106,8 +106,8 @@
                 typeSideMap);
         assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets());
         assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all()));
-        assertEquals(ISIDE_TOP, typeSideMap.get(ID_STATUS_BAR));
-        assertEquals(ISIDE_BOTTOM, typeSideMap.get(ID_IME));
+        assertEquals(SIDE_TOP, typeSideMap.get(ID_STATUS_BAR));
+        assertEquals(SIDE_BOTTOM, typeSideMap.get(ID_IME));
         assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars()));
         assertEquals(Insets.of(0, 0, 0, 100), insets.getInsets(ime()));
     }
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java
index f0f3a96..0075128 100644
--- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java
@@ -433,6 +433,72 @@
         assertThat(session.mEvents).isEmpty();
     }
 
+    @Test
+    public void notifyViewAppearedBelowMaximumBufferSize() throws RemoteException {
+        ContentCaptureOptions options =
+                createOptions(
+                        /* enableContentCaptureReceiver= */ true,
+                        /* enableContentProtectionReceiver= */ true);
+        MainContentCaptureSessionV2 session = createSession(options);
+        session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+        session.onSessionStarted(0x2, null);
+        for (int i = 0; i < BUFFER_SIZE - 1; i++) {
+            View view = prepareView(session);
+            session.notifyViewAppeared(session.newViewStructure(view));
+        }
+        mTestableLooper.processAllMessages();
+
+        verify(mMockContentCaptureDirectManager, times(0))
+                .sendEvents(any(), anyInt(), any());
+        assertThat(session.mEvents).isNull();
+        assertThat(session.mEventProcessQueue).hasSize(BUFFER_SIZE - 1);
+    }
+
+    @Test
+    public void notifyViewAppearedExactAsMaximumBufferSize() throws RemoteException {
+        ContentCaptureOptions options =
+                createOptions(
+                        /* enableContentCaptureReceiver= */ true,
+                        /* enableContentProtectionReceiver= */ true);
+        MainContentCaptureSessionV2 session = createSession(options);
+        session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+        session.onSessionStarted(0x2, null);
+        for (int i = 0; i < BUFFER_SIZE; i++) {
+            View view = prepareView(session);
+            session.notifyViewAppeared(session.newViewStructure(view));
+        }
+        mTestableLooper.processAllMessages();
+
+        verify(mMockContentCaptureDirectManager, times(1))
+                .sendEvents(any(), anyInt(), any());
+        assertThat(session.mEvents).isEmpty();
+        assertThat(session.mEventProcessQueue).isEmpty();
+    }
+
+    @Test
+    public void notifyViewAppearedAboveMaximumBufferSize() throws RemoteException {
+        ContentCaptureOptions options =
+                createOptions(
+                        /* enableContentCaptureReceiver= */ true,
+                        /* enableContentProtectionReceiver= */ true);
+        MainContentCaptureSessionV2 session = createSession(options);
+        session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+        session.onSessionStarted(0x2, null);
+        for (int i = 0; i < BUFFER_SIZE * 2 + 1; i++) {
+            View view = prepareView(session);
+            session.notifyViewAppeared(session.newViewStructure(view));
+        }
+        mTestableLooper.processAllMessages();
+
+        verify(mMockContentCaptureDirectManager, times(2))
+                .sendEvents(any(), anyInt(), any());
+        assertThat(session.mEvents).isEmpty();
+        assertThat(session.mEventProcessQueue).hasSize(1);
+    }
+
     /** Simulates the regular content capture events sequence. */
     private void notifyContentCaptureEvents(final MainContentCaptureSessionV2 session) {
         final ArrayList<Object> events = new ArrayList<>(
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
deleted file mode 100644
index 909af7b..0000000
--- a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.inputmethod;
-
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
-
-import android.annotation.XmlRes;
-import android.content.Context;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.platform.test.flag.junit.SetFlagsRule;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.frameworks.coretests.R;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class InputMethodInfoTest {
-
-    @Rule
-    public SetFlagsRule mSetFlagsRule =
-            new SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
-
-    @Test
-    public void testEqualsAndHashCode() throws Exception {
-        final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta);
-        final InputMethodInfo clone = cloneViaParcel(imi);
-
-        assertThat(clone.equals(imi), is(true));
-        assertThat(clone.hashCode(), equalTo(imi.hashCode()));
-    }
-
-    @Test
-    public void testBooleanAttributes_DefaultValues() throws Exception {
-        final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta);
-
-        assertThat(imi.supportsSwitchingToNextInputMethod(), is(false));
-        assertThat(imi.isInlineSuggestionsEnabled(), is(false));
-        assertThat(imi.supportsInlineSuggestionsWithTouchExploration(), is(false));
-
-        final InputMethodInfo clone = cloneViaParcel(imi);
-
-        assertThat(clone.supportsSwitchingToNextInputMethod(), is(false));
-        assertThat(imi.isInlineSuggestionsEnabled(), is(false));
-        assertThat(imi.supportsInlineSuggestionsWithTouchExploration(), is(false));
-        assertThat(imi.supportsStylusHandwriting(), is(false));
-        assertThat(imi.createStylusHandwritingSettingsActivityIntent(), equalTo(null));
-    }
-
-    @Test
-    public void testSupportsSwitchingToNextInputMethod() throws Exception {
-        final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta_sw_next);
-
-        assertThat(imi.supportsSwitchingToNextInputMethod(), is(true));
-
-        final InputMethodInfo clone = cloneViaParcel(imi);
-
-        assertThat(clone.supportsSwitchingToNextInputMethod(), is(true));
-    }
-
-    @Test
-    public void testInlineSuggestionsEnabled() throws Exception {
-        final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta_inline_suggestions);
-
-        assertThat(imi.isInlineSuggestionsEnabled(), is(true));
-
-        final InputMethodInfo clone = cloneViaParcel(imi);
-
-        assertThat(clone.isInlineSuggestionsEnabled(), is(true));
-    }
-
-    @Test
-    public void testInlineSuggestionsEnabledWithTouchExploration() throws Exception {
-        final InputMethodInfo imi =
-                buildInputMethodForTest(R.xml.ime_meta_inline_suggestions_with_touch_exploration);
-
-        assertThat(imi.supportsInlineSuggestionsWithTouchExploration(), is(true));
-
-        final InputMethodInfo clone = cloneViaParcel(imi);
-
-        assertThat(clone.supportsInlineSuggestionsWithTouchExploration(), is(true));
-    }
-
-    @Test
-    public void testIsVrOnly() throws Exception {
-        final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta_vr_only);
-
-        assertThat(imi.isVrOnly(), is(true));
-
-        final InputMethodInfo clone = cloneViaParcel(imi);
-
-        assertThat(clone.isVrOnly(), is(true));
-    }
-
-    @Test
-    public void testIsVirtualDeviceOnly() throws Exception {
-        mSetFlagsRule.enableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_CUSTOM_IME);
-
-        final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta_virtual_device_only);
-
-        assertThat(imi.isVirtualDeviceOnly(), is(true));
-
-        final InputMethodInfo clone = cloneViaParcel(imi);
-
-        assertThat(clone.isVirtualDeviceOnly(), is(true));
-    }
-
-    private InputMethodInfo buildInputMethodForTest(final @XmlRes int metaDataRes)
-            throws Exception {
-        final Context context = InstrumentationRegistry.getContext();
-        final ServiceInfo serviceInfo = new ServiceInfo();
-        serviceInfo.applicationInfo = context.getApplicationInfo();
-        serviceInfo.packageName = context.getPackageName();
-        serviceInfo.name = "DummyImeForTest";
-        serviceInfo.metaData = new Bundle();
-        serviceInfo.metaData.putInt(InputMethod.SERVICE_META_DATA, metaDataRes);
-        final ResolveInfo resolveInfo = new ResolveInfo();
-        resolveInfo.serviceInfo = serviceInfo;
-        return new InputMethodInfo(context, resolveInfo, null /* additionalSubtypesMap */);
-    }
-
-    private InputMethodInfo cloneViaParcel(final InputMethodInfo original) {
-        Parcel parcel = null;
-        try {
-            parcel = Parcel.obtain();
-            original.writeToParcel(parcel, 0);
-            parcel.setDataPosition(0);
-            return InputMethodInfo.CREATOR.createFromParcel(parcel);
-        } finally {
-            if (parcel != null) {
-                parcel.recycle();
-            }
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 15c9047..543d73b 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -16,6 +16,8 @@
 
 package android.widget;
 
+import static android.appwidget.flags.Flags.drawDataParcel;
+
 import static com.android.internal.R.id.pending_intent_tag;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -63,6 +65,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
@@ -414,6 +417,48 @@
         assertNotNull(view.findViewById(R.id.light_background_text));
     }
 
+    @Test
+    public void remoteCanvasCanAccessDrawInstructions() {
+        if (!drawDataParcel()) {
+            return;
+        }
+        final RemoteViews.DrawInstructions drawInstructions = getDrawInstructions();
+        final RemoteViews rv = new RemoteViews(drawInstructions);
+        final View view = rv.apply(mContext, mContainer);
+        assertTrue(view instanceof RemoteCanvas);
+        assertEquals(drawInstructions, view.getTag());
+    }
+
+    @Test
+    public void remoteCanvasWiresClickHandlers() {
+        if (!drawDataParcel()) {
+            return;
+        }
+        final RemoteViews.DrawInstructions drawInstructions = getDrawInstructions();
+        final RemoteViews rv = new RemoteViews(drawInstructions);
+        final PendingIntent pi = PendingIntent.getActivity(mContext, 0,
+                new Intent(Intent.ACTION_VIEW), PendingIntent.FLAG_IMMUTABLE);
+        final Intent i = new Intent().putExtra("TEST", "Success");
+        final int viewId = 1;
+        rv.setPendingIntentTemplate(viewId, pi);
+        rv.setOnClickFillInIntent(viewId, i);
+        final View view = rv.apply(mContext, mContainer);
+        assertTrue(view instanceof RemoteCanvas);
+        RemoteCanvas target = (RemoteCanvas) view;
+        assertEquals(1, target.getCallbacks().size());
+        assertNotNull(target.getCallbacks().get(viewId));
+    }
+
+    private RemoteViews.DrawInstructions getDrawInstructions() {
+        final byte[] first = new byte[] {'f', 'i', 'r', 's', 't'};
+        final byte[] second = new byte[] {'s', 'e', 'c', 'o', 'n', 'd'};
+        final RemoteViews.DrawInstructions drawInstructions =
+                new RemoteViews.DrawInstructions.Builder(
+                        Collections.singletonList(first)).build();
+        drawInstructions.appendInstructions(second);
+        return drawInstructions;
+    }
+
     private RemoteViews createViewChained(int depth, String... texts) {
         RemoteViews result = new RemoteViews(mPackage, R.layout.remote_view_host);
 
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index a709d7b..52ff0d4 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -20,6 +20,7 @@
 import static android.window.OnBackInvokedDispatcher.PRIORITY_OVERLAY;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.atLeast;
@@ -358,7 +359,7 @@
     }
 
     @Test
-    public void onDetachFromWindow_cancelCallbackAndIgnoreOnBackInvoked() throws RemoteException {
+    public void onDetachFromWindow_cancelsBackAnimation() throws RemoteException {
         mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
 
         OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
@@ -368,13 +369,12 @@
         waitForIdle();
         verify(mCallback1).onBackStarted(any(BackEvent.class));
 
-        // This should trigger mCallback1.onBackCancelled()
+        // This should trigger mCallback1.onBackCancelled() and unset the callback in WM
         mDispatcher.detachFromWindow();
-        // This should be ignored by mCallback1
-        callbackInfo.getCallback().onBackInvoked();
 
+        OnBackInvokedCallbackInfo callbackInfo1 = assertSetCallbackInfo();
+        assertNull(callbackInfo1);
         waitForIdle();
-        verify(mCallback1, never()).onBackInvoked();
         verify(mCallback1).onBackCancelled();
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index 75b0d4a..145aa60d 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -21,7 +21,6 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN;
 import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
 import static android.provider.Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
 import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
@@ -83,6 +82,7 @@
 
 import com.android.internal.R;
 import com.android.internal.accessibility.AccessibilityShortcutController.FrameworkObjectProvider;
+import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.util.test.FakeSettingsProvider;
 
 import org.junit.AfterClass;
@@ -726,14 +726,14 @@
 
     private void configureNoShortcutService() throws Exception {
         when(mAccessibilityManagerService
-                .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
+                .getAccessibilityShortcutTargets(ShortcutConstants.UserShortcutType.HARDWARE))
                 .thenReturn(Collections.emptyList());
         Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
     }
 
     private void configureValidShortcutService() throws Exception {
         when(mAccessibilityManagerService
-                .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
+                .getAccessibilityShortcutTargets(ShortcutConstants.UserShortcutType.HARDWARE))
                 .thenReturn(Collections.singletonList(SERVICE_NAME_STRING));
         Settings.Secure.putString(
                 mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, SERVICE_NAME_STRING);
@@ -744,7 +744,7 @@
                 (ComponentName) AccessibilityShortcutController.getFrameworkShortcutFeaturesMap()
                         .keySet().toArray()[0];
         when(mAccessibilityManagerService
-                .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
+                .getAccessibilityShortcutTargets(ShortcutConstants.UserShortcutType.HARDWARE))
                 .thenReturn(Collections.singletonList(featureComponentName.flattenToString()));
         Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
                 featureComponentName.flattenToString());
@@ -806,7 +806,7 @@
 
     private void configureDefaultAccessibilityService() throws Exception {
         when(mAccessibilityManagerService
-                .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
+                .getAccessibilityShortcutTargets(ShortcutConstants.UserShortcutType.HARDWARE))
                 .thenReturn(Collections.singletonList(SERVICE_NAME_STRING));
 
         when(mResources.getString(R.string.config_defaultAccessibilityService)).thenReturn(
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
index 69b6a9b7a..2ea044c 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
@@ -39,6 +39,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.accessibility.TestUtils;
+import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 
@@ -99,7 +100,7 @@
 
         mSut = new InvisibleToggleAccessibilityServiceTarget(
                 mContextSpy,
-                AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY, accessibilityServiceInfo);
+                ShortcutConstants.UserShortcutType.HARDWARE, accessibilityServiceInfo);
     }
 
     @Test
diff --git a/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java b/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java
new file mode 100644
index 0000000..7054cc0
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.logging;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.metrics.LogMaker;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.logging.testing.FakeMetricsLogger;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MetricsLoggerTest {
+    private FakeMetricsLogger mLogger;
+
+    private static final int TEST_ACTION = 42;
+
+    @Before
+    public void setUp() throws Exception {
+        mLogger = new FakeMetricsLogger();
+    }
+
+    @Test
+    public void testEmpty() throws Exception {
+        assertThat(mLogger.getLogs().size()).isEqualTo(0);
+    }
+
+    @Test
+    public void testAction() throws Exception {
+        mLogger.action(TEST_ACTION);
+        assertThat(mLogger.getLogs().size()).isEqualTo(1);
+        final LogMaker event = mLogger.getLogs().peek();
+        assertThat(event.getType()).isEqualTo(MetricsProto.MetricsEvent.TYPE_ACTION);
+        assertThat(event.getCategory()).isEqualTo(TEST_ACTION);
+    }
+
+    @Test
+    public void testVisible() throws Exception {
+        // Limited testing to confirm we don't crash
+        mLogger.visible(TEST_ACTION);
+        mLogger.hidden(TEST_ACTION);
+        mLogger.visibility(TEST_ACTION, true);
+        mLogger.visibility(TEST_ACTION, false);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java b/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java
new file mode 100644
index 0000000..7840f71
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.logging;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.testing.UiEventLoggerFake;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class UiEventLoggerTest {
+    private UiEventLoggerFake mLogger;
+
+    private static final int TEST_EVENT_ID = 42;
+    private static final int TEST_INSTANCE_ID = 21;
+
+    private enum MyUiEventEnum implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "Example event")
+        TEST_EVENT(TEST_EVENT_ID);
+
+        private final int mId;
+
+        MyUiEventEnum(int id) {
+            mId = id;
+        }
+
+        @Override
+        public int getId() {
+            return mId;
+        }
+    }
+
+    private InstanceId TEST_INSTANCE = InstanceId.fakeInstanceId(TEST_INSTANCE_ID);
+
+    @Before
+    public void setUp() throws Exception {
+        mLogger = new UiEventLoggerFake();
+    }
+
+    @Test
+    public void testEmpty() throws Exception {
+        assertThat(mLogger.numLogs()).isEqualTo(0);
+    }
+
+    @Test
+    public void testSimple() throws Exception {
+        mLogger.log(MyUiEventEnum.TEST_EVENT);
+        assertThat(mLogger.numLogs()).isEqualTo(1);
+        assertThat(mLogger.eventId(0)).isEqualTo(TEST_EVENT_ID);
+    }
+
+    @Test
+    public void testWithInstance() throws Exception {
+        mLogger.log(MyUiEventEnum.TEST_EVENT, TEST_INSTANCE);
+        assertThat(mLogger.numLogs()).isEqualTo(1);
+        assertThat(mLogger.eventId(0)).isEqualTo(TEST_EVENT_ID);
+        assertThat(mLogger.get(0).instanceId.getId()).isEqualTo(TEST_INSTANCE_ID);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
index c9536b9..533b799 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
@@ -22,7 +22,6 @@
 
 import android.os.BadParcelableException;
 import android.os.Parcel;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
@@ -34,7 +33,6 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-@IgnoreUnderRavenwood(blockedBy = LongArrayMultiStateCounter.class)
 public class LongArrayMultiStateCounterTest {
     @Rule
     public final RavenwoodRule mRavenwood = new RavenwoodRule();
diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
index 0742052..06d888b 100644
--- a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
@@ -18,39 +18,75 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.util.Xml;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileWriter;
 import java.io.IOException;
+import java.nio.file.Files;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class MonotonicClockTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     private final MockClock mClock = new MockClock();
+    private File mFile;
+
+    @Before
+    public void setup() throws IOException {
+        File systemDir = Files.createTempDirectory("MonotonicClockTest").toFile();
+        mFile = new File(systemDir, "test_monotonic_clock.xml");
+        if (mFile.exists()) {
+            assertThat(mFile.delete()).isTrue();
+        }
+    }
 
     @Test
     public void persistence() throws IOException {
-        MonotonicClock monotonicClock = new MonotonicClock(1000, mClock);
+        MonotonicClock monotonicClock = new MonotonicClock(mFile, 1000, mClock);
         mClock.realtime = 234;
 
         assertThat(monotonicClock.monotonicTime()).isEqualTo(1234);
 
-        ByteArrayOutputStream out = new ByteArrayOutputStream();
-        monotonicClock.writeXml(out, Xml.newBinarySerializer());
+        monotonicClock.write();
 
         mClock.realtime = 42;
-        MonotonicClock newMonotonicClock = new MonotonicClock(0, mClock);
-        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
-        newMonotonicClock.readXml(in, Xml.newBinaryPullParser());
+        MonotonicClock newMonotonicClock = new MonotonicClock(mFile, 0, mClock);
 
         mClock.realtime = 2000;
         assertThat(newMonotonicClock.monotonicTime()).isEqualTo(1234 - 42 + 2000);
     }
+
+    @Test
+    public void constructor() {
+        MonotonicClock monotonicClock = new MonotonicClock(null, 1000, mClock);
+        mClock.realtime = 234;
+
+        assertThat(monotonicClock.monotonicTime()).isEqualTo(1234);
+    }
+
+    @Test
+    @IgnoreUnderRavenwood(reason = "b/321832617")
+    public void corruptedFile() throws IOException {
+        // Create an invalid binary XML file to cause IOException: "Unexpected magic number"
+        try (FileWriter w = new FileWriter(mFile)) {
+            w.write("garbage");
+        }
+
+        MonotonicClock monotonicClock = new MonotonicClock(mFile, 1000, mClock);
+        mClock.realtime = 234;
+
+        assertThat(monotonicClock.monotonicTime()).isEqualTo(1234);
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java b/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java
index 84c93c2..80061a5 100644
--- a/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java
@@ -18,7 +18,6 @@
 
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.os.FileUtils;
 
@@ -73,8 +72,7 @@
     @Test
     public void testReadNonexistentFile() throws Exception {
         mStoragedUidIoStatsReader.readAbsolute(mCallback);
-        verifyZeroInteractions(mCallback);
-
+        verifyNoMoreInteractions(mCallback);
     }
 
     /**
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
index 1a668f7..b90480a 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
@@ -16,20 +16,279 @@
 
 package com.android.internal.widget;
 
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.app.trust.TrustManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.UserInfo;
+import android.os.Looper;
+import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.test.FakeSettingsProvider;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@IgnoreUnderRavenwood(blockedBy = LockPatternUtils.class)
 public class LockPatternUtilsTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    private ILockSettings mLockSettings;
+    private static final int USER_ID = 1;
+    private static final int DEMO_USER_ID = 5;
+
+    private LockPatternUtils mLockPatternUtils;
+
+    private void configureTest(boolean isSecure, boolean isDemoUser, int deviceDemoMode)
+            throws Exception {
+        mLockSettings = mock(ILockSettings.class);
+        final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+
+        final MockContentResolver cr = new MockContentResolver(context);
+        cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        when(context.getContentResolver()).thenReturn(cr);
+        Settings.Global.putInt(cr, Settings.Global.DEVICE_DEMO_MODE, deviceDemoMode);
+
+        when(mLockSettings.getCredentialType(DEMO_USER_ID)).thenReturn(
+                isSecure ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+                         : LockPatternUtils.CREDENTIAL_TYPE_NONE);
+        when(mLockSettings.getLong("lockscreen.password_type", PASSWORD_QUALITY_UNSPECIFIED,
+                DEMO_USER_ID)).thenReturn((long) PASSWORD_QUALITY_MANAGED);
+        when(mLockSettings.hasSecureLockScreen()).thenReturn(true);
+        mLockPatternUtils = new LockPatternUtils(context, mLockSettings);
+
+        final UserInfo userInfo = mock(UserInfo.class);
+        when(userInfo.isDemo()).thenReturn(isDemoUser);
+        final UserManager um = mock(UserManager.class);
+        when(um.getUserInfo(DEMO_USER_ID)).thenReturn(userInfo);
+        when(context.getSystemService(Context.USER_SERVICE)).thenReturn(um);
+    }
+
+    @Test
+    public void isUserInLockDown() throws Exception {
+        configureTest(true, false, 2);
+
+        // GIVEN strong auth not required
+        when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(STRONG_AUTH_NOT_REQUIRED);
+
+        // THEN user isn't in lockdown
+        assertFalse(mLockPatternUtils.isUserInLockdown(USER_ID));
+
+        // GIVEN lockdown
+        when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
+                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+
+        // THEN user is in lockdown
+        assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
+
+        // GIVEN lockdown and lockout
+        when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
+                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN | STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
+
+        // THEN user is in lockdown
+        assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
+    }
+
+    @Test
+    public void isLockScreenDisabled_isDemoUser_true() throws Exception {
+        configureTest(false, true, 2);
+        assertTrue(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+    }
+
+    @Test
+    public void isLockScreenDisabled_isSecureAndDemoUser_false() throws Exception {
+        configureTest(true, true, 2);
+        assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+    }
+
+    @Test
+    public void isLockScreenDisabled_isNotDemoUser_false() throws Exception {
+        configureTest(false, false, 2);
+        assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+    }
+
+    @Test
+    public void isLockScreenDisabled_isNotInDemoMode_false() throws Exception {
+        configureTest(false, true, 0);
+        assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+    }
+
+    @Test
+    public void testAddWeakEscrowToken() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
+        int testUserId = 10;
+        IWeakEscrowTokenActivatedListener listener = createWeakEscrowTokenListener();
+        mLockPatternUtils.addWeakEscrowToken(testToken, testUserId, listener);
+        verify(ils).addWeakEscrowToken(eq(testToken), eq(testUserId), eq(listener));
+    }
+
+    @Test
+    public void testRegisterWeakEscrowTokenRemovedListener() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
+        mLockPatternUtils.registerWeakEscrowTokenRemovedListener(testListener);
+        verify(ils).registerWeakEscrowTokenRemovedListener(eq(testListener));
+    }
+
+    @Test
+    public void testUnregisterWeakEscrowTokenRemovedListener() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
+        mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(testListener);
+        verify(ils).unregisterWeakEscrowTokenRemovedListener(eq(testListener));
+    }
+
+    @Test
+    public void testRemoveAutoEscrowToken() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        int testUserId = 10;
+        long testHandle = 100L;
+        mLockPatternUtils.removeWeakEscrowToken(testHandle, testUserId);
+        verify(ils).removeWeakEscrowToken(eq(testHandle), eq(testUserId));
+    }
+
+    @Test
+    public void testIsAutoEscrowTokenActive() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        int testUserId = 10;
+        long testHandle = 100L;
+        mLockPatternUtils.isWeakEscrowTokenActive(testHandle, testUserId);
+        verify(ils).isWeakEscrowTokenActive(eq(testHandle), eq(testUserId));
+    }
+
+    @Test
+    public void testIsAutoEscrowTokenValid() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        int testUserId = 10;
+        byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
+        long testHandle = 100L;
+        mLockPatternUtils.isWeakEscrowTokenValid(testHandle, testToken, testUserId);
+        verify(ils).isWeakEscrowTokenValid(eq(testHandle), eq(testToken), eq(testUserId));
+    }
+
+    @Test
+    public void testSetEnabledTrustAgents() throws RemoteException {
+        int testUserId = 10;
+        ILockSettings ils = createTestLockSettings();
+        ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
+        doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
+        List<ComponentName> enabledTrustAgents = Lists.newArrayList(
+                ComponentName.unflattenFromString("com.android/.TrustAgent"),
+                ComponentName.unflattenFromString("com.test/.TestAgent"));
+
+        mLockPatternUtils.setEnabledTrustAgents(enabledTrustAgents, testUserId);
+
+        assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
+    }
+
+    @Test
+    public void testGetEnabledTrustAgents() throws RemoteException {
+        int testUserId = 10;
+        ILockSettings ils = createTestLockSettings();
+        when(ils.getString(anyString(), any(), anyInt())).thenReturn(
+                "com.android/.TrustAgent,com.test/.TestAgent");
+
+        List<ComponentName> trustAgents = mLockPatternUtils.getEnabledTrustAgents(testUserId);
+
+        assertThat(trustAgents).containsExactly(
+                ComponentName.unflattenFromString("com.android/.TrustAgent"),
+                ComponentName.unflattenFromString("com.test/.TestAgent"));
+    }
+
+    @Test
+    public void testSetKnownTrustAgents() throws RemoteException {
+        int testUserId = 10;
+        ILockSettings ils = createTestLockSettings();
+        ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
+        doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
+        List<ComponentName> knownTrustAgents = Lists.newArrayList(
+                ComponentName.unflattenFromString("com.android/.TrustAgent"),
+                ComponentName.unflattenFromString("com.test/.TestAgent"));
+
+        mLockPatternUtils.setKnownTrustAgents(knownTrustAgents, testUserId);
+
+        assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
+    }
+
+    @Test
+    public void testGetKnownTrustAgents() throws RemoteException {
+        int testUserId = 10;
+        ILockSettings ils = createTestLockSettings();
+        when(ils.getString(anyString(), any(), anyInt())).thenReturn(
+                "com.android/.TrustAgent,com.test/.TestAgent");
+
+        List<ComponentName> trustAgents = mLockPatternUtils.getKnownTrustAgents(testUserId);
+
+        assertThat(trustAgents).containsExactly(
+                ComponentName.unflattenFromString("com.android/.TrustAgent"),
+                ComponentName.unflattenFromString("com.test/.TestAgent"));
+    }
+
+    @Test
+    public void isBiometricAllowedForUser_afterTrustagentExpired_returnsTrue()
+            throws RemoteException {
+        TestStrongAuthTracker tracker = createStrongAuthTracker();
+        tracker.changeStrongAuth(SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED);
+
+        assertTrue(tracker.isBiometricAllowedForUser(
+                /* isStrongBiometric = */ true,
+                DEMO_USER_ID));
+    }
+
+    @Test
+    public void isBiometricAllowedForUser_afterLockout_returnsFalse()
+            throws RemoteException {
+        TestStrongAuthTracker tracker = createStrongAuthTracker();
+        tracker.changeStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
+
+        assertFalse(tracker.isBiometricAllowedForUser(
+                /* isStrongBiometric = */ true,
+                DEMO_USER_ID));
+    }
 
     @Test
     public void testUserFrp_isNotRegularUser() throws Exception {
@@ -56,4 +315,49 @@
         assertNotEquals(UserHandle.USER_CURRENT, LockPatternUtils.USER_REPAIR_MODE);
         assertNotEquals(UserHandle.USER_CURRENT_OR_SELF, LockPatternUtils.USER_REPAIR_MODE);
     }
+
+    private TestStrongAuthTracker createStrongAuthTracker() {
+        final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext());
+        return new TestStrongAuthTracker(context, Looper.getMainLooper());
+    }
+
+    private static class TestStrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
+
+        TestStrongAuthTracker(Context context, Looper looper) {
+            super(context, looper);
+        }
+
+        public void changeStrongAuth(@StrongAuthFlags int strongAuthFlags) {
+            handleStrongAuthRequiredChanged(strongAuthFlags, DEMO_USER_ID);
+        }
+    }
+
+    private ILockSettings createTestLockSettings() {
+        final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+
+        final TrustManager trustManager = mock(TrustManager.class);
+        when(context.getSystemService(Context.TRUST_SERVICE)).thenReturn(trustManager);
+
+        final ILockSettings ils = mock(ILockSettings.class);
+        mLockPatternUtils = new LockPatternUtils(context, ils);
+        return ils;
+    }
+
+    private IWeakEscrowTokenActivatedListener createWeakEscrowTokenListener() {
+        return new IWeakEscrowTokenActivatedListener.Stub() {
+            @Override
+            public void onWeakEscrowTokenActivated(long handle, int userId) {
+                // Do nothing.
+            }
+        };
+    }
+
+    private IWeakEscrowTokenRemovedListener createTestAutoEscrowTokenRemovedListener() {
+        return new IWeakEscrowTokenRemovedListener.Stub() {
+            @Override
+            public void onWeakEscrowTokenRemoved(long handle, int userId) {
+                // Do nothing.
+            }
+        };
+    }
 }
diff --git a/core/tests/nfctests/Android.bp b/core/tests/nfctests/Android.bp
deleted file mode 100644
index f81be49..0000000
--- a/core/tests/nfctests/Android.bp
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test {
-    name: "NfcManagerTests",
-    static_libs: [
-        "androidx.test.ext.junit",
-        "androidx.test.rules",
-        "mockito-target-minus-junit4",
-        "truth",
-    ],
-    libs: [
-        "android.test.runner",
-    ],
-    srcs: ["src/**/*.java"],
-    platform_apis: true,
-    certificate: "platform",
-    test_suites: ["device-tests"],
-}
diff --git a/core/tests/nfctests/OWNERS b/core/tests/nfctests/OWNERS
deleted file mode 100644
index 34b095c..0000000
--- a/core/tests/nfctests/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /core/java/android/nfc/OWNERS
diff --git a/core/tests/systemproperties/Android.bp b/core/tests/systemproperties/Android.bp
index 765ca3e..21aa3c44 100644
--- a/core/tests/systemproperties/Android.bp
+++ b/core/tests/systemproperties/Android.bp
@@ -15,6 +15,9 @@
     static_libs: [
         "android-common",
         "frameworks-core-util-lib",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "ravenwood-junit",
     ],
     libs: [
         "android.test.runner",
@@ -23,3 +26,22 @@
     platform_apis: true,
     certificate: "platform",
 }
+
+android_ravenwood_test {
+    name: "FrameworksCoreSystemPropertiesTestsRavenwood",
+    static_libs: [
+        "android-common",
+        "frameworks-core-util-lib",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "ravenwood-junit",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+    auto_gen_config: true,
+}
diff --git a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
index 67783bf..ea65de0 100644
--- a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
+++ b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
@@ -16,19 +16,36 @@
 
 package android.os;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import junit.framework.TestCase;
+import org.junit.Rule;
+import org.junit.Test;
 
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-public class SystemPropertiesTest extends TestCase {
+public class SystemPropertiesTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setSystemPropertyMutable(KEY, null)
+            .setSystemPropertyMutable(UNSET_KEY, null)
+            .setSystemPropertyMutable(PERSIST_KEY, null)
+            .build();
+
     private static final String KEY = "sys.testkey";
     private static final String UNSET_KEY = "Aiw7woh6ie4toh7W";
     private static final String PERSIST_KEY = "persist.sys.testkey";
 
+    @Test
     @SmallTest
     public void testStressPersistPropertyConsistency() throws Exception {
         for (int i = 0; i < 100; ++i) {
@@ -38,6 +55,7 @@
         }
     }
 
+    @Test
     @SmallTest
     public void testStressMemoryPropertyConsistency() throws Exception {
         for (int i = 0; i < 100; ++i) {
@@ -47,6 +65,7 @@
         }
     }
 
+    @Test
     @SmallTest
     public void testProperties() throws Exception {
         String value;
@@ -93,6 +112,7 @@
       assertEquals(expected, value);
     }
 
+    @Test
     @SmallTest
     public void testHandle() throws Exception {
         String value;
@@ -114,6 +134,7 @@
         assertEquals(12345, handle.getInt(12345));
     }
 
+    @Test
     @SmallTest
     public void testIntegralProperties() throws Exception {
         testInt("", 123, 123);
@@ -133,6 +154,7 @@
         testLong("-3147483647", 124, -3147483647L);
     }
 
+    @Test
     @SmallTest
     public void testUnset() throws Exception {
         assertEquals("abc", SystemProperties.get(UNSET_KEY, "abc"));
@@ -142,6 +164,7 @@
         assertEquals(-10, SystemProperties.getLong(UNSET_KEY, -10));
     }
 
+    @Test
     @SmallTest
     @SuppressWarnings("null")
     public void testNullKey() throws Exception {
@@ -176,6 +199,7 @@
         }
     }
 
+    @Test
     @SmallTest
     public void testCallbacks() {
         // Latches are not really necessary, but are easy to use.
@@ -220,6 +244,7 @@
         }
     }
 
+    @Test
     @SmallTest
     public void testDigestOf() {
         final String empty = SystemProperties.digestOf();
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
deleted file mode 100644
index 0df5b0a..0000000
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.util;
-
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.UserInfo;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.UserManager;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
-import android.platform.test.ravenwood.RavenwoodRule;
-import android.provider.Settings;
-import android.test.mock.MockContentResolver;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.widget.ILockSettings;
-import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
-import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
-import com.android.internal.widget.LockPatternUtils;
-
-import com.google.android.collect.Lists;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
-
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@IgnoreUnderRavenwood(blockedBy = LockPatternUtils.class)
-public class LockPatternUtilsTest {
-    @Rule
-    public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
-    private static final int DEMO_USER_ID = 5;
-
-    private LockPatternUtils mLockPatternUtils;
-
-    private void configureTest(boolean isSecure, boolean isDemoUser, int deviceDemoMode)
-            throws Exception {
-        final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
-
-        final MockContentResolver cr = new MockContentResolver(context);
-        cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
-        when(context.getContentResolver()).thenReturn(cr);
-        Settings.Global.putInt(cr, Settings.Global.DEVICE_DEMO_MODE, deviceDemoMode);
-
-        final ILockSettings ils = Mockito.mock(ILockSettings.class);
-        when(ils.getCredentialType(DEMO_USER_ID)).thenReturn(
-                isSecure ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
-                         : LockPatternUtils.CREDENTIAL_TYPE_NONE);
-        when(ils.getLong("lockscreen.password_type", PASSWORD_QUALITY_UNSPECIFIED, DEMO_USER_ID))
-                .thenReturn((long) PASSWORD_QUALITY_MANAGED);
-        // TODO(b/63758238): stop spying the class under test
-        mLockPatternUtils = spy(new LockPatternUtils(context));
-        when(mLockPatternUtils.getLockSettings()).thenReturn(ils);
-        doReturn(true).when(mLockPatternUtils).hasSecureLockScreen();
-
-        final UserInfo userInfo = Mockito.mock(UserInfo.class);
-        when(userInfo.isDemo()).thenReturn(isDemoUser);
-        final UserManager um = Mockito.mock(UserManager.class);
-        when(um.getUserInfo(DEMO_USER_ID)).thenReturn(userInfo);
-        when(context.getSystemService(Context.USER_SERVICE)).thenReturn(um);
-    }
-
-    @Test
-    public void isLockScreenDisabled_isDemoUser_true() throws Exception {
-        configureTest(false, true, 2);
-        assertTrue(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
-    }
-
-    @Test
-    public void isLockScreenDisabled_isSecureAndDemoUser_false() throws Exception {
-        configureTest(true, true, 2);
-        assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
-    }
-
-    @Test
-    public void isLockScreenDisabled_isNotDemoUser_false() throws Exception {
-        configureTest(false, false, 2);
-        assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
-    }
-
-    @Test
-    public void isLockScreenDisabled_isNotInDemoMode_false() throws Exception {
-        configureTest(false, true, 0);
-        assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
-    }
-
-    @Test
-    public void testAddWeakEscrowToken() throws RemoteException {
-        ILockSettings ils = createTestLockSettings();
-        byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
-        int testUserId = 10;
-        IWeakEscrowTokenActivatedListener listener = createWeakEscrowTokenListener();
-        mLockPatternUtils.addWeakEscrowToken(testToken, testUserId, listener);
-        verify(ils).addWeakEscrowToken(eq(testToken), eq(testUserId), eq(listener));
-    }
-
-    @Test
-    public void testRegisterWeakEscrowTokenRemovedListener() throws RemoteException {
-        ILockSettings ils = createTestLockSettings();
-        IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
-        mLockPatternUtils.registerWeakEscrowTokenRemovedListener(testListener);
-        verify(ils).registerWeakEscrowTokenRemovedListener(eq(testListener));
-    }
-
-    @Test
-    public void testUnregisterWeakEscrowTokenRemovedListener() throws RemoteException {
-        ILockSettings ils = createTestLockSettings();
-        IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
-        mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(testListener);
-        verify(ils).unregisterWeakEscrowTokenRemovedListener(eq(testListener));
-    }
-
-    @Test
-    public void testRemoveAutoEscrowToken() throws RemoteException {
-        ILockSettings ils = createTestLockSettings();
-        int testUserId = 10;
-        long testHandle = 100L;
-        mLockPatternUtils.removeWeakEscrowToken(testHandle, testUserId);
-        verify(ils).removeWeakEscrowToken(eq(testHandle), eq(testUserId));
-    }
-
-    @Test
-    public void testIsAutoEscrowTokenActive() throws RemoteException {
-        ILockSettings ils = createTestLockSettings();
-        int testUserId = 10;
-        long testHandle = 100L;
-        mLockPatternUtils.isWeakEscrowTokenActive(testHandle, testUserId);
-        verify(ils).isWeakEscrowTokenActive(eq(testHandle), eq(testUserId));
-    }
-
-    @Test
-    public void testIsAutoEscrowTokenValid() throws RemoteException {
-        ILockSettings ils = createTestLockSettings();
-        int testUserId = 10;
-        byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
-        long testHandle = 100L;
-        mLockPatternUtils.isWeakEscrowTokenValid(testHandle, testToken, testUserId);
-        verify(ils).isWeakEscrowTokenValid(eq(testHandle), eq(testToken), eq(testUserId));
-    }
-
-    @Test
-    public void testSetEnabledTrustAgents() throws RemoteException {
-        int testUserId = 10;
-        ILockSettings ils = createTestLockSettings();
-        ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
-        doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
-        List<ComponentName> enabledTrustAgents = Lists.newArrayList(
-                ComponentName.unflattenFromString("com.android/.TrustAgent"),
-                ComponentName.unflattenFromString("com.test/.TestAgent"));
-
-        mLockPatternUtils.setEnabledTrustAgents(enabledTrustAgents, testUserId);
-
-        assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
-    }
-
-    @Test
-    public void testGetEnabledTrustAgents() throws RemoteException {
-        int testUserId = 10;
-        ILockSettings ils = createTestLockSettings();
-        when(ils.getString(anyString(), any(), anyInt())).thenReturn(
-                "com.android/.TrustAgent,com.test/.TestAgent");
-
-        List<ComponentName> trustAgents = mLockPatternUtils.getEnabledTrustAgents(testUserId);
-
-        assertThat(trustAgents).containsExactly(
-                ComponentName.unflattenFromString("com.android/.TrustAgent"),
-                ComponentName.unflattenFromString("com.test/.TestAgent"));
-    }
-
-    @Test
-    public void testSetKnownTrustAgents() throws RemoteException {
-        int testUserId = 10;
-        ILockSettings ils = createTestLockSettings();
-        ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
-        doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
-        List<ComponentName> knownTrustAgents = Lists.newArrayList(
-                ComponentName.unflattenFromString("com.android/.TrustAgent"),
-                ComponentName.unflattenFromString("com.test/.TestAgent"));
-
-        mLockPatternUtils.setKnownTrustAgents(knownTrustAgents, testUserId);
-
-        assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
-    }
-
-    @Test
-    public void testGetKnownTrustAgents() throws RemoteException {
-        int testUserId = 10;
-        ILockSettings ils = createTestLockSettings();
-        when(ils.getString(anyString(), any(), anyInt())).thenReturn(
-                "com.android/.TrustAgent,com.test/.TestAgent");
-
-        List<ComponentName> trustAgents = mLockPatternUtils.getKnownTrustAgents(testUserId);
-
-        assertThat(trustAgents).containsExactly(
-                ComponentName.unflattenFromString("com.android/.TrustAgent"),
-                ComponentName.unflattenFromString("com.test/.TestAgent"));
-    }
-
-    @Test
-    public void isBiometricAllowedForUser_afterTrustagentExpired_returnsTrue()
-            throws RemoteException {
-        TestStrongAuthTracker tracker = createStrongAuthTracker();
-        tracker.changeStrongAuth(SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED);
-
-        assertTrue(tracker.isBiometricAllowedForUser(
-                /* isStrongBiometric = */ true,
-                DEMO_USER_ID));
-    }
-
-    @Test
-    public void isBiometricAllowedForUser_afterLockout_returnsFalse()
-            throws RemoteException {
-        TestStrongAuthTracker tracker = createStrongAuthTracker();
-        tracker.changeStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
-
-        assertFalse(tracker.isBiometricAllowedForUser(
-                /* isStrongBiometric = */ true,
-                DEMO_USER_ID));
-    }
-
-
-    private TestStrongAuthTracker createStrongAuthTracker() {
-        final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext());
-        return new TestStrongAuthTracker(context, Looper.getMainLooper());
-    }
-
-    private static class TestStrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
-
-        TestStrongAuthTracker(Context context, Looper looper) {
-            super(context, looper);
-        }
-
-        public void changeStrongAuth(@StrongAuthFlags int strongAuthFlags) {
-            handleStrongAuthRequiredChanged(strongAuthFlags, DEMO_USER_ID);
-        }
-    }
-
-    private ILockSettings createTestLockSettings() {
-        final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
-        mLockPatternUtils = spy(new LockPatternUtils(context));
-        final ILockSettings ils = Mockito.mock(ILockSettings.class);
-        when(mLockPatternUtils.getLockSettings()).thenReturn(ils);
-        return ils;
-    }
-
-    private IWeakEscrowTokenActivatedListener createWeakEscrowTokenListener() {
-        return new IWeakEscrowTokenActivatedListener.Stub() {
-            @Override
-            public void onWeakEscrowTokenActivated(long handle, int userId) {
-                // Do nothing.
-            }
-        };
-    }
-
-    private IWeakEscrowTokenRemovedListener createTestAutoEscrowTokenRemovedListener() {
-        return new IWeakEscrowTokenRemovedListener.Stub() {
-            @Override
-            public void onWeakEscrowTokenRemoved(long handle, int userId) {
-                // Do nothing.
-            }
-        };
-    }
-}
diff --git a/data/etc/com.android.documentsui.xml b/data/etc/com.android.documentsui.xml
index d32cbec..2e521e3 100644
--- a/data/etc/com.android.documentsui.xml
+++ b/data/etc/com.android.documentsui.xml
@@ -23,5 +23,7 @@
         <permission name="android.permission.MODIFY_QUIET_MODE"/>
         <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
         <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"/>
+        <!-- Permissions required for reading device configs -->
+        <permission name="android.permission.READ_DEVICE_CONFIG" />
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index dcc9686..fbe1b8e 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -48,6 +48,7 @@
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
         <permission name="android.permission.READ_SEARCH_INDEXABLES"/>
         <permission name="android.permission.REBOOT"/>
+        <permission name="android.permission.RECOVERY"/>
         <permission name="android.permission.STATUS_BAR"/>
         <permission name="android.permission.SUGGEST_MANUAL_TIME_AND_ZONE"/>
         <permission name="android.permission.TETHER_PRIVILEGED"/>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index a1ea2b8..2873428 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -23,40 +23,48 @@
     <!-- Needed for Build.getSerial(), which is used to send a unique number for serial, per HUIG. -->
     <privapp-permissions package="android.car.usb.handler">
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.angle">
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.apps.tag">
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.backupconfirm">
         <permission name="android.permission.BACKUP"/>
         <permission name="android.permission.CRYPT_KEEPER"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.externalstorage">
         <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
         <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
         <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.imsserviceentitlement">
         <permission name="android.permission.MODIFY_PHONE_STATE" />
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.launcher3">
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.location.fused">
         <permission name="android.permission.INSTALL_LOCATION_PROVIDER"/>
         <permission name="android.permission.UPDATE_DEVICE_STATS"/>
         <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.managedprovisioning">
@@ -79,12 +87,14 @@
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
         <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
         <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.mms.service">
         <permission name="android.permission.BIND_CARRIER_MESSAGING_SERVICE"/>
         <permission name="android.permission.BIND_CARRIER_SERVICES"/>
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.mtp">
@@ -94,16 +104,19 @@
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
         <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
         <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.musicfx">
         <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.networkrecommendation">
         <permission name="android.permission.SCORE_NETWORKS"/>
         <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.packageinstaller">
@@ -114,6 +127,7 @@
         <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
         <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
         <permission name="android.permission.PACKAGE_USAGE_STATS"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.phone">
@@ -121,6 +135,7 @@
         <permission name="android.permission.BIND_CARRIER_MESSAGING_SERVICE"/>
         <permission name="android.permission.BIND_CARRIER_SERVICES"/>
         <permission name="android.permission.BIND_CELL_BROADCAST_SERVICE"/>
+        <permission name="android.permission.BIND_DOMAIN_SELECTION_SERVICE"/>
         <permission name="android.permission.BIND_IMS_SERVICE"/>
         <permission name="android.permission.BIND_SATELLITE_GATEWAY_SERVICE"/>
         <permission name="android.permission.BIND_SATELLITE_SERVICE"/>
@@ -169,6 +184,7 @@
         <permission name="android.permission.LOG_COMPAT_CHANGE"/>
         <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
         <permission name="android.permission.UWB_PRIVILEGED"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.providers.calendar">
@@ -179,6 +195,7 @@
         <permission name="android.permission.USE_RESERVED_DISK"/>
         <permission name="android.permission.LOG_COMPAT_CHANGE" />
         <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.providers.contacts">
@@ -192,6 +209,7 @@
         <permission name="android.permission.USE_RESERVED_DISK"/>
         <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
         <permission name="android.permission.LOG_COMPAT_CHANGE" />
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.providers.downloads">
@@ -204,6 +222,7 @@
         <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
         <permission name="android.permission.UPDATE_DEVICE_STATS"/>
         <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.providers.telephony">
@@ -213,6 +232,7 @@
         <!-- Permissions required for reading and logging compat changes -->
         <permission name="android.permission.LOG_COMPAT_CHANGE" />
         <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.server.telecom">
@@ -228,11 +248,13 @@
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
         <permission name="android.permission.STOP_APP_SWITCHES"/>
         <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.sharedstoragebackup">
         <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
         <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.shell">
@@ -399,6 +421,8 @@
         <permission name="android.permission.REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING" />
         <permission name="android.permission.REQUEST_COMPANION_PROFILE_COMPUTER" />
         <permission name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
+        <permission name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE" />
+
         <!-- Permission required for testing registering pull atom callbacks. -->
         <permission name="android.permission.REGISTER_STATS_PULL_ATOM"/>
         <!-- Permission required for testing system audio effect APIs. -->
@@ -426,6 +450,8 @@
         <!-- Permissions required for CTS test - android.server.biometrics -->
         <permission name="android.permission.USE_BIOMETRIC" />
         <permission name="android.permission.TEST_BIOMETRIC" />
+        <permission name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" />
+        <permission name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
         <!-- Permissions required for CTS test - CtsContactsProviderTestCases -->
         <permission name="android.contacts.permission.MANAGE_SIM_ACCOUNTS" />
         <!-- Permissions required for CTS test - CtsHdmiCecHostTestCases -->
@@ -511,6 +537,7 @@
         <permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
         <!-- Permission required for CTS test - CtsTelephonyTestCases -->
         <permission name="android.permission.BIND_TELECOM_CONNECTION_SERVICE" />
+        <permission name="android.permission.ACCESS_LAST_KNOWN_CELL_ID" />
         <!-- Permission required for CTS test - CtsAppTestCases -->
         <permission name="android.permission.CAPTURE_MEDIA_OUTPUT" />
         <permission name="android.permission.CAPTURE_TUNER_AUDIO_INPUT" />
@@ -541,16 +568,21 @@
         <permission name="android.permission.GET_BINDING_UID_IMPORTANCE"/>
         <!-- Permission required for CTS test NotificationManagerZenTest -->
         <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
+        <!-- Permission required for BinaryTransparencyService shell API and host test -->
+        <permission name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" />
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
         <permission name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"/>
         <permission name="android.permission.DOMAIN_VERIFICATION_AGENT"/>
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.soundpicker">
         <permission name="android.permission.INTERACT_ACROSS_USERS" />
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.tv">
@@ -562,15 +594,18 @@
         <permission name="android.permission.READ_CONTENT_RATING_SYSTEMS"/>
         <permission name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA"/>
         <permission name="com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.vpndialogs">
         <permission name="android.permission.CONTROL_VPN"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.wallpaper.livepicker">
         <permission name="android.permission.SET_WALLPAPER_COMPONENT"/>
         <permission name="android.permission.BIND_WALLPAPER"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.wallpaper">
@@ -578,25 +613,35 @@
         <permission name="android.permission.BIND_WALLPAPER"/>
         <permission name="android.permission.CUSTOMIZE_SYSTEM_UI"/>
         <permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.dynsystem">
         <permission name="android.permission.REBOOT"/>
         <permission name="android.permission.MANAGE_DYNAMIC_SYSTEM"/>
         <permission name="android.permission.READ_OEM_UNLOCK_STATE"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
     <privapp-permissions package="com.android.settings">
         <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
         <permission name="android.permission.BIND_CELL_BROADCAST_SERVICE"/>
         <permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
         <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.bips">
         <permission name="android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.calllogbackup">
         <permission name="com.android.voicemail.permission.READ_VOICEMAIL"/>
+        <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
+    </privapp-permissions>
+
+   <privapp-permissions package="com.android.devicediagnostics">
+        <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+        <permission name="android.permission.BATTERY_STATS"/>
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 917a300..da91a96 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -535,6 +535,12 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/ActivityStarter.java"
     },
+    "-1583619037": {
+      "message": "Failed to register MediaProjectionWatcherCallback",
+      "level": "ERROR",
+      "group": "WM_ERROR",
+      "at": "com\/android\/server\/wm\/ScreenRecordingCallbackController.java"
+    },
     "-1582845629": {
       "message": "Starting animation on %s",
       "level": "VERBOSE",
@@ -1861,6 +1867,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
     },
+    "-483957611": {
+      "message": "Resuming configuration dispatch for %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-481924678": {
       "message": "handleNotObscuredLocked w: %s, w.mHasSurface: %b, w.isOnScreen(): %b, w.isDisplayedLw(): %b, w.mAttrs.userActivityTimeout: %d",
       "level": "DEBUG",
@@ -2983,12 +2995,6 @@
       "group": "WM_DEBUG_SCREEN_ON",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "466506262": {
-      "message": "Clear freezing of %s: visible=%b freezing=%b",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_ORIENTATION",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "485170982": {
       "message": "Not finishing noHistory %s on stop because we're just sleeping",
       "level": "DEBUG",
@@ -4021,6 +4027,12 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
+    "1473051122": {
+      "message": "Pausing configuration dispatch for  %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "1494644409": {
       "message": "  Rejecting as detached: %s",
       "level": "VERBOSE",
@@ -4333,6 +4345,12 @@
       "group": "WM_DEBUG_ANIM",
       "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
     },
+    "1810872941": {
+      "message": "setWallpaperCropHints: non-existent wallpaper token: %s",
+      "level": "WARN",
+      "group": "WM_ERROR",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "1820873642": {
       "message": "SyncGroup %d:  Unfinished dependencies: %s",
       "level": "VERBOSE",
diff --git a/data/fonts/Android.bp b/data/fonts/Android.bp
index 3dd9ba9..f1a6b69 100644
--- a/data/fonts/Android.bp
+++ b/data/fonts/Android.bp
@@ -48,12 +48,41 @@
 // Copies the font configuration file into system/etc for the product as fonts.xml.
 // Additional fonts should be installed to /product/fonts/ alongside a corresponding
 // fonts_customiztion.xml in /product/etc/
-prebuilt_etc {
-    name: "fonts.xml",
-    src: "fonts.xml",
+
+soong_config_bool_variable {
+    name: "use_var_font",
 }
 
-prebuilt_etc {
+soong_config_module_type {
+    name: "prebuilt_fonts_xml",
+    module_type: "prebuilt_etc",
+    config_namespace: "noto_sans_cjk_config",
+    bool_variables: ["use_var_font"],
+    properties: ["src"],
+}
+
+prebuilt_fonts_xml {
+    name: "fonts.xml",
+    src: "fonts.xml",
+    soong_config_variables: {
+        use_var_font: {
+            src: "fonts_cjkvf.xml",
+        },
+    },
+}
+
+prebuilt_fonts_xml {
     name: "font_fallback.xml",
     src: "font_fallback.xml",
+    soong_config_variables: {
+        use_var_font: {
+            src: "font_fallback_cjkvf.xml",
+        },
+    },
 }
+
+/////////////////////////////////
+// Move `fontchain_lint` to `core/tasks/fontchain_lint.mk`.
+// Because `system.img` is a dependency of `fontchain_lint`, it cannot be
+// converted to Android.bp for now.
+// After system.img can be generated by Soong, then it can be converted to Android.bp.
diff --git a/data/fonts/Android.mk b/data/fonts/Android.mk
deleted file mode 100644
index a322b82..0000000
--- a/data/fonts/Android.mk
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright (C) 2011 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-# Run sanity tests on fonts on checkbuild
-checkbuild: fontchain_lint
-
-FONTCHAIN_LINTER := $(HOST_OUT_EXECUTABLES)/fontchain_linter
-ifeq ($(MINIMAL_FONT_FOOTPRINT),true)
-CHECK_EMOJI := false
-else
-CHECK_EMOJI := true
-endif
-
-fontchain_lint_timestamp := $(call intermediates-dir-for,PACKAGING,fontchain_lint)/stamp
-
-.PHONY: fontchain_lint
-fontchain_lint: $(fontchain_lint_timestamp)
-
-fontchain_lint_deps := \
-    external/unicode/DerivedAge.txt \
-    external/unicode/emoji-data.txt \
-    external/unicode/emoji-sequences.txt \
-    external/unicode/emoji-variation-sequences.txt \
-    external/unicode/emoji-zwj-sequences.txt \
-    external/unicode/additions/emoji-data.txt \
-    external/unicode/additions/emoji-sequences.txt \
-    external/unicode/additions/emoji-zwj-sequences.txt \
-
-$(fontchain_lint_timestamp): $(FONTCHAIN_LINTER) $(TARGET_OUT)/etc/fonts.xml $(PRODUCT_OUT)/system.img $(fontchain_lint_deps)
-	@echo Running fontchain lint
-	$(FONTCHAIN_LINTER) $(TARGET_OUT) $(CHECK_EMOJI) external/unicode
-	touch $@
diff --git a/data/fonts/font_fallback.xml b/data/fonts/font_fallback.xml
index 02e032b..1bd182f 100644
--- a/data/fonts/font_fallback.xml
+++ b/data/fonts/font_fallback.xml
@@ -730,6 +730,16 @@
               postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
         </font>
     </family>
+    <family lang="ja">
+        <font weight="400" style="normal">
+            NotoSerifHentaigana-EL.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="700" style="normal">
+            NotoSerifHentaigana-EL.ttf
+          <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
     <family lang="ko">
         <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Regular">
             NotoSansCJK-Regular.ttc
diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml
new file mode 100644
index 0000000..75bc74e
--- /dev/null
+++ b/data/fonts/font_fallback_cjkvf.xml
@@ -0,0 +1,1025 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    In this file, all fonts without names are added to the default list.
+    Fonts are chosen based on a match: full BCP-47 language tag including
+    script, then just language, and finally order (the first font containing
+    the glyph).
+
+    Order of appearance is also the tiebreaker for weight matching. This is
+    the reason why the 900 weights of Roboto precede the 700 weights - we
+    prefer the former when an 800 weight is requested. Since bold spans
+    effectively add 300 to the weight, this ensures that 900 is the bold
+    paired with the 500 weight, ensuring adequate contrast.
+
+    TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
+-->
+<familyset version="23">
+    <!-- first font is default -->
+    <family name="sans-serif" varFamilyType="2">
+        <font>Roboto-Regular.ttf
+          <axis tag="wdth" stylevalue="100" />
+        </font>
+   </family>
+
+
+    <!-- Note that aliases must come after the fonts they reference. -->
+    <alias name="sans-serif-thin" to="sans-serif" weight="100" />
+    <alias name="sans-serif-light" to="sans-serif" weight="300" />
+    <alias name="sans-serif-medium" to="sans-serif" weight="500" />
+    <alias name="sans-serif-black" to="sans-serif" weight="900" />
+    <alias name="arial" to="sans-serif" />
+    <alias name="helvetica" to="sans-serif" />
+    <alias name="tahoma" to="sans-serif" />
+    <alias name="verdana" to="sans-serif" />
+
+    <family name="sans-serif-condensed" varFamilyType="2">
+      <font>Roboto-Regular.ttf
+        <axis tag="wdth" stylevalue="75" />
+      </font>
+    </family>
+    <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
+    <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" />
+
+    <family name="serif">
+        <font weight="400" style="normal" postScriptName="NotoSerif">NotoSerif-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSerif-Bold.ttf</font>
+        <font weight="400" style="italic">NotoSerif-Italic.ttf</font>
+        <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font>
+    </family>
+    <alias name="serif-bold" to="serif" weight="700" />
+    <alias name="times" to="serif" />
+    <alias name="times new roman" to="serif" />
+    <alias name="palatino" to="serif" />
+    <alias name="georgia" to="serif" />
+    <alias name="baskerville" to="serif" />
+    <alias name="goudy" to="serif" />
+    <alias name="fantasy" to="serif" />
+    <alias name="ITC Stone Serif" to="serif" />
+
+    <family name="monospace">
+        <font weight="400" style="normal">DroidSansMono.ttf</font>
+    </family>
+    <alias name="sans-serif-monospace" to="monospace" />
+    <alias name="monaco" to="monospace" />
+
+    <family name="serif-monospace">
+        <font weight="400" style="normal" postScriptName="CutiveMono-Regular">CutiveMono.ttf</font>
+    </family>
+    <alias name="courier" to="serif-monospace" />
+    <alias name="courier new" to="serif-monospace" />
+
+    <family name="casual">
+        <font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
+    </family>
+
+    <family name="cursive" varFamilyType="1">
+      <font>DancingScript-Regular.ttf</font>
+    </family>
+
+    <family name="sans-serif-smallcaps">
+        <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
+    </family>
+
+    <family name="source-sans-pro">
+        <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
+        <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
+        <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font>
+        <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font>
+        <font weight="700" style="normal">SourceSansPro-Bold.ttf</font>
+        <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font>
+    </family>
+    <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
+
+    <family name="roboto-flex" varFamilyType="2">
+        <font>RobotoFlex-Regular.ttf
+          <axis tag="wdth" stylevalue="100" />
+        </font>
+    </family>
+
+    <!-- fallback fonts -->
+    <family lang="und-Arab" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
+            NotoNaskhArabic-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
+    </family>
+    <family lang="und-Arab" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
+            NotoNaskhArabicUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Ethi" varFamilyType="1">
+        <font postScriptName="NotoSansEthiopic-Regular">
+            NotoSansEthiopic-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular">
+            NotoSerifEthiopic-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Hebr">
+        <font weight="400" style="normal" postScriptName="NotoSansHebrew">
+            NotoSansHebrew-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font>
+    </family>
+    <family lang="und-Thai" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">
+            NotoSerifThai-Regular.ttf
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
+    </family>
+    <family lang="und-Thai" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansThaiUI">
+            NotoSansThaiUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Armn" varFamilyType="1">
+        <font postScriptName="NotoSansArmenian-Regular">
+            NotoSansArmenian-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular">
+            NotoSerifArmenian-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Geor,und-Geok" varFamilyType="1">
+        <font postScriptName="NotoSansGeorgian-Regular">
+            NotoSansGeorgian-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular">
+            NotoSerifGeorgian-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Deva" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansDevanagari-Regular">
+            NotoSansDevanagari-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular">
+            NotoSerifDevanagari-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Deva" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansDevanagariUI-Regular">
+            NotoSansDevanagariUI-VF.ttf
+        </font>
+    </family>
+
+    <!-- All scripts of India should come after Devanagari, due to shared
+         danda characters.
+    -->
+    <family lang="und-Gujr" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansGujarati">
+            NotoSansGujarati-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Gujr" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansGujaratiUI">
+            NotoSansGujaratiUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Guru" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansGurmukhi-Regular">
+            NotoSansGurmukhi-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular">
+            NotoSerifGurmukhi-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Guru" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansGurmukhiUI-Regular">
+            NotoSansGurmukhiUI-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Taml" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansTamil-Regular">
+            NotoSansTamil-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular">
+            NotoSerifTamil-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Taml" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansTamilUI-Regular">
+            NotoSansTamilUI-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Mlym" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansMalayalam-Regular">
+            NotoSansMalayalam-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular">
+            NotoSerifMalayalam-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Mlym" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansMalayalamUI-Regular">
+            NotoSansMalayalamUI-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Beng" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansBengali-Regular">
+            NotoSansBengali-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular">
+            NotoSerifBengali-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Beng" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansBengaliUI-Regular">
+            NotoSansBengaliUI-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Telu" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansTelugu-Regular">
+            NotoSansTelugu-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular">
+            NotoSerifTelugu-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Telu" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansTeluguUI-Regular">
+            NotoSansTeluguUI-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Knda" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansKannada-Regular">
+            NotoSansKannada-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular">
+            NotoSerifKannada-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Knda" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansKannadaUI-Regular">
+            NotoSansKannadaUI-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Orya" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansOriya">NotoSansOriya-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansOriya-Bold.ttf</font>
+    </family>
+    <family lang="und-Orya" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansOriyaUI">
+            NotoSansOriyaUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Sinh" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansSinhala-Regular">
+            NotoSansSinhala-VF.ttf
+        </font>
+        <font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular">
+            NotoSerifSinhala-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Sinh" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansSinhalaUI-Regular">
+            NotoSansSinhalaUI-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Khmr" variant="elegant">
+        <font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="26.0"/>
+        </font>
+        <font weight="200" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="39.0"/>
+        </font>
+        <font weight="300" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="58.0"/>
+        </font>
+        <font weight="400" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="90.0"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="108.0"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="128.0"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="151.0"/>
+        </font>
+        <font weight="800" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="169.0"/>
+        </font>
+        <font weight="900" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="190.0"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifKhmer-Regular.otf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font>
+    </family>
+    <family lang="und-Khmr" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansKhmerUI">
+            NotoSansKhmerUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Laoo" variant="elegant">
+        <font weight="400" style="normal">NotoSansLao-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">
+            NotoSerifLao-Regular.ttf
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
+    </family>
+    <family lang="und-Laoo" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Mymr" variant="elegant">
+        <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font>
+        <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font>
+        <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
+    </family>
+    <family lang="und-Mymr" variant="compact">
+        <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font>
+        <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font>
+        <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font>
+    </family>
+    <family lang="und-Thaa">
+        <font weight="400" style="normal" postScriptName="NotoSansThaana">
+            NotoSansThaana-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
+    </family>
+    <family lang="und-Cham">
+        <font weight="400" style="normal" postScriptName="NotoSansCham">NotoSansCham-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
+    </family>
+    <family lang="und-Ahom">
+        <font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
+    </family>
+    <family lang="und-Adlm" varFamilyType="1">
+        <font postScriptName="NotoSansAdlam-Regular">
+            NotoSansAdlam-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Avst">
+        <font weight="400" style="normal" postScriptName="NotoSansAvestan">
+            NotoSansAvestan-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Bali">
+        <font weight="400" style="normal" postScriptName="NotoSansBalinese">
+            NotoSansBalinese-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Bamu">
+        <font weight="400" style="normal" postScriptName="NotoSansBamum">NotoSansBamum-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Batk">
+        <font weight="400" style="normal" postScriptName="NotoSansBatak">NotoSansBatak-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Brah">
+        <font weight="400" style="normal" postScriptName="NotoSansBrahmi">
+            NotoSansBrahmi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Bugi">
+        <font weight="400" style="normal" postScriptName="NotoSansBuginese">
+            NotoSansBuginese-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Buhd">
+        <font weight="400" style="normal" postScriptName="NotoSansBuhid">NotoSansBuhid-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Cans">
+        <font weight="400" style="normal">
+            NotoSansCanadianAboriginal-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Cari">
+        <font weight="400" style="normal" postScriptName="NotoSansCarian">
+            NotoSansCarian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Cakm">
+        <font weight="400" style="normal">NotoSansChakma-Regular.otf</font>
+    </family>
+    <family lang="und-Cher">
+        <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
+    </family>
+    <family lang="und-Copt">
+        <font weight="400" style="normal" postScriptName="NotoSansCoptic">
+            NotoSansCoptic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Xsux">
+        <font weight="400" style="normal" postScriptName="NotoSansCuneiform">
+            NotoSansCuneiform-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Cprt">
+        <font weight="400" style="normal" postScriptName="NotoSansCypriot">
+            NotoSansCypriot-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Dsrt">
+        <font weight="400" style="normal" postScriptName="NotoSansDeseret">
+            NotoSansDeseret-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Egyp">
+        <font weight="400" style="normal" postScriptName="NotoSansEgyptianHieroglyphs">
+            NotoSansEgyptianHieroglyphs-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Elba">
+        <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font>
+    </family>
+    <family lang="und-Glag">
+        <font weight="400" style="normal" postScriptName="NotoSansGlagolitic">
+            NotoSansGlagolitic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Goth">
+        <font weight="400" style="normal" postScriptName="NotoSansGothic">
+            NotoSansGothic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Hano">
+        <font weight="400" style="normal" postScriptName="NotoSansHanunoo">
+            NotoSansHanunoo-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Armi">
+        <font weight="400" style="normal" postScriptName="NotoSansImperialAramaic">
+            NotoSansImperialAramaic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Phli">
+        <font weight="400" style="normal" postScriptName="NotoSansInscriptionalPahlavi">
+            NotoSansInscriptionalPahlavi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Prti">
+        <font weight="400" style="normal" postScriptName="NotoSansInscriptionalParthian">
+            NotoSansInscriptionalParthian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Java">
+        <font weight="400" style="normal">NotoSansJavanese-Regular.otf</font>
+    </family>
+    <family lang="und-Kthi">
+        <font weight="400" style="normal" postScriptName="NotoSansKaithi">
+            NotoSansKaithi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Kali">
+        <font weight="400" style="normal" postScriptName="NotoSansKayahLi">
+            NotoSansKayahLi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Khar">
+        <font weight="400" style="normal" postScriptName="NotoSansKharoshthi">
+            NotoSansKharoshthi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lepc">
+        <font weight="400" style="normal" postScriptName="NotoSansLepcha">
+            NotoSansLepcha-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Limb">
+        <font weight="400" style="normal" postScriptName="NotoSansLimbu">NotoSansLimbu-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Linb">
+        <font weight="400" style="normal" postScriptName="NotoSansLinearB">
+            NotoSansLinearB-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lisu">
+        <font weight="400" style="normal" postScriptName="NotoSansLisu">NotoSansLisu-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lyci">
+        <font weight="400" style="normal" postScriptName="NotoSansLycian">
+            NotoSansLycian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lydi">
+        <font weight="400" style="normal" postScriptName="NotoSansLydian">
+            NotoSansLydian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Mand">
+        <font weight="400" style="normal" postScriptName="NotoSansMandaic">
+            NotoSansMandaic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Mtei">
+        <font weight="400" style="normal" postScriptName="NotoSansMeeteiMayek">
+            NotoSansMeeteiMayek-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Talu">
+        <font weight="400" style="normal" postScriptName="NotoSansNewTaiLue">
+            NotoSansNewTaiLue-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Nkoo">
+        <font weight="400" style="normal" postScriptName="NotoSansNKo">NotoSansNKo-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Ogam">
+        <font weight="400" style="normal" postScriptName="NotoSansOgham">NotoSansOgham-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Olck">
+        <font weight="400" style="normal" postScriptName="NotoSansOlChiki">
+            NotoSansOlChiki-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Ital">
+        <font weight="400" style="normal" postScriptName="NotoSansOldItalic">
+            NotoSansOldItalic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Xpeo">
+        <font weight="400" style="normal" postScriptName="NotoSansOldPersian">
+            NotoSansOldPersian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Sarb">
+        <font weight="400" style="normal" postScriptName="NotoSansOldSouthArabian">
+            NotoSansOldSouthArabian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Orkh">
+        <font weight="400" style="normal" postScriptName="NotoSansOldTurkic">
+            NotoSansOldTurkic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Osge">
+        <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font>
+    </family>
+    <family lang="und-Osma">
+        <font weight="400" style="normal" postScriptName="NotoSansOsmanya">
+            NotoSansOsmanya-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Phnx">
+        <font weight="400" style="normal" postScriptName="NotoSansPhoenician">
+            NotoSansPhoenician-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Rjng">
+        <font weight="400" style="normal" postScriptName="NotoSansRejang">
+            NotoSansRejang-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Runr">
+        <font weight="400" style="normal" postScriptName="NotoSansRunic">NotoSansRunic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Samr">
+        <font weight="400" style="normal" postScriptName="NotoSansSamaritan">
+            NotoSansSamaritan-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Saur">
+        <font weight="400" style="normal" postScriptName="NotoSansSaurashtra">
+            NotoSansSaurashtra-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Shaw">
+        <font weight="400" style="normal" postScriptName="NotoSansShavian">
+            NotoSansShavian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Sund">
+        <font weight="400" style="normal" postScriptName="NotoSansSundanese">
+            NotoSansSundanese-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Sylo">
+        <font weight="400" style="normal" postScriptName="NotoSansSylotiNagri">
+            NotoSansSylotiNagri-Regular.ttf
+        </font>
+    </family>
+    <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. -->
+    <family lang="und-Syre">
+        <font weight="400" style="normal" postScriptName="NotoSansSyriacEstrangela">
+            NotoSansSyriacEstrangela-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Syrn">
+        <font weight="400" style="normal" postScriptName="NotoSansSyriacEastern">
+            NotoSansSyriacEastern-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Syrj">
+        <font weight="400" style="normal" postScriptName="NotoSansSyriacWestern">
+            NotoSansSyriacWestern-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Tglg">
+        <font weight="400" style="normal" postScriptName="NotoSansTagalog">
+            NotoSansTagalog-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Tagb">
+        <font weight="400" style="normal" postScriptName="NotoSansTagbanwa">
+            NotoSansTagbanwa-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lana">
+        <font weight="400" style="normal" postScriptName="NotoSansTaiTham">
+            NotoSansTaiTham-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Tavt">
+        <font weight="400" style="normal" postScriptName="NotoSansTaiViet">
+            NotoSansTaiViet-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Tibt" varFamilyType="1">
+        <font postScriptName="NotoSerifTibetan-Regular">
+            NotoSerifTibetan-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Tfng">
+        <font weight="400" style="normal">NotoSansTifinagh-Regular.otf</font>
+    </family>
+    <family lang="und-Ugar">
+        <font weight="400" style="normal" postScriptName="NotoSansUgaritic">
+            NotoSansUgaritic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Vaii">
+        <font weight="400" style="normal" postScriptName="NotoSansVai">NotoSansVai-Regular.ttf
+        </font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
+    </family>
+    <family lang="zh-Hans">
+        <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="100"/>
+        </font>
+        <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="200"/>
+        </font>
+        <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="300"/>
+        </font>
+        <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="800"/>
+        </font>
+        <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="900"/>
+        </font>
+        <font weight="400" style="normal" index="2" fallbackFor="serif"
+              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+        </font>
+    </family>
+    <family lang="zh-Hant,zh-Bopo">
+        <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="100"/>
+        </font>
+        <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="200"/>
+        </font>
+        <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="300"/>
+        </font>
+        <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="800"/>
+        </font>
+        <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="900"/>
+        </font>
+        <font weight="400" style="normal" index="3" fallbackFor="serif"
+              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+        </font>
+    </family>
+    <family lang="ja">
+        <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="100"/>
+        </font>
+        <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="200"/>
+        </font>
+        <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="300"/>
+        </font>
+        <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="800"/>
+        </font>
+        <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="900"/>
+        </font>
+        <font weight="400" style="normal" index="0" fallbackFor="serif"
+              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+        </font>
+    </family>
+    <family lang="ja">
+        <font weight="400" style="normal">
+            NotoSerifHentaigana-EL.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="700" style="normal">
+            NotoSerifHentaigana-EL.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="ko">
+        <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="100"/>
+        </font>
+        <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="200"/>
+        </font>
+        <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="300"/>
+        </font>
+        <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="800"/>
+        </font>
+        <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="900"/>
+        </font>
+        <font weight="400" style="normal" index="1" fallbackFor="serif"
+              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+        </font>
+    </family>
+    <family lang="und-Zsye">
+        <font weight="400" style="normal">NotoColorEmoji.ttf</font>
+    </family>
+    <family lang="und-Zsye">
+        <font weight="400" style="normal">NotoColorEmojiFlags.ttf</font>
+    </family>
+    <family lang="und-Zsym">
+        <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font>
+    </family>
+    <!--
+        Tai Le, Yi, Mongolian, and Phags-pa are intentionally kept last, to make sure they don't
+        override the East Asian punctuation for Chinese.
+    -->
+    <family lang="und-Tale">
+        <font weight="400" style="normal" postScriptName="NotoSansTaiLe">NotoSansTaiLe-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Yiii">
+        <font weight="400" style="normal" postScriptName="NotoSansYi">NotoSansYi-Regular.ttf</font>
+    </family>
+    <family lang="und-Mong">
+        <font weight="400" style="normal" postScriptName="NotoSansMongolian">
+            NotoSansMongolian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Phag">
+        <font weight="400" style="normal" postScriptName="NotoSansPhagsPa">
+            NotoSansPhagsPa-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Hluw">
+        <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font>
+    </family>
+    <family lang="und-Bass">
+        <font weight="400" style="normal">NotoSansBassaVah-Regular.otf</font>
+    </family>
+    <family lang="und-Bhks">
+        <font weight="400" style="normal">NotoSansBhaiksuki-Regular.otf</font>
+    </family>
+    <family lang="und-Hatr">
+        <font weight="400" style="normal">NotoSansHatran-Regular.otf</font>
+    </family>
+    <family lang="und-Lina">
+        <font weight="400" style="normal">NotoSansLinearA-Regular.otf</font>
+    </family>
+    <family lang="und-Mani">
+        <font weight="400" style="normal">NotoSansManichaean-Regular.otf</font>
+    </family>
+    <family lang="und-Marc">
+        <font weight="400" style="normal">NotoSansMarchen-Regular.otf</font>
+    </family>
+    <family lang="und-Merc">
+        <font weight="400" style="normal">NotoSansMeroitic-Regular.otf</font>
+    </family>
+    <family lang="und-Plrd">
+        <font weight="400" style="normal">NotoSansMiao-Regular.otf</font>
+    </family>
+    <family lang="und-Mroo">
+        <font weight="400" style="normal">NotoSansMro-Regular.otf</font>
+    </family>
+    <family lang="und-Mult">
+        <font weight="400" style="normal">NotoSansMultani-Regular.otf</font>
+    </family>
+    <family lang="und-Nbat">
+        <font weight="400" style="normal">NotoSansNabataean-Regular.otf</font>
+    </family>
+    <family lang="und-Newa">
+        <font weight="400" style="normal">NotoSansNewa-Regular.otf</font>
+    </family>
+    <family lang="und-Narb">
+        <font weight="400" style="normal">NotoSansOldNorthArabian-Regular.otf</font>
+    </family>
+    <family lang="und-Perm">
+        <font weight="400" style="normal">NotoSansOldPermic-Regular.otf</font>
+    </family>
+    <family lang="und-Hmng">
+        <font weight="400" style="normal">NotoSansPahawhHmong-Regular.otf</font>
+    </family>
+    <family lang="und-Palm">
+        <font weight="400" style="normal">NotoSansPalmyrene-Regular.otf</font>
+    </family>
+    <family lang="und-Pauc">
+        <font weight="400" style="normal">NotoSansPauCinHau-Regular.otf</font>
+    </family>
+    <family lang="und-Shrd">
+        <font weight="400" style="normal">NotoSansSharada-Regular.otf</font>
+    </family>
+    <family lang="und-Sora">
+        <font weight="400" style="normal">NotoSansSoraSompeng-Regular.otf</font>
+    </family>
+    <family lang="und-Gong">
+        <font weight="400" style="normal">NotoSansGunjalaGondi-Regular.otf</font>
+    </family>
+    <family lang="und-Rohg">
+        <font weight="400" style="normal">NotoSansHanifiRohingya-Regular.otf</font>
+    </family>
+    <family lang="und-Khoj">
+        <font weight="400" style="normal">NotoSansKhojki-Regular.otf</font>
+    </family>
+    <family lang="und-Gonm">
+        <font weight="400" style="normal">NotoSansMasaramGondi-Regular.otf</font>
+    </family>
+    <family lang="und-Wcho">
+        <font weight="400" style="normal">NotoSansWancho-Regular.otf</font>
+    </family>
+    <family lang="und-Wara">
+        <font weight="400" style="normal">NotoSansWarangCiti-Regular.otf</font>
+    </family>
+    <family lang="und-Gran">
+        <font weight="400" style="normal">NotoSansGrantha-Regular.ttf</font>
+    </family>
+    <family lang="und-Modi">
+        <font weight="400" style="normal">NotoSansModi-Regular.ttf</font>
+    </family>
+    <family lang="und-Dogr">
+        <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
+    </family>
+    <family lang="und-Medf" varFamilyType="1">
+        <font postScriptName="NotoSansMedefaidrin-Regular">
+            NotoSansMedefaidrin-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Soyo" varFamilyType="1">
+        <font postScriptName="NotoSansSoyombo-Regular">
+            NotoSansSoyombo-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Takr" varFamilyType="1">
+        <font postScriptName="NotoSansTakri-Regular">
+            NotoSansTakri-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Hmnp" varFamilyType="1">
+        <font postScriptName="NotoSerifHmongNyiakeng-Regular">
+            NotoSerifNyiakengPuachueHmong-VF.ttf
+        </font>
+    </family>
+    <family lang="und-Yezi" varFamilyType="1">
+        <font postScriptName="NotoSerifYezidi-Regular">
+            NotoSerifYezidi-VF.ttf
+        </font>
+    </family>
+</familyset>
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 9320c14..b23f005 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -1432,6 +1432,16 @@
               postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
         </font>
     </family>
+    <family lang="ja">
+        <font weight="400" style="normal">
+            NotoSerifHentaigana-EL.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="700" style="normal">
+            NotoSerifHentaigana-EL.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
     <family lang="ko">
         <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Regular">
             NotoSansCJK-Regular.ttc
diff --git a/data/fonts/fonts_cjkvf.xml b/data/fonts/fonts_cjkvf.xml
new file mode 100644
index 0000000..1ab71ae
--- /dev/null
+++ b/data/fonts/fonts_cjkvf.xml
@@ -0,0 +1,1795 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    DEPRECATED: This XML file is no longer a source of the font files installed
+    in the system.
+
+    For the device vendors: please add your font configurations to the
+    platform/frameworks/base/data/font_fallback.xml and also add it to this XML
+    file as much as possible for apps that reads this XML file.
+
+    For the application developers: please stop reading this XML file and use
+    android.graphics.fonts.SystemFonts#getAvailableFonts Java API or
+    ASystemFontIterator_open NDK API for getting list of system installed
+    font files.
+
+    WARNING: Parsing of this file by third-party apps is not supported. The
+    file, and the font files it refers to, will be renamed and/or moved out
+    from their respective location in the next Android release, and/or the
+    format or syntax of the file may change significantly. If you parse this
+    file for information about system fonts, do it at your own risk. Your
+    application will almost certainly break with the next major Android
+    release.
+
+    In this file, all fonts without names are added to the default list.
+    Fonts are chosen based on a match: full BCP-47 language tag including
+    script, then just language, and finally order (the first font containing
+    the glyph).
+
+    Order of appearance is also the tiebreaker for weight matching. This is
+    the reason why the 900 weights of Roboto precede the 700 weights - we
+    prefer the former when an 800 weight is requested. Since bold spans
+    effectively add 300 to the weight, this ensures that 900 is the bold
+    paired with the 500 weight, ensuring adequate contrast.
+
+    TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
+-->
+<familyset version="23">
+    <!-- first font is default -->
+    <family name="sans-serif">
+        <font weight="100" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <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>
+
+
+    <!-- Note that aliases must come after the fonts they reference. -->
+    <alias name="sans-serif-thin" to="sans-serif" weight="100" />
+    <alias name="sans-serif-light" to="sans-serif" weight="300" />
+    <alias name="sans-serif-medium" to="sans-serif" weight="500" />
+    <alias name="sans-serif-black" to="sans-serif" weight="900" />
+    <alias name="arial" to="sans-serif" />
+    <alias name="helvetica" to="sans-serif" />
+    <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" />
+        <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" />
+    <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" />
+
+    <family name="serif">
+        <font weight="400" style="normal" postScriptName="NotoSerif">NotoSerif-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSerif-Bold.ttf</font>
+        <font weight="400" style="italic">NotoSerif-Italic.ttf</font>
+        <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font>
+    </family>
+    <alias name="serif-bold" to="serif" weight="700" />
+    <alias name="times" to="serif" />
+    <alias name="times new roman" to="serif" />
+    <alias name="palatino" to="serif" />
+    <alias name="georgia" to="serif" />
+    <alias name="baskerville" to="serif" />
+    <alias name="goudy" to="serif" />
+    <alias name="fantasy" to="serif" />
+    <alias name="ITC Stone Serif" to="serif" />
+
+    <family name="monospace">
+        <font weight="400" style="normal">DroidSansMono.ttf</font>
+    </family>
+    <alias name="sans-serif-monospace" to="monospace" />
+    <alias name="monaco" to="monospace" />
+
+    <family name="serif-monospace">
+        <font weight="400" style="normal" postScriptName="CutiveMono-Regular">CutiveMono.ttf</font>
+    </family>
+    <alias name="courier" to="serif-monospace" />
+    <alias name="courier new" to="serif-monospace" />
+
+    <family name="casual">
+        <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>
+
+    <family name="sans-serif-smallcaps">
+        <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
+    </family>
+
+    <family name="source-sans-pro">
+        <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
+        <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
+        <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font>
+        <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font>
+        <font weight="700" style="normal">SourceSansPro-Bold.ttf</font>
+        <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font>
+    </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" />
+          <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>
+
+    <!-- fallback fonts -->
+    <family lang="und-Arab" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
+            NotoNaskhArabic-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
+    </family>
+    <family lang="und-Arab" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
+            NotoNaskhArabicUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Ethi">
+        <font weight="400" style="normal" 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>
+    </family>
+    <family lang="und-Hebr">
+        <font weight="400" style="normal" postScriptName="NotoSansHebrew">
+            NotoSansHebrew-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font>
+    </family>
+    <family lang="und-Thai" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">
+            NotoSerifThai-Regular.ttf
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
+    </family>
+    <family lang="und-Thai" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansThaiUI">
+            NotoSansThaiUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Armn">
+        <font weight="400" style="normal" 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>
+    </family>
+    <family lang="und-Geor,und-Geok">
+        <font weight="400" style="normal" 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>
+    </family>
+    <family lang="und-Deva" variant="elegant">
+        <font weight="400" style="normal" 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>
+    </family>
+    <family lang="und-Deva" variant="compact">
+        <font weight="400" style="normal" 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>
+
+    <!-- All scripts of India should come after Devanagari, due to shared
+         danda characters.
+    -->
+    <family lang="und-Gujr" variant="elegant">
+        <font weight="400" style="normal" postScriptName="NotoSansGujarati">
+            NotoSansGujarati-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif"
+              postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="und-Gujr" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansGujaratiUI">
+            NotoSansGujaratiUI-Regular.ttf
+        </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">
+            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>
+    </family>
+    <family lang="und-Guru" variant="compact">
+        <font weight="400" style="normal" 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">
+            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>
+    </family>
+    <family lang="und-Taml" variant="compact">
+        <font weight="400" style="normal" 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">
+            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>
+    </family>
+    <family lang="und-Mlym" variant="compact">
+        <font weight="400" style="normal" 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">
+            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>
+    </family>
+    <family lang="und-Beng" variant="compact">
+        <font weight="400" style="normal" 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">
+            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>
+    </family>
+    <family lang="und-Telu" variant="compact">
+        <font weight="400" style="normal" 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">
+            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>
+    </family>
+    <family lang="und-Knda" variant="compact">
+        <font weight="400" style="normal" 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">
+        <font weight="400" style="normal" postScriptName="NotoSansOriya">NotoSansOriya-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansOriya-Bold.ttf</font>
+    </family>
+    <family lang="und-Orya" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansOriyaUI">
+            NotoSansOriyaUI-Regular.ttf
+        </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">
+            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>
+    </family>
+    <family lang="und-Sinh" variant="compact">
+        <font weight="400" style="normal" 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">
+        <font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="26.0"/>
+        </font>
+        <font weight="200" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="39.0"/>
+        </font>
+        <font weight="300" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="58.0"/>
+        </font>
+        <font weight="400" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="90.0"/>
+        </font>
+        <font weight="500" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="108.0"/>
+        </font>
+        <font weight="600" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="128.0"/>
+        </font>
+        <font weight="700" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="151.0"/>
+        </font>
+        <font weight="800" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="169.0"/>
+        </font>
+        <font weight="900" style="normal" postScriptName="NotoSansKhmer-Regular">
+            NotoSansKhmer-VF.ttf
+            <axis tag="wdth" stylevalue="100.0"/>
+            <axis tag="wght" stylevalue="190.0"/>
+        </font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifKhmer-Regular.otf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font>
+    </family>
+    <family lang="und-Khmr" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansKhmerUI">
+            NotoSansKhmerUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Laoo" variant="elegant">
+        <font weight="400" style="normal">NotoSansLao-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
+        <font weight="400" style="normal" fallbackFor="serif">
+            NotoSerifLao-Regular.ttf
+        </font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
+    </family>
+    <family lang="und-Laoo" variant="compact">
+        <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
+    </family>
+    <family lang="und-Mymr" variant="elegant">
+        <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font>
+        <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font>
+        <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font>
+        <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
+        <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
+    </family>
+    <family lang="und-Mymr" variant="compact">
+        <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font>
+        <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font>
+        <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font>
+    </family>
+    <family lang="und-Thaa">
+        <font weight="400" style="normal" postScriptName="NotoSansThaana">
+            NotoSansThaana-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
+    </family>
+    <family lang="und-Cham">
+        <font weight="400" style="normal" postScriptName="NotoSansCham">NotoSansCham-Regular.ttf
+        </font>
+        <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
+    </family>
+    <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">
+            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">
+        <font weight="400" style="normal" postScriptName="NotoSansAvestan">
+            NotoSansAvestan-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Bali">
+        <font weight="400" style="normal" postScriptName="NotoSansBalinese">
+            NotoSansBalinese-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Bamu">
+        <font weight="400" style="normal" postScriptName="NotoSansBamum">NotoSansBamum-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Batk">
+        <font weight="400" style="normal" postScriptName="NotoSansBatak">NotoSansBatak-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Brah">
+        <font weight="400" style="normal" postScriptName="NotoSansBrahmi">
+            NotoSansBrahmi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Bugi">
+        <font weight="400" style="normal" postScriptName="NotoSansBuginese">
+            NotoSansBuginese-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Buhd">
+        <font weight="400" style="normal" postScriptName="NotoSansBuhid">NotoSansBuhid-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Cans">
+        <font weight="400" style="normal">
+            NotoSansCanadianAboriginal-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Cari">
+        <font weight="400" style="normal" postScriptName="NotoSansCarian">
+            NotoSansCarian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Cakm">
+        <font weight="400" style="normal">NotoSansChakma-Regular.otf</font>
+    </family>
+    <family lang="und-Cher">
+        <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
+    </family>
+    <family lang="und-Copt">
+        <font weight="400" style="normal" postScriptName="NotoSansCoptic">
+            NotoSansCoptic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Xsux">
+        <font weight="400" style="normal" postScriptName="NotoSansCuneiform">
+            NotoSansCuneiform-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Cprt">
+        <font weight="400" style="normal" postScriptName="NotoSansCypriot">
+            NotoSansCypriot-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Dsrt">
+        <font weight="400" style="normal" postScriptName="NotoSansDeseret">
+            NotoSansDeseret-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Egyp">
+        <font weight="400" style="normal" postScriptName="NotoSansEgyptianHieroglyphs">
+            NotoSansEgyptianHieroglyphs-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Elba">
+        <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font>
+    </family>
+    <family lang="und-Glag">
+        <font weight="400" style="normal" postScriptName="NotoSansGlagolitic">
+            NotoSansGlagolitic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Goth">
+        <font weight="400" style="normal" postScriptName="NotoSansGothic">
+            NotoSansGothic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Hano">
+        <font weight="400" style="normal" postScriptName="NotoSansHanunoo">
+            NotoSansHanunoo-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Armi">
+        <font weight="400" style="normal" postScriptName="NotoSansImperialAramaic">
+            NotoSansImperialAramaic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Phli">
+        <font weight="400" style="normal" postScriptName="NotoSansInscriptionalPahlavi">
+            NotoSansInscriptionalPahlavi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Prti">
+        <font weight="400" style="normal" postScriptName="NotoSansInscriptionalParthian">
+            NotoSansInscriptionalParthian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Java">
+        <font weight="400" style="normal">NotoSansJavanese-Regular.otf</font>
+    </family>
+    <family lang="und-Kthi">
+        <font weight="400" style="normal" postScriptName="NotoSansKaithi">
+            NotoSansKaithi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Kali">
+        <font weight="400" style="normal" postScriptName="NotoSansKayahLi">
+            NotoSansKayahLi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Khar">
+        <font weight="400" style="normal" postScriptName="NotoSansKharoshthi">
+            NotoSansKharoshthi-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lepc">
+        <font weight="400" style="normal" postScriptName="NotoSansLepcha">
+            NotoSansLepcha-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Limb">
+        <font weight="400" style="normal" postScriptName="NotoSansLimbu">NotoSansLimbu-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Linb">
+        <font weight="400" style="normal" postScriptName="NotoSansLinearB">
+            NotoSansLinearB-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lisu">
+        <font weight="400" style="normal" postScriptName="NotoSansLisu">NotoSansLisu-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lyci">
+        <font weight="400" style="normal" postScriptName="NotoSansLycian">
+            NotoSansLycian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lydi">
+        <font weight="400" style="normal" postScriptName="NotoSansLydian">
+            NotoSansLydian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Mand">
+        <font weight="400" style="normal" postScriptName="NotoSansMandaic">
+            NotoSansMandaic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Mtei">
+        <font weight="400" style="normal" postScriptName="NotoSansMeeteiMayek">
+            NotoSansMeeteiMayek-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Talu">
+        <font weight="400" style="normal" postScriptName="NotoSansNewTaiLue">
+            NotoSansNewTaiLue-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Nkoo">
+        <font weight="400" style="normal" postScriptName="NotoSansNKo">NotoSansNKo-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Ogam">
+        <font weight="400" style="normal" postScriptName="NotoSansOgham">NotoSansOgham-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Olck">
+        <font weight="400" style="normal" postScriptName="NotoSansOlChiki">
+            NotoSansOlChiki-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Ital">
+        <font weight="400" style="normal" postScriptName="NotoSansOldItalic">
+            NotoSansOldItalic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Xpeo">
+        <font weight="400" style="normal" postScriptName="NotoSansOldPersian">
+            NotoSansOldPersian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Sarb">
+        <font weight="400" style="normal" postScriptName="NotoSansOldSouthArabian">
+            NotoSansOldSouthArabian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Orkh">
+        <font weight="400" style="normal" postScriptName="NotoSansOldTurkic">
+            NotoSansOldTurkic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Osge">
+        <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font>
+    </family>
+    <family lang="und-Osma">
+        <font weight="400" style="normal" postScriptName="NotoSansOsmanya">
+            NotoSansOsmanya-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Phnx">
+        <font weight="400" style="normal" postScriptName="NotoSansPhoenician">
+            NotoSansPhoenician-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Rjng">
+        <font weight="400" style="normal" postScriptName="NotoSansRejang">
+            NotoSansRejang-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Runr">
+        <font weight="400" style="normal" postScriptName="NotoSansRunic">NotoSansRunic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Samr">
+        <font weight="400" style="normal" postScriptName="NotoSansSamaritan">
+            NotoSansSamaritan-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Saur">
+        <font weight="400" style="normal" postScriptName="NotoSansSaurashtra">
+            NotoSansSaurashtra-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Shaw">
+        <font weight="400" style="normal" postScriptName="NotoSansShavian">
+            NotoSansShavian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Sund">
+        <font weight="400" style="normal" postScriptName="NotoSansSundanese">
+            NotoSansSundanese-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Sylo">
+        <font weight="400" style="normal" postScriptName="NotoSansSylotiNagri">
+            NotoSansSylotiNagri-Regular.ttf
+        </font>
+    </family>
+    <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. -->
+    <family lang="und-Syre">
+        <font weight="400" style="normal" postScriptName="NotoSansSyriacEstrangela">
+            NotoSansSyriacEstrangela-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Syrn">
+        <font weight="400" style="normal" postScriptName="NotoSansSyriacEastern">
+            NotoSansSyriacEastern-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Syrj">
+        <font weight="400" style="normal" postScriptName="NotoSansSyriacWestern">
+            NotoSansSyriacWestern-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Tglg">
+        <font weight="400" style="normal" postScriptName="NotoSansTagalog">
+            NotoSansTagalog-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Tagb">
+        <font weight="400" style="normal" postScriptName="NotoSansTagbanwa">
+            NotoSansTagbanwa-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Lana">
+        <font weight="400" style="normal" postScriptName="NotoSansTaiTham">
+            NotoSansTaiTham-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Tavt">
+        <font weight="400" style="normal" postScriptName="NotoSansTaiViet">
+            NotoSansTaiViet-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Tibt">
+        <font weight="400" style="normal" 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">
+        <font weight="400" style="normal">NotoSansTifinagh-Regular.otf</font>
+    </family>
+    <family lang="und-Ugar">
+        <font weight="400" style="normal" postScriptName="NotoSansUgaritic">
+            NotoSansUgaritic-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Vaii">
+        <font weight="400" style="normal" postScriptName="NotoSansVai">NotoSansVai-Regular.ttf
+        </font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
+    </family>
+    <family lang="zh-Hans">
+        <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="100"/>
+        </font>
+        <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="200"/>
+        </font>
+        <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="300"/>
+        </font>
+        <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="800"/>
+        </font>
+        <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="900"/>
+        </font>
+        <font weight="400" style="normal" index="2" fallbackFor="serif"
+              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+        </font>
+    </family>
+    <family lang="zh-Hant,zh-Bopo">
+        <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="100"/>
+        </font>
+        <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="200"/>
+        </font>
+        <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="300"/>
+        </font>
+        <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="800"/>
+        </font>
+        <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="900"/>
+        </font>
+        <font weight="400" style="normal" index="3" fallbackFor="serif"
+              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+        </font>
+    </family>
+    <family lang="ja">
+        <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="100"/>
+        </font>
+        <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="200"/>
+        </font>
+        <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="300"/>
+        </font>
+        <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="800"/>
+        </font>
+        <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="900"/>
+        </font>
+        <font weight="400" style="normal" index="0" fallbackFor="serif"
+              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+        </font>
+    </family>
+    <family lang="ja">
+        <font weight="400" style="normal">
+            NotoSerifHentaigana-EL.ttf
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="700" style="normal">
+            NotoSerifHentaigana-EL.ttf
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+    </family>
+    <family lang="ko">
+        <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="100"/>
+        </font>
+        <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="200"/>
+        </font>
+        <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="300"/>
+        </font>
+        <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="400"/>
+        </font>
+        <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="500"/>
+        </font>
+        <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="600"/>
+        </font>
+        <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="700"/>
+        </font>
+        <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="800"/>
+        </font>
+        <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+            NotoSansCJK-Regular.ttc
+            <axis tag="wght" stylevalue="900"/>
+        </font>
+        <font weight="400" style="normal" index="1" fallbackFor="serif"
+              postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+        </font>
+    </family>
+    <family lang="und-Zsye" ignore="true">
+        <font weight="400" style="normal">NotoColorEmojiLegacy.ttf</font>
+    </family>
+    <family lang="und-Zsye">
+        <font weight="400" style="normal">NotoColorEmoji.ttf</font>
+    </family>
+    <family lang="und-Zsye">
+        <font weight="400" style="normal">NotoColorEmojiFlags.ttf</font>
+    </family>
+    <family lang="und-Zsym">
+        <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font>
+    </family>
+    <!--
+        Tai Le, Yi, Mongolian, and Phags-pa are intentionally kept last, to make sure they don't
+        override the East Asian punctuation for Chinese.
+    -->
+    <family lang="und-Tale">
+        <font weight="400" style="normal" postScriptName="NotoSansTaiLe">NotoSansTaiLe-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Yiii">
+        <font weight="400" style="normal" postScriptName="NotoSansYi">NotoSansYi-Regular.ttf</font>
+    </family>
+    <family lang="und-Mong">
+        <font weight="400" style="normal" postScriptName="NotoSansMongolian">
+            NotoSansMongolian-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Phag">
+        <font weight="400" style="normal" postScriptName="NotoSansPhagsPa">
+            NotoSansPhagsPa-Regular.ttf
+        </font>
+    </family>
+    <family lang="und-Hluw">
+        <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font>
+    </family>
+    <family lang="und-Bass">
+        <font weight="400" style="normal">NotoSansBassaVah-Regular.otf</font>
+    </family>
+    <family lang="und-Bhks">
+        <font weight="400" style="normal">NotoSansBhaiksuki-Regular.otf</font>
+    </family>
+    <family lang="und-Hatr">
+        <font weight="400" style="normal">NotoSansHatran-Regular.otf</font>
+    </family>
+    <family lang="und-Lina">
+        <font weight="400" style="normal">NotoSansLinearA-Regular.otf</font>
+    </family>
+    <family lang="und-Mani">
+        <font weight="400" style="normal">NotoSansManichaean-Regular.otf</font>
+    </family>
+    <family lang="und-Marc">
+        <font weight="400" style="normal">NotoSansMarchen-Regular.otf</font>
+    </family>
+    <family lang="und-Merc">
+        <font weight="400" style="normal">NotoSansMeroitic-Regular.otf</font>
+    </family>
+    <family lang="und-Plrd">
+        <font weight="400" style="normal">NotoSansMiao-Regular.otf</font>
+    </family>
+    <family lang="und-Mroo">
+        <font weight="400" style="normal">NotoSansMro-Regular.otf</font>
+    </family>
+    <family lang="und-Mult">
+        <font weight="400" style="normal">NotoSansMultani-Regular.otf</font>
+    </family>
+    <family lang="und-Nbat">
+        <font weight="400" style="normal">NotoSansNabataean-Regular.otf</font>
+    </family>
+    <family lang="und-Newa">
+        <font weight="400" style="normal">NotoSansNewa-Regular.otf</font>
+    </family>
+    <family lang="und-Narb">
+        <font weight="400" style="normal">NotoSansOldNorthArabian-Regular.otf</font>
+    </family>
+    <family lang="und-Perm">
+        <font weight="400" style="normal">NotoSansOldPermic-Regular.otf</font>
+    </family>
+    <family lang="und-Hmng">
+        <font weight="400" style="normal">NotoSansPahawhHmong-Regular.otf</font>
+    </family>
+    <family lang="und-Palm">
+        <font weight="400" style="normal">NotoSansPalmyrene-Regular.otf</font>
+    </family>
+    <family lang="und-Pauc">
+        <font weight="400" style="normal">NotoSansPauCinHau-Regular.otf</font>
+    </family>
+    <family lang="und-Shrd">
+        <font weight="400" style="normal">NotoSansSharada-Regular.otf</font>
+    </family>
+    <family lang="und-Sora">
+        <font weight="400" style="normal">NotoSansSoraSompeng-Regular.otf</font>
+    </family>
+    <family lang="und-Gong">
+        <font weight="400" style="normal">NotoSansGunjalaGondi-Regular.otf</font>
+    </family>
+    <family lang="und-Rohg">
+        <font weight="400" style="normal">NotoSansHanifiRohingya-Regular.otf</font>
+    </family>
+    <family lang="und-Khoj">
+        <font weight="400" style="normal">NotoSansKhojki-Regular.otf</font>
+    </family>
+    <family lang="und-Gonm">
+        <font weight="400" style="normal">NotoSansMasaramGondi-Regular.otf</font>
+    </family>
+    <family lang="und-Wcho">
+        <font weight="400" style="normal">NotoSansWancho-Regular.otf</font>
+    </family>
+    <family lang="und-Wara">
+        <font weight="400" style="normal">NotoSansWarangCiti-Regular.otf</font>
+    </family>
+    <family lang="und-Gran">
+        <font weight="400" style="normal">NotoSansGrantha-Regular.ttf</font>
+    </family>
+    <family lang="und-Modi">
+        <font weight="400" style="normal">NotoSansModi-Regular.ttf</font>
+    </family>
+    <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">
+            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">
+            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">
+            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">
+            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">
+            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_0031.idc b/data/keyboards/Vendor_0957_Product_0031.idc
new file mode 100644
index 0000000..f0320c8
--- /dev/null
+++ b/data/keyboards/Vendor_0957_Product_0031.idc
@@ -0,0 +1,21 @@
+# Copyright 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Input Device Configuration file for Google Reference RCU Remote.
+# PID 0031 is for old GPIO pin
+
+# Basic Parameters
+keyboard.doNotWakeByDefault = 1
+audio.mic = 1
\ No newline at end of file
diff --git a/data/keyboards/Vendor_0957_Product_0034.idc b/data/keyboards/Vendor_0957_Product_0034.idc
new file mode 100644
index 0000000..52ed0bc
--- /dev/null
+++ b/data/keyboards/Vendor_0957_Product_0034.idc
@@ -0,0 +1,23 @@
+# Copyright 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Input Device Configuration file for Google Reference RCU Remote.
+# PID 0034 is for new GPIO pin PCB.
+
+# Basic Parameters
+keyboard.layout = Vendor_0957_Product_0031
+# The reason why we set is follow https://docs.partner.android.com/tv/build/gtv/boot-resume
+keyboard.doNotWakeByDefault = 1
+audio.mic = 1
\ No newline at end of file
diff --git a/framework-jarjar-rules.txt b/framework-jarjar-rules.txt
index 03b268d..6339a87 100644
--- a/framework-jarjar-rules.txt
+++ b/framework-jarjar-rules.txt
@@ -8,3 +8,6 @@
 
 # for modules-utils-build dependency
 rule com.android.modules.utils.build.** android.internal.modules.utils.build.@1
+
+# For Perfetto proto dependencies
+rule perfetto.protos.** android.internal.perfetto.protos.@1
diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java
index d659ddd..4e88b0e 100644
--- a/graphics/java/android/graphics/BaseRecordingCanvas.java
+++ b/graphics/java/android/graphics/BaseRecordingCanvas.java
@@ -607,7 +607,8 @@
     }
 
     @Override
-    public final void drawMesh(@NonNull Mesh mesh, BlendMode blendMode, @NonNull Paint paint) {
+    public final void drawMesh(@NonNull Mesh mesh, @Nullable BlendMode blendMode,
+            @NonNull Paint paint) {
         if (blendMode == null) {
             blendMode = BlendMode.MODULATE;
         }
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index f10cdb8..ae61a2d 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -17,6 +17,7 @@
 package android.graphics;
 
 import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
+import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
 
 import android.annotation.ColorInt;
 import android.annotation.ColorLong;
@@ -133,7 +134,9 @@
             FAKE_BOLD_TEXT_FLAG,
             LINEAR_TEXT_FLAG,
             SUBPIXEL_TEXT_FLAG,
-            EMBEDDED_BITMAP_TEXT_FLAG
+            EMBEDDED_BITMAP_TEXT_FLAG,
+            TEXT_RUN_FLAG_LEFT_EDGE,
+            TEXT_RUN_FLAG_RIGHT_EDGE
     })
     public @interface PaintFlag{}
 
@@ -264,6 +267,66 @@
     /** @hide bit mask for the flag enabling vertical rendering for text */
     public static final int VERTICAL_TEXT_FLAG = 0x1000;
 
+    /**
+     * A text run flag that indicates the run is located the visually most left segment of the line.
+     * <p>
+     * This flag is used for telling the underlying text layout engine that the text is located at
+     * the most left of the line. This flag is used for controlling the amount letter spacing
+     * added. If the text is in the middle of the line, the text layout engine assigns additional
+     * letter spacing to the both side of each letter. On the other hand, the letter spacing should
+     * not be added to the visually most left and right of the line. By setting this flag, text
+     * layout engine calculates the layout as it is located at the most visually left of the line
+     * and doesn't add letter spacing to the left of this run.
+     * <p>
+     * Note that the caller must resolve BiDi runs and reorder them visually and set this flag only
+     * if the target run is located visually most left position. This left does not always mean the
+     * beginning of the text.
+     * <p>
+     * If the run covers entire line, caller should set {@link #TEXT_RUN_FLAG_RIGHT_EDGE} as well.
+     * <p>
+     * Note that this flag is only effective for run based APIs. For example, this flag works for
+     * {@link Canvas#drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)}
+     * and
+     * {@link Paint#getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int)}.
+     * However, this doesn't work for
+     * {@link Canvas#drawText(CharSequence, int, int, float, float, Paint)} or
+     * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
+     * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
+     */
+    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    public static final int TEXT_RUN_FLAG_LEFT_EDGE = 0x2000;
+
+
+    /**
+     * A text run flag that indicates the run is located the visually most right segment of the
+     * line.
+     * <p>
+     * This flag is used for telling the underlying text layout engine that the text is located at
+     * the most right of the line. This flag is used for controlling the amount letter spacing
+     * added. If the text is in the middle of the line, the text layout engine assigns additional
+     * letter spacing to the both side of each letter. On the other hand, the letter spacing should
+     * not be added to the visually most left and right of the line. By setting this flag, text
+     * layout engine calculates the layout as it is located at the most visually left of the line
+     * and doesn't add letter spacing to the left of this run.
+     * <p>
+     * Note that the caller must resolve BiDi runs and reorder them visually and set this flag only
+     * if the target run is located visually most right position. This right does not always mean
+     * the end of the text.
+     * <p>
+     * If the run covers entire line, caller should set {@link #TEXT_RUN_FLAG_LEFT_EDGE} as well.
+     * <p>
+     * Note that this flag is only effective for run based APIs. For example, this flag works for
+     * {@link Canvas#drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)}
+     * and
+     * {@link Paint#getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int)}.
+     * However, this doesn't work for
+     * {@link Canvas#drawText(CharSequence, int, int, float, float, Paint)} or
+     * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
+     * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
+     */
+    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 0x4000;
+
     // These flags are always set on a new/reset paint, even if flags 0 is passed.
     static final int HIDDEN_DEFAULT_PAINT_FLAGS = DEV_KERN_TEXT_FLAG | EMBEDDED_BITMAP_TEXT_FLAG
             | FILTER_BITMAP_FLAG;
@@ -2474,6 +2537,19 @@
         nGetFontMetricsInt(mNativePaint, metrics, true);
     }
 
+    /** @hide */
+    public static final class RunInfo {
+        private int mClusterCount = 0;
+
+        public int getClusterCount() {
+            return mClusterCount;
+        }
+
+        public void setClusterCount(int clusterCount) {
+            mClusterCount = clusterCount;
+        }
+    }
+
     /**
      * Return the recommend line spacing based on the current typeface and
      * text size.
@@ -2507,17 +2583,24 @@
         if (text.length == 0 || count == 0) {
             return 0f;
         }
-        if (!mHasCompatScaling) {
-            return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
-                    index, count, index, count, mBidiFlags, null, 0));
-        }
+        int oldFlag = getFlags();
+        setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+        try {
 
-        final float oldSize = getTextSize();
-        setTextSize(oldSize * mCompatScaling);
-        final float w = nGetTextAdvances(mNativePaint, text, index, count, index, count,
-                mBidiFlags, null, 0);
-        setTextSize(oldSize);
-        return (float) Math.ceil(w*mInvCompatScaling);
+            if (!mHasCompatScaling) {
+                return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
+                        index, count, index, count, mBidiFlags, null, 0));
+            }
+
+            final float oldSize = getTextSize();
+            setTextSize(oldSize * mCompatScaling);
+            final float w = nGetTextAdvances(mNativePaint, text, index, count, index, count,
+                    mBidiFlags, null, 0);
+            setTextSize(oldSize);
+            return (float) Math.ceil(w * mInvCompatScaling);
+        } finally {
+            setFlags(oldFlag);
+        }
     }
 
     /**
@@ -2539,16 +2622,22 @@
         if (text.length() == 0 || start == end) {
             return 0f;
         }
-        if (!mHasCompatScaling) {
-            return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
-                    start, end, start, end, mBidiFlags, null, 0));
+        int oldFlag = getFlags();
+        setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+        try {
+            if (!mHasCompatScaling) {
+                return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
+                        start, end, start, end, mBidiFlags, null, 0));
+            }
+            final float oldSize = getTextSize();
+            setTextSize(oldSize * mCompatScaling);
+            final float w = nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags,
+                    null, 0);
+            setTextSize(oldSize);
+            return (float) Math.ceil(w * mInvCompatScaling);
+        } finally {
+            setFlags(oldFlag);
         }
-        final float oldSize = getTextSize();
-        setTextSize(oldSize * mCompatScaling);
-        final float w = nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags,
-                null, 0);
-        setTextSize(oldSize);
-        return (float) Math.ceil(w * mInvCompatScaling);
     }
 
     /**
@@ -2753,19 +2842,26 @@
         if (text.length == 0 || count == 0) {
             return 0;
         }
-        if (!mHasCompatScaling) {
-            nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
-            return count;
-        }
+        int oldFlag = getFlags();
+        setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+        try {
+            if (!mHasCompatScaling) {
+                nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths,
+                        0);
+                return count;
+            }
 
-        final float oldSize = getTextSize();
-        setTextSize(oldSize * mCompatScaling);
-        nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
-        setTextSize(oldSize);
-        for (int i = 0; i < count; i++) {
-            widths[i] *= mInvCompatScaling;
+            final float oldSize = getTextSize();
+            setTextSize(oldSize * mCompatScaling);
+            nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
+            setTextSize(oldSize);
+            for (int i = 0; i < count; i++) {
+                widths[i] *= mInvCompatScaling;
+            }
+            return count;
+        } finally {
+            setFlags(oldFlag);
         }
-        return count;
     }
 
     /**
@@ -2836,19 +2932,25 @@
         if (text.length() == 0 || start == end) {
             return 0;
         }
-        if (!mHasCompatScaling) {
-            nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
-            return end - start;
-        }
+        int oldFlag = getFlags();
+        setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+        try {
+            if (!mHasCompatScaling) {
+                nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
+                return end - start;
+            }
 
-        final float oldSize = getTextSize();
-        setTextSize(oldSize * mCompatScaling);
-        nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
-        setTextSize(oldSize);
-        for (int i = 0; i < end - start; i++) {
-            widths[i] *= mInvCompatScaling;
+            final float oldSize = getTextSize();
+            setTextSize(oldSize * mCompatScaling);
+            nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
+            setTextSize(oldSize);
+            for (int i = 0; i < end - start; i++) {
+                widths[i] *= mInvCompatScaling;
+            }
+            return end - start;
+        } finally {
+            setFlags(oldFlag);
         }
-        return end - start;
     }
 
     /**
@@ -3320,7 +3422,7 @@
             int contextEnd, boolean isRtl, int offset,
             @Nullable float[] advances, int advancesIndex) {
         return getRunCharacterAdvance(text, start, end, contextStart, contextEnd, isRtl, offset,
-                advances, advancesIndex, null);
+                advances, advancesIndex, null, null);
     }
 
     /**
@@ -3339,12 +3441,14 @@
      * @param advances the array that receives the computed character advances
      * @param advancesIndex the start index from which the advances array is filled
      * @param drawBounds the output parameter for the bounding box of drawing text, optional
+     * @param runInfo the output parameter for storing run information.
      * @return width measurement between start and offset
-     * @hide
+     * @hide TODO: Reorganize APIs
      */
     public float getRunCharacterAdvance(@NonNull char[] text, int start, int end, int contextStart,
             int contextEnd, boolean isRtl, int offset,
-            @Nullable float[] advances, int advancesIndex, @Nullable RectF drawBounds) {
+            @Nullable float[] advances, int advancesIndex, @Nullable RectF drawBounds,
+            @Nullable RunInfo runInfo) {
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
@@ -3370,11 +3474,14 @@
         }
 
         if (end == start) {
+            if (runInfo != null) {
+                runInfo.setClusterCount(0);
+            }
             return 0.0f;
         }
 
         return nGetRunCharacterAdvance(mNativePaint, text, start, end, contextStart, contextEnd,
-                isRtl, offset, advances, advancesIndex, drawBounds);
+                isRtl, offset, advances, advancesIndex, drawBounds, runInfo);
     }
 
     /**
@@ -3402,7 +3509,7 @@
             int contextStart, int contextEnd, boolean isRtl, int offset,
             @Nullable float[] advances, int advancesIndex) {
         return getRunCharacterAdvance(text, start, end, contextStart, contextEnd, isRtl, offset,
-                advances, advancesIndex, null);
+                advances, advancesIndex, null, null);
     }
 
     /**
@@ -3418,12 +3525,14 @@
      * @param advances the array that receives the computed character advances
      * @param advancesIndex the start index from which the advances array is filled
      * @param drawBounds the output parameter for the bounding box of drawing text, optional
+     * @param runInfo an optional output parameter for filling run information.
      * @return width measurement between start and offset
-     * @hide
+     * @hide  TODO: Reorganize APIs
      */
     public float getRunCharacterAdvance(@NonNull CharSequence text, int start, int end,
             int contextStart, int contextEnd, boolean isRtl, int offset,
-            @Nullable float[] advances, int advancesIndex, @Nullable RectF drawBounds) {
+            @Nullable float[] advances, int advancesIndex, @Nullable RectF drawBounds,
+            @Nullable RunInfo runInfo) {
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
@@ -3456,7 +3565,7 @@
         TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
         final float result = getRunCharacterAdvance(buf, start - contextStart, end - contextStart,
                 0, contextEnd - contextStart, isRtl, offset - contextStart,
-                advances, advancesIndex, drawBounds);
+                advances, advancesIndex, drawBounds, runInfo);
         TemporaryBuffer.recycle(buf);
         return result;
     }
@@ -3574,7 +3683,7 @@
             int contextStart, int contextEnd, boolean isRtl, int offset);
     private static native float nGetRunCharacterAdvance(long paintPtr, char[] text, int start,
             int end, int contextStart, int contextEnd, boolean isRtl, int offset, float[] advances,
-            int advancesIndex, RectF drawingBounds);
+            int advancesIndex, RectF drawingBounds, RunInfo runInfo);
     private static native int nGetOffsetForAdvance(long paintPtr, char[] text, int start, int end,
             int contextStart, int contextEnd, boolean isRtl, float advance);
     private static native void nGetFontMetricsIntForText(long paintPtr, char[] text,
@@ -3729,4 +3838,11 @@
     private static native void nSetTextSize(long paintPtr, float textSize);
     @CriticalNative
     private static native boolean nEqualsForTextMeasurement(long leftPaintPtr, long rightPaintPtr);
+
+
+    // Following Native methods are kept for old Robolectric JNI signature used by
+    // SystemUIGoogleRoboRNGTests
+    private static native float nGetRunCharacterAdvance(long paintPtr, char[] text,
+            int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset,
+            float[] advances, int advancesIndex, RectF drawingBounds);
 }
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index ddae673..b21bf11 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -23,9 +23,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.compat.CompatChanges;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
+import android.app.ActivityThread;
 import android.os.Build;
 import android.os.LocaleList;
 import android.os.Parcel;
@@ -43,15 +41,6 @@
  * line-break property</a> for more information.
  */
 public final class LineBreakConfig implements Parcelable {
-
-    /**
-     * A feature ID for automatic line break word style.
-     * @hide
-     */
-    @ChangeId
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
-    public static final long WORD_STYLE_AUTO = 280005585L;
-
     /**
      * No hyphenation preference is specified.
      *
@@ -487,8 +476,15 @@
      * @hide
      */
     public static @LineBreakStyle int getResolvedLineBreakStyle(@Nullable LineBreakConfig config) {
-        final int defaultStyle = CompatChanges.isChangeEnabled(WORD_STYLE_AUTO)
-                ? LINE_BREAK_STYLE_AUTO : LINE_BREAK_STYLE_NONE;
+        final int targetSdkVersion = ActivityThread.currentApplication().getApplicationInfo()
+                .targetSdkVersion;
+        final int defaultStyle;
+        final int vicVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM;
+        if (targetSdkVersion >= vicVersion) {
+            defaultStyle = LINE_BREAK_STYLE_AUTO;
+        } else {
+            defaultStyle = LINE_BREAK_STYLE_NONE;
+        }
         if (config == null) {
             return defaultStyle;
         }
@@ -515,8 +511,15 @@
      */
     public static @LineBreakWordStyle int getResolvedLineBreakWordStyle(
             @Nullable LineBreakConfig config) {
-        final int defaultWordStyle = CompatChanges.isChangeEnabled(WORD_STYLE_AUTO)
-                ? LINE_BREAK_WORD_STYLE_AUTO : LINE_BREAK_WORD_STYLE_NONE;
+        final int targetSdkVersion = ActivityThread.currentApplication().getApplicationInfo()
+                .targetSdkVersion;
+        final int defaultWordStyle;
+        final int vicVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM;
+        if (targetSdkVersion >= vicVersion) {
+            defaultWordStyle = LINE_BREAK_WORD_STYLE_AUTO;
+        } else {
+            defaultWordStyle = LINE_BREAK_WORD_STYLE_NONE;
+        }
         if (config == null) {
             return defaultWordStyle;
         }
diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java
index 0e3fb16..9707126 100644
--- a/graphics/java/android/graphics/text/LineBreaker.java
+++ b/graphics/java/android/graphics/text/LineBreaker.java
@@ -17,6 +17,7 @@
 package android.graphics.text;
 
 import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
+import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
 
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
@@ -163,7 +164,8 @@
     /** @hide */
     @IntDef(prefix = { "JUSTIFICATION_MODE_" }, value = {
             JUSTIFICATION_MODE_NONE,
-            JUSTIFICATION_MODE_INTER_WORD
+            JUSTIFICATION_MODE_INTER_WORD,
+            JUSTIFICATION_MODE_INTER_CHARACTER,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface JustificationMode {}
@@ -179,6 +181,12 @@
     public static final int JUSTIFICATION_MODE_INTER_WORD = 1;
 
     /**
+     * Value for justification mode indicating the text is justified by stretching letter spacing.
+     */
+    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2;
+
+    /**
      * Helper class for creating a {@link LineBreaker}.
      */
     public static final class Builder {
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 2d33e8d..6da0719 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -269,6 +269,10 @@
          * offset is zero. After the style is applied the internal offset is moved to {@code offset
          * + length}, and next call will start from this new position.
          *
+         * <p>
+         * {@link Paint#TEXT_RUN_FLAG_RIGHT_EDGE} and {@link Paint#TEXT_RUN_FLAG_LEFT_EDGE} are
+         * ignored and treated as both of them are set.
+         *
          * @param paint a paint
          * @param length a length to be applied with a given paint, can not exceed the length of the
          *               text
diff --git a/keystore/java/android/security/AndroidProtectedConfirmation.java b/keystore/java/android/security/AndroidProtectedConfirmation.java
index dfe485a..268e0a5 100644
--- a/keystore/java/android/security/AndroidProtectedConfirmation.java
+++ b/keystore/java/android/security/AndroidProtectedConfirmation.java
@@ -59,6 +59,10 @@
 
     /**
      * Requests keystore call into the confirmationui HAL to display a prompt.
+     * @deprecated Android Protected Confirmation had a low adoption rate among Android device
+     *             makers and developers alike. Given the lack of devices supporting the
+     *             feature, it is deprecated. Developers can use auth-bound Keystore keys
+     *             as a partial replacement.
      *
      * @param listener the binder to use for callbacks.
      * @param promptText the prompt to display.
@@ -68,6 +72,7 @@
      * @return one of the {@code CONFIRMATIONUI_*} constants, for
      * example {@code KeyStore.CONFIRMATIONUI_OK}.
      */
+    @Deprecated
     public int presentConfirmationPrompt(IConfirmationCallback listener, String promptText,
                                          byte[] extraData, String locale, int uiOptionsAsFlags) {
         try {
@@ -84,11 +89,16 @@
 
     /**
      * Requests keystore call into the confirmationui HAL to cancel displaying a prompt.
+     * @deprecated Android Protected Confirmation had a low adoption rate among Android device
+     *             makers and developers alike. Given the lack of devices supporting the
+     *             feature, it is deprecated. Developers can use auth-bound Keystore keys
+     *             as a partial replacement.
      *
      * @param listener the binder passed to the {@link #presentConfirmationPrompt} method.
      * @return one of the {@code CONFIRMATIONUI_*} constants, for
      * example {@code KeyStore.CONFIRMATIONUI_OK}.
      */
+    @Deprecated
     public int cancelConfirmationPrompt(IConfirmationCallback listener) {
         try {
             getService().cancelPrompt(listener);
@@ -103,9 +113,14 @@
 
     /**
      * Requests keystore to check if the confirmationui HAL is available.
+     * @deprecated Android Protected Confirmation had a low adoption rate among Android device
+     *             makers and developers alike. Given the lack of devices supporting the
+     *             feature, it is deprecated. Developers can use auth-bound Keystore keys
+     *             as a partial replacement.
      *
      * @return whether the confirmationUI HAL is available.
      */
+    @Deprecated
     public boolean isConfirmationPromptSupported() {
         try {
             return getService().isSupported();
diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java
index 4ec5e1b..6404c4b 100644
--- a/keystore/java/android/security/Authorization.java
+++ b/keystore/java/android/security/Authorization.java
@@ -100,12 +100,14 @@
      *
      * @param userId - the user's Android user ID
      * @param unlockingSids - list of biometric SIDs with which the device may be unlocked again
+     * @param weakUnlockEnabled - true if non-strong biometric or trust agent unlock is enabled
      * @return 0 if successful or a {@code ResponseCode}.
      */
-    public static int onDeviceLocked(int userId, @NonNull long[] unlockingSids) {
+    public static int onDeviceLocked(int userId, @NonNull long[] unlockingSids,
+            boolean weakUnlockEnabled) {
         StrictMode.noteDiskWrite();
         try {
-            getService().onDeviceLocked(userId, unlockingSids);
+            getService().onDeviceLocked(userId, unlockingSids, weakUnlockEnabled);
             return 0;
         } catch (RemoteException | NullPointerException e) {
             Log.w(TAG, "Can not connect to keystore", e);
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index 9c05a3a..83ddfc5 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -109,13 +109,29 @@
         }
     }
 
+    // For curve 25519, KeyMint uses the KM_ALGORITHM_EC constant, but in the Java layer we need
+    // to distinguish between Curve 25519 and other EC algorithms, so we use a different constant
+    // with a value that is outside the range of the enum used for KeyMint algorithms.
+    private static final int ALGORITHM_XDH = KeymasterDefs.KM_ALGORITHM_EC + 1200;
+    private static final int ALGORITHM_ED25519 = ALGORITHM_XDH + 1;
+
     /**
-     * XDH represents Curve 25519 providers.
+     * XDH represents Curve 25519 agreement key provider.
      */
     public static class XDH extends AndroidKeyStoreKeyPairGeneratorSpi {
         // XDH is treated as EC.
         public XDH() {
-            super(KeymasterDefs.KM_ALGORITHM_EC);
+            super(ALGORITHM_XDH);
+        }
+    }
+
+    /**
+     * ED25519 represents Curve 25519 signing key provider.
+     */
+    public static class ED25519 extends AndroidKeyStoreKeyPairGeneratorSpi {
+        // ED25519 is treated as EC.
+        public ED25519() {
+            super(ALGORITHM_ED25519);
         }
     }
 
@@ -241,7 +257,9 @@
 
             KeyGenParameterSpec spec;
             boolean encryptionAtRestRequired = false;
-            int keymasterAlgorithm = mOriginalKeymasterAlgorithm;
+            int keymasterAlgorithm = (mOriginalKeymasterAlgorithm == ALGORITHM_XDH
+                    || mOriginalKeymasterAlgorithm == ALGORITHM_ED25519)
+                    ? KeymasterDefs.KM_ALGORITHM_EC : mOriginalKeymasterAlgorithm;
             if (params instanceof KeyGenParameterSpec) {
                 spec = (KeyGenParameterSpec) params;
             } else if (params instanceof KeyPairGeneratorSpec) {
@@ -610,6 +628,15 @@
                 if (algSpecificSpec instanceof ECGenParameterSpec) {
                     ECGenParameterSpec ecSpec = (ECGenParameterSpec) algSpecificSpec;
                     mEcCurveName = ecSpec.getName();
+                    if (mOriginalKeymasterAlgorithm == ALGORITHM_XDH
+                            && !mEcCurveName.equalsIgnoreCase("x25519")) {
+                        throw new InvalidAlgorithmParameterException("XDH algorithm only supports"
+                                + " x25519 curve.");
+                    } else if (mOriginalKeymasterAlgorithm == ALGORITHM_ED25519
+                            && !mEcCurveName.equalsIgnoreCase("ed25519")) {
+                        throw new InvalidAlgorithmParameterException("Ed25519 algorithm only"
+                                + " supports ed25519 curve.");
+                    }
                     final Integer ecSpecKeySizeBits = SUPPORTED_EC_CURVE_NAME_TO_SIZE.get(
                             mEcCurveName.toLowerCase(Locale.US));
                     if (ecSpecKeySizeBits == null) {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
index 11278e8..d204f13 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
@@ -86,11 +86,14 @@
         put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC");
         put("KeyPairGenerator.RSA", PACKAGE_NAME +  ".AndroidKeyStoreKeyPairGeneratorSpi$RSA");
         put("KeyPairGenerator.XDH", PACKAGE_NAME +  ".AndroidKeyStoreKeyPairGeneratorSpi$XDH");
+        put("KeyPairGenerator.ED25519", PACKAGE_NAME
+                +  ".AndroidKeyStoreKeyPairGeneratorSpi$ED25519");
 
         // java.security.KeyFactory
         putKeyFactoryImpl("EC");
         putKeyFactoryImpl("RSA");
         putKeyFactoryImpl("XDH");
+        putKeyFactoryImpl("ED25519");
 
         // javax.crypto.KeyGenerator
         put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES");
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 066f38b..b2e5b75 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -17,6 +17,7 @@
 package androidx.window.extensions.embedding;
 
 import static android.app.ActivityManager.START_SUCCESS;
+import static android.app.ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -41,10 +42,10 @@
 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent;
 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
 import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
-import static androidx.window.extensions.embedding.SplitPresenter.boundsSmallerThanMinDimensions;
 import static androidx.window.extensions.embedding.SplitPresenter.getActivitiesMinDimensionsPair;
 import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
 import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
+import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds;
 import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
 
 import android.annotation.CallbackExecutor;
@@ -110,6 +111,10 @@
     static final boolean ENABLE_SHELL_TRANSITIONS =
             SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
 
+    // TODO(b/295993745): remove after prebuilt library is updated.
+    private static final String KEY_ACTIVITY_STACK_TOKEN =
+            "androidx.window.extensions.embedding.ActivityStackToken";
+
     @VisibleForTesting
     @GuardedBy("mLock")
     final SplitPresenter mPresenter;
@@ -569,7 +574,8 @@
 
             final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
             final WindowContainerTransaction wct = transactionRecord.getTransaction();
-            mPresenter.applyActivityStackAttributes(wct, container, attributes);
+            mPresenter.applyActivityStackAttributes(wct, container, attributes,
+                    container.getMinDimensions());
             transactionRecord.apply(false /* shouldApplyIndependently */);
         }
     }
@@ -822,11 +828,6 @@
         // Checks if container should be updated before apply new parentInfo.
         final boolean shouldUpdateContainer = taskContainer.shouldUpdateContainer(parentInfo);
         taskContainer.updateTaskFragmentParentInfo(parentInfo);
-        if (!taskContainer.isVisible()) {
-            // Don't update containers if the task is not visible. We only update containers when
-            // parentInfo#isVisibleRequested is true.
-            return;
-        }
 
         // If the last direct activity of the host task is dismissed and the overlay container is
         // the only taskFragment, the overlay container should also be dismissed.
@@ -1567,7 +1568,8 @@
     private TaskFragmentContainer createEmptyExpandedContainer(
             @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
             @Nullable Activity launchingActivity) {
-        return createEmptyContainer(wct, intent, taskId, new Rect(), launchingActivity,
+        return createEmptyContainer(wct, intent, taskId,
+                new ActivityStackAttributes.Builder().build(), launchingActivity,
                 null /* overlayTag */, null /* launchOptions */);
     }
 
@@ -1581,8 +1583,9 @@
     @Nullable
     TaskFragmentContainer createEmptyContainer(
             @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
-            @NonNull Rect bounds, @Nullable Activity launchingActivity,
-            @Nullable String overlayTag, @Nullable Bundle launchOptions) {
+            @NonNull ActivityStackAttributes activityStackAttributes,
+            @Nullable Activity launchingActivity, @Nullable String overlayTag,
+            @Nullable Bundle launchOptions) {
         // We need an activity in the organizer process in the same Task to use as the owner
         // activity, as well as to get the Task window info.
         final Activity activityInTask;
@@ -1605,43 +1608,21 @@
         // Note that taskContainer will not exist before calling #newContainer if the container
         // is the first embedded TF in the task.
         final TaskContainer taskContainer = container.getTaskContainer();
-        final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics().getBounds();
-        final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
+        // TODO(b/265271880): remove redundant logic after all TF operations take fragmentToken.
+        final Rect taskBounds = taskContainer.getBounds();
+        final Rect sanitizedBounds = sanitizeBounds(activityStackAttributes.getRelativeBounds(),
+                getMinDimensions(intent), taskBounds);
         final int windowingMode = taskContainer
                 .getWindowingModeForTaskFragment(sanitizedBounds);
         mPresenter.createTaskFragment(wct, taskFragmentToken, activityInTask.getActivityToken(),
                 sanitizedBounds, windowingMode);
-        mPresenter.updateAnimationParams(wct, taskFragmentToken,
-                TaskFragmentAnimationParams.DEFAULT);
-        mPresenter.setTaskFragmentIsolatedNavigation(wct, taskFragmentToken,
-                overlayTag != null && !sanitizedBounds.isEmpty());
+        mPresenter.applyActivityStackAttributes(wct, container, activityStackAttributes,
+                getMinDimensions(intent));
 
         return container;
     }
 
     /**
-     * Returns the expanded bounds if the {@code bounds} violate minimum dimension or are not fully
-     * covered by the task bounds. Otherwise, returns {@code bounds}.
-     */
-    @NonNull
-    private static Rect sanitizeBounds(@NonNull Rect bounds, @NonNull Intent intent,
-            @NonNull Rect taskBounds) {
-        if (bounds.isEmpty()) {
-            // Don't need to check if the bounds follows the task bounds.
-            return bounds;
-        }
-        if (boundsSmallerThanMinDimensions(bounds, getMinDimensions(intent))) {
-            // Expand the bounds if the bounds are smaller than minimum dimensions.
-            return new Rect();
-        }
-        if (!taskBounds.contains(bounds)) {
-            // Expand the bounds if the bounds exceed the task bounds.
-            return new Rect();
-        }
-        return bounds;
-    }
-
-    /**
      * Returns a container for the new activity intent to launch into as splitting with the primary
      * activity.
      */
@@ -1958,6 +1939,12 @@
             return;
         }
 
+        if (mActivityStackAttributesCalculator == null) {
+            Log.e(TAG, "ActivityStackAttributesCalculator is not set. Thus the overlay container"
+                    + " can not be updated.");
+            return;
+        }
+
         if (mActivityStackAttributesCalculator != null) {
             final ActivityStackAttributesCalculatorParams params =
                     new ActivityStackAttributesCalculatorParams(
@@ -1967,7 +1954,8 @@
                             container.getLaunchOptions());
             final ActivityStackAttributes attributes = mActivityStackAttributesCalculator
                     .apply(params);
-            mPresenter.applyActivityStackAttributes(wct, container, attributes);
+            mPresenter.applyActivityStackAttributes(wct, container, attributes,
+                    container.getMinDimensions());
         }
     }
 
@@ -2603,15 +2591,15 @@
                         mPresenter.createParentContainerInfoFromTaskProperties(
                                 mPresenter.getTaskProperties(launchActivity)), overlayTag, options);
         // Fallback to expand the bounds if there's no activityStackAttributes calculator.
-        final Rect relativeBounds = mActivityStackAttributesCalculator != null
-                ? new Rect(mActivityStackAttributesCalculator.apply(params).getRelativeBounds())
-                : new Rect();
-        final boolean shouldExpandContainer = boundsSmallerThanMinDimensions(relativeBounds,
-                getMinDimensions(intent));
-        // Expand the bounds if the requested bounds are smaller than minimum dimensions.
-        if (shouldExpandContainer) {
-            relativeBounds.setEmpty();
+        final ActivityStackAttributes attrs;
+        if (mActivityStackAttributesCalculator != null) {
+            attrs = mActivityStackAttributesCalculator.apply(params);
+        } else {
+            attrs = new ActivityStackAttributes.Builder().build();
+            Log.e(TAG, "ActivityStackAttributesCalculator isn't set. Fallback to set overlay "
+                    + "container as expected.");
         }
+
         final int taskId = getTaskId(launchActivity);
         if (!overlayContainers.isEmpty()) {
             for (final TaskFragmentContainer overlayContainer : overlayContainers) {
@@ -2631,20 +2619,8 @@
                 }
                 if (overlayTag.equals(overlayContainer.getOverlayTag())
                         && taskId == overlayContainer.getTaskId()) {
-                    // If there's an overlay container with the same tag and task ID, we treat
-                    // the OverlayCreateParams as the update to the container.
-                    final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
-                    final TaskContainer taskContainer = overlayContainer.getTaskContainer();
-                    final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics()
-                            .getBounds();
-                    final Rect sanitizedBounds = sanitizeBounds(relativeBounds, intent, taskBounds);
-
-                    mPresenter.resizeTaskFragment(wct, overlayToken, sanitizedBounds);
-                    final int windowingMode = taskContainer
-                            .getWindowingModeForTaskFragment(sanitizedBounds);
-                    mPresenter.updateWindowingMode(wct, overlayToken, windowingMode);
-                    mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayContainer,
-                            !sanitizedBounds.isEmpty());
+                    mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs,
+                            getMinDimensions(intent));
                     // We can just return the updated overlay container and don't need to
                     // check other condition since we only have one OverlayCreateParams, and
                     // if the tag and task are matched, it's impossible to match another task
@@ -2654,7 +2630,7 @@
             }
         }
         // Launch the overlay container to the task with taskId.
-        return createEmptyContainer(wct, intent, taskId, relativeBounds, launchActivity, overlayTag,
+        return createEmptyContainer(wct, intent, taskId, attrs, launchActivity, overlayTag,
                 options);
     }
 
@@ -2784,8 +2760,17 @@
             // TODO(b/232042367): Consolidate the activity create handling so that we can handle
             // cross-process the same as normal.
 
+            IBinder activityStackToken = options.getBinder(KEY_ACTIVITY_STACK_TOKEN);
+            if (activityStackToken != null) {
+                // Put activityStack token to #KEY_LAUNCH_TASK_FRAGMENT_TOKEN to launch the activity
+                // into the taskFragment associated with the token.
+                options.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, activityStackToken);
+            }
+
             // Early return if the launching taskfragment is already been set.
-            if (options.getBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN) != null) {
+            // TODO(b/295993745): Use KEY_LAUNCH_TASK_FRAGMENT_TOKEN after WM Jetpack migrates to
+            // bundle. This is still needed to support #setLaunchingActivityStack.
+            if (options.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN) != null) {
                 synchronized (mLock) {
                     mCurrentIntent = intent;
                 }
@@ -2842,7 +2827,7 @@
                     // Amend the request to let the WM know that the activity should be placed in
                     // the dedicated container.
                     // TODO(b/229680885): skip override launching TaskFragment token by split-rule
-                    options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
+                    options.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
                             launchedInTaskFragment.getTaskFragmentToken());
                     mCurrentIntent = intent;
                 } else {
@@ -2860,8 +2845,7 @@
                 if (mCurrentIntent != null && result != START_SUCCESS) {
                     // Clear the pending appeared intent if the activity was not started
                     // successfully.
-                    final IBinder token = bOptions.getBinder(
-                            ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN);
+                    final IBinder token = bOptions.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN);
                     if (token != null) {
                         final TaskFragmentContainer container = getContainer(token);
                         if (container != null) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 8b7fd10..2f2da8c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -16,8 +16,6 @@
 
 package androidx.window.extensions.embedding;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.content.pm.PackageManager.MATCH_ALL;
 
 import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;
@@ -426,7 +424,8 @@
      * creation has not been reported from the server yet.
      */
     // TODO(b/190433398): Handle resize if the fragment hasn't appeared yet.
-    private void resizeTaskFragmentIfRegistered(@NonNull WindowContainerTransaction wct,
+    @VisibleForTesting
+    void resizeTaskFragmentIfRegistered(@NonNull WindowContainerTransaction wct,
             @NonNull TaskFragmentContainer container,
             @Nullable Rect relBounds) {
         if (container.getInfo() == null) {
@@ -435,7 +434,8 @@
         resizeTaskFragment(wct, container.getTaskFragmentToken(), relBounds);
     }
 
-    private void updateTaskFragmentWindowingModeIfRegistered(
+    @VisibleForTesting
+    void updateTaskFragmentWindowingModeIfRegistered(
             @NonNull WindowContainerTransaction wct,
             @NonNull TaskFragmentContainer container,
             @WindowingMode int windowingMode) {
@@ -579,13 +579,53 @@
         super.setCompanionTaskFragment(wct, primary, secondary);
     }
 
-    void applyActivityStackAttributes(@NonNull WindowContainerTransaction wct,
-            @NonNull TaskFragmentContainer container, @NonNull ActivityStackAttributes attributes) {
-        final Rect bounds = attributes.getRelativeBounds();
+    void applyActivityStackAttributes(
+            @NonNull WindowContainerTransaction wct,
+            @NonNull TaskFragmentContainer container,
+            @NonNull ActivityStackAttributes attributes,
+            @Nullable Size minDimensions) {
+        final Rect taskBounds = container.getTaskContainer().getBounds();
+        final Rect relativeBounds = sanitizeBounds(attributes.getRelativeBounds(), minDimensions,
+                taskBounds);
+        final boolean isFillParent = relativeBounds.isEmpty();
+        final boolean isIsolatedNavigated = !isFillParent && container.isOverlay();
+        final boolean dimOnTask = !isFillParent
+                && attributes.getWindowAttributes().getDimArea() == DIM_AREA_ON_TASK
+                && Flags.fullscreenDimFlag();
+        final IBinder fragmentToken = container.getTaskFragmentToken();
 
-        resizeTaskFragment(wct, container.getTaskFragmentToken(), bounds);
-        updateWindowingMode(wct, container.getTaskFragmentToken(),
-                bounds.isEmpty() ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_MULTI_WINDOW);
+        // TODO(b/243518738): Update to resizeTaskFragment after we migrate WCT#setRelativeBounds
+        //  and WCT#setWindowingMode to take fragmentToken.
+        resizeTaskFragmentIfRegistered(wct, container, relativeBounds);
+        int windowingMode = container.getTaskContainer().getWindowingModeForTaskFragment(
+                relativeBounds);
+        updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode);
+        // Always use default animation for standalone ActivityStack.
+        updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
+        setTaskFragmentIsolatedNavigation(wct, container, isIsolatedNavigated);
+        setTaskFragmentDimOnTask(wct, fragmentToken, dimOnTask);
+    }
+
+    /**
+     * Returns the expanded bounds if the {@code bounds} violate minimum dimension or are not fully
+     * covered by the task bounds. Otherwise, returns {@code bounds}.
+     */
+    @NonNull
+    static Rect sanitizeBounds(@NonNull Rect bounds, @Nullable Size minDimension,
+                               @NonNull Rect taskBounds) {
+        if (bounds.isEmpty()) {
+            // Don't need to check if the bounds follows the task bounds.
+            return bounds;
+        }
+        if (boundsSmallerThanMinDimensions(bounds, minDimension)) {
+            // Expand the bounds if the bounds are smaller than minimum dimensions.
+            return new Rect();
+        }
+        if (!taskBounds.contains(bounds)) {
+            // Expand the bounds if the bounds exceed the task bounds.
+            return new Rect();
+        }
+        return bounds;
     }
 
     @Override
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 71195b6..73109e2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -126,6 +126,11 @@
     }
 
     @NonNull
+    Rect getBounds() {
+        return mConfiguration.windowConfiguration.getBounds();
+    }
+
+    @NonNull
     TaskProperties getTaskProperties() {
         return new TaskProperties(mDisplayId, mConfiguration);
     }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index 9b84a48..6e704f3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -356,7 +356,7 @@
             }
             if (featureRect.left == 0
                     && featureRect.width() != windowConfiguration.getBounds().width()) {
-                Log.wtf(TAG, "Horizontal FoldingFeature must have full width."
+                Log.w(TAG, "Horizontal FoldingFeature must have full width."
                         + " BaseFeatureRect: " + baseFeature.getRect()
                         + ", FeatureRect: " + featureRect
                         + ", WindowConfiguration: " + windowConfiguration);
@@ -364,7 +364,7 @@
             }
             if (featureRect.top == 0
                     && featureRect.height() != windowConfiguration.getBounds().height()) {
-                Log.wtf(TAG, "Vertical FoldingFeature must have full height."
+                Log.w(TAG, "Vertical FoldingFeature must have full height."
                         + " BaseFeatureRect: " + baseFeature.getRect()
                         + ", FeatureRect: " + featureRect
                         + ", WindowConfiguration: " + windowConfiguration);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index bc92101..34d43ad 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -16,14 +16,17 @@
 
 package androidx.window.extensions.embedding;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TEST_TAG;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder;
+import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -36,7 +39,6 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -56,6 +58,8 @@
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.Size;
+import android.window.TaskFragmentAnimationParams;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentParentInfo;
 import android.window.WindowContainerTransaction;
@@ -266,62 +270,21 @@
     }
 
     @Test
-    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_smallerThanMinDimens_expandOverlay() {
+    public void testSanitizeBounds_smallerThanMinDimens_expandOverlay() {
         mIntent.setComponent(new ComponentName(ApplicationProvider.getApplicationContext(),
                 MinimumDimensionActivity.class));
-
         final Rect bounds = new Rect(0, 0, 100, 100);
-        mSplitController.setActivityStackAttributesCalculator(params ->
-                new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
-        final TaskFragmentContainer overlayContainer =
-                createOrUpdateOverlayTaskFragmentIfNeeded("test");
-        final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
 
-        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
-                .containsExactly(overlayContainer);
-        assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
-        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
-                false);
-
-        // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
-        clearInvocations(mSplitPresenter);
-        createOrUpdateOverlayTaskFragmentIfNeeded("test");
-
-        verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
-        verify(mSplitPresenter).updateWindowingMode(mTransaction, overlayToken,
-                WINDOWING_MODE_UNDEFINED);
-        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayContainer,
-                false);
+        SplitPresenter.sanitizeBounds(bounds, SplitPresenter.getMinDimensions(mIntent),
+                TASK_BOUNDS);
     }
 
     @Test
-    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_notInTaskBounds_expandOverlay() {
+    public void testSanitizeBounds_notInTaskBounds_expandOverlay() {
         final Rect bounds = new Rect(TASK_BOUNDS);
         bounds.offset(10, 10);
-        mSplitController.setActivityStackAttributesCalculator(params ->
-                new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
 
-        final TaskFragmentContainer overlayContainer =
-                createOrUpdateOverlayTaskFragmentIfNeeded("test");
-        final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
-
-        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
-                .containsExactly(overlayContainer);
-        assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
-        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
-                false);
-
-        // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
-        clearInvocations(mSplitPresenter);
-        createOrUpdateOverlayTaskFragmentIfNeeded("test");
-
-        verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
-        verify(mSplitPresenter).updateWindowingMode(mTransaction,
-                overlayToken, WINDOWING_MODE_UNDEFINED);
-        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction,
-                overlayContainer, false);
-        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
-                .containsExactly(overlayContainer);
+        SplitPresenter.sanitizeBounds(bounds, null, TASK_BOUNDS);
     }
 
     @Test
@@ -331,6 +294,7 @@
                 new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
         final TaskFragmentContainer overlayContainer =
                 createOrUpdateOverlayTaskFragmentIfNeeded("test");
+        setupTaskFragmentInfo(overlayContainer, mActivity);
 
         assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
                 .containsExactly(overlayContainer);
@@ -437,7 +401,7 @@
         assertThrows(NullPointerException.class, () ->
                 mSplitController.updateActivityStackAttributes(new Binder(), null));
 
-        verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any());
+        verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any(), any());
     }
 
     @Test
@@ -447,7 +411,7 @@
         mSplitController.updateActivityStackAttributes(container.getTaskFragmentToken(),
                 new ActivityStackAttributes.Builder().build());
 
-        verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any());
+        verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any(), any());
     }
 
     @Test
@@ -457,26 +421,29 @@
         mSplitController.updateActivityStackAttributes(container.getTaskFragmentToken(),
                 new ActivityStackAttributes.Builder().build());
 
-        verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any());
+        verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any(), any());
     }
 
     @Test
     public void testUpdateActivityStackAttributes() {
         final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, "test");
-        doNothing().when(mSplitPresenter).applyActivityStackAttributes(any(), any(), any());
+        doNothing().when(mSplitPresenter).applyActivityStackAttributes(any(), any(), any(), any());
         final ActivityStackAttributes attrs = new ActivityStackAttributes.Builder().build();
         final IBinder token = container.getTaskFragmentToken();
 
         mSplitController.updateActivityStackAttributes(token, attrs);
 
-        verify(mSplitPresenter).applyActivityStackAttributes(any(), eq(container), eq(attrs));
+        verify(mSplitPresenter).applyActivityStackAttributes(any(), eq(container), eq(attrs),
+                any());
     }
 
     @Test
     public void testOnTaskFragmentParentInfoChanged_positionOnlyChange_earlyReturn() {
         final TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test");
-
         final TaskContainer taskContainer = overlayContainer.getTaskContainer();
+
+        assertThat(taskContainer.getOverlayContainer()).isEqualTo(overlayContainer);
+
         spyOn(taskContainer);
         final TaskContainer.TaskProperties taskProperties = taskContainer.getTaskProperties();
         final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(
@@ -495,6 +462,113 @@
                 .that(taskContainer.getOverlayContainer()).isNull();
     }
 
+    @Test
+    public void testOnTaskFragmentParentInfoChanged_invisibleTask_callDismissOverlayContainer() {
+        final TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test");
+        final TaskContainer taskContainer = overlayContainer.getTaskContainer();
+
+        assertThat(taskContainer.getOverlayContainer()).isEqualTo(overlayContainer);
+
+        spyOn(taskContainer);
+        final TaskContainer.TaskProperties taskProperties = taskContainer.getTaskProperties();
+        final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(
+                new Configuration(taskProperties.getConfiguration()), taskProperties.getDisplayId(),
+                false /* visible */, false /* hasDirectActivity */, null /* decorSurface */);
+
+        mSplitController.onTaskFragmentParentInfoChanged(mTransaction, TASK_ID, parentInfo);
+
+        // The parent info must be applied to the task container
+        verify(taskContainer).updateTaskFragmentParentInfo(parentInfo);
+        verify(mSplitController, never()).updateContainer(any(), any());
+
+        assertWithMessage("The overlay container must still be dismissed even if "
+                + "#updateContainer is not called")
+                .that(taskContainer.getOverlayContainer()).isNull();
+    }
+
+    @Test
+    public void testApplyActivityStackAttributesForExpandedContainer() {
+        final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+        final IBinder token = container.getTaskFragmentToken();
+        final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder().build();
+
+        mSplitPresenter.applyActivityStackAttributes(mTransaction, container, attributes,
+                null /* minDimensions */);
+
+        verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+                attributes.getRelativeBounds());
+        verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
+                WINDOWING_MODE_UNDEFINED);
+        verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+                TaskFragmentAnimationParams.DEFAULT);
+        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+        verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
+    }
+
+    @Test
+    public void testApplyActivityStackAttributesForOverlayContainer() {
+        final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, TEST_TAG);
+        final IBinder token = container.getTaskFragmentToken();
+        final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder()
+                .setRelativeBounds(new Rect(0, 0, 200, 200))
+                .setWindowAttributes(new WindowAttributes(DIM_AREA_ON_TASK))
+                .build();
+
+        mSplitPresenter.applyActivityStackAttributes(mTransaction, container, attributes,
+                null /* minDimensions */);
+
+        verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+                attributes.getRelativeBounds());
+        verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
+                WINDOWING_MODE_MULTI_WINDOW);
+        verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+                TaskFragmentAnimationParams.DEFAULT);
+        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, true);
+        verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, true);
+    }
+
+    @Test
+    public void testApplyActivityStackAttributesForExpandedOverlayContainer() {
+        final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, TEST_TAG);
+        final IBinder token = container.getTaskFragmentToken();
+        final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder().build();
+
+        mSplitPresenter.applyActivityStackAttributes(mTransaction, container, attributes,
+                null /* minDimensions */);
+
+        verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+                attributes.getRelativeBounds());
+        verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
+                WINDOWING_MODE_UNDEFINED);
+        verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+                TaskFragmentAnimationParams.DEFAULT);
+        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+        verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
+    }
+
+    @Test
+    public void testApplyActivityStackAttributesForOverlayContainer_exceedsMinDimensions() {
+        final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, TEST_TAG);
+        final IBinder token = container.getTaskFragmentToken();
+        final Rect relativeBounds = new Rect(0, 0, 200, 200);
+        final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder()
+                .setRelativeBounds(relativeBounds)
+                .setWindowAttributes(new WindowAttributes(DIM_AREA_ON_TASK))
+                .build();
+
+        mSplitPresenter.applyActivityStackAttributes(mTransaction, container, attributes,
+                new Size(relativeBounds.width() + 1, relativeBounds.height()));
+
+        verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+                new Rect());
+        verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
+                WINDOWING_MODE_UNDEFINED);
+        verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+                TaskFragmentAnimationParams.DEFAULT);
+        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+        verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
+    }
+
     /**
      * A simplified version of {@link SplitController.ActivityStartMonitor
      * #createOrUpdateOverlayTaskFragmentIfNeeded}
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 5ad144d..4cdc06a 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -162,6 +162,7 @@
         "com_android_wm_shell_flags_lib",
         "com.android.window.flags.window-aconfig-java",
         "WindowManager-Shell-proto",
+        "perfetto_trace_java_protos",
         "dagger2",
         "jsr330",
     ],
@@ -175,3 +176,74 @@
     plugins: ["dagger2-compiler"],
     use_resource_processor: true,
 }
+
+android_app {
+    name: "WindowManagerShellRobolectric",
+    platform_apis: true,
+    static_libs: [
+        "WindowManager-Shell",
+    ],
+    manifest: "multivalentTests/AndroidManifestRobolectric.xml",
+    use_resource_processor: true,
+}
+
+android_robolectric_test {
+    name: "WMShellRobolectricTests",
+    instrumentation_for: "WindowManagerShellRobolectric",
+    upstream: true,
+    java_resource_dirs: [
+        "multivalentTests/robolectric/config",
+    ],
+    srcs: [
+        "multivalentTests/src/**/*.kt",
+    ],
+    static_libs: [
+        "junit",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "mockito-robolectric-prebuilt",
+        "mockito-kotlin2",
+        "truth",
+    ],
+}
+
+android_test {
+    name: "WMShellMultivalentTestsOnDevice",
+    srcs: [
+        "multivalentTests/src/**/*.kt",
+    ],
+    static_libs: [
+        "WindowManager-Shell",
+        "junit",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "frameworks-base-testutils",
+        "mockito-kotlin2",
+        "mockito-target-extended-minus-junit4",
+        "truth",
+        "platform-test-annotations",
+        "platform-test-rules",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+    kotlincflags: ["-Xjvm-default=all"],
+    optimize: {
+        enabled: false,
+    },
+    test_suites: ["device-tests"],
+    platform_apis: true,
+    certificate: "platform",
+    aaptflags: [
+        "--extra-packages",
+        "com.android.wm.shell",
+    ],
+    manifest: "multivalentTests/AndroidManifest.xml",
+}
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 901d5fa..0e04658 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -1,13 +1,6 @@
 package: "com.android.wm.shell"
 
 flag {
-    name: "example_flag"
-    namespace: "multitasking"
-    description: "An Example Flag"
-    bug: "300136750"
-}
-
-flag {
     name: "enable_app_pairs"
     namespace: "multitasking"
     description: "Enables the ability to create and save app pairs to the Home screen"
diff --git a/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml b/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml
new file mode 100644
index 0000000..f8f8338
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml
@@ -0,0 +1,13 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.wm.shell.multivalenttests">
+
+    <application android:debuggable="true" android:supportsRtl="true" >
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="Multivalent tests for WindowManager-Shell"
+        android:targetPackage="com.android.wm.shell.multivalenttests">
+    </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/multivalentTests/AndroidManifestRobolectric.xml b/libs/WindowManager/Shell/multivalentTests/AndroidManifestRobolectric.xml
new file mode 100644
index 0000000..ffcd7d4
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/AndroidManifestRobolectric.xml
@@ -0,0 +1,3 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.wm.shell.multivalenttests">
+</manifest>
diff --git a/libs/WindowManager/Shell/multivalentTests/AndroidTest.xml b/libs/WindowManager/Shell/multivalentTests/AndroidTest.xml
new file mode 100644
index 0000000..36fe8ec
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs Tests for WindowManagerShellLib">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="WMShellMultivalentTestsOnDevice.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="framework-base-presubmit" />
+    <option name="test-tag" value="WMShellMultivalentTestsOnDevice" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.wm.shell.multivalenttests" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/libs/WindowManager/Shell/multivalentTests/OWNERS b/libs/WindowManager/Shell/multivalentTests/OWNERS
new file mode 100644
index 0000000..24c1a3a
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/OWNERS
@@ -0,0 +1,4 @@
+atsjenk@google.com
+liranb@google.com
+madym@google.com
+
diff --git a/libs/WindowManager/Shell/multivalentTests/robolectric/config/robolectric.properties b/libs/WindowManager/Shell/multivalentTests/robolectric/config/robolectric.properties
new file mode 100644
index 0000000..7a0527c
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/robolectric/config/robolectric.properties
@@ -0,0 +1,2 @@
+sdk=NEWEST_SDK
+
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
new file mode 100644
index 0000000..ea7c6ed
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
@@ -0,0 +1,481 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ShortcutInfo
+import android.graphics.Insets
+import android.graphics.PointF
+import android.graphics.Rect
+import android.os.UserHandle
+import android.view.WindowManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.R
+import com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Tests operations and the resulting state managed by [BubblePositioner]. */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubblePositionerTest {
+
+    private lateinit var positioner: BubblePositioner
+    private val context = ApplicationProvider.getApplicationContext<Context>()
+    private val defaultDeviceConfig =
+        DeviceConfig(
+            windowBounds = Rect(0, 0, 1000, 2000),
+            isLargeScreen = false,
+            isSmallTablet = false,
+            isLandscape = false,
+            isRtl = false,
+            insets = Insets.of(0, 0, 0, 0)
+        )
+
+    @Before
+    fun setUp() {
+        val windowManager = context.getSystemService(WindowManager::class.java)
+        positioner = BubblePositioner(context, windowManager)
+    }
+
+    @Test
+    fun testUpdate() {
+        val insets = Insets.of(10, 20, 5, 15)
+        val screenBounds = Rect(0, 0, 1000, 1200)
+        val availableRect = Rect(screenBounds)
+        availableRect.inset(insets)
+        positioner.update(defaultDeviceConfig.copy(insets = insets, windowBounds = screenBounds))
+        assertThat(positioner.availableRect).isEqualTo(availableRect)
+        assertThat(positioner.isLandscape).isFalse()
+        assertThat(positioner.isLargeScreen).isFalse()
+        assertThat(positioner.insets).isEqualTo(insets)
+    }
+
+    @Test
+    fun testShowBubblesVertically_phonePortrait() {
+        positioner.update(defaultDeviceConfig)
+        assertThat(positioner.showBubblesVertically()).isFalse()
+    }
+
+    @Test
+    fun testShowBubblesVertically_phoneLandscape() {
+        positioner.update(defaultDeviceConfig.copy(isLandscape = true))
+        assertThat(positioner.isLandscape).isTrue()
+        assertThat(positioner.showBubblesVertically()).isTrue()
+    }
+
+    @Test
+    fun testShowBubblesVertically_tablet() {
+        positioner.update(defaultDeviceConfig.copy(isLargeScreen = true))
+        assertThat(positioner.showBubblesVertically()).isTrue()
+    }
+
+    /** If a resting position hasn't been set, calling it will return the default position. */
+    @Test
+    fun testGetRestingPosition_returnsDefaultPosition() {
+        positioner.update(defaultDeviceConfig)
+        val restingPosition = positioner.getRestingPosition()
+        val defaultPosition = positioner.defaultStartPosition
+        assertThat(restingPosition).isEqualTo(defaultPosition)
+    }
+
+    /** If a resting position has been set, it'll return that instead of the default position. */
+    @Test
+    fun testGetRestingPosition_returnsRestingPosition() {
+        positioner.update(defaultDeviceConfig)
+        val restingPosition = PointF(100f, 100f)
+        positioner.restingPosition = restingPosition
+        assertThat(positioner.getRestingPosition()).isEqualTo(restingPosition)
+    }
+
+    /** Test that the default resting position on phone is in upper left. */
+    @Test
+    fun testGetRestingPosition_bubble_onPhone() {
+        positioner.update(defaultDeviceConfig)
+        val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+        val restingPosition = positioner.getRestingPosition()
+        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left)
+        assertThat(restingPosition.y).isEqualTo(defaultYPosition)
+    }
+
+    @Test
+    fun testGetRestingPosition_bubble_onPhone_RTL() {
+        positioner.update(defaultDeviceConfig.copy(isRtl = true))
+        val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+        val restingPosition = positioner.getRestingPosition()
+        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right)
+        assertThat(restingPosition.y).isEqualTo(defaultYPosition)
+    }
+
+    /** Test that the default resting position on tablet is middle left. */
+    @Test
+    fun testGetRestingPosition_chatBubble_onTablet() {
+        positioner.update(defaultDeviceConfig.copy(isLargeScreen = true))
+        val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+        val restingPosition = positioner.getRestingPosition()
+        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left)
+        assertThat(restingPosition.y).isEqualTo(defaultYPosition)
+    }
+
+    @Test
+    fun testGetRestingPosition_chatBubble_onTablet_RTL() {
+        positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
+        val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+        val restingPosition = positioner.getRestingPosition()
+        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right)
+        assertThat(restingPosition.y).isEqualTo(defaultYPosition)
+    }
+
+    /** Test that the default resting position on tablet is middle right. */
+    @Test
+    fun testGetDefaultPosition_appBubble_onTablet() {
+        positioner.update(defaultDeviceConfig.copy(isLargeScreen = true))
+        val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+        val startPosition = positioner.getDefaultStartPosition(true /* isAppBubble */)
+        assertThat(startPosition.x).isEqualTo(allowableStackRegion.right)
+        assertThat(startPosition.y).isEqualTo(defaultYPosition)
+    }
+
+    @Test
+    fun testGetRestingPosition_appBubble_onTablet_RTL() {
+        positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
+        val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+        val startPosition = positioner.getDefaultStartPosition(true /* isAppBubble */)
+        assertThat(startPosition.x).isEqualTo(allowableStackRegion.left)
+        assertThat(startPosition.y).isEqualTo(defaultYPosition)
+    }
+
+    @Test
+    fun testHasUserModifiedDefaultPosition_false() {
+        positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
+        assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse()
+        positioner.restingPosition = positioner.defaultStartPosition
+        assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse()
+    }
+
+    @Test
+    fun testHasUserModifiedDefaultPosition_true() {
+        positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
+        assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse()
+        positioner.restingPosition = PointF(0f, 100f)
+        assertThat(positioner.hasUserModifiedDefaultPosition()).isTrue()
+    }
+
+    @Test
+    fun testGetExpandedViewHeight_max() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isLargeScreen = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+        val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+        val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+        assertThat(positioner.getExpandedViewHeight(bubble)).isEqualTo(MAX_HEIGHT)
+    }
+
+    @Test
+    fun testGetExpandedViewHeight_customHeight_valid() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isLargeScreen = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+        val minHeight =
+            context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_default_height)
+        val bubble =
+            Bubble(
+                "key",
+                ShortcutInfo.Builder(context, "id").build(),
+                minHeight + 100 /* desiredHeight */,
+                0 /* desiredHeightResId */,
+                "title",
+                0 /* taskId */,
+                null /* locus */,
+                true /* isDismissable */,
+                directExecutor()) {}
+
+        // Ensure the height is the same as the desired value
+        assertThat(positioner.getExpandedViewHeight(bubble))
+            .isEqualTo(bubble.getDesiredHeight(context))
+    }
+
+    @Test
+    fun testGetExpandedViewHeight_customHeight_tooSmall() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isLargeScreen = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        val bubble =
+            Bubble(
+                "key",
+                ShortcutInfo.Builder(context, "id").build(),
+                10 /* desiredHeight */,
+                0 /* desiredHeightResId */,
+                "title",
+                0 /* taskId */,
+                null /* locus */,
+                true /* isDismissable */,
+                directExecutor()) {}
+
+        // Ensure the height is the same as the desired value
+        val minHeight =
+            context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_default_height)
+        assertThat(positioner.getExpandedViewHeight(bubble)).isEqualTo(minHeight)
+    }
+
+    @Test
+    fun testGetMaxExpandedViewHeight_onLargeTablet() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isLargeScreen = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        val manageButtonHeight =
+            context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_height)
+        val pointerWidth = context.resources.getDimensionPixelSize(R.dimen.bubble_pointer_width)
+        val expandedViewPadding =
+            context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding)
+        val expectedHeight =
+            1800 - 2 * 20 - manageButtonHeight - pointerWidth - expandedViewPadding * 2
+        assertThat(positioner.getMaxExpandedViewHeight(false /* isOverflow */))
+            .isEqualTo(expectedHeight)
+    }
+
+    @Test
+    fun testAreBubblesBottomAligned_largeScreen_true() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isLargeScreen = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        assertThat(positioner.areBubblesBottomAligned()).isTrue()
+    }
+
+    @Test
+    fun testAreBubblesBottomAligned_largeScreen_landscape_false() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isLargeScreen = true,
+                isLandscape = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        assertThat(positioner.areBubblesBottomAligned()).isFalse()
+    }
+
+    @Test
+    fun testAreBubblesBottomAligned_smallTablet_false() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isLargeScreen = true,
+                isSmallTablet = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        assertThat(positioner.areBubblesBottomAligned()).isFalse()
+    }
+
+    @Test
+    fun testAreBubblesBottomAligned_phone_false() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        assertThat(positioner.areBubblesBottomAligned()).isFalse()
+    }
+
+    @Test
+    fun testExpandedViewY_phoneLandscape() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isLandscape = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+        val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+        // This bubble will have max height so it'll always be top aligned
+        assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+            .isEqualTo(positioner.getExpandedViewYTopAligned())
+    }
+
+    @Test
+    fun testExpandedViewY_phonePortrait() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+        val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+        // Always top aligned in phone portrait
+        assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+            .isEqualTo(positioner.getExpandedViewYTopAligned())
+    }
+
+    @Test
+    fun testExpandedViewY_smallTabletLandscape() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isSmallTablet = true,
+                isLandscape = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+        val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+        // This bubble will have max height which is always top aligned on small tablets
+        assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+            .isEqualTo(positioner.getExpandedViewYTopAligned())
+    }
+
+    @Test
+    fun testExpandedViewY_smallTabletPortrait() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isSmallTablet = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+        val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+        // This bubble will have max height which is always top aligned on small tablets
+        assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+            .isEqualTo(positioner.getExpandedViewYTopAligned())
+    }
+
+    @Test
+    fun testExpandedViewY_largeScreenLandscape() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isLargeScreen = true,
+                isLandscape = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+        val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+        // This bubble will have max height which is always top aligned on landscape, large tablet
+        assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+            .isEqualTo(positioner.getExpandedViewYTopAligned())
+    }
+
+    @Test
+    fun testExpandedViewY_largeScreenPortrait() {
+        val deviceConfig =
+            defaultDeviceConfig.copy(
+                isLargeScreen = true,
+                insets = Insets.of(10, 20, 5, 15),
+                windowBounds = Rect(0, 0, 1800, 2600)
+            )
+        positioner.update(deviceConfig)
+
+        val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+        val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+
+        val manageButtonHeight =
+            context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_height)
+        val manageButtonPlusMargin =
+            manageButtonHeight +
+                2 * context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_margin)
+        val pointerWidth = context.resources.getDimensionPixelSize(R.dimen.bubble_pointer_width)
+
+        val expectedExpandedViewY =
+            positioner.availableRect.bottom -
+                manageButtonPlusMargin -
+                positioner.getExpandedViewHeightForLargeScreen() -
+                pointerWidth
+
+        // Bubbles are bottom aligned on portrait, large tablet
+        assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+            .isEqualTo(expectedExpandedViewY)
+    }
+
+    private val defaultYPosition: Float
+        /**
+         * Calculates the Y position bubbles should be placed based on the config. Based on the
+         * calculations in [BubblePositioner.getDefaultStartPosition] and
+         * [BubbleStackView.RelativeStackPosition].
+         */
+        get() {
+            val isTablet = positioner.isLargeScreen
+
+            // On tablet the position is centered, on phone it is an offset from the top.
+            val desiredY =
+                if (isTablet) {
+                    positioner.screenRect.height() / 2f - positioner.bubbleSize / 2f
+                } else {
+                    context.resources
+                        .getDimensionPixelOffset(R.dimen.bubble_stack_starting_offset_y)
+                        .toFloat()
+                }
+            // Since we're visually centering the bubbles on tablet, use total screen height rather
+            // than the available height.
+            val height =
+                if (isTablet) {
+                    positioner.screenRect.height()
+                } else {
+                    positioner.availableRect.height()
+                }
+            val offsetPercent = (desiredY / height).coerceIn(0f, 1f)
+            val allowableStackRegion =
+                positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+            return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent
+        }
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
new file mode 100644
index 0000000..4c76168
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.content.ComponentName
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewTaskController
+
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubbleTaskViewTest {
+
+    private lateinit var bubbleTaskView: BubbleTaskView
+    private val context = ApplicationProvider.getApplicationContext<Context>()
+
+    @Before
+    fun setUp() {
+        val taskView = TaskView(context, mock<TaskViewTaskController>())
+        bubbleTaskView = BubbleTaskView(taskView, directExecutor())
+    }
+
+    @Test
+    fun onTaskCreated_updatesState() {
+        val componentName = ComponentName(context, "TestClass")
+        bubbleTaskView.listener.onTaskCreated(123, componentName)
+
+        assertThat(bubbleTaskView.taskId).isEqualTo(123)
+        assertThat(bubbleTaskView.componentName).isEqualTo(componentName)
+        assertThat(bubbleTaskView.isCreated).isTrue()
+    }
+
+    @Test
+    fun onTaskCreated_callsDelegateListener() {
+        var actualTaskId = -1
+        var actualComponentName: ComponentName? = null
+        val delegateListener = object : TaskView.Listener {
+            override fun onTaskCreated(taskId: Int, name: ComponentName) {
+                actualTaskId = taskId
+                actualComponentName = name
+            }
+        }
+        bubbleTaskView.delegateListener = delegateListener
+
+        val componentName = ComponentName(context, "TestClass")
+        bubbleTaskView.listener.onTaskCreated(123, componentName)
+
+        assertThat(actualTaskId).isEqualTo(123)
+        assertThat(actualComponentName).isEqualTo(componentName)
+    }
+}
diff --git a/libs/WindowManager/Shell/multivalentTestsForDevice b/libs/WindowManager/Shell/multivalentTestsForDevice
new file mode 120000
index 0000000..20ee34a
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTestsForDevice
@@ -0,0 +1 @@
+multivalentTests
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/multivalentTestsForDeviceless b/libs/WindowManager/Shell/multivalentTestsForDeviceless
new file mode 120000
index 0000000..20ee34a
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTestsForDeviceless
@@ -0,0 +1 @@
+multivalentTests
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index e4abae4..9854e58 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -134,6 +134,13 @@
     <!-- Whether the additional education about reachability is enabled -->
     <bool name="config_letterboxIsReachabilityEducationEnabled">false</bool>
 
+    <!-- The minimum tolerance of the percentage of activity bounds within its task to hide
+         size compat restart button. Value lower than 0 or higher than 100 will be ignored.
+         100 is the default value where the activity has to fit exactly within the task to allow
+         size compat restart button to be hidden. 0 means size compat restart button will always
+         be hidden. -->
+    <integer name="config_letterboxRestartButtonHideTolerance">100</integer>
+
     <!-- Whether DragAndDrop capability is enabled -->
     <bool name="config_enableShellDragDrop">true</bool>
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 06210ff..44ee561 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -331,7 +331,9 @@
             if (!animation.hasExtension()) {
                 continue;
             }
-            if (adapter.mChange.hasFlags(FLAG_TRANSLUCENT)) {
+            if (adapter.mChange.hasFlags(FLAG_TRANSLUCENT)
+                    && adapter.mChange.getActivityComponent() != null) {
+                // Skip edge extension for translucent activity.
                 continue;
             }
             final TransitionInfo.Change change = adapter.mChange;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 81d9638..e7f6f0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -17,7 +17,7 @@
 package com.android.wm.shell.back;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
-import static com.android.window.flags.Flags.predictiveBackSystemAnimations;
+import static com.android.window.flags.Flags.predictiveBackSystemAnims;
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
@@ -176,6 +176,10 @@
     private StatusBarCustomizer mCustomizer;
     private boolean mTrackingLatency;
 
+    // Keep previous navigation type before remove mBackNavigationInfo.
+    @BackNavigationInfo.BackTargetType
+    private int mPreviousNavigationType;
+
     public BackAnimationController(
             @NonNull ShellInit shellInit,
             @NonNull ShellController shellController,
@@ -240,7 +244,7 @@
     private void setupAnimationDeveloperSettingsObserver(
             @NonNull ContentResolver contentResolver,
             @NonNull @ShellBackgroundThread final Handler backgroundHandler) {
-        if (predictiveBackSystemAnimations()) {
+        if (predictiveBackSystemAnims()) {
             ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation aconfig flag is enabled, therefore "
                     + "developer settings flag is ignored and no content observer registered");
             return;
@@ -263,7 +267,7 @@
      */
     @ShellBackgroundThread
     private void updateEnableAnimationFromFlags() {
-        boolean isEnabled = predictiveBackSystemAnimations() || isDeveloperSettingEnabled();
+        boolean isEnabled = predictiveBackSystemAnims() || isDeveloperSettingEnabled();
         mEnableAnimations.set(isEnabled);
         ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled);
     }
@@ -871,6 +875,7 @@
         mShellBackAnimationRegistry.resetDefaultCrossActivity();
         cancelLatencyTracking();
         if (mBackNavigationInfo != null) {
+            mPreviousNavigationType = mBackNavigationInfo.getType();
             mBackNavigationInfo.onBackNavigationFinished(triggerBack);
             mBackNavigationInfo = null;
         }
@@ -983,7 +988,9 @@
                         mShellExecutor.execute(
                                 () -> {
                                     if (!mShellBackAnimationRegistry.cancel(
-                                            mBackNavigationInfo.getType())) {
+                                            mBackNavigationInfo != null
+                                                    ? mBackNavigationInfo.getType()
+                                                    : mPreviousNavigationType)) {
                                         return;
                                     }
                                     if (!mBackGestureStarted) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
index 30d5edb..160f922 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
@@ -199,6 +199,10 @@
     }
 
     private void applyTransform(SurfaceControl leash, RectF targetRect, float targetAlpha) {
+        if (leash == null || !leash.isValid()) {
+            return;
+        }
+
         final float scale = targetRect.width() / mStartTaskRect.width();
         mTransformMatrix.reset();
         mTransformMatrix.setScale(scale, scale);
@@ -211,12 +215,16 @@
 
     private void finishAnimation() {
         if (mEnteringTarget != null) {
-            mTransaction.setCornerRadius(mEnteringTarget.leash, 0);
-            mEnteringTarget.leash.release();
+            if (mEnteringTarget.leash != null && mEnteringTarget.leash.isValid()) {
+                mTransaction.setCornerRadius(mEnteringTarget.leash, 0);
+                mEnteringTarget.leash.release();
+            }
             mEnteringTarget = null;
         }
         if (mClosingTarget != null) {
-            mClosingTarget.leash.release();
+            if (mClosingTarget.leash != null) {
+                mClosingTarget.leash.release();
+            }
             mClosingTarget = null;
         }
         if (mBackground != null) {
@@ -260,7 +268,9 @@
     }
 
     private void onGestureCommitted() {
-        if (mEnteringTarget == null || mClosingTarget == null) {
+        if (mEnteringTarget == null || mClosingTarget == null || mClosingTarget.leash == null
+                || mEnteringTarget.leash == null || !mEnteringTarget.leash.isValid()
+                || !mClosingTarget.leash.isValid()) {
             finishAnimation();
             return;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 80fc3a8..adc7839 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -136,6 +136,9 @@
         mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds());
         mStartTaskRect.offsetTo(0, 0);
 
+        // inset bottom in case of pinned taskbar being present
+        mStartTaskRect.inset(0, 0, 0, mClosingTarget.contentInsets.bottom);
+
         // Draw background.
         mBackground.ensureBackground(mClosingTarget.windowConfiguration.getBounds(),
                 BACKGROUNDCOLOR, mTransaction);
@@ -205,7 +208,9 @@
         float top = mapRange(progress, mClosingStartRect.top, targetTop);
         float width = mapRange(progress, mClosingStartRect.width(), targetWidth);
         float height = mapRange(progress, mClosingStartRect.height(), targetHeight);
-        mTransaction.setLayer(mClosingTarget.leash, 0);
+        if (mClosingTarget.leash != null && mClosingTarget.leash.isValid()) {
+            mTransaction.setLayer(mClosingTarget.leash, 0);
+        }
 
         mClosingCurrentRect.set(left, top, left + width, top + height);
         applyTransform(mClosingTarget.leash, mClosingCurrentRect, mCornerRadius);
@@ -223,7 +228,7 @@
 
     /** Transform the target window to match the target rect. */
     private void applyTransform(SurfaceControl leash, RectF targetRect, float cornerRadius) {
-        if (leash == null) {
+        if (leash == null || !leash.isValid()) {
             return;
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 7a3210e..87c8f52 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -64,7 +64,7 @@
     private static final String TAG = "Bubble";
 
     /** A string suffix used in app bubbles' {@link #mKey}. */
-    private static final String KEY_APP_BUBBLE = "key_app_bubble";
+    public static final String KEY_APP_BUBBLE = "key_app_bubble";
 
     /** Whether the bubble is an app bubble. */
     private final boolean mIsAppBubble;
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 896bcaf..a5f7880 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
@@ -137,7 +137,7 @@
  * The controller manages addition, removal, and visible state of bubbles on screen.
  */
 public class BubbleController implements ConfigurationChangeListener,
-        RemoteCallable<BubbleController> {
+        RemoteCallable<BubbleController>, Bubbles.SysuiProxy.Provider {
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
 
@@ -706,6 +706,7 @@
         return mBubbleIconFactory;
     }
 
+    @Override
     public Bubbles.SysuiProxy getSysuiProxy() {
         return mSysuiProxy;
     }
@@ -732,8 +733,7 @@
             if (mStackView == null) {
                 mStackView = new BubbleStackView(
                         mContext, this, mBubbleData, mSurfaceSynchronizer,
-                        mFloatingContentCoordinator,
-                        mMainExecutor);
+                        mFloatingContentCoordinator, this, mMainExecutor);
                 mStackView.onOrientationChanged();
                 if (mExpandListener != null) {
                     mStackView.setExpandListener(mExpandListener);
@@ -1249,6 +1249,7 @@
         }
 
         String appBubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(), user);
+        Log.i(TAG, "showOrHideAppBubble, with key: " + appBubbleKey);
         PackageManager packageManager = getPackageManagerForUser(mContext, user.getIdentifier());
         if (!isResizableActivity(intent, packageManager, appBubbleKey)) return;
 
@@ -1258,18 +1259,22 @@
             if (isStackExpanded()) {
                 if (selectedBubble != null && appBubbleKey.equals(selectedBubble.getKey())) {
                     // App bubble is expanded, lets collapse
+                    Log.i(TAG, "  showOrHideAppBubble, selected bubble is app bubble, collapsing");
                     collapseStack();
                 } else {
                     // App bubble is not selected, select it
+                    Log.i(TAG, "  showOrHideAppBubble, expanded, selecting existing app bubble");
                     mBubbleData.setSelectedBubble(existingAppBubble);
                 }
             } else {
                 // App bubble is not selected, select it & expand
+                Log.i(TAG, "  showOrHideAppBubble, expand and select existing app bubble");
                 mBubbleData.setSelectedBubble(existingAppBubble);
                 mBubbleData.setExpanded(true);
             }
         } else {
             // App bubble does not exist, lets add and expand it
+            Log.i(TAG, "  showOrHideAppBubble, creating and expanding app bubble");
             Bubble b = Bubble.createAppBubble(intent, user, icon, mMainExecutor);
             b.setShouldAutoExpand(true);
             inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index bbb4b74..6e0c804 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -17,6 +17,7 @@
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
@@ -649,8 +650,8 @@
     }
 
     private void doRemove(String key, @DismissReason int reason) {
-        if (DEBUG_BUBBLE_DATA) {
-            Log.d(TAG, "doRemove: " + key);
+        if (DEBUG_BUBBLE_DATA || (key != null && key.contains(KEY_APP_BUBBLE))) {
+            Log.d(TAG, "doRemove: " + key + " reason: " + reason);
         }
         //  If it was pending remove it
         if (mPendingBubbles.containsKey(key)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index f3fe895..9f7d0ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -74,7 +74,6 @@
 import com.android.wm.shell.common.AlphaOptimizedButton;
 import com.android.wm.shell.common.TriangleShape;
 import com.android.wm.shell.taskview.TaskView;
-import com.android.wm.shell.taskview.TaskViewTaskController;
 
 import java.io.PrintWriter;
 
@@ -146,7 +145,6 @@
 
     private AlphaOptimizedButton mManageButton;
     private TaskView mTaskView;
-    private TaskViewTaskController mTaskViewTaskController;
     private BubbleOverflowContainerView mOverflowView;
 
     private int mTaskId = INVALID_TASK_ID;
@@ -434,7 +432,8 @@
      * Initialize {@link BubbleController} and {@link BubbleStackView} here, this method must need
      * to be called after view inflate.
      */
-    void initialize(BubbleController controller, BubbleStackView stackView, boolean isOverflow) {
+    void initialize(BubbleController controller, BubbleStackView stackView, boolean isOverflow,
+            @Nullable BubbleTaskView bubbleTaskView) {
         mController = controller;
         mStackView = stackView;
         mIsOverflow = isOverflow;
@@ -451,18 +450,22 @@
             bringChildToFront(mOverflowView);
             mManageButton.setVisibility(GONE);
         } else {
-            mTaskViewTaskController = new TaskViewTaskController(mContext,
-                    mController.getTaskOrganizer(),
-                    mController.getTaskViewTransitions(), mController.getSyncTransactionQueue());
-            mTaskView = new TaskView(mContext, mTaskViewTaskController);
-            mTaskView.setListener(mController.getMainExecutor(), mTaskViewListener);
+            mTaskView = bubbleTaskView.getTaskView();
+            bubbleTaskView.setDelegateListener(mTaskViewListener);
 
             // set a fixed width so it is not recalculated as part of a rotation. the width will be
             // updated manually after the rotation.
             FrameLayout.LayoutParams lp =
                     new FrameLayout.LayoutParams(getContentWidth(), MATCH_PARENT);
+            if (mTaskView.getParent() != null) {
+                ((ViewGroup) mTaskView.getParent()).removeView(mTaskView);
+            }
             mExpandedViewContainer.addView(mTaskView, lp);
             bringChildToFront(mTaskView);
+            if (bubbleTaskView.isCreated()) {
+                mTaskViewListener.onTaskCreated(
+                        bubbleTaskView.getTaskId(), bubbleTaskView.getComponentName());
+            }
         }
     }
 
@@ -876,7 +879,7 @@
             return;
         }
         boolean isNew = mBubble == null || didBackingContentChange(bubble);
-        if (isNew || bubble != null && bubble.getKey().equals(mBubble.getKey())) {
+        if (isNew || bubble.getKey().equals(mBubble.getKey())) {
             mBubble = bubble;
             mManageButton.setContentDescription(getResources().getString(
                     R.string.bubbles_settings_button_description, bubble.getAppName()));
@@ -1107,7 +1110,8 @@
      * has been removed.
      *
      * If this view should be reused after this method is called, then
-     * {@link #initialize(BubbleController, BubbleStackView, boolean)} must be invoked first.
+     * {@link #initialize(BubbleController, BubbleStackView, boolean, BubbleTaskView)}
+     * must be invoked first.
      */
     public void cleanUpExpandedState() {
         if (DEBUG_BUBBLE_EXPANDED_VIEW) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 22e836a..e5d9ace 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -29,6 +29,7 @@
 import android.view.LayoutInflater
 import android.view.View.VISIBLE
 import android.widget.FrameLayout
+import androidx.core.content.ContextCompat
 import com.android.launcher3.icons.BubbleIconFactory
 import com.android.wm.shell.R
 import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView
@@ -57,10 +58,16 @@
     /** Call before use and again if cleanUpExpandedState was called. */
     fun initialize(controller: BubbleController, forBubbleBar: Boolean) {
         if (forBubbleBar) {
-            createBubbleBarExpandedView().initialize(controller, true /* isOverflow */)
+            createBubbleBarExpandedView()
+                .initialize(controller, /* isOverflow= */ true, /* bubbleTaskView= */ null)
         } else {
             createExpandedView()
-                .initialize(controller, controller.stackView, true /* isOverflow */)
+                .initialize(
+                    controller,
+                    controller.stackView,
+                    /* isOverflow= */ true,
+                    /* bubbleTaskView= */ null
+                )
         }
     }
 
@@ -113,7 +120,10 @@
                 context,
                 res.getDimensionPixelSize(R.dimen.bubble_size),
                 res.getDimensionPixelSize(R.dimen.bubble_badge_size),
-                res.getColor(com.android.launcher3.icons.R.color.important_conversation),
+                ContextCompat.getColor(
+                    context,
+                    com.android.launcher3.icons.R.color.important_conversation
+                ),
                 res.getDimensionPixelSize(com.android.internal.R.dimen.importance_ring_stroke_width)
             )
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 470a825..a619401 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -206,6 +206,7 @@
             };
     private final BubbleController mBubbleController;
     private final BubbleData mBubbleData;
+    private final Bubbles.SysuiProxy.Provider mSysuiProxyProvider;
     private StackViewState mStackViewState = new StackViewState();
 
     private final ValueAnimator mDismissBubbleAnimator;
@@ -300,6 +301,9 @@
      */
     private int mPointerIndexDown = -1;
 
+    /** Indicates whether bubbles should be reordered at the end of a gesture. */
+    private boolean mShouldReorderBubblesAfterGestureCompletes = false;
+
     @Nullable
     private BubblesNavBarGestureTracker mBubblesNavBarGestureTracker;
 
@@ -708,6 +712,11 @@
 
             // Hide the stack after a delay, if needed.
             updateTemporarilyInvisibleAnimation(false /* hideImmediately */);
+
+            if (mShouldReorderBubblesAfterGestureCompletes) {
+                mShouldReorderBubblesAfterGestureCompletes = false;
+                updateBubbleOrderInternal(mBubbleData.getBubbles(), true);
+            }
         }
     };
 
@@ -867,12 +876,14 @@
     public BubbleStackView(Context context, BubbleController bubbleController,
             BubbleData data, @Nullable SurfaceSynchronizer synchronizer,
             FloatingContentCoordinator floatingContentCoordinator,
+            Bubbles.SysuiProxy.Provider sysuiProxyProvider,
             ShellExecutor mainExecutor) {
         super(context);
 
         mMainExecutor = mainExecutor;
         mBubbleController = bubbleController;
         mBubbleData = data;
+        mSysuiProxyProvider = sysuiProxyProvider;
 
         Resources res = getResources();
         mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
@@ -1177,14 +1188,17 @@
             if (mStackAnimationController.isStackOnLeftSide()) {
                 int availableRectOffsetX =
                         mPositioner.getAvailableRect().left - mPositioner.getScreenRect().left;
-                animate().translationX(-(mBubbleSize + availableRectOffsetX)).start();
+                mBubbleContainer
+                        .animate()
+                        .translationX(-(mBubbleSize + availableRectOffsetX))
+                        .start();
             } else {
                 int availableRectOffsetX =
                         mPositioner.getAvailableRect().right - mPositioner.getScreenRect().right;
-                animate().translationX(mBubbleSize - availableRectOffsetX).start();
+                mBubbleContainer.animate().translationX(mBubbleSize - availableRectOffsetX).start();
             }
         } else {
-            animate().translationX(0).start();
+            mBubbleContainer.animate().translationX(0).start();
         }
     };
 
@@ -1925,7 +1939,18 @@
     /**
      * Update bubble order and pointer position.
      */
-    public void updateBubbleOrder(List<Bubble> bubbles, boolean updatePointerPositoion) {
+    public void updateBubbleOrder(List<Bubble> bubbles, boolean updatePointerPosition) {
+        // Don't reorder bubbles in the middle of a gesture because that would remove bubbles from
+        // view hierarchy and will cancel all touch events. Instead wait until the gesture is
+        // finished and then reorder.
+        if (mIsGestureInProgress) {
+            mShouldReorderBubblesAfterGestureCompletes = true;
+            return;
+        }
+        updateBubbleOrderInternal(bubbles, updatePointerPosition);
+    }
+
+    private void updateBubbleOrderInternal(List<Bubble> bubbles, boolean updatePointerPosition) {
         final Runnable reorder = () -> {
             for (int i = 0; i < bubbles.size(); i++) {
                 Bubble bubble = bubbles.get(i);
@@ -1936,13 +1961,13 @@
             reorder.run();
             updateBadges(false /* setBadgeForCollapsedStack */);
             updateZOrder();
-        } else if (!isExpansionAnimating()) {
+        } else {
             List<View> bubbleViews = bubbles.stream()
                     .map(b -> b.getIconView()).collect(Collectors.toList());
             mStackAnimationController.animateReorder(bubbleViews, reorder);
         }
 
-        if (updatePointerPositoion) {
+        if (updatePointerPosition) {
             updatePointerPosition(false /* forIme */);
         }
     }
@@ -2068,7 +2093,7 @@
 
         hideCurrentInputMethod();
 
-        mBubbleController.getSysuiProxy().onStackExpandChanged(shouldExpand);
+        mSysuiProxyProvider.getSysuiProxy().onStackExpandChanged(shouldExpand);
 
         if (wasExpanded) {
             stopMonitoringSwipeUpGesture();
@@ -3012,7 +3037,7 @@
         if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
             mManageMenu.setVisibility(View.INVISIBLE);
             mManageMenuScrim.setVisibility(INVISIBLE);
-            mBubbleController.getSysuiProxy().onManageMenuExpandChanged(false /* show */);
+            mSysuiProxyProvider.getSysuiProxy().onManageMenuExpandChanged(false /* show */);
             return;
         }
         if (show) {
@@ -3026,7 +3051,7 @@
             }
         };
 
-        mBubbleController.getSysuiProxy().onManageMenuExpandChanged(show);
+        mSysuiProxyProvider.getSysuiProxy().onManageMenuExpandChanged(show);
         mManageMenuScrim.animate()
                 .setInterpolator(show ? ALPHA_IN : ALPHA_OUT)
                 .alpha(show ? BUBBLE_EXPANDED_SCRIM_ALPHA : 0f)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
new file mode 100644
index 0000000..2fcd133
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.app.ActivityTaskManager.INVALID_TASK_ID
+import android.content.ComponentName
+import androidx.annotation.VisibleForTesting
+import com.android.wm.shell.taskview.TaskView
+import java.util.concurrent.Executor
+
+/**
+ * A wrapper class around [TaskView] for bubble expanded views.
+ *
+ * [delegateListener] allows callers to change listeners after a task has been created.
+ */
+class BubbleTaskView(val taskView: TaskView, executor: Executor) {
+
+    /** Whether the task is already created. */
+    var isCreated = false
+      private set
+
+    /** The task id. */
+    var taskId = INVALID_TASK_ID
+      private set
+
+    /** The component name of the application running in the task. */
+    var componentName: ComponentName? = null
+      private set
+
+    /** [TaskView.Listener] for users of this class. */
+    var delegateListener: TaskView.Listener? = null
+
+    /** A [TaskView.Listener] that delegates to [delegateListener]. */
+    @get:VisibleForTesting
+    val listener = object : TaskView.Listener {
+        override fun onInitialized() {
+            delegateListener?.onInitialized()
+        }
+
+        override fun onReleased() {
+            delegateListener?.onReleased()
+        }
+
+        override fun onTaskCreated(taskId: Int, name: ComponentName) {
+            delegateListener?.onTaskCreated(taskId, name)
+            this@BubbleTaskView.taskId = taskId
+            isCreated = true
+            componentName = name
+        }
+
+        override fun onTaskVisibilityChanged(taskId: Int, visible: Boolean) {
+            delegateListener?.onTaskVisibilityChanged(taskId, visible)
+        }
+
+        override fun onTaskRemovalStarted(taskId: Int) {
+            delegateListener?.onTaskRemovalStarted(taskId)
+        }
+
+        override fun onBackPressedOnTaskRoot(taskId: Int) {
+            delegateListener?.onBackPressedOnTaskRoot(taskId)
+        }
+    }
+
+    init {
+        taskView.setListener(executor, listener)
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index f6c382f..5855a81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -35,10 +35,7 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.taskview.TaskView;
-import com.android.wm.shell.taskview.TaskViewTaskController;
 
 /**
  * Handles creating and updating the {@link TaskView} associated with a {@link Bubble}.
@@ -65,7 +62,6 @@
 
     private final Context mContext;
     private final BubbleController mController;
-    private final @ShellMainThread ShellExecutor mMainExecutor;
     private final BubbleTaskViewHelper.Listener mListener;
     private final View mParentView;
 
@@ -73,7 +69,6 @@
     private Bubble mBubble;
     @Nullable
     private PendingIntent mPendingIntent;
-    private TaskViewTaskController mTaskViewTaskController;
     @Nullable
     private TaskView mTaskView;
     private int mTaskId = INVALID_TASK_ID;
@@ -204,17 +199,18 @@
     public BubbleTaskViewHelper(Context context,
             BubbleController controller,
             BubbleTaskViewHelper.Listener listener,
+            BubbleTaskView bubbleTaskView,
             View parent) {
         mContext = context;
         mController = controller;
-        mMainExecutor = mController.getMainExecutor();
         mListener = listener;
         mParentView = parent;
-        mTaskViewTaskController = new TaskViewTaskController(mContext,
-                mController.getTaskOrganizer(),
-                mController.getTaskViewTransitions(), mController.getSyncTransactionQueue());
-        mTaskView = new TaskView(mContext, mTaskViewTaskController);
-        mTaskView.setListener(mMainExecutor, mTaskViewListener);
+        mTaskView = bubbleTaskView.getTaskView();
+        bubbleTaskView.setDelegateListener(mTaskViewListener);
+        if (bubbleTaskView.isCreated()) {
+            mTaskId = bubbleTaskView.getTaskId();
+            mListener.onTaskCreated();
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index bb30c5e..c3d899e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -46,6 +46,8 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
 import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
+import com.android.wm.shell.taskview.TaskView;
+import com.android.wm.shell.taskview.TaskViewTaskController;
 
 import java.lang.ref.WeakReference;
 import java.util.Objects;
@@ -173,10 +175,12 @@
             BubbleViewInfo info = new BubbleViewInfo();
 
             if (!skipInflation && !b.isInflated()) {
+                BubbleTaskView bubbleTaskView = createBubbleTaskView(c, controller);
                 LayoutInflater inflater = LayoutInflater.from(c);
                 info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate(
                         R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */);
-                info.bubbleBarExpandedView.initialize(controller, false /* isOverflow */);
+                info.bubbleBarExpandedView.initialize(
+                        controller, false /* isOverflow */, bubbleTaskView);
             }
 
             if (!populateCommonInfo(info, c, b, iconFactory)) {
@@ -201,9 +205,11 @@
                         R.layout.bubble_view, stackView, false /* attachToRoot */);
                 info.imageView.initialize(controller.getPositioner());
 
+                BubbleTaskView bubbleTaskView = createBubbleTaskView(c, controller);
                 info.expandedView = (BubbleExpandedView) inflater.inflate(
                         R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
-                info.expandedView.initialize(controller, stackView, false /* isOverflow */);
+                info.expandedView.initialize(
+                        controller, stackView, false /* isOverflow */, bubbleTaskView);
             }
 
             if (!populateCommonInfo(info, c, b, iconFactory)) {
@@ -219,6 +225,15 @@
             }
             return info;
         }
+
+        private static BubbleTaskView createBubbleTaskView(
+                Context context, BubbleController controller) {
+            TaskViewTaskController taskViewTaskController = new TaskViewTaskController(context,
+                    controller.getTaskOrganizer(),
+                    controller.getTaskViewTransitions(), controller.getSyncTransactionQueue());
+            TaskView taskView = new TaskView(context, taskViewTaskController);
+            return new BubbleTaskView(taskView, controller.getMainExecutor());
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 759246e..28af0ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -321,6 +321,13 @@
 
     /** Callback to tell SysUi components execute some methods. */
     interface SysuiProxy {
+
+        /** Provider interface for {@link SysuiProxy}. */
+        interface Provider {
+            /** Returns {@link SysuiProxy}. */
+            SysuiProxy getSysuiProxy();
+        }
+
         void isNotificationPanelExpand(Consumer<Boolean> callback);
 
         void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index e487328..bb0dd95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -769,8 +769,10 @@
         boolean swapped = false;
         for (int newIndex = 0; newIndex < bubbleViews.size(); newIndex++) {
             View view = bubbleViews.get(newIndex);
-            final int oldIndex = mLayout.indexOfChild(view);
-            swapped |= animateSwap(view, oldIndex, newIndex, updateAllIcons, after);
+            if (view != null) {
+                final int oldIndex = mLayout.indexOfChild(view);
+                swapped |= animateSwap(view, oldIndex, newIndex, updateAllIcons, after);
+            }
         }
         if (!swapped) {
             // All bubbles were at the right position. Make sure badges and z order is correct.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 66c0c96..3cf23ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -16,6 +16,8 @@
 
 package com.android.wm.shell.bubbles.bar;
 
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
@@ -27,6 +29,7 @@
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
 import android.widget.FrameLayout;
 
@@ -35,6 +38,7 @@
 import com.android.wm.shell.bubbles.Bubble;
 import com.android.wm.shell.bubbles.BubbleController;
 import com.android.wm.shell.bubbles.BubbleOverflowContainerView;
+import com.android.wm.shell.bubbles.BubbleTaskView;
 import com.android.wm.shell.bubbles.BubbleTaskViewHelper;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.taskview.TaskView;
@@ -130,7 +134,8 @@
     }
 
     /** Set the BubbleController on the view, must be called before doing anything else. */
-    public void initialize(BubbleController controller, boolean isOverflow) {
+    public void initialize(BubbleController controller, boolean isOverflow,
+            @Nullable BubbleTaskView bubbleTaskView) {
         mController = controller;
         mIsOverflow = isOverflow;
 
@@ -140,14 +145,19 @@
             mOverflowView.setBubbleController(mController);
             addView(mOverflowView);
         } else {
-
+            mTaskView = bubbleTaskView.getTaskView();
             mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, mController,
-                    /* listener= */ this,
+                    /* listener= */ this, bubbleTaskView,
                     /* viewParent= */ this);
-            mTaskView = mBubbleTaskViewHelper.getTaskView();
-            addView(mTaskView);
+            if (mTaskView.getParent() != null) {
+                ((ViewGroup) mTaskView.getParent()).removeView(mTaskView);
+            }
+            FrameLayout.LayoutParams lp =
+                    new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
+            addView(mTaskView, lp);
             mTaskView.setEnableSurfaceClipping(true);
             mTaskView.setCornerRadius(mCornerRadius);
+            mTaskView.setVisibility(VISIBLE);
 
             // Handle view needs to draw on top of task view.
             bringChildToFront(mHandleView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 1211451..bd8ce80 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -26,6 +26,7 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.drawable.ColorDrawable;
+import android.view.Gravity;
 import android.view.TouchDelegate;
 import android.view.View;
 import android.view.ViewTreeObserver;
@@ -74,10 +75,6 @@
     private DismissView mDismissView;
     private @Nullable Consumer<String> mUnBubbleConversationCallback;
 
-    // TODO(b/273310265) - currently the view is always on the right, need to update for RTL.
-    /** Whether the expanded view is displaying on the left of the screen or not. */
-    private boolean mOnLeft = false;
-
     /** Whether a bubble is expanded. */
     private boolean mIsExpanded = false;
 
@@ -154,10 +151,10 @@
         return mIsExpanded;
     }
 
-    // (TODO: b/273310265): BubblePositioner should be source of truth when this work is done.
+    // TODO(b/313661121) - when dragging is implemented, check user setting first
     /** Whether the expanded view is positioned on the left or right side of the screen. */
     public boolean isOnLeft() {
-        return mOnLeft;
+        return getLayoutDirection() == LAYOUT_DIRECTION_RTL;
     }
 
     /** Shows the expanded view of the provided bubble. */
@@ -216,7 +213,7 @@
                         return Unit.INSTANCE;
                     });
 
-            addView(mExpandedView, new FrameLayout.LayoutParams(width, height));
+            addView(mExpandedView, new LayoutParams(width, height, Gravity.LEFT));
         }
 
         if (mEducationViewController.isEducationVisible()) {
@@ -311,7 +308,7 @@
         lp.width = width;
         lp.height = height;
         mExpandedView.setLayoutParams(lp);
-        if (mOnLeft) {
+        if (isOnLeft()) {
             mExpandedView.setX(mPositioner.getInsets().left + padding);
         } else {
             mExpandedView.setX(mPositioner.getAvailableRect().width() - width - padding);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java
new file mode 100644
index 0000000..4cbb78f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.pip;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.graphics.Rect;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Static utilities to get appropriate {@link PipDoubleTapHelper.PipSizeSpec} on a double tap.
+ */
+public class PipDoubleTapHelper {
+
+    /**
+     * Should not be instantiated as a stateless class.
+     */
+    private PipDoubleTapHelper() {}
+
+    /**
+     * A constant that represents a pip screen size.
+     *
+     * <p>CUSTOM - user resized screen size (by pinching in/out)</p>
+     * <p>DEFAULT - normal screen size used as default when entering pip mode</p>
+     * <p>MAX - maximum allowed screen size</p>
+     */
+    @IntDef(value = {
+        SIZE_SPEC_DEFAULT,
+        SIZE_SPEC_MAX,
+        SIZE_SPEC_CUSTOM
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface PipSizeSpec {}
+
+    public static final int SIZE_SPEC_DEFAULT = 0;
+    public static final int SIZE_SPEC_MAX = 1;
+    public static final int SIZE_SPEC_CUSTOM = 2;
+
+    /**
+     * Returns MAX or DEFAULT {@link PipSizeSpec} to toggle to/from.
+     *
+     * <p>Each double tap toggles back and forth between {@code PipSizeSpec.CUSTOM} and
+     * either {@code PipSizeSpec.MAX} or {@code PipSizeSpec.DEFAULT}. The choice between
+     * the latter two sizes is determined based on the current state of the pip screen.</p>
+     *
+     * @param mPipBoundsState current state of the pip screen
+     */
+    @PipSizeSpec
+    private static int getMaxOrDefaultPipSizeSpec(@NonNull PipBoundsState mPipBoundsState) {
+        // determine the average pip screen width
+        int averageWidth = (mPipBoundsState.getMaxSize().x
+                + mPipBoundsState.getMinSize().x) / 2;
+
+        // If pip screen width is above average, DEFAULT is the size spec we need to
+        // toggle to. Otherwise, we choose MAX.
+        return (mPipBoundsState.getBounds().width() > averageWidth)
+                ? SIZE_SPEC_DEFAULT
+                : SIZE_SPEC_MAX;
+    }
+
+    /**
+     * Determines the {@link PipSizeSpec} to toggle to on double tap.
+     *
+     * @param mPipBoundsState current state of the pip screen
+     * @param userResizeBounds latest user resized bounds (by pinching in/out)
+     * @return pip screen size to switch to
+     */
+    @PipSizeSpec
+    public static int nextSizeSpec(@NonNull PipBoundsState mPipBoundsState,
+            @NonNull Rect userResizeBounds) {
+        // is pip screen at its maximum
+        boolean isScreenMax = mPipBoundsState.getBounds().width()
+                == mPipBoundsState.getMaxSize().x;
+
+        // is pip screen at its normal default size
+        boolean isScreenDefault = (mPipBoundsState.getBounds().width()
+                == mPipBoundsState.getNormalBounds().width())
+                && (mPipBoundsState.getBounds().height()
+                == mPipBoundsState.getNormalBounds().height());
+
+        // edge case 1
+        // if user hasn't resized screen yet, i.e. CUSTOM size does not exist yet
+        // or if user has resized exactly to DEFAULT, then we just want to maximize
+        if (isScreenDefault
+                && userResizeBounds.width() == mPipBoundsState.getNormalBounds().width()) {
+            return SIZE_SPEC_MAX;
+        }
+
+        // edge case 2
+        // if user has maximized, then we want to toggle to DEFAULT
+        if (isScreenMax
+                && userResizeBounds.width() == mPipBoundsState.getMaxSize().x) {
+            return SIZE_SPEC_DEFAULT;
+        }
+
+        // otherwise in general we want to toggle back to user's CUSTOM size
+        if (isScreenDefault || isScreenMax) {
+            return SIZE_SPEC_CUSTOM;
+        }
+
+        // if we are currently in user resized CUSTOM size state
+        // then we toggle either to MAX or DEFAULT depending on the current pip screen state
+        return getMaxOrDefaultPipSizeSpec(mPipBoundsState);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
index 09d99b2..fa2e236 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
@@ -71,6 +71,8 @@
     private static final String HAS_SEEN_VERTICAL_REACHABILITY_EDUCATION_KEY_PREFIX =
             "has_seen_vertical_reachability_education";
 
+    private static final int MAX_PERCENTAGE_VAL = 100;
+
     /**
      * The {@link SharedPreferences} instance for the restart dialog and the reachability
      * education.
@@ -82,6 +84,12 @@
      */
     private final SharedPreferences mLetterboxEduSharedPreferences;
 
+    /**
+     * The minimum tolerance of the percentage of activity bounds within its task to hide
+     * size compat restart button.
+     */
+    private final int mHideSizeCompatRestartButtonTolerance;
+
     // Whether the extended restart dialog is enabled
     private boolean mIsRestartDialogEnabled;
 
@@ -106,6 +114,9 @@
                 R.bool.config_letterboxIsRestartDialogEnabled);
         mIsReachabilityEducationEnabled = context.getResources().getBoolean(
                 R.bool.config_letterboxIsReachabilityEducationEnabled);
+        final int tolerance = context.getResources().getInteger(
+                R.integer.config_letterboxRestartButtonHideTolerance);
+        mHideSizeCompatRestartButtonTolerance = getHideSizeCompatRestartButtonTolerance(tolerance);
         mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG,
                 DEFAULT_VALUE_ENABLE_LETTERBOX_RESTART_DIALOG);
@@ -179,6 +190,10 @@
                     || !hasSeenVerticalReachabilityEducation(taskInfo));
     }
 
+    int getHideSizeCompatRestartButtonTolerance() {
+        return mHideSizeCompatRestartButtonTolerance;
+    }
+
     boolean getHasSeenLetterboxEducation(int userId) {
         return mLetterboxEduSharedPreferences
                 .getBoolean(dontShowLetterboxEduKey(userId), /* default= */ false);
@@ -218,6 +233,15 @@
         }
     }
 
+    // Returns the minimum tolerance of the percentage of activity bounds within its task to hide
+    // size compat restart button. Value lower than 0 or higher than 100 will be ignored.
+    // 100 is the default value where the activity has to fit exactly within the task to allow
+    // size compat restart button to be hidden. 0 means size compat restart button will always
+    // be hidden.
+    private int getHideSizeCompatRestartButtonTolerance(int tolerance) {
+        return tolerance < 0 || tolerance > MAX_PERCENTAGE_VAL ? MAX_PERCENTAGE_VAL : tolerance;
+    }
+
     private boolean isReachabilityEducationEnabled() {
         return mIsReachabilityEducationOverrideEnabled || (mIsReachabilityEducationEnabled
                 && mIsLetterboxReachabilityEducationAllowed);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 00e0cdb..dbf7186 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -22,7 +22,9 @@
 import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
 import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.AppCompatTaskInfo;
 import android.app.AppCompatTaskInfo.CameraCompatControlState;
 import android.app.TaskInfo;
 import android.content.Context;
@@ -33,6 +35,7 @@
 import android.view.View;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayLayout;
@@ -68,6 +71,8 @@
     @VisibleForTesting
     CompatUILayout mLayout;
 
+    private final float mHideScmTolerance;
+
     CompatUIWindowManager(Context context, TaskInfo taskInfo,
             SyncTransactionQueue syncQueue, CompatUICallback callback,
             ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
@@ -80,6 +85,7 @@
         mCompatUIHintsState = compatUIHintsState;
         mCompatUIConfiguration = compatUIConfiguration;
         mOnRestartButtonClicked = onRestartButtonClicked;
+        mHideScmTolerance = mCompatUIConfiguration.getHideSizeCompatRestartButtonTolerance();
     }
 
     @Override
@@ -99,7 +105,8 @@
 
     @Override
     protected boolean eligibleToShowLayout() {
-        return mHasSizeCompat || shouldShowCameraControl();
+        return (mHasSizeCompat && shouldShowSizeCompatRestartButton(getLastTaskInfo()))
+                || shouldShowCameraControl();
     }
 
     @Override
@@ -208,6 +215,30 @@
         updateSurfacePosition(positionX, positionY);
     }
 
+    @VisibleForTesting
+    boolean shouldShowSizeCompatRestartButton(@NonNull TaskInfo taskInfo) {
+        if (!Flags.allowHideScmButton()) {
+            return true;
+        }
+        final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo;
+        final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
+        final int letterboxArea = computeArea(appCompatTaskInfo.topActivityLetterboxWidth,
+                appCompatTaskInfo.topActivityLetterboxHeight);
+        final int taskArea = computeArea(taskBounds.width(), taskBounds.height());
+        if (letterboxArea == 0 || taskArea == 0) {
+            return false;
+        }
+        final float percentageAreaOfLetterboxInTask = (float) letterboxArea / taskArea * 100;
+        return percentageAreaOfLetterboxInTask < mHideScmTolerance;
+    }
+
+    private int computeArea(int width, int height) {
+        if (width == 0 || height == 0) {
+            return 0;
+        }
+        return width * height;
+    }
+
     private void updateVisibilityOfViews() {
         if (mLayout == null) {
             return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
index 180498c..0564c95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
@@ -332,7 +332,7 @@
         updateSurfacePosition();
     }
 
-    @Nullable
+    @NonNull
     protected TaskInfo getLastTaskInfo() {
         return mTaskInfo;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index b22e1e5..8eecf1c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -55,15 +55,16 @@
 public abstract class Pip2Module {
     @WMSingleton
     @Provides
-    static PipTransition providePipTransition(@NonNull ShellInit shellInit,
+    static PipTransition providePipTransition(Context context,
+            @NonNull ShellInit shellInit,
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
             @NonNull Transitions transitions,
             PipBoundsState pipBoundsState,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             Optional<PipController> pipController,
             @NonNull PipScheduler pipScheduler) {
-        return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,
-                pipBoundsAlgorithm, pipScheduler);
+        return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
+                pipBoundsState, null, pipBoundsAlgorithm, pipScheduler);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index a587bed..6250fc5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -16,6 +16,10 @@
 
 package com.android.wm.shell.desktopmode;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
 import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
 
 import android.animation.Animator;
@@ -39,7 +43,6 @@
 
 import com.android.wm.shell.R;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
@@ -48,100 +51,70 @@
  * Animated visual indicator for Desktop Mode windowing transitions.
  */
 public class DesktopModeVisualIndicator {
-    public static final int INVALID_INDICATOR = -1;
-    /** Indicates impending transition into desktop mode */
-    public static final int TO_DESKTOP_INDICATOR = 1;
-    /** Indicates impending transition into fullscreen */
-    public static final int TO_FULLSCREEN_INDICATOR = 2;
-    /** Indicates impending transition into split select on the left side */
-    public static final int TO_SPLIT_LEFT_INDICATOR = 3;
-    /** Indicates impending transition into split select on the right side */
-    public static final int TO_SPLIT_RIGHT_INDICATOR = 4;
+    public enum IndicatorType {
+        /** To be used when we don't want to indicate any transition */
+        NO_INDICATOR,
+        /** Indicates impending transition into desktop mode */
+        TO_DESKTOP_INDICATOR,
+        /** Indicates impending transition into fullscreen */
+        TO_FULLSCREEN_INDICATOR,
+        /** Indicates impending transition into split select on the left side */
+        TO_SPLIT_LEFT_INDICATOR,
+        /** Indicates impending transition into split select on the right side */
+        TO_SPLIT_RIGHT_INDICATOR
+    }
 
     private final Context mContext;
     private final DisplayController mDisplayController;
-    private final ShellTaskOrganizer mTaskOrganizer;
     private final RootTaskDisplayAreaOrganizer mRootTdaOrganizer;
     private final ActivityManager.RunningTaskInfo mTaskInfo;
     private final SurfaceControl mTaskSurface;
-    private final Rect mIndicatorRange = new Rect();
     private SurfaceControl mLeash;
 
     private final SyncTransactionQueue mSyncQueue;
     private SurfaceControlViewHost mViewHost;
 
     private View mView;
-    private boolean mIsFullscreen;
-    private int mType;
+    private IndicatorType mCurrentType;
 
     public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue,
             ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController,
-            Context context, SurfaceControl taskSurface, ShellTaskOrganizer taskOrganizer,
-            RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer, int type) {
+            Context context, SurfaceControl taskSurface,
+            RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer) {
         mSyncQueue = syncQueue;
         mTaskInfo = taskInfo;
         mDisplayController = displayController;
         mContext = context;
         mTaskSurface = taskSurface;
-        mTaskOrganizer = taskOrganizer;
         mRootTdaOrganizer = taskDisplayAreaOrganizer;
-        mType = type;
-        defineIndicatorRange();
-        createView();
+        mCurrentType = IndicatorType.NO_INDICATOR;
     }
 
     /**
-     * If an indicator is warranted based on the input and task bounds, return the type of
-     * indicator that should be created.
+     * Based on the coordinates of the current drag event, determine which indicator type we should
+     * display, including no visible indicator.
+     * TODO(b/280828642): Update drag zones per starting windowing mode.
      */
-    public static int determineIndicatorType(PointF inputCoordinates, Rect taskBounds,
-            DisplayLayout layout, Context context) {
-        int transitionAreaHeight = context.getResources().getDimensionPixelSize(
-                com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
-        int transitionAreaWidth = context.getResources().getDimensionPixelSize(
-                com.android.wm.shell.R.dimen.desktop_mode_transition_area_width);
-        if (taskBounds.top <= transitionAreaHeight) return TO_FULLSCREEN_INDICATOR;
-        if (inputCoordinates.x <= transitionAreaWidth) return TO_SPLIT_LEFT_INDICATOR;
-        if (inputCoordinates.x >= layout.width() - transitionAreaWidth) {
-            return TO_SPLIT_RIGHT_INDICATOR;
-        }
-        return INVALID_INDICATOR;
-    }
-
-    /**
-     * Determine range of inputs that will keep this indicator displaying.
-     */
-    private void defineIndicatorRange() {
-        DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
-        int captionHeight = mContext.getResources().getDimensionPixelSize(
-                com.android.wm.shell.R.dimen.freeform_decor_caption_height);
+    IndicatorType updateIndicatorType(PointF inputCoordinates) {
+        final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+        // If we are in freeform, we don't want a visible indicator in the "freeform" drag zone.
+        IndicatorType result = mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+                ? IndicatorType.NO_INDICATOR : IndicatorType.TO_DESKTOP_INDICATOR;
         int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
                 com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
         int transitionAreaWidth = mContext.getResources().getDimensionPixelSize(
                 com.android.wm.shell.R.dimen.desktop_mode_transition_area_width);
-        switch (mType) {
-            case TO_DESKTOP_INDICATOR:
-                // TO_DESKTOP indicator is only dismissed on release; entire display is valid.
-                mIndicatorRange.set(0, 0, layout.width(), layout.height());
-                break;
-            case TO_FULLSCREEN_INDICATOR:
-                // If drag results in caption going above the top edge of the display, we still
-                // want to transition to fullscreen.
-                mIndicatorRange.set(0, -captionHeight, layout.width(), transitionAreaHeight);
-                break;
-            case TO_SPLIT_LEFT_INDICATOR:
-                mIndicatorRange.set(0, transitionAreaHeight, transitionAreaWidth, layout.height());
-                break;
-            case TO_SPLIT_RIGHT_INDICATOR:
-                mIndicatorRange.set(layout.width() - transitionAreaWidth, transitionAreaHeight,
-                        layout.width(), layout.height());
-                break;
-            default:
-                break;
+        if (inputCoordinates.y <= transitionAreaHeight) {
+            result = IndicatorType.TO_FULLSCREEN_INDICATOR;
+        } else if (inputCoordinates.x <= transitionAreaWidth) {
+            result = IndicatorType.TO_SPLIT_LEFT_INDICATOR;
+        } else if (inputCoordinates.x >= layout.width() - transitionAreaWidth) {
+            result = IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
         }
+        transitionIndicator(result);
+        return result;
     }
 
-
     /**
      * Create a fullscreen indicator with no animation
      */
@@ -155,34 +128,15 @@
         mView = new View(mContext);
         final SurfaceControl.Builder builder = new SurfaceControl.Builder();
         mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder);
-        String description;
-        switch (mType) {
-            case TO_DESKTOP_INDICATOR:
-                description = "Desktop indicator";
-                break;
-            case TO_FULLSCREEN_INDICATOR:
-                description = "Fullscreen indicator";
-                break;
-            case TO_SPLIT_LEFT_INDICATOR:
-                description = "Split Left indicator";
-                break;
-            case TO_SPLIT_RIGHT_INDICATOR:
-                description = "Split Right indicator";
-                break;
-            default:
-                description = "Invalid indicator";
-                break;
-        }
         mLeash = builder
-                .setName(description)
+                .setName("Desktop Mode Visual Indicator")
                 .setContainerLayer()
                 .build();
         t.show(mLeash);
         final WindowManager.LayoutParams lp =
-                new WindowManager.LayoutParams(screenWidth, screenHeight,
-                        WindowManager.LayoutParams.TYPE_APPLICATION,
-                        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
-        lp.setTitle(description + " for Task=" + mTaskInfo.taskId);
+                new WindowManager.LayoutParams(screenWidth, screenHeight, TYPE_APPLICATION,
+                        FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
+        lp.setTitle("Desktop Mode Visual Indicator");
         lp.setTrustedOverlay();
         final WindowlessWindowManager windowManager = new WindowlessWindowManager(
                 mTaskInfo.configuration, mLeash,
@@ -201,46 +155,48 @@
     }
 
     /**
-     * Create an indicator. Animator fades it in while expanding the bounds outwards.
+     * Fade indicator in as provided type. Animator fades it in while expanding the bounds outwards.
      */
-    public void createIndicatorWithAnimatedBounds() {
-        mIsFullscreen = mType == TO_FULLSCREEN_INDICATOR;
+    private void fadeInIndicator(IndicatorType type) {
         mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
         final VisualIndicatorAnimator animator = VisualIndicatorAnimator
-                .animateBounds(mView, mType,
+                .fadeBoundsIn(mView, type,
                         mDisplayController.getDisplayLayout(mTaskInfo.displayId));
         animator.start();
+        mCurrentType = type;
     }
 
     /**
-     * Takes existing fullscreen indicator and animates it to freeform bounds
+     * Fade out indicator without fully releasing it. Animator fades it out while shrinking bounds.
      */
-    public void transitionFullscreenIndicatorToFreeform() {
-        mIsFullscreen = false;
-        mType = TO_DESKTOP_INDICATOR;
-        final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFreeformAnimator(
-                mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
+    private void fadeOutIndicator() {
+        final VisualIndicatorAnimator animator = VisualIndicatorAnimator
+                .fadeBoundsOut(mView, mCurrentType,
+                        mDisplayController.getDisplayLayout(mTaskInfo.displayId));
         animator.start();
+        mCurrentType = IndicatorType.NO_INDICATOR;
+
     }
 
     /**
-     * Takes the existing freeform indicator and animates it to fullscreen
+     * Takes existing indicator and animates it to bounds reflecting a new indicator type.
      */
-    public void transitionFreeformIndicatorToFullscreen() {
-        mIsFullscreen = true;
-        mType = TO_FULLSCREEN_INDICATOR;
-        final VisualIndicatorAnimator animator =
-                VisualIndicatorAnimator.toFullscreenAnimatorWithAnimatedBounds(
-                mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
-        animator.start();
-    }
-
-    /**
-     * Determine if a MotionEvent is in the same range that enabled the indicator.
-     * Used to dismiss the indicator when a transition will no longer result from releasing.
-     */
-    public boolean eventOutsideRange(float x, float y) {
-        return !mIndicatorRange.contains((int) x, (int) y);
+    private void transitionIndicator(IndicatorType newType) {
+        if (mCurrentType == newType) return;
+        if (mView == null) {
+            createView();
+        }
+        if (mCurrentType == IndicatorType.NO_INDICATOR) {
+            fadeInIndicator(newType);
+        } else if (newType == IndicatorType.NO_INDICATOR) {
+            fadeOutIndicator();
+        } else {
+            final VisualIndicatorAnimator animator = VisualIndicatorAnimator.animateIndicatorType(
+                    mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId), mCurrentType,
+                    newType);
+            mCurrentType = newType;
+            animator.start();
+        }
     }
 
     /**
@@ -260,13 +216,6 @@
     }
 
     /**
-     * Returns true if visual indicator is fullscreen
-     */
-    public boolean isFullscreen() {
-        return mIsFullscreen;
-    }
-
-    /**
      * Animator for Desktop Mode transitions which supports bounds and alpha animation.
      */
     private static class VisualIndicatorAnimator extends ValueAnimator {
@@ -274,6 +223,13 @@
         private static final float FULLSCREEN_SCALE_ADJUSTMENT_PERCENT = 0.015f;
         private static final float INDICATOR_FINAL_OPACITY = 0.7f;
 
+        /** Determines how this animator will interact with the view's alpha:
+         *  Fade in, fade out, or no change to alpha
+         */
+        private enum AlphaAnimType{
+            ALPHA_FADE_IN_ANIM, ALPHA_FADE_OUT_ANIM, ALPHA_NO_CHANGE_ANIM
+        }
+
         private final View mView;
         private final Rect mStartBounds;
         private final Rect mEndBounds;
@@ -288,87 +244,91 @@
             mRectEvaluator = new RectEvaluator(new Rect());
         }
 
-        /**
-         * Create animator for visual indicator of fullscreen transition
-         *
-         * @param view the view for this indicator
-         * @param displayLayout information about the display the transitioning task is currently on
-         */
-        public static VisualIndicatorAnimator toFullscreenAnimatorWithAnimatedBounds(
-                @NonNull View view, @NonNull DisplayLayout displayLayout) {
-            final int padding = displayLayout.stableInsets().top;
-            Rect startBounds = new Rect(padding, padding,
-                    displayLayout.width() - padding, displayLayout.height() - padding);
+        private static VisualIndicatorAnimator fadeBoundsIn(
+                @NonNull View view, IndicatorType type, @NonNull DisplayLayout displayLayout) {
+            final Rect startBounds = getIndicatorBounds(displayLayout, type);
             view.getBackground().setBounds(startBounds);
 
             final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
                     view, startBounds, getMaxBounds(startBounds));
             animator.setInterpolator(new DecelerateInterpolator());
-            setupIndicatorAnimation(animator);
+            setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_FADE_IN_ANIM);
             return animator;
         }
 
-        public static VisualIndicatorAnimator animateBounds(
-                @NonNull View view, int type, @NonNull DisplayLayout displayLayout) {
-            final int padding = displayLayout.stableInsets().top;
-            Rect startBounds = new Rect();
-            switch (type) {
-                case TO_FULLSCREEN_INDICATOR:
-                    startBounds.set(padding, padding,
-                            displayLayout.width() - padding,
-                            displayLayout.height() - padding);
-                    break;
-                case TO_SPLIT_LEFT_INDICATOR:
-                    startBounds.set(padding, padding,
-                            displayLayout.width() / 2 - padding,
-                            displayLayout.height() - padding);
-                    break;
-                case TO_SPLIT_RIGHT_INDICATOR:
-                    startBounds.set(displayLayout.width() / 2 + padding, padding,
-                            displayLayout.width() - padding,
-                            displayLayout.height() - padding);
-                    break;
-            }
+        private static VisualIndicatorAnimator fadeBoundsOut(
+                @NonNull View view, IndicatorType type, @NonNull DisplayLayout displayLayout) {
+            final Rect endBounds = getIndicatorBounds(displayLayout, type);
+            final Rect startBounds = getMaxBounds(endBounds);
             view.getBackground().setBounds(startBounds);
 
             final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
-                    view, startBounds, getMaxBounds(startBounds));
-            animator.setInterpolator(new DecelerateInterpolator());
-            setupIndicatorAnimation(animator);
-            return animator;
-        }
-
-        /**
-         * Create animator for visual indicator of freeform transition
-         *
-         * @param view the view for this indicator
-         * @param displayLayout information about the display the transitioning task is currently on
-         */
-        public static VisualIndicatorAnimator toFreeformAnimator(@NonNull View view,
-                @NonNull DisplayLayout displayLayout) {
-            final float adjustmentPercentage = 1f - FINAL_FREEFORM_SCALE;
-            final int width = displayLayout.width();
-            final int height = displayLayout.height();
-            Rect startBounds = new Rect(0, 0, width, height);
-            Rect endBounds = new Rect((int) (adjustmentPercentage * width / 2),
-                    (int) (adjustmentPercentage * height / 2),
-                    (int) (displayLayout.width() - (adjustmentPercentage * width / 2)),
-                    (int) (displayLayout.height() - (adjustmentPercentage * height / 2)));
-            final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
                     view, startBounds, endBounds);
             animator.setInterpolator(new DecelerateInterpolator());
-            setupIndicatorAnimation(animator);
+            setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_FADE_OUT_ANIM);
             return animator;
         }
 
         /**
+         * Create animator for visual indicator changing type (i.e., fullscreen to freeform,
+         * freeform to split, etc.)
+         *
+         * @param view the view for this indicator
+         * @param displayLayout information about the display the transitioning task is currently on
+         * @param origType the original indicator type
+         * @param newType the new indicator type
+         */
+        private static VisualIndicatorAnimator animateIndicatorType(@NonNull View view,
+                @NonNull DisplayLayout displayLayout, IndicatorType origType,
+                IndicatorType newType) {
+            final Rect startBounds = getIndicatorBounds(displayLayout, origType);
+            final Rect endBounds = getIndicatorBounds(displayLayout, newType);
+            final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
+                    view, startBounds, endBounds);
+            animator.setInterpolator(new DecelerateInterpolator());
+            setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_NO_CHANGE_ANIM);
+            return animator;
+        }
+
+        private static Rect getIndicatorBounds(DisplayLayout layout, IndicatorType type) {
+            final int padding = layout.stableInsets().top;
+            switch (type) {
+                case TO_FULLSCREEN_INDICATOR:
+                    return new Rect(padding, padding,
+                            layout.width() - padding,
+                            layout.height() - padding);
+                case TO_DESKTOP_INDICATOR:
+                    final float adjustmentPercentage = 1f - FINAL_FREEFORM_SCALE;
+                    return new Rect((int) (adjustmentPercentage * layout.width() / 2),
+                            (int) (adjustmentPercentage * layout.height() / 2),
+                            (int) (layout.width() - (adjustmentPercentage * layout.width() / 2)),
+                            (int) (layout.height() - (adjustmentPercentage * layout.height() / 2)));
+                case TO_SPLIT_LEFT_INDICATOR:
+                    return new Rect(padding, padding,
+                            layout.width() / 2 - padding,
+                            layout.height() - padding);
+                case TO_SPLIT_RIGHT_INDICATOR:
+                    return new Rect(layout.width() / 2 + padding, padding,
+                            layout.width() - padding,
+                            layout.height() - padding);
+                default:
+                    throw new IllegalArgumentException("Invalid indicator type provided.");
+            }
+        }
+
+        /**
          * Add necessary listener for animation of indicator
          */
-        private static void setupIndicatorAnimation(@NonNull VisualIndicatorAnimator animator) {
+        private static void setupIndicatorAnimation(@NonNull VisualIndicatorAnimator animator,
+                AlphaAnimType animType) {
             animator.addUpdateListener(a -> {
                 if (animator.mView != null) {
                     animator.updateBounds(a.getAnimatedFraction(), animator.mView);
-                    animator.updateIndicatorAlpha(a.getAnimatedFraction(), animator.mView);
+                    if (animType == AlphaAnimType.ALPHA_FADE_IN_ANIM) {
+                        animator.updateIndicatorAlpha(a.getAnimatedFraction(), animator.mView);
+                    } else if (animType == AlphaAnimType.ALPHA_FADE_OUT_ANIM) {
+                        animator.updateIndicatorAlpha(1 - a.getAnimatedFraction(), animator.mView);
+                    }
                 } else {
                     animator.cancel();
                 }
@@ -394,7 +354,7 @@
             if (mStartBounds.equals(mEndBounds)) {
                 return;
             }
-            Rect currentBounds = mRectEvaluator.evaluate(fraction, mStartBounds, mEndBounds);
+            final Rect currentBounds = mRectEvaluator.evaluate(fraction, mStartBounds, mEndBounds);
             view.getBackground().setBounds(currentBounds);
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index b1c43c1..28c06a4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -57,7 +57,6 @@
 import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
 import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
-import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.TO_DESKTOP_INDICATOR
 import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
 import com.android.wm.shell.recents.RecentsTransitionHandler
@@ -871,31 +870,34 @@
      *
      * @param taskInfo the task being dragged.
      * @param taskSurface SurfaceControl of dragged task.
-     * @param inputCoordinate coordinates of input. Used for checks against left/right edge of screen.
+     * @param inputX x coordinate of input. Used for checks against left/right edge of screen.
      * @param taskBounds bounds of dragged task. Used for checks against status bar height.
      */
     fun onDragPositioningMove(
         taskInfo: RunningTaskInfo,
         taskSurface: SurfaceControl,
-        inputCoordinate: PointF,
+        inputX: Float,
         taskBounds: Rect
     ) {
-        val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
         if (taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) return
-        var type = DesktopModeVisualIndicator.determineIndicatorType(inputCoordinate,
-            taskBounds, displayLayout, context)
-        if (type != DesktopModeVisualIndicator.INVALID_INDICATOR && visualIndicator == null) {
+        updateVisualIndicator(taskInfo, taskSurface, inputX, taskBounds.top.toFloat())
+    }
+
+    fun updateVisualIndicator(
+        taskInfo: RunningTaskInfo,
+        taskSurface: SurfaceControl,
+        inputX: Float,
+        taskTop: Float
+    ) {
+        // If the visual indicator does not exist, create it.
+        if (visualIndicator == null) {
             visualIndicator = DesktopModeVisualIndicator(
-                syncQueue, taskInfo,
-                displayController, context, taskSurface, shellTaskOrganizer,
-                rootTaskDisplayAreaOrganizer, type)
-            visualIndicator?.createIndicatorWithAnimatedBounds()
-            return
+                syncQueue, taskInfo, displayController, context, taskSurface,
+                rootTaskDisplayAreaOrganizer)
         }
-        if (visualIndicator?.eventOutsideRange(inputCoordinate.x,
-                taskBounds.top.toFloat()) == true) {
-            releaseVisualIndicator()
-        }
+        // Then, update the indicator type.
+        val indicator = visualIndicator ?: return
+        indicator.updateIndicatorType(PointF(inputX, taskTop))
     }
 
     /**
@@ -917,61 +919,27 @@
         }
         if (taskBounds.top <= transitionAreaHeight) {
             moveToFullscreenWithAnimation(taskInfo, position)
+            return
         }
         if (inputCoordinate.x <= transitionAreaWidth) {
             releaseVisualIndicator()
-            var wct = WindowContainerTransaction()
+            val wct = WindowContainerTransaction()
             addMoveToSplitChanges(wct, taskInfo)
             splitScreenController.requestEnterSplitSelect(taskInfo, wct,
                 SPLIT_POSITION_TOP_OR_LEFT, taskBounds)
+            return
         }
         if (inputCoordinate.x >= (displayController.getDisplayLayout(taskInfo.displayId)?.width()
             ?.minus(transitionAreaWidth) ?: return)) {
             releaseVisualIndicator()
-            var wct = WindowContainerTransaction()
+            val wct = WindowContainerTransaction()
             addMoveToSplitChanges(wct, taskInfo)
             splitScreenController.requestEnterSplitSelect(taskInfo, wct,
                 SPLIT_POSITION_BOTTOM_OR_RIGHT, taskBounds)
-        }
-    }
-
-    /**
-     * Perform checks required on drag move. Create/release fullscreen indicator and transitions
-     * indicator to freeform or fullscreen dimensions as needed.
-     *
-     * @param taskInfo the task being dragged.
-     * @param taskSurface SurfaceControl of dragged task.
-     * @param y coordinate of dragged task. Used for checks against status bar height.
-     */
-    fun onDragPositioningMoveThroughStatusBar(
-            taskInfo: RunningTaskInfo,
-            taskSurface: SurfaceControl,
-            y: Float
-    ) {
-        // If the motion event is above the status bar and the visual indicator is not yet visible,
-        // return since we do not need to show the visual indicator at this point.
-        if (y < getStatusBarHeight(taskInfo) && visualIndicator == null) {
             return
         }
-        if (visualIndicator == null) {
-            visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
-                    displayController, context, taskSurface, shellTaskOrganizer,
-                    rootTaskDisplayAreaOrganizer, TO_DESKTOP_INDICATOR)
-            // TODO(b/301106941): don't show the indicator until the drag-to-desktop animation has
-            // started, or it'll be visible too early on top of the task surface, especially in
-            // the cancel-early case. Also because it shouldn't even be shown in the cancel-early
-            // case since its dismissal is tied to the cancel animation end, which doesn't even run
-            // in cancel-early.
-            visualIndicator?.createIndicatorWithAnimatedBounds()
-        }
-        val indicator = visualIndicator ?: return
-        if (y >= getFreeformTransitionStatusBarDragThreshold(taskInfo)) {
-            if (indicator.isFullscreen) {
-                indicator.transitionFullscreenIndicatorToFreeform()
-            }
-        } else if (!indicator.isFullscreen) {
-            indicator.transitionFreeformIndicatorToFullscreen()
-        }
+        // A freeform drag-move ended, remove the indicator immediately.
+        releaseVisualIndicator()
     }
 
     /**
@@ -992,14 +960,6 @@
     }
 
     /**
-     * Returns the threshold at which we transition a task into freeform when dragging a
-     * fullscreen task down from the status bar
-     */
-    private fun getFreeformTransitionStatusBarDragThreshold(taskInfo: RunningTaskInfo): Int {
-        return 2 * getStatusBarHeight(taskInfo)
-    }
-
-    /**
      * Update the exclusion region for a specified task
      */
     fun onExclusionRegionChanged(taskId: Int, exclusionRegion: Region) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 95d7ad5..c3a82ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -99,7 +99,11 @@
             windowDecoration: DesktopModeWindowDecoration
     ) {
         if (inProgress) {
-            error("A drag to desktop is already in progress")
+            KtProtoLog.v(
+                    ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+                    "DragToDesktop: Drag to desktop transition already in progress."
+            )
+            return
         }
 
         val options = ActivityOptions.makeBasic().apply {
@@ -144,6 +148,12 @@
      * inside the desktop drop zone.
      */
     fun finishDragToDesktopTransition(wct: WindowContainerTransaction) {
+        if (!inProgress) {
+            // Don't attempt to finish a drag to desktop transition since there is no transition in
+            // progress which means that the drag to desktop transition was never successfully
+            // started.
+            return
+        }
         if (requireTransitionState().startAborted) {
             // Don't attempt to complete the drag-to-desktop since the start transition didn't
             // succeed as expected. Just reset the state as if nothing happened.
@@ -161,6 +171,12 @@
      * means the user wants to remain in their current windowing mode.
      */
     fun cancelDragToDesktopTransition() {
+        if (!inProgress) {
+            // Don't attempt to cancel a drag to desktop transition since there is no transition in
+            // progress which means that the drag to desktop transition was never successfully
+            // started.
+            return
+        }
         val state = requireTransitionState()
         if (state.startAborted) {
             // Don't attempt to cancel the drag-to-desktop since the start transition didn't
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index d52fd1b..52a06e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -600,9 +600,7 @@
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             if (requestEnterSplit && mSplitScreenOptional.isPresent()) {
                 wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
-                mSplitScreenOptional.get().prepareEnterSplitScreen(wct, mTaskInfo,
-                        isPipToTopLeft()
-                                ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
+                mSplitScreenOptional.get().onPipExpandToSplit(wct, mTaskInfo);
                 mPipTransitionController.startExitTransition(
                         TRANSIT_EXIT_PIP_TO_SPLIT, wct, destinationBounds);
                 return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 388d630..e739266 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -988,10 +988,12 @@
                     0 /* startingAngle */, rotationDelta);
             if (sourceHintRect == null) {
                 // We use content overlay when there is no source rect hint to enter PiP use bounds
-                // animation.
+                // animation. We also temporarily disallow app icon overlay and use color overlay
+                // instead when in fixed rotation enter PiP in button nav with no sourceRectHint.
+                // TODO(b/319286295): Fix App Icon Overlay animation in fixed rotation in btn nav.
                 // TODO(b/272819817): cleanup the null-check and extra logging.
                 final boolean hasTopActivityInfo = taskInfo.topActivityInfo != null;
-                if (hasTopActivityInfo) {
+                if (hasTopActivityInfo && mFixedRotationState != FIXED_ROTATION_TRANSITION) {
                     animator.setAppIconContentOverlay(
                             mContext, currentBounds, destinationBounds, taskInfo.topActivityInfo,
                             mPipBoundsState.getLauncherState().getAppIconSizePx());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 7a32f8b..d1fd207 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -48,6 +48,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * Responsible supplying PiP Transitions.
@@ -117,6 +118,17 @@
     }
 
     /**
+     * Called when the Shell wants to start resizing Pip transition/animation.
+     *
+     * @param onFinishResizeCallback callback guaranteed to execute when animation ends and
+     *                               client completes any potential draws upon WM state updates.
+     */
+    public void startResizeTransition(WindowContainerTransaction wct,
+            Consumer<Rect> onFinishResizeCallback) {
+        // Default implementation does nothing.
+    }
+
+    /**
      * Called when the transition animation can't continue (eg. task is removed during
      * animation)
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
deleted file mode 100644
index 1b1ebc3..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.pip.phone;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.graphics.Rect;
-
-import com.android.wm.shell.common.pip.PipBoundsState;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Static utilities to get appropriate {@link PipDoubleTapHelper.PipSizeSpec} on a double tap.
- */
-public class PipDoubleTapHelper {
-
-    /**
-     * Should not be instantiated as a stateless class.
-     */
-    private PipDoubleTapHelper() {}
-
-    /**
-     * A constant that represents a pip screen size.
-     *
-     * <p>CUSTOM - user resized screen size (by pinching in/out)</p>
-     * <p>DEFAULT - normal screen size used as default when entering pip mode</p>
-     * <p>MAX - maximum allowed screen size</p>
-     */
-    @IntDef(value = {
-        SIZE_SPEC_DEFAULT,
-        SIZE_SPEC_MAX,
-        SIZE_SPEC_CUSTOM
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface PipSizeSpec {}
-
-    static final int SIZE_SPEC_DEFAULT = 0;
-    static final int SIZE_SPEC_MAX = 1;
-    static final int SIZE_SPEC_CUSTOM = 2;
-
-    /**
-     * Returns MAX or DEFAULT {@link PipSizeSpec} to toggle to/from.
-     *
-     * <p>Each double tap toggles back and forth between {@code PipSizeSpec.CUSTOM} and
-     * either {@code PipSizeSpec.MAX} or {@code PipSizeSpec.DEFAULT}. The choice between
-     * the latter two sizes is determined based on the current state of the pip screen.</p>
-     *
-     * @param mPipBoundsState current state of the pip screen
-     */
-    @PipSizeSpec
-    private static int getMaxOrDefaultPipSizeSpec(@NonNull PipBoundsState mPipBoundsState) {
-        // determine the average pip screen width
-        int averageWidth = (mPipBoundsState.getMaxSize().x
-                + mPipBoundsState.getMinSize().x) / 2;
-
-        // If pip screen width is above average, DEFAULT is the size spec we need to
-        // toggle to. Otherwise, we choose MAX.
-        return (mPipBoundsState.getBounds().width() > averageWidth)
-                ? SIZE_SPEC_DEFAULT
-                : SIZE_SPEC_MAX;
-    }
-
-    /**
-     * Determines the {@link PipSizeSpec} to toggle to on double tap.
-     *
-     * @param mPipBoundsState current state of the pip screen
-     * @param userResizeBounds latest user resized bounds (by pinching in/out)
-     * @return pip screen size to switch to
-     */
-    @PipSizeSpec
-    static int nextSizeSpec(@NonNull PipBoundsState mPipBoundsState,
-            @NonNull Rect userResizeBounds) {
-        // is pip screen at its maximum
-        boolean isScreenMax = mPipBoundsState.getBounds().width()
-                == mPipBoundsState.getMaxSize().x;
-
-        // is pip screen at its normal default size
-        boolean isScreenDefault = (mPipBoundsState.getBounds().width()
-                == mPipBoundsState.getNormalBounds().width())
-                && (mPipBoundsState.getBounds().height()
-                == mPipBoundsState.getNormalBounds().height());
-
-        // edge case 1
-        // if user hasn't resized screen yet, i.e. CUSTOM size does not exist yet
-        // or if user has resized exactly to DEFAULT, then we just want to maximize
-        if (isScreenDefault
-                && userResizeBounds.width() == mPipBoundsState.getNormalBounds().width()) {
-            return SIZE_SPEC_MAX;
-        }
-
-        // edge case 2
-        // if user has maximized, then we want to toggle to DEFAULT
-        if (isScreenMax
-                && userResizeBounds.width() == mPipBoundsState.getMaxSize().x) {
-            return SIZE_SPEC_DEFAULT;
-        }
-
-        // otherwise in general we want to toggle back to user's CUSTOM size
-        if (isScreenDefault || isScreenMax) {
-            return SIZE_SPEC_CUSTOM;
-        }
-
-        // if we are currently in user resized CUSTOM size state
-        // then we toggle either to MAX or DEFAULT depending on the current pip screen state
-        return getMaxOrDefaultPipSizeSpec(mPipBoundsState);
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 452a416..81705e2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -52,6 +52,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDoubleTapHelper;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.common.pip.SizeSpecSource;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
index 1c94625..54e162b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
@@ -54,6 +54,7 @@
     // Referenced in com.android.systemui.util.NotificationChannels.
     public static final String NOTIFICATION_CHANNEL = "TVPIP";
     private static final String NOTIFICATION_TAG = "TvPip";
+    private static final String EXTRA_COMPONENT_NAME = "TvPipComponentName";
 
     private final Context mContext;
     private final PackageManager mPackageManager;
@@ -176,6 +177,7 @@
 
         Bundle extras = new Bundle();
         extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, mMediaSessionToken);
+        extras.putParcelable(EXTRA_COMPONENT_NAME, PipUtils.getTopPipActivity(mContext).first);
         mNotificationBuilder.setExtras(extras);
 
         PendingIntent closeIntent = mTvPipActionsProvider.getCloseAction().getPendingIntent();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 0b8f60e..57b73b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -24,10 +24,12 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.graphics.Rect;
 import android.view.SurfaceControl;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
+import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 import androidx.core.content.ContextCompat;
 
@@ -36,6 +38,10 @@
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.pip.PipTransitionController;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.function.Consumer;
+
 /**
  * Scheduler for Shell initiated PiP transitions and animations.
  */
@@ -58,13 +64,37 @@
     private SurfaceControl mPinnedTaskLeash;
 
     /**
-     * A temporary broadcast receiver to initiate exit PiP via expand.
-     * This will later be modified to be triggered by the PiP menu.
+     * Temporary PiP CUJ codes to schedule PiP related transitions directly from Shell.
+     * This is used for a broadcast receiver to resolve intents. This should be removed once
+     * there is an equivalent of PipTouchHandler and PipResizeGestureHandler for PiP2.
+     */
+    private static final int PIP_EXIT_VIA_EXPAND_CODE = 0;
+    private static final int PIP_DOUBLE_TAP = 1;
+
+    @IntDef(value = {
+            PIP_EXIT_VIA_EXPAND_CODE,
+            PIP_DOUBLE_TAP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface PipUserJourneyCode {}
+
+    /**
+     * A temporary broadcast receiver to initiate PiP CUJs.
      */
     private class PipSchedulerReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            scheduleExitPipViaExpand();
+            int userJourneyCode = intent.getIntExtra("cuj_code_extra", 0);
+            switch (userJourneyCode) {
+                case PIP_EXIT_VIA_EXPAND_CODE:
+                    scheduleExitPipViaExpand();
+                    break;
+                case PIP_DOUBLE_TAP:
+                    scheduleDoubleTapToResize();
+                    break;
+                default:
+                    throw new IllegalStateException("unexpected CUJ code=" + userJourneyCode);
+            }
         }
     }
 
@@ -121,6 +151,23 @@
         }
     }
 
+    /**
+     * Schedules resize PiP via double tap.
+     */
+    public void scheduleDoubleTapToResize() {}
+
+    /**
+     * Animates resizing of the pinned stack given the duration.
+     */
+    public void scheduleAnimateResizePip(Rect toBounds, Consumer<Rect> onFinishResizeCallback) {
+        if (mPipTaskToken == null) {
+            return;
+        }
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.setBounds(mPipTaskToken, toBounds);
+        mPipTransitionController.startResizeTransition(wct, onFinishResizeCallback);
+    }
+
     void onExitPip() {
         mPipTaskToken = null;
         mPinnedTaskLeash = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 1399ef6..fbf4d13 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -22,10 +22,12 @@
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_RESIZE_PIP;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.PictureInPictureParams;
+import android.content.Context;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.view.SurfaceControl;
@@ -36,6 +38,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.common.pip.PipBoundsState;
@@ -45,25 +48,29 @@
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
+import java.util.function.Consumer;
+
 /**
  * Implementation of transitions for PiP on phone.
  */
 public class PipTransition extends PipTransitionController {
     private static final String TAG = PipTransition.class.getSimpleName();
 
+    private final Context mContext;
     private final PipScheduler mPipScheduler;
     @Nullable
     private WindowContainerToken mPipTaskToken;
     @Nullable
     private IBinder mEnterTransition;
     @Nullable
-    private IBinder mAutoEnterButtonNavTransition;
-    @Nullable
     private IBinder mExitViaExpandTransition;
     @Nullable
-    private IBinder mLegacyEnterTransition;
+    private IBinder mResizeTransition;
+
+    private Consumer<Rect> mFinishResizeCallback;
 
     public PipTransition(
+            Context context,
             @NonNull ShellInit shellInit,
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
             @NonNull Transitions transitions,
@@ -74,6 +81,7 @@
         super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
                 pipBoundsAlgorithm);
 
+        mContext = context;
         mPipScheduler = pipScheduler;
         mPipScheduler.setPipTransitionController(this);
     }
@@ -87,7 +95,7 @@
 
     @Override
     public void startExitTransition(int type, WindowContainerTransaction out,
-            @android.annotation.Nullable Rect destinationBounds) {
+            @Nullable Rect destinationBounds) {
         if (out == null) {
             return;
         }
@@ -97,6 +105,16 @@
         }
     }
 
+    @Override
+    public void startResizeTransition(WindowContainerTransaction wct,
+            Consumer<Rect> onFinishResizeCallback) {
+        if (wct == null) {
+            return;
+        }
+        mResizeTransition = mTransitions.startTransition(TRANSIT_RESIZE_PIP, wct, this);
+        mFinishResizeCallback = onFinishResizeCallback;
+    }
+
     @Nullable
     @Override
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@@ -126,43 +144,6 @@
     public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
             @Nullable SurfaceControl.Transaction finishT) {}
 
-    private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
-            @NonNull TransitionRequestInfo request) {
-        // cache the original task token to check for multi-activity case later
-        final ActivityManager.RunningTaskInfo pipTask = request.getPipTask();
-        PictureInPictureParams pipParams = pipTask.pictureInPictureParams;
-        mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo,
-                pipParams, mPipBoundsAlgorithm);
-
-        // calculate the entry bounds and notify core to move task to pinned with final bounds
-        final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
-        mPipBoundsState.setBounds(entryBounds);
-
-        WindowContainerTransaction wct = new WindowContainerTransaction();
-        wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds);
-        return wct;
-    }
-
-    private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) {
-        final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipTask();
-        if (pipTask == null) {
-            return false;
-        }
-        if (pipTask.pictureInPictureParams == null) {
-            return false;
-        }
-
-        // Assuming auto-enter is enabled and pipTask is non-null, the TRANSIT_OPEN request type
-        // implies that we are entering PiP in button navigation mode. This is guaranteed by
-        // TaskFragment#startPausing()` in Core which wouldn't get called in gesture nav.
-        return requestInfo.getType() == TRANSIT_OPEN
-                && pipTask.pictureInPictureParams.isAutoEnterEnabled();
-    }
-
-    private boolean isEnterPictureInPictureModeRequest(@NonNull TransitionRequestInfo requestInfo) {
-        return requestInfo.getType() == TRANSIT_PIP;
-    }
-
     @Override
     public boolean startAnimation(@NonNull IBinder transition,
             @NonNull TransitionInfo info,
@@ -182,16 +163,48 @@
         } else if (transition == mExitViaExpandTransition) {
             mExitViaExpandTransition = null;
             return startExpandAnimation(info, startTransaction, finishTransaction, finishCallback);
+        } else if (transition == mResizeTransition) {
+            mResizeTransition = null;
+            return startResizeAnimation(info, startTransaction, finishTransaction, finishCallback);
         }
         return false;
     }
 
-    private boolean isLegacyEnter(@NonNull TransitionInfo info) {
+    private boolean startResizeAnimation(@NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
         TransitionInfo.Change pipChange = getPipChange(info);
-        // If the only change in the changes list is a TO_FRONT mode PiP task,
-        // then this is legacy-enter PiP.
-        return pipChange != null && pipChange.getMode() == TRANSIT_TO_FRONT
-                && info.getChanges().size() == 1;
+        if (pipChange == null) {
+            return false;
+        }
+        SurfaceControl pipLeash = pipChange.getLeash();
+        Rect destinationBounds = pipChange.getEndAbsBounds();
+
+        // Even though the final bounds and crop are applied with finishTransaction since
+        // this is a visible change, we still need to handle the app draw coming in. Snapshot
+        // covering app draw during collection will be removed by startTransaction. So we make
+        // the crop equal to the final bounds and then scale the leash back to starting bounds.
+        startTransaction.setWindowCrop(pipLeash, pipChange.getEndAbsBounds().width(),
+                pipChange.getEndAbsBounds().height());
+        startTransaction.setScale(pipLeash,
+                (float) mPipBoundsState.getBounds().width() / destinationBounds.width(),
+                (float) mPipBoundsState.getBounds().height() / destinationBounds.height());
+        startTransaction.apply();
+
+        finishTransaction.setScale(pipLeash,
+                (float) mPipBoundsState.getBounds().width() / destinationBounds.width(),
+                (float) mPipBoundsState.getBounds().height() / destinationBounds.height());
+
+        // We are done with the transition, but will continue animating leash to final bounds.
+        finishCallback.onTransitionFinished(null);
+
+        // Animate the pip leash with the new buffer
+        final int duration = mContext.getResources().getInteger(
+                R.integer.config_pipResizeAnimationDuration);
+        // TODO: b/275910498 Couple this routine with a new implementation of the PiP animator.
+        startResizeAnimation(pipLeash, mPipBoundsState.getBounds(), destinationBounds, duration);
+        return true;
     }
 
     private boolean startBoundsTypeEnterAnimation(@NonNull TransitionInfo info,
@@ -251,6 +264,57 @@
         return null;
     }
 
+    private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
+            @NonNull TransitionRequestInfo request) {
+        // cache the original task token to check for multi-activity case later
+        final ActivityManager.RunningTaskInfo pipTask = request.getPipTask();
+        PictureInPictureParams pipParams = pipTask.pictureInPictureParams;
+        mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo,
+                pipParams, mPipBoundsAlgorithm);
+
+        // calculate the entry bounds and notify core to move task to pinned with final bounds
+        final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
+        mPipBoundsState.setBounds(entryBounds);
+
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds);
+        return wct;
+    }
+
+    private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) {
+        final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipTask();
+        if (pipTask == null) {
+            return false;
+        }
+        if (pipTask.pictureInPictureParams == null) {
+            return false;
+        }
+
+        // Assuming auto-enter is enabled and pipTask is non-null, the TRANSIT_OPEN request type
+        // implies that we are entering PiP in button navigation mode. This is guaranteed by
+        // TaskFragment#startPausing()` in Core which wouldn't get called in gesture nav.
+        return requestInfo.getType() == TRANSIT_OPEN
+                && pipTask.pictureInPictureParams.isAutoEnterEnabled();
+    }
+
+    private boolean isEnterPictureInPictureModeRequest(@NonNull TransitionRequestInfo requestInfo) {
+        return requestInfo.getType() == TRANSIT_PIP;
+    }
+
+    private boolean isLegacyEnter(@NonNull TransitionInfo info) {
+        TransitionInfo.Change pipChange = getPipChange(info);
+        // If the only change in the changes list is a TO_FRONT mode PiP task,
+        // then this is legacy-enter PiP.
+        return pipChange != null && pipChange.getMode() == TRANSIT_TO_FRONT
+                && info.getChanges().size() == 1;
+    }
+
+    /**
+     * TODO: b/275910498 Use a new implementation of the PiP animator here.
+     */
+    private void startResizeAnimation(SurfaceControl leash, Rect startBounds,
+            Rect endBounds, int duration) {}
+
     private void onExitPip() {
         mPipTaskToken = null;
         mPipScheduler.onExitPip();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index d023cea..1232baa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -1020,7 +1020,7 @@
                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                                 "RecentsController.finishInner: no valid PiP leash;"
                                         + "mPipTransaction=%s, mPipTask=%s, mPipTaskId=%d",
-                                mPipTransaction.toString(), mPipTask.toString(), mPipTaskId);
+                                mPipTransaction, mPipTask, mPipTaskId);
                     } else {
                         t.show(pipLeash);
                         PictureInPictureSurfaceTransaction.apply(mPipTransaction, pipLeash, t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 253acc4..0ca244c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -158,5 +158,10 @@
      * does not expect split to currently be running.
      */
     RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
+
+    /**
+     * Reverse the split.
+     */
+    oneway void switchSplitPosition() = 22;
 }
-// Last id = 21
\ No newline at end of file
+// Last id = 22
\ No newline at end of file
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 880d952..70cb2fc 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
@@ -452,6 +452,17 @@
     }
 
     /**
+     * Performs previous child eviction and such to prepare for the pip task expending into one of
+     * the split stages
+     *
+     * @param taskInfo TaskInfo of the pip task
+     */
+    public void onPipExpandToSplit(WindowContainerTransaction wct,
+            ActivityManager.RunningTaskInfo taskInfo) {
+        mStageCoordinator.onPipExpandToSplit(wct, taskInfo);
+    }
+
+    /**
      * Doing necessary window transaction for other transition handler need to exit split in
      * transition.
      */
@@ -1098,6 +1109,12 @@
         mStageCoordinator.onDroppedToSplit(position, dragSessionId);
     }
 
+    void switchSplitPosition(String reason) {
+        if (isSplitScreenVisible()) {
+            mStageCoordinator.switchSplitPosition(reason);
+        }
+    }
+
     /**
      * Return the {@param exitReason} as a string.
      */
@@ -1462,5 +1479,11 @@
                     true /* blocking */);
             return out[0];
         }
+
+        @Override
+        public void switchSplitPosition() {
+            executeRemoteCallWithTaskPermission(mController, "switchSplitPosition",
+                    (controller) -> controller.switchSplitPosition("remoteCall"));
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
index 7fd03a9..7f16c5e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
@@ -43,6 +43,8 @@
                 return runRemoveFromSideStage(args, pw);
             case "setSideStagePosition":
                 return runSetSideStagePosition(args, pw);
+            case "switchSplitPosition":
+                return runSwitchSplitPosition();
             default:
                 pw.println("Invalid command: " + args[0]);
                 return false;
@@ -84,6 +86,11 @@
         return true;
     }
 
+    private boolean runSwitchSplitPosition() {
+        mController.switchSplitPosition("shellCommand");
+        return true;
+    }
+
     @Override
     public void printShellCommandHelp(PrintWriter pw, String prefix) {
         pw.println(prefix + "moveToSideStage <taskId> <SideStagePosition>");
@@ -92,5 +99,7 @@
         pw.println(prefix + "  Remove a task with given id in split-screen mode.");
         pw.println(prefix + "setSideStagePosition <SideStagePosition>");
         pw.println(prefix + "  Sets the position of the side-stage.");
+        pw.println(prefix + "switchSplitPosition");
+        pw.println(prefix + "  Reverses the split.");
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index bc1a575..5de8a9b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -481,18 +481,20 @@
     private void startFadeAnimation(@NonNull SurfaceControl leash, boolean show) {
         final float end = show ? 1.f : 0.f;
         final float start = 1.f - end;
-        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
         final ValueAnimator va = ValueAnimator.ofFloat(start, end);
         va.setDuration(FADE_DURATION);
         va.setInterpolator(show ? ALPHA_IN : ALPHA_OUT);
         va.addUpdateListener(animation -> {
             float fraction = animation.getAnimatedFraction();
+            final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
             transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
             transaction.apply();
+            mTransactionPool.release(transaction);
         });
         va.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
+                final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
                 transaction.setAlpha(leash, end);
                 transaction.apply();
                 mTransactionPool.release(transaction);
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 0781a9e..af05aa2 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
@@ -2982,6 +2982,25 @@
 
     }
 
+    /**
+     * Performs previous child eviction and such to prepare for the pip task expending into one of
+     * the split stages
+     *
+     * @param taskInfo TaskInfo of the pip task
+     */
+    public void onPipExpandToSplit(WindowContainerTransaction wct,
+            ActivityManager.RunningTaskInfo taskInfo) {
+        prepareEnterSplitScreen(wct, taskInfo, getActivateSplitPosition(taskInfo),
+                false /*resizeAnim*/);
+
+        if (!isSplitScreenVisible() || mSplitRequest == null) {
+            return;
+        }
+
+        boolean replacingMainStage = getMainStagePosition() == mSplitRequest.mActivatePosition;
+        (replacingMainStage ? mMainStage : mSideStage).evictOtherChildren(wct, taskInfo.taskId);
+    }
+
     boolean isLaunchToSplit(TaskInfo taskInfo) {
         return getActivateSplitPosition(taskInfo) != SPLIT_POSITION_UNDEFINED;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index e6418f3..1a0c011 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -135,7 +135,6 @@
         } catch (RemoteException e) {
             snapshotSurface.clearWindowSynced();
         }
-        window.setOuter(snapshotSurface);
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
             session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0,
@@ -161,7 +160,7 @@
             ShellExecutor splashScreenExecutor) {
         mSplashScreenExecutor = splashScreenExecutor;
         mSession = WindowManagerGlobal.getWindowSession();
-        mWindow = new Window();
+        mWindow = new Window(this);
         mWindow.setSession(mSession);
         int backgroundColor = taskDescription.getBackgroundColor();
         mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
@@ -204,9 +203,9 @@
     }
 
     static class Window extends BaseIWindow {
-        private WeakReference<TaskSnapshotWindow> mOuter;
+        private final WeakReference<TaskSnapshotWindow> mOuter;
 
-        public void setOuter(TaskSnapshotWindow outer) {
+        Window(TaskSnapshotWindow outer) {
             mOuter = new WeakReference<>(outer);
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index 0eb7c2d..a7843e2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -293,11 +293,7 @@
     private class ShellInterfaceImpl implements ShellInterface {
         @Override
         public void onInit() {
-            try {
-                mMainExecutor.executeBlocking(() -> ShellController.this.handleInit());
-            } catch (InterruptedException e) {
-                throw new RuntimeException("Failed to initialize the Shell in 2s", e);
-            }
+            mMainExecutor.execute(ShellController.this::handleInit);
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index db84513..8c2203e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -21,17 +21,9 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
 import static android.view.WindowManager.TRANSIT_PIP;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
-import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 
-import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
-import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
-import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
 import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
 
 import android.annotation.NonNull;
@@ -56,7 +48,6 @@
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.recents.RecentsTransitionHandler;
-import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.splitscreen.StageCoordinator;
 import com.android.wm.shell.sysui.ShellInit;
@@ -84,7 +75,7 @@
     private UnfoldTransitionHandler mUnfoldHandler;
     private ActivityEmbeddingController mActivityEmbeddingController;
 
-    private static class MixedTransition {
+    abstract static class MixedTransition {
         static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
 
         /** Both the display and split-state (enter/exit) is changing */
@@ -124,6 +115,12 @@
         int mAnimType = ANIM_TYPE_DEFAULT;
         final IBinder mTransition;
 
+        protected final Transitions mPlayer;
+        protected final DefaultMixedHandler mMixedHandler;
+        protected final PipTransitionController mPipHandler;
+        protected final StageCoordinator mSplitHandler;
+        protected final KeyguardTransitionHandler mKeyguardHandler;
+
         Transitions.TransitionHandler mLeftoversHandler = null;
         TransitionInfo mInfo = null;
         WindowContainerTransaction mFinishWCT = null;
@@ -144,12 +141,35 @@
          */
         int mInFlightSubAnimations = 0;
 
-        MixedTransition(int type, IBinder transition) {
+        MixedTransition(int type, IBinder transition, Transitions player,
+                DefaultMixedHandler mixedHandler, PipTransitionController pipHandler,
+                StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler) {
             mType = type;
             mTransition = transition;
+            mPlayer = player;
+            mMixedHandler = mixedHandler;
+            mPipHandler = pipHandler;
+            mSplitHandler = splitHandler;
+            mKeyguardHandler = keyguardHandler;
         }
 
-        boolean startSubAnimation(Transitions.TransitionHandler handler, TransitionInfo info,
+        abstract boolean startAnimation(
+                @NonNull IBinder transition, @NonNull TransitionInfo info,
+                @NonNull SurfaceControl.Transaction startTransaction,
+                @NonNull SurfaceControl.Transaction finishTransaction,
+                @NonNull Transitions.TransitionFinishCallback finishCallback);
+
+        abstract void mergeAnimation(
+                @NonNull IBinder transition, @NonNull TransitionInfo info,
+                @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+                @NonNull Transitions.TransitionFinishCallback finishCallback);
+
+        abstract void onTransitionConsumed(
+                @NonNull IBinder transition, boolean aborted,
+                @Nullable SurfaceControl.Transaction finishT);
+
+        protected boolean startSubAnimation(
+                Transitions.TransitionHandler handler, TransitionInfo info,
                 SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
             if (mInfo != null) {
                 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
@@ -164,7 +184,7 @@
             return true;
         }
 
-        void onSubAnimationFinished(TransitionInfo info, WindowContainerTransaction wct) {
+        private void onSubAnimationFinished(TransitionInfo info, WindowContainerTransaction wct) {
             mInFlightSubAnimations--;
             if (mInfo != null) {
                 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
@@ -235,8 +255,8 @@
                 throw new IllegalStateException("Unexpected remote transition in"
                         + "pip-enter-from-split request");
             }
-            mActiveTransitions.add(new MixedTransition(MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT,
-                    transition));
+            mActiveTransitions.add(createDefaultMixedTransition(
+                    MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition));
 
             WindowContainerTransaction out = new WindowContainerTransaction();
             mPipHandler.augmentRequest(transition, request, out);
@@ -247,7 +267,7 @@
                 mActivityEmbeddingController != null)) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                     " Got a PiP-enter request from an Activity Embedding split");
-            mActiveTransitions.add(new MixedTransition(
+            mActiveTransitions.add(createDefaultMixedTransition(
                     MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition));
             // Postpone transition splitting to later.
             WindowContainerTransaction out = new WindowContainerTransaction();
@@ -266,7 +286,7 @@
             if (handler == null) {
                 return null;
             }
-            final MixedTransition mixed = new MixedTransition(
+            final MixedTransition mixed = createDefaultMixedTransition(
                     MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE, transition);
             mixed.mLeftoversHandler = handler.first;
             mActiveTransitions.add(mixed);
@@ -292,7 +312,7 @@
                         mPlayer.getRemoteTransitionHandler(),
                         new WindowContainerTransaction());
             }
-            final MixedTransition mixed = new MixedTransition(
+            final MixedTransition mixed = createRecentsMixedTransition(
                     MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
             mixed.mLeftoversHandler = handler.first;
             mActiveTransitions.add(mixed);
@@ -301,16 +321,20 @@
             final WindowContainerTransaction wct =
                     mUnfoldHandler.handleRequest(transition, request);
             if (wct != null) {
-                final MixedTransition mixed = new MixedTransition(
-                        MixedTransition.TYPE_UNFOLD, transition);
-                mixed.mLeftoversHandler = mUnfoldHandler;
-                mActiveTransitions.add(mixed);
+                mActiveTransitions.add(createDefaultMixedTransition(
+                        MixedTransition.TYPE_UNFOLD, transition));
             }
             return wct;
         }
         return null;
     }
 
+    private DefaultMixedTransition createDefaultMixedTransition(int type, IBinder transition) {
+        return new DefaultMixedTransition(
+                type, transition, mPlayer, this, mPipHandler, mSplitHandler, mKeyguardHandler,
+                mUnfoldHandler, mActivityEmbeddingController);
+    }
+
     @Override
     public Consumer<IBinder> handleRecentsRequest(WindowContainerTransaction outWCT) {
         if (mRecentsHandler != null) {
@@ -330,31 +354,30 @@
     private void setRecentsTransitionDuringSplit(IBinder transition) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
                 + "Split-Screen is foreground, so treat it as Mixed.");
-        final MixedTransition mixed = new MixedTransition(
-                MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
-        mixed.mLeftoversHandler = mRecentsHandler;
-        mActiveTransitions.add(mixed);
+        mActiveTransitions.add(createRecentsMixedTransition(
+                MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition));
     }
 
     private void setRecentsTransitionDuringKeyguard(IBinder transition) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
                 + "keyguard is visible, so treat it as Mixed.");
-        final MixedTransition mixed = new MixedTransition(
-                MixedTransition.TYPE_RECENTS_DURING_KEYGUARD, transition);
-        mixed.mLeftoversHandler = mRecentsHandler;
-        mActiveTransitions.add(mixed);
+        mActiveTransitions.add(createRecentsMixedTransition(
+                MixedTransition.TYPE_RECENTS_DURING_KEYGUARD, transition));
     }
 
     private void setRecentsTransitionDuringDesktop(IBinder transition) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
                 + "desktop mode is active, so treat it as Mixed.");
-        final MixedTransition mixed = new MixedTransition(
-                MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition);
-        mixed.mLeftoversHandler = mRecentsHandler;
-        mActiveTransitions.add(mixed);
+        mActiveTransitions.add(createRecentsMixedTransition(
+                MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition));
     }
 
-    private TransitionInfo subCopy(@NonNull TransitionInfo info,
+    private MixedTransition createRecentsMixedTransition(int type, IBinder transition) {
+        return new RecentsMixedTransition(type, transition, mPlayer, this, mPipHandler,
+                mSplitHandler, mKeyguardHandler, mRecentsHandler, mDesktopTasksController);
+    }
+
+    static TransitionInfo subCopy(@NonNull TransitionInfo info,
             @WindowManager.TransitionType int newType, boolean withChanges) {
         final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0);
         out.setTrack(info.getTrack());
@@ -371,15 +394,6 @@
         return out;
     }
 
-    private boolean isHomeOpening(@NonNull TransitionInfo.Change change) {
-        return change.getTaskInfo() != null
-                && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME;
-    }
-
-    private boolean isWallpaper(@NonNull TransitionInfo.Change change) {
-        return (change.getFlags() & FLAG_IS_WALLPAPER) != 0;
-    }
-
     @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
@@ -398,14 +412,15 @@
         if (KeyguardTransitionHandler.handles(info)) {
             if (mixed != null && mixed.mType != MixedTransition.TYPE_KEYGUARD) {
                 final MixedTransition keyguardMixed =
-                        new MixedTransition(MixedTransition.TYPE_KEYGUARD, transition);
+                        createDefaultMixedTransition(MixedTransition.TYPE_KEYGUARD, transition);
                 mActiveTransitions.add(keyguardMixed);
                 Transitions.TransitionFinishCallback callback = wct -> {
                     mActiveTransitions.remove(keyguardMixed);
                     finishCallback.onTransitionFinished(wct);
                 };
                 final boolean hasAnimateKeyguard = animateKeyguard(
-                        keyguardMixed, info, startTransaction, finishTransaction, callback);
+                        keyguardMixed, info, startTransaction, finishTransaction, callback,
+                        mKeyguardHandler, mPipHandler);
                 if (hasAnimateKeyguard) {
                     ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                             "Converting mixed transition into a keyguard transition");
@@ -429,279 +444,12 @@
             finishCallback.onTransitionFinished(wct);
         };
 
-        if (chosenTransition.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
-            return animateEnterPipFromSplit(
-                    chosenTransition, info, startTransaction, finishTransaction, callback);
-        } else if (chosenTransition.mType
-                == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
-            return animateEnterPipFromActivityEmbedding(
-                    chosenTransition, info, startTransaction, finishTransaction, callback);
-        } else if (chosenTransition.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
-            return false;
-        } else if (chosenTransition.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
-            final boolean handledToPip = animateOpenIntentWithRemoteAndPip(
-                    chosenTransition, info, startTransaction, finishTransaction, callback);
-            // Consume the transition on remote handler if the leftover handler already handle this
-            // transition. And if it cannot, the transition will be handled by remote handler, so
-            // don't consume here.
-            // Need to check leftOverHandler as it may change in #animateOpenIntentWithRemoteAndPip
-            if (handledToPip && chosenTransition.mHasRequestToRemote
-                    && chosenTransition.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
-                mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, false, null);
-            }
-            return handledToPip;
-        } else if (chosenTransition.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
-            for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-                final TransitionInfo.Change change = info.getChanges().get(i);
-                // Pip auto-entering info might be appended to recent transition like pressing
-                // home-key in 3-button navigation. This offers split handler the opportunity to
-                // handle split to pip animation.
-                if (mPipHandler.isEnteringPip(change, info.getType())
-                        && mSplitHandler.getSplitItemPosition(change.getLastParent())
-                        != SPLIT_POSITION_UNDEFINED) {
-                    return animateEnterPipFromSplit(
-                            chosenTransition, info, startTransaction, finishTransaction, callback);
-                }
-            }
-
-            return animateRecentsDuringSplit(
-                    chosenTransition, info, startTransaction, finishTransaction, callback);
-        } else if (chosenTransition.mType == MixedTransition.TYPE_KEYGUARD) {
-            return animateKeyguard(
-                    chosenTransition, info, startTransaction, finishTransaction, callback);
-        } else if (chosenTransition.mType == MixedTransition.TYPE_RECENTS_DURING_KEYGUARD) {
-            return animateRecentsDuringKeyguard(
-                    chosenTransition, info, startTransaction, finishTransaction, callback);
-        } else if (chosenTransition.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
-            return animateRecentsDuringDesktop(
-                    chosenTransition, info, startTransaction, finishTransaction, callback);
-        } else if (chosenTransition.mType == MixedTransition.TYPE_UNFOLD) {
-            return animateUnfold(
-                    chosenTransition, info, startTransaction, finishTransaction, callback);
-        } else {
+        boolean handled = chosenTransition.startAnimation(
+                transition, info, startTransaction, finishTransaction, callback);
+        if (!handled) {
             mActiveTransitions.remove(chosenTransition);
-            throw new IllegalStateException("Starting mixed animation without a known mixed type? "
-                    + chosenTransition.mType);
         }
-    }
-
-    private boolean animateEnterPipFromActivityEmbedding(@NonNull MixedTransition mixed,
-            @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction startTransaction,
-            @NonNull SurfaceControl.Transaction finishTransaction,
-            @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
-                + "entering PIP from an Activity Embedding window");
-        // Split into two transitions (wct)
-        TransitionInfo.Change pipChange = null;
-        final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
-        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-            TransitionInfo.Change change = info.getChanges().get(i);
-            if (mPipHandler.isEnteringPip(change, info.getType())) {
-                if (pipChange != null) {
-                    throw new IllegalStateException("More than 1 pip-entering changes in one"
-                            + " transition? " + info);
-                }
-                pipChange = change;
-                // going backwards, so remove-by-index is fine.
-                everythingElse.getChanges().remove(i);
-            }
-        }
-
-        final Transitions.TransitionFinishCallback finishCB = (wct) -> {
-            --mixed.mInFlightSubAnimations;
-            mixed.joinFinishArgs(wct);
-            if (mixed.mInFlightSubAnimations > 0) return;
-            mActiveTransitions.remove(mixed);
-            finishCallback.onTransitionFinished(mixed.mFinishWCT);
-        };
-
-        if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) {
-            // Fallback to dispatching to other handlers.
-            return false;
-        }
-
-        // PIP window should always be on the highest Z order.
-        if (pipChange != null) {
-            mixed.mInFlightSubAnimations = 2;
-            mPipHandler.startEnterAnimation(
-                    pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE),
-                    finishTransaction,
-                    finishCB);
-        } else {
-            mixed.mInFlightSubAnimations = 1;
-        }
-
-        mActivityEmbeddingController.startAnimation(mixed.mTransition, everythingElse,
-                startTransaction, finishTransaction, finishCB);
-        return true;
-    }
-
-    private boolean animateOpenIntentWithRemoteAndPip(@NonNull MixedTransition mixed,
-            @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction startTransaction,
-            @NonNull SurfaceControl.Transaction finishTransaction,
-            @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        TransitionInfo.Change pipChange = null;
-        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-            TransitionInfo.Change change = info.getChanges().get(i);
-            if (mPipHandler.isEnteringPip(change, info.getType())) {
-                if (pipChange != null) {
-                    throw new IllegalStateException("More than 1 pip-entering changes in one"
-                            + " transition? " + info);
-                }
-                pipChange = change;
-                info.getChanges().remove(i);
-            }
-        }
-        Transitions.TransitionFinishCallback finishCB = (wct) -> {
-            --mixed.mInFlightSubAnimations;
-            mixed.joinFinishArgs(wct);
-            if (mixed.mInFlightSubAnimations > 0) return;
-            mActiveTransitions.remove(mixed);
-            finishCallback.onTransitionFinished(mixed.mFinishWCT);
-        };
-        if (pipChange == null) {
-            if (mixed.mLeftoversHandler != null) {
-                mixed.mInFlightSubAnimations = 1;
-                if (mixed.mLeftoversHandler.startAnimation(mixed.mTransition,
-                        info, startTransaction, finishTransaction, finishCB)) {
-                    return true;
-                }
-            }
-            mActiveTransitions.remove(mixed);
-            return false;
-        }
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Splitting PIP into a separate"
-                        + " animation because remote-animation likely doesn't support it");
-        // Split the transition into 2 parts: the pip part and the rest.
-        mixed.mInFlightSubAnimations = 2;
-        // make a new startTransaction because pip's startEnterAnimation "consumes" it so
-        // we need a separate one to send over to launcher.
-        SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
-
-        mPipHandler.startEnterAnimation(pipChange, otherStartT, finishTransaction, finishCB);
-
-        // Dispatch the rest of the transition normally.
-        if (mixed.mLeftoversHandler != null
-                && mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info,
-                    startTransaction, finishTransaction, finishCB)) {
-            return true;
-        }
-        mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, info,
-                startTransaction, finishTransaction, finishCB, this);
-        return true;
-    }
-
-    private boolean animateEnterPipFromSplit(@NonNull final MixedTransition mixed,
-            @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction startTransaction,
-            @NonNull SurfaceControl.Transaction finishTransaction,
-            @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
-                + "entering PIP while Split-Screen is foreground.");
-        TransitionInfo.Change pipChange = null;
-        TransitionInfo.Change wallpaper = null;
-        final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
-        boolean homeIsOpening = false;
-        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-            TransitionInfo.Change change = info.getChanges().get(i);
-            if (mPipHandler.isEnteringPip(change, info.getType())) {
-                if (pipChange != null) {
-                    throw new IllegalStateException("More than 1 pip-entering changes in one"
-                            + " transition? " + info);
-                }
-                pipChange = change;
-                // going backwards, so remove-by-index is fine.
-                everythingElse.getChanges().remove(i);
-            } else if (isHomeOpening(change)) {
-                homeIsOpening = true;
-            } else if (isWallpaper(change)) {
-                wallpaper = change;
-            }
-        }
-        if (pipChange == null) {
-            // um, something probably went wrong.
-            mActiveTransitions.remove(mixed);
-            return false;
-        }
-        final boolean isGoingHome = homeIsOpening;
-        Transitions.TransitionFinishCallback finishCB = (wct) -> {
-            --mixed.mInFlightSubAnimations;
-            mixed.joinFinishArgs(wct);
-            if (mixed.mInFlightSubAnimations > 0) return;
-            mActiveTransitions.remove(mixed);
-            if (isGoingHome) {
-                mSplitHandler.onTransitionAnimationComplete();
-            }
-            finishCallback.onTransitionFinished(mixed.mFinishWCT);
-        };
-        if (isGoingHome || mSplitHandler.getSplitItemPosition(pipChange.getLastParent())
-                != SPLIT_POSITION_UNDEFINED) {
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is actually mixed "
-                    + "since entering-PiP caused us to leave split and return home.");
-            // We need to split the transition into 2 parts: the pip part (animated by pip)
-            // and the dismiss-part (animated by launcher).
-            mixed.mInFlightSubAnimations = 2;
-            // immediately make the wallpaper visible (so that we don't see it pop-in during
-            // the time it takes to start recents animation (which is remote).
-            if (wallpaper != null) {
-                startTransaction.show(wallpaper.getLeash()).setAlpha(wallpaper.getLeash(), 1.f);
-            }
-            // make a new startTransaction because pip's startEnterAnimation "consumes" it so
-            // we need a separate one to send over to launcher.
-            SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
-            @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED;
-            if (mSplitHandler.isSplitScreenVisible()) {
-                // The non-going home case, we could be pip-ing one of the split stages and keep
-                // showing the other
-                for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-                    TransitionInfo.Change change = info.getChanges().get(i);
-                    if (change == pipChange) {
-                        // Ignore the change/task that's going into Pip
-                        continue;
-                    }
-                    @SplitScreen.StageType int splitItemStage =
-                            mSplitHandler.getSplitItemStage(change.getLastParent());
-                    if (splitItemStage != STAGE_TYPE_UNDEFINED) {
-                        topStageToKeep = splitItemStage;
-                        break;
-                    }
-                }
-            }
-            // Let split update internal state for dismiss.
-            mSplitHandler.prepareDismissAnimation(topStageToKeep,
-                    EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
-                    finishTransaction);
-
-            // We are trying to accommodate launcher's close animation which can't handle the
-            // divider-bar, so if split-handler is closing the divider-bar, just hide it and remove
-            // from transition info.
-            for (int i = everythingElse.getChanges().size() - 1; i >= 0; --i) {
-                if ((everythingElse.getChanges().get(i).getFlags() & FLAG_IS_DIVIDER_BAR) != 0) {
-                    everythingElse.getChanges().remove(i);
-                    break;
-                }
-            }
-
-            mPipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA);
-            mPipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
-                    finishCB);
-            // Dispatch the rest of the transition normally. This will most-likely be taken by
-            // recents or default handler.
-            mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, everythingElse,
-                    otherStartT, finishTransaction, finishCB, this);
-        } else {
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  Not leaving split, so just "
-                    + "forward animation to Pip-Handler.");
-            // This happens if the pip-ing activity is in a multi-activity task (and thus a
-            // new pip task is spawned). In this case, we don't actually exit split so we can
-            // just let pip transition handle the animation verbatim.
-            mixed.mInFlightSubAnimations = 1;
-            mPipHandler.startAnimation(mixed.mTransition, info, startTransaction, finishTransaction,
-                    finishCB);
-        }
-        return true;
+        return handled;
     }
 
     private void unlinkMissingParents(TransitionInfo from) {
@@ -735,14 +483,14 @@
     public boolean animatePendingEnterPipFromSplit(IBinder transition, TransitionInfo info,
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
             Transitions.TransitionFinishCallback finishCallback) {
-        final MixedTransition mixed = new MixedTransition(
+        final MixedTransition mixed = createDefaultMixedTransition(
                 MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition);
         mActiveTransitions.add(mixed);
         Transitions.TransitionFinishCallback callback = wct -> {
             mActiveTransitions.remove(mixed);
             finishCallback.onTransitionFinished(wct);
         };
-        return animateEnterPipFromSplit(mixed, info, startT, finishT, callback);
+        return mixed.startAnimation(transition, info, startT, finishT, callback);
     }
 
     /**
@@ -765,7 +513,7 @@
         }
         if (displayPart.getChanges().isEmpty()) return false;
         unlinkMissingParents(everythingElse);
-        final MixedTransition mixed = new MixedTransition(
+        final MixedTransition mixed = createDefaultMixedTransition(
                 MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE, transition);
         mActiveTransitions.add(mixed);
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is a mix of display change "
@@ -794,116 +542,22 @@
         return true;
     }
 
-    private boolean animateRecentsDuringSplit(@NonNull final MixedTransition mixed,
+    private static boolean animateKeyguard(@NonNull final MixedTransition mixed,
             @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
-            @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        // Split-screen is only interested in the recents transition finishing (and merging), so
-        // just wrap finish and start recents animation directly.
-        Transitions.TransitionFinishCallback finishCB = (wct) -> {
-            mixed.mInFlightSubAnimations = 0;
-            mActiveTransitions.remove(mixed);
-            // If pair-to-pair switching, the post-recents clean-up isn't needed.
-            wct = wct != null ? wct : new WindowContainerTransaction();
-            if (mixed.mAnimType != MixedTransition.ANIM_TYPE_PAIR_TO_PAIR) {
-                mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction);
-            } else {
-                // notify pair-to-pair recents animation finish
-                mSplitHandler.onRecentsPairToPairAnimationFinish(wct);
-            }
-            mSplitHandler.onTransitionAnimationComplete();
-            finishCallback.onTransitionFinished(wct);
-        };
-        mixed.mInFlightSubAnimations = 1;
-        mSplitHandler.onRecentsInSplitAnimationStart(info);
-        final boolean handled = mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info,
-                startTransaction, finishTransaction, finishCB);
-        if (!handled) {
-            mSplitHandler.onRecentsInSplitAnimationCanceled();
-            mActiveTransitions.remove(mixed);
-        }
-        return handled;
-    }
-
-    private boolean animateKeyguard(@NonNull final MixedTransition mixed,
-            @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction startTransaction,
-            @NonNull SurfaceControl.Transaction finishTransaction,
-            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+            @NonNull Transitions.TransitionFinishCallback finishCallback,
+            @NonNull KeyguardTransitionHandler keyguardHandler,
+            PipTransitionController pipHandler) {
         if (mixed.mFinishT == null) {
             mixed.mFinishT = finishTransaction;
             mixed.mFinishCB = finishCallback;
         }
         // Sync pip state.
-        if (mPipHandler != null) {
-            mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
+        if (pipHandler != null) {
+            pipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
         }
-        return mixed.startSubAnimation(mKeyguardHandler, info, startTransaction, finishTransaction);
-    }
-
-    private boolean animateRecentsDuringKeyguard(@NonNull final MixedTransition mixed,
-            @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction startTransaction,
-            @NonNull SurfaceControl.Transaction finishTransaction,
-            @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        if (mixed.mInfo == null) {
-            mixed.mInfo = info;
-            mixed.mFinishT = finishTransaction;
-            mixed.mFinishCB = finishCallback;
-        }
-        return mixed.startSubAnimation(mRecentsHandler, info, startTransaction, finishTransaction);
-    }
-
-    private boolean animateRecentsDuringDesktop(@NonNull final MixedTransition mixed,
-            @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction startTransaction,
-            @NonNull SurfaceControl.Transaction finishTransaction,
-            @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        Transitions.TransitionFinishCallback finishCB = wct -> {
-            mixed.mInFlightSubAnimations--;
-            if (mixed.mInFlightSubAnimations == 0) {
-                mActiveTransitions.remove(mixed);
-                finishCallback.onTransitionFinished(wct);
-            }
-        };
-
-        mixed.mInFlightSubAnimations++;
-        boolean consumed = mRecentsHandler.startAnimation(
-                mixed.mTransition, info, startTransaction, finishTransaction, finishCB);
-        if (!consumed) {
-            mixed.mInFlightSubAnimations--;
-            return false;
-        }
-        if (mDesktopTasksController != null) {
-            mDesktopTasksController.syncSurfaceState(info, finishTransaction);
-            return true;
-        }
-
-        return false;
-    }
-
-    private boolean animateUnfold(@NonNull final MixedTransition mixed,
-            @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction startTransaction,
-            @NonNull SurfaceControl.Transaction finishTransaction,
-            @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        final Transitions.TransitionFinishCallback finishCB = (wct) -> {
-            mixed.mInFlightSubAnimations--;
-            if (mixed.mInFlightSubAnimations > 0) return;
-            mActiveTransitions.remove(mixed);
-            finishCallback.onTransitionFinished(wct);
-        };
-        mixed.mInFlightSubAnimations = 1;
-        // Sync pip state.
-        if (mPipHandler != null) {
-            mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
-        }
-        if (mSplitHandler != null && mSplitHandler.isSplitActive()) {
-            mSplitHandler.updateSurfaces(startTransaction);
-        }
-        return mUnfoldHandler.startAnimation(
-                mixed.mTransition, info, startTransaction, finishTransaction, finishCB);
+        return mixed.startSubAnimation(keyguardHandler, info, startTransaction, finishTransaction);
     }
 
     /** Use to when split use intent to enter, check if this enter transition should be mixed or
@@ -947,65 +601,13 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         for (int i = 0; i < mActiveTransitions.size(); ++i) {
             if (mActiveTransitions.get(i).mTransition != mergeTarget) continue;
+
             MixedTransition mixed = mActiveTransitions.get(i);
             if (mixed.mInFlightSubAnimations <= 0) {
                 // Already done, so no need to end it.
                 return;
             }
-            if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
-                // queue since no actual animation.
-            } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
-                if (mixed.mAnimType == MixedTransition.ANIM_TYPE_GOING_HOME) {
-                    boolean ended = mSplitHandler.end();
-                    // If split couldn't end (because it is remote), then don't end everything else
-                    // since we have to play out the animation anyways.
-                    if (!ended) return;
-                    mPipHandler.end();
-                    if (mixed.mLeftoversHandler != null) {
-                        mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
-                                finishCallback);
-                    }
-                } else {
-                    mPipHandler.end();
-                }
-            } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
-                mPipHandler.end();
-                mActivityEmbeddingController.mergeAnimation(transition, info, t, mergeTarget,
-                        finishCallback);
-            } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
-                mPipHandler.end();
-                if (mixed.mLeftoversHandler != null) {
-                    mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
-                            finishCallback);
-                }
-            } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
-                if (mSplitHandler.isPendingEnter(transition)) {
-                    // Recents -> enter-split means that we are switching from one pair to
-                    // another pair.
-                    mixed.mAnimType = MixedTransition.ANIM_TYPE_PAIR_TO_PAIR;
-                }
-                mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
-                        finishCallback);
-            } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
-                mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
-            } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_KEYGUARD) {
-                if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
-                    handoverTransitionLeashes(mixed, info, t, mixed.mFinishT);
-                    if (animateKeyguard(mixed, info, t, mixed.mFinishT, mixed.mFinishCB)) {
-                        finishCallback.onTransitionFinished(null);
-                    }
-                }
-                mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
-                        finishCallback);
-            } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
-                mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
-                        finishCallback);
-            } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
-                mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
-            } else {
-                throw new IllegalStateException("Playing a mixed transition with unknown type? "
-                        + mixed.mType);
-            }
+            mixed.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
         }
     }
 
@@ -1018,46 +620,30 @@
             mixed = mActiveTransitions.remove(i);
             break;
         }
-        if (mixed == null) return;
-        if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
-            mPipHandler.onTransitionConsumed(transition, aborted, finishT);
-        } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
-            mPipHandler.onTransitionConsumed(transition, aborted, finishT);
-            mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT);
-        } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
-            mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
-        } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
-            mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
-        } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
-            mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT);
-        } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
-            mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
-        } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
-            mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT);
-        }
-        if (mixed.mHasRequestToRemote) {
-            mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT);
+        if (mixed != null) {
+            mixed.onTransitionConsumed(transition, aborted, finishT);
         }
     }
 
     /**
-     * Update an incoming {@link TransitionInfo} with the leashes from an ongoing
-     * {@link MixedTransition} so that it can take over some parts of the animation without
+     * Update an incoming {@link TransitionInfo} with the leashes from an existing
+     * {@link TransitionInfo} so that it can take over some parts of the animation without
      * reparenting to new transition roots.
      */
-    private static void handoverTransitionLeashes(@NonNull MixedTransition mixed,
-            @NonNull TransitionInfo info,
+    static void handoverTransitionLeashes(
+            @NonNull TransitionInfo from,
+            @NonNull TransitionInfo to,
             @NonNull SurfaceControl.Transaction startT,
             @NonNull SurfaceControl.Transaction finishT) {
 
         // Show the roots in case they contain new changes not present in the original transition.
-        for (int j = info.getRootCount() - 1; j >= 0; --j) {
-            startT.show(info.getRoot(j).getLeash());
+        for (int j = to.getRootCount() - 1; j >= 0; --j) {
+            startT.show(to.getRoot(j).getLeash());
         }
 
         // Find all of the leashes from the original transition.
         Map<WindowContainerToken, TransitionInfo.Change> originalChanges = new ArrayMap<>();
-        for (TransitionInfo.Change oldChange : mixed.mInfo.getChanges()) {
+        for (TransitionInfo.Change oldChange : from.getChanges()) {
             if (oldChange.getContainer() != null) {
                 originalChanges.put(oldChange.getContainer(), oldChange);
             }
@@ -1065,9 +651,10 @@
 
         // Merge the animation leashes by re-using the original ones if we see the same container
         // in the new transition and the old.
-        for (TransitionInfo.Change newChange : info.getChanges()) {
+        for (TransitionInfo.Change newChange : to.getChanges()) {
             if (originalChanges.containsKey(newChange.getContainer())) {
-                final TransitionInfo.Change oldChange = originalChanges.get(newChange.getContainer());
+                final TransitionInfo.Change oldChange = originalChanges.get(
+                        newChange.getContainer());
                 startT.reparent(newChange.getLeash(), null);
                 newChange.setLeash(oldChange.getLeash());
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
new file mode 100644
index 0000000..e9cd73b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.transition;
+
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+
+import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy;
+import static com.android.wm.shell.transition.MixedTransitionHelper.animateEnterPipFromSplit;
+import static com.android.wm.shell.transition.MixedTransitionHelper.animateKeyguard;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.splitscreen.StageCoordinator;
+import com.android.wm.shell.unfold.UnfoldTransitionHandler;
+
+class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
+    private final UnfoldTransitionHandler mUnfoldHandler;
+    private final ActivityEmbeddingController mActivityEmbeddingController;
+
+    DefaultMixedTransition(int type, IBinder transition, Transitions player,
+            DefaultMixedHandler mixedHandler, PipTransitionController pipHandler,
+            StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler,
+            UnfoldTransitionHandler unfoldHandler,
+            ActivityEmbeddingController activityEmbeddingController) {
+        super(type, transition, player, mixedHandler, pipHandler, splitHandler, keyguardHandler);
+        mUnfoldHandler = unfoldHandler;
+        mActivityEmbeddingController = activityEmbeddingController;
+
+        switch (type) {
+            case TYPE_UNFOLD:
+                mLeftoversHandler = mUnfoldHandler;
+                break;
+            case TYPE_DISPLAY_AND_SPLIT_CHANGE:
+            case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
+            case TYPE_ENTER_PIP_FROM_SPLIT:
+            case TYPE_KEYGUARD:
+            case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
+            default:
+                break;
+        }
+    }
+
+    @Override
+    boolean startAnimation(
+            @NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        return switch (mType) {
+            case TYPE_DISPLAY_AND_SPLIT_CHANGE -> false;
+            case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING ->
+                    animateEnterPipFromActivityEmbedding(
+                            info, startTransaction, finishTransaction, finishCallback);
+            case TYPE_ENTER_PIP_FROM_SPLIT ->
+                    animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
+                            finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
+            case TYPE_KEYGUARD ->
+                    animateKeyguard(this, info, startTransaction, finishTransaction, finishCallback,
+                            mKeyguardHandler, mPipHandler);
+            case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE ->
+                    animateOpenIntentWithRemoteAndPip(transition, info, startTransaction,
+                            finishTransaction, finishCallback);
+            case TYPE_UNFOLD ->
+                    animateUnfold(info, startTransaction, finishTransaction, finishCallback);
+            default -> throw new IllegalStateException(
+                    "Starting default mixed animation with unknown or illegal type: " + mType);
+        };
+    }
+
+    private boolean animateEnterPipFromActivityEmbedding(
+            @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for entering PIP from"
+                + " an Activity Embedding window #%d", info.getDebugId());
+        // Split into two transitions (wct)
+        TransitionInfo.Change pipChange = null;
+        final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            TransitionInfo.Change change = info.getChanges().get(i);
+            if (mPipHandler.isEnteringPip(change, info.getType())) {
+                if (pipChange != null) {
+                    throw new IllegalStateException("More than 1 pip-entering changes in one"
+                            + " transition? " + info);
+                }
+                pipChange = change;
+                // going backwards, so remove-by-index is fine.
+                everythingElse.getChanges().remove(i);
+            }
+        }
+
+        final Transitions.TransitionFinishCallback finishCB = (wct) -> {
+            --mInFlightSubAnimations;
+            joinFinishArgs(wct);
+            if (mInFlightSubAnimations > 0) return;
+            finishCallback.onTransitionFinished(mFinishWCT);
+        };
+
+        if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) {
+            // Fallback to dispatching to other handlers.
+            return false;
+        }
+
+        // PIP window should always be on the highest Z order.
+        if (pipChange != null) {
+            mInFlightSubAnimations = 2;
+            mPipHandler.startEnterAnimation(
+                    pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE),
+                    finishTransaction,
+                    finishCB);
+        } else {
+            mInFlightSubAnimations = 1;
+        }
+
+        mActivityEmbeddingController.startAnimation(
+                mTransition, everythingElse, startTransaction, finishTransaction, finishCB);
+        return true;
+    }
+
+    private boolean animateOpenIntentWithRemoteAndPip(
+            @NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for opening an intent"
+                + " with a remote transition and PIP #%d", info.getDebugId());
+        boolean handledToPip = tryAnimateOpenIntentWithRemoteAndPip(
+                info, startTransaction, finishTransaction, finishCallback);
+        // Consume the transition on remote handler if the leftover handler already handle this
+        // transition. And if it cannot, the transition will be handled by remote handler, so don't
+        // consume here.
+        // Need to check leftOverHandler as it may change in #animateOpenIntentWithRemoteAndPip
+        if (handledToPip && mHasRequestToRemote
+                && mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
+            mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, false, null);
+        }
+        return handledToPip;
+    }
+
+    private boolean tryAnimateOpenIntentWithRemoteAndPip(
+            @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        TransitionInfo.Change pipChange = null;
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            TransitionInfo.Change change = info.getChanges().get(i);
+            if (mPipHandler.isEnteringPip(change, info.getType())) {
+                if (pipChange != null) {
+                    throw new IllegalStateException("More than 1 pip-entering changes in one"
+                            + " transition? " + info);
+                }
+                pipChange = change;
+                info.getChanges().remove(i);
+            }
+        }
+        Transitions.TransitionFinishCallback finishCB = (wct) -> {
+            --mInFlightSubAnimations;
+            joinFinishArgs(wct);
+            if (mInFlightSubAnimations > 0) return;
+            finishCallback.onTransitionFinished(mFinishWCT);
+        };
+        if (pipChange == null) {
+            if (mLeftoversHandler != null) {
+                mInFlightSubAnimations = 1;
+                if (mLeftoversHandler.startAnimation(
+                        mTransition, info, startTransaction, finishTransaction, finishCB)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Splitting PIP into a separate"
+                + " animation because remote-animation likely doesn't support it #%d",
+                info.getDebugId());
+        // Split the transition into 2 parts: the pip part and the rest.
+        mInFlightSubAnimations = 2;
+        // make a new startTransaction because pip's startEnterAnimation "consumes" it so
+        // we need a separate one to send over to launcher.
+        SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
+
+        mPipHandler.startEnterAnimation(pipChange, otherStartT, finishTransaction, finishCB);
+
+        // Dispatch the rest of the transition normally.
+        if (mLeftoversHandler != null
+                && mLeftoversHandler.startAnimation(mTransition, info,
+                startTransaction, finishTransaction, finishCB)) {
+            return true;
+        }
+        mLeftoversHandler = mPlayer.dispatchTransition(
+                mTransition, info, startTransaction, finishTransaction, finishCB, mMixedHandler);
+        return true;
+    }
+
+    private boolean animateUnfold(
+            @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for unfolding #%d",
+                info.getDebugId());
+
+        final Transitions.TransitionFinishCallback finishCB = (wct) -> {
+            mInFlightSubAnimations--;
+            if (mInFlightSubAnimations > 0) return;
+            finishCallback.onTransitionFinished(wct);
+        };
+        mInFlightSubAnimations = 1;
+        // Sync pip state.
+        if (mPipHandler != null) {
+            mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
+        }
+        if (mSplitHandler != null && mSplitHandler.isSplitActive()) {
+            mSplitHandler.updateSurfaces(startTransaction);
+        }
+        return mUnfoldHandler.startAnimation(
+                mTransition, info, startTransaction, finishTransaction, finishCB);
+    }
+
+    @Override
+    void mergeAnimation(
+            @NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        switch (mType) {
+            case TYPE_DISPLAY_AND_SPLIT_CHANGE:
+                // queue since no actual animation.
+                return;
+            case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
+                mPipHandler.end();
+                mActivityEmbeddingController.mergeAnimation(
+                        transition, info, t, mergeTarget, finishCallback);
+                return;
+            case TYPE_ENTER_PIP_FROM_SPLIT:
+                if (mAnimType == ANIM_TYPE_GOING_HOME) {
+                    boolean ended = mSplitHandler.end();
+                    // If split couldn't end (because it is remote), then don't end everything else
+                    // since we have to play out the animation anyways.
+                    if (!ended) return;
+                    mPipHandler.end();
+                    if (mLeftoversHandler != null) {
+                        mLeftoversHandler.mergeAnimation(
+                                transition, info, t, mergeTarget, finishCallback);
+                    }
+                } else {
+                    mPipHandler.end();
+                }
+                return;
+            case TYPE_KEYGUARD:
+                mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+                return;
+            case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
+                mPipHandler.end();
+                if (mLeftoversHandler != null) {
+                    mLeftoversHandler.mergeAnimation(
+                            transition, info, t, mergeTarget, finishCallback);
+                }
+                return;
+            case TYPE_UNFOLD:
+                mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+                return;
+            default:
+                throw new IllegalStateException("Playing a default mixed transition with unknown or"
+                        + " illegal type: " + mType);
+        }
+    }
+
+    @Override
+    void onTransitionConsumed(
+            @NonNull IBinder transition, boolean aborted,
+            @Nullable SurfaceControl.Transaction finishT) {
+        switch (mType) {
+            case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
+                mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+                mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT);
+                break;
+            case TYPE_ENTER_PIP_FROM_SPLIT:
+                mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+                break;
+            case TYPE_KEYGUARD:
+                mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT);
+                break;
+            case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
+                mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
+                break;
+            case TYPE_UNFOLD:
+                mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT);
+                break;
+            default:
+                break;
+        }
+
+        if (mHasRequestToRemote) {
+            mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT);
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
new file mode 100644
index 0000000..0974cd1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.transition;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+
+import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
+import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy;
+
+import android.annotation.NonNull;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.splitscreen.StageCoordinator;
+
+public class MixedTransitionHelper {
+    static boolean animateEnterPipFromSplit(
+            @NonNull DefaultMixedHandler.MixedTransition mixed, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback,
+            @NonNull Transitions player, @NonNull DefaultMixedHandler mixedHandler,
+            @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
+                + "entering PIP while Split-Screen is foreground.");
+        TransitionInfo.Change pipChange = null;
+        TransitionInfo.Change wallpaper = null;
+        final TransitionInfo everythingElse =
+                subCopy(info, TRANSIT_TO_BACK, true /* changes */);
+        boolean homeIsOpening = false;
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            TransitionInfo.Change change = info.getChanges().get(i);
+            if (pipHandler.isEnteringPip(change, info.getType())) {
+                if (pipChange != null) {
+                    throw new IllegalStateException("More than 1 pip-entering changes in one"
+                            + " transition? " + info);
+                }
+                pipChange = change;
+                // going backwards, so remove-by-index is fine.
+                everythingElse.getChanges().remove(i);
+            } else if (isHomeOpening(change)) {
+                homeIsOpening = true;
+            } else if (isWallpaper(change)) {
+                wallpaper = change;
+            }
+        }
+        if (pipChange == null) {
+            // um, something probably went wrong.
+            return false;
+        }
+        final boolean isGoingHome = homeIsOpening;
+        Transitions.TransitionFinishCallback finishCB = (wct) -> {
+            --mixed.mInFlightSubAnimations;
+            mixed.joinFinishArgs(wct);
+            if (mixed.mInFlightSubAnimations > 0) return;
+            if (isGoingHome) {
+                splitHandler.onTransitionAnimationComplete();
+            }
+            finishCallback.onTransitionFinished(mixed.mFinishWCT);
+        };
+        if (isGoingHome || splitHandler.getSplitItemPosition(pipChange.getLastParent())
+                != SPLIT_POSITION_UNDEFINED) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is actually mixed "
+                    + "since entering-PiP caused us to leave split and return home.");
+            // We need to split the transition into 2 parts: the pip part (animated by pip)
+            // and the dismiss-part (animated by launcher).
+            mixed.mInFlightSubAnimations = 2;
+            // immediately make the wallpaper visible (so that we don't see it pop-in during
+            // the time it takes to start recents animation (which is remote).
+            if (wallpaper != null) {
+                startTransaction.show(wallpaper.getLeash()).setAlpha(wallpaper.getLeash(), 1.f);
+            }
+            // make a new startTransaction because pip's startEnterAnimation "consumes" it so
+            // we need a separate one to send over to launcher.
+            SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
+            @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED;
+            if (splitHandler.isSplitScreenVisible()) {
+                // The non-going home case, we could be pip-ing one of the split stages and keep
+                // showing the other
+                for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                    TransitionInfo.Change change = info.getChanges().get(i);
+                    if (change == pipChange) {
+                        // Ignore the change/task that's going into Pip
+                        continue;
+                    }
+                    @SplitScreen.StageType int splitItemStage =
+                            splitHandler.getSplitItemStage(change.getLastParent());
+                    if (splitItemStage != STAGE_TYPE_UNDEFINED) {
+                        topStageToKeep = splitItemStage;
+                        break;
+                    }
+                }
+            }
+            // Let split update internal state for dismiss.
+            splitHandler.prepareDismissAnimation(topStageToKeep,
+                    EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
+                    finishTransaction);
+
+            // We are trying to accommodate launcher's close animation which can't handle the
+            // divider-bar, so if split-handler is closing the divider-bar, just hide it and
+            // remove from transition info.
+            for (int i = everythingElse.getChanges().size() - 1; i >= 0; --i) {
+                if ((everythingElse.getChanges().get(i).getFlags() & FLAG_IS_DIVIDER_BAR)
+                        != 0) {
+                    everythingElse.getChanges().remove(i);
+                    break;
+                }
+            }
+
+            pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA);
+            pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
+                    finishCB);
+            // Dispatch the rest of the transition normally. This will most-likely be taken by
+            // recents or default handler.
+            mixed.mLeftoversHandler = player.dispatchTransition(mixed.mTransition, everythingElse,
+                    otherStartT, finishTransaction, finishCB, mixedHandler);
+        } else {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  Not leaving split, so just "
+                    + "forward animation to Pip-Handler.");
+            // This happens if the pip-ing activity is in a multi-activity task (and thus a
+            // new pip task is spawned). In this case, we don't actually exit split so we can
+            // just let pip transition handle the animation verbatim.
+            mixed.mInFlightSubAnimations = 1;
+            pipHandler.startAnimation(
+                    mixed.mTransition, info, startTransaction, finishTransaction, finishCB);
+        }
+        return true;
+    }
+
+    private static boolean isHomeOpening(@NonNull TransitionInfo.Change change) {
+        return change.getTaskInfo() != null
+                && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME;
+    }
+
+    private static boolean isWallpaper(@NonNull TransitionInfo.Change change) {
+        return (change.getFlags() & FLAG_IS_WALLPAPER) != 0;
+    }
+
+    static boolean animateKeyguard(
+            @NonNull DefaultMixedHandler.MixedTransition mixed, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback,
+            @NonNull KeyguardTransitionHandler keyguardHandler,
+            PipTransitionController pipHandler) {
+        if (mixed.mFinishT == null) {
+            mixed.mFinishT = finishTransaction;
+            mixed.mFinishCB = finishCallback;
+        }
+        // Sync pip state.
+        if (pipHandler != null) {
+            pipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
+        }
+        return mixed.startSubAnimation(keyguardHandler, info, startTransaction, finishTransaction);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
new file mode 100644
index 0000000..4ea71490
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition;
+
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
+
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.transition.DefaultMixedHandler.handoverTransitionLeashes;
+import static com.android.wm.shell.transition.MixedTransitionHelper.animateEnterPipFromSplit;
+import static com.android.wm.shell.transition.MixedTransitionHelper.animateKeyguard;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.WindowContainerTransaction;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.splitscreen.StageCoordinator;
+
+class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition {
+    private final RecentsTransitionHandler mRecentsHandler;
+    private final DesktopTasksController mDesktopTasksController;
+
+    RecentsMixedTransition(int type, IBinder transition, Transitions player,
+            DefaultMixedHandler mixedHandler, PipTransitionController pipHandler,
+            StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler,
+            RecentsTransitionHandler recentsHandler,
+            DesktopTasksController desktopTasksController) {
+        super(type, transition, player, mixedHandler, pipHandler, splitHandler, keyguardHandler);
+        mRecentsHandler = recentsHandler;
+        mDesktopTasksController = desktopTasksController;
+        mLeftoversHandler = mRecentsHandler;
+    }
+
+    @Override
+    boolean startAnimation(
+            @NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        return switch (mType) {
+            case TYPE_RECENTS_DURING_DESKTOP ->
+                    animateRecentsDuringDesktop(
+                            info, startTransaction, finishTransaction, finishCallback);
+            case TYPE_RECENTS_DURING_KEYGUARD ->
+                    animateRecentsDuringKeyguard(
+                            info, startTransaction, finishTransaction, finishCallback);
+            case TYPE_RECENTS_DURING_SPLIT ->
+                    animateRecentsDuringSplit(
+                            info, startTransaction, finishTransaction, finishCallback);
+            default -> throw new IllegalStateException(
+                    "Starting Recents mixed animation with unknown or illegal type: " + mType);
+        };
+    }
+
+    private boolean animateRecentsDuringDesktop(
+            @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition for Recents during"
+                + " Desktop #%d", info.getDebugId());
+
+        if (mInfo == null) {
+            mInfo = info;
+            mFinishT = finishTransaction;
+            mFinishCB = finishCallback;
+        }
+        Transitions.TransitionFinishCallback finishCB = wct -> {
+            mInFlightSubAnimations--;
+            if (mInFlightSubAnimations == 0) {
+                finishCallback.onTransitionFinished(wct);
+            }
+        };
+
+        mInFlightSubAnimations++;
+        boolean consumed = mRecentsHandler.startAnimation(
+                mTransition, info, startTransaction, finishTransaction, finishCB);
+        if (!consumed) {
+            mInFlightSubAnimations--;
+            return false;
+        }
+        if (mDesktopTasksController != null) {
+            mDesktopTasksController.syncSurfaceState(info, finishTransaction);
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean animateRecentsDuringKeyguard(
+            @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for Recents during"
+                + " Keyguard #%d", info.getDebugId());
+
+        if (mInfo == null) {
+            mInfo = info;
+            mFinishT = finishTransaction;
+            mFinishCB = finishCallback;
+        }
+        return startSubAnimation(mRecentsHandler, info, startTransaction, finishTransaction);
+    }
+
+    private boolean animateRecentsDuringSplit(
+            @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for Recents during"
+                + " split screen #%d", info.getDebugId());
+
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            // Pip auto-entering info might be appended to recent transition like pressing
+            // home-key in 3-button navigation. This offers split handler the opportunity to
+            // handle split to pip animation.
+            if (mPipHandler.isEnteringPip(change, info.getType())
+                    && mSplitHandler.getSplitItemPosition(change.getLastParent())
+                    != SPLIT_POSITION_UNDEFINED) {
+                return animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
+                        finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
+            }
+        }
+
+        // Split-screen is only interested in the recents transition finishing (and merging), so
+        // just wrap finish and start recents animation directly.
+        Transitions.TransitionFinishCallback finishCB = (wct) -> {
+            mInFlightSubAnimations = 0;
+            // If pair-to-pair switching, the post-recents clean-up isn't needed.
+            wct = wct != null ? wct : new WindowContainerTransaction();
+            if (mAnimType != ANIM_TYPE_PAIR_TO_PAIR) {
+                mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction);
+            } else {
+                // notify pair-to-pair recents animation finish
+                mSplitHandler.onRecentsPairToPairAnimationFinish(wct);
+            }
+            mSplitHandler.onTransitionAnimationComplete();
+            finishCallback.onTransitionFinished(wct);
+        };
+        mInFlightSubAnimations = 1;
+        mSplitHandler.onRecentsInSplitAnimationStart(info);
+        final boolean handled = mLeftoversHandler.startAnimation(
+                mTransition, info, startTransaction, finishTransaction, finishCB);
+        if (!handled) {
+            mSplitHandler.onRecentsInSplitAnimationCanceled();
+        }
+        return handled;
+    }
+
+    @Override
+    void mergeAnimation(
+            @NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        switch (mType) {
+            case TYPE_RECENTS_DURING_DESKTOP:
+                mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+                return;
+            case TYPE_RECENTS_DURING_KEYGUARD:
+                if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
+                    handoverTransitionLeashes(mInfo, info, t, mFinishT);
+                    if (animateKeyguard(
+                            this, info, t, mFinishT, mFinishCB, mKeyguardHandler, mPipHandler)) {
+                        finishCallback.onTransitionFinished(null);
+                    }
+                }
+                mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+                        finishCallback);
+                return;
+            case TYPE_RECENTS_DURING_SPLIT:
+                if (mSplitHandler.isPendingEnter(transition)) {
+                    // Recents -> enter-split means that we are switching from one pair to
+                    // another pair.
+                    mAnimType = DefaultMixedHandler.MixedTransition.ANIM_TYPE_PAIR_TO_PAIR;
+                }
+                mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+                return;
+            default:
+                throw new IllegalStateException("Playing a Recents mixed transition with unknown or"
+                        + " illegal type: " + mType);
+        }
+    }
+
+    @Override
+    void onTransitionConsumed(
+            @NonNull IBinder transition, boolean aborted,
+            @Nullable SurfaceControl.Transaction finishT) {
+        switch (mType) {
+            case TYPE_RECENTS_DURING_DESKTOP:
+            case TYPE_RECENTS_DURING_SPLIT:
+                mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
+                break;
+            default:
+                break;
+        }
+
+        if (mHasRequestToRemote) {
+            mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT);
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
deleted file mode 100644
index 5919aad..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.transition;
-
-import static android.os.Build.IS_USER;
-
-import static com.android.wm.shell.nano.WmShellTransitionTraceProto.MAGIC_NUMBER_H;
-import static com.android.wm.shell.nano.WmShellTransitionTraceProto.MAGIC_NUMBER_L;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.util.Log;
-
-import com.android.internal.util.TraceBuffer;
-import com.android.wm.shell.sysui.ShellCommandHandler;
-
-import com.google.protobuf.nano.MessageNano;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Queue;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper class to collect and dump transition traces.
- */
-public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
-    private static final int ALWAYS_ON_TRACING_CAPACITY = 15 * 1024; // 15 KB
-    private static final int ACTIVE_TRACING_BUFFER_CAPACITY = 5000 * 1024; // 5 MB
-
-    private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
-
-    static final String WINSCOPE_EXT = ".winscope";
-    private static final String TRACE_FILE =
-            "/data/misc/wmtrace/shell_transition_trace" + WINSCOPE_EXT;
-
-    private final Object mEnabledLock = new Object();
-    private boolean mActiveTracingEnabled = false;
-
-    private final TraceBuffer.ProtoProvider mProtoProvider =
-            new TraceBuffer.ProtoProvider<MessageNano,
-                com.android.wm.shell.nano.WmShellTransitionTraceProto,
-                com.android.wm.shell.nano.Transition>() {
-        @Override
-        public int getItemSize(MessageNano proto) {
-            return proto.getCachedSize();
-        }
-
-        @Override
-        public byte[] getBytes(MessageNano proto) {
-            return MessageNano.toByteArray(proto);
-        }
-
-        @Override
-        public void write(
-                com.android.wm.shell.nano.WmShellTransitionTraceProto encapsulatingProto,
-                Queue<com.android.wm.shell.nano.Transition> buffer, OutputStream os)
-                        throws IOException {
-            encapsulatingProto.transitions = buffer.toArray(
-                    new com.android.wm.shell.nano.Transition[0]);
-            os.write(getBytes(encapsulatingProto));
-        }
-    };
-    private final TraceBuffer<MessageNano,
-            com.android.wm.shell.nano.WmShellTransitionTraceProto,
-            com.android.wm.shell.nano.Transition> mTraceBuffer
-                    = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY, mProtoProvider,
-                            (proto) -> handleOnEntryRemovedFromTrace(proto));
-    private final Map<Object, Runnable> mRemovedFromTraceCallbacks = new HashMap<>();
-
-    private final Map<Transitions.TransitionHandler, Integer> mHandlerIds = new HashMap<>();
-    private final Map<Transitions.TransitionHandler, Integer> mHandlerUseCountInTrace =
-            new HashMap<>();
-
-    /**
-     * Adds an entry in the trace to log that a transition has been dispatched to a handler.
-     *
-     * @param transitionId The id of the transition being dispatched.
-     * @param handler The handler the transition is being dispatched to.
-     */
-    public void logDispatched(int transitionId, Transitions.TransitionHandler handler) {
-        final int handlerId;
-        if (mHandlerIds.containsKey(handler)) {
-            handlerId = mHandlerIds.get(handler);
-        } else {
-            // + 1 to avoid 0 ids which can be confused with missing value when dumped to proto
-            handlerId = mHandlerIds.size() + 1;
-            mHandlerIds.put(handler, handlerId);
-        }
-
-        com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
-        proto.id = transitionId;
-        proto.dispatchTimeNs = SystemClock.elapsedRealtimeNanos();
-        proto.handler = handlerId;
-
-        final int useCountAfterAdd = mHandlerUseCountInTrace.getOrDefault(handler, 0) + 1;
-        mHandlerUseCountInTrace.put(handler, useCountAfterAdd);
-
-        mRemovedFromTraceCallbacks.put(proto, () -> {
-            final int useCountAfterRemove = mHandlerUseCountInTrace.get(handler) - 1;
-            mHandlerUseCountInTrace.put(handler, useCountAfterRemove);
-        });
-
-        mTraceBuffer.add(proto);
-    }
-
-    /**
-     * Adds an entry in the trace to log that a request to merge a transition was made.
-     *
-     * @param mergeRequestedTransitionId The id of the transition we are requesting to be merged.
-     */
-    public void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
-        com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
-        proto.id = mergeRequestedTransitionId;
-        proto.mergeRequestTimeNs = SystemClock.elapsedRealtimeNanos();
-        proto.mergeTarget = playingTransitionId;
-
-        mTraceBuffer.add(proto);
-    }
-
-    /**
-     * Adds an entry in the trace to log that a transition was merged by the handler.
-     *
-     * @param mergedTransitionId The id of the transition that was merged.
-     * @param playingTransitionId The id of the transition the transition was merged into.
-     */
-    public void logMerged(int mergedTransitionId, int playingTransitionId) {
-        com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
-        proto.id = mergedTransitionId;
-        proto.mergeTimeNs = SystemClock.elapsedRealtimeNanos();
-        proto.mergeTarget = playingTransitionId;
-
-        mTraceBuffer.add(proto);
-    }
-
-    /**
-     * Adds an entry in the trace to log that a transition was aborted.
-     *
-     * @param transitionId The id of the transition that was aborted.
-     */
-    public void logAborted(int transitionId) {
-        com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
-        proto.id = transitionId;
-        proto.abortTimeNs = SystemClock.elapsedRealtimeNanos();
-
-        mTraceBuffer.add(proto);
-    }
-
-    /**
-     * Starts collecting transitions for the trace.
-     * If called while a trace is already running, this will reset the trace.
-     */
-    public void startTrace(@Nullable PrintWriter pw) {
-        if (IS_USER) {
-            LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
-            return;
-        }
-        Trace.beginSection("Tracer#startTrace");
-        LogAndPrintln.i(pw, "Starting shell transition trace.");
-        synchronized (mEnabledLock) {
-            mActiveTracingEnabled = true;
-            mTraceBuffer.resetBuffer();
-            mTraceBuffer.setCapacity(ACTIVE_TRACING_BUFFER_CAPACITY);
-        }
-        Trace.endSection();
-    }
-
-    /**
-     * Stops collecting the transition trace and dump to trace to file.
-     *
-     * Dumps the trace to @link{TRACE_FILE}.
-     */
-    public void stopTrace(@Nullable PrintWriter pw) {
-        stopTrace(pw, new File(TRACE_FILE));
-    }
-
-    /**
-     * Stops collecting the transition trace and dump to trace to file.
-     * @param outputFile The file to dump the transition trace to.
-     */
-    public void stopTrace(@Nullable PrintWriter pw, File outputFile) {
-        if (IS_USER) {
-            LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
-            return;
-        }
-        Trace.beginSection("Tracer#stopTrace");
-        LogAndPrintln.i(pw, "Stopping shell transition trace.");
-        synchronized (mEnabledLock) {
-            mActiveTracingEnabled = false;
-            writeTraceToFileLocked(pw, outputFile);
-            mTraceBuffer.resetBuffer();
-            mTraceBuffer.setCapacity(ALWAYS_ON_TRACING_CAPACITY);
-        }
-        Trace.endSection();
-    }
-
-    /**
-     * Being called while taking a bugreport so that tracing files can be included in the bugreport.
-     *
-     * @param pw Print writer
-     */
-    public void saveForBugreport(@Nullable PrintWriter pw) {
-        if (IS_USER) {
-            LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
-            return;
-        }
-        Trace.beginSection("TransitionTracer#saveForBugreport");
-        synchronized (mEnabledLock) {
-            final File outputFile = new File(TRACE_FILE);
-            writeTraceToFileLocked(pw, outputFile);
-        }
-        Trace.endSection();
-    }
-
-    private void writeTraceToFileLocked(@Nullable PrintWriter pw, File file) {
-        Trace.beginSection("TransitionTracer#writeTraceToFileLocked");
-        try {
-            com.android.wm.shell.nano.WmShellTransitionTraceProto proto =
-                    new com.android.wm.shell.nano.WmShellTransitionTraceProto();
-            proto.magicNumber = MAGIC_NUMBER_VALUE;
-            writeHandlerMappingToProto(proto);
-            long timeOffsetNs =
-                    TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
-                            - SystemClock.elapsedRealtimeNanos();
-            proto.realToElapsedTimeOffsetNanos = timeOffsetNs;
-            int pid = android.os.Process.myPid();
-            LogAndPrintln.i(pw, "Writing file to " + file.getAbsolutePath()
-                    + " from process " + pid);
-            mTraceBuffer.writeTraceToFile(file, proto);
-        } catch (IOException e) {
-            LogAndPrintln.e(pw, "Unable to write buffer to file", e);
-        }
-        Trace.endSection();
-    }
-
-    private void writeHandlerMappingToProto(
-            com.android.wm.shell.nano.WmShellTransitionTraceProto proto) {
-        ArrayList<com.android.wm.shell.nano.HandlerMapping> handlerMappings = new ArrayList<>();
-        for (Transitions.TransitionHandler handler : mHandlerUseCountInTrace.keySet()) {
-            final int count = mHandlerUseCountInTrace.get(handler);
-            if (count > 0) {
-                com.android.wm.shell.nano.HandlerMapping mapping =
-                        new com.android.wm.shell.nano.HandlerMapping();
-                mapping.id = mHandlerIds.get(handler);
-                mapping.name = handler.getClass().getName();
-                handlerMappings.add(mapping);
-            }
-        }
-        proto.handlerMappings = handlerMappings.toArray(
-                new com.android.wm.shell.nano.HandlerMapping[0]);
-    }
-
-    private void handleOnEntryRemovedFromTrace(Object proto) {
-        if (mRemovedFromTraceCallbacks.containsKey(proto)) {
-            mRemovedFromTraceCallbacks.get(proto).run();
-            mRemovedFromTraceCallbacks.remove(proto);
-        }
-    }
-
-    @Override
-    public boolean onShellCommand(String[] args, PrintWriter pw) {
-        switch (args[0]) {
-            case "start": {
-                startTrace(pw);
-                return true;
-            }
-            case "stop": {
-                stopTrace(pw);
-                return true;
-            }
-            case "save-for-bugreport": {
-                saveForBugreport(pw);
-                return true;
-            }
-            default: {
-                pw.println("Invalid command: " + args[0]);
-                printShellCommandHelp(pw, "");
-                return false;
-            }
-        }
-    }
-
-    @Override
-    public void printShellCommandHelp(PrintWriter pw, String prefix) {
-        pw.println(prefix + "start");
-        pw.println(prefix + "  Start tracing the transitions.");
-        pw.println(prefix + "stop");
-        pw.println(prefix + "  Stop tracing the transitions.");
-        pw.println(prefix + "save-for-bugreport");
-        pw.println(prefix + "  Flush in memory transition trace to file.");
-    }
-
-    private static class LogAndPrintln {
-        private static final String LOG_TAG = "ShellTransitionTracer";
-
-        private static void i(@Nullable PrintWriter pw, String msg) {
-            Log.i(LOG_TAG, msg);
-            if (pw != null) {
-                pw.println(msg);
-                pw.flush();
-            }
-        }
-
-        private static void e(@Nullable PrintWriter pw, String msg) {
-            Log.e(LOG_TAG, msg);
-            if (pw != null) {
-                pw.println("ERROR: " + msg);
-                pw.flush();
-            }
-        }
-
-        private static void e(@Nullable PrintWriter pw, String msg, @NonNull Exception e) {
-            Log.e(LOG_TAG, msg, e);
-            if (pw != null) {
-                pw.println("ERROR: " + msg + " ::\n " + e);
-                pw.flush();
-            }
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index b0d8b47..67fc7e2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -83,6 +83,9 @@
 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.tracing.LegacyTransitionTracer;
+import com.android.wm.shell.transition.tracing.PerfettoTransitionTracer;
+import com.android.wm.shell.transition.tracing.TransitionTracer;
 import com.android.wm.shell.util.TransitionUtil;
 
 import java.io.PrintWriter;
@@ -172,6 +175,9 @@
     /** Transition to animate task to desktop. */
     public static final int TRANSIT_MOVE_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 15;
 
+    /** Transition to resize PiP task. */
+    public static final int TRANSIT_RESIZE_PIP = TRANSIT_FIRST_CUSTOM + 16;
+
     private final ShellTaskOrganizer mOrganizer;
     private final Context mContext;
     private final ShellExecutor mMainExecutor;
@@ -184,7 +190,7 @@
     private final ShellController mShellController;
     private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
     private final SleepHandler mSleepHandler = new SleepHandler();
-    private final Tracer mTracer = new Tracer();
+    private final TransitionTracer mTransitionTracer;
     private boolean mIsRegistered = false;
 
     /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
@@ -307,6 +313,12 @@
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
         shellInit.addInitCallback(this::onInit, this);
         mHomeTransitionObserver = observer;
+
+        if (android.tracing.Flags.perfettoTransitionTracing()) {
+            mTransitionTracer = new PerfettoTransitionTracer();
+        } else {
+            mTransitionTracer = new LegacyTransitionTracer();
+        }
     }
 
     private void onInit() {
@@ -868,7 +880,7 @@
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
                 + " %s is still animating. Notify the animating transition"
                 + " in case they can be merged", ready, playing);
-        mTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
+        mTransitionTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
         playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
                 playing.mToken, (wct) -> onMerged(playing, ready));
     }
@@ -902,7 +914,7 @@
         for (int i = 0; i < mObservers.size(); ++i) {
             mObservers.get(i).onTransitionMerged(merged.mToken, playing.mToken);
         }
-        mTracer.logMerged(merged.mInfo.getDebugId(), playing.mInfo.getDebugId());
+        mTransitionTracer.logMerged(merged.mInfo.getDebugId(), playing.mInfo.getDebugId());
         // See if we should merge another transition.
         processReadyQueue(track);
     }
@@ -923,7 +935,7 @@
                     active.mStartT, active.mFinishT, (wct) -> onFinish(active, wct));
             if (consumed) {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
-                mTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
+                mTransitionTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
                 return;
             }
         }
@@ -948,7 +960,7 @@
             if (consumed) {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s",
                         mHandlers.get(i));
-                mTracer.logDispatched(info.getDebugId(), mHandlers.get(i));
+                mTransitionTracer.logDispatched(info.getDebugId(), mHandlers.get(i));
                 return mHandlers.get(i);
             }
         }
@@ -978,7 +990,7 @@
         final Track track = mTracks.get(transition.getTrack());
         transition.mAborted = true;
 
-        mTracer.logAborted(transition.mInfo.getDebugId());
+        mTransitionTracer.logAborted(transition.mInfo.getDebugId());
 
         if (transition.mHandler != null) {
             // Notifies to clean-up the aborted transition.
@@ -1506,12 +1518,18 @@
         }
     }
 
-
     @Override
     public boolean onShellCommand(String[] args, PrintWriter pw) {
         switch (args[0]) {
             case "tracing": {
-                mTracer.onShellCommand(Arrays.copyOfRange(args, 1, args.length), pw);
+                if (!android.tracing.Flags.perfettoTransitionTracing()) {
+                    ((LegacyTransitionTracer) mTransitionTracer)
+                            .onShellCommand(Arrays.copyOfRange(args, 1, args.length), pw);
+                } else {
+                    pw.println("Command not supported. Use the Perfetto command instead to start "
+                            + "and stop this trace instead.");
+                    return false;
+                }
                 return true;
             }
             default: {
@@ -1524,8 +1542,10 @@
 
     @Override
     public void printShellCommandHelp(PrintWriter pw, String prefix) {
-        pw.println(prefix + "tracing");
-        mTracer.printShellCommandHelp(pw, prefix + "  ");
+        if (!android.tracing.Flags.perfettoTransitionTracing()) {
+            pw.println(prefix + "tracing");
+            ((LegacyTransitionTracer) mTransitionTracer).printShellCommandHelp(pw, prefix + "  ");
+        }
     }
 
     private void dump(@NonNull PrintWriter pw, String prefix) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/LegacyTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/LegacyTransitionTracer.java
new file mode 100644
index 0000000..9c84886
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/LegacyTransitionTracer.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.transition.tracing;
+
+import static android.os.Build.IS_USER;
+
+import static com.android.wm.shell.nano.WmShellTransitionTraceProto.MAGIC_NUMBER_H;
+import static com.android.wm.shell.nano.WmShellTransitionTraceProto.MAGIC_NUMBER_L;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.Log;
+
+import com.android.internal.util.TraceBuffer;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.transition.Transitions;
+
+import com.google.protobuf.nano.MessageNano;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper class to collect and dump transition traces.
+ */
+public class LegacyTransitionTracer
+        implements ShellCommandHandler.ShellCommandActionHandler, TransitionTracer {
+    private static final int ALWAYS_ON_TRACING_CAPACITY = 15 * 1024; // 15 KB
+    private static final int ACTIVE_TRACING_BUFFER_CAPACITY = 5000 * 1024; // 5 MB
+
+    private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+
+    static final String WINSCOPE_EXT = ".winscope";
+    private static final String TRACE_FILE =
+            "/data/misc/wmtrace/shell_transition_trace" + WINSCOPE_EXT;
+
+    private final Object mEnabledLock = new Object();
+    private boolean mActiveTracingEnabled = false;
+
+    private final TraceBuffer.ProtoProvider mProtoProvider =
+            new TraceBuffer.ProtoProvider<MessageNano,
+                    com.android.wm.shell.nano.WmShellTransitionTraceProto,
+                    com.android.wm.shell.nano.Transition>() {
+                @Override
+                public int getItemSize(MessageNano proto) {
+                    return proto.getCachedSize();
+                }
+
+                @Override
+                public byte[] getBytes(MessageNano proto) {
+                    return MessageNano.toByteArray(proto);
+                }
+
+                @Override
+                public void write(
+                        com.android.wm.shell.nano.WmShellTransitionTraceProto encapsulatingProto,
+                        Queue<com.android.wm.shell.nano.Transition> buffer, OutputStream os)
+                        throws IOException {
+                    encapsulatingProto.transitions = buffer.toArray(
+                            new com.android.wm.shell.nano.Transition[0]);
+                    os.write(getBytes(encapsulatingProto));
+                }
+            };
+    private final TraceBuffer<MessageNano,
+            com.android.wm.shell.nano.WmShellTransitionTraceProto,
+            com.android.wm.shell.nano.Transition> mTraceBuffer =
+                new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY, mProtoProvider,
+                        this::handleOnEntryRemovedFromTrace);
+    private final Map<Object, Runnable> mRemovedFromTraceCallbacks = new HashMap<>();
+
+    private final Map<Transitions.TransitionHandler, Integer> mHandlerIds = new HashMap<>();
+    private final Map<Transitions.TransitionHandler, Integer> mHandlerUseCountInTrace =
+            new HashMap<>();
+
+    /**
+     * Adds an entry in the trace to log that a transition has been dispatched to a handler.
+     *
+     * @param transitionId The id of the transition being dispatched.
+     * @param handler The handler the transition is being dispatched to.
+     */
+    @Override
+    public void logDispatched(int transitionId, Transitions.TransitionHandler handler) {
+        final int handlerId;
+        if (mHandlerIds.containsKey(handler)) {
+            handlerId = mHandlerIds.get(handler);
+        } else {
+            // + 1 to avoid 0 ids which can be confused with missing value when dumped to proto
+            handlerId = mHandlerIds.size() + 1;
+            mHandlerIds.put(handler, handlerId);
+        }
+
+        com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
+        proto.id = transitionId;
+        proto.dispatchTimeNs = SystemClock.elapsedRealtimeNanos();
+        proto.handler = handlerId;
+
+        final int useCountAfterAdd = mHandlerUseCountInTrace.getOrDefault(handler, 0) + 1;
+        mHandlerUseCountInTrace.put(handler, useCountAfterAdd);
+
+        mRemovedFromTraceCallbacks.put(proto, () -> {
+            final int useCountAfterRemove = mHandlerUseCountInTrace.get(handler) - 1;
+            mHandlerUseCountInTrace.put(handler, useCountAfterRemove);
+        });
+
+        mTraceBuffer.add(proto);
+    }
+
+    /**
+     * Adds an entry in the trace to log that a request to merge a transition was made.
+     *
+     * @param mergeRequestedTransitionId The id of the transition we are requesting to be merged.
+     */
+    @Override
+    public void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
+        com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
+        proto.id = mergeRequestedTransitionId;
+        proto.mergeRequestTimeNs = SystemClock.elapsedRealtimeNanos();
+        proto.mergeTarget = playingTransitionId;
+
+        mTraceBuffer.add(proto);
+    }
+
+    /**
+     * Adds an entry in the trace to log that a transition was merged by the handler.
+     *
+     * @param mergedTransitionId The id of the transition that was merged.
+     * @param playingTransitionId The id of the transition the transition was merged into.
+     */
+    @Override
+    public void logMerged(int mergedTransitionId, int playingTransitionId) {
+        com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
+        proto.id = mergedTransitionId;
+        proto.mergeTimeNs = SystemClock.elapsedRealtimeNanos();
+        proto.mergeTarget = playingTransitionId;
+
+        mTraceBuffer.add(proto);
+    }
+
+    /**
+     * Adds an entry in the trace to log that a transition was aborted.
+     *
+     * @param transitionId The id of the transition that was aborted.
+     */
+    @Override
+    public void logAborted(int transitionId) {
+        com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
+        proto.id = transitionId;
+        proto.abortTimeNs = SystemClock.elapsedRealtimeNanos();
+
+        mTraceBuffer.add(proto);
+    }
+
+    /**
+     * Starts collecting transitions for the trace.
+     * If called while a trace is already running, this will reset the trace.
+     */
+    public void startTrace(@Nullable PrintWriter pw) {
+        if (IS_USER) {
+            LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
+            return;
+        }
+        Trace.beginSection("Tracer#startTrace");
+        LogAndPrintln.i(pw, "Starting shell transition trace.");
+        synchronized (mEnabledLock) {
+            mActiveTracingEnabled = true;
+            mTraceBuffer.resetBuffer();
+            mTraceBuffer.setCapacity(ACTIVE_TRACING_BUFFER_CAPACITY);
+        }
+        Trace.endSection();
+    }
+
+    /**
+     * Stops collecting the transition trace and dump to trace to file.
+     *
+     * Dumps the trace to @link{TRACE_FILE}.
+     */
+    public void stopTrace(@Nullable PrintWriter pw) {
+        stopTrace(pw, new File(TRACE_FILE));
+    }
+
+    /**
+     * Stops collecting the transition trace and dump to trace to file.
+     * @param outputFile The file to dump the transition trace to.
+     */
+    public void stopTrace(@Nullable PrintWriter pw, File outputFile) {
+        if (IS_USER) {
+            LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
+            return;
+        }
+        Trace.beginSection("Tracer#stopTrace");
+        LogAndPrintln.i(pw, "Stopping shell transition trace.");
+        synchronized (mEnabledLock) {
+            mActiveTracingEnabled = false;
+            writeTraceToFileLocked(pw, outputFile);
+            mTraceBuffer.resetBuffer();
+            mTraceBuffer.setCapacity(ALWAYS_ON_TRACING_CAPACITY);
+        }
+        Trace.endSection();
+    }
+
+    /**
+     * Being called while taking a bugreport so that tracing files can be included in the bugreport.
+     *
+     * @param pw Print writer
+     */
+    public void saveForBugreport(@Nullable PrintWriter pw) {
+        if (IS_USER) {
+            LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
+            return;
+        }
+        Trace.beginSection("TransitionTracer#saveForBugreport");
+        synchronized (mEnabledLock) {
+            final File outputFile = new File(TRACE_FILE);
+            writeTraceToFileLocked(pw, outputFile);
+        }
+        Trace.endSection();
+    }
+
+    private void writeTraceToFileLocked(@Nullable PrintWriter pw, File file) {
+        Trace.beginSection("TransitionTracer#writeTraceToFileLocked");
+        try {
+            com.android.wm.shell.nano.WmShellTransitionTraceProto proto =
+                    new com.android.wm.shell.nano.WmShellTransitionTraceProto();
+            proto.magicNumber = MAGIC_NUMBER_VALUE;
+            writeHandlerMappingToProto(proto);
+            long timeOffsetNs =
+                    TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
+                            - SystemClock.elapsedRealtimeNanos();
+            proto.realToElapsedTimeOffsetNanos = timeOffsetNs;
+            int pid = android.os.Process.myPid();
+            LogAndPrintln.i(pw, "Writing file to " + file.getAbsolutePath()
+                    + " from process " + pid);
+            mTraceBuffer.writeTraceToFile(file, proto);
+        } catch (IOException e) {
+            LogAndPrintln.e(pw, "Unable to write buffer to file", e);
+        }
+        Trace.endSection();
+    }
+
+    private void writeHandlerMappingToProto(
+            com.android.wm.shell.nano.WmShellTransitionTraceProto proto) {
+        ArrayList<com.android.wm.shell.nano.HandlerMapping> handlerMappings = new ArrayList<>();
+        for (Transitions.TransitionHandler handler : mHandlerUseCountInTrace.keySet()) {
+            final int count = mHandlerUseCountInTrace.get(handler);
+            if (count > 0) {
+                com.android.wm.shell.nano.HandlerMapping mapping =
+                        new com.android.wm.shell.nano.HandlerMapping();
+                mapping.id = mHandlerIds.get(handler);
+                mapping.name = handler.getClass().getName();
+                handlerMappings.add(mapping);
+            }
+        }
+        proto.handlerMappings = handlerMappings.toArray(
+                new com.android.wm.shell.nano.HandlerMapping[0]);
+    }
+
+    private void handleOnEntryRemovedFromTrace(Object proto) {
+        if (mRemovedFromTraceCallbacks.containsKey(proto)) {
+            mRemovedFromTraceCallbacks.get(proto).run();
+            mRemovedFromTraceCallbacks.remove(proto);
+        }
+    }
+
+    @Override
+    public boolean onShellCommand(String[] args, PrintWriter pw) {
+        switch (args[0]) {
+            case "start": {
+                startTrace(pw);
+                return true;
+            }
+            case "stop": {
+                stopTrace(pw);
+                return true;
+            }
+            case "save-for-bugreport": {
+                saveForBugreport(pw);
+                return true;
+            }
+            default: {
+                pw.println("Invalid command: " + args[0]);
+                printShellCommandHelp(pw, "");
+                return false;
+            }
+        }
+    }
+
+    @Override
+    public void printShellCommandHelp(PrintWriter pw, String prefix) {
+        pw.println(prefix + "start");
+        pw.println(prefix + "  Start tracing the transitions.");
+        pw.println(prefix + "stop");
+        pw.println(prefix + "  Stop tracing the transitions.");
+        pw.println(prefix + "save-for-bugreport");
+        pw.println(prefix + "  Flush in memory transition trace to file.");
+    }
+
+    private static class LogAndPrintln {
+        private static final String LOG_TAG = "ShellTransitionTracer";
+
+        private static void i(@Nullable PrintWriter pw, String msg) {
+            Log.i(LOG_TAG, msg);
+            if (pw != null) {
+                pw.println(msg);
+                pw.flush();
+            }
+        }
+
+        private static void e(@Nullable PrintWriter pw, String msg) {
+            Log.e(LOG_TAG, msg);
+            if (pw != null) {
+                pw.println("ERROR: " + msg);
+                pw.flush();
+            }
+        }
+
+        private static void e(@Nullable PrintWriter pw, String msg, @NonNull Exception e) {
+            Log.e(LOG_TAG, msg, e);
+            if (pw != null) {
+                pw.println("ERROR: " + msg + " ::\n " + e);
+                pw.flush();
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
new file mode 100644
index 0000000..99df6a3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition.tracing;
+
+import android.internal.perfetto.protos.PerfettoTrace;
+import android.os.SystemClock;
+import android.tracing.perfetto.DataSourceInstance;
+import android.tracing.perfetto.DataSourceParams;
+import android.tracing.perfetto.InitArguments;
+import android.tracing.perfetto.Producer;
+import android.tracing.perfetto.TracingContext;
+import android.tracing.transition.TransitionDataSource;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Helper class to collect and dump transition traces.
+ */
+public class PerfettoTransitionTracer implements TransitionTracer {
+    private final AtomicInteger mActiveTraces = new AtomicInteger(0);
+    private final TransitionDataSource mDataSource = new TransitionDataSource(
+            mActiveTraces::incrementAndGet,
+            this::onFlush,
+            mActiveTraces::decrementAndGet);
+
+    public PerfettoTransitionTracer() {
+        Producer.init(InitArguments.DEFAULTS);
+        mDataSource.register(DataSourceParams.DEFAULTS);
+    }
+
+    /**
+     * Adds an entry in the trace to log that a transition has been dispatched to a handler.
+     *
+     * @param transitionId The id of the transition being dispatched.
+     * @param handler The handler the transition is being dispatched to.
+     */
+    @Override
+    public void logDispatched(int transitionId, Transitions.TransitionHandler handler) {
+        if (!isTracing()) {
+            return;
+        }
+
+        mDataSource.trace(ctx -> {
+            final int handlerId = getHandlerId(handler, ctx);
+
+            final ProtoOutputStream os = ctx.newTracePacket();
+            final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+            os.write(PerfettoTrace.ShellTransition.ID, transitionId);
+            os.write(PerfettoTrace.ShellTransition.DISPATCH_TIME_NS,
+                    SystemClock.elapsedRealtimeNanos());
+            os.write(PerfettoTrace.ShellTransition.HANDLER, handlerId);
+            os.end(token);
+        });
+    }
+
+    private static int getHandlerId(Transitions.TransitionHandler handler,
+            TracingContext<DataSourceInstance, TransitionDataSource.TlsState, Void> ctx) {
+        final Map<String, Integer> handlerMapping =
+                ctx.getCustomTlsState().handlerMapping;
+        final int handlerId;
+        if (handlerMapping.containsKey(handler.getClass().getName())) {
+            handlerId = handlerMapping.get(handler.getClass().getName());
+        } else {
+            // + 1 to avoid 0 ids which can be confused with missing value when dumped to proto
+            handlerId = handlerMapping.size() + 1;
+            handlerMapping.put(handler.getClass().getName(), handlerId);
+        }
+        return handlerId;
+    }
+
+    /**
+     * Adds an entry in the trace to log that a request to merge a transition was made.
+     *
+     * @param mergeRequestedTransitionId The id of the transition we are requesting to be merged.
+     */
+    @Override
+    public void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
+        if (!isTracing()) {
+            return;
+        }
+
+        mDataSource.trace(ctx -> {
+            final ProtoOutputStream os = ctx.newTracePacket();
+            final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+            os.write(PerfettoTrace.ShellTransition.ID, mergeRequestedTransitionId);
+            os.write(PerfettoTrace.ShellTransition.MERGE_REQUEST_TIME_NS,
+                    SystemClock.elapsedRealtimeNanos());
+            os.write(PerfettoTrace.ShellTransition.MERGE_TARGET, playingTransitionId);
+            os.end(token);
+        });
+    }
+
+    /**
+     * Adds an entry in the trace to log that a transition was merged by the handler.
+     *
+     * @param mergedTransitionId The id of the transition that was merged.
+     * @param playingTransitionId The id of the transition the transition was merged into.
+     */
+    @Override
+    public void logMerged(int mergedTransitionId, int playingTransitionId) {
+        if (!isTracing()) {
+            return;
+        }
+
+        mDataSource.trace(ctx -> {
+            final ProtoOutputStream os = ctx.newTracePacket();
+            final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+            os.write(PerfettoTrace.ShellTransition.ID, mergedTransitionId);
+            os.write(PerfettoTrace.ShellTransition.MERGE_TIME_NS,
+                    SystemClock.elapsedRealtimeNanos());
+            os.write(PerfettoTrace.ShellTransition.MERGE_TARGET, playingTransitionId);
+            os.end(token);
+        });
+    }
+
+    /**
+     * Adds an entry in the trace to log that a transition was aborted.
+     *
+     * @param transitionId The id of the transition that was aborted.
+     */
+    @Override
+    public void logAborted(int transitionId) {
+        if (!isTracing()) {
+            return;
+        }
+
+        mDataSource.trace(ctx -> {
+            final ProtoOutputStream os = ctx.newTracePacket();
+            final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+            os.write(PerfettoTrace.ShellTransition.ID, transitionId);
+            os.write(PerfettoTrace.ShellTransition.SHELL_ABORT_TIME_NS,
+                    SystemClock.elapsedRealtimeNanos());
+            os.end(token);
+        });
+    }
+
+    private boolean isTracing() {
+        return mActiveTraces.get() > 0;
+    }
+
+    private void onFlush() {
+        mDataSource.trace(ctx -> {
+            final ProtoOutputStream os = ctx.newTracePacket();
+
+            final Map<String, Integer> handlerMapping = ctx.getCustomTlsState().handlerMapping;
+            for (String handler : handlerMapping.keySet()) {
+                final long token = os.start(PerfettoTrace.TracePacket.SHELL_HANDLER_MAPPINGS);
+                os.write(PerfettoTrace.ShellHandlerMapping.ID, handlerMapping.get(handler));
+                os.write(PerfettoTrace.ShellHandlerMapping.NAME, handler);
+                os.end(token);
+            }
+
+            ctx.flush();
+        });
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java
new file mode 100644
index 0000000..5857ad8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition.tracing;
+
+import com.android.wm.shell.transition.Transitions;
+
+public interface TransitionTracer {
+    /**
+     * Adds an entry in the trace to log that a transition has been dispatched to a handler.
+     *
+     * @param transitionId The id of the transition being dispatched.
+     * @param handler The handler the transition is being dispatched to.
+     */
+    void logDispatched(int transitionId, Transitions.TransitionHandler handler);
+
+    /**
+     * Adds an entry in the trace to log that a request to merge a transition was made.
+     *
+     * @param mergeRequestedTransitionId The id of the transition we are requesting to be merged.
+     */
+    void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId);
+
+    /**
+     * Adds an entry in the trace to log that a transition was merged by the handler.
+     *
+     * @param mergedTransitionId The id of the transition that was merged.
+     * @param playingTransitionId The id of the transition the transition was merged into.
+     */
+    void logMerged(int mergedTransitionId, int playingTransitionId);
+
+    /**
+     * Adds an entry in the trace to log that a transition was aborted.
+     *
+     * @param transitionId The id of the transition that was aborted.
+     */
+    void logAborted(int transitionId);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
index d7cb490..e6d35e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
@@ -28,11 +28,11 @@
 import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
 import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
 
+import dagger.Lazy;
+
 import java.util.List;
 import java.util.Optional;
 
-import dagger.Lazy;
-
 /**
  * Manages fold/unfold animations of tasks on foldable devices.
  * When folding or unfolding a foldable device we play animations that
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 1a793a1..b2eeea7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -271,6 +271,9 @@
                     if (e.findPointerIndex(mDragPointerId) == -1) {
                         mDragPointerId = e.getPointerId(0);
                     }
+                    final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+                    // If a decor's resize drag zone is active, don't also try to reposition it.
+                    if (decoration.isHandlingDragResize()) break;
                     final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
                     mDragPositioningCallback.onDragPositioningMove(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 1debb02..96eaa1e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -93,7 +93,7 @@
 
         // On a smaller screen, don't require as much empty space on screen, as offscreen
         // drags will be restricted too much.
-        final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.taskId)
+        final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.displayId)
                 .getResources().getConfiguration().smallestScreenWidthDp >= 600
                 ? R.dimen.freeform_required_visible_empty_space_in_header :
                 R.dimen.small_screen_required_visible_empty_space_in_header;
@@ -286,6 +286,10 @@
         closeBackground.setTintList(buttonTintColor);
     }
 
+    boolean isHandlingDragResize() {
+        return mDragResizeListener != null && mDragResizeListener.isHandlingDragResize();
+    }
+
     private void closeDragResizeListener() {
         if (mDragResizeListener == null) {
             return;
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 d07c646..1f7cc5a 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
@@ -32,6 +32,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityTaskManager;
@@ -60,7 +61,6 @@
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -345,7 +345,7 @@
                 mTaskOperations.injectBackKey();
             } else if (id == R.id.caption_handle || id == R.id.open_menu_button) {
                 if (!decoration.isHandleMenuActive()) {
-                    moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
+                    moveTaskToFront(decoration.mTaskInfo);
                     decoration.createHandleMenu();
                 } else {
                     decoration.closeHandleMenu();
@@ -419,11 +419,11 @@
                     && id != R.id.maximize_window) {
                 return false;
             }
-            moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
+            final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+            moveTaskToFront(decoration.mTaskInfo);
 
-            if (!mHasLongClicked) {
-                final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
-                decoration.closeMaximizeMenu();
+            if (!mHasLongClicked && id != R.id.maximize_window) {
+                decoration.closeMaximizeMenuIfNeeded(e);
             }
 
             final long eventDuration = e.getEventTime() - e.getDownTime();
@@ -466,7 +466,8 @@
          */
         @Override
         public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
-            final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+            final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+            final RunningTaskInfo taskInfo = decoration.mTaskInfo;
             if (DesktopModeStatus.isEnabled()
                     && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
                 return false;
@@ -491,8 +492,9 @@
                     return true;
                 }
                 case MotionEvent.ACTION_MOVE: {
-                    final DesktopModeWindowDecoration decoration =
-                            mWindowDecorByTaskId.get(mTaskId);
+                    mShouldClick = false;
+                    // If a decor's resize drag zone is active, don't also try to reposition it.
+                    if (decoration.isHandlingDragResize()) break;
                     decoration.closeMaximizeMenu();
                     if (e.findPointerIndex(mDragPointerId) == -1) {
                         mDragPointerId = e.getPointerId(0);
@@ -502,10 +504,9 @@
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
                             decoration.mTaskSurface,
-                            new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
+                            e.getRawX(dragPointerIdx),
                             newTaskBounds));
                     mIsDragging = true;
-                    mShouldClick = false;
                     return true;
                 }
                 case MotionEvent.ACTION_UP:
@@ -542,11 +543,22 @@
             return true;
         }
 
+        /**
+         * Perform a task size toggle on release of the double-tap, assuming no drag event
+         * was handled during the double-tap.
+         * @param e The motion event that occurred during the double-tap gesture.
+         * @return true if the event should be consumed, false if not
+         */
         @Override
-        public boolean onDoubleTap(@NonNull MotionEvent e) {
-            final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+        public boolean onDoubleTapEvent(@NonNull MotionEvent e) {
+            final int action = e.getActionMasked();
+            if (mIsDragging || (action != MotionEvent.ACTION_UP
+                    && action != MotionEvent.ACTION_CANCEL)) {
+                return false;
+            }
             mDesktopTasksController.ifPresent(c -> {
-                c.toggleDesktopTaskSize(taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId));
+                final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+                c.toggleDesktopTaskSize(decoration.mTaskInfo, decoration);
             });
             return true;
         }
@@ -643,7 +655,7 @@
                 handleCaptionThroughStatusBar(ev, relevantDecor);
             }
         }
-        handleEventOutsideFocusedCaption(ev, relevantDecor);
+        handleEventOutsideCaption(ev, relevantDecor);
         // Prevent status bar from reacting to a caption drag.
         if (DesktopModeStatus.isEnabled()) {
             if (mTransitionDragActive) {
@@ -652,11 +664,17 @@
         }
     }
 
-    // If an UP/CANCEL action is received outside of caption bounds, turn off handle menu
-    private void handleEventOutsideFocusedCaption(MotionEvent ev,
+    /**
+     * If an UP/CANCEL action is received outside of the caption bounds, close the handle and
+     * maximize the menu.
+     *
+     * @param relevantDecor the window decoration of the focused task's caption. This method only
+     *                      handles motion events outside this caption's bounds.
+     */
+    private void handleEventOutsideCaption(MotionEvent ev,
             DesktopModeWindowDecoration relevantDecor) {
         // Returns if event occurs within caption
-        if (relevantDecor == null || relevantDecor.checkTouchEventInCaptionHandle(ev)) {
+        if (relevantDecor == null || relevantDecor.checkTouchEventInCaption(ev)) {
             return;
         }
 
@@ -692,7 +710,7 @@
                     }
 
                     if (dragFromStatusBarAllowed
-                            && relevantDecor.checkTouchEventInCaptionHandle(ev)) {
+                            && relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)) {
                         mTransitionDragActive = true;
                     }
                 }
@@ -731,9 +749,9 @@
                 }
                 if (mTransitionDragActive) {
                     mDesktopTasksController.ifPresent(
-                            c -> c.onDragPositioningMoveThroughStatusBar(
+                            c -> c.updateVisualIndicator(
                                     relevantDecor.mTaskInfo,
-                                    relevantDecor.mTaskSurface, ev.getY()));
+                                    relevantDecor.mTaskSurface, ev.getX(), ev.getY()));
                     final int statusBarHeight = getStatusBarHeight(
                             relevantDecor.mTaskInfo.displayId);
                     if (ev.getY() > statusBarHeight) {
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 5f77192..3f0a281 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
@@ -36,7 +36,6 @@
 import android.graphics.Region;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
-import android.util.Log;
 import android.view.Choreographer;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
@@ -387,24 +386,21 @@
         return mHandleMenu != null;
     }
 
+    boolean isHandlingDragResize() {
+        return mDragResizeListener != null && mDragResizeListener.isHandlingDragResize();
+    }
+
     private void loadAppInfo() {
-        String packageName = mTaskInfo.realActivity.getPackageName();
         PackageManager pm = mContext.getApplicationContext().getPackageManager();
-        try {
-            final IconProvider provider = new IconProvider(mContext);
-            mAppIconDrawable = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity,
-                    PackageManager.ComponentInfoFlags.of(0)));
-            final Resources resources = mContext.getResources();
-            final BaseIconFactory factory = new BaseIconFactory(mContext,
-                    resources.getDisplayMetrics().densityDpi,
-                    resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius));
-            mAppIconBitmap = factory.createScaledBitmap(mAppIconDrawable, MODE_DEFAULT);
-            final ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
-                    PackageManager.ApplicationInfoFlags.of(0));
-            mAppName = pm.getApplicationLabel(applicationInfo);
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.w(TAG, "Package not found: " + packageName, e);
-        }
+        final IconProvider provider = new IconProvider(mContext);
+        mAppIconDrawable = provider.getIcon(mTaskInfo.topActivityInfo);
+        final Resources resources = mContext.getResources();
+        final BaseIconFactory factory = new BaseIconFactory(mContext,
+                resources.getDisplayMetrics().densityDpi,
+                resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius));
+        mAppIconBitmap = factory.createScaledBitmap(mAppIconDrawable, MODE_DEFAULT);
+        final ApplicationInfo applicationInfo = mTaskInfo.topActivityInfo.applicationInfo;
+        mAppName = pm.getApplicationLabel(applicationInfo);
     }
 
     private void closeDragResizeListener() {
@@ -612,8 +608,7 @@
     void closeMaximizeMenuIfNeeded(MotionEvent ev) {
         if (!isMaximizeMenuActive()) return;
 
-        final PointF inputPoint = offsetCaptionLocation(ev);
-        if (!mMaximizeMenu.isValidMenuInput(inputPoint)) {
+        if (!mMaximizeMenu.isValidMenuInput(ev)) {
             closeMaximizeMenu();
         }
     }
@@ -639,20 +634,34 @@
     }
 
     /**
-     * Checks if motion event occurs in the caption handle area. This should be used in cases where
+     * Checks if motion event occurs in the caption handle area of a focused caption (the caption on
+     * a task in fullscreen or in multi-windowing mode). This should be used in cases where
      * onTouchListener will not work (i.e. when caption is in status bar area).
      *
      * @param ev       the {@link MotionEvent} to check
-     * @return {@code true} if event is inside the specified view, {@code false} if not
+     * @return {@code true} if event is inside caption handle view, {@code false} if not
      */
-    boolean checkTouchEventInCaptionHandle(MotionEvent ev) {
+    boolean checkTouchEventInFocusedCaptionHandle(MotionEvent ev) {
         if (isHandleMenuActive() || !(mWindowDecorViewHolder
                 instanceof DesktopModeFocusedWindowDecorationViewHolder)) {
             return false;
         }
+
+        return checkTouchEventInCaption(ev);
+    }
+
+    /**
+     * Checks if touch event occurs in caption.
+     *
+     * @param ev       the {@link MotionEvent} to check
+     * @return {@code true} if event is inside caption view, {@code false} if not
+     */
+    boolean checkTouchEventInCaption(MotionEvent ev) {
         final PointF inputPoint = offsetCaptionLocation(ev);
-        return ((DesktopModeFocusedWindowDecorationViewHolder) mWindowDecorViewHolder)
-                .pointInCaption(inputPoint, mResult.mCaptionX);
+        return inputPoint.x >= mResult.mCaptionX
+                && inputPoint.x <= mResult.mCaptionX + mResult.mCaptionWidth
+                && inputPoint.y >= 0
+                && inputPoint.y <= mResult.mCaptionHeight;
     }
 
     /**
@@ -668,7 +677,7 @@
             // Click if point in caption handle view
             final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
             final View handle = caption.findViewById(R.id.caption_handle);
-            if (checkTouchEventInCaptionHandle(ev)) {
+            if (checkTouchEventInFocusedCaptionHandle(ev)) {
                 mOnCaptionButtonClickListener.onClick(handle);
             }
         } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 8511a21..d902621 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -320,6 +320,10 @@
         }
     }
 
+    boolean isHandlingDragResize() {
+        return mInputEventReceiver.isHandlingEvents();
+    }
+
     @Override
     public void close() {
         mInputEventReceiver.dispose();
@@ -349,6 +353,7 @@
         private boolean mShouldHandleEvents;
         private int mLastCursorType = PointerIcon.TYPE_DEFAULT;
         private Rect mDragStartTaskBounds;
+        private final Rect mTmpRect = new Rect();
 
         private TaskResizeInputEventReceiver(
                 InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -386,6 +391,10 @@
             finishInputEvent(inputEvent, handleInputEvent(inputEvent));
         }
 
+        boolean isHandlingEvents() {
+            return mShouldHandleEvents;
+        }
+
         private boolean handleInputEvent(InputEvent inputEvent) {
             if (!(inputEvent instanceof MotionEvent)) {
                 return false;
@@ -409,7 +418,6 @@
                         mShouldHandleEvents = isInResizeHandleBounds(x, y);
                     }
                     if (mShouldHandleEvents) {
-                        mInputManager.pilferPointers(mInputChannel.getToken());
                         mDragPointerId = e.getPointerId(0);
                         float rawX = e.getRawX(0);
                         float rawY = e.getRawY(0);
@@ -427,6 +435,7 @@
                     if (!mShouldHandleEvents) {
                         break;
                     }
+                    mInputManager.pilferPointers(mInputChannel.getToken());
                     int dragPointerIndex = e.findPointerIndex(mDragPointerId);
                     float rawX = e.getRawX(dragPointerIndex);
                     float rawY = e.getRawY(dragPointerIndex);
@@ -437,6 +446,7 @@
                 }
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL: {
+                    mInputManager.pilferPointers(mInputChannel.getToken());
                     if (mShouldHandleEvents) {
                         int dragPointerIndex = e.findPointerIndex(mDragPointerId);
                         final Rect taskBounds = mCallback.onDragPositioningEnd(
@@ -468,14 +478,15 @@
         }
 
         private void updateInputSinkRegionForDrag(Rect taskBounds) {
+            mTmpRect.set(taskBounds);
             final DisplayLayout layout = mDisplayController.getDisplayLayout(mDisplayId);
             final Region dragTouchRegion = new Region(-taskBounds.left,
                     -taskBounds.top,
                     -taskBounds.left + layout.width(),
                     -taskBounds.top + layout.height());
             // Remove the localized task bounds from the touch region.
-            taskBounds.offsetTo(0, 0);
-            dragTouchRegion.op(taskBounds, Region.Op.DIFFERENCE);
+            mTmpRect.offsetTo(0, 0);
+            dragTouchRegion.op(mTmpRect, Region.Op.DIFFERENCE);
             updateSinkInputChannel(dragTouchRegion);
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 921708f..794b357 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -22,10 +22,10 @@
 import android.graphics.PixelFormat
 import android.graphics.PointF
 import android.view.LayoutInflater
+import android.view.MotionEvent
 import android.view.SurfaceControl
 import android.view.SurfaceControl.Transaction
 import android.view.SurfaceControlViewHost
-import android.view.View
 import android.view.View.OnClickListener
 import android.view.WindowManager
 import android.view.WindowlessWindowManager
@@ -62,6 +62,8 @@
     private val cornerRadius = loadDimensionPixelSize(
             R.dimen.desktop_mode_maximize_menu_corner_radius
     ).toFloat()
+    private val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_width)
+    private val menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_height)
 
     /** Position the menu relative to the caption's position. */
     fun positionMenu(position: PointF, t: Transaction) {
@@ -95,8 +97,6 @@
                 .setName("Maximize Menu")
                 .setContainerLayer()
                 .build()
-        val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_width)
-        val menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_height)
         val lp = WindowManager.LayoutParams(
                 menuWidth,
                 menuHeight,
@@ -160,14 +160,11 @@
      *
      * @param inputPoint the input to compare against.
      */
-    fun isValidMenuInput(inputPoint: PointF): Boolean {
-        val menuView = maximizeMenu?.mWindowViewHost?.view ?: return true
-        return !viewsLaidOut() || pointInView(menuView, inputPoint.x - menuPosition.x,
-                inputPoint.y - menuPosition.y)
-    }
-
-    private fun pointInView(v: View, x: Float, y: Float): Boolean {
-        return v.left <= x && v.right >= x && v.top <= y && v.bottom >= y
+    fun isValidMenuInput(ev: MotionEvent): Boolean {
+        val x = ev.rawX
+        val y = ev.rawY
+        return !viewsLaidOut() || (menuPosition.x <= x && menuPosition.x + menuWidth >= x &&
+                menuPosition.y <= y && menuPosition.y + menuHeight >= y)
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
index 368231e..b0d3b50 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
@@ -125,6 +125,7 @@
 
         relayout(taskBounds, t);
         if (fadeIn) {
+            cancelAnimation();
             mVeilAnimator = new ValueAnimator();
             mVeilAnimator.setFloatValues(0f, 1f);
             mVeilAnimator.setDuration(RESIZE_ALPHA_DURATION);
@@ -210,15 +211,16 @@
      * Animate veil's alpha to 0, fading it out.
      */
     public void hideVeil() {
-        final ValueAnimator animator = new ValueAnimator();
-        animator.setFloatValues(1, 0);
-        animator.setDuration(RESIZE_ALPHA_DURATION);
-        animator.addUpdateListener(animation -> {
+        cancelAnimation();
+        mVeilAnimator = new ValueAnimator();
+        mVeilAnimator.setFloatValues(1, 0);
+        mVeilAnimator.setDuration(RESIZE_ALPHA_DURATION);
+        mVeilAnimator.addUpdateListener(animation -> {
             SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
-            t.setAlpha(mVeilSurface, 1 - animator.getAnimatedFraction());
+            t.setAlpha(mVeilSurface, 1 - mVeilAnimator.getAnimatedFraction());
             t.apply();
         });
-        animator.addListener(new AnimatorListenerAdapter() {
+        mVeilAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
                 SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
@@ -226,7 +228,7 @@
                 t.apply();
             }
         });
-        animator.start();
+        mVeilAnimator.start();
     }
 
     @ColorRes
@@ -240,10 +242,20 @@
         }
     }
 
+    private void cancelAnimation() {
+        if (mVeilAnimator != null) {
+            mVeilAnimator.removeAllUpdateListeners();
+            mVeilAnimator.cancel();
+        }
+    }
+
     /**
      * Dispose of veil when it is no longer needed, likely on close of its container decor.
      */
     void dispose() {
+        cancelAnimation();
+        mVeilAnimator = null;
+
         if (mViewHost != null) {
             mViewHost.release();
             mViewHost = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index c1b18f9..7c6e69e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -61,6 +61,7 @@
     private int mCtrlType;
     private boolean mIsResizingOrAnimatingResize;
     @Surface.Rotation private int mRotation;
+    private boolean mVeilIsVisible;
 
     public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
             DesktopModeWindowDecoration windowDecoration,
@@ -94,7 +95,6 @@
                 mDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
         mRepositionStartPoint.set(x, y);
         if (isResizing()) {
-            mDesktopWindowDecoration.showResizeVeil(mTaskBoundsAtDragStart);
             if (!mDesktopWindowDecoration.mTaskInfo.isFocused) {
                 WindowContainerTransaction wct = new WindowContainerTransaction();
                 wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true);
@@ -119,8 +119,13 @@
         if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType,
                 mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta,
                 mDisplayController, mDesktopWindowDecoration)) {
-            mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
             mIsResizingOrAnimatingResize = true;
+            if (!mVeilIsVisible) {
+                mDesktopWindowDecoration.showResizeVeil(mRepositionTaskBounds);
+                mVeilIsVisible = true;
+            } else {
+                mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
+            }
         } else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
             final SurfaceControl.Transaction t = mTransactionSupplier.get();
             DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
@@ -143,7 +148,7 @@
                 final WindowContainerTransaction wct = new WindowContainerTransaction();
                 wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
                 mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
-            } else {
+            } else if (mVeilIsVisible) {
                 // If bounds haven't changed, perform necessary veil reset here as startAnimation
                 // won't be called.
                 mDesktopWindowDecoration.hideResizeVeil();
@@ -163,6 +168,7 @@
         mCtrlType = CTRL_TYPE_UNDEFINED;
         mTaskBoundsAtDragStart.setEmpty();
         mRepositionStartPoint.set(0, 0);
+        mVeilIsVisible = false;
         return new Rect(mRepositionTaskBounds);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 6a9258c..afe837e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -279,11 +279,12 @@
         }
 
         outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
-        final int captionWidth = params.mCaptionWidthId != Resources.ID_NULL
+        outResult.mCaptionWidth = params.mCaptionWidthId != Resources.ID_NULL
                 ? loadDimensionPixelSize(resources, params.mCaptionWidthId) : taskBounds.width();
-        outResult.mCaptionX = (outResult.mWidth - captionWidth) / 2;
+        outResult.mCaptionX = (outResult.mWidth - outResult.mCaptionWidth) / 2;
 
-        startT.setWindowCrop(mCaptionContainerSurface, captionWidth, outResult.mCaptionHeight)
+        startT.setWindowCrop(mCaptionContainerSurface, outResult.mCaptionWidth,
+                        outResult.mCaptionHeight)
                 .setPosition(mCaptionContainerSurface, outResult.mCaptionX, 0 /* y */)
                 .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
                 .show(mCaptionContainerSurface);
@@ -356,7 +357,7 @@
         // Caption view
         mCaptionWindowManager.setConfiguration(taskConfig);
         final WindowManager.LayoutParams lp =
-                new WindowManager.LayoutParams(captionWidth, outResult.mCaptionHeight,
+                new WindowManager.LayoutParams(outResult.mCaptionWidth, outResult.mCaptionHeight,
                         WindowManager.LayoutParams.TYPE_APPLICATION,
                         WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
         lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
@@ -578,6 +579,7 @@
 
     static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
         int mCaptionHeight;
+        int mCaptionWidth;
         int mCaptionX;
         int mWidth;
         int mHeight;
@@ -587,6 +589,7 @@
             mWidth = 0;
             mHeight = 0;
             mCaptionHeight = 0;
+            mCaptionWidth = 0;
             mCaptionX = 0;
             mRootView = null;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
index 5f77022..6dcae27 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
@@ -5,7 +5,6 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.content.res.ColorStateList
 import android.graphics.Color
-import android.graphics.PointF
 import android.view.View
 import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
 import android.widget.ImageButton
@@ -47,17 +46,6 @@
         animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
     }
 
-    /**
-     * Returns true if input point is in the caption's view.
-     * @param inputPoint the input point relative to the task in full "focus" (i.e. fullscreen).
-     */
-    fun pointInCaption(inputPoint: PointF, captionX: Int): Boolean {
-        return inputPoint.x >= captionX &&
-                inputPoint.x <= captionX + captionView.width &&
-                inputPoint.y >= 0 &&
-                inputPoint.y <= captionView.height
-    }
-
     private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
         return if (shouldUseLightCaptionColors(taskInfo)) {
             context.getColor(R.color.desktop_mode_caption_handle_bar_light)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
index e61f762..faeb342 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
@@ -30,6 +30,7 @@
         "src/**/B*.kt",
         "src/**/C*.kt",
         "src/**/D*.kt",
+        "src/**/F*.kt",
         "src/**/S*.kt",
     ],
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
deleted file mode 100644
index a5c2c89..0000000
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import android.platform.test.annotations.Presubmit
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import android.tools.device.helpers.WindowUtils
-import android.tools.device.traces.parsers.toFlickerComponent
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.wm.shell.flicker.utils.SplitScreenUtils
-import org.junit.Assume
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test entering pip from an app via auto-enter property when navigating to home from split screen.
- *
- * To run this test: `atest WMShellFlickerTests:AutoEnterPipOnGoToHomeTest`
- *
- * Actions:
- * ```
- *     Launch an app in full screen
- *     Select "Auto-enter PiP" radio button
- *     Open all apps and drag another app icon to enter split screen
- *     Press Home button or swipe up to go Home and put [pipApp] in pip mode
- * ```
- *
- * Notes:
- * ```
- *     1. All assertions are inherited from [EnterPipTest]
- *     2. Part of the test setup occurs automatically via
- *        [android.tools.device.flicker.legacy.runner.TransitionRunner],
- *        including configuring navigation mode, initial orientation and ensuring no
- *        apps are running before setup
- * ```
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) :
-    AutoEnterPipOnGoToHomeTest(flicker) {
-    private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
-    /** Second app used to enter split screen mode */
-    private val secondAppForSplitScreen =
-        SimpleAppHelper(
-            instrumentation,
-            ActivityOptions.SplitScreen.Primary.LABEL,
-            ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
-        )
-
-    /** Defines the transition used to run the test */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            setup {
-                secondAppForSplitScreen.launchViaIntent(wmHelper)
-                pipApp.launchViaIntent(wmHelper)
-                tapl.goHome()
-                SplitScreenUtils.enterSplit(
-                    wmHelper,
-                    tapl,
-                    device,
-                    pipApp,
-                    secondAppForSplitScreen,
-                    flicker.scenario.startRotation
-                )
-                pipApp.enableAutoEnterForPipActivity()
-            }
-            teardown {
-                pipApp.exit(wmHelper)
-                secondAppForSplitScreen.exit(wmHelper)
-            }
-            transitions { tapl.goHome() }
-        }
-
-    @Presubmit
-    @Test
-    override fun pipOverlayLayerAppearThenDisappear() {
-        // when entering from split screen we use alpha animation, without overlay
-    }
-
-    @Presubmit
-    @Test
-    override fun pipLayerOrOverlayRemainInsideVisibleBounds() {
-        // when entering from split screen we use alpha animation, without overlay
-    }
-
-    @Presubmit
-    @Test
-    override fun pipLayerReduces() {
-        // when entering from split screen we use alpha animation, so there is no size change
-        Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
-        super.pipLayerReduces()
-    }
-
-    @Presubmit
-    @Test
-    override fun pipAppLayerAlwaysVisible() {
-        // pip layer in gesture nav will disappear during transition with alpha animation
-        Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
-        super.pipAppLayerAlwaysVisible()
-    }
-
-    @Presubmit
-    @Test
-    override fun pipWindowRemainInsideVisibleBounds() {
-        if (tapl.isTablet) {
-            flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
-        } else {
-            // on phones home screen does not rotate in landscape, PiP enters back to portrait
-            // orientation - if we go from landscape to portrait it should switch between the bounds
-            // otherwise it should be the same as tablet (i.e. portrait to portrait)
-            if (flicker.scenario.isLandscapeOrSeascapeAtStart) {
-                flicker.assertWmVisibleRegion(pipApp) {
-                    // first check against landscape bounds then against portrait bounds
-                    coversAtMost(displayBounds).then().coversAtMost(portraitDisplayBounds)
-                }
-            } else {
-                // always check against the display bounds which do not change during transition
-                flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
-            }
-        }
-    }
-
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams() =
-            LegacyFlickerTestFactory.nonRotationTests(
-                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
-                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
-            )
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
new file mode 100644
index 0000000..2792298
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.device.helpers.WindowUtils
+import android.tools.device.traces.parsers.toFlickerComponent
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test entering pip from an app via auto-enter property when navigating to home from split screen.
+ *
+ * To run this test: `atest WMShellFlickerTests:AutoEnterPipOnGoToHomeTest`
+ *
+ * Actions:
+ * ```
+ *     Launch an app in full screen
+ *     Select "Auto-enter PiP" radio button
+ *     Open all apps and drag another app icon to enter split screen
+ *     Press Home button or swipe up to go Home and put [pipApp] in pip mode
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. All assertions are inherited from [EnterPipTest]
+ *     2. Part of the test setup occurs automatically via
+ *        [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ *        including configuring navigation mode, initial orientation and ensuring no
+ *        apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class FromSplitScreenAutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) :
+    AutoEnterPipOnGoToHomeTest(flicker) {
+    private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
+    /** Second app used to enter split screen mode */
+    private val secondAppForSplitScreen =
+        SimpleAppHelper(
+            instrumentation,
+            ActivityOptions.SplitScreen.Primary.LABEL,
+            ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
+        )
+
+    /** Defines the transition used to run the test */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                secondAppForSplitScreen.launchViaIntent(wmHelper)
+                pipApp.launchViaIntent(wmHelper)
+                tapl.goHome()
+                SplitScreenUtils.enterSplit(
+                    wmHelper,
+                    tapl,
+                    device,
+                    pipApp,
+                    secondAppForSplitScreen,
+                    flicker.scenario.startRotation
+                )
+                pipApp.enableAutoEnterForPipActivity()
+            }
+            teardown {
+                pipApp.exit(wmHelper)
+                secondAppForSplitScreen.exit(wmHelper)
+            }
+            transitions { tapl.goHome() }
+        }
+
+    @Presubmit
+    @Test
+    override fun pipOverlayLayerAppearThenDisappear() {
+        // when entering from split screen we use alpha animation, without overlay
+    }
+
+    @Presubmit
+    @Test
+    override fun pipLayerOrOverlayRemainInsideVisibleBounds() {
+        // when entering from split screen we use alpha animation, without overlay
+    }
+
+    @Presubmit
+    @Test
+    override fun pipLayerReduces() {
+        // when entering from split screen we use alpha animation, so there is no size change
+        Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+        super.pipLayerReduces()
+    }
+
+    @Presubmit
+    @Test
+    override fun pipAppLayerAlwaysVisible() {
+        // pip layer in should disappear during transition with alpha animation
+    }
+
+    @Presubmit
+    @Test
+    override fun pipWindowRemainInsideVisibleBounds() {
+        if (tapl.isTablet) {
+            flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+        } else {
+            // on phones home screen does not rotate in landscape, PiP enters back to portrait
+            // orientation - if we go from landscape to portrait it should switch between the bounds
+            // otherwise it should be the same as tablet (i.e. portrait to portrait)
+            if (flicker.scenario.isLandscapeOrSeascapeAtStart) {
+                flicker.assertWmVisibleRegion(pipApp) {
+                    // first check against landscape bounds then against portrait bounds
+                    coversAtMost(displayBounds).then().coversAtMost(portraitDisplayBounds)
+                }
+            } else {
+                // always check against the display bounds which do not change during transition
+                flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+            }
+        }
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(Rotation.ROTATION_0)
+            )
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
new file mode 100644
index 0000000..4c23153
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.device.helpers.WindowUtils
+import android.tools.device.traces.parsers.toFlickerComponent
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.pip.common.EnterPipTransition
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test entering pip from an app via auto-enter property when navigating to home from split screen.
+ *
+ * To run this test: `atest WMShellFlickerTests:AutoEnterPipOnGoToHomeTest`
+ *
+ * Actions:
+ * ```
+ *     Launch an app in full screen
+ *     Select "Auto-enter PiP" radio button
+ *     Open all apps and drag another app icon to enter split screen
+ *     Press Home button or swipe up to go Home and put [pipApp] in pip mode
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. All assertions are inherited from [EnterPipTest]
+ *     2. Part of the test setup occurs automatically via
+ *        [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ *        including configuring navigation mode, initial orientation and ensuring no
+ *        apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class FromSplitScreenEnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) :
+    EnterPipTransition(flicker) {
+    private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
+    /** Second app used to enter split screen mode */
+    private val secondAppForSplitScreen =
+        SimpleAppHelper(
+            instrumentation,
+            ActivityOptions.SplitScreen.Primary.LABEL,
+            ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
+        )
+
+    /** Defines the transition used to run the test */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                secondAppForSplitScreen.launchViaIntent(wmHelper)
+                pipApp.launchViaIntent(wmHelper)
+                tapl.goHome()
+                SplitScreenUtils.enterSplit(
+                    wmHelper,
+                    tapl,
+                    device,
+                    pipApp,
+                    secondAppForSplitScreen,
+                    flicker.scenario.startRotation
+                )
+                pipApp.enableEnterPipOnUserLeaveHint()
+            }
+            teardown {
+                pipApp.exit(wmHelper)
+                secondAppForSplitScreen.exit(wmHelper)
+            }
+            transitions { tapl.goHome() }
+        }
+
+    @Presubmit
+    @Test
+    override fun pipOverlayLayerAppearThenDisappear() {
+        // when entering from split screen we use alpha animation, without overlay
+    }
+
+    @Presubmit
+    @Test
+    override fun pipLayerOrOverlayRemainInsideVisibleBounds() {
+        // when entering from split screen we use alpha animation, without overlay
+    }
+
+    @Presubmit
+    @Test
+    override fun pipLayerReduces() {
+        // when entering from split screen we use alpha animation, so there is no size change
+        Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+        super.pipLayerReduces()
+    }
+
+    @Presubmit
+    @Test
+    override fun pipAppLayerAlwaysVisible() {
+        // pip layer in should disappear during transition with alpha animation
+    }
+
+    @Presubmit
+    @Test
+    override fun focusChanges() {
+        // in gestural nav the focus goes to different activity on swipe up
+        Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+        super.focusChanges()
+    }
+
+    @Presubmit
+    @Test
+    fun pipAppWindowVisibleChanges() {
+        // TODO(b/322394235) this method comes from EnterPipOnUserLeaveHintTest, but due to how
+        // it is being packaged in Android.bp we cannot inherit from it. Needs to be refactored.
+        Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+        flicker.assertWm {
+            this.isAppWindowVisible(pipApp)
+                .then()
+                .isAppWindowInvisible(pipApp, isOptional = true)
+                .then()
+                .isAppWindowVisible(pipApp, isOptional = true)
+        }
+    }
+
+    @Presubmit
+    @Test
+    override fun pipAppWindowAlwaysVisible() {
+        // TODO(b/322394235) this method comes from EnterPipOnUserLeaveHintTest, but due to how
+        // it is being packaged in Android.bp we cannot inherit from it. Needs to be refactored.
+        // In gestural nav the pip will first move behind home and then above home. The visual
+        // appearance visible->invisible->visible is asserted by pipAppLayerAlwaysVisible().
+        // But the internal states of activity don't need to follow that, such as a temporary
+        // visibility state can be changed quickly outside a transaction so the test doesn't
+        // detect that. Hence, skip the case to avoid restricting the internal implementation.
+        Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+        super.pipAppWindowAlwaysVisible()
+    }
+
+    @Presubmit
+    @Test
+    override fun pipWindowRemainInsideVisibleBounds() {
+        if (tapl.isTablet) {
+            flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+        } else {
+            // on phones home screen does not rotate in landscape, PiP enters back to portrait
+            // orientation - if we go from landscape to portrait it should switch between the bounds
+            // otherwise it should be the same as tablet (i.e. portrait to portrait)
+            if (flicker.scenario.isLandscapeOrSeascapeAtStart) {
+                flicker.assertWmVisibleRegion(pipApp) {
+                    // first check against landscape bounds then against portrait bounds
+                    coversAtMost(displayBounds).then().coversAtMost(portraitDisplayBounds)
+                }
+            } else {
+                // always check against the display bounds which do not change during transition
+                flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+            }
+        }
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests(
+            supportedRotations = listOf(Rotation.ROTATION_0)
+        )
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
index 182a908..be77171 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
@@ -101,7 +101,8 @@
     override fun pipLayerReduces() {
         flicker.assertLayers {
             val pipLayerList =
-                this.layers { standardAppHelper.layerMatchesAnyOf(it) && it.isVisible }
+                this.layers { standardAppHelper.packageNameMatcher.layerMatchesAnyOf(it)
+                        && it.isVisible }
             pipLayerList.zipWithNext { previous, current ->
                 current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
             }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
index 47bff8d..0d18535 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
@@ -78,6 +78,14 @@
             uiAutomation.dropShellPermissionIdentity()
         }
 
+        override fun onProcessStarted(
+            pid: Int,
+            processUid: Int,
+            packageUid: Int,
+            packageName: String,
+            processName: String
+        ) {}
+
         override fun onForegroundActivitiesChanged(pid: Int, uid: Int, foreground: Boolean) {}
 
         override fun onForegroundServicesChanged(pid: Int, uid: Int, serviceTypes: Int) {}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
deleted file mode 100644
index 6ebee73..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
+++ /dev/null
@@ -1,602 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.bubbles;
-
-import static com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
-
-import static org.mockito.Mockito.mock;
-
-import android.content.Intent;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Insets;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.os.UserHandle;
-import android.testing.AndroidTestingRunner;
-import android.view.WindowManager;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.R;
-import com.android.wm.shell.ShellTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests operations and the resulting state managed by {@link BubblePositioner}.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class BubblePositionerTest extends ShellTestCase {
-
-    private BubblePositioner mPositioner;
-
-    @Before
-    public void setUp() {
-        WindowManager windowManager = mContext.getSystemService(WindowManager.class);
-        mPositioner = new BubblePositioner(mContext, windowManager);
-    }
-
-    @Test
-    public void testUpdate() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1000, 1200);
-        Rect availableRect = new Rect(screenBounds);
-        availableRect.inset(insets);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        assertThat(mPositioner.getAvailableRect()).isEqualTo(availableRect);
-        assertThat(mPositioner.isLandscape()).isFalse();
-        assertThat(mPositioner.isLargeScreen()).isFalse();
-        assertThat(mPositioner.getInsets()).isEqualTo(insets);
-    }
-
-    @Test
-    public void testShowBubblesVertically_phonePortrait() {
-        DeviceConfig deviceConfig = new ConfigBuilder().build();
-        mPositioner.update(deviceConfig);
-
-        assertThat(mPositioner.showBubblesVertically()).isFalse();
-    }
-
-    @Test
-    public void testShowBubblesVertically_phoneLandscape() {
-        DeviceConfig deviceConfig = new ConfigBuilder().setLandscape().build();
-        mPositioner.update(deviceConfig);
-
-        assertThat(mPositioner.isLandscape()).isTrue();
-        assertThat(mPositioner.showBubblesVertically()).isTrue();
-    }
-
-    @Test
-    public void testShowBubblesVertically_tablet() {
-        DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().build();
-        mPositioner.update(deviceConfig);
-
-        assertThat(mPositioner.showBubblesVertically()).isTrue();
-    }
-
-    /** If a resting position hasn't been set, calling it will return the default position. */
-    @Test
-    public void testGetRestingPosition_returnsDefaultPosition() {
-        DeviceConfig deviceConfig = new ConfigBuilder().build();
-        mPositioner.update(deviceConfig);
-
-        PointF restingPosition = mPositioner.getRestingPosition();
-        PointF defaultPosition = mPositioner.getDefaultStartPosition();
-
-        assertThat(restingPosition).isEqualTo(defaultPosition);
-    }
-
-    /** If a resting position has been set, it'll return that instead of the default position. */
-    @Test
-    public void testGetRestingPosition_returnsRestingPosition() {
-        DeviceConfig deviceConfig = new ConfigBuilder().build();
-        mPositioner.update(deviceConfig);
-
-        PointF restingPosition = new PointF(100, 100);
-        mPositioner.setRestingPosition(restingPosition);
-
-        assertThat(mPositioner.getRestingPosition()).isEqualTo(restingPosition);
-    }
-
-    /** Test that the default resting position on phone is in upper left. */
-    @Test
-    public void testGetRestingPosition_bubble_onPhone() {
-        DeviceConfig deviceConfig = new ConfigBuilder().build();
-        mPositioner.update(deviceConfig);
-
-        RectF allowableStackRegion =
-                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
-        PointF restingPosition = mPositioner.getRestingPosition();
-
-        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left);
-        assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
-    }
-
-    @Test
-    public void testGetRestingPosition_bubble_onPhone_RTL() {
-        DeviceConfig deviceConfig = new ConfigBuilder().setRtl().build();
-        mPositioner.update(deviceConfig);
-
-        RectF allowableStackRegion =
-                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
-        PointF restingPosition = mPositioner.getRestingPosition();
-
-        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right);
-        assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
-    }
-
-    /** Test that the default resting position on tablet is middle left. */
-    @Test
-    public void testGetRestingPosition_chatBubble_onTablet() {
-        DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().build();
-        mPositioner.update(deviceConfig);
-
-        RectF allowableStackRegion =
-                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
-        PointF restingPosition = mPositioner.getRestingPosition();
-
-        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left);
-        assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
-    }
-
-    @Test
-    public void testGetRestingPosition_chatBubble_onTablet_RTL() {
-        DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build();
-        mPositioner.update(deviceConfig);
-
-        RectF allowableStackRegion =
-                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
-        PointF restingPosition = mPositioner.getRestingPosition();
-
-        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right);
-        assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
-    }
-
-    /** Test that the default resting position on tablet is middle right. */
-    @Test
-    public void testGetDefaultPosition_appBubble_onTablet() {
-        DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().build();
-        mPositioner.update(deviceConfig);
-
-        RectF allowableStackRegion =
-                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
-        PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */);
-
-        assertThat(startPosition.x).isEqualTo(allowableStackRegion.right);
-        assertThat(startPosition.y).isEqualTo(getDefaultYPosition());
-    }
-
-    @Test
-    public void testGetRestingPosition_appBubble_onTablet_RTL() {
-        DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build();
-        mPositioner.update(deviceConfig);
-
-        RectF allowableStackRegion =
-                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
-        PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */);
-
-        assertThat(startPosition.x).isEqualTo(allowableStackRegion.left);
-        assertThat(startPosition.y).isEqualTo(getDefaultYPosition());
-    }
-
-    @Test
-    public void testHasUserModifiedDefaultPosition_false() {
-        DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build();
-        mPositioner.update(deviceConfig);
-
-        assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse();
-
-        mPositioner.setRestingPosition(mPositioner.getDefaultStartPosition());
-
-        assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse();
-    }
-
-    @Test
-    public void testHasUserModifiedDefaultPosition_true() {
-        DeviceConfig deviceConfig = new ConfigBuilder().setLargeScreen().setRtl().build();
-        mPositioner.update(deviceConfig);
-
-        assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse();
-
-        mPositioner.setRestingPosition(new PointF(0, 100));
-
-        assertThat(mPositioner.hasUserModifiedDefaultPosition()).isTrue();
-    }
-
-    @Test
-    public void testGetExpandedViewHeight_max() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setLargeScreen()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
-        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
-        assertThat(mPositioner.getExpandedViewHeight(bubble)).isEqualTo(MAX_HEIGHT);
-    }
-
-    @Test
-    public void testGetExpandedViewHeight_customHeight_valid() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setLargeScreen()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        final int minHeight = mContext.getResources().getDimensionPixelSize(
-                R.dimen.bubble_expanded_default_height);
-        Bubble bubble = new Bubble("key",
-                mock(ShortcutInfo.class),
-                minHeight + 100 /* desiredHeight */,
-                0 /* desiredHeightResId */,
-                "title",
-                0 /* taskId */,
-                null /* locus */,
-                true /* isDismissable */,
-                directExecutor(),
-                mock(Bubbles.BubbleMetadataFlagListener.class));
-
-        // Ensure the height is the same as the desired value
-        assertThat(mPositioner.getExpandedViewHeight(bubble)).isEqualTo(
-                bubble.getDesiredHeight(mContext));
-    }
-
-
-    @Test
-    public void testGetExpandedViewHeight_customHeight_tooSmall() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setLargeScreen()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        Bubble bubble = new Bubble("key",
-                mock(ShortcutInfo.class),
-                10 /* desiredHeight */,
-                0 /* desiredHeightResId */,
-                "title",
-                0 /* taskId */,
-                null /* locus */,
-                true /* isDismissable */,
-                directExecutor(),
-                mock(Bubbles.BubbleMetadataFlagListener.class));
-
-        // Ensure the height is the same as the minimum value
-        final int minHeight = mContext.getResources().getDimensionPixelSize(
-                R.dimen.bubble_expanded_default_height);
-        assertThat(mPositioner.getExpandedViewHeight(bubble)).isEqualTo(minHeight);
-    }
-
-    @Test
-    public void testGetMaxExpandedViewHeight_onLargeTablet() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setLargeScreen()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        int manageButtonHeight =
-                mContext.getResources().getDimensionPixelSize(R.dimen.bubble_manage_button_height);
-        int pointerWidth = mContext.getResources().getDimensionPixelSize(
-                R.dimen.bubble_pointer_width);
-        int expandedViewPadding = mContext.getResources().getDimensionPixelSize(R
-                .dimen.bubble_expanded_view_padding);
-        float expectedHeight = 1800 - 2 * 20 - manageButtonHeight - pointerWidth
-                - expandedViewPadding * 2;
-        assertThat(((float) mPositioner.getMaxExpandedViewHeight(false /* isOverflow */)))
-                .isWithin(0.1f).of(expectedHeight);
-    }
-
-    @Test
-    public void testAreBubblesBottomAligned_largeScreen_true() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setLargeScreen()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        assertThat(mPositioner.areBubblesBottomAligned()).isTrue();
-    }
-
-    @Test
-    public void testAreBubblesBottomAligned_largeScreen_false() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setLargeScreen()
-                .setLandscape()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        assertThat(mPositioner.areBubblesBottomAligned()).isFalse();
-    }
-
-    @Test
-    public void testAreBubblesBottomAligned_smallTablet_false() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setLargeScreen()
-                .setSmallTablet()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        assertThat(mPositioner.areBubblesBottomAligned()).isFalse();
-    }
-
-    @Test
-    public void testAreBubblesBottomAligned_phone_false() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        assertThat(mPositioner.areBubblesBottomAligned()).isFalse();
-    }
-
-    @Test
-    public void testExpandedViewY_phoneLandscape() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setLandscape()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
-        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
-        // This bubble will have max height so it'll always be top aligned
-        assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
-                .isEqualTo(mPositioner.getExpandedViewYTopAligned());
-    }
-
-    @Test
-    public void testExpandedViewY_phonePortrait() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
-        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
-        // Always top aligned in phone portrait
-        assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
-                .isEqualTo(mPositioner.getExpandedViewYTopAligned());
-    }
-
-    @Test
-    public void testExpandedViewY_smallTabletLandscape() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setSmallTablet()
-                .setLandscape()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
-        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
-        // This bubble will have max height which is always top aligned on small tablets
-        assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
-                .isEqualTo(mPositioner.getExpandedViewYTopAligned());
-    }
-
-    @Test
-    public void testExpandedViewY_smallTabletPortrait() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setSmallTablet()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
-        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
-        // This bubble will have max height which is always top aligned on small tablets
-        assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
-                .isEqualTo(mPositioner.getExpandedViewYTopAligned());
-    }
-
-    @Test
-    public void testExpandedViewY_largeScreenLandscape() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setLargeScreen()
-                .setLandscape()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
-        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
-        // This bubble will have max height which is always top aligned on landscape, large tablet
-        assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
-                .isEqualTo(mPositioner.getExpandedViewYTopAligned());
-    }
-
-    @Test
-    public void testExpandedViewY_largeScreenPortrait() {
-        Insets insets = Insets.of(10, 20, 5, 15);
-        Rect screenBounds = new Rect(0, 0, 1800, 2600);
-
-        DeviceConfig deviceConfig = new ConfigBuilder()
-                .setLargeScreen()
-                .setInsets(insets)
-                .setScreenBounds(screenBounds)
-                .build();
-        mPositioner.update(deviceConfig);
-
-        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
-        Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
-
-        int manageButtonHeight =
-                mContext.getResources().getDimensionPixelSize(R.dimen.bubble_manage_button_height);
-        int manageButtonPlusMargin = manageButtonHeight + 2
-                * mContext.getResources().getDimensionPixelSize(
-                        R.dimen.bubble_manage_button_margin);
-        int pointerWidth = mContext.getResources().getDimensionPixelSize(
-                R.dimen.bubble_pointer_width);
-
-        final float expectedExpandedViewY = mPositioner.getAvailableRect().bottom
-                - manageButtonPlusMargin
-                - mPositioner.getExpandedViewHeightForLargeScreen()
-                - pointerWidth;
-
-        // Bubbles are bottom aligned on portrait, large tablet
-        assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
-                .isEqualTo(expectedExpandedViewY);
-    }
-
-    /**
-     * Calculates the Y position bubbles should be placed based on the config. Based on
-     * the calculations in {@link BubblePositioner#getDefaultStartPosition()} and
-     * {@link BubbleStackView.RelativeStackPosition}.
-     */
-    private float getDefaultYPosition() {
-        final boolean isTablet = mPositioner.isLargeScreen();
-
-        // On tablet the position is centered, on phone it is an offset from the top.
-        final float desiredY = isTablet
-                ? mPositioner.getScreenRect().height() / 2f - (mPositioner.getBubbleSize() / 2f)
-                : mContext.getResources().getDimensionPixelOffset(
-                        R.dimen.bubble_stack_starting_offset_y);
-        // Since we're visually centering the bubbles on tablet, use total screen height rather
-        // than the available height.
-        final float height = isTablet
-                ? mPositioner.getScreenRect().height()
-                : mPositioner.getAvailableRect().height();
-        float offsetPercent = desiredY / height;
-        offsetPercent = Math.max(0f, Math.min(1f, offsetPercent));
-        final RectF allowableStackRegion =
-                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
-        return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent;
-    }
-
-    /**
-     * Sets up window manager to return config values based on what you need for the test.
-     * By default it sets up a portrait phone without any insets.
-     */
-    private static class ConfigBuilder {
-        private Rect mScreenBounds = new Rect(0, 0, 1000, 2000);
-        private boolean mIsLargeScreen = false;
-        private boolean mIsSmallTablet = false;
-        private boolean mIsLandscape = false;
-        private boolean mIsRtl = false;
-        private Insets mInsets = Insets.of(0, 0, 0, 0);
-
-        public ConfigBuilder setScreenBounds(Rect screenBounds) {
-            mScreenBounds = screenBounds;
-            return this;
-        }
-
-        public ConfigBuilder setLargeScreen() {
-            mIsLargeScreen = true;
-            return this;
-        }
-
-        public ConfigBuilder setSmallTablet() {
-            mIsSmallTablet = true;
-            return this;
-        }
-
-        public ConfigBuilder setLandscape() {
-            mIsLandscape = true;
-            return this;
-        }
-
-        public ConfigBuilder setRtl() {
-            mIsRtl = true;
-            return this;
-        }
-
-        public ConfigBuilder setInsets(Insets insets) {
-            mInsets = insets;
-            return this;
-        }
-
-        private DeviceConfig build() {
-            return new DeviceConfig(mIsLargeScreen, mIsSmallTablet, mIsLandscape, mIsRtl,
-                    mScreenBounds, mInsets);
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index f583321..4878df8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -125,7 +125,7 @@
                 mock<BubbleProperties>())
 
         bubbleStackView = BubbleStackView(context, bubbleController, bubbleData,
-                surfaceSynchronizer, FloatingContentCoordinator(), mainExecutor)
+                surfaceSynchronizer, FloatingContentCoordinator(), bubbleController, mainExecutor)
         bubbleBarLayerView = BubbleBarLayerView(context, bubbleController)
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index 23a4e39..dd358e7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -30,6 +30,7 @@
 import android.app.ActivityManager;
 import android.app.AppCompatTaskInfo.CameraCompatControlState;
 import android.app.TaskInfo;
+import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
 import android.util.Pair;
 import android.view.LayoutInflater;
@@ -83,6 +84,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        doReturn(100).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
         mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
         mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
                 mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
@@ -221,6 +223,9 @@
         taskInfo.taskId = TASK_ID;
         taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
         taskInfo.appCompatTaskInfo.cameraCompatControlState = cameraCompatControlState;
+        taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1000;
+        taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
+        taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
         return taskInfo;
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index d4b97ed..4f261cd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -20,6 +20,7 @@
 import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
 import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
 import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 import static android.view.WindowInsets.Type.navigationBars;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -27,6 +28,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
@@ -39,6 +41,7 @@
 import android.app.TaskInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.util.Pair;
 import android.view.DisplayInfo;
@@ -50,6 +53,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.window.flags.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayLayout;
@@ -59,6 +63,7 @@
 import junit.framework.Assert;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -76,8 +81,12 @@
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
 public class CompatUIWindowManagerTest extends ShellTestCase {
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
 
     private static final int TASK_ID = 1;
+    private static final int TASK_WIDTH = 2000;
+    private static final int TASK_HEIGHT = 2000;
 
     @Mock private SyncTransactionQueue mSyncTransactionQueue;
     @Mock private CompatUIController.CompatUICallback mCallback;
@@ -93,6 +102,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        doReturn(100).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
         mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
         mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
                 mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
@@ -138,6 +148,13 @@
         mWindowManager.mHasSizeCompat = false;
         assertFalse(mWindowManager.createLayout(/* canShow= */ true));
 
+        // Returns false and doesn't create layout if restart button should be hidden.
+        clearInvocations(mWindowManager);
+        mWindowManager.mHasSizeCompat = true;
+        mTaskInfo.appCompatTaskInfo.topActivityLetterboxWidth = TASK_WIDTH;
+        mTaskInfo.appCompatTaskInfo.topActivityLetterboxHeight = TASK_HEIGHT;
+        assertFalse(mWindowManager.createLayout(/* canShow= */ true));
+
         verify(mWindowManager, never()).inflateLayout();
     }
 
@@ -199,6 +216,7 @@
         // No diff
         clearInvocations(mWindowManager);
         TaskInfo taskInfo = createTaskInfo(/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
+        doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(any());
         assertTrue(mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true));
 
         verify(mWindowManager, never()).updateSurfacePosition();
@@ -283,7 +301,6 @@
 
     @Test
     public void testUpdateCompatInfoLayoutNotInflatedYet() {
-        mWindowManager.mHasSizeCompat = true;
         mWindowManager.createLayout(/* canShow= */ false);
 
         verify(mWindowManager, never()).inflateLayout();
@@ -303,6 +320,15 @@
         mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
 
         verify(mWindowManager).inflateLayout();
+
+        // Change shouldShowSizeCompatRestartButton to false and pass canShow true, layout
+        // shouldn't be inflated
+        clearInvocations(mWindowManager);
+        taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = TASK_WIDTH;
+        taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = TASK_HEIGHT;
+        mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+
+        verify(mWindowManager, never()).inflateLayout();
     }
 
     @Test
@@ -464,6 +490,36 @@
         Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener));
     }
 
+    @Test
+    public void testShouldShowSizeCompatRestartButton() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON);
+
+        doReturn(86).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
+        mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
+                mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+                mCompatUIConfiguration, mOnRestartButtonClicked);
+
+        // Simulate rotation of activity in square display
+        TaskInfo taskInfo = createTaskInfo(true, CAMERA_COMPAT_CONTROL_HIDDEN);
+        taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
+        taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 2000;
+        taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1850;
+
+        assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+
+        // Simulate exiting split screen/folding
+        taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
+        assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+
+        // Simulate folding
+        taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 1000, 2000));
+        assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+
+        taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
+        taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 500;
+        assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+    }
+
     private static TaskInfo createTaskInfo(boolean hasSizeCompat,
             @AppCompatTaskInfo.CameraCompatControlState int cameraCompatControlState) {
         ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
@@ -471,6 +527,11 @@
         taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
         taskInfo.appCompatTaskInfo.cameraCompatControlState = cameraCompatControlState;
         taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK;
+        // Letterboxed activity that takes half the screen should show size compat restart button
+        taskInfo.configuration.windowConfiguration.setBounds(
+                new Rect(0, 0, TASK_WIDTH, TASK_HEIGHT));
+        taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1000;
+        taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
         return taskInfo;
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index 3bc90ad..be639e8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -34,6 +34,7 @@
 import org.mockito.Mock
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
+import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.verifyZeroInteractions
 import org.mockito.kotlin.whenever
@@ -150,6 +151,23 @@
     }
 
     @Test
+    fun startDragToDesktop_anotherTransitionInProgress_startDropped() {
+        val task = createTask()
+        val dragAnimator = mock<MoveToDesktopAnimator>()
+
+        // Simulate attempt to start two drag to desktop transitions.
+        startDragToDesktopTransition(task, dragAnimator)
+        startDragToDesktopTransition(task, dragAnimator)
+
+        // Verify transition only started once.
+        verify(transitions, times(1)).startTransition(
+                eq(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP),
+                any(),
+                eq(handler)
+        )
+    }
+
+    @Test
     fun cancelDragToDesktop_startWasReady_cancel() {
         val task = createTask()
         val dragAnimator = mock<MoveToDesktopAnimator>()
@@ -189,6 +207,32 @@
         verifyZeroInteractions(dragAnimator)
     }
 
+    @Test
+    fun cancelDragToDesktop_transitionNotInProgress_dropCancel() {
+        // Then cancel is called before the transition was started.
+        handler.cancelDragToDesktopTransition()
+
+        // Verify cancel is dropped.
+        verify(transitions, never()).startTransition(
+                eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP),
+                any(),
+                eq(handler)
+        )
+    }
+
+    @Test
+    fun finishDragToDesktop_transitionNotInProgress_dropFinish() {
+        // Then finish is called before the transition was started.
+        handler.finishDragToDesktopTransition(WindowContainerTransaction())
+
+        // Verify finish is dropped.
+        verify(transitions, never()).startTransition(
+                eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP),
+                any(),
+                eq(handler)
+        )
+    }
+
     private fun startDragToDesktopTransition(
         task: RunningTaskInfo,
         dragAnimator: MoveToDesktopAnimator
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
index 0f8db85..b583acd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
@@ -16,10 +16,10 @@
 
 package com.android.wm.shell.pip.phone;
 
-import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_CUSTOM;
-import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_DEFAULT;
-import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_MAX;
-import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.nextSizeSpec;
+import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_CUSTOM;
+import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_DEFAULT;
+import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_MAX;
+import static com.android.wm.shell.common.pip.PipDoubleTapHelper.nextSizeSpec;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -30,6 +30,7 @@
 
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDoubleTapHelper;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -38,7 +39,7 @@
 import org.mockito.Mock;
 
 /**
- * Unit test against {@link PipDoubleTapHelper}.
+ * Unit test against {@link com.android.wm.shell.common.pip.PipDoubleTapHelper}.
  */
 @RunWith(AndroidTestingRunner.class)
 public class PipDoubleTapHelperTest extends ShellTestCase {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 12a5594..7f3bfbb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -421,6 +421,15 @@
         assertEquals(false, controller.supportsMultiInstanceSplit(component));
     }
 
+    @Test
+    public void testSwitchSplitPosition_checksIsSplitScreenVisible() {
+        final String reason = "test";
+        when(mSplitScreenController.isSplitScreenVisible()).thenReturn(true, false);
+        mSplitScreenController.switchSplitPosition(reason);
+        mSplitScreenController.switchSplitPosition(reason);
+        verify(mStageCoordinator, times(1)).switchSplitPosition(reason);
+    }
+
     private Intent createStartIntent(String activityName) {
         Intent intent = new Intent();
         intent.setComponent(new ComponentName(mContext, activityName));
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 193f16d..40e61dd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -27,6 +27,8 @@
 
 import android.app.ActivityManager;
 import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -202,6 +204,8 @@
                 .setTaskDescriptionBuilder(taskDescriptionBuilder)
                 .setVisible(visible)
                 .build();
+        taskInfo.topActivityInfo = new ActivityInfo();
+        taskInfo.topActivityInfo.applicationInfo = new ApplicationInfo();
         taskInfo.realActivity = new ComponentName("com.android.wm.shell.windowdecor",
                 "DesktopModeWindowDecorationTests");
         taskInfo.baseActivity = new ComponentName("com.android.wm.shell.windowdecor",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 0841210..86253f3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -144,13 +144,13 @@
     }
 
     @Test
-    fun testDragResize_noMove_showsResizeVeil() {
+    fun testDragResize_noMove_doesNotShowResizeVeil() {
         taskPositioner.onDragPositioningStart(
             CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
             STARTING_BOUNDS.left.toFloat(),
             STARTING_BOUNDS.top.toFloat()
         )
-        verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS)
+        verify(mockDesktopWindowDecoration, never()).showResizeVeil(STARTING_BOUNDS)
 
         taskPositioner.onDragPositioningEnd(
             STARTING_BOUNDS.left.toFloat(),
@@ -162,7 +162,7 @@
                         (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
                         change.configuration.windowConfiguration.bounds == STARTING_BOUNDS}},
             eq(taskPositioner))
-        verify(mockDesktopWindowDecoration).hideResizeVeil()
+        verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
     }
 
     @Test
@@ -212,7 +212,6 @@
             STARTING_BOUNDS.right.toFloat(),
             STARTING_BOUNDS.top.toFloat()
         )
-        verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS)
 
         taskPositioner.onDragPositioningMove(
             STARTING_BOUNDS.right.toFloat() + 10,
@@ -222,6 +221,7 @@
         val rectAfterMove = Rect(STARTING_BOUNDS)
         rectAfterMove.right += 10
         rectAfterMove.top += 10
+        verify(mockDesktopWindowDecoration).showResizeVeil(rectAfterMove)
         verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
             return@argThat wct.changes.any { (token, change) ->
                 token == taskBinder &&
@@ -237,7 +237,7 @@
         val rectAfterEnd = Rect(rectAfterMove)
         rectAfterEnd.right += 10
         rectAfterEnd.top += 10
-        verify(mockDesktopWindowDecoration, times(2)).updateResizeVeil(any())
+        verify(mockDesktopWindowDecoration).updateResizeVeil(any())
         verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
             return@argThat wct.changes.any { (token, change) ->
                 token == taskBinder &&
@@ -253,7 +253,6 @@
             STARTING_BOUNDS.left.toFloat(),
             STARTING_BOUNDS.top.toFloat()
         )
-        verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS)
 
         taskPositioner.onDragPositioningMove(
             STARTING_BOUNDS.left.toFloat(),
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 2f28363..77800a3 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -31,6 +31,12 @@
     ],
 }
 
+cc_aconfig_library {
+    name: "backup_flags_cc_lib",
+    host_supported: true,
+    aconfig_declarations: "backup_flags",
+}
+
 cc_defaults {
     name: "libandroidfw_defaults",
     cpp_std: "gnu++2b",
@@ -115,7 +121,10 @@
                 "libutils",
                 "libz",
             ],
-            static_libs: ["libziparchive_for_incfs"],
+            static_libs: [
+                "libziparchive_for_incfs",
+                "backup_flags_cc_lib",
+            ],
             static: {
                 enabled: false,
             },
diff --git a/libs/androidfw/BackupHelpers.cpp b/libs/androidfw/BackupHelpers.cpp
index 1a6a952..a1e7c2f 100644
--- a/libs/androidfw/BackupHelpers.cpp
+++ b/libs/androidfw/BackupHelpers.cpp
@@ -36,6 +36,9 @@
 #include <utils/KeyedVector.h>
 #include <utils/String8.h>
 
+#include <com_android_server_backup.h>
+namespace backup_flags = com::android::server::backup;
+
 namespace android {
 
 #define MAGIC0 0x70616e53 // Snap
@@ -214,7 +217,7 @@
 {
     LOGP("write_update_file %s (%s) : mode 0%o\n", realFilename, key.c_str(), mode);
 
-    const int bufsize = 4*1024;
+    const int bufsize = backup_flags::enable_max_size_writes_to_pipes() ? (64*1024) : (4*1024);
     int err;
     int amt;
     int fileSize;
@@ -550,7 +553,8 @@
     }
 
     // read/write up to this much at a time.
-    const size_t BUFSIZE = 32 * 1024;
+    const size_t BUFSIZE = backup_flags::enable_max_size_writes_to_pipes() ? (64*1024) : (32*1024);
+
     char* buf = (char *)calloc(1,BUFSIZE);
     const size_t PAXHEADER_OFFSET = 512;
     const size_t PAXHEADER_SIZE = 512;
@@ -726,7 +730,7 @@
 
 
 
-#define RESTORE_BUF_SIZE (8*1024)
+const size_t RESTORE_BUF_SIZE = backup_flags::enable_max_size_writes_to_pipes() ? 64*1024 : 8*1024;
 
 RestoreHelperBase::RestoreHelperBase()
 {
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index c9d5e07..d9166a1 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -21,6 +21,7 @@
 #include <algorithm>
 #include <cstddef>
 #include <limits>
+#include <optional>
 
 #include "android-base/logging.h"
 #include "android-base/stringprintf.h"
@@ -50,7 +51,9 @@
 // contiguous block of memory to store both the TypeSpec struct and
 // the Type structs.
 struct TypeSpecBuilder {
-  explicit TypeSpecBuilder(incfs::verified_map_ptr<ResTable_typeSpec> header) : header_(header) {}
+  explicit TypeSpecBuilder(incfs::verified_map_ptr<ResTable_typeSpec> header) : header_(header) {
+    type_entries.reserve(dtohs(header_->typesCount));
+  }
 
   void AddType(incfs::verified_map_ptr<ResTable_type> type) {
     TypeSpec::TypeEntry& entry = type_entries.emplace_back();
@@ -59,6 +62,7 @@
   }
 
   TypeSpec Build() {
+    type_entries.shrink_to_fit();
     return {header_, std::move(type_entries)};
   }
 
@@ -450,7 +454,8 @@
 std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
                                                          package_property_t property_flags) {
   ATRACE_NAME("LoadedPackage::Load");
-  std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage());
+  const bool optimize_name_lookups = (property_flags & PROPERTY_OPTIMIZE_NAME_LOOKUPS) != 0;
+  std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage(optimize_name_lookups));
 
   // typeIdOffset was added at some point, but we still must recognize apps built before this
   // was added.
@@ -499,7 +504,7 @@
   // A map of TypeSpec builders, each associated with an type index.
   // We use these to accumulate the set of Types available for a TypeSpec, and later build a single,
   // contiguous block of memory that holds all the Types together with the TypeSpec.
-  std::unordered_map<int, std::unique_ptr<TypeSpecBuilder>> type_builder_map;
+  std::unordered_map<int, std::optional<TypeSpecBuilder>> type_builder_map;
 
   ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
   while (iter.HasNext()) {
@@ -567,14 +572,14 @@
           return {};
         }
 
-        if (entry_count * sizeof(uint32_t) > chunk.data_size()) {
+        if (entry_count * sizeof(uint32_t) > child_chunk.data_size()) {
           LOG(ERROR) << "RES_TABLE_TYPE_SPEC_TYPE too small to hold entries.";
           return {};
         }
 
-        std::unique_ptr<TypeSpecBuilder>& builder_ptr = type_builder_map[type_spec->id];
-        if (builder_ptr == nullptr) {
-          builder_ptr = util::make_unique<TypeSpecBuilder>(type_spec.verified());
+        auto& maybe_type_builder = type_builder_map[type_spec->id];
+        if (!maybe_type_builder) {
+          maybe_type_builder.emplace(type_spec.verified());
           loaded_package->resource_ids_.set(type_spec->id, entry_count);
         } else {
           LOG(WARNING) << StringPrintf("RES_TABLE_TYPE_SPEC_TYPE already defined for ID %02x",
@@ -594,9 +599,9 @@
         }
 
         // Type chunks must be preceded by their TypeSpec chunks.
-        std::unique_ptr<TypeSpecBuilder>& builder_ptr = type_builder_map[type->id];
-        if (builder_ptr != nullptr) {
-          builder_ptr->AddType(type.verified());
+        auto& maybe_type_builder = type_builder_map[type->id];
+        if (maybe_type_builder) {
+          maybe_type_builder->AddType(type.verified());
         } else {
           LOG(ERROR) << StringPrintf(
               "RES_TABLE_TYPE_TYPE with ID %02x found without preceding RES_TABLE_TYPE_SPEC_TYPE.",
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 4c992be..2c99f1a 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -447,15 +447,19 @@
 // --------------------------------------------------------------------
 // --------------------------------------------------------------------
 
-ResStringPool::ResStringPool()
-    : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL)
-{
+ResStringPool::ResStringPool() : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL) {
 }
 
-ResStringPool::ResStringPool(const void* data, size_t size, bool copyData)
-    : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL)
-{
-    setTo(data, size, copyData);
+ResStringPool::ResStringPool(bool optimize_name_lookups) : ResStringPool() {
+  if (optimize_name_lookups) {
+    mIndexLookupCache.emplace();
+  }
+}
+
+ResStringPool::ResStringPool(const void* data, size_t size, bool copyData,
+                             bool optimize_name_lookups)
+    : ResStringPool(optimize_name_lookups) {
+  setTo(data, size, copyData);
 }
 
 ResStringPool::~ResStringPool()
@@ -683,6 +687,14 @@
         mStylePoolSize = 0;
     }
 
+    if (mIndexLookupCache) {
+      if ((mHeader->flags & ResStringPool_header::UTF8_FLAG) != 0) {
+        mIndexLookupCache->first.reserve(mHeader->stringCount);
+      } else {
+        mIndexLookupCache->second.reserve(mHeader->stringCount);
+      }
+    }
+
     return (mError=NO_ERROR);
 }
 
@@ -708,6 +720,10 @@
         free(mOwnedData);
         mOwnedData = NULL;
     }
+    if (mIndexLookupCache) {
+      mIndexLookupCache->first.clear();
+      mIndexLookupCache->second.clear();
+    }
 }
 
 /**
@@ -824,11 +840,11 @@
 
                 // encLen must be less than 0x7FFF due to encoding.
                 if ((uint32_t)(u8str+*u8len-strings) < mStringPoolSize) {
-                    AutoMutex lock(mDecodeLock);
+                  AutoMutex lock(mCachesLock);
 
-                    if (mCache != NULL && mCache[idx] != NULL) {
-                        return StringPiece16(mCache[idx], *u16len);
-                    }
+                  if (mCache != NULL && mCache[idx] != NULL) {
+                    return StringPiece16(mCache[idx], *u16len);
+                  }
 
                     // Retrieve the actual length of the utf8 string if the
                     // encoded length was truncated
@@ -1093,12 +1109,24 @@
             // block, start searching at the back.
             String8 str8(str, strLen);
             const size_t str8Len = str8.size();
+            std::optional<AutoMutex> cacheLock;
+            if (mIndexLookupCache) {
+              cacheLock.emplace(mCachesLock);
+              if (auto it = mIndexLookupCache->first.find(std::string_view(str8));
+                  it != mIndexLookupCache->first.end()) {
+                return it->second;
+              }
+            }
+
             for (int i=mHeader->stringCount-1; i>=0; i--) {
                 const base::expected<StringPiece, NullOrIOError> s = string8At(i);
                 if (UNLIKELY(IsIOError(s))) {
                     return base::unexpected(s.error());
                 }
                 if (s.has_value()) {
+                  if (mIndexLookupCache) {
+                    mIndexLookupCache->first.insert({*s, i});
+                  }
                     if (kDebugStringPoolNoisy) {
                         ALOGI("Looking at %s, i=%d\n", s->data(), i);
                     }
@@ -1151,20 +1179,32 @@
             // most often this happens because we want to get IDs for style
             // span tags; since those always appear at the end of the string
             // block, start searching at the back.
+            std::optional<AutoMutex> cacheLock;
+            if (mIndexLookupCache) {
+              cacheLock.emplace(mCachesLock);
+              if (auto it = mIndexLookupCache->second.find({str, strLen});
+                  it != mIndexLookupCache->second.end()) {
+                return it->second;
+              }
+            }
             for (int i=mHeader->stringCount-1; i>=0; i--) {
                 const base::expected<StringPiece16, NullOrIOError> s = stringAt(i);
                 if (UNLIKELY(IsIOError(s))) {
                     return base::unexpected(s.error());
                 }
                 if (kDebugStringPoolNoisy) {
-                    ALOGI("Looking at %s, i=%d\n", String8(s->data(), s->size()).c_str(), i);
+                  ALOGI("Looking16 at %s, i=%d\n", String8(s->data(), s->size()).c_str(), i);
                 }
-                if (s.has_value() && strLen == s->size() &&
-                        strzcmp16(s->data(), s->size(), str, strLen) == 0) {
+                if (s.has_value()) {
+                  if (mIndexLookupCache) {
+                    mIndexLookupCache->second.insert({*s, i});
+                  }
+                  if (strLen == s->size() && strzcmp16(s->data(), s->size(), str, strLen) == 0) {
                     if (kDebugStringPoolNoisy) {
-                        ALOGI("MATCH!");
+                      ALOGI("MATCH16!");
                     }
                     return i;
+                  }
                 }
             }
         }
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index 6068912..d9f7c2a 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -71,7 +71,7 @@
 
   // Rewrites a compile-time overlay resource id to the runtime resource id of corresponding target
   // resource.
-  virtual status_t lookupResourceIdNoRewrite(uint32_t* resId) const;
+  status_t lookupResourceIdNoRewrite(uint32_t* resId) const;
 
   const Idmap_data_header* data_header_;
   const Idmap_overlay_entry* entries_;
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 3a72871..413b278 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -99,6 +99,9 @@
 
   // The apk assets only contain the overlayable declarations information.
   PROPERTY_ONLY_OVERLAYABLES = 1U << 5U,
+
+  // Optimize the resource lookups by name via an in-memory lookup table.
+  PROPERTY_OPTIMIZE_NAME_LOOKUPS = 1U << 6U,
 };
 
 struct OverlayableInfo {
@@ -285,7 +288,9 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
 
-  LoadedPackage() = default;
+  explicit LoadedPackage(bool optimize_name_lookups = false)
+      : type_string_pool_(optimize_name_lookups), key_string_pool_(optimize_name_lookups) {
+  }
 
   ResStringPool type_string_pool_;
   ResStringPool key_string_pool_;
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index c0514fd..3d1403d 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -22,27 +22,28 @@
 
 #include <android-base/expected.h>
 #include <android-base/unique_fd.h>
-
+#include <android/configuration.h>
 #include <androidfw/Asset.h>
 #include <androidfw/Errors.h>
 #include <androidfw/LocaleData.h>
 #include <androidfw/StringPiece.h>
 #include <utils/ByteOrder.h>
 #include <utils/Errors.h>
+#include <utils/KeyedVector.h>
 #include <utils/String16.h>
 #include <utils/Vector.h>
-#include <utils/KeyedVector.h>
-
 #include <utils/threads.h>
 
 #include <stdint.h>
 #include <sys/types.h>
 
-#include <android/configuration.h>
-
 #include <array>
 #include <map>
 #include <memory>
+#include <optional>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
 
 namespace android {
 
@@ -512,23 +513,24 @@
 class ResStringPool
 {
 public:
-    ResStringPool();
-    ResStringPool(const void* data, size_t size, bool copyData=false);
-    virtual ~ResStringPool();
+ ResStringPool();
+ explicit ResStringPool(bool optimize_name_lookups);
+ ResStringPool(const void* data, size_t size, bool copyData = false,
+               bool optimize_name_lookups = false);
+ virtual ~ResStringPool();
 
-    void setToEmpty();
-    status_t setTo(incfs::map_ptr<void> data, size_t size, bool copyData=false);
+ void setToEmpty();
+ status_t setTo(incfs::map_ptr<void> data, size_t size, bool copyData = false);
 
-    status_t getError() const;
+ status_t getError() const;
 
-    void uninit();
+ void uninit();
 
-    // Return string entry as UTF16; if the pool is UTF8, the string will
-    // be converted before returning.
-    inline base::expected<StringPiece16, NullOrIOError> stringAt(
-            const ResStringPool_ref& ref) const {
-        return stringAt(ref.index);
-    }
+ // Return string entry as UTF16; if the pool is UTF8, the string will
+ // be converted before returning.
+ inline base::expected<StringPiece16, NullOrIOError> stringAt(const ResStringPool_ref& ref) const {
+   return stringAt(ref.index);
+ }
     virtual base::expected<StringPiece16, NullOrIOError> stringAt(size_t idx) const;
 
     // Note: returns null if the string pool is not UTF8.
@@ -557,7 +559,7 @@
     void*                                         mOwnedData;
     incfs::verified_map_ptr<ResStringPool_header> mHeader;
     size_t                                        mSize;
-    mutable Mutex                                 mDecodeLock;
+    mutable Mutex                                 mCachesLock;
     incfs::map_ptr<uint32_t>                      mEntries;
     incfs::map_ptr<uint32_t>                      mEntryStyles;
     incfs::map_ptr<void>                          mStrings;
@@ -566,6 +568,10 @@
     incfs::map_ptr<uint32_t>                      mStyles;
     uint32_t                                      mStylePoolSize;    // number of uint32_t
 
+    mutable std::optional<std::pair<std::unordered_map<std::string_view, int>,
+                                    std::unordered_map<std::u16string_view, int>>>
+        mIndexLookupCache;
+
     base::expected<StringPiece, NullOrIOError> stringDecodeAt(
         size_t idx, incfs::map_ptr<uint8_t> str, size_t encLen) const;
 };
@@ -1401,8 +1407,8 @@
     
     // Must be 0.
     uint8_t res0;
-    // Must be 0.
-    uint16_t res1;
+    // Used to be reserved, if >0 specifies the number of ResTable_type entries for this spec.
+    uint16_t typesCount;
     
     // Number of uint32_t entry configuration masks that follow.
     uint32_t entryCount;
diff --git a/libs/hostgraphics/gui/Surface.h b/libs/hostgraphics/gui/Surface.h
index de1ba00..2573931 100644
--- a/libs/hostgraphics/gui/Surface.h
+++ b/libs/hostgraphics/gui/Surface.h
@@ -50,6 +50,8 @@
     virtual int unlockAndPost() { return 0; }
     virtual int query(int what, int* value) const { return 0; }
 
+    virtual void destroy() {}
+
 protected:
     virtual ~Surface() {}
 
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index eebf8aa..4e330da 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -229,15 +229,6 @@
     path: "apex/java",
 }
 
-java_api_contribution {
-    name: "framework-graphics-public-stubs",
-    api_surface: "public",
-    api_file: "api/current.txt",
-    visibility: [
-        "//build/orchestrator/apis",
-    ],
-}
-
 // ------------------------
 // APEX
 // ------------------------
@@ -638,14 +629,6 @@
             // Allow implicit fallthroughs in HardwareBitmapUploader.cpp until they are fixed.
             cflags: ["-Wno-implicit-fallthrough"],
         },
-        host: {
-            srcs: [
-                "utils/HostColorSpace.cpp",
-            ],
-            export_static_lib_headers: [
-                "libarect",
-            ],
-        },
     },
 }
 
@@ -716,7 +699,6 @@
     ],
     shared_libs: [
         "libmemunreachable",
-        "server_configurable_flags",
     ],
     srcs: [
         "tests/unit/main.cpp",
diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp
index 4d020c5..5f5ffe9 100644
--- a/libs/hwui/AutoBackendTextureRelease.cpp
+++ b/libs/hwui/AutoBackendTextureRelease.cpp
@@ -21,6 +21,7 @@
 #include <include/gpu/GrDirectContext.h>
 #include <include/gpu/GrBackendSurface.h>
 #include <include/gpu/MutableTextureState.h>
+#include <include/gpu/vk/VulkanMutableTextureState.h>
 #include "renderthread/RenderThread.h"
 #include "utils/Color.h"
 #include "utils/PaintUtils.h"
@@ -142,8 +143,9 @@
     LOG_ALWAYS_FATAL_IF(Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan);
     if (mBackendTexture.isValid()) {
         // Passing in VK_IMAGE_LAYOUT_UNDEFINED means we keep the old layout.
-        skgpu::MutableTextureState newState(VK_IMAGE_LAYOUT_UNDEFINED,
-                                            VK_QUEUE_FAMILY_FOREIGN_EXT);
+        skgpu::MutableTextureState newState = skgpu::MutableTextureStates::MakeVulkan(
+                                                                  VK_IMAGE_LAYOUT_UNDEFINED,
+                                                                  VK_QUEUE_FAMILY_FOREIGN_EXT);
 
         // The unref for this ref happens in the releaseProc passed into setBackendTextureState. The
         // releaseProc callback will be made when the work to set the new state has finished on the
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
index d4af087..a5a841e 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -23,6 +23,7 @@
 
 #include <mutex>
 
+#include "Properties.h"
 #include "utils/Macros.h"
 
 namespace android {
@@ -60,7 +61,13 @@
     static void setWideColorDataspace(ADataSpace dataspace);
 
     static void setSupportFp16ForHdr(bool supportFp16ForHdr);
-    static bool isSupportFp16ForHdr() { return get()->mSupportFp16ForHdr; };
+    static bool isSupportFp16ForHdr() {
+        if (!Properties::hdr10bitPlus) {
+            return false;
+        }
+
+        return get()->mSupportFp16ForHdr;
+    };
 
     static void setSupportMixedColorSpaces(bool supportMixedColorSpaces);
     static bool isSupportMixedColorSpaces() { return get()->mSupportMixedColorSpaces; };
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index 00d049c..6ebfc63 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -41,6 +41,14 @@
 #endif  // __ANDROID__
 }
 
+inline bool inter_character_justification() {
+#ifdef __ANDROID__
+    return com_android_text_flags_inter_character_justification();
+#else
+    return true;
+#endif  // __ANDROID__
+}
+
 }  // namespace text_feature
 
 }  // namespace android
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index 16de21d..71f7926 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -379,7 +379,7 @@
         case kAlpha_8_SkColorType:
             formatInfo.isSupported = HardwareBitmapUploader::hasAlpha8Support();
             formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8_UNORM;
-            formatInfo.format = GL_R8;
+            formatInfo.format = GL_RED;
             formatInfo.type = GL_UNSIGNED_BYTE;
             formatInfo.vkFormat = VK_FORMAT_R8_UNORM;
             break;
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 6c3172a..755332ff 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -38,6 +38,9 @@
 constexpr bool clip_surfaceviews() {
     return false;
 }
+constexpr bool hdr_10bit_plus() {
+    return false;
+}
 }  // namespace hwui_flags
 #endif
 
@@ -56,6 +59,7 @@
 
 bool Properties::debugLayersUpdates = false;
 bool Properties::debugOverdraw = false;
+bool Properties::debugTraceGpuResourceCategories = false;
 bool Properties::showDirtyRegions = false;
 bool Properties::skipEmptyFrames = true;
 bool Properties::useBufferAge = true;
@@ -104,6 +108,7 @@
 float Properties::maxHdrHeadroomOn8bit = 5.f;  // TODO: Refine this number
 
 bool Properties::clipSurfaceViews = false;
+bool Properties::hdr10bitPlus = false;
 
 StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
 
@@ -151,10 +156,12 @@
 
     skpCaptureEnabled = debuggingEnabled && base::GetBoolProperty(PROPERTY_CAPTURE_SKP_ENABLED, false);
 
-    SkAndroidFrameworkTraceUtil::setEnableTracing(
-            base::GetBoolProperty(PROPERTY_SKIA_TRACING_ENABLED, false));
+    bool skiaBroadTracing = base::GetBoolProperty(PROPERTY_SKIA_TRACING_ENABLED, false);
+    SkAndroidFrameworkTraceUtil::setEnableTracing(skiaBroadTracing);
     SkAndroidFrameworkTraceUtil::setUsePerfettoTrackEvents(
             base::GetBoolProperty(PROPERTY_SKIA_USE_PERFETTO_TRACK_EVENTS, false));
+    debugTraceGpuResourceCategories =
+            base::GetBoolProperty(PROPERTY_TRACE_GPU_RESOURCES, skiaBroadTracing);
 
     runningInEmulator = base::GetBoolProperty(PROPERTY_IS_EMULATOR, false);
 
@@ -174,6 +181,7 @@
 
     clipSurfaceViews =
             base::GetBoolProperty("debug.hwui.clip_surfaceviews", hwui_flags::clip_surfaceviews());
+    hdr10bitPlus = hwui_flags::hdr_10bit_plus();
 
     return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
 }
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index bca57e9..ec53070 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -143,6 +143,15 @@
 #define PROPERTY_CAPTURE_SKP_ENABLED "debug.hwui.capture_skp_enabled"
 
 /**
+ * Might split Skia's GPU resource utilization into separate tracing tracks (slow).
+ *
+ * Aggregate total and purgeable numbers will still be reported under a "misc" track when this is
+ * disabled, they just won't be split into distinct categories. Results may vary depending on GPU
+ * backend/API, and the category mappings defined in ATraceMemoryDump's hardcoded sResourceMap.
+ */
+#define PROPERTY_TRACE_GPU_RESOURCES "debug.hwui.trace_gpu_resources"
+
+/**
  * Allows broad recording of Skia drawing commands.
  *
  * If disabled, a very minimal set of trace events *may* be recorded.
@@ -254,6 +263,7 @@
 
     static bool debugLayersUpdates;
     static bool debugOverdraw;
+    static bool debugTraceGpuResourceCategories;
     static bool showDirtyRegions;
     // TODO: Remove after stabilization period
     static bool skipEmptyFrames;
@@ -326,6 +336,7 @@
     static float maxHdrHeadroomOn8bit;
 
     static bool clipSurfaceViews;
+    static bool hdr10bitPlus;
 
     static StretchEffectBehavior getStretchEffectBehavior() {
         return stretchEffectBehavior;
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 0b42c88..f526a28 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -230,7 +230,7 @@
  * stencil buffer may be needed. Views that use a functor to draw will be forced onto a layer.
  */
 void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) {
-    if (mDamageGenerationId == info.damageGenerationId) {
+    if (mDamageGenerationId == info.damageGenerationId && mDamageGenerationId != 0) {
         // We hit the same node a second time in the same tree. We don't know the minimal
         // damage rect anymore, so just push the biggest we can onto our parent's transform
         // We push directly onto parent in case we are clipped to bounds but have moved position.
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 1f3834be..c904542 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -262,7 +262,7 @@
     DisplayList mDisplayList;
     DisplayList mStagingDisplayList;
 
-    int64_t mDamageGenerationId;
+    int64_t mDamageGenerationId = 0;
 
     friend class AnimatorManager;
     AnimatorManager mAnimatorManager;
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index c156c46..72ddecc 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -22,6 +22,13 @@
 }
 
 flag {
+  name: "high_contrast_text_small_text_rect"
+  namespace: "accessibility"
+  description: "Draw a solid rectangle background behind text instead of a stroke outline"
+  bug: "186567103"
+}
+
+flag {
   name: "hdr_10bit_plus"
   namespace: "core_graphics"
   description: "Use 10101010 and FP16 formats for HDR-UI when available"
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index 80b6c03..e9f4b81c 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -18,6 +18,7 @@
 
 #include <SkFontMetrics.h>
 #include <SkRRect.h>
+#include <minikin/MinikinRect.h>
 
 #include "FeatureFlags.h"
 #include "MinikinUtils.h"
@@ -107,7 +108,13 @@
     // care of all alignment.
     paint.setTextAlign(Paint::kLeft_Align);
 
-    DrawTextFunctor f(layout, this, paint, x, y, layout.getAdvance());
+    minikin::MinikinRect bounds;
+    // We only need the bounds to draw a rectangular background in high contrast mode. Let's save
+    // the cycles otherwise.
+    if (flags::high_contrast_text_small_text_rect() && isHighContrastText()) {
+        MinikinUtils::getBounds(&paint, bidiFlags, typeface, text, textSize, &bounds);
+    }
+    DrawTextFunctor f(layout, this, paint, x, y, layout.getAdvance(), bounds);
     MinikinUtils::forFontRun(layout, &paint, f);
 
     if (text_feature::fix_double_underline()) {
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
index 8f99990..ba65439 100644
--- a/libs/hwui/hwui/DrawTextFunctor.h
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -33,6 +33,8 @@
 
 namespace android {
 
+inline constexpr int kHighContrastTextBorderWidth = 4;
+
 static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
                               const Paint& paint, Canvas* canvas) {
     const SkScalar strokeWidth = fmax(thickness, 1.0f);
@@ -45,15 +47,26 @@
     paint->setShader(nullptr);
     paint->setColorFilter(nullptr);
     paint->setLooper(nullptr);
-    paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
+    paint->setStrokeWidth(kHighContrastTextBorderWidth + 0.04 * paint->getSkFont().getSize());
     paint->setStrokeJoin(SkPaint::kRound_Join);
     paint->setLooper(nullptr);
 }
 
 class DrawTextFunctor {
 public:
+    /**
+     * Creates a Functor to draw the given text layout.
+     *
+     * @param layout
+     * @param canvas
+     * @param paint
+     * @param x
+     * @param y
+     * @param totalAdvance
+     * @param bounds bounds of the text. Only required if high contrast text mode is enabled.
+     */
     DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
-                    float y, float totalAdvance)
+                    float y, float totalAdvance, const minikin::MinikinRect& bounds)
             : layout(layout)
             , canvas(canvas)
             , paint(paint)
@@ -61,7 +74,8 @@
             , y(y)
             , totalAdvance(totalAdvance)
             , underlinePosition(0)
-            , underlineThickness(0) {}
+            , underlineThickness(0)
+            , bounds(bounds) {}
 
     void operator()(size_t start, size_t end) {
         auto glyphFunc = [&](uint16_t* text, float* positions) {
@@ -91,7 +105,16 @@
             Paint outlinePaint(paint);
             simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
             outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
-            canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
+            if (flags::high_contrast_text_small_text_rect()) {
+                auto bgBounds(bounds);
+                auto padding = kHighContrastTextBorderWidth + 0.1f * paint.getSkFont().getSize();
+                bgBounds.offset(x, y);
+                canvas->drawRect(bgBounds.mLeft - padding, bgBounds.mTop - padding,
+                                 bgBounds.mRight + padding, bgBounds.mBottom + padding,
+                                 outlinePaint);
+            } else {
+                canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
+            }
 
             // inner
             gDrawTextBlobMode = DrawTextBlobMode::HctInner;
@@ -146,6 +169,7 @@
     float totalAdvance;
     float underlinePosition;
     float underlineThickness;
+    const minikin::MinikinRect& bounds;
 };
 
 }  // namespace android
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index f4ee36ec..bbb1420 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -131,11 +131,6 @@
         const std::vector<minikin::FontVariation>& variations) const {
     SkFontArguments args;
 
-    int ttcIndex;
-    std::unique_ptr<SkStreamAsset> stream(mTypeface->openStream(&ttcIndex));
-    LOG_ALWAYS_FATAL_IF(stream == nullptr, "openStream failed");
-
-    args.setCollectionIndex(ttcIndex);
     std::vector<SkFontArguments::VariationPosition::Coordinate> skVariation;
     skVariation.resize(variations.size());
     for (size_t i = 0; i < variations.size(); i++) {
@@ -143,11 +138,10 @@
         skVariation[i].value = SkFloatToScalar(variations[i].value);
     }
     args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())});
-    sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
-    sk_sp<SkTypeface> face(fm->makeFromStream(std::move(stream), args));
+    sk_sp<SkTypeface> face = mTypeface->makeClone(args);
 
     return std::make_shared<MinikinFontSkia>(std::move(face), mSourceId, mFontData, mFontSize,
-                                             mFilePath, ttcIndex, variations);
+                                             mFilePath, mTtcIndex, variations);
 }
 
 // hinting<<16 | edging<<8 | bools:5bits
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 7552b56d..5613369 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -72,10 +72,13 @@
     const minikin::Range contextRange(contextStart, contextStart + contextCount);
     const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
     const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
+    const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+                                                    ? paint->getRunFlag()
+                                                    : minikin::RunFlag::NONE;
 
     if (mt == nullptr) {
         return minikin::Layout(textBuf.substr(contextRange), range - contextStart, bidiFlags,
-                               minikinPaint, startHyphen, endHyphen);
+                               minikinPaint, startHyphen, endHyphen, minikinRunFlag);
     } else {
         return mt->buildLayout(textBuf, range, contextRange, minikinPaint, startHyphen, endHyphen);
     }
@@ -96,15 +99,18 @@
 float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags,
                                 const Typeface* typeface, const uint16_t* buf, size_t start,
                                 size_t count, size_t bufSize, float* advances,
-                                minikin::MinikinRect* bounds) {
+                                minikin::MinikinRect* bounds, uint32_t* clusterCount) {
     minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface);
     const minikin::U16StringPiece textBuf(buf, bufSize);
     const minikin::Range range(start, start + count);
     const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
     const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
+    const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+                                                    ? paint->getRunFlag()
+                                                    : minikin::RunFlag::NONE;
 
     return minikin::Layout::measureText(textBuf, range, bidiFlags, minikinPaint, startHyphen,
-                                        endHyphen, advances, bounds);
+                                        endHyphen, advances, bounds, clusterCount, minikinRunFlag);
 }
 
 minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index 61bc881..f8574ee 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -53,7 +53,7 @@
 
     static float measureText(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface,
                              const uint16_t* buf, size_t start, size_t count, size_t bufSize,
-                             float* advances, minikin::MinikinRect* bounds);
+                             float* advances, minikin::MinikinRect* bounds, uint32_t* clusterCount);
 
     static minikin::MinikinExtent getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
                                                 const Typeface* typeface, const uint16_t* buf,
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index ef4dce5..708f96e 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -25,6 +25,7 @@
 #include <minikin/FontFamily.h>
 #include <minikin/FontFeature.h>
 #include <minikin/Hyphenator.h>
+#include <minikin/Layout.h>
 
 #include <string>
 
@@ -144,6 +145,9 @@
     bool isDevKern() const { return mDevKern; }
     void setDevKern(bool d) { mDevKern = d; }
 
+    minikin::RunFlag getRunFlag() const { return mRunFlag; }
+    void setRunFlag(minikin::RunFlag runFlag) { mRunFlag = runFlag; }
+
     // Deprecated -- bitmapshaders will be taking this flag explicitly
     bool isFilterBitmap() const { return mFilterBitmap; }
     void setFilterBitmap(bool filter) { mFilterBitmap = filter; }
@@ -188,6 +192,7 @@
     bool mStrikeThru = false;
     bool mUnderline = false;
     bool mDevKern = false;
+    minikin::RunFlag mRunFlag = minikin::RunFlag::NONE;
 };
 
 }  // namespace android
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index aac928f..c32ea01 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -47,8 +47,8 @@
         , mFilterBitmap(paint.mFilterBitmap)
         , mStrikeThru(paint.mStrikeThru)
         , mUnderline(paint.mUnderline)
-        , mDevKern(paint.mDevKern) {}
-
+        , mDevKern(paint.mDevKern)
+        , mRunFlag(paint.mRunFlag) {}
 
 Paint::~Paint() {}
 
@@ -68,21 +68,19 @@
     mStrikeThru = other.mStrikeThru;
     mUnderline = other.mUnderline;
     mDevKern = other.mDevKern;
+    mRunFlag = other.mRunFlag;
     return *this;
 }
 
 bool operator==(const Paint& a, const Paint& b) {
-    return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) &&
-           a.mFont == b.mFont &&
-           a.mLooper == b.mLooper && 
-           a.mLetterSpacing == b.mLetterSpacing && a.mWordSpacing == b.mWordSpacing &&
-           a.mFontFeatureSettings == b.mFontFeatureSettings &&
+    return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) && a.mFont == b.mFont &&
+           a.mLooper == b.mLooper && a.mLetterSpacing == b.mLetterSpacing &&
+           a.mWordSpacing == b.mWordSpacing && a.mFontFeatureSettings == b.mFontFeatureSettings &&
            a.mMinikinLocaleListId == b.mMinikinLocaleListId &&
            a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit &&
            a.mTypeface == b.mTypeface && a.mAlign == b.mAlign &&
-           a.mFilterBitmap == b.mFilterBitmap &&
-           a.mStrikeThru == b.mStrikeThru && a.mUnderline == b.mUnderline &&
-           a.mDevKern == b.mDevKern;
+           a.mFilterBitmap == b.mFilterBitmap && a.mStrikeThru == b.mStrikeThru &&
+           a.mUnderline == b.mUnderline && a.mDevKern == b.mDevKern && a.mRunFlag == b.mRunFlag;
 }
 
 void Paint::reset() {
@@ -96,6 +94,7 @@
     mStrikeThru = false;
     mUnderline = false;
     mDevKern = false;
+    mRunFlag = minikin::RunFlag::NONE;
 }
 
 void Paint::setLooper(sk_sp<BlurDrawLooper> looper) {
@@ -133,6 +132,8 @@
 // flags related to minikin::Paint
 static const uint32_t sUnderlineFlag    = 0x08;
 static const uint32_t sStrikeThruFlag   = 0x10;
+static const uint32_t sTextRunLeftEdge = 0x2000;
+static const uint32_t sTextRunRightEdge = 0x4000;
 // flags no longer supported on native side (but mirrored for compatibility)
 static const uint32_t sDevKernFlag      = 0x100;
 
@@ -186,6 +187,12 @@
     flags |= -(int)mUnderline    & sUnderlineFlag;
     flags |= -(int)mDevKern      & sDevKernFlag;
     flags |= -(int)mFilterBitmap & sFilterBitmapFlag;
+    if (mRunFlag & minikin::RunFlag::LEFT_EDGE) {
+        flags |= sTextRunLeftEdge;
+    }
+    if (mRunFlag & minikin::RunFlag::RIGHT_EDGE) {
+        flags |= sTextRunRightEdge;
+    }
     return flags;
 }
 
@@ -196,6 +203,15 @@
     mUnderline    = (flags & sUnderlineFlag) != 0;
     mDevKern      = (flags & sDevKernFlag) != 0;
     mFilterBitmap = (flags & sFilterBitmapFlag) != 0;
+
+    std::underlying_type<minikin::RunFlag>::type rawFlag = minikin::RunFlag::NONE;
+    if (flags & sTextRunLeftEdge) {
+        rawFlag |= minikin::RunFlag::LEFT_EDGE;
+    }
+    if (flags & sTextRunRightEdge) {
+        rawFlag |= minikin::RunFlag::RIGHT_EDGE;
+    }
+    mRunFlag = static_cast<minikin::RunFlag>(rawFlag);
 }
 
 }  // namespace android
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 7cc4866..8315c4c 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -247,6 +247,9 @@
 static jfieldID gFontMetricsInt_bottom;
 static jfieldID gFontMetricsInt_leading;
 
+static jclass gRunInfo_class;
+static jfieldID gRunInfo_clusterCount;
+
 ///////////////////////////////////////////////////////////////////////////////
 
 void GraphicsJNI::get_jrect(JNIEnv* env, jobject obj, int* L, int* T, int* R, int* B)
@@ -511,6 +514,10 @@
     return descent - ascent + leading;
 }
 
+void GraphicsJNI::set_cluster_count_to_run_info(JNIEnv* env, jobject runInfo, jint clusterCount) {
+    env->SetIntField(runInfo, gRunInfo_clusterCount, clusterCount);
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////
 
 jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, BitmapRegionDecoderWrapper* bitmap) {
@@ -834,5 +841,10 @@
     gFontMetricsInt_bottom = GetFieldIDOrDie(env, gFontMetricsInt_class, "bottom", "I");
     gFontMetricsInt_leading = GetFieldIDOrDie(env, gFontMetricsInt_class, "leading", "I");
 
+    gRunInfo_class = FindClassOrDie(env, "android/graphics/Paint$RunInfo");
+    gRunInfo_class = MakeGlobalRefOrDie(env, gRunInfo_class);
+
+    gRunInfo_clusterCount = GetFieldIDOrDie(env, gRunInfo_class, "mClusterCount", "I");
+
     return 0;
 }
diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h
index b9fff36..b0a1074 100644
--- a/libs/hwui/jni/GraphicsJNI.h
+++ b/libs/hwui/jni/GraphicsJNI.h
@@ -77,6 +77,8 @@
     static SkRect* jrect_to_rect(JNIEnv*, jobject jrect, SkRect*);
     static void rect_to_jrectf(const SkRect&, JNIEnv*, jobject jrectf);
 
+    static void set_cluster_count_to_run_info(JNIEnv* env, jobject runInfo, jint clusterCount);
+
     static void set_jpoint(JNIEnv*, jobject jrect, int x, int y);
 
     static SkIPoint* jpoint_to_ipoint(JNIEnv*, jobject jpoint, SkIPoint* point);
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index d84b73d..286f06a 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -114,7 +114,7 @@
 
         std::unique_ptr<float[]> advancesArray(new float[count]);
         MinikinUtils::measureText(&paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, 0,
-                                  count, count, advancesArray.get(), nullptr);
+                                  count, count, advancesArray.get(), nullptr, nullptr);
 
         for (int i = 0; i < count; i++) {
             // traverse in the given direction
@@ -206,7 +206,7 @@
         }
         const float advance = MinikinUtils::measureText(
                 paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, start, count,
-                contextCount, advancesArray.get(), nullptr);
+                contextCount, advancesArray.get(), nullptr, nullptr);
         if (advances) {
             env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get());
         }
@@ -244,7 +244,7 @@
         minikin::Bidi bidiFlags = dir == 1 ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
         std::unique_ptr<float[]> advancesArray(new float[count]);
         MinikinUtils::measureText(paint, bidiFlags, typeface, text, start, count, start + count,
-                                  advancesArray.get(), nullptr);
+                                  advancesArray.get(), nullptr, nullptr);
         size_t result = minikin::GraphemeBreak::getTextRunCursor(advancesArray.get(), text,
                 start, count, offset, moveOpt);
         return static_cast<jint>(result);
@@ -508,7 +508,7 @@
     static jfloat doRunAdvance(JNIEnv* env, const Paint* paint, const Typeface* typeface,
                                const jchar buf[], jint start, jint count, jint bufSize,
                                jboolean isRtl, jint offset, jfloatArray advances,
-                               jint advancesIndex, SkRect* drawBounds) {
+                               jint advancesIndex, SkRect* drawBounds, uint32_t* clusterCount) {
         if (advances) {
             size_t advancesLength = env->GetArrayLength(advances);
             if ((size_t)(count + advancesIndex) > advancesLength) {
@@ -519,9 +519,9 @@
         minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
         minikin::MinikinRect bounds;
         if (offset == start + count && advances == nullptr) {
-            float result =
-                    MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count,
-                                              bufSize, nullptr, drawBounds ? &bounds : nullptr);
+            float result = MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count,
+                                                     bufSize, nullptr,
+                                                     drawBounds ? &bounds : nullptr, clusterCount);
             if (drawBounds) {
                 copyMinikinRectToSkRect(bounds, drawBounds);
             }
@@ -529,7 +529,8 @@
         }
         std::unique_ptr<float[]> advancesArray(new float[count]);
         MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize,
-                                  advancesArray.get(), drawBounds ? &bounds : nullptr);
+                                  advancesArray.get(), drawBounds ? &bounds : nullptr,
+                                  clusterCount);
 
         if (drawBounds) {
             copyMinikinRectToSkRect(bounds, drawBounds);
@@ -549,7 +550,7 @@
         ScopedCharArrayRO textArray(env, text);
         jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart,
                                      start - contextStart, end - start, contextEnd - contextStart,
-                                     isRtl, offset - contextStart, nullptr, 0, nullptr);
+                                     isRtl, offset - contextStart, nullptr, 0, nullptr, nullptr);
         return result;
     }
 
@@ -558,18 +559,22 @@
                                                         jint contextStart, jint contextEnd,
                                                         jboolean isRtl, jint offset,
                                                         jfloatArray advances, jint advancesIndex,
-                                                        jobject drawBounds) {
+                                                        jobject drawBounds, jobject runInfo) {
         const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
         const Typeface* typeface = paint->getAndroidTypeface();
         ScopedCharArrayRO textArray(env, text);
         SkRect skDrawBounds;
+        uint32_t clusterCount = 0;
         jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart,
                                      start - contextStart, end - start, contextEnd - contextStart,
                                      isRtl, offset - contextStart, advances, advancesIndex,
-                                     drawBounds ? &skDrawBounds : nullptr);
+                                     drawBounds ? &skDrawBounds : nullptr, &clusterCount);
         if (drawBounds != nullptr) {
             GraphicsJNI::rect_to_jrectf(skDrawBounds, env, drawBounds);
         }
+        if (runInfo) {
+            GraphicsJNI::set_cluster_count_to_run_info(env, runInfo, clusterCount);
+        }
         return result;
     }
 
@@ -578,7 +583,7 @@
         minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
         std::unique_ptr<float[]> advancesArray(new float[count]);
         MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize,
-                                  advancesArray.get(), nullptr);
+                                  advancesArray.get(), nullptr, nullptr);
         return minikin::getOffsetForAdvance(advancesArray.get(), buf, start, count, advance);
     }
 
@@ -1145,7 +1150,8 @@
          (void*)PaintGlue::getCharArrayBounds},
         {"nHasGlyph", "(JILjava/lang/String;)Z", (void*)PaintGlue::hasGlyph},
         {"nGetRunAdvance", "(J[CIIIIZI)F", (void*)PaintGlue::getRunAdvance___CIIIIZI_F},
-        {"nGetRunCharacterAdvance", "(J[CIIIIZI[FILandroid/graphics/RectF;)F",
+        {"nGetRunCharacterAdvance",
+         "(J[CIIIIZI[FILandroid/graphics/RectF;Landroid/graphics/Paint$RunInfo;)F",
          (void*)PaintGlue::getRunCharacterAdvance___CIIIIZI_FI_F},
         {"nGetOffsetForAdvance", "(J[CIIIIZF)I", (void*)PaintGlue::getOffsetForAdvance___CIIIIZF_I},
         {"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V",
diff --git a/libs/hwui/jni/PathMeasure.cpp b/libs/hwui/jni/PathMeasure.cpp
index acf893e..79acb6c 100644
--- a/libs/hwui/jni/PathMeasure.cpp
+++ b/libs/hwui/jni/PathMeasure.cpp
@@ -17,7 +17,11 @@
 
 #include "GraphicsJNI.h"
 
+#include "SkMatrix.h"
+#include "SkPath.h"
 #include "SkPathMeasure.h"
+#include "SkPoint.h"
+#include "SkScalar.h"
 
 /*  We declare an explicit pair, so that we don't have to rely on the java
     client to be sure not to edit the path while we have an active measure
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 8ba7503..d572593 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -613,6 +613,12 @@
     Paint* paint = reinterpret_cast<Paint*>(paintHandle);
     const Typeface* typeface = paint->getAndroidTypeface();
     ScopedCharArrayRO text(env, charArray);
+
+    // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+    // text as entire line mode.
+    const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+    paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
     // drawTextString and drawTextChars doesn't use context info
     get_canvas(canvasHandle)->drawText(
             text.get() + index, count,  // text buffer
@@ -620,6 +626,7 @@
             0, count,  // context range
             x, y,  // draw position
             static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr /* measured text */);
+    paint->setRunFlag(originalRunFlag);
 }
 
 static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring strObj,
@@ -629,6 +636,12 @@
     Paint* paint = reinterpret_cast<Paint*>(paintHandle);
     const Typeface* typeface = paint->getAndroidTypeface();
     const int count = end - start;
+
+    // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+    // text as entire line mode.
+    const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+    paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
     // drawTextString and drawTextChars doesn't use context info
     get_canvas(canvasHandle)->drawText(
             text.get() + start, count,  // text buffer
@@ -636,6 +649,7 @@
             0, count,  // context range
             x, y,  // draw position
             static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr /* measured text */);
+    paint->setRunFlag(originalRunFlag);
 }
 
 static void drawTextRunChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray charArray,
@@ -681,9 +695,15 @@
 
     jchar* jchars = env->GetCharArrayElements(text, NULL);
 
+    // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+    // text as entire line mode.
+    const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+    paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
     get_canvas(canvasHandle)->drawTextOnPath(jchars + index, count,
             static_cast<minikin::Bidi>(bidiFlags), *path, hOffset, vOffset, *paint, typeface);
 
+    paint->setRunFlag(originalRunFlag);
     env->ReleaseCharArrayElements(text, jchars, 0);
 }
 
@@ -697,9 +717,15 @@
     const jchar* jchars = env->GetStringChars(text, NULL);
     int count = env->GetStringLength(text);
 
+    // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+    // text as entire line mode.
+    const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+    paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
     get_canvas(canvasHandle)->drawTextOnPath(jchars, count, static_cast<minikin::Bidi>(bidiFlags),
             *path, hOffset, vOffset, *paint, typeface);
 
+    paint->setRunFlag(originalRunFlag);
     env->ReleaseStringChars(text, jchars);
 }
 
diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
index 234f42d..756b937 100644
--- a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
+++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
@@ -20,6 +20,8 @@
 
 #include <cstring>
 
+#include "GrDirectContext.h"
+
 namespace android {
 namespace uirenderer {
 namespace skiapipeline {
@@ -114,8 +116,16 @@
 
 /**
  * logTraces reads from mCurrentValues and logs the counters with ATRACE.
+ *
+ * gpuMemoryIsAlreadyInDump must be true if GrDirectContext::dumpMemoryStatistics(...) was called
+ * with this tracer, false otherwise. Leaving this false allows this function to quickly query total
+ * and purgable GPU memory without the caller having to spend time in
+ * GrDirectContext::dumpMemoryStatistics(...) first, which iterates over every resource in the GPU
+ * cache. This can save significant time, but buckets all GPU memory into a single "misc" track,
+ * which may be a loss of granularity depending on the GPU backend and the categories defined in
+ * sResourceMap.
  */
-void ATraceMemoryDump::logTraces() {
+void ATraceMemoryDump::logTraces(bool gpuMemoryIsAlreadyInDump, GrDirectContext* grContext) {
     // Accumulate data from last dumpName
     recordAndResetCountersIfNeeded("");
     uint64_t hwui_all_frame_memory = 0;
@@ -126,6 +136,20 @@
             ATRACE_INT64((std::string("Purgeable ") + it.first).c_str(), it.second.purgeableMemory);
         }
     }
+
+    if (!gpuMemoryIsAlreadyInDump && grContext) {
+        // Total GPU memory
+        int gpuResourceCount;
+        size_t gpuResourceBytes;
+        grContext->getResourceCacheUsage(&gpuResourceCount, &gpuResourceBytes);
+        hwui_all_frame_memory += (uint64_t)gpuResourceBytes;
+        ATRACE_INT64("HWUI Misc Memory", gpuResourceBytes);
+
+        // Purgable subset of GPU memory
+        size_t purgeableGpuResourceBytes = grContext->getResourceCachePurgeableBytes();
+        ATRACE_INT64("Purgeable HWUI Misc Memory", purgeableGpuResourceBytes);
+    }
+
     ATRACE_INT64("HWUI All Memory", hwui_all_frame_memory);
 }
 
diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.h b/libs/hwui/pipeline/skia/ATraceMemoryDump.h
index 4592711..777d1a2 100644
--- a/libs/hwui/pipeline/skia/ATraceMemoryDump.h
+++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <GrDirectContext.h>
 #include <SkString.h>
 #include <SkTraceMemoryDump.h>
 
@@ -50,7 +51,7 @@
 
     void startFrame();
 
-    void logTraces();
+    void logTraces(bool gpuMemoryIsAlreadyInDump, GrDirectContext* grContext);
 
 private:
     std::string mLastDumpName;
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index c5ffbb7..c8d5987 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -118,7 +118,7 @@
         const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
         const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
         const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
-        const HardwareBufferRenderParams& bufferParams) {
+        const HardwareBufferRenderParams& bufferParams, std::mutex& profilerLock) {
     if (!isCapturingSkp() && !mHardwareBuffer) {
         mEglManager.damageFrame(frame, dirty);
     }
@@ -171,6 +171,7 @@
     // Draw visual debugging features
     if (CC_UNLIKELY(Properties::showDirtyRegions ||
                     ProfileType::None != Properties::getProfileType())) {
+        std::scoped_lock lock(profilerLock);
         SkCanvas* profileCanvas = surface->getCanvas();
         SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height());
         profiler->draw(profileRenderer);
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index 098a746..ebe8b6e 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -42,7 +42,8 @@
             const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
             const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
             const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler,
-            const renderthread::HardwareBufferRenderParams& bufferParams) override;
+            const renderthread::HardwareBufferRenderParams& bufferParams,
+            std::mutex& profilerLock) override;
     GrSurfaceOrigin getSurfaceOrigin() override { return kBottomLeft_GrSurfaceOrigin; }
     bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
                      const SkRect& screenDirty, FrameInfo* currentFrameInfo,
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index e0f1f6e..326b6ed 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -650,9 +650,14 @@
             mSurfaceColorSpace = DeviceInfo::get()->getWideColorSpace();
             break;
         case ColorMode::Hdr:
-            mSurfaceColorType = SkColorType::kN32_SkColorType;
-            mSurfaceColorSpace = SkColorSpace::MakeRGB(
-                    GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
+            if (DeviceInfo::get()->isSupportFp16ForHdr()) {
+                mSurfaceColorType = SkColorType::kRGBA_F16_SkColorType;
+                mSurfaceColorSpace = SkColorSpace::MakeSRGB();
+            } else {
+                mSurfaceColorType = SkColorType::kN32_SkColorType;
+                mSurfaceColorSpace = SkColorSpace::MakeRGB(
+                        GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
+            }
             break;
         case ColorMode::Hdr10:
             mSurfaceColorType = SkColorType::kRGBA_1010102_SkColorType;
@@ -669,8 +674,13 @@
 void SkiaPipeline::setTargetSdrHdrRatio(float ratio) {
     if (mColorMode == ColorMode::Hdr || mColorMode == ColorMode::Hdr10) {
         mTargetSdrHdrRatio = ratio;
-        mSurfaceColorSpace = SkColorSpace::MakeRGB(GetExtendedTransferFunction(mTargetSdrHdrRatio),
-                                                   SkNamedGamut::kDisplayP3);
+
+        if (mColorMode == ColorMode::Hdr && DeviceInfo::get()->isSupportFp16ForHdr()) {
+            mSurfaceColorSpace = SkColorSpace::MakeSRGB();
+        } else {
+            mSurfaceColorSpace = SkColorSpace::MakeRGB(
+                    GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
+        }
     } else {
         mTargetSdrHdrRatio = 1.f;
     }
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index d747489..fd0a8e0 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -75,7 +75,7 @@
         const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
         const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
         const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
-        const HardwareBufferRenderParams& bufferParams) {
+        const HardwareBufferRenderParams& bufferParams, std::mutex& profilerLock) {
     sk_sp<SkSurface> backBuffer;
     SkMatrix preTransform;
     if (mHardwareBuffer) {
@@ -103,6 +103,7 @@
     // Draw visual debugging features
     if (CC_UNLIKELY(Properties::showDirtyRegions ||
                     ProfileType::None != Properties::getProfileType())) {
+        std::scoped_lock lock(profilerLock);
         SkCanvas* profileCanvas = backBuffer->getCanvas();
         SkAutoCanvasRestore saver(profileCanvas, true);
         profileCanvas->concat(preTransform);
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index e2ea57d..624eaa5 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -42,7 +42,8 @@
             const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
             const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
             const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler,
-            const renderthread::HardwareBufferRenderParams& bufferParams) override;
+            const renderthread::HardwareBufferRenderParams& bufferParams,
+            std::mutex& profilerLock) override;
     GrSurfaceOrigin getSurfaceOrigin() override { return kTopLeft_GrSurfaceOrigin; }
     bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
                      const SkRect& screenDirty, FrameInfo* currentFrameInfo,
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index 30d4612..ac2a936 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -124,24 +124,19 @@
     // flush and submit all work to the gpu and wait for it to finish
     mGrContext->flushAndSubmit(GrSyncCpu::kYes);
 
-    switch (mode) {
-        case TrimLevel::BACKGROUND:
-            mGrContext->freeGpuResources();
-            SkGraphics::PurgeAllCaches();
-            mRenderThread.destroyRenderingContext();
-            break;
-        case TrimLevel::UI_HIDDEN:
-            // Here we purge all the unlocked scratch resources and then toggle the resources cache
-            // limits between the background and max amounts. This causes the unlocked resources
-            // that have persistent data to be purged in LRU order.
-            mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
-            SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
-            mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly));
-            mGrContext->setResourceCacheLimit(mMaxResourceBytes);
-            SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
-            break;
-        default:
-            break;
+    if (mode >= TrimLevel::BACKGROUND) {
+        mGrContext->freeGpuResources();
+        SkGraphics::PurgeAllCaches();
+        mRenderThread.destroyRenderingContext();
+    } else if (mode == TrimLevel::UI_HIDDEN) {
+        // Here we purge all the unlocked scratch resources and then toggle the resources cache
+        // limits between the background and max amounts. This causes the unlocked resources
+        // that have persistent data to be purged in LRU order.
+        mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
+        SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
+        mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly));
+        mGrContext->setResourceCacheLimit(mMaxResourceBytes);
+        SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
     }
 }
 
@@ -269,13 +264,14 @@
     cancelDestroyContext();
     mFrameCompletions.next() = systemTime(CLOCK_MONOTONIC);
     if (ATRACE_ENABLED()) {
+        ATRACE_NAME("dumpingMemoryStatistics");
         static skiapipeline::ATraceMemoryDump tracer;
         tracer.startFrame();
         SkGraphics::DumpMemoryStatistics(&tracer);
-        if (mGrContext) {
+        if (mGrContext && Properties::debugTraceGpuResourceCategories) {
             mGrContext->dumpMemoryStatistics(&tracer);
         }
-        tracer.logTraces();
+        tracer.logTraces(Properties::debugTraceGpuResourceCategories, mGrContext.get());
     }
 }
 
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 56b52dc..9c7f7cc 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -625,14 +625,9 @@
     {
         // FrameInfoVisualizer accesses the frame events, which cannot be mutated mid-draw
         // or it can lead to memory corruption.
-        // This lock is overly broad, but it's the quickest fix since this mutex is otherwise
-        // not visible to IRenderPipeline much less FrameInfoVisualizer. And since this is
-        // the thread we're primarily concerned about being responsive, this being too broad
-        // shouldn't pose a performance issue.
-        std::scoped_lock lock(mFrameMetricsReporterMutex);
-        drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
-                                           &mLayerUpdateQueue, mContentDrawBounds, mOpaque,
-                                           mLightInfo, mRenderNodes, &(profiler()), mBufferParams);
+        drawResult = mRenderPipeline->draw(
+                frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue, mContentDrawBounds,
+                mOpaque, mLightInfo, mRenderNodes, &(profiler()), mBufferParams, profilerLock());
     }
 
     uint64_t frameCompleteNr = getFrameNumber();
@@ -762,7 +757,7 @@
             mCurrentFrameInfo->markFrameCompleted();
             mCurrentFrameInfo->set(FrameInfoIndex::GpuCompleted)
                     = mCurrentFrameInfo->get(FrameInfoIndex::FrameCompleted);
-            std::scoped_lock lock(mFrameMetricsReporterMutex);
+            std::scoped_lock lock(mFrameInfoMutex);
             mJankTracker.finishFrame(*mCurrentFrameInfo, mFrameMetricsReporter, frameCompleteNr,
                                      mSurfaceControlGenerationId);
         }
@@ -791,7 +786,7 @@
 
 void CanvasContext::reportMetricsWithPresentTime() {
     {  // acquire lock
-        std::scoped_lock lock(mFrameMetricsReporterMutex);
+        std::scoped_lock lock(mFrameInfoMutex);
         if (mFrameMetricsReporter == nullptr) {
             return;
         }
@@ -826,7 +821,7 @@
 
     forthBehind->set(FrameInfoIndex::DisplayPresentTime) = presentTime;
     {  // acquire lock
-        std::scoped_lock lock(mFrameMetricsReporterMutex);
+        std::scoped_lock lock(mFrameInfoMutex);
         if (mFrameMetricsReporter != nullptr) {
             mFrameMetricsReporter->reportFrameMetrics(forthBehind->data(), true /*hasPresentTime*/,
                                                       frameNumber, surfaceControlId);
@@ -835,7 +830,7 @@
 }
 
 void CanvasContext::addFrameMetricsObserver(FrameMetricsObserver* observer) {
-    std::scoped_lock lock(mFrameMetricsReporterMutex);
+    std::scoped_lock lock(mFrameInfoMutex);
     if (mFrameMetricsReporter.get() == nullptr) {
         mFrameMetricsReporter.reset(new FrameMetricsReporter());
     }
@@ -849,7 +844,7 @@
 }
 
 void CanvasContext::removeFrameMetricsObserver(FrameMetricsObserver* observer) {
-    std::scoped_lock lock(mFrameMetricsReporterMutex);
+    std::scoped_lock lock(mFrameInfoMutex);
     if (mFrameMetricsReporter.get() != nullptr) {
         mFrameMetricsReporter->removeObserver(observer);
         if (!mFrameMetricsReporter->hasObservers()) {
@@ -886,7 +881,7 @@
     FrameInfo* frameInfo = instance->getFrameInfoFromLast4(frameNumber, surfaceControlId);
 
     if (frameInfo != nullptr) {
-        std::scoped_lock lock(instance->mFrameMetricsReporterMutex);
+        std::scoped_lock lock(instance->mFrameInfoMutex);
         frameInfo->set(FrameInfoIndex::FrameCompleted) = std::max(gpuCompleteTime,
                 frameInfo->get(FrameInfoIndex::SwapBuffersCompleted));
         frameInfo->set(FrameInfoIndex::GpuCompleted) = std::max(
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index be9b649..e2e3fa3 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -164,6 +164,7 @@
     void notifyFramePending();
 
     FrameInfoVisualizer& profiler() { return mProfiler; }
+    std::mutex& profilerLock() { return mFrameInfoMutex; }
 
     void dumpFrames(int fd);
     void resetFrameStats();
@@ -342,9 +343,8 @@
     std::string mName;
     JankTracker mJankTracker;
     FrameInfoVisualizer mProfiler;
-    std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter
-            GUARDED_BY(mFrameMetricsReporterMutex);
-    std::mutex mFrameMetricsReporterMutex;
+    std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter GUARDED_BY(mFrameInfoMutex);
+    std::mutex mFrameInfoMutex;
 
     std::set<RenderNode*> mPrefetchedLayers;
 
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index facf30b..2904dfe 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -441,22 +441,32 @@
             colorMode = ColorMode::Default;
         }
 
-        if (DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType) {
+        // TODO: maybe we want to get rid of the WCG check if overlay properties just works?
+        const bool canUseFp16 = DeviceInfo::get()->isSupportFp16ForHdr() ||
+                                DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType;
+
+        if (canUseFp16) {
             if (mEglConfigF16 == EGL_NO_CONFIG_KHR) {
                 colorMode = ColorMode::Default;
             } else {
                 config = mEglConfigF16;
             }
         }
+
         if (EglExtensions.glColorSpace) {
             attribs[0] = EGL_GL_COLORSPACE_KHR;
             switch (colorMode) {
                 case ColorMode::Default:
                     attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR;
                     break;
+                case ColorMode::Hdr:
+                    if (canUseFp16) {
+                        attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT;
+                        break;
+                        // No fp16 support so fallthrough to HDR10
+                    }
                 // We don't have an EGL colorspace for extended range P3 that's used for HDR
                 // So override it after configuring the EGL context
-                case ColorMode::Hdr:
                 case ColorMode::Hdr10:
                     overrideWindowDataSpaceForHdr = true;
                     attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index 9c879d5..b8c3a4d 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -68,7 +68,8 @@
                             const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
                             const std::vector<sp<RenderNode>>& renderNodes,
                             FrameInfoVisualizer* profiler,
-                            const HardwareBufferRenderParams& bufferParams) = 0;
+                            const HardwareBufferRenderParams& bufferParams,
+                            std::mutex& profilerLock) = 0;
     virtual bool swapBuffers(const Frame& frame, IRenderPipeline::DrawResult&,
                              const SkRect& screenDirty, FrameInfo* currentFrameInfo,
                              bool* requireSwap) = 0;
diff --git a/libs/hwui/tests/unit/UnderlineTest.cpp b/libs/hwui/tests/unit/UnderlineTest.cpp
index c70a304..9911bfa 100644
--- a/libs/hwui/tests/unit/UnderlineTest.cpp
+++ b/libs/hwui/tests/unit/UnderlineTest.cpp
@@ -103,8 +103,9 @@
     // Create minikin::Layout
     std::unique_ptr<Typeface> typeface(makeTypeface());
     minikin::Layout layout = doLayout(text, *paint, typeface.get());
+    minikin::MinikinRect bounds;
 
-    DrawTextFunctor f(layout, &canvas, *paint, 0, 0, layout.getAdvance());
+    DrawTextFunctor f(layout, &canvas, *paint, 0, 0, layout.getAdvance(), bounds);
     MinikinUtils::forFontRun(layout, paint, f);
     return f;
 }
diff --git a/libs/hwui/utils/HostColorSpace.cpp b/libs/hwui/utils/HostColorSpace.cpp
deleted file mode 100644
index 77a6820..0000000
--- a/libs/hwui/utils/HostColorSpace.cpp
+++ /dev/null
@@ -1,417 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// This is copied from framework/native/libs/ui in order not to include libui in host build
-
-#include <ui/ColorSpace.h>
-
-using namespace std::placeholders;
-
-namespace android {
-
-static constexpr float linearResponse(float v) {
-    return v;
-}
-
-static constexpr float rcpResponse(float x, const ColorSpace::TransferParameters& p) {
-    return x >= p.d * p.c ? (std::pow(x, 1.0f / p.g) - p.b) / p.a : x / p.c;
-}
-
-static constexpr float response(float x, const ColorSpace::TransferParameters& p) {
-    return x >= p.d ? std::pow(p.a * x + p.b, p.g) : p.c * x;
-}
-
-static constexpr float rcpFullResponse(float x, const ColorSpace::TransferParameters& p) {
-    return x >= p.d * p.c ? (std::pow(x - p.e, 1.0f / p.g) - p.b) / p.a : (x - p.f) / p.c;
-}
-
-static constexpr float fullResponse(float x, const ColorSpace::TransferParameters& p) {
-    return x >= p.d ? std::pow(p.a * x + p.b, p.g) + p.e : p.c * x + p.f;
-}
-
-static float absRcpResponse(float x, float g,float a, float b, float c, float d) {
-    float xx = std::abs(x);
-    return std::copysign(xx >= d * c ? (std::pow(xx, 1.0f / g) - b) / a : xx / c, x);
-}
-
-static float absResponse(float x, float g, float a, float b, float c, float d) {
-   float xx = std::abs(x);
-   return std::copysign(xx >= d ? std::pow(a * xx + b, g) : c * xx, x);
-}
-
-static float safePow(float x, float e) {
-    return powf(x < 0.0f ? 0.0f : x, e);
-}
-
-static ColorSpace::transfer_function toOETF(const ColorSpace::TransferParameters& parameters) {
-    if (parameters.e == 0.0f && parameters.f == 0.0f) {
-        return std::bind(rcpResponse, _1, parameters);
-    }
-    return std::bind(rcpFullResponse, _1, parameters);
-}
-
-static ColorSpace::transfer_function toEOTF( const ColorSpace::TransferParameters& parameters) {
-    if (parameters.e == 0.0f && parameters.f == 0.0f) {
-        return std::bind(response, _1, parameters);
-    }
-    return std::bind(fullResponse, _1, parameters);
-}
-
-static ColorSpace::transfer_function toOETF(float gamma) {
-    if (gamma == 1.0f) {
-        return linearResponse;
-    }
-    return std::bind(safePow, _1, 1.0f / gamma);
-}
-
-static ColorSpace::transfer_function toEOTF(float gamma) {
-    if (gamma == 1.0f) {
-        return linearResponse;
-    }
-    return std::bind(safePow, _1, gamma);
-}
-
-static constexpr std::array<float2, 3> computePrimaries(const mat3& rgbToXYZ) {
-    float3 r(rgbToXYZ * float3{1, 0, 0});
-    float3 g(rgbToXYZ * float3{0, 1, 0});
-    float3 b(rgbToXYZ * float3{0, 0, 1});
-
-    return {{r.xy / dot(r, float3{1}),
-             g.xy / dot(g, float3{1}),
-             b.xy / dot(b, float3{1})}};
-}
-
-static constexpr float2 computeWhitePoint(const mat3& rgbToXYZ) {
-    float3 w(rgbToXYZ * float3{1});
-    return w.xy / dot(w, float3{1});
-}
-
-ColorSpace::ColorSpace(
-        const std::string& name,
-        const mat3& rgbToXYZ,
-        transfer_function OETF,
-        transfer_function EOTF,
-        clamping_function clamper) noexcept
-        : mName(name)
-        , mRGBtoXYZ(rgbToXYZ)
-        , mXYZtoRGB(inverse(rgbToXYZ))
-        , mOETF(std::move(OETF))
-        , mEOTF(std::move(EOTF))
-        , mClamper(std::move(clamper))
-        , mPrimaries(computePrimaries(rgbToXYZ))
-        , mWhitePoint(computeWhitePoint(rgbToXYZ)) {
-}
-
-ColorSpace::ColorSpace(
-        const std::string& name,
-        const mat3& rgbToXYZ,
-        const TransferParameters parameters,
-        clamping_function clamper) noexcept
-        : mName(name)
-        , mRGBtoXYZ(rgbToXYZ)
-        , mXYZtoRGB(inverse(rgbToXYZ))
-        , mParameters(parameters)
-        , mOETF(toOETF(mParameters))
-        , mEOTF(toEOTF(mParameters))
-        , mClamper(std::move(clamper))
-        , mPrimaries(computePrimaries(rgbToXYZ))
-        , mWhitePoint(computeWhitePoint(rgbToXYZ)) {
-}
-
-ColorSpace::ColorSpace(
-        const std::string& name,
-        const mat3& rgbToXYZ,
-        float gamma,
-        clamping_function clamper) noexcept
-        : mName(name)
-        , mRGBtoXYZ(rgbToXYZ)
-        , mXYZtoRGB(inverse(rgbToXYZ))
-        , mParameters({gamma, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f})
-        , mOETF(toOETF(gamma))
-        , mEOTF(toEOTF(gamma))
-        , mClamper(std::move(clamper))
-        , mPrimaries(computePrimaries(rgbToXYZ))
-        , mWhitePoint(computeWhitePoint(rgbToXYZ)) {
-}
-
-ColorSpace::ColorSpace(
-        const std::string& name,
-        const std::array<float2, 3>& primaries,
-        const float2& whitePoint,
-        transfer_function OETF,
-        transfer_function EOTF,
-        clamping_function clamper) noexcept
-        : mName(name)
-        , mRGBtoXYZ(computeXYZMatrix(primaries, whitePoint))
-        , mXYZtoRGB(inverse(mRGBtoXYZ))
-        , mOETF(std::move(OETF))
-        , mEOTF(std::move(EOTF))
-        , mClamper(std::move(clamper))
-        , mPrimaries(primaries)
-        , mWhitePoint(whitePoint) {
-}
-
-ColorSpace::ColorSpace(
-        const std::string& name,
-        const std::array<float2, 3>& primaries,
-        const float2& whitePoint,
-        const TransferParameters parameters,
-        clamping_function clamper) noexcept
-        : mName(name)
-        , mRGBtoXYZ(computeXYZMatrix(primaries, whitePoint))
-        , mXYZtoRGB(inverse(mRGBtoXYZ))
-        , mParameters(parameters)
-        , mOETF(toOETF(mParameters))
-        , mEOTF(toEOTF(mParameters))
-        , mClamper(std::move(clamper))
-        , mPrimaries(primaries)
-        , mWhitePoint(whitePoint) {
-}
-
-ColorSpace::ColorSpace(
-        const std::string& name,
-        const std::array<float2, 3>& primaries,
-        const float2& whitePoint,
-        float gamma,
-        clamping_function clamper) noexcept
-        : mName(name)
-        , mRGBtoXYZ(computeXYZMatrix(primaries, whitePoint))
-        , mXYZtoRGB(inverse(mRGBtoXYZ))
-        , mParameters({gamma, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f})
-        , mOETF(toOETF(gamma))
-        , mEOTF(toEOTF(gamma))
-        , mClamper(std::move(clamper))
-        , mPrimaries(primaries)
-        , mWhitePoint(whitePoint) {
-}
-
-constexpr mat3 ColorSpace::computeXYZMatrix(
-        const std::array<float2, 3>& primaries, const float2& whitePoint) {
-    const float2& R = primaries[0];
-    const float2& G = primaries[1];
-    const float2& B = primaries[2];
-    const float2& W = whitePoint;
-
-    float oneRxRy = (1 - R.x) / R.y;
-    float oneGxGy = (1 - G.x) / G.y;
-    float oneBxBy = (1 - B.x) / B.y;
-    float oneWxWy = (1 - W.x) / W.y;
-
-    float RxRy = R.x / R.y;
-    float GxGy = G.x / G.y;
-    float BxBy = B.x / B.y;
-    float WxWy = W.x / W.y;
-
-    float BY =
-            ((oneWxWy - oneRxRy) * (GxGy - RxRy) - (WxWy - RxRy) * (oneGxGy - oneRxRy)) /
-            ((oneBxBy - oneRxRy) * (GxGy - RxRy) - (BxBy - RxRy) * (oneGxGy - oneRxRy));
-    float GY = (WxWy - RxRy - BY * (BxBy - RxRy)) / (GxGy - RxRy);
-    float RY = 1 - GY - BY;
-
-    float RYRy = RY / R.y;
-    float GYGy = GY / G.y;
-    float BYBy = BY / B.y;
-
-    return {
-        float3{RYRy * R.x, RY, RYRy * (1 - R.x - R.y)},
-        float3{GYGy * G.x, GY, GYGy * (1 - G.x - G.y)},
-        float3{BYBy * B.x, BY, BYBy * (1 - B.x - B.y)}
-    };
-}
-
-const ColorSpace ColorSpace::sRGB() {
-    return {
-        "sRGB IEC61966-2.1",
-        {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
-        {0.3127f, 0.3290f},
-        {2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f, 0.0f, 0.0f}
-    };
-}
-
-const ColorSpace ColorSpace::linearSRGB() {
-    return {
-        "sRGB IEC61966-2.1 (Linear)",
-        {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
-        {0.3127f, 0.3290f}
-    };
-}
-
-const ColorSpace ColorSpace::extendedSRGB() {
-    return {
-        "scRGB-nl IEC 61966-2-2:2003",
-        {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
-        {0.3127f, 0.3290f},
-        std::bind(absRcpResponse, _1, 2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f),
-        std::bind(absResponse,    _1, 2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f),
-        std::bind(clamp<float>, _1, -0.799f, 2.399f)
-    };
-}
-
-const ColorSpace ColorSpace::linearExtendedSRGB() {
-    return {
-        "scRGB IEC 61966-2-2:2003",
-        {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
-        {0.3127f, 0.3290f},
-        1.0f,
-        std::bind(clamp<float>, _1, -0.5f, 7.499f)
-    };
-}
-
-const ColorSpace ColorSpace::NTSC() {
-    return {
-        "NTSC (1953)",
-        {{float2{0.67f, 0.33f}, {0.21f, 0.71f}, {0.14f, 0.08f}}},
-        {0.310f, 0.316f},
-        {1 / 0.45f, 1 / 1.099f, 0.099f / 1.099f, 1 / 4.5f, 0.081f, 0.0f, 0.0f}
-    };
-}
-
-const ColorSpace ColorSpace::BT709() {
-    return {
-        "Rec. ITU-R BT.709-5",
-        {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
-        {0.3127f, 0.3290f},
-        {1 / 0.45f, 1 / 1.099f, 0.099f / 1.099f, 1 / 4.5f, 0.081f, 0.0f, 0.0f}
-    };
-}
-
-const ColorSpace ColorSpace::BT2020() {
-    return {
-        "Rec. ITU-R BT.2020-1",
-        {{float2{0.708f, 0.292f}, {0.170f, 0.797f}, {0.131f, 0.046f}}},
-        {0.3127f, 0.3290f},
-        {1 / 0.45f, 1 / 1.099f, 0.099f / 1.099f, 1 / 4.5f, 0.081f, 0.0f, 0.0f}
-    };
-}
-
-const ColorSpace ColorSpace::AdobeRGB() {
-    return {
-        "Adobe RGB (1998)",
-        {{float2{0.64f, 0.33f}, {0.21f, 0.71f}, {0.15f, 0.06f}}},
-        {0.3127f, 0.3290f},
-        2.2f
-    };
-}
-
-const ColorSpace ColorSpace::ProPhotoRGB() {
-    return {
-        "ROMM RGB ISO 22028-2:2013",
-        {{float2{0.7347f, 0.2653f}, {0.1596f, 0.8404f}, {0.0366f, 0.0001f}}},
-        {0.34567f, 0.35850f},
-        {1.8f, 1.0f, 0.0f, 1 / 16.0f, 0.031248f, 0.0f, 0.0f}
-    };
-}
-
-const ColorSpace ColorSpace::DisplayP3() {
-    return {
-        "Display P3",
-        {{float2{0.680f, 0.320f}, {0.265f, 0.690f}, {0.150f, 0.060f}}},
-        {0.3127f, 0.3290f},
-        {2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.039f, 0.0f, 0.0f}
-    };
-}
-
-const ColorSpace ColorSpace::DCIP3() {
-    return {
-        "SMPTE RP 431-2-2007 DCI (P3)",
-        {{float2{0.680f, 0.320f}, {0.265f, 0.690f}, {0.150f, 0.060f}}},
-        {0.314f, 0.351f},
-        2.6f
-    };
-}
-
-const ColorSpace ColorSpace::ACES() {
-    return {
-        "SMPTE ST 2065-1:2012 ACES",
-        {{float2{0.73470f, 0.26530f}, {0.0f, 1.0f}, {0.00010f, -0.0770f}}},
-        {0.32168f, 0.33767f},
-        1.0f,
-        std::bind(clamp<float>, _1, -65504.0f, 65504.0f)
-    };
-}
-
-const ColorSpace ColorSpace::ACEScg() {
-    return {
-        "Academy S-2014-004 ACEScg",
-        {{float2{0.713f, 0.293f}, {0.165f, 0.830f}, {0.128f, 0.044f}}},
-        {0.32168f, 0.33767f},
-        1.0f,
-        std::bind(clamp<float>, _1, -65504.0f, 65504.0f)
-    };
-}
-
-std::unique_ptr<float3[]> ColorSpace::createLUT(uint32_t size, const ColorSpace& src,
-                                                const ColorSpace& dst) {
-    size = clamp(size, 2u, 256u);
-    float m = 1.0f / float(size - 1);
-
-    std::unique_ptr<float3[]> lut(new float3[size * size * size]);
-    float3* data = lut.get();
-
-    ColorSpaceConnector connector(src, dst);
-
-    for (uint32_t z = 0; z < size; z++) {
-        for (int32_t y = int32_t(size - 1); y >= 0; y--) {
-            for (uint32_t x = 0; x < size; x++) {
-                *data++ = connector.transform({x * m, y * m, z * m});
-            }
-        }
-    }
-
-    return lut;
-}
-
-static const float2 ILLUMINANT_D50_XY = {0.34567f, 0.35850f};
-static const float3 ILLUMINANT_D50_XYZ = {0.964212f, 1.0f, 0.825188f};
-static const mat3 BRADFORD = mat3{
-    float3{ 0.8951f, -0.7502f,  0.0389f},
-    float3{ 0.2664f,  1.7135f, -0.0685f},
-    float3{-0.1614f,  0.0367f,  1.0296f}
-};
-
-static mat3 adaptation(const mat3& matrix, const float3& srcWhitePoint, const float3& dstWhitePoint) {
-    float3 srcLMS = matrix * srcWhitePoint;
-    float3 dstLMS = matrix * dstWhitePoint;
-    return inverse(matrix) * mat3{dstLMS / srcLMS} * matrix;
-}
-
-ColorSpaceConnector::ColorSpaceConnector(
-        const ColorSpace& src,
-        const ColorSpace& dst) noexcept
-        : mSource(src)
-        , mDestination(dst) {
-
-    if (all(lessThan(abs(src.getWhitePoint() - dst.getWhitePoint()), float2{1e-3f}))) {
-        mTransform = dst.getXYZtoRGB() * src.getRGBtoXYZ();
-    } else {
-        mat3 rgbToXYZ(src.getRGBtoXYZ());
-        mat3 xyzToRGB(dst.getXYZtoRGB());
-
-        float3 srcXYZ = ColorSpace::XYZ(float3{src.getWhitePoint(), 1});
-        float3 dstXYZ = ColorSpace::XYZ(float3{dst.getWhitePoint(), 1});
-
-        if (any(greaterThan(abs(src.getWhitePoint() - ILLUMINANT_D50_XY), float2{1e-3f}))) {
-            rgbToXYZ = adaptation(BRADFORD, srcXYZ, ILLUMINANT_D50_XYZ) * src.getRGBtoXYZ();
-        }
-
-        if (any(greaterThan(abs(dst.getWhitePoint() - ILLUMINANT_D50_XY), float2{1e-3f}))) {
-            xyzToRGB = inverse(adaptation(BRADFORD, dstXYZ, ILLUMINANT_D50_XYZ) * dst.getRGBtoXYZ());
-        }
-
-        mTransform = xyzToRGB * rgbToXYZ;
-    }
-}
-
-}; // namespace android
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index bba9c97..f84107e 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -111,7 +111,11 @@
       : PointerController(
                 policy, looper, spriteController, enabled,
                 [](const sp<android::gui::WindowInfosListener>& listener) {
-                    SurfaceComposerClient::getDefault()->addWindowInfosListener(listener);
+                    auto initialInfo = std::make_pair(std::vector<android::gui::WindowInfo>{},
+                                                      std::vector<android::gui::DisplayInfo>{});
+                    SurfaceComposerClient::getDefault()->addWindowInfosListener(listener,
+                                                                                &initialInfo);
+                    return initialInfo.second;
                 },
                 [](const sp<android::gui::WindowInfosListener>& listener) {
                     SurfaceComposerClient::getDefault()->removeWindowInfosListener(listener);
@@ -119,8 +123,9 @@
 
 PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
                                      const sp<Looper>& looper, SpriteController& spriteController,
-                                     bool enabled, WindowListenerConsumer registerListener,
-                                     WindowListenerConsumer unregisterListener)
+                                     bool enabled,
+                                     const WindowListenerRegisterConsumer& registerListener,
+                                     WindowListenerUnregisterConsumer unregisterListener)
       : mEnabled(enabled),
         mContext(policy, looper, spriteController, *this),
         mCursorController(mContext),
@@ -128,7 +133,8 @@
         mUnregisterWindowInfosListener(std::move(unregisterListener)) {
     std::scoped_lock lock(getLock());
     mLocked.presentation = Presentation::SPOT;
-    registerListener(mDisplayInfoListener);
+    const auto& initialDisplayInfos = registerListener(mDisplayInfoListener);
+    onDisplayInfosChangedLocked(initialDisplayInfos);
 }
 
 PointerController::~PointerController() {
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index a8b9633..6ee5707 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -79,14 +79,16 @@
     std::string dump() override;
 
 protected:
-    using WindowListenerConsumer =
+    using WindowListenerRegisterConsumer = std::function<std::vector<gui::DisplayInfo>(
+            const sp<android::gui::WindowInfosListener>&)>;
+    using WindowListenerUnregisterConsumer =
             std::function<void(const sp<android::gui::WindowInfosListener>&)>;
 
     // Constructor used to test WindowInfosListener registration.
     PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
                       SpriteController& spriteController, bool enabled,
-                      WindowListenerConsumer registerListener,
-                      WindowListenerConsumer unregisterListener);
+                      const WindowListenerRegisterConsumer& registerListener,
+                      WindowListenerUnregisterConsumer unregisterListener);
 
     PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
                       SpriteController& spriteController, bool enabled);
@@ -129,7 +131,7 @@
     };
 
     sp<DisplayInfoListener> mDisplayInfoListener;
-    const WindowListenerConsumer mUnregisterWindowInfosListener;
+    const WindowListenerUnregisterConsumer mUnregisterWindowInfosListener;
 
     const ui::Transform& getTransformForDisplayLocked(int displayId) const REQUIRES(getLock());
 
diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp
index b8de919..99952aa 100644
--- a/libs/input/TouchSpotController.cpp
+++ b/libs/input/TouchSpotController.cpp
@@ -93,7 +93,7 @@
         const PointerCoords& c = spotCoords[spotIdToIndex[id]];
         ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f, displayId=%" PRId32 ".", id,
               c.getAxisValue(AMOTION_EVENT_AXIS_X), c.getAxisValue(AMOTION_EVENT_AXIS_Y),
-              c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), displayId);
+              c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), mDisplayId);
     }
 #endif
 
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index adfa91e..a1bb5b3 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -160,9 +160,11 @@
           : PointerController(
                     policy, looper, spriteController,
                     /*enabled=*/true,
-                    [&registeredListener](const sp<android::gui::WindowInfosListener>& listener) {
+                    [&registeredListener](const sp<android::gui::WindowInfosListener>& listener)
+                            -> std::vector<gui::DisplayInfo> {
                         // Register listener
                         registeredListener = listener;
+                        return {};
                     },
                     [&registeredListener](const sp<android::gui::WindowInfosListener>& listener) {
                         // Unregister listener
diff --git a/lint-baseline.xml b/lint-baseline.xml
index 79b2155..660884a 100644
--- a/lint-baseline.xml
+++ b/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.1.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NonUserGetterCalled"
@@ -433,7 +433,7 @@
     <issue
         id="NonUserGetterCalled"
         message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. "
-        errorLine1="        boolean cap = System.getInt(resolver, System.TEXT_AUTO_CAPS, 1) > 0;"
+        errorLine1="        boolean cap = System.getInt(resolver, System.TEXT_AUTO_CAPS, 1) &gt; 0;"
         errorLine2="                             ~~~~~~">
         <location
             file="frameworks/base/core/java/android/text/method/TextKeyListener.java"
@@ -444,7 +444,7 @@
     <issue
         id="NonUserGetterCalled"
         message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. "
-        errorLine1="        boolean text = System.getInt(resolver, System.TEXT_AUTO_REPLACE, 1) > 0;"
+        errorLine1="        boolean text = System.getInt(resolver, System.TEXT_AUTO_REPLACE, 1) &gt; 0;"
         errorLine2="                              ~~~~~~">
         <location
             file="frameworks/base/core/java/android/text/method/TextKeyListener.java"
@@ -455,7 +455,7 @@
     <issue
         id="NonUserGetterCalled"
         message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. "
-        errorLine1="        boolean period = System.getInt(resolver, System.TEXT_AUTO_PUNCTUATE, 1) > 0;"
+        errorLine1="        boolean period = System.getInt(resolver, System.TEXT_AUTO_PUNCTUATE, 1) &gt; 0;"
         errorLine2="                                ~~~~~~">
         <location
             file="frameworks/base/core/java/android/text/method/TextKeyListener.java"
@@ -466,7 +466,7 @@
     <issue
         id="NonUserGetterCalled"
         message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. "
-        errorLine1="        boolean pw = System.getInt(resolver, System.TEXT_SHOW_PASSWORD, 1) > 0;"
+        errorLine1="        boolean pw = System.getInt(resolver, System.TEXT_SHOW_PASSWORD, 1) &gt; 0;"
         errorLine2="                            ~~~~~~">
         <location
             file="frameworks/base/core/java/android/text/method/TextKeyListener.java"
@@ -562,4 +562,4 @@
             column="74"/>
     </issue>
 
-</issues>
+</issues>
\ No newline at end of file
diff --git a/location/java/android/location/altitude/AltitudeConverter.java b/location/java/android/location/altitude/AltitudeConverter.java
index 3dc024ef..6f88912 100644
--- a/location/java/android/location/altitude/AltitudeConverter.java
+++ b/location/java/android/location/altitude/AltitudeConverter.java
@@ -19,9 +19,11 @@
 import android.annotation.NonNull;
 import android.annotation.WorkerThread;
 import android.content.Context;
+import android.frameworks.location.altitude.GetGeoidHeightRequest;
+import android.frameworks.location.altitude.GetGeoidHeightResponse;
 import android.location.Location;
 
-import com.android.internal.location.altitude.GeoidHeightMap;
+import com.android.internal.location.altitude.GeoidMap;
 import com.android.internal.location.altitude.S2CellIdUtils;
 import com.android.internal.location.altitude.nano.MapParamsProto;
 import com.android.internal.util.Preconditions;
@@ -37,7 +39,7 @@
  * <pre>
  * Brian Julian and Michael Angermann.
  * "Resource efficient and accurate altitude conversion to Mean Sea Level."
- * To appear in 2023 IEEE/ION Position, Location and Navigation Symposium (PLANS).
+ * 2023 IEEE/ION Position, Location and Navigation Symposium (PLANS).
  * </pre>
  */
 public final class AltitudeConverter {
@@ -45,8 +47,8 @@
     private static final double MAX_ABS_VALID_LATITUDE = 90;
     private static final double MAX_ABS_VALID_LONGITUDE = 180;
 
-    /** Manages a mapping of geoid heights associated with S2 cells. */
-    private final GeoidHeightMap mGeoidHeightMap = new GeoidHeightMap();
+    /** Manages a mapping of geoid heights and expiration distances associated with S2 cells. */
+    private final GeoidMap mGeoidMap = new GeoidMap();
 
     /**
      * Creates an instance that manages an independent cache to optimized conversions of locations
@@ -78,75 +80,87 @@
     /**
      * Returns the four S2 cell IDs for the map square associated with the {@code location}.
      *
-     * <p>The first map cell contains the location, while the others are located horizontally,
-     * vertically, and diagonally, in that order, with respect to the S2 (i,j) coordinate system. If
-     * the diagonal map cell does not exist (i.e., the location is near an S2 cube vertex), its
-     * corresponding ID is set to zero.
+     * <p>The first map cell, denoted z11 in the appendix of the referenced paper above, contains
+     * the location. The others are the map cells denoted z21, z12, and z22, in that order.
      */
-    @NonNull
-    private static long[] findMapSquare(@NonNull MapParamsProto params,
+    private static long[] findMapSquare(@NonNull MapParamsProto geoidHeightParams,
             @NonNull Location location) {
         long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
                 location.getLongitude());
 
         // Cell-space properties and coordinates.
-        int sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - params.mapS2Level);
+        int sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - geoidHeightParams.mapS2Level);
         int maxIj = 1 << S2CellIdUtils.MAX_LEVEL;
-        long s0 = S2CellIdUtils.getParent(s2CellId, params.mapS2Level);
-        int f0 = S2CellIdUtils.getFace(s2CellId);
-        int i0 = S2CellIdUtils.getI(s2CellId);
-        int j0 = S2CellIdUtils.getJ(s2CellId);
-        int i1 = i0 + sizeIj;
-        int j1 = j0 + sizeIj;
+        long z11 = S2CellIdUtils.getParent(s2CellId, geoidHeightParams.mapS2Level);
+        int f11 = S2CellIdUtils.getFace(s2CellId);
+        int i1 = S2CellIdUtils.getI(s2CellId);
+        int j1 = S2CellIdUtils.getJ(s2CellId);
+        int i2 = i1 + sizeIj;
+        int j2 = j1 + sizeIj;
 
         // Non-boundary region calculation - simplest and most common case.
-        if (i1 < maxIj && j1 < maxIj) {
-            return new long[]{
-                    s0,
-                    S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f0, i1, j0), params.mapS2Level),
-                    S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f0, i0, j1), params.mapS2Level),
-                    S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f0, i1, j1), params.mapS2Level)
-            };
+        if (i2 < maxIj && j2 < maxIj) {
+            return new long[]{z11, S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f11, i2, j1),
+                    geoidHeightParams.mapS2Level), S2CellIdUtils.getParent(
+                    S2CellIdUtils.fromFij(f11, i1, j2), geoidHeightParams.mapS2Level),
+                    S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f11, i2, j2),
+                            geoidHeightParams.mapS2Level)};
         }
 
-        // Boundary region calculation.
+        // Boundary region calculation
         long[] edgeNeighbors = new long[4];
-        S2CellIdUtils.getEdgeNeighbors(s0, edgeNeighbors);
-        long s1 = edgeNeighbors[1];
-        long s2 = edgeNeighbors[2];
-        long s3;
-        if (f0 % 2 == 1) {
-            S2CellIdUtils.getEdgeNeighbors(s1, edgeNeighbors);
-            if (i1 < maxIj) {
-                s3 = edgeNeighbors[2];
-            } else {
-                s3 = s1;
-                s1 = edgeNeighbors[1];
-            }
-        } else {
-            S2CellIdUtils.getEdgeNeighbors(s2, edgeNeighbors);
-            if (j1 < maxIj) {
-                s3 = edgeNeighbors[1];
-            } else {
-                s3 = s2;
-                s2 = edgeNeighbors[3];
-            }
-        }
+        S2CellIdUtils.getEdgeNeighbors(z11, edgeNeighbors);
+        long z11W = edgeNeighbors[0];
+        long z11S = edgeNeighbors[1];
+        long z11E = edgeNeighbors[2];
+        long z11N = edgeNeighbors[3];
+
+        long[] otherEdgeNeighbors = new long[4];
+        S2CellIdUtils.getEdgeNeighbors(z11W, otherEdgeNeighbors);
+        S2CellIdUtils.getEdgeNeighbors(z11S, edgeNeighbors);
+        long z11Sw = findCommonNeighbor(edgeNeighbors, otherEdgeNeighbors, z11);
+        S2CellIdUtils.getEdgeNeighbors(z11E, otherEdgeNeighbors);
+        long z11Se = findCommonNeighbor(edgeNeighbors, otherEdgeNeighbors, z11);
+        S2CellIdUtils.getEdgeNeighbors(z11N, edgeNeighbors);
+        long z11Ne = findCommonNeighbor(edgeNeighbors, otherEdgeNeighbors, z11);
+
+        long z21 = (f11 % 2 == 1 && i2 >= maxIj) ? z11Sw : z11S;
+        long z12 = (f11 % 2 == 0 && j2 >= maxIj) ? z11Ne : z11E;
+        long z22 = (z21 == z11Sw) ? z11S : (z12 == z11Ne) ? z11E : z11Se;
 
         // Reuse edge neighbors' array to avoid an extra allocation.
-        edgeNeighbors[0] = s0;
-        edgeNeighbors[1] = s1;
-        edgeNeighbors[2] = s2;
-        edgeNeighbors[3] = s3;
+        edgeNeighbors[0] = z11;
+        edgeNeighbors[1] = z21;
+        edgeNeighbors[2] = z12;
+        edgeNeighbors[3] = z22;
         return edgeNeighbors;
     }
 
     /**
+     * Returns the first common non-z11 neighbor found between the two arrays of edge neighbors. If
+     * such a common neighbor does not exist, returns z11.
+     */
+    private static long findCommonNeighbor(long[] edgeNeighbors, long[] otherEdgeNeighbors,
+            long z11) {
+        for (long edgeNeighbor : edgeNeighbors) {
+            if (edgeNeighbor == z11) {
+                continue;
+            }
+            for (long otherEdgeNeighbor : otherEdgeNeighbors) {
+                if (edgeNeighbor == otherEdgeNeighbor) {
+                    return edgeNeighbor;
+                }
+            }
+        }
+        return z11;
+    }
+
+    /**
      * Adds to {@code location} the bilinearly interpolated Mean Sea Level altitude. In addition, a
      * Mean Sea Level altitude accuracy is added if the {@code location} has a valid vertical
      * accuracy; otherwise, does not add a corresponding accuracy.
      */
-    private static void addMslAltitude(@NonNull MapParamsProto params,
+    private static void addMslAltitude(@NonNull MapParamsProto geoidHeightParams,
             @NonNull double[] geoidHeightsMeters, @NonNull Location location) {
         double h0 = geoidHeightsMeters[0];
         double h1 = geoidHeightsMeters[1];
@@ -158,7 +172,7 @@
         // employ the simplified unit square formulation.
         long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
                 location.getLongitude());
-        double sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - params.mapS2Level);
+        double sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - geoidHeightParams.mapS2Level);
         double wi = (S2CellIdUtils.getI(s2CellId) % sizeIj) / sizeIj;
         double wj = (S2CellIdUtils.getJ(s2CellId) % sizeIj) / sizeIj;
         double offsetMeters = h0 + (h1 - h0) * wi + (h2 - h0) * wj + (h3 - h1 - h2 + h0) * wi * wj;
@@ -167,8 +181,8 @@
         if (location.hasVerticalAccuracy()) {
             double verticalAccuracyMeters = location.getVerticalAccuracyMeters();
             if (Double.isFinite(verticalAccuracyMeters) && verticalAccuracyMeters >= 0) {
-                location.setMslAltitudeAccuracyMeters(
-                        (float) Math.hypot(verticalAccuracyMeters, params.modelRmseMeters));
+                location.setMslAltitudeAccuracyMeters((float) Math.hypot(verticalAccuracyMeters,
+                        geoidHeightParams.modelRmseMeters));
             }
         }
     }
@@ -191,10 +205,11 @@
     public void addMslAltitudeToLocation(@NonNull Context context, @NonNull Location location)
             throws IOException {
         validate(location);
-        MapParamsProto params = GeoidHeightMap.getParams(context);
-        long[] s2CellIds = findMapSquare(params, location);
-        double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, context, s2CellIds);
-        addMslAltitude(params, geoidHeightsMeters, location);
+        MapParamsProto geoidHeightParams = GeoidMap.getGeoidHeightParams(context);
+        long[] mapCells = findMapSquare(geoidHeightParams, location);
+        double[] geoidHeightsMeters = mGeoidMap.readGeoidHeights(geoidHeightParams, context,
+                mapCells);
+        addMslAltitude(geoidHeightParams, geoidHeightsMeters, location);
     }
 
     /**
@@ -206,18 +221,68 @@
      */
     public boolean addMslAltitudeToLocation(@NonNull Location location) {
         validate(location);
-        MapParamsProto params = GeoidHeightMap.getParams();
-        if (params == null) {
+        MapParamsProto geoidHeightParams = GeoidMap.getGeoidHeightParams();
+        if (geoidHeightParams == null) {
             return false;
         }
 
-        long[] s2CellIds = findMapSquare(params, location);
-        double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, s2CellIds);
+        long[] mapCells = findMapSquare(geoidHeightParams, location);
+        double[] geoidHeightsMeters = mGeoidMap.readGeoidHeights(geoidHeightParams, mapCells);
         if (geoidHeightsMeters == null) {
             return false;
         }
 
-        addMslAltitude(params, geoidHeightsMeters, location);
+        addMslAltitude(geoidHeightParams, geoidHeightsMeters, location);
         return true;
     }
+
+    /**
+     * Returns the geoid height (a.k.a. geoid undulation) at the location specified in {@code
+     * request}. The geoid height at a location is defined as the difference between an altitude
+     * measured above the World Geodetic System 1984 reference ellipsoid (WGS84) and its
+     * corresponding Mean Sea Level altitude.
+     *
+     * <p>Must be called off the main thread as data may be loaded from raw assets.
+     *
+     * @throws IOException              if an I/O error occurs when loading data from raw assets.
+     * @throws IllegalArgumentException if the {@code request} has an invalid latitude or longitude.
+     *                                  Specifically, the latitude must be between -90 and 90 (both
+     *                                  inclusive), and the longitude must be between -180 and 180
+     *                                  (both inclusive).
+     * @hide
+     */
+    @WorkerThread
+    public @NonNull GetGeoidHeightResponse getGeoidHeight(@NonNull Context context,
+            @NonNull GetGeoidHeightRequest request) throws IOException {
+        // Create a valid location from which the geoid height and its accuracy will be extracted.
+        Location location = new Location("");
+        location.setLatitude(request.latitudeDegrees);
+        location.setLongitude(request.longitudeDegrees);
+        location.setAltitude(0.0);
+        location.setVerticalAccuracyMeters(0.0f);
+
+        addMslAltitudeToLocation(context, location);
+        // The geoid height for a location with zero WGS84 altitude is equal in value to the
+        // negative of corresponding MSL altitude.
+        double geoidHeightMeters = -location.getMslAltitudeMeters();
+        // The geoid height error for a location with zero vertical accuracy is equal in value to
+        // the corresponding MSL altitude accuracy.
+        float geoidHeightErrorMeters = location.getMslAltitudeAccuracyMeters();
+
+        MapParamsProto expirationDistanceParams = GeoidMap.getExpirationDistanceParams(context);
+        long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
+                location.getLongitude());
+        long[] mapCell = {S2CellIdUtils.getParent(s2CellId, expirationDistanceParams.mapS2Level)};
+        double expirationDistanceMeters = mGeoidMap.readExpirationDistances(
+                expirationDistanceParams, context, mapCell)[0];
+        float additionalGeoidHeightErrorMeters = (float) expirationDistanceParams.modelRmseMeters;
+
+        GetGeoidHeightResponse response = new GetGeoidHeightResponse();
+        response.geoidHeightMeters = geoidHeightMeters;
+        response.geoidHeightErrorMeters = geoidHeightErrorMeters;
+        response.expirationDistanceMeters = expirationDistanceMeters;
+        response.additionalGeoidHeightErrorMeters = additionalGeoidHeightErrorMeters;
+        response.success = true;
+        return response;
+    }
 }
diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig
deleted file mode 100644
index 5f1279e..0000000
--- a/location/java/android/location/flags/gnss.aconfig
+++ /dev/null
@@ -1,50 +0,0 @@
-package: "android.location.flags"
-
-flag {
-    name: "gnss_api_navic_l1"
-    namespace: "location"
-    description: "Flag for GNSS API for NavIC L1"
-    bug: "302199306"
-}
-
-flag {
-    name: "gnss_call_stop_before_set_position_mode"
-    namespace: "location"
-    description: "Flag for calling stop() before setPositionMode()"
-    bug: "306874828"
-}
-
-flag {
-    name: "gnss_api_measurement_request_work_source"
-    namespace: "location"
-    description: "Flag for GnssMeasurementRequest WorkSource API"
-    bug: "295235160"
-}
-
-flag {
-    name: "release_supl_connection_on_timeout"
-    namespace: "location"
-    description: "Flag for releasing SUPL connection on timeout"
-    bug: "315024652"
-}
-
-flag {
-    name: "location_validation"
-    namespace: "location"
-    description: "Flag for location validation"
-    bug: "314328533"
-}
-
-flag {
-    name: "gnss_configuration_from_resource"
-    namespace: "location"
-    description: "Flag for GNSS configuration from resource"
-    bug: "317734846"
-}
-
-flag {
-    name: "replace_future_elapsed_realtime_jni"
-    namespace: "location"
-    description: "Flag for replacing future elapsedRealtime in JNI"
-    bug: "314328533"
-}
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
new file mode 100644
index 0000000..32ad09c
--- /dev/null
+++ b/location/java/android/location/flags/location.aconfig
@@ -0,0 +1,57 @@
+package: "android.location.flags"
+
+flag {
+    name: "fix_service_watcher"
+    namespace: "location"
+    description: "Enable null explicit services in ServiceWatcher"
+    bug: "311210517"
+}
+
+flag {
+    name: "gnss_api_navic_l1"
+    namespace: "location"
+    description: "Flag for GNSS API for NavIC L1"
+    bug: "302199306"
+}
+
+flag {
+    name: "gnss_call_stop_before_set_position_mode"
+    namespace: "location"
+    description: "Flag for calling stop() before setPositionMode()"
+    bug: "306874828"
+}
+
+flag {
+    name: "gnss_api_measurement_request_work_source"
+    namespace: "location"
+    description: "Flag for GnssMeasurementRequest WorkSource API"
+    bug: "295235160"
+}
+
+flag {
+    name: "release_supl_connection_on_timeout"
+    namespace: "location"
+    description: "Flag for releasing SUPL connection on timeout"
+    bug: "315024652"
+}
+
+flag {
+    name: "location_validation"
+    namespace: "location"
+    description: "Flag for location validation"
+    bug: "314328533"
+}
+
+flag {
+    name: "replace_future_elapsed_realtime_jni"
+    namespace: "location"
+    description: "Flag for replacing future elapsedRealtime in JNI"
+    bug: "314328533"
+}
+
+flag {
+    name: "gnss_configuration_from_resource"
+    namespace: "location"
+    description: "Flag for GNSS configuration from resource"
+    bug: "317734846"
+}
diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
index ee2510f..0d5af50 100644
--- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
+++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
@@ -19,6 +19,7 @@
 import android.Manifest;
 import android.annotation.RequiresPermission;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.location.LocationManager;
 import android.os.SystemClock;
 import android.telephony.TelephonyCallback;
@@ -26,6 +27,8 @@
 import android.telephony.emergency.EmergencyNumber;
 import android.util.Log;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -139,8 +142,20 @@
                 (mCallEndElapsedRealtimeMillis > 0)
                         && ((SystemClock.elapsedRealtime() - mCallEndElapsedRealtimeMillis)
                         < emergencyExtensionMillis);
-        boolean isInEmergencyCallback = mTelephonyManager.getEmergencyCallbackMode();
-        boolean isInEmergencySmsMode = mTelephonyManager.isInEmergencySmsMode();
+        boolean isInEmergencyCallback = false;
+        boolean isInEmergencySmsMode = false;
+        if (!Flags.enforceTelephonyFeatureMappingForPublicApis()) {
+            isInEmergencyCallback = mTelephonyManager.getEmergencyCallbackMode();
+            isInEmergencySmsMode = mTelephonyManager.isInEmergencySmsMode();
+        } else {
+            PackageManager pm = mContext.getPackageManager();
+            if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING)) {
+                isInEmergencyCallback = mTelephonyManager.getEmergencyCallbackMode();
+            }
+            if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)) {
+                isInEmergencySmsMode = mTelephonyManager.isInEmergencySmsMode();
+            }
+        }
         return mIsInEmergencyCall || isInEmergencyCallback || isInEmergencyExtension
                 || isInEmergencySmsMode;
     }
diff --git a/location/java/com/android/internal/location/altitude/GeoidHeightMap.java b/location/java/com/android/internal/location/altitude/GeoidHeightMap.java
deleted file mode 100644
index 8067050..0000000
--- a/location/java/com/android/internal/location/altitude/GeoidHeightMap.java
+++ /dev/null
@@ -1,388 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.location.altitude;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.util.LruCache;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.location.altitude.nano.MapParamsProto;
-import com.android.internal.location.altitude.nano.S2TileProto;
-import com.android.internal.util.Preconditions;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.util.Objects;
-
-/**
- * Manages a mapping of geoid heights associated with S2 cells, referred to as MAP CELLS.
- *
- * <p>Tiles are used extensively to reduce the number of entries needed to be stored in memory and
- * on disk. A tile associates geoid heights with all map cells of a common parent at a specified S2
- * level.
- *
- * <p>Since bilinear interpolation considers at most four map cells at a time, at most four tiles
- * are simultaneously stored in memory. These tiles, referred to as CACHE TILES, are each keyed by
- * its common parent's S2 cell ID, referred to as a CACHE KEY.
- *
- * <p>Absent cache tiles needed for interpolation are constructed from larger tiles stored on disk.
- * The latter tiles, referred to as DISK TILES, are each keyed by its common parent's S2 cell token,
- * referred to as a DISK TOKEN.
- */
-public final class GeoidHeightMap {
-
-    private static final Object sLock = new Object();
-
-    @GuardedBy("sLock")
-    @Nullable
-    private static MapParamsProto sParams;
-
-    /** Defines a cache large enough to hold all cache tiles needed for interpolation. */
-    private final LruCache<Long, S2TileProto> mCacheTiles = new LruCache<>(4);
-
-    /**
-     * Returns the singleton parameter instance for a spherically projected geoid height map and its
-     * corresponding tile management.
-     */
-    @NonNull
-    public static MapParamsProto getParams(@NonNull Context context) throws IOException {
-        synchronized (sLock) {
-            if (sParams == null) {
-                try (InputStream is = context.getApplicationContext().getAssets().open(
-                        "geoid_height_map/map-params.pb")) {
-                    sParams = MapParamsProto.parseFrom(is.readAllBytes());
-                }
-            }
-            return sParams;
-        }
-    }
-
-    /**
-     * Same as {@link #getParams(Context)} except that null is returned if the singleton parameter
-     * instance is not yet initialized.
-     */
-    @Nullable
-    public static MapParamsProto getParams() {
-        synchronized (sLock) {
-            return sParams;
-        }
-    }
-
-    private static long getCacheKey(@NonNull MapParamsProto params, long s2CellId) {
-        return S2CellIdUtils.getParent(s2CellId, params.cacheTileS2Level);
-    }
-
-    @NonNull
-    private static String getDiskToken(@NonNull MapParamsProto params, long s2CellId) {
-        return S2CellIdUtils.getToken(
-                S2CellIdUtils.getParent(s2CellId, params.diskTileS2Level));
-    }
-
-    /**
-     * Adds to {@code values} values in the unit interval [0, 1] for the map cells identified by
-     * {@code s2CellIds}. Returns true if values are present for all IDs; otherwise, returns false
-     * and adds NaNs for absent values.
-     */
-    private static boolean getUnitIntervalValues(@NonNull MapParamsProto params,
-            @NonNull TileFunction tileFunction,
-            @NonNull long[] s2CellIds, @NonNull double[] values) {
-        int len = s2CellIds.length;
-
-        S2TileProto[] tiles = new S2TileProto[len];
-        for (int i = 0; i < len; i++) {
-            long cacheKey = getCacheKey(params, s2CellIds[i]);
-            tiles[i] = tileFunction.getTile(cacheKey);
-            values[i] = Double.NaN;
-        }
-
-        for (int i = 0; i < len; i++) {
-            if (tiles[i] == null || !Double.isNaN(values[i])) {
-                continue;
-            }
-
-            mergeByteBufferValues(params, s2CellIds, tiles, i, values);
-            mergeByteJpegValues(params, s2CellIds, tiles, i, values);
-            mergeBytePngValues(params, s2CellIds, tiles, i, values);
-        }
-
-        boolean allFound = true;
-        for (int i = 0; i < len; i++) {
-            if (Double.isNaN(values[i])) {
-                allFound = false;
-            } else {
-                values[i] = (((int) values[i]) & 0xFF) / 255.0;
-            }
-        }
-        return allFound;
-    }
-
-    @SuppressWarnings("ReferenceEquality")
-    private static void mergeByteBufferValues(@NonNull MapParamsProto params,
-            @NonNull long[] s2CellIds,
-            @NonNull S2TileProto[] tiles,
-            int tileIndex, @NonNull double[] values) {
-        byte[] bytes = tiles[tileIndex].byteBuffer;
-        if (bytes == null || bytes.length == 0) {
-            return;
-        }
-
-        ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).asReadOnlyBuffer();
-        int tileS2Level = params.mapS2Level - Integer.numberOfTrailingZeros(byteBuffer.limit()) / 2;
-        int numBitsLeftOfTile = 2 * tileS2Level + 3;
-
-        for (int i = tileIndex; i < tiles.length; i++) {
-            if (tiles[i] != tiles[tileIndex]) {
-                continue;
-            }
-
-            long maskedS2CellId = s2CellIds[i] & (-1L >>> numBitsLeftOfTile);
-            int numBitsRightOfMap = 2 * (S2CellIdUtils.MAX_LEVEL - params.mapS2Level) + 1;
-            int bufferIndex = (int) (maskedS2CellId >>> numBitsRightOfMap);
-            values[i] = Double.isNaN(values[i]) ? 0 : values[i];
-            values[i] += ((int) byteBuffer.get(bufferIndex)) & 0xFF;
-        }
-    }
-
-    private static void mergeByteJpegValues(@NonNull MapParamsProto params,
-            @NonNull long[] s2CellIds,
-            @NonNull S2TileProto[] tiles,
-            int tileIndex, @NonNull double[] values) {
-        mergeByteImageValues(params, tiles[tileIndex].byteJpeg, s2CellIds, tiles, tileIndex,
-                values);
-    }
-
-    private static void mergeBytePngValues(@NonNull MapParamsProto params,
-            @NonNull long[] s2CellIds,
-            @NonNull S2TileProto[] tiles,
-            int tileIndex, @NonNull double[] values) {
-        mergeByteImageValues(params, tiles[tileIndex].bytePng, s2CellIds, tiles, tileIndex, values);
-    }
-
-    @SuppressWarnings("ReferenceEquality")
-    private static void mergeByteImageValues(@NonNull MapParamsProto params, @NonNull byte[] bytes,
-            @NonNull long[] s2CellIds,
-            @NonNull S2TileProto[] tiles, int tileIndex, @NonNull double[] values) {
-        if (bytes == null || bytes.length == 0) {
-            return;
-        }
-        Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
-        if (bitmap == null) {
-            return;
-        }
-
-        for (int i = tileIndex; i < tiles.length; i++) {
-            if (tiles[i] != tiles[tileIndex]) {
-                continue;
-            }
-
-            values[i] = Double.isNaN(values[i]) ? 0 : values[i];
-            values[i] += bitmap.getPixel(getIndexX(params, s2CellIds[i], bitmap.getWidth()),
-                    getIndexY(params, s2CellIds[i], bitmap.getHeight())) & 0xFF;
-        }
-    }
-
-    /** Returns the X index for an S2 cell within an S2 tile image of specified width. */
-    private static int getIndexX(@NonNull MapParamsProto params, long s2CellId, int width) {
-        return getIndexXOrY(params, S2CellIdUtils.getI(s2CellId), width);
-    }
-
-    /** Returns the Y index for an S2 cell within an S2 tile image of specified height. */
-    private static int getIndexY(@NonNull MapParamsProto params, long s2CellId, int height) {
-        return getIndexXOrY(params, S2CellIdUtils.getJ(s2CellId), height);
-    }
-
-    private static int getIndexXOrY(@NonNull MapParamsProto params, int iOrJ, int widthOrHeight) {
-        return (iOrJ >> (S2CellIdUtils.MAX_LEVEL - params.mapS2Level)) % widthOrHeight;
-    }
-
-    /**
-     * Throws an {@link IllegalArgumentException} if the {@code s2CellIds} has an invalid length or
-     * ID.
-     */
-    private static void validate(@NonNull MapParamsProto params, @NonNull long[] s2CellIds) {
-        Preconditions.checkArgument(s2CellIds.length == 4);
-        for (long s2CellId : s2CellIds) {
-            Preconditions.checkArgument(S2CellIdUtils.getLevel(s2CellId) == params.mapS2Level);
-        }
-    }
-
-    /**
-     * Returns the geoid heights in meters associated with the map cells identified by
-     * {@code s2CellIds}. Throws an {@link IOException} if a geoid height cannot be calculated for
-     * an ID.
-     */
-    @NonNull
-    public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull Context context,
-            @NonNull long[] s2CellIds) throws IOException {
-        validate(params, s2CellIds);
-        double[] heightsMeters = new double[s2CellIds.length];
-        if (getGeoidHeights(params, mCacheTiles::get, s2CellIds, heightsMeters)) {
-            return heightsMeters;
-        }
-
-        TileFunction loadedTiles = loadFromCacheAndDisk(params, context, s2CellIds);
-        if (getGeoidHeights(params, loadedTiles, s2CellIds, heightsMeters)) {
-            return heightsMeters;
-        }
-        throw new IOException("Unable to calculate geoid heights from raw assets.");
-    }
-
-    /**
-     * Same as {@link #readGeoidHeights(MapParamsProto, Context, long[])} except that data will not
-     * be loaded from raw assets. Returns the heights if present for all IDs; otherwise, returns
-     * null.
-     */
-    @Nullable
-    public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull long[] s2CellIds) {
-        validate(params, s2CellIds);
-        double[] heightsMeters = new double[s2CellIds.length];
-        if (getGeoidHeights(params, mCacheTiles::get, s2CellIds, heightsMeters)) {
-            return heightsMeters;
-        }
-        return null;
-    }
-
-    /**
-     * Adds to {@code heightsMeters} the geoid heights in meters associated with the map cells
-     * identified by {@code s2CellIds}. Returns true if heights are present for all IDs; otherwise,
-     * returns false and adds NaNs for absent heights.
-     */
-    private boolean getGeoidHeights(@NonNull MapParamsProto params,
-            @NonNull TileFunction tileFunction, @NonNull long[] s2CellIds,
-            @NonNull double[] heightsMeters) {
-        boolean allFound = getUnitIntervalValues(params, tileFunction, s2CellIds, heightsMeters);
-        for (int i = 0; i < heightsMeters.length; i++) {
-            // NaNs are properly preserved.
-            heightsMeters[i] *= params.modelAMeters;
-            heightsMeters[i] += params.modelBMeters;
-        }
-        return allFound;
-    }
-
-    @NonNull
-    private TileFunction loadFromCacheAndDisk(@NonNull MapParamsProto params,
-            @NonNull Context context, @NonNull long[] s2CellIds) throws IOException {
-        int len = s2CellIds.length;
-
-        // Enable batch loading by finding all cache keys upfront.
-        long[] cacheKeys = new long[len];
-        for (int i = 0; i < len; i++) {
-            cacheKeys[i] = getCacheKey(params, s2CellIds[i]);
-        }
-
-        // Attempt to load tiles from cache.
-        S2TileProto[] loadedTiles = new S2TileProto[len];
-        String[] diskTokens = new String[len];
-        for (int i = 0; i < len; i++) {
-            if (diskTokens[i] != null) {
-                continue;
-            }
-            loadedTiles[i] = mCacheTiles.get(cacheKeys[i]);
-            diskTokens[i] = getDiskToken(params, cacheKeys[i]);
-
-            // Batch across common cache key.
-            for (int j = i + 1; j < len; j++) {
-                if (cacheKeys[j] == cacheKeys[i]) {
-                    loadedTiles[j] = loadedTiles[i];
-                    diskTokens[j] = diskTokens[i];
-                }
-            }
-        }
-
-        // Attempt to load tiles from disk.
-        for (int i = 0; i < len; i++) {
-            if (loadedTiles[i] != null) {
-                continue;
-            }
-
-            S2TileProto tile;
-            try (InputStream is = context.getApplicationContext().getAssets().open(
-                    "geoid_height_map/tile-" + diskTokens[i] + ".pb")) {
-                tile = S2TileProto.parseFrom(is.readAllBytes());
-            }
-            mergeFromDiskTile(params, tile, cacheKeys, diskTokens, i, loadedTiles);
-        }
-
-        return cacheKey -> {
-            for (int i = 0; i < cacheKeys.length; i++) {
-                if (cacheKeys[i] == cacheKey) {
-                    return loadedTiles[i];
-                }
-            }
-            return null;
-        };
-    }
-
-    private void mergeFromDiskTile(@NonNull MapParamsProto params, @NonNull S2TileProto diskTile,
-            @NonNull long[] cacheKeys, @NonNull String[] diskTokens, int diskTokenIndex,
-            @NonNull S2TileProto[] loadedTiles) throws IOException {
-        int len = cacheKeys.length;
-        int numMapCellsPerCacheTile = 1 << (2 * (params.mapS2Level - params.cacheTileS2Level));
-
-        // Reusable arrays.
-        long[] s2CellIds = new long[numMapCellsPerCacheTile];
-        double[] values = new double[numMapCellsPerCacheTile];
-
-        // Each cache key identifies a different sub-tile of the same disk tile.
-        TileFunction diskTileFunction = cacheKey -> diskTile;
-        for (int i = diskTokenIndex; i < len; i++) {
-            if (!Objects.equals(diskTokens[i], diskTokens[diskTokenIndex])
-                    || loadedTiles[i] != null) {
-                continue;
-            }
-
-            // Find all map cells within the current cache tile.
-            long s2CellId = S2CellIdUtils.getTraversalStart(cacheKeys[i], params.mapS2Level);
-            for (int j = 0; j < numMapCellsPerCacheTile; j++) {
-                s2CellIds[j] = s2CellId;
-                s2CellId = S2CellIdUtils.getTraversalNext(s2CellId);
-            }
-
-            if (!getUnitIntervalValues(params, diskTileFunction, s2CellIds, values)) {
-                throw new IOException("Corrupted disk tile of disk token: " + diskTokens[i]);
-            }
-
-            loadedTiles[i] = new S2TileProto();
-            loadedTiles[i].byteBuffer = new byte[numMapCellsPerCacheTile];
-            for (int j = 0; j < numMapCellsPerCacheTile; j++) {
-                loadedTiles[i].byteBuffer[j] = (byte) Math.round(values[j] * 0xFF);
-            }
-
-            // Batch across common cache key.
-            for (int j = i + 1; j < len; j++) {
-                if (cacheKeys[j] == cacheKeys[i]) {
-                    loadedTiles[j] = loadedTiles[i];
-                }
-            }
-
-            // Side load into tile cache.
-            mCacheTiles.put(cacheKeys[i], loadedTiles[i]);
-        }
-    }
-
-    /** Defines a function-like object to retrieve tiles for cache keys. */
-    private interface TileFunction {
-
-        @Nullable
-        S2TileProto getTile(long cacheKey);
-    }
-}
diff --git a/location/java/com/android/internal/location/altitude/GeoidMap.java b/location/java/com/android/internal/location/altitude/GeoidMap.java
new file mode 100644
index 0000000..9bf5689
--- /dev/null
+++ b/location/java/com/android/internal/location/altitude/GeoidMap.java
@@ -0,0 +1,447 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.location.altitude;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.LruCache;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.altitude.nano.MapParamsProto;
+import com.android.internal.location.altitude.nano.S2TileProto;
+import com.android.internal.util.Preconditions;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.Objects;
+
+/**
+ * Manages a mapping of geoid heights and expiration distances associated with S2 cells, referred to
+ * as MAP CELLS.
+ *
+ * <p>Tiles are used extensively to reduce the number of entries needed to be stored in memory and
+ * on disk. A tile associates geoid heights or expiration distances with all map cells of a common
+ * parent at a specified S2 level.
+ *
+ * <p>Since bilinear interpolation considers at most four map cells at a time, at most four tiles
+ * are simultaneously stored in memory. These tiles, referred to as CACHE TILES, are each keyed by
+ * its common parent's S2 cell ID, referred to as a CACHE KEY.
+ *
+ * <p>Absent cache tiles needed for interpolation are constructed from larger tiles stored on disk.
+ * The latter tiles, referred to as DISK TILES, are each keyed by its common parent's S2 cell token,
+ * referred to as a DISK TOKEN.
+ */
+public final class GeoidMap {
+
+    private static final Object GEOID_HEIGHT_PARAMS_LOCK = new Object();
+
+    private static final Object EXPIRATION_DISTANCE_PARAMS_LOCK = new Object();
+
+    @GuardedBy("GEOID_HEIGHT_PARAMS_LOCK")
+    @Nullable
+    private static MapParamsProto sGeoidHeightParams;
+
+    @GuardedBy("EXPIRATION_DISTANCE_PARAMS_LOCK")
+    @Nullable
+    private static MapParamsProto sExpirationDistanceParams;
+
+    /**
+     * Defines a cache large enough to hold all geoid height cache tiles needed for interpolation.
+     */
+    private final LruCache<Long, S2TileProto> mGeoidHeightCacheTiles = new LruCache<>(4);
+
+    /**
+     * Defines a cache large enough to hold all expiration distance cache tiles needed for
+     * interpolation.
+     */
+    private final LruCache<Long, S2TileProto> mExpirationDistanceCacheTiles = new LruCache<>(4);
+
+    /**
+     * Returns the singleton parameter instance for geoid height parameters of a spherically
+     * projected map.
+     */
+    @NonNull
+    public static MapParamsProto getGeoidHeightParams(@NonNull Context context) throws IOException {
+        synchronized (GEOID_HEIGHT_PARAMS_LOCK) {
+            if (sGeoidHeightParams == null) {
+                // TODO: b/304375846 - Configure with disk tile prefix once resources are updated.
+                sGeoidHeightParams = parseParams(context);
+            }
+            return sGeoidHeightParams;
+        }
+    }
+
+    /**
+     * Returns the singleton parameter instance for expiration distance parameters of a spherically
+     * projected
+     * map.
+     */
+    @NonNull
+    public static MapParamsProto getExpirationDistanceParams(@NonNull Context context)
+            throws IOException {
+        synchronized (EXPIRATION_DISTANCE_PARAMS_LOCK) {
+            if (sExpirationDistanceParams == null) {
+                // TODO: b/304375846 - Configure with disk tile prefix once resources are updated.
+                sExpirationDistanceParams = parseParams(context);
+            }
+            return sExpirationDistanceParams;
+        }
+    }
+
+    @NonNull
+    private static MapParamsProto parseParams(@NonNull Context context) throws IOException {
+        try (InputStream is = context.getApplicationContext().getAssets().open(
+                "geoid_height_map/map-params.pb")) {
+            return MapParamsProto.parseFrom(is.readAllBytes());
+        }
+    }
+
+    /**
+     * Same as {@link #getGeoidHeightParams(Context)} except that null is returned if the singleton
+     * parameter instance is not yet initialized.
+     */
+    @Nullable
+    public static MapParamsProto getGeoidHeightParams() {
+        synchronized (GEOID_HEIGHT_PARAMS_LOCK) {
+            return sGeoidHeightParams;
+        }
+    }
+
+    private static long getCacheKey(@NonNull MapParamsProto params, long s2CellId) {
+        return S2CellIdUtils.getParent(s2CellId, params.cacheTileS2Level);
+    }
+
+    @NonNull
+    private static String getDiskToken(@NonNull MapParamsProto params, long s2CellId) {
+        return S2CellIdUtils.getToken(S2CellIdUtils.getParent(s2CellId, params.diskTileS2Level));
+    }
+
+    /**
+     * Adds to {@code values} values in the unit interval [0, 1] for the map cells identified by
+     * {@code s2CellIds}. Returns true if values are present for all IDs; otherwise, adds NaNs for
+     * absent values and returns false.
+     */
+    private static boolean getUnitIntervalValues(@NonNull MapParamsProto params,
+            @NonNull TileFunction tileFunction, @NonNull long[] s2CellIds,
+            @NonNull double[] values) {
+        int len = s2CellIds.length;
+
+        S2TileProto[] tiles = new S2TileProto[len];
+        for (int i = 0; i < len; i++) {
+            long cacheKey = getCacheKey(params, s2CellIds[i]);
+            tiles[i] = tileFunction.getTile(cacheKey);
+            values[i] = Double.NaN;
+        }
+
+        for (int i = 0; i < len; i++) {
+            if (tiles[i] == null || !Double.isNaN(values[i])) {
+                continue;
+            }
+
+            mergeByteBufferValues(params, s2CellIds, tiles, i, values);
+            mergeByteJpegValues(params, s2CellIds, tiles, i, values);
+            mergeBytePngValues(params, s2CellIds, tiles, i, values);
+        }
+
+        boolean allFound = true;
+        for (int i = 0; i < len; i++) {
+            if (Double.isNaN(values[i])) {
+                allFound = false;
+            } else {
+                values[i] = (((int) values[i]) & 0xFF) / 255.0;
+            }
+        }
+        return allFound;
+    }
+
+    @SuppressWarnings("ReferenceEquality")
+    private static void mergeByteBufferValues(@NonNull MapParamsProto params,
+            @NonNull long[] s2CellIds, @NonNull S2TileProto[] tiles, int tileIndex,
+            @NonNull double[] values) {
+        byte[] bytes = tiles[tileIndex].byteBuffer;
+        if (bytes == null || bytes.length == 0) {
+            return;
+        }
+
+        ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).asReadOnlyBuffer();
+        int tileS2Level = params.mapS2Level - Integer.numberOfTrailingZeros(byteBuffer.limit()) / 2;
+        int numBitsLeftOfTile = 2 * tileS2Level + 3;
+
+        for (int i = tileIndex; i < tiles.length; i++) {
+            if (tiles[i] != tiles[tileIndex]) {
+                continue;
+            }
+
+            long maskedS2CellId = s2CellIds[i] & (-1L >>> numBitsLeftOfTile);
+            int numBitsRightOfMap = 2 * (S2CellIdUtils.MAX_LEVEL - params.mapS2Level) + 1;
+            int bufferIndex = (int) (maskedS2CellId >>> numBitsRightOfMap);
+            values[i] = Double.isNaN(values[i]) ? 0 : values[i];
+            values[i] += ((int) byteBuffer.get(bufferIndex)) & 0xFF;
+        }
+    }
+
+    private static void mergeByteJpegValues(@NonNull MapParamsProto params,
+            @NonNull long[] s2CellIds, @NonNull S2TileProto[] tiles, int tileIndex,
+            @NonNull double[] values) {
+        mergeByteImageValues(params, tiles[tileIndex].byteJpeg, s2CellIds, tiles, tileIndex,
+                values);
+    }
+
+    private static void mergeBytePngValues(@NonNull MapParamsProto params,
+            @NonNull long[] s2CellIds, @NonNull S2TileProto[] tiles, int tileIndex,
+            @NonNull double[] values) {
+        mergeByteImageValues(params, tiles[tileIndex].bytePng, s2CellIds, tiles, tileIndex, values);
+    }
+
+    @SuppressWarnings("ReferenceEquality")
+    private static void mergeByteImageValues(@NonNull MapParamsProto params, @NonNull byte[] bytes,
+            @NonNull long[] s2CellIds, @NonNull S2TileProto[] tiles, int tileIndex,
+            @NonNull double[] values) {
+        if (bytes == null || bytes.length == 0) {
+            return;
+        }
+        Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
+        if (bitmap == null) {
+            return;
+        }
+
+        for (int i = tileIndex; i < tiles.length; i++) {
+            if (tiles[i] != tiles[tileIndex]) {
+                continue;
+            }
+
+            values[i] = Double.isNaN(values[i]) ? 0 : values[i];
+            values[i] += bitmap.getPixel(getIndexX(params, s2CellIds[i], bitmap.getWidth()),
+                    getIndexY(params, s2CellIds[i], bitmap.getHeight())) & 0xFF;
+        }
+    }
+
+    /** Returns the X index for an S2 cell within an S2 tile image of specified width. */
+    private static int getIndexX(@NonNull MapParamsProto params, long s2CellId, int width) {
+        return getIndexXOrY(params, S2CellIdUtils.getI(s2CellId), width);
+    }
+
+    /** Returns the Y index for an S2 cell within an S2 tile image of specified height. */
+    private static int getIndexY(@NonNull MapParamsProto params, long s2CellId, int height) {
+        return getIndexXOrY(params, S2CellIdUtils.getJ(s2CellId), height);
+    }
+
+    private static int getIndexXOrY(@NonNull MapParamsProto params, int iOrJ, int widthOrHeight) {
+        return (iOrJ >> (S2CellIdUtils.MAX_LEVEL - params.mapS2Level)) % widthOrHeight;
+    }
+
+    /**
+     * Throws an {@link IllegalArgumentException} if the {@code s2CellIds} has an invalid length or
+     * ID.
+     */
+    private static void validate(@NonNull MapParamsProto params, @NonNull long[] s2CellIds) {
+        Preconditions.checkArgument(s2CellIds.length <= 4);
+        for (long s2CellId : s2CellIds) {
+            Preconditions.checkArgument(S2CellIdUtils.getLevel(s2CellId) == params.mapS2Level);
+        }
+    }
+
+    /**
+     * Returns the geoid heights in meters associated with the map cells identified by
+     * {@code s2CellIds}. Throws an {@link IOException} if a geoid height cannot be calculated for
+     * an ID.
+     */
+    @NonNull
+    public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull Context context,
+            @NonNull long[] s2CellIds) throws IOException {
+        return readMapValues(params, context, s2CellIds, mGeoidHeightCacheTiles);
+    }
+
+    /**
+     * Returns the expiration distances in meters associated with the map cells identified by
+     * {@code s2CellIds}. Throws an {@link IOException} if a geoid height cannot be calculated for
+     * an ID.
+     */
+    @NonNull
+    public double[] readExpirationDistances(@NonNull MapParamsProto params,
+            @NonNull Context context, @NonNull long[] s2CellIds) throws IOException {
+        return readMapValues(params, context, s2CellIds, mExpirationDistanceCacheTiles);
+    }
+
+    /**
+     * Returns the map values in meters associated with the map cells identified by
+     * {@code s2CellIds}. Throws an {@link IOException} if a map value cannot be calculated for an
+     * ID.
+     */
+    @NonNull
+    private static double[] readMapValues(@NonNull MapParamsProto params, @NonNull Context context,
+            @NonNull long[] s2CellIds, @NonNull LruCache<Long, S2TileProto> cacheTiles)
+            throws IOException {
+        validate(params, s2CellIds);
+        double[] mapValuesMeters = new double[s2CellIds.length];
+        if (getMapValues(params, cacheTiles::get, s2CellIds, mapValuesMeters)) {
+            return mapValuesMeters;
+        }
+
+        TileFunction loadedTiles = loadFromCacheAndDisk(params, context, s2CellIds, cacheTiles);
+        if (getMapValues(params, loadedTiles, s2CellIds, mapValuesMeters)) {
+            return mapValuesMeters;
+        }
+        throw new IOException("Unable to calculate geoid heights from raw assets.");
+    }
+
+    /**
+     * Same as {@link #readGeoidHeights(MapParamsProto, Context, long[])} except that data will not
+     * be loaded from raw assets. Returns the heights if present for all IDs; otherwise, returns
+     * null.
+     */
+    @Nullable
+    public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull long[] s2CellIds) {
+        validate(params, s2CellIds);
+        double[] heightsMeters = new double[s2CellIds.length];
+        if (getMapValues(params, mGeoidHeightCacheTiles::get, s2CellIds, heightsMeters)) {
+            return heightsMeters;
+        }
+        return null;
+    }
+
+    /**
+     * Adds to {@code mapValuesMeters} the map values in meters associated with the map cells
+     * identified by {@code s2CellIds}. Returns true if heights are present for all IDs; otherwise,
+     * adds NaNs for absent heights and returns false.
+     */
+    private static boolean getMapValues(@NonNull MapParamsProto params,
+            @NonNull TileFunction tileFunction, @NonNull long[] s2CellIds,
+            @NonNull double[] mapValuesMeters) {
+        boolean allFound = getUnitIntervalValues(params, tileFunction, s2CellIds, mapValuesMeters);
+        for (int i = 0; i < mapValuesMeters.length; i++) {
+            // NaNs are properly preserved.
+            mapValuesMeters[i] *= params.modelAMeters;
+            mapValuesMeters[i] += params.modelBMeters;
+        }
+        return allFound;
+    }
+
+    @NonNull
+    private static TileFunction loadFromCacheAndDisk(@NonNull MapParamsProto params,
+            @NonNull Context context, @NonNull long[] s2CellIds,
+            @NonNull LruCache<Long, S2TileProto> cacheTiles) throws IOException {
+        int len = s2CellIds.length;
+
+        // Enable batch loading by finding all cache keys upfront.
+        long[] cacheKeys = new long[len];
+        for (int i = 0; i < len; i++) {
+            cacheKeys[i] = getCacheKey(params, s2CellIds[i]);
+        }
+
+        // Attempt to load tiles from cache.
+        S2TileProto[] loadedTiles = new S2TileProto[len];
+        String[] diskTokens = new String[len];
+        for (int i = 0; i < len; i++) {
+            if (diskTokens[i] != null) {
+                continue;
+            }
+            loadedTiles[i] = cacheTiles.get(cacheKeys[i]);
+            diskTokens[i] = getDiskToken(params, cacheKeys[i]);
+
+            // Batch across common cache key.
+            for (int j = i + 1; j < len; j++) {
+                if (cacheKeys[j] == cacheKeys[i]) {
+                    loadedTiles[j] = loadedTiles[i];
+                    diskTokens[j] = diskTokens[i];
+                }
+            }
+        }
+
+        // Attempt to load tiles from disk.
+        for (int i = 0; i < len; i++) {
+            if (loadedTiles[i] != null) {
+                continue;
+            }
+
+            S2TileProto tile;
+            try (InputStream is = context.getApplicationContext().getAssets().open(
+                    "geoid_height_map/tile-" + diskTokens[i] + ".pb")) {
+                tile = S2TileProto.parseFrom(is.readAllBytes());
+            }
+            mergeFromDiskTile(params, tile, cacheKeys, diskTokens, i, loadedTiles, cacheTiles);
+        }
+
+        return cacheKey -> {
+            for (int i = 0; i < cacheKeys.length; i++) {
+                if (cacheKeys[i] == cacheKey) {
+                    return loadedTiles[i];
+                }
+            }
+            return null;
+        };
+    }
+
+    private static void mergeFromDiskTile(@NonNull MapParamsProto params,
+            @NonNull S2TileProto diskTile, @NonNull long[] cacheKeys, @NonNull String[] diskTokens,
+            int diskTokenIndex, @NonNull S2TileProto[] loadedTiles,
+            @NonNull LruCache<Long, S2TileProto> cacheTiles) throws IOException {
+        int len = cacheKeys.length;
+        int numMapCellsPerCacheTile = 1 << (2 * (params.mapS2Level - params.cacheTileS2Level));
+
+        // Reusable arrays.
+        long[] s2CellIds = new long[numMapCellsPerCacheTile];
+        double[] values = new double[numMapCellsPerCacheTile];
+
+        // Each cache key identifies a different sub-tile of the same disk tile.
+        TileFunction diskTileFunction = cacheKey -> diskTile;
+        for (int i = diskTokenIndex; i < len; i++) {
+            if (!Objects.equals(diskTokens[i], diskTokens[diskTokenIndex])
+                    || loadedTiles[i] != null) {
+                continue;
+            }
+
+            // Find all map cells within the current cache tile.
+            long s2CellId = S2CellIdUtils.getTraversalStart(cacheKeys[i], params.mapS2Level);
+            for (int j = 0; j < numMapCellsPerCacheTile; j++) {
+                s2CellIds[j] = s2CellId;
+                s2CellId = S2CellIdUtils.getTraversalNext(s2CellId);
+            }
+
+            if (!getUnitIntervalValues(params, diskTileFunction, s2CellIds, values)) {
+                throw new IOException("Corrupted disk tile of disk token: " + diskTokens[i]);
+            }
+
+            loadedTiles[i] = new S2TileProto();
+            loadedTiles[i].byteBuffer = new byte[numMapCellsPerCacheTile];
+            for (int j = 0; j < numMapCellsPerCacheTile; j++) {
+                loadedTiles[i].byteBuffer[j] = (byte) Math.round(values[j] * 0xFF);
+            }
+
+            // Batch across common cache key.
+            for (int j = i + 1; j < len; j++) {
+                if (cacheKeys[j] == cacheKeys[i]) {
+                    loadedTiles[j] = loadedTiles[i];
+                }
+            }
+
+            // Side load into tile cache.
+            cacheTiles.put(cacheKeys[i], loadedTiles[i]);
+        }
+    }
+
+    /** Defines a function-like object to retrieve tiles for cache keys. */
+    private interface TileFunction {
+
+        @Nullable
+        S2TileProto getTile(long cacheKey);
+    }
+}
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index b002bbf..ea136ed 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -1079,7 +1079,7 @@
      * @return one of the values that can be set in {@link Builder#setEncoding(int)} or
      * {@link AudioFormat#ENCODING_INVALID} if not set.
      */
-    public int getEncoding() {
+    public @Encoding int getEncoding() {
         return mEncoding;
     }
 
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 2eec9b3..8dfa6be 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -754,15 +754,16 @@
 
     void unregisterLoudnessCodecUpdatesDispatcher(in ILoudnessCodecUpdatesDispatcher dispatcher);
 
-    oneway void startLoudnessCodecUpdates(int piid, in List<LoudnessCodecInfo> codecInfoSet);
+    oneway void startLoudnessCodecUpdates(int sessionId);
 
-    oneway void stopLoudnessCodecUpdates(int piid);
+    oneway void stopLoudnessCodecUpdates(int sessionId);
 
-    oneway void addLoudnessCodecInfo(int piid, int mediaCodecHash, in LoudnessCodecInfo codecInfo);
+    oneway void addLoudnessCodecInfo(int sessionId, int mediaCodecHash,
+            in LoudnessCodecInfo codecInfo);
 
-    oneway void removeLoudnessCodecInfo(int piid, in LoudnessCodecInfo codecInfo);
+    oneway void removeLoudnessCodecInfo(int sessionId, in LoudnessCodecInfo codecInfo);
 
-    PersistableBundle getLoudnessParams(int piid, in LoudnessCodecInfo codecInfo);
+    PersistableBundle getLoudnessParams(in LoudnessCodecInfo codecInfo);
 
     @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)")
diff --git a/media/java/android/media/ILoudnessCodecUpdatesDispatcher.aidl b/media/java/android/media/ILoudnessCodecUpdatesDispatcher.aidl
index 16eaaea..2022427 100644
--- a/media/java/android/media/ILoudnessCodecUpdatesDispatcher.aidl
+++ b/media/java/android/media/ILoudnessCodecUpdatesDispatcher.aidl
@@ -16,6 +16,7 @@
 
 package android.media;
 
+import android.media.AudioAttributes;
 import android.os.PersistableBundle;
 
 /**
@@ -26,6 +27,6 @@
  */
 oneway interface ILoudnessCodecUpdatesDispatcher {
 
-    void dispatchLoudnessCodecParameterChange(int piid, in PersistableBundle params);
+    void dispatchLoudnessCodecParameterChange(int sessionId, in PersistableBundle params);
 
 }
\ No newline at end of file
diff --git a/media/java/android/media/LoudnessCodecConfigurator.java b/media/java/android/media/LoudnessCodecConfigurator.java
deleted file mode 100644
index aadd783..0000000
--- a/media/java/android/media/LoudnessCodecConfigurator.java
+++ /dev/null
@@ -1,431 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-import static android.media.AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
-import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_4;
-import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_D;
-import static android.media.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API;
-
-import android.annotation.CallbackExecutor;
-import android.annotation.FlaggedApi;
-import android.os.Bundle;
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Class for getting recommended loudness parameter updates for audio decoders, according to the
- * encoded format and current audio routing. Those updates can be automatically applied to the
- * {@link MediaCodec} instance(s), or be provided to the user. The codec loudness management
- * parameter updates are defined by the CTA-2075 standard.
- * <p>A new object should be instantiated for each {@link AudioTrack} with the help
- * of {@link #create()} or {@link #create(Executor, OnLoudnessCodecUpdateListener)}.
- */
-@FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
-public class LoudnessCodecConfigurator {
-    private static final String TAG = "LoudnessCodecConfigurator";
-
-    /**
-     * Listener used for receiving asynchronous loudness metadata updates.
-     */
-    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
-    public interface OnLoudnessCodecUpdateListener {
-        /**
-         * Contains the MediaCodec key/values that can be set directly to
-         * configure the loudness of the handle's corresponding decoder (see
-         * {@link MediaCodec#setParameters(Bundle)}).
-         *
-         * @param mediaCodec  the mediaCodec that will receive the new parameters
-         * @param codecValues contains loudness key/value pairs that can be set
-         *                    directly on the mediaCodec. The listener can modify
-         *                    these values with their own edits which will be
-         *                    returned for the mediaCodec configuration
-         * @return a Bundle which contains the original computed codecValues
-         * aggregated with user edits. The platform will configure the associated
-         * MediaCodecs with the returned Bundle params.
-         */
-        @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
-        @NonNull
-        default Bundle onLoudnessCodecUpdate(@NonNull MediaCodec mediaCodec,
-                                             @NonNull Bundle codecValues) {
-            return codecValues;
-        }
-    }
-
-    @NonNull private final LoudnessCodecDispatcher mLcDispatcher;
-
-    private final Object mConfiguratorLock = new Object();
-
-    @GuardedBy("mConfiguratorLock")
-    private AudioTrack mAudioTrack;
-
-    @GuardedBy("mConfiguratorLock")
-    private final Executor mExecutor;
-
-    @GuardedBy("mConfiguratorLock")
-    private final OnLoudnessCodecUpdateListener mListener;
-
-    @GuardedBy("mConfiguratorLock")
-    private final HashMap<LoudnessCodecInfo, Set<MediaCodec>> mMediaCodecs = new HashMap<>();
-
-    /**
-     * Creates a new instance of {@link LoudnessCodecConfigurator}
-     *
-     * <p>This method should be used when the client does not need to alter the
-     * codec loudness parameters before they are applied to the audio decoders.
-     * Otherwise, use {@link #create(Executor, OnLoudnessCodecUpdateListener)}.
-     *
-     * @return the {@link LoudnessCodecConfigurator} instance
-     */
-    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
-    public static @NonNull LoudnessCodecConfigurator create() {
-        return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(AudioManager.getService()),
-                Executors.newSingleThreadExecutor(), new OnLoudnessCodecUpdateListener() {});
-    }
-
-    /**
-     * Creates a new instance of {@link LoudnessCodecConfigurator}
-     *
-     * <p>This method should be used when the client wants to alter the codec
-     * loudness parameters before they are applied to the audio decoders.
-     * Otherwise, use {@link #create()}.
-     *
-     * @param executor {@link Executor} to handle the callbacks
-     * @param listener used for receiving updates
-     *
-     * @return the {@link LoudnessCodecConfigurator} instance
-     */
-    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
-    public static @NonNull LoudnessCodecConfigurator create(
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull OnLoudnessCodecUpdateListener listener) {
-        Objects.requireNonNull(executor, "Executor cannot be null");
-        Objects.requireNonNull(listener, "OnLoudnessCodecUpdateListener cannot be null");
-
-        return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(AudioManager.getService()),
-                executor, listener);
-    }
-
-    /**
-     * Creates a new instance of {@link LoudnessCodecConfigurator}
-     *
-     * <p>This method should be used only in testing
-     *
-     * @param service interface for communicating with AudioService
-     * @param executor {@link Executor} to handle the callbacks
-     * @param listener used for receiving updates
-     *
-     * @return the {@link LoudnessCodecConfigurator} instance
-     *
-     * @hide
-     */
-    public static @NonNull LoudnessCodecConfigurator createForTesting(
-            @NonNull IAudioService service,
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull OnLoudnessCodecUpdateListener listener) {
-        Objects.requireNonNull(service, "IAudioService cannot be null");
-        Objects.requireNonNull(executor, "Executor cannot be null");
-        Objects.requireNonNull(listener, "OnLoudnessCodecUpdateListener cannot be null");
-
-        return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(service),
-                executor, listener);
-    }
-
-    /** @hide */
-    private LoudnessCodecConfigurator(@NonNull LoudnessCodecDispatcher lcDispatcher,
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull OnLoudnessCodecUpdateListener listener) {
-        mLcDispatcher = Objects.requireNonNull(lcDispatcher, "Dispatcher cannot be null");
-        mExecutor = Objects.requireNonNull(executor, "Executor cannot be null");
-        mListener = Objects.requireNonNull(listener,
-                "OnLoudnessCodecUpdateListener cannot be null");
-    }
-
-    /**
-     * Sets the {@link AudioTrack} and starts receiving asynchronous updates for
-     * the registered {@link MediaCodec}s (see {@link #addMediaCodec(MediaCodec)})
-     *
-     * <p>The AudioTrack should be the one that receives audio data from the
-     * added audio decoders and is used to determine the device routing on which
-     * the audio streaming will take place. This will directly influence the
-     * loudness parameters.
-     * <p>After calling this method the framework will compute the initial set of
-     * parameters which will be applied to the registered codecs/returned to the
-     * listener for modification.
-     *
-     * @param audioTrack the track that will receive audio data from the provided
-     *                   audio decoders. In case this is {@code null} this
-     *                   method will have the effect of clearing the existing set
-     *                   {@link AudioTrack} and will stop receiving asynchronous
-     *                   loudness updates
-     */
-    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
-    public void setAudioTrack(@Nullable AudioTrack audioTrack) {
-        List<LoudnessCodecInfo> codecInfos;
-        int piid = PLAYER_PIID_INVALID;
-        int oldPiid = PLAYER_PIID_INVALID;
-        synchronized (mConfiguratorLock) {
-            if (mAudioTrack != null && mAudioTrack == audioTrack) {
-                Log.v(TAG, "Loudness configurator already started for piid: "
-                        + mAudioTrack.getPlayerIId());
-                return;
-            }
-
-            codecInfos = getLoudnessCodecInfoList_l();
-            if (mAudioTrack != null) {
-                oldPiid = mAudioTrack.getPlayerIId();
-                mLcDispatcher.removeLoudnessCodecListener(this);
-            }
-            if (audioTrack != null) {
-                piid = audioTrack.getPlayerIId();
-                mLcDispatcher.addLoudnessCodecListener(this, mExecutor, mListener);
-            }
-
-            mAudioTrack = audioTrack;
-        }
-
-        if (oldPiid != PLAYER_PIID_INVALID) {
-            Log.v(TAG, "Loudness configurator stopping updates for piid: " + oldPiid);
-            mLcDispatcher.stopLoudnessCodecUpdates(oldPiid);
-        }
-        if (piid != PLAYER_PIID_INVALID) {
-            Log.v(TAG, "Loudness configurator starting updates for piid: " + piid);
-            mLcDispatcher.startLoudnessCodecUpdates(piid, codecInfos);
-        }
-    }
-
-    /**
-     * Adds a new {@link MediaCodec} that will stream data to an {@link AudioTrack}
-     * which the client sets
-     * (see {@link LoudnessCodecConfigurator#setAudioTrack(AudioTrack)}).
-     *
-     * <p>This method can be called while asynchronous updates are live.
-     *
-     * <p>No new element will be added if the passed {@code mediaCodec} was
-     * previously added.
-     *
-     * @param mediaCodec the codec to start receiving asynchronous loudness
-     *                   updates. The codec has to be in a configured or started
-     *                   state in order to add it for loudness updates.
-     * @throws IllegalArgumentException if the same {@code mediaCodec} was already
-     *                                  added before.
-     * @return {@code false} if the {@code mediaCodec} was not configured or does
-     *         not contain loudness metadata, {@code true} otherwise.
-     */
-    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
-    public boolean addMediaCodec(@NonNull MediaCodec mediaCodec) {
-        final MediaCodec mc = Objects.requireNonNull(mediaCodec,
-                "MediaCodec for addMediaCodec cannot be null");
-        int piid = PLAYER_PIID_INVALID;
-        final LoudnessCodecInfo mcInfo = getCodecInfo(mc);
-
-        if (mcInfo == null) {
-            Log.v(TAG, "Could not extract codec loudness information");
-            return false;
-        }
-        synchronized (mConfiguratorLock) {
-            final AtomicBoolean containsCodec = new AtomicBoolean(false);
-            Set<MediaCodec> newSet = mMediaCodecs.computeIfPresent(mcInfo, (info, codecSet) -> {
-                containsCodec.set(!codecSet.add(mc));
-                return codecSet;
-            });
-            if (newSet == null) {
-                newSet = new HashSet<>();
-                newSet.add(mc);
-                mMediaCodecs.put(mcInfo, newSet);
-            }
-            if (containsCodec.get()) {
-                throw new IllegalArgumentException(
-                        "Loudness configurator already added " + mediaCodec);
-            }
-            if (mAudioTrack != null) {
-                piid = mAudioTrack.getPlayerIId();
-            }
-        }
-
-        if (piid != PLAYER_PIID_INVALID) {
-            mLcDispatcher.addLoudnessCodecInfo(piid, mediaCodec.hashCode(), mcInfo);
-        }
-
-        return true;
-    }
-
-    /**
-     * Removes the {@link MediaCodec} from receiving loudness updates.
-     *
-     * <p>This method can be called while asynchronous updates are live.
-     *
-     * <p>No elements will be removed if the passed mediaCodec was not added before.
-     *
-     * @param mediaCodec the element to remove for receiving asynchronous updates
-     * @throws IllegalArgumentException if the {@code mediaCodec} was not configured,
-     *                                  does not contain loudness metadata or if it
-     *                                  was not added before
-     */
-    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
-    public void removeMediaCodec(@NonNull MediaCodec mediaCodec) {
-        int piid = PLAYER_PIID_INVALID;
-        LoudnessCodecInfo mcInfo;
-        AtomicBoolean removedMc = new AtomicBoolean(false);
-        AtomicBoolean removeInfo = new AtomicBoolean(false);
-
-        mcInfo = getCodecInfo(Objects.requireNonNull(mediaCodec,
-                "MediaCodec for removeMediaCodec cannot be null"));
-
-        if (mcInfo == null) {
-            throw new IllegalArgumentException("Could not extract codec loudness information");
-        }
-        synchronized (mConfiguratorLock) {
-            if (mAudioTrack != null) {
-                piid = mAudioTrack.getPlayerIId();
-            }
-            mMediaCodecs.computeIfPresent(mcInfo, (format, mcs) -> {
-                removedMc.set(mcs.remove(mediaCodec));
-                if (mcs.isEmpty()) {
-                    // remove the entry
-                    removeInfo.set(true);
-                    return null;
-                }
-                return mcs;
-            });
-            if (!removedMc.get()) {
-                throw new IllegalArgumentException(
-                        "Loudness configurator does not contain " + mediaCodec);
-            }
-        }
-
-        if (piid != PLAYER_PIID_INVALID && removeInfo.get()) {
-            mLcDispatcher.removeLoudnessCodecInfo(piid, mcInfo);
-        }
-    }
-
-    /**
-     * Gets synchronous loudness updates when no listener is required. The provided
-     * {@link MediaCodec} streams audio data to the passed {@link AudioTrack}.
-     *
-     * @param audioTrack track that receives audio data from the passed
-     *                   {@link MediaCodec}
-     * @param mediaCodec codec that decodes loudness annotated data for the passed
-     *                   {@link AudioTrack}
-     *
-     * @return the {@link Bundle} containing the current loudness parameters. Caller is
-     * responsible to update the {@link MediaCodec}
-     */
-    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
-    @NonNull
-    public Bundle getLoudnessCodecParams(@NonNull AudioTrack audioTrack,
-            @NonNull MediaCodec mediaCodec) {
-        Objects.requireNonNull(audioTrack, "Passed audio track cannot be null");
-
-        LoudnessCodecInfo codecInfo = getCodecInfo(mediaCodec);
-        if (codecInfo == null) {
-            return new Bundle();
-        }
-
-        return mLcDispatcher.getLoudnessCodecParams(audioTrack.getPlayerIId(), codecInfo);
-    }
-
-    /** @hide */
-    /*package*/ int getAssignedTrackPiid() {
-        int piid = PLAYER_PIID_INVALID;
-
-        synchronized (mConfiguratorLock) {
-            if (mAudioTrack == null) {
-                return piid;
-            }
-            piid = mAudioTrack.getPlayerIId();
-        }
-
-        return piid;
-    }
-
-    /** @hide */
-    /*package*/ Map<LoudnessCodecInfo, Set<MediaCodec>> getRegisteredMediaCodecs() {
-        synchronized (mConfiguratorLock) {
-            return mMediaCodecs;
-        }
-    }
-
-    @GuardedBy("mConfiguratorLock")
-    private List<LoudnessCodecInfo> getLoudnessCodecInfoList_l() {
-        return mMediaCodecs.values().stream().flatMap(listMc -> listMc.stream().map(
-                LoudnessCodecConfigurator::getCodecInfo)).toList();
-    }
-
-    @Nullable
-    private static LoudnessCodecInfo getCodecInfo(@NonNull MediaCodec mediaCodec) {
-        LoudnessCodecInfo lci = new LoudnessCodecInfo();
-        final MediaCodecInfo codecInfo = mediaCodec.getCodecInfo();
-        if (codecInfo.isEncoder()) {
-            // loudness info only for decoders
-            Log.w(TAG, "MediaCodec used for encoding does not support loudness annotation");
-            return null;
-        }
-
-        try {
-            final MediaFormat inputFormat = mediaCodec.getInputFormat();
-            final String mimeType = inputFormat.getString(MediaFormat.KEY_MIME);
-            if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mimeType)) {
-                // check both KEY_AAC_PROFILE and KEY_PROFILE as some codecs may only recognize
-                // one of these two keys
-                int aacProfile = -1;
-                int profile = -1;
-                try {
-                    aacProfile = inputFormat.getInteger(MediaFormat.KEY_AAC_PROFILE);
-                } catch (NullPointerException e) {
-                    // does not contain KEY_AAC_PROFILE. do nothing
-                }
-                try {
-                    profile = inputFormat.getInteger(MediaFormat.KEY_PROFILE);
-                } catch (NullPointerException e) {
-                    // does not contain KEY_PROFILE. do nothing
-                }
-                if (aacProfile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE
-                        || profile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE) {
-                    lci.metadataType = CODEC_METADATA_TYPE_MPEG_D;
-                } else {
-                    lci.metadataType = CODEC_METADATA_TYPE_MPEG_4;
-                }
-            } else {
-                Log.w(TAG, "MediaCodec mime type not supported for loudness annotation");
-                return null;
-            }
-
-            final MediaFormat outputFormat = mediaCodec.getOutputFormat();
-            lci.isDownmixing = outputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT)
-                    < inputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
-        } catch (IllegalStateException e) {
-            Log.e(TAG, "MediaCodec is not configured", e);
-            return null;
-        }
-
-        return lci;
-    }
-}
diff --git a/media/java/android/media/LoudnessCodecController.java b/media/java/android/media/LoudnessCodecController.java
new file mode 100644
index 0000000..61c9131
--- /dev/null
+++ b/media/java/android/media/LoudnessCodecController.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_4;
+import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_D;
+import static android.media.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.media.permission.SafeCloseable;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+/**
+ * Class for getting recommended loudness parameter updates for audio decoders as they are used
+ * to play back media content according to the encoded format and current audio routing. These
+ * audio decoder updates leverage loudness metadata present in compressed audio streams. They
+ * ensure the loudness and dynamic range of the content is optimized to the physical
+ * characteristics of the audio output device (e.g. phone microspeakers vs headphones vs TV
+ * speakers).Those updates can be automatically applied to the {@link MediaCodec} instance(s), or
+ * be provided to the user. The codec loudness management parameter updates are computed in
+ * accordance to the CTA-2075 standard.
+ * <p>A new object should be instantiated for each audio session
+ * (see {@link AudioManager#generateAudioSessionId()}) using creator methods {@link #create(int)} or
+ * {@link #create(int, Executor, OnLoudnessCodecUpdateListener)}.
+ */
+@FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+public class LoudnessCodecController implements SafeCloseable {
+    private static final String TAG = "LoudnessCodecController";
+
+    /**
+     * Listener used for receiving asynchronous loudness metadata updates.
+     */
+    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public interface OnLoudnessCodecUpdateListener {
+        /**
+         * Contains the MediaCodec key/values that can be set directly to
+         * configure the loudness of the handle's corresponding decoder (see
+         * {@link MediaCodec#setParameters(Bundle)}).
+         *
+         * @param mediaCodec  the mediaCodec that will receive the new parameters
+         * @param codecValues contains loudness key/value pairs that can be set
+         *                    directly on the mediaCodec. The listener can modify
+         *                    these values with their own edits which will be
+         *                    returned for the mediaCodec configuration
+         *
+         * @return a Bundle which contains the original computed codecValues
+         * aggregated with user edits. The platform will configure the associated
+         * MediaCodecs with the returned Bundle params.
+         */
+        @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+        @NonNull
+        default Bundle onLoudnessCodecUpdate(@NonNull MediaCodec mediaCodec,
+                @NonNull Bundle codecValues) {
+            return codecValues;
+        }
+    }
+
+    @NonNull
+    private final LoudnessCodecDispatcher mLcDispatcher;
+
+    private final Object mControllerLock = new Object();
+
+    private final int mSessionId;
+
+    @GuardedBy("mControllerLock")
+    private final HashMap<LoudnessCodecInfo, Set<MediaCodec>> mMediaCodecs = new HashMap<>();
+
+    /**
+     * Creates a new instance of {@link LoudnessCodecController}
+     *
+     * <p>This method should be used when the client does not need to alter the
+     * codec loudness parameters before they are applied to the audio decoders.
+     * Otherwise, use {@link #create(int, Executor, OnLoudnessCodecUpdateListener)}.
+     *
+     * @param sessionId  the session ID of the track that will receive data
+     *                        from the added {@link MediaCodec}'s
+     *
+     * @return the {@link LoudnessCodecController} instance
+     */
+    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public static @NonNull LoudnessCodecController create(int sessionId) {
+        final LoudnessCodecDispatcher dispatcher = new LoudnessCodecDispatcher(
+                AudioManager.getService());
+        final LoudnessCodecController controller = new LoudnessCodecController(dispatcher,
+                sessionId);
+        dispatcher.addLoudnessCodecListener(controller, Executors.newSingleThreadExecutor(),
+                new OnLoudnessCodecUpdateListener() {});
+        dispatcher.startLoudnessCodecUpdates(sessionId);
+        return controller;
+    }
+
+    /**
+     * Creates a new instance of {@link LoudnessCodecController}
+     *
+     * <p>This method should be used when the client wants to alter the codec
+     * loudness parameters before they are applied to the audio decoders.
+     * Otherwise, use {@link #create( int)}.
+     *
+     * @param sessionId       the session ID of the track that will receive data
+     *                        from the added {@link MediaCodec}'s
+     * @param executor        {@link Executor} to handle the callbacks
+     * @param listener        used for receiving updates
+     *
+     * @return the {@link LoudnessCodecController} instance
+     */
+    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public static @NonNull LoudnessCodecController create(
+            int sessionId,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnLoudnessCodecUpdateListener listener) {
+        Objects.requireNonNull(executor, "Executor cannot be null");
+        Objects.requireNonNull(listener, "OnLoudnessCodecUpdateListener cannot be null");
+
+        final LoudnessCodecDispatcher dispatcher = new LoudnessCodecDispatcher(
+                AudioManager.getService());
+        final LoudnessCodecController controller = new LoudnessCodecController(dispatcher,
+                sessionId);
+        dispatcher.addLoudnessCodecListener(controller, executor, listener);
+        dispatcher.startLoudnessCodecUpdates(sessionId);
+        return controller;
+    }
+
+    /**
+     * Creates a new instance of {@link LoudnessCodecController}
+     *
+     * <p>This method should be used only in testing
+     *
+     * @param sessionId  the session ID of the track that will receive data
+     *                        from the added {@link MediaCodec}'s
+     * @param executor {@link Executor} to handle the callbacks
+     * @param listener used for receiving updates
+     * @param service  interface for communicating with AudioService
+     *
+     * @return the {@link LoudnessCodecController} instance
+     * @hide
+     */
+    public static @NonNull LoudnessCodecController createForTesting(
+            int sessionId,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnLoudnessCodecUpdateListener listener,
+            @NonNull IAudioService service) {
+        Objects.requireNonNull(service, "IAudioService cannot be null");
+        Objects.requireNonNull(executor, "Executor cannot be null");
+        Objects.requireNonNull(listener, "OnLoudnessCodecUpdateListener cannot be null");
+
+        final LoudnessCodecDispatcher dispatcher = new LoudnessCodecDispatcher(service);
+        final LoudnessCodecController controller = new LoudnessCodecController(dispatcher,
+                sessionId);
+        dispatcher.addLoudnessCodecListener(controller, executor, listener);
+        dispatcher.startLoudnessCodecUpdates(sessionId);
+        return controller;
+    }
+
+    /** @hide */
+    private LoudnessCodecController(@NonNull LoudnessCodecDispatcher lcDispatcher, int sessionId) {
+        mLcDispatcher = Objects.requireNonNull(lcDispatcher, "Dispatcher cannot be null");
+        mSessionId = sessionId;
+    }
+
+    /**
+     * Adds a new {@link MediaCodec} that will stream data to a player
+     * which uses {@link #mSessionId}.
+     *
+     * <p>No new element will be added if the passed {@code mediaCodec} was
+     * previously added.
+     *
+     * @param mediaCodec the codec to start receiving asynchronous loudness
+     *                   updates. The codec has to be in a configured or started
+     *                   state in order to add it for loudness updates.
+     * @return {@code false} if the {@code mediaCodec} was not configured or does
+     * not contain loudness metadata, {@code true} otherwise.
+     * @throws IllegalArgumentException if the same {@code mediaCodec} was already
+     *                                  added before.
+     */
+    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public boolean addMediaCodec(@NonNull MediaCodec mediaCodec) {
+        final MediaCodec mc = Objects.requireNonNull(mediaCodec,
+                "MediaCodec for addMediaCodec cannot be null");
+        final LoudnessCodecInfo mcInfo = getCodecInfo(mc);
+
+        if (mcInfo == null) {
+            Log.v(TAG, "Could not extract codec loudness information");
+            return false;
+        }
+        synchronized (mControllerLock) {
+            final AtomicBoolean containsCodec = new AtomicBoolean(false);
+            Set<MediaCodec> newSet = mMediaCodecs.computeIfPresent(mcInfo, (info, codecSet) -> {
+                containsCodec.set(!codecSet.add(mc));
+                return codecSet;
+            });
+            if (newSet == null) {
+                newSet = new HashSet<>();
+                newSet.add(mc);
+                mMediaCodecs.put(mcInfo, newSet);
+            }
+            if (containsCodec.get()) {
+                throw new IllegalArgumentException(
+                        "Loudness controller already added " + mediaCodec);
+            }
+        }
+
+        mLcDispatcher.addLoudnessCodecInfo(mSessionId, mediaCodec.hashCode(),
+                mcInfo);
+
+        return true;
+    }
+
+    /**
+     * Removes the {@link MediaCodec} from receiving loudness updates.
+     *
+     * <p>This method can be called while asynchronous updates are live.
+     *
+     * <p>No elements will be removed if the passed mediaCodec was not added before.
+     *
+     * @param mediaCodec the element to remove for receiving asynchronous updates
+     * @throws IllegalArgumentException if the {@code mediaCodec} was not configured,
+     *                                  does not contain loudness metadata or if it
+     *                                  was not added before
+     */
+    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void removeMediaCodec(@NonNull MediaCodec mediaCodec) {
+        LoudnessCodecInfo mcInfo;
+        AtomicBoolean removedMc = new AtomicBoolean(false);
+        AtomicBoolean removeInfo = new AtomicBoolean(false);
+
+        mcInfo = getCodecInfo(Objects.requireNonNull(mediaCodec,
+                "MediaCodec for removeMediaCodec cannot be null"));
+
+        if (mcInfo == null) {
+            throw new IllegalArgumentException("Could not extract codec loudness information");
+        }
+        synchronized (mControllerLock) {
+            mMediaCodecs.computeIfPresent(mcInfo, (format, mcs) -> {
+                removedMc.set(mcs.remove(mediaCodec));
+                if (mcs.isEmpty()) {
+                    // remove the entry
+                    removeInfo.set(true);
+                    return null;
+                }
+                return mcs;
+            });
+            if (!removedMc.get()) {
+                throw new IllegalArgumentException(
+                        "Loudness controller does not contain " + mediaCodec);
+            }
+        }
+
+        if (removeInfo.get()) {
+            mLcDispatcher.removeLoudnessCodecInfo(mSessionId, mcInfo);
+        }
+    }
+
+    /**
+     * Returns the loudness parameters of the registered audio decoders
+     *
+     * <p>Those parameters may have been automatically applied if the
+     * {@code LoudnessCodecController} was created with {@link #create(int)}, or they are the
+     * parameters that have been sent to the {@link OnLoudnessCodecUpdateListener} if using a
+     * codec update listener.
+     *
+     * @param mediaCodec codec that decodes loudness annotated data. Has to be added
+     *                   with {@link #addMediaCodec(MediaCodec)} before calling this
+     *                   method
+     * @throws IllegalArgumentException if the passed {@link MediaCodec} was not
+     *                                  added before calling this method
+     *
+     * @return the {@link Bundle} containing the current loudness parameters.
+     */
+    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+    @NonNull
+    public Bundle getLoudnessCodecParams(@NonNull MediaCodec mediaCodec) {
+        Objects.requireNonNull(mediaCodec, "MediaCodec cannot be null");
+
+        LoudnessCodecInfo codecInfo = getCodecInfo(mediaCodec);
+        if (codecInfo == null) {
+            throw new IllegalArgumentException("MediaCodec does not have valid codec information");
+        }
+
+        synchronized (mControllerLock) {
+            final Set<MediaCodec> codecs = mMediaCodecs.get(codecInfo);
+            if (codecs == null || !codecs.contains(mediaCodec)) {
+                throw new IllegalArgumentException(
+                        "MediaCodec was not added for loudness annotation");
+            }
+        }
+
+        return mLcDispatcher.getLoudnessCodecParams(codecInfo);
+    }
+
+    /**
+     * Stops any loudness updates and frees up the resources.
+     */
+    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+    @Override
+    public void close() {
+        synchronized (mControllerLock) {
+            mMediaCodecs.clear();
+        }
+        mLcDispatcher.stopLoudnessCodecUpdates(mSessionId);
+    }
+
+    /** @hide */
+    /*package*/ int getSessionId() {
+        return mSessionId;
+    }
+
+    /** @hide */
+    /*package*/ void mediaCodecsConsume(
+            Consumer<Entry<LoudnessCodecInfo, Set<MediaCodec>>> consumer) {
+        synchronized (mControllerLock) {
+            for (Entry<LoudnessCodecInfo, Set<MediaCodec>> entry : mMediaCodecs.entrySet()) {
+                consumer.accept(entry);
+            }
+        }
+    }
+
+    @Nullable
+    private static LoudnessCodecInfo getCodecInfo(@NonNull MediaCodec mediaCodec) {
+        LoudnessCodecInfo lci = new LoudnessCodecInfo();
+        final MediaCodecInfo codecInfo = mediaCodec.getCodecInfo();
+        if (codecInfo.isEncoder()) {
+            // loudness info only for decoders
+            Log.w(TAG, "MediaCodec used for encoding does not support loudness annotation");
+            return null;
+        }
+
+        try {
+            final MediaFormat inputFormat = mediaCodec.getInputFormat();
+            final String mimeType = inputFormat.getString(MediaFormat.KEY_MIME);
+            if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mimeType)) {
+                // check both KEY_AAC_PROFILE and KEY_PROFILE as some codecs may only recognize
+                // one of these two keys
+                int aacProfile = -1;
+                int profile = -1;
+                try {
+                    aacProfile = inputFormat.getInteger(MediaFormat.KEY_AAC_PROFILE);
+                } catch (NullPointerException e) {
+                    // does not contain KEY_AAC_PROFILE. do nothing
+                }
+                try {
+                    profile = inputFormat.getInteger(MediaFormat.KEY_PROFILE);
+                } catch (NullPointerException e) {
+                    // does not contain KEY_PROFILE. do nothing
+                }
+                if (aacProfile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE
+                        || profile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE) {
+                    lci.metadataType = CODEC_METADATA_TYPE_MPEG_D;
+                } else {
+                    lci.metadataType = CODEC_METADATA_TYPE_MPEG_4;
+                }
+            } else {
+                Log.w(TAG, "MediaCodec mime type not supported for loudness annotation");
+                return null;
+            }
+
+            final MediaFormat outputFormat = mediaCodec.getOutputFormat();
+            lci.isDownmixing = outputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT)
+                    < inputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
+        } catch (IllegalStateException e) {
+            Log.e(TAG, "MediaCodec is not configured", e);
+            return null;
+        }
+
+        return lci;
+    }
+}
diff --git a/media/java/android/media/LoudnessCodecDispatcher.java b/media/java/android/media/LoudnessCodecDispatcher.java
index b546a81..fa08658 100644
--- a/media/java/android/media/LoudnessCodecDispatcher.java
+++ b/media/java/android/media/LoudnessCodecDispatcher.java
@@ -21,7 +21,7 @@
 import static android.media.MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL;
 
 import android.annotation.CallbackExecutor;
-import android.media.LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener;
+import android.media.LoudnessCodecController.OnLoudnessCodecUpdateListener;
 import android.os.Bundle;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
@@ -32,8 +32,6 @@
 
 import java.util.HashMap;
 import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
@@ -59,7 +57,7 @@
         private final Object mLock = new Object();
 
         @GuardedBy("mLock")
-        private final HashMap<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator>
+        private final HashMap<OnLoudnessCodecUpdateListener, LoudnessCodecController>
                 mConfiguratorListener = new HashMap<>();
 
         public static synchronized LoudnessCodecUpdatesDispatcherStub getInstance() {
@@ -72,26 +70,25 @@
         private LoudnessCodecUpdatesDispatcherStub() {}
 
         @Override
-        public void dispatchLoudnessCodecParameterChange(int piid, PersistableBundle params) {
+        public void dispatchLoudnessCodecParameterChange(int sessionId, PersistableBundle params) {
             if (DEBUG) {
-                Log.d(TAG, "dispatchLoudnessCodecParameterChange for piid " + piid
+                Log.d(TAG, "dispatchLoudnessCodecParameterChange for sessionId " + sessionId
                         + " persistable bundle: " + params);
             }
             mLoudnessListenerMgr.callListeners(listener -> {
                 synchronized (mLock) {
                     mConfiguratorListener.computeIfPresent(listener, (l, lcConfig) -> {
                         // send the appropriate bundle for the user to update
-                        if (lcConfig.getAssignedTrackPiid() == piid) {
-                            final Map<LoudnessCodecInfo, Set<MediaCodec>> mediaCodecsMap =
-                                    lcConfig.getRegisteredMediaCodecs();
-                            for (LoudnessCodecInfo codecInfo : mediaCodecsMap.keySet()) {
+                        if (lcConfig.getSessionId() == sessionId) {
+                            lcConfig.mediaCodecsConsume(mcEntry -> {
+                                final LoudnessCodecInfo codecInfo = mcEntry.getKey();
                                 final String infoKey = Integer.toString(codecInfo.hashCode());
                                 Bundle bundle = null;
                                 if (params.containsKey(infoKey)) {
                                     bundle = new Bundle(params.getPersistableBundle(infoKey));
                                 }
 
-                                final Set<MediaCodec> mediaCodecs = mediaCodecsMap.get(codecInfo);
+                                final Set<MediaCodec> mediaCodecs = mcEntry.getValue();
                                 for (MediaCodec mediaCodec : mediaCodecs) {
                                     final String mediaCodecKey = Integer.toString(
                                             mediaCodec.hashCode());
@@ -111,13 +108,18 @@
                                                             bundle));
 
                                     if (!bundle.isDefinitelyEmpty()) {
-                                        mediaCodec.setParameters(bundle);
+                                        try {
+                                            mediaCodec.setParameters(bundle);
+                                        } catch (IllegalStateException e) {
+                                            Log.w(TAG, "Cannot set loudness bundle on media codec "
+                                                    + mediaCodec);
+                                        }
                                     }
                                     if (canBreak) {
                                         break;
                                     }
                                 }
-                            }
+                            });
                         }
                         return lcConfig;
                     });
@@ -145,7 +147,7 @@
         }
 
         void addLoudnessCodecListener(@NonNull CallbackUtil.DispatcherStub dispatcher,
-                @NonNull LoudnessCodecConfigurator configurator,
+                @NonNull LoudnessCodecController configurator,
                 @NonNull @CallbackExecutor Executor executor,
                 @NonNull OnLoudnessCodecUpdateListener listener) {
             Objects.requireNonNull(configurator);
@@ -160,15 +162,15 @@
             }
         }
 
-        void removeLoudnessCodecListener(@NonNull LoudnessCodecConfigurator configurator) {
+        void removeLoudnessCodecListener(@NonNull LoudnessCodecController configurator) {
             Objects.requireNonNull(configurator);
 
             OnLoudnessCodecUpdateListener listenerToRemove = null;
             synchronized (mLock) {
-                Iterator<Entry<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator>> iterator =
+                Iterator<Entry<OnLoudnessCodecUpdateListener, LoudnessCodecController>> iterator =
                         mConfiguratorListener.entrySet().iterator();
                 while (iterator.hasNext()) {
-                    Entry<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator> e =
+                    Entry<OnLoudnessCodecUpdateListener, LoudnessCodecController> e =
                             iterator.next();
                     if (e.getValue() == configurator) {
                         final OnLoudnessCodecUpdateListener listener = e.getKey();
@@ -208,7 +210,7 @@
     }
 
     /** @hide */
-    public void addLoudnessCodecListener(@NonNull LoudnessCodecConfigurator configurator,
+    public void addLoudnessCodecListener(@NonNull LoudnessCodecController configurator,
                                          @NonNull @CallbackExecutor Executor executor,
                                          @NonNull OnLoudnessCodecUpdateListener listener) {
         LoudnessCodecUpdatesDispatcherStub.getInstance().addLoudnessCodecListener(this,
@@ -216,52 +218,52 @@
     }
 
     /** @hide */
-    public void removeLoudnessCodecListener(@NonNull LoudnessCodecConfigurator configurator) {
+    public void removeLoudnessCodecListener(@NonNull LoudnessCodecController configurator) {
         LoudnessCodecUpdatesDispatcherStub.getInstance().removeLoudnessCodecListener(configurator);
     }
 
     /** @hide */
-    public void startLoudnessCodecUpdates(int piid, List<LoudnessCodecInfo> codecInfoList) {
+    public void startLoudnessCodecUpdates(int sessionId) {
         try {
-            mAudioService.startLoudnessCodecUpdates(piid, codecInfoList);
+            mAudioService.startLoudnessCodecUpdates(sessionId);
         }  catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
     }
 
     /** @hide */
-    public void stopLoudnessCodecUpdates(int piid) {
+    public void stopLoudnessCodecUpdates(int sessionId) {
         try {
-            mAudioService.stopLoudnessCodecUpdates(piid);
+            mAudioService.stopLoudnessCodecUpdates(sessionId);
         }  catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
     }
 
     /** @hide */
-    public void addLoudnessCodecInfo(int piid, int mediaCodecHash,
+    public void addLoudnessCodecInfo(int sessionId, int mediaCodecHash,
             @NonNull LoudnessCodecInfo mcInfo) {
         try {
-            mAudioService.addLoudnessCodecInfo(piid, mediaCodecHash, mcInfo);
+            mAudioService.addLoudnessCodecInfo(sessionId, mediaCodecHash, mcInfo);
         }  catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
     }
 
     /** @hide */
-    public void removeLoudnessCodecInfo(int piid, @NonNull LoudnessCodecInfo mcInfo) {
+    public void removeLoudnessCodecInfo(int sessionId, @NonNull LoudnessCodecInfo mcInfo) {
         try {
-            mAudioService.removeLoudnessCodecInfo(piid, mcInfo);
+            mAudioService.removeLoudnessCodecInfo(sessionId, mcInfo);
         }  catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
     }
 
     /** @hide */
-    public Bundle getLoudnessCodecParams(int piid, @NonNull LoudnessCodecInfo mcInfo) {
+    public Bundle getLoudnessCodecParams(@NonNull LoudnessCodecInfo mcInfo) {
         Bundle loudnessParams = null;
         try {
-            loudnessParams = new Bundle(mAudioService.getLoudnessParams(piid, mcInfo));
+            loudnessParams = new Bundle(mAudioService.getLoudnessParams(mcInfo));
         }  catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 470a8ac..bfb4b42 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -17,6 +17,7 @@
 package android.media;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -43,21 +44,25 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.ReadOnlyBufferException;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
+import static com.android.media.codec.flags.Flags.FLAG_LARGE_AUDIO_FRAME;
 /**
  MediaCodec class can be used to access low-level media codecs, i.e. encoder/decoder components.
  It is part of the Android low-level multimedia support infrastructure (normally used together
@@ -1824,6 +1829,7 @@
     private static final String EOS_AND_DECODE_ONLY_ERROR_MESSAGE = "An input buffer cannot have "
             + "both BUFFER_FLAG_END_OF_STREAM and BUFFER_FLAG_DECODE_ONLY flags";
     private static final int CB_CRYPTO_ERROR = 6;
+    private static final int CB_LARGE_FRAME_OUTPUT_AVAILABLE = 7;
 
     private class EventHandler extends Handler {
         private MediaCodec mCodec;
@@ -1945,6 +1951,39 @@
                     break;
                 }
 
+                case CB_LARGE_FRAME_OUTPUT_AVAILABLE:
+                {
+                    int index = msg.arg2;
+                    ArrayDeque<BufferInfo> infos = (ArrayDeque<BufferInfo>)msg.obj;
+                    synchronized(mBufferLock) {
+                        switch (mBufferMode) {
+                            case BUFFER_MODE_LEGACY:
+                                validateOutputByteBuffersLocked(mCachedOutputBuffers,
+                                        index, infos);
+                                break;
+                            case BUFFER_MODE_BLOCK:
+                                while (mOutputFrames.size() <= index) {
+                                    mOutputFrames.add(null);
+                                }
+                                OutputFrame frame = mOutputFrames.get(index);
+                                if (frame == null) {
+                                    frame = new OutputFrame(index);
+                                    mOutputFrames.set(index, frame);
+                                }
+                                frame.setBufferInfos(infos);
+                                frame.setAccessible(true);
+                                break;
+                            default:
+                                throw new IllegalArgumentException(
+                                        "Unrecognized buffer mode: for large frame output");
+                        }
+                    }
+                    mCallback.onOutputBuffersAvailable(
+                            mCodec, index, infos);
+
+                    break;
+                }
+
                 case CB_ERROR:
                 {
                     mCallback.onError(mCodec, (MediaCodec.CodecException) msg.obj);
@@ -2836,11 +2875,72 @@
         }
     }
 
+    /**
+     * Submit multiple access units to the codec along with multiple
+     * {@link MediaCodec.BufferInfo} describing the contents of the buffer. This method
+     * is supported only in asynchronous mode. While this method can be used for all codecs,
+     * it is meant for buffer batching, which is only supported by codecs that advertise
+     * FEATURE_MultipleFrames. Other codecs will not output large output buffers via
+     * onOutputBuffersAvailable, and instead will output single-access-unit output via
+     * onOutputBufferAvailable.
+     * <p>
+     * Output buffer size can be configured using the following MediaFormat keys.
+     * {@link MediaFormat#KEY_BUFFER_BATCH_MAX_OUTPUT_SIZE} and
+     * {@link MediaFormat#KEY_BUFFER_BATCH_THRESHOLD_OUTPUT_SIZE}.
+     * Details for each access unit present in the buffer should be described using
+     * {@link MediaCodec.BufferInfo}. Access units must be laid out contiguously (without any gaps)
+     * and in order. Multiple access units in the output if present, will be available in
+     * {@link Callback#onOutputBuffersAvailable} or {@link Callback#onOutputBufferAvailable}
+     * in case of single-access-unit output or when output does not contain any buffers,
+     * such as flags.
+     * <p>
+     * All other details for populating {@link MediaCodec.BufferInfo} is the same as described in
+     * {@link #queueInputBuffer}.
+     *
+     * @param index The index of a client-owned input buffer previously returned
+     *              in a call to {@link #dequeueInputBuffer}.
+     * @param bufferInfos ArrayDeque of {@link MediaCodec.BufferInfo} that describes the
+     *                    contents in the buffer. The ArrayDeque and the BufferInfo objects provided
+     *                    can be recycled by the caller for re-use.
+     * @throws IllegalStateException if not in the Executing state or not in asynchronous mode.
+     * @throws MediaCodec.CodecException upon codec error.
+     * @throws IllegalArgumentException upon if bufferInfos is empty, contains null, or if the
+     *                    access units are not contiguous.
+     * @throws CryptoException if a crypto object has been specified in
+     *         {@link #configure}
+     */
+    @FlaggedApi(FLAG_LARGE_AUDIO_FRAME)
+    public final void queueInputBuffers(
+            int index,
+            @NonNull ArrayDeque<BufferInfo> bufferInfos) {
+        synchronized(mBufferLock) {
+            if (mBufferMode == BUFFER_MODE_BLOCK) {
+                throw new IncompatibleWithBlockModelException("queueInputBuffers() "
+                        + "is not compatible with CONFIGURE_FLAG_USE_BLOCK_MODEL. "
+                        + "Please use getQueueRequest() to queue buffers");
+            }
+            invalidateByteBufferLocked(mCachedInputBuffers, index, true /* input */);
+            mDequeuedInputBuffers.remove(index);
+        }
+        try {
+            native_queueInputBuffers(
+                    index, bufferInfos.toArray());
+        } catch (CryptoException | IllegalStateException | IllegalArgumentException e) {
+            revalidateByteBuffer(mCachedInputBuffers, index, true /* input */);
+            throw e;
+        }
+    }
+
     private native final void native_queueInputBuffer(
             int index,
             int offset, int size, long presentationTimeUs, int flags)
         throws CryptoException;
 
+    private native final void native_queueInputBuffers(
+            int index,
+            @NonNull Object[] infos)
+        throws CryptoException, CodecException;
+
     public static final int CRYPTO_MODE_UNENCRYPTED = 0;
     public static final int CRYPTO_MODE_AES_CTR     = 1;
     public static final int CRYPTO_MODE_AES_CBC     = 2;
@@ -3464,6 +3564,26 @@
         }
 
         /**
+         * Sets MediaCodec.BufferInfo objects describing the access units
+         * contained in this queue request. Access units must be laid out
+         * contiguously without gaps and in order.
+         *
+         * @param infos Represents {@link MediaCodec.BufferInfo} objects to mark
+         *              individual access-unit boundaries and the timestamps associated with it.
+         *              The buffer is expected to contain the data in a continuous manner.
+         * @return this object
+         */
+        @FlaggedApi(FLAG_LARGE_AUDIO_FRAME)
+        public @NonNull QueueRequest setBufferInfos(@NonNull ArrayDeque<BufferInfo> infos) {
+            if (!isAccessible()) {
+                throw new IllegalStateException("The request is stale");
+            }
+            mBufferInfos.clear();
+            mBufferInfos.addAll(infos);
+            return this;
+        }
+
+        /**
          * Add an integer parameter.
          * See {@link MediaFormat} for an exhaustive list of supported keys with
          * values of type int, that can also be set with {@link MediaFormat#setInteger}.
@@ -3579,10 +3699,18 @@
                 throw new IllegalStateException("No block is set");
             }
             setAccessible(false);
+            if (mBufferInfos.isEmpty()) {
+                BufferInfo info = new BufferInfo();
+                info.size = mSize;
+                info.offset = mOffset;
+                info.presentationTimeUs = mPresentationTimeUs;
+                info.flags = mFlags;
+                mBufferInfos.add(info);
+            }
             if (mLinearBlock != null) {
                 mCodec.native_queueLinearBlock(
-                        mIndex, mLinearBlock, mOffset, mSize, mCryptoInfo,
-                        mPresentationTimeUs, mFlags,
+                        mIndex, mLinearBlock, mCryptoInfo,
+                        mBufferInfos.toArray(),
                         mTuningKeys, mTuningValues);
             } else if (mHardwareBuffer != null) {
                 mCodec.native_queueHardwareBuffer(
@@ -3600,6 +3728,7 @@
             mHardwareBuffer = null;
             mPresentationTimeUs = 0;
             mFlags = 0;
+            mBufferInfos.clear();
             mTuningKeys.clear();
             mTuningValues.clear();
             return this;
@@ -3623,6 +3752,7 @@
         private HardwareBuffer mHardwareBuffer = null;
         private long mPresentationTimeUs = 0;
         private @BufferFlag int mFlags = 0;
+        private final ArrayDeque<BufferInfo> mBufferInfos = new ArrayDeque<>();
         private final ArrayList<String> mTuningKeys = new ArrayList<>();
         private final ArrayList<Object> mTuningValues = new ArrayList<>();
 
@@ -3632,11 +3762,8 @@
     private native void native_queueLinearBlock(
             int index,
             @NonNull LinearBlock block,
-            int offset,
-            int size,
             @Nullable CryptoInfo cryptoInfo,
-            long presentationTimeUs,
-            int flags,
+            @NonNull Object[] bufferInfos,
             @NonNull ArrayList<String> keys,
             @NonNull ArrayList<Object> values);
 
@@ -4050,6 +4177,27 @@
         }
     }
 
+    private void validateOutputByteBuffersLocked(
+        @Nullable ByteBuffer[] buffers, int index, @NonNull ArrayDeque<BufferInfo> infoDeque) {
+        Optional<BufferInfo> minInfo = infoDeque.stream().min(
+                (info1, info2) -> Integer.compare(info1.offset, info2.offset));
+        Optional<BufferInfo> maxInfo = infoDeque.stream().max(
+                (info1, info2) -> Integer.compare(info1.offset, info2.offset));
+        if (buffers == null) {
+            if (index >= 0) {
+                mValidOutputIndices.set(index);
+            }
+        } else if (index >= 0 && index < buffers.length) {
+            ByteBuffer buffer = buffers[index];
+            if (buffer != null && minInfo.isPresent() && maxInfo.isPresent()) {
+                buffer.setAccessible(true);
+                buffer.limit(maxInfo.get().offset + maxInfo.get().size);
+                buffer.position(minInfo.get().offset);
+            }
+        }
+
+    }
+
     private void validateOutputByteBufferLocked(
             @Nullable ByteBuffer[] buffers, int index, @NonNull BufferInfo info) {
         if (buffers == null) {
@@ -4407,6 +4555,22 @@
             return mFlags;
         }
 
+        /*
+         * Returns the BufferInfos associated with this OutputFrame. These BufferInfos
+         * describes the access units present in the OutputFrame. Access units are laid
+         * out contiguously without gaps and in order.
+         */
+        @FlaggedApi(FLAG_LARGE_AUDIO_FRAME)
+        public @NonNull ArrayDeque<BufferInfo> getBufferInfos() {
+            if (mBufferInfos.isEmpty()) {
+                // single BufferInfo could be present.
+                BufferInfo bufferInfo = new BufferInfo();
+                bufferInfo.set(0, 0, mPresentationTimeUs, mFlags);
+                mBufferInfos.add(bufferInfo);
+            }
+            return mBufferInfos;
+        }
+
         /**
          * Returns a read-only {@link MediaFormat} for this frame. The returned
          * object is valid only until the client calls {@link MediaCodec#releaseOutputBuffer}.
@@ -4432,6 +4596,7 @@
             mLinearBlock = null;
             mHardwareBuffer = null;
             mFormat = null;
+            mBufferInfos.clear();
             mChangedKeys.clear();
             mKeySet.clear();
             mLoaded = false;
@@ -4450,6 +4615,11 @@
             mFlags = info.flags;
         }
 
+        void setBufferInfos(ArrayDeque<BufferInfo> infos) {
+            mBufferInfos.clear();
+            mBufferInfos.addAll(infos);
+        }
+
         boolean isLoaded() {
             return mLoaded;
         }
@@ -4464,6 +4634,7 @@
         private long mPresentationTimeUs = 0;
         private @BufferFlag int mFlags = 0;
         private MediaFormat mFormat = null;
+        private final ArrayDeque<BufferInfo> mBufferInfos = new ArrayDeque<>();
         private final ArrayList<String> mChangedKeys = new ArrayList<>();
         private final Set<String> mKeySet = new HashSet<>();
         private boolean mAccessible = false;
@@ -5172,6 +5343,32 @@
                 @NonNull MediaCodec codec, int index, @NonNull BufferInfo info);
 
         /**
+         * Called when multiple access-units are available in the output.
+         *
+         * @param codec The MediaCodec object.
+         * @param index The index of the available output buffer.
+         * @param infos Infos describing the available output buffer {@link MediaCodec.BufferInfo}.
+         *              Access units present in the output buffer are laid out contiguously
+         *              without gaps and in order.
+         */
+        @FlaggedApi(FLAG_LARGE_AUDIO_FRAME)
+        public void onOutputBuffersAvailable(
+                @NonNull MediaCodec codec, int index, @NonNull ArrayDeque<BufferInfo> infos) {
+            /*
+             * This callback returns multiple BufferInfos when codecs are configured to operate on
+             * large audio frame. Since at this point, we have a single large buffer, returning
+             * each BufferInfo using
+             * {@link Callback#onOutputBufferAvailable onOutputBufferAvailable} may cause the
+             * index to be released to the codec using {@link MediaCodec#releaseOutputBuffer}
+             * before all BuffersInfos can be returned to the client.
+             * Hence this callback is required to be implemented or else an exception is thrown.
+             */
+            throw new IllegalStateException(
+                    "Client must override onOutputBuffersAvailable when codec is " +
+                    "configured to operate with multiple access units");
+        }
+
+        /**
          * Called when the MediaCodec encountered an error
          *
          * @param codec The MediaCodec object.
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 46db777..5e40eee 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -16,6 +16,10 @@
 
 package android.media;
 
+import static com.android.media.codec.flags.Flags.FLAG_CODEC_IMPORTANCE;
+import static com.android.media.codec.flags.Flags.FLAG_LARGE_AUDIO_FRAME;
+
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -118,6 +122,10 @@
  * <tr><td>{@link #KEY_MPEGH_REFERENCE_CHANNEL_LAYOUT}</td>
  *     <td>Integer</td><td><b>decoder-only</b>, optional, if content is MPEG-H audio,
  *         specifies the preferred reference channel layout of the stream.</td></tr>
+ * <tr><td>{@link #KEY_MAX_BUFFER_BATCH_OUTPUT_SIZE}</td><td>Integer</td><td>optional, used with
+ *         large audio frame support, specifies max size of output buffer in bytes.</td></tr>
+ * <tr><td>{@link #KEY_BUFFER_BATCH_THRESHOLD_OUTPUT_SIZE}</td><td>Integer</td><td>optional,
+ *         used with large audio frame support, specifies threshold output size in bytes.</td></tr>
  * </table>
  *
  * Subtitle formats have the following keys:
@@ -456,6 +464,50 @@
     public static final String KEY_MAX_INPUT_SIZE = "max-input-size";
 
     /**
+     * A key describing the maximum output buffer size in bytes when using
+     * large buffer mode containing multiple access units.
+     *
+     * When not-set - codec functions with one access-unit per frame.
+     * When set less than the size of two access-units - will make codec
+     * operate in single access-unit per output frame.
+     * When set to a value too big - The component or the framework will
+     * override this value to a reasonable max size not exceeding typical
+     * 10 seconds of data (device dependent) when set to a value larger than
+     * that. The value final value used will be returned in the output format.
+     *
+     * The associated value is an integer
+     *
+     * @see FEATURE_MultipleFrames
+     */
+    @FlaggedApi(FLAG_LARGE_AUDIO_FRAME)
+    public static final String KEY_BUFFER_BATCH_MAX_OUTPUT_SIZE = "buffer-batch-max-output-size";
+
+    /**
+     * A key describing the threshold output size in bytes when using large buffer
+     * mode containing multiple access units.
+     *
+     * This is an optional parameter.
+     *
+     * If not set - the component can set this to a reasonable value.
+     * If set larger than max size, the components will
+     * clip this setting to maximum buffer batching output size.
+     *
+     * The component will return a partial output buffer if the output buffer reaches or
+     * surpass this limit.
+     *
+     * Threshold size should be always less or equal to KEY_MAX_BUFFER_BATCH_OUTPUT_SIZE.
+     * The final setting of this value as determined by the component will be returned
+     * in the output format
+     *
+     * The associated value is an integer
+     *
+     * @see FEATURE_MultipleFrames
+     */
+    @FlaggedApi(FLAG_LARGE_AUDIO_FRAME)
+    public static final String KEY_BUFFER_BATCH_THRESHOLD_OUTPUT_SIZE =
+            "buffer-batch-threshold-output-size";
+
+    /**
      * A key describing the pixel aspect ratio width.
      * The associated value is an integer
      */
@@ -1635,6 +1687,34 @@
      */
     public static final String KEY_ALLOW_FRAME_DROP = "allow-frame-drop";
 
+    /**
+     * A key describing the desired codec importance for the application.
+     * <p>
+     * The associated value is a positive integer including zero.
+     * Higher value means lesser importance.
+     * <p>
+     * The resource manager may use the codec importance, along with other factors
+     * when reclaiming codecs from an application.
+     * The specifics of reclaim policy is device dependent, but specifying the codec importance,
+     * will allow the resource manager to prioritize reclaiming less important codecs
+     * (assigned higher values) from the (reclaim) requesting application first.
+     * So, the codec importance is only relevant within the context of that application.
+     * <p>
+     * The codec importance can be set:
+     * <ul>
+     * <li>through {@link MediaCodec#configure}. </li>
+     * <li>through {@link MediaCodec#setParameters} if the codec has been configured already,
+     * which allows the users to change the codec importance multiple times.
+     * </ul>
+     * Any change/update in codec importance is guaranteed upon the completion of the function call
+     * that sets the codec importance. So, in case of concurrent codec operations,
+     * make sure to wait for the change in codec importance, before using another codec.
+     * Note that unless specified, by default the codecs will have highest importance (of value 0).
+     *
+     */
+    @FlaggedApi(FLAG_CODEC_IMPORTANCE)
+    public static final String KEY_IMPORTANCE = "importance";
+
     /* package private */ MediaFormat(@NonNull Map<String, Object> map) {
         mMap = map;
     }
diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java
index 30f4562..80b606c 100644
--- a/media/java/android/media/MediaMuxer.java
+++ b/media/java/android/media/MediaMuxer.java
@@ -514,7 +514,7 @@
      * The following table summarizes support for specific format keys across android releases.
      * Keys marked with '+:' are required.
      *
-     * <table style="width: 0%">
+     * <table>
      *  <thead>
      *   <tr>
      *    <th rowspan=2>OS Version(s)</th>
@@ -583,7 +583,7 @@
      * <p>
      * The following table summarizes codec support for containers across android releases:
      *
-     * <table style="width: 0%">
+     * <table>
      *  <thead>
      *   <tr>
      *    <th rowspan=2>OS Version(s)</th>
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 8e9c079..a0f8ae5 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1154,6 +1154,7 @@
             setDataSource(afd);
             return true;
         } catch (NullPointerException | SecurityException | IOException ex) {
+            Log.w(TAG, "Error setting data source via ContentResolver", ex);
             return false;
         }
     }
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index aa30748..bdfa6301 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -65,7 +65,8 @@
  *
  * <p>A common case of using MediaRecorder to record audio works as follows:
  *
- * <pre>MediaRecorder recorder = new MediaRecorder();
+ * <pre>
+ * MediaRecorder recorder = new MediaRecorder(context);
  * recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
  * recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
  * recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 89792c7..425db06 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -48,6 +48,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -141,7 +142,9 @@
      * dispatch. This is only used to determine what callback a route should be assigned to (added,
      * removed, changed) in {@link #dispatchFilteredRoutesUpdatedOnHandler(List)}.
      */
-    private volatile ArrayMap<String, MediaRoute2Info> mPreviousRoutes = new ArrayMap<>();
+    private volatile ArrayMap<String, MediaRoute2Info> mPreviousFilteredRoutes = new ArrayMap<>();
+
+    private final Map<String, MediaRoute2Info> mPreviousUnfilteredRoutes = new ArrayMap<>();
 
     /**
      * Stores the latest copy of exposed routes after filtering, sorting, and deduplication. Can be
@@ -180,30 +183,30 @@
      *       preference} passed by a proxy router. Use {@link RouteDiscoveryPreference#EMPTY} when
      *       setting a route callback.
      *   <li>
-     *       <p>Methods returning non-system {@link RoutingController controllers} always return
-     *       new instances with the latest data. Do not attempt to compare or store them. Instead,
-     *       use {@link #getController(String)} or {@link #getControllers()} to query the most
+     *       <p>Methods returning non-system {@link RoutingController controllers} always return new
+     *       instances with the latest data. Do not attempt to compare or store them. Instead, use
+     *       {@link #getController(String)} or {@link #getControllers()} to query the most
      *       up-to-date state.
      *   <li>
      *       <p>Calls to {@link #setOnGetControllerHintsListener} are ignored.
      * </ul>
      *
      * @param clientPackageName the package name of the app to control
-     * @throws SecurityException if the caller doesn't have {@link
-     *     Manifest.permission#MEDIA_CONTENT_CONTROL MEDIA_CONTENT_CONTROL} permission.
-     * @hide
+     * @return a proxy MediaRouter2 instance if {@code clientPackageName} exists or {@code null}.
      */
-    // TODO (b/311711420): Deprecate once #getInstance(Context, Looper, String, UserHandle)
-    //  reaches public SDK.
-    @SystemApi
-    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+    @FlaggedApi(FLAG_ENABLE_CROSS_USER_ROUTING_IN_MEDIA_ROUTER2)
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.MEDIA_CONTENT_CONTROL,
+                Manifest.permission.MEDIA_ROUTING_CONTROL
+            })
     @Nullable
     public static MediaRouter2 getInstance(
             @NonNull Context context, @NonNull String clientPackageName) {
         // Capturing the IAE here to not break nullability.
         try {
             return findOrCreateProxyInstanceForCallingUser(
-                    context, Looper.getMainLooper(), clientPackageName, context.getUser());
+                    context, clientPackageName, context.getUser());
         } catch (IllegalArgumentException ex) {
             Log.e(TAG, "Package " + clientPackageName + " not found. Ignoring.");
             return null;
@@ -214,8 +217,6 @@
      * Returns a proxy MediaRouter2 instance that allows you to control the routing of an app
      * specified by {@code clientPackageName} and {@code user}.
      *
-     * <p>You can specify any {@link Looper} of choice on which internal state updates will run.
-     *
      * <p>Proxy MediaRouter2 instances operate differently than regular MediaRouter2 instances:
      *
      * <ul>
@@ -225,16 +226,15 @@
      *       {@link RouteDiscoveryPreference.Builder#setPreferredFeatures(List) preferred features}
      *       when setting a route callback.
      *   <li>
-     *       <p>Methods returning non-system {@link RoutingController controllers} always return
-     *       new instances with the latest data. Do not attempt to compare or store them. Instead,
-     *       use {@link #getController(String)} or {@link #getControllers()} to query the most
+     *       <p>Methods returning non-system {@link RoutingController controllers} always return new
+     *       instances with the latest data. Do not attempt to compare or store them. Instead, use
+     *       {@link #getController(String)} or {@link #getControllers()} to query the most
      *       up-to-date state.
      *   <li>
      *       <p>Calls to {@link #setOnGetControllerHintsListener} are ignored.
      * </ul>
      *
      * @param context The {@link Context} of the caller.
-     * @param looper The {@link Looper} on which to process internal state changes.
      * @param clientPackageName The package name of the app you want to control the routing of.
      * @param user The {@link UserHandle} of the user running the app for which to get the proxy
      *     router instance. Must match {@link Process#myUserHandle()} if the caller doesn't hold
@@ -242,8 +242,8 @@
      * @throws SecurityException if {@code user} does not match {@link Process#myUserHandle()} and
      *     the caller does not hold {@code Manifest.permission#INTERACT_ACROSS_USERS_FULL}.
      * @throws IllegalArgumentException if {@code clientPackageName} does not exist in {@code user}.
+     * @hide
      */
-    @FlaggedApi(FLAG_ENABLE_CROSS_USER_ROUTING_IN_MEDIA_ROUTER2)
     @RequiresPermission(
             anyOf = {
                 Manifest.permission.MEDIA_CONTENT_CONTROL,
@@ -251,11 +251,8 @@
             })
     @NonNull
     public static MediaRouter2 getInstance(
-            @NonNull Context context,
-            @NonNull Looper looper,
-            @NonNull String clientPackageName,
-            @NonNull UserHandle user) {
-        return findOrCreateProxyInstanceForCallingUser(context, looper, clientPackageName, user);
+            @NonNull Context context, @NonNull String clientPackageName, @NonNull UserHandle user) {
+        return findOrCreateProxyInstanceForCallingUser(context, clientPackageName, user);
     }
 
     /**
@@ -267,9 +264,8 @@
      */
     @NonNull
     private static MediaRouter2 findOrCreateProxyInstanceForCallingUser(
-            Context context, Looper looper, String clientPackageName, UserHandle user) {
+            Context context, String clientPackageName, UserHandle user) {
         Objects.requireNonNull(context, "context must not be null");
-        Objects.requireNonNull(looper, "looper must not be null");
         Objects.requireNonNull(user, "user must not be null");
 
         if (TextUtils.isEmpty(clientPackageName)) {
@@ -281,7 +277,10 @@
         synchronized (sSystemRouterLock) {
             MediaRouter2 instance = sAppToProxyRouterMap.get(key);
             if (instance == null) {
-                instance = new MediaRouter2(context, looper, clientPackageName, user);
+                instance =
+                        new MediaRouter2(context, Looper.getMainLooper(), clientPackageName, user);
+                // Register proxy router after instantiation to avoid race condition.
+                ((ProxyMediaRouter2Impl) instance.mImpl).registerProxyRouter();
                 sAppToProxyRouterMap.put(key, instance);
             }
             return instance;
@@ -368,6 +367,7 @@
                 new SystemRoutingController(
                         ProxyMediaRouter2Impl.getSystemSessionInfoImpl(
                                 mMediaRouterService, clientPackageName));
+
         mImpl = new ProxyMediaRouter2Impl(context, clientPackageName, user);
     }
 
@@ -713,7 +713,7 @@
         mImpl.transfer(
                 controller.getRoutingSessionInfo(),
                 route,
-                android.os.Process.myUserHandle(),
+                Process.myUserHandle(),
                 mContext.getPackageName());
     }
 
@@ -728,7 +728,7 @@
      *     request.
      * @param transferInitiatorPackageName the package name of the app that initiated the transfer.
      *     This value is used with the user handle to populate {@link
-     *     RoutingController#wasTransferRequestedBySelf()}.
+     *     RoutingController#wasTransferInitiatedBySelf()}.
      * @hide
      */
     @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
@@ -883,7 +883,7 @@
                 newRoutes.stream().map(MediaRoute2Info::getId).collect(Collectors.toSet());
 
         for (MediaRoute2Info route : newRoutes) {
-            MediaRoute2Info prevRoute = mPreviousRoutes.get(route.getId());
+            MediaRoute2Info prevRoute = mPreviousFilteredRoutes.get(route.getId());
             if (prevRoute == null) {
                 addedRoutes.add(route);
             } else if (!prevRoute.equals(route)) {
@@ -891,21 +891,21 @@
             }
         }
 
-        for (int i = 0; i < mPreviousRoutes.size(); i++) {
-            if (!newRouteIds.contains(mPreviousRoutes.keyAt(i))) {
-                removedRoutes.add(mPreviousRoutes.valueAt(i));
+        for (int i = 0; i < mPreviousFilteredRoutes.size(); i++) {
+            if (!newRouteIds.contains(mPreviousFilteredRoutes.keyAt(i))) {
+                removedRoutes.add(mPreviousFilteredRoutes.valueAt(i));
             }
         }
 
         // update previous routes
         for (MediaRoute2Info route : removedRoutes) {
-            mPreviousRoutes.remove(route.getId());
+            mPreviousFilteredRoutes.remove(route.getId());
         }
         for (MediaRoute2Info route : addedRoutes) {
-            mPreviousRoutes.put(route.getId(), route);
+            mPreviousFilteredRoutes.put(route.getId(), route);
         }
         for (MediaRoute2Info route : changedRoutes) {
-            mPreviousRoutes.put(route.getId(), route);
+            mPreviousFilteredRoutes.put(route.getId(), route);
         }
 
         if (!addedRoutes.isEmpty()) {
@@ -924,6 +924,27 @@
         }
     }
 
+    void dispatchControllerUpdatedIfNeededOnHandler(Map<String, MediaRoute2Info> routesMap) {
+        List<RoutingController> controllers = getControllers();
+        for (RoutingController controller : controllers) {
+
+            for (String selectedRoute : controller.getRoutingSessionInfo().getSelectedRoutes()) {
+                if (routesMap.containsKey(selectedRoute)
+                        && mPreviousUnfilteredRoutes.containsKey(selectedRoute)) {
+                    MediaRoute2Info currentRoute = routesMap.get(selectedRoute);
+                    MediaRoute2Info oldRoute = mPreviousUnfilteredRoutes.get(selectedRoute);
+                    if (!currentRoute.equals(oldRoute)) {
+                        notifyControllerUpdated(controller);
+                        break;
+                    }
+                }
+            }
+        }
+
+        mPreviousUnfilteredRoutes.clear();
+        mPreviousUnfilteredRoutes.putAll(routesMap);
+    }
+
     void updateRoutesOnHandler(List<MediaRoute2Info> newRoutes) {
         synchronized (mLock) {
             mRoutes.clear();
@@ -945,6 +966,11 @@
                         MediaRouter2::dispatchFilteredRoutesUpdatedOnHandler,
                         this,
                         mFilteredRoutes));
+        mHandler.sendMessage(
+                obtainMessage(
+                        MediaRouter2::dispatchControllerUpdatedIfNeededOnHandler,
+                        this,
+                        new HashMap<>(mRoutes)));
     }
 
     /**
@@ -1518,17 +1544,17 @@
         }
 
         /**
-         * Returns whether the transfer was requested by the calling app (as determined by comparing
+         * Returns whether the transfer was initiated by the calling app (as determined by comparing
          * {@link UserHandle} and package name).
          */
         @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
-        public boolean wasTransferRequestedBySelf() {
+        public boolean wasTransferInitiatedBySelf() {
             RoutingSessionInfo sessionInfo = getRoutingSessionInfo();
 
             UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle();
             String transferInitiatorPackageName = sessionInfo.getTransferInitiatorPackageName();
 
-            return Objects.equals(android.os.Process.myUserHandle(), transferInitiatorUserHandle)
+            return Objects.equals(Process.myUserHandle(), transferInitiatorUserHandle)
                     && Objects.equals(mContext.getPackageName(), transferInitiatorPackageName);
         }
 
@@ -2153,18 +2179,19 @@
             mClientUser = user;
             mClientPackageName = clientPackageName;
             mClient = new Client();
+            mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
+        }
 
+        public void registerProxyRouter() {
             try {
                 mMediaRouterService.registerProxyRouter(
                         mClient,
-                        context.getApplicationContext().getPackageName(),
-                        clientPackageName,
-                        user);
+                        mContext.getApplicationContext().getPackageName(),
+                        mClientPackageName,
+                        mClientUser);
             } catch (RemoteException ex) {
                 throw ex.rethrowFromSystemServer();
             }
-
-            mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
         }
 
         @Override
@@ -2294,11 +2321,7 @@
 
             List<RoutingSessionInfo> sessionInfos = getRoutingSessions();
             RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1);
-            transfer(
-                    targetSession,
-                    route,
-                    android.os.Process.myUserHandle(),
-                    mContext.getPackageName());
+            transfer(targetSession, route, Process.myUserHandle(), mContext.getPackageName());
         }
 
         @Override
@@ -3055,11 +3078,7 @@
         public void registerRouteCallback() {
             synchronized (mLock) {
                 try {
-                    if (mStub == null) {
-                        MediaRouter2Stub stub = new MediaRouter2Stub();
-                        mMediaRouterService.registerRouter2(stub, mPackageName);
-                        mStub = stub;
-                    }
+                    registerRouterStubIfNeededLocked();
 
                     if (updateDiscoveryPreferenceIfNeededLocked()) {
                         mMediaRouterService.setDiscoveryRequestWithRouter2(
@@ -3084,10 +3103,8 @@
                                 mStub, mDiscoveryPreference);
                     }
 
-                    if (mRouteCallbackRecords.isEmpty() && mNonSystemRoutingControllers.isEmpty()) {
-                        mMediaRouterService.unregisterRouter2(mStub);
-                        mStub = null;
-                    }
+                    unregisterRouterStubIfNeededLocked();
+
                 } catch (RemoteException ex) {
                     Log.e(TAG, "unregisterRouteCallback: Unable to set discovery request.", ex);
                 }
@@ -3103,11 +3120,7 @@
                 }
                 mRouteListingPreference = preference;
                 try {
-                    if (mStub == null) {
-                        MediaRouter2Stub stub = new MediaRouter2Stub();
-                        mMediaRouterService.registerRouter2(stub, mImpl.getPackageName());
-                        mStub = stub;
-                    }
+                    registerRouterStubIfNeededLocked();
                     mMediaRouterService.setRouteListingPreference(mStub, mRouteListingPreference);
                 } catch (RemoteException ex) {
                     ex.rethrowFromSystemServer();
@@ -3165,8 +3178,12 @@
                 return;
             }
 
-            requestCreateController(controller, route, MANAGER_REQUEST_ID_NONE,
-                    android.os.Process.myUserHandle(), mContext.getPackageName());
+            requestCreateController(
+                    controller,
+                    route,
+                    MANAGER_REQUEST_ID_NONE,
+                    Process.myUserHandle(),
+                    mContext.getPackageName());
         }
 
         @Override
@@ -3295,18 +3312,32 @@
                             obtainMessage(MediaRouter2::notifyStop, MediaRouter2.this, controller));
                 }
 
-                if (mRouteCallbackRecords.isEmpty()
-                        && mNonSystemRoutingControllers.isEmpty()
-                        && mStub != null) {
-                    try {
-                        mMediaRouterService.unregisterRouter2(mStub);
-                    } catch (RemoteException ex) {
-                        ex.rethrowFromSystemServer();
-                    }
-                    mStub = null;
+                try {
+                    unregisterRouterStubIfNeededLocked();
+                } catch (RemoteException ex) {
+                    ex.rethrowFromSystemServer();
                 }
+
             }
         }
 
+        @GuardedBy("mLock")
+        private void registerRouterStubIfNeededLocked() throws RemoteException {
+            if (mStub == null) {
+                MediaRouter2Stub stub = new MediaRouter2Stub();
+                mMediaRouterService.registerRouter2(stub, mPackageName);
+                mStub = stub;
+            }
+        }
+
+        @GuardedBy("mLock")
+        private void unregisterRouterStubIfNeededLocked() throws RemoteException {
+            if (mStub != null
+                    && mRouteCallbackRecords.isEmpty()
+                    && mNonSystemRoutingControllers.isEmpty()) {
+                mMediaRouterService.unregisterRouter2(mStub);
+                mStub = null;
+            }
+        }
     }
 }
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index d28c26d..2202766 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -182,7 +182,7 @@
         mControlHints = src.readBundle();
         mIsSystemSession = src.readBoolean();
         mTransferReason = src.readInt();
-        mTransferInitiatorUserHandle = src.readParcelable(null, android.os.UserHandle.class);
+        mTransferInitiatorUserHandle = UserHandle.readFromParcel(src);
         mTransferInitiatorPackageName = src.readString();
     }
 
@@ -417,11 +417,7 @@
         dest.writeBundle(mControlHints);
         dest.writeBoolean(mIsSystemSession);
         dest.writeInt(mTransferReason);
-        if (mTransferInitiatorUserHandle != null) {
-            mTransferInitiatorUserHandle.writeToParcel(dest, /* flags= */ 0);
-        } else {
-            dest.writeParcelable(null, /* flags= */ 0);
-        }
+        UserHandle.writeToParcel(mTransferInitiatorUserHandle, dest);
         dest.writeString(mTransferInitiatorPackageName);
     }
 
diff --git a/media/java/android/media/audiofx/Virtualizer.java b/media/java/android/media/audiofx/Virtualizer.java
index 74b6fc1..71147f4 100644
--- a/media/java/android/media/audiofx/Virtualizer.java
+++ b/media/java/android/media/audiofx/Virtualizer.java
@@ -46,6 +46,11 @@
  * <p>See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions.
  * <p>See {@link android.media.audiofx.AudioEffect} class for more details on controlling
  * audio effects.
+ *
+ * @deprecated use the {@link android.media.Spatializer} class to query the capabilities of the
+ *     platform with regards to spatialization, a different name for audio channel virtualization,
+ *     and the {@link android.media.AudioAttributes.Builder#setSpatializationBehavior(int)} to
+ *     characterize how you want your content to be played when spatialization is supported.
  */
 
 public class Virtualizer extends AudioEffect {
diff --git a/media/java/android/media/flags/editing.aconfig b/media/java/android/media/flags/editing.aconfig
new file mode 100644
index 0000000..c3997e9
--- /dev/null
+++ b/media/java/android/media/flags/editing.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.media.editing.flags"
+
+flag {
+  name: "add_media_metrics_editing"
+  namespace: "media_solutions"
+  description: "Add media metrics for transcoding/editing events."
+  bug: "297487694"
+}
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 7f95886..df9ecdc 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -83,3 +83,10 @@
     description: "Notify ActivityManager with the changes in playback state of the media session."
     bug: "295518668"
 }
+
+flag {
+    name: "enable_prevention_of_keep_alive_route_providers"
+    namespace: "media_solutions"
+    description: "Enables mechanisms to prevent route providers from keeping malicious apps alive."
+    bug: "263520343"
+}
diff --git a/media/java/android/media/metrics/EditingEndedEvent.aidl b/media/java/android/media/metrics/EditingEndedEvent.aidl
new file mode 100644
index 0000000..e099dea
--- /dev/null
+++ b/media/java/android/media/metrics/EditingEndedEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.metrics;
+
+parcelable EditingEndedEvent;
diff --git a/media/java/android/media/metrics/EditingEndedEvent.java b/media/java/android/media/metrics/EditingEndedEvent.java
new file mode 100644
index 0000000..72e6db8
--- /dev/null
+++ b/media/java/android/media/metrics/EditingEndedEvent.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.metrics;
+
+import static com.android.media.editing.flags.Flags.FLAG_ADD_MEDIA_METRICS_EDITING;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.util.Objects;
+
+/** Event for an editing operation having ended. */
+@FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
+public final class EditingEndedEvent extends Event implements Parcelable {
+
+    // The special value 0 is reserved for the field being unspecified in the proto.
+
+    /** The editing operation was successful. */
+    public static final int FINAL_STATE_SUCCEEDED = 1;
+
+    /** The editing operation was canceled. */
+    public static final int FINAL_STATE_CANCELED = 2;
+
+    /** The editing operation failed due to an error. */
+    public static final int FINAL_STATE_ERROR = 3;
+
+    /** @hide */
+    @IntDef(
+            prefix = {"FINAL_STATE_"},
+            value = {
+                FINAL_STATE_SUCCEEDED,
+                FINAL_STATE_CANCELED,
+                FINAL_STATE_ERROR,
+            })
+    @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    public @interface FinalState {}
+
+    private final @FinalState int mFinalState;
+
+    // The special value 0 is reserved for the field being unspecified in the proto.
+
+    /** Special value representing that no error occurred. */
+    public static final int ERROR_CODE_NONE = 1;
+
+    /** Error code for unexpected runtime errors. */
+    public static final int ERROR_CODE_FAILED_RUNTIME_CHECK = 2;
+
+    /** Error code for non-specific errors during input/output. */
+    public static final int ERROR_CODE_IO_UNSPECIFIED = 3;
+
+    /** Error code for network connection failures. */
+    public static final int ERROR_CODE_IO_NETWORK_CONNECTION_FAILED = 4;
+
+    /** Error code for network timeouts. */
+    public static final int ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT = 5;
+
+    /** Caused by an HTTP server returning an unexpected HTTP response status code. */
+    public static final int ERROR_CODE_IO_BAD_HTTP_STATUS = 6;
+
+    /** Caused by a non-existent file. */
+    public static final int ERROR_CODE_IO_FILE_NOT_FOUND = 7;
+
+    /**
+     * Caused by lack of permission to perform an IO operation. For example, lack of permission to
+     * access internet or external storage.
+     */
+    public static final int ERROR_CODE_IO_NO_PERMISSION = 8;
+
+    /** */
+    public static final int ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED = 9;
+
+    /** Caused by reading data out of the data bounds. */
+    public static final int ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE = 10;
+
+    /** Caused by a decoder initialization failure. */
+    public static final int ERROR_CODE_DECODER_INIT_FAILED = 11;
+
+    /** Caused by a failure while trying to decode media samples. */
+    public static final int ERROR_CODE_DECODING_FAILED = 12;
+
+    /** Caused by trying to decode content whose format is not supported. */
+    public static final int ERROR_CODE_DECODING_FORMAT_UNSUPPORTED = 13;
+
+    /** Caused by an encoder initialization failure. */
+    public static final int ERROR_CODE_ENCODER_INIT_FAILED = 14;
+
+    /** Caused by a failure while trying to encode media samples. */
+    public static final int ERROR_CODE_ENCODING_FAILED = 15;
+
+    /** Caused by trying to encode content whose format is not supported. */
+    public static final int ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED = 16;
+
+    /** Caused by a video frame processing failure. */
+    public static final int ERROR_CODE_VIDEO_FRAME_PROCESSING_FAILED = 17;
+
+    /** Caused by an audio processing failure. */
+    public static final int ERROR_CODE_AUDIO_PROCESSING_FAILED = 18;
+
+    /** Caused by a failure while muxing media samples. */
+    public static final int ERROR_CODE_MUXING_FAILED = 19;
+
+    /** @hide */
+    @IntDef(
+            prefix = {"ERROR_CODE_"},
+            value = {
+                ERROR_CODE_NONE,
+                ERROR_CODE_FAILED_RUNTIME_CHECK,
+                ERROR_CODE_IO_UNSPECIFIED,
+                ERROR_CODE_IO_NETWORK_CONNECTION_FAILED,
+                ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT,
+                ERROR_CODE_IO_BAD_HTTP_STATUS,
+                ERROR_CODE_IO_FILE_NOT_FOUND,
+                ERROR_CODE_IO_NO_PERMISSION,
+                ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED,
+                ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE,
+                ERROR_CODE_DECODER_INIT_FAILED,
+                ERROR_CODE_DECODING_FAILED,
+                ERROR_CODE_DECODING_FORMAT_UNSUPPORTED,
+                ERROR_CODE_ENCODER_INIT_FAILED,
+                ERROR_CODE_ENCODING_FAILED,
+                ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED,
+                ERROR_CODE_VIDEO_FRAME_PROCESSING_FAILED,
+                ERROR_CODE_AUDIO_PROCESSING_FAILED,
+                ERROR_CODE_MUXING_FAILED,
+            })
+    @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    public @interface ErrorCode {}
+
+    private final @ErrorCode int mErrorCode;
+    @SuppressWarnings("HidingField") // Hiding field from superclass as for playback events.
+    private final long mTimeSinceCreatedMillis;
+
+    private EditingEndedEvent(
+            @FinalState int finalState,
+            @ErrorCode int errorCode,
+            long timeSinceCreatedMillis,
+            @NonNull Bundle extras) {
+        mFinalState = finalState;
+        mErrorCode = errorCode;
+        mTimeSinceCreatedMillis = timeSinceCreatedMillis;
+        mMetricsBundle = extras.deepCopy();
+    }
+
+    /** Returns the state of the editing session when it ended. */
+    @FinalState
+    public int getFinalState() {
+        return mFinalState;
+    }
+
+    /** Returns the error code for a {@linkplain #FINAL_STATE_ERROR failed} editing session. */
+    @ErrorCode
+    public int getErrorCode() {
+        return mErrorCode;
+    }
+
+    /**
+     * Gets the elapsed time since creating of the editing session, in milliseconds, or -1 if
+     * unknown.
+     *
+     * @return The elapsed time since creating the editing session, in milliseconds, or -1 if
+     *     unknown.
+     * @see LogSessionId
+     * @see EditingSession
+     */
+    @Override
+    @IntRange(from = -1)
+    public long getTimeSinceCreatedMillis() {
+        return mTimeSinceCreatedMillis;
+    }
+
+    /**
+     * Gets metrics-related information that is not supported by dedicated methods.
+     *
+     * <p>It is intended to be used for backwards compatibility by the metrics infrastructure.
+     */
+    @Override
+    @NonNull
+    public Bundle getMetricsBundle() {
+        return mMetricsBundle;
+    }
+
+    @Override
+    @NonNull
+    public String toString() {
+        return "PlaybackErrorEvent { "
+                + "finalState = "
+                + mFinalState
+                + ", "
+                + "errorCode = "
+                + mErrorCode
+                + ", "
+                + "timeSinceCreatedMillis = "
+                + mTimeSinceCreatedMillis
+                + " }";
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        EditingEndedEvent that = (EditingEndedEvent) o;
+        return mFinalState == that.mFinalState
+                && mErrorCode == that.mErrorCode
+                && mTimeSinceCreatedMillis == that.mTimeSinceCreatedMillis;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mFinalState, mErrorCode, mTimeSinceCreatedMillis);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mFinalState);
+        dest.writeInt(mErrorCode);
+        dest.writeLong(mTimeSinceCreatedMillis);
+        dest.writeBundle(mMetricsBundle);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    private EditingEndedEvent(@NonNull Parcel in) {
+        int finalState = in.readInt();
+        int errorCode = in.readInt();
+        long timeSinceCreatedMillis = in.readLong();
+        Bundle metricsBundle = in.readBundle();
+
+        mFinalState = finalState;
+        mErrorCode = errorCode;
+        mTimeSinceCreatedMillis = timeSinceCreatedMillis;
+        mMetricsBundle = metricsBundle;
+    }
+
+    public static final @NonNull Creator<EditingEndedEvent> CREATOR =
+            new Creator<>() {
+                @Override
+                public EditingEndedEvent[] newArray(int size) {
+                    return new EditingEndedEvent[size];
+                }
+
+                @Override
+                public EditingEndedEvent createFromParcel(@NonNull Parcel in) {
+                    return new EditingEndedEvent(in);
+                }
+            };
+
+    /** Builder for {@link EditingEndedEvent} */
+    @FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
+    public static final class Builder {
+        private final @FinalState int mFinalState;
+        private @ErrorCode int mErrorCode;
+        private long mTimeSinceCreatedMillis;
+        private Bundle mMetricsBundle;
+
+        /**
+         * Creates a new Builder.
+         *
+         * @param finalState The state of the editing session when it ended.
+         */
+        public Builder(@FinalState int finalState) {
+            mFinalState = finalState;
+            mErrorCode = ERROR_CODE_NONE;
+            mTimeSinceCreatedMillis = -1;
+            mMetricsBundle = new Bundle();
+        }
+
+        /**
+         * Sets the elapsed time since creating the editing session, in milliseconds.
+         *
+         * @param timeSinceCreatedMillis The elapsed time since creating the editing session, in
+         *     milliseconds, or -1 if the value is unknown.
+         * @see #getTimeSinceCreatedMillis()
+         */
+        public @NonNull Builder setTimeSinceCreatedMillis(
+                @IntRange(from = -1) long timeSinceCreatedMillis) {
+            mTimeSinceCreatedMillis = timeSinceCreatedMillis;
+            return this;
+        }
+
+        /** Sets the error code for a {@linkplain #FINAL_STATE_ERROR failed} editing session. */
+        public @NonNull Builder setErrorCode(@ErrorCode int value) {
+            mErrorCode = value;
+            return this;
+        }
+
+        /**
+         * Sets metrics-related information that is not supported by dedicated methods.
+         *
+         * <p>Used for backwards compatibility by the metrics infrastructure.
+         */
+        public @NonNull Builder setMetricsBundle(@NonNull Bundle metricsBundle) {
+            mMetricsBundle = metricsBundle;
+            return this;
+        }
+
+        /** Builds an instance. */
+        public @NonNull EditingEndedEvent build() {
+            return new EditingEndedEvent(
+                    mFinalState, mErrorCode, mTimeSinceCreatedMillis, mMetricsBundle);
+        }
+    }
+}
diff --git a/media/java/android/media/metrics/EditingSession.java b/media/java/android/media/metrics/EditingSession.java
index 2ddf623b..964e12c 100644
--- a/media/java/android/media/metrics/EditingSession.java
+++ b/media/java/android/media/metrics/EditingSession.java
@@ -16,6 +16,9 @@
 
 package android.media.metrics;
 
+import static com.android.media.editing.flags.Flags.FLAG_ADD_MEDIA_METRICS_EDITING;
+
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
@@ -24,7 +27,8 @@
 import java.util.Objects;
 
 /**
- * An instances of this class represents a session of media editing.
+ * Represents a session of media editing, for example, transcoding between formats, transmuxing or
+ * applying trimming or audio/video effects to a stream.
  */
 public final class EditingSession implements AutoCloseable {
     private final @NonNull String mId;
@@ -40,6 +44,13 @@
         mLogSessionId = new LogSessionId(mId);
     }
 
+    /** Reports that an editing operation ended. */
+    @FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
+    public void reportEditingEndedEvent(@NonNull EditingEndedEvent editingEndedEvent) {
+        mManager.reportEditingEndedEvent(mId, editingEndedEvent);
+    }
+
+    /** Returns the identifier for logging this session. */
     public @NonNull LogSessionId getSessionId() {
         return mLogSessionId;
     }
diff --git a/media/java/android/media/metrics/IMediaMetricsManager.aidl b/media/java/android/media/metrics/IMediaMetricsManager.aidl
index 51b1cc2..e07ca67 100644
--- a/media/java/android/media/metrics/IMediaMetricsManager.aidl
+++ b/media/java/android/media/metrics/IMediaMetricsManager.aidl
@@ -16,6 +16,7 @@
 
 package android.media.metrics;
 
+import android.media.metrics.EditingEndedEvent;
 import android.media.metrics.NetworkEvent;
 import android.media.metrics.PlaybackErrorEvent;
 import android.media.metrics.PlaybackMetrics;
@@ -24,7 +25,7 @@
 import android.os.PersistableBundle;
 
 /**
- * Interface to the playback manager service.
+ * Interface to the media metrics manager service.
  * @hide
  */
 interface IMediaMetricsManager {
@@ -37,6 +38,8 @@
     void reportPlaybackStateEvent(in String sessionId, in PlaybackStateEvent event, int userId);
     void reportTrackChangeEvent(in String sessionId, in TrackChangeEvent event, int userId);
 
+    void reportEditingEndedEvent(in String sessionId, in EditingEndedEvent event, int userId);
+
     String getTranscodingSessionId(int userId);
     String getEditingSessionId(int userId);
     String getBundleSessionId(int userId);
diff --git a/media/java/android/media/metrics/MediaMetricsManager.java b/media/java/android/media/metrics/MediaMetricsManager.java
index 0898874..622b0c1 100644
--- a/media/java/android/media/metrics/MediaMetricsManager.java
+++ b/media/java/android/media/metrics/MediaMetricsManager.java
@@ -193,4 +193,18 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Reports the event of an editing session ending.
+     *
+     * @hide
+     */
+    public void reportEditingEndedEvent(
+            @NonNull String sessionId, EditingEndedEvent editingEndedEvent) {
+        try {
+            mService.reportEditingEndedEvent(sessionId, editingEndedEvent, mUserId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/media/java/android/media/projection/IMediaProjection.aidl b/media/java/android/media/projection/IMediaProjection.aidl
index 388b2c5..2fb0af5 100644
--- a/media/java/android/media/projection/IMediaProjection.aidl
+++ b/media/java/android/media/projection/IMediaProjection.aidl
@@ -18,6 +18,7 @@
 
 import android.media.projection.IMediaProjectionCallback;
 import android.os.IBinder;
+import android.app.ActivityOptions.LaunchCookie;
 
 /** {@hide} */
 interface IMediaProjection {
@@ -38,22 +39,22 @@
     void unregisterCallback(IMediaProjectionCallback callback);
 
     /**
-     * Returns the {@link android.os.IBinder} identifying the task to record, or {@code null} if
+     * Returns the {@link LaunchCookie} identifying the task to record, or {@code null} if
      * there is none.
      */
     @EnforcePermission("MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
-    IBinder getLaunchCookie();
+    LaunchCookie getLaunchCookie();
 
     /**
-     * Updates the {@link android.os.IBinder} identifying the task to record, or {@code null} if
+     * Updates the {@link LaunchCookie} identifying the task to record, or {@code null} if
      * there is none.
      */
     @EnforcePermission("MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
-    void setLaunchCookie(in IBinder launchCookie);
+    void setLaunchCookie(in LaunchCookie launchCookie);
 
     /**
      * Returns {@code true} if this token is still valid. A token is valid as long as the token
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index a7ec6c6..3d927d3 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -37,31 +37,41 @@
     const String EXTRA_PACKAGE_REUSING_GRANTED_CONSENT =
             "extra_media_projection_package_reusing_consent";
 
+    /**
+     * Returns whether a combination of process UID and package has the projection permission.
+     *
+     * @param processUid the process UID as returned by {@link android.os.Process.myUid()}.
+     */
     @UnsupportedAppUsage
-    boolean hasProjectionPermission(int uid, String packageName);
+    boolean hasProjectionPermission(int processUid, String packageName);
 
     /**
      * Returns a new {@link IMediaProjection} instance associated with the given package.
+     *
+     * @param processUid the process UID as returned by {@link android.os.Process.myUid()}.
      */
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
-    IMediaProjection createProjection(int uid, String packageName, int type,
+    IMediaProjection createProjection(int processUid, String packageName, int type,
             boolean permanentGrant);
 
     /**
      * Returns the current {@link IMediaProjection} instance associated with the given
-     * package, or {@code null} if it is not possible to re-use the current projection.
+     * package and process UID, or {@code null} if it is not possible to re-use the current
+     * projection.
      *
      * <p>Should only be invoked when the user has reviewed consent for a re-used projection token.
      * Requires that there is a prior session waiting for the user to review consent, and the given
      * package details match those on the current projection.
      *
      * @see {@link #isCurrentProjection}
+     *
+     * @param processUid the process UID as returned by {@link android.os.Process.myUid()}.
      */
     @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
-    IMediaProjection getProjection(int uid, String packageName);
+    IMediaProjection getProjection(int processUid, String packageName);
 
     /**
      * Returns {@code true} if the given {@link IMediaProjection} corresponds to the current
@@ -111,7 +121,7 @@
     @EnforcePermission("MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
                 + ".permission.MANAGE_MEDIA_PROJECTION)")
-    void addCallback(IMediaProjectionWatcherCallback callback);
+    MediaProjectionInfo addCallback(IMediaProjectionWatcherCallback callback);
 
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
@@ -162,8 +172,8 @@
      *
      * <p>Only used for emitting atoms.
      *
-     * @param hostUid               The uid of the process requesting consent to capture, may be an app or
-     *                              SystemUI.
+     * @param hostProcessUid        The uid of the process requesting consent to capture, may be an
+     *                              app or SystemUI.
      * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED.
      *                              Indicates the entry point for requesting the permission. Must be
      *                              a valid state defined
@@ -172,49 +182,49 @@
     @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
-    oneway void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource);
+    oneway void notifyPermissionRequestInitiated(int hostProcessUid, int sessionCreationSource);
 
     /**
      * Notifies system server that the permission request was displayed.
      *
      * <p>Only used for emitting atoms.
      *
-     * @param hostUid The uid of the process requesting consent to capture, may be an app or
-     *                SystemUI.
+     * @param hostProcessUid The uid of the process requesting consent to capture, may be an app or
+     *                       SystemUI.
      */
     @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
-    oneway void notifyPermissionRequestDisplayed(int hostUid);
+    oneway void notifyPermissionRequestDisplayed(int hostProcessUid);
 
     /**
      * Notifies system server that the permission request was cancelled.
      *
      * <p>Only used for emitting atoms.
      *
-     * @param hostUid The uid of the process requesting consent to capture, may be an app or
-     *                SystemUI.
+     * @param hostProcessUid The uid of the process requesting consent to capture, may be an app or
+     *                       SystemUI.
      */
     @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
-    oneway void notifyPermissionRequestCancelled(int hostUid);
+    oneway void notifyPermissionRequestCancelled(int hostProcessUid);
 
     /**
      * Notifies system server that the app selector was displayed.
      *
      * <p>Only used for emitting atoms.
      *
-     * @param hostUid The uid of the process requesting consent to capture, may be an app or
-     *                SystemUI.
+     * @param hostProcessUid The uid of the process requesting consent to capture, may be an app or
+     *                       SystemUI.
      */
     @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
-    oneway void notifyAppSelectorDisplayed(int hostUid);
+    oneway void notifyAppSelectorDisplayed(int hostProcessUid);
 
     @EnforcePermission("MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
-    void notifyWindowingModeChanged(int contentToRecord, int targetUid, int windowingMode);
+    void notifyWindowingModeChanged(int contentToRecord, int targetProcessUid, int windowingMode);
 }
diff --git a/media/java/android/media/projection/MediaProjectionInfo.java b/media/java/android/media/projection/MediaProjectionInfo.java
index ff60856..cd0763d 100644
--- a/media/java/android/media/projection/MediaProjectionInfo.java
+++ b/media/java/android/media/projection/MediaProjectionInfo.java
@@ -16,6 +16,7 @@
 
 package android.media.projection;
 
+import android.app.ActivityOptions.LaunchCookie;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
@@ -26,15 +27,18 @@
 public final class MediaProjectionInfo implements Parcelable {
     private final String mPackageName;
     private final UserHandle mUserHandle;
+    private final LaunchCookie mLaunchCookie;
 
-    public MediaProjectionInfo(String packageName, UserHandle handle) {
+    public MediaProjectionInfo(String packageName, UserHandle handle, LaunchCookie launchCookie) {
         mPackageName = packageName;
         mUserHandle = handle;
+        mLaunchCookie = launchCookie;
     }
 
     public MediaProjectionInfo(Parcel in) {
         mPackageName = in.readString();
         mUserHandle = UserHandle.readFromParcel(in);
+        mLaunchCookie = LaunchCookie.readFromParcel(in);
     }
 
     public String getPackageName() {
@@ -45,12 +49,16 @@
         return mUserHandle;
     }
 
+    public LaunchCookie getLaunchCookie() {
+        return mLaunchCookie;
+    }
+
     @Override
     public boolean equals(Object o) {
-        if (o instanceof MediaProjectionInfo) {
-            final MediaProjectionInfo other = (MediaProjectionInfo) o;
+        if (o instanceof MediaProjectionInfo other) {
             return Objects.equals(other.mPackageName, mPackageName)
-                    && Objects.equals(other.mUserHandle, mUserHandle);
+                    && Objects.equals(other.mUserHandle, mUserHandle)
+                    && Objects.equals(other.mLaunchCookie, mLaunchCookie);
         }
         return false;
     }
@@ -64,7 +72,8 @@
     public String toString() {
         return "MediaProjectionInfo{mPackageName="
             + mPackageName + ", mUserHandle="
-            + mUserHandle + "}";
+            + mUserHandle + ", mLaunchCookie="
+            + mLaunchCookie + "}";
     }
 
     @Override
@@ -76,6 +85,7 @@
     public void writeToParcel(Parcel out, int flags) {
         out.writeString(mPackageName);
         UserHandle.writeToParcel(mUserHandle, out);
+        LaunchCookie.writeToParcel(mLaunchCookie, out);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<MediaProjectionInfo> CREATOR =
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 9790d02..e3290d6 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -18,8 +18,11 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.app.Activity;
+import android.app.ActivityOptions.LaunchCookie;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -73,6 +76,9 @@
     /** @hide */
     public static final String EXTRA_MEDIA_PROJECTION =
             "android.media.projection.extra.EXTRA_MEDIA_PROJECTION";
+    /** @hide */
+    public static final String EXTRA_LAUNCH_COOKIE =
+            "android.media.projection.extra.EXTRA_LAUNCH_COOKIE";
 
     /** @hide */
     public static final int TYPE_SCREEN_CAPTURE = 0;
@@ -158,17 +164,29 @@
      */
     @NonNull
     public Intent createScreenCaptureIntent(@NonNull MediaProjectionConfig config) {
-        Intent i = new Intent();
-        final ComponentName mediaProjectionPermissionDialogComponent =
-                ComponentName.unflattenFromString(mContext.getResources()
-                        .getString(com.android.internal.R.string
-                                .config_mediaProjectionPermissionDialogComponent));
-        i.setComponent(mediaProjectionPermissionDialogComponent);
+        Intent i = createScreenCaptureIntent();
         i.putExtra(EXTRA_MEDIA_PROJECTION_CONFIG, config);
         return i;
     }
 
     /**
+     * Returns an intent similar to {@link #createScreenCaptureIntent()} that will enable screen
+     * recording of the task with the specified launch cookie. This method should only be used for
+     * testing.
+     *
+     * @param launchCookie the launch cookie corresponding to the task to record.
+     * @hide
+     */
+    @SuppressLint("UnflaggedApi")
+    @TestApi
+    @NonNull
+    public Intent createScreenCaptureIntent(@Nullable LaunchCookie launchCookie) {
+        Intent i = createScreenCaptureIntent();
+        i.putExtra(EXTRA_LAUNCH_COOKIE, launchCookie);
+        return i;
+    }
+
+    /**
      * Retrieves the {@link MediaProjection} obtained from a successful screen
      * capture request. The result code and data from the request are provided by overriding
      * {@link Activity#onActivityResult(int, int, Intent) onActivityResult(int, int, Intent)},
diff --git a/media/java/android/media/tv/BroadcastInfoRequest.java b/media/java/android/media/tv/BroadcastInfoRequest.java
index cbd8c1f..694756c 100644
--- a/media/java/android/media/tv/BroadcastInfoRequest.java
+++ b/media/java/android/media/tv/BroadcastInfoRequest.java
@@ -32,7 +32,8 @@
 public abstract class BroadcastInfoRequest implements Parcelable {
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({REQUEST_OPTION_REPEAT, REQUEST_OPTION_AUTO_UPDATE})
+    @IntDef({REQUEST_OPTION_REPEAT, REQUEST_OPTION_AUTO_UPDATE,
+            REQUEST_OPTION_ONEWAY, REQUEST_OPTION_ONESHOT})
     public @interface RequestOption {}
 
     /**
@@ -47,6 +48,18 @@
      * first time, new values are detected.
      */
     public static final int REQUEST_OPTION_AUTO_UPDATE = 1;
+    /**
+     * Request option: one-way
+     * <p> With this option, no response is expected after sending the request.
+     * @hide
+     */
+    public static final int REQUEST_OPTION_ONEWAY = 2;
+    /**
+     * Request option: one-shot
+     * <p> With this option, only one response will be given per request.
+     * @hide
+     */
+    public static final int REQUEST_OPTION_ONESHOT = 3;
 
     public static final @NonNull Parcelable.Creator<BroadcastInfoRequest> CREATOR =
             new Parcelable.Creator<BroadcastInfoRequest>() {
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index 0f8a00a..8978277 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -44,6 +44,7 @@
     void onTrackSelected(int type, in String trackId, int seq);
     void onVideoAvailable(int seq);
     void onVideoUnavailable(int reason, int seq);
+    void onVideoFreezeUpdated(boolean isFrozen, int seq);
     void onContentAllowed(int seq);
     void onContentBlocked(in String rating, int seq);
     void onLayoutSurface(int left, int top, int right, int bottom, int seq);
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index a8ffd2b..2f6575e 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -149,4 +149,7 @@
     // For CTS purpose only. Add/remove a TvInputHardware device
     void addHardwareDevice(in int deviceId);
     void removeHardwareDevice(in int deviceId);
+
+    // For freezing video playback
+    void setVideoFrozen(in IBinder sessionToken, boolean isFrozen, int userId);
 }
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index e37ee6e..a93f18d 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -83,4 +83,7 @@
     // For TV messages
     void notifyTvMessage(int type, in Bundle data);
     void setTvMessageEnabled(int type, boolean enabled);
+
+    // For freezing video
+    void setVideoFrozen(boolean isFrozen);
 }
diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl
index a52e9a5..8e2702a 100644
--- a/media/java/android/media/tv/ITvInputSessionCallback.aidl
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -41,6 +41,7 @@
     void onTrackSelected(int type, in String trackId);
     void onVideoAvailable();
     void onVideoUnavailable(int reason);
+    void onVideoFreezeUpdated(boolean isFrozen);
     void onContentAllowed();
     void onContentBlocked(in String rating);
     void onLayoutSurface(int left, int top, int right, int bottom);
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index ae3ee65..921104d 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -81,6 +81,7 @@
     private static final int DO_NOTIFY_TV_MESSAGE = 32;
     private static final int DO_STOP_PLAYBACK = 33;
     private static final int DO_START_PLAYBACK = 34;
+    private static final int DO_SET_VIDEO_FROZEN = 35;
 
     private final boolean mIsRecordingSession;
     private final HandlerCaller mCaller;
@@ -296,6 +297,10 @@
                 mTvInputSessionImpl.startPlayback();
                 break;
             }
+            case DO_SET_VIDEO_FROZEN: {
+                mTvInputSessionImpl.setVideoFrozen((Boolean) msg.obj);
+                break;
+            }
             default: {
                 Log.w(TAG, "Unhandled message code: " + msg.what);
                 break;
@@ -483,6 +488,11 @@
     }
 
     @Override
+    public void setVideoFrozen(boolean isFrozen) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_VIDEO_FROZEN, isFrozen));
+    }
+
+    @Override
     public void notifyTvMessage(int type, Bundle data) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_NOTIFY_TV_MESSAGE, type, data));
     }
diff --git a/media/java/android/media/tv/SignalingDataRequest.aidl b/media/java/android/media/tv/SignalingDataRequest.aidl
new file mode 100644
index 0000000..29e89fe
--- /dev/null
+++ b/media/java/android/media/tv/SignalingDataRequest.aidl
@@ -0,0 +1,3 @@
+package android.media.tv;
+
+parcelable SignalingDataRequest;
diff --git a/media/java/android/media/tv/SignalingDataRequest.java b/media/java/android/media/tv/SignalingDataRequest.java
new file mode 100644
index 0000000..dcf1d48
--- /dev/null
+++ b/media/java/android/media/tv/SignalingDataRequest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcelable;
+
+/**
+ * Request to retrieve the Low-level Signalling Tables (LLS) and Service-layer Signalling (SLS)
+ * metadata.
+ *
+ * <p>For more details on each type of metadata that can be requested, refer to the ATSC standard
+ * A/344:2023-5 9.2.10 - Query Signaling Data API.
+ *
+ * @hide
+ */
+public class SignalingDataRequest extends BroadcastInfoRequest implements Parcelable {
+    private static final @TvInputManager.BroadcastInfoType int REQUEST_TYPE =
+            TvInputManager.BROADCAST_INFO_TYPE_SIGNALING_DATA;
+
+    public static final @NonNull Parcelable.Creator<SignalingDataRequest> CREATOR =
+            new Parcelable.Creator<SignalingDataRequest>() {
+                @Override
+                public SignalingDataRequest[] newArray(int size) {
+                    return new SignalingDataRequest[size];
+                }
+
+                @Override
+                public SignalingDataRequest createFromParcel(@NonNull android.os.Parcel in) {
+                    return new SignalingDataRequest(in);
+                }
+            };
+
+    /** SLS Metadata: All metadata objects for the requested service(s) */
+    public static final int SLS_METADATA_ALL = 0x7FFFFFF;
+
+    /** SLS Metadata: APD for the requested service(s) */
+    public static final int SLS_METADATA_APD = 1;
+
+    /** SLS Metadata: USBD for the requested service(s) */
+    public static final int SLS_METADATA_USBD = 1 << 1;
+
+    /** SLS Metadata: S-TSID for the requested service(s) */
+    public static final int SLS_METADATA_STSID = 1 << 2;
+
+    /** SLS Metadata: DASH MPD for the requested service(s) */
+    public static final int SLS_METADATA_MPD = 1 << 3;
+
+    /** SLS Metadata: User Service Description for MMTP */
+    public static final int SLS_METADATA_USD = 1 << 4;
+
+    /** SLS Metadata: MMT Package Access Table for the requested service(s) */
+    public static final int SLS_METADATA_PAT = 1 << 5;
+
+    /** SLS Metadata: MMT Package Table for the requested service(s) */
+    public static final int SLS_METADATA_MPT = 1 << 6;
+
+    /** SLS Metadata: MMT Media Presentation Information Table for the requested service(s) */
+    public static final int SLS_METADATA_MPIT = 1 << 7;
+
+    /** SLS Metadata: MMT Clock Relation Information for the requested service(s) */
+    public static final int SLS_METADATA_CRIT = 1 << 8;
+
+    /** SLS Metadata: MMT Device Capabilities Information Table for the requested service(s) */
+    public static final int SLS_METADATA_DCIT = 1 << 9;
+
+    /** SLS Metadata: HTML Entry Pages Location Description for the requested service(s) */
+    public static final int SLS_METADATA_HELD = 1 << 10;
+
+    /** SLS Metadata: Distribution Window Desciription for the requested service(s) */
+    public static final int SLS_METADATA_DWD = 1 << 11;
+
+    /** SLS Metadata: MMT Application Event Information for the requested service(s) */
+    public static final int SLS_METADATA_AEI = 1 << 12;
+
+    /** SLS Metadata: Video Stream Properties Descriptor */
+    public static final int SLS_METADATA_VSPD = 1 << 13;
+
+    /** SLS Metadata: ATSC Staggercast Descriptor */
+    public static final int SLS_METADATA_ASD = 1 << 14;
+
+    /** SLS Metadata: Inband Event Descriptor */
+    public static final int SLS_METADATA_IED = 1 << 15;
+
+    /** SLS Metadata: Caption Asset Descriptor */
+    public static final int SLS_METADATA_CAD = 1 << 16;
+
+    /** SLS Metadata: Audio Stream Properties Descriptor */
+    public static final int SLS_METADATA_ASPD = 1 << 17;
+
+    /** SLS Metadata: Security Properties Descriptor */
+    public static final int SLS_METADATA_SSD = 1 << 18;
+
+    /** SLS Metadata: ROUTE/DASH Application Dynamic Event for the requested service(s) */
+    public static final int SLS_METADATA_EMSG = 1 << 19;
+
+    /** SLS Metadata: MMT Application Dynamic Event for the requested service(s) */
+    public static final int SLS_METADATA_EVTI = 1 << 20;
+
+    /** Regional Service Availability Table for the requested service(s) */
+    public static final int SLS_METADATA_RSAT = 1 << 21;
+
+    private final int mGroup;
+    private @NonNull final int[] mLlsTableIds;
+    private final int mSlsMetadataTypes;
+
+    SignalingDataRequest(
+            int requestId,
+            int option,
+            int group,
+            @NonNull int[] llsTableIds,
+            int slsMetadataTypes) {
+        super(REQUEST_TYPE, requestId, option);
+        mGroup = group;
+        mLlsTableIds = llsTableIds;
+        mSlsMetadataTypes = slsMetadataTypes;
+    }
+
+    SignalingDataRequest(@NonNull android.os.Parcel in) {
+        super(REQUEST_TYPE, in);
+
+        int group = in.readInt();
+        int[] llsTableIds = in.createIntArray();
+        int slsMetadataTypes = in.readInt();
+
+        this.mGroup = group;
+        this.mLlsTableIds = llsTableIds;
+        com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mLlsTableIds);
+        this.mSlsMetadataTypes = slsMetadataTypes;
+    }
+
+    public int getGroup() {
+        return mGroup;
+    }
+
+    public @NonNull int[] getLlsTableIds() {
+        return mLlsTableIds;
+    }
+
+    public int getSlsMetadataTypes() {
+        return mSlsMetadataTypes;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(mGroup);
+        dest.writeIntArray(mLlsTableIds);
+        dest.writeInt(mSlsMetadataTypes);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index db01950..7f8f1a3 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -16,6 +16,7 @@
 
 package android.media.tv;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -29,6 +30,7 @@
 import android.content.ContentUris;
 import android.content.Context;
 import android.content.Intent;
+import android.media.tv.flags.Flags;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -2540,9 +2542,9 @@
          * <p>This is used to indicate the broadcast visibility type defined in the underlying
          * broadcast standard or country/operator profile, if applicable. For example,
          * {@code visible_service_flag} and {@code numeric_selection_flag} of
-         * {@code service_attribute_descriptor} in D-Book, {@code visible_service_flag} and
-         * {@code selectable_service_flag} of {@code ciplus_service_descriptor} in CI Plus 1.3
-         * specification.
+         * {@code service_attribute_descriptor} in D-Book, the specification for UK-based TV
+         * products, {@code visible_service_flag} and {@code selectable_service_flag} of
+         * {@code ciplus_service_descriptor} in the CI Plus 1.3 specification.
          *
          * <p>The value should match one of the following:
          * {@link #BROADCAST_VISIBILITY_TYPE_VISIBLE},
@@ -2553,8 +2555,8 @@
          * by default.
          *
          * <p>Type: INTEGER
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_BROADCAST_VISIBILITY_TYPES)
         public static final String COLUMN_BROADCAST_VISIBILITY_TYPE = "broadcast_visibility_type";
 
         /** @hide */
@@ -2571,8 +2573,8 @@
          * visible from users and selectable by users via normal service navigation mechanisms.
          *
          * @see #COLUMN_BROADCAST_VISIBILITY_TYPE
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_BROADCAST_VISIBILITY_TYPES)
         public static final int BROADCAST_VISIBILITY_TYPE_VISIBLE = 0;
 
         /**
@@ -2581,18 +2583,18 @@
          * the logical channel number.
          *
          * @see #COLUMN_BROADCAST_VISIBILITY_TYPE
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_BROADCAST_VISIBILITY_TYPES)
         public static final int BROADCAST_VISIBILITY_TYPE_NUMERIC_SELECTABLE_ONLY = 1;
 
         /**
          * The broadcast visibility type for invisible services. Use this type when the service
-         * is invisible from users and unselectable by users via any of normal service navigation
-         * mechanisms.
+         * is invisible from users and not able to be selected by users via any of the normal
+         * service navigation mechanisms.
          *
          * @see #COLUMN_BROADCAST_VISIBILITY_TYPE
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_BROADCAST_VISIBILITY_TYPES)
         public static final int BROADCAST_VISIBILITY_TYPE_INVISIBLE = 2;
 
         private Channels() {}
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index c685a5a..be1b675 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -33,6 +33,7 @@
 import android.media.AudioFormat.Encoding;
 import android.media.AudioPresentation;
 import android.media.PlaybackParams;
+import android.media.tv.ad.TvAdManager;
 import android.media.tv.interactive.TvInteractiveAppManager;
 import android.net.Uri;
 import android.os.Binder;
@@ -488,10 +489,19 @@
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = "BROADCAST_INFO_TYPE_", value =
-            {BROADCAST_INFO_TYPE_TS, BROADCAST_INFO_TYPE_TABLE, BROADCAST_INFO_TYPE_SECTION,
-            BROADCAST_INFO_TYPE_PES, BROADCAST_INFO_STREAM_EVENT, BROADCAST_INFO_TYPE_DSMCC,
-            BROADCAST_INFO_TYPE_COMMAND, BROADCAST_INFO_TYPE_TIMELINE})
+    @IntDef(
+            prefix = "BROADCAST_INFO_TYPE_",
+            value = {
+                BROADCAST_INFO_TYPE_TS,
+                BROADCAST_INFO_TYPE_TABLE,
+                BROADCAST_INFO_TYPE_SECTION,
+                BROADCAST_INFO_TYPE_PES,
+                BROADCAST_INFO_STREAM_EVENT,
+                BROADCAST_INFO_TYPE_DSMCC,
+                BROADCAST_INFO_TYPE_COMMAND,
+                BROADCAST_INFO_TYPE_TIMELINE,
+                BROADCAST_INFO_TYPE_SIGNALING_DATA
+            })
     public @interface BroadcastInfoType {}
 
     public static final int BROADCAST_INFO_TYPE_TS = 1;
@@ -504,6 +514,9 @@
     public static final int BROADCAST_INFO_TYPE_TIMELINE = 8;
 
     /** @hide */
+    public static final int BROADCAST_INFO_TYPE_SIGNALING_DATA = 9;
+
+    /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "SIGNAL_STRENGTH_",
             value = {SIGNAL_STRENGTH_LOST, SIGNAL_STRENGTH_WEAK, SIGNAL_STRENGTH_STRONG})
@@ -740,6 +753,15 @@
         }
 
         /**
+         * This is called when the video freeze state has been updated.
+         * If {@code true}, the video is frozen on the last frame while audio playback continues.
+         * @param session A {@link TvInputManager.Session} associated with this callback.
+         * @param isFrozen Whether the video is frozen
+         */
+        public void onVideoFreezeUpdated(Session session, boolean isFrozen) {
+        }
+
+        /**
          * This is called when the current program content turns out to be allowed to watch since
          * its content rating is not blocked by parental controls.
          *
@@ -1030,6 +1052,19 @@
             });
         }
 
+        void postVideoFreezeUpdated(boolean isFrozen) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onVideoFreezeUpdated(mSession, isFrozen);
+                    if (mSession.mIAppNotificationEnabled
+                            && mSession.getInteractiveAppSession() != null) {
+                        mSession.getInteractiveAppSession().notifyVideoFreezeUpdated(isFrozen);
+                    }
+                }
+            });
+        }
+
         void postContentAllowed() {
             mHandler.post(new Runnable() {
                 @Override
@@ -1546,6 +1581,18 @@
             }
 
             @Override
+            public void onVideoFreezeUpdated(boolean isFrozen, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postVideoFreezeUpdated(isFrozen);
+                }
+            }
+
+            @Override
             public void onContentAllowed(int seq) {
                 synchronized (mSessionCallbackRecordMap) {
                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
@@ -2710,6 +2757,7 @@
         private int mVideoHeight;
 
         private TvInteractiveAppManager.Session mIAppSession;
+        private TvAdManager.Session mAdSession;
         private boolean mIAppNotificationEnabled = false;
 
         private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId,
@@ -2730,6 +2778,14 @@
             this.mIAppSession = iAppSession;
         }
 
+        public TvAdManager.Session getAdSession() {
+            return mAdSession;
+        }
+
+        public void setAdSession(TvAdManager.Session adSession) {
+            this.mAdSession = adSession;
+        }
+
         /**
          * Releases this session.
          */
@@ -3334,6 +3390,18 @@
             }
         }
 
+        void setVideoFrozen(boolean isFrozen) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.setVideoFrozen(mToken, isFrozen, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         /**
          * Sends TV messages to the service for testing purposes
          */
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 55fa517..6301a27 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -763,6 +763,34 @@
         }
 
         /**
+         * Informs the application that the video freeze state has been updated.
+         *
+         * When {@code true}, the video is frozen on the last frame but audio playback remains
+         * active.
+         *
+         * @param isFrozen Whether or not the video is frozen
+         * @hide
+         */
+        public void notifyVideoFreezeUpdated(boolean isFrozen) {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) {
+                            Log.d(TAG, "notifyVideoFreezeUpdated");
+                        }
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onVideoFreezeUpdated(isFrozen);
+                        }
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "error in notifyVideoFreezeUpdated", e);
+                    }
+                }
+            });
+        }
+
+        /**
          * Sends an updated list of all audio presentations available from a Next Generation Audio
          * service. This is used by the framework to maintain the audio presentation information for
          * a given track of {@link TvTrackInfo#TYPE_AUDIO}, which in turn is used by
@@ -1558,6 +1586,17 @@
         }
 
         /**
+         * Called when a request to freeze the video is received from the TV app. The audio should
+         * continue playback while the video is frozen.
+         *
+         * <p> This should freeze the video to the last frame when the state is set to {@code true}.
+         * @param isFrozen whether or not the video should be frozen.
+         * @hide
+         */
+        public void onSetVideoFrozen(boolean isFrozen) {
+        }
+
+        /**
          * Called when the application requests to play a given recorded TV program.
          *
          * @param recordedProgramUri The URI of a recorded TV program.
@@ -2034,6 +2073,13 @@
         }
 
         /**
+         * Calls {@link #onSetVideoFrozen(boolean)}.
+         */
+        void setVideoFrozen(boolean isFrozen) {
+            onSetVideoFrozen(isFrozen);
+        }
+
+        /**
          * Calls {@link #onTimeShiftPlay(Uri)}.
          */
         void timeShiftPlay(Uri recordedProgramUri) {
diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java
index 78d7d76..2ebb19a 100644
--- a/media/java/android/media/tv/TvTrackInfo.java
+++ b/media/java/android/media/tv/TvTrackInfo.java
@@ -55,6 +55,27 @@
      */
     public static final int TYPE_SUBTITLE = 2;
 
+    /**
+     * The component tag identifies a component carried by a MPEG-2 TS.
+     *
+     * This corresponds to the component_tag in the component descriptor in the
+     * Elementary Stream loop of the stream in the Program Map Table
+     * (PMT) [EN 300 468], or undefined if the component is not carried in an
+     * MPEG-2 TS.
+     *
+     * @hide
+     */
+    public static final String EXTRA_BUNDLE_KEY_COMPONENT_TAG = "component_tag";
+
+    /**
+     * The MPEG Program ID (PID) of the component in the MPEG2-TS in
+     * which it is carried, or undefined if the component is not carried in an
+     * MPEG-2 TS.
+     *
+     * @hide
+     */
+    public static final String EXTRA_BUNDLE_KEY_PID = "pid";
+
     private final int mType;
     private final String mId;
     private final String mLanguage;
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 233f966..cb45661 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -675,6 +675,23 @@
     }
 
     /**
+     * Sets whether or not the video is frozen. While the video is frozen, audio playback will
+     * continue.
+     *
+     * <p> This should be invoked after a {@link TvInteractiveAppService.Session#requestCommand} is
+     * received with the command to freeze the video.
+     *
+     * <p> This will freeze the video to the last frame when the state is set to {@code true}.
+     * @param isFrozen whether or not the video is frozen.
+     * @hide
+     */
+    public void setVideoFrozen(boolean isFrozen) {
+        if (mSession != null) {
+            mSession.setVideoFrozen(isFrozen);
+        }
+    }
+
+    /**
      * Sends TV messages to the session for testing purposes
      *
      * @hide
diff --git a/media/java/android/media/tv/ad/ITvAdClient.aidl b/media/java/android/media/tv/ad/ITvAdClient.aidl
new file mode 100644
index 0000000..34d96b3
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdClient.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+import android.view.InputChannel;
+
+/**
+ * Interface a client of the ITvAdManager implements, to identify itself and receive
+ * information about changes to the state of each TV AD service.
+ * @hide
+ */
+oneway interface ITvAdClient {
+    void onSessionCreated(in String serviceId, IBinder token, in InputChannel channel, int seq);
+    void onSessionReleased(int seq);
+    void onLayoutSurface(int left, int top, int right, int bottom, int seq);
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdManager.aidl b/media/java/android/media/tv/ad/ITvAdManager.aidl
index 92cc923..9620065 100644
--- a/media/java/android/media/tv/ad/ITvAdManager.aidl
+++ b/media/java/android/media/tv/ad/ITvAdManager.aidl
@@ -16,10 +16,46 @@
 
 package android.media.tv.ad;
 
+import android.graphics.Rect;
+import android.media.tv.TvTrackInfo;
+import android.media.tv.ad.ITvAdClient;
+import android.media.tv.ad.ITvAdManagerCallback;
+import android.media.tv.ad.TvAdServiceInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.Surface;
+
 /**
  * Interface to the TV AD service.
  * @hide
  */
 interface ITvAdManager {
+    List<TvAdServiceInfo> getTvAdServiceList(int userId);
+    void createSession(
+            in ITvAdClient client, in String serviceId, in String type, int seq, int userId);
+    void releaseSession(in IBinder sessionToken, int userId);
     void startAdService(in IBinder sessionToken, int userId);
+    void stopAdService(in IBinder sessionToken, int userId);
+    void resetAdService(in IBinder sessionToken, int userId);
+    void setSurface(in IBinder sessionToken, in Surface surface, int userId);
+    void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
+            int userId);
+
+    void sendCurrentVideoBounds(in IBinder sessionToken, in Rect bounds, int userId);
+    void sendCurrentChannelUri(in IBinder sessionToken, in Uri channelUri, int userId);
+    void sendTrackInfoList(in IBinder sessionToken, in List<TvTrackInfo> tracks, int userId);
+    void sendCurrentTvInputId(in IBinder sessionToken, in String inputId, int userId);
+    void sendSigningResult(in IBinder sessionToken, in String signingId, in byte[] result,
+            int userId);
+
+    void notifyError(in IBinder sessionToken, in String errMsg, in Bundle params, int userId);
+    void notifyTvMessage(in IBinder sessionToken, in int type, in Bundle data, int userId);
+
+    void registerCallback(in ITvAdManagerCallback callback, int userId);
+    void unregisterCallback(in ITvAdManagerCallback callback, int userId);
+
+    void createMediaView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
+            int userId);
+    void relayoutMediaView(in IBinder sessionToken, in Rect frame, int userId);
+    void removeMediaView(in IBinder sessionToken, int userId);
 }
diff --git a/media/java/android/media/tv/ad/ITvAdManagerCallback.aidl b/media/java/android/media/tv/ad/ITvAdManagerCallback.aidl
new file mode 100644
index 0000000..f55f67e
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdManagerCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+/**
+ * Interface to receive callbacks from ITvAdManager regardless of sessions.
+ * @hide
+ */
+oneway interface ITvAdManagerCallback {
+    void onAdServiceAdded(in String serviceId);
+    void onAdServiceRemoved(in String serviceId);
+    void onAdServiceUpdated(in String serviceId);
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdService.aidl b/media/java/android/media/tv/ad/ITvAdService.aidl
new file mode 100644
index 0000000..3bb0409
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdService.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+import android.media.tv.ad.ITvAdServiceCallback;
+import android.media.tv.ad.ITvAdSessionCallback;
+import android.os.Bundle;
+import android.view.InputChannel;
+
+/**
+ * Top-level interface to a TV AD component (implemented in a Service). It's used for
+ * TvAdManagerService to communicate with TvAdService.
+ * @hide
+ */
+oneway interface ITvAdService {
+    void registerCallback(in ITvAdServiceCallback callback);
+    void unregisterCallback(in ITvAdServiceCallback callback);
+    void createSession(in InputChannel channel, in ITvAdSessionCallback callback,
+            in String serviceId, in String type);
+    void sendAppLinkCommand(in Bundle command);
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdServiceCallback.aidl b/media/java/android/media/tv/ad/ITvAdServiceCallback.aidl
new file mode 100644
index 0000000..a087181
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdServiceCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+/**
+ * Helper interface for ITvAdService to allow the TvAdService to notify the TvAdManagerService.
+ * @hide
+ */
+oneway interface ITvAdServiceCallback {
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdSession.aidl b/media/java/android/media/tv/ad/ITvAdSession.aidl
index b834f1b9..69afb17 100644
--- a/media/java/android/media/tv/ad/ITvAdSession.aidl
+++ b/media/java/android/media/tv/ad/ITvAdSession.aidl
@@ -16,10 +16,34 @@
 
 package android.media.tv.ad;
 
+import android.graphics.Rect;
+import android.media.tv.TvTrackInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.Surface;
+
 /**
- * Sub-interface of ITvAdService which is created per session and has its own context.
+ * Sub-interface of ITvAdService.aidl which is created per session and has its own context.
  * @hide
  */
 oneway interface ITvAdSession {
+    void release();
     void startAdService();
+    void stopAdService();
+    void resetAdService();
+    void setSurface(in Surface surface);
+    void dispatchSurfaceChanged(int format, int width, int height);
+
+    void sendCurrentVideoBounds(in Rect bounds);
+    void sendCurrentChannelUri(in Uri channelUri);
+    void sendTrackInfoList(in List<TvTrackInfo> tracks);
+    void sendCurrentTvInputId(in String inputId);
+    void sendSigningResult(in String signingId, in byte[] result);
+
+    void notifyError(in String errMsg, in Bundle params);
+    void notifyTvMessage(int type, in Bundle data);
+
+    void createMediaView(in IBinder windowToken, in Rect frame);
+    void relayoutMediaView(in Rect frame);
+    void removeMediaView();
 }
diff --git a/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl b/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl
new file mode 100644
index 0000000..f21ef19
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+import android.media.tv.ad.ITvAdSession;
+
+/**
+ * Helper interface for ITvAdSession to allow TvAdService to notify the system service when there is
+ * a related event.
+ * @hide
+ */
+oneway interface ITvAdSessionCallback {
+    void onSessionCreated(in ITvAdSession session);
+    void onLayoutSurface(int left, int top, int right, int bottom);
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdSessionWrapper.java b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
new file mode 100644
index 0000000..251351d
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Rect;
+import android.media.tv.TvTrackInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.Surface;
+
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+
+import java.util.List;
+
+/**
+ * Implements the internal ITvAdSession interface.
+ * @hide
+ */
+public class ITvAdSessionWrapper
+        extends ITvAdSession.Stub implements HandlerCaller.Callback {
+
+    private static final String TAG = "ITvAdSessionWrapper";
+
+    private static final int EXECUTE_MESSAGE_TIMEOUT_SHORT_MILLIS = 1000;
+    private static final int EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS = 5 * 1000;
+    private static final int DO_RELEASE = 1;
+    private static final int DO_SET_SURFACE = 2;
+    private static final int DO_DISPATCH_SURFACE_CHANGED = 3;
+    private static final int DO_CREATE_MEDIA_VIEW = 4;
+    private static final int DO_RELAYOUT_MEDIA_VIEW = 5;
+    private static final int DO_REMOVE_MEDIA_VIEW = 6;
+    private static final int DO_START_AD_SERVICE = 7;
+    private static final int DO_STOP_AD_SERVICE = 8;
+    private static final int DO_RESET_AD_SERVICE = 9;
+    private static final int DO_SEND_CURRENT_VIDEO_BOUNDS = 10;
+    private static final int DO_SEND_CURRENT_CHANNEL_URI = 11;
+    private static final int DO_SEND_TRACK_INFO_LIST = 12;
+    private static final int DO_SEND_CURRENT_TV_INPUT_ID = 13;
+    private static final int DO_SEND_SIGNING_RESULT = 14;
+    private static final int DO_NOTIFY_ERROR = 15;
+    private static final int DO_NOTIFY_TV_MESSAGE = 16;
+
+    private final HandlerCaller mCaller;
+    private TvAdService.Session mSessionImpl;
+    private InputChannel mChannel;
+    private TvAdEventReceiver mReceiver;
+
+    public ITvAdSessionWrapper(
+            Context context, TvAdService.Session mSessionImpl, InputChannel channel) {
+        this.mSessionImpl = mSessionImpl;
+        mCaller = new HandlerCaller(context, null, this, true /* asyncHandler */);
+        mChannel = channel;
+        if (channel != null) {
+            mReceiver = new TvAdEventReceiver(channel, context.getMainLooper());
+        }
+    }
+
+    @Override
+    public void release() {
+        mSessionImpl.scheduleMediaViewCleanup();
+        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
+    }
+
+
+    @Override
+    public void executeMessage(Message msg) {
+        if (mSessionImpl == null) {
+            return;
+        }
+
+        long startTime = System.nanoTime();
+        switch (msg.what) {
+            case DO_RELEASE: {
+                mSessionImpl.release();
+                mSessionImpl = null;
+                if (mReceiver != null) {
+                    mReceiver.dispose();
+                    mReceiver = null;
+                }
+                if (mChannel != null) {
+                    mChannel.dispose();
+                    mChannel = null;
+                }
+                break;
+            }
+            case DO_SET_SURFACE: {
+                mSessionImpl.setSurface((Surface) msg.obj);
+                break;
+            }
+            case DO_DISPATCH_SURFACE_CHANGED: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                mSessionImpl.dispatchSurfaceChanged(
+                        (Integer) args.argi1, (Integer) args.argi2, (Integer) args.argi3);
+                args.recycle();
+                break;
+            }
+            case DO_CREATE_MEDIA_VIEW: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                mSessionImpl.createMediaView((IBinder) args.arg1, (Rect) args.arg2);
+                args.recycle();
+                break;
+            }
+            case DO_RELAYOUT_MEDIA_VIEW: {
+                mSessionImpl.relayoutMediaView((Rect) msg.obj);
+                break;
+            }
+            case DO_REMOVE_MEDIA_VIEW: {
+                mSessionImpl.removeMediaView(true);
+                break;
+            }
+            case DO_START_AD_SERVICE: {
+                mSessionImpl.startAdService();
+                break;
+            }
+            case DO_STOP_AD_SERVICE: {
+                mSessionImpl.stopAdService();
+                break;
+            }
+            case DO_RESET_AD_SERVICE: {
+                mSessionImpl.resetAdService();
+                break;
+            }
+            case DO_SEND_CURRENT_VIDEO_BOUNDS: {
+                mSessionImpl.sendCurrentVideoBounds((Rect) msg.obj);
+                break;
+            }
+            case DO_SEND_CURRENT_CHANNEL_URI: {
+                mSessionImpl.sendCurrentChannelUri((Uri) msg.obj);
+                break;
+            }
+            case DO_SEND_TRACK_INFO_LIST: {
+                mSessionImpl.sendTrackInfoList((List<TvTrackInfo>) msg.obj);
+                break;
+            }
+            case DO_SEND_CURRENT_TV_INPUT_ID: {
+                mSessionImpl.sendCurrentTvInputId((String) msg.obj);
+                break;
+            }
+            case DO_SEND_SIGNING_RESULT: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                mSessionImpl.sendSigningResult((String) args.arg1, (byte[]) args.arg2);
+                args.recycle();
+                break;
+            }
+            case DO_NOTIFY_ERROR: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                mSessionImpl.notifyError((String) args.arg1, (Bundle) args.arg2);
+                args.recycle();
+                break;
+            }
+            case DO_NOTIFY_TV_MESSAGE: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                mSessionImpl.notifyTvMessage((Integer) args.arg1, (Bundle) args.arg2);
+                args.recycle();
+                break;
+            }
+            default: {
+                Log.w(TAG, "Unhandled message code: " + msg.what);
+                break;
+            }
+        }
+        long durationMs = (System.nanoTime() - startTime) / (1000 * 1000);
+        if (durationMs > EXECUTE_MESSAGE_TIMEOUT_SHORT_MILLIS) {
+            Log.w(TAG, "Handling message (" + msg.what + ") took too long time (duration="
+                    + durationMs + "ms)");
+            if (durationMs > EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS) {
+                // TODO: handle timeout
+            }
+        }
+
+    }
+
+    @Override
+    public void startAdService() throws RemoteException {
+        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_START_AD_SERVICE));
+    }
+
+    @Override
+    public void stopAdService() {
+        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_STOP_AD_SERVICE));
+    }
+
+    @Override
+    public void resetAdService() {
+        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RESET_AD_SERVICE));
+    }
+
+    @Override
+    public void setSurface(Surface surface) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_SURFACE, surface));
+    }
+
+    @Override
+    public void dispatchSurfaceChanged(int format, int width, int height) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageIIII(DO_DISPATCH_SURFACE_CHANGED, format, width, height, 0));
+    }
+
+    @Override
+    public void sendCurrentVideoBounds(@Nullable Rect bounds) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageO(DO_SEND_CURRENT_VIDEO_BOUNDS, bounds));
+    }
+
+    @Override
+    public void sendCurrentChannelUri(@Nullable Uri channelUri) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageO(DO_SEND_CURRENT_CHANNEL_URI, channelUri));
+    }
+
+    @Override
+    public void sendTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageO(DO_SEND_TRACK_INFO_LIST, tracks));
+    }
+
+    @Override
+    public void sendCurrentTvInputId(@Nullable String inputId) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageO(DO_SEND_CURRENT_TV_INPUT_ID, inputId));
+    }
+
+    @Override
+    public void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageOO(DO_SEND_SIGNING_RESULT, signingId, result));
+    }
+
+    @Override
+    public void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageOO(DO_NOTIFY_ERROR, errMsg, params));
+    }
+
+    @Override
+    public void notifyTvMessage(int type, Bundle data) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageOO(DO_NOTIFY_TV_MESSAGE, type, data));
+    }
+
+    @Override
+    public void createMediaView(IBinder windowToken, Rect frame) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageOO(DO_CREATE_MEDIA_VIEW, windowToken, frame));
+    }
+
+    @Override
+    public void relayoutMediaView(Rect frame) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_RELAYOUT_MEDIA_VIEW, frame));
+    }
+
+    @Override
+    public void removeMediaView() {
+        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_MEDIA_VIEW));
+    }
+
+    private final class TvAdEventReceiver extends InputEventReceiver {
+        TvAdEventReceiver(InputChannel inputChannel, Looper looper) {
+            super(inputChannel, looper);
+        }
+
+        @Override
+        public void onInputEvent(InputEvent event) {
+            if (mSessionImpl == null) {
+                // The session has been finished.
+                finishInputEvent(event, false);
+                return;
+            }
+
+            int handled = mSessionImpl.dispatchInputEvent(event, this);
+            if (handled != TvAdManager.Session.DISPATCH_IN_PROGRESS) {
+                finishInputEvent(
+                        event, handled == TvAdManager.Session.DISPATCH_HANDLED);
+            }
+        }
+    }
+}
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index 2b52c4b..4dce72f 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -17,12 +17,35 @@
 package android.media.tv.ad;
 
 import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.content.Context;
+import android.graphics.Rect;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvTrackInfo;
 import android.media.tv.flags.Flags;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.Pools;
+import android.util.SparseArray;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventSender;
+import android.view.Surface;
+import android.view.View;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * Central system API to the overall client-side TV AD architecture, which arbitrates interaction
@@ -37,10 +60,163 @@
     private final ITvAdManager mService;
     private final int mUserId;
 
+    // A mapping from the sequence number of a session to its SessionCallbackRecord.
+    private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap =
+            new SparseArray<>();
+
+    // @GuardedBy("mLock")
+    private final List<TvAdServiceCallbackRecord> mCallbackRecords = new ArrayList<>();
+
+    // A sequence number for the next session to be created. Should be protected by a lock
+    // {@code mSessionCallbackRecordMap}.
+    private int mNextSeq;
+
+    private final Object mLock = new Object();
+    private final ITvAdClient mClient;
+
     /** @hide */
     public TvAdManager(ITvAdManager service, int userId) {
         mService = service;
         mUserId = userId;
+        mClient = new ITvAdClient.Stub() {
+            @Override
+            public void onSessionCreated(String serviceId, IBinder token, InputChannel channel,
+                    int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for " + token);
+                        return;
+                    }
+                    Session session = null;
+                    if (token != null) {
+                        session = new Session(token, channel, mService, mUserId, seq,
+                                mSessionCallbackRecordMap);
+                    } else {
+                        mSessionCallbackRecordMap.delete(seq);
+                    }
+                    record.postSessionCreated(session);
+                }
+            }
+
+            @Override
+            public void onSessionReleased(int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    mSessionCallbackRecordMap.delete(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq:" + seq);
+                        return;
+                    }
+                    record.mSession.releaseInternal();
+                    record.postSessionReleased();
+                }
+            }
+
+            @Override
+            public void onLayoutSurface(int left, int top, int right, int bottom, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postLayoutSurface(left, top, right, bottom);
+                }
+            }
+
+        };
+
+        ITvAdManagerCallback managerCallback =
+                new ITvAdManagerCallback.Stub() {
+                    @Override
+                    public void onAdServiceAdded(String serviceId) {
+                        synchronized (mLock) {
+                            for (TvAdServiceCallbackRecord record : mCallbackRecords) {
+                                record.postAdServiceAdded(serviceId);
+                            }
+                        }
+                    }
+
+                    @Override
+                    public void onAdServiceRemoved(String serviceId) {
+                        synchronized (mLock) {
+                            for (TvAdServiceCallbackRecord record : mCallbackRecords) {
+                                record.postAdServiceRemoved(serviceId);
+                            }
+                        }
+                    }
+
+                    @Override
+                    public void onAdServiceUpdated(String serviceId) {
+                        synchronized (mLock) {
+                            for (TvAdServiceCallbackRecord record : mCallbackRecords) {
+                                record.postAdServiceUpdated(serviceId);
+                            }
+                        }
+                    }
+                };
+        try {
+            if (mService != null) {
+                mService.registerCallback(managerCallback, mUserId);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the complete list of TV AD service on the system.
+     *
+     * @return List of {@link TvAdServiceInfo} for each TV AD service that describes its meta
+     * information.
+     * @hide
+     */
+    @NonNull
+    public List<TvAdServiceInfo> getTvAdServiceList() {
+        try {
+            return mService.getTvAdServiceList(mUserId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Creates a {@link Session} for a given TV AD service.
+     *
+     * <p>The number of sessions that can be created at the same time is limited by the capability
+     * of the given AD service.
+     *
+     * @param serviceId The ID of the AD service.
+     * @param callback A callback used to receive the created session.
+     * @param handler A {@link Handler} that the session creation will be delivered to.
+     * @hide
+     */
+    public void createSession(
+            @NonNull String serviceId,
+            @NonNull String type,
+            @NonNull final TvAdManager.SessionCallback callback,
+            @NonNull Handler handler) {
+        createSessionInternal(serviceId, type, callback, handler);
+    }
+
+    private void createSessionInternal(String serviceId, String type,
+            TvAdManager.SessionCallback callback, Handler handler) {
+        Preconditions.checkNotNull(serviceId);
+        Preconditions.checkNotNull(type);
+        Preconditions.checkNotNull(callback);
+        Preconditions.checkNotNull(handler);
+        TvAdManager.SessionCallbackRecord
+                record = new TvAdManager.SessionCallbackRecord(callback, handler);
+        synchronized (mSessionCallbackRecordMap) {
+            int seq = mNextSeq++;
+            mSessionCallbackRecordMap.put(seq, record);
+            try {
+                mService.createSession(mClient, serviceId, type, seq, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
     }
 
     /**
@@ -48,14 +224,190 @@
      * @hide
      */
     public static final class Session {
-        private final IBinder mToken;
+        static final int DISPATCH_IN_PROGRESS = -1;
+        static final int DISPATCH_NOT_HANDLED = 0;
+        static final int DISPATCH_HANDLED = 1;
+
+        private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500;
         private final ITvAdManager mService;
         private final int mUserId;
+        private final int mSeq;
+        private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap;
 
-        private Session(IBinder token, ITvAdManager service, int userId) {
+        // For scheduling input event handling on the main thread. This also serves as a lock to
+        // protect pending input events and the input channel.
+        private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper());
+
+        private TvInputManager.Session mInputSession;
+        private final Pools.Pool<PendingEvent> mPendingEventPool = new Pools.SimplePool<>(20);
+        private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);
+        private TvInputEventSender mSender;
+        private InputChannel mInputChannel;
+        private IBinder mToken;
+
+        private Session(IBinder token, InputChannel channel, ITvAdManager service, int userId,
+                int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
             mToken = token;
+            mInputChannel = channel;
             mService = service;
             mUserId = userId;
+            mSeq = seq;
+            mSessionCallbackRecordMap = sessionCallbackRecordMap;
+        }
+
+        public TvInputManager.Session getInputSession() {
+            return mInputSession;
+        }
+
+        public void setInputSession(TvInputManager.Session inputSession) {
+            mInputSession = inputSession;
+        }
+
+        /**
+         * Releases this session.
+         */
+        public void release() {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.releaseSession(mToken, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+
+            releaseInternal();
+        }
+
+        /**
+         * Sets the {@link android.view.Surface} for this session.
+         *
+         * @param surface A {@link android.view.Surface} used to render AD.
+         */
+        public void setSurface(Surface surface) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            // surface can be null.
+            try {
+                mService.setSurface(mToken, surface, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Creates a media view. Once the media view is created, {@link #relayoutMediaView}
+         * should be called whenever the layout of its containing view is changed.
+         * {@link #removeMediaView()} should be called to remove the media view.
+         * Since a session can have only one media view, this method should be called only once
+         * or it can be called again after calling {@link #removeMediaView()}.
+         *
+         * @param view A view for AD service.
+         * @param frame A position of the media view.
+         * @throws IllegalStateException if {@code view} is not attached to a window.
+         */
+        void createMediaView(@NonNull View view, @NonNull Rect frame) {
+            Preconditions.checkNotNull(view);
+            Preconditions.checkNotNull(frame);
+            if (view.getWindowToken() == null) {
+                throw new IllegalStateException("view must be attached to a window");
+            }
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.createMediaView(mToken, view.getWindowToken(), frame, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Relayouts the current media view.
+         *
+         * @param frame A new position of the media view.
+         */
+        void relayoutMediaView(@NonNull Rect frame) {
+            Preconditions.checkNotNull(frame);
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.relayoutMediaView(mToken, frame, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Removes the current media view.
+         */
+        void removeMediaView() {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.removeMediaView(mToken, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Notifies of any structural changes (format or size) of the surface passed in
+         * {@link #setSurface}.
+         *
+         * @param format The new PixelFormat of the surface.
+         * @param width The new width of the surface.
+         * @param height The new height of the surface.
+         */
+        public void dispatchSurfaceChanged(int format, int width, int height) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.dispatchSurfaceChanged(mToken, format, width, height, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        private void flushPendingEventsLocked() {
+            mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
+
+            final int count = mPendingEvents.size();
+            for (int i = 0; i < count; i++) {
+                int seq = mPendingEvents.keyAt(i);
+                Message msg = mHandler.obtainMessage(
+                        InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0);
+                msg.setAsynchronous(true);
+                msg.sendToTarget();
+            }
+        }
+
+        private void releaseInternal() {
+            mToken = null;
+            synchronized (mHandler) {
+                if (mInputChannel != null) {
+                    if (mSender != null) {
+                        flushPendingEventsLocked();
+                        mSender.dispose();
+                        mSender = null;
+                    }
+                    mInputChannel.dispose();
+                    mInputChannel = null;
+                }
+            }
+            synchronized (mSessionCallbackRecordMap) {
+                mSessionCallbackRecordMap.delete(mSeq);
+            }
         }
 
         void startAdService() {
@@ -69,5 +421,435 @@
                 throw e.rethrowFromSystemServer();
             }
         }
+
+        void stopAdService() {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.stopAdService(mToken, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        void resetAdService() {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.resetAdService(mToken, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        void sendCurrentVideoBounds(@NonNull Rect bounds) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.sendCurrentVideoBounds(mToken, bounds, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        void sendCurrentChannelUri(@Nullable Uri channelUri) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.sendCurrentChannelUri(mToken, channelUri, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        void sendTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.sendTrackInfoList(mToken, tracks, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        void sendCurrentTvInputId(@Nullable String inputId) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.sendCurrentTvInputId(mToken, inputId, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.sendSigningResult(mToken, signingId, result, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyError(mToken, errMsg, params, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Notifies AD service session when a new TV message is received.
+         */
+        public void notifyTvMessage(int type, Bundle data) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyTvMessage(mToken, type, data, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        private final class InputEventHandler extends Handler {
+            public static final int MSG_SEND_INPUT_EVENT = 1;
+            public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
+            public static final int MSG_FLUSH_INPUT_EVENT = 3;
+
+            InputEventHandler(Looper looper) {
+                super(looper, null, true);
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case MSG_SEND_INPUT_EVENT: {
+                        sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj);
+                        return;
+                    }
+                    case MSG_TIMEOUT_INPUT_EVENT: {
+                        finishedInputEvent(msg.arg1, false, true);
+                        return;
+                    }
+                    case MSG_FLUSH_INPUT_EVENT: {
+                        finishedInputEvent(msg.arg1, false, false);
+                        return;
+                    }
+                }
+            }
+        }
+
+        // Assumes the event has already been removed from the queue.
+        void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
+            p.mHandled = handled;
+            if (p.mEventHandler.getLooper().isCurrentThread()) {
+                // Already running on the callback handler thread so we can send the callback
+                // immediately.
+                p.run();
+            } else {
+                // Post the event to the callback handler thread.
+                // In this case, the callback will be responsible for recycling the event.
+                Message msg = Message.obtain(p.mEventHandler, p);
+                msg.setAsynchronous(true);
+                msg.sendToTarget();
+            }
+        }
+
+        // Must be called on the main looper
+        private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
+            synchronized (mHandler) {
+                int result = sendInputEventOnMainLooperLocked(p);
+                if (result == DISPATCH_IN_PROGRESS) {
+                    return;
+                }
+            }
+
+            invokeFinishedInputEventCallback(p, false);
+        }
+
+        private int sendInputEventOnMainLooperLocked(PendingEvent p) {
+            if (mInputChannel != null) {
+                if (mSender == null) {
+                    mSender = new TvInputEventSender(mInputChannel, mHandler.getLooper());
+                }
+
+                final InputEvent event = p.mEvent;
+                final int seq = event.getSequenceNumber();
+                if (mSender.sendInputEvent(seq, event)) {
+                    mPendingEvents.put(seq, p);
+                    Message msg = mHandler.obtainMessage(
+                            InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
+                    msg.setAsynchronous(true);
+                    mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT);
+                    return DISPATCH_IN_PROGRESS;
+                }
+
+                Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:"
+                        + event);
+            }
+            return DISPATCH_NOT_HANDLED;
+        }
+
+        void finishedInputEvent(int seq, boolean handled, boolean timeout) {
+            final PendingEvent p;
+            synchronized (mHandler) {
+                int index = mPendingEvents.indexOfKey(seq);
+                if (index < 0) {
+                    return; // spurious, event already finished or timed out
+                }
+
+                p = mPendingEvents.valueAt(index);
+                mPendingEvents.removeAt(index);
+
+                if (timeout) {
+                    Log.w(TAG, "Timeout waiting for session to handle input event after "
+                            + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken);
+                } else {
+                    mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
+                }
+            }
+
+            invokeFinishedInputEventCallback(p, handled);
+        }
+
+        private void recyclePendingEventLocked(PendingEvent p) {
+            p.recycle();
+            mPendingEventPool.release(p);
+        }
+
+        /**
+         * Callback that is invoked when an input event that was dispatched to this session has been
+         * finished.
+         *
+         * @hide
+         */
+        public interface FinishedInputEventCallback {
+            /**
+             * Called when the dispatched input event is finished.
+             *
+             * @param token A token passed to {@link #dispatchInputEvent}.
+             * @param handled {@code true} if the dispatched input event was handled properly.
+             *            {@code false} otherwise.
+             */
+            void onFinishedInputEvent(Object token, boolean handled);
+        }
+
+        private final class TvInputEventSender extends InputEventSender {
+            TvInputEventSender(InputChannel inputChannel, Looper looper) {
+                super(inputChannel, looper);
+            }
+
+            @Override
+            public void onInputEventFinished(int seq, boolean handled) {
+                finishedInputEvent(seq, handled, false);
+            }
+        }
+
+        private final class PendingEvent implements Runnable {
+            public InputEvent mEvent;
+            public Object mEventToken;
+            public Session.FinishedInputEventCallback mCallback;
+            public Handler mEventHandler;
+            public boolean mHandled;
+
+            public void recycle() {
+                mEvent = null;
+                mEventToken = null;
+                mCallback = null;
+                mEventHandler = null;
+                mHandled = false;
+            }
+
+            @Override
+            public void run() {
+                mCallback.onFinishedInputEvent(mEventToken, mHandled);
+
+                synchronized (mEventHandler) {
+                    recyclePendingEventLocked(this);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Interface used to receive the created session.
+     * @hide
+     */
+    public abstract static class SessionCallback {
+        /**
+         * This is called after {@link TvAdManager#createSession} has been processed.
+         *
+         * @param session A {@link TvAdManager.Session} instance created. This can be
+         *                {@code null} if the creation request failed.
+         */
+        public void onSessionCreated(@Nullable Session session) {
+        }
+
+        /**
+         * This is called when {@link TvAdManager.Session} is released.
+         * This typically happens when the process hosting the session has crashed or been killed.
+         *
+         * @param session the {@link TvAdManager.Session} instance released.
+         */
+        public void onSessionReleased(@NonNull Session session) {
+        }
+
+        /**
+         * This is called when {@link TvAdService.Session#layoutSurface} is called to
+         * change the layout of surface.
+         *
+         * @param session A {@link TvAdManager.Session} associated with this callback.
+         * @param left Left position.
+         * @param top Top position.
+         * @param right Right position.
+         * @param bottom Bottom position.
+         */
+        public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
+        }
+
+    }
+
+    /**
+     * Callback used to monitor status of the TV AD service.
+     * @hide
+     */
+    public abstract static class TvAdServiceCallback {
+        /**
+         * This is called when a TV AD service is added to the system.
+         *
+         * <p>Normally it happens when the user installs a new TV AD service package that implements
+         * {@link TvAdService} interface.
+         *
+         * @param serviceId The ID of the TV AD service.
+         */
+        public void onAdServiceAdded(@NonNull String serviceId) {
+        }
+
+        /**
+         * This is called when a TV AD service is removed from the system.
+         *
+         * <p>Normally it happens when the user uninstalls the previously installed TV AD service
+         * package.
+         *
+         * @param serviceId The ID of the TV AD service.
+         */
+        public void onAdServiceRemoved(@NonNull String serviceId) {
+        }
+
+        /**
+         * This is called when a TV AD service is updated on the system.
+         *
+         * <p>Normally it happens when a previously installed TV AD service package is re-installed
+         * or a newer version of the package exists becomes available/unavailable.
+         *
+         * @param serviceId The ID of the TV AD service.
+         */
+        public void onAdServiceUpdated(@NonNull String serviceId) {
+        }
+
+    }
+
+    private static final class SessionCallbackRecord {
+        private final SessionCallback mSessionCallback;
+        private final Handler mHandler;
+        private Session mSession;
+
+        SessionCallbackRecord(SessionCallback sessionCallback, Handler handler) {
+            mSessionCallback = sessionCallback;
+            mHandler = handler;
+        }
+
+        void postSessionCreated(final Session session) {
+            mSession = session;
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onSessionCreated(session);
+                }
+            });
+        }
+
+        void postSessionReleased() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onSessionReleased(mSession);
+                }
+            });
+        }
+
+        void postLayoutSurface(final int left, final int top, final int right,
+                final int bottom) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onLayoutSurface(mSession, left, top, right, bottom);
+                }
+            });
+        }
+    }
+
+    private static final class TvAdServiceCallbackRecord {
+        private final TvAdServiceCallback mCallback;
+        private final Executor mExecutor;
+
+        TvAdServiceCallbackRecord(TvAdServiceCallback callback, Executor executor) {
+            mCallback = callback;
+            mExecutor = executor;
+        }
+
+        public TvAdServiceCallback getCallback() {
+            return mCallback;
+        }
+
+        public void postAdServiceAdded(final String serviceId) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onAdServiceAdded(serviceId);
+                }
+            });
+        }
+
+        public void postAdServiceRemoved(final String serviceId) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onAdServiceRemoved(serviceId);
+                }
+            });
+        }
+
+        public void postAdServiceUpdated(final String serviceId) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onAdServiceUpdated(serviceId);
+                }
+            });
+        }
     }
 }
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index 6897a78..5d81837 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -16,8 +16,47 @@
 
 package android.media.tv.ad;
 
+import android.annotation.CallSuper;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Px;
+import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
+import android.app.ActivityManager;
 import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvTrackInfo;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
 import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import com.android.internal.os.SomeArgs;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * The TvAdService class represents a TV client-side advertisement service.
@@ -27,6 +66,8 @@
     private static final boolean DEBUG = false;
     private static final String TAG = "TvAdService";
 
+    private static final int DETACH_MEDIA_VIEW_TIMEOUT_MS = 5000;
+
     /**
      * Name under which a TvAdService component publishes information about itself. This meta-data
      * must reference an XML resource containing an
@@ -36,33 +77,730 @@
     public static final String SERVICE_META_DATA = "android.media.tv.ad.service";
 
     /**
+     * This is the interface name that a service implementing a TV AD service should
+     * say that it supports -- that is, this is the action it uses for its intent filter. To be
+     * supported, the service must also require the
+     * android.Manifest.permission#BIND_TV_AD_SERVICE permission so that other
+     * applications cannot abuse it.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE = "android.media.tv.ad.TvAdService";
+
+    private final Handler mServiceHandler = new ServiceHandler();
+    private final RemoteCallbackList<ITvAdServiceCallback> mCallbacks = new RemoteCallbackList<>();
+
+    @Override
+    @Nullable
+    public final IBinder onBind(@NonNull Intent intent) {
+        ITvAdService.Stub tvAdServiceBinder = new ITvAdService.Stub() {
+            @Override
+            public void registerCallback(ITvAdServiceCallback cb) {
+                if (cb != null) {
+                    mCallbacks.register(cb);
+                }
+            }
+
+            @Override
+            public void unregisterCallback(ITvAdServiceCallback cb) {
+                if (cb != null) {
+                    mCallbacks.unregister(cb);
+                }
+            }
+
+            @Override
+            public void createSession(InputChannel channel, ITvAdSessionCallback cb,
+                    String serviceId, String type) {
+                if (cb == null) {
+                    return;
+                }
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = channel;
+                args.arg2 = cb;
+                args.arg3 = serviceId;
+                args.arg4 = type;
+                mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args)
+                        .sendToTarget();
+            }
+
+            @Override
+            public void sendAppLinkCommand(Bundle command) {
+                onAppLinkCommand(command);
+            }
+        };
+        return tvAdServiceBinder;
+    }
+
+    /**
+     * Called when app link command is received.
+     */
+    public void onAppLinkCommand(@NonNull Bundle command) {
+    }
+
+
+    /**
+     * Returns a concrete implementation of {@link Session}.
+     *
+     * <p>May return {@code null} if this TV AD service fails to create a session for some
+     * reason.
+     *
+     * @param serviceId The ID of the TV AD associated with the session.
+     * @param type The type of the TV AD associated with the session.
+     */
+    @Nullable
+    public abstract Session onCreateSession(@NonNull String serviceId, @NonNull String type);
+
+    /**
      * Base class for derived classes to implement to provide a TV AD session.
      */
     public abstract static class Session implements KeyEvent.Callback {
+        private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
+
+        private final Object mLock = new Object();
+        // @GuardedBy("mLock")
+        private ITvAdSessionCallback mSessionCallback;
+        // @GuardedBy("mLock")
+        private final List<Runnable> mPendingActions = new ArrayList<>();
+        private final Context mContext;
+        final Handler mHandler;
+        private final WindowManager mWindowManager;
+        private WindowManager.LayoutParams mWindowParams;
+        private Surface mSurface;
+        private FrameLayout mMediaViewContainer;
+        private View mMediaView;
+        private MediaViewCleanUpTask mMediaViewCleanUpTask;
+        private boolean mMediaViewEnabled;
+        private IBinder mWindowToken;
+        private Rect mMediaFrame;
+
+
+        /**
+         * Creates a new Session.
+         *
+         * @param context The context of the application
+         */
+        public Session(@NonNull Context context) {
+            mContext = context;
+            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+            mHandler = new Handler(context.getMainLooper());
+        }
+
+        /**
+         * Enables or disables the media view.
+         *
+         * <p>By default, the media view is disabled. Must be called explicitly after the
+         * session is created to enable the media view.
+         *
+         * <p>The TV AD service can disable its media view when needed.
+         *
+         * @param enable {@code true} if you want to enable the media view. {@code false}
+         *            otherwise.
+         * @hide
+         */
+        @CallSuper
+        public void setMediaViewEnabled(final boolean enable) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (enable == mMediaViewEnabled) {
+                        return;
+                    }
+                    mMediaViewEnabled = enable;
+                    if (enable) {
+                        if (mWindowToken != null) {
+                            createMediaView(mWindowToken, mMediaFrame);
+                        }
+                    } else {
+                        removeMediaView(false);
+                    }
+                }
+            });
+        }
+
+        /**
+         * Returns {@code true} if media view is enabled, {@code false} otherwise.
+         *
+         * @see #setMediaViewEnabled(boolean)
+         * @hide
+         */
+        public boolean isMediaViewEnabled() {
+            return mMediaViewEnabled;
+        }
+
+        /**
+         * Releases TvAdService session.
+         */
+        public abstract void onRelease();
+
+        void release() {
+            onRelease();
+            if (mSurface != null) {
+                mSurface.release();
+                mSurface = null;
+            }
+            synchronized (mLock) {
+                mSessionCallback = null;
+                mPendingActions.clear();
+            }
+            // Removes the media view lastly so that any hanging on the main thread can be handled
+            // in {@link #scheduleMediaViewCleanup}.
+            removeMediaView(true);
+        }
+
         /**
          * Starts TvAdService session.
+         * @hide
          */
         public void onStartAdService() {
         }
 
+        /**
+         * Stops TvAdService session.
+         * @hide
+         */
+        public void onStopAdService() {
+        }
+
+        /**
+         * Resets TvAdService session.
+         * @hide
+         */
+        public void onResetAdService() {
+        }
+
         void startAdService() {
             onStartAdService();
         }
-    }
 
-    /**
-     * Implements the internal ITvAdService interface.
-     */
-    public static class ITvAdSessionWrapper extends ITvAdSession.Stub {
-        private final Session mSessionImpl;
+        void stopAdService() {
+            onStopAdService();
+        }
 
-        public ITvAdSessionWrapper(Session mSessionImpl) {
-            this.mSessionImpl = mSessionImpl;
+        void resetAdService() {
+            onResetAdService();
         }
 
         @Override
-        public void startAdService() {
-            mSessionImpl.startAdService();
+        public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
+            return false;
         }
+
+        @Override
+        public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event) {
+            return false;
+        }
+
+        @Override
+        public boolean onKeyMultiple(int keyCode, int count, @NonNull KeyEvent event) {
+            return false;
+        }
+
+        @Override
+        public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
+            return false;
+        }
+
+        /**
+         * Implement this method to handle touch screen motion events on the current session.
+         *
+         * @param event The motion event being received.
+         * @return If you handled the event, return {@code true}. If you want to allow the event to
+         *         be handled by the next receiver, return {@code false}.
+         * @see View#onTouchEvent
+         */
+        public boolean onTouchEvent(@NonNull MotionEvent event) {
+            return false;
+        }
+
+        /**
+         * Implement this method to handle trackball events on the current session.
+         *
+         * @param event The motion event being received.
+         * @return If you handled the event, return {@code true}. If you want to allow the event to
+         *         be handled by the next receiver, return {@code false}.
+         * @see View#onTrackballEvent
+         */
+        public boolean onTrackballEvent(@NonNull MotionEvent event) {
+            return false;
+        }
+
+        /**
+         * Implement this method to handle generic motion events on the current session.
+         *
+         * @param event The motion event being received.
+         * @return If you handled the event, return {@code true}. If you want to allow the event to
+         *         be handled by the next receiver, return {@code false}.
+         * @see View#onGenericMotionEvent
+         */
+        public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
+            return false;
+        }
+
+        /**
+         * Assigns a size and position to the surface passed in {@link #onSetSurface}. The position
+         * is relative to the overlay view that sits on top of this surface.
+         *
+         * @param left Left position in pixels, relative to the overlay view.
+         * @param top Top position in pixels, relative to the overlay view.
+         * @param right Right position in pixels, relative to the overlay view.
+         * @param bottom Bottom position in pixels, relative to the overlay view.
+         */
+        @CallSuper
+        public void layoutSurface(final int left, final int top, final int right,
+                final int bottom) {
+            if (left > right || top > bottom) {
+                throw new IllegalArgumentException("Invalid parameter");
+            }
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) {
+                            Log.d(TAG, "layoutSurface (l=" + left + ", t=" + top
+                                    + ", r=" + right + ", b=" + bottom + ",)");
+                        }
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onLayoutSurface(left, top, right, bottom);
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in layoutSurface", e);
+                    }
+                }
+            });
+        }
+
+        /**
+         * Called when the application sets the surface.
+         *
+         * <p>The TV AD service should render AD UI onto the given surface. When called with
+         * {@code null}, the AD service should immediately free any references to the currently set
+         * surface and stop using it.
+         *
+         * @param surface The surface to be used for AD UI rendering. Can be {@code null}.
+         * @return {@code true} if the surface was set successfully, {@code false} otherwise.
+         */
+        public abstract boolean onSetSurface(@Nullable Surface surface);
+
+        /**
+         * Called after any structural changes (format or size) have been made to the surface passed
+         * in {@link #onSetSurface}. This method is always called at least once, after
+         * {@link #onSetSurface} is called with non-null surface.
+         *
+         * @param format The new {@link PixelFormat} of the surface.
+         * @param width The new width of the surface.
+         * @param height The new height of the surface.
+         */
+        public void onSurfaceChanged(@PixelFormat.Format int format, int width, int height) {
+        }
+
+        /**
+         * Receives current video bounds.
+         *
+         * @param bounds the rectangle area for rendering the current video.
+         * @hide
+         */
+        public void onCurrentVideoBounds(@NonNull Rect bounds) {
+        }
+
+        /**
+         * Receives current channel URI.
+         * @hide
+         */
+        public void onCurrentChannelUri(@Nullable Uri channelUri) {
+        }
+
+        /**
+         * Receives track list.
+         * @hide
+         */
+        public void onTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
+        }
+
+        /**
+         * Receives current TV input ID.
+         * @hide
+         */
+        public void onCurrentTvInputId(@Nullable String inputId) {
+        }
+
+        /**
+         * Receives signing result.
+         *
+         * @param signingId the ID to identify the request. It's the same as the corresponding ID in
+         *        {@link Session#requestSigning(String, String, String, byte[])}
+         * @param result the signed result.
+         *
+         * @see #requestSigning(String, String, String, byte[])
+         * @hide
+         */
+        public void onSigningResult(@NonNull String signingId, @NonNull byte[] result) {
+        }
+
+        /**
+         * Called when the application sends information of an error.
+         *
+         * @param errMsg the message of the error.
+         * @param params additional parameters of the error. For example, the signingId of {@link
+         *     TvAdView.TvAdCallback#onRequestSigning(String, String, String, String, byte[])}
+         *     can be included to identify the related signing request, and the method name
+         *     "onRequestSigning" can also be added to the params.
+         *
+         * @see TvAdView#ERROR_KEY_METHOD_NAME
+         * @hide
+         */
+        public void onError(@NonNull String errMsg, @NonNull Bundle params) {
+        }
+
+        /**
+         * Called when a TV message is received
+         *
+         * @param type The type of message received, such as
+         * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
+         * @param data The raw data of the message. The bundle keys are:
+         *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+         *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+         *             how to parse this data.
+         * @hide
+         */
+        public void onTvMessage(@TvInputManager.TvMessageType int type,
+                @NonNull Bundle data) {
+        }
+
+        /**
+         * Called when the size of the media view is changed by the application.
+         *
+         * <p>This is always called at least once when the session is created regardless of whether
+         * the media view is enabled or not. The media view container size is the same as the
+         * containing {@link TvAdView}. Note that the size of the underlying surface can
+         * be different if the surface was changed by calling {@link #layoutSurface}.
+         *
+         * @param width The width of the media view, in pixels.
+         * @param height The height of the media view, in pixels.
+         * @hide
+         */
+        public void onMediaViewSizeChanged(@Px int width, @Px int height) {
+        }
+
+        /**
+         * Called when the application requests to create a media view. Each session
+         * implementation can override this method and return its own view.
+         *
+         * @return a view attached to the media window. {@code null} if no media view is created.
+         * @hide
+         */
+        @Nullable
+        public View onCreateMediaView() {
+            return null;
+        }
+
+        /**
+         * Takes care of dispatching incoming input events and tells whether the event was handled.
+         */
+        int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
+            if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
+            if (event instanceof KeyEvent) {
+                KeyEvent keyEvent = (KeyEvent) event;
+                if (keyEvent.dispatch(this, mDispatcherState, this)) {
+                    return TvAdManager.Session.DISPATCH_HANDLED;
+                }
+
+                // TODO: special handlings of navigation keys and media keys
+            } else if (event instanceof MotionEvent) {
+                MotionEvent motionEvent = (MotionEvent) event;
+                final int source = motionEvent.getSource();
+                if (motionEvent.isTouchEvent()) {
+                    if (onTouchEvent(motionEvent)) {
+                        return TvAdManager.Session.DISPATCH_HANDLED;
+                    }
+                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+                    if (onTrackballEvent(motionEvent)) {
+                        return TvAdManager.Session.DISPATCH_HANDLED;
+                    }
+                } else {
+                    if (onGenericMotionEvent(motionEvent)) {
+                        return TvAdManager.Session.DISPATCH_HANDLED;
+                    }
+                }
+            }
+            // TODO: handle overlay view
+            return TvAdManager.Session.DISPATCH_NOT_HANDLED;
+        }
+
+
+        private void initialize(ITvAdSessionCallback callback) {
+            synchronized (mLock) {
+                mSessionCallback = callback;
+                for (Runnable runnable : mPendingActions) {
+                    runnable.run();
+                }
+                mPendingActions.clear();
+            }
+        }
+
+        /**
+         * Calls {@link #onSetSurface}.
+         */
+        void setSurface(Surface surface) {
+            onSetSurface(surface);
+            if (mSurface != null) {
+                mSurface.release();
+            }
+            mSurface = surface;
+            // TODO: Handle failure.
+        }
+
+        /**
+         * Calls {@link #onSurfaceChanged}.
+         */
+        void dispatchSurfaceChanged(int format, int width, int height) {
+            if (DEBUG) {
+                Log.d(TAG, "dispatchSurfaceChanged(format=" + format + ", width=" + width
+                        + ", height=" + height + ")");
+            }
+            onSurfaceChanged(format, width, height);
+        }
+
+        void sendCurrentVideoBounds(@NonNull Rect bounds) {
+            onCurrentVideoBounds(bounds);
+        }
+
+        void sendCurrentChannelUri(@Nullable Uri channelUri) {
+            onCurrentChannelUri(channelUri);
+        }
+
+        void sendTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
+            onTrackInfoList(tracks);
+        }
+
+        void sendCurrentTvInputId(@Nullable String inputId) {
+            onCurrentTvInputId(inputId);
+        }
+
+        void sendSigningResult(String signingId, byte[] result) {
+            onSigningResult(signingId, result);
+        }
+
+        void notifyError(String errMsg, Bundle params) {
+            onError(errMsg, params);
+        }
+
+        void notifyTvMessage(int type, Bundle data) {
+            if (DEBUG) {
+                Log.d(TAG, "notifyTvMessage (type=" + type + ", data= " + data + ")");
+            }
+            onTvMessage(type, data);
+        }
+
+        private void executeOrPostRunnableOnMainThread(Runnable action) {
+            synchronized (mLock) {
+                if (mSessionCallback == null) {
+                    // The session is not initialized yet.
+                    mPendingActions.add(action);
+                } else {
+                    if (mHandler.getLooper().isCurrentThread()) {
+                        action.run();
+                    } else {
+                        // Posts the runnable if this is not called from the main thread
+                        mHandler.post(action);
+                    }
+                }
+            }
+        }
+
+        /**
+         * Creates a media view. This calls {@link #onCreateMediaView} to get a view to attach
+         * to the media window.
+         *
+         * @param windowToken A window token of the application.
+         * @param frame A position of the media view.
+         */
+        void createMediaView(IBinder windowToken, Rect frame) {
+            if (mMediaViewContainer != null) {
+                removeMediaView(false);
+            }
+            if (DEBUG) Log.d(TAG, "create media view(" + frame + ")");
+            mWindowToken = windowToken;
+            mMediaFrame = frame;
+            onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
+            if (!mMediaViewEnabled) {
+                return;
+            }
+            mMediaView = onCreateMediaView();
+            if (mMediaView == null) {
+                return;
+            }
+            if (mMediaViewCleanUpTask != null) {
+                mMediaViewCleanUpTask.cancel(true);
+                mMediaViewCleanUpTask = null;
+            }
+            // Creates a container view to check hanging on the media view detaching.
+            // Adding/removing the media view to/from the container make the view attach/detach
+            // logic run on the main thread.
+            mMediaViewContainer = new FrameLayout(mContext.getApplicationContext());
+            mMediaViewContainer.addView(mMediaView);
+
+            int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+            // We make the overlay view non-focusable and non-touchable so that
+            // the application that owns the window token can decide whether to consume or
+            // dispatch the input events.
+            int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+            if (ActivityManager.isHighEndGfx()) {
+                flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+            }
+            mWindowParams = new WindowManager.LayoutParams(
+                    frame.right - frame.left, frame.bottom - frame.top,
+                    frame.left, frame.top, type, flags, PixelFormat.TRANSPARENT);
+            mWindowParams.privateFlags |=
+                    WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+            mWindowParams.gravity = Gravity.START | Gravity.TOP;
+            mWindowParams.token = windowToken;
+            mWindowManager.addView(mMediaViewContainer, mWindowParams);
+        }
+
+        /**
+         * Relayouts the current media view.
+         *
+         * @param frame A new position of the media view.
+         */
+        void relayoutMediaView(Rect frame) {
+            if (DEBUG) Log.d(TAG, "relayoutMediaView(" + frame + ")");
+            if (mMediaFrame == null || mMediaFrame.width() != frame.width()
+                    || mMediaFrame.height() != frame.height()) {
+                // Note: relayoutMediaView is called whenever TvAdView's layout is
+                // changed regardless of setMediaViewEnabled.
+                onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
+            }
+            mMediaFrame = frame;
+            if (!mMediaViewEnabled || mMediaViewContainer == null) {
+                return;
+            }
+            mWindowParams.x = frame.left;
+            mWindowParams.y = frame.top;
+            mWindowParams.width = frame.right - frame.left;
+            mWindowParams.height = frame.bottom - frame.top;
+            mWindowManager.updateViewLayout(mMediaViewContainer, mWindowParams);
+        }
+
+        /**
+         * Removes the current media view.
+         */
+        void removeMediaView(boolean clearWindowToken) {
+            if (DEBUG) Log.d(TAG, "removeMediaView(" + mMediaViewContainer + ")");
+            if (clearWindowToken) {
+                mWindowToken = null;
+                mMediaFrame = null;
+            }
+            if (mMediaViewContainer != null) {
+                // Removes the media view from the view hierarchy in advance so that it can be
+                // cleaned up in the {@link MediaViewCleanUpTask} if the remove process is
+                // hanging.
+                mMediaViewContainer.removeView(mMediaView);
+                mMediaView = null;
+                mWindowManager.removeView(mMediaViewContainer);
+                mMediaViewContainer = null;
+                mWindowParams = null;
+            }
+        }
+
+        /**
+         * Schedules a task which checks whether the media view is detached and kills the process
+         * if it is not. Note that this method is expected to be called in a non-main thread.
+         */
+        void scheduleMediaViewCleanup() {
+            View mediaViewParent = mMediaViewContainer;
+            if (mediaViewParent != null) {
+                mMediaViewCleanUpTask = new MediaViewCleanUpTask();
+                mMediaViewCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
+                        mediaViewParent);
+            }
+        }
+    }
+
+    private static final class MediaViewCleanUpTask extends AsyncTask<View, Void, Void> {
+        @Override
+        protected Void doInBackground(View... views) {
+            View mediaViewParent = views[0];
+            try {
+                Thread.sleep(DETACH_MEDIA_VIEW_TIMEOUT_MS);
+            } catch (InterruptedException e) {
+                return null;
+            }
+            if (isCancelled()) {
+                return null;
+            }
+            if (mediaViewParent.isAttachedToWindow()) {
+                Log.e(TAG, "Time out on releasing media view. Killing "
+                        + mediaViewParent.getContext().getPackageName());
+                android.os.Process.killProcess(Process.myPid());
+            }
+            return null;
+        }
+    }
+
+
+    @SuppressLint("HandlerLeak")
+    private final class ServiceHandler extends Handler {
+        private static final int DO_CREATE_SESSION = 1;
+        private static final int DO_NOTIFY_SESSION_CREATED = 2;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case DO_CREATE_SESSION: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    InputChannel channel = (InputChannel) args.arg1;
+                    ITvAdSessionCallback cb = (ITvAdSessionCallback) args.arg2;
+                    String serviceId = (String) args.arg3;
+                    String type = (String) args.arg4;
+                    args.recycle();
+                    TvAdService.Session sessionImpl = onCreateSession(serviceId, type);
+                    if (sessionImpl == null) {
+                        try {
+                            // Failed to create a session.
+                            cb.onSessionCreated(null);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "error in onSessionCreated", e);
+                        }
+                        return;
+                    }
+                    ITvAdSession stub =
+                            new ITvAdSessionWrapper(TvAdService.this, sessionImpl, channel);
+
+                    SomeArgs someArgs = SomeArgs.obtain();
+                    someArgs.arg1 = sessionImpl;
+                    someArgs.arg2 = stub;
+                    someArgs.arg3 = cb;
+                    mServiceHandler.obtainMessage(
+                            DO_NOTIFY_SESSION_CREATED, someArgs).sendToTarget();
+                    return;
+                }
+                case DO_NOTIFY_SESSION_CREATED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    Session sessionImpl = (Session) args.arg1;
+                    ITvAdSession stub = (ITvAdSession) args.arg2;
+                    ITvAdSessionCallback cb = (ITvAdSessionCallback) args.arg3;
+                    try {
+                        cb.onSessionCreated(stub);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "error in onSessionCreated", e);
+                    }
+                    if (sessionImpl != null) {
+                        sessionImpl.initialize(cb);
+                    }
+                    args.recycle();
+                    return;
+                }
+                default: {
+                    Log.w(TAG, "Unhandled message code: " + msg.what);
+                    return;
+                }
+            }
+        }
+
     }
 }
diff --git a/media/java/android/media/tv/ad/TvAdServiceInfo.java b/media/java/android/media/tv/ad/TvAdServiceInfo.java
index ed04f1f..45dc89d 100644
--- a/media/java/android/media/tv/ad/TvAdServiceInfo.java
+++ b/media/java/android/media/tv/ad/TvAdServiceInfo.java
@@ -24,6 +24,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -63,8 +64,7 @@
         if (context == null) {
             throw new IllegalArgumentException("context cannot be null.");
         }
-        // TODO: use a constant
-        Intent intent = new Intent("android.media.tv.ad.TvAdService").setComponent(component);
+        Intent intent = new Intent(TvAdService.SERVICE_INTERFACE).setComponent(component);
         ResolveInfo resolveInfo = context.getPackageManager().resolveService(
                 intent, PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
         if (resolveInfo == null) {
@@ -80,6 +80,7 @@
 
         mService = resolveInfo;
         mId = id;
+        mTypes.addAll(types);
     }
 
     private TvAdServiceInfo(ResolveInfo service, String id, List<String> types) {
@@ -147,9 +148,8 @@
             ResolveInfo resolveInfo, Context context, List<String> types) {
         ServiceInfo serviceInfo = resolveInfo.serviceInfo;
         PackageManager pm = context.getPackageManager();
-        // TODO: use constant for the metadata
         try (XmlResourceParser parser =
-                     serviceInfo.loadXmlMetaData(pm, "android.media.tv.ad.service")) {
+                     serviceInfo.loadXmlMetaData(pm, TvAdService.SERVICE_META_DATA)) {
             if (parser == null) {
                 throw new IllegalStateException(
                         "No " + "android.media.tv.ad.service"
@@ -171,7 +171,15 @@
                         + XML_START_TAG_NAME + " tag for " + serviceInfo.name);
             }
 
-            // TODO: parse attributes
+            TypedArray sa = resources.obtainAttributes(attrs,
+                    com.android.internal.R.styleable.TvAdService);
+            CharSequence[] textArr = sa.getTextArray(
+                    com.android.internal.R.styleable.TvAdService_adServiceTypes);
+            for (CharSequence cs : textArr) {
+                types.add(cs.toString().toLowerCase());
+            }
+
+            sa.recycle();
         } catch (IOException | XmlPullParserException e) {
             throw new IllegalStateException(
                     "Failed reading meta-data for " + serviceInfo.packageName, e);
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index 1a3771a..ec23b7c 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -16,10 +16,33 @@
 
 package android.media.tv.ad;
 
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvTrackInfo;
+import android.media.tv.TvView;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.AttributeSet;
 import android.util.Log;
+import android.util.Xml;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
 import android.view.ViewGroup;
 
+import java.util.List;
+import java.util.concurrent.Executor;
+
 /**
  * Displays contents of TV AD services.
  * @hide
@@ -28,30 +51,567 @@
     private static final String TAG = "TvAdView";
     private static final boolean DEBUG = false;
 
-    // TODO: create session
-    private TvAdManager.Session mSession;
+    /**
+     * The name of the method where the error happened, if applicable. For example, if there is an
+     * error during signing, the request name is "onRequestSigning".
+     * @see #notifyError(String, Bundle)
+     * @hide
+     */
+    public static final String ERROR_KEY_METHOD_NAME = "method_name";
 
-    public TvAdView(Context context) {
-        super(context, /* attrs = */null, /* defStyleAttr = */0);
+    private final TvAdManager mTvAdManager;
+
+    private final Handler mHandler = new Handler();
+    private final Object mCallbackLock = new Object();
+    private TvAdManager.Session mSession;
+    private MySessionCallback mSessionCallback;
+    private TvAdCallback mCallback;
+    private Executor mCallbackExecutor;
+
+    private final AttributeSet mAttrs;
+    private final int mDefStyleAttr;
+    private final XmlResourceParser mParser;
+
+    private SurfaceView mSurfaceView;
+    private Surface mSurface;
+
+    private boolean mSurfaceChanged;
+    private int mSurfaceFormat;
+    private int mSurfaceWidth;
+    private int mSurfaceHeight;
+
+    private boolean mUseRequestedSurfaceLayout;
+    private int mSurfaceViewLeft;
+    private int mSurfaceViewRight;
+    private int mSurfaceViewTop;
+    private int mSurfaceViewBottom;
+
+    private boolean mMediaViewCreated;
+    private Rect mMediaViewFrame;
+
+
+
+    private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+            if (DEBUG) {
+                Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format
+                        + ", width=" + width + ", height=" + height + ")");
+            }
+            mSurfaceFormat = format;
+            mSurfaceWidth = width;
+            mSurfaceHeight = height;
+            mSurfaceChanged = true;
+            dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
+        }
+
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+            mSurface = holder.getSurface();
+            setSessionSurface(mSurface);
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+            mSurface = null;
+            mSurfaceChanged = false;
+            setSessionSurface(null);
+        }
+    };
+
+
+    public TvAdView(@NonNull Context context) {
+        this(context, null, 0);
+    }
+
+    public TvAdView(@NonNull Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public TvAdView(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        int sourceResId = Resources.getAttributeSetSourceResId(attrs);
+        if (sourceResId != Resources.ID_NULL) {
+            Log.d(TAG, "Build local AttributeSet");
+            mParser  = context.getResources().getXml(sourceResId);
+            mAttrs = Xml.asAttributeSet(mParser);
+        } else {
+            Log.d(TAG, "Use passed in AttributeSet");
+            mParser = null;
+            mAttrs = attrs;
+        }
+        mDefStyleAttr = defStyleAttr;
+        resetSurfaceView();
+        mTvAdManager = (TvAdManager) getContext().getSystemService(Context.TV_AD_SERVICE);
+    }
+
+    /**
+     * Sets the TvAdView to receive events from TvInputService. This method links the session of
+     * TvAdManager to TvInputManager session, so the TvAdService can get the TvInputService events.
+     *
+     * @param tvView the TvView to be linked to this TvAdView via linking of Sessions. {@code null}
+     *               to unlink the TvView.
+     * @return {@code true} if it's linked successfully; {@code false} otherwise.
+     * @hide
+     */
+    public boolean setTvView(@Nullable TvView tvView) {
+        if (tvView == null) {
+            return unsetTvView();
+        }
+        TvInputManager.Session inputSession = tvView.getInputSession();
+        if (inputSession == null || mSession == null) {
+            return false;
+        }
+        mSession.setInputSession(inputSession);
+        inputSession.setAdSession(mSession);
+        return true;
+    }
+
+    private boolean unsetTvView() {
+        if (mSession == null || mSession.getInputSession() == null) {
+            return false;
+        }
+        mSession.getInputSession().setAdSession(null);
+        mSession.setInputSession(null);
+        return true;
+    }
+
+    /** @hide */
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        createSessionMediaView();
+    }
+
+    /** @hide */
+    @Override
+    public void onDetachedFromWindow() {
+        removeSessionMediaView();
+        super.onDetachedFromWindow();
     }
 
     @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
         if (DEBUG) {
-            Log.d(TAG,
-                    "onLayout (left=" + l + ", top=" + t + ", right=" + r + ", bottom=" + b + ",)");
+            Log.d(TAG, "onLayout (left=" + left + ", top=" + top + ", right=" + right
+                    + ", bottom=" + bottom + ",)");
+        }
+        if (mUseRequestedSurfaceLayout) {
+            mSurfaceView.layout(mSurfaceViewLeft, mSurfaceViewTop, mSurfaceViewRight,
+                    mSurfaceViewBottom);
+        } else {
+            mSurfaceView.layout(0, 0, right - left, bottom - top);
+        }
+    }
+
+    @Override
+    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        mSurfaceView.measure(widthMeasureSpec, heightMeasureSpec);
+        int width = mSurfaceView.getMeasuredWidth();
+        int height = mSurfaceView.getMeasuredHeight();
+        int childState = mSurfaceView.getMeasuredState();
+        setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, childState),
+                resolveSizeAndState(height, heightMeasureSpec,
+                        childState << MEASURED_HEIGHT_STATE_SHIFT));
+    }
+
+    @Override
+    public void onVisibilityChanged(@NonNull View changedView, int visibility) {
+        super.onVisibilityChanged(changedView, visibility);
+        mSurfaceView.setVisibility(visibility);
+        if (visibility == View.VISIBLE) {
+            createSessionMediaView();
+        } else {
+            removeSessionMediaView();
+        }
+    }
+
+    private void resetSurfaceView() {
+        if (mSurfaceView != null) {
+            mSurfaceView.getHolder().removeCallback(mSurfaceHolderCallback);
+            removeView(mSurfaceView);
+        }
+        mSurface = null;
+        mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr) {
+            @Override
+            protected void updateSurface() {
+                super.updateSurface();
+                relayoutSessionMediaView();
+            }};
+        // The surface view's content should be treated as secure all the time.
+        mSurfaceView.setSecure(true);
+        mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback);
+        mSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
+
+        mSurfaceView.setZOrderOnTop(false);
+        mSurfaceView.setZOrderMediaOverlay(true);
+
+        addView(mSurfaceView);
+    }
+
+    /**
+     * Resets this TvAdView to release its resources.
+     *
+     * <p>It can be reused by call {@link #prepareAdService(String, String)}.
+     * @hide
+     */
+    public void reset() {
+        if (DEBUG) Log.d(TAG, "reset()");
+        resetInternal();
+    }
+
+    private void resetInternal() {
+        mSessionCallback = null;
+        if (mSession != null) {
+            setSessionSurface(null);
+            removeSessionMediaView();
+            mUseRequestedSurfaceLayout = false;
+            mSession.release();
+            mSession = null;
+            resetSurfaceView();
+        }
+    }
+
+    private void createSessionMediaView() {
+        // TODO: handle z-order
+        if (mSession == null || !isAttachedToWindow() || mMediaViewCreated) {
+            return;
+        }
+        mMediaViewFrame = getViewFrameOnScreen();
+        mSession.createMediaView(this, mMediaViewFrame);
+        mMediaViewCreated = true;
+    }
+
+    private void removeSessionMediaView() {
+        if (mSession == null || !mMediaViewCreated) {
+            return;
+        }
+        mSession.removeMediaView();
+        mMediaViewCreated = false;
+        mMediaViewFrame = null;
+    }
+
+    private void relayoutSessionMediaView() {
+        if (mSession == null || !isAttachedToWindow() || !mMediaViewCreated) {
+            return;
+        }
+        Rect viewFrame = getViewFrameOnScreen();
+        if (viewFrame.equals(mMediaViewFrame)) {
+            return;
+        }
+        mSession.relayoutMediaView(viewFrame);
+        mMediaViewFrame = viewFrame;
+    }
+
+    private Rect getViewFrameOnScreen() {
+        Rect frame = new Rect();
+        getGlobalVisibleRect(frame);
+        RectF frameF = new RectF(frame);
+        getMatrix().mapRect(frameF);
+        frameF.round(frame);
+        return frame;
+    }
+
+    private void setSessionSurface(Surface surface) {
+        if (mSession == null) {
+            return;
+        }
+        mSession.setSurface(surface);
+    }
+
+    private void dispatchSurfaceChanged(int format, int width, int height) {
+        if (mSession == null) {
+            return;
+        }
+        mSession.dispatchSurfaceChanged(format, width, height);
+    }
+
+    /**
+     * Prepares the AD service of corresponding {@link TvAdService}.
+     *
+     * @param serviceId the AD service ID, which can be found in TvAdServiceInfo#getId().
+     */
+    public void prepareAdService(@NonNull String serviceId, @NonNull String type) {
+        if (DEBUG) {
+            Log.d(TAG, "prepareAdService");
+        }
+        mSessionCallback = new TvAdView.MySessionCallback(serviceId);
+        if (mTvAdManager != null) {
+            mTvAdManager.createSession(serviceId, type, mSessionCallback, mHandler);
         }
     }
 
     /**
      * Starts the AD service.
+     * @hide
      */
     public void startAdService() {
         if (DEBUG) {
-            Log.d(TAG, "start");
+            Log.d(TAG, "startAdService");
         }
         if (mSession != null) {
             mSession.startAdService();
         }
     }
+
+    /**
+     * Stops the AD service.
+     */
+    public void stopAdService() {
+        if (DEBUG) {
+            Log.d(TAG, "stopAdService");
+        }
+        if (mSession != null) {
+            mSession.stopAdService();
+        }
+    }
+
+    /**
+     * Resets the AD service.
+     *
+     * <p>This releases the resources of the corresponding {@link TvAdService.Session}.
+     */
+    public void resetAdService() {
+        if (DEBUG) {
+            Log.d(TAG, "resetAdService");
+        }
+        if (mSession != null) {
+            mSession.resetAdService();
+        }
+    }
+
+    /**
+     * Sends current video bounds to related TV AD service.
+     *
+     * @param bounds the rectangle area for rendering the current video.
+     */
+    public void sendCurrentVideoBounds(@NonNull Rect bounds) {
+        if (DEBUG) {
+            Log.d(TAG, "sendCurrentVideoBounds");
+        }
+        if (mSession != null) {
+            mSession.sendCurrentVideoBounds(bounds);
+        }
+    }
+
+    /**
+     * Sends current channel URI to related TV AD service.
+     *
+     * @param channelUri The current channel URI; {@code null} if there is no currently tuned
+     *                   channel.
+     */
+    public void sendCurrentChannelUri(@Nullable Uri channelUri) {
+        if (DEBUG) {
+            Log.d(TAG, "sendCurrentChannelUri");
+        }
+        if (mSession != null) {
+            mSession.sendCurrentChannelUri(channelUri);
+        }
+    }
+
+    /**
+     * Sends track info list to related TV AD service.
+     */
+    public void sendTrackInfoList(@Nullable List<TvTrackInfo> tracks) {
+        if (DEBUG) {
+            Log.d(TAG, "sendTrackInfoList");
+        }
+        if (mSession != null) {
+            mSession.sendTrackInfoList(tracks);
+        }
+    }
+
+    /**
+     * Sends current TV input ID to related TV AD service.
+     *
+     * @param inputId The current TV input ID whose channel is tuned. {@code null} if no channel is
+     *                tuned.
+     * @see android.media.tv.TvInputInfo
+     */
+    public void sendCurrentTvInputId(@Nullable String inputId) {
+        if (DEBUG) {
+            Log.d(TAG, "sendCurrentTvInputId");
+        }
+        if (mSession != null) {
+            mSession.sendCurrentTvInputId(inputId);
+        }
+    }
+
+    /**
+     * Sends signing result to related TV AD service.
+     *
+     * <p>This is used when the corresponding server of the ADs requires signing during handshaking,
+     * and the AD service doesn't have the built-in private key. The private key is provided by the
+     * content providers and pre-built in the related app, such as TV app.
+     *
+     * @param signingId the ID to identify the request. It's the same as the corresponding ID in
+     *        {@link TvAdService.Session#requestSigning(String, String, String, byte[])}
+     * @param result the signed result.
+     * @hide
+     */
+    public void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
+        if (DEBUG) {
+            Log.d(TAG, "sendSigningResult");
+        }
+        if (mSession != null) {
+            mSession.sendSigningResult(signingId, result);
+        }
+    }
+
+    /**
+     * Notifies the corresponding {@link TvAdService} when there is an error.
+     *
+     * @param errMsg the message of the error.
+     * @param params additional parameters of the error. For example, the signingId of {@link
+     *     TvAdView.TvAdCallback#onRequestSigning(String, String, String, String, byte[])} can be
+     *     included to identify the related signing request, and the method name "onRequestSigning"
+     *     can also be added to the params.
+     *
+     * @see #ERROR_KEY_METHOD_NAME
+     */
+    public void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
+        if (DEBUG) {
+            Log.d(TAG, "notifyError msg=" + errMsg + "; params=" + params);
+        }
+        if (mSession != null) {
+            mSession.notifyError(errMsg, params);
+        }
+    }
+
+    /**
+     * This is called to notify the corresponding TV AD service when a new TV message is received.
+     *
+     * @param type The type of message received, such as
+     * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
+     * @param data The raw data of the message. The bundle keys are:
+     *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+     *             {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID},
+     *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+     *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+     *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+     *             how to parse this data.
+     */
+    public void notifyTvMessage(@NonNull @TvInputManager.TvMessageType int type,
+            @NonNull Bundle data) {
+        if (DEBUG) {
+            Log.d(TAG, "notifyTvMessage type=" + type
+                    + "; data=" + data);
+        }
+        if (mSession != null) {
+            mSession.notifyTvMessage(type, data);
+        }
+    }
+
+    /**
+     * Sets the callback to be invoked when an event is dispatched to this TvAdView.
+     *
+     * @param callback the callback to receive events. MUST NOT be {@code null}.
+     *
+     * @see #clearCallback()
+     * @hide
+     */
+    public void setCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull TvAdCallback callback) {
+        com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, callback);
+        synchronized (mCallbackLock) {
+            mCallbackExecutor = executor;
+            mCallback = callback;
+        }
+    }
+
+    /**
+     * Clears the callback.
+     *
+     * @see #setCallback(Executor, TvAdCallback)
+     * @hide
+     */
+    public void clearCallback() {
+        synchronized (mCallbackLock) {
+            mCallback = null;
+            mCallbackExecutor = null;
+        }
+    }
+
+    private class MySessionCallback extends TvAdManager.SessionCallback {
+        final String mServiceId;
+
+        MySessionCallback(String serviceId) {
+            mServiceId = serviceId;
+        }
+
+        @Override
+        public void onSessionCreated(TvAdManager.Session session) {
+            if (DEBUG) {
+                Log.d(TAG, "onSessionCreated()");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onSessionCreated - session already created");
+                // This callback is obsolete.
+                if (session != null) {
+                    session.release();
+                }
+                return;
+            }
+            mSession = session;
+            if (session != null) {
+                // mSurface may not be ready yet as soon as starting an application.
+                // In the case, we don't send Session.setSurface(null) unnecessarily.
+                // setSessionSurface will be called in surfaceCreated.
+                if (mSurface != null) {
+                    setSessionSurface(mSurface);
+                    if (mSurfaceChanged) {
+                        dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
+                    }
+                }
+                createSessionMediaView();
+            } else {
+                // Failed to create
+                // Todo: forward error to Tv App
+                mSessionCallback = null;
+            }
+        }
+
+        @Override
+        public void onSessionReleased(TvAdManager.Session session) {
+            if (DEBUG) {
+                Log.d(TAG, "onSessionReleased()");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onSessionReleased - session not created");
+                return;
+            }
+            mMediaViewCreated = false;
+            mMediaViewFrame = null;
+            mSessionCallback = null;
+            mSession = null;
+        }
+
+        @Override
+        public void onLayoutSurface(
+                TvAdManager.Session session, int left, int top, int right, int bottom) {
+            if (DEBUG) {
+                Log.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top + ", right="
+                        + right + ", bottom=" + bottom + ",)");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onLayoutSurface - session not created");
+                return;
+            }
+            mSurfaceViewLeft = left;
+            mSurfaceViewTop = top;
+            mSurfaceViewRight = right;
+            mSurfaceViewBottom = bottom;
+            mUseRequestedSurfaceLayout = true;
+            requestLayout();
+        }
+    }
+
+    /**
+     * Callback used to receive various status updates on the {@link TvAdView}.
+     * @hide
+     */
+    public abstract static class TvAdCallback {
+    }
 }
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index 7739184..1404d7c 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -48,6 +48,7 @@
     void onRequestCurrentChannelLcn(int seq);
     void onRequestStreamVolume(int seq);
     void onRequestTrackInfoList(int seq);
+    void onRequestSelectedTrackInfo(int seq);
     void onRequestCurrentTvInputId(int seq);
     void onRequestTimeShiftMode(int seq);
     void onRequestAvailableSpeeds(int seq);
@@ -60,7 +61,10 @@
     void onSetTvRecordingInfo(in String recordingId, in TvRecordingInfo recordingInfo, int seq);
     void onRequestTvRecordingInfo(in String recordingId, int seq);
     void onRequestTvRecordingInfoList(in int type, int seq);
-    void onRequestSigning(
-            in String id, in String algorithm, in String alias, in byte[] data, int seq);
+    void onRequestSigning(in String id, in String algorithm, in String alias, in byte[] data,
+            int seq);
+    void onRequestSigning2(in String id, in String algorithm, in String host,
+            int port, in byte[] data, int seq);
+    void onRequestCertificate(in String host, int port, int seq);
     void onAdRequest(in AdRequest request, int Seq);
 }
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index 41cbe4a..1b9450b 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -58,6 +58,8 @@
     void sendAvailableSpeeds(in IBinder sessionToken, in float[] speeds, int userId);
     void sendSigningResult(in IBinder sessionToken, in String signingId, in byte[] result,
             int userId);
+    void sendCertificate(in IBinder sessionToken, in String host, int port,
+            in Bundle certBundle, int userId);
     void sendTvRecordingInfo(in IBinder sessionToken, in TvRecordingInfo recordingInfo, int userId);
     void sendTvRecordingInfoList(in IBinder sessionToken,
             in List<TvRecordingInfo> recordingInfoList, int userId);
@@ -88,6 +90,7 @@
     void notifyTracksChanged(in IBinder sessionToken, in List<TvTrackInfo> tracks, int userId);
     void notifyVideoAvailable(in IBinder sessionToken, int userId);
     void notifyVideoUnavailable(in IBinder sessionToken, int reason, int userId);
+    void notifyVideoFreezeUpdated(in IBinder sessionToken, boolean isFrozen, int userId);
     void notifyContentAllowed(in IBinder sessionToken, int userId);
     void notifyContentBlocked(in IBinder sessionToken, in String rating, int userId);
     void notifySignalStrength(in IBinder sessionToken, int stength, int userId);
@@ -102,6 +105,8 @@
             int UserId);
     void notifyAdResponse(in IBinder sessionToken, in AdResponse response, int UserId);
     void notifyAdBufferConsumed(in IBinder sessionToken, in AdBuffer buffer, int userId);
+    void sendSelectedTrackInfo(in IBinder sessionToken, in List<TvTrackInfo> tracks,
+            int userId);
 
     void createMediaView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
             int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index 052bc3d..3969315 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -49,6 +49,7 @@
     void sendTimeShiftMode(int mode);
     void sendAvailableSpeeds(in float[] speeds);
     void sendSigningResult(in String signingId, in byte[] result);
+    void sendCertificate(in String host, int port, in Bundle certBundle);
     void sendTvRecordingInfo(in TvRecordingInfo recordingInfo);
     void sendTvRecordingInfoList(in List<TvRecordingInfo> recordingInfoList);
     void notifyError(in String errMsg, in Bundle params);
@@ -67,6 +68,7 @@
     void notifyTracksChanged(in List<TvTrackInfo> tracks);
     void notifyVideoAvailable();
     void notifyVideoUnavailable(int reason);
+    void notifyVideoFreezeUpdated(boolean isFrozen);
     void notifyContentAllowed();
     void notifyContentBlocked(in String rating);
     void notifySignalStrength(int strength);
@@ -78,6 +80,7 @@
     void notifyBroadcastInfoResponse(in BroadcastInfoResponse response);
     void notifyAdResponse(in AdResponse response);
     void notifyAdBufferConsumed(in AdBuffer buffer);
+    void sendSelectedTrackInfo(in List<TvTrackInfo> tracks);
 
     void createMediaView(in IBinder windowToken, in Rect frame);
     void relayoutMediaView(in Rect frame);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index 9e43e79..3c91a3e 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -50,6 +50,7 @@
     void onRequestCurrentTvInputId();
     void onRequestTimeShiftMode();
     void onRequestAvailableSpeeds();
+    void onRequestSelectedTrackInfo();
     void onRequestStartRecording(in String requestId, in Uri programUri);
     void onRequestStopRecording(in String recordingId);
     void onRequestScheduleRecording(in String requestId, in String inputId, in Uri channelUri,
@@ -60,5 +61,7 @@
     void onRequestTvRecordingInfo(in String recordingId);
     void onRequestTvRecordingInfoList(in int type);
     void onRequestSigning(in String id, in String algorithm, in String alias, in byte[] data);
+    void onRequestSigning2(in String id, in String algorithm, in String host, int port, in byte[] data);
+    void onRequestCertificate(in String host, int port);
     void onAdRequest(in AdRequest request);
 }
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index 253ade8..ec6c2bf 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -102,6 +102,9 @@
     private static final int DO_NOTIFY_RECORDING_SCHEDULED = 45;
     private static final int DO_SEND_TIME_SHIFT_MODE = 46;
     private static final int DO_SEND_AVAILABLE_SPEEDS = 47;
+    private static final int DO_SEND_SELECTED_TRACK_INFO = 48;
+    private static final int DO_NOTIFY_VIDEO_FREEZE_UPDATED = 49;
+    private static final int DO_SEND_CERTIFICATE = 50;
 
     private final HandlerCaller mCaller;
     private Session mSessionImpl;
@@ -247,6 +250,10 @@
                 args.recycle();
                 break;
             }
+            case DO_SEND_SELECTED_TRACK_INFO: {
+                mSessionImpl.sendSelectedTrackInfo((List<TvTrackInfo>) msg.obj);
+                break;
+            }
             case DO_NOTIFY_VIDEO_AVAILABLE: {
                 mSessionImpl.notifyVideoAvailable();
                 break;
@@ -359,6 +366,17 @@
                 args.recycle();
                 break;
             }
+            case DO_NOTIFY_VIDEO_FREEZE_UPDATED: {
+                mSessionImpl.notifyVideoFreezeUpdated((Boolean) msg.obj);
+                break;
+            }
+            case DO_SEND_CERTIFICATE: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                mSessionImpl.sendCertificate((String) args.arg1, (Integer) args.arg2,
+                        (Bundle) args.arg3);
+                args.recycle();
+                break;
+            }
             default: {
                 Log.w(TAG, "Unhandled message code: " + msg.what);
                 break;
@@ -473,6 +491,12 @@
     }
 
     @Override
+    public void sendCertificate(@NonNull String host, int port, @NonNull Bundle certBundle) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageOOO(DO_SEND_CERTIFICATE, host, port, certBundle));
+    }
+
+    @Override
     public void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
         mCaller.executeOrSendMessage(
                 mCaller.obtainMessageOO(DO_NOTIFY_ERROR, errMsg, params));
@@ -526,6 +550,12 @@
     }
 
     @Override
+    public void sendSelectedTrackInfo(List<TvTrackInfo> tracks) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageO(DO_SEND_SELECTED_TRACK_INFO, tracks));
+    }
+
+    @Override
     public void notifyTracksChanged(List<TvTrackInfo> tracks) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_TRACKS_CHANGED, tracks));
     }
@@ -541,6 +571,12 @@
     }
 
     @Override
+    public void notifyVideoFreezeUpdated(boolean isFrozen) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_VIDEO_FREEZE_UPDATED,
+                isFrozen));
+    }
+
+    @Override
     public void notifyContentAllowed() {
         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_NOTIFY_CONTENT_ALLOWED));
     }
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 7cce84a..498eec6 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -33,8 +33,8 @@
 import android.media.tv.TvInputManager;
 import android.media.tv.TvRecordingInfo;
 import android.media.tv.TvTrackInfo;
-import android.media.tv.interactive.TvInteractiveAppService.Session;
 import android.net.Uri;
+import android.net.http.SslCertificate;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -506,6 +506,18 @@
             }
 
             @Override
+            public void onRequestSelectedTrackInfo(int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postRequestSelectedTrackInfo();
+                }
+            }
+
+            @Override
             public void onRequestCurrentTvInputId(int seq) {
                 synchronized (mSessionCallbackRecordMap) {
                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
@@ -645,6 +657,31 @@
             }
 
             @Override
+            public void onRequestSigning2(
+                    String id, String algorithm, String host, int port, byte[] data, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postRequestSigning(id, algorithm, host, port, data);
+                }
+            }
+
+            @Override
+            public void onRequestCertificate(String host, int port, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postRequestCertificate(host, port);
+                }
+            }
+
+            @Override
             public void onSessionStateChanged(int state, int err, int seq) {
                 synchronized (mSessionCallbackRecordMap) {
                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
@@ -1209,6 +1246,18 @@
             }
         }
 
+        void sendSelectedTrackInfo(@NonNull List<TvTrackInfo> tracks) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.sendSelectedTrackInfo(mToken, tracks, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         void sendCurrentTvInputId(@Nullable String inputId) {
             if (mToken == null) {
                 Log.w(TAG, "The session has been already released");
@@ -1305,6 +1354,19 @@
             }
         }
 
+        void sendCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.sendCertificate(mToken, host, port, SslCertificate.saveState(cert),
+                        mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
             if (mToken == null) {
                 Log.w(TAG, "The session has been already released");
@@ -1708,6 +1770,22 @@
         }
 
         /**
+         * Notifies Interactive app session when the video freeze state is updated
+         * @param isFrozen Whether or not the video is frozen
+         */
+        public void notifyVideoFreezeUpdated(boolean isFrozen) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyVideoFreezeUpdated(mToken, isFrozen, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
          * Notifies Interactive APP session when content is allowed.
          */
         public void notifyContentAllowed() {
@@ -2108,6 +2186,15 @@
             });
         }
 
+        void postRequestSelectedTrackInfo() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onRequestSelectedTrackInfo(mSession);
+                }
+            });
+        }
+
         void postRequestCurrentTvInputId() {
             mHandler.post(new Runnable() {
                 @Override
@@ -2184,6 +2271,26 @@
             });
         }
 
+        void postRequestSigning(String id, String algorithm, String host, int port,
+                byte[] data) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onRequestSigning(mSession, id, algorithm, host,
+                            port, data);
+                }
+            });
+        }
+
+        void postRequestCertificate(String host, int port) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onRequestCertificate(mSession, host, port);
+                }
+            });
+        }
+
         void postRequestTvRecordingInfo(String recordingId) {
             mHandler.post(new Runnable() {
                 @Override
@@ -2378,6 +2485,15 @@
         }
 
         /**
+         * This is called when {@link TvInteractiveAppService.Session#requestSelectedTrackInfo()} is
+         * called.
+         *
+         * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
+         */
+        public void onRequestSelectedTrackInfo(Session session) {
+        }
+
+        /**
          * This is called when {@link TvInteractiveAppService.Session#requestCurrentTvInputId} is
          * called.
          *
@@ -2517,6 +2633,36 @@
         }
 
         /**
+         * This is called when
+         * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, int, byte[])} is
+         * called.
+         *
+         * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
+         * @param signingId the ID to identify the request.
+         * @param algorithm the standard name of the signature algorithm requested, such as
+         *                  MD5withRSA, SHA256withDSA, etc.
+         * @param host The host of the SSL CLient Authentication Server
+         * @param port The port of the SSL Client Authentication Server
+         * @param data the original bytes to be signed.
+         * @hide
+         */
+        public void onRequestSigning(
+                Session session, String signingId, String algorithm, String host,
+                int port, byte[] data) {
+        }
+
+        /**
+         * This is called when the service requests a SSL certificate for client validation.
+         *
+         * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
+         * @param host the host name of the SSL authentication server.
+         * @param port the port of the SSL authentication server. E.g., 443
+         * @hide
+         */
+        public void onRequestCertificate(Session session, String host, int port) {
+        }
+
+        /**
          * This is called when {@link TvInteractiveAppService.Session#notifySessionStateChanged} is
          * called.
          *
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 2419404..7b6dc38 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -46,6 +46,7 @@
 import android.media.tv.TvView;
 import android.media.tv.interactive.TvInteractiveAppView.TvInteractiveAppCallback;
 import android.net.Uri;
+import android.net.http.SslCertificate;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
@@ -112,7 +113,8 @@
             PLAYBACK_COMMAND_TYPE_TUNE_PREV,
             PLAYBACK_COMMAND_TYPE_STOP,
             PLAYBACK_COMMAND_TYPE_SET_STREAM_VOLUME,
-            PLAYBACK_COMMAND_TYPE_SELECT_TRACK
+            PLAYBACK_COMMAND_TYPE_SELECT_TRACK,
+            PLAYBACK_COMMAND_TYPE_FREEZE
     })
     public @interface PlaybackCommandType {}
 
@@ -142,8 +144,11 @@
      * Playback command type: select the given track.
      */
     public static final String PLAYBACK_COMMAND_TYPE_SELECT_TRACK = "select_track";
-
-
+    /**
+     * Playback command type: freeze the video playback on the current frame.
+     * @hide
+     */
+    public static final String PLAYBACK_COMMAND_TYPE_FREEZE = "freeze";
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -730,6 +735,17 @@
         }
 
         /**
+         * Receives the requested Certificate
+         *
+         * @param host the host name of the SSL authentication server.
+         * @param port the port of the SSL authentication server. E.g., 443
+         * @param cert the SSL certificate received.
+         * @hide
+         */
+        public void onCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
+        }
+
+        /**
          * Called when the application sends information of an error.
          *
          * @param errMsg the message of the error.
@@ -878,6 +894,15 @@
         }
 
         /**
+         * Called when video becomes frozen or unfrozen. Audio playback will continue while
+         * video will be frozen to the last frame if {@code true}.
+         * @param isFrozen Whether or not the video is frozen.
+         * @hide
+         */
+        public void onVideoFreezeUpdated(boolean isFrozen) {
+        }
+
+        /**
          * Called when content is allowed.
          */
         public void onContentAllowed() {
@@ -932,6 +957,16 @@
                 @NonNull Bundle data) {
         }
 
+        /**
+         * Called when the TV App sends the selected track info as a response to
+         * requestSelectedTrackInfo.
+         *
+         * @param tracks
+         * @hide
+         */
+        public void onSelectedTrackInfo(List<TvTrackInfo> tracks) {
+        }
+
         @Override
         public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
             return false;
@@ -1338,6 +1373,30 @@
         }
 
         /**
+         * Requests the currently selected {@link TvTrackInfo} from the TV App.
+         *
+         * <p> Normally, track info cannot be synchronized until the channel has
+         * been changed. This is used when the session of the TIAS is newly
+         * created and the normal synchronization has not happened yet.
+         * @hide
+         */
+        @CallSuper
+        public void requestSelectedTrackInfo() {
+            executeOrPostRunnableOnMainThread(() -> {
+                try {
+                    if (DEBUG) {
+                        Log.d(TAG, "requestSelectedTrackInfo");
+                    }
+                    if (mSessionCallback != null) {
+                        mSessionCallback.onRequestSelectedTrackInfo();
+                    }
+                } catch (RemoteException e) {
+                    Log.w(TAG, "error in requestSelectedTrackInfo", e);
+                }
+            });
+        }
+
+        /**
          * Requests starting of recording
          *
          * <p> This is used to request the active {@link android.media.tv.TvRecordingClient} to
@@ -1586,6 +1645,76 @@
         }
 
         /**
+         * Requests signing of the given data.
+         *
+         * <p>This is used when the corresponding server of the broadcast-independent interactive
+         * app requires signing during handshaking, and the interactive app service doesn't have
+         * the built-in private key. The private key is provided by the content providers and
+         * pre-built in the related app, such as TV app.
+         *
+         * @param signingId the ID to identify the request. When a result is received, this ID can
+         *                  be used to correlate the result with the request.
+         * @param algorithm the standard name of the signature algorithm requested, such as
+         *                  MD5withRSA, SHA256withDSA, etc. The name is from standards like
+         *                  FIPS PUB 186-4 and PKCS #1.
+         * @param host the host of the SSL client authentication server.
+         * @param port the port of the SSL client authentication server.
+         * @param data the original bytes to be signed.
+         *
+         * @see #onSigningResult(String, byte[])
+         * @see TvInteractiveAppView#createBiInteractiveApp(Uri, Bundle)
+         * @see TvInteractiveAppView#BI_INTERACTIVE_APP_KEY_ALIAS
+         * @hide
+         */
+        @CallSuper
+        public void requestSigning(@NonNull String signingId, @NonNull String algorithm,
+                @NonNull String host, int port, @NonNull byte[] data) {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) {
+                            Log.d(TAG, "requestSigning");
+                        }
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onRequestSigning2(signingId, algorithm,
+                                    host, port, data);
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in requestSigning", e);
+                    }
+                }
+            });
+        }
+
+        /**
+         * Requests a SSL certificate for client validation.
+         *
+         * @param host the host name of the SSL authentication server.
+         * @param port the port of the SSL authentication server. E.g., 443
+         * @hide
+         */
+        public void requestCertificate(@NonNull String host, int port) {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) {
+                            Log.d(TAG, "requestCertificate");
+                        }
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onRequestCertificate(host, port);
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in requestCertificate", e);
+                    }
+                }
+            });
+        }
+
+        /**
          * Sends an advertisement request to be processed by the related TV input.
          *
          * @param request The advertisement request
@@ -1678,6 +1807,11 @@
             onSigningResult(signingId, result);
         }
 
+        void sendCertificate(String host, int port, Bundle certBundle) {
+            SslCertificate cert = SslCertificate.restoreState(certBundle);
+            onCertificate(host, port, cert);
+        }
+
         void notifyError(String errMsg, Bundle params) {
             onError(errMsg, params);
         }
@@ -1732,6 +1866,13 @@
             onVideoUnavailable(reason);
         }
 
+        void notifyVideoFreezeUpdated(boolean isFrozen) {
+            if (DEBUG) {
+                Log.d(TAG, "notifyVideoFreezeUpdated (isFrozen=" + isFrozen + ")");
+            }
+            onVideoFreezeUpdated(isFrozen);
+        }
+
         void notifyContentAllowed() {
             if (DEBUG) {
                 Log.d(TAG, "notifyContentAllowed");
@@ -1781,6 +1922,13 @@
             onTvMessage(type, data);
         }
 
+        void sendSelectedTrackInfo(List<TvTrackInfo> tracks) {
+            if (DEBUG) {
+                Log.d(TAG, "notifySelectedTrackInfo (tracks= " + tracks + ")");
+            }
+            onSelectedTrackInfo(tracks);
+        }
+
         /**
          * Calls {@link #onAdBufferConsumed}.
          */
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index cbaf5e4..3b29574 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -34,6 +34,7 @@
 import android.media.tv.interactive.TvInteractiveAppManager.Session.FinishedInputEventCallback;
 import android.media.tv.interactive.TvInteractiveAppManager.SessionCallback;
 import android.net.Uri;
+import android.net.http.SslCertificate;
 import android.os.Bundle;
 import android.os.Handler;
 import android.util.AttributeSet;
@@ -582,6 +583,20 @@
     }
 
     /**
+     * Sends the currently selected track info to the TV Interactive App.
+     *
+     * @hide
+     */
+    public void sendSelectedTrackInfo(@Nullable List<TvTrackInfo> tracks) {
+        if (DEBUG) {
+            Log.d(TAG, "sendSelectedTrackInfo");
+        }
+        if (mSession != null) {
+            mSession.sendSelectedTrackInfo(tracks);
+        }
+    }
+
+    /**
      * Sends current TV input ID to related TV interactive app.
      *
      * @param inputId The current TV input ID whose channel is tuned. {@code null} if no channel is
@@ -705,6 +720,22 @@
     }
 
     /**
+     * Alerts the TV Interactive app that the video freeze state has been updated.
+     * If {@code true}, the video is frozen on the last frame while audio playback continues.
+     *
+     * @param isFrozen Whether the video is frozen.
+     * @hide
+     */
+    public void notifyVideoFreezeUpdated(boolean isFrozen) {
+        if (DEBUG) {
+            Log.d(TAG, "notifyVideoFreezeUpdated");
+        }
+        if (mSession != null) {
+            mSession.notifyVideoFreezeUpdated(isFrozen);
+        }
+    }
+
+    /**
      * Sends signing result to related TV interactive app.
      *
      * <p>This is used when the corresponding server of the broadcast-independent interactive
@@ -726,6 +757,22 @@
     }
 
     /**
+     * Send the requested SSL certificate to the TV Interactive App
+     * @param host the host name of the SSL authentication server.
+     * @param port the port of the SSL authentication server. E.g., 443
+     * @param cert the SSL certificate requested
+     * @hide
+     */
+    public void sendCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
+        if (DEBUG) {
+            Log.d(TAG, "sendCertificate");
+        }
+        if (mSession != null) {
+            mSession.sendCertificate(host, port, cert);
+        }
+    }
+
+    /**
      * Notifies the corresponding {@link TvInteractiveAppService} when there is an error.
      *
      * @param errMsg the message of the error.
@@ -1197,6 +1244,16 @@
         }
 
         /**
+         * This is called when {@link TvInteractiveAppService.Session#requestSelectedTrackInfo()} is
+         * called.
+         *
+         * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @hide
+         */
+        public void onRequestSelectedTrackInfo(@NonNull String iAppServiceId) {
+        }
+
+        /**
          * This is called when {@link TvInteractiveAppService.Session#requestCurrentTvInputId()} is
          * called.
          *
@@ -1714,6 +1771,28 @@
         }
 
         @Override
+        public void onRequestSelectedTrackInfo(Session session) {
+            if (DEBUG) {
+                Log.d(TAG, "onRequestSelectedTrackInfo");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onRequestSelectedTrackInfo - session not created");
+                return;
+            }
+            synchronized (mCallbackLock) {
+                if (mCallbackExecutor != null) {
+                    mCallbackExecutor.execute(() -> {
+                        synchronized (mCallbackLock) {
+                            if (mCallback != null) {
+                                mCallback.onRequestSelectedTrackInfo(mIAppServiceId);
+                            }
+                        }
+                    });
+                }
+            }
+        }
+
+        @Override
         public void onRequestCurrentTvInputId(Session session) {
             if (DEBUG) {
                 Log.d(TAG, "onRequestCurrentTvInputId");
diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java
index f9eaabd..0b9429d 100644
--- a/media/java/android/media/tv/tuner/Lnb.java
+++ b/media/java/android/media/tv/tuner/Lnb.java
@@ -264,6 +264,25 @@
         }
     }
 
+    /* package */ void closeInternal() {
+        synchronized (mLock) {
+            if (mIsClosed) {
+                return;
+            }
+            int res = nativeClose();
+            if (res != Tuner.RESULT_SUCCESS) {
+                TunerUtils.throwExceptionForResult(res, "Failed to close LNB");
+            } else {
+                mIsClosed = true;
+                if (mOwner != null) {
+                    mOwner.releaseLnb();
+                    mOwner = null;
+                }
+                mCallbackMap.clear();
+            }
+        }
+    }
+
     /**
      * Sets the LNB's power voltage.
      *
@@ -330,22 +349,7 @@
     public void close() {
         acquireTRMSLock("close()");
         try {
-            synchronized (mLock) {
-                if (mIsClosed) {
-                    return;
-                }
-                int res = nativeClose();
-                if (res != Tuner.RESULT_SUCCESS) {
-                    TunerUtils.throwExceptionForResult(res, "Failed to close LNB");
-                } else {
-                    mIsClosed = true;
-                    if (mOwner != null) {
-                        mOwner.releaseLnb();
-                        mOwner = null;
-                    }
-                    mCallbackMap.clear();
-                }
-            }
+            closeInternal();
         } finally {
             releaseTRMSLock();
         }
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 09f09b9..f28c2c1 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -30,6 +30,7 @@
 import android.hardware.tv.tuner.Constant;
 import android.hardware.tv.tuner.Constant64Bit;
 import android.hardware.tv.tuner.FrontendScanType;
+import android.media.MediaCodec;
 import android.media.tv.TvInputService;
 import android.media.tv.tuner.dvr.DvrPlayback;
 import android.media.tv.tuner.dvr.DvrRecorder;
@@ -272,8 +273,12 @@
         try {
             System.loadLibrary("media_tv_tuner");
             nativeInit();
+            // Load and initialize MediaCodec to avoid flaky cts test result.
+            Class.forName(MediaCodec.class.getName());
         } catch (UnsatisfiedLinkError e) {
             Log.d(TAG, "tuner JNI library not found!");
+        } catch (ClassNotFoundException e) {
+            Log.e(TAG, "MediaCodec class not found!", e);
         }
     }
 
@@ -914,7 +919,7 @@
                 if (DEBUG) {
                     Log.d(TAG, "calling mLnb.close() : " + mClientId);
                 }
-                mLnb.close();
+                mLnb.closeInternal();
             } else {
                 if (DEBUG) {
                     Log.d(TAG, "NOT calling mLnb.close() : " + mClientId);
@@ -2348,6 +2353,7 @@
     @Nullable
     public Lnb openLnbByName(@NonNull String name, @CallbackExecutor @NonNull Executor executor,
             @NonNull LnbCallback cb) {
+        acquireTRMSLock("openLnbByName");
         mLnbLock.lock();
         try {
             Objects.requireNonNull(name, "LNB name must not be null");
@@ -2356,7 +2362,7 @@
             Lnb newLnb = nativeOpenLnbByName(name);
             if (newLnb != null) {
                 if (mLnb != null) {
-                    mLnb.close();
+                    mLnb.closeInternal();
                     mLnbHandle = null;
                 }
                 mLnb = newLnb;
@@ -2367,6 +2373,7 @@
             }
             return mLnb;
         } finally {
+            releaseTRMSLock();
             mLnbLock.unlock();
         }
     }
@@ -2784,8 +2791,8 @@
         }
     }
 
+    // Must be called while TRMS lock is being held
     /* package */ void releaseLnb() {
-        acquireTRMSLock("releaseLnb()");
         mLnbLock.lock();
         try {
             if (mLnbHandle != null) {
@@ -2802,7 +2809,6 @@
             }
             mLnb = null;
         } finally {
-            releaseTRMSLock();
             mLnbLock.unlock();
         }
     }
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index ef90bf9..8cdd59e 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -163,6 +163,13 @@
 static struct {
     jclass clazz;
     jmethodID ctorId;
+    jmethodID sizeId;
+    jmethodID addId;
+} gArrayDequeInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID ctorId;
     jmethodID setInternalStateId;
     jfieldID contextId;
     jfieldID validId;
@@ -200,8 +207,14 @@
     jfieldID queueRequestIndexID;
     jfieldID outputFrameLinearBlockID;
     jfieldID outputFrameHardwareBufferID;
+    jfieldID outputFramebufferInfosID;
     jfieldID outputFrameChangedKeysID;
     jfieldID outputFrameFormatID;
+    jfieldID bufferInfoFlags;
+    jfieldID bufferInfoOffset;
+    jfieldID bufferInfoSize;
+    jfieldID bufferInfoPresentationTimeUs;
+
 };
 
 static fields_t gFields;
@@ -412,6 +425,22 @@
             index, offset, size, timeUs, flags, errorDetailMsg);
 }
 
+status_t JMediaCodec::queueInputBuffers(
+        size_t index,
+        size_t offset,
+        size_t size,
+        const sp<RefBase> &infos,
+        AString *errorDetailMsg) {
+
+    sp<BufferInfosWrapper> auInfo((BufferInfosWrapper *)infos.get());
+    return mCodec->queueInputBuffers(
+            index,
+            offset,
+            size,
+            auInfo,
+            errorDetailMsg);
+}
+
 status_t JMediaCodec::queueSecureInputBuffer(
         size_t index,
         size_t offset,
@@ -430,10 +459,11 @@
 }
 
 status_t JMediaCodec::queueBuffer(
-        size_t index, const std::shared_ptr<C2Buffer> &buffer, int64_t timeUs,
-        uint32_t flags, const sp<AMessage> &tunings, AString *errorDetailMsg) {
+        size_t index, const std::shared_ptr<C2Buffer> &buffer,
+        const sp<RefBase> &infos, const sp<AMessage> &tunings, AString *errorDetailMsg) {
+    sp<BufferInfosWrapper> auInfo((BufferInfosWrapper *)infos.get());
     return mCodec->queueBuffer(
-            index, buffer, timeUs, flags, tunings, errorDetailMsg);
+            index, buffer, auInfo, tunings, errorDetailMsg);
 }
 
 status_t JMediaCodec::queueEncryptedLinearBlock(
@@ -446,13 +476,13 @@
         const uint8_t iv[16],
         CryptoPlugin::Mode mode,
         const CryptoPlugin::Pattern &pattern,
-        int64_t presentationTimeUs,
-        uint32_t flags,
+        const sp<RefBase> &infos,
         const sp<AMessage> &tunings,
         AString *errorDetailMsg) {
+    sp<BufferInfosWrapper> auInfo((BufferInfosWrapper *)infos.get());
     return mCodec->queueEncryptedBuffer(
             index, buffer, offset, subSamples, numSubSamples, key, iv, mode, pattern,
-            presentationTimeUs, flags, tunings, errorDetailMsg);
+            auInfo, tunings, errorDetailMsg);
 }
 
 status_t JMediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) {
@@ -722,6 +752,42 @@
     return OK;
 }
 
+void maybeSetBufferInfos(JNIEnv *env, jobject &frame, const sp<BufferInfosWrapper> &bufInfos) {
+    if (!bufInfos) {
+        return;
+    }
+    std::vector<AccessUnitInfo> &infos = bufInfos.get()->value;
+    if (infos.empty()) {
+        return;
+    }
+    ScopedLocalRef<jobject> dequeObj{env, env->NewObject(
+            gArrayDequeInfo.clazz, gArrayDequeInfo.ctorId)};
+    jint offset = 0;
+    std::vector<jobject> jObjectInfos;
+    for (int i = 0 ; i < infos.size(); i++) {
+        jobject bufferInfo = env->NewObject(
+                gBufferInfo.clazz, gBufferInfo.ctorId);
+        if (bufferInfo != NULL) {
+            env->CallVoidMethod(bufferInfo, gBufferInfo.setId,
+                    offset,
+                    (jint)(infos)[i].mSize,
+                    (infos)[i].mTimestamp,
+                    (infos)[i].mFlags);
+            (void)env->CallBooleanMethod(
+                    dequeObj.get(), gArrayDequeInfo.addId, bufferInfo);
+            offset += (infos)[i].mSize;
+            jObjectInfos.push_back(bufferInfo);
+        }
+    }
+    env->SetObjectField(
+            frame,
+            gFields.outputFramebufferInfosID,
+            dequeObj.get());
+    for (int i = 0; i < jObjectInfos.size(); i++) {
+        env->DeleteLocalRef(jObjectInfos[i]);
+    }
+}
+
 status_t JMediaCodec::getOutputFrame(
         JNIEnv *env, jobject frame, size_t index) const {
     sp<MediaCodecBuffer> buffer;
@@ -732,6 +798,11 @@
     }
 
     if (buffer->size() > 0) {
+        sp<RefBase> obj;
+        sp<BufferInfosWrapper> bufInfos;
+        if (buffer->meta()->findObject("accessUnitInfo", &obj)) {
+            bufInfos = std::move(((decltype(bufInfos.get()))obj.get()));
+        }
         std::shared_ptr<C2Buffer> c2Buffer = buffer->asC2Buffer();
         if (c2Buffer) {
             switch (c2Buffer->data().type()) {
@@ -747,6 +818,7 @@
                             (jlong)context.release(),
                             true);
                     env->SetObjectField(frame, gFields.outputFrameLinearBlockID, linearBlock.get());
+                    maybeSetBufferInfos(env, frame, bufInfos);
                     break;
                 }
                 case C2BufferData::GRAPHIC: {
@@ -787,6 +859,7 @@
                         (jlong)context.release(),
                         true);
                 env->SetObjectField(frame, gFields.outputFrameLinearBlockID, linearBlock.get());
+                maybeSetBufferInfos(env, frame, bufInfos);
             } else {
                 // No-op.
             }
@@ -1250,6 +1323,7 @@
 void JMediaCodec::handleCallback(const sp<AMessage> &msg) {
     int32_t arg1, arg2 = 0;
     jobject obj = NULL;
+    std::vector<jobject> jObjectInfos;
     CHECK(msg->findInt32("callbackID", &arg1));
     JNIEnv *env = AndroidRuntime::getJNIEnv();
 
@@ -1287,6 +1361,35 @@
             break;
         }
 
+        case MediaCodec::CB_LARGE_FRAME_OUTPUT_AVAILABLE:
+        {
+            sp<RefBase> spobj = nullptr;
+            CHECK(msg->findInt32("index", &arg2));
+            CHECK(msg->findObject("accessUnitInfo", &spobj));
+            if (spobj != nullptr) {
+                sp<BufferInfosWrapper> bufferInfoParamsWrapper {
+                        (BufferInfosWrapper *)spobj.get()};
+                std::vector<AccessUnitInfo> &bufferInfoParams =
+                        bufferInfoParamsWrapper.get()->value;
+                obj = env->NewObject(gArrayDequeInfo.clazz, gArrayDequeInfo.ctorId);
+                jint offset = 0;
+                for (int i = 0 ; i < bufferInfoParams.size(); i++) {
+                    jobject bufferInfo = env->NewObject(gBufferInfo.clazz, gBufferInfo.ctorId);
+                    if (bufferInfo != NULL) {
+                        env->CallVoidMethod(bufferInfo, gBufferInfo.setId,
+                                            offset,
+                                            (jint)(bufferInfoParams)[i].mSize,
+                                            (bufferInfoParams)[i].mTimestamp,
+                                            (bufferInfoParams)[i].mFlags);
+                        (void)env->CallBooleanMethod(obj, gArrayDequeInfo.addId, bufferInfo);
+                        offset += (bufferInfoParams)[i].mSize;
+                        jObjectInfos.push_back(bufferInfo);
+                    }
+                }
+            }
+            break;
+        }
+
         case MediaCodec::CB_CRYPTO_ERROR:
         {
             int32_t err, actionCode;
@@ -1346,6 +1449,9 @@
             arg2,
             obj);
 
+    for (int i = 0; i < jObjectInfos.size(); i++) {
+        env->DeleteLocalRef(jObjectInfos[i]);
+    }
     env->DeleteLocalRef(obj);
 }
 
@@ -1913,6 +2019,103 @@
             codec->getExceptionMessage(errorDetailMsg.c_str()).c_str());
 }
 
+static status_t extractInfosFromObject(
+        JNIEnv * const env,
+        jint * const initialOffset,
+        jint * const totalSize,
+        std::vector<AccessUnitInfo> * const infos,
+        const jobjectArray &objArray,
+        AString * const errorDetailMsg) {
+    if (totalSize == nullptr
+            || initialOffset == nullptr
+            || infos == nullptr) {
+        if (errorDetailMsg) {
+            *errorDetailMsg = "Error: Null arguments provided for extracting Access unit info";
+        }
+        return BAD_VALUE;
+    }
+    const jsize numEntries = env->GetArrayLength(objArray);
+    if (numEntries <= 0) {
+        if (errorDetailMsg) {
+            *errorDetailMsg = "Error: No BufferInfo found while queuing for large frame input";
+        }
+        return BAD_VALUE;
+    }
+    *initialOffset = 0;
+    *totalSize = 0;
+    for (jsize i = 0; i < numEntries; i++) {
+        jobject param = env->GetObjectArrayElement(objArray, i);
+        if (param == NULL) {
+            if (errorDetailMsg) {
+                *errorDetailMsg = "Error: Queuing a null BufferInfo";
+            }
+            return BAD_VALUE;
+        }
+        size_t offset = static_cast<size_t>(env->GetIntField(param, gFields.bufferInfoOffset));
+        size_t size = static_cast<size_t>(env->GetIntField(param, gFields.bufferInfoSize));
+        uint32_t flags = static_cast<uint32_t>(env->GetIntField(param, gFields.bufferInfoFlags));
+        if (flags == 0 && size == 0) {
+            if (errorDetailMsg) {
+                *errorDetailMsg = "Error: Queuing an empty BufferInfo";
+            }
+            return BAD_VALUE;
+        }
+        if (i == 0) {
+            *initialOffset = offset;
+        }
+        if (CC_UNLIKELY((offset >  UINT32_MAX)
+                || ((long)(offset + size) > UINT32_MAX)
+                || ((offset - *initialOffset) != *totalSize))) {
+            if (errorDetailMsg) {
+                *errorDetailMsg = "Error: offset/size in BufferInfo";
+            }
+            return BAD_VALUE;
+        }
+        infos->emplace_back(
+                flags,
+                size,
+                env->GetLongField(param, gFields.bufferInfoPresentationTimeUs));
+        *totalSize += size;
+    }
+    return OK;
+}
+
+static void android_media_MediaCodec_queueInputBuffers(
+        JNIEnv *env,
+        jobject thiz,
+        jint index,
+        jobjectArray objArray) {
+    ALOGV("android_media_MediaCodec_queueInputBuffers");
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+    if (codec == NULL || codec->initCheck() != OK || objArray == NULL) {
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
+        return;
+    }
+    sp<BufferInfosWrapper> infoObj =
+            new BufferInfosWrapper{decltype(infoObj->value)()};
+    AString errorDetailMsg;
+    jint initialOffset = 0;
+    jint totalSize = 0;
+    status_t err = extractInfosFromObject(
+            env,
+            &initialOffset,
+            &totalSize,
+            &infoObj->value,
+            objArray,
+            &errorDetailMsg);
+    if (err == OK) {
+        err = codec->queueInputBuffers(
+            index,
+            initialOffset,
+            totalSize,
+            infoObj,
+            &errorDetailMsg);
+    }
+    throwExceptionAsNecessary(
+            env, err, ACTION_CODE_FATAL,
+            codec->getExceptionMessage(errorDetailMsg.c_str()).c_str());
+}
+
 struct NativeCryptoInfo {
     NativeCryptoInfo(JNIEnv *env, jobject cryptoInfoObj)
         : mEnv{env},
@@ -2559,8 +2762,7 @@
 
 static void android_media_MediaCodec_native_queueLinearBlock(
         JNIEnv *env, jobject thiz, jint index, jobject bufferObj,
-        jint offset, jint size, jobject cryptoInfoObj,
-        jlong presentationTimeUs, jint flags, jobject keys, jobject values) {
+        jobject cryptoInfoObj, jobjectArray objArray, jobject keys, jobject values) {
     ALOGV("android_media_MediaCodec_native_queueLinearBlock");
 
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
@@ -2578,7 +2780,24 @@
                 "error occurred while converting tunings from Java to native");
         return;
     }
-
+    jint totalSize;
+    jint initialOffset;
+    std::vector<AccessUnitInfo> infoVec;
+    AString errorDetailMsg;
+    err = extractInfosFromObject(env,
+            &initialOffset,
+            &totalSize,
+            &infoVec,
+            objArray,
+            &errorDetailMsg);
+    if (err != OK) {
+        throwExceptionAsNecessary(
+                env, INVALID_OPERATION, ACTION_CODE_FATAL,
+                codec->getExceptionMessage(errorDetailMsg.c_str()).c_str());
+        return;
+    }
+    sp<BufferInfosWrapper> infos =
+            new BufferInfosWrapper{std::move(infoVec)};
     std::shared_ptr<C2Buffer> buffer;
     sp<hardware::HidlMemory> memory;
     ScopedLocalRef<jobject> lock{env, env->GetObjectField(bufferObj, gLinearBlockInfo.lockId)};
@@ -2587,10 +2806,10 @@
             JMediaCodecLinearBlock *context =
                 (JMediaCodecLinearBlock *)env->GetLongField(bufferObj, gLinearBlockInfo.contextId);
             if (codec->hasCryptoOrDescrambler()) {
-                extractMemoryFromContext(context, offset, size, &memory);
-                offset += context->mHidlMemoryOffset;
+                extractMemoryFromContext(context, initialOffset, totalSize, &memory);
+                initialOffset += context->mHidlMemoryOffset;
             } else {
-                extractBufferFromContext(context, offset, size, &buffer);
+                extractBufferFromContext(context, initialOffset, totalSize, &buffer);
             }
         }
         env->MonitorExit(lock.get());
@@ -2601,7 +2820,6 @@
         return;
     }
 
-    AString errorDetailMsg;
     if (codec->hasCryptoOrDescrambler()) {
         if (!memory) {
             // It means there was an unexpected failure in extractMemoryFromContext above
@@ -2615,7 +2833,7 @@
             return;
         }
         auto cryptoInfo =
-                cryptoInfoObj ? NativeCryptoInfo{env, cryptoInfoObj} : NativeCryptoInfo{size};
+                cryptoInfoObj ? NativeCryptoInfo{env, cryptoInfoObj} : NativeCryptoInfo{totalSize};
         if (env->ExceptionCheck()) {
             // Creation of cryptoInfo failed. Let the exception bubble up.
             return;
@@ -2623,13 +2841,12 @@
         err = codec->queueEncryptedLinearBlock(
                 index,
                 memory,
-                offset,
+                initialOffset,
                 cryptoInfo.mSubSamples, cryptoInfo.mNumSubSamples,
                 (const uint8_t *)cryptoInfo.mKey, (const uint8_t *)cryptoInfo.mIv,
                 cryptoInfo.mMode,
                 cryptoInfo.mPattern,
-                presentationTimeUs,
-                flags,
+                infos,
                 tunings,
                 &errorDetailMsg);
         ALOGI_IF(err != OK, "queueEncryptedLinearBlock returned err = %d", err);
@@ -2646,7 +2863,7 @@
             return;
         }
         err = codec->queueBuffer(
-                index, buffer, presentationTimeUs, flags, tunings, &errorDetailMsg);
+                index, buffer, infos, tunings, &errorDetailMsg);
     }
     throwExceptionAsNecessary(
             env, err, ACTION_CODE_FATAL,
@@ -2704,8 +2921,11 @@
     std::shared_ptr<C2Buffer> buffer = C2Buffer::CreateGraphicBuffer(block->share(
             block->crop(), C2Fence{}));
     AString errorDetailMsg;
+    sp<BufferInfosWrapper> infos =
+        new BufferInfosWrapper{decltype(infos->value)()};
+    infos->value.emplace_back(flags, 0 /*not used*/, presentationTimeUs);
     err = codec->queueBuffer(
-            index, buffer, presentationTimeUs, flags, tunings, &errorDetailMsg);
+            index, buffer, infos, tunings, &errorDetailMsg);
     throwExceptionAsNecessary(
             env, err, ACTION_CODE_FATAL,
             codec->getExceptionMessage(errorDetailMsg.c_str()).c_str());
@@ -3214,6 +3434,10 @@
         env->GetFieldID(clazz.get(), "mLinearBlock", "Landroid/media/MediaCodec$LinearBlock;");
     CHECK(gFields.outputFrameLinearBlockID != NULL);
 
+    gFields.outputFramebufferInfosID =
+        env->GetFieldID(clazz.get(), "mBufferInfos", "Ljava/util/ArrayDeque;");
+    CHECK(gFields.outputFramebufferInfosID != NULL);
+
     gFields.outputFrameHardwareBufferID =
         env->GetFieldID(clazz.get(), "mHardwareBuffer", "Landroid/hardware/HardwareBuffer;");
     CHECK(gFields.outputFrameHardwareBufferID != NULL);
@@ -3401,6 +3625,19 @@
     gArrayListInfo.addId = env->GetMethodID(clazz.get(), "add", "(Ljava/lang/Object;)Z");
     CHECK(gArrayListInfo.addId != NULL);
 
+    clazz.reset(env->FindClass("java/util/ArrayDeque"));
+    CHECK(clazz.get() != NULL);
+    gArrayDequeInfo.clazz = (jclass)env->NewGlobalRef(clazz.get());
+
+    gArrayDequeInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V");
+    CHECK(gArrayDequeInfo.ctorId != NULL);
+
+    gArrayDequeInfo.sizeId = env->GetMethodID(clazz.get(), "size", "()I");
+    CHECK(gArrayDequeInfo.sizeId != NULL);
+
+    gArrayDequeInfo.addId = env->GetMethodID(clazz.get(), "add", "(Ljava/lang/Object;)Z");
+    CHECK(gArrayDequeInfo.addId != NULL);
+
     clazz.reset(env->FindClass("android/media/MediaCodec$LinearBlock"));
     CHECK(clazz.get() != NULL);
 
@@ -3444,6 +3681,12 @@
 
     gBufferInfo.setId = env->GetMethodID(clazz.get(), "set", "(IIJI)V");
     CHECK(gBufferInfo.setId != NULL);
+
+    gFields.bufferInfoSize = env->GetFieldID(clazz.get(), "size", "I");
+    gFields.bufferInfoFlags = env->GetFieldID(clazz.get(), "flags", "I");
+    gFields.bufferInfoOffset = env->GetFieldID(clazz.get(), "offset", "I");
+    gFields.bufferInfoPresentationTimeUs =
+            env->GetFieldID(clazz.get(), "presentationTimeUs", "J");
 }
 
 static void android_media_MediaCodec_native_setup(
@@ -3701,6 +3944,9 @@
     { "native_queueInputBuffer", "(IIIJI)V",
       (void *)android_media_MediaCodec_queueInputBuffer },
 
+    { "native_queueInputBuffers", "(I[Ljava/lang/Object;)V",
+      (void *)android_media_MediaCodec_queueInputBuffers },
+
     { "native_queueSecureInputBuffer", "(IILandroid/media/MediaCodec$CryptoInfo;JI)V",
       (void *)android_media_MediaCodec_queueSecureInputBuffer },
 
@@ -3711,8 +3957,8 @@
     { "native_closeMediaImage", "(J)V", (void *)android_media_MediaCodec_closeMediaImage },
 
     { "native_queueLinearBlock",
-      "(ILandroid/media/MediaCodec$LinearBlock;IILandroid/media/MediaCodec$CryptoInfo;JI"
-      "Ljava/util/ArrayList;Ljava/util/ArrayList;)V",
+      "(ILandroid/media/MediaCodec$LinearBlock;Landroid/media/MediaCodec$CryptoInfo;"
+      "[Ljava/lang/Object;Ljava/util/ArrayList;Ljava/util/ArrayList;)V",
       (void *)android_media_MediaCodec_native_queueLinearBlock },
 
     { "native_queueHardwareBuffer",
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index fbaf64f..02708ef 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -35,6 +35,7 @@
 namespace android {
 
 struct ABuffer;
+struct AccessUnitInfo;
 struct ALooper;
 struct AMessage;
 struct AString;
@@ -93,6 +94,13 @@
             size_t offset, size_t size, int64_t timeUs, uint32_t flags,
             AString *errorDetailMsg);
 
+    status_t queueInputBuffers(
+            size_t index,
+            size_t offset,
+            size_t size,
+            const sp<RefBase> &auInfo,
+            AString *errorDetailMsg = NULL);
+
     status_t queueSecureInputBuffer(
             size_t index,
             size_t offset,
@@ -108,7 +116,7 @@
 
     status_t queueBuffer(
             size_t index, const std::shared_ptr<C2Buffer> &buffer,
-            int64_t timeUs, uint32_t flags, const sp<AMessage> &tunings,
+            const sp<RefBase> &infos, const sp<AMessage> &tunings,
             AString *errorDetailMsg);
 
     status_t queueEncryptedLinearBlock(
@@ -121,8 +129,7 @@
             const uint8_t iv[16],
             CryptoPlugin::Mode mode,
             const CryptoPlugin::Pattern &pattern,
-            int64_t presentationTimeUs,
-            uint32_t flags,
+            const sp<RefBase> &infos,
             const sp<AMessage> &tunings,
             AString *errorDetailMsg);
 
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 3fcb871..00b0e57 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -967,7 +967,7 @@
     ScopedLocalRef<jobject> filter(env);
     {
         android::Mutex::Autolock autoLock(mLock);
-        if (env->IsSameObject(filter.get(), nullptr)) {
+        if (env->IsSameObject(mFilterObj, nullptr)) {
             ALOGE("FilterClientCallbackImpl::onFilterStatus:"
                   "Filter object has been freed. Ignoring callback.");
             return;
diff --git a/media/jni/soundpool/SoundDecoder.cpp b/media/jni/soundpool/SoundDecoder.cpp
index 5ed10b0..ae57634 100644
--- a/media/jni/soundpool/SoundDecoder.cpp
+++ b/media/jni/soundpool/SoundDecoder.cpp
@@ -29,14 +29,15 @@
 // before the SoundDecoder thread closes.
 static constexpr int32_t kWaitTimeBeforeCloseMs = 1000;
 
-SoundDecoder::SoundDecoder(SoundManager* soundManager, size_t threads)
+SoundDecoder::SoundDecoder(SoundManager* soundManager, size_t threads, int32_t threadPriority)
     : mSoundManager(soundManager)
 {
     ALOGV("%s(%p, %zu)", __func__, soundManager, threads);
     // ThreadPool is created, but we don't launch any threads.
     mThreadPool = std::make_unique<ThreadPool>(
             std::min(threads, (size_t)std::thread::hardware_concurrency()),
-            "SoundDecoder_");
+            "SoundDecoder_",
+            threadPriority);
 }
 
 SoundDecoder::~SoundDecoder()
diff --git a/media/jni/soundpool/SoundDecoder.h b/media/jni/soundpool/SoundDecoder.h
index 7b62114..3f44a0d 100644
--- a/media/jni/soundpool/SoundDecoder.h
+++ b/media/jni/soundpool/SoundDecoder.h
@@ -28,7 +28,7 @@
  */
 class SoundDecoder {
 public:
-    SoundDecoder(SoundManager* soundManager, size_t threads);
+    SoundDecoder(SoundManager* soundManager, size_t threads, int32_t threadPriority);
     ~SoundDecoder();
     void loadSound(int32_t soundID) NO_THREAD_SAFETY_ANALYSIS; // uses unique_lock
     void quit();
diff --git a/media/jni/soundpool/SoundManager.cpp b/media/jni/soundpool/SoundManager.cpp
index 5b16174..fa35813 100644
--- a/media/jni/soundpool/SoundManager.cpp
+++ b/media/jni/soundpool/SoundManager.cpp
@@ -29,7 +29,7 @@
 static const size_t kDecoderThreads = std::thread::hardware_concurrency() >= 4 ? 2 : 1;
 
 SoundManager::SoundManager()
-    : mDecoder{std::make_unique<SoundDecoder>(this, kDecoderThreads)}
+    : mDecoder{std::make_unique<SoundDecoder>(this, kDecoderThreads, ANDROID_PRIORITY_NORMAL)}
 {
     ALOGV("%s()", __func__);
 }
diff --git a/media/jni/soundpool/StreamManager.cpp b/media/jni/soundpool/StreamManager.cpp
index 52060f1..e11ccbc 100644
--- a/media/jni/soundpool/StreamManager.cpp
+++ b/media/jni/soundpool/StreamManager.cpp
@@ -35,10 +35,9 @@
 // In R, we change this to true, as it is the correct way per SoundPool documentation.
 static constexpr bool kStealActiveStream_OldestFirst = true;
 
-// kPlayOnCallingThread = true prior to R.
 // Changing to false means calls to play() are almost instantaneous instead of taking around
 // ~10ms to launch the AudioTrack. It is perhaps 100x faster.
-static constexpr bool kPlayOnCallingThread = true;
+static constexpr bool kPlayOnCallingThread = false;
 
 // Amount of time for a StreamManager thread to wait before closing.
 static constexpr int64_t kWaitTimeBeforeCloseNs = 9 * NANOS_PER_SECOND;
@@ -127,7 +126,8 @@
     mThreadPool = std::make_unique<ThreadPool>(
             std::min((size_t)streams,  // do not make more threads than streams to play
                     std::min(threads, (size_t)std::thread::hardware_concurrency())),
-            "SoundPool_");
+            "SoundPool_",
+            ANDROID_PRIORITY_AUDIO);
 }
 
 #pragma clang diagnostic pop
diff --git a/media/jni/soundpool/StreamManager.h b/media/jni/soundpool/StreamManager.h
index adbab4b..a4cb286 100644
--- a/media/jni/soundpool/StreamManager.h
+++ b/media/jni/soundpool/StreamManager.h
@@ -46,9 +46,9 @@
  */
 class JavaThread {
 public:
-    JavaThread(std::function<void()> f, const char *name)
+    JavaThread(std::function<void()> f, const char *name, int32_t threadPriority)
         : mF{std::move(f)} {
-        createThreadEtc(staticFunction, this, name);
+        createThreadEtc(staticFunction, this, name, threadPriority);
     }
 
     JavaThread(JavaThread &&) = delete; // uses "this" ptr, not moveable.
@@ -109,9 +109,11 @@
  */
 class ThreadPool {
 public:
-    ThreadPool(size_t maxThreadCount, std::string name)
+    ThreadPool(size_t maxThreadCount, std::string name,
+            int32_t threadPriority = ANDROID_PRIORITY_NORMAL)
         : mMaxThreadCount(maxThreadCount)
-        , mName{std::move(name)} { }
+        , mName{std::move(name)}
+        , mThreadPriority(threadPriority) {}
 
     ~ThreadPool() { quit(); }
 
@@ -159,7 +161,8 @@
             const int32_t id = mNextThreadId;
             mThreads.emplace_back(std::make_unique<JavaThread>(
                     [this, id, mf = std::move(f)] { mf(id); --mActiveThreadCount; },
-                    (mName + std::to_string(id)).c_str()));
+                    (mName + std::to_string(id)).c_str(),
+                    mThreadPriority));
             ++mActiveThreadCount;
             return id;
         }
@@ -180,6 +183,7 @@
 private:
     const size_t            mMaxThreadCount;
     const std::string       mName;
+    const int32_t           mThreadPriority;
 
     std::atomic_size_t      mActiveThreadCount = 0;
 
diff --git a/media/jni/soundpool/android_media_SoundPool.cpp b/media/jni/soundpool/android_media_SoundPool.cpp
index 25040a9..e872a58 100644
--- a/media/jni/soundpool/android_media_SoundPool.cpp
+++ b/media/jni/soundpool/android_media_SoundPool.cpp
@@ -86,7 +86,7 @@
     }
 
     // Retrieves the associated object, returns nullValue T if not available.
-    T get(JNIEnv *env, jobject thiz) {
+    T get(JNIEnv *env, jobject thiz) const {
         std::lock_guard lg(mLock);
         // NOLINTNEXTLINE(performance-no-int-to-ptr)
         auto ptr = reinterpret_cast<T*>(env->GetLongField(thiz, mFieldId));
@@ -167,8 +167,10 @@
 //    is possible by checking if the WeakGlobalRef is null equivalent.
 
 auto& getSoundPoolManager() {
-    static ObjectManager<std::shared_ptr<SoundPool>> soundPoolManager(fields.mNativeContext);
-    return soundPoolManager;
+    // never-delete singleton
+    static auto soundPoolManager =
+            new ObjectManager<std::shared_ptr<SoundPool>>(fields.mNativeContext);
+    return *soundPoolManager;
 }
 
 inline auto getSoundPool(JNIEnv *env, jobject thiz) {
@@ -274,8 +276,9 @@
 auto& getSoundPoolJavaRefManager() {
     // Note this can store shared_ptrs to either jweak and jobject,
     // as the underlying type is identical.
-    static ConcurrentHashMap<SoundPool *, std::shared_ptr<JWeakValue>> concurrentHashMap;
-    return concurrentHashMap;
+    static auto concurrentHashMap =
+            new ConcurrentHashMap<SoundPool *, std::shared_ptr<JWeakValue>>();
+    return *concurrentHashMap;
 }
 
 // make_shared_globalref_from_localref() creates a sharable Java global
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
index 262f5f1..096e8ad 100644
--- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
@@ -50,8 +50,16 @@
     private static final String TAG = "BluetoothMidiDevice";
     private static final boolean DEBUG = false;
 
-    private static final int DEFAULT_PACKET_SIZE = 20;
-    private static final int MAX_PACKET_SIZE = 512;
+    // Bluetooth services should subtract 5 bytes from the MTU for headers.
+    private static final int HEADER_SIZE = 5;
+    // Min MTU size for BLE
+    private static final int MIN_L2CAP_MTU = 23;
+    // 23 (min L2CAP MTU) - 5 (header size)
+    private static final int DEFAULT_PACKET_SIZE = MIN_L2CAP_MTU - HEADER_SIZE;
+    // Max MTU size on Android
+    private static final int MAX_ANDROID_MTU = 517;
+    // 517 (max Android MTU) - 5 (header size)
+    private static final int MAX_PACKET_SIZE = MAX_ANDROID_MTU - HEADER_SIZE;
 
     //  Bluetooth MIDI Gatt service UUID
     private static final UUID MIDI_SERVICE = UUID.fromString(
@@ -135,8 +143,8 @@
                         // switch to receiving notifications
                         mBluetoothGatt.readCharacteristic(characteristic);
 
-                        // Request higher MTU size
-                        if (!gatt.requestMtu(MAX_PACKET_SIZE)) {
+                        // Request max MTU size
+                        if (!gatt.requestMtu(MAX_ANDROID_MTU)) {
                             Log.e(TAG, "request mtu failed");
                             mPacketEncoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
                             mPacketDecoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
@@ -204,8 +212,15 @@
         public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
             Log.d(TAG, "onMtuChanged callback received. mtu: " + mtu + ", status: " + status);
             if (status == BluetoothGatt.GATT_SUCCESS) {
-                mPacketEncoder.setMaxPacketSize(Math.min(mtu, MAX_PACKET_SIZE));
-                mPacketDecoder.setMaxPacketSize(Math.min(mtu, MAX_PACKET_SIZE));
+                int packetSize = Math.min(mtu - HEADER_SIZE, MAX_PACKET_SIZE);
+                if (packetSize <= 0) {
+                    Log.e(TAG, "onMtuChanged non-positive packet size: " + packetSize);
+                    packetSize = DEFAULT_PACKET_SIZE;
+                } else if (packetSize < DEFAULT_PACKET_SIZE) {
+                    Log.w(TAG, "onMtuChanged small packet size: " + packetSize);
+                }
+                mPacketEncoder.setMaxPacketSize(packetSize);
+                mPacketDecoder.setMaxPacketSize(packetSize);
             } else {
                 mPacketEncoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
                 mPacketDecoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
diff --git a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
deleted file mode 100644
index 74e5612..0000000
--- a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.loudnesscodecapitest;
-
-import static android.media.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyList;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.res.AssetFileDescriptor;
-import android.media.AudioAttributes;
-import android.media.AudioFormat;
-import android.media.AudioTrack;
-import android.media.IAudioService;
-import android.media.LoudnessCodecConfigurator;
-import android.media.LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener;
-import android.media.MediaCodec;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.os.PersistableBundle;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.util.Log;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.List;
-import java.util.concurrent.Executors;
-
-/**
- * Unit tests for {@link LoudnessCodecConfigurator} checking the internal interactions with a mocked
- * {@link IAudioService} without any real IPC interactions.
- */
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class LoudnessCodecConfiguratorTest {
-    private static final String TAG = "LoudnessCodecConfiguratorTest";
-
-    private static final String TEST_MEDIA_AUDIO_CODEC_PREFIX = "audio/";
-    private static final int TEST_AUDIO_TRACK_BUFFER_SIZE = 2048;
-    private static final int TEST_AUDIO_TRACK_SAMPLERATE = 48000;
-    private static final int TEST_AUDIO_TRACK_CHANNELS = 2;
-
-    @Rule
-    public final MockitoRule mockito = MockitoJUnit.rule();
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
-    @Mock
-    private IAudioService mAudioService;
-
-    private LoudnessCodecConfigurator mLcc;
-
-    @Before
-    public void setUp() {
-        mLcc = LoudnessCodecConfigurator.createForTesting(mAudioService,
-                Executors.newSingleThreadExecutor(), new OnLoudnessCodecUpdateListener() {});
-    }
-
-    @Test
-    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
-    public void setAudioTrack_callsAudioServiceStart() throws Exception {
-        final AudioTrack track = createAudioTrack();
-        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
-
-        try {
-            mLcc.addMediaCodec(mediaCodec);
-            mLcc.setAudioTrack(track);
-
-            verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
-                    anyList());
-        } finally {
-            mediaCodec.release();
-        }
-    }
-
-    @Test
-    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
-    public void getLoudnessCodecParams_callsAudioServiceGetLoudness() throws Exception {
-        when(mAudioService.getLoudnessParams(anyInt(), any())).thenReturn(new PersistableBundle());
-        final AudioTrack track = createAudioTrack();
-        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
-
-        try {
-            mLcc.getLoudnessCodecParams(track, mediaCodec);
-
-            verify(mAudioService).getLoudnessParams(eq(track.getPlayerIId()), any());
-        } finally {
-            mediaCodec.release();
-        }
-    }
-
-    @Test
-    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
-    public void setAudioTrack_addsAudioServicePiidCodecs() throws Exception {
-        final AudioTrack track = createAudioTrack();
-        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
-
-        try {
-            mLcc.addMediaCodec(mediaCodec);
-            mLcc.setAudioTrack(track);
-
-            verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
-        } finally {
-            mediaCodec.release();
-        }
-    }
-
-    @Test
-    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
-    public void setAudioTrackTwice_ignoresSecondCall() throws Exception {
-        final AudioTrack track = createAudioTrack();
-        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
-
-        try {
-            mLcc.addMediaCodec(mediaCodec);
-            mLcc.setAudioTrack(track);
-            mLcc.setAudioTrack(track);
-
-            verify(mAudioService, times(1)).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
-                    anyList());
-        } finally {
-            mediaCodec.release();
-        }
-    }
-
-    @Test
-    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
-    public void setTrackNull_stopCodecUpdates() throws Exception {
-        final AudioTrack track = createAudioTrack();
-        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
-
-        try {
-            mLcc.addMediaCodec(mediaCodec);
-            mLcc.setAudioTrack(track);
-
-            mLcc.setAudioTrack(null);  // stops updates
-            verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId()));
-        } finally {
-            mediaCodec.release();
-        }
-    }
-
-    @Test
-    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
-    public void addMediaCodecTwice_triggersIAE() throws Exception {
-        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
-
-        try {
-            mLcc.addMediaCodec(mediaCodec);
-
-            assertThrows(IllegalArgumentException.class, () -> mLcc.addMediaCodec(mediaCodec));
-        } finally {
-            mediaCodec.release();
-        }
-    }
-
-    @Test
-    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
-    public void addUnconfiguredMediaCodec_returnsFalse() throws Exception {
-        final MediaCodec mediaCodec = MediaCodec.createDecoderByType("audio/mpeg");
-
-        try {
-            assertFalse(mLcc.addMediaCodec(mediaCodec));
-        } finally {
-            mediaCodec.release();
-        }
-    }
-
-    @Test
-    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
-    public void setClearTrack_removeAllAudioServicePiidCodecs() throws Exception {
-        final ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
-        final AudioTrack track = createAudioTrack();
-        final MediaCodec mediaCodec1 = createAndConfigureMediaCodec();
-        final MediaCodec mediaCodec2 = createAndConfigureMediaCodec();
-
-        try {
-            mLcc.addMediaCodec(mediaCodec1);
-            mLcc.setAudioTrack(track);
-            verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
-                    argument.capture());
-            assertEquals(argument.getValue().size(), 1);
-
-            mLcc.addMediaCodec(mediaCodec2);
-            mLcc.setAudioTrack(null);
-            verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId()));
-        } finally {
-            mediaCodec1.release();
-            mediaCodec2.release();
-        }
-    }
-
-    @Test
-    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
-    public void removeAddedMediaCodecAfterSetTrack_callsAudioServiceRemoveCodec() throws Exception {
-        final AudioTrack track = createAudioTrack();
-        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
-
-        try {
-            mLcc.addMediaCodec(mediaCodec);
-            mLcc.setAudioTrack(track);
-            mLcc.removeMediaCodec(mediaCodec);
-
-            verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any());
-        } finally {
-            mediaCodec.release();
-        }
-    }
-
-    @Test
-    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
-    public void addMediaCodecAfterSetTrack_callsAudioServiceAdd() throws Exception {
-        final AudioTrack track = createAudioTrack();
-        final MediaCodec mediaCodec1 = createAndConfigureMediaCodec();
-        final MediaCodec mediaCodec2 = createAndConfigureMediaCodec();
-
-        try {
-            mLcc.addMediaCodec(mediaCodec1);
-            mLcc.setAudioTrack(track);
-            verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
-
-            mLcc.addMediaCodec(mediaCodec2);
-            verify(mAudioService).addLoudnessCodecInfo(eq(track.getPlayerIId()), anyInt(), any());
-        } finally {
-            mediaCodec1.release();
-            mediaCodec2.release();
-        }
-    }
-
-    @Test
-    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
-    public void removeMediaCodecAfterSetTrack_callsAudioServiceRemove() throws Exception {
-        final AudioTrack track = createAudioTrack();
-        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
-
-        try {
-            mLcc.addMediaCodec(mediaCodec);
-            mLcc.setAudioTrack(track);
-            verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
-
-            mLcc.removeMediaCodec(mediaCodec);
-            verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any());
-        } finally {
-            mediaCodec.release();
-        }
-    }
-
-    @Test
-    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
-    public void removeWrongMediaCodecAfterSetTrack_triggersIAE() throws Exception {
-        final AudioTrack track = createAudioTrack();
-        final MediaCodec mediaCodec1 = createAndConfigureMediaCodec();
-        final MediaCodec mediaCodec2 = createAndConfigureMediaCodec();
-
-        try {
-            mLcc.addMediaCodec(mediaCodec1);
-            mLcc.setAudioTrack(track);
-            verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
-
-            assertThrows(IllegalArgumentException.class,
-                    () -> mLcc.removeMediaCodec(mediaCodec2));
-        } finally {
-            mediaCodec1.release();
-            mediaCodec2.release();
-        }
-    }
-
-    private static AudioTrack createAudioTrack() {
-        return new AudioTrack.Builder()
-                .setAudioAttributes(new AudioAttributes.Builder().build())
-                .setBufferSizeInBytes(TEST_AUDIO_TRACK_BUFFER_SIZE)
-                .setAudioFormat(new AudioFormat.Builder()
-                        .setChannelMask(TEST_AUDIO_TRACK_CHANNELS)
-                        .setSampleRate(TEST_AUDIO_TRACK_SAMPLERATE).build())
-                .build();
-    }
-
-    private MediaCodec createAndConfigureMediaCodec() throws Exception {
-        AssetFileDescriptor testFd = InstrumentationRegistry.getInstrumentation().getContext()
-                .getResources()
-                .openRawResourceFd(R.raw.noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4);
-
-        MediaExtractor extractor;
-        extractor = new MediaExtractor();
-        try {
-            extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
-                testFd.getLength());
-            assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
-            MediaFormat format = extractor.getTrackFormat(0);
-            String mime = format.getString(MediaFormat.KEY_MIME);
-            assertTrue("not an audio file", mime.startsWith(TEST_MEDIA_AUDIO_CODEC_PREFIX));
-            final MediaCodec mediaCodec = MediaCodec.createDecoderByType(mime);
-
-            Log.v(TAG, "configuring with " + format);
-            mediaCodec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
-            return mediaCodec;
-        } finally {
-            testFd.close();
-            extractor.release();
-        }
-    }
-}
diff --git a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java
new file mode 100644
index 0000000..46256ba
--- /dev/null
+++ b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.loudnesscodecapitest;
+
+import static android.media.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioManager;
+import android.media.IAudioService;
+import android.media.LoudnessCodecController;
+import android.media.LoudnessCodecController.OnLoudnessCodecUpdateListener;
+import android.media.MediaCodec;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.os.PersistableBundle;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.concurrent.Executors;
+
+/**
+ * Unit tests for {@link LoudnessCodecController} checking the internal interactions with a mocked
+ * {@link IAudioService} without any real IPC interactions.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LoudnessCodecControllerTest {
+    private static final String TAG = "LoudnessCodecConfiguratorTest";
+
+    private static final String TEST_MEDIA_AUDIO_CODEC_PREFIX = "audio/";
+    private static final int TEST_AUDIO_TRACK_BUFFER_SIZE = 2048;
+    private static final int TEST_AUDIO_TRACK_SAMPLERATE = 48000;
+    private static final int TEST_AUDIO_TRACK_CHANNELS = 2;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Mock
+    private IAudioService mAudioService;
+
+    private LoudnessCodecController mLcc;
+
+    private int mSessionId;
+
+    @Before
+    public void setUp() {
+        final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        final AudioManager audioManager = (AudioManager) context.getSystemService(
+                AudioManager.class);
+        mSessionId = 0;
+        if (audioManager != null) {
+            mSessionId = audioManager.generateAudioSessionId();
+        }
+        mLcc = LoudnessCodecController.createForTesting(mSessionId,
+                Executors.newSingleThreadExecutor(), new OnLoudnessCodecUpdateListener() {
+                }, mAudioService);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void createLcc_callsAudioServiceStart() throws Exception {
+        verify(mAudioService).startLoudnessCodecUpdates(eq(mSessionId));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void getLoudnessCodecParams_callsAudioServiceGetLoudness() throws Exception {
+        when(mAudioService.getLoudnessParams(any())).thenReturn(new PersistableBundle());
+        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
+
+        try {
+            mLcc.addMediaCodec(mediaCodec);
+            mLcc.getLoudnessCodecParams(mediaCodec);
+
+            verify(mAudioService).getLoudnessParams(any());
+        } finally {
+            mediaCodec.release();
+        }
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void release_stopCodecUpdates() throws Exception {
+        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
+
+        try {
+            mLcc.addMediaCodec(mediaCodec);
+            mLcc.close();  // stops updates
+
+            verify(mAudioService).stopLoudnessCodecUpdates(eq(mSessionId));
+        } finally {
+            mediaCodec.release();
+        }
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void addMediaCodecTwice_triggersIAE() throws Exception {
+        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
+
+        try {
+            mLcc.addMediaCodec(mediaCodec);
+
+            assertThrows(IllegalArgumentException.class, () -> mLcc.addMediaCodec(mediaCodec));
+        } finally {
+            mediaCodec.release();
+        }
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void addUnconfiguredMediaCodec_returnsFalse() throws Exception {
+        final MediaCodec mediaCodec = MediaCodec.createDecoderByType("audio/mpeg");
+
+        try {
+            assertFalse(mLcc.addMediaCodec(mediaCodec));
+        } finally {
+            mediaCodec.release();
+        }
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void removeAddedMediaCodecAfterSetTrack_callsAudioServiceRemoveCodec() throws Exception {
+        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
+
+        try {
+            mLcc.addMediaCodec(mediaCodec);
+            mLcc.removeMediaCodec(mediaCodec);
+
+            verify(mAudioService).removeLoudnessCodecInfo(eq(mSessionId), any());
+        } finally {
+            mediaCodec.release();
+        }
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void addMediaCodec_callsAudioServiceAdd() throws Exception {
+        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
+
+        try {
+            mLcc.addMediaCodec(mediaCodec);
+            verify(mAudioService).addLoudnessCodecInfo(eq(mSessionId), anyInt(), any());
+        } finally {
+            mediaCodec.release();
+        }
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void removeMediaCodec_callsAudioServiceRemove() throws Exception {
+        final MediaCodec mediaCodec = createAndConfigureMediaCodec();
+
+        try {
+            mLcc.addMediaCodec(mediaCodec);
+            verify(mAudioService).addLoudnessCodecInfo(eq(mSessionId), anyInt(), any());
+
+            mLcc.removeMediaCodec(mediaCodec);
+            verify(mAudioService).removeLoudnessCodecInfo(eq(mSessionId), any());
+        } finally {
+            mediaCodec.release();
+        }
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void removeWrongMediaCodec_triggersIAE() throws Exception {
+        final MediaCodec mediaCodec1 = createAndConfigureMediaCodec();
+        final MediaCodec mediaCodec2 = createAndConfigureMediaCodec();
+
+        try {
+            mLcc.addMediaCodec(mediaCodec1);
+            verify(mAudioService).addLoudnessCodecInfo(eq(mSessionId), anyInt(), any());
+
+            assertThrows(IllegalArgumentException.class,
+                    () -> mLcc.removeMediaCodec(mediaCodec2));
+        } finally {
+            mediaCodec1.release();
+            mediaCodec2.release();
+        }
+    }
+
+    private MediaCodec createAndConfigureMediaCodec() throws Exception {
+        AssetFileDescriptor testFd = InstrumentationRegistry.getInstrumentation().getContext()
+                .getResources()
+                .openRawResourceFd(R.raw.noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4);
+
+        MediaExtractor extractor;
+        extractor = new MediaExtractor();
+        try {
+            extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+                    testFd.getLength());
+            assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
+            MediaFormat format = extractor.getTrackFormat(0);
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            assertTrue("not an audio file", mime.startsWith(TEST_MEDIA_AUDIO_CODEC_PREFIX));
+            final MediaCodec mediaCodec = MediaCodec.createDecoderByType(mime);
+
+            Log.v(TAG, "configuring with " + format);
+            mediaCodec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
+            return mediaCodec;
+        } finally {
+            testFd.close();
+            extractor.release();
+        }
+    }
+}
diff --git a/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java b/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java
index 774de5f..0df36af 100644
--- a/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java
+++ b/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java
@@ -19,7 +19,7 @@
 import static android.Manifest.permission.MANAGE_MEDIA_PROJECTION;
 
 import android.annotation.EnforcePermission;
-import android.os.IBinder;
+import android.app.ActivityOptions.LaunchCookie;
 import android.os.PermissionEnforcer;
 import android.os.RemoteException;
 
@@ -29,7 +29,7 @@
  */
 public final class FakeIMediaProjection extends IMediaProjection.Stub {
     boolean mIsStarted = false;
-    IBinder mLaunchCookie = null;
+    LaunchCookie mLaunchCookie = null;
     IMediaProjectionCallback mIMediaProjectionCallback = null;
 
     FakeIMediaProjection(PermissionEnforcer enforcer) {
@@ -80,14 +80,14 @@
 
     @Override
     @EnforcePermission(MANAGE_MEDIA_PROJECTION)
-    public IBinder getLaunchCookie() throws RemoteException {
+    public LaunchCookie getLaunchCookie() throws RemoteException {
         getLaunchCookie_enforcePermission();
         return mLaunchCookie;
     }
 
     @Override
     @EnforcePermission(MANAGE_MEDIA_PROJECTION)
-    public void setLaunchCookie(IBinder launchCookie) throws RemoteException {
+    public void setLaunchCookie(LaunchCookie launchCookie) throws RemoteException {
         setLaunchCookie_enforcePermission();
         mLaunchCookie = launchCookie;
     }
diff --git a/media/tests/projection/src/android/media/projection/MediaProjectionTest.java b/media/tests/projection/src/android/media/projection/MediaProjectionTest.java
index 2e0396f..1323e89 100644
--- a/media/tests/projection/src/android/media/projection/MediaProjectionTest.java
+++ b/media/tests/projection/src/android/media/projection/MediaProjectionTest.java
@@ -31,15 +31,14 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
 
 import android.annotation.Nullable;
+import android.app.ActivityOptions.LaunchCookie;
 import android.compat.testing.PlatformCompatChangeRule;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.VirtualDisplay;
 import android.hardware.display.VirtualDisplayConfig;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.test.FakePermissionEnforcer;
@@ -117,7 +116,7 @@
         permissionEnforcer.grant(MANAGE_MEDIA_PROJECTION);
         // Support the MediaProjection instance.
         mFakeIMediaProjection = new FakeIMediaProjection(permissionEnforcer);
-        mFakeIMediaProjection.setLaunchCookie(mock(IBinder.class));
+        mFakeIMediaProjection.setLaunchCookie(new LaunchCookie());
         mMediaProjection = new MediaProjection(mTestableContext, mFakeIMediaProjection,
                 mDisplayManager);
 
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index abe4a3d..c572944 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -228,11 +228,6 @@
 }
 
 int APerformanceHintSession::reportActualWorkDuration(int64_t actualDurationNanos) {
-    if (actualDurationNanos <= 0) {
-        ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__);
-        return EINVAL;
-    }
-
     WorkDuration workDuration(0, actualDurationNanos, actualDurationNanos, 0);
 
     return reportActualWorkDurationInternal(&workDuration);
@@ -320,23 +315,6 @@
 
 int APerformanceHintSession::reportActualWorkDuration(AWorkDuration* aWorkDuration) {
     WorkDuration* workDuration = static_cast<WorkDuration*>(aWorkDuration);
-    if (workDuration->workPeriodStartTimestampNanos <= 0) {
-        ALOGE("%s: workPeriodStartTimestampNanos must be positive", __FUNCTION__);
-        return EINVAL;
-    }
-    if (workDuration->actualTotalDurationNanos <= 0) {
-        ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__);
-        return EINVAL;
-    }
-    if (workDuration->actualCpuDurationNanos <= 0) {
-        ALOGE("%s: cpuDurationNanos must be positive", __FUNCTION__);
-        return EINVAL;
-    }
-    if (workDuration->actualGpuDurationNanos < 0) {
-        ALOGE("%s: gpuDurationNanos must be non negative", __FUNCTION__);
-        return EINVAL;
-    }
-
     return reportActualWorkDurationInternal(workDuration);
 }
 
@@ -428,62 +406,87 @@
     return APerformanceHintManager::getInstance();
 }
 
+#define VALIDATE_PTR(ptr) \
+    LOG_ALWAYS_FATAL_IF(ptr == nullptr, "%s: " #ptr " is nullptr", __FUNCTION__);
+
+#define VALIDATE_INT(value, cmp)                                                             \
+    if (!(value cmp)) {                                                                      \
+        ALOGE("%s: Invalid value. Check failed: (" #value " " #cmp ") with value: %" PRIi64, \
+              __FUNCTION__, value);                                                          \
+        return EINVAL;                                                                       \
+    }
+
+#define WARN_INT(value, cmp)                                                                 \
+    if (!(value cmp)) {                                                                      \
+        ALOGE("%s: Invalid value. Check failed: (" #value " " #cmp ") with value: %" PRIi64, \
+              __FUNCTION__, value);                                                          \
+    }
+
 APerformanceHintSession* APerformanceHint_createSession(APerformanceHintManager* manager,
                                                         const int32_t* threadIds, size_t size,
                                                         int64_t initialTargetWorkDurationNanos) {
+    VALIDATE_PTR(manager)
+    VALIDATE_PTR(threadIds)
     return manager->createSession(threadIds, size, initialTargetWorkDurationNanos);
 }
 
 int64_t APerformanceHint_getPreferredUpdateRateNanos(APerformanceHintManager* manager) {
+    VALIDATE_PTR(manager)
     return manager->getPreferredRateNanos();
 }
 
 int APerformanceHint_updateTargetWorkDuration(APerformanceHintSession* session,
                                               int64_t targetDurationNanos) {
+    VALIDATE_PTR(session)
     return session->updateTargetWorkDuration(targetDurationNanos);
 }
 
 int APerformanceHint_reportActualWorkDuration(APerformanceHintSession* session,
                                               int64_t actualDurationNanos) {
+    VALIDATE_PTR(session)
+    VALIDATE_INT(actualDurationNanos, > 0)
     return session->reportActualWorkDuration(actualDurationNanos);
 }
 
 void APerformanceHint_closeSession(APerformanceHintSession* session) {
+    VALIDATE_PTR(session)
     delete session;
 }
 
 int APerformanceHint_sendHint(void* session, SessionHint hint) {
+    VALIDATE_PTR(session)
     return reinterpret_cast<APerformanceHintSession*>(session)->sendHint(hint);
 }
 
 int APerformanceHint_setThreads(APerformanceHintSession* session, const pid_t* threadIds,
                                 size_t size) {
-    if (session == nullptr) {
-        return EINVAL;
-    }
+    VALIDATE_PTR(session)
+    VALIDATE_PTR(threadIds)
     return session->setThreads(threadIds, size);
 }
 
 int APerformanceHint_getThreadIds(void* aPerformanceHintSession, int32_t* const threadIds,
                                   size_t* const size) {
-    if (aPerformanceHintSession == nullptr) {
-        return EINVAL;
-    }
+    VALIDATE_PTR(aPerformanceHintSession)
     return static_cast<APerformanceHintSession*>(aPerformanceHintSession)
             ->getThreadIds(threadIds, size);
 }
 
 int APerformanceHint_setPreferPowerEfficiency(APerformanceHintSession* session, bool enabled) {
+    VALIDATE_PTR(session)
     return session->setPreferPowerEfficiency(enabled);
 }
 
 int APerformanceHint_reportActualWorkDuration2(APerformanceHintSession* session,
-                                               AWorkDuration* workDuration) {
-    if (session == nullptr || workDuration == nullptr) {
-        ALOGE("Invalid value: (session %p, workDuration %p)", session, workDuration);
-        return EINVAL;
-    }
-    return session->reportActualWorkDuration(workDuration);
+                                               AWorkDuration* workDurationPtr) {
+    VALIDATE_PTR(session)
+    VALIDATE_PTR(workDurationPtr)
+    WorkDuration& workDuration = *static_cast<WorkDuration*>(workDurationPtr);
+    VALIDATE_INT(workDuration.workPeriodStartTimestampNanos, > 0)
+    VALIDATE_INT(workDuration.actualTotalDurationNanos, > 0)
+    VALIDATE_INT(workDuration.actualCpuDurationNanos, > 0)
+    VALIDATE_INT(workDuration.actualGpuDurationNanos, >= 0)
+    return session->reportActualWorkDuration(workDurationPtr);
 }
 
 AWorkDuration* AWorkDuration_create() {
@@ -492,46 +495,36 @@
 }
 
 void AWorkDuration_release(AWorkDuration* aWorkDuration) {
-    if (aWorkDuration == nullptr) {
-        ALOGE("%s: aWorkDuration is nullptr", __FUNCTION__);
-    }
+    VALIDATE_PTR(aWorkDuration)
     delete aWorkDuration;
 }
 
 void AWorkDuration_setWorkPeriodStartTimestampNanos(AWorkDuration* aWorkDuration,
                                                     int64_t workPeriodStartTimestampNanos) {
-    if (aWorkDuration == nullptr || workPeriodStartTimestampNanos <= 0) {
-        ALOGE("%s: Invalid value. (AWorkDuration: %p, workPeriodStartTimestampNanos: %" PRIi64 ")",
-              __FUNCTION__, aWorkDuration, workPeriodStartTimestampNanos);
-    }
+    VALIDATE_PTR(aWorkDuration)
+    WARN_INT(workPeriodStartTimestampNanos, > 0)
     static_cast<WorkDuration*>(aWorkDuration)->workPeriodStartTimestampNanos =
             workPeriodStartTimestampNanos;
 }
 
 void AWorkDuration_setActualTotalDurationNanos(AWorkDuration* aWorkDuration,
                                                int64_t actualTotalDurationNanos) {
-    if (aWorkDuration == nullptr || actualTotalDurationNanos <= 0) {
-        ALOGE("%s: Invalid value. (AWorkDuration: %p, actualTotalDurationNanos: %" PRIi64 ")",
-              __FUNCTION__, aWorkDuration, actualTotalDurationNanos);
-    }
+    VALIDATE_PTR(aWorkDuration)
+    WARN_INT(actualTotalDurationNanos, > 0)
     static_cast<WorkDuration*>(aWorkDuration)->actualTotalDurationNanos = actualTotalDurationNanos;
 }
 
 void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* aWorkDuration,
                                              int64_t actualCpuDurationNanos) {
-    if (aWorkDuration == nullptr || actualCpuDurationNanos <= 0) {
-        ALOGE("%s: Invalid value. (AWorkDuration: %p, actualCpuDurationNanos: %" PRIi64 ")",
-              __FUNCTION__, aWorkDuration, actualCpuDurationNanos);
-    }
+    VALIDATE_PTR(aWorkDuration)
+    WARN_INT(actualCpuDurationNanos, > 0)
     static_cast<WorkDuration*>(aWorkDuration)->actualCpuDurationNanos = actualCpuDurationNanos;
 }
 
 void AWorkDuration_setActualGpuDurationNanos(AWorkDuration* aWorkDuration,
                                              int64_t actualGpuDurationNanos) {
-    if (aWorkDuration == nullptr || actualGpuDurationNanos < 0) {
-        ALOGE("%s: Invalid value. (AWorkDuration: %p, actualGpuDurationNanos: %" PRIi64 ")",
-              __FUNCTION__, aWorkDuration, actualGpuDurationNanos);
-    }
+    VALIDATE_PTR(aWorkDuration)
+    WARN_INT(actualGpuDurationNanos, >= 0)
     static_cast<WorkDuration*>(aWorkDuration)->actualGpuDurationNanos = actualGpuDurationNanos;
 }
 
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 10c570b..8ea4632 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -72,6 +72,9 @@
             ],
         },
     },
+    stubs: {
+        symbol_file: "libjnigraphics.map.txt",
+    },
 }
 
 // The headers module is in frameworks/native/Android.bp.
@@ -93,15 +96,18 @@
     ],
     static_libs: ["libarect"],
     fuzz_config: {
-        cc: ["dichenzhang@google.com","scroggo@google.com"],
+        cc: [
+            "dichenzhang@google.com",
+            "scroggo@google.com",
+        ],
         asan_options: [
             "detect_odr_violation=1",
         ],
         hwasan_options: [
-             // Image decoders may attempt to allocate a large amount of memory
-             // (especially if the encoded image is large). This doesn't
-             // necessarily mean there is a bug. Set allocator_may_return_null=1
-             // for hwasan so the fuzzer can continue running.
+            // Image decoders may attempt to allocate a large amount of memory
+            // (especially if the encoded image is large). This doesn't
+            // necessarily mean there is a bug. Set allocator_may_return_null=1
+            // for hwasan so the fuzzer can continue running.
             "allocator_may_return_null = 1",
         ],
     },
diff --git a/nfc-extras/Android.bp b/nfc-extras/Android.bp
index cb9ac6f..1f187e8 100644
--- a/nfc-extras/Android.bp
+++ b/nfc-extras/Android.bp
@@ -23,9 +23,13 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+// TODO(b/303286040): Deprecate this API surface since this is no longer supported (see ag/443092)
 java_sdk_library {
     name: "com.android.nfc_extras",
     srcs: ["java/**/*.java"],
+    libs: [
+        "framework-nfc.impl"
+    ],
     api_packages: ["com.android.nfc_extras"],
     dist_group: "android",
 }
diff --git a/nfc/Android.bp b/nfc/Android.bp
index bf9f47c..7136866 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -10,7 +10,15 @@
 filegroup {
     name: "framework-nfc-non-updatable-sources",
     path: "java",
-    srcs: [],
+    srcs: [
+        "java/android/nfc/NfcServiceManager.java",
+        "java/android/nfc/cardemulation/ApduServiceInfo.aidl",
+        "java/android/nfc/cardemulation/ApduServiceInfo.java",
+        "java/android/nfc/cardemulation/NfcFServiceInfo.aidl",
+        "java/android/nfc/cardemulation/NfcFServiceInfo.java",
+        "java/android/nfc/cardemulation/AidGroup.aidl",
+        "java/android/nfc/cardemulation/AidGroup.java",
+    ],
 }
 
 filegroup {
@@ -30,10 +38,22 @@
     libs: [
         "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage
     ],
+    static_libs: [
+        "android.nfc.flags-aconfig-java",
+        "android.permission.flags-aconfig-java",
+    ],
     srcs: [
         ":framework-nfc-updatable-sources",
+        ":framework-nfc-javastream-protos",
     ],
-    defaults: ["framework-non-updatable-unbundled-defaults"],
+    defaults: ["framework-module-defaults"],
+    sdk_version: "module_current",
+    min_sdk_version: "34", // should be 35 (making it 34 for compiling for `-next`)
+    installable: true,
+    optimize: {
+        enabled: false,
+    },
+    hostdex: true, // for hiddenapi check
     permitted_packages: [
         "android.nfc",
         "com.android.nfc",
@@ -41,11 +61,22 @@
     hidden_api_packages: [
         "com.android.nfc",
     ],
-    aidl: {
-        include_dirs: [
-	    // TODO (b/303286040): Remove these when we change to |framework-module-defaults|
-            "frameworks/base/nfc/java",
-            "frameworks/base/core/java",
-        ],
+    impl_library_visibility: [
+        "//frameworks/base:__subpackages__",
+        "//cts/tests/tests/nfc",
+        "//packages/apps/Nfc:__subpackages__",
+    ],
+    jarjar_rules: ":nfc-jarjar-rules",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
     },
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.nfcservices",
+    ],
+}
+
+filegroup {
+    name: "nfc-jarjar-rules",
+    srcs: ["jarjar-rules.txt"],
 }
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index d802177..1046d8e9 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -1 +1,456 @@
 // Signature format: 2.0
+package android.nfc {
+
+  public final class AvailableNfcAntenna implements android.os.Parcelable {
+    ctor public AvailableNfcAntenna(int, int);
+    method public int describeContents();
+    method public int getLocationX();
+    method public int getLocationY();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.AvailableNfcAntenna> CREATOR;
+  }
+
+  public class FormatException extends java.lang.Exception {
+    ctor public FormatException();
+    ctor public FormatException(String);
+    ctor public FormatException(String, Throwable);
+  }
+
+  public final class NdefMessage implements android.os.Parcelable {
+    ctor public NdefMessage(byte[]) throws android.nfc.FormatException;
+    ctor public NdefMessage(android.nfc.NdefRecord, android.nfc.NdefRecord...);
+    ctor public NdefMessage(android.nfc.NdefRecord[]);
+    method public int describeContents();
+    method public int getByteArrayLength();
+    method public android.nfc.NdefRecord[] getRecords();
+    method public byte[] toByteArray();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NdefMessage> CREATOR;
+  }
+
+  public final class NdefRecord implements android.os.Parcelable {
+    ctor public NdefRecord(short, byte[], byte[], byte[]);
+    ctor @Deprecated public NdefRecord(byte[]) throws android.nfc.FormatException;
+    method public static android.nfc.NdefRecord createApplicationRecord(String);
+    method public static android.nfc.NdefRecord createExternal(String, String, byte[]);
+    method public static android.nfc.NdefRecord createMime(String, byte[]);
+    method public static android.nfc.NdefRecord createTextRecord(String, String);
+    method public static android.nfc.NdefRecord createUri(android.net.Uri);
+    method public static android.nfc.NdefRecord createUri(String);
+    method public int describeContents();
+    method public byte[] getId();
+    method public byte[] getPayload();
+    method public short getTnf();
+    method public byte[] getType();
+    method @Deprecated public byte[] toByteArray();
+    method public String toMimeType();
+    method public android.net.Uri toUri();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NdefRecord> CREATOR;
+    field public static final byte[] RTD_ALTERNATIVE_CARRIER;
+    field public static final byte[] RTD_HANDOVER_CARRIER;
+    field public static final byte[] RTD_HANDOVER_REQUEST;
+    field public static final byte[] RTD_HANDOVER_SELECT;
+    field public static final byte[] RTD_SMART_POSTER;
+    field public static final byte[] RTD_TEXT;
+    field public static final byte[] RTD_URI;
+    field public static final short TNF_ABSOLUTE_URI = 3; // 0x3
+    field public static final short TNF_EMPTY = 0; // 0x0
+    field public static final short TNF_EXTERNAL_TYPE = 4; // 0x4
+    field public static final short TNF_MIME_MEDIA = 2; // 0x2
+    field public static final short TNF_UNCHANGED = 6; // 0x6
+    field public static final short TNF_UNKNOWN = 5; // 0x5
+    field public static final short TNF_WELL_KNOWN = 1; // 0x1
+  }
+
+  public final class NfcAdapter {
+    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean allowTransaction();
+    method public void disableForegroundDispatch(android.app.Activity);
+    method public void disableReaderMode(android.app.Activity);
+    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean disallowTransaction();
+    method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]);
+    method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
+    method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
+    method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo();
+    method @FlaggedApi("android.nfc.enable_nfc_charging") @Nullable public android.nfc.WlcListenerDeviceInfo getWlcListenerDeviceInfo();
+    method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
+    method public boolean isEnabled();
+    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported();
+    method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionEnabled();
+    method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported();
+    method public boolean isSecureNfcEnabled();
+    method public boolean isSecureNfcSupported();
+    method @FlaggedApi("android.nfc.enable_nfc_charging") public boolean isWlcEnabled();
+    method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void resetDiscoveryTechnology(@NonNull android.app.Activity);
+    method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void setDiscoveryTechnology(@NonNull android.app.Activity, int, int);
+    field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
+    field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
+    field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
+    field public static final String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
+    field public static final String ACTION_TECH_DISCOVERED = "android.nfc.action.TECH_DISCOVERED";
+    field @RequiresPermission(android.Manifest.permission.NFC_TRANSACTION_EVENT) public static final String ACTION_TRANSACTION_DETECTED = "android.nfc.action.TRANSACTION_DETECTED";
+    field public static final String EXTRA_ADAPTER_STATE = "android.nfc.extra.ADAPTER_STATE";
+    field public static final String EXTRA_AID = "android.nfc.extra.AID";
+    field public static final String EXTRA_DATA = "android.nfc.extra.DATA";
+    field public static final String EXTRA_ID = "android.nfc.extra.ID";
+    field public static final String EXTRA_NDEF_MESSAGES = "android.nfc.extra.NDEF_MESSAGES";
+    field public static final String EXTRA_PREFERRED_PAYMENT_CHANGED_REASON = "android.nfc.extra.PREFERRED_PAYMENT_CHANGED_REASON";
+    field public static final String EXTRA_READER_PRESENCE_CHECK_DELAY = "presence";
+    field public static final String EXTRA_SECURE_ELEMENT_NAME = "android.nfc.extra.SECURE_ELEMENT_NAME";
+    field public static final String EXTRA_TAG = "android.nfc.extra.TAG";
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_DISABLE = 0; // 0x0
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_KEEP = -1; // 0xffffffff
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_A = 1; // 0x1
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_B = 2; // 0x2
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_F = 4; // 0x4
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_DISABLE = 0; // 0x0
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_KEEP = -1; // 0xffffffff
+    field public static final int FLAG_READER_NFC_A = 1; // 0x1
+    field public static final int FLAG_READER_NFC_B = 2; // 0x2
+    field public static final int FLAG_READER_NFC_BARCODE = 16; // 0x10
+    field public static final int FLAG_READER_NFC_F = 4; // 0x4
+    field public static final int FLAG_READER_NFC_V = 8; // 0x8
+    field public static final int FLAG_READER_NO_PLATFORM_SOUNDS = 256; // 0x100
+    field public static final int FLAG_READER_SKIP_NDEF_CHECK = 128; // 0x80
+    field public static final int PREFERRED_PAYMENT_CHANGED = 2; // 0x2
+    field public static final int PREFERRED_PAYMENT_LOADED = 1; // 0x1
+    field public static final int PREFERRED_PAYMENT_UPDATED = 3; // 0x3
+    field public static final int STATE_OFF = 1; // 0x1
+    field public static final int STATE_ON = 3; // 0x3
+    field public static final int STATE_TURNING_OFF = 4; // 0x4
+    field public static final int STATE_TURNING_ON = 2; // 0x2
+  }
+
+  @Deprecated public static interface NfcAdapter.CreateBeamUrisCallback {
+    method @Deprecated public android.net.Uri[] createBeamUris(android.nfc.NfcEvent);
+  }
+
+  @Deprecated public static interface NfcAdapter.CreateNdefMessageCallback {
+    method @Deprecated public android.nfc.NdefMessage createNdefMessage(android.nfc.NfcEvent);
+  }
+
+  @Deprecated public static interface NfcAdapter.OnNdefPushCompleteCallback {
+    method @Deprecated public void onNdefPushComplete(android.nfc.NfcEvent);
+  }
+
+  public static interface NfcAdapter.OnTagRemovedListener {
+    method public void onTagRemoved();
+  }
+
+  public static interface NfcAdapter.ReaderCallback {
+    method public void onTagDiscovered(android.nfc.Tag);
+  }
+
+  public final class NfcAntennaInfo implements android.os.Parcelable {
+    ctor public NfcAntennaInfo(int, int, boolean, @NonNull java.util.List<android.nfc.AvailableNfcAntenna>);
+    method public int describeContents();
+    method @NonNull public java.util.List<android.nfc.AvailableNfcAntenna> getAvailableNfcAntennas();
+    method public int getDeviceHeight();
+    method public int getDeviceWidth();
+    method public boolean isDeviceFoldable();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NfcAntennaInfo> CREATOR;
+  }
+
+  public final class NfcEvent {
+    field public final android.nfc.NfcAdapter nfcAdapter;
+    field public final int peerLlcpMajorVersion;
+    field public final int peerLlcpMinorVersion;
+  }
+
+  public final class NfcManager {
+    method public android.nfc.NfcAdapter getDefaultAdapter();
+  }
+
+  public final class Tag implements android.os.Parcelable {
+    method public int describeContents();
+    method public byte[] getId();
+    method public String[] getTechList();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.Tag> CREATOR;
+  }
+
+  public class TagLostException extends java.io.IOException {
+    ctor public TagLostException();
+    ctor public TagLostException(String);
+  }
+
+  @FlaggedApi("android.nfc.enable_nfc_charging") public final class WlcListenerDeviceInfo implements android.os.Parcelable {
+    ctor public WlcListenerDeviceInfo(int, double, double, int);
+    method public int describeContents();
+    method @FloatRange(from=0.0, to=100.0) public double getBatteryLevel();
+    method public int getProductId();
+    method public int getState();
+    method public double getTemperature();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.WlcListenerDeviceInfo> CREATOR;
+    field public static final int STATE_CONNECTED_CHARGING = 2; // 0x2
+    field public static final int STATE_CONNECTED_DISCHARGING = 3; // 0x3
+    field public static final int STATE_DISCONNECTED = 1; // 0x1
+  }
+
+}
+
+package android.nfc.cardemulation {
+
+  public final class CardEmulation {
+    method public boolean categoryAllowsForegroundPreference(String);
+    method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public java.util.List<java.lang.String> getAidsForPreferredPaymentService();
+    method public java.util.List<java.lang.String> getAidsForService(android.content.ComponentName, String);
+    method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
+    method public static android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter);
+    method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
+    method public int getSelectionModeForCategory(String);
+    method public boolean isDefaultServiceForAid(android.content.ComponentName, String);
+    method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
+    method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
+    method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String);
+    method public boolean removeAidsForService(android.content.ComponentName, String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean setOffHostForService(@NonNull android.content.ComponentName, @NonNull String);
+    method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
+    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setServiceObserveModeDefault(@NonNull android.content.ComponentName, boolean);
+    method public boolean supportsAidPrefixRegistration();
+    method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName);
+    method public boolean unsetPreferredService(android.app.Activity);
+    field @Deprecated public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
+    field public static final String CATEGORY_OTHER = "other";
+    field public static final String CATEGORY_PAYMENT = "payment";
+    field public static final String EXTRA_CATEGORY = "category";
+    field public static final String EXTRA_SERVICE_COMPONENT = "component";
+    field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1
+    field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2
+    field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
+  }
+
+  public abstract class HostApduService extends android.app.Service {
+    ctor public HostApduService();
+    method public final void notifyUnhandled();
+    method public final android.os.IBinder onBind(android.content.Intent);
+    method public abstract void onDeactivated(int);
+    method public abstract byte[] processCommandApdu(byte[], android.os.Bundle);
+    method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.os.Bundle>);
+    method public final void sendResponseApdu(byte[]);
+    field public static final int DEACTIVATION_DESELECTED = 1; // 0x1
+    field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_A = 65; // 0x0041 'A'
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_B = 66; // 0x0042 'B'
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_F = 70; // 0x0046 'F'
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_OFF = 88; // 0x0058 'X'
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_ON = 79; // 0x004f 'O'
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x0055 'U'
+    field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_APDU_SERVICE";
+    field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_apdu_service";
+  }
+
+  public abstract class HostNfcFService extends android.app.Service {
+    ctor public HostNfcFService();
+    method public final android.os.IBinder onBind(android.content.Intent);
+    method public abstract void onDeactivated(int);
+    method public abstract byte[] processNfcFPacket(byte[], android.os.Bundle);
+    method public final void sendResponsePacket(byte[]);
+    field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
+    field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_NFCF_SERVICE";
+    field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_nfcf_service";
+  }
+
+  public final class NfcFCardEmulation {
+    method public boolean disableService(android.app.Activity) throws java.lang.RuntimeException;
+    method public boolean enableService(android.app.Activity, android.content.ComponentName) throws java.lang.RuntimeException;
+    method public static android.nfc.cardemulation.NfcFCardEmulation getInstance(android.nfc.NfcAdapter);
+    method public String getNfcid2ForService(android.content.ComponentName) throws java.lang.RuntimeException;
+    method public String getSystemCodeForService(android.content.ComponentName) throws java.lang.RuntimeException;
+    method public boolean registerSystemCodeForService(android.content.ComponentName, String) throws java.lang.RuntimeException;
+    method public boolean setNfcid2ForService(android.content.ComponentName, String) throws java.lang.RuntimeException;
+    method public boolean unregisterSystemCodeForService(android.content.ComponentName) throws java.lang.RuntimeException;
+  }
+
+  public abstract class OffHostApduService extends android.app.Service {
+    ctor public OffHostApduService();
+    field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE";
+    field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.off_host_apdu_service";
+  }
+
+}
+
+package android.nfc.tech {
+
+  public final class IsoDep implements android.nfc.tech.TagTechnology {
+    method public void close() throws java.io.IOException;
+    method public void connect() throws java.io.IOException;
+    method public static android.nfc.tech.IsoDep get(android.nfc.Tag);
+    method public byte[] getHiLayerResponse();
+    method public byte[] getHistoricalBytes();
+    method public int getMaxTransceiveLength();
+    method public android.nfc.Tag getTag();
+    method public int getTimeout();
+    method public boolean isConnected();
+    method public boolean isExtendedLengthApduSupported();
+    method public void setTimeout(int);
+    method public byte[] transceive(byte[]) throws java.io.IOException;
+  }
+
+  public final class MifareClassic implements android.nfc.tech.TagTechnology {
+    method public boolean authenticateSectorWithKeyA(int, byte[]) throws java.io.IOException;
+    method public boolean authenticateSectorWithKeyB(int, byte[]) throws java.io.IOException;
+    method public int blockToSector(int);
+    method public void close() throws java.io.IOException;
+    method public void connect() throws java.io.IOException;
+    method public void decrement(int, int) throws java.io.IOException;
+    method public static android.nfc.tech.MifareClassic get(android.nfc.Tag);
+    method public int getBlockCount();
+    method public int getBlockCountInSector(int);
+    method public int getMaxTransceiveLength();
+    method public int getSectorCount();
+    method public int getSize();
+    method public android.nfc.Tag getTag();
+    method public int getTimeout();
+    method public int getType();
+    method public void increment(int, int) throws java.io.IOException;
+    method public boolean isConnected();
+    method public byte[] readBlock(int) throws java.io.IOException;
+    method public void restore(int) throws java.io.IOException;
+    method public int sectorToBlock(int);
+    method public void setTimeout(int);
+    method public byte[] transceive(byte[]) throws java.io.IOException;
+    method public void transfer(int) throws java.io.IOException;
+    method public void writeBlock(int, byte[]) throws java.io.IOException;
+    field public static final int BLOCK_SIZE = 16; // 0x10
+    field public static final byte[] KEY_DEFAULT;
+    field public static final byte[] KEY_MIFARE_APPLICATION_DIRECTORY;
+    field public static final byte[] KEY_NFC_FORUM;
+    field public static final int SIZE_1K = 1024; // 0x400
+    field public static final int SIZE_2K = 2048; // 0x800
+    field public static final int SIZE_4K = 4096; // 0x1000
+    field public static final int SIZE_MINI = 320; // 0x140
+    field public static final int TYPE_CLASSIC = 0; // 0x0
+    field public static final int TYPE_PLUS = 1; // 0x1
+    field public static final int TYPE_PRO = 2; // 0x2
+    field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
+  }
+
+  public final class MifareUltralight implements android.nfc.tech.TagTechnology {
+    method public void close() throws java.io.IOException;
+    method public void connect() throws java.io.IOException;
+    method public static android.nfc.tech.MifareUltralight get(android.nfc.Tag);
+    method public int getMaxTransceiveLength();
+    method public android.nfc.Tag getTag();
+    method public int getTimeout();
+    method public int getType();
+    method public boolean isConnected();
+    method public byte[] readPages(int) throws java.io.IOException;
+    method public void setTimeout(int);
+    method public byte[] transceive(byte[]) throws java.io.IOException;
+    method public void writePage(int, byte[]) throws java.io.IOException;
+    field public static final int PAGE_SIZE = 4; // 0x4
+    field public static final int TYPE_ULTRALIGHT = 1; // 0x1
+    field public static final int TYPE_ULTRALIGHT_C = 2; // 0x2
+    field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
+  }
+
+  public final class Ndef implements android.nfc.tech.TagTechnology {
+    method public boolean canMakeReadOnly();
+    method public void close() throws java.io.IOException;
+    method public void connect() throws java.io.IOException;
+    method public static android.nfc.tech.Ndef get(android.nfc.Tag);
+    method public android.nfc.NdefMessage getCachedNdefMessage();
+    method public int getMaxSize();
+    method public android.nfc.NdefMessage getNdefMessage() throws android.nfc.FormatException, java.io.IOException;
+    method public android.nfc.Tag getTag();
+    method public String getType();
+    method public boolean isConnected();
+    method public boolean isWritable();
+    method public boolean makeReadOnly() throws java.io.IOException;
+    method public void writeNdefMessage(android.nfc.NdefMessage) throws android.nfc.FormatException, java.io.IOException;
+    field public static final String MIFARE_CLASSIC = "com.nxp.ndef.mifareclassic";
+    field public static final String NFC_FORUM_TYPE_1 = "org.nfcforum.ndef.type1";
+    field public static final String NFC_FORUM_TYPE_2 = "org.nfcforum.ndef.type2";
+    field public static final String NFC_FORUM_TYPE_3 = "org.nfcforum.ndef.type3";
+    field public static final String NFC_FORUM_TYPE_4 = "org.nfcforum.ndef.type4";
+  }
+
+  public final class NdefFormatable implements android.nfc.tech.TagTechnology {
+    method public void close() throws java.io.IOException;
+    method public void connect() throws java.io.IOException;
+    method public void format(android.nfc.NdefMessage) throws android.nfc.FormatException, java.io.IOException;
+    method public void formatReadOnly(android.nfc.NdefMessage) throws android.nfc.FormatException, java.io.IOException;
+    method public static android.nfc.tech.NdefFormatable get(android.nfc.Tag);
+    method public android.nfc.Tag getTag();
+    method public boolean isConnected();
+  }
+
+  public final class NfcA implements android.nfc.tech.TagTechnology {
+    method public void close() throws java.io.IOException;
+    method public void connect() throws java.io.IOException;
+    method public static android.nfc.tech.NfcA get(android.nfc.Tag);
+    method public byte[] getAtqa();
+    method public int getMaxTransceiveLength();
+    method public short getSak();
+    method public android.nfc.Tag getTag();
+    method public int getTimeout();
+    method public boolean isConnected();
+    method public void setTimeout(int);
+    method public byte[] transceive(byte[]) throws java.io.IOException;
+  }
+
+  public final class NfcB implements android.nfc.tech.TagTechnology {
+    method public void close() throws java.io.IOException;
+    method public void connect() throws java.io.IOException;
+    method public static android.nfc.tech.NfcB get(android.nfc.Tag);
+    method public byte[] getApplicationData();
+    method public int getMaxTransceiveLength();
+    method public byte[] getProtocolInfo();
+    method public android.nfc.Tag getTag();
+    method public boolean isConnected();
+    method public byte[] transceive(byte[]) throws java.io.IOException;
+  }
+
+  public final class NfcBarcode implements android.nfc.tech.TagTechnology {
+    method public void close() throws java.io.IOException;
+    method public void connect() throws java.io.IOException;
+    method public static android.nfc.tech.NfcBarcode get(android.nfc.Tag);
+    method public byte[] getBarcode();
+    method public android.nfc.Tag getTag();
+    method public int getType();
+    method public boolean isConnected();
+    field public static final int TYPE_KOVIO = 1; // 0x1
+    field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
+  }
+
+  public final class NfcF implements android.nfc.tech.TagTechnology {
+    method public void close() throws java.io.IOException;
+    method public void connect() throws java.io.IOException;
+    method public static android.nfc.tech.NfcF get(android.nfc.Tag);
+    method public byte[] getManufacturer();
+    method public int getMaxTransceiveLength();
+    method public byte[] getSystemCode();
+    method public android.nfc.Tag getTag();
+    method public int getTimeout();
+    method public boolean isConnected();
+    method public void setTimeout(int);
+    method public byte[] transceive(byte[]) throws java.io.IOException;
+  }
+
+  public final class NfcV implements android.nfc.tech.TagTechnology {
+    method public void close() throws java.io.IOException;
+    method public void connect() throws java.io.IOException;
+    method public static android.nfc.tech.NfcV get(android.nfc.Tag);
+    method public byte getDsfId();
+    method public int getMaxTransceiveLength();
+    method public byte getResponseFlags();
+    method public android.nfc.Tag getTag();
+    method public boolean isConnected();
+    method public byte[] transceive(byte[]) throws java.io.IOException;
+  }
+
+  public interface TagTechnology extends java.io.Closeable {
+    method public void connect() throws java.io.IOException;
+    method public android.nfc.Tag getTag();
+    method public boolean isConnected();
+  }
+
+}
+
diff --git a/nfc/api/lint-baseline.txt b/nfc/api/lint-baseline.txt
new file mode 100644
index 0000000..ef9aab6
--- /dev/null
+++ b/nfc/api/lint-baseline.txt
@@ -0,0 +1,95 @@
+// Baseline format: 1.0
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_ADAPTER_STATE_CHANGED:
+    Field 'ACTION_ADAPTER_STATE_CHANGED' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_PREFERRED_PAYMENT_CHANGED:
+    Field 'ACTION_PREFERRED_PAYMENT_CHANGED' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED:
+    Field 'ACTION_TRANSACTION_DETECTED' is missing @BroadcastBehavior
+
+
+MissingNullability: android.nfc.cardemulation.OffHostApduService#onBind(android.content.Intent):
+    Missing nullability on method `onBind` return
+MissingNullability: android.nfc.cardemulation.OffHostApduService#onBind(android.content.Intent) parameter #0:
+    Missing nullability on parameter `intent` in method `onBind`
+
+
+RequiresPermission: android.nfc.NfcAdapter#disableForegroundDispatch(android.app.Activity):
+    Method 'disableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.NfcAdapter#enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]):
+    Method 'enableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForAid(android.content.ComponentName, String):
+    Method 'isDefaultServiceForAid' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String):
+    Method 'isDefaultServiceForCategory' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#setOffHostForService(android.content.ComponentName, String):
+    Method 'setOffHostForService' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyA(int, byte[]):
+    Method 'authenticateSectorWithKeyA' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyB(int, byte[]):
+    Method 'authenticateSectorWithKeyB' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#decrement(int, int):
+    Method 'decrement' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#increment(int, int):
+    Method 'increment' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#readBlock(int):
+    Method 'readBlock' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#restore(int):
+    Method 'restore' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#transfer(int):
+    Method 'transfer' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#writeBlock(int, byte[]):
+    Method 'writeBlock' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#readPages(int):
+    Method 'readPages' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#writePage(int, byte[]):
+    Method 'writePage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#getNdefMessage():
+    Method 'getNdefMessage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#isWritable():
+    Method 'isWritable' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#makeReadOnly():
+    Method 'makeReadOnly' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#writeNdefMessage(android.nfc.NdefMessage):
+    Method 'writeNdefMessage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NdefFormatable#format(android.nfc.NdefMessage):
+    Method 'format' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NdefFormatable#formatReadOnly(android.nfc.NdefMessage):
+    Method 'formatReadOnly' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcB#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcV#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.TagTechnology#close():
+    Method 'close' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.TagTechnology#connect():
+    Method 'connect' documentation mentions permissions without declaring @RequiresPermission
diff --git a/nfc/api/module-lib-current.txt b/nfc/api/module-lib-current.txt
index d802177..5ebe911 100644
--- a/nfc/api/module-lib-current.txt
+++ b/nfc/api/module-lib-current.txt
@@ -1 +1,10 @@
 // Signature format: 2.0
+package android.nfc {
+
+  public class NfcFrameworkInitializer {
+    method public static void registerServiceWrappers();
+    method public static void setNfcServiceManager(@NonNull android.nfc.NfcServiceManager);
+  }
+
+}
+
diff --git a/nfc/api/module-lib-lint-baseline.txt b/nfc/api/module-lib-lint-baseline.txt
new file mode 100644
index 0000000..f7f8ee3
--- /dev/null
+++ b/nfc/api/module-lib-lint-baseline.txt
@@ -0,0 +1,93 @@
+// Baseline format: 1.0
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_ADAPTER_STATE_CHANGED:
+    Field 'ACTION_ADAPTER_STATE_CHANGED' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_PREFERRED_PAYMENT_CHANGED:
+    Field 'ACTION_PREFERRED_PAYMENT_CHANGED' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_REQUIRE_UNLOCK_FOR_NFC:
+    Field 'ACTION_REQUIRE_UNLOCK_FOR_NFC' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED:
+    Field 'ACTION_TRANSACTION_DETECTED' is missing @BroadcastBehavior
+
+RequiresPermission: android.nfc.NfcAdapter#disableForegroundDispatch(android.app.Activity):
+    Method 'disableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.NfcAdapter#enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]):
+    Method 'enableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForAid(android.content.ComponentName, String):
+    Method 'isDefaultServiceForAid' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String):
+    Method 'isDefaultServiceForCategory' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#setOffHostForService(android.content.ComponentName, String):
+    Method 'setOffHostForService' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyA(int, byte[]):
+    Method 'authenticateSectorWithKeyA' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyB(int, byte[]):
+    Method 'authenticateSectorWithKeyB' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#decrement(int, int):
+    Method 'decrement' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#increment(int, int):
+    Method 'increment' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#readBlock(int):
+    Method 'readBlock' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#restore(int):
+    Method 'restore' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#transfer(int):
+    Method 'transfer' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#writeBlock(int, byte[]):
+    Method 'writeBlock' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#readPages(int):
+    Method 'readPages' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#writePage(int, byte[]):
+    Method 'writePage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#getNdefMessage():
+    Method 'getNdefMessage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#isWritable():
+    Method 'isWritable' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#makeReadOnly():
+    Method 'makeReadOnly' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#writeNdefMessage(android.nfc.NdefMessage):
+    Method 'writeNdefMessage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NdefFormatable#format(android.nfc.NdefMessage):
+    Method 'format' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NdefFormatable#formatReadOnly(android.nfc.NdefMessage):
+    Method 'formatReadOnly' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcB#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcV#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.TagTechnology#close():
+    Method 'close' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.TagTechnology#connect():
+    Method 'connect' documentation mentions permissions without declaring @RequiresPermission
+
+SdkConstant: android.nfc.NfcAdapter#ACTION_REQUIRE_UNLOCK_FOR_NFC:
+    Field 'ACTION_REQUIRE_UNLOCK_FOR_NFC' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
diff --git a/nfc/api/removed.txt b/nfc/api/removed.txt
index d802177..fb82b5d 100644
--- a/nfc/api/removed.txt
+++ b/nfc/api/removed.txt
@@ -1 +1,17 @@
 // Signature format: 2.0
+package android.nfc {
+
+  public final class NfcAdapter {
+    method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void disableForegroundNdefPush(android.app.Activity);
+    method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage);
+    method @Deprecated @android.compat.annotation.UnsupportedAppUsage public boolean invokeBeam(android.app.Activity);
+    method @Deprecated @android.compat.annotation.UnsupportedAppUsage public boolean isNdefPushEnabled();
+    method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void setBeamPushUris(android.net.Uri[], android.app.Activity);
+    method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity);
+    method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
+    method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...);
+    method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...);
+  }
+
+}
+
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index d802177..3524f8c 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -1 +1,53 @@
 // Signature format: 2.0
+package android.nfc {
+
+  public final class NfcAdapter {
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean addNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler, String[]);
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable();
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(boolean);
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
+    method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean);
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getAdapterState();
+    method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.Map<java.lang.String,java.lang.Boolean> getTagIntentAppPreferenceForUser(int);
+    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
+    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported();
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagIntentAppPreferenceSupported();
+    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+    method @FlaggedApi("android.nfc.enable_nfc_charging") public void registerWlcStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.WlcStateListener);
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
+    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean);
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
+    method @FlaggedApi("android.nfc.enable_nfc_charging") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setWlcEnabled(boolean);
+    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+    method @FlaggedApi("android.nfc.enable_nfc_charging") public void unregisterWlcStateListener(@NonNull android.nfc.NfcAdapter.WlcStateListener);
+    field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
+    field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff
+    field public static final int TAG_INTENT_APP_PREF_RESULT_SUCCESS = 0; // 0x0
+    field public static final int TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE = -2; // 0xfffffffe
+  }
+
+  public static interface NfcAdapter.ControllerAlwaysOnListener {
+    method public void onControllerAlwaysOnChanged(boolean);
+  }
+
+  public static interface NfcAdapter.NfcUnlockHandler {
+    method public boolean onUnlockAttempted(android.nfc.Tag);
+  }
+
+  @FlaggedApi("android.nfc.enable_nfc_charging") public static interface NfcAdapter.WlcStateListener {
+    method public void onWlcStateChanged(@NonNull android.nfc.WlcListenerDeviceInfo);
+  }
+
+}
+
+package android.nfc.cardemulation {
+
+  public final class CardEmulation {
+    method @FlaggedApi("android.permission.flags.wallet_role_enabled") @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static android.content.ComponentName getPreferredPaymentService(@NonNull android.content.Context);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<android.nfc.cardemulation.ApduServiceInfo> getServices(@NonNull String, int);
+  }
+
+}
+
diff --git a/nfc/api/system-lint-baseline.txt b/nfc/api/system-lint-baseline.txt
new file mode 100644
index 0000000..761c8e6
--- /dev/null
+++ b/nfc/api/system-lint-baseline.txt
@@ -0,0 +1,105 @@
+// Baseline format: 1.0
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_ADAPTER_STATE_CHANGED:
+    Field 'ACTION_ADAPTER_STATE_CHANGED' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_PREFERRED_PAYMENT_CHANGED:
+    Field 'ACTION_PREFERRED_PAYMENT_CHANGED' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_REQUIRE_UNLOCK_FOR_NFC:
+    Field 'ACTION_REQUIRE_UNLOCK_FOR_NFC' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED:
+    Field 'ACTION_TRANSACTION_DETECTED' is missing @BroadcastBehavior
+
+
+MissingNullability: android.nfc.cardemulation.OffHostApduService#onBind(android.content.Intent):
+    Missing nullability on method `onBind` return
+MissingNullability: android.nfc.cardemulation.OffHostApduService#onBind(android.content.Intent) parameter #0:
+    Missing nullability on parameter `intent` in method `onBind`
+
+
+RequiresPermission: android.nfc.NfcAdapter#disableForegroundDispatch(android.app.Activity):
+    Method 'disableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.NfcAdapter#enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]):
+    Method 'enableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForAid(android.content.ComponentName, String):
+    Method 'isDefaultServiceForAid' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String):
+    Method 'isDefaultServiceForCategory' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#setOffHostForService(android.content.ComponentName, String):
+    Method 'setOffHostForService' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyA(int, byte[]):
+    Method 'authenticateSectorWithKeyA' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyB(int, byte[]):
+    Method 'authenticateSectorWithKeyB' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#decrement(int, int):
+    Method 'decrement' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#increment(int, int):
+    Method 'increment' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#readBlock(int):
+    Method 'readBlock' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#restore(int):
+    Method 'restore' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#transfer(int):
+    Method 'transfer' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#writeBlock(int, byte[]):
+    Method 'writeBlock' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#readPages(int):
+    Method 'readPages' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#writePage(int, byte[]):
+    Method 'writePage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#getNdefMessage():
+    Method 'getNdefMessage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#isWritable():
+    Method 'isWritable' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#makeReadOnly():
+    Method 'makeReadOnly' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#writeNdefMessage(android.nfc.NdefMessage):
+    Method 'writeNdefMessage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NdefFormatable#format(android.nfc.NdefMessage):
+    Method 'format' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NdefFormatable#formatReadOnly(android.nfc.NdefMessage):
+    Method 'formatReadOnly' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcB#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcV#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.TagTechnology#close():
+    Method 'close' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.TagTechnology#connect():
+    Method 'connect' documentation mentions permissions without declaring @RequiresPermission
+
+SamShouldBeLast: android.nfc.NfcAdapter#enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle):
+    SAM-compatible parameters (such as parameter 2, "callback", in android.nfc.NfcAdapter.enableReaderMode) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.nfc.NfcAdapter#ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 3, "tagRemovedListener", in android.nfc.NfcAdapter.ignore) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+
+SdkConstant: android.nfc.NfcAdapter#ACTION_REQUIRE_UNLOCK_FOR_NFC:
+    Field 'ACTION_REQUIRE_UNLOCK_FOR_NFC' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
diff --git a/nfc/api/system-removed.txt b/nfc/api/system-removed.txt
index d802177..c6eaa57 100644
--- a/nfc/api/system-removed.txt
+++ b/nfc/api/system-removed.txt
@@ -1 +1,12 @@
 // Signature format: 2.0
+package android.nfc {
+
+  public final class NfcAdapter {
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disableNdefPush();
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableNdefPush();
+    method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int);
+    field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1
+  }
+
+}
+
diff --git a/nfc/jarjar-rules.txt b/nfc/jarjar-rules.txt
new file mode 100644
index 0000000..99ae144
--- /dev/null
+++ b/nfc/jarjar-rules.txt
@@ -0,0 +1,39 @@
+# Used by framework-nfc for proto debug dumping
+rule android.app.PendingIntentProto* com.android.nfc.x.@0
+rule android.content.ComponentNameProto* com.android.nfc.x.@0
+rule android.content.IntentProto* com.android.nfc.x.@0
+rule android.content.IntentFilterProto* com.android.nfc.x.@0
+rule android.content.AuthorityEntryProto* com.android.nfc.x.@0
+rule android.content.UriRelativeFilter* com.android.nfc.x.@0
+rule android.nfc.cardemulation.AidGroupProto* com.android.nfc.x.@0
+rule android.nfc.cardemulation.ApduServiceInfoProto* com.android.nfc.x.@0
+rule android.nfc.cardemulation.NfcFServiceInfoProto* com.android.nfc.x.@0
+rule android.nfc.NdefMessageProto* com.android.nfc.x.@0
+rule android.nfc.NdefRecordProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.CardEmulationManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.RegisteredServicesCacheProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.RegisteredNfcFServicesCacheProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.PreferredServicesProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.EnabledNfcFServicesProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.RegisteredAidCacheProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.AidRoutingManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.RegisteredT3tIdentifiersCacheProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.SystemCodeRoutingManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.HostEmulationManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.HostNfcFEmulationManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.NfcServiceDumpProto* com.android.nfc.x.@0
+rule com.android.nfc.DiscoveryParamsProto* com.android.nfc.x.@0
+rule com.android.nfc.NfcDispatcherProto* com.android.nfc.x.@0
+rule android.os.PersistableBundleProto* com.android.nfc.x.@0
+
+# Used by framework-nfc for reading trunk stable flags
+rule android.nfc.FakeFeatureFlagsImpl* com.android.nfc.x.@0
+rule android.nfc.FeatureFlags* com.android.nfc.x.@0
+rule android.nfc.Flags* com.android.nfc.x.@0
+rule android.permission.flags.** com.android.nfc.x.@0
+
+# Used by framework-nfc for misc utilities
+rule android.os.PatternMatcher* com.android.nfc.x.@0
+
+rule com.android.incident.Privacy* com.android.nfc.x.@0
+rule com.android.incident.PrivacyFlags* com.android.nfc.x.@0
diff --git a/core/java/android/nfc/ApduList.aidl b/nfc/java/android/nfc/ApduList.aidl
similarity index 100%
rename from core/java/android/nfc/ApduList.aidl
rename to nfc/java/android/nfc/ApduList.aidl
diff --git a/core/java/android/nfc/ApduList.java b/nfc/java/android/nfc/ApduList.java
similarity index 100%
rename from core/java/android/nfc/ApduList.java
rename to nfc/java/android/nfc/ApduList.java
diff --git a/core/java/android/nfc/AvailableNfcAntenna.aidl b/nfc/java/android/nfc/AvailableNfcAntenna.aidl
similarity index 100%
rename from core/java/android/nfc/AvailableNfcAntenna.aidl
rename to nfc/java/android/nfc/AvailableNfcAntenna.aidl
diff --git a/core/java/android/nfc/AvailableNfcAntenna.java b/nfc/java/android/nfc/AvailableNfcAntenna.java
similarity index 100%
rename from core/java/android/nfc/AvailableNfcAntenna.java
rename to nfc/java/android/nfc/AvailableNfcAntenna.java
diff --git a/core/java/android/nfc/Constants.java b/nfc/java/android/nfc/Constants.java
similarity index 100%
rename from core/java/android/nfc/Constants.java
rename to nfc/java/android/nfc/Constants.java
diff --git a/core/java/android/nfc/ErrorCodes.java b/nfc/java/android/nfc/ErrorCodes.java
similarity index 100%
rename from core/java/android/nfc/ErrorCodes.java
rename to nfc/java/android/nfc/ErrorCodes.java
diff --git a/core/java/android/nfc/FormatException.java b/nfc/java/android/nfc/FormatException.java
similarity index 100%
rename from core/java/android/nfc/FormatException.java
rename to nfc/java/android/nfc/FormatException.java
diff --git a/core/java/android/nfc/IAppCallback.aidl b/nfc/java/android/nfc/IAppCallback.aidl
similarity index 100%
rename from core/java/android/nfc/IAppCallback.aidl
rename to nfc/java/android/nfc/IAppCallback.aidl
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
new file mode 100644
index 0000000..63c3414
--- /dev/null
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+import android.app.PendingIntent;
+import android.content.IntentFilter;
+import android.nfc.NdefMessage;
+import android.nfc.Tag;
+import android.nfc.TechListParcel;
+import android.nfc.IAppCallback;
+import android.nfc.INfcAdapterExtras;
+import android.nfc.INfcControllerAlwaysOnListener;
+import android.nfc.INfcTag;
+import android.nfc.INfcCardEmulation;
+import android.nfc.INfcFCardEmulation;
+import android.nfc.INfcUnlockHandler;
+import android.nfc.ITagRemovedCallback;
+import android.nfc.INfcDta;
+import android.nfc.INfcWlcStateListener;
+import android.nfc.NfcAntennaInfo;
+import android.nfc.WlcListenerDeviceInfo;
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface INfcAdapter
+{
+    INfcTag getNfcTagInterface();
+    INfcCardEmulation getNfcCardEmulationInterface();
+    INfcFCardEmulation getNfcFCardEmulationInterface();
+    INfcAdapterExtras getNfcAdapterExtrasInterface(in String pkg);
+    INfcDta getNfcDtaInterface(in String pkg);
+    int getState();
+    boolean disable(boolean saveState);
+    boolean enable();
+    void pausePolling(int timeoutInMs);
+    void resumePolling();
+
+    void setForegroundDispatch(in PendingIntent intent,
+            in IntentFilter[] filters, in TechListParcel techLists);
+    void setAppCallback(in IAppCallback callback);
+
+    boolean ignore(int nativeHandle, int debounceMs, ITagRemovedCallback callback);
+
+    void dispatch(in Tag tag);
+
+    void setReaderMode (IBinder b, IAppCallback callback, int flags, in Bundle extras);
+
+    void addNfcUnlockHandler(INfcUnlockHandler unlockHandler, in int[] techList);
+    void removeNfcUnlockHandler(INfcUnlockHandler unlockHandler);
+
+    void verifyNfcPermission();
+    boolean isNfcSecureEnabled();
+    boolean deviceSupportsNfcSecure();
+    boolean setNfcSecure(boolean enable);
+    NfcAntennaInfo getNfcAntennaInfo();
+
+    boolean setControllerAlwaysOn(boolean value);
+    boolean isControllerAlwaysOn();
+    boolean isControllerAlwaysOnSupported();
+    void registerControllerAlwaysOnListener(in INfcControllerAlwaysOnListener listener);
+    void unregisterControllerAlwaysOnListener(in INfcControllerAlwaysOnListener listener);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+    boolean isTagIntentAppPreferenceSupported();
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+    Map getTagIntentAppPreferenceForUser(int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+    int setTagIntentAppPreferenceForUser(int userId, String pkg, boolean allow);
+
+    boolean isReaderOptionEnabled();
+    boolean isReaderOptionSupported();
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+    boolean enableReaderOption(boolean enable);
+    boolean isObserveModeSupported();
+    boolean setObserveMode(boolean enabled);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+    boolean setWlcEnabled(boolean enable);
+    boolean isWlcEnabled();
+    void registerWlcStateListener(in INfcWlcStateListener listener);
+    void unregisterWlcStateListener(in INfcWlcStateListener listener);
+    WlcListenerDeviceInfo getWlcListenerDeviceInfo();
+
+    void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags);
+
+    void notifyPollingLoop(in Bundle frame);
+    void notifyHceDeactivated();
+}
diff --git a/core/java/android/nfc/INfcAdapterExtras.aidl b/nfc/java/android/nfc/INfcAdapterExtras.aidl
similarity index 100%
rename from core/java/android/nfc/INfcAdapterExtras.aidl
rename to nfc/java/android/nfc/INfcAdapterExtras.aidl
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
new file mode 100644
index 0000000..791bd8c
--- /dev/null
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+import android.content.ComponentName;
+import android.nfc.cardemulation.AidGroup;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.os.RemoteCallback;
+
+/**
+ * @hide
+ */
+interface INfcCardEmulation
+{
+    boolean isDefaultServiceForCategory(int userHandle, in ComponentName service, String category);
+    boolean isDefaultServiceForAid(int userHandle, in ComponentName service, String aid);
+    boolean setDefaultServiceForCategory(int userHandle, in ComponentName service, String category);
+    boolean setDefaultForNextTap(int userHandle, in ComponentName service);
+    boolean setServiceObserveModeDefault(int userId, in android.content.ComponentName service, boolean enable);
+    boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup);
+    boolean registerPollingLoopFilterForService(int userHandle, in ComponentName service, in String pollingLoopFilter);
+    boolean setOffHostForService(int userHandle, in ComponentName service, in String offHostSecureElement);
+    boolean unsetOffHostForService(int userHandle, in ComponentName service);
+    AidGroup getAidGroupForService(int userHandle, in ComponentName service, String category);
+    boolean removeAidGroupForService(int userHandle, in ComponentName service, String category);
+    List<ApduServiceInfo> getServices(int userHandle, in String category);
+    boolean setPreferredService(in ComponentName service);
+    boolean unsetPreferredService();
+    boolean supportsAidPrefixRegistration();
+    ApduServiceInfo getPreferredPaymentService(int userHandle);
+    boolean setServiceEnabledForCategoryOther(int userHandle, in ComponentName app, boolean status);
+    boolean isDefaultPaymentRegistered();
+
+    boolean overrideRoutingTable(int userHandle, String protocol, String technology);
+    boolean recoverRoutingTable(int userHandle);
+}
diff --git a/core/java/android/nfc/INfcControllerAlwaysOnListener.aidl b/nfc/java/android/nfc/INfcControllerAlwaysOnListener.aidl
similarity index 100%
rename from core/java/android/nfc/INfcControllerAlwaysOnListener.aidl
rename to nfc/java/android/nfc/INfcControllerAlwaysOnListener.aidl
diff --git a/core/java/android/nfc/INfcDta.aidl b/nfc/java/android/nfc/INfcDta.aidl
similarity index 100%
rename from core/java/android/nfc/INfcDta.aidl
rename to nfc/java/android/nfc/INfcDta.aidl
diff --git a/core/java/android/nfc/INfcFCardEmulation.aidl b/nfc/java/android/nfc/INfcFCardEmulation.aidl
similarity index 100%
rename from core/java/android/nfc/INfcFCardEmulation.aidl
rename to nfc/java/android/nfc/INfcFCardEmulation.aidl
diff --git a/core/java/android/nfc/INfcTag.aidl b/nfc/java/android/nfc/INfcTag.aidl
similarity index 100%
rename from core/java/android/nfc/INfcTag.aidl
rename to nfc/java/android/nfc/INfcTag.aidl
diff --git a/core/java/android/nfc/INfcUnlockHandler.aidl b/nfc/java/android/nfc/INfcUnlockHandler.aidl
similarity index 100%
rename from core/java/android/nfc/INfcUnlockHandler.aidl
rename to nfc/java/android/nfc/INfcUnlockHandler.aidl
diff --git a/nfc/java/android/nfc/INfcWlcStateListener.aidl b/nfc/java/android/nfc/INfcWlcStateListener.aidl
new file mode 100644
index 0000000..584eb9a
--- /dev/null
+++ b/nfc/java/android/nfc/INfcWlcStateListener.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+import android.nfc.WlcListenerDeviceInfo;
+/**
+ * @hide
+ */
+oneway interface INfcWlcStateListener {
+  /**
+   * Called whenever NFC WLC state changes
+   *
+   * @param wlcListenerDeviceInfo NFC wlc listener information
+   */
+  void onWlcStateChanged(in WlcListenerDeviceInfo wlcListenerDeviceInfo);
+}
diff --git a/core/java/android/nfc/ITagRemovedCallback.aidl b/nfc/java/android/nfc/ITagRemovedCallback.aidl
similarity index 100%
rename from core/java/android/nfc/ITagRemovedCallback.aidl
rename to nfc/java/android/nfc/ITagRemovedCallback.aidl
diff --git a/core/java/android/nfc/NdefMessage.aidl b/nfc/java/android/nfc/NdefMessage.aidl
similarity index 100%
rename from core/java/android/nfc/NdefMessage.aidl
rename to nfc/java/android/nfc/NdefMessage.aidl
diff --git a/core/java/android/nfc/NdefMessage.java b/nfc/java/android/nfc/NdefMessage.java
similarity index 100%
rename from core/java/android/nfc/NdefMessage.java
rename to nfc/java/android/nfc/NdefMessage.java
diff --git a/core/java/android/nfc/NdefRecord.aidl b/nfc/java/android/nfc/NdefRecord.aidl
similarity index 100%
rename from core/java/android/nfc/NdefRecord.aidl
rename to nfc/java/android/nfc/NdefRecord.aidl
diff --git a/core/java/android/nfc/NdefRecord.java b/nfc/java/android/nfc/NdefRecord.java
similarity index 100%
rename from core/java/android/nfc/NdefRecord.java
rename to nfc/java/android/nfc/NdefRecord.java
diff --git a/core/java/android/nfc/NfcActivityManager.java b/nfc/java/android/nfc/NfcActivityManager.java
similarity index 100%
rename from core/java/android/nfc/NfcActivityManager.java
rename to nfc/java/android/nfc/NfcActivityManager.java
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
new file mode 100644
index 0000000..11eb97b
--- /dev/null
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -0,0 +1,2968 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UserIdInt;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.nfc.tech.MifareClassic;
+import android.nfc.tech.Ndef;
+import android.nfc.tech.NfcA;
+import android.nfc.tech.NfcF;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Represents the local NFC adapter.
+ * <p>
+ * Use the helper {@link #getDefaultAdapter(Context)} to get the default NFC
+ * adapter for this Android device.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using NFC, read the
+ * <a href="{@docRoot}guide/topics/nfc/index.html">Near Field Communication</a> developer guide.</p>
+ * <p>To perform basic file sharing between devices, read
+ * <a href="{@docRoot}training/beam-files/index.html">Sharing Files with NFC</a>.
+ * </div>
+ */
+public final class NfcAdapter {
+    static final String TAG = "NFC";
+
+    private final NfcControllerAlwaysOnListener mControllerAlwaysOnListener;
+    private final NfcWlcStateListener mNfcWlcStateListener;
+
+    /**
+     * Intent to start an activity when a tag with NDEF payload is discovered.
+     *
+     * <p>The system inspects the first {@link NdefRecord} in the first {@link NdefMessage} and
+     * looks for a URI, SmartPoster, or MIME record. If a URI or SmartPoster record is found the
+     * intent will contain the URI in its data field. If a MIME record is found the intent will
+     * contain the MIME type in its type field. This allows activities to register
+     * {@link IntentFilter}s targeting specific content on tags. Activities should register the
+     * most specific intent filters possible to avoid the activity chooser dialog, which can
+     * disrupt the interaction with the tag as the user interacts with the screen.
+     *
+     * <p>If the tag has an NDEF payload this intent is started before
+     * {@link #ACTION_TECH_DISCOVERED}. If any activities respond to this intent neither
+     * {@link #ACTION_TECH_DISCOVERED} or {@link #ACTION_TAG_DISCOVERED} will be started.
+     *
+     * <p>The MIME type or data URI of this intent are normalized before dispatch -
+     * so that MIME, URI scheme and URI host are always lower-case.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
+
+    /**
+     * Intent to start an activity when a tag is discovered and activities are registered for the
+     * specific technologies on the tag.
+     *
+     * <p>To receive this intent an activity must include an intent filter
+     * for this action and specify the desired tech types in a
+     * manifest <code>meta-data</code> entry. Here is an example manfiest entry:
+     * <pre>
+     * &lt;activity android:name=".nfc.TechFilter" android:label="NFC/TechFilter"&gt;
+     *     &lt;!-- Add a technology filter --&gt;
+     *     &lt;intent-filter&gt;
+     *         &lt;action android:name="android.nfc.action.TECH_DISCOVERED" /&gt;
+     *     &lt;/intent-filter&gt;
+     *
+     *     &lt;meta-data android:name="android.nfc.action.TECH_DISCOVERED"
+     *         android:resource="@xml/filter_nfc"
+     *     /&gt;
+     * &lt;/activity&gt;</pre>
+     *
+     * <p>The meta-data XML file should contain one or more <code>tech-list</code> entries
+     * each consisting or one or more <code>tech</code> entries. The <code>tech</code> entries refer
+     * to the qualified class name implementing the technology, for example "android.nfc.tech.NfcA".
+     *
+     * <p>A tag matches if any of the
+     * <code>tech-list</code> sets is a subset of {@link Tag#getTechList() Tag.getTechList()}. Each
+     * of the <code>tech-list</code>s is considered independently and the
+     * activity is considered a match is any single <code>tech-list</code> matches the tag that was
+     * discovered. This provides AND and OR semantics for filtering desired techs. Here is an
+     * example that will match any tag using {@link NfcF} or any tag using {@link NfcA},
+     * {@link MifareClassic}, and {@link Ndef}:
+     *
+     * <pre>
+     * &lt;resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"&gt;
+     *     &lt;!-- capture anything using NfcF --&gt;
+     *     &lt;tech-list&gt;
+     *         &lt;tech&gt;android.nfc.tech.NfcF&lt;/tech&gt;
+     *     &lt;/tech-list&gt;
+     *
+     *     &lt;!-- OR --&gt;
+     *
+     *     &lt;!-- capture all MIFARE Classics with NDEF payloads --&gt;
+     *     &lt;tech-list&gt;
+     *         &lt;tech&gt;android.nfc.tech.NfcA&lt;/tech&gt;
+     *         &lt;tech&gt;android.nfc.tech.MifareClassic&lt;/tech&gt;
+     *         &lt;tech&gt;android.nfc.tech.Ndef&lt;/tech&gt;
+     *     &lt;/tech-list&gt;
+     * &lt;/resources&gt;</pre>
+     *
+     * <p>This intent is started after {@link #ACTION_NDEF_DISCOVERED} and before
+     * {@link #ACTION_TAG_DISCOVERED}. If any activities respond to {@link #ACTION_NDEF_DISCOVERED}
+     * this intent will not be started. If any activities respond to this intent
+     * {@link #ACTION_TAG_DISCOVERED} will not be started.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_TECH_DISCOVERED = "android.nfc.action.TECH_DISCOVERED";
+
+    /**
+     * Intent to start an activity when a tag is discovered.
+     *
+     * <p>This intent will not be started when a tag is discovered if any activities respond to
+     * {@link #ACTION_NDEF_DISCOVERED} or {@link #ACTION_TECH_DISCOVERED} for the current tag.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
+
+    /**
+     * Broadcast Action: Intent to notify an application that a transaction event has occurred
+     * on the Secure Element.
+     *
+     * <p>This intent will only be sent if the application has requested permission for
+     * {@link android.Manifest.permission#NFC_TRANSACTION_EVENT} and if the application has the
+     * necessary access to Secure Element which witnessed the particular event.
+     */
+    @RequiresPermission(android.Manifest.permission.NFC_TRANSACTION_EVENT)
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_TRANSACTION_DETECTED =
+            "android.nfc.action.TRANSACTION_DETECTED";
+
+    /**
+     * Broadcast Action: Intent to notify if the preferred payment service changed.
+     *
+     * <p>This intent will only be sent to the application has requested permission for
+     * {@link android.Manifest.permission#NFC_PREFERRED_PAYMENT_INFO} and if the application
+     * has the necessary access to Secure Element which witnessed the particular event.
+     */
+    @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PREFERRED_PAYMENT_CHANGED =
+            "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
+
+    /**
+     * Broadcast to only the activity that handles ACTION_TAG_DISCOVERED
+     * @hide
+     */
+    public static final String ACTION_TAG_LEFT_FIELD = "android.nfc.action.TAG_LOST";
+
+    /**
+     * Mandatory extra containing the {@link Tag} that was discovered for the
+     * {@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED}, and
+     * {@link #ACTION_TAG_DISCOVERED} intents.
+     */
+    public static final String EXTRA_TAG = "android.nfc.extra.TAG";
+
+    /**
+     * Extra containing an array of {@link NdefMessage} present on the discovered tag.<p>
+     * This extra is mandatory for {@link #ACTION_NDEF_DISCOVERED} intents,
+     * and optional for {@link #ACTION_TECH_DISCOVERED}, and
+     * {@link #ACTION_TAG_DISCOVERED} intents.<p>
+     * When this extra is present there will always be at least one
+     * {@link NdefMessage} element. Most NDEF tags have only one NDEF message,
+     * but we use an array for future compatibility.
+     */
+    public static final String EXTRA_NDEF_MESSAGES = "android.nfc.extra.NDEF_MESSAGES";
+
+    /**
+     * Optional extra containing a byte array containing the ID of the discovered tag for
+     * the {@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED}, and
+     * {@link #ACTION_TAG_DISCOVERED} intents.
+     */
+    public static final String EXTRA_ID = "android.nfc.extra.ID";
+
+    /**
+     * Broadcast Action: The state of the local NFC adapter has been
+     * changed.
+     * <p>For example, NFC has been turned on or off.
+     * <p>Always contains the extra field {@link #EXTRA_ADAPTER_STATE}
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_ADAPTER_STATE_CHANGED =
+            "android.nfc.action.ADAPTER_STATE_CHANGED";
+
+    /**
+     * Used as an int extra field in {@link #ACTION_ADAPTER_STATE_CHANGED}
+     * intents to request the current power state. Possible values are:
+     * {@link #STATE_OFF},
+     * {@link #STATE_TURNING_ON},
+     * {@link #STATE_ON},
+     * {@link #STATE_TURNING_OFF},
+     */
+    public static final String EXTRA_ADAPTER_STATE = "android.nfc.extra.ADAPTER_STATE";
+
+    /**
+     * Mandatory byte[] extra field in {@link #ACTION_TRANSACTION_DETECTED}
+     */
+    public static final String EXTRA_AID = "android.nfc.extra.AID";
+
+    /**
+     * Optional byte[] extra field in {@link #ACTION_TRANSACTION_DETECTED}
+     */
+    public static final String EXTRA_DATA = "android.nfc.extra.DATA";
+
+    /**
+     * Mandatory String extra field in {@link #ACTION_TRANSACTION_DETECTED}
+     * Indicates the Secure Element on which the transaction occurred.
+     * eSE1...eSEn for Embedded Secure Elements, SIM1...SIMn for UICC, etc.
+     */
+    public static final String EXTRA_SECURE_ELEMENT_NAME = "android.nfc.extra.SECURE_ELEMENT_NAME";
+
+    /**
+     * Mandatory String extra field in {@link #ACTION_PREFERRED_PAYMENT_CHANGED}
+     * Indicates the condition when trigger this event. Possible values are:
+     * {@link #PREFERRED_PAYMENT_LOADED},
+     * {@link #PREFERRED_PAYMENT_CHANGED},
+     * {@link #PREFERRED_PAYMENT_UPDATED},
+     */
+    public static final String EXTRA_PREFERRED_PAYMENT_CHANGED_REASON =
+            "android.nfc.extra.PREFERRED_PAYMENT_CHANGED_REASON";
+    /**
+     * Nfc is enabled and the preferred payment aids are registered.
+     */
+    public static final int PREFERRED_PAYMENT_LOADED = 1;
+    /**
+     * User selected another payment application as the preferred payment.
+     */
+    public static final int PREFERRED_PAYMENT_CHANGED = 2;
+    /**
+     * Current preferred payment has issued an update (registered/unregistered new aids or has been
+     * updated itself).
+     */
+    public static final int PREFERRED_PAYMENT_UPDATED = 3;
+
+    public static final int STATE_OFF = 1;
+    public static final int STATE_TURNING_ON = 2;
+    public static final int STATE_ON = 3;
+    public static final int STATE_TURNING_OFF = 4;
+
+    /**
+     * Possible states from {@link #getAdapterState}.
+     *
+     * @hide
+     */
+    @IntDef(prefix = { "STATE_" }, value = {
+            STATE_OFF,
+            STATE_TURNING_ON,
+            STATE_ON,
+            STATE_TURNING_OFF
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AdapterState{}
+
+    /**
+     * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
+     * <p>
+     * Setting this flag enables polling for Nfc-A technology.
+     */
+    public static final int FLAG_READER_NFC_A = 0x1;
+
+    /**
+     * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
+     * <p>
+     * Setting this flag enables polling for Nfc-B technology.
+     */
+    public static final int FLAG_READER_NFC_B = 0x2;
+
+    /**
+     * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
+     * <p>
+     * Setting this flag enables polling for Nfc-F technology.
+     */
+    public static final int FLAG_READER_NFC_F = 0x4;
+
+    /**
+     * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
+     * <p>
+     * Setting this flag enables polling for Nfc-V (ISO15693) technology.
+     */
+    public static final int FLAG_READER_NFC_V = 0x8;
+
+    /**
+     * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
+     * <p>
+     * Setting this flag enables polling for NfcBarcode technology.
+     */
+    public static final int FLAG_READER_NFC_BARCODE = 0x10;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = {"FLAG_READER_"}, value = {
+        FLAG_READER_KEEP,
+        FLAG_READER_DISABLE,
+        FLAG_READER_NFC_A,
+        FLAG_READER_NFC_B,
+        FLAG_READER_NFC_F,
+        FLAG_READER_NFC_V,
+        FLAG_READER_NFC_BARCODE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PollTechnology {}
+
+    /**
+     * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
+     * <p>
+     * Setting this flag allows the caller to prevent the
+     * platform from performing an NDEF check on the tags it
+     * finds.
+     */
+    public static final int FLAG_READER_SKIP_NDEF_CHECK = 0x80;
+
+    /**
+     * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
+     * <p>
+     * Setting this flag allows the caller to prevent the
+     * platform from playing sounds when it discovers a tag.
+     */
+    public static final int FLAG_READER_NO_PLATFORM_SOUNDS = 0x100;
+
+    /**
+     * Int Extra for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
+     * <p>
+     * Setting this integer extra allows the calling application to specify
+     * the delay that the platform will use for performing presence checks
+     * on any discovered tag.
+     */
+    public static final String EXTRA_READER_PRESENCE_CHECK_DELAY = "presence";
+
+    /**
+     * Flag for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag enables listening for Nfc-A technology.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public static final int FLAG_LISTEN_NFC_PASSIVE_A = 0x1;
+
+    /**
+     * Flag for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag enables listening for Nfc-B technology.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public static final int FLAG_LISTEN_NFC_PASSIVE_B = 1 << 1;
+
+    /**
+     * Flag for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag enables listening for Nfc-F technology.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public static final int FLAG_LISTEN_NFC_PASSIVE_F = 1 << 2;
+
+    /**
+     * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag disables listening.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public static final int FLAG_LISTEN_DISABLE = 0x0;
+
+    /**
+     * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag disables polling.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public static final int FLAG_READER_DISABLE = 0x0;
+
+    /**
+     * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag makes listening to use current flags.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public static final int FLAG_LISTEN_KEEP = -1;
+
+    /**
+     * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag makes polling to use current flags.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public static final int FLAG_READER_KEEP = -1;
+
+    /** @hide */
+    public static final int FLAG_USE_ALL_TECH = 0xff;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = {"FLAG_LISTEN_"}, value = {
+        FLAG_LISTEN_KEEP,
+        FLAG_LISTEN_DISABLE,
+        FLAG_LISTEN_NFC_PASSIVE_A,
+        FLAG_LISTEN_NFC_PASSIVE_B,
+        FLAG_LISTEN_NFC_PASSIVE_F
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ListenTechnology {}
+
+    /**
+     * @hide
+     * @removed
+     */
+    @SystemApi
+    @UnsupportedAppUsage
+    public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 0x1;
+
+    /** @hide */
+    public static final String ACTION_HANDOVER_TRANSFER_STARTED =
+            "android.nfc.action.HANDOVER_TRANSFER_STARTED";
+
+    /** @hide */
+    public static final String ACTION_HANDOVER_TRANSFER_DONE =
+            "android.nfc.action.HANDOVER_TRANSFER_DONE";
+
+    /** @hide */
+    public static final String EXTRA_HANDOVER_TRANSFER_STATUS =
+            "android.nfc.extra.HANDOVER_TRANSFER_STATUS";
+
+    /** @hide */
+    public static final int HANDOVER_TRANSFER_STATUS_SUCCESS = 0;
+    /** @hide */
+    public static final int HANDOVER_TRANSFER_STATUS_FAILURE = 1;
+
+    /** @hide */
+    public static final String EXTRA_HANDOVER_TRANSFER_URI =
+            "android.nfc.extra.HANDOVER_TRANSFER_URI";
+
+    /**
+     * Broadcast Action: Notify possible NFC transaction blocked because device is locked.
+     * <p>An external NFC field detected when device locked and SecureNfc enabled.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC =
+            "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
+
+    /**
+     * The requested app is correctly added to the Tag intent app preference.
+     *
+     * @see #setTagIntentAppPreferenceForUser(int userId, String pkg, boolean allow)
+     * @hide
+     */
+    @SystemApi
+    public static final int TAG_INTENT_APP_PREF_RESULT_SUCCESS = 0;
+
+    /**
+     * The requested app is not installed on the device.
+     *
+     * @see #setTagIntentAppPreferenceForUser(int userId, String pkg, boolean allow)
+     * @hide
+     */
+    @SystemApi
+    public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1;
+
+    /**
+     * The NfcService is not available.
+     *
+     * @see #setTagIntentAppPreferenceForUser(int userId, String pkg, boolean allow)
+     * @hide
+     */
+    @SystemApi
+    public static final int TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE = -2;
+
+    /**
+     * Possible response codes from {@link #setTagIntentAppPreferenceForUser}.
+     *
+     * @hide
+     */
+    @IntDef(prefix = { "TAG_INTENT_APP_PREF_RESULT" }, value = {
+            TAG_INTENT_APP_PREF_RESULT_SUCCESS,
+            TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND,
+            TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TagIntentAppPreferenceResult {}
+
+    // Guarded by sLock
+    static boolean sIsInitialized = false;
+    static boolean sHasNfcFeature;
+    static boolean sHasCeFeature;
+    static boolean sHasNfcWlcFeature;
+
+    static Object sLock = new Object();
+
+    // Final after first constructor, except for
+    // attemptDeadServiceRecovery() when NFC crashes - we accept a best effort
+    // recovery
+    @UnsupportedAppUsage
+    static INfcAdapter sService;
+    static NfcServiceManager.ServiceRegisterer sServiceRegisterer;
+    static INfcTag sTagService;
+    static INfcCardEmulation sCardEmulationService;
+    static INfcFCardEmulation sNfcFCardEmulationService;
+
+    /**
+     * The NfcAdapter object for each application context.
+     * There is a 1-1 relationship between application context and
+     * NfcAdapter object.
+     */
+    static HashMap<Context, NfcAdapter> sNfcAdapters = new HashMap(); //guard by NfcAdapter.class
+
+    /**
+     * NfcAdapter used with a null context. This ctor was deprecated but we have
+     * to support it for backwards compatibility. New methods that require context
+     * might throw when called on the null-context NfcAdapter.
+     */
+    static NfcAdapter sNullContextNfcAdapter;  // protected by NfcAdapter.class
+
+    final NfcActivityManager mNfcActivityManager;
+    final Context mContext;
+    final HashMap<NfcUnlockHandler, INfcUnlockHandler> mNfcUnlockHandlers;
+    final Object mLock;
+
+    ITagRemovedCallback mTagRemovedListener; // protected by mLock
+
+    /**
+     * A callback to be invoked when the system finds a tag while the foreground activity is
+     * operating in reader mode.
+     * <p>Register your {@code ReaderCallback} implementation with {@link
+     * NfcAdapter#enableReaderMode} and disable it with {@link
+     * NfcAdapter#disableReaderMode}.
+     * @see NfcAdapter#enableReaderMode
+     */
+    public interface ReaderCallback {
+        public void onTagDiscovered(Tag tag);
+    }
+
+    /**
+     * A listener to be invoked when NFC controller always on state changes.
+     * <p>Register your {@code ControllerAlwaysOnListener} implementation with {@link
+     * NfcAdapter#registerControllerAlwaysOnListener} and disable it with {@link
+     * NfcAdapter#unregisterControllerAlwaysOnListener}.
+     * @see #registerControllerAlwaysOnListener
+     * @hide
+     */
+    @SystemApi
+    public interface ControllerAlwaysOnListener {
+        /**
+         * Called on NFC controller always on state changes
+         */
+        void onControllerAlwaysOnChanged(boolean isEnabled);
+    }
+
+    /**
+     * A callback to be invoked when the system successfully delivers your {@link NdefMessage}
+     * to another device.
+     * @deprecated this feature is removed. File sharing can work using other technology like
+     * Bluetooth.
+     */
+    @java.lang.Deprecated
+    public interface OnNdefPushCompleteCallback {
+        /**
+         * Called on successful NDEF push.
+         *
+         * <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
+         */
+        public void onNdefPushComplete(NfcEvent event);
+    }
+
+    /**
+     * 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 {@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 {@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
+     * Bluetooth.
+     */
+    @java.lang.Deprecated
+    public interface CreateNdefMessageCallback {
+        /**
+         * Called to provide a {@link NdefMessage} to push.
+         *
+         * <p>This callback is usually made on a binder thread (not the UI thread).
+         *
+         * <p>Called when this device is in range of another device
+         * that might support NDEF push. It allows the application to
+         * create the NDEF message only when it is required.
+         *
+         * <p>NDEF push cannot occur until this method returns, so do not
+         * block for too long.
+         *
+         * <p>The Android operating system will usually show a system UI
+         * on top of your activity during this time, so do not try to request
+         * input from the user to complete the callback, or provide custom NDEF
+         * push UI. The user probably will not see it.
+         *
+         * @param event {@link NfcEvent} with the {@link NfcEvent#nfcAdapter} field set
+         * @return NDEF message to push, or null to not provide a message
+         */
+        public NdefMessage createNdefMessage(NfcEvent event);
+    }
+
+
+     /**
+     * @deprecated this feature is removed. File sharing can work using other technology like
+     * Bluetooth.
+     */
+    @java.lang.Deprecated
+    public interface CreateBeamUrisCallback {
+        public Uri[] createBeamUris(NfcEvent event);
+    }
+
+    /**
+     * A callback that is invoked when a tag is removed from the field.
+     * @see NfcAdapter#ignore
+     */
+    public interface OnTagRemovedListener {
+        void onTagRemoved();
+    }
+
+    /**
+     * A callback to be invoked when an application has registered as a
+     * handler to unlock the device given an NFC tag at the lockscreen.
+     * @hide
+     */
+    @SystemApi
+    public interface NfcUnlockHandler {
+        /**
+         * Called at the lock screen to attempt to unlock the device with the given tag.
+         * @param tag the detected tag, to be used to unlock the device
+         * @return true if the device was successfully unlocked
+         */
+        public boolean onUnlockAttempted(Tag tag);
+    }
+
+    /**
+     * Return list of Secure Elements which support off host card emulation.
+     *
+     * @return List<String> containing secure elements on the device which supports
+     *                      off host card emulation. eSE for Embedded secure element,
+     *                      SIM for UICC and so on.
+     * @hide
+     */
+    public @NonNull List<String> getSupportedOffHostSecureElements() {
+        if (mContext == null) {
+            throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
+                    + " getSupportedOffHostSecureElements APIs");
+        }
+        List<String> offHostSE = new ArrayList<String>();
+        PackageManager pm = mContext.getPackageManager();
+        if (pm == null) {
+            Log.e(TAG, "Cannot get package manager, assuming no off-host CE feature");
+            return offHostSE;
+        }
+        if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC)) {
+            offHostSE.add("SIM");
+        }
+        if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE)) {
+            offHostSE.add("eSE");
+        }
+        return offHostSE;
+    }
+
+    private static void retrieveServiceRegisterer() {
+        if (sServiceRegisterer == null) {
+            NfcServiceManager manager = NfcFrameworkInitializer.getNfcServiceManager();
+            if (manager == null) {
+                Log.e(TAG, "NfcServiceManager is null");
+                throw new UnsupportedOperationException();
+            }
+            sServiceRegisterer = manager.getNfcManagerServiceRegisterer();
+        }
+    }
+
+    /**
+     * Returns the NfcAdapter for application context,
+     * or throws if NFC is not available.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static synchronized NfcAdapter getNfcAdapter(Context context) {
+        if (context == null) {
+            if (sNullContextNfcAdapter == null) {
+                sNullContextNfcAdapter = new NfcAdapter(null);
+            }
+            return sNullContextNfcAdapter;
+        }
+        if (!sIsInitialized) {
+            PackageManager pm;
+            pm = context.getPackageManager();
+            sHasNfcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC);
+            sHasCeFeature =
+                    pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)
+                    || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF)
+                    || pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC)
+                    || pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE);
+            sHasNfcWlcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC_CHARGING);
+            /* is this device meant to have NFC */
+            if (!sHasNfcFeature && !sHasCeFeature && !sHasNfcWlcFeature) {
+                Log.v(TAG, "this device does not have NFC support");
+                throw new UnsupportedOperationException();
+            }
+            retrieveServiceRegisterer();
+            sService = getServiceInterface();
+            if (sService == null) {
+                Log.e(TAG, "could not retrieve NFC service");
+                throw new UnsupportedOperationException();
+            }
+            if (sHasNfcFeature) {
+                try {
+                    sTagService = sService.getNfcTagInterface();
+                } catch (RemoteException e) {
+                    sTagService = null;
+                    Log.e(TAG, "could not retrieve NFC Tag service");
+                    throw new UnsupportedOperationException();
+                }
+            }
+            if (sHasCeFeature) {
+                try {
+                    sNfcFCardEmulationService = sService.getNfcFCardEmulationInterface();
+                } catch (RemoteException e) {
+                    sNfcFCardEmulationService = null;
+                    Log.e(TAG, "could not retrieve NFC-F card emulation service");
+                    throw new UnsupportedOperationException();
+                }
+                try {
+                    sCardEmulationService = sService.getNfcCardEmulationInterface();
+                } catch (RemoteException e) {
+                    sCardEmulationService = null;
+                    Log.e(TAG, "could not retrieve card emulation service");
+                    throw new UnsupportedOperationException();
+                }
+            }
+
+            sIsInitialized = true;
+        }
+        NfcAdapter adapter = sNfcAdapters.get(context);
+        if (adapter == null) {
+            adapter = new NfcAdapter(context);
+            sNfcAdapters.put(context, adapter);
+        }
+        return adapter;
+    }
+
+    /** get handle to NFC service interface */
+    private static INfcAdapter getServiceInterface() {
+        /* get a handle to NFC service */
+        IBinder b = sServiceRegisterer.get();
+        if (b == null) {
+            return null;
+        }
+        return INfcAdapter.Stub.asInterface(b);
+    }
+
+    /**
+     * Helper to get the default NFC Adapter.
+     * <p>
+     * Most Android devices will only have one NFC Adapter (NFC Controller).
+     * <p>
+     * This helper is the equivalent of:
+     * <pre>
+     * NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE);
+     * NfcAdapter adapter = manager.getDefaultAdapter();</pre>
+     * @param context the calling application's context
+     *
+     * @return the default NFC adapter, or null if no NFC adapter exists
+     */
+    public static NfcAdapter getDefaultAdapter(Context context) {
+        if (context == null) {
+            throw new IllegalArgumentException("context cannot be null");
+        }
+        context = context.getApplicationContext();
+        if (context == null) {
+            throw new IllegalArgumentException(
+                    "context not associated with any application (using a mock context?)");
+        }
+        retrieveServiceRegisterer();
+        if (sServiceRegisterer.tryGet() == null) {
+            if (sIsInitialized) {
+                synchronized (NfcAdapter.class) {
+                    /* Stale sService pointer */
+                    if (sIsInitialized) sIsInitialized = false;
+                }
+            }
+            return null;
+        }
+        /* Try to initialize the service */
+        NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE);
+        if (manager == null) {
+            // NFC not available
+            return null;
+        }
+        return manager.getDefaultAdapter();
+    }
+
+    /**
+     * Legacy NfcAdapter getter, always use {@link #getDefaultAdapter(Context)} instead.<p>
+     * This method was deprecated at API level 10 (Gingerbread MR1) because a context is required
+     * for many NFC API methods. Those methods will fail when called on an NfcAdapter
+     * object created from this method.<p>
+     * @deprecated use {@link #getDefaultAdapter(Context)}
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public static NfcAdapter getDefaultAdapter() {
+        // introduced in API version 9 (GB 2.3)
+        // deprecated in API version 10 (GB 2.3.3)
+        // removed from public API in version 16 (ICS MR2)
+        // should maintain as a hidden API for binary compatibility for a little longer
+        Log.w(TAG, "WARNING: NfcAdapter.getDefaultAdapter() is deprecated, use " +
+                "NfcAdapter.getDefaultAdapter(Context) instead", new Exception());
+
+        return NfcAdapter.getNfcAdapter(null);
+    }
+
+    NfcAdapter(Context context) {
+        mContext = context;
+        mNfcActivityManager = new NfcActivityManager(this);
+        mNfcUnlockHandlers = new HashMap<NfcUnlockHandler, INfcUnlockHandler>();
+        mTagRemovedListener = null;
+        mLock = new Object();
+        mControllerAlwaysOnListener = new NfcControllerAlwaysOnListener(getService());
+        mNfcWlcStateListener = new NfcWlcStateListener(getService());
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Returns the binder interface to the service.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public INfcAdapter getService() {
+        isEnabled();  // NOP call to recover sService if it is stale
+        return sService;
+    }
+
+    /**
+     * Returns the binder interface to the tag service.
+     * @hide
+     */
+    public INfcTag getTagService() {
+        isEnabled();  // NOP call to recover sTagService if it is stale
+        return sTagService;
+    }
+
+    /**
+     * Returns the binder interface to the card emulation service.
+     * @hide
+     */
+    public INfcCardEmulation getCardEmulationService() {
+        isEnabled();
+        return sCardEmulationService;
+    }
+
+    /**
+     * Returns the binder interface to the NFC-F card emulation service.
+     * @hide
+     */
+    public INfcFCardEmulation getNfcFCardEmulationService() {
+        isEnabled();
+        return sNfcFCardEmulationService;
+    }
+
+    /**
+     * Returns the binder interface to the NFC-DTA test interface.
+     * @hide
+     */
+    public INfcDta getNfcDtaInterface() {
+        if (mContext == null) {
+            throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
+                    + " NFC extras APIs");
+        }
+        try {
+            return sService.getNfcDtaInterface(mContext.getPackageName());
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return null;
+            }
+            try {
+                return sService.getNfcDtaInterface(mContext.getPackageName());
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return null;
+        }
+    }
+
+    /**
+     * NFC service dead - attempt best effort recovery
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void attemptDeadServiceRecovery(Exception e) {
+        Log.e(TAG, "NFC service dead - attempting to recover", e);
+        INfcAdapter service = getServiceInterface();
+        if (service == null) {
+            Log.e(TAG, "could not retrieve NFC service during service recovery");
+            // nothing more can be done now, sService is still stale, we'll hit
+            // this recovery path again later
+            return;
+        }
+        // assigning to sService is not thread-safe, but this is best-effort code
+        // and on a well-behaved system should never happen
+        sService = service;
+        if (sHasNfcFeature) {
+            try {
+                sTagService = service.getNfcTagInterface();
+            } catch (RemoteException ee) {
+                sTagService = null;
+                Log.e(TAG, "could not retrieve NFC tag service during service recovery");
+                // nothing more can be done now, sService is still stale, we'll hit
+                // this recovery path again later
+                return;
+            }
+        }
+
+        if (sHasCeFeature) {
+            try {
+                sCardEmulationService = service.getNfcCardEmulationInterface();
+            } catch (RemoteException ee) {
+                sCardEmulationService = null;
+                Log.e(TAG,
+                        "could not retrieve NFC card emulation service during service recovery");
+            }
+
+            try {
+                sNfcFCardEmulationService = service.getNfcFCardEmulationInterface();
+            } catch (RemoteException ee) {
+                sNfcFCardEmulationService = null;
+                Log.e(TAG,
+                        "could not retrieve NFC-F card emulation service during service recovery");
+            }
+        }
+
+        return;
+    }
+
+    private boolean isCardEmulationEnabled() {
+        if (sHasCeFeature) {
+            return (sCardEmulationService != null || sNfcFCardEmulationService != null);
+        }
+        return false;
+    }
+
+    private boolean isTagReadingEnabled() {
+        if (sHasNfcFeature) {
+            return sTagService != null;
+        }
+        return false;
+    }
+
+
+    /**
+     * Return true if this NFC Adapter has any features enabled.
+     *
+     * <p>If this method returns false, the NFC hardware is guaranteed not to
+     * generate or respond to any NFC communication over its NFC radio.
+     * <p>Applications can use this to check if NFC is enabled. Applications
+     * can request Settings UI allowing the user to toggle NFC using:
+     * <p><pre>startActivity(new Intent(Settings.ACTION_NFC_SETTINGS))</pre>
+     *
+     * @see android.provider.Settings#ACTION_NFC_SETTINGS
+     * @return true if this NFC Adapter has any features enabled
+     */
+    public boolean isEnabled() {
+        boolean serviceState = false;
+        try {
+            serviceState = sService.getState() == STATE_ON;
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                serviceState = sService.getState() == STATE_ON;
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+        }
+        return serviceState
+                && (isTagReadingEnabled() || isCardEmulationEnabled() || sHasNfcWlcFeature);
+    }
+
+    /**
+     * Return the state of this NFC Adapter.
+     *
+     * <p>Returns one of {@link #STATE_ON}, {@link #STATE_TURNING_ON},
+     * {@link #STATE_OFF}, {@link #STATE_TURNING_OFF}.
+     *
+     * <p>{@link #isEnabled()} is equivalent to
+     * <code>{@link #getAdapterState()} == {@link #STATE_ON}</code>
+     *
+     * @return the current state of this NFC adapter
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public @AdapterState int getAdapterState() {
+        try {
+            return sService.getState();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return NfcAdapter.STATE_OFF;
+            }
+            try {
+                return sService.getState();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return NfcAdapter.STATE_OFF;
+        }
+    }
+
+    /**
+     * Enable NFC hardware.
+     *
+     * <p>This call is asynchronous. Listen for
+     * {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the
+     * operation is complete.
+     *
+     * <p>If this returns true, then either NFC is already on, or
+     * a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent
+     * to indicate a state transition. If this returns false, then
+     * there is some problem that prevents an attempt to turn
+     * NFC on (for example we are in airplane mode and NFC is not
+     * toggleable in airplane mode on this platform).
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    public boolean enable() {
+        try {
+            return sService.enable();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.enable();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Disable NFC hardware.
+     *
+     * <p>No NFC features will work after this call, and the hardware
+     * will not perform or respond to any NFC communication.
+     *
+     * <p>This call is asynchronous. Listen for
+     * {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the
+     * operation is complete.
+     *
+     * <p>If this returns true, then either NFC is already off, or
+     * a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent
+     * to indicate a state transition. If this returns false, then
+     * there is some problem that prevents an attempt to turn
+     * NFC off.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    public boolean disable() {
+        try {
+            return sService.disable(true);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.disable(true);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Disable NFC hardware.
+     * @hide
+    */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    public boolean disable(boolean persist) {
+        try {
+            return sService.disable(persist);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.disable(persist);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Pauses polling for a {@code timeoutInMs} millis. If polling must be resumed before timeout,
+     * use {@link #resumePolling()}.
+     * @hide
+     */
+    public void pausePolling(int timeoutInMs) {
+        try {
+            sService.pausePolling(timeoutInMs);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+        }
+    }
+
+
+    /**
+     * Returns whether the device supports observer mode or not. When observe
+     * mode is enabled, the NFC hardware will listen for NFC readers, but not
+     * respond to them. When observe mode is disabled, the NFC hardware will
+     * resoond to the reader and proceed with the transaction.
+     * @return true if the mode is supported, false otherwise.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
+    public boolean isObserveModeSupported() {
+        try {
+            return sService.isObserveModeSupported();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            return false;
+        }
+    }
+
+   /**
+    * Disables observe mode to allow the transaction to proceed. See
+    * {@link #isObserveModeSupported()} for a description of observe mode and
+    * use {@link #disallowTransaction()} to enable observe mode and block
+    * transactions again.
+    *
+    * @return boolean indicating success or failure.
+    */
+    @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
+    public boolean allowTransaction() {
+        try {
+            return sService.setObserveMode(false);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            return false;
+        }
+    }
+
+    /**
+    * Signals that the transaction has completed and observe mode may be
+    * reenabled. See {@link #isObserveModeSupported()} for a description of
+    * observe mode and use {@link #allowTransaction()} to disable observe
+    * mode and allow transactions to proceed.
+    *
+    * @return boolean indicating success or failure.
+    */
+
+    @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
+    public boolean disallowTransaction() {
+        try {
+            return sService.setObserveMode(true);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            return false;
+        }
+    }
+
+    /**
+     * Resumes default polling for the current device state if polling is paused. Calling
+     * this while polling is not paused is a no-op.
+     *
+     * @hide
+     */
+    public void resumePolling() {
+        try {
+            sService.resumePolling();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+        }
+    }
+
+    /**
+     * Set one or more {@link Uri}s to send using Android Beam (TM). Every
+     * Uri you provide must have either scheme 'file' or scheme 'content'.
+     *
+     * <p>For the data provided through this method, Android Beam tries to
+     * switch to alternate transports such as Bluetooth to achieve a fast
+     * transfer speed. Hence this method is very suitable
+     * for transferring large files such as pictures or songs.
+     *
+     * <p>The receiving side will store the content of each Uri in
+     * a file and present a notification to the user to open the file
+     * with a {@link android.content.Intent} with action
+     * {@link android.content.Intent#ACTION_VIEW}.
+     * If multiple URIs are sent, the {@link android.content.Intent} will refer
+     * to the first of the stored files.
+     *
+     * <p>This method may be called at any time before {@link Activity#onDestroy},
+     * but the URI(s) are only made available for Android Beam when the
+     * specified activity(s) are in resumed (foreground) state. The recommended
+     * approach is to call this method during your Activity's
+     * {@link Activity#onCreate} - see sample
+     * code below. This method does not immediately perform any I/O or blocking work,
+     * so is safe to call on your main thread.
+     *
+     * <p>{@link #setBeamPushUris} and {@link #setBeamPushUrisCallback}
+     * have priority over both {@link #setNdefPushMessage} and
+     * {@link #setNdefPushMessageCallback}.
+     *
+     * <p>If {@link #setBeamPushUris} is called with a null Uri array,
+     * and/or {@link #setBeamPushUrisCallback} is called with a null callback,
+     * then the Uri push will be completely disabled for the specified activity(s).
+     *
+     * <p>Code example:
+     * <pre>
+     * protected void onCreate(Bundle savedInstanceState) {
+     *     super.onCreate(savedInstanceState);
+     *     NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
+     *     if (nfcAdapter == null) return;  // NFC not available on this device
+     *     nfcAdapter.setBeamPushUris(new Uri[] {uri1, uri2}, this);
+     * }</pre>
+     * And that is it. Only one call per activity is necessary. The Android
+     * OS will automatically release its references to the Uri(s) and the
+     * Activity object when it is destroyed if you follow this pattern.
+     *
+     * <p>If your Activity wants to dynamically supply Uri(s),
+     * then set a callback using {@link #setBeamPushUrisCallback} instead
+     * of using this method.
+     *
+     * <p class="note">Do not pass in an Activity that has already been through
+     * {@link Activity#onDestroy}. This is guaranteed if you call this API
+     * during {@link Activity#onCreate}.
+     *
+     * <p class="note">If this device does not support alternate transports
+     * such as Bluetooth or WiFI, calling this method does nothing.
+     *
+     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+     *
+     * @param uris an array of Uri(s) to push over Android Beam
+     * @param activity activity for which the Uri(s) will be pushed
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     * @removed this feature is removed. File sharing can work using other technology like
+     * Bluetooth.
+     */
+    @java.lang.Deprecated
+    @UnsupportedAppUsage
+    public void setBeamPushUris(Uri[] uris, Activity activity) {
+        synchronized (sLock) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
+    }
+
+    /**
+     * Set a callback that will dynamically generate one or more {@link Uri}s
+     * to send using Android Beam (TM). Every Uri the callback provides
+     * must have either scheme 'file' or scheme 'content'.
+     *
+     * <p>For the data provided through this callback, Android Beam tries to
+     * switch to alternate transports such as Bluetooth to achieve a fast
+     * transfer speed. Hence this method is very suitable
+     * for transferring large files such as pictures or songs.
+     *
+     * <p>The receiving side will store the content of each Uri in
+     * a file and present a notification to the user to open the file
+     * with a {@link android.content.Intent} with action
+     * {@link android.content.Intent#ACTION_VIEW}.
+     * If multiple URIs are sent, the {@link android.content.Intent} will refer
+     * to the first of the stored files.
+     *
+     * <p>This method may be called at any time before {@link Activity#onDestroy},
+     * but the URI(s) are only made available for Android Beam when the
+     * specified activity(s) are in resumed (foreground) state. The recommended
+     * approach is to call this method during your Activity's
+     * {@link Activity#onCreate} - see sample
+     * code below. This method does not immediately perform any I/O or blocking work,
+     * so is safe to call on your main thread.
+     *
+     * <p>{@link #setBeamPushUris} and {@link #setBeamPushUrisCallback}
+     * have priority over both {@link #setNdefPushMessage} and
+     * {@link #setNdefPushMessageCallback}.
+     *
+     * <p>If {@link #setBeamPushUris} is called with a null Uri array,
+     * and/or {@link #setBeamPushUrisCallback} is called with a null callback,
+     * then the Uri push will be completely disabled for the specified activity(s).
+     *
+     * <p>Code example:
+     * <pre>
+     * protected void onCreate(Bundle savedInstanceState) {
+     *     super.onCreate(savedInstanceState);
+     *     NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
+     *     if (nfcAdapter == null) return;  // NFC not available on this device
+     *     nfcAdapter.setBeamPushUrisCallback(callback, this);
+     * }</pre>
+     * And that is it. Only one call per activity is necessary. The Android
+     * OS will automatically release its references to the Uri(s) and the
+     * Activity object when it is destroyed if you follow this pattern.
+     *
+     * <p class="note">Do not pass in an Activity that has already been through
+     * {@link Activity#onDestroy}. This is guaranteed if you call this API
+     * during {@link Activity#onCreate}.
+     *
+     * <p class="note">If this device does not support alternate transports
+     * such as Bluetooth or WiFI, calling this method does nothing.
+     *
+     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+     *
+     * @param callback callback, or null to disable
+     * @param activity activity for which the Uri(s) will be pushed
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     * @removed this feature is removed. File sharing can work using other technology like
+     * Bluetooth.
+     */
+    @java.lang.Deprecated
+    @UnsupportedAppUsage
+    public void setBeamPushUrisCallback(CreateBeamUrisCallback callback, Activity activity) {
+        synchronized (sLock) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
+    }
+
+    /**
+     * Set a static {@link NdefMessage} to send using Android Beam (TM).
+     *
+     * <p>This method may be called at any time before {@link Activity#onDestroy},
+     * but the NDEF message is only made available for NDEF push when the
+     * specified activity(s) are in resumed (foreground) state. The recommended
+     * approach is to call this method during your Activity's
+     * {@link Activity#onCreate} - see sample
+     * code below. This method does not immediately perform any I/O or blocking work,
+     * so is safe to call on your main thread.
+     *
+     * <p>Only one NDEF message can be pushed by the currently resumed activity.
+     * If both {@link #setNdefPushMessage} and
+     * {@link #setNdefPushMessageCallback} are set, then
+     * the callback will take priority.
+     *
+     * <p>If neither {@link #setNdefPushMessage} or
+     * {@link #setNdefPushMessageCallback} have been called for your activity, then
+     * the Android OS may choose to send a default NDEF message on your behalf,
+     * such as a URI for your application.
+     *
+     * <p>If {@link #setNdefPushMessage} is called with a null NDEF message,
+     * and/or {@link #setNdefPushMessageCallback} is called with a null callback,
+     * then NDEF push will be completely disabled for the specified activity(s).
+     * This also disables any default NDEF message the Android OS would have
+     * otherwise sent on your behalf for those activity(s).
+     *
+     * <p>If you want to prevent the Android OS from sending default NDEF
+     * messages completely (for all activities), you can include a
+     * {@code <meta-data>} element inside the {@code <application>}
+     * element of your AndroidManifest.xml file, like this:
+     * <pre>
+     * &lt;application ...>
+     *     &lt;meta-data android:name="android.nfc.disable_beam_default"
+     *         android:value="true" />
+     * &lt;/application></pre>
+     *
+     * <p>The API allows for multiple activities to be specified at a time,
+     * but it is strongly recommended to just register one at a time,
+     * and to do so during the activity's {@link Activity#onCreate}. For example:
+     * <pre>
+     * protected void onCreate(Bundle savedInstanceState) {
+     *     super.onCreate(savedInstanceState);
+     *     NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
+     *     if (nfcAdapter == null) return;  // NFC not available on this device
+     *     nfcAdapter.setNdefPushMessage(ndefMessage, this);
+     * }</pre>
+     * And that is it. Only one call per activity is necessary. The Android
+     * OS will automatically release its references to the NDEF message and the
+     * Activity object when it is destroyed if you follow this pattern.
+     *
+     * <p>If your Activity wants to dynamically generate an NDEF message,
+     * then set a callback using {@link #setNdefPushMessageCallback} instead
+     * of a static message.
+     *
+     * <p class="note">Do not pass in an Activity that has already been through
+     * {@link Activity#onDestroy}. This is guaranteed if you call this API
+     * during {@link Activity#onCreate}.
+     *
+     * <p class="note">For sending large content such as pictures and songs,
+     * consider using {@link #setBeamPushUris}, which switches to alternate transports
+     * such as Bluetooth to achieve a fast transfer rate.
+     *
+     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+     *
+     * @param message NDEF message to push over NFC, or null to disable
+     * @param activity activity for which the NDEF message will be pushed
+     * @param activities optional additional activities, however we strongly recommend
+     *        to only register one at a time, and to do so in that activity's
+     *        {@link Activity#onCreate}
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     * @removed this feature is removed. File sharing can work using other technology like
+     * Bluetooth.
+     */
+    @java.lang.Deprecated
+    @UnsupportedAppUsage
+    public void setNdefPushMessage(NdefMessage message, Activity activity,
+            Activity ... activities) {
+        synchronized (sLock) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
+    }
+
+    /**
+     * @hide
+     * @removed
+     */
+    @SystemApi
+    @UnsupportedAppUsage
+    public void setNdefPushMessage(NdefMessage message, Activity activity, int flags) {
+        synchronized (sLock) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
+    }
+
+    /**
+     * Set a callback that dynamically generates NDEF messages to send using Android Beam (TM).
+     *
+     * <p>This method may be called at any time before {@link Activity#onDestroy},
+     * but the NDEF message callback can only occur when the
+     * specified activity(s) are in resumed (foreground) state. The recommended
+     * approach is to call this method during your Activity's
+     * {@link Activity#onCreate} - see sample
+     * code below. This method does not immediately perform any I/O or blocking work,
+     * so is safe to call on your main thread.
+     *
+     * <p>Only one NDEF message can be pushed by the currently resumed activity.
+     * If both {@link #setNdefPushMessage} and
+     * {@link #setNdefPushMessageCallback} are set, then
+     * the callback will take priority.
+     *
+     * <p>If neither {@link #setNdefPushMessage} or
+     * {@link #setNdefPushMessageCallback} have been called for your activity, then
+     * the Android OS may choose to send a default NDEF message on your behalf,
+     * such as a URI for your application.
+     *
+     * <p>If {@link #setNdefPushMessage} is called with a null NDEF message,
+     * and/or {@link #setNdefPushMessageCallback} is called with a null callback,
+     * then NDEF push will be completely disabled for the specified activity(s).
+     * This also disables any default NDEF message the Android OS would have
+     * otherwise sent on your behalf for those activity(s).
+     *
+     * <p>If you want to prevent the Android OS from sending default NDEF
+     * messages completely (for all activities), you can include a
+     * {@code <meta-data>} element inside the {@code <application>}
+     * element of your AndroidManifest.xml file, like this:
+     * <pre>
+     * &lt;application ...>
+     *     &lt;meta-data android:name="android.nfc.disable_beam_default"
+     *         android:value="true" />
+     * &lt;/application></pre>
+     *
+     * <p>The API allows for multiple activities to be specified at a time,
+     * but it is strongly recommended to just register one at a time,
+     * and to do so during the activity's {@link Activity#onCreate}. For example:
+     * <pre>
+     * protected void onCreate(Bundle savedInstanceState) {
+     *     super.onCreate(savedInstanceState);
+     *     NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
+     *     if (nfcAdapter == null) return;  // NFC not available on this device
+     *     nfcAdapter.setNdefPushMessageCallback(callback, this);
+     * }</pre>
+     * And that is it. Only one call per activity is necessary. The Android
+     * OS will automatically release its references to the callback and the
+     * Activity object when it is destroyed if you follow this pattern.
+     *
+     * <p class="note">Do not pass in an Activity that has already been through
+     * {@link Activity#onDestroy}. This is guaranteed if you call this API
+     * during {@link Activity#onCreate}.
+     * <p class="note">For sending large content such as pictures and songs,
+     * consider using {@link #setBeamPushUris}, which switches to alternate transports
+     * such as Bluetooth to achieve a fast transfer rate.
+     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+     *
+     * @param callback callback, or null to disable
+     * @param activity activity for which the NDEF message will be pushed
+     * @param activities optional additional activities, however we strongly recommend
+     *        to only register one at a time, and to do so in that activity's
+     *        {@link Activity#onCreate}
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     * @removed this feature is removed. File sharing can work using other technology like
+     * Bluetooth.
+     */
+    @java.lang.Deprecated
+    @UnsupportedAppUsage
+    public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity,
+            Activity ... activities) {
+        synchronized (sLock) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
+    }
+
+    /**
+     * Set a callback on successful Android Beam (TM).
+     *
+     * <p>This method may be called at any time before {@link Activity#onDestroy},
+     * but the callback can only occur when the
+     * specified activity(s) are in resumed (foreground) state. The recommended
+     * approach is to call this method during your Activity's
+     * {@link Activity#onCreate} - see sample
+     * code below. This method does not immediately perform any I/O or blocking work,
+     * so is safe to call on your main thread.
+     *
+     * <p>The API allows for multiple activities to be specified at a time,
+     * but it is strongly recommended to just register one at a time,
+     * and to do so during the activity's {@link Activity#onCreate}. For example:
+     * <pre>
+     * protected void onCreate(Bundle savedInstanceState) {
+     *     super.onCreate(savedInstanceState);
+     *     NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
+     *     if (nfcAdapter == null) return;  // NFC not available on this device
+     *     nfcAdapter.setOnNdefPushCompleteCallback(callback, this);
+     * }</pre>
+     * And that is it. Only one call per activity is necessary. The Android
+     * OS will automatically release its references to the callback and the
+     * Activity object when it is destroyed if you follow this pattern.
+     *
+     * <p class="note">Do not pass in an Activity that has already been through
+     * {@link Activity#onDestroy}. This is guaranteed if you call this API
+     * during {@link Activity#onCreate}.
+     *
+     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+     *
+     * @param callback callback, or null to disable
+     * @param activity activity for which the NDEF message will be pushed
+     * @param activities optional additional activities, however we strongly recommend
+     *        to only register one at a time, and to do so in that activity's
+     *        {@link Activity#onCreate}
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     * @removed this feature is removed. File sharing can work using other technology like
+     * Bluetooth.
+     */
+    @java.lang.Deprecated
+    @UnsupportedAppUsage
+    public void setOnNdefPushCompleteCallback(OnNdefPushCompleteCallback callback,
+            Activity activity, Activity ... activities) {
+        synchronized (sLock) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
+    }
+
+    /**
+     * Enable foreground dispatch to the given Activity.
+     *
+     * <p>This will give priority to the foreground activity when
+     * dispatching a discovered {@link Tag} to an application.
+     *
+     * <p>If any IntentFilters are provided to this method they are used to match dispatch Intents
+     * for both the {@link NfcAdapter#ACTION_NDEF_DISCOVERED} and
+     * {@link NfcAdapter#ACTION_TAG_DISCOVERED}. Since {@link NfcAdapter#ACTION_TECH_DISCOVERED}
+     * relies on meta data outside of the IntentFilter matching for that dispatch Intent is handled
+     * by passing in the tech lists separately. Each first level entry in the tech list represents
+     * an array of technologies that must all be present to match. If any of the first level sets
+     * match then the dispatch is routed through the given PendingIntent. In other words, the second
+     * level is ANDed together and the first level entries are ORed together.
+     *
+     * <p>If you pass {@code null} for both the {@code filters} and {@code techLists} parameters
+     * that acts a wild card and will cause the foreground activity to receive all tags via the
+     * {@link NfcAdapter#ACTION_TAG_DISCOVERED} intent.
+     *
+     * <p>This method must be called from the main thread, and only when the activity is in the
+     * foreground (resumed). Also, activities must call {@link #disableForegroundDispatch} before
+     * the completion of their {@link Activity#onPause} callback to disable foreground dispatch
+     * after it has been enabled.
+     *
+     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+     *
+     * @param activity the Activity to dispatch to
+     * @param intent the PendingIntent to start for the dispatch
+     * @param filters the IntentFilters to override dispatching for, or null to always dispatch
+     * @param techLists the tech lists used to perform matching for dispatching of the
+     *      {@link NfcAdapter#ACTION_TECH_DISCOVERED} intent
+     * @throws IllegalStateException if the Activity is not currently in the foreground
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     */
+    public void enableForegroundDispatch(Activity activity, PendingIntent intent,
+            IntentFilter[] filters, String[][] techLists) {
+        synchronized (sLock) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
+        if (activity == null || intent == null) {
+            throw new NullPointerException();
+        }
+        try {
+            TechListParcel parcel = null;
+            if (techLists != null && techLists.length > 0) {
+                parcel = new TechListParcel(techLists);
+            }
+            sService.setForegroundDispatch(intent, filters, parcel);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+        }
+    }
+
+    /**
+     * Disable foreground dispatch to the given activity.
+     *
+     * <p>After calling {@link #enableForegroundDispatch}, an activity
+     * must call this method before its {@link Activity#onPause} callback
+     * completes.
+     *
+     * <p>This method must be called from the main thread.
+     *
+     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+     *
+     * @param activity the Activity to disable dispatch to
+     * @throws IllegalStateException if the Activity has already been paused
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     */
+    public void disableForegroundDispatch(Activity activity) {
+        synchronized (sLock) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
+        try {
+            sService.setForegroundDispatch(null, null, null);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+        }
+    }
+
+    /**
+     * Limit the NFC controller to reader mode while this Activity is in the foreground.
+     *
+     * <p>In this mode the NFC controller will only act as an NFC tag reader/writer,
+     * thus disabling any peer-to-peer (Android Beam) and card-emulation modes of
+     * the NFC adapter on this device.
+     *
+     * <p>Use {@link #FLAG_READER_SKIP_NDEF_CHECK} to prevent the platform from
+     * performing any NDEF checks in reader mode. Note that this will prevent the
+     * {@link Ndef} tag technology from being enumerated on the tag, and that
+     * NDEF-based tag dispatch will not be functional.
+     *
+     * <p>For interacting with tags that are emulated on another Android device
+     * using Android's host-based card-emulation, the recommended flags are
+     * {@link #FLAG_READER_NFC_A} and {@link #FLAG_READER_SKIP_NDEF_CHECK}.
+     *
+     * @param activity the Activity that requests the adapter to be in reader mode
+     * @param callback the callback to be called when a tag is discovered
+     * @param flags Flags indicating poll technologies and other optional parameters
+     * @param extras Additional extras for configuring reader mode.
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     */
+    public void enableReaderMode(Activity activity, ReaderCallback callback, int flags,
+            Bundle extras) {
+        synchronized (sLock) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
+        mNfcActivityManager.enableReaderMode(activity, callback, flags, extras);
+    }
+
+    /**
+     * Restore the NFC adapter to normal mode of operation: supporting
+     * peer-to-peer (Android Beam), card emulation, and polling for
+     * all supported tag technologies.
+     *
+     * @param activity the Activity that currently has reader mode enabled
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     */
+    public void disableReaderMode(Activity activity) {
+        synchronized (sLock) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
+        mNfcActivityManager.disableReaderMode(activity);
+    }
+
+    // Flags arguments to NFC adapter to enable/disable NFC
+    private static final int DISABLE_POLLING_FLAGS = 0x1000;
+    private static final int ENABLE_POLLING_FLAGS = 0x0000;
+
+    /**
+     * Privileged API to enable disable reader polling.
+     * Note: Use with caution! The app is responsible for ensuring that the polling state is
+     * returned to normal.
+     *
+     * @see #enableReaderMode(Activity, ReaderCallback, int, Bundle)  for more detailed
+     * documentation.
+     *
+     * @param enablePolling whether to enable or disable polling.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @SuppressLint("VisiblySynchronized")
+    public void setReaderMode(boolean enablePolling) {
+        synchronized (sLock) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
+        Binder token = new Binder();
+        int flags = enablePolling ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS;
+        try {
+            NfcAdapter.sService.setReaderMode(token, null, flags, null);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+        }
+    }
+
+    /**
+     * Set the NFC controller to enable specific poll/listen technologies,
+     * as specified in parameters, while this Activity is in the foreground.
+     *
+     * Use {@link #FLAG_READER_KEEP} to keep current polling technology.
+     * Use {@link #FLAG_LISTEN_KEEP} to keep current listenig technology.
+     * Use {@link #FLAG_READER_DISABLE} to disable polling.
+     * Use {@link #FLAG_LISTEN_DISABLE} to disable listening.
+     * Also refer to {@link #resetDiscoveryTechnology(Activity)} to restore these changes.
+     * </p>
+     * The pollTechnology, listenTechnology parameters can be one or several of below list.
+     * <pre>
+     *                    Poll                    Listen
+     *  Passive A         0x01   (NFC_A)           0x01  (NFC_PASSIVE_A)
+     *  Passive B         0x02   (NFC_B)           0x02  (NFC_PASSIVE_B)
+     *  Passive F         0x04   (NFC_F)           0x04  (NFC_PASSIVE_F)
+     *  ISO 15693         0x08   (NFC_V)             -
+     *  Kovio             0x10   (NFC_BARCODE)       -
+     * </pre>
+     * <p>Example usage in an Activity that requires to disable poll,
+     * keep current listen technologies:
+     * <pre>
+     * protected void onResume() {
+     *     mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
+     *     mNfcAdapter.setDiscoveryTechnology(this,
+     *         NfcAdapter.FLAG_READER_DISABLE, NfcAdapter.FLAG_LISTEN_KEEP);
+     * }</pre></p>
+     * @param activity The Activity that requests NFC controller to enable specific technologies.
+     * @param pollTechnology Flags indicating poll technologies.
+     * @param listenTechnology Flags indicating listen technologies.
+     * @throws UnsupportedOperationException if FEATURE_NFC,
+     * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF are unavailable.
+     */
+
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public void setDiscoveryTechnology(@NonNull Activity activity,
+            @PollTechnology int pollTechnology, @ListenTechnology int listenTechnology) {
+        if (listenTechnology == FLAG_LISTEN_DISABLE) {
+            synchronized (sLock) {
+                if (!sHasNfcFeature) {
+                    throw new UnsupportedOperationException();
+                }
+            }
+            mNfcActivityManager.enableReaderMode(activity, null, pollTechnology, null);
+            return;
+        }
+        if (pollTechnology == FLAG_READER_DISABLE) {
+            synchronized (sLock) {
+                if (!sHasCeFeature) {
+                    throw new UnsupportedOperationException();
+                }
+            }
+        } else {
+            synchronized (sLock) {
+                if (!sHasNfcFeature || !sHasCeFeature) {
+                    throw new UnsupportedOperationException();
+                }
+            }
+        }
+        mNfcActivityManager.setDiscoveryTech(activity, pollTechnology, listenTechnology);
+    }
+
+    /**
+     * Restore the poll/listen technologies of NFC controller,
+     * which were changed by {@link #setDiscoveryTechnology(Activity , int , int)}
+     *
+     * @param activity The Activity that requests to changed technologies.
+     */
+
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public void resetDiscoveryTechnology(@NonNull Activity activity) {
+        mNfcActivityManager.resetDiscoveryTech(activity);
+    }
+
+    /**
+     * Manually invoke Android Beam to share data.
+     *
+     * <p>The Android Beam animation is normally only shown when two NFC-capable
+     * devices come into range.
+     * By calling this method, an Activity can invoke the Beam animation directly
+     * even if no other NFC device is in range yet. The Beam animation will then
+     * prompt the user to tap another NFC-capable device to complete the data
+     * transfer.
+     *
+     * <p>The main advantage of using this method is that it avoids the need for the
+     * user to tap the screen to complete the transfer, as this method already
+     * establishes the direction of the transfer and the consent of the user to
+     * share data. Callers are responsible for making sure that the user has
+     * consented to sharing data on NFC tap.
+     *
+     * <p>Note that to use this method, the passed in Activity must have already
+     * set data to share over Beam by using method calls such as
+     * {@link #setNdefPushMessageCallback} or
+     * {@link #setBeamPushUrisCallback}.
+     *
+     * @param activity the current foreground Activity that has registered data to share
+     * @return whether the Beam animation was successfully invoked
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     * @removed this feature is removed. File sharing can work using other technology like
+     * Bluetooth.
+     */
+    @java.lang.Deprecated
+    @UnsupportedAppUsage
+    public boolean invokeBeam(Activity activity) {
+        synchronized (sLock) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Enable NDEF message push over NFC while this Activity is in the foreground.
+     *
+     * <p>You must explicitly call this method every time the activity is
+     * resumed, and you must call {@link #disableForegroundNdefPush} before
+     * your activity completes {@link Activity#onPause}.
+     *
+     * <p>Strongly recommend to use the new {@link #setNdefPushMessage}
+     * instead: it automatically hooks into your activity life-cycle,
+     * so you do not need to call enable/disable in your onResume/onPause.
+     *
+     * <p>For NDEF push to function properly the other NFC device must
+     * support either NFC Forum's SNEP (Simple Ndef Exchange Protocol), or
+     * Android's "com.android.npp" (Ndef Push Protocol). This was optional
+     * on Gingerbread level Android NFC devices, but SNEP is mandatory on
+     * Ice-Cream-Sandwich and beyond.
+     *
+     * <p>This method must be called from the main thread.
+     *
+     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+     *
+     * @param activity foreground activity
+     * @param message a NDEF Message to push over NFC
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable
+     * @removed this feature is removed. File sharing can work using other technology like
+     * Bluetooth.
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public void enableForegroundNdefPush(Activity activity, NdefMessage message) {
+        synchronized (sLock) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
+    }
+
+    /**
+     * Disable NDEF message push over P2P.
+     *
+     * <p>After calling {@link #enableForegroundNdefPush}, an activity
+     * must call this method before its {@link Activity#onPause} callback
+     * completes.
+     *
+     * <p>Strongly recommend to use the new {@link #setNdefPushMessage}
+     * instead: it automatically hooks into your activity life-cycle,
+     * so you do not need to call enable/disable in your onResume/onPause.
+     *
+     * <p>This method must be called from the main thread.
+     *
+     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+     *
+     * @param activity the Foreground activity
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable
+     * @removed this feature is removed. File sharing can work using other technology like
+     * Bluetooth.
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public void disableForegroundNdefPush(Activity activity) {
+        synchronized (sLock) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
+    }
+
+    /**
+     * Sets Secure NFC feature.
+     * <p>This API is for the Settings application.
+     * @return True if successful
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    public boolean enableSecureNfc(boolean enable) {
+        if (!sHasNfcFeature && !sHasCeFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.setNfcSecure(enable);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.setNfcSecure(enable);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Checks if the device supports Secure NFC functionality.
+     *
+     * @return True if device supports Secure NFC, false otherwise
+     * @throws UnsupportedOperationException if FEATURE_NFC,
+     * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+     * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
+     * are unavailable
+     */
+    public boolean isSecureNfcSupported() {
+        if (!sHasNfcFeature && !sHasCeFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.deviceSupportsNfcSecure();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.deviceSupportsNfcSecure();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Returns information regarding Nfc antennas on the device
+     * such as their relative positioning on the device.
+     *
+     * @return Information on the nfc antenna(s) on the device.
+     * @throws UnsupportedOperationException if FEATURE_NFC,
+     * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+     * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
+     * are unavailable
+     */
+    @Nullable
+    public NfcAntennaInfo getNfcAntennaInfo() {
+        if (!sHasNfcFeature && !sHasCeFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.getNfcAntennaInfo();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return null;
+            }
+            try {
+                return sService.getNfcAntennaInfo();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Checks Secure NFC feature is enabled.
+     *
+     * @return True if Secure NFC is enabled, false otherwise
+     * @throws UnsupportedOperationException if FEATURE_NFC,
+     * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+     * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
+     * are unavailable
+     * @throws UnsupportedOperationException if device doesn't support
+     *         Secure NFC functionality. {@link #isSecureNfcSupported}
+     */
+    public boolean isSecureNfcEnabled() {
+        if (!sHasNfcFeature && !sHasCeFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.isNfcSecureEnabled();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.isNfcSecureEnabled();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Sets NFC Reader option feature.
+     * <p>This API is for the Settings application.
+     * @return True if successful
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_READER_OPTION)
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    public boolean enableReaderOption(boolean enable) {
+        if (!sHasNfcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.enableReaderOption(enable);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.enableReaderOption(enable);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Checks if the device supports NFC Reader option functionality.
+     *
+     * @return True if device supports NFC Reader option, false otherwise
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_READER_OPTION)
+    public boolean isReaderOptionSupported() {
+        if (!sHasNfcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.isReaderOptionSupported();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.isReaderOptionSupported();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Checks NFC Reader option feature is enabled.
+     *
+     * @return True if NFC Reader option  is enabled, false otherwise
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     * @throws UnsupportedOperationException if device doesn't support
+     *         NFC Reader option functionality. {@link #isReaderOptionSupported}
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_READER_OPTION)
+    public boolean isReaderOptionEnabled() {
+        if (!sHasNfcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.isReaderOptionEnabled();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.isReaderOptionEnabled();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Enable NDEF Push feature.
+     * <p>This API is for the Settings application.
+     * @hide
+     * @removed
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    @UnsupportedAppUsage
+    public boolean enableNdefPush() {
+        return false;
+    }
+
+    /**
+     * Disable NDEF Push feature.
+     * <p>This API is for the Settings application.
+     * @hide
+     * @removed
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    @UnsupportedAppUsage
+    public boolean disableNdefPush() {
+        return false;
+    }
+
+    /**
+     * Return true if the NDEF Push (Android Beam) feature is enabled.
+     * <p>This function will return true only if both NFC is enabled, and the
+     * NDEF Push feature is enabled.
+     * <p>Note that if NFC is enabled but NDEF Push is disabled then this
+     * device can still <i>receive</i> NDEF messages, it just cannot send them.
+     * <p>Applications cannot directly toggle the NDEF Push feature, but they
+     * can request Settings UI allowing the user to toggle NDEF Push using
+     * <code>startActivity(new Intent(Settings.ACTION_NFCSHARING_SETTINGS))</code>
+     * <p>Example usage in an Activity that requires NDEF Push:
+     * <p><pre>
+     * protected void onResume() {
+     *     super.onResume();
+     *     if (!nfcAdapter.isEnabled()) {
+     *         startActivity(new Intent(Settings.ACTION_NFC_SETTINGS));
+     *     } else if (!nfcAdapter.isNdefPushEnabled()) {
+     *         startActivity(new Intent(Settings.ACTION_NFCSHARING_SETTINGS));
+     *     }
+     * }</pre>
+     *
+     * @see android.provider.Settings#ACTION_NFCSHARING_SETTINGS
+     * @return true if NDEF Push feature is enabled
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     * @removed this feature is removed. File sharing can work using other technology like
+     * Bluetooth.
+     */
+    @java.lang.Deprecated
+    @UnsupportedAppUsage
+    public boolean isNdefPushEnabled() {
+        synchronized (sLock) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Signals that you are no longer interested in communicating with an NFC tag
+     * for as long as it remains in range.
+     *
+     * All future attempted communication to this tag will fail with {@link IOException}.
+     * The NFC controller will be put in a low-power polling mode, allowing the device
+     * to save power in cases where it's "attached" to a tag all the time (e.g. a tag in
+     * car dock).
+     *
+     * Additionally the debounceMs parameter allows you to specify for how long the tag needs
+     * to have gone out of range, before it will be dispatched again.
+     *
+     * Note: the NFC controller typically polls at a pretty slow interval (100 - 500 ms).
+     * This means that if the tag repeatedly goes in and out of range (for example, in
+     * case of a flaky connection), and the controller happens to poll every time the
+     * tag is out of range, it *will* re-dispatch the tag after debounceMs, despite the tag
+     * having been "in range" during the interval.
+     *
+     * Note 2: if a tag with another UID is detected after this API is called, its effect
+     * will be cancelled; if this tag shows up before the amount of time specified in
+     * debounceMs, it will be dispatched again.
+     *
+     * Note 3: some tags have a random UID, in which case this API won't work reliably.
+     *
+     * @param tag        the {@link android.nfc.Tag Tag} to ignore.
+     * @param debounceMs minimum amount of time the tag needs to be out of range before being
+     *                   dispatched again.
+     * @param tagRemovedListener listener to be called when the tag is removed from the field.
+     *                           Note that this will only be called if the tag has been out of range
+     *                           for at least debounceMs, or if another tag came into range before
+     *                           debounceMs. May be null in case you don't want a callback.
+     * @param handler the {@link android.os.Handler Handler} that will be used for delivering
+     *                the callback. if the handler is null, then the thread used for delivering
+     *                the callback is unspecified.
+     * @return false if the tag couldn't be found (or has already gone out of range), true otherwise
+     */
+    public boolean ignore(final Tag tag, int debounceMs,
+                          final OnTagRemovedListener tagRemovedListener, final Handler handler) {
+        ITagRemovedCallback.Stub iListener = null;
+        if (tagRemovedListener != null) {
+            iListener = new ITagRemovedCallback.Stub() {
+                @Override
+                public void onTagRemoved() throws RemoteException {
+                    if (handler != null) {
+                        handler.post(new Runnable() {
+                            @Override
+                            public void run() {
+                                tagRemovedListener.onTagRemoved();
+                            }
+                        });
+                    } else {
+                        tagRemovedListener.onTagRemoved();
+                    }
+                    synchronized (mLock) {
+                        mTagRemovedListener = null;
+                    }
+                }
+            };
+        }
+        synchronized (mLock) {
+            mTagRemovedListener = iListener;
+        }
+        try {
+            return sService.ignore(tag.getServiceHandle(), debounceMs, iListener);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Inject a mock NFC tag.<p>
+     * Used for testing purposes.
+     * <p class="note">Requires the
+     * {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission.
+     * @hide
+     */
+    public void dispatch(Tag tag) {
+        if (tag == null) {
+            throw new NullPointerException("tag cannot be null");
+        }
+        try {
+            sService.dispatch(tag);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+        }
+    }
+
+    /**
+     * Registers a new NFC unlock handler with the NFC service.
+     *
+     * <p />NFC unlock handlers are intended to unlock the keyguard in the presence of a trusted
+     * NFC device. The handler should return true if it successfully authenticates the user and
+     * unlocks the keyguard.
+     *
+     * <p /> The parameter {@code tagTechnologies} determines which Tag technologies will be polled for
+     * at the lockscreen. Polling for less tag technologies reduces latency, and so it is
+     * strongly recommended to only provide the Tag technologies that the handler is expected to
+     * receive. There must be at least one tag technology provided, otherwise the unlock handler
+     * is ignored.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    public boolean addNfcUnlockHandler(final NfcUnlockHandler unlockHandler,
+                                       String[] tagTechnologies) {
+        synchronized (sLock) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
+        // If there are no tag technologies, don't bother adding unlock handler
+        if (tagTechnologies.length == 0) {
+            return false;
+        }
+
+        try {
+            synchronized (mLock) {
+                if (mNfcUnlockHandlers.containsKey(unlockHandler)) {
+                    // update the tag technologies
+                    sService.removeNfcUnlockHandler(mNfcUnlockHandlers.get(unlockHandler));
+                    mNfcUnlockHandlers.remove(unlockHandler);
+                }
+
+                INfcUnlockHandler.Stub iHandler = new INfcUnlockHandler.Stub() {
+                    @Override
+                    public boolean onUnlockAttempted(Tag tag) throws RemoteException {
+                        return unlockHandler.onUnlockAttempted(tag);
+                    }
+                };
+
+                sService.addNfcUnlockHandler(iHandler,
+                        Tag.getTechCodesFromStrings(tagTechnologies));
+                mNfcUnlockHandlers.put(unlockHandler, iHandler);
+            }
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            return false;
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Unable to register LockscreenDispatch", e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Removes a previously registered unlock handler. Also removes the tag technologies
+     * associated with the removed unlock handler.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    public boolean removeNfcUnlockHandler(NfcUnlockHandler unlockHandler) {
+        synchronized (sLock) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
+        try {
+            synchronized (mLock) {
+                if (mNfcUnlockHandlers.containsKey(unlockHandler)) {
+                    sService.removeNfcUnlockHandler(mNfcUnlockHandlers.remove(unlockHandler));
+                }
+
+                return true;
+            }
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            return false;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public INfcAdapterExtras getNfcAdapterExtrasInterface() {
+        if (mContext == null) {
+            throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
+                    + " NFC extras APIs");
+        }
+        try {
+            return sService.getNfcAdapterExtrasInterface(mContext.getPackageName());
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return null;
+            }
+            try {
+                return sService.getNfcAdapterExtrasInterface(mContext.getPackageName());
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return null;
+        }
+    }
+
+    void enforceResumed(Activity activity) {
+        if (!activity.isResumed()) {
+            throw new IllegalStateException("API cannot be called while activity is paused");
+        }
+    }
+
+    int getSdkVersion() {
+        if (mContext == null) {
+            return android.os.Build.VERSION_CODES.GINGERBREAD; // best guess
+        } else {
+            return mContext.getApplicationInfo().targetSdkVersion;
+        }
+    }
+
+    /**
+     * Sets NFC controller always on feature.
+     * <p>This API is for the NFCC internal state management. It allows to discriminate
+     * the controller function from the NFC function by keeping the NFC controller on without
+     * any NFC RF enabled if necessary.
+     * <p>This call is asynchronous. Register a listener {@link #ControllerAlwaysOnListener}
+     * by {@link #registerControllerAlwaysOnListener} to find out when the operation is
+     * complete.
+     * <p>If this returns true, then either NFCC always on state has been set based on the value,
+     * or a {@link ControllerAlwaysOnListener#onControllerAlwaysOnChanged(boolean)} will be invoked
+     * to indicate the state change.
+     * If this returns false, then there is some problem that prevents an attempt to turn NFCC
+     * always on.
+     * @param value if true the NFCC will be kept on (with no RF enabled if NFC adapter is
+     * disabled), if false the NFCC will follow completely the Nfc adapter state.
+     * @throws UnsupportedOperationException if FEATURE_NFC,
+     * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+     * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
+     * are unavailable
+     * @return void
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
+    public boolean setControllerAlwaysOn(boolean value) {
+        if (!sHasNfcFeature && !sHasCeFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.setControllerAlwaysOn(value);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.setControllerAlwaysOn(value);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Checks NFC controller always on feature is enabled.
+     *
+     * @return True if NFC controller always on is enabled, false otherwise
+     * @throws UnsupportedOperationException if FEATURE_NFC,
+     * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+     * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
+     * are unavailable
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
+    public boolean isControllerAlwaysOn() {
+        try {
+            return sService.isControllerAlwaysOn();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.isControllerAlwaysOn();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Checks if the device supports NFC controller always on functionality.
+     *
+     * @return True if device supports NFC controller always on, false otherwise
+     * @throws UnsupportedOperationException if FEATURE_NFC,
+     * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+     * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
+     * are unavailable
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
+    public boolean isControllerAlwaysOnSupported() {
+        if (!sHasNfcFeature && !sHasCeFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.isControllerAlwaysOnSupported();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.isControllerAlwaysOnSupported();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Register a {@link ControllerAlwaysOnListener} to listen for NFC controller always on
+     * state changes
+     * <p>The provided listener will be invoked by the given {@link Executor}.
+     *
+     * @param executor an {@link Executor} to execute given listener
+     * @param listener user implementation of the {@link ControllerAlwaysOnListener}
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
+    public void registerControllerAlwaysOnListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull ControllerAlwaysOnListener listener) {
+        mControllerAlwaysOnListener.register(executor, listener);
+    }
+
+    /**
+     * Unregister the specified {@link ControllerAlwaysOnListener}
+     * <p>The same {@link ControllerAlwaysOnListener} object used when calling
+     * {@link #registerControllerAlwaysOnListener(Executor, ControllerAlwaysOnListener)}
+     * must be used.
+     *
+     * <p>Listeners are automatically unregistered when application process goes away
+     *
+     * @param listener user implementation of the {@link ControllerAlwaysOnListener}
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
+    public void unregisterControllerAlwaysOnListener(
+            @NonNull ControllerAlwaysOnListener listener) {
+        mControllerAlwaysOnListener.unregister(listener);
+    }
+
+
+    /**
+     * Sets whether we dispatch NFC Tag intents to the package.
+     *
+     * <p>{@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED} or
+     * {@link #ACTION_TAG_DISCOVERED} will not be dispatched to an Activity if its package is
+     * disallowed.
+     * <p>An app is added to the preference list with the allowed flag set to {@code true}
+     * when a Tag intent is dispatched to the package for the first time. This API is called
+     * by settings to note that the user wants to change this default preference.
+     *
+     * @param userId the user to whom this package name will belong to
+     * @param pkg the full name (i.e. com.google.android.tag) of the package that will be added to
+     * the preference list
+     * @param allow {@code true} to allow dispatching Tag intents to the package's activity,
+     * {@code false} otherwise
+     * @return the {@link #TagIntentAppPreferenceResult} value
+     * @throws UnsupportedOperationException if {@link isTagIntentAppPreferenceSupported} returns
+     * {@code false}
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    @TagIntentAppPreferenceResult
+    public int setTagIntentAppPreferenceForUser(@UserIdInt int userId,
+                @NonNull String pkg, boolean allow) {
+        Objects.requireNonNull(pkg, "pkg cannot be null");
+        if (!isTagIntentAppPreferenceSupported()) {
+            Log.e(TAG, "TagIntentAppPreference is not supported");
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.setTagIntentAppPreferenceForUser(userId, pkg, allow);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            try {
+                return sService.setTagIntentAppPreferenceForUser(userId, pkg, allow);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE;
+        }
+    }
+
+
+    /**
+     * Get the Tag dispatch preference list of the UserId.
+     *
+     * <p>This returns a mapping of package names for this user id to whether we dispatch Tag
+     * intents to the package. {@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED} or
+     * {@link #ACTION_TAG_DISCOVERED} will not be dispatched to an Activity if its package is
+     * mapped to {@code false}.
+     * <p>There are three different possible cases:
+     * <p>A package not being in the preference list.
+     * It does not contain any Tag intent filters or the user never triggers a Tag detection that
+     * matches the intent filter of the package.
+     * <p>A package being mapped to {@code true}.
+     * When a package has been launched by a tag detection for the first time, the package name is
+     * put to the map and by default mapped to {@code true}. The package will receive Tag intents as
+     * usual.
+     * <p>A package being mapped to {@code false}.
+     * The user chooses to disable this package and it will not receive any Tag intents anymore.
+     *
+     * @param userId the user to whom this preference list will belong to
+     * @return a map of the UserId which indicates the mapping from package name to
+     * boolean(allow status), otherwise return an empty map
+     * @throws UnsupportedOperationException if {@link isTagIntentAppPreferenceSupported} returns
+     * {@code false}
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    @NonNull
+    public Map<String, Boolean> getTagIntentAppPreferenceForUser(@UserIdInt int userId) {
+        if (!isTagIntentAppPreferenceSupported()) {
+            Log.e(TAG, "TagIntentAppPreference is not supported");
+            throw new UnsupportedOperationException();
+        }
+        try {
+            Map<String, Boolean> result = (Map<String, Boolean>) sService
+                     .getTagIntentAppPreferenceForUser(userId);
+            return result;
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return Collections.emptyMap();
+            }
+            try {
+                Map<String, Boolean> result = (Map<String, Boolean>) sService
+                        .getTagIntentAppPreferenceForUser(userId);
+                return result;
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return Collections.emptyMap();
+        }
+    }
+
+    /**
+     * Checks if the device supports Tag application preference.
+     *
+     * @return {@code true} if the device supports Tag application preference, {@code false}
+     * otherwise
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    public boolean isTagIntentAppPreferenceSupported() {
+        if (!sHasNfcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.isTagIntentAppPreferenceSupported();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.isTagIntentAppPreferenceSupported();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+   /**
+     * Notifies the system of a new polling loop.
+     *
+     * @param frame is the new frame.
+     *
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public void notifyPollingLoop(@NonNull Bundle frame) {
+        try {
+            if (sService == null) {
+                attemptDeadServiceRecovery(null);
+            }
+            sService.notifyPollingLoop(frame);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return;
+            }
+            try {
+                sService.notifyPollingLoop(frame);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+        }
+    }
+
+   /**
+     * Notifies the system of a an HCE session being deactivated.
+     *     *
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public void notifyHceDeactivated() {
+        try {
+            if (sService == null) {
+                attemptDeadServiceRecovery(null);
+            }
+            sService.notifyHceDeactivated();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return;
+            }
+            try {
+                sService.notifyHceDeactivated();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+        }
+    }
+
+    /**
+     * Sets NFC charging feature.
+     * <p>This API is for the Settings application.
+     * @return True if successful
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    public boolean setWlcEnabled(boolean enable) {
+        if (!sHasNfcWlcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.setWlcEnabled(enable);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.setWlcEnabled(enable);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Checks NFC charging feature is enabled.
+     *
+     * @return True if NFC charging is enabled, false otherwise
+     * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+     * is unavailable
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+    public boolean isWlcEnabled() {
+        if (!sHasNfcWlcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.isWlcEnabled();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.isWlcEnabled();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * A listener to be invoked when NFC controller always on state changes.
+     * <p>Register your {@code ControllerAlwaysOnListener} implementation with {@link
+     * NfcAdapter#registerWlcStateListener} and disable it with {@link
+     * NfcAdapter#unregisterWlcStateListenerListener}.
+     * @see #registerWlcStateListener
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+    public interface WlcStateListener {
+        /**
+         * Called on NFC WLC state changes
+         */
+        void onWlcStateChanged(@NonNull WlcListenerDeviceInfo wlcListenerDeviceInfo);
+    }
+
+    /**
+     * Register a {@link WlcStateListener} to listen for NFC WLC state changes
+     * <p>The provided listener will be invoked by the given {@link Executor}.
+     *
+     * @param executor an {@link Executor} to execute given listener
+     * @param listener user implementation of the {@link WlcStateListener}
+     * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+     * is unavailable
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+    public void registerWlcStateListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull WlcStateListener listener) {
+        if (!sHasNfcWlcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        mNfcWlcStateListener.register(executor, listener);
+    }
+
+    /**
+     * Unregister the specified {@link WlcStateListener}
+     * <p>The same {@link WlcStateListener} object used when calling
+     * {@link #registerWlcStateListener(Executor, WlcStateListener)}
+     * must be used.
+     *
+     * <p>Listeners are automatically unregistered when application process goes away
+     *
+     * @param listener user implementation of the {@link WlcStateListener}a
+     * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+     * is unavailable
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+    public void unregisterWlcStateListener(
+            @NonNull WlcStateListener listener) {
+        if (!sHasNfcWlcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        mNfcWlcStateListener.unregister(listener);
+    }
+
+    /**
+     * Returns information on the NFC charging listener device
+     *
+     * @return Information on the NFC charging listener device
+     * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+     * is unavailable
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+    @Nullable
+    public WlcListenerDeviceInfo getWlcListenerDeviceInfo() {
+        if (!sHasNfcWlcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.getWlcListenerDeviceInfo();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return null;
+            }
+            try {
+                return sService.getWlcListenerDeviceInfo();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return null;
+        }
+    }
+}
diff --git a/core/java/android/nfc/NfcAntennaInfo.aidl b/nfc/java/android/nfc/NfcAntennaInfo.aidl
similarity index 100%
rename from core/java/android/nfc/NfcAntennaInfo.aidl
rename to nfc/java/android/nfc/NfcAntennaInfo.aidl
diff --git a/core/java/android/nfc/NfcAntennaInfo.java b/nfc/java/android/nfc/NfcAntennaInfo.java
similarity index 100%
rename from core/java/android/nfc/NfcAntennaInfo.java
rename to nfc/java/android/nfc/NfcAntennaInfo.java
diff --git a/core/java/android/nfc/NfcControllerAlwaysOnListener.java b/nfc/java/android/nfc/NfcControllerAlwaysOnListener.java
similarity index 100%
rename from core/java/android/nfc/NfcControllerAlwaysOnListener.java
rename to nfc/java/android/nfc/NfcControllerAlwaysOnListener.java
diff --git a/core/java/android/nfc/NfcEvent.java b/nfc/java/android/nfc/NfcEvent.java
similarity index 100%
rename from core/java/android/nfc/NfcEvent.java
rename to nfc/java/android/nfc/NfcEvent.java
diff --git a/core/java/android/nfc/NfcFrameworkInitializer.java b/nfc/java/android/nfc/NfcFrameworkInitializer.java
similarity index 100%
rename from core/java/android/nfc/NfcFrameworkInitializer.java
rename to nfc/java/android/nfc/NfcFrameworkInitializer.java
diff --git a/core/java/android/nfc/NfcManager.java b/nfc/java/android/nfc/NfcManager.java
similarity index 100%
rename from core/java/android/nfc/NfcManager.java
rename to nfc/java/android/nfc/NfcManager.java
diff --git a/core/java/android/nfc/NfcServiceManager.java b/nfc/java/android/nfc/NfcServiceManager.java
similarity index 100%
rename from core/java/android/nfc/NfcServiceManager.java
rename to nfc/java/android/nfc/NfcServiceManager.java
diff --git a/nfc/java/android/nfc/NfcWlcStateListener.java b/nfc/java/android/nfc/NfcWlcStateListener.java
new file mode 100644
index 0000000..890cb09
--- /dev/null
+++ b/nfc/java/android/nfc/NfcWlcStateListener.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+import android.annotation.NonNull;
+import android.nfc.NfcAdapter.WlcStateListener;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+public class NfcWlcStateListener extends INfcWlcStateListener.Stub {
+    private static final String TAG = NfcWlcStateListener.class.getSimpleName();
+
+    private final INfcAdapter mAdapter;
+
+    private final Map<WlcStateListener, Executor> mListenerMap = new HashMap<>();
+
+    private WlcListenerDeviceInfo mCurrentState = null;
+    private boolean mIsRegistered = false;
+
+    public NfcWlcStateListener(@NonNull INfcAdapter adapter) {
+        mAdapter = adapter;
+    }
+
+    /**
+     * Register a {@link WlcStateListener} with this
+     * {@link WlcStateListener}
+     *
+     * @param executor an {@link Executor} to execute given listener
+     * @param listener user implementation of the {@link WlcStateListener}
+     */
+    public void register(@NonNull Executor executor, @NonNull WlcStateListener listener) {
+        synchronized (this) {
+            if (mListenerMap.containsKey(listener)) {
+                return;
+            }
+
+            mListenerMap.put(listener, executor);
+
+            if (!mIsRegistered) {
+                try {
+                    mAdapter.registerWlcStateListener(this);
+                    mIsRegistered = true;
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to register");
+                }
+            }
+        }
+    }
+
+    /**
+     * Unregister the specified {@link WlcStateListener}
+     *
+     * @param listener user implementation of the {@link WlcStateListener}
+     */
+    public void unregister(@NonNull WlcStateListener listener) {
+        synchronized (this) {
+            if (!mListenerMap.containsKey(listener)) {
+                return;
+            }
+
+            mListenerMap.remove(listener);
+
+            if (mListenerMap.isEmpty() && mIsRegistered) {
+                try {
+                    mAdapter.unregisterWlcStateListener(this);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to unregister");
+                }
+                mIsRegistered = false;
+            }
+        }
+    }
+
+    private void sendCurrentState(@NonNull WlcStateListener listener) {
+        synchronized (this) {
+            Executor executor = mListenerMap.get(listener);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                if (Flags.enableNfcCharging()) {
+                    executor.execute(() -> listener.onWlcStateChanged(
+                            mCurrentState));
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    @Override
+    public void onWlcStateChanged(@NonNull WlcListenerDeviceInfo wlcListenerDeviceInfo) {
+        synchronized (this) {
+            mCurrentState = wlcListenerDeviceInfo;
+
+            for (WlcStateListener cb : mListenerMap.keySet()) {
+                sendCurrentState(cb);
+            }
+        }
+    }
+}
+
diff --git a/core/java/android/nfc/Tag.aidl b/nfc/java/android/nfc/Tag.aidl
similarity index 100%
rename from core/java/android/nfc/Tag.aidl
rename to nfc/java/android/nfc/Tag.aidl
diff --git a/core/java/android/nfc/Tag.java b/nfc/java/android/nfc/Tag.java
similarity index 100%
rename from core/java/android/nfc/Tag.java
rename to nfc/java/android/nfc/Tag.java
diff --git a/core/java/android/nfc/TagLostException.java b/nfc/java/android/nfc/TagLostException.java
similarity index 100%
rename from core/java/android/nfc/TagLostException.java
rename to nfc/java/android/nfc/TagLostException.java
diff --git a/core/java/android/nfc/TechListParcel.aidl b/nfc/java/android/nfc/TechListParcel.aidl
similarity index 100%
rename from core/java/android/nfc/TechListParcel.aidl
rename to nfc/java/android/nfc/TechListParcel.aidl
diff --git a/core/java/android/nfc/TechListParcel.java b/nfc/java/android/nfc/TechListParcel.java
similarity index 100%
rename from core/java/android/nfc/TechListParcel.java
rename to nfc/java/android/nfc/TechListParcel.java
diff --git a/core/java/android/nfc/TransceiveResult.aidl b/nfc/java/android/nfc/TransceiveResult.aidl
similarity index 100%
rename from core/java/android/nfc/TransceiveResult.aidl
rename to nfc/java/android/nfc/TransceiveResult.aidl
diff --git a/core/java/android/nfc/TransceiveResult.java b/nfc/java/android/nfc/TransceiveResult.java
similarity index 100%
rename from core/java/android/nfc/TransceiveResult.java
rename to nfc/java/android/nfc/TransceiveResult.java
diff --git a/nfc/java/android/nfc/WlcListenerDeviceInfo.aidl b/nfc/java/android/nfc/WlcListenerDeviceInfo.aidl
new file mode 100644
index 0000000..7f2ca54
--- /dev/null
+++ b/nfc/java/android/nfc/WlcListenerDeviceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+parcelable WlcListenerDeviceInfo;
diff --git a/nfc/java/android/nfc/WlcListenerDeviceInfo.java b/nfc/java/android/nfc/WlcListenerDeviceInfo.java
new file mode 100644
index 0000000..45315f8
--- /dev/null
+++ b/nfc/java/android/nfc/WlcListenerDeviceInfo.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Contains information of the nfc wireless charging listener device information.
+ */
+@FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+public final class WlcListenerDeviceInfo implements Parcelable {
+    /**
+     * Device is currently not connected with any WlcListenerDevice.
+     */
+    public static final int STATE_DISCONNECTED = 1;
+
+    /**
+     * Device is currently connected with a WlcListenerDevice and is charging it.
+     */
+    public static final int STATE_CONNECTED_CHARGING = 2;
+
+    /**
+     * Device is currently connected with a WlcListenerDevice without charging it.
+     */
+    public static final int STATE_CONNECTED_DISCHARGING = 3;
+
+    /**
+     * Possible states from {@link #getState}.
+     * @hide
+     */
+    @IntDef(prefix = { "STATE_" }, value = {
+            STATE_DISCONNECTED,
+            STATE_CONNECTED_CHARGING,
+            STATE_CONNECTED_DISCHARGING
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface WlcListenerState{}
+
+    private int mProductId;
+    private double mTemperature;
+    private double mBatteryLevel;
+    private int mState;
+
+     /**
+     * Create a new object containing wlc listener information.
+     *
+     * @param productId code for the device vendor
+     * @param temperature current temperature
+     * @param batteryLevel current battery level
+     * @param state current state
+     */
+    public WlcListenerDeviceInfo(int productId, double temperature, double batteryLevel,
+            @WlcListenerState int state) {
+        this.mProductId = productId;
+        this.mTemperature = temperature;
+        this.mBatteryLevel = batteryLevel;
+        this.mState = state;
+    }
+
+    /**
+     * ProductId of the WLC listener device.
+     * @return integer that is converted from USI Stylus VendorID[11:0].
+     */
+    public int getProductId() {
+        return mProductId;
+    }
+
+    /**
+     * Temperature of the WLC listener device.
+     * @return the value represents the temperature in °C.
+     */
+    public double getTemperature() {
+        return mTemperature;
+    }
+
+    /**
+     * BatteryLevel of the WLC listener device.
+     * @return battery level in percentage [0-100]
+     */
+    public @FloatRange(from = 0.0, to = 100.0) double getBatteryLevel() {
+        return mBatteryLevel;
+    }
+
+    /**
+     * State of the WLC listener device.
+     */
+    public @WlcListenerState int getState() {
+        return mState;
+    }
+
+    private WlcListenerDeviceInfo(Parcel in) {
+        this.mProductId = in.readInt();
+        this.mTemperature = in.readDouble();
+        this.mBatteryLevel = in.readDouble();
+        this.mState = in.readInt();
+    }
+
+    public static final @NonNull Parcelable.Creator<WlcListenerDeviceInfo> CREATOR =
+            new Parcelable.Creator<WlcListenerDeviceInfo>() {
+                @Override
+                public WlcListenerDeviceInfo createFromParcel(Parcel in) {
+                    return new WlcListenerDeviceInfo(in);
+                }
+
+                @Override
+                public WlcListenerDeviceInfo[] newArray(int size) {
+                    return new WlcListenerDeviceInfo[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mProductId);
+        dest.writeDouble(mTemperature);
+        dest.writeDouble(mBatteryLevel);
+        dest.writeInt(mState);
+    }
+}
diff --git a/core/java/android/nfc/cardemulation/AidGroup.aidl b/nfc/java/android/nfc/cardemulation/AidGroup.aidl
similarity index 100%
rename from core/java/android/nfc/cardemulation/AidGroup.aidl
rename to nfc/java/android/nfc/cardemulation/AidGroup.aidl
diff --git a/core/java/android/nfc/cardemulation/AidGroup.java b/nfc/java/android/nfc/cardemulation/AidGroup.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/AidGroup.java
rename to nfc/java/android/nfc/cardemulation/AidGroup.java
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.aidl b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.aidl
similarity index 100%
rename from core/java/android/nfc/cardemulation/ApduServiceInfo.aidl
rename to nfc/java/android/nfc/cardemulation/ApduServiceInfo.aidl
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
new file mode 100644
index 0000000..426c5aa
--- /dev/null
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -0,0 +1,956 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**********************************************************************
+ * This file is not a part of the NFC mainline module                 *
+ * *******************************************************************/
+
+package android.nfc.cardemulation;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.nfc.Flags;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+import android.util.proto.ProtoOutputStream;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * Class holding APDU service info.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+public final class ApduServiceInfo implements Parcelable {
+    private static final String TAG = "ApduServiceInfo";
+
+    /**
+     * The service that implements this
+     */
+    private final ResolveInfo mService;
+
+    /**
+     * Description of the service
+     */
+    private final String mDescription;
+
+    /**
+     * Whether this service represents AIDs running on the host CPU
+     */
+    private final boolean mOnHost;
+
+    /**
+     * Offhost reader name.
+     * eg: SIM, eSE etc
+     */
+    private String mOffHostName;
+
+    /**
+     * Offhost reader name from manifest file.
+     * Used for resetOffHostSecureElement()
+     */
+    private final String mStaticOffHostName;
+
+    /**
+     * Mapping from category to static AID group
+     */
+    private final HashMap<String, AidGroup> mStaticAidGroups;
+
+    /**
+     * Mapping from category to dynamic AID group
+     */
+    private final HashMap<String, AidGroup> mDynamicAidGroups;
+
+    private final ArrayList<String> mPollingLoopFilters;
+
+    /**
+     * Whether this service should only be started when the device is unlocked.
+     */
+    private final boolean mRequiresDeviceUnlock;
+
+    /**
+     * Whether this service should only be started when the device is screen on.
+     */
+    private final boolean mRequiresDeviceScreenOn;
+
+    /**
+     * The id of the service banner specified in XML.
+     */
+    private final int mBannerResourceId;
+
+    /**
+     * The uid of the package the service belongs to
+     */
+    private final int mUid;
+
+    /**
+     * Settings Activity for this service
+     */
+    private final String mSettingsActivityName;
+
+    /**
+     * State of the service for CATEGORY_OTHER selection
+     */
+    private boolean mCategoryOtherServiceEnabled;
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
+            ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups,
+            boolean requiresUnlock, int bannerResource, int uid,
+            String settingsActivityName, String offHost, String staticOffHost) {
+        this(info, onHost, description, staticAidGroups, dynamicAidGroups,
+                requiresUnlock, bannerResource, uid, settingsActivityName,
+                offHost, staticOffHost, false);
+    }
+
+    /**
+     * @hide
+     */
+    public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
+            ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups,
+            boolean requiresUnlock, int bannerResource, int uid,
+            String settingsActivityName, String offHost, String staticOffHost,
+            boolean isEnabled) {
+        this(info, onHost, description, staticAidGroups, dynamicAidGroups,
+                requiresUnlock, onHost ? true : false, bannerResource, uid,
+                settingsActivityName, offHost, staticOffHost, isEnabled);
+    }
+
+    /**
+     * @hide
+     */
+    public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
+            List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups,
+            boolean requiresUnlock, boolean requiresScreenOn, int bannerResource, int uid,
+            String settingsActivityName, String offHost, String staticOffHost, boolean isEnabled) {
+        this.mService = info;
+        this.mDescription = description;
+        this.mStaticAidGroups = new HashMap<String, AidGroup>();
+        this.mDynamicAidGroups = new HashMap<String, AidGroup>();
+        this.mPollingLoopFilters = new ArrayList<String>();
+        this.mOffHostName = offHost;
+        this.mStaticOffHostName = staticOffHost;
+        this.mOnHost = onHost;
+        this.mRequiresDeviceUnlock = requiresUnlock;
+        this.mRequiresDeviceScreenOn = requiresScreenOn;
+        for (AidGroup aidGroup : staticAidGroups) {
+            this.mStaticAidGroups.put(aidGroup.getCategory(), aidGroup);
+        }
+        for (AidGroup aidGroup : dynamicAidGroups) {
+            this.mDynamicAidGroups.put(aidGroup.getCategory(), aidGroup);
+        }
+        this.mBannerResourceId = bannerResource;
+        this.mUid = uid;
+        this.mSettingsActivityName = settingsActivityName;
+        this.mCategoryOtherServiceEnabled = isEnabled;
+
+    }
+
+    /**
+     * Creates a new ApduServiceInfo object.
+     *
+     * @param pm packageManager instance
+     * @param info app component info
+     * @param onHost whether service is on host or not (secure element)
+     * @throws XmlPullParserException If an error occurs parsing the element.
+     * @throws IOException If an error occurs reading the element.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public ApduServiceInfo(@NonNull PackageManager pm, @NonNull ResolveInfo info, boolean onHost)
+            throws XmlPullParserException, IOException {
+        ServiceInfo si = info.serviceInfo;
+        XmlResourceParser parser = null;
+        try {
+            if (onHost) {
+                parser = si.loadXmlMetaData(pm, HostApduService.SERVICE_META_DATA);
+                if (parser == null) {
+                    throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA +
+                            " meta-data");
+                }
+            } else {
+                parser = si.loadXmlMetaData(pm, OffHostApduService.SERVICE_META_DATA);
+                if (parser == null) {
+                    throw new XmlPullParserException("No " + OffHostApduService.SERVICE_META_DATA +
+                            " meta-data");
+                }
+            }
+
+            int eventType = parser.getEventType();
+            while (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_DOCUMENT) {
+                eventType = parser.next();
+            }
+
+            String tagName = parser.getName();
+            if (onHost && !"host-apdu-service".equals(tagName)) {
+                throw new XmlPullParserException(
+                        "Meta-data does not start with <host-apdu-service> tag");
+            } else if (!onHost && !"offhost-apdu-service".equals(tagName)) {
+                throw new XmlPullParserException(
+                        "Meta-data does not start with <offhost-apdu-service> tag");
+            }
+
+            Resources res = pm.getResourcesForApplication(si.applicationInfo);
+            AttributeSet attrs = Xml.asAttributeSet(parser);
+            if (onHost) {
+                TypedArray sa = res.obtainAttributes(attrs,
+                        com.android.internal.R.styleable.HostApduService);
+                mService = info;
+                mDescription = sa.getString(
+                        com.android.internal.R.styleable.HostApduService_description);
+                mRequiresDeviceUnlock = sa.getBoolean(
+                        com.android.internal.R.styleable.HostApduService_requireDeviceUnlock,
+                        false);
+                mRequiresDeviceScreenOn = sa.getBoolean(
+                        com.android.internal.R.styleable.HostApduService_requireDeviceScreenOn,
+                        true);
+                mBannerResourceId = sa.getResourceId(
+                        com.android.internal.R.styleable.HostApduService_apduServiceBanner, -1);
+                mSettingsActivityName = sa.getString(
+                        com.android.internal.R.styleable.HostApduService_settingsActivity);
+                mOffHostName = null;
+                mStaticOffHostName = mOffHostName;
+                sa.recycle();
+            } else {
+                TypedArray sa = res.obtainAttributes(attrs,
+                        com.android.internal.R.styleable.OffHostApduService);
+                mService = info;
+                mDescription = sa.getString(
+                        com.android.internal.R.styleable.OffHostApduService_description);
+                mRequiresDeviceUnlock = sa.getBoolean(
+                        com.android.internal.R.styleable.OffHostApduService_requireDeviceUnlock,
+                        false);
+                mRequiresDeviceScreenOn = sa.getBoolean(
+                        com.android.internal.R.styleable.OffHostApduService_requireDeviceScreenOn,
+                        false);
+                mBannerResourceId = sa.getResourceId(
+                        com.android.internal.R.styleable.OffHostApduService_apduServiceBanner, -1);
+                mSettingsActivityName = sa.getString(
+                        com.android.internal.R.styleable.HostApduService_settingsActivity);
+                mOffHostName = sa.getString(
+                        com.android.internal.R.styleable.OffHostApduService_secureElementName);
+                if (mOffHostName != null) {
+                    if (mOffHostName.equals("eSE")) {
+                        mOffHostName = "eSE1";
+                    } else if (mOffHostName.equals("SIM")) {
+                        mOffHostName = "SIM1";
+                    }
+                }
+                mStaticOffHostName = mOffHostName;
+                sa.recycle();
+            }
+
+            mStaticAidGroups = new HashMap<String, AidGroup>();
+            mDynamicAidGroups = new HashMap<String, AidGroup>();
+            mPollingLoopFilters = new ArrayList<String>();
+            mOnHost = onHost;
+
+            final int depth = parser.getDepth();
+            AidGroup currentGroup = null;
+
+            // Parsed values for the current AID group
+            while (((eventType = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+                    && eventType != XmlPullParser.END_DOCUMENT) {
+                tagName = parser.getName();
+                if (eventType == XmlPullParser.START_TAG && "aid-group".equals(tagName) &&
+                        currentGroup == null) {
+                    final TypedArray groupAttrs = res.obtainAttributes(attrs,
+                            com.android.internal.R.styleable.AidGroup);
+                    // Get category of AID group
+                    String groupCategory = groupAttrs.getString(
+                            com.android.internal.R.styleable.AidGroup_category);
+                    String groupDescription = groupAttrs.getString(
+                            com.android.internal.R.styleable.AidGroup_description);
+                    if (!CardEmulation.CATEGORY_PAYMENT.equals(groupCategory)) {
+                        groupCategory = CardEmulation.CATEGORY_OTHER;
+                    }
+                    currentGroup = mStaticAidGroups.get(groupCategory);
+                    if (currentGroup != null) {
+                        if (!CardEmulation.CATEGORY_OTHER.equals(groupCategory)) {
+                            Log.e(TAG, "Not allowing multiple aid-groups in the " +
+                                    groupCategory + " category");
+                            currentGroup = null;
+                        }
+                    } else {
+                        currentGroup = new AidGroup(groupCategory, groupDescription);
+                    }
+                    groupAttrs.recycle();
+                } else if (eventType == XmlPullParser.END_TAG && "aid-group".equals(tagName) &&
+                        currentGroup != null) {
+                    if (currentGroup.getAids().size() > 0) {
+                        if (!mStaticAidGroups.containsKey(currentGroup.getCategory())) {
+                            mStaticAidGroups.put(currentGroup.getCategory(), currentGroup);
+                        }
+                    } else {
+                        Log.e(TAG, "Not adding <aid-group> with empty or invalid AIDs");
+                    }
+                    currentGroup = null;
+                } else if (eventType == XmlPullParser.START_TAG && "aid-filter".equals(tagName) &&
+                        currentGroup != null) {
+                    final TypedArray a = res.obtainAttributes(attrs,
+                            com.android.internal.R.styleable.AidFilter);
+                    String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
+                            toUpperCase();
+                    if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+                        currentGroup.getAids().add(aid);
+                    } else {
+                        Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
+                    }
+                    a.recycle();
+                } else if (eventType == XmlPullParser.START_TAG &&
+                        "aid-prefix-filter".equals(tagName) && currentGroup != null) {
+                    final TypedArray a = res.obtainAttributes(attrs,
+                            com.android.internal.R.styleable.AidFilter);
+                    String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
+                            toUpperCase();
+                    // Add wildcard char to indicate prefix
+                    aid = aid.concat("*");
+                    if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+                        currentGroup.getAids().add(aid);
+                    } else {
+                        Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
+                    }
+                    a.recycle();
+                } else if (eventType == XmlPullParser.START_TAG &&
+                        tagName.equals("aid-suffix-filter") && currentGroup != null) {
+                    final TypedArray a = res.obtainAttributes(attrs,
+                            com.android.internal.R.styleable.AidFilter);
+                    String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
+                            toUpperCase();
+                    // Add wildcard char to indicate suffix
+                    aid = aid.concat("#");
+                    if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+                        currentGroup.getAids().add(aid);
+                    } else {
+                        Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
+                    }
+                    a.recycle();
+                } else if (eventType == XmlPullParser.START_TAG
+                        && "polling-loop-filter".equals(tagName) && currentGroup == null) {
+                    final TypedArray a = res.obtainAttributes(attrs,
+                            com.android.internal.R.styleable.PollingLoopFilter);
+                    String plf =
+                            a.getString(com.android.internal.R.styleable.PollingLoopFilter_name)
+                            .toUpperCase(Locale.ROOT);
+                    mPollingLoopFilters.add(plf);
+                    a.recycle();
+                }
+            }
+        } catch (NameNotFoundException e) {
+            throw new XmlPullParserException("Unable to create context for: " + si.packageName);
+        } finally {
+            if (parser != null) parser.close();
+        }
+        // Set uid
+        mUid = si.applicationInfo.uid;
+
+        mCategoryOtherServiceEnabled = true;    // support other category
+
+    }
+
+    /**
+     * Returns the app component corresponding to this APDU service.
+     *
+     * @return app component for this service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public ComponentName getComponent() {
+        return new ComponentName(mService.serviceInfo.packageName,
+                mService.serviceInfo.name);
+    }
+
+    /**
+     * Returns the offhost secure element name (if the service is offhost).
+     *
+     * @return offhost secure element name for offhost services
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @Nullable
+    public String getOffHostSecureElement() {
+        return mOffHostName;
+    }
+
+    /**
+     * Returns a consolidated list of AIDs from the AID groups
+     * registered by this service. Note that if a service has both
+     * a static (manifest-based) AID group for a category and a dynamic
+     * AID group, only the dynamically registered AIDs will be returned
+     * for that category.
+     * @return List of AIDs registered by the service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public List<String> getAids() {
+        final ArrayList<String> aids = new ArrayList<String>();
+        for (AidGroup group : getAidGroups()) {
+            aids.addAll(group.getAids());
+        }
+        return aids;
+    }
+
+    /**
+     * Returns the current polling loop filters for this service.
+     * @return List of polling loop filters.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    @NonNull
+    public List<String> getPollingLoopFilters() {
+        return mPollingLoopFilters;
+    }
+
+    /**
+     * Returns a consolidated list of AIDs with prefixes from the AID groups
+     * registered by this service. Note that if a service has both
+     * a static (manifest-based) AID group for a category and a dynamic
+     * AID group, only the dynamically registered AIDs will be returned
+     * for that category.
+     * @return List of prefix AIDs registered by the service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public List<String> getPrefixAids() {
+        final ArrayList<String> prefixAids = new ArrayList<String>();
+        for (AidGroup group : getAidGroups()) {
+            for (String aid : group.getAids()) {
+                if (aid.endsWith("*")) {
+                    prefixAids.add(aid);
+                }
+            }
+        }
+        return prefixAids;
+    }
+
+    /**
+     * Returns a consolidated list of AIDs with subsets from the AID groups
+     * registered by this service. Note that if a service has both
+     * a static (manifest-based) AID group for a category and a dynamic
+     * AID group, only the dynamically registered AIDs will be returned
+     * for that category.
+     * @return List of prefix AIDs registered by the service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public List<String> getSubsetAids() {
+        final ArrayList<String> subsetAids = new ArrayList<String>();
+        for (AidGroup group : getAidGroups()) {
+            for (String aid : group.getAids()) {
+                if (aid.endsWith("#")) {
+                    subsetAids.add(aid);
+                }
+            }
+        }
+        return subsetAids;
+    }
+
+    /**
+     * Returns the registered AID group for this category.
+     *
+     * @param category category name
+     * @return {@link AidGroup} instance for the provided category
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public AidGroup getDynamicAidGroupForCategory(@NonNull String category) {
+        return mDynamicAidGroups.get(category);
+    }
+
+    /**
+     * Removes the registered AID group for this category.
+     *
+     * @param category category name
+     * @return {@code true} if an AID group existed
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public boolean removeDynamicAidGroupForCategory(@NonNull String category) {
+        return (mDynamicAidGroups.remove(category) != null);
+    }
+
+    /**
+     * Returns a consolidated list of AID groups
+     * registered by this service. Note that if a service has both
+     * a static (manifest-based) AID group for a category and a dynamic
+     * AID group, only the dynamically registered AID group will be returned
+     * for that category.
+     * @return List of AIDs registered by the service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public List<AidGroup> getAidGroups() {
+        final ArrayList<AidGroup> groups = new ArrayList<AidGroup>();
+        for (Map.Entry<String, AidGroup> entry : mDynamicAidGroups.entrySet()) {
+            groups.add(entry.getValue());
+        }
+        for (Map.Entry<String, AidGroup> entry : mStaticAidGroups.entrySet()) {
+            if (!mDynamicAidGroups.containsKey(entry.getKey())) {
+                // Consolidate AID groups - don't return static ones
+                // if a dynamic group exists for the category.
+                groups.add(entry.getValue());
+            }
+        }
+        return groups;
+    }
+
+    /**
+     * Returns the category to which this service has attributed the AID that is passed in,
+     * or null if we don't know this AID.
+     * @param aid AID to lookup for
+     * @return category name corresponding to this AID
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public String getCategoryForAid(@NonNull String aid) {
+        List<AidGroup> groups = getAidGroups();
+        for (AidGroup group : groups) {
+            if (group.getAids().contains(aid.toUpperCase())) {
+                return group.getCategory();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns whether there is any AID group for this category.
+     * @param category category name
+     * @return {@code true} if an AID group exists
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public boolean hasCategory(@NonNull String category) {
+        return (mStaticAidGroups.containsKey(category) || mDynamicAidGroups.containsKey(category));
+    }
+
+    /**
+     * Returns whether the service is on host or not.
+     * @return true if the service is on host (not secure element)
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public boolean isOnHost() {
+        return mOnHost;
+    }
+
+    /**
+     * Returns whether the service requires device unlock.
+     * @return whether the service requires device unlock
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public boolean requiresUnlock() {
+        return mRequiresDeviceUnlock;
+    }
+
+    /**
+     * Returns whether this service should only be started when the device is screen on.
+     * @return whether the service requires screen on
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public boolean requiresScreenOn() {
+        return mRequiresDeviceScreenOn;
+    }
+
+    /**
+     * Returns description of service.
+     * @return user readable description of service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public String getDescription() {
+        return mDescription;
+    }
+
+    /**
+     * Returns uid of service.
+     * @return uid of the service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public int getUid() {
+        return mUid;
+    }
+
+    /**
+     * Add or replace an AID group to this service.
+     * @param aidGroup instance of aid group to set or replace
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public void setDynamicAidGroup(@NonNull AidGroup aidGroup) {
+        mDynamicAidGroups.put(aidGroup.getCategory(), aidGroup);
+    }
+
+    /**
+     * Add a Polling Loop Filter. Custom NFC polling frames that match this filter will be
+     * delivered to {@link HostApduService#processPollingFrames(List)}.
+     * @param pollingLoopFilter this polling loop filter to add.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public void addPollingLoopFilter(@NonNull String pollingLoopFilter) {
+        mPollingLoopFilters.add(pollingLoopFilter.toUpperCase(Locale.ROOT));
+    }
+
+    /**
+     * Remove a Polling Loop Filter. Custom NFC polling frames that match this filter will no
+     * longer be delivered to {@link HostApduService#processPollingFrames(List)}.
+     * @param pollingLoopFilter this polling loop filter to add.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public void removePollingLoopFilter(@NonNull String pollingLoopFilter) {
+        mPollingLoopFilters.remove(pollingLoopFilter.toUpperCase(Locale.ROOT));
+    }
+
+    /**
+     * Sets the off host Secure Element.
+     * @param  offHost  Secure Element to set. Only accept strings with prefix SIM or prefix eSE.
+     *                  Ref: GSMA TS.26 - NFC Handset Requirements
+     *                  TS26_NFC_REQ_069: For UICC, Secure Element Name SHALL be SIM[smartcard slot]
+     *                                    (e.g. SIM/SIM1, SIM2… SIMn).
+     *                  TS26_NFC_REQ_070: For embedded SE, Secure Element Name SHALL be eSE[number]
+     *                                    (e.g. eSE/eSE1, eSE2, etc.).
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public void setOffHostSecureElement(@NonNull String offHost) {
+        mOffHostName = offHost;
+    }
+
+    /**
+     * Resets the off host Secure Element to statically defined
+     * by the service in the manifest file.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public void resetOffHostSecureElement() {
+        mOffHostName = mStaticOffHostName;
+    }
+
+    /**
+     * Load label for this service.
+     * @param pm packagemanager instance
+     * @return label name corresponding to service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public CharSequence loadLabel(@NonNull PackageManager pm) {
+        return mService.loadLabel(pm);
+    }
+
+    /**
+     * Load application label for this service.
+     * @param pm packagemanager instance
+     * @return app label name corresponding to service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public CharSequence loadAppLabel(@NonNull PackageManager pm) {
+        try {
+            return pm.getApplicationLabel(pm.getApplicationInfo(
+                    mService.resolvePackageName, PackageManager.GET_META_DATA));
+        } catch (PackageManager.NameNotFoundException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Load application icon for this service.
+     * @param pm packagemanager instance
+     * @return app icon corresponding to service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public Drawable loadIcon(@NonNull PackageManager pm) {
+        return mService.loadIcon(pm);
+    }
+
+    /**
+     * Load application banner for this service.
+     * @param pm packagemanager instance
+     * @return app banner corresponding to service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public Drawable loadBanner(@NonNull PackageManager pm) {
+        Resources res;
+        try {
+            res = pm.getResourcesForApplication(mService.serviceInfo.packageName);
+            Drawable banner = res.getDrawable(mBannerResourceId);
+            return banner;
+        } catch (NotFoundException e) {
+            Log.e(TAG, "Could not load banner.");
+            return null;
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Could not load banner.");
+            return null;
+        }
+    }
+
+    /**
+     * Load activity name for this service.
+     * @return activity name for this service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public String getSettingsActivityName() { return mSettingsActivityName; }
+
+    @Override
+    public String toString() {
+        StringBuilder out = new StringBuilder("ApduService: ");
+        out.append(getComponent());
+        out.append(", UID: " + mUid);
+        out.append(", description: " + mDescription);
+        out.append(", Static AID Groups: ");
+        for (AidGroup aidGroup : mStaticAidGroups.values()) {
+            out.append(aidGroup.toString());
+        }
+        out.append(", Dynamic AID Groups: ");
+        for (AidGroup aidGroup : mDynamicAidGroups.values()) {
+            out.append(aidGroup.toString());
+        }
+        return out.toString();
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ApduServiceInfo)) return false;
+        ApduServiceInfo thatService = (ApduServiceInfo) o;
+
+        return thatService.getComponent().equals(this.getComponent())
+                && thatService.getUid() == this.getUid();
+    }
+
+    @Override
+    public int hashCode() {
+        return getComponent().hashCode();
+    }
+
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        mService.writeToParcel(dest, flags);
+        dest.writeString(mDescription);
+        dest.writeInt(mOnHost ? 1 : 0);
+        dest.writeString(mOffHostName);
+        dest.writeString(mStaticOffHostName);
+        dest.writeInt(mStaticAidGroups.size());
+        if (mStaticAidGroups.size() > 0) {
+            dest.writeTypedList(new ArrayList<AidGroup>(mStaticAidGroups.values()));
+        }
+        dest.writeInt(mDynamicAidGroups.size());
+        if (mDynamicAidGroups.size() > 0) {
+            dest.writeTypedList(new ArrayList<AidGroup>(mDynamicAidGroups.values()));
+        }
+        dest.writeInt(mRequiresDeviceUnlock ? 1 : 0);
+        dest.writeInt(mRequiresDeviceScreenOn ? 1 : 0);
+        dest.writeInt(mBannerResourceId);
+        dest.writeInt(mUid);
+        dest.writeString(mSettingsActivityName);
+
+        dest.writeInt(mCategoryOtherServiceEnabled ? 1 : 0);
+    };
+
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public static final @NonNull Parcelable.Creator<ApduServiceInfo> CREATOR =
+            new Parcelable.Creator<ApduServiceInfo>() {
+                @Override
+                public ApduServiceInfo createFromParcel(Parcel source) {
+                    ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source);
+                    String description = source.readString();
+                    boolean onHost = source.readInt() != 0;
+                    String offHostName = source.readString();
+                    String staticOffHostName = source.readString();
+                    ArrayList<AidGroup> staticAidGroups = new ArrayList<AidGroup>();
+                    int numStaticGroups = source.readInt();
+                    if (numStaticGroups > 0) {
+                        source.readTypedList(staticAidGroups, AidGroup.CREATOR);
+                    }
+                    ArrayList<AidGroup> dynamicAidGroups = new ArrayList<AidGroup>();
+                    int numDynamicGroups = source.readInt();
+                    if (numDynamicGroups > 0) {
+                        source.readTypedList(dynamicAidGroups, AidGroup.CREATOR);
+                    }
+                    boolean requiresUnlock = source.readInt() != 0;
+                    boolean requiresScreenOn = source.readInt() != 0;
+                    int bannerResource = source.readInt();
+                    int uid = source.readInt();
+                    String settingsActivityName = source.readString();
+                    boolean isEnabled = source.readInt() != 0;
+                    return new ApduServiceInfo(info, onHost, description, staticAidGroups,
+                            dynamicAidGroups, requiresUnlock, requiresScreenOn, bannerResource, uid,
+                            settingsActivityName, offHostName, staticOffHostName,
+                            isEnabled);
+                }
+
+                @Override
+                public ApduServiceInfo[] newArray(int size) {
+                    return new ApduServiceInfo[size];
+                }
+            };
+
+    /**
+     * Dump contents for debugging.
+     * @param fd parcelfiledescriptor instance
+     * @param pw printwriter instance
+     * @param args args for dumping
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public void dump(@NonNull ParcelFileDescriptor fd, @NonNull PrintWriter pw,
+                     @NonNull String[] args) {
+        pw.println("    " + getComponent()
+                + " (Description: " + getDescription() + ")"
+                + " (UID: " + getUid() + ")");
+        if (mOnHost) {
+            pw.println("    On Host Service");
+        } else {
+            pw.println("    Off-host Service");
+            pw.println("        " + "Current off-host SE:" + mOffHostName
+                    + " static off-host SE:" + mStaticOffHostName);
+        }
+        pw.println("    Static AID groups:");
+        for (AidGroup group : mStaticAidGroups.values()) {
+            pw.println("        Category: " + group.getCategory()
+                    + "(enabled: " + mCategoryOtherServiceEnabled + ")");
+            for (String aid : group.getAids()) {
+                pw.println("            AID: " + aid);
+            }
+        }
+        pw.println("    Dynamic AID groups:");
+        for (AidGroup group : mDynamicAidGroups.values()) {
+            pw.println("        Category: " + group.getCategory()
+                    + "(enabled: " + mCategoryOtherServiceEnabled + ")");
+            for (String aid : group.getAids()) {
+                pw.println("            AID: " + aid);
+            }
+        }
+        pw.println("    Settings Activity: " + mSettingsActivityName);
+        pw.println("    Requires Device Unlock: " + mRequiresDeviceUnlock);
+        pw.println("    Requires Device ScreenOn: " + mRequiresDeviceScreenOn);
+    }
+
+
+    /**
+     * Enable or disable this CATEGORY_OTHER service.
+     *
+     * @param enabled true to indicate if user has enabled this service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public void setCategoryOtherServiceEnabled(boolean enabled) {
+        mCategoryOtherServiceEnabled = enabled;
+    }
+
+
+    /**
+     * Returns whether this CATEGORY_OTHER service is enabled or not.
+     *
+     * @return true to indicate if user has enabled this service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public boolean isCategoryOtherServiceEnabled() {
+        return mCategoryOtherServiceEnabled;
+    }
+
+    /**
+     * Dump debugging info as ApduServiceInfoProto.
+     *
+     * If the output belongs to a sub message, the caller is responsible for wrapping this function
+     * between {@link ProtoOutputStream#start(long)} and {@link ProtoOutputStream#end(long)}.
+     * See proto definition in frameworks/base/core/proto/android/nfc/apdu_service_info.proto
+     *
+     * @param proto the ProtoOutputStream to write to
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public void dumpDebug(@NonNull ProtoOutputStream proto) {
+        getComponent().dumpDebug(proto, ApduServiceInfoProto.COMPONENT_NAME);
+        proto.write(ApduServiceInfoProto.DESCRIPTION, getDescription());
+        proto.write(ApduServiceInfoProto.ON_HOST, mOnHost);
+        if (!mOnHost) {
+            proto.write(ApduServiceInfoProto.OFF_HOST_NAME, mOffHostName);
+            proto.write(ApduServiceInfoProto.STATIC_OFF_HOST_NAME, mStaticOffHostName);
+        }
+        for (AidGroup group : mStaticAidGroups.values()) {
+            long token = proto.start(ApduServiceInfoProto.STATIC_AID_GROUPS);
+            group.dump(proto);
+            proto.end(token);
+        }
+        for (AidGroup group : mDynamicAidGroups.values()) {
+            long token = proto.start(ApduServiceInfoProto.STATIC_AID_GROUPS);
+            group.dump(proto);
+            proto.end(token);
+        }
+        proto.write(ApduServiceInfoProto.SETTINGS_ACTIVITY_NAME, mSettingsActivityName);
+    }
+
+    private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
+    /**
+     * Copied over from {@link CardEmulation#isValidAid(String)}
+     * @hide
+     */
+    private static boolean isValidAid(String aid) {
+        if (aid == null)
+            return false;
+
+        // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
+        if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
+            Log.e(TAG, "AID " + aid + " is not a valid AID.");
+            return false;
+        }
+
+        // If not a prefix/subset AID, the total length must be even (even # of AID chars)
+        if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
+            Log.e(TAG, "AID " + aid + " is not a valid AID.");
+            return false;
+        }
+
+        // Verify hex characters
+        if (!AID_PATTERN.matcher(aid).matches()) {
+            Log.e(TAG, "AID " + aid + " is not a valid AID.");
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
new file mode 100644
index 0000000..1f41b81
--- /dev/null
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -0,0 +1,1169 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc.cardemulation;
+
+import android.Manifest;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
+import android.annotation.UserHandleAware;
+import android.annotation.UserIdInt;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.nfc.Constants;
+import android.nfc.Flags;
+import android.nfc.INfcCardEmulation;
+import android.nfc.NfcAdapter;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * This class can be used to query the state of
+ * NFC card emulation services.
+ *
+ * For a general introduction into NFC card emulation,
+ * please read the <a href="{@docRoot}guide/topics/connectivity/nfc/hce.html">
+ * NFC card emulation developer guide</a>.</p>
+ *
+ * <p class="note">Use of this class requires the
+ * {@link PackageManager#FEATURE_NFC_HOST_CARD_EMULATION} to be present
+ * on the device.
+ */
+public final class CardEmulation {
+    private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
+    private static final Pattern PLF_PATTERN = Pattern.compile("[0-9A-Fa-f]{1,32}");
+
+    static final String TAG = "CardEmulation";
+
+    /**
+     * Activity action: ask the user to change the default
+     * card emulation service for a certain category. This will
+     * show a dialog that asks the user whether they want to
+     * replace the current default service with the service
+     * identified with the ComponentName specified in
+     * {@link #EXTRA_SERVICE_COMPONENT}, for the category
+     * specified in {@link #EXTRA_CATEGORY}. There is an optional
+     * extra field using {@link Intent#EXTRA_USER} to specify
+     * the {@link UserHandle} of the user that owns the app.
+     *
+     * @deprecated Please use {@link android.app.role.RoleManager#createRequestRoleIntent(String)}
+     * with {@link android.app.role.RoleManager#ROLE_WALLET} parameter
+     * and {@link Activity#startActivityForResult(Intent, int)} instead.
+     */
+    @Deprecated
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CHANGE_DEFAULT =
+            "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
+
+    /**
+     * The category extra for {@link #ACTION_CHANGE_DEFAULT}.
+     *
+     * @see #ACTION_CHANGE_DEFAULT
+     */
+    public static final String EXTRA_CATEGORY = "category";
+
+    /**
+     * The service {@link ComponentName} object passed in as an
+     * extra for {@link #ACTION_CHANGE_DEFAULT}.
+     *
+     * @see #ACTION_CHANGE_DEFAULT
+     */
+    public static final String EXTRA_SERVICE_COMPONENT = "component";
+
+    /**
+     * Category used for NFC payment services.
+     */
+    public static final String CATEGORY_PAYMENT = "payment";
+
+    /**
+     * Category that can be used for all other card emulation
+     * services.
+     */
+    public static final String CATEGORY_OTHER = "other";
+
+    /**
+     * Return value for {@link #getSelectionModeForCategory(String)}.
+     *
+     * <p>In this mode, the user has set a default service for this
+     *    category.
+     *
+     * <p>When using ISO-DEP card emulation with {@link HostApduService}
+     *    or {@link OffHostApduService}, if a remote NFC device selects
+     *    any of the Application IDs (AIDs)
+     *    that the default service has registered in this category,
+     *    that service will automatically be bound to to handle
+     *    the transaction.
+     */
+    public static final int SELECTION_MODE_PREFER_DEFAULT = 0;
+
+    /**
+     * Return value for {@link #getSelectionModeForCategory(String)}.
+     *
+     * <p>In this mode, when using ISO-DEP card emulation with {@link HostApduService}
+     *    or {@link OffHostApduService}, whenever an Application ID (AID) of this category
+     *    is selected, the user is asked which service they want to use to handle
+     *    the transaction, even if there is only one matching service.
+     */
+    public static final int SELECTION_MODE_ALWAYS_ASK = 1;
+
+    /**
+     * Return value for {@link #getSelectionModeForCategory(String)}.
+     *
+     * <p>In this mode, when using ISO-DEP card emulation with {@link HostApduService}
+     *    or {@link OffHostApduService}, the user will only be asked to select a service
+     *    if the Application ID (AID) selected by the reader has been registered by multiple
+     *    services. If there is only one service that has registered for the AID,
+     *    that service will be invoked directly.
+     */
+    public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2;
+
+    static boolean sIsInitialized = false;
+    static HashMap<Context, CardEmulation> sCardEmus = new HashMap<Context, CardEmulation>();
+    static INfcCardEmulation sService;
+
+    final Context mContext;
+
+    private CardEmulation(Context context, INfcCardEmulation service) {
+        mContext = context.getApplicationContext();
+        sService = service;
+    }
+
+    /**
+     * Helper to get an instance of this class.
+     *
+     * @param adapter A reference to an NfcAdapter object.
+     * @return
+     */
+    public static synchronized CardEmulation getInstance(NfcAdapter adapter) {
+        if (adapter == null) throw new NullPointerException("NfcAdapter is null");
+        Context context = adapter.getContext();
+        if (context == null) {
+            Log.e(TAG, "NfcAdapter context is null.");
+            throw new UnsupportedOperationException();
+        }
+        if (!sIsInitialized) {
+            PackageManager pm = context.getPackageManager();
+            if (pm == null) {
+                Log.e(TAG, "Cannot get PackageManager");
+                throw new UnsupportedOperationException();
+            }
+            if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
+                Log.e(TAG, "This device does not support card emulation");
+                throw new UnsupportedOperationException();
+            }
+            sIsInitialized = true;
+        }
+        CardEmulation manager = sCardEmus.get(context);
+        if (manager == null) {
+            // Get card emu service
+            INfcCardEmulation service = adapter.getCardEmulationService();
+            if (service == null) {
+                Log.e(TAG, "This device does not implement the INfcCardEmulation interface.");
+                throw new UnsupportedOperationException();
+            }
+            manager = new CardEmulation(context, service);
+            sCardEmus.put(context, manager);
+        }
+        return manager;
+    }
+
+    /**
+     * Allows an application to query whether a service is currently
+     * the default service to handle a card emulation category.
+     *
+     * <p>Note that if {@link #getSelectionModeForCategory(String)}
+     * returns {@link #SELECTION_MODE_ALWAYS_ASK} or {@link #SELECTION_MODE_ASK_IF_CONFLICT},
+     * this method will always return false. That is because in these
+     * selection modes a default can't be set at the category level. For categories where
+     * the selection mode is {@link #SELECTION_MODE_ALWAYS_ASK} or
+     * {@link #SELECTION_MODE_ASK_IF_CONFLICT}, use
+     * {@link #isDefaultServiceForAid(ComponentName, String)} to determine whether a service
+     * is the default for a specific AID.
+     *
+     * @param service The ComponentName of the service
+     * @param category The category
+     * @return whether service is currently the default service for the category.
+     *
+     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+     */
+    public boolean isDefaultServiceForCategory(ComponentName service, String category) {
+        try {
+            return sService.isDefaultServiceForCategory(mContext.getUser().getIdentifier(),
+                    service, category);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.isDefaultServiceForCategory(mContext.getUser().getIdentifier(),
+                        service, category);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     *
+     * Allows an application to query whether a service is currently
+     * the default handler for a specified ISO7816-4 Application ID.
+     *
+     * @param service The ComponentName of the service
+     * @param aid The ISO7816-4 Application ID
+     * @return whether the service is the default handler for the specified AID
+     *
+     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+     */
+    public boolean isDefaultServiceForAid(ComponentName service, String aid) {
+        try {
+            return sService.isDefaultServiceForAid(mContext.getUser().getIdentifier(),
+                    service, aid);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.isDefaultServiceForAid(mContext.getUser().getIdentifier(),
+                        service, aid);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Returns whether the user has allowed AIDs registered in the
+     * specified category to be handled by a service that is preferred
+     * by the foreground application, instead of by a pre-configured default.
+     *
+     * Foreground applications can set such preferences using the
+     * {@link #setPreferredService(Activity, ComponentName)} method.
+     *
+     * @param category The category, e.g. {@link #CATEGORY_PAYMENT}
+     * @return whether AIDs in the category can be handled by a service
+     *         specified by the foreground app.
+     */
+    @SuppressWarnings("NonUserGetterCalled")
+    public boolean categoryAllowsForegroundPreference(String category) {
+        if (CATEGORY_PAYMENT.equals(category)) {
+            boolean preferForeground = false;
+            Context contextAsUser = mContext.createContextAsUser(
+                    UserHandle.of(UserHandle.myUserId()), 0);
+            try {
+                preferForeground = Settings.Secure.getInt(
+                        contextAsUser.getContentResolver(),
+                        Constants.SETTINGS_SECURE_NFC_PAYMENT_FOREGROUND) != 0;
+            } catch (SettingNotFoundException e) {
+            }
+            return preferForeground;
+        } else {
+            // Allowed for all other categories
+            return true;
+        }
+    }
+
+    /**
+     * Returns the service selection mode for the passed in category.
+     * Valid return values are:
+     * <p>{@link #SELECTION_MODE_PREFER_DEFAULT} the user has requested a default
+     *    service for this category, which will be preferred.
+     * <p>{@link #SELECTION_MODE_ALWAYS_ASK} the user has requested to be asked
+     *    every time what service they would like to use in this category.
+     * <p>{@link #SELECTION_MODE_ASK_IF_CONFLICT} the user will only be asked
+     *    to pick a service if there is a conflict.
+     * @param category The category, for example {@link #CATEGORY_PAYMENT}
+     * @return the selection mode for the passed in category
+     */
+    public int getSelectionModeForCategory(String category) {
+        if (CATEGORY_PAYMENT.equals(category)) {
+            boolean paymentRegistered = false;
+            try {
+                paymentRegistered = sService.isDefaultPaymentRegistered();
+            } catch (RemoteException e) {
+                recoverService();
+                if (sService == null) {
+                    Log.e(TAG, "Failed to recover CardEmulationService.");
+                    return SELECTION_MODE_ALWAYS_ASK;
+                }
+                try {
+                    paymentRegistered = sService.isDefaultPaymentRegistered();
+                } catch (RemoteException ee) {
+                    Log.e(TAG, "Failed to reach CardEmulationService.");
+                    return SELECTION_MODE_ALWAYS_ASK;
+                }
+            }
+            if (paymentRegistered) {
+                return SELECTION_MODE_PREFER_DEFAULT;
+            } else {
+                return SELECTION_MODE_ALWAYS_ASK;
+            }
+        } else {
+            return SELECTION_MODE_ASK_IF_CONFLICT;
+        }
+    }
+    /**
+     * Sets whether the system should default to observe mode or not when the service is in the
+     * foreground or the default payment service. The default is to not enable observe mode when
+     * a service either the foreground default service or the default payment service so not
+     * calling this method will preserve that behavior.
+     *
+     * @param service The component name of the service
+     * @param enable Whether the servic should default to observe mode or not
+     * @return whether the change was successful.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
+    public boolean setServiceObserveModeDefault(@NonNull ComponentName service, boolean enable) {
+        try {
+            return sService.setServiceObserveModeDefault(mContext.getUser().getIdentifier(),
+                    service, enable);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to reach CardEmulationService.");
+        }
+        return  false;
+    }
+
+    /**
+     * Register a polling loop filter for a HostApduService.
+     * @param service The HostApduService to register the filter for.
+     * @param pollingLoopFilter The filter to register.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public boolean registerPollingLoopFilterForService(@NonNull ComponentName service,
+            @NonNull String pollingLoopFilter) {
+        try {
+            return sService.registerPollingLoopFilterForService(mContext.getUser().getIdentifier(),
+                    service, pollingLoopFilter);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.registerPollingLoopFilterForService(
+                        mContext.getUser().getIdentifier(), service, pollingLoopFilter);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Registers a list of AIDs for a specific category for the
+     * specified service.
+     *
+     * <p>If a list of AIDs for that category was previously
+     * registered for this service (either statically
+     * through the manifest, or dynamically by using this API),
+     * that list of AIDs will be replaced with this one.
+     *
+     * <p>Note that you can only register AIDs for a service that
+     * is running under the same UID as the caller of this API. Typically
+     * this means you need to call this from the same
+     * package as the service itself, though UIDs can also
+     * be shared between packages using shared UIDs.
+     *
+     * @param service The component name of the service
+     * @param category The category of AIDs to be registered
+     * @param aids A list containing the AIDs to be registered
+     * @return whether the registration was successful.
+     */
+    public boolean registerAidsForService(ComponentName service, String category,
+            List<String> aids) {
+        AidGroup aidGroup = new AidGroup(aids, category);
+        try {
+            return sService.registerAidGroupForService(mContext.getUser().getIdentifier(),
+                    service, aidGroup);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.registerAidGroupForService(mContext.getUser().getIdentifier(),
+                        service, aidGroup);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Unsets the off-host Secure Element for the given service.
+     *
+     * <p>Note that this will only remove Secure Element that was dynamically
+     * set using the {@link #setOffHostForService(ComponentName, String)}
+     * and resets it to a value that was statically assigned using manifest.
+     *
+     * <p>Note that you can only unset off-host SE for a service that
+     * is running under the same UID as the caller of this API. Typically
+     * this means you need to call this from the same
+     * package as the service itself, though UIDs can also
+     * be shared between packages using shared UIDs.
+     *
+     * @param service The component name of the service
+     * @return whether the registration was successful.
+     */
+    @RequiresPermission(android.Manifest.permission.NFC)
+    @NonNull
+    public boolean unsetOffHostForService(@NonNull ComponentName service) {
+        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
+        if (adapter == null) {
+            return false;
+        }
+
+        try {
+            return sService.unsetOffHostForService(mContext.getUser().getIdentifier(), service);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.unsetOffHostForService(mContext.getUser().getIdentifier(), service);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Sets the off-host Secure Element for the given service.
+     *
+     * <p>If off-host SE was initially set (either statically
+     * through the manifest, or dynamically by using this API),
+     * it will be replaced with this one. All AIDs registered by
+     * this service will be re-routed to this Secure Element if
+     * successful. AIDs that was statically assigned using manifest
+     * will re-route to off-host SE that stated in manifest after NFC
+     * toggle.
+     *
+     * <p>Note that you can only set off-host SE for a service that
+     * is running under the same UID as the caller of this API. Typically
+     * this means you need to call this from the same
+     * package as the service itself, though UIDs can also
+     * be shared between packages using shared UIDs.
+     *
+     * <p>Registeration will be successful only if the Secure Element
+     * exists on the device.
+     *
+     * @param service The component name of the service
+     * @param offHostSecureElement Secure Element to register the AID to. Only accept strings with
+     *                             prefix SIM or prefix eSE.
+     *                             Ref: GSMA TS.26 - NFC Handset Requirements
+     *                             TS26_NFC_REQ_069: For UICC, Secure Element Name SHALL be
+     *                                               SIM[smartcard slot]
+     *                                               (e.g. SIM/SIM1, SIM2… SIMn).
+     *                             TS26_NFC_REQ_070: For embedded SE, Secure Element Name SHALL be
+     *                                               eSE[number]
+     *                                               (e.g. eSE/eSE1, eSE2, etc.).
+     * @return whether the registration was successful.
+     */
+    @RequiresPermission(android.Manifest.permission.NFC)
+    @NonNull
+    public boolean setOffHostForService(@NonNull ComponentName service,
+            @NonNull String offHostSecureElement) {
+        boolean validSecureElement = false;
+
+        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
+        if (adapter == null || offHostSecureElement == null) {
+            return false;
+        }
+
+        List<String> validSE = adapter.getSupportedOffHostSecureElements();
+        if ((offHostSecureElement.startsWith("eSE") && !validSE.contains("eSE"))
+                || (offHostSecureElement.startsWith("SIM") && !validSE.contains("SIM"))) {
+            return false;
+        }
+
+        if (!offHostSecureElement.startsWith("eSE") && !offHostSecureElement.startsWith("SIM")) {
+            return false;
+        }
+
+        if (offHostSecureElement.equals("eSE")) {
+            offHostSecureElement = "eSE1";
+        } else if (offHostSecureElement.equals("SIM")) {
+            offHostSecureElement = "SIM1";
+        }
+
+        try {
+            return sService.setOffHostForService(mContext.getUser().getIdentifier(), service,
+                offHostSecureElement);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.setOffHostForService(mContext.getUser().getIdentifier(), service,
+                        offHostSecureElement);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Retrieves the currently registered AIDs for the specified
+     * category for a service.
+     *
+     * <p>Note that this will only return AIDs that were dynamically
+     * registered using {@link #registerAidsForService(ComponentName, String, List)}
+     * method. It will *not* return AIDs that were statically registered
+     * in the manifest.
+     *
+     * @param service The component name of the service
+     * @param category The category for which the AIDs were registered,
+     *                 e.g. {@link #CATEGORY_PAYMENT}
+     * @return The list of AIDs registered for this category, or null if it couldn't be found.
+     */
+    public List<String> getAidsForService(ComponentName service, String category) {
+        try {
+            AidGroup group =  sService.getAidGroupForService(mContext.getUser().getIdentifier(),
+                    service, category);
+            return (group != null ? group.getAids() : null);
+        } catch (RemoteException e) {
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return null;
+            }
+            try {
+                AidGroup group = sService.getAidGroupForService(mContext.getUser().getIdentifier(),
+                        service, category);
+                return (group != null ? group.getAids() : null);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Removes a previously registered list of AIDs for the specified category for the
+     * service provided.
+     *
+     * <p>Note that this will only remove AIDs that were dynamically
+     * registered using the {@link #registerAidsForService(ComponentName, String, List)}
+     * method. It will *not* remove AIDs that were statically registered in
+     * the manifest. If dynamically registered AIDs are removed using
+     * this method, and a statically registered AID group for the same category
+     * exists in the manifest, the static AID group will become active again.
+     *
+     * @param service The component name of the service
+     * @param category The category of the AIDs to be removed, e.g. {@link #CATEGORY_PAYMENT}
+     * @return whether the group was successfully removed.
+     */
+    public boolean removeAidsForService(ComponentName service, String category) {
+        try {
+            return sService.removeAidGroupForService(mContext.getUser().getIdentifier(), service,
+                    category);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.removeAidGroupForService(mContext.getUser().getIdentifier(),
+                        service, category);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Allows a foreground application to specify which card emulation service
+     * should be preferred while a specific Activity is in the foreground.
+     *
+     * <p>The specified Activity must currently be in resumed state. A good
+     * paradigm is to call this method in your {@link Activity#onResume}, and to call
+     * {@link #unsetPreferredService(Activity)} in your {@link Activity#onPause}.
+     *
+     * <p>This method call will fail in two specific scenarios:
+     * <ul>
+     * <li> If the service registers one or more AIDs in the {@link #CATEGORY_PAYMENT}
+     * category, but the user has indicated that foreground apps are not allowed
+     * to override the default payment service.
+     * <li> If the service registers one or more AIDs in the {@link #CATEGORY_OTHER}
+     * category that are also handled by the default payment service, and the
+     * user has indicated that foreground apps are not allowed to override the
+     * default payment service.
+     * </ul>
+     *
+     * <p> Use {@link #categoryAllowsForegroundPreference(String)} to determine
+     * whether foreground apps can override the default payment service.
+     *
+     * <p>Note that this preference is not persisted by the OS, and hence must be
+     * called every time the Activity is resumed.
+     *
+     * @param activity The activity which prefers this service to be invoked
+     * @param service The service to be preferred while this activity is in the foreground
+     * @return whether the registration was successful
+     */
+    public boolean setPreferredService(Activity activity, ComponentName service) {
+        // Verify the activity is in the foreground before calling into NfcService
+        if (activity == null || service == null) {
+            throw new NullPointerException("activity or service or category is null");
+        }
+        try {
+            return sService.setPreferredService(service);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.setPreferredService(service);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Unsets the preferred service for the specified Activity.
+     *
+     * <p>Note that the specified Activity must still be in resumed
+     * state at the time of this call. A good place to call this method
+     * is in your {@link Activity#onPause} implementation.
+     *
+     * @param activity The activity which the service was registered for
+     * @return true when successful
+     */
+    public boolean unsetPreferredService(Activity activity) {
+        if (activity == null) {
+            throw new NullPointerException("activity is null");
+        }
+        try {
+            return sService.unsetPreferredService();
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.unsetPreferredService();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Some devices may allow an application to register all
+     * AIDs that starts with a certain prefix, e.g.
+     * "A000000004*" to register all MasterCard AIDs.
+     *
+     * Use this method to determine whether this device
+     * supports registering AID prefixes.
+     *
+     * @return whether AID prefix registering is supported on this device.
+     */
+    public boolean supportsAidPrefixRegistration() {
+        try {
+            return sService.supportsAidPrefixRegistration();
+        } catch (RemoteException e) {
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.supportsAidPrefixRegistration();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Retrieves the registered AIDs for the preferred payment service.
+     *
+     * @return The list of AIDs registered for this category, or null if it couldn't be found.
+     */
+    @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+    @Nullable
+    public List<String> getAidsForPreferredPaymentService() {
+        try {
+            ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(
+                    mContext.getUser().getIdentifier());
+            return (serviceInfo != null ? serviceInfo.getAids() : null);
+        } catch (RemoteException e) {
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                throw e.rethrowFromSystemServer();
+            }
+            try {
+                ApduServiceInfo serviceInfo =
+                        sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
+                return (serviceInfo != null ? serviceInfo.getAids() : null);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Retrieves the route destination for the preferred payment service.
+     *
+     * @return The route destination secure element name of the preferred payment service.
+     *         HCE payment: "Host"
+     *         OffHost payment: 1. String with prefix SIM or prefix eSE string.
+     *                             Ref: GSMA TS.26 - NFC Handset Requirements
+     *                             TS26_NFC_REQ_069: For UICC, Secure Element Name SHALL be
+     *                                               SIM[smartcard slot]
+     *                                               (e.g. SIM/SIM1, SIM2… SIMn).
+     *                             TS26_NFC_REQ_070: For embedded SE, Secure Element Name SHALL be
+     *                                               eSE[number]
+     *                                               (e.g. eSE/eSE1, eSE2, etc.).
+     *                          2. "OffHost" if the payment service does not specify secure element
+     *                             name.
+     */
+    @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+    @Nullable
+    public String getRouteDestinationForPreferredPaymentService() {
+        try {
+            ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(
+                    mContext.getUser().getIdentifier());
+            if (serviceInfo != null) {
+                if (!serviceInfo.isOnHost()) {
+                    return serviceInfo.getOffHostSecureElement() == null ?
+                            "OffHost" : serviceInfo.getOffHostSecureElement();
+                }
+                return "Host";
+            }
+            return null;
+        } catch (RemoteException e) {
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                throw e.rethrowFromSystemServer();
+            }
+            try {
+                ApduServiceInfo serviceInfo =
+                        sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
+                if (serviceInfo != null) {
+                    if (!serviceInfo.isOnHost()) {
+                        return serviceInfo.getOffHostSecureElement() == null ?
+                                "Offhost" : serviceInfo.getOffHostSecureElement();
+                    }
+                    return "Host";
+                }
+                return null;
+
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Returns a user-visible description of the preferred payment service.
+     *
+     * @return the preferred payment service description
+     */
+    @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+    @Nullable
+    public CharSequence getDescriptionForPreferredPaymentService() {
+        try {
+            ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(
+                    mContext.getUser().getIdentifier());
+            return (serviceInfo != null ? serviceInfo.getDescription() : null);
+        } catch (RemoteException e) {
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                throw e.rethrowFromSystemServer();
+            }
+            try {
+                ApduServiceInfo serviceInfo =
+                        sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
+                return (serviceInfo != null ? serviceInfo.getDescription() : null);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public boolean setDefaultServiceForCategory(ComponentName service, String category) {
+        try {
+            return sService.setDefaultServiceForCategory(mContext.getUser().getIdentifier(),
+                    service, category);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.setDefaultServiceForCategory(mContext.getUser().getIdentifier(),
+                        service, category);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public boolean setDefaultForNextTap(ComponentName service) {
+        try {
+            return sService.setDefaultForNextTap(mContext.getUser().getIdentifier(), service);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.setDefaultForNextTap(mContext.getUser().getIdentifier(), service);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public boolean setDefaultForNextTap(int userId, ComponentName service) {
+        try {
+            return sService.setDefaultForNextTap(userId, service);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.setDefaultForNextTap(userId, service);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public List<ApduServiceInfo> getServices(String category) {
+        try {
+            return sService.getServices(mContext.getUser().getIdentifier(), category);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return null;
+            }
+            try {
+                return sService.getServices(mContext.getUser().getIdentifier(), category);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Retrieves list of services registered of the provided category for the provided user.
+     *
+     * @param category Category string, one of {@link #CATEGORY_PAYMENT} or {@link #CATEGORY_OTHER}
+     * @param userId the user handle of the user whose information is being requested.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public List<ApduServiceInfo> getServices(@NonNull String category, @UserIdInt int userId) {
+        try {
+            return sService.getServices(userId, category);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return null;
+            }
+            try {
+                return sService.getServices(userId, category);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Tests the validity of the polling loop filter.
+     * @param pollingLoopFilter The polling loop filter to test.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static boolean isValidPollingLoopFilter(@NonNull String pollingLoopFilter) {
+        // Verify hex characters
+        if (!PLF_PATTERN.matcher(pollingLoopFilter).matches()) {
+            Log.e(TAG, "Polling Loop Filter " + pollingLoopFilter
+                    + " is not a valid Polling Loop Filter.");
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * A valid AID according to ISO/IEC 7816-4:
+     * <ul>
+     * <li>Has >= 5 bytes and <=16 bytes (>=10 hex chars and <= 32 hex chars)
+     * <li>Consist of only hex characters
+     * <li>Additionally, we allow an asterisk at the end, to indicate
+     *     a prefix
+     * <li>Additinally we allow an (#) at symbol at the end, to indicate
+     *     a subset
+     * </ul>
+     *
+     * @hide
+     */
+    public static boolean isValidAid(String aid) {
+        if (aid == null)
+            return false;
+
+        // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
+        if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
+            Log.e(TAG, "AID " + aid + " is not a valid AID.");
+            return false;
+        }
+
+        // If not a prefix/subset AID, the total length must be even (even # of AID chars)
+        if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
+            Log.e(TAG, "AID " + aid + " is not a valid AID.");
+            return false;
+        }
+
+        // Verify hex characters
+        if (!AID_PATTERN.matcher(aid).matches()) {
+            Log.e(TAG, "AID " + aid + " is not a valid AID.");
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Allows to set or unset preferred service (category other) to avoid  AID Collision.
+     *
+     * @param service The ComponentName of the service
+     * @param status  true to enable, false to disable
+     * @return set service for the category and true if service is already set return false.
+     *
+     * @hide
+     */
+    public boolean setServiceEnabledForCategoryOther(ComponentName service, boolean status) {
+        if (service == null) {
+            throw new NullPointerException("activity or service or category is null");
+        }
+        int userId = mContext.getUser().getIdentifier();
+
+        try {
+            return sService.setServiceEnabledForCategoryOther(userId, service, status);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.setServiceEnabledForCategoryOther(userId, service, status);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+     /**
+      * Setting NFC controller routing table, which includes Protocol Route and Technology Route,
+      * while this Activity is in the foreground.
+      *
+      * The parameter set to null can be used to keep current values for that entry.
+      * <p>
+      * Example usage in an Activity that requires to set proto route to "ESE" and keep tech route:
+      * <pre>
+      * protected void onResume() {
+      *     mNfcAdapter.overrideRoutingTable(this , "ESE" , null);
+      * }</pre>
+      * </p>
+      * Also activities must call this method when it goes to the background,
+      * with all parameters set to null.
+      * @param activity The Activity that requests NFC controller routing table to be changed.
+      * @param protocol ISO-DEP route destination, which can be "DH" or "UICC" or "ESE".
+      * @param technology Tech-A, Tech-B route destination, which can be "DH" or "UICC" or "ESE".
+      * @return true if operation is successful and false otherwise
+      *
+      * This is a high risk API and only included to support mainline effort
+      * @hide
+      */
+    public boolean overrideRoutingTable(Activity activity, String protocol, String technology) {
+        if (activity == null) {
+            throw new NullPointerException("activity or service or category is null");
+        }
+        if (!activity.isResumed()) {
+            throw new IllegalArgumentException("Activity must be resumed.");
+        }
+        try {
+            return sService.overrideRoutingTable(UserHandle.myUserId(), protocol, technology);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.overrideRoutingTable(UserHandle.myUserId(), protocol, technology);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Restore the NFC controller routing table,
+     * which was changed by {@link #overrideRoutingTable(Activity, String, String)}
+     *
+     * @param activity The Activity that requested NFC controller routing table to be changed.
+     * @return true if operation is successful and false otherwise
+     *
+     * @hide
+     */
+    public boolean recoverRoutingTable(Activity activity) {
+        if (activity == null) {
+            throw new NullPointerException("activity is null");
+        }
+        if (!activity.isResumed()) {
+            throw new IllegalArgumentException("Activity must be resumed.");
+        }
+        try {
+            return sService.recoverRoutingTable(UserHandle.myUserId());
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.recoverRoutingTable(UserHandle.myUserId());
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    void recoverService() {
+        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
+        sService = adapter.getCardEmulationService();
+    }
+
+    /**
+     * Returns the value of {@link Settings.Secure#NFC_PAYMENT_DEFAULT_COMPONENT}.
+     *
+     * @param context A context
+     * @return A ComponentName for the setting value, or null.
+     *
+     * @hide
+     */
+    @SystemApi
+    @UserHandleAware
+    @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+    @SuppressWarnings("AndroidFrameworkClientSidePermissionCheck")
+    @FlaggedApi(android.permission.flags.Flags.FLAG_WALLET_ROLE_ENABLED)
+    @Nullable
+    public static ComponentName getPreferredPaymentService(@NonNull Context context) {
+        context.checkCallingOrSelfPermission(Manifest.permission.NFC_PREFERRED_PAYMENT_INFO);
+        String defaultPaymentComponent = Settings.Secure.getString(context.getContentResolver(),
+                Constants.SETTINGS_SECURE_NFC_PAYMENT_DEFAULT_COMPONENT);
+
+        if (defaultPaymentComponent == null) {
+            return null;
+        }
+
+        return ComponentName.unflattenFromString(defaultPaymentComponent);
+    }
+}
diff --git a/core/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/HostApduService.java
rename to nfc/java/android/nfc/cardemulation/HostApduService.java
diff --git a/core/java/android/nfc/cardemulation/HostNfcFService.java b/nfc/java/android/nfc/cardemulation/HostNfcFService.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/HostNfcFService.java
rename to nfc/java/android/nfc/cardemulation/HostNfcFService.java
diff --git a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java b/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/NfcFCardEmulation.java
rename to nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java
diff --git a/core/java/android/nfc/cardemulation/NfcFServiceInfo.aidl b/nfc/java/android/nfc/cardemulation/NfcFServiceInfo.aidl
similarity index 100%
rename from core/java/android/nfc/cardemulation/NfcFServiceInfo.aidl
rename to nfc/java/android/nfc/cardemulation/NfcFServiceInfo.aidl
diff --git a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java b/nfc/java/android/nfc/cardemulation/NfcFServiceInfo.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/NfcFServiceInfo.java
rename to nfc/java/android/nfc/cardemulation/NfcFServiceInfo.java
diff --git a/core/java/android/nfc/cardemulation/OWNERS b/nfc/java/android/nfc/cardemulation/OWNERS
similarity index 100%
rename from core/java/android/nfc/cardemulation/OWNERS
rename to nfc/java/android/nfc/cardemulation/OWNERS
diff --git a/core/java/android/nfc/cardemulation/OffHostApduService.java b/nfc/java/android/nfc/cardemulation/OffHostApduService.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/OffHostApduService.java
rename to nfc/java/android/nfc/cardemulation/OffHostApduService.java
diff --git a/core/java/android/nfc/cardemulation/Utils.java b/nfc/java/android/nfc/cardemulation/Utils.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/Utils.java
rename to nfc/java/android/nfc/cardemulation/Utils.java
diff --git a/core/java/android/nfc/dta/NfcDta.java b/nfc/java/android/nfc/dta/NfcDta.java
similarity index 100%
rename from core/java/android/nfc/dta/NfcDta.java
rename to nfc/java/android/nfc/dta/NfcDta.java
diff --git a/core/java/android/nfc/dta/OWNERS b/nfc/java/android/nfc/dta/OWNERS
similarity index 100%
rename from core/java/android/nfc/dta/OWNERS
rename to nfc/java/android/nfc/dta/OWNERS
diff --git a/core/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
similarity index 100%
rename from core/java/android/nfc/flags.aconfig
rename to nfc/java/android/nfc/flags.aconfig
diff --git a/core/java/android/nfc/package.html b/nfc/java/android/nfc/package.html
similarity index 100%
rename from core/java/android/nfc/package.html
rename to nfc/java/android/nfc/package.html
diff --git a/core/java/android/nfc/tech/BasicTagTechnology.java b/nfc/java/android/nfc/tech/BasicTagTechnology.java
similarity index 100%
rename from core/java/android/nfc/tech/BasicTagTechnology.java
rename to nfc/java/android/nfc/tech/BasicTagTechnology.java
diff --git a/core/java/android/nfc/tech/IsoDep.java b/nfc/java/android/nfc/tech/IsoDep.java
similarity index 100%
rename from core/java/android/nfc/tech/IsoDep.java
rename to nfc/java/android/nfc/tech/IsoDep.java
diff --git a/core/java/android/nfc/tech/MifareClassic.java b/nfc/java/android/nfc/tech/MifareClassic.java
similarity index 100%
rename from core/java/android/nfc/tech/MifareClassic.java
rename to nfc/java/android/nfc/tech/MifareClassic.java
diff --git a/core/java/android/nfc/tech/MifareUltralight.java b/nfc/java/android/nfc/tech/MifareUltralight.java
similarity index 100%
rename from core/java/android/nfc/tech/MifareUltralight.java
rename to nfc/java/android/nfc/tech/MifareUltralight.java
diff --git a/core/java/android/nfc/tech/Ndef.java b/nfc/java/android/nfc/tech/Ndef.java
similarity index 100%
rename from core/java/android/nfc/tech/Ndef.java
rename to nfc/java/android/nfc/tech/Ndef.java
diff --git a/core/java/android/nfc/tech/NdefFormatable.java b/nfc/java/android/nfc/tech/NdefFormatable.java
similarity index 100%
rename from core/java/android/nfc/tech/NdefFormatable.java
rename to nfc/java/android/nfc/tech/NdefFormatable.java
diff --git a/core/java/android/nfc/tech/NfcA.java b/nfc/java/android/nfc/tech/NfcA.java
similarity index 100%
rename from core/java/android/nfc/tech/NfcA.java
rename to nfc/java/android/nfc/tech/NfcA.java
diff --git a/core/java/android/nfc/tech/NfcB.java b/nfc/java/android/nfc/tech/NfcB.java
similarity index 100%
rename from core/java/android/nfc/tech/NfcB.java
rename to nfc/java/android/nfc/tech/NfcB.java
diff --git a/core/java/android/nfc/tech/NfcBarcode.java b/nfc/java/android/nfc/tech/NfcBarcode.java
similarity index 100%
rename from core/java/android/nfc/tech/NfcBarcode.java
rename to nfc/java/android/nfc/tech/NfcBarcode.java
diff --git a/core/java/android/nfc/tech/NfcF.java b/nfc/java/android/nfc/tech/NfcF.java
similarity index 100%
rename from core/java/android/nfc/tech/NfcF.java
rename to nfc/java/android/nfc/tech/NfcF.java
diff --git a/core/java/android/nfc/tech/NfcV.java b/nfc/java/android/nfc/tech/NfcV.java
similarity index 100%
rename from core/java/android/nfc/tech/NfcV.java
rename to nfc/java/android/nfc/tech/NfcV.java
diff --git a/core/java/android/nfc/tech/OWNERS b/nfc/java/android/nfc/tech/OWNERS
similarity index 100%
rename from core/java/android/nfc/tech/OWNERS
rename to nfc/java/android/nfc/tech/OWNERS
diff --git a/core/java/android/nfc/tech/TagTechnology.java b/nfc/java/android/nfc/tech/TagTechnology.java
similarity index 100%
rename from core/java/android/nfc/tech/TagTechnology.java
rename to nfc/java/android/nfc/tech/TagTechnology.java
diff --git a/core/java/android/nfc/tech/package.html b/nfc/java/android/nfc/tech/package.html
similarity index 100%
rename from core/java/android/nfc/tech/package.html
rename to nfc/java/android/nfc/tech/package.html
diff --git a/nfc/lint-baseline.xml b/nfc/lint-baseline.xml
new file mode 100644
index 0000000..1dfdd29
--- /dev/null
+++ b/nfc/lint-baseline.xml
@@ -0,0 +1,213 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `new android.nfc.cardemulation.AidGroup`"
+        errorLine1="        AidGroup aidGroup = new AidGroup(aids, category);"
+        errorLine2="                            ~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="377"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#getAids`"
+        errorLine1="            return (group != null ? group.getAids() : null);"
+        errorLine2="                                          ~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="537"
+            column="43"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#getAids`"
+        errorLine1="                return (group != null ? group.getAids() : null);"
+        errorLine2="                                              ~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="547"
+            column="47"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getAids`"
+        errorLine1="            return (serviceInfo != null ? serviceInfo.getAids() : null);"
+        errorLine2="                                                      ~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="714"
+            column="55"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getAids`"
+        errorLine1="                return (serviceInfo != null ? serviceInfo.getAids() : null);"
+        errorLine2="                                                          ~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="724"
+            column="59"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`"
+        errorLine1="                if (!serviceInfo.isOnHost()) {"
+        errorLine2="                                 ~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="755"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
+        errorLine1="                    return serviceInfo.getOffHostSecureElement() == null ?"
+        errorLine2="                                       ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="756"
+            column="40"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
+        errorLine1='                            "OffHost" : serviceInfo.getOffHostSecureElement();'
+        errorLine2="                                                    ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="757"
+            column="53"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`"
+        errorLine1="                    if (!serviceInfo.isOnHost()) {"
+        errorLine2="                                     ~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="772"
+            column="38"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
+        errorLine1="                        return serviceInfo.getOffHostSecureElement() == null ?"
+        errorLine2="                                           ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="773"
+            column="44"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
+        errorLine1='                                "Offhost" : serviceInfo.getOffHostSecureElement();'
+        errorLine2="                                                        ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="774"
+            column="57"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getDescription`"
+        errorLine1="            return (serviceInfo != null ? serviceInfo.getDescription() : null);"
+        errorLine2="                                                      ~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="798"
+            column="55"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getDescription`"
+        errorLine1="                return (serviceInfo != null ? serviceInfo.getDescription() : null);"
+        errorLine2="                                                          ~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="808"
+            column="59"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+        errorLine1="        if (!activity.isResumed()) {"
+        errorLine2="                      ~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="1032"
+            column="23"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+        errorLine1="        if (!activity.isResumed()) {"
+        errorLine2="                      ~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+            line="1066"
+            column="23"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+        errorLine1="            resumed = activity.isResumed();"
+        errorLine2="                               ~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/NfcActivityManager.java"
+            line="124"
+            column="32"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+        errorLine1="        if (!activity.isResumed()) {"
+        errorLine2="                      ~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/NfcAdapter.java"
+            line="2457"
+            column="23"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+        errorLine1="        if (!activity.isResumed()) {"
+        errorLine2="                      ~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java"
+            line="315"
+            column="23"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+        errorLine1="        if (!activity.isResumed()) {"
+        errorLine2="                      ~~~~~~~~~">
+        <location
+            file="frameworks/base/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java"
+            line="351"
+            column="23"/>
+    </issue>
+
+</issues>
\ No newline at end of file
diff --git a/nfc/tests/Android.bp b/nfc/tests/Android.bp
new file mode 100644
index 0000000..62566ee
--- /dev/null
+++ b/nfc/tests/Android.bp
@@ -0,0 +1,40 @@
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "NfcManagerTests",
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "mockito-target-minus-junit4",
+        "truth",
+    ],
+    libs: [
+        "framework-nfc.impl",
+        "android.test.runner",
+    ],
+    srcs: ["src/**/*.java"],
+    platform_apis: true,
+    certificate: "platform",
+    test_suites: ["device-tests"],
+}
diff --git a/core/tests/nfctests/AndroidManifest.xml b/nfc/tests/AndroidManifest.xml
similarity index 100%
rename from core/tests/nfctests/AndroidManifest.xml
rename to nfc/tests/AndroidManifest.xml
diff --git a/core/tests/nfctests/AndroidTest.xml b/nfc/tests/AndroidTest.xml
similarity index 100%
rename from core/tests/nfctests/AndroidTest.xml
rename to nfc/tests/AndroidTest.xml
diff --git a/core/tests/nfctests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java b/nfc/tests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java
similarity index 100%
rename from core/tests/nfctests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java
rename to nfc/tests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java
diff --git a/core/tests/nfctests/src/android/nfc/TechListParcelTest.java b/nfc/tests/src/android/nfc/TechListParcelTest.java
similarity index 100%
rename from core/tests/nfctests/src/android/nfc/TechListParcelTest.java
rename to nfc/tests/src/android/nfc/TechListParcelTest.java
diff --git a/packages/CrashRecovery/aconfig/flags.aconfig b/packages/CrashRecovery/aconfig/flags.aconfig
new file mode 100644
index 0000000..5636266
--- /dev/null
+++ b/packages/CrashRecovery/aconfig/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.crashrecovery.flags"
+
+flag {
+    name: "recoverability_detection"
+    namespace: "package_watchdog"
+    description: "Feature flag for recoverability detection"
+    bug: "310236690"
+    is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp
index 991fe41..c292b502 100644
--- a/packages/CredentialManager/Android.bp
+++ b/packages/CredentialManager/Android.bp
@@ -7,19 +7,14 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-android_app {
-    name: "CredentialManager",
-    defaults: ["platform_app_defaults"],
-    certificate: "platform",
+android_library {
+    name: "CredentialManager-handheld",
+
+    platform_apis: true,
+
     srcs: ["src/**/*.kt"],
     resource_dirs: ["res"],
 
-    dex_preopt: {
-        profile_guided: true,
-        //TODO: b/312357299 - Update baseline profile
-        profile: "profile.txt.prof",
-    },
-
     static_libs: [
         "CredentialManagerShared",
         "PlatformComposeCore",
@@ -42,6 +37,23 @@
         "androidx.recyclerview_recyclerview",
         "kotlinx-coroutines-core",
     ],
+}
+
+android_app {
+    name: "CredentialManager",
+    defaults: ["platform_app_defaults"],
+    certificate: "platform",
+
+    dex_preopt: {
+        profile_guided: true,
+        //TODO: b/312357299 - Update baseline profile
+        profile: "profile.txt.prof",
+    },
+
+    // Do not add new dependencies here. Add to CredentialManager-handheld instead.
+    static_libs: [
+        "CredentialManager-handheld",
+    ],
 
     platform_apis: true,
     privileged: true,
diff --git a/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml b/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml
deleted file mode 100644
index 9d16f32d..0000000
--- a/packages/CredentialManager/res/drawable/autofill_light_selectable_item_background.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-  -->
-
-<!-- Copied from //frameworks/base/core/res/res/drawable/item_background_material.xml -->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        android:color="@color/autofill_light_colorControlHighlight">
-    <item android:id="@android:id/mask">
-        <color android:color="@android:color/white"/>
-    </item>
-</ripple>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
index 2f0c83b..f13402c 100644
--- a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
+++ b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2023 The Android Open Source Project
+  ~ Copyright (C) 2024 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -23,8 +23,8 @@
         android:shape="rectangle"
         android:top="1dp">
         <shape>
-            <corners android:radius="28dp" />
-            <solid android:color="@android:color/system_surface_container_high_light" />
+            <corners android:radius="4dp" />
+            <solid android:color="@color/dropdown_container" />
         </shape>
     </item>
 </ripple>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/more_options_list_item.xml b/packages/CredentialManager/res/drawable/more_options_list_item.xml
new file mode 100644
index 0000000..d7b509e
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/more_options_list_item.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi"
+        android:color="@android:color/transparent">
+    <item
+        android:bottom="1dp"
+        android:shape="rectangle"
+        android:top="1dp">
+        <shape>
+            <corners android:bottomLeftRadius="4dp"
+                     android:bottomRightRadius="4dp"/>
+            <solid android:color="@color/sign_in_options_container" />
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml b/packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml
deleted file mode 100644
index e4e9f7a..0000000
--- a/packages/CredentialManager/res/layout/autofill_dataset_left_with_item_tag_hint.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-                android:id="@android:id/content"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                style="@style/autofill.Dataset">
-    <ImageView
-        android:id="@android:id/icon1"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_centerVertical="true"
-        android:layout_alignParentStart="true"
-        android:background="@null"/>
-    <TextView
-        android:id="@android:id/text1"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentTop="true"
-        android:layout_toEndOf="@android:id/icon1"
-        style="@style/autofill.TextAppearance"/>
-
-</RelativeLayout>
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
new file mode 100644
index 0000000..929756c
--- /dev/null
+++ b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
@@ -0,0 +1,42 @@
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:id="@android:id/content"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
+                android:elevation="3dp">
+
+    <ImageView
+        android:id="@android:id/icon1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_alignParentStart="true"
+        android:contentDescription="@string/provider_icon_content_description"
+        android:background="@null"/>
+    <TextView
+        android:id="@android:id/text1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_toEndOf="@android:id/icon1"
+        android:minWidth="@dimen/autofill_dropdown_textview_min_width"
+        android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
+        style="@style/autofill.TextTitle"/>
+
+</RelativeLayout>
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
new file mode 100644
index 0000000..1fe5e0e
--- /dev/null
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -0,0 +1,50 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:id="@android:id/content"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
+                android:elevation="3dp">
+
+        <ImageView
+            android:id="@android:id/icon1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:contentDescription="@string/provider_icon_content_description"
+            android:layout_centerVertical="true"
+            android:layout_alignParentStart="true"
+            android:background="@null"/>
+        <TextView
+            android:id="@android:id/text1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            android:layout_toEndOf="@android:id/icon1"
+            android:minWidth="@dimen/autofill_dropdown_textview_min_width"
+            android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
+            style="@style/autofill.TextTitle"/>
+        <TextView
+            android:id="@android:id/text2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@android:id/text1"
+            android:layout_toEndOf="@android:id/icon1"
+            android:minWidth="@dimen/autofill_dropdown_textview_min_width"
+            android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
+            style="@style/autofill.TextSubtitle"/>
+
+</RelativeLayout>
diff --git a/packages/CredentialManager/res/values-af/strings.xml b/packages/CredentialManager/res/values-af/strings.xml
index a2d2a96..3614240 100644
--- a/packages/CredentialManager/res/values-af/strings.xml
+++ b/packages/CredentialManager/res/values-af/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Van ’n ander toestel af"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Gebruik ’n ander toestel"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Versoek is deur <xliff:g id="APP_NAME">%1$s</xliff:g> gekanselleer"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-am/strings.xml b/packages/CredentialManager/res/values-am/strings.xml
index 475dcf7..e5759fa 100644
--- a/packages/CredentialManager/res/values-am/strings.xml
+++ b/packages/CredentialManager/res/values-am/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ከሌላ መሣሪያ"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"የተለየ መሣሪያ ይጠቀሙ"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"ጥያቄ በ<xliff:g id="APP_NAME">%1$s</xliff:g> ተሰርዟል"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-ar/strings.xml b/packages/CredentialManager/res/values-ar/strings.xml
index 3f85b58..fdd9043 100644
--- a/packages/CredentialManager/res/values-ar/strings.xml
+++ b/packages/CredentialManager/res/values-ar/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"من جهاز آخر"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"استخدام جهاز مختلف"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"تم إلغاء الطلب بواسطة \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"."</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-as/strings.xml b/packages/CredentialManager/res/values-as/strings.xml
index e14b34b..005079e 100644
--- a/packages/CredentialManager/res/values-as/strings.xml
+++ b/packages/CredentialManager/res/values-as/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"অন্য এটা ডিভাইচৰ পৰা"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"অন্য এটা ডিভাইচ ব্যৱহাৰ কৰক"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ অনুৰোধটো বাতিল কৰিছে"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-az/strings.xml b/packages/CredentialManager/res/values-az/strings.xml
index d0f8bb0..9937942 100644
--- a/packages/CredentialManager/res/values-az/strings.xml
+++ b/packages/CredentialManager/res/values-az/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Başqa cihazdan"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Başqa cihaz istifadə edin"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> sorğunu ləğv etdi"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-b+sr+Latn/strings.xml b/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
index 780274c..171e841 100644
--- a/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
+++ b/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Sa drugog uređaja"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Koristi drugi uređaj"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Zahtve je otkazala aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-be/strings.xml b/packages/CredentialManager/res/values-be/strings.xml
index 144bb86..323b683 100644
--- a/packages/CredentialManager/res/values-be/strings.xml
+++ b/packages/CredentialManager/res/values-be/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"З іншай прылады"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Скарыстаць іншую прыладу"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Запыт скасаваны праграмай \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-bg/strings.xml b/packages/CredentialManager/res/values-bg/strings.xml
index ef4dd54c..1efec45 100644
--- a/packages/CredentialManager/res/values-bg/strings.xml
+++ b/packages/CredentialManager/res/values-bg/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"От друго устройство"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Използване на друго устройство"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Заявката е анулирана от <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-bn/strings.xml b/packages/CredentialManager/res/values-bn/strings.xml
index 14f4a9b..3fcc91a 100644
--- a/packages/CredentialManager/res/values-bn/strings.xml
+++ b/packages/CredentialManager/res/values-bn/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"অন্য ডিভাইস থেকে"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"আলাদা ডিভাইস ব্যবহার করুন"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> দ্বারা অনুরোধ বাতিল করা হয়েছে"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-bs/strings.xml b/packages/CredentialManager/res/values-bs/strings.xml
index 6315ea8..6a3d25e 100644
--- a/packages/CredentialManager/res/values-bs/strings.xml
+++ b/packages/CredentialManager/res/values-bs/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"S drugog uređaja"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Upotrijebite drugi uređaj"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> je otkazala zahtjev"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-ca/strings.xml b/packages/CredentialManager/res/values-ca/strings.xml
index 8e3fda8..d491b70 100644
--- a/packages/CredentialManager/res/values-ca/strings.xml
+++ b/packages/CredentialManager/res/values-ca/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Des d\'un altre dispositiu"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Utilitza un dispositiu diferent"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> ha cancel·lat la sol·licitud"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-cs/strings.xml b/packages/CredentialManager/res/values-cs/strings.xml
index 9fe5a49..133f3f8 100644
--- a/packages/CredentialManager/res/values-cs/strings.xml
+++ b/packages/CredentialManager/res/values-cs/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Z jiného zařízení"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Použít jiné zařízení"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> žádost zrušila"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-da/strings.xml b/packages/CredentialManager/res/values-da/strings.xml
index c7ee038..93de75f 100644
--- a/packages/CredentialManager/res/values-da/strings.xml
+++ b/packages/CredentialManager/res/values-da/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Fra en anden enhed"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Brug en anden enhed"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Anmodningen blev annulleret af <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-de/strings.xml b/packages/CredentialManager/res/values-de/strings.xml
index 29d9a86b..d3648a1 100644
--- a/packages/CredentialManager/res/values-de/strings.xml
+++ b/packages/CredentialManager/res/values-de/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Von einem anderen Gerät"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Anderes Gerät verwenden"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Anfrage abgebrochen von <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-el/strings.xml b/packages/CredentialManager/res/values-el/strings.xml
index 089f898..da3ae13 100644
--- a/packages/CredentialManager/res/values-el/strings.xml
+++ b/packages/CredentialManager/res/values-el/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Από άλλη συσκευή"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Χρήση διαφορετικής συσκευής"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Το αίτημα ακυρώθηκε από την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-en-rAU/strings.xml b/packages/CredentialManager/res/values-en-rAU/strings.xml
index 7b80db0..056b8ff 100644
--- a/packages/CredentialManager/res/values-en-rAU/strings.xml
+++ b/packages/CredentialManager/res/values-en-rAU/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"From another device"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use a different device"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Request cancelled by <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-en-rCA/strings.xml b/packages/CredentialManager/res/values-en-rCA/strings.xml
index b173616..e23711f 100644
--- a/packages/CredentialManager/res/values-en-rCA/strings.xml
+++ b/packages/CredentialManager/res/values-en-rCA/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"From another device"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use a different device"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Request cancelled by <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-en-rGB/strings.xml b/packages/CredentialManager/res/values-en-rGB/strings.xml
index 7b80db0..056b8ff 100644
--- a/packages/CredentialManager/res/values-en-rGB/strings.xml
+++ b/packages/CredentialManager/res/values-en-rGB/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"From another device"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use a different device"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Request cancelled by <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-en-rIN/strings.xml b/packages/CredentialManager/res/values-en-rIN/strings.xml
index 7b80db0..056b8ff 100644
--- a/packages/CredentialManager/res/values-en-rIN/strings.xml
+++ b/packages/CredentialManager/res/values-en-rIN/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"From another device"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use a different device"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Request cancelled by <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-en-rXC/strings.xml b/packages/CredentialManager/res/values-en-rXC/strings.xml
index c3eeb04..0db7324 100644
--- a/packages/CredentialManager/res/values-en-rXC/strings.xml
+++ b/packages/CredentialManager/res/values-en-rXC/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‎‎‏‏‏‏‎‎‎‎‏‏‎‎‎‎‎‏‎‏‏‎‏‏‎‏‏‎‎‏‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‎‎‎‎From another device‎‏‎‎‏‎"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‏‏‎‏‏‏‎‎‎‏‏‏‏‎‏‎‎‏‏‏‏‎‎‎‏‎‏‏‏‎‏‏‎‏‎‏‎‏‎‎‏‎‏‏‏‏‎‏‎Use a different device‎‏‎‎‏‎"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‏‎‏‏‎‎‎‏‎‏‏‎‎‏‏‏‎‏‎‏‎‏‎‎‎‏‏‏‏‎‎‏‎‏‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‎‎Request cancelled by ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-es-rUS/strings.xml b/packages/CredentialManager/res/values-es-rUS/strings.xml
index f6a5dcb..818d715 100644
--- a/packages/CredentialManager/res/values-es-rUS/strings.xml
+++ b/packages/CredentialManager/res/values-es-rUS/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Desde otro dispositivo"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar otra voz"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> canceló la solicitud"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-es/strings.xml b/packages/CredentialManager/res/values-es/strings.xml
index fb0cbf9..f9776fb 100644
--- a/packages/CredentialManager/res/values-es/strings.xml
+++ b/packages/CredentialManager/res/values-es/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"De otro dispositivo"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar otro dispositivo"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> ha cancelado la solicitud"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-et/strings.xml b/packages/CredentialManager/res/values-et/strings.xml
index 97dbe4d..4870e70 100644
--- a/packages/CredentialManager/res/values-et/strings.xml
+++ b/packages/CredentialManager/res/values-et/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Muus seadmes"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Kasuta teist seadet"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Rakendus <xliff:g id="APP_NAME">%1$s</xliff:g> tühistas taotluse"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-eu/strings.xml b/packages/CredentialManager/res/values-eu/strings.xml
index 8316283..21a66f9 100644
--- a/packages/CredentialManager/res/values-eu/strings.xml
+++ b/packages/CredentialManager/res/values-eu/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Beste gailu batean gordetakoak"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Erabili beste gailu bat"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Utzi du bertan behera eskaera <xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-fa/strings.xml b/packages/CredentialManager/res/values-fa/strings.xml
index a6e0d3d..47385cd 100644
--- a/packages/CredentialManager/res/values-fa/strings.xml
+++ b/packages/CredentialManager/res/values-fa/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"از دستگاهی دیگر"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"استفاده از دستگاه دیگری"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"درخواست را <xliff:g id="APP_NAME">%1$s</xliff:g> لغو کرد"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-fi/strings.xml b/packages/CredentialManager/res/values-fi/strings.xml
index fff45c9..d463ea1 100644
--- a/packages/CredentialManager/res/values-fi/strings.xml
+++ b/packages/CredentialManager/res/values-fi/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Toiselta laitteelta"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Käytä toista laitetta"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> hylkäsi pyynnön"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-fr-rCA/strings.xml b/packages/CredentialManager/res/values-fr-rCA/strings.xml
index 155be6f..3596604 100644
--- a/packages/CredentialManager/res/values-fr-rCA/strings.xml
+++ b/packages/CredentialManager/res/values-fr-rCA/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"À partir d\'un autre appareil"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Utiliser un autre appareil"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Demande annulée par <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-fr/strings.xml b/packages/CredentialManager/res/values-fr/strings.xml
index e042815..fe545b3 100644
--- a/packages/CredentialManager/res/values-fr/strings.xml
+++ b/packages/CredentialManager/res/values-fr/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Depuis un autre appareil"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Utiliser un autre appareil"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Requête annulée par <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-gl/strings.xml b/packages/CredentialManager/res/values-gl/strings.xml
index 79e0b5e..6341ed89 100644
--- a/packages/CredentialManager/res/values-gl/strings.xml
+++ b/packages/CredentialManager/res/values-gl/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Doutro dispositivo"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar outro dispositivo"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> cancelou a solicitude"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-gu/strings.xml b/packages/CredentialManager/res/values-gu/strings.xml
index c3c5b62..19930df 100644
--- a/packages/CredentialManager/res/values-gu/strings.xml
+++ b/packages/CredentialManager/res/values-gu/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"કોઈ અન્ય ડિવાઇસમાંથી"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"કોઈ અન્ય ડિવાઇસનો ઉપયોગ કરો"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> દ્વારા વિનંતી રદ કરવામાં આવી"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-hi/strings.xml b/packages/CredentialManager/res/values-hi/strings.xml
index 05d21e0..8505b19 100644
--- a/packages/CredentialManager/res/values-hi/strings.xml
+++ b/packages/CredentialManager/res/values-hi/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"किसी दूसरे डिवाइस से"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"दूसरे डिवाइस का इस्तेमाल करें"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> की ओर से अनुरोध रद्द किया गया"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-hr/strings.xml b/packages/CredentialManager/res/values-hr/strings.xml
index 4425e24..3ea8847 100644
--- a/packages/CredentialManager/res/values-hr/strings.xml
+++ b/packages/CredentialManager/res/values-hr/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Na drugom uređaju"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Upotrijebite drugi uređaj"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Zahtjev je otkazala aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-hu/strings.xml b/packages/CredentialManager/res/values-hu/strings.xml
index c25fa99..620e976 100644
--- a/packages/CredentialManager/res/values-hu/strings.xml
+++ b/packages/CredentialManager/res/values-hu/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Másik eszközről"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Másik eszköz használata"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"A kérelmet törölte a(z) <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-hy/strings.xml b/packages/CredentialManager/res/values-hy/strings.xml
index a6bda50..5423efe 100644
--- a/packages/CredentialManager/res/values-hy/strings.xml
+++ b/packages/CredentialManager/res/values-hy/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Մեկ այլ սարքից"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Օգտագործել այլ սարք"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Հարցումը չեղարկվել է <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի կողմից"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-in/strings.xml b/packages/CredentialManager/res/values-in/strings.xml
index d6bf946..df9d32d 100644
--- a/packages/CredentialManager/res/values-in/strings.xml
+++ b/packages/CredentialManager/res/values-in/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Dari perangkat lain"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Gunakan perangkat lain"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Permintaan dibatalkan oleh <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-is/strings.xml b/packages/CredentialManager/res/values-is/strings.xml
index 76a869f..ba836f9 100644
--- a/packages/CredentialManager/res/values-is/strings.xml
+++ b/packages/CredentialManager/res/values-is/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Úr öðru tæki"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Nota annað tæki"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> hætti við beiðnina"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-it/strings.xml b/packages/CredentialManager/res/values-it/strings.xml
index c5fd89c..d16d11f 100644
--- a/packages/CredentialManager/res/values-it/strings.xml
+++ b/packages/CredentialManager/res/values-it/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Da un altro dispositivo"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usa un dispositivo diverso"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Richiesta annullata da <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-iw/strings.xml b/packages/CredentialManager/res/values-iw/strings.xml
index 0643568..a9b01e1 100644
--- a/packages/CredentialManager/res/values-iw/strings.xml
+++ b/packages/CredentialManager/res/values-iw/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ממכשיר אחר"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"צריך להשתמש במכשיר אחר"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> ביטלה את הבקשה"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-ja/strings.xml b/packages/CredentialManager/res/values-ja/strings.xml
index afbff90..b452ec3 100644
--- a/packages/CredentialManager/res/values-ja/strings.xml
+++ b/packages/CredentialManager/res/values-ja/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"別のデバイスを使う"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"別のデバイスを使用"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> がリクエストをキャンセルしました"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-ka/strings.xml b/packages/CredentialManager/res/values-ka/strings.xml
index 8125ec6..30479335 100644
--- a/packages/CredentialManager/res/values-ka/strings.xml
+++ b/packages/CredentialManager/res/values-ka/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"სხვა მოწყობილობიდან"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"გამოიყენეთ სხვა მოწყობილობა"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"თხოვნა გაუქმებულია <xliff:g id="APP_NAME">%1$s</xliff:g>-ის მიერ"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-kk/strings.xml b/packages/CredentialManager/res/values-kk/strings.xml
index cb68444..891a600 100644
--- a/packages/CredentialManager/res/values-kk/strings.xml
+++ b/packages/CredentialManager/res/values-kk/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Басқа құрылғыдан жасау"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Басқа құрылғыны пайдалану"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы сұрауды тоқтатты."</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-km/strings.xml b/packages/CredentialManager/res/values-km/strings.xml
index d361ad9..d6fc505 100644
--- a/packages/CredentialManager/res/values-km/strings.xml
+++ b/packages/CredentialManager/res/values-km/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ពីឧបករណ៍ផ្សេងទៀត"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ប្រើឧបករណ៍ផ្សេង"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"បានបោះបង់សំណើដោយ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-kn/strings.xml b/packages/CredentialManager/res/values-kn/strings.xml
index 7447ab6..ff38e19 100644
--- a/packages/CredentialManager/res/values-kn/strings.xml
+++ b/packages/CredentialManager/res/values-kn/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ಮತ್ತೊಂದು ಸಾಧನದಿಂದ"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ಬೇರೆ ಸಾಧನವನ್ನು ಬಳಸಿ"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> ನಿಂದ ವಿನಂತಿಯನ್ನು ರದ್ದುಗೊಳಿಸಲಾಗಿದೆ"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-ko/strings.xml b/packages/CredentialManager/res/values-ko/strings.xml
index 07a7fbc..557c3ef 100644
--- a/packages/CredentialManager/res/values-ko/strings.xml
+++ b/packages/CredentialManager/res/values-ko/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"다른 기기에서"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"다른 기기 사용"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g>에 의해 요청이 취소됨"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-ky/strings.xml b/packages/CredentialManager/res/values-ky/strings.xml
index e2de2ef..4bc96b1d 100644
--- a/packages/CredentialManager/res/values-ky/strings.xml
+++ b/packages/CredentialManager/res/values-ky/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Башка түзмөктөн"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Башка түзмөктү колдонуу"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Сурамды <xliff:g id="APP_NAME">%1$s</xliff:g> жокко чыгарды"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-lo/strings.xml b/packages/CredentialManager/res/values-lo/strings.xml
index 3b2e2aa..ce103e0 100644
--- a/packages/CredentialManager/res/values-lo/strings.xml
+++ b/packages/CredentialManager/res/values-lo/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ຈາກອຸປະກອນອື່ນ"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ໃຊ້ອຸປະກອນອື່ນ"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"ການຮ້ອງຂໍຖືກຍົກເລີກໂດຍ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-lt/strings.xml b/packages/CredentialManager/res/values-lt/strings.xml
index c3b941b..af27824 100644
--- a/packages/CredentialManager/res/values-lt/strings.xml
+++ b/packages/CredentialManager/res/values-lt/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Naudojant kitą įrenginį"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Naudoti kitą įrenginį"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Užklausą atšaukė „<xliff:g id="APP_NAME">%1$s</xliff:g>“"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-lv/strings.xml b/packages/CredentialManager/res/values-lv/strings.xml
index 27115ca..532ac5e 100644
--- a/packages/CredentialManager/res/values-lv/strings.xml
+++ b/packages/CredentialManager/res/values-lv/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"No citas ierīces"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Izmantot citu ierīci"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> atcēla pieprasījumu"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-mk/strings.xml b/packages/CredentialManager/res/values-mk/strings.xml
index 1f456bf..ec9ebef 100644
--- a/packages/CredentialManager/res/values-mk/strings.xml
+++ b/packages/CredentialManager/res/values-mk/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Од друг уред"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Употребете друг уред"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Барањето е откажано од <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-ml/strings.xml b/packages/CredentialManager/res/values-ml/strings.xml
index 07fea38..16ef8c8 100644
--- a/packages/CredentialManager/res/values-ml/strings.xml
+++ b/packages/CredentialManager/res/values-ml/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"മറ്റൊരു ഉപകരണത്തിൽ നിന്ന്"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"മറ്റൊരു ഉപകരണം ഉപയോഗിക്കുക"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"അഭ്യർത്ഥന <xliff:g id="APP_NAME">%1$s</xliff:g> റദ്ദാക്കി"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-mn/strings.xml b/packages/CredentialManager/res/values-mn/strings.xml
index e37155a..4a5a8eea 100644
--- a/packages/CredentialManager/res/values-mn/strings.xml
+++ b/packages/CredentialManager/res/values-mn/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Өөр төхөөрөмжөөс"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Өөр төхөөрөмж ашиглах"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Хүсэлтийг <xliff:g id="APP_NAME">%1$s</xliff:g> цуцалсан"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-mr/strings.xml b/packages/CredentialManager/res/values-mr/strings.xml
index ceba101..6a76b02 100644
--- a/packages/CredentialManager/res/values-mr/strings.xml
+++ b/packages/CredentialManager/res/values-mr/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"दुसऱ्या डिव्हाइस वरून"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"वेगळे डिव्हाइस वापरा"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> ने विनंती रद्द केली आहे"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-ms/strings.xml b/packages/CredentialManager/res/values-ms/strings.xml
index b933f3e..f759eed 100644
--- a/packages/CredentialManager/res/values-ms/strings.xml
+++ b/packages/CredentialManager/res/values-ms/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Daripada peranti lain"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Gunakan peranti yang lain"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Permintaan dibatalkan oleh <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-my/strings.xml b/packages/CredentialManager/res/values-my/strings.xml
index c359ce1..9c15226 100644
--- a/packages/CredentialManager/res/values-my/strings.xml
+++ b/packages/CredentialManager/res/values-my/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"စက်နောက်တစ်ခုမှ"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"အခြားစက်သုံးရန်"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"တောင်းဆိုချက်ကို <xliff:g id="APP_NAME">%1$s</xliff:g> က ပယ်ဖျက်လိုက်သည်"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-nb/strings.xml b/packages/CredentialManager/res/values-nb/strings.xml
index 7f63f10..327bc7f 100644
--- a/packages/CredentialManager/res/values-nb/strings.xml
+++ b/packages/CredentialManager/res/values-nb/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Fra en annen enhet"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Bruk en annen enhet"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Forespørselen er kansellert av <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-ne/strings.xml b/packages/CredentialManager/res/values-ne/strings.xml
index 042ed62..044853a 100644
--- a/packages/CredentialManager/res/values-ne/strings.xml
+++ b/packages/CredentialManager/res/values-ne/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"अर्को डिभाइसका लागि"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"अर्कै डिभाइस प्रयोग गरी हेर्नुहोस्"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> ले अनुरोध रद्द गरेको छ"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-nl/strings.xml b/packages/CredentialManager/res/values-nl/strings.xml
index 68f95a7..8386995 100644
--- a/packages/CredentialManager/res/values-nl/strings.xml
+++ b/packages/CredentialManager/res/values-nl/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Via een ander apparaat"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Een ander apparaat gebruiken"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Verzoek geannuleerd door <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-or/strings.xml b/packages/CredentialManager/res/values-or/strings.xml
index 150ef0b..9f305ab 100644
--- a/packages/CredentialManager/res/values-or/strings.xml
+++ b/packages/CredentialManager/res/values-or/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ଅନ୍ୟ ଏକ ଡିଭାଇସରୁ"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ଏକ ଭିନ୍ନ ଡିଭାଇସ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> ଦ୍ୱାରା ଅନୁରୋଧ ବାତିଲ ହୋଇଛି"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-pa/strings.xml b/packages/CredentialManager/res/values-pa/strings.xml
index 10ff1ad..b90ae5e 100644
--- a/packages/CredentialManager/res/values-pa/strings.xml
+++ b/packages/CredentialManager/res/values-pa/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ਹੋਰ ਡੀਵਾਈਸ ਤੋਂ"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ਵੱਖਰੇ ਡੀਵਾਈਸ ਦੀ ਵਰਤੋਂ ਕਰੋ"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਵੱਲੋਂ ਬੇਨਤੀ ਰੱਦ ਕੀਤੀ ਗਈ"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-pl/strings.xml b/packages/CredentialManager/res/values-pl/strings.xml
index be60af5..6966d21 100644
--- a/packages/CredentialManager/res/values-pl/strings.xml
+++ b/packages/CredentialManager/res/values-pl/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Na innym urządzeniu"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Użyj innego urządzenia"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Żądanie anulowane przez aplikację <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-pt-rBR/strings.xml b/packages/CredentialManager/res/values-pt-rBR/strings.xml
index 93459e6..79da371 100644
--- a/packages/CredentialManager/res/values-pt-rBR/strings.xml
+++ b/packages/CredentialManager/res/values-pt-rBR/strings.xml
@@ -17,7 +17,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="4539824758261855508">"Gerenciador de credenciais"</string>
+    <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
     <string name="string_more_options" msgid="2763852250269945472">"Salvar de outra forma"</string>
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"De outro dispositivo"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar um dispositivo diferente"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Solicitação cancelada por <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-pt-rPT/strings.xml b/packages/CredentialManager/res/values-pt-rPT/strings.xml
index 27b84aa..208b475 100644
--- a/packages/CredentialManager/res/values-pt-rPT/strings.xml
+++ b/packages/CredentialManager/res/values-pt-rPT/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"De outro dispositivo"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use um dispositivo diferente"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Pedido cancelado pela app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-pt/strings.xml b/packages/CredentialManager/res/values-pt/strings.xml
index 93459e6..79da371 100644
--- a/packages/CredentialManager/res/values-pt/strings.xml
+++ b/packages/CredentialManager/res/values-pt/strings.xml
@@ -17,7 +17,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="4539824758261855508">"Gerenciador de credenciais"</string>
+    <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
     <string name="string_more_options" msgid="2763852250269945472">"Salvar de outra forma"</string>
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"De outro dispositivo"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar um dispositivo diferente"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Solicitação cancelada por <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-ro/strings.xml b/packages/CredentialManager/res/values-ro/strings.xml
index 5292eca..8984cf2 100644
--- a/packages/CredentialManager/res/values-ro/strings.xml
+++ b/packages/CredentialManager/res/values-ro/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"De pe alt dispozitiv"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Folosește alt dispozitiv"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Solicitare anulată de <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-ru/strings.xml b/packages/CredentialManager/res/values-ru/strings.xml
index 99d2d7cc..0f99831 100644
--- a/packages/CredentialManager/res/values-ru/strings.xml
+++ b/packages/CredentialManager/res/values-ru/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"С другого устройства"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Использовать другое устройство"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" отменило запрос."</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-si/strings.xml b/packages/CredentialManager/res/values-si/strings.xml
index 79eaa13..9969a0c 100644
--- a/packages/CredentialManager/res/values-si/strings.xml
+++ b/packages/CredentialManager/res/values-si/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"වෙනත් උපාංගයකින්"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"වෙනස් උපාංගයක් භාවිතා කරන්න"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> විසින් ඉල්ලීම අවලංගු කරන ලදී"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-sk/strings.xml b/packages/CredentialManager/res/values-sk/strings.xml
index 90805a4..f91f546 100644
--- a/packages/CredentialManager/res/values-sk/strings.xml
+++ b/packages/CredentialManager/res/values-sk/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Z iného zariadenia"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Použiť iné zariadenie"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Požiadavku zrušila aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-sl/strings.xml b/packages/CredentialManager/res/values-sl/strings.xml
index 16ba222..196b0aa 100644
--- a/packages/CredentialManager/res/values-sl/strings.xml
+++ b/packages/CredentialManager/res/values-sl/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Iz druge naprave"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Uporaba druge naprave"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Zahtevo je preklicala aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-sq/strings.xml b/packages/CredentialManager/res/values-sq/strings.xml
index 391c511..5fceff5 100644
--- a/packages/CredentialManager/res/values-sq/strings.xml
+++ b/packages/CredentialManager/res/values-sq/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Nga një pajisje tjetër"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Përdor një pajisje tjetër"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Kërkesa u anulua nga <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-sr/strings.xml b/packages/CredentialManager/res/values-sr/strings.xml
index b83c698..d721a4b 100644
--- a/packages/CredentialManager/res/values-sr/strings.xml
+++ b/packages/CredentialManager/res/values-sr/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Са другог уређаја"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Користи други уређај"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Захтве је отказала апликација <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-sv/strings.xml b/packages/CredentialManager/res/values-sv/strings.xml
index 65319b0..d3099cbc4 100644
--- a/packages/CredentialManager/res/values-sv/strings.xml
+++ b/packages/CredentialManager/res/values-sv/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Via en annan enhet"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Använd en annan enhet"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Begäran avbruten av <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-sw/strings.xml b/packages/CredentialManager/res/values-sw/strings.xml
index ffb4fa0..1e31128 100644
--- a/packages/CredentialManager/res/values-sw/strings.xml
+++ b/packages/CredentialManager/res/values-sw/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Kutoka kwenye kifaa kingine"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Tumia kifaa tofauti"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Ombi lilighairiwa na <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-ta/strings.xml b/packages/CredentialManager/res/values-ta/strings.xml
index 750b67d..1d4e55a 100644
--- a/packages/CredentialManager/res/values-ta/strings.xml
+++ b/packages/CredentialManager/res/values-ta/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"மற்றொரு சாதனத்திலிருந்து பயன்படுத்து"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"வேறு சாதனத்தைப் பயன்படுத்து"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸால் கோரிக்கை ரத்துசெய்யப்பட்டது"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-te/strings.xml b/packages/CredentialManager/res/values-te/strings.xml
index 064ee96..d546b66 100644
--- a/packages/CredentialManager/res/values-te/strings.xml
+++ b/packages/CredentialManager/res/values-te/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"మరొక పరికరం నుండి"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"వేరే పరికరాన్ని ఉపయోగించండి"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g>, రిక్వెస్ట్‌ను రద్దు చేసింది"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-th/strings.xml b/packages/CredentialManager/res/values-th/strings.xml
index 249bd88..7ebe82f 100644
--- a/packages/CredentialManager/res/values-th/strings.xml
+++ b/packages/CredentialManager/res/values-th/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"จากอุปกรณ์อื่น"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ใช้อุปกรณ์อื่น"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"ยกเลิกคำขอแล้วโดย <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-tl/strings.xml b/packages/CredentialManager/res/values-tl/strings.xml
index e33f1bf..d069ffe 100644
--- a/packages/CredentialManager/res/values-tl/strings.xml
+++ b/packages/CredentialManager/res/values-tl/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Mula sa ibang device"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Gumamit ng ibang device"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Kinansela ng <xliff:g id="APP_NAME">%1$s</xliff:g> ang kahilingan"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-tr/strings.xml b/packages/CredentialManager/res/values-tr/strings.xml
index 4e4894c..718d7bd 100644
--- a/packages/CredentialManager/res/values-tr/strings.xml
+++ b/packages/CredentialManager/res/values-tr/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Başka bir cihazdan"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Farklı bir cihaz kullan"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"İstek, <xliff:g id="APP_NAME">%1$s</xliff:g> tarafından iptal edildi"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-uk/strings.xml b/packages/CredentialManager/res/values-uk/strings.xml
index 78a5a5b..c23933a 100644
--- a/packages/CredentialManager/res/values-uk/strings.xml
+++ b/packages/CredentialManager/res/values-uk/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"З іншого пристрою"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Використовувати інший пристрій"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> скасував запит"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-ur/strings.xml b/packages/CredentialManager/res/values-ur/strings.xml
index d3d5d85..9bb1662 100644
--- a/packages/CredentialManager/res/values-ur/strings.xml
+++ b/packages/CredentialManager/res/values-ur/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"دوسرے آلے سے"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ایک مختلف آلہ استعمال کریں"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> نے درخواست منسوخ کر دی"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-uz/strings.xml b/packages/CredentialManager/res/values-uz/strings.xml
index a0785b6..90264e9 100644
--- a/packages/CredentialManager/res/values-uz/strings.xml
+++ b/packages/CredentialManager/res/values-uz/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Boshqa qurilmada"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Boshqa qurilmadan foydalanish"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Soʻrovni <xliff:g id="APP_NAME">%1$s</xliff:g> bekor qilgan"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-vi/strings.xml b/packages/CredentialManager/res/values-vi/strings.xml
index 0e17025..c6f897e 100644
--- a/packages/CredentialManager/res/values-vi/strings.xml
+++ b/packages/CredentialManager/res/values-vi/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Từ một thiết bị khác"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Dùng thiết bị khác"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> đã huỷ yêu cầu"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-zh-rCN/strings.xml b/packages/CredentialManager/res/values-zh-rCN/strings.xml
index 495abe6..f5bb10f 100644
--- a/packages/CredentialManager/res/values-zh-rCN/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rCN/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"通过另一台设备"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"使用其他设备"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g>已取消请求"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-zh-rHK/strings.xml b/packages/CredentialManager/res/values-zh-rHK/strings.xml
index f786254..ab54793 100644
--- a/packages/CredentialManager/res/values-zh-rHK/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rHK/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"透過其他裝置"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"使用其他裝置"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」已取消要求"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-zh-rTW/strings.xml b/packages/CredentialManager/res/values-zh-rTW/strings.xml
index c09bf86..f8f8eec 100644
--- a/packages/CredentialManager/res/values-zh-rTW/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rTW/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"透過其他裝置"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"使用其他裝置"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"要求已由 <xliff:g id="APP_NAME">%1$s</xliff:g> 取消"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values-zu/strings.xml b/packages/CredentialManager/res/values-zu/strings.xml
index 91f93e2..85fe60a 100644
--- a/packages/CredentialManager/res/values-zu/strings.xml
+++ b/packages/CredentialManager/res/values-zu/strings.xml
@@ -92,4 +92,8 @@
     <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Kusukela kwenye idivayisi"</string>
     <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Sebenzisa idivayisi ehlukile"</string>
     <string name="request_cancelled_by" msgid="3735222326886267820">"Isicelo sikhanselwe yi-<xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <!-- no translation found for dropdown_presentation_more_sign_in_options_text (1693727354272417902) -->
+    <skip />
+    <!-- no translation found for provider_icon_content_description (4023359912607637248) -->
+    <skip />
 </resources>
diff --git a/packages/CredentialManager/res/values/colors.xml b/packages/CredentialManager/res/values/colors.xml
index 63b9f24..7cb1d01 100644
--- a/packages/CredentialManager/res/values/colors.xml
+++ b/packages/CredentialManager/res/values/colors.xml
@@ -16,23 +16,10 @@
 
 <!-- Color palette -->
 <resources>
-    <color name="autofill_light_colorPrimary">@color/primary_material_light</color>
-    <color name="autofill_light_colorAccent">@color/accent_material_light</color>
-    <color name="autofill_light_colorControlHighlight">@color/ripple_material_light</color>
-    <color name="autofill_light_colorButtonNormal">@color/button_material_light</color>
-
-    <!-- Text colors -->
-    <color name="autofill_light_textColorPrimary">@color/abc_primary_text_material_light</color>
-    <color name="autofill_light_textColorSecondary">@color/abc_secondary_text_material_light</color>
-    <color name="autofill_light_textColorHint">@color/abc_hint_foreground_material_light</color>
-    <color name="autofill_light_textColorHintInverse">@color/abc_hint_foreground_material_dark
-    </color>
-    <color name="autofill_light_textColorHighlight">@color/highlighted_text_material_light</color>
-    <color name="autofill_light_textColorLink">@color/autofill_light_colorAccent</color>
-
     <!-- These colors are used for Remote Views. -->
-    <color name="background_dark_mode">#0E0C0B</color>
-    <color name="background">#F1F3F4</color>
-    <color name="text_primary_dark_mode">#DFDEDB</color>
-    <color name="text_primary">#202124</color>
+    <color name="text_primary">#1A1B20</color>
+    <color name="text_secondary">#44474F</color>
+    <color name="dropdown_container">#F3F3FA</color>
+    <color name="sign_in_options_container">#DADADA</color>
+    <color name="sign_in_options_icon_color">#1B1B1B</color>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml
index 67003a3..3a8c78f 100644
--- a/packages/CredentialManager/res/values/dimens.xml
+++ b/packages/CredentialManager/res/values/dimens.xml
@@ -17,6 +17,14 @@
   -->
 
 <resources>
-    <dimen name="autofill_view_padding">16dp</dimen>
-    <dimen name="autofill_icon_size">16dp</dimen>
+    <dimen name="autofill_view_top_padding">12dp</dimen>
+    <dimen name="autofill_view_right_padding">12dp</dimen>
+    <dimen name="autofill_view_bottom_padding">12dp</dimen>
+    <dimen name="autofill_view_left_padding">16dp</dimen>
+    <dimen name="autofill_view_icon_to_text_padding">10dp</dimen>
+    <dimen name="autofill_icon_size">24dp</dimen>
+    <dimen name="autofill_dropdown_textview_min_width">112dp</dimen>
+    <dimen name="autofill_dropdown_textview_max_width">230dp</dimen>
+    <dimen name="dropdown_layout_horizontal_margin">24dp</dimen>
+    <integer name="autofill_max_visible_datasets">3</integer>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 605e77b..f98164b 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -168,4 +168,9 @@
   <string name="get_dialog_option_headline_use_a_different_device">Use a different device</string>
   <!-- Text shown on a snackbar when the app cancelled the UI. [CHAR LIMIT=120] -->
   <string name="request_cancelled_by">Request cancelled by <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
+
+  <!-- Strings for dropdown presentation. -->
+  <!-- Text shown in the dropdown presentation to select more sign in options. [CHAR LIMIT=120] -->
+  <string name="dropdown_presentation_more_sign_in_options_text">Sign-in options</string>
+  <string name="provider_icon_content_description">Credential provider icon</string>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/styles.xml b/packages/CredentialManager/res/values/styles.xml
index 4a5761a..7de941e 100644
--- a/packages/CredentialManager/res/values/styles.xml
+++ b/packages/CredentialManager/res/values/styles.xml
@@ -15,24 +15,13 @@
   -->
 
 <resources>
-    <style name="autofill.TextAppearance.Small" parent="@style/autofill.TextAppearance">
-        <item name="android:textSize">12sp</item>
-    </style>
-
-
-    <style name="autofill.Dataset" parent="">
-        <item name="android:background">@drawable/autofill_light_selectable_item_background</item>
-    </style>
-
-    <style name="autofill.TextAppearance" parent="">
-        <item name="android:textColor">@color/autofill_light_textColorPrimary</item>
-        <item name="android:textColorHint">@color/autofill_light_textColorHint</item>
-        <item name="android:textColorHighlight">@color/autofill_light_textColorHighlight</item>
-        <item name="android:textColorLink">@color/autofill_light_textColorLink</item>
+    <style name="autofill.TextTitle" parent="">
+        <item name="android:fontFamily">google-sans-medium</item>
         <item name="android:textSize">14sp</item>
     </style>
 
-    <style name="autofill.TextAppearance.Primary">
-        <item name="android:textColor">@color/autofill_light_textColorPrimary</item>
+    <style name="autofill.TextSubtitle" parent="">
+        <item name="android:fontFamily">google-sans-text</item>
+        <item name="android:textSize">12sp</item>
     </style>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
index 325d3f8..0fa248d 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
@@ -19,7 +19,7 @@
 import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
-import android.credentials.ui.RequestInfo
+import android.credentials.selection.RequestInfo
 import android.util.Log
 import com.android.credentialmanager.ktx.appLabel
 import com.android.credentialmanager.ktx.cancelUiRequest
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/CredentialManagerClient.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/CredentialManagerClient.kt
index 49387cf..3fbff37 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/CredentialManagerClient.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/CredentialManagerClient.kt
@@ -17,8 +17,8 @@
 package com.android.credentialmanager.client
 
 import android.content.Intent
-import android.credentials.ui.BaseDialogResult
-import android.credentials.ui.UserSelectionDialogResult
+import android.credentials.selection.BaseDialogResult
+import android.credentials.selection.UserSelectionDialogResult
 import com.android.credentialmanager.model.Request
 import kotlinx.coroutines.flow.StateFlow
 
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
index 3ef65b0..ec1f052 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
@@ -18,8 +18,8 @@
 
 import android.content.Context
 import android.content.Intent
-import android.credentials.ui.BaseDialogResult
-import android.credentials.ui.UserSelectionDialogResult
+import android.credentials.selection.BaseDialogResult
+import android.credentials.selection.UserSelectionDialogResult
 import android.os.Bundle
 import android.util.Log
 import com.android.credentialmanager.TAG
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
index f063074..a5f227a 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
@@ -23,9 +23,9 @@
 import android.content.pm.PackageManager
 import android.credentials.Credential
 import android.credentials.flags.Flags
-import android.credentials.ui.AuthenticationEntry
-import android.credentials.ui.Entry
-import android.credentials.ui.GetCredentialProviderData
+import android.credentials.selection.AuthenticationEntry
+import android.credentials.selection.Entry
+import android.credentials.selection.GetCredentialProviderData
 import android.graphics.drawable.Drawable
 import android.text.TextUtils
 import android.util.Log
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
index 3abdb6f..4155b03 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
@@ -17,12 +17,12 @@
 package com.android.credentialmanager.ktx
 
 import android.content.Intent
-import android.credentials.ui.CancelUiRequest
-import android.credentials.ui.Constants
-import android.credentials.ui.CreateCredentialProviderData
-import android.credentials.ui.GetCredentialProviderData
-import android.credentials.ui.ProviderData
-import android.credentials.ui.RequestInfo
+import android.credentials.selection.CancelUiRequest
+import android.credentials.selection.Constants
+import android.credentials.selection.CreateCredentialProviderData
+import android.credentials.selection.GetCredentialProviderData
+import android.credentials.selection.ProviderData
+import android.credentials.selection.RequestInfo
 import android.os.ResultReceiver
 
 val Intent.cancelUiRequest: CancelUiRequest?
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index c0d7149..6cafcf7 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -18,16 +18,16 @@
 
 import android.content.Context
 import android.content.Intent
-import android.credentials.ui.CancelUiRequest
-import android.credentials.ui.Constants
-import android.credentials.ui.CreateCredentialProviderData
-import android.credentials.ui.GetCredentialProviderData
-import android.credentials.ui.DisabledProviderData
-import android.credentials.ui.ProviderData
-import android.credentials.ui.RequestInfo
-import android.credentials.ui.BaseDialogResult
-import android.credentials.ui.ProviderPendingIntentResponse
-import android.credentials.ui.UserSelectionDialogResult
+import android.credentials.selection.CancelUiRequest
+import android.credentials.selection.Constants
+import android.credentials.selection.CreateCredentialProviderData
+import android.credentials.selection.GetCredentialProviderData
+import android.credentials.selection.DisabledProviderData
+import android.credentials.selection.ProviderData
+import android.credentials.selection.RequestInfo
+import android.credentials.selection.BaseDialogResult
+import android.credentials.selection.ProviderPendingIntentResponse
+import android.credentials.selection.UserSelectionDialogResult
 import android.os.IBinder
 import android.os.Bundle
 import android.os.ResultReceiver
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index c409ba6..fa975aa 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -18,8 +18,8 @@
 
 import android.app.Activity
 import android.content.Intent
-import android.credentials.ui.BaseDialogResult
-import android.credentials.ui.RequestInfo
+import android.credentials.selection.BaseDialogResult
+import android.credentials.selection.RequestInfo
 import android.net.Uri
 import android.os.Bundle
 import android.os.ResultReceiver
@@ -34,6 +34,7 @@
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.ui.res.stringResource
 import androidx.lifecycle.viewmodel.compose.viewModel
+import com.android.compose.theme.PlatformTheme
 import com.android.credentialmanager.common.Constants
 import com.android.credentialmanager.common.DialogState
 import com.android.credentialmanager.common.ProviderActivityResult
@@ -43,7 +44,6 @@
 import com.android.credentialmanager.createflow.hasContentToDisplay
 import com.android.credentialmanager.getflow.GetCredentialScreen
 import com.android.credentialmanager.getflow.hasContentToDisplay
-import com.android.credentialmanager.ui.theme.PlatformTheme
 
 @ExperimentalMaterialApi
 class CredentialSelectorActivity : ComponentActivity() {
@@ -213,7 +213,7 @@
     private fun onInitializationError(e: Exception, intent: Intent) {
         Log.e(Constants.LOG_TAG, "Failed to show the credential selector; closing the activity", e)
         val resultReceiver = intent.getParcelableExtra(
-            android.credentials.ui.Constants.EXTRA_RESULT_RECEIVER,
+            android.credentials.selection.Constants.EXTRA_RESULT_RECEIVER,
             ResultReceiver::class.java
         )
         val requestInfo = intent.extras?.getParcelable(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index fc3970d..64595e2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -20,11 +20,11 @@
 import android.content.Context
 import android.content.pm.PackageInfo
 import android.content.pm.PackageManager
-import android.credentials.ui.CreateCredentialProviderData
-import android.credentials.ui.DisabledProviderData
-import android.credentials.ui.Entry
-import android.credentials.ui.GetCredentialProviderData
-import android.credentials.ui.RequestInfo
+import android.credentials.selection.CreateCredentialProviderData
+import android.credentials.selection.DisabledProviderData
+import android.credentials.selection.Entry
+import android.credentials.selection.GetCredentialProviderData
+import android.credentials.selection.RequestInfo
 import android.graphics.drawable.Drawable
 import android.text.TextUtils
 import android.util.Log
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 8ac364e7..1f1d236 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -25,11 +25,12 @@
 import android.credentials.GetCandidateCredentialsResponse
 import android.credentials.GetCandidateCredentialsException
 import android.credentials.CredentialOption
+import android.credentials.selection.GetCredentialProviderData
 import android.graphics.drawable.Icon
-import android.credentials.ui.GetCredentialProviderData
 import android.os.Bundle
 import android.os.CancellationSignal
 import android.os.OutcomeReceiver
+import android.provider.Settings
 import android.credentials.Credential
 import android.service.autofill.AutofillService
 import android.service.autofill.Dataset
@@ -48,6 +49,7 @@
 import android.view.autofill.AutofillId
 import android.widget.inline.InlinePresentationSpec
 import android.credentials.CredentialManager
+import android.widget.RemoteViews
 import androidx.autofill.inline.v1.InlineSuggestionUi
 import androidx.credentials.provider.CustomCredentialEntry
 import androidx.credentials.provider.PasswordCredentialEntry
@@ -57,6 +59,7 @@
 import com.android.credentialmanager.getflow.ProviderDisplayInfo
 import com.android.credentialmanager.getflow.toProviderDisplayInfo
 import com.android.credentialmanager.ktx.credentialEntry
+import com.android.credentialmanager.model.CredentialType
 import com.android.credentialmanager.model.get.CredentialEntryInfo
 import com.android.credentialmanager.model.get.ProviderInfo
 import org.json.JSONException
@@ -69,7 +72,8 @@
     companion object {
         private const val TAG = "CredAutofill"
 
-        private const val SESSION_ID_KEY = "session_id"
+        private const val SESSION_ID_KEY = "autofill_session_id"
+        private const val REQUEST_ID_KEY = "autofill_request_id"
         private const val CRED_HINT_PREFIX = "credential="
         private const val REQUEST_DATA_KEY = "requestData"
         private const val CANDIDATE_DATA_KEY = "candidateQueryData"
@@ -97,16 +101,23 @@
         val callingPackage = structure.activityComponent.packageName
         Log.i(TAG, "onFillCredentialRequest called for $callingPackage")
 
-        var sessionId = request.clientState?.getInt(SESSION_ID_KEY)
-
-        Log.i(TAG, "Autofill sessionId: " + sessionId)
-        if (sessionId == null) {
-            Log.i(TAG, "Session Id not found")
-            callback.onFailure("Session Id not found")
+        val clientState = request.clientState
+        if (clientState == null) {
+            Log.i(TAG, "Client state not found")
+            callback.onFailure("Client state not found")
+            return
+        }
+        val sessionId = clientState.getInt(SESSION_ID_KEY)
+        val requestId = clientState.getInt(REQUEST_ID_KEY)
+        Log.i(TAG, "Autofill sessionId: $sessionId, autofill requestId: $requestId")
+        if (sessionId == 0 || requestId == 0) {
+            Log.i(TAG, "Session Id or request Id not found")
+            callback.onFailure("Session Id or request Id not found")
             return
         }
 
-        val getCredRequest: GetCredentialRequest? = getCredManRequest(structure)
+        val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId,
+                requestId)
         if (getCredRequest == null) {
             Log.i(TAG, "No credential manager request found")
             callback.onFailure("No credential manager request found")
@@ -164,7 +175,7 @@
                 CancellationSignal(),
                 Executors.newSingleThreadExecutor(),
                 outcome,
-                autofillCallback
+                autofillCallback.asBinder()
         )
     }
 
@@ -298,19 +309,25 @@
         val inlineMaxSuggestedCount = inlineSuggestionsRequest?.maxSuggestionCount ?: 0
         val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
         val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0
-        var maxItemCount = totalEntryCount
-        if (inlineMaxSuggestedCount > 0) {
-            maxItemCount = maxItemCount.coerceAtMost(inlineMaxSuggestedCount)
-        }
+        val maxDropdownDisplayLimit = this.resources.getInteger(
+                com.android.credentialmanager.R.integer.autofill_max_visible_datasets)
+        var maxInlineItemCount = totalEntryCount
+        maxInlineItemCount = maxInlineItemCount.coerceAtMost(inlineMaxSuggestedCount)
+        val lastDropdownDatasetIndex = Settings.Global.getInt(this.contentResolver,
+                Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
+                (maxDropdownDisplayLimit - 1).coerceAtMost(totalEntryCount - 1))
+
         var i = 0
         var datasetAdded = false
 
-        val duplicateDisplayNames: MutableMap<String, Boolean> = mutableMapOf()
+        val duplicateDisplayNamesForPasskeys: MutableMap<String, Boolean> = mutableMapOf()
         providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach {
             val credentialEntry = it.sortedCredentialEntryList.first()
-            credentialEntry.displayName?.let {displayName ->
-                val duplicateEntry = duplicateDisplayNames.contains(displayName)
-                duplicateDisplayNames[displayName] = duplicateEntry
+            if (credentialEntry.credentialType == CredentialType.PASSKEY) {
+                credentialEntry.displayName?.let { displayName ->
+                    val duplicateEntry = duplicateDisplayNamesForPasskeys.contains(displayName)
+                    duplicateDisplayNamesForPasskeys[displayName] = duplicateEntry
+                }
             }
         }
         providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@{
@@ -322,13 +339,8 @@
                 Log.e(TAG, "PendingIntent was missing from the entry.")
                 return@usernameLoop
             }
-            if (inlinePresentationSpecs == null) {
-                Log.i(TAG, "Inline presentation spec is null, " +
-                        "building dropdown presentation only")
-            }
-            if (i >= maxItemCount) {
-                Log.e(TAG, "Skipping because reached the max item count.")
-                return@usernameLoop
+            if (i >= maxInlineItemCount && i >= lastDropdownDatasetIndex) {
+                return@usernameLoop;
             }
             val icon: Icon = if (primaryEntry.icon == null) {
                 // The empty entry icon has non-null icon reference but null drawable reference.
@@ -340,31 +352,26 @@
             }
             // Create inline presentation
             var inlinePresentation: InlinePresentation? = null
-            var spec: InlinePresentationSpec?
-            if (inlinePresentationSpecs != null) {
-                if (i < inlinePresentationSpecsCount) {
-                    spec = inlinePresentationSpecs[i]
+            if (inlinePresentationSpecs != null && i < maxInlineItemCount) {
+                val spec: InlinePresentationSpec? = if (i < inlinePresentationSpecsCount) {
+                    inlinePresentationSpecs[i]
                 } else {
-                    spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
+                    inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
                 }
-                val displayName : String = primaryEntry.displayName ?: primaryEntry.userName
-                val sliceBuilder = InlineSuggestionUi
-                        .newContentBuilder(pendingIntent)
-                        .setTitle(displayName)
-                sliceBuilder.setStartIcon(icon)
-                if (duplicateDisplayNames[displayName] == true) {
-                    sliceBuilder.setSubtitle(primaryEntry.userName)
-                }
-                inlinePresentation = InlinePresentation(
-                        sliceBuilder.build().slice, spec, /* pinned= */ false)
+                inlinePresentation = createInlinePresentation(primaryEntry, pendingIntent, icon,
+                        spec!!, duplicateDisplayNamesForPasskeys)
             }
-            val dropdownPresentation = RemoteViewsFactory.createDropdownPresentation(
-                    this, icon, primaryEntry)
-            i++
+            var dropdownPresentation: RemoteViews? = null
+            if (i < lastDropdownDatasetIndex) {
+                dropdownPresentation = RemoteViewsFactory
+                        .createDropdownPresentation(this, icon, primaryEntry)
+            }
 
             val dataSetBuilder = Dataset.Builder()
             val presentationBuilder = Presentations.Builder()
-                    .setMenuPresentation(dropdownPresentation)
+            if (dropdownPresentation != null) {
+                presentationBuilder.setMenuPresentation(dropdownPresentation)
+            }
             if (inlinePresentation != null) {
                 presentationBuilder.setInlinePresentation(inlinePresentation)
             }
@@ -380,6 +387,12 @@
                             .setAuthenticationExtras(fillInIntent.extras)
                             .build())
             datasetAdded = true
+            i++
+
+            if (i == lastDropdownDatasetIndex && bottomSheetPendingIntent != null) {
+                addDropdownMoreOptionsPresentation(bottomSheetPendingIntent, autofillId,
+                        fillResponseBuilder)
+            }
         }
         val pinnedSpec = getLastInlinePresentationSpec(inlinePresentationSpecs,
                 inlinePresentationSpecsCount)
@@ -390,6 +403,49 @@
         return datasetAdded
     }
 
+    private fun createInlinePresentation(primaryEntry: CredentialEntryInfo,
+                                         pendingIntent: PendingIntent,
+                                         icon: Icon,
+                                         spec: InlinePresentationSpec,
+                                         duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>):
+            InlinePresentation {
+        val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY
+                && primaryEntry.displayName != null) {
+            primaryEntry.displayName!!
+        } else {
+            primaryEntry.userName
+        }
+        val sliceBuilder = InlineSuggestionUi
+                .newContentBuilder(pendingIntent)
+                .setTitle(displayName)
+        sliceBuilder.setStartIcon(icon)
+        if (primaryEntry.credentialType ==
+                CredentialType.PASSKEY && duplicateDisplayNameForPasskeys[displayName] == true) {
+            sliceBuilder.setSubtitle(primaryEntry.userName)
+        }
+        return InlinePresentation(
+                sliceBuilder.build().slice, spec, /* pinned= */ false)
+    }
+
+    private fun addDropdownMoreOptionsPresentation(
+            bottomSheetPendingIntent: PendingIntent,
+            autofillId: AutofillId,
+            fillResponseBuilder: FillResponse.Builder) {
+        val presentationBuilder = Presentations.Builder()
+                .setMenuPresentation(RemoteViewsFactory.createMoreSignInOptionsPresentation(this))
+
+        fillResponseBuilder.addDataset(
+                Dataset.Builder()
+                        .setField(
+                                autofillId,
+                                Field.Builder().setPresentations(
+                                        presentationBuilder.build())
+                                        .build())
+                        .setAuthentication(bottomSheetPendingIntent.intentSender)
+                        .build()
+        )
+    }
+
     private fun getLastInlinePresentationSpec(
             inlinePresentationSpecs: List<InlinePresentationSpec>?,
             inlinePresentationSpecsCount: Int
@@ -515,12 +571,19 @@
         TODO("Not yet implemented")
     }
 
-    private fun getCredManRequest(structure: AssistStructure): GetCredentialRequest? {
+    private fun getCredManRequest(
+            structure: AssistStructure,
+            sessionId: Int,
+            requestId: Int
+    ): GetCredentialRequest? {
         val credentialOptions: MutableList<CredentialOption> = mutableListOf()
         traverseStructure(structure, credentialOptions)
 
         if (credentialOptions.isNotEmpty()) {
-            return GetCredentialRequest.Builder(Bundle.EMPTY)
+            val dataBundle = Bundle()
+            dataBundle.putInt(SESSION_ID_KEY, sessionId)
+            dataBundle.putInt(REQUEST_ID_KEY, requestId)
+            return GetCredentialRequest.Builder(dataBundle)
                     .setCredentialOptions(credentialOptions)
                     .build()
         }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/DialogType.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/DialogType.kt
index f40dc7e..22a5ec1 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/DialogType.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/DialogType.kt
@@ -16,7 +16,7 @@
 
 package com.android.credentialmanager.common
 
-import android.credentials.ui.RequestInfo
+import android.credentials.selection.RequestInfo
 
 enum class DialogType {
   CREATE_CREDENTIAL,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
index db69b8b..d319e4c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
@@ -24,11 +24,11 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import com.android.compose.rememberSystemUiController
+import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.credentialmanager.common.material.ModalBottomSheetLayout
 import com.android.credentialmanager.common.material.ModalBottomSheetValue
 import com.android.credentialmanager.common.material.rememberModalBottomSheetState
 import com.android.credentialmanager.ui.theme.EntryShape
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 import kotlinx.coroutines.launch
 
 
@@ -54,7 +54,7 @@
         setBottomSheetSystemBarsColor(sysUiController)
     }
     ModalBottomSheetLayout(
-        sheetBackgroundColor = LocalAndroidColorScheme.current.colorSurfaceBright,
+        sheetBackgroundColor = LocalAndroidColorScheme.current.surfaceBright,
         modifier = Modifier.background(Color.Transparent),
         sheetState = state,
         sheetContent = sheetContent,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
index 3976f9a..bdfe399 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
@@ -30,8 +30,8 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
+import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.credentialmanager.ui.theme.Shapes
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 
 /**
  * Container card for the whole sheet.
@@ -50,7 +50,7 @@
         modifier = modifier.fillMaxWidth().wrapContentHeight(),
         border = null,
         colors = CardDefaults.cardColors(
-            containerColor = LocalAndroidColorScheme.current.colorSurfaceBright,
+            containerColor = LocalAndroidColorScheme.current.surfaceBright,
         ),
     ) {
         if (topAppBar != null) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index 1c394ec..a6253b8 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -56,9 +56,9 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.credentialmanager.R
 import com.android.credentialmanager.ui.theme.EntryShape
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 import com.android.credentialmanager.ui.theme.Shapes
 
 @Composable
@@ -168,7 +168,7 @@
                             // Decorative purpose only.
                             contentDescription = null,
                             modifier = Modifier.size(24.dp),
-                            tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+                            tint = LocalAndroidColorScheme.current.onSurfaceVariant,
                         )
                     }
                 }
@@ -182,7 +182,7 @@
                         Icon(
                             modifier = iconSize,
                             bitmap = iconImageBitmap,
-                            tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+                            tint = LocalAndroidColorScheme.current.onSurfaceVariant,
                             // Decorative purpose only.
                             contentDescription = null,
                         )
@@ -206,7 +206,7 @@
                     Icon(
                         modifier = iconSize,
                         imageVector = iconImageVector,
-                        tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+                        tint = LocalAndroidColorScheme.current.onSurfaceVariant,
                         // Decorative purpose only.
                         contentDescription = null,
                     )
@@ -218,7 +218,7 @@
                     Icon(
                         modifier = iconSize,
                         painter = iconPainter,
-                        tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+                        tint = LocalAndroidColorScheme.current.onSurfaceVariant,
                         // Decorative purpose only.
                         contentDescription = null,
                     )
@@ -229,9 +229,9 @@
         },
         border = null,
         colors = SuggestionChipDefaults.suggestionChipColors(
-            containerColor = LocalAndroidColorScheme.current.colorSurfaceContainerHigh,
-            labelColor = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
-            iconContentColor = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+            containerColor = LocalAndroidColorScheme.current.surfaceContainerHigh,
+            labelColor = LocalAndroidColorScheme.current.onSurfaceVariant,
+            iconContentColor = LocalAndroidColorScheme.current.onSurfaceVariant,
         ),
     )
 }
@@ -294,7 +294,7 @@
         Icon(
             modifier = Modifier.size(24.dp),
             painter = leadingIconPainter,
-            tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+            tint = LocalAndroidColorScheme.current.onSurfaceVariant,
             // Decorative purpose only.
             contentDescription = null,
         )
@@ -353,7 +353,7 @@
                             R.string.accessibility_back_arrow_button
                         ),
                         modifier = Modifier.size(24.dp).autoMirrored(),
-                        tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+                        tint = LocalAndroidColorScheme.current.onSurfaceVariant,
                     )
                 }
             }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
index 4dc7f00..68f1c86 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -21,6 +21,7 @@
 import android.widget.RemoteViews
 import androidx.core.content.ContextCompat
 import com.android.credentialmanager.model.get.CredentialEntryInfo
+import com.android.credentialmanager.model.CredentialType
 import android.graphics.drawable.Icon
 
 class RemoteViewsFactory {
@@ -29,48 +30,125 @@
         private const val setAdjustViewBoundsMethodName = "setAdjustViewBounds"
         private const val setMaxHeightMethodName = "setMaxHeight"
         private const val setBackgroundResourceMethodName = "setBackgroundResource"
+        private const val bulletPoint = "\u2022"
+        private const val passwordCharacterLength = 15
 
         fun createDropdownPresentation(
                 context: Context,
                 icon: Icon,
                 credentialEntryInfo: CredentialEntryInfo
         ): RemoteViews {
-            val padding = context.resources.getDimensionPixelSize(com.android
-                    .credentialmanager.R.dimen.autofill_view_padding)
             var layoutId: Int = com.android.credentialmanager.R.layout
-                    .autofill_dataset_left_with_item_tag_hint
+                    .credman_dropdown_presentation_layout
             val remoteViews = RemoteViews(context.packageName, layoutId)
-            setRemoteViewsPaddings(remoteViews, padding)
-            val textColorPrimary = getTextColorPrimary(isDarkMode(context), context);
-            remoteViews.setTextColor(android.R.id.text1, textColorPrimary);
-            remoteViews.setTextViewText(android.R.id.text1, credentialEntryInfo.userName)
-
+            if (credentialEntryInfo.credentialType == CredentialType.UNKNOWN) {
+                return remoteViews
+            }
+            setRemoteViewsPaddings(remoteViews, context, /* primaryTextBottomPadding=*/0)
+            if (credentialEntryInfo.credentialType == CredentialType.PASSKEY) {
+                val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName
+                remoteViews.setTextViewText(android.R.id.text1, displayName)
+                val secondaryText = if (credentialEntryInfo.displayName != null)
+                    (credentialEntryInfo.userName + " " + bulletPoint + " "
+                            + credentialEntryInfo.credentialTypeDisplayName
+                            + " " + bulletPoint + " " + credentialEntryInfo.providerDisplayName)
+                else (credentialEntryInfo.credentialTypeDisplayName + " " + bulletPoint + " "
+                        + credentialEntryInfo.providerDisplayName)
+                remoteViews.setTextViewText(android.R.id.text2, secondaryText)
+            } else {
+                remoteViews.setTextViewText(android.R.id.text1, credentialEntryInfo.userName)
+                remoteViews.setTextViewText(android.R.id.text2,
+                        bulletPoint.repeat(passwordCharacterLength))
+            }
+            val textColorPrimary = ContextCompat.getColor(context,
+                    com.android.credentialmanager.R.color.text_primary)
+            remoteViews.setTextColor(android.R.id.text1, textColorPrimary)
+            val textColorSecondary = ContextCompat.getColor(context, com.android
+                    .credentialmanager.R.color.text_secondary)
+            remoteViews.setTextColor(android.R.id.text2, textColorSecondary)
             remoteViews.setImageViewIcon(android.R.id.icon1, icon);
             remoteViews.setBoolean(
                     android.R.id.icon1, setAdjustViewBoundsMethodName, true);
             remoteViews.setInt(
                     android.R.id.icon1,
-                     setMaxHeightMethodName,
+                    setMaxHeightMethodName,
                     context.resources.getDimensionPixelSize(
                             com.android.credentialmanager.R.dimen.autofill_icon_size));
-            val drawableId = if (isDarkMode(context))
-                com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one_dark
-            else com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one
+            val drawableId =
+                    com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one
+            remoteViews.setInt(
+                    android.R.id.content, setBackgroundResourceMethodName, drawableId);
+            return remoteViews
+        }
+
+        fun createMoreSignInOptionsPresentation(context: Context): RemoteViews {
+            var layoutId: Int = com.android.credentialmanager.R.layout
+                    .credman_dropdown_bottom_sheet
+            val remoteViews = RemoteViews(context.packageName, layoutId)
+            setRemoteViewsPaddings(remoteViews, context)
+            remoteViews.setTextViewText(android.R.id.text1, ContextCompat.getString(context,
+                    com.android.credentialmanager
+                            .R.string.dropdown_presentation_more_sign_in_options_text))
+
+            val textColorPrimary = ContextCompat.getColor(context,
+                    com.android.credentialmanager.R.color.text_primary)
+            remoteViews.setTextColor(android.R.id.text1, textColorPrimary)
+            val icon = Icon.createWithResource(context, com
+                    .android.credentialmanager.R.drawable.more_horiz_24px)
+            icon.setTint(ContextCompat.getColor(context,
+                    com.android.credentialmanager.R.color.sign_in_options_icon_color))
+            remoteViews.setImageViewIcon(android.R.id.icon1, icon)
+            remoteViews.setBoolean(
+                    android.R.id.icon1, setAdjustViewBoundsMethodName, true);
+            remoteViews.setInt(
+                    android.R.id.icon1,
+                    setMaxHeightMethodName,
+                    context.resources.getDimensionPixelSize(
+                            com.android.credentialmanager.R.dimen.autofill_icon_size));
+            val drawableId =
+                    com.android.credentialmanager.R.drawable.more_options_list_item
             remoteViews.setInt(
                     android.R.id.content, setBackgroundResourceMethodName, drawableId);
             return remoteViews
         }
 
         private fun setRemoteViewsPaddings(
-                remoteViews: RemoteViews,
-                padding: Int) {
-            val halfPadding = padding / 2
+                remoteViews: RemoteViews, context: Context) {
+            val bottomPadding = context.resources.getDimensionPixelSize(
+                    com.android.credentialmanager.R.dimen.autofill_view_bottom_padding)
+            setRemoteViewsPaddings(remoteViews, context, bottomPadding)
+        }
+
+        private fun setRemoteViewsPaddings(
+                remoteViews: RemoteViews, context: Context, primaryTextBottomPadding: Int) {
+            val leftPadding = context.resources.getDimensionPixelSize(
+                    com.android.credentialmanager.R.dimen.autofill_view_left_padding)
+            val iconToTextPadding = context.resources.getDimensionPixelSize(
+                    com.android.credentialmanager.R.dimen.autofill_view_icon_to_text_padding)
+            val rightPadding = context.resources.getDimensionPixelSize(
+                    com.android.credentialmanager.R.dimen.autofill_view_right_padding)
+            val topPadding = context.resources.getDimensionPixelSize(
+                    com.android.credentialmanager.R.dimen.autofill_view_top_padding)
+            val bottomPadding = context.resources.getDimensionPixelSize(
+                    com.android.credentialmanager.R.dimen.autofill_view_bottom_padding)
+            remoteViews.setViewPadding(
+                    android.R.id.icon1,
+                    leftPadding,
+                    /* top=*/0,
+                    /* right=*/0,
+                    /* bottom=*/0)
             remoteViews.setViewPadding(
                     android.R.id.text1,
-                    halfPadding,
-                    halfPadding,
-                    halfPadding,
-                    halfPadding)
+                    iconToTextPadding,
+                    /* top=*/topPadding,
+                    /* right=*/rightPadding,
+                    primaryTextBottomPadding)
+            remoteViews.setViewPadding(
+                    android.R.id.text2,
+                    iconToTextPadding,
+                    /* top=*/0,
+                    /* right=*/rightPadding,
+                    /* bottom=*/bottomPadding)
         }
 
         private fun isDarkMode(context: Context): Boolean {
@@ -78,11 +156,5 @@
                     Configuration.UI_MODE_NIGHT_MASK
             return currentNightMode == Configuration.UI_MODE_NIGHT_YES
         }
-
-        private fun getTextColorPrimary(darkMode: Boolean, context: Context): Int {
-            return if (darkMode) ContextCompat.getColor(
-                    context, com.android.credentialmanager.R.color.text_primary_dark_mode)
-            else ContextCompat.getColor(context, com.android.credentialmanager.R.color.text_primary)
-        }
     }
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
index 2df0c7a9..342af3b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
@@ -24,20 +24,20 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
+import com.android.compose.theme.LocalAndroidColorScheme
 
 @Composable
 fun CredentialListSectionHeader(text: String, isFirstSection: Boolean) {
     InternalSectionHeader(
         text = text,
-        color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+        color = LocalAndroidColorScheme.current.onSurfaceVariant,
         applyTopPadding = !isFirstSection
     )
 }
 
 @Composable
 fun MoreAboutPasskeySectionHeader(text: String) {
-    InternalSectionHeader(text, LocalAndroidColorScheme.current.colorOnSurface)
+    InternalSectionHeader(text, LocalAndroidColorScheme.current.onSurface)
 }
 
 @Composable
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt
index a619523..b4075f1 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt
@@ -19,8 +19,8 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.graphics.Color
 import com.android.compose.SystemUiController
+import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.credentialmanager.common.material.ModalBottomSheetDefaults
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 
 @Composable
 fun setTransparentSystemBarsColor(sysUiController: SystemUiController) {
@@ -34,7 +34,7 @@
         darkIcons = false
     )
     sysUiController.setNavigationBarColor(
-        color = LocalAndroidColorScheme.current.colorSurfaceBright,
+        color = LocalAndroidColorScheme.current.surfaceBright,
         darkIcons = false
     )
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
index 6b46636..9111e61 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
@@ -25,7 +25,7 @@
 import androidx.compose.ui.text.TextLayoutResult
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.text.style.TextOverflow
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
+import com.android.compose.theme.LocalAndroidColorScheme
 
 /**
  * The headline for a screen. E.g. "Create a passkey for X", "Choose a saved sign-in for X".
@@ -37,7 +37,7 @@
     Text(
         modifier = modifier.wrapContentSize(),
         text = text,
-        color = LocalAndroidColorScheme.current.colorOnSurface,
+        color = LocalAndroidColorScheme.current.onSurface,
         textAlign = TextAlign.Center,
         style = MaterialTheme.typography.headlineSmall,
     )
@@ -51,7 +51,7 @@
     Text(
         modifier = modifier.wrapContentSize(),
         text = text,
-        color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+        color = LocalAndroidColorScheme.current.onSurfaceVariant,
         style = MaterialTheme.typography.bodyMedium,
     )
 }
@@ -69,7 +69,7 @@
     Text(
         modifier = modifier.wrapContentSize(),
         text = text,
-        color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+        color = LocalAndroidColorScheme.current.onSurfaceVariant,
         style = MaterialTheme.typography.bodySmall,
         overflow = TextOverflow.Ellipsis,
         maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE,
@@ -85,7 +85,7 @@
     Text(
         modifier = modifier.wrapContentSize(),
         text = text,
-        color = LocalAndroidColorScheme.current.colorOnSurface,
+        color = LocalAndroidColorScheme.current.onSurface,
         style = MaterialTheme.typography.titleLarge,
     )
 }
@@ -103,7 +103,7 @@
     Text(
         modifier = modifier.wrapContentSize(),
         text = text,
-        color = LocalAndroidColorScheme.current.colorOnSurface,
+        color = LocalAndroidColorScheme.current.onSurface,
         style = MaterialTheme.typography.titleSmall,
         overflow = TextOverflow.Ellipsis,
         maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE,
@@ -159,7 +159,7 @@
         modifier = modifier.wrapContentSize(),
         text = text,
         textAlign = TextAlign.Center,
-        color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+        color = LocalAndroidColorScheme.current.onSurfaceVariant,
         style = MaterialTheme.typography.labelLarge,
     )
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 14a9165..f261d1f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -46,6 +46,7 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.core.graphics.drawable.toBitmap
+import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.credentialmanager.CredentialSelectorViewModel
 import com.android.credentialmanager.R
 import com.android.credentialmanager.model.EntryInfo
@@ -70,7 +71,6 @@
 import com.android.credentialmanager.logging.CreateCredentialEvent
 import com.android.credentialmanager.model.creation.CreateOptionInfo
 import com.android.credentialmanager.model.creation.RemoteInfo
-import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 import com.android.internal.logging.UiEventLogger.UiEventEnum
 
 @Composable
@@ -460,7 +460,7 @@
             item {
                 Divider(
                     thickness = 1.dp,
-                    color = LocalAndroidColorScheme.current.colorOutlineVariant,
+                    color = LocalAndroidColorScheme.current.outlineVariant,
                     modifier = Modifier.padding(vertical = 16.dp)
                 )
             }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index a291f59..458a99a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -26,7 +26,7 @@
 import com.android.internal.util.Preconditions
 
 data class GetCredentialUiState(
-        val isRequestForAllOptions: Boolean,
+    val isRequestForAllOptions: Boolean,
     val providerInfoList: List<ProviderInfo>,
     val requestDisplayInfo: RequestDisplayInfo,
     val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList),
@@ -165,7 +165,7 @@
     )
 }
 
-private fun toActiveEntry(
+fun toActiveEntry(
     providerDisplayInfo: ProviderDisplayInfo,
 ): EntryInfo? {
     val sortedUserNameToCredentialEntryList =
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
deleted file mode 100644
index a33904d..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.theme
-
-import android.annotation.ColorInt
-import android.content.Context
-import androidx.compose.runtime.staticCompositionLocalOf
-import androidx.compose.ui.graphics.Color
-import com.android.internal.R
-
-/** File copied from PlatformComposeCore. */
-
-/** CompositionLocal used to pass [AndroidColorScheme] down the tree. */
-val LocalAndroidColorScheme =
-    staticCompositionLocalOf<AndroidColorScheme> {
-        throw IllegalStateException(
-            "No AndroidColorScheme configured. Make sure to use LocalAndroidColorScheme in a " +
-                "Composable surrounded by a PlatformTheme {}."
-        )
-    }
-
-/**
- * The Android color scheme.
- *
- * Important: Use M3 colors from MaterialTheme.colorScheme whenever possible instead. In the future,
- * most of the colors in this class will be removed in favor of their M3 counterpart.
- */
-class AndroidColorScheme internal constructor(context: Context) {
-    val colorSurfaceBright = getColor(context, R.attr.materialColorSurfaceBright)
-    val colorSurfaceContainerHigh = getColor(context, R.attr.materialColorSurfaceContainerHigh)
-    val colorOutlineVariant = getColor(context, R.attr.materialColorOutlineVariant)
-    val colorOnSurface = getColor(context, R.attr.materialColorOnSurface)
-    val colorOnSurfaceVariant = getColor(context, R.attr.materialColorOnSurfaceVariant)
-
-    companion object {
-        fun getColor(context: Context, attr: Int): Color {
-            val ta = context.obtainStyledAttributes(intArrayOf(attr))
-            @ColorInt val color = ta.getColor(0, 0)
-            ta.recycle()
-            return Color(color)
-        }
-    }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt
deleted file mode 100644
index c923845..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.theme
-
-import android.annotation.AttrRes
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.ReadOnlyComposable
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalContext
-
-/** Read the [Color] from the given [attribute]. */
-@Composable
-@ReadOnlyComposable
-fun colorAttr(@AttrRes attribute: Int): Color {
-    return AndroidColorScheme.getColor(LocalContext.current, attribute)
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt
deleted file mode 100644
index 2f1ce68..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.theme
-
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.dynamicDarkColorScheme
-import androidx.compose.material3.dynamicLightColorScheme
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
-import com.android.credentialmanager.ui.theme.typography.TypeScaleTokens
-import com.android.credentialmanager.ui.theme.typography.TypefaceNames
-import com.android.credentialmanager.ui.theme.typography.TypefaceTokens
-import com.android.credentialmanager.ui.theme.typography.TypographyTokens
-import com.android.credentialmanager.ui.theme.typography.platformTypography
-
-/** File copied from PlatformComposeCore. */
-
-/**
- * The Material 3 theme that should wrap all Platform Composables.
- *
- * TODO(b/280685309): Merge with the official SysUI platform theme.
- */
-@Composable
-fun PlatformTheme(
-    isDarkTheme: Boolean = isSystemInDarkTheme(),
-    content: @Composable () -> Unit,
-) {
-    val context = LocalContext.current
-
-    val colorScheme =
-        if (isDarkTheme) {
-            dynamicDarkColorScheme(context)
-        } else {
-            dynamicLightColorScheme(context)
-        }
-    val androidColorScheme = AndroidColorScheme(context)
-    val typefaceNames = remember(context) { TypefaceNames.get(context) }
-    val typography =
-        remember(typefaceNames) {
-            platformTypography(TypographyTokens(TypeScaleTokens(TypefaceTokens(typefaceNames))))
-        }
-
-    MaterialTheme(colorScheme, typography = typography) {
-        CompositionLocalProvider(
-            LocalAndroidColorScheme provides androidColorScheme,
-        ) {
-            content()
-        }
-    }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt
deleted file mode 100644
index 984e4f1..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.theme.typography
-
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Typography
-
-/** File copied from PlatformComposeCore. */
-
-/**
- * The typography for Platform Compose code.
- *
- * Do not use directly and call [MaterialTheme.typography] instead to access the different text
- * styles.
- */
-internal fun platformTypography(typographyTokens: TypographyTokens): Typography {
-    return Typography(
-        displayLarge = typographyTokens.displayLarge,
-        displayMedium = typographyTokens.displayMedium,
-        displaySmall = typographyTokens.displaySmall,
-        headlineLarge = typographyTokens.headlineLarge,
-        headlineMedium = typographyTokens.headlineMedium,
-        headlineSmall = typographyTokens.headlineSmall,
-        titleLarge = typographyTokens.titleLarge,
-        titleMedium = typographyTokens.titleMedium,
-        titleSmall = typographyTokens.titleSmall,
-        bodyLarge = typographyTokens.bodyLarge,
-        bodyMedium = typographyTokens.bodyMedium,
-        bodySmall = typographyTokens.bodySmall,
-        labelLarge = typographyTokens.labelLarge,
-        labelMedium = typographyTokens.labelMedium,
-        labelSmall = typographyTokens.labelSmall,
-    )
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt
deleted file mode 100644
index b2dd207..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.theme.typography
-
-import androidx.compose.ui.unit.sp
-
-/** File copied from PlatformComposeCore. */
-internal class TypeScaleTokens(typefaceTokens: TypefaceTokens) {
-    val bodyLargeFont = typefaceTokens.plain
-    val bodyLargeLineHeight = 24.0.sp
-    val bodyLargeSize = 16.sp
-    val bodyLargeTracking = 0.0.sp
-    val bodyLargeWeight = TypefaceTokens.WeightRegular
-    val bodyMediumFont = typefaceTokens.plain
-    val bodyMediumLineHeight = 20.0.sp
-    val bodyMediumSize = 14.sp
-    val bodyMediumTracking = 0.0.sp
-    val bodyMediumWeight = TypefaceTokens.WeightRegular
-    val bodySmallFont = typefaceTokens.plain
-    val bodySmallLineHeight = 16.0.sp
-    val bodySmallSize = 12.sp
-    val bodySmallTracking = 0.1.sp
-    val bodySmallWeight = TypefaceTokens.WeightRegular
-    val displayLargeFont = typefaceTokens.brand
-    val displayLargeLineHeight = 64.0.sp
-    val displayLargeSize = 57.sp
-    val displayLargeTracking = 0.0.sp
-    val displayLargeWeight = TypefaceTokens.WeightRegular
-    val displayMediumFont = typefaceTokens.brand
-    val displayMediumLineHeight = 52.0.sp
-    val displayMediumSize = 45.sp
-    val displayMediumTracking = 0.0.sp
-    val displayMediumWeight = TypefaceTokens.WeightRegular
-    val displaySmallFont = typefaceTokens.brand
-    val displaySmallLineHeight = 44.0.sp
-    val displaySmallSize = 36.sp
-    val displaySmallTracking = 0.0.sp
-    val displaySmallWeight = TypefaceTokens.WeightRegular
-    val headlineLargeFont = typefaceTokens.brand
-    val headlineLargeLineHeight = 40.0.sp
-    val headlineLargeSize = 32.sp
-    val headlineLargeTracking = 0.0.sp
-    val headlineLargeWeight = TypefaceTokens.WeightRegular
-    val headlineMediumFont = typefaceTokens.brand
-    val headlineMediumLineHeight = 36.0.sp
-    val headlineMediumSize = 28.sp
-    val headlineMediumTracking = 0.0.sp
-    val headlineMediumWeight = TypefaceTokens.WeightRegular
-    val headlineSmallFont = typefaceTokens.brand
-    val headlineSmallLineHeight = 32.0.sp
-    val headlineSmallSize = 24.sp
-    val headlineSmallTracking = 0.0.sp
-    val headlineSmallWeight = TypefaceTokens.WeightRegular
-    val labelLargeFont = typefaceTokens.plain
-    val labelLargeLineHeight = 20.0.sp
-    val labelLargeSize = 14.sp
-    val labelLargeTracking = 0.0.sp
-    val labelLargeWeight = TypefaceTokens.WeightMedium
-    val labelMediumFont = typefaceTokens.plain
-    val labelMediumLineHeight = 16.0.sp
-    val labelMediumSize = 12.sp
-    val labelMediumTracking = 0.1.sp
-    val labelMediumWeight = TypefaceTokens.WeightMedium
-    val labelSmallFont = typefaceTokens.plain
-    val labelSmallLineHeight = 16.0.sp
-    val labelSmallSize = 11.sp
-    val labelSmallTracking = 0.1.sp
-    val labelSmallWeight = TypefaceTokens.WeightMedium
-    val titleLargeFont = typefaceTokens.brand
-    val titleLargeLineHeight = 28.0.sp
-    val titleLargeSize = 22.sp
-    val titleLargeTracking = 0.0.sp
-    val titleLargeWeight = TypefaceTokens.WeightRegular
-    val titleMediumFont = typefaceTokens.plain
-    val titleMediumLineHeight = 24.0.sp
-    val titleMediumSize = 16.sp
-    val titleMediumTracking = 0.0.sp
-    val titleMediumWeight = TypefaceTokens.WeightMedium
-    val titleSmallFont = typefaceTokens.plain
-    val titleSmallLineHeight = 20.0.sp
-    val titleSmallSize = 14.sp
-    val titleSmallTracking = 0.0.sp
-    val titleSmallWeight = TypefaceTokens.WeightMedium
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt
deleted file mode 100644
index 3cc761f..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:OptIn(ExperimentalTextApi::class)
-
-package com.android.credentialmanager.ui.theme.typography
-
-import android.content.Context
-import androidx.compose.ui.text.ExperimentalTextApi
-import androidx.compose.ui.text.font.DeviceFontFamilyName
-import androidx.compose.ui.text.font.Font
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.font.FontWeight
-
-/** File copied from PlatformComposeCore. */
-internal class TypefaceTokens(typefaceNames: TypefaceNames) {
-    companion object {
-        val WeightMedium = FontWeight.Medium
-        val WeightRegular = FontWeight.Normal
-    }
-
-    private val brandFont = DeviceFontFamilyName(typefaceNames.brand)
-    private val plainFont = DeviceFontFamilyName(typefaceNames.plain)
-
-    val brand =
-        FontFamily(
-            Font(brandFont, weight = WeightMedium),
-            Font(brandFont, weight = WeightRegular),
-        )
-    val plain =
-        FontFamily(
-            Font(plainFont, weight = WeightMedium),
-            Font(plainFont, weight = WeightRegular),
-        )
-}
-
-internal data class TypefaceNames
-private constructor(
-    val brand: String,
-    val plain: String,
-) {
-    private enum class Config(val configName: String, val default: String) {
-        Brand("config_headlineFontFamily", "sans-serif"),
-        Plain("config_bodyFontFamily", "sans-serif"),
-    }
-
-    companion object {
-        fun get(context: Context): TypefaceNames {
-            return TypefaceNames(
-                brand = getTypefaceName(context, Config.Brand),
-                plain = getTypefaceName(context, Config.Plain),
-            )
-        }
-
-        private fun getTypefaceName(context: Context, config: Config): String {
-            return context
-                .getString(context.resources.getIdentifier(config.configName, "string", "android"))
-                .takeIf { it.isNotEmpty() }
-                ?: config.default
-        }
-    }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt
deleted file mode 100644
index aadab92..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.theme.typography
-
-import androidx.compose.ui.text.TextStyle
-
-/** File copied from PlatformComposeCore. */
-internal class TypographyTokens(typeScaleTokens: TypeScaleTokens) {
-    val bodyLarge =
-        TextStyle(
-            fontFamily = typeScaleTokens.bodyLargeFont,
-            fontWeight = typeScaleTokens.bodyLargeWeight,
-            fontSize = typeScaleTokens.bodyLargeSize,
-            lineHeight = typeScaleTokens.bodyLargeLineHeight,
-            letterSpacing = typeScaleTokens.bodyLargeTracking,
-        )
-    val bodyMedium =
-        TextStyle(
-            fontFamily = typeScaleTokens.bodyMediumFont,
-            fontWeight = typeScaleTokens.bodyMediumWeight,
-            fontSize = typeScaleTokens.bodyMediumSize,
-            lineHeight = typeScaleTokens.bodyMediumLineHeight,
-            letterSpacing = typeScaleTokens.bodyMediumTracking,
-        )
-    val bodySmall =
-        TextStyle(
-            fontFamily = typeScaleTokens.bodySmallFont,
-            fontWeight = typeScaleTokens.bodySmallWeight,
-            fontSize = typeScaleTokens.bodySmallSize,
-            lineHeight = typeScaleTokens.bodySmallLineHeight,
-            letterSpacing = typeScaleTokens.bodySmallTracking,
-        )
-    val displayLarge =
-        TextStyle(
-            fontFamily = typeScaleTokens.displayLargeFont,
-            fontWeight = typeScaleTokens.displayLargeWeight,
-            fontSize = typeScaleTokens.displayLargeSize,
-            lineHeight = typeScaleTokens.displayLargeLineHeight,
-            letterSpacing = typeScaleTokens.displayLargeTracking,
-        )
-    val displayMedium =
-        TextStyle(
-            fontFamily = typeScaleTokens.displayMediumFont,
-            fontWeight = typeScaleTokens.displayMediumWeight,
-            fontSize = typeScaleTokens.displayMediumSize,
-            lineHeight = typeScaleTokens.displayMediumLineHeight,
-            letterSpacing = typeScaleTokens.displayMediumTracking,
-        )
-    val displaySmall =
-        TextStyle(
-            fontFamily = typeScaleTokens.displaySmallFont,
-            fontWeight = typeScaleTokens.displaySmallWeight,
-            fontSize = typeScaleTokens.displaySmallSize,
-            lineHeight = typeScaleTokens.displaySmallLineHeight,
-            letterSpacing = typeScaleTokens.displaySmallTracking,
-        )
-    val headlineLarge =
-        TextStyle(
-            fontFamily = typeScaleTokens.headlineLargeFont,
-            fontWeight = typeScaleTokens.headlineLargeWeight,
-            fontSize = typeScaleTokens.headlineLargeSize,
-            lineHeight = typeScaleTokens.headlineLargeLineHeight,
-            letterSpacing = typeScaleTokens.headlineLargeTracking,
-        )
-    val headlineMedium =
-        TextStyle(
-            fontFamily = typeScaleTokens.headlineMediumFont,
-            fontWeight = typeScaleTokens.headlineMediumWeight,
-            fontSize = typeScaleTokens.headlineMediumSize,
-            lineHeight = typeScaleTokens.headlineMediumLineHeight,
-            letterSpacing = typeScaleTokens.headlineMediumTracking,
-        )
-    val headlineSmall =
-        TextStyle(
-            fontFamily = typeScaleTokens.headlineSmallFont,
-            fontWeight = typeScaleTokens.headlineSmallWeight,
-            fontSize = typeScaleTokens.headlineSmallSize,
-            lineHeight = typeScaleTokens.headlineSmallLineHeight,
-            letterSpacing = typeScaleTokens.headlineSmallTracking,
-        )
-    val labelLarge =
-        TextStyle(
-            fontFamily = typeScaleTokens.labelLargeFont,
-            fontWeight = typeScaleTokens.labelLargeWeight,
-            fontSize = typeScaleTokens.labelLargeSize,
-            lineHeight = typeScaleTokens.labelLargeLineHeight,
-            letterSpacing = typeScaleTokens.labelLargeTracking,
-        )
-    val labelMedium =
-        TextStyle(
-            fontFamily = typeScaleTokens.labelMediumFont,
-            fontWeight = typeScaleTokens.labelMediumWeight,
-            fontSize = typeScaleTokens.labelMediumSize,
-            lineHeight = typeScaleTokens.labelMediumLineHeight,
-            letterSpacing = typeScaleTokens.labelMediumTracking,
-        )
-    val labelSmall =
-        TextStyle(
-            fontFamily = typeScaleTokens.labelSmallFont,
-            fontWeight = typeScaleTokens.labelSmallWeight,
-            fontSize = typeScaleTokens.labelSmallSize,
-            lineHeight = typeScaleTokens.labelSmallLineHeight,
-            letterSpacing = typeScaleTokens.labelSmallTracking,
-        )
-    val titleLarge =
-        TextStyle(
-            fontFamily = typeScaleTokens.titleLargeFont,
-            fontWeight = typeScaleTokens.titleLargeWeight,
-            fontSize = typeScaleTokens.titleLargeSize,
-            lineHeight = typeScaleTokens.titleLargeLineHeight,
-            letterSpacing = typeScaleTokens.titleLargeTracking,
-        )
-    val titleMedium =
-        TextStyle(
-            fontFamily = typeScaleTokens.titleMediumFont,
-            fontWeight = typeScaleTokens.titleMediumWeight,
-            fontSize = typeScaleTokens.titleMediumSize,
-            lineHeight = typeScaleTokens.titleMediumLineHeight,
-            letterSpacing = typeScaleTokens.titleMediumTracking,
-        )
-    val titleSmall =
-        TextStyle(
-            fontFamily = typeScaleTokens.titleSmallFont,
-            fontWeight = typeScaleTokens.titleSmallWeight,
-            fontSize = typeScaleTokens.titleSmallSize,
-            lineHeight = typeScaleTokens.titleSmallLineHeight,
-            letterSpacing = typeScaleTokens.titleSmallTracking,
-        )
-}
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 4f9fc46..c9c66b4 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,8 +17,8 @@
 package com.android.credentialmanager.ui.screens.single.password
 
 import android.content.Intent
-import android.credentials.ui.ProviderPendingIntentResponse
-import android.credentials.ui.UserSelectionDialogResult
+import android.credentials.selection.ProviderPendingIntentResponse
+import android.credentials.selection.UserSelectionDialogResult
 import androidx.activity.result.IntentSenderRequest
 import androidx.annotation.MainThread
 import androidx.lifecycle.ViewModel
diff --git a/packages/InputDevices/res/values-sv/strings.xml b/packages/InputDevices/res/values-sv/strings.xml
index 3d0b945..b1e5d75 100644
--- a/packages/InputDevices/res/values-sv/strings.xml
+++ b/packages/InputDevices/res/values-sv/strings.xml
@@ -3,50 +3,50 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="8016145283189546017">"Indataenheter"</string>
     <string name="keyboard_layouts_label" msgid="6688773268302087545">"Androids tangentbord"</string>
-    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Engelskt (Storbritannien)"</string>
-    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Engelskt (USA)"</string>
-    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engelskt (USA), internationellt"</string>
-    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engelskt (USA), colemak"</string>
-    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engelskt (USA), dvorak"</string>
-    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Engelskt (USA), workman"</string>
-    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tyskt"</string>
-    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Franskt"</string>
-    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franskt (Kanada)"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"engelska (Storbritannien)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"engelska (USA)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"engelska (USA), internationell"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"engelska (USA), colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"engelska (USA), dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"engelska (USA), workman"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"tyska"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"franska"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"franska (Kanada)"</string>
     <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"ryska"</string>
-    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Ryskt, Mac"</string>
-    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Spanskt"</string>
-    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Franskt (Schweiz)"</string>
-    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Tyskt (Schweiz)"</string>
-    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgiskt"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"ryska, Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"spanska"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"franska (Schweiz)"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"tyska (Schweiz)"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"belgiska"</string>
     <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"bulgariska"</string>
-    <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgariska (fonetiskt)"</string>
-    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italienskt"</string>
-    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Danskt"</string>
-    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norskt"</string>
-    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Svenskt"</string>
-    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Finskt"</string>
-    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Kroatiskt"</string>
-    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Tjeckiskt"</string>
-    <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"Tjeckiskt QWERTY"</string>
-    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estniskt"</string>
-    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Ungerskt"</string>
-    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Isländskt"</string>
-    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Portugisiskt (Brasilien)"</string>
-    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portugisiskt"</string>
-    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Slovakiskt"</string>
-    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Slovenskt"</string>
-    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Turkiskt"</string>
+    <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"bulgariska (fonetiskt)"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"italienska"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"danska"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"norska"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"svenska"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"finska"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"kroatiska"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"tjeckiska"</string>
+    <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"tjeckiska QWERTY"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"estniska"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"ungerska"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"isländska"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"portugisiska (Brasilien)"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"portugisiska"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"slovakiska"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"slovenska"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"turkiska"</string>
     <string name="keyboard_layout_turkish_f" msgid="9130320856010776018">"turkiska, F"</string>
-    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ukrainskt"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"ukrainska"</string>
     <string name="keyboard_layout_arabic" msgid="5671970465174968712">"arabiska"</string>
     <string name="keyboard_layout_greek" msgid="7289253560162386040">"grekiska"</string>
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"hebreiska"</string>
-    <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Litauiska"</string>
-    <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Spanska (latinamerikansk)"</string>
+    <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"litauiska"</string>
+    <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"spanska (latinamerikansk)"</string>
     <string name="keyboard_layout_latvian" msgid="4405417142306250595">"lettiska"</string>
     <string name="keyboard_layout_persian" msgid="3920643161015888527">"persiska"</string>
     <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"azerbajdzjanska"</string>
-    <string name="keyboard_layout_polish" msgid="1121588624094925325">"Polska"</string>
+    <string name="keyboard_layout_polish" msgid="1121588624094925325">"polska"</string>
     <string name="keyboard_layout_belarusian" msgid="7619281752698687588">"vitryska"</string>
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"mongoliska"</string>
     <string name="keyboard_layout_georgian" msgid="4596185456863747454">"georgiska"</string>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
index b5af845..9af799c 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
@@ -31,7 +31,7 @@
 import android.os.Process;
 import android.util.Log;
 
-import androidx.annotation.Nullable;
+import androidx.annotation.NonNull;
 
 import java.io.IOException;
 import java.util.Arrays;
@@ -105,7 +105,7 @@
         }
     }
 
-    @Nullable
+    @NonNull
     private String[] getRequestedPermissions(String callingPackage) {
         String[] requestedPermissions = null;
         try {
@@ -115,7 +115,7 @@
             // Should be unreachable because we've just fetched the packageName above.
             Log.e(TAG, "Package not found for " + callingPackage);
         }
-        return requestedPermissions;
+        return requestedPermissions == null ? new String[]{} : requestedPermissions;
     }
 
     void startUnarchive() {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
index 170cb45..9ad3e3c 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
@@ -91,7 +91,8 @@
         // be stale, if e.g. the app was uninstalled while the activity was destroyed.
         super.onCreate(null);
 
-        if (usePiaV2() && !isTv()) {
+        // TODO(b/318521110) Enable PIA v2 for archive dialog.
+        if (usePiaV2() && !isTv() && !isArchiveDialog(getIntent())) {
             Log.i(TAG, "Using Pia V2");
 
             boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
@@ -224,6 +225,11 @@
         showConfirmationDialog();
     }
 
+    private boolean isArchiveDialog(Intent intent) {
+        return (intent.getIntExtra(PackageInstaller.EXTRA_DELETE_FLAGS, 0)
+                & PackageManager.DELETE_ARCHIVE) != 0;
+    }
+
     /**
      * Parses specific {@link android.content.pm.PackageManager.DeleteFlags} from {@link Intent}
      * to archive an app if requested.
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index 2da8c8c..221ca4f 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -32,6 +32,7 @@
 import android.os.Bundle;
 import android.os.Flags;
 import android.os.Process;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
@@ -131,7 +132,7 @@
         final boolean isUpdate =
                 ((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
         final boolean isArchive =
-                android.content.pm.Flags.archiving() && (
+                isArchivingEnabled() && (
                         (dialogInfo.deleteFlags & PackageManager.DELETE_ARCHIVE) != 0);
         final UserHandle myUserHandle = Process.myUserHandle();
         UserManager userManager = getContext().getSystemService(UserManager.class);
@@ -242,6 +243,11 @@
         return dialogBuilder.create();
     }
 
+    private static boolean isArchivingEnabled() {
+        return android.content.pm.Flags.archiving()
+                || SystemProperties.getBoolean("pm.archiving.enabled", false);
+    }
+
     private boolean isCloneProfile(UserHandle userHandle) {
         UserManager customUserManager = getContext()
                 .createContextAsUser(UserHandle.of(userHandle.getIdentifier()), 0)
diff --git a/packages/SettingsLib/ActionBarShadow/Android.bp b/packages/SettingsLib/ActionBarShadow/Android.bp
index 6f94458..77cbb00 100644
--- a/packages/SettingsLib/ActionBarShadow/Android.bp
+++ b/packages/SettingsLib/ActionBarShadow/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibActionBarShadow",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
 
diff --git a/packages/SettingsLib/ActionButtonsPreference/Android.bp b/packages/SettingsLib/ActionButtonsPreference/Android.bp
index 1228555..c36b82d 100644
--- a/packages/SettingsLib/ActionButtonsPreference/Android.bp
+++ b/packages/SettingsLib/ActionButtonsPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibActionButtonsPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/ActivityEmbedding/Android.bp b/packages/SettingsLib/ActivityEmbedding/Android.bp
index 41de29a..838a9e5 100644
--- a/packages/SettingsLib/ActivityEmbedding/Android.bp
+++ b/packages/SettingsLib/ActivityEmbedding/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibActivityEmbedding",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
 
diff --git a/packages/SettingsLib/AdaptiveIcon/Android.bp b/packages/SettingsLib/AdaptiveIcon/Android.bp
index 044ba87..67b6fb5 100644
--- a/packages/SettingsLib/AdaptiveIcon/Android.bp
+++ b/packages/SettingsLib/AdaptiveIcon/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibAdaptiveIcon",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/AdaptiveIcon/lint-baseline.xml b/packages/SettingsLib/AdaptiveIcon/lint-baseline.xml
index 7f16517..8127e1a 100644
--- a/packages/SettingsLib/AdaptiveIcon/lint-baseline.xml
+++ b/packages/SettingsLib/AdaptiveIcon/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NewApi"
@@ -8,7 +8,7 @@
         errorLine2="                                 ~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIcon.java"
-            line="78"
+            line="79"
             column="34"/>
     </issue>
 
@@ -19,7 +19,7 @@
         errorLine2="                                   ~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIcon.java"
-            line="90"
+            line="91"
             column="36"/>
     </issue>
 
@@ -30,7 +30,7 @@
         errorLine2="                                             ~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java"
-            line="43"
+            line="45"
             column="46"/>
     </issue>
 
@@ -41,7 +41,7 @@
         errorLine2="        ~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java"
-            line="65"
+            line="67"
             column="9"/>
     </issue>
 
@@ -52,7 +52,7 @@
         errorLine2="        ~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java"
-            line="72"
+            line="74"
             column="9"/>
     </issue>
 
@@ -63,7 +63,7 @@
         errorLine2="        ~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java"
-            line="80"
+            line="82"
             column="9"/>
     </issue>
 
@@ -74,8 +74,8 @@
         errorLine2="                         ~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java"
-            line="105"
+            line="107"
             column="26"/>
     </issue>
 
-</issues>
+</issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index ffe3d1d..bd56aae 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -9,10 +9,14 @@
 
 android_library {
     name: "SettingsLib",
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     static_libs: [
         "androidx.localbroadcastmanager_localbroadcastmanager",
         "androidx.room_room-runtime",
+        "androidx.sqlite_sqlite",
         "zxing-core",
         "guava",
 
@@ -60,8 +64,15 @@
         "src/**/*.java",
         "src/**/*.kt",
     ],
+}
+
+// defaults for lint option
+java_defaults {
+    name: "SettingsLintDefaults",
     lint: {
-        baseline_filename: "lint-baseline.xml",
+        extra_check_modules: [
+            "SettingsLibLintChecker",
+        ],
     },
 }
 
diff --git a/packages/SettingsLib/AppPreference/Android.bp b/packages/SettingsLib/AppPreference/Android.bp
index 69b9d44..c5b2ef6 100644
--- a/packages/SettingsLib/AppPreference/Android.bp
+++ b/packages/SettingsLib/AppPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibAppPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/BannerMessagePreference/Android.bp b/packages/SettingsLib/BannerMessagePreference/Android.bp
index da91344..07290de 100644
--- a/packages/SettingsLib/BannerMessagePreference/Android.bp
+++ b/packages/SettingsLib/BannerMessagePreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibBannerMessagePreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/BarChartPreference/Android.bp b/packages/SettingsLib/BarChartPreference/Android.bp
index be1e0cf..448ed56 100644
--- a/packages/SettingsLib/BarChartPreference/Android.bp
+++ b/packages/SettingsLib/BarChartPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibBarChartPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/ButtonPreference/Android.bp b/packages/SettingsLib/ButtonPreference/Android.bp
index 35572fad..0382829 100644
--- a/packages/SettingsLib/ButtonPreference/Android.bp
+++ b/packages/SettingsLib/ButtonPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibButtonPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
index 70f7554..87ec0b8 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibCollapsingToolbarBaseActivity",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/DisplayUtils/Android.bp b/packages/SettingsLib/DisplayUtils/Android.bp
index eab35a1..279bb70 100644
--- a/packages/SettingsLib/DisplayUtils/Android.bp
+++ b/packages/SettingsLib/DisplayUtils/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibDisplayUtils",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
 
diff --git a/packages/SettingsLib/EmergencyNumber/lint-baseline.xml b/packages/SettingsLib/EmergencyNumber/lint-baseline.xml
index e9c687f..13bf5f5 100644
--- a/packages/SettingsLib/EmergencyNumber/lint-baseline.xml
+++ b/packages/SettingsLib/EmergencyNumber/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NewApi"
@@ -8,7 +8,7 @@
         errorLine2="                                        ~~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
-            line="77"
+            line="81"
             column="41"/>
     </issue>
 
@@ -19,7 +19,7 @@
         errorLine2="                                            ~~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
-            line="78"
+            line="82"
             column="45"/>
     </issue>
 
@@ -30,18 +30,18 @@
         errorLine2="                                                             ~~~~~~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
-            line="78"
+            line="82"
             column="62"/>
     </issue>
 
     <issue
         id="NewApi"
         message="Call requires API level 29 (current min is 21): `android.telephony.TelephonyManager#getEmergencyNumberList`"
-        errorLine1="        Map&lt;Integer, List&lt;EmergencyNumber>> allLists = mTelephonyManager.getEmergencyNumberList("
+        errorLine1="        Map&lt;Integer, List&lt;EmergencyNumber&gt;&gt; allLists = mTelephonyManager.getEmergencyNumberList("
         errorLine2="                                                                         ~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
-            line="173"
+            line="177"
             column="74"/>
     </issue>
 
@@ -52,7 +52,7 @@
         errorLine2="                                        ~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
-            line="196"
+            line="200"
             column="41"/>
     </issue>
 
@@ -63,7 +63,7 @@
         errorLine2="                                                                    ~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
-            line="219"
+            line="223"
             column="69"/>
     </issue>
 
@@ -74,7 +74,7 @@
         errorLine2="                                        ~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
-            line="234"
+            line="238"
             column="41"/>
     </issue>
 
@@ -85,8 +85,8 @@
         errorLine2="                                                   ~~~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java"
-            line="251"
+            line="255"
             column="52"/>
     </issue>
 
-</issues>
+</issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/EntityHeaderWidgets/Android.bp b/packages/SettingsLib/EntityHeaderWidgets/Android.bp
index 17b662c..83f81c6 100644
--- a/packages/SettingsLib/EntityHeaderWidgets/Android.bp
+++ b/packages/SettingsLib/EntityHeaderWidgets/Android.bp
@@ -10,13 +10,16 @@
 android_library {
     name: "SettingsLibEntityHeaderWidgets",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
 
     static_libs: [
-          "androidx.annotation_annotation",
-          "SettingsLibSettingsTheme"
+        "androidx.annotation_annotation",
+        "SettingsLibSettingsTheme",
     ],
 
     sdk_version: "system_current",
diff --git a/packages/SettingsLib/FooterPreference/Android.bp b/packages/SettingsLib/FooterPreference/Android.bp
index b45cd65..d1ad80d 100644
--- a/packages/SettingsLib/FooterPreference/Android.bp
+++ b/packages/SettingsLib/FooterPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibFooterPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/HelpUtils/Android.bp b/packages/SettingsLib/HelpUtils/Android.bp
index 041fce2..284106e 100644
--- a/packages/SettingsLib/HelpUtils/Android.bp
+++ b/packages/SettingsLib/HelpUtils/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibHelpUtils",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/IllustrationPreference/Android.bp b/packages/SettingsLib/IllustrationPreference/Android.bp
index 4d4759b..6407810 100644
--- a/packages/SettingsLib/IllustrationPreference/Android.bp
+++ b/packages/SettingsLib/IllustrationPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibIllustrationPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/LayoutPreference/Android.bp b/packages/SettingsLib/LayoutPreference/Android.bp
index 53ded23..8cf636a 100644
--- a/packages/SettingsLib/LayoutPreference/Android.bp
+++ b/packages/SettingsLib/LayoutPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibLayoutPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp
index d9f74da..b984aaf 100644
--- a/packages/SettingsLib/MainSwitchPreference/Android.bp
+++ b/packages/SettingsLib/MainSwitchPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibMainSwitchPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
@@ -28,7 +31,4 @@
         "com.android.extservices",
         "com.android.healthfitness",
     ],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
diff --git a/packages/SettingsLib/MainSwitchPreference/lint-baseline.xml b/packages/SettingsLib/MainSwitchPreference/lint-baseline.xml
deleted file mode 100644
index cfa64a4..0000000
--- a/packages/SettingsLib/MainSwitchPreference/lint-baseline.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
-
-    <issue
-        id="NewApi"
-        message="`@android:dimen/config_restrictedIconSize` requires API level 29 (current min is 28)"
-        errorLine1='    &lt;dimen name="settingslib_restricted_icon_size"&gt;@android:dimen/config_restrictedIconSize&lt;/dimen&gt;'
-        errorLine2="                                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml"
-            line="21"
-            column="52"/>
-    </issue>
-
-</issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/ProfileSelector/Android.bp b/packages/SettingsLib/ProfileSelector/Android.bp
index 155ed2e..6dc07b2 100644
--- a/packages/SettingsLib/ProfileSelector/Android.bp
+++ b/packages/SettingsLib/ProfileSelector/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibProfileSelector",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/RestrictedLockUtils/Android.bp b/packages/SettingsLib/RestrictedLockUtils/Android.bp
index 3b04bd9..8d722eb 100644
--- a/packages/SettingsLib/RestrictedLockUtils/Android.bp
+++ b/packages/SettingsLib/RestrictedLockUtils/Android.bp
@@ -16,6 +16,9 @@
 android_library {
     name: "SettingsLibRestrictedLockUtils",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/RestrictedLockUtils/lint-baseline.xml b/packages/SettingsLib/RestrictedLockUtils/lint-baseline.xml
index 26d05a6..45a07fe 100644
--- a/packages/SettingsLib/RestrictedLockUtils/lint-baseline.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/lint-baseline.xml
@@ -1,38 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 24 (current min is 23): `android.os.UserHandle#of`"
-        errorLine1="        context.startActivityAsUser(intent, UserHandle.of(targetUserId));"
-        errorLine2="                                                       ~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java"
-            line="97"
-            column="56"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 24 (current min is 23): `android.os.UserHandle#of`"
-        errorLine1="        return um.getUserProfiles().contains(UserHandle.of(userId));"
-        errorLine2="                                                        ~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java"
-            line="140"
-            column="57"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 26 (current min is 23): `android.app.admin.DevicePolicyManager#getDeviceOwnerComponentOnAnyUser`"
-        errorLine1="            adminComponent = dpm.getDeviceOwnerComponentOnAnyUser();"
-        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java"
-            line="75"
-            column="34"/>
-    </issue>
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NewApi"
@@ -58,6 +25,28 @@
 
     <issue
         id="NewApi"
+        message="Call requires API level 26 (current min is 23): `android.app.admin.DevicePolicyManager#getDeviceOwnerComponentOnAnyUser`"
+        errorLine1="            adminComponent = dpm.getDeviceOwnerComponentOnAnyUser();"
+        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java"
+            line="75"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.os.UserHandle#of`"
+        errorLine1="        context.startActivityAsUser(intent, UserHandle.of(targetUserId));"
+        errorLine2="                                                       ~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java"
+            line="97"
+            column="56"/>
+    </issue>
+
+    <issue
+        id="NewApi"
         message="Call requires API level 29 (current min is 23): `android.content.Context#startActivityAsUser`"
         errorLine1="        context.startActivityAsUser(intent, UserHandle.of(targetUserId));"
         errorLine2="                ~~~~~~~~~~~~~~~~~~~">
@@ -67,4 +56,15 @@
             column="17"/>
     </issue>
 
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.os.UserHandle#of`"
+        errorLine1="        return um.getUserProfiles().contains(UserHandle.of(userId));"
+        errorLine2="                                                        ~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java"
+            line="120"
+            column="57"/>
+    </issue>
+
 </issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/SchedulesProvider/Android.bp b/packages/SettingsLib/SchedulesProvider/Android.bp
index 22e4e94..c0fc741e7 100644
--- a/packages/SettingsLib/SchedulesProvider/Android.bp
+++ b/packages/SettingsLib/SchedulesProvider/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibSchedulesProvider",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
 
diff --git a/packages/SettingsLib/SchedulesProvider/lint-baseline.xml b/packages/SettingsLib/SchedulesProvider/lint-baseline.xml
index 0744710..db6a882 100644
--- a/packages/SettingsLib/SchedulesProvider/lint-baseline.xml
+++ b/packages/SettingsLib/SchedulesProvider/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NewApi"
diff --git a/packages/SettingsLib/SearchProvider/Android.bp b/packages/SettingsLib/SearchProvider/Android.bp
index c385d38..61ed65c 100644
--- a/packages/SettingsLib/SearchProvider/Android.bp
+++ b/packages/SettingsLib/SearchProvider/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibSearchProvider",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
 
diff --git a/packages/SettingsLib/SearchProvider/lint-baseline.xml b/packages/SettingsLib/SearchProvider/lint-baseline.xml
index 53346e0..3cfca1d 100644
--- a/packages/SettingsLib/SearchProvider/lint-baseline.xml
+++ b/packages/SettingsLib/SearchProvider/lint-baseline.xml
@@ -1,27 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 23 (current min is 21): `new android.provider.SearchIndexableResource`"
-        errorLine1="            super("
-        errorLine2="            ~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
-            line="107"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 23 (current min is 21): `android.provider.SearchIndexableResource`"
-        errorLine1="    public static final class SearchIndexableIntentResource extends SearchIndexableResource {"
-        errorLine2="                                                                    ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
-            line="97"
-            column="69"/>
-    </issue>
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NewApi"
@@ -36,6 +14,39 @@
 
     <issue
         id="NewApi"
+        message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexablesContract#INDEXABLES_XML_RES_COLUMNS`"
+        errorLine1="        final MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);"
+        errorLine2="                                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
+            line="45"
+            column="54"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableData#rank`"
+        errorLine1="                    .add(XmlResource.COLUMN_RANK, indexableResource.rank)"
+        errorLine2="                                                  ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
+            line="50"
+            column="51"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableResource#xmlResId`"
+        errorLine1="                    .add(XmlResource.COLUMN_XML_RESID, indexableResource.xmlResId)"
+        errorLine2="                                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
+            line="51"
+            column="56"/>
+    </issue>
+
+    <issue
+        id="NewApi"
         message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableData#className`"
         errorLine1="                    .add(XmlResource.COLUMN_CLASS_NAME, indexableResource.className)"
         errorLine2="                                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -58,6 +69,39 @@
 
     <issue
         id="NewApi"
+        message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableData#intentTargetClass`"
+        errorLine1="                            indexableResource.intentTargetClass);"
+        errorLine2="                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
+            line="56"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Class requires API level 23 (current min is 21): `android.provider.SearchIndexableResource`"
+        errorLine1="    public static final class SearchIndexableIntentResource extends SearchIndexableResource {"
+        errorLine2="                                                                    ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
+            line="97"
+            column="69"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 23 (current min is 21): `new android.provider.SearchIndexableResource`"
+        errorLine1="            super("
+        errorLine2="            ~~~~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
+            line="107"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="NewApi"
         message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableData#intentAction`"
         errorLine1='                this.intentAction = "android.intent.action.MAIN";'
         errorLine2="                ~~~~~~~~~~~~~~~~~">
@@ -81,17 +125,6 @@
     <issue
         id="NewApi"
         message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableData#intentTargetClass`"
-        errorLine1="                            indexableResource.intentTargetClass);"
-        errorLine2="                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
-            line="56"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableData#intentTargetClass`"
         errorLine1="            this.intentTargetClass = className;"
         errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -100,37 +133,4 @@
             column="13"/>
     </issue>
 
-    <issue
-        id="NewApi"
-        message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableData#rank`"
-        errorLine1="                    .add(XmlResource.COLUMN_RANK, indexableResource.rank)"
-        errorLine2="                                                  ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
-            line="50"
-            column="51"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexableResource#xmlResId`"
-        errorLine1="                    .add(XmlResource.COLUMN_XML_RESID, indexableResource.xmlResId)"
-        errorLine2="                                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
-            line="51"
-            column="56"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 23 (current min is 21): `android.provider.SearchIndexablesContract#INDEXABLES_XML_RES_COLUMNS`"
-        errorLine1="        final MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);"
-        errorLine2="                                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/SearchProvider/src/com/android/settingslib/searchprovider/SettingsXmlIndexProvider.java"
-            line="45"
-            column="54"/>
-    </issue>
-
 </issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
index 702387e..2fe446d 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibSelectorWithWidgetPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v33/preference_selector_with_widget.xml b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v33/preference_selector_with_widget.xml
index 0b27464..0764609 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v33/preference_selector_with_widget.xml
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v33/preference_selector_with_widget.xml
@@ -66,6 +66,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:maxLines="2"
+            android:ellipsize="end"
             android:hyphenationFrequency="normalFast"
             android:lineBreakWordStyle="phrase"
             android:textAppearance="?android:attr/textAppearanceListItem"/>
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout/preference_selector_with_widget.xml b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout/preference_selector_with_widget.xml
index 8bb56ff..4f1a910 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout/preference_selector_with_widget.xml
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout/preference_selector_with_widget.xml
@@ -66,6 +66,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:maxLines="2"
+            android:ellipsize="end"
             android:textAppearance="?android:attr/textAppearanceListItem"/>
 
         <LinearLayout
diff --git a/packages/SettingsLib/SettingsSpinner/Android.bp b/packages/SettingsLib/SettingsSpinner/Android.bp
index 0eec505..8fed61f 100644
--- a/packages/SettingsLib/SettingsSpinner/Android.bp
+++ b/packages/SettingsLib/SettingsSpinner/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibSettingsSpinner",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/SettingsTransition/Android.bp b/packages/SettingsLib/SettingsTransition/Android.bp
index 06493c0..e04af6c 100644
--- a/packages/SettingsLib/SettingsTransition/Android.bp
+++ b/packages/SettingsLib/SettingsTransition/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibSettingsTransition",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
 
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 8b136da..6f9556f 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
 
 allprojects {
     extra["androidTop"] = androidTop
-    extra["jetpackComposeVersion"] = "1.6.0-beta02"
+    extra["jetpackComposeVersion"] = "1.6.0-rc01"
 }
 
 subprojects {
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index 965fdcf..df5644b 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -73,6 +73,10 @@
             android:authorities="com.android.spa.gallery.debug.provider"
             android:exported="false">
         </provider>
-
+        <activity
+            android:name="com.android.settingslib.spa.gallery.GalleryDialogActivity"
+            android:exported="true"
+            android:theme="@style/Theme.SpaLib.Dialog">
+        </activity>
     </application>
 </manifest>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDialogActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDialogActivity.kt
new file mode 100644
index 0000000..e22ed35
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDialogActivity.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import com.android.settingslib.spa.SpaBaseDialogActivity
+import com.android.settingslib.spa.widget.dialog.AlertDialogButton
+import com.android.settingslib.spa.widget.dialog.SettingsAlertDialogWithIcon
+
+class GalleryDialogActivity : SpaBaseDialogActivity() {
+    @Composable
+    override fun Content() {
+        SettingsAlertDialogWithIcon(
+            onDismissRequest = { finish() },
+            confirmButton = AlertDialogButton("confirm") { finish() },
+            dismissButton = AlertDialogButton("dismiss") { finish() },
+            title = "title",
+            text = {
+                Text(
+                    "text",
+                    modifier = Modifier.fillMaxWidth(),
+                    textAlign = TextAlign.Center
+                )
+            }
+        )
+    }
+}
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 9703c347..1f78a9c 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
 #
 
 [versions]
-agp = "8.2.0"
+agp = "8.2.1"
 compose-compiler = "1.5.1"
 dexmaker-mockito = "2.28.3"
 jvm = "17"
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 7eccfe5..618dc37 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -57,13 +57,13 @@
     api("androidx.slice:slice-builders:1.1.0-alpha02")
     api("androidx.slice:slice-core:1.1.0-alpha02")
     api("androidx.slice:slice-view:1.1.0-alpha02")
-    api("androidx.compose.material3:material3:1.2.0-alpha12")
+    api("androidx.compose.material3:material3:1.2.0-beta02")
     api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
     api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
     api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
     api("androidx.lifecycle:lifecycle-livedata-ktx")
     api("androidx.lifecycle:lifecycle-runtime-compose")
-    api("androidx.navigation:navigation-compose:2.7.4")
+    api("androidx.navigation:navigation-compose:2.7.6")
     api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
     api("com.google.android.material:material:1.7.0-alpha03")
     debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/res/values/themes.xml b/packages/SettingsLib/Spa/spa/res/values/themes.xml
index 25846ec..4b5a9bc 100644
--- a/packages/SettingsLib/Spa/spa/res/values/themes.xml
+++ b/packages/SettingsLib/Spa/spa/res/values/themes.xml
@@ -22,4 +22,6 @@
         <item name="android:windowActionBar">false</item>
         <item name="android:windowNoTitle">true</item>
     </style>
+
+    <style name="Theme.SpaLib.Dialog" parent="Theme.Material3.DayNight.Dialog"/>
 </resources>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaBaseDialogActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaBaseDialogActivity.kt
new file mode 100644
index 0000000..dfb780a
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaBaseDialogActivity.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.common.LogCategory
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+abstract class SpaBaseDialogActivity : ComponentActivity() {
+    private val spaEnvironment get() = SpaEnvironmentFactory.instance
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
+        setContent {
+            SettingsTheme {
+                Content()
+            }
+        }
+    }
+
+    @Composable
+    abstract fun Content()
+
+    companion object {
+        private const val TAG = "SpaBaseDialogActivity"
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index 5605485..da1ee77 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -22,12 +22,14 @@
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
 import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Modifier
 import androidx.core.view.WindowCompat
 import androidx.navigation.NavBackStackEntry
 import androidx.navigation.NavGraph.Companion.findStartDestination
@@ -133,6 +135,7 @@
     NavHost(
         navController = navController,
         startDestination = NullPageProvider.name,
+        modifier = Modifier.fillMaxSize(),
     ) {
         composable(NullPageProvider.name) {}
         for (spp in allProvider) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 81bee5e..0281ab8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -86,7 +86,7 @@
     )
 }
 
-object NullPageProvider : SettingsPageProvider {
+internal object NullPageProvider : SettingsPageProvider {
     override val name = NULL_PAGE_NAME
 }
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt
index 192b125..93ad644 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt
@@ -30,6 +30,7 @@
 import androidx.navigation.NavDeepLink
 import androidx.navigation.NavGraphBuilder
 import androidx.navigation.compose.composable
+import com.android.settingslib.spa.framework.common.NullPageProvider
 
 /**
  * Add the [Composable] to the [NavGraphBuilder] with animation
@@ -49,11 +50,13 @@
     arguments = arguments,
     deepLinks = deepLinks,
     enterTransition = {
-        slideIntoContainer(
-            towards = AnimatedContentTransitionScope.SlideDirection.Start,
-            animationSpec = slideInEffect,
-            initialOffset = offsetFunc,
-        ) + fadeIn(animationSpec = fadeInEffect)
+        if (initialState.destination.route != NullPageProvider.name) {
+            slideIntoContainer(
+                towards = AnimatedContentTransitionScope.SlideDirection.Start,
+                animationSpec = slideInEffect,
+                initialOffset = offsetFunc,
+            ) + fadeIn(animationSpec = fadeInEffect)
+        } else null
     },
     exitTransition = {
         slideOutOfContainer(
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
index 207c174..de080e3 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
@@ -99,11 +99,11 @@
 }
 
 @Composable
-private fun getDialogWidth(): Dp {
+fun getDialogWidth(): Dp {
     val configuration = LocalConfiguration.current
     return configuration.screenWidthDp.dp * when (configuration.orientation) {
-        Configuration.ORIENTATION_LANDSCAPE -> 0.6f
-        else -> 0.8f
+        Configuration.ORIENTATION_LANDSCAPE -> 0.65f
+        else -> 0.85f
     }
 }
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
new file mode 100644
index 0000000..1695e4f
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.dialog
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.WarningAmber
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.window.DialogProperties
+
+@Composable
+fun SettingsAlertDialogWithIcon(
+    onDismissRequest: () -> Unit,
+    confirmButton: AlertDialogButton?,
+    dismissButton: AlertDialogButton?,
+    title: String?,
+    text: @Composable (() -> Unit)?,
+) {
+    AlertDialog(
+        onDismissRequest = onDismissRequest,
+        icon = { Icon(Icons.Default.WarningAmber, contentDescription = null) },
+        modifier = Modifier.width(getDialogWidth()),
+        confirmButton = {
+            confirmButton?.let {
+                Button(
+                    onClick = {
+                        it.onClick()
+                    },
+                ) {
+                    Text(it.text)
+                }
+            }
+        },
+        dismissButton = dismissButton?.let {
+            {
+                OutlinedButton(
+                    onClick = {
+                        it.onClick()
+                    },
+                ) {
+                    Text(it.text)
+                }
+            }
+        },
+        title = title?.let {
+            {
+                Text(
+                    it,
+                    modifier = Modifier.fillMaxWidth(),
+                    textAlign = TextAlign.Center
+                )
+            }
+        },
+        text = text?.let {
+            {
+                Column(Modifier.verticalScroll(rememberScrollState())) {
+                    text()
+                }
+            }
+        },
+        properties = DialogProperties(usePlatformDefaultWidth = false),
+    )
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
index ba8e354..b34c310 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
@@ -54,16 +54,18 @@
     selectedOptionsState: SnapshotStateList<Int>,
     emptyVal: String = "",
     enabled: Boolean,
+    errorMessage: String? = null,
     onSelectedOptionStateChange: () -> Unit,
 ) {
     var dropDownWidth by remember { mutableIntStateOf(0) }
     var expanded by remember { mutableStateOf(false) }
+    val allIndex = options.indexOf("*")
     ExposedDropdownMenuBox(
         expanded = expanded,
         onExpandedChange = { expanded = it },
         modifier = Modifier
             .width(350.dp)
-            .padding(SettingsDimension.menuFieldPadding)
+            .padding(SettingsDimension.textFieldPadding)
             .onSizeChanged { dropDownWidth = it.width },
     ) {
         OutlinedTextField(
@@ -72,7 +74,8 @@
                 .menuAnchor()
                 .fillMaxWidth(),
             value = if (selectedOptionsState.size == 0) emptyVal
-                    else selectedOptionsState.joinToString { options[it] },
+            else if (selectedOptionsState.contains(allIndex)) "*"
+            else selectedOptionsState.joinToString { options[it] },
             onValueChange = {},
             label = { Text(text = label) },
             trailingIcon = {
@@ -81,7 +84,13 @@
                 )
             },
             readOnly = true,
-            enabled = enabled
+            enabled = enabled,
+            isError = errorMessage != null,
+            supportingText = {
+                if (errorMessage != null) {
+                    Text(text = errorMessage)
+                }
+            }
         )
         if (options.isNotEmpty()) {
             ExposedDropdownMenu(
@@ -98,9 +107,17 @@
                             .fillMaxWidth(),
                         onClick = {
                             if (selectedOptionsState.contains(index)) {
-                                selectedOptionsState.remove(
-                                    index
-                                )
+                                if (index == allIndex)
+                                    selectedOptionsState.clear()
+                                else {
+                                    selectedOptionsState.remove(
+                                        index
+                                    )
+                                    if (selectedOptionsState.contains(allIndex))
+                                        selectedOptionsState.remove(
+                                            allIndex
+                                        )
+                                }
                             } else {
                                 selectedOptionsState.add(
                                     index
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index 3a0e51b..4f61966 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -64,6 +64,8 @@
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.heading
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Constraints
@@ -129,7 +131,8 @@
         modifier = Modifier.padding(
             start = SettingsDimension.itemPaddingAround,
             end = SettingsDimension.itemPaddingEnd,
-        ),
+        )
+        .semantics { heading() },
         overflow = TextOverflow.Ellipsis,
         maxLines = maxLines,
     )
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt
index 84fea15..d92a863 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt
@@ -35,8 +35,8 @@
 /**
  * Scope for the children of [MoreOptionsAction].
  */
-interface MoreOptionsScope {
-    fun dismiss()
+abstract class MoreOptionsScope {
+    abstract fun dismiss()
 
     @Composable
     fun MenuItem(text: String, enabled: Boolean = true, onClick: () -> Unit) {
@@ -60,7 +60,7 @@
     val onDismiss = { expanded = false }
     DropdownMenu(expanded = expanded, onDismissRequest = onDismiss) {
         val moreOptionsScope = remember(this) {
-            object : MoreOptionsScope {
+            object : MoreOptionsScope() {
                 override fun dismiss() {
                     onDismiss()
                 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
index a9974dc..514ad669 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
@@ -39,6 +39,9 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -68,6 +71,7 @@
     ) {
         val contentPadding = PaddingValues(horizontal = SettingsDimension.itemPaddingEnd)
         Button(
+            modifier = Modifier.semantics { role = Role.DropdownList },
             onClick = { expanded = true },
             colors = ButtonDefaults.buttonColors(
                 containerColor = SettingsTheme.colorScheme.spinnerHeaderContainer,
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/LiveDataTestUtil.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/LiveDataTestUtil.kt
deleted file mode 100644
index dddda55..0000000
--- a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/LiveDataTestUtil.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.testutils
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.Observer
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
-import java.util.concurrent.TimeoutException
-
-fun <T> LiveData<T>.getOrAwaitValue(
-    timeout: Long = 1,
-    timeUnit: TimeUnit = TimeUnit.SECONDS,
-    afterObserve: () -> Unit = {},
-): T? {
-    var data: T? = null
-    val latch = CountDownLatch(1)
-    val observer = Observer<T> { newData ->
-        data = newData
-        latch.countDown()
-    }
-    this.observeForever(observer)
-
-    afterObserve()
-
-    try {
-        // Don't wait indefinitely if the LiveData is not set.
-        if (!latch.await(timeout, timeUnit)) {
-            throw TimeoutException("LiveData value was never set.")
-        }
-    } finally {
-        this.removeObserver(observer)
-    }
-
-    return data
-}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt
index e099f11..0a424bc 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt
@@ -37,10 +37,11 @@
 fun Context.broadcastReceiverFlow(intentFilter: IntentFilter): Flow<Intent> = callbackFlow {
     val broadcastReceiver = object : BroadcastReceiver() {
         override fun onReceive(context: Context, intent: Intent) {
+            Log.d(TAG, "onReceive: $intent")
             trySend(intent)
         }
     }
-    registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
+    registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_VISIBLE_TO_INSTANT_APPS)
 
     awaitClose { unregisterReceiver(broadcastReceiver) }
 }.catch { e ->
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index abeffec..0a98791 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -24,6 +24,7 @@
 import android.content.pm.PackageManager
 import android.content.pm.PackageManager.ApplicationInfoFlags
 import android.content.pm.ResolveInfo
+import android.os.SystemProperties
 import com.android.internal.R
 import com.android.settingslib.spaprivileged.framework.common.userManager
 import kotlinx.coroutines.async
@@ -110,7 +111,7 @@
     ): List<ApplicationInfo> {
         val disabledComponentsFlag = (PackageManager.MATCH_DISABLED_COMPONENTS or
             PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
-        val archivedPackagesFlag: Long = if (featureFlags.archiving())
+        val archivedPackagesFlag: Long = if (isArchivingEnabled(featureFlags))
             PackageManager.MATCH_ARCHIVED_PACKAGES else 0L
         val regularFlags = ApplicationInfoFlags.of(
             disabledComponentsFlag or
@@ -148,6 +149,9 @@
         }
     }
 
+    private fun isArchivingEnabled(featureFlags: FeatureFlags) =
+            featureFlags.archiving() || SystemProperties.getBoolean("pm.archiving.enabled", false)
+
     override fun showSystemPredicate(
         userIdFlow: Flow<Int>,
         showSystemFlow: Flow<Boolean>,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
index 81a8b324..cea3d13 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
@@ -37,6 +37,7 @@
 interface AppRepository {
     fun loadLabel(app: ApplicationInfo): String
 
+    @Suppress("ABSTRACT_COMPOSABLE_DEFAULT_PARAMETER_VALUE")
     @Composable
     fun produceLabel(app: ApplicationInfo, isClonedAppPage: Boolean = false): State<String> {
         val context = LocalContext.current
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PermissionsChangedFlow.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PermissionsChangedFlow.kt
new file mode 100644
index 0000000..367244a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PermissionsChangedFlow.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.model.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import com.android.settingslib.spaprivileged.framework.common.asUser
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flowOn
+
+/**
+ * Creates an instance of a cold Flow for permissions changed callback of given [app].
+ *
+ * An initial element will be always sent.
+ */
+fun Context.permissionsChangedFlow(app: ApplicationInfo) = callbackFlow {
+    val userPackageManager = asUser(app.userHandle).packageManager
+
+    val onPermissionsChangedListener = PackageManager.OnPermissionsChangedListener { uid ->
+        if (uid == app.uid) trySend(Unit)
+    }
+    userPackageManager.addOnPermissionsChangeListener(onPermissionsChangedListener)
+    trySend(Unit)
+
+    awaitClose {
+        userPackageManager.removeOnPermissionsChangeListener(onPermissionsChangedListener)
+    }
+}.conflate().flowOn(Dispatchers.Default)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 1c830c1..74b556e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -162,6 +162,7 @@
                 uid = checkNotNull(applicationInfo).uid,
                 packageName = packageName) })
         RestrictedSwitchPreference(switchModel, restrictions, restrictionsProviderFactory)
+        InfoPageAdditionalContent(record, isAllowed)
     }
 }
 
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
index 916d83a..3f7a852 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
@@ -77,6 +77,9 @@
      * Sets whether the permission is allowed for the given app.
      */
     fun setAllowed(record: T, newAllowed: Boolean)
+
+    @Composable
+    fun InfoPageAdditionalContent(record: T, isAllowed: () -> Boolean?){}
 }
 
 interface TogglePermissionAppListProvider {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
index eef5225..772f925 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
@@ -43,7 +43,7 @@
 
     private val context = mock<Context> {
         on {
-            registerReceiver(any(), eq(INTENT_FILTER), eq(Context.RECEIVER_NOT_EXPORTED))
+            registerReceiver(any(), eq(INTENT_FILTER), eq(Context.RECEIVER_VISIBLE_TO_INSTANT_APPS))
         } doAnswer {
             registeredBroadcastReceiver = it.arguments[0] as BroadcastReceiver
             null
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PermissionsChangedFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PermissionsChangedFlowTest.kt
new file mode 100644
index 0000000..31522c12
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PermissionsChangedFlowTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.model.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.android.settingslib.spa.testutils.toListWithTimeout
+import com.android.settingslib.spaprivileged.framework.common.asUser
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+
+@RunWith(AndroidJUnit4::class)
+class PermissionsChangedFlowTest {
+
+    private var onPermissionsChangedListener: PackageManager.OnPermissionsChangedListener? = null
+
+    private val mockPackageManager = mock<PackageManager> {
+        on { addOnPermissionsChangeListener(any()) } doAnswer {
+            onPermissionsChangedListener =
+                it.arguments[0] as PackageManager.OnPermissionsChangedListener
+        }
+    }
+
+    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+        on { asUser(APP.userHandle) } doReturn mock
+        on { packageManager } doReturn mockPackageManager
+    }
+
+    @Test
+    fun permissionsChangedFlow_sendInitialValueTrue() = runBlocking {
+        val flow = context.permissionsChangedFlow(APP)
+
+        assertThat(flow.firstWithTimeoutOrNull()).isNotNull()
+    }
+
+    @Test
+    fun permissionsChangedFlow_collectChanged_getTwo() = runBlocking {
+        val listDeferred = async {
+            context.permissionsChangedFlow(APP).toListWithTimeout()
+        }
+        delay(100)
+
+        onPermissionsChangedListener?.onPermissionsChanged(APP.uid)
+
+        assertThat(listDeferred.await()).hasSize(2)
+    }
+
+    private companion object {
+        val APP = ApplicationInfo().apply {
+            packageName = "package.name"
+            uid = 10000
+        }
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
index 983284c..2ccf323 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
@@ -130,7 +130,7 @@
     }
 
     private fun setContent(restrictions: Restrictions) {
-        val fakeMoreOptionsScope = object : MoreOptionsScope {
+        val fakeMoreOptionsScope = object : MoreOptionsScope() {
             override fun dismiss() {}
         }
         composeTestRule.setContent {
diff --git a/packages/SettingsLib/Tile/Android.bp b/packages/SettingsLib/Tile/Android.bp
index 19c59dd..54b9748 100644
--- a/packages/SettingsLib/Tile/Android.bp
+++ b/packages/SettingsLib/Tile/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibTile",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
 
diff --git a/packages/SettingsLib/Tile/lint-baseline.xml b/packages/SettingsLib/Tile/lint-baseline.xml
index 326ec0d..56b1bca 100644
--- a/packages/SettingsLib/Tile/lint-baseline.xml
+++ b/packages/SettingsLib/Tile/lint-baseline.xml
@@ -1,55 +1,59 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NewApi"
-        message="Call requires API level 24 (current min is 21): `java.lang.Iterable#forEach`"
-        errorLine1="        controllers.forEach(controller -&gt; {"
-        errorLine2="                    ~~~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/SwitchesProvider.java"
-            line="79"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 23 (current min is 21): `android.graphics.drawable.Icon#createWithResource`">
+        message="Call requires API level 29 (current min is 21): `android.os.Parcel#writeBoolean`"
+        errorLine1="        dest.writeBoolean(this instanceof ProviderTile);"
+        errorLine2="             ~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java"
-            line="312"/>
+            line="114"
+            column="14"/>
     </issue>
 
     <issue
         id="NewApi"
-        message="Call requires API level 23 (current min is 21): `android.graphics.drawable.Icon#setTint`">
+        message="Call requires API level 23 (current min is 21): `android.graphics.drawable.Icon#createWithResource`"
+        errorLine1="            final Icon icon = Icon.createWithResource(componentInfo.packageName, iconResId);"
+        errorLine2="                                   ~~~~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java"
-            line="318"/>
+            line="326"
+            column="36"/>
     </issue>
 
     <issue
         id="NewApi"
-        message="Call requires API level 29 (current min is 21): `android.os.Parcel#readBoolean`">
+        message="Call requires API level 23 (current min is 21): `android.graphics.drawable.Icon#setTint`"
+        errorLine1="                icon.setTint(tintColor);"
+        errorLine2="                     ~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java"
-            line="373"/>
+            line="332"
+            column="22"/>
     </issue>
 
     <issue
         id="NewApi"
-        message="Call requires API level 29 (current min is 21): `android.os.Parcel#writeBoolean`">
+        message="Call requires API level 29 (current min is 21): `android.os.Parcel#readBoolean`"
+        errorLine1="            final boolean isProviderTile = source.readBoolean();"
+        errorLine2="                                                  ~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java"
-            line="108"/>
+            line="387"
+            column="51"/>
     </issue>
 
     <issue
         id="NewApi"
-        message="Call requires API level 31 (current min is 21): `android.content.Context#getAttributionSource`">
+        message="Call requires API level 31 (current min is 21): `android.content.Context#getAttributionSource`"
+        errorLine1="            return provider.call(context.getAttributionSource(),"
+        errorLine2="                                         ~~~~~~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java"
-            line="565"/>
+            line="601"
+            column="42"/>
     </issue>
 
 </issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/TopIntroPreference/Android.bp b/packages/SettingsLib/TopIntroPreference/Android.bp
index 77b7ac1..e70201b 100644
--- a/packages/SettingsLib/TopIntroPreference/Android.bp
+++ b/packages/SettingsLib/TopIntroPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibTopIntroPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/TwoTargetPreference/Android.bp b/packages/SettingsLib/TwoTargetPreference/Android.bp
index 5aa906e..70d9630 100644
--- a/packages/SettingsLib/TwoTargetPreference/Android.bp
+++ b/packages/SettingsLib/TwoTargetPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibTwoTargetPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/UsageProgressBarPreference/Android.bp b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
index 4cc90cc..0a83aab 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/Android.bp
+++ b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibUsageProgressBarPreference",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/Utils/Android.bp b/packages/SettingsLib/Utils/Android.bp
index d5a56c8..5d2aef7 100644
--- a/packages/SettingsLib/Utils/Android.bp
+++ b/packages/SettingsLib/Utils/Android.bp
@@ -10,6 +10,9 @@
 android_library {
     name: "SettingsLibUtils",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
 
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/Utils/lint-baseline.xml b/packages/SettingsLib/Utils/lint-baseline.xml
index 3fcd56c..2f6cc3a 100644
--- a/packages/SettingsLib/Utils/lint-baseline.xml
+++ b/packages/SettingsLib/Utils/lint-baseline.xml
@@ -1,26 +1,15 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NewApi"
-        message="Call requires API level 24 (current min is 21): `android.os.UserHandle#of`"
-        errorLine1="                            mContext.getPackageName(), 0, UserHandle.of(managedUserId)"
-        errorLine2="                                                                     ~~">
+        message="Call requires API level 24 (current min is 23): `android.os.UserManager#isManagedProfile`"
+        errorLine1="        return context.getSystemService(UserManager.class).isManagedProfile(userId)"
+        errorLine2="                                                           ~~~~~~~~~~~~~~~~">
         <location
-            file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
-            line="119"
-            column="70"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 24 (current min is 21): `android.os.UserHandle#of`"
-        errorLine1="                        intent, 0, UserHandle.of(managedUserId));"
-        errorLine2="                                              ~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
-            line="150"
-            column="47"/>
+            file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/applications/AppUtils.java"
+            line="62"
+            column="60"/>
     </issue>
 
     <issue
@@ -36,35 +25,13 @@
 
     <issue
         id="NewApi"
-        message="Call requires API level 24 (current min is 21): `android.os.UserManager#isManagedProfile`"
-        errorLine1="            if (mUserManager.isManagedProfile(id)) {"
-        errorLine2="                             ~~~~~~~~~~~~~~~~">
+        message="Call requires API level 29 (current min is 21): `android.content.Context#startActivityAsUser`"
+        errorLine1="            activityContext.startActivityAsUser(intent, UserHandle.of(userId));"
+        errorLine2="                            ~~~~~~~~~~~~~~~~~~~">
         <location
             file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
-            line="173"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 24 (current min is 23): `android.os.UserManager#isManagedProfile`"
-        errorLine1="        return context.getSystemService(UserManager.class).isManagedProfile(userId)"
-        errorLine2="                                                           ~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/applications/AppUtils.java"
-            line="62"
-            column="60"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 26 (current min is 21): `android.app.admin.DevicePolicyManager#getDeviceOwnerComponentOnAnyUser`"
-        errorLine1="        return mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser();"
-        errorLine2="                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
-            line="163"
-            column="37"/>
+            line="80"
+            column="29"/>
     </issue>
 
     <issue
@@ -80,13 +47,13 @@
 
     <issue
         id="NewApi"
-        message="Call requires API level 29 (current min is 21): `android.content.Context#startActivityAsUser`"
-        errorLine1="            activityContext.startActivityAsUser(intent, UserHandle.of(userId));"
-        errorLine2="                            ~~~~~~~~~~~~~~~~~~~">
+        message="Call requires API level 24 (current min is 21): `android.os.UserHandle#of`"
+        errorLine1="                            mContext.getPackageName(), 0, UserHandle.of(managedUserId)"
+        errorLine2="                                                                     ~~">
         <location
             file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
-            line="80"
-            column="29"/>
+            line="119"
+            column="70"/>
     </issue>
 
     <issue
@@ -102,6 +69,28 @@
 
     <issue
         id="NewApi"
+        message="Call requires API level 24 (current min is 21): `android.os.UserHandle#of`"
+        errorLine1="                        intent, 0, UserHandle.of(managedUserId));"
+        errorLine2="                                              ~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
+            line="150"
+            column="47"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 26 (current min is 21): `android.app.admin.DevicePolicyManager#getDeviceOwnerComponentOnAnyUser`"
+        errorLine1="        return mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser();"
+        errorLine2="                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
+            line="163"
+            column="37"/>
+    </issue>
+
+    <issue
+        id="NewApi"
         message="Call requires API level 30 (current min is 21): `android.os.UserManager#getAllProfiles`"
         errorLine1="        List&lt;UserHandle&gt; allProfiles = mUserManager.getAllProfiles();"
         errorLine2="                                                    ~~~~~~~~~~~~~~">
@@ -111,4 +100,15 @@
             column="53"/>
     </issue>
 
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 21): `android.os.UserManager#isManagedProfile`"
+        errorLine1="            if (mUserManager.isManagedProfile(id)) {"
+        errorLine2="                             ~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/Utils/src/com/android/settingslib/utils/WorkPolicyUtils.java"
+            line="173"
+            column="30"/>
+    </issue>
+
 </issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/lint-baseline.xml b/packages/SettingsLib/lint-baseline.xml
deleted file mode 100644
index d6a23fd..0000000
--- a/packages/SettingsLib/lint-baseline.xml
+++ /dev/null
@@ -1,204 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.bluetooth.BluetoothDevice#setAlias`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java"
-            line="584"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.wifi.WifiInfo#getSubscriptionId`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java"
-            line="248"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.wifi.WifiInfo#getSubscriptionId`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java"
-            line="278"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.wifi.WifiManager#registerSubsystemRestartTrackingCallback`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java"
-            line="201"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.net.wifi.WifiManager#unregisterSubsystemRestartTrackingCallback`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java"
-            line="208"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.os.UserManager#isUserForeground`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java"
-            line="78"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#isDataCapable`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/Utils.java"
-            line="498"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#isDataCapable`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java"
-            line="225"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#registerTelephonyCallback`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java"
-            line="215"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#registerTelephonyCallback`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
-            line="86"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#unregisterTelephonyCallback`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java"
-            line="222"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#unregisterTelephonyCallback`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
-            line="88"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 34 (current min is 30): `android.os.UserManager#isAdminUser`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java"
-            line="66"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 34 (current min is 30): `android.os.UserManager#isAdminUser`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/development/DevelopmentSettingsEnabler.java"
-            line="49"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 34 (current min is 30): `android.os.UserManager#isAdminUser`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java"
-            line="33"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.net.wifi.WifiManager.SubsystemRestartTrackingCallback`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java"
-            line="64"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
-            line="125"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.CarrierNetworkListener`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
-            line="124"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.DataActivityListener`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
-            line="123"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.DataConnectionStateListener`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
-            line="122"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.DisplayInfoListener`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
-            line="126"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.ServiceStateListener`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
-            line="120"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback.SignalStrengthsListener`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
-            line="121"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java"
-            line="79"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyCallback`">
-        <location
-            file="frameworks/base/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java"
-            line="119"/>
-    </issue>
-
-</issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/ic_external_display.xml b/packages/SettingsLib/res/drawable/ic_external_display.xml
new file mode 100644
index 0000000..de50de8
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_external_display.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="25dp"
+    android:viewportWidth="24"
+    android:viewportHeight="25">
+  <group>
+    <clip-path
+        android:pathData="M0,0.307h24v24h-24z"/>
+    <path
+        android:pathData="M8,21.307V19.307H10V17.307H4C3.45,17.307 2.975,17.115 2.575,16.732C2.192,16.332 2,15.857 2,15.307V5.307C2,4.757 2.192,4.29 2.575,3.907C2.975,3.507 3.45,3.307 4,3.307H20C20.55,3.307 21.017,3.507 21.4,3.907C21.8,4.29 22,4.757 22,5.307V15.307C22,15.857 21.8,16.332 21.4,16.732C21.017,17.115 20.55,17.307 20,17.307H14V19.307H16V21.307H8ZM4,15.307H20V5.307H4V15.307ZM4,15.307V5.307V15.307Z"
+        android:fillColor="#E5E3D6"/>
+  </group>
+</vector>
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 9ae4d0c..fd2c076 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Prenttoestel"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Oorfoon"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Randinvoertoestel"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Gehoortoestelle"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi af."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi is ontkoppel."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Kleurregstelling kan nuttig wees wanneer jy die volgende wil doen:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Om kleure meer akkuraat te sien&lt;/li&gt; &lt;li&gt;&amp;nbsp;Om kleure te verwyder om jou te help fokus&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Geneutraliseer deur <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Laaiproses is onderbreek om battery te beskerm"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Kontroleer tans laaibykomstigheid"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g> oor"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g> oor (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g> oor gegrond op jou gebruik"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Laai tans draadloos"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Laai tans"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Laai nie"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Gekoppel, laai nie"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Gekoppel, maar laai nie"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Gelaai"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Volgelaai"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Laai wag tans"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Beheer deur administrateur"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Beheer deur Beperkte Instellings"</string>
     <string name="disabled" msgid="8017887509554714950">"Gedeaktiveer"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index c2be887..dbd1af5 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"ምስል መስራት"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"የጆሮ ማዳመጫ"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"የግቤት መለዋወጫ"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"መስሚያ አጋዥ መሣሪያዎች"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"ብሉቱዝ"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wifi ጠፍቷል።"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"የWifi ግንኙነት ተቋርጧል።"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"የሚከተሉትን ለማድረግ ሲፈልጉ የቀለም ማስተካከያ ጠቃሚ ሊሆን ይችላል፡-&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;ቀለሞችን ይበልጥ በትክክል ለመመልከት&lt;/li&gt; &lt;li&gt;&amp;nbsp;ትኩረት ለማድረግ እንዲያግዙዎ ቀለሞችን ለማስወገድ&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"በ<xliff:g id="TITLE">%1$s</xliff:g> ተሽሯል"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ባትሪን ለመጠበቅ ኃይል መሙላት በይቆይ ላይ"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - የኃይል መሙያ መለዋወጫዎችን በመፈተሽ ላይ"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ገደማ ቀርቷል"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>) ገደማ ቀርቷል"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"በአጠቃቀምዎ መሠረት <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ገደማ ቀርቷል"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"በገመድ-አልባ ኃይል በመሙላት ላይ"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"ኃይል በመሙላት ላይ"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"ባትሪ እየሞላ አይደለም"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"ተገናኝቷል፣ ኃይል በመሙላት ላይ አይደለም"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"ተገናኝቷል፣ ነገር ግን ኃይል እየሞላ አይደለም"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"ባትሪ ሞልቷል"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"ሙሉ ለሙሉ ኃይል ተሞልቷል"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"ኃይል መሙላት በይቆይ ላይ"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"በአስተዳዳሪ ቁጥጥር የተደረገበት"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"በተገደበ ቅንብር ቁጥጥር የሚደረግበት"</string>
     <string name="disabled" msgid="8017887509554714950">"ቦዝኗል"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 6c31c76..5afd9dc 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"تصوير"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"السمّاعة"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"جهاز إدخال ملحق"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"سماعات أذن طبية"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"بلوتوث"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"‏تم إيقاف Wi-Fi."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"‏تم قطع اتصال Wi-Fi."</string>
@@ -218,8 +219,8 @@
     <item msgid="6946761421234586000">"400%"</item>
   </string-array>
     <string name="choose_profile" msgid="343803890897657450">"اختيار ملف شخصي"</string>
-    <string name="category_personal" msgid="6236798763159385225">"التطبيقات الشخصية"</string>
-    <string name="category_work" msgid="4014193632325996115">"تطبيقات العمل"</string>
+    <string name="category_personal" msgid="6236798763159385225">"الحسابات الشخصية"</string>
+    <string name="category_work" msgid="4014193632325996115">"حسابات العمل"</string>
     <string name="category_private" msgid="4244892185452788977">"ملف شخصي"</string>
     <string name="category_clone" msgid="1554511758987195974">"استنساخ"</string>
     <string name="development_settings_title" msgid="140296922921597393">"خيارات المطورين"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"‏يمكنك الاستفادة من ميزة \"تصحيح الألوان\" من أجل:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;رؤية الألوان بدقة أكبر&lt;/li&gt; &lt;li&gt;&amp;nbsp;إزالة الألوان لمساعدتك على التركيز&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"تم الاستبدال بـ <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"‫<xliff:g id="LEVEL">%1$s</xliff:g> - الشحن معلَّق لحماية البطارية"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"‫<xliff:g id="LEVEL">%1$s</xliff:g> - يجب فحص ملحق الشحن"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"يتبقى <xliff:g id="TIME_REMAINING">%1$s</xliff:g> تقريبًا."</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"يتبقى <xliff:g id="TIME_REMAINING">%1$s</xliff:g> تقريبًا (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"يتبقى <xliff:g id="TIME_REMAINING">%1$s</xliff:g> تقريبًا، بناءً على استخدامك"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"جارٍ الشحن لاسلكيًا"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"جارٍ الشحن"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"لا يتم الشحن"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"الجهاز متصل بالشاحن، ولا يتم الشحن."</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"الجهاز متصل ولكن لا يتم شحنه"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"مشحونة"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"البطارية مشحونة بالكامل."</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"الشحن معلَّق"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"إعدادات يتحكم فيها المشرف"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"يتحكّم فيه إعداد محظور"</string>
     <string name="disabled" msgid="8017887509554714950">"غير مفعّل"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index 8b06f5d..47dad1f 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"ইমেজিং"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"হেডফ\'ন"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"ইনপুট সম্পৰ্কীয় বাহ্য় ডিভাইচ"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"শ্ৰৱণ যন্ত্ৰ"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"ব্লুটুথ"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"ৱাই-ফাই অফ হৈ আছে।"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"ৱাইফাই সংযোগ বিচ্ছিন্ন হৈ আছে।"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"আপুনি এই কামসমূহ কৰিবলৈ বিচাৰিলে ৰং শুধৰণিৰ সুবিধাটো সহায়ক হ’ব পাৰে:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;ৰঙবোৰ অধিক সঠিককৈ দেখা পোৱা&lt;/li&gt; &lt;li&gt;&amp;nbsp;আপোনাক মনোযোগ দিয়াত সহায় কৰিবলৈ ৰং আঁতৰোৱা&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g>ৰ দ্বাৰা অগ্ৰাহ্য কৰা হৈছে"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - বেটাৰী সুৰক্ষিত কৰিবলৈ চাৰ্জিং স্থগিত ৰখা হৈছে"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - চাৰ্জিঙৰ আনুষংগিক বস্তু পৰীক্ষা কৰি থকা হৈছে"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"প্রায় <xliff:g id="TIME_REMAINING">%1$s</xliff:g> বাকী আছে"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"প্রায় <xliff:g id="TIME_REMAINING">%1$s</xliff:g> বাকী আছে (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"আপোনাৰ ব্যৱহাৰৰ ওপৰত ভিত্তি কৰি প্ৰায় <xliff:g id="TIME_REMAINING">%1$s</xliff:g> বাকী আছে"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"বেতাঁৰৰ মাধ্যমেৰে চাৰ্জ হৈ আছে"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"চাৰ্জ কৰি থকা হৈছে"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"চাৰ্জ কৰা নাই"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"সংযোগ হৈ আছে, চাৰ্জ হৈ থকা নাই"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"সংযোজিত হৈছে, কিন্তু চাৰ্জ হৈ থকা নাই"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"চাৰ্জ হ’ল"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"সম্পূৰ্ণ চাৰ্জ হৈছে"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"চাৰ্জিং স্থগিত ৰখা হৈছে"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"এডমিনৰ দ্বাৰা নিয়ন্ত্ৰিত"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"প্ৰতিবন্ধিত ছেটিঙৰ দ্বাৰা নিয়ন্ত্ৰিত"</string>
     <string name="disabled" msgid="8017887509554714950">"নিষ্ক্ৰিয়"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index a36ab27..0ee8b89 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Şəkilləndirmə"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Qulaqlıq"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Daxiletmə periferiki"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Eşitmə aparatları"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wifi sönülüdür."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wifi bağlantı kəsildi."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Rəng korreksiyası bunları etmək istədikdə faydalı ola bilər:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Rəngləri daha dəqiq görmək&lt;/li&gt; &lt;li&gt;&amp;nbsp;Fokuslanmaq üçün rəngləri ləğv etmək&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> tərəfindən qəbul edilmir"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Batareyanı qorumaq üçün şarj gözlədilir"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj aksesuarı yoxlanır"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Təxminən <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qalıb"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Təxminən <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qalıb (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"İstifadəyə əsasən təxminən <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qalıb"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Simsiz şarj edilir"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Şarj edilir"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Doldurulmur"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Qoşulub, şarj edilmir"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Qoşulub, amma şarj edilmir"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Şarj edilib"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Tam şarj edilib"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Şarj gözlədilir"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Admin tərəfindən nəzarət olunur"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Məhdudlaşdırılmış Ayar ilə nəzarət edilir"</string>
     <string name="disabled" msgid="8017887509554714950">"Deaktiv"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 83f7c05..0cf6887 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Obrada slika"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Slušalice"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Periferni uređaj za unos"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Slušni aparati"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"WiFi je isključen."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"WiFi veza je prekinuta."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Korekcija boja može da bude korisna kada želite:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Preciznije da vidite boje&lt;/li&gt; &lt;li&gt;&amp;nbsp;Da uklonite boje kako biste se fokusirali&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Zamenjuje ga <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>–<xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje je na čekanju da bi se zaštitila baterija"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – provera dodatne opreme za punjenje"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Preostalo je oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Preostalo je oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Preostalo je oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g> na osnovu korišćenja"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Bežično punjenje"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Punjenje"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ne puni se"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Povezano, ne puni se"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Uređaj je povezan, ali se ne puni"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Napunjeno"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Napunjeno do kraja"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Punjenje je na čekanju"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Kontroliše administrator"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kontrolišu ograničena podešavanja"</string>
     <string name="disabled" msgid="8017887509554714950">"Onemogućeno"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 7e8cd8b..fe0e60c 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Прылада апрацоўкі відарысаў"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Навушнікі"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Перыферыйная прылада ўводу"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Слыхавыя апараты"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi выключаны."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi адлучаны."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Карэкцыя колераў можа спатрэбіцца, калі вы захочаце:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;бачыць колеры больш дакладна;&lt;/li&gt; &lt;li&gt;&amp;nbsp;выдаліць колеры, каб сканцэнтраваць увагу.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Перавызначаны <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарадка прыпынена, каб абараніць акумулятар"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – правяраецца зарадная прылада"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Зараду хопіць прыблізна на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Зараду (<xliff:g id="LEVEL">%2$s</xliff:g>) хопіць прыблізна на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Зараду пры такім выкарыстанні хопіць прыблізна на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Бесправадная зарадка"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Ідзе зарадка"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не зараджаецца"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Падключана, не зараджаецца"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Прылада падключана, але не зараджаецца"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Зараджаны"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Акумулятар поўнасцю зараджаны"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Зарадка прыпынена"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Кантралюецца адміністратарам"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Пад кіраваннем Абмежаванага наладжвання"</string>
     <string name="disabled" msgid="8017887509554714950">"Адключанае"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index d279349..b7e90bc 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Изображения"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Слушалки"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Периферен вход"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Слухови апарати"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi е изключен."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Няма връзка с Wi-Fi."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Функцията за корекция на цветовете може да бъде полезна, когато искате:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;да виждате по-точни цветове;&lt;/li&gt; &lt;li&gt;&amp;nbsp;да премахнете цветовете, за да се съсредоточите.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Заменено от „<xliff:g id="TITLE">%1$s</xliff:g>“"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Зареждането е поставено на пауза с цел запазване на батерията"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Аксесоарът за зареждане се проверява"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Още около <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Още около <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Още около <xliff:g id="TIME_REMAINING">%1$s</xliff:g> въз основа на използването"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Зарежда се безжично"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Зареждане"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не се зарежда"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Свързано, не се зарежда"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Свързано, но не се зарежда"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Заредена"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Напълно заредено"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Зареждането е поставено на пауза"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Контролира се от администратор"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Управлява се чрез ограничена настройка"</string>
     <string name="disabled" msgid="8017887509554714950">"Деактивирано"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 3ac3a3b..4e57e5f 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"ইমেজিং"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"হেডফোন"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"পেরিফেরাল ইনপুট"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"হিয়ারিং এড"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"ব্লুটুথ"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"ওয়াই ফাই বন্ধ৷"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"ওয়াই-ফাই ডিসকানেক্ট হয়েছে৷"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"\'রঙ সংশোধন করা\' ফিচারের সাহায্যে এইসব কাজে করা যেতে পারে:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;আরও সঠিকভাবে রঙ দেখা&lt;/li&gt; &lt;li&gt;&amp;nbsp;ফোকাস করার জন্য রঙ সরানো&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> এর দ্বারা ওভাররাইড করা হয়েছে"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ব্যাটারিকে সুরক্ষিত রাখতে চার্জিং হোল্ড করা হয়েছে"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - চার্জিংয়ের সরঞ্জাম চেক করা হচ্ছে"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"আর আনুমানিক <xliff:g id="TIME_REMAINING">%1$s</xliff:g> চলবে"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"আর আনুমানিক <xliff:g id="TIME_REMAINING">%1$s</xliff:g> চলবে (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"ব্যবহারের উপর ভিত্তি করে আর আনুমানিক <xliff:g id="TIME_REMAINING">%1$s</xliff:g> চলবে"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"কেবল ছাড়া চার্জ হচ্ছে"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"চার্জ হচ্ছে"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"চার্জ হচ্ছে না"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"কানেক্ট করা থাকলেও চার্জ করা হচ্ছে না"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"কানেক্ট করা আছে, কিন্তু চার্জ হচ্ছে না"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"চার্জ হয়েছে"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"সম্পূর্ণ চার্জ আছে"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"চার্জিং হোল্ডে আছে"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"প্রশাসকের দ্বারা নিয়ন্ত্রিত"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"এটি বিধিনিষেধ সেটিং থেকে নিয়ন্ত্রণ করা হয়"</string>
     <string name="disabled" msgid="8017887509554714950">"অক্ষম হয়েছে"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index f84ccb0..d2bcfb6 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Snimanje"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Slušalice"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Ulazni periferni uređaj"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Slušni aparati"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"WiFi je isključen."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"WiFi nije povezan."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Ispravka boja može biti korisna kada želite:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;jasnije vidjeti boje&lt;/li&gt; &lt;li&gt;&amp;nbsp;ukloniti boje radi lakšeg fokusiranja&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Zamjenjuje <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje je na čekanju radi zaštite baterije"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – provjera opreme za punjenje"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Preostalo je još oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Preostalo je još oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Preostalo je još oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g> na osnovu vaše potrošnje"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Bežično punjenje"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Punjenje"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ne puni se"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Povezano, ne puni se"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Uređaj je povezan, ali se ne puni"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Napunjeno"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Potpuno napunjeno"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Punjenje je na čekanju"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Pod kontrolom administratora"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kontrolira ograničena postavka"</string>
     <string name="disabled" msgid="8017887509554714950">"Onemogućeno"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index c205f74..a556cf1 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Imatges"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Auricular"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Perifèric d\'entrada"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Audiòfons"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi desactivada."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi desconnectada."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"La correcció de color pot ser útil si vols:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Veure els colors amb més precisió.&lt;/li&gt; &lt;li&gt;&amp;nbsp;Suprimir els colors per concentrar-te millor.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"S\'ha substituït per <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>: <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g>: la càrrega s\'ha posat en espera per protegir la bateria"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g>: s\'està comprovant l\'accessori de càrrega"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Temps restant aproximat: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Temps restant aproximat: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Temps restant aproximat segons l\'ús que en fas: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Carregant sense fil"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"S\'està carregant"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"No s\'està carregant"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Connectat; no s\'està carregant"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Connectat, però sense carregar"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Carregada"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Totalment carregada"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Càrrega en espera"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlat per l\'administrador"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlat per la configuració restringida"</string>
     <string name="disabled" msgid="8017887509554714950">"Desactivat"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 53b7c70..45e089e 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Zobrazovací zařízení"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Sluchátka"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Periferní vstupní zařízení"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Naslouchátka"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Síť Wi-Fi je vypnuta."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Síť Wi-Fi je odpojena."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Korekce barev se může hodit, když chcete:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Vidět barvy přesněji.&lt;/li&gt; &lt;li&gt;&amp;nbsp;Odstranit barvy kvůli zlepšení soustředění.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Přepsáno nastavením <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Nabíjení je pozastaveno za účelem ochrany baterie"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Kontrola nabíjecího příslušenství"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Zbývá asi <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Zbývá asi <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Při vašem obvyklém využití zbývá asi <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Bezdrátové nabíjení"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Nabíjení"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nenabíjí se"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Připojeno, nenabíjí se"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Připojeno, ale nenabíjí se"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Nabito"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Plně nabito"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Nabíjení pozastaveno"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Spravováno administrátorem"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Spravováno omezeným nastavením"</string>
     <string name="disabled" msgid="8017887509554714950">"Deaktivováno"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 0d0f0c6..89d34a6 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Billede"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Hovedtelefoner"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Eksterne inputenheder"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Høreapparater"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi er slået fra."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi er afbrudt."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Farvekorrigering kan være en nyttig funktion, når du vil:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Se farver mere nøjagtigt&lt;/li&gt; &lt;li&gt;&amp;nbsp;Fjerne farver, så du nemmere kan fokusere&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Tilsidesat af <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Opladningen er sat på pause for at beskytte batteriet"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Tjekker opladningstilbehøret"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ca. <xliff:g id="TIME_REMAINING">%1$s</xliff:g> tilbage"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Ca. <xliff:g id="TIME_REMAINING">%1$s</xliff:g> tilbage (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Ca. <xliff:g id="TIME_REMAINING">%1$s</xliff:g> tilbage, alt efter hvordan du bruger enheden"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Trådløs opladning"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Oplader"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Oplader ikke"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Tilsluttet, oplader ikke"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Forbundet, men oplader ikke"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Opladet"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fuldt opladet"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Opladningen er blevet sat på pause"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Kontrolleret af administratoren"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Styres af en begrænset indstilling"</string>
     <string name="disabled" msgid="8017887509554714950">"Deaktiveret"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 7936478..58713d6 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Bildverarbeitung"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Kopfhörer"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Eingabeperipherie"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Hörgerät"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"WLAN: aus"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"WLAN getrennt"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Die Farbkorrektur kann nützlich sein, wenn du:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Farben noch genauer sehen möchtest&lt;/li&gt; &lt;li&gt;&amp;nbsp;bestimmte Farben entfernen möchtest, um dich besser zu konzentrieren&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Außer Kraft gesetzt von \"<xliff:g id="TITLE">%1$s</xliff:g>\""</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ladevorgang zum Schutz des Akkus angehalten"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ladezubehör wird geprüft"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Noch etwa <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Noch etwa <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Bei deinem Nutzungsmuster hast du noch ca. <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Kabelloses Laden"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Wird geladen"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Wird nicht geladen"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Verbunden, wird nicht geladen"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Verbunden, wird aber nicht geladen"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Aufgeladen"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Vollständig geladen"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Ladevorgang angehalten"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Durch den Administrator verwaltet"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Gesteuert durch eingeschränkte Einstellung"</string>
     <string name="disabled" msgid="8017887509554714950">"Deaktiviert"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 65f1f77..b7df368 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Απεικόνιση"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Ακουστικά"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Περιφερειακό εισόδου"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Βοηθήματα ακοής"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi ανενεργό."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Το Wi-Fi έχει αποσυνδεθεί."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Η διόρθωση χρωμάτων μπορεί να σας φανεί χρήσιμη όταν θέλετε:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Μεγαλύτερη ακρίβεια στην απεικόνιση χρωμάτων&lt;/li&gt; &lt;li&gt;&amp;nbsp;Να καταργήσετε χρώματα για να συγκεντρωθείτε&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Αντικαταστάθηκε από <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Η φόρτιση τέθηκε σε αναμονή για προστασία της μπαταρίας"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Έλεγχος αξεσουάρ φόρτισης"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Απομένει/ουν περίπου <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Απομένει/ουν περίπου <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Απομένει/ουν περίπου <xliff:g id="TIME_REMAINING">%1$s</xliff:g>, βάσει της χρήσης σας"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Ασύρματη φόρτιση"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Φόρτιση"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Δεν φορτίζει"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Συνδεδεμένη, δεν φορτίζει"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Συνδέθηκε, αλλά δεν φορτίζει"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Φορτισμένη"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Πλήρως φορτισμένο"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Η φόρτιση τέθηκε σε αναμονή"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Ελέγχονται από το διαχειριστή"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Ελέγχεται από τη Ρύθμιση με περιορισμό"</string>
     <string name="disabled" msgid="8017887509554714950">"Απενεργοποιημένο"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index a5f089a..66aaaf5 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Imaging"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Headphone"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Input Peripheral"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Hearing aids"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi off."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi disconnected."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Colour correction can be helpful when you want to:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;See colours more accurately&lt;/li&gt; &lt;li&gt;&amp;nbsp;Remove colours to help you focus&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Charging on hold to protect battery"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Checking charging accessory"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left based on your usage"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Charging wirelessly"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Charging"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Connected, not charging"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Connected, but not charging"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Charged"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fully charged"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Charging on hold"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlled by admin"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by restricted setting"</string>
     <string name="disabled" msgid="8017887509554714950">"Disabled"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 1c51ea5..ad68317 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Imaging"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Headphone"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Input Peripheral"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Hearing Aids"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wifi off."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wifi disconnected."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Color correction can be helpful when you want to:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;See colors more accurately&lt;/li&gt; &lt;li&gt;&amp;nbsp;Remove colors to help you focus&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging on hold to protect battery"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Checking charging accessory"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left based on your usage"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Charging wirelessly"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Charging"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Connected, not charging"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Connected, but not charging"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Charged"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fully Charged"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Charging on hold"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlled by admin"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by Restricted Setting"</string>
     <string name="disabled" msgid="8017887509554714950">"Disabled"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index a5f089a..66aaaf5 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Imaging"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Headphone"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Input Peripheral"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Hearing aids"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi off."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi disconnected."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Colour correction can be helpful when you want to:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;See colours more accurately&lt;/li&gt; &lt;li&gt;&amp;nbsp;Remove colours to help you focus&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Charging on hold to protect battery"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Checking charging accessory"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left based on your usage"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Charging wirelessly"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Charging"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Connected, not charging"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Connected, but not charging"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Charged"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fully charged"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Charging on hold"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlled by admin"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by restricted setting"</string>
     <string name="disabled" msgid="8017887509554714950">"Disabled"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index a5f089a..66aaaf5 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Imaging"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Headphone"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Input Peripheral"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Hearing aids"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi off."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi disconnected."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Colour correction can be helpful when you want to:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;See colours more accurately&lt;/li&gt; &lt;li&gt;&amp;nbsp;Remove colours to help you focus&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Charging on hold to protect battery"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Checking charging accessory"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left based on your usage"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Charging wirelessly"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Charging"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Connected, not charging"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Connected, but not charging"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Charged"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fully charged"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Charging on hold"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlled by admin"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by restricted setting"</string>
     <string name="disabled" msgid="8017887509554714950">"Disabled"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 2ff386c..98106bf 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‏‏‎‏‏‎‎‏‏‏‎‎‎‏‎‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‎‏‏‎‏‏‎‏‏‎‏‎‎‏‏‎‏‏‎Imaging‎‏‎‎‏‎"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‎‎‎‎‏‏‎‏‏‏‏‏‎‎‏‎‎‎‎‎‎‏‏‏‏‎‏‎‏‏‏‏‎‎‎‎‏‏‎Headphone‎‏‎‎‏‎"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‏‏‏‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‎‏‎‎‏‎‏‏‎‏‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎Input Peripheral‎‏‎‎‏‎"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‏‏‎‏‎‎‎‏‏‏‎‏‏‏‎‏‎‎‎‎‎‎‎‎‎‏‏‎‎‏‎‎‏‏‎‎‎‎‎‎‎‎‏‏‏‏‎‎‏‏‏‎‎‏‎‏‏‏‎Hearing Aids‎‏‎‎‏‎"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‎‎‏‏‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‏‎‎‏‎‏‎‏‏‏‎‎‏‏‎‏‎‏‎‏‏‏‎‏‎‏‎Bluetooth‎‏‎‎‏‎"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‎‏‎‎‏‎‏‏‏‎‎‎‏‎‎‏‏‏‎‎‎‏‎‏‏‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‎‏‎‏‎‎‎‎‏‏‎Wifi off.‎‏‎‎‏‎"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‎‏‏‎‎‎‎‎‏‏‎‎‏‎‎‏‏‎‎‎‎‏‎‏‎‏‎‏‎‎‎‎‏‏‏‏‎‎‎‏‏‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‏‏‎Wifi disconnected.‎‏‎‎‏‎"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‎‏‎‎‎‏‏‏‏‏‏‎‎‏‎‏‏‎‏‏‏‎‎‏‏‎‎‎‏‏‎‏‏‏‎‎‎‏‎‏‎‎‏‎‏‏‏‎‎‏‎‎‎‏‎‏‏‏‎Color correction can be helpful when you want to:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;See colors more accurately&lt;/li&gt; &lt;li&gt;&amp;nbsp;Remove colors to help you focus&lt;/li&gt; &lt;/ol&gt;‎‏‎‎‏‎"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‏‎‏‎‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‎‏‎‏‏‎‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎Overridden by ‎‏‎‎‏‏‎<xliff:g id="TITLE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‏‎‏‏‏‎‎‏‏‎‎‎‎‏‏‎‎‏‏‏‎‎‎‏‏‏‎‎‏‏‎‎‏‎‎‏‏‎‎‏‏‎‏‎‎‎‎‏‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ - ‎‏‎‎‏‏‎<xliff:g id="TIME_STRING">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‎‏‎‎‎‎‎‎‏‎‎‎‏‎‎‏‏‏‎‏‎‏‎‎‎‏‎‎‏‎‎‎‎‏‏‎‎‎‎‏‎‎‏‏‏‏‎‎‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="LEVEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎ - Charging on hold to protect battery‎‏‎‎‏‎"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‎‎‎‏‎‏‎‏‎‏‎‎‎‏‎‎‏‏‎‏‎‎‎‎‎‎‏‎‏‏‏‎‏‏‏‎‏‎‎‎‎‏‎‏‏‏‎‏‏‏‏‎‎‎‎‏‎‎‏‏‎<xliff:g id="LEVEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎ - Checking charging accessory‎‏‎‎‏‎"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‎‎‎‏‎‏‎‏‎‏‎‏‎‏‏‎‎‏‎‏‏‎‎‏‎‏‏‎‏‏‎‎‏‎‏‏‎‏‏‏‎‏‏‎‏‎‎‏‏‏‎About ‎‏‎‎‏‏‎<xliff:g id="TIME_REMAINING">%1$s</xliff:g>‎‏‎‎‏‏‏‎ left‎‏‎‎‏‎"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‎‏‏‏‏‎‎‎‎‏‎‏‏‎‏‏‎‏‏‎‎‎‎‏‎‎‎‎‎‏‎‏‏‎‎‎‎‎‎‎‎‏‎‏‎‏‏‏‎‎‏‏‎‎‏‎‎‎About ‎‏‎‎‏‏‎<xliff:g id="TIME_REMAINING">%1$s</xliff:g>‎‏‎‎‏‏‏‎ left (‎‏‎‎‏‏‎<xliff:g id="LEVEL">%2$s</xliff:g>‎‏‎‎‏‏‏‎)‎‏‎‎‏‎"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‏‎‏‎‎‏‎‏‏‎‎‏‏‏‎‎‏‏‏‎‏‏‎‏‎‏‏‎‏‏‎‏‎‎‎‏‏‎‎‎‏‎‎‎‎‏‎‎‎‎‎‏‎‎About ‎‏‎‎‏‏‎<xliff:g id="TIME_REMAINING">%1$s</xliff:g>‎‏‎‎‏‏‏‎ left based on your usage‎‏‎‎‏‎"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‎‎‎‏‏‎‎‏‏‏‎‏‎‎‎‏‎‎‏‎‏‏‏‏‎‏‏‏‎‎‏‎‏‎‏‎Charging wirelessly‎‏‎‎‏‎"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‏‎‎‏‏‎‎‎‏‏‎‏‎‏‏‏‎‏‏‎‎‎‏‏‎‎‎‏‏‏‏‏‏‎‎‏‏‎‏‏‏‎‎‎‏‏‎‏‏‏‎Charging‎‏‎‎‏‎"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎‏‎‎‎‎‏‎‎‎‏‎‎‎‏‎‏‎‏‎‎‏‎‏‏‎‎‎‎‏‏‎‎‏‎‎‎‎‎‏‎‏‎Not charging‎‏‎‎‏‎"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‏‎‏‎‏‎‎‎‎‎‎‏‏‎‏‎‏‏‎‎‎‎‎‎‎‎‏‎‏‎‏‎Connected, not charging‎‏‎‎‏‎"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‏‎‏‎‎‏‏‏‎‏‏‏‏‎‎‎‏‏‎‏‎‏‏‏‎‏‎‏‎‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‎‏‎‏‎‎‏‎‏‏‎‎‎‎Connected, but not charging‎‏‎‎‏‎"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‏‎‏‎‎‏‎‏‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‏‎‏‏‏‎‎‎‏‏‎‎‎‏‏‎‏‎‏‎‎‏‏‏‎‎‎‎‎Charged‎‏‎‎‏‎"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎‎‏‎‎‏‎‏‎‎‏‎‏‏‎‏‎‎‏‏‏‎‏‏‎‏‏‎‎‏‏‏‎‎‎‏‎‏‏‏‎‏‎‏‏‎‎‏‏‎‎‏‏‎‎‎Fully Charged‎‏‎‎‏‎"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‎‎‏‎‏‎‏‏‏‎‏‏‎‏‎‎‏‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‎‎‎‎‎‎‎‎‎‏‏‎‏‏‏‎‎‏‏‎‎Charging on hold‎‏‎‎‏‎"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‏‎‏‎‎‏‎‏‏‎‎‎‏‏‎‏‏‏‎‎‎‎‎‏‏‎‏‏‏‏‎‎‎‏‎‏‎‎‏‏‎‎‎‏‎‎‎‎‏‏‎‎‎‏‎Controlled by admin‎‏‎‎‏‎"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‎‏‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‎‎‏‎‏‎‎‎‏‏‎‏‎‎‏‏‏‎‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‎‎‎Controlled by Restricted Setting‎‏‎‎‏‎"</string>
     <string name="disabled" msgid="8017887509554714950">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‏‎‏‎‏‎‎‎‎‏‎‎‎‏‏‎‏‎‏‏‎‎‏‏‎‎‎‎‎‎‎‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‎‎‏‏‎‎Disabled‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 780a45d..5e496cd 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Imágenes"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Auriculares"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Periférico de entrada"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Audífonos"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi inhabilitado"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi desconectado"</string>
@@ -180,7 +181,7 @@
     <string name="launch_defaults_none" msgid="8049374306261262709">"Sin configuraciones predeterminadas"</string>
     <string name="tts_settings" msgid="8130616705989351312">"Configuración de texto a voz"</string>
     <string name="tts_settings_title" msgid="7602210956640483039">"Salida de texto a voz"</string>
-    <string name="tts_default_rate_title" msgid="3964187817364304022">"Velocidad de voz"</string>
+    <string name="tts_default_rate_title" msgid="3964187817364304022">"Velocidad de habla"</string>
     <string name="tts_default_rate_summary" msgid="3781937042151716987">"Velocidad en la que se habla el texto"</string>
     <string name="tts_default_pitch_title" msgid="6988592215554485479">"Tono"</string>
     <string name="tts_default_pitch_summary" msgid="9132719475281551884">"Afecta el tono de la voz sintetizada"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"La corrección de colores puede ser útil cuando quieres hacer lo siguiente:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Ver los colores con mayor precisión&lt;/li&gt; &lt;li&gt;&amp;nbsp;Quitar colores para concentrarte&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Reemplazado por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Se detuvo la carga para proteger la batería"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Verificando el accesorio de carga"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tiempo restante: aproximadamente <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Tiempo restante: aproximadamente <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Tiempo restante: aproximadamente <xliff:g id="TIME_REMAINING">%1$s</xliff:g> en función de tu uso"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Carga inalámbrica"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Cargando"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"No se está cargando."</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Conectado; no se está cargando"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Conectado, pero no se está cargando"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Cargada"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Carga completa"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Se detuvo la carga"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlada por el administrador"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlada por la configuración restringida"</string>
     <string name="disabled" msgid="8017887509554714950">"Inhabilitada"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index c07e1fd..3ce371f 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Escáner"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Auriculares"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Periférico de entrada"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Audífonos"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi desactivado."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi desconectado."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Corrección de color puede ser útil si quieres:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Ver los colores mejor&lt;/li&gt; &lt;li&gt;&amp;nbsp;Quitar los colores para concentrarte mejor&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Anulado por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>: <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carga pausada para proteger la batería"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Comprobando accesorio de carga"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tiempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Tiempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Tiempo restante aproximado según tu uso: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Carga inalámbrica"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Cargando"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"No se está cargando"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Conectado pero sin cargar"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Conectado, pero sin cargar"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Cargada"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Carga completa"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Carga pausada"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlada por el administrador"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlado por ajustes restringidos"</string>
     <string name="disabled" msgid="8017887509554714950">"Inhabilitada"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index ddc16b0..115a64a 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Pildindus"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Kõrvaklapid"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Sisestatud välisseade"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Kuuldeaparaadid"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"WiFi on välja lülitatud."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"WiFi-ühendus on katkestatud."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Värvide korrigeerimisest võib abi olla, kui soovite:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;värve täpsemalt näha;&lt;/li&gt; &lt;li&gt;&amp;nbsp;värve eemaldada, et paremini keskenduda.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Alistas <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – laadimine on aku kaitsmiseks ootele pandud"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – laadimistarviku kontrollimine"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ligikaudu <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäänud"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Ligikaudu <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäänud (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Teie kasutuse põhjal on jäänud ligikaudu <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Juhtmevaba laadimine"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Laadimine"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ei lae"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Ühendatud, ei laeta"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Ühendatud, kuid ei laadita"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Laetud"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Täielikult laetud"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Laadimine on ootele pandud"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Juhib administraator"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Haldavad piiranguga seaded"</string>
     <string name="disabled" msgid="8017887509554714950">"Keelatud"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 5d2210c..f1e507e 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Irudietarako gailua"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Entzungailua"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Idazteko gailua"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Audifonoak"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth bidezko gailua"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Desaktibatuta dago wifi-konexioa."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Deskonektatu egin da wifi-konexioa."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Baliteke koloreen zuzenketa lagungarria izatea hauek egin nahi dituzunean:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Koloreak zehaztasun handiagoz ikusi.&lt;/li&gt; &lt;li&gt;&amp;nbsp;Koloreak kendu, arreta gal ez dezazun.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> hobespena gainjarri zaio"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g>: kargatze-prozesua zain dago bateria babesteko"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g>: kargatzeko osagarria egiaztatzen"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> inguru gelditzen dira"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> inguru gelditzen dira (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Erabilera kontuan izanda, <xliff:g id="TIME_REMAINING">%1$s</xliff:g> inguru gelditzen dira"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Hari gabe kargatzen"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Kargatzen"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ez da kargatzen ari"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Konektatuta dago, baina ez da kargatzen ari"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Konektatuta dago, baina ez da kargatzen ari"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Kargatuta"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Erabat kargatuta"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Kargatze-prozesua zain dago"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Administratzaileak kontrolatzen du"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Ezarpen mugatuak kontrolatzen du"</string>
     <string name="disabled" msgid="8017887509554714950">"Desgaituta"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index d73571e..68d9ed1 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"تصویربرداری"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"هدفون"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"ورودی محیطی"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"سمعک"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"بلوتوث"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"‏Wi‑Fi خاموش است."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"‏Wi-Fi قطع‌ شد."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"‏«تصحیح رنگ» می‌تواند در مواقع زیر مفید باشد:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;وقتی می‌خواهید رنگ‌ها را دقیق‌تر ببینید&lt;/li&gt; &lt;li&gt;&amp;nbsp;وقتی می‌خواهید رنگ‌ها را حذف کنید تا بتوانید راحت‌تر تمرکز کنید&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"توسط <xliff:g id="TITLE">%1$s</xliff:g> لغو شد"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - برای محافظت از باتری، شارژ موقتاً متوقف شده است"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - بررسی لوازم شارژ"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> شارژ باقی مانده است"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> شارژ باقی مانده است (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"براساس مصرفتان، تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> شارژ باقی مانده است"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"درحال شارژ بی‌سیم"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"درحال شارژ شدن"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"شارژ نمی‌شود"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"متصل، شارژ نمی‌شود"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"متصل است، اما شارژ نمی‌شود"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"شارژ کامل شد"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"کاملاً شارژ شده است"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"شارژ موقتاً متوقف شده است"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"توسط سرپرست سیستم کنترل می‌شود"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"با تنظیم «حالت محدود» کنترل می‌شود"</string>
     <string name="disabled" msgid="8017887509554714950">"غیر فعال شد"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 3533d77..13cface 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Kuvannuslaite"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Kuulokkeet"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Syöttölisälaite"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Kuulolaitteet"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi pois käytöstä"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Ei Wi-Fi-yhteyttä"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Värinkorjaus voi auttaa seuraavissa:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Värien tarkempi näkeminen&lt;/li&gt; &lt;li&gt;&amp;nbsp;Värien poistaminen keskittymisen parantamiseksi&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Tämän ohittaa <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Lataus on keskeytetty akun suojaamiseksi"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Tarkistetaan latauslisävarustetta"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Noin <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäljellä"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Noin <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäljellä (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Noin <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäljellä käyttösi perusteella"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Langaton lataus"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Ladataan"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ei laturissa"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Yhdistetty, ei ladata"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Yhdistetty, mutta ei latauksessa"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Ladattu"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Täyteen ladattu"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Lataus on pidossa"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Järjestelmänvalvoja hallinnoi tätä asetusta."</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Rajoitettujen asetusten mukaisesti"</string>
     <string name="disabled" msgid="8017887509554714950">"Pois päältä"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 06eeae0..5d23510 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Imagerie"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Écouteurs"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Périphérique d\'entrée"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Prothèses auditives"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi désactivé."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi déconnecté."</string>
@@ -180,7 +181,7 @@
     <string name="launch_defaults_none" msgid="8049374306261262709">"Aucune préférence par défaut définie"</string>
     <string name="tts_settings" msgid="8130616705989351312">"Paramètres de synthèse vocale"</string>
     <string name="tts_settings_title" msgid="7602210956640483039">"Synthèse vocale"</string>
-    <string name="tts_default_rate_title" msgid="3964187817364304022">"Cadence"</string>
+    <string name="tts_default_rate_title" msgid="3964187817364304022">"Débit"</string>
     <string name="tts_default_rate_summary" msgid="3781937042151716987">"Vitesse à laquelle le texte est énoncé"</string>
     <string name="tts_default_pitch_title" msgid="6988592215554485479">"Ton"</string>
     <string name="tts_default_pitch_summary" msgid="9132719475281551884">"Touche le ton utilisé pour la synthèse vocale"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"La correction des couleurs peut être utile lorsque vous souhaitez :&lt;br/&gt; &lt;ol&gt; &lt;li&gt; voir les couleurs avec plus de précision;&lt;/li&gt; &lt;li&gt; retirer les couleurs pour vous aider à vous concentrer.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Remplacé par <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> : <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – La recharge a été mise en pause pour protéger la pile"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Vérification de l\'accessoire de recharge en cours…"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Il reste environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Il reste environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Il reste environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> en fonction de votre usage"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"En recharge sans fil"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Recharge en cours…"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"N\'est pas en charge"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Connecté, pas en charge"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Connecté, mais ne se recharge pas"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Chargée"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Complètement rechargée"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Recharge en pause"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Contrôlé par l\'administrateur"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Contrôlé par les paramètres restreints"</string>
     <string name="disabled" msgid="8017887509554714950">"Désactivée"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index e30a4ab..ae6d48e 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Imagerie"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Casque audio"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Périphérique d\'entrée"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Appareils auditifs"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi désactivé"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi déconnecté"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"La correction des couleurs peut vous être utile pour :&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Distinguer les couleurs plus précisément&lt;/li&gt; &lt;li&gt;&amp;nbsp;Supprimer les couleurs afin de mieux vous concentrer&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Remplacé par <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Recharge en pause pour protéger la batterie"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Vérification de l\'accessoire de recharge"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Temps restant : environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Temps restant : environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Temps restant en fonction de votre utilisation : environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"En charge sans fil"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Recharge"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Pas en charge"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Connectée, pas en charge"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Connecté, mais pas en cours de recharge"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Chargée"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Complètement chargée"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Recharge en pause"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Contrôlé par l\'administrateur"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Contrôlé par les paramètres restreints"</string>
     <string name="disabled" msgid="8017887509554714950">"Désactivée"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index f896d16..75eb21f 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Dispositivo de imaxe"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Auriculares"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Periférico de entrada"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Audiófonos"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wifi desactivada."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wifi desconectada."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"A corrección da cor pode serche útil se queres:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Ver mellor as cores&lt;/li&gt; &lt;li&gt;&amp;nbsp;Quitar as cores para concentrarte mellor&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Anulado por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g>. A carga púxose en pausa para protexer a batería"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g>. Comprobando accesorio de carga"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Tempo restante aproximado (<xliff:g id="LEVEL">%2$s</xliff:g>): <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Tempo restante aproximado en función do uso: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Cargando sen fíos"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Cargando"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Non se está cargando"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Conectado, sen cargar"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Conectado, pero non cargando"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Cargada"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Carga completa"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Carga en pausa"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Opción controlada polo administrador"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Baixo o control de opcións restrinxidas"</string>
     <string name="disabled" msgid="8017887509554714950">"Desactivada"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 50741dd..e477063 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"ઇમેજિંગ"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"હેડફોન"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"ઇનપુટ પેરિફેરલ"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"સાંભળવામાં મદદ આપતા યંત્રો"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"બ્લૂટૂથ"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wifi બંધ."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wifi ડિસ્કનેક્ટ થયું."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"રંગમાં સુધારણા કરવાની સુવિધાનો ઉપયોગ ત્યારે સહાયરૂપ બની શકે છે કે જ્યારે તમે આ કરવા માગતા હો:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;વધુ સચોટપણે રંગ જોવા&lt;/li&gt; &lt;li&gt;&amp;nbsp;ફોકસ કરવામાં સહાય માટે અમુક રંગ કાઢી નાખવા&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> દ્વારા ઓવરરાઇડ થયું"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - બૅટરીને સુરક્ષિત રાખવા માટે, ચાર્જિંગ હોલ્ડ પર રાખવામાં આવ્યું છે"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - ચાર્જિંગ ઍક્સેસરી ચેક કરી રહ્યાં છીએ"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"લગભગ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> બાકી છે"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"લગભગ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> બાકી છે (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"તમારા વપરાશના આધારે લગભગ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> બાકી છે"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"વાયરલેસથી ચાર્જિંગ"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"ચાર્જ થઈ રહ્યું છે"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"ચાર્જ થઈ રહ્યું નથી"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"કનેક્ટ કરેલું છે, પણ ચાર્જ થઈ રહ્યું નથી"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"કનેક્ટેડ છે, પરંતુ ચાર્જ થઈ રહ્યું નથી"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"ચાર્જ થયું"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"સંપૂર્ણપણે ચાર્જ છે"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"ચાર્જિંગ હોલ્ડ પર રાખવામાં આવ્યું"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"વ્યવસ્થાપક દ્વારા નિયંત્રિત"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"પ્રતિબંધિત સેટિંગ દ્વારા નિયંત્રિત"</string>
     <string name="disabled" msgid="8017887509554714950">"અક્ષમ કર્યો"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 0a29b6a..0e91a37 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"इमेजिंग"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"हेडफ़ोन"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"इनपुट पेरिफ़ेरल"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"कान की मशीनें"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"ब्लूटूथ"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"वाई-फ़ाई बंद है."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"वाई-फ़ाई डिसकनेक्ट है."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"रंग में सुधार करने की सुविधा का इस्तेमाल, इन मामलों में किया जा सकता है:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;आपको ज़्यादा सटीक तरह से रंग देखने हों&lt;/li&gt; &lt;li&gt;&amp;nbsp;ज़्यादा फ़ोकस करने के लिए, आपको कुछ खास रंग हटाने हों&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> के द्वारा ओवरराइड किया गया"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - बैटरी को सुरक्षित रखने के लिए, फ़ोन को चार्ज होने से रोक दिया गया है"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिंग ऐक्सेसरी की जांच की जा रही है"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"बैटरी करीब <xliff:g id="TIME_REMAINING">%1$s</xliff:g> में खत्म हो जाएगी"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"बैटरी करीब <xliff:g id="TIME_REMAINING">%1$s</xliff:g> में खत्म हो जाएगी (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"आपके इस्तेमाल के हिसाब से बैटरी करीब <xliff:g id="TIME_REMAINING">%1$s</xliff:g> में खत्म हो जाएगी"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"वायरलेस चार्जिंग"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"चार्ज हो रहा है"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"चार्ज नहीं हो रही है"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"कनेक्ट किया गया, चार्ज नहीं हो रहा है"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"फ़ोन कनेक्ट हो गया, लेकिन चार्ज नहीं हो रहा है"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"बैटरी चार्ज हो गई"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"बैटरी पूरी चार्ज है"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"फ़ोन को चार्ज होने से रोक दिया गया है"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"इसका नियंत्रण एडमिन के पास है"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"इसे पाबंदी मोड वाली सेटिंग से कंट्रोल किया जाता है"</string>
     <string name="disabled" msgid="8017887509554714950">"बंद किया गया"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index b206ac0..259779a 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Snimanje"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Slušalice"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Periferni uređaj za unos"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Slušna pomagala"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi je isključen."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi je isključen."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Korekcija boja može biti korisna kad želite:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;preciznije vidjeti boje&lt;/li&gt; &lt;li&gt;&amp;nbsp;ukloniti boje kako biste se lakše usredotočili.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Premošćeno postavkom <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje je pauzirano radi zaštite baterije"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – provjera dodatka za punjenje"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Još oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Još otprilike <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Još otprilike <xliff:g id="TIME_REMAINING">%1$s</xliff:g> na temelju vaše upotrebe"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Bežično punjenje"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Punjenje"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ne puni se"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Povezano, ne puni se"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Povezano, ali se ne puni"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Napunjeno"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Posve puna"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Punjenje na čekanju"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Kontrolira administrator"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kontrolira ograničena postavka"</string>
     <string name="disabled" msgid="8017887509554714950">"Onemogućeno"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 68ff9ae..2ffb08c 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Képalkotó"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Fejhallgató"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Beviteli periféria"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Hallókészülékek"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi kikapcsolva."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Nincs Wi-Fi-kapcsolat."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"A színjavítás funkció például a következő esetekben lehet hasznos:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Ha jobban szeretné látni a színeket.&lt;/li&gt; &lt;li&gt;&amp;nbsp;Ha a lényeges részek kiemelése érdekében el szeretne távolítani bizonyos színeket.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Felülírva erre: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Az akkumulátor védelme érdekében a töltés szünetel"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Akkumulátortartozék ellenőrzése…"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Körülbelül <xliff:g id="TIME_REMAINING">%1$s</xliff:g> maradt hátra"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Körülbelül <xliff:g id="TIME_REMAINING">%1$s</xliff:g> maradt hátra (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Körülbelül <xliff:g id="TIME_REMAINING">%1$s</xliff:g> maradt hátra az eszköz használata alapján"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Vezeték nélküli töltés"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Töltés…"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nem tölt"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Csatlakoztatva, nem töltődik"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Csatlakoztatva, de nem tölt"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Feltöltve"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Teljesen feltöltve"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"A töltés szünetel"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Rendszergazda által irányítva"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Korlátozott beállítás vezérli"</string>
     <string name="disabled" msgid="8017887509554714950">"Letiltva"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index f3061a7..1b28a0d 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Պատկերներ"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Ականջակալ"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Մուտքի արտաքին սարքեր"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Լսողական սարք"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi-ն անջատված է:"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi-ը կապակցված չէ:"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Գունաշտկումը կարող է օգնել, երբ դուք ուզում եք՝&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Ավելի հստակ տեսնել գույները&lt;/li&gt; &lt;li&gt;&amp;nbsp;Հեռացնել գույները, որպեսզի կարողանաք կենտրոնանալ&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Գերազանցված է <xliff:g id="TITLE">%1$s</xliff:g>-ից"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Լիցքավորումը դադարեցվել է՝ մարտկոցը պաշտպանելու համար"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Լիցքավորման սարքը ստուգվում է"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Լիցքը կբավարարի մոտ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Լիցքը (<xliff:g id="LEVEL">%2$s</xliff:g>) կբավարարի մոտ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Լիցքը կբավարարի մոտ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>՝ կախված օգտագործման եղանակից"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Անլար լիցքավորում"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Լիցքավորում"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Չի լիցքավորվում"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Միացված է, չի լիցքավորվում"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Սարքը միացած է, սակայն չի լիցքավորվում"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Լիցքավորված է"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Լրիվ լիցքավորված է"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Լրցքավորումը դադարեցված է"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Վերահսկվում է ադմինիստրատորի կողմից"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Կառավարվում է սահմանափակ ռեժիմի կարգավորումներով"</string>
     <string name="disabled" msgid="8017887509554714950">"Կասեցված է"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 2c16603..9e9c79c 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Pencitraan"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Headphone"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Periferal Masukan"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Alat Bantu Dengar"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi tidak aktif."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi tidak terhubung."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Koreksi warna dapat berguna jika Anda ingin:&lt;br/&gt; &lt;ol&gt; &lt;li&gt; Melihat warna dengan lebih akurat&lt;/li&gt; &lt;li&gt; Menghapus warna agar Anda lebih fokus&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Digantikan oleh <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengisian daya dihentikan sementara untuk melindungi baterai"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Memeriksa aksesori pengisi daya"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Sekitar <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Sekitar <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Sekitar <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi berdasarkan penggunaan Anda"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Mengisi daya nirkabel"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Pengisian daya"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Tidak mengisi daya"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Terhubung, tidak mengisi daya"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Terhubung, tetapi tidak mengisi daya"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Terisi"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Baterai Terisi Penuh"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Pengisian daya dihentikan sementara"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Dikontrol oleh admin"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Dikontrol oleh Setelan Terbatas"</string>
     <string name="disabled" msgid="8017887509554714950">"Dinonaktifkan"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 09587ee..071597a 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Myndherming"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Heyrnartól"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Jaðartæki með inntak"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Heyrnartæki"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Slökkt á Wi-Fi."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi ótengt."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Litaleiðrétting kemur m.a. að gagni þegar þú vilt:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Sjá liti í aukinni skerpu&lt;/li&gt; &lt;li&gt;&amp;nbsp;Fjarlægja liti til að geta einbeitt þér betur&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Hnekkt af <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Hleðsla í bið til að vernda rafhlöðuna"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Athugar hleðslutæki"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Um það bil <xliff:g id="TIME_REMAINING">%1$s</xliff:g> eftir"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Um það bil <xliff:g id="TIME_REMAINING">%1$s</xliff:g> eftir (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Um það bil <xliff:g id="TIME_REMAINING">%1$s</xliff:g> eftir miðað við notkun þína"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Hleður þráðlaust"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Í hleðslu"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ekki í hleðslu"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Tengt, ekki í hleðslu"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Tækið er tengt en hleðst ekki"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Fullhlaðin"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Full hleðsla"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Hleðsla í bið"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Stjórnað af kerfisstjóra"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Stýrt af takmarkaði stillingu"</string>
     <string name="disabled" msgid="8017887509554714950">"Óvirkt"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 98b2bd3..0540432 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Sistema di imaging"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Cuffie"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Periferica di immissione"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Apparecchi acustici"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi non attivo."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Rete Wi-Fi scollegata."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"La correzione del colore può essere utile quando vuoi:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Vedere i colori con più precisione&lt;/li&gt; &lt;li&gt;&amp;nbsp;Rimuovere i colori per mettere meglio a fuoco&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Valore sostituito da <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ricarica in sospeso per proteggere la batteria"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Controllo dell\'accessorio di ricarica"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo rimanente: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> circa"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Tempo rimanente: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> circa (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Tempo rimanente in base al tuo utilizzo: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> circa"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"In carica, wireless"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"In carica"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Non in carica"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Dispositivo connesso, non in carica"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Connesso, ma non in carica"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Carica"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Batteria completamente carica"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Ricarica in sospeso"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Gestita dall\'amministratore"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Gestita tramite impostazioni con restrizioni"</string>
     <string name="disabled" msgid="8017887509554714950">"Disattivato"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index fbd715a..6dbf7ab 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"הדמיה"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"אוזניות"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"ציוד קלט היקפי"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"מכשירי שמיעה"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"‏Wi-Fi כבוי."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"‏Wi-Fi מנותק."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"‏תיקון הצבע יכול לעזור אם רוצים:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;לראות צבעים מדויקים יותר&lt;/li&gt; &lt;li&gt;&amp;nbsp;לראות פחות צבעים כדי לשפר את הריכוז&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"נעקף על ידי <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – הטעינה הושהתה כדי להגן על הסוללה"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – מתבצעת בדיקה של אביזר הטעינה"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"הזמן הנותר: בערך <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"הזמן הנותר: בערך <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"הזמן הנותר על סמך השימוש שלך: בערך <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"בטעינה אלחוטית"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"טעינה"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"לא בטעינה"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"מחובר, לא בטעינה"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"המכשיר מחובר אבל לא נטען"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"הסוללה טעונה"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"טעונה במלואה"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"הטעינה הושהתה"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"נמצא בשליטת מנהל מערכת"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"בשליטה של הגדרה מוגבלת"</string>
     <string name="disabled" msgid="8017887509554714950">"מושבת"</string>
@@ -518,7 +522,7 @@
     <string name="ims_reg_title" msgid="8197592958123671062">"‏סטטוס הרשמה ל-IMS"</string>
     <string name="ims_reg_status_registered" msgid="884916398194885457">"רשום"</string>
     <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"לא רשום"</string>
-    <string name="status_unavailable" msgid="5279036186589861608">"לא זמין"</string>
+    <string name="status_unavailable" msgid="5279036186589861608">"לא זמינה"</string>
     <string name="wifi_status_mac_randomized" msgid="466382542497832189">"‏כתובת ה-MAC אקראית"</string>
     <string name="wifi_tether_connected_summary" msgid="5100712926640492336">"{count,plural, =1{מכשיר אחד מחובר}one{# מכשירים מחוברים}two{# מכשירים מחוברים}other{# מכשירים מחוברים}}"</string>
     <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"יותר זמן."</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 5d57843..fbdca54 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"画像"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"ヘッドフォン"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"入力用周辺機器"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"補聴器"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-FiはOFFです。"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fiが切断されました。"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"色補正は以下の場合に役立ちます。&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;色をより正確に表示したい場合&lt;/li&gt; &lt;li&gt;&amp;nbsp;はっきり読み取れるよう色を取り除きたい場合&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g>によって上書き済み"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - バッテリーを保護するため、充電を一時停止しています"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電用アクセサリを確認しています"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"残り時間: 約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"残り時間: 約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>(<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"残り時間: 約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>(使用状況に基づく)"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"ワイヤレス充電中"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"充電中"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"充電していません"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"接続済み、充電していません"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"接続済み(充電していません)"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"充電が完了しました"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"充電完了"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"充電を一時停止しています"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"管理者により管理されています"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"制限付き設定によって管理されています"</string>
     <string name="disabled" msgid="8017887509554714950">"無効"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index eb89b19..0f02b44 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"გამოსახულებათა დამუშავების მოწყობილობა"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"ყურსასმენი"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"შეყვანის პერიფერიული მოწყობილობა"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"სმენის მოწყობილობები"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"WiFi გამორთულია."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"WiFi არ არის დაკავშირებული."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"ფერთა კორექცია შეიძლება დაგეხმაროთ, როცა გსურთ:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;ფერების მეტი სიზუსტით დანახვა&lt;/li&gt; &lt;li&gt;&amp;nbsp;ფერების მოცილება, რომ უკეთ კონცენტრირდეთ&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"უკუგებულია <xliff:g id="TITLE">%1$s</xliff:g>-ის მიერ"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – დატენვა შეჩერებულია ბატარეის დასაცავად"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – მიმდინარეობს დამტენი აქსესუარის შემოწმება"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"დარჩა დაახლოებით <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"დარჩა დაახლოებით <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"დარჩა დაახლოებით <xliff:g id="TIME_REMAINING">%1$s</xliff:g>, ბატარეის მოხმარების გათვალისწინებით"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"უსადენოდ დატენა"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"იტენება"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"არ იტენება"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"დაკავშირებულია, არ იტენება"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"დაკავშირებულია, მაგრამ არ იტენება"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"დატენილია"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"ბოლომდე დატენილი"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"დატენვა შეჩერებულია"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"იმართება ადმინისტრატორის მიერ"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"კონტროლდება შეზღუდული რეჟიმის პარამეტრით"</string>
     <string name="disabled" msgid="8017887509554714950">"გამორთული"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index a8fb9aa..cc560b8 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Бейне құралы"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Құлақаспап"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Кіріс құралы"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Есту аппараттары"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi өшірулі."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi ажыратылған."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Түсті түзету мына жағдайларда пайдалы болуы мүмкін:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;түстерді анығырақ көру;&lt;/li&gt; &lt;li&gt;&amp;nbsp;зейін қоюға көмектесу үшін түстерді алып тастау.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> үстінен басқан"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g>: батареяны қорғау үшін зарядтау кідіртіледі."</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g>: зарядтау құрылғысы тексеріледі."</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Шамамен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> қалды"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Шамамен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> қалды (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Пайдалану деректеріңізге сәйкес енді шамамен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> қалды"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Сымсыз зарядталуда"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Зарядталып жатыр."</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Зарядталу орындалып жатқан жоқ"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Жалғанған, зарядталып жатқан жоқ"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Құрылғы жалғанған, бірақ зарядталып жатқан жоқ."</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Зарядталды"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Толық зарядталды."</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Зарядтау кідіртілді."</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Әкімші басқарады"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Шектелген параметрлер арқылы басқарылады."</string>
     <string name="disabled" msgid="8017887509554714950">"Өшірілген"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 16f14ff..bf88707 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"កំពុងបង្ហាញ"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"កាស"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"ធាតុបញ្ចូលបន្ថែម"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"ឧបករណ៍​ជំនួយការ​ស្ដាប់"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"ប៊្លូធូស"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"បានបិទ Wifi"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"បានផ្តាច់ Wifi"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"ការ​កែតម្រូវ​ពណ៌អាចមានប្រយោជន៍ នៅពេលអ្នកចង់៖&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;មើលពណ៌កាន់តែត្រឹមត្រូវ&lt;/li&gt; &lt;li&gt;&amp;nbsp;លុបពណ៌ចេញ ដើម្បីជួយឱ្យអ្នកផ្ដោតអារម្មណ៍&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"បដិសេធ​ដោយ <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - កំពុងផ្អាកការសាកថ្ម ដើម្បីការពារថ្ម"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - កំពុងពិនិត្យមើលគ្រឿងសាកថ្ម"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"នៅសល់​ប្រហែល <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ទៀត"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"នៅសល់​ប្រហែល <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ទៀត (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"នៅសល់​ប្រហែល <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ទៀត ផ្អែក​លើការ​ប្រើប្រាស់​របស់អ្នក"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"កំពុង​សាកថ្ម​ឥតខ្សែ"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"កំពុងសាកថ្ម"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"មិនកំពុង​សាក​ថ្ម"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"បានភ្ជាប់ មិនកំពុង​សាកថ្ម"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"បានភ្ជាប់ តែមិនកំពុងសាកថ្មទេ"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"បាន​សាក​ថ្មពេញ"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"បានសាក​ថ្មពេញ"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"កំពុងផ្អាកការសាកថ្ម"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"គ្រប់គ្រងដោយអ្នកគ្រប់គ្រង"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"គ្រប់គ្រងដោយការកំណត់ដែលបានរឹតបន្តឹង"</string>
     <string name="disabled" msgid="8017887509554714950">"បិទ"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 640d4e9..0288e92 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -60,7 +60,7 @@
     <string name="wifi_not_in_range" msgid="1541760821805777772">"ವ್ಯಾಪ್ತಿಯಲ್ಲಿಲ್ಲ"</string>
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಸಂಪರ್ಕಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಪ್ರವೇಶವಿಲ್ಲ"</string>
-    <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> ನಿಂದ ಉಳಿಸಲಾಗಿದೆ"</string>
+    <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> ನಿಂದ ಸೇವ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s ಮೂಲಕ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಸಂಪರ್ಕಿಸಲಾಗಿದೆ"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"ನೆಟ್‌ವರ್ಕ್ ರೇಟಿಂಗ್ ಒದಗಿಸುವವರ ಮೂಲಕ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಸಂಪರ್ಕಿಸಲಾಗಿದೆ"</string>
     <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ಆ್ಯಪ್ ಮೂಲಕ ಸಂಪರ್ಕಿಸಲಾಗಿದೆ"</string>
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"ಇಮೇಜಿಂಗ್"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"ಹೆಡ್‌ಫೋನ್"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"ಪೆರಿಪೆರಲ್ ಇನ್‌ಪುಟ್‌‌"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"ಶ್ರವಣ ಸಾಧನಗಳು"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"ಬ್ಲೂಟೂತ್"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"ವೈಫೈ ಆಫ್."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"ವೈಫೈ ಸಂಪರ್ಕ ಕಡಿತಗೊಂಡಿದೆ."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"ನೀವು ಹೆಚ್ಚು ಸ್ಪಷ್ಟವಾದ ಬಣ್ಣಗಳನ್ನು ನೋಡಲು ಬಯಸಿದರೆ :&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp; ಬಣ್ಣದ ತಿದ್ದುಪಡಿಯು ಸಹಾಯಕವಾಗಿರುತ್ತದೆ; ಗಮನವನ್ನು ಕೇಂದ್ರೀಕರಿಸಲು ನಿಮಗೆ ಸಹಾಯ ಮಾಡಲು ಬಣ್ಣಗಳನ್ನು ತೆಗೆದುಹಾಕಿ &lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ಮೂಲಕ ಅತಿಕ್ರಮಿಸುತ್ತದೆ"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಬ್ಯಾಟರಿಯನ್ನು ರಕ್ಷಿಸಲು ಚಾರ್ಜಿಂಗ್ ಅನ್ನು ಹೋಲ್ಡ್‌ ಮಾಡಲಾಗಿದೆ"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಚಾರ್ಜಿಂಗ್ ಪರಿಕರವನ್ನು ಪರಿಶೀಲಿಸಲಾಗುತ್ತಿದೆ"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ಸಮಯ ಬಾಕಿ ಉಳಿದಿದೆ"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"(<xliff:g id="LEVEL">%2$s</xliff:g>) ತಲುಪಲು <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ಸಮಯ ಬಾಕಿ ಉಳಿದಿದೆ"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"ನಿಮ್ಮ ಬಳಕೆಯ ಆಧಾರದ ಮೇಲೆ ಸುಮಾರು <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ಸಮಯ ಬಾಕಿ ಉಳಿದಿದೆ"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"ವೈರ್‌ಲೆಸ್ ಚಾರ್ಜಿಂಗ್"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"ಚಾರ್ಜ್‌ ಆಗುತ್ತಿಲ್ಲ"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"ಕನೆಕ್ಟ್ ಆಗಿದೆ, ಚಾರ್ಜ್ ಆಗುತ್ತಿಲ್ಲ"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ, ಆದರೆ ಚಾರ್ಜಿಂಗ್ ಆಗುತ್ತಿಲ್ಲ"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"ಚಾರ್ಜ್ ಆಗಿದೆ"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"ಪೂರ್ಣವಾಗಿ ಚಾರ್ಜ್ ಆಗಿದೆ"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"ಚಾರ್ಜಿಂಗ್ ಅನ್ನು ಹೋಲ್ಡ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"ನಿರ್ವಾಹಕರ ಮೂಲಕ ನಿಯಂತ್ರಿಸಲಾಗಿದೆ"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"ನಿರ್ಬಂಧಿಸಲಾದ ಸೆಟ್ಟಿಂಗ್ ಮೂಲಕ ನಿಯಂತ್ರಿಸಲಾಗುತ್ತದೆ"</string>
     <string name="disabled" msgid="8017887509554714950">"ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 1bf0a51..432f4ed 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"이미징"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"헤드폰"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"입력 주변기기"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"보청기"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"블루투스"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi가 꺼져 있습니다."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi 연결이 끊어졌습니다."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"색상 보정은 다음과 같은 경우에 유용할 수 있습니다.&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;색상을 보다 정확하게 확인하려는 경우&lt;/li&gt; &lt;li&gt;&amp;nbsp;집중에 도움이 되도록 색상을 제거하려는 경우&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> 우선 적용됨"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>, <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - 배터리 보호를 위해 충전 일시중지"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - 충전 액세서리 확인 중"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"남은 시간: 약 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"남은 시간 약 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>(<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"내 사용량을 기준으로 약 <xliff:g id="TIME_REMAINING">%1$s</xliff:g> 남음"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"무선 충전 중"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"충전"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"충전 안함"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"연결됨, 충전 중 아님"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"연결되었으나 충전 중이 아님"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"충전됨"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"완전히 충전됨"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"충전 일시중지"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"관리자가 제어"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"제한된 설정으로 제어됨"</string>
     <string name="disabled" msgid="8017887509554714950">"사용 안함"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 9565fb0..aa685a2 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Сүрөт тартуучу түзмөк"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Кулакчын"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Дайындарды киргизүүчү тышкы түзмөк"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Угуу аппараттары"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wifi өчүк."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wifi туташуусу жок."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Түстөрдү тууралоо менен:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Керектүү түстөрдү аласыз&lt;/li&gt; &lt;li&gt;&amp;nbsp;Алагды кылган түстөрдү өчүрүп саласыз&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> менен алмаштырылган"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Батареяны коргоо үчүн кубаттоо тындырылды"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Кубаттоо шайманы текшерилүүдө"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Болжол менен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> калды"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Болжол менен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> калды (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Колдонгонуңузга караганда болжол менен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> калды"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Зымсыз кубатталууда"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Кубатталууда"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Кубат алган жок"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Туташты, кубатталган жок"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Туташкан, бирок кубатталбай жатат"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Кубатталды"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Толук кубатталды"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Кубаттоо күтүү режиминде"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Администратор тарабынан көзөмөлдөнөт"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Чектелген параметр аркылуу көзөмөлдөнөт"</string>
     <string name="disabled" msgid="8017887509554714950">"Өчүрүлгөн"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 23f249a0d..4e47201 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"ຮູບພາບ"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"ຫູຟັງ"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"ອຸປະກອນພ່ວງອິນພຸດ"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"ເຄື່ອງຊ່ວຍຟັງ"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"WiFi ປິດຢູ່."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"ຕັດການເຊື່ອມຕໍ່ Wi-Fi ແລ້ວ."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"ການແກ້ໄຂສີອາດມີປະໂຫຍດໃນເວລາທີ່ທ່ານຕ້ອງການ:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;ເບິ່ງສີໃຫ້ມີຄວາມຖືກຕ້ອງຫຼາຍຂຶ້ນ&lt;/li&gt; &lt;li&gt;&amp;nbsp;ລຶບສີອອກເພື່ອຊ່ວຍທ່ານໂຟກັສ&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"ຖືກແທນໂດຍ <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ຢຸດການສາກຊົ່ວຄາວເພື່ອປົກປ້ອງແບັດເຕີຣີ"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - ກຳລັງກວດສອບອຸປະກອນເສີມສຳລັບການສາກ"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"ເຫຼືອອີກປະມານ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"ເຫຼືອອີກປະມານ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"ເຫຼືອອີກປະມານ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ອ້າງອີງຈາກການນຳໃຊ້ຂອງທ່ານ"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"ກຳລັງສາກໄຟໄຮ້ສາຍ"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"ກຳລັງສາກໄຟ"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"ບໍ່ໄດ້ສາກໄຟ"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"ເຊື່ອມຕໍ່ແລ້ວ, ບໍ່ໄດ້ສາກໄຟ"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"ເຊື່ອມຕໍ່ແລ້ວ, ແຕ່ຍັງບໍ່ສາກ"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"ສາກເຕັມແລ້ວ"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"ສາກເຕັມແລ້ວ"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"ຢຸດການສາກຊົ່ວຄາວ"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"ຄວບຄຸມໂດຍຜູ້ເບິ່ງແຍງ"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"ຄວບຄຸມໂດຍການຕັ້ງຄ່າທີ່ຈຳກັດໄວ້"</string>
     <string name="disabled" msgid="8017887509554714950">"ປິດການນຳໃຊ້"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index f529ca4..d0d9507 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Vaizdavimo įrenginys"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Ausinės"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Išorinis įvesties įrenginys"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Klausos aparatai"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"„Wi-Fi“ išjungtas."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"„Wi-Fi“ atjungtas."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Spalvų taisymas gali būti naudingas, kai norite:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;aiškiau matyti spalvas;&lt;/li&gt; &lt;li&gt;&amp;nbsp;pašalinti spalvas, kad galėtumėte sutelkti dėmesį.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Nepaisyta naudojant nuostatą „<xliff:g id="TITLE">%1$s</xliff:g>“"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – įkrovimas pristabdytas, siekiant apsaugoti akumuliatorių"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – tikrinamas įkrovimo priedas"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Liko maždaug <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Liko maždaug <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Liko maždaug <xliff:g id="TIME_REMAINING">%1$s</xliff:g>, atsižvelgiant į naudojimą"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Kraunama be laidų"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Įkraunama"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nekraunama"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Prijungta, neįkraunama"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Prijungta, bet nekraunama"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Įkrauta"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Visiškai įkrautas"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Įkrovimas pristabdytas"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Valdo administratorius"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Valdoma pagal apribotą nustatymą"</string>
     <string name="disabled" msgid="8017887509554714950">"Neleidžiama"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 24be0aa..43a940b 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Attēlu apstrādes ierīce"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Austiņas"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Ievades ierīce"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Dzirdes aparāti"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi savienojums izslēgts"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi savienojums pārtraukts"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Krāsu korekcija var būt noderīga šādiem mērķiem:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;precīzākai krāsu attēlošanai;&lt;/li&gt; &lt;li&gt;&amp;nbsp;krāsu noņemšanai, lai būtu vieglāk koncentrēties.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Jaunā preference: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> — <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> — uzlāde apturēta, lai aizsargātu akumulatoru"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> — notiek uzlādes piederuma pārbaude"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Aptuvenais atlikušais laiks: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Aptuvenais atlikušais laiks: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Ņemot vērā lietojumu, atlikušais laiks: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Bezvadu uzlāde"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Notiek uzlāde"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nenotiek uzlāde"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Ierīce pievienota, uzlāde nenotiek"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Savienota, taču netiek uzlādēta"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Uzlādēts"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Pilnībā uzlādēts"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Uzlāde apturēta"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Kontrolē administrators"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kontrolē ierobežots iestatījums"</string>
     <string name="disabled" msgid="8017887509554714950">"Atspējots"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 030a438..db5bdf4 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Слики"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Слушалка"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Периферен влез"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Слушни помагала"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi е исклучено."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi е исклучено."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Корекцијата на боите може да биде корисна кога сакате:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;да ги гледате боите попрецизно&lt;/li&gt; &lt;li&gt;&amp;nbsp;да ги отстраните боите за полесно да се концентрирате&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Прескокнато според <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - полнењето е паузирано за да се заштити батеријата"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - се проверува додатокот за полнење"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Уште околу <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Уште околу <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Уште околу <xliff:g id="TIME_REMAINING">%1$s</xliff:g> според вашето користење"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Се полни безжично"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Се полни"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не се полни"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Поврзано, не се полни"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Поврзано, но не се полни"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Полна"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Целосно полна"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Полнењето е паузирано"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Контролирано од администраторот"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Контролирано со ограничени поставки"</string>
     <string name="disabled" msgid="8017887509554714950">"Оневозможено"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 9a01b53..947ab91 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"ഇമേജിംഗ്"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"ഹെഡ്ഫോൺ"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"ഇൻപുട്ട് പെരിഫറൽ"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"ശ്രവണ സഹായികൾ"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"വൈഫൈ ഓഫാണ്."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"വൈഫൈ വിച്ഛേദിച്ചു."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"ഇനിപ്പറയുന്ന കാര്യങ്ങൾ ചെയ്യാൻ ആഗ്രഹിക്കുമ്പോൾ നിറം ശരിയാക്കൽ സഹായകരമാകും:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;നിറങ്ങൾ കൂടുതൽ കൃത്യമായി കാണാൻ&lt;/li&gt; &lt;li&gt;&amp;nbsp;ഫോക്കസ് ചെയ്യാൻ നിങ്ങളെ സഹായിക്കുന്നതിന് നിറങ്ങൾ നീക്കം ചെയ്യാൻ&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ഉപയോഗിച്ച് അസാധുവാക്കി"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ബാറ്ററി പരിരക്ഷിക്കാൻ ചാർജിംഗ് ഹോൾഡിലാണ്"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - ചാർജിംഗ് ആക്സസറി പരിശോധിക്കുന്നു"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"ഏതാണ്ട് <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ശേഷിക്കുന്നു"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"ഏതാണ്ട് <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ശേഷിക്കുന്നു (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"നിങ്ങളുടെ ഉപയോഗത്തെ അടിസ്ഥാനമാക്കി ഏതാണ്ട് <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ശേഷിക്കുന്നു"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"വയർലെസായി ചാർജുചെയ്യുന്നു"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"ചാർജ് ചെയ്യുന്നു"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"ചാർജ്ജുചെയ്യുന്നില്ല"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"കണക്റ്റ് ചെയ്‌തിരിക്കുന്നു, ചാർജ് ചെയ്യുന്നില്ല"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"കണക്റ്റ് ചെയ്തു, എന്നാൽ ചാർജ് ചെയ്യുന്നില്ല"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"ചാർജായി"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"പൂർണ്ണമായി ചാർജ് ചെയ്തു"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"ചാർജിംഗ് ഹോൾഡിലാണ്"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"അഡ്‌മിൻ നിയന്ത്രിക്കുന്നത്"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"നിയന്ത്രിത ക്രമീകരണം ഉപയോഗിച്ച് നിയന്ത്രിക്കുന്നത്"</string>
     <string name="disabled" msgid="8017887509554714950">"പ്രവർത്തനരഹിതമാക്കി"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index 03fecc4..787ce97 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Зураглал"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Чихэвч"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Нэмэлт оролт"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Сонсголын төхөөрөмжүүд"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wifi унтраалттай байна."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wifi холбогдоогүй байна."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Өнгө тохируулга нь таныг дараахыг хийхийг хүсэх үед хэрэгтэй байж болно:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Өнгөнүүдийг илүү нарийвчилж харах&lt;/li&gt; &lt;li&gt;&amp;nbsp;Төвлөрөхийн тулд өнгөнүүдийг хасах&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Давхарласан <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Батарейг хамгаалахын тулд цэнэглэхийг хүлээлгэсэн"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Цэнэглэх нэмэлт хэрэгслийг шалгаж байна"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ойролцоогоор <xliff:g id="TIME_REMAINING">%1$s</xliff:g> үлдсэн"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Ойролцоогоор <xliff:g id="TIME_REMAINING">%1$s</xliff:g> үлдсэн (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Таны хэрэглээнд үндэслэн ойролцоогоор <xliff:g id="TIME_REMAINING">%1$s</xliff:g> үлдсэн"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Утасгүй цэнэглэж байна"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Цэнэглэж байна"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Цэнэглэхгүй байна"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Холбогдсон, цэнэглээгүй байна"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Холбогдсон ч цэнэглэхгүй байна"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Цэнэглэсэн"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Бүрэн цэнэглэсэн"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Цэнэглэхийг хүлээлгэд оруулсан"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Админ удирдсан"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Хязгаарлагдсан тохиргоогоор хянадаг"</string>
     <string name="disabled" msgid="8017887509554714950">"Идэвхгүйжүүлсэн"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index e4edf08..00482ea 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"इमेज"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"हेडफोन"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"इनपुट परिधीय"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"श्रवणयंत्रे"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"ब्लूटूथ"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"वाय-फाय बंद."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"वाय-फाय डिस्कनेक्ट झाले."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"तुम्हाला पुढील गोष्टी करायच्या असतील, तेव्हा रंग सुधारणेची मदत होऊ शकते:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;रंग आणखी अचूकपणे पाहण्यासाठी&lt;/li&gt; &lt;li&gt;&amp;nbsp;तुम्हाला लक्ष केंद्रित करण्यात मदत करण्याकरिता रंग काढून टाकण्यासाठी&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> द्वारे अधिलिखित"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - बॅटरीचे संरक्षण करण्यासाठी चार्जिंग थांबवले आहे"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिंगसंबंधित ॲक्सेसरी तपासत आहे"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"अंदाजे <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाकी आहे"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"अंदाजे <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाकी आहे (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"तुमच्‍या वापरावर आधारित अंदाजे <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाकी आहे"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"वायरलेसने चार्ज होत आहे"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"चार्ज होत आहे"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"चार्ज होत नाही"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"कनेक्ट केले, चार्ज होत नाही"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"कनेक्ट केलेले आहे, पण चार्ज होत नाही"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"चार्ज झाली"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"पूर्ण चार्ज झाली"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"चार्जिंग थांबवले आहे"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"प्रशासकाने नियंत्रित केलेले"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"प्रतिबंधित केलेल्या सेटिंग द्वारे नियंत्रित"</string>
     <string name="disabled" msgid="8017887509554714950">"अक्षम"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 88f13f6f..bd3eb99 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Pengimejan"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Fon kepala"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Persisian Input"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Alat Bantu Pendengaran"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi dimatikan."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi diputuskan sambungannya."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Pembetulan warna dapat membantu apabila anda mahu:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Melihat warna dengan lebih tepat&lt;/li&gt; &lt;li&gt;&amp;nbsp;Mengalih keluar warna agar anda dapat menumpukan perhatian&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Diatasi oleh <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengecasan ditunda untuk melindungi bateri"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Memeriksa aksesori pengecasan"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Kira-kira <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Kira-kira <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Kira-kira <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi berdasarkan penggunaan anda"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Mengecas tanpa wayar"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Pengecasan"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Tidak mengecas"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Bersambung, tidak mengecas"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Disambungkan tetapi tidak dicas"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Sudah dicas"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Dicas Penuh"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Pengecasan ditunda"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Dikawal oleh pentadbir"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Dikawal oleh Tetapan Terhad"</string>
     <string name="disabled" msgid="8017887509554714950">"Dilumpuhkan"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 58e2cf4..c18b273 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"ဓာတ်ပုံဆိုင်ရာ"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"နားကြပ်"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"ချိတ်ဆက်အသုံးပြုရသည့် စက်ပစ္စည်းများ"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"နားကြားကိရိယာ"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"ဘလူးတုသ်"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi  ပိတ်ထားသည်"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi  ချိတ်ဆက်ထားမှု မရှိပါ"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"အရောင် အမှန်ပြင်ခြင်းသည် အောက်ပါတို့အတွက် အသုံးဝင်နိုင်သည်-&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;အရောင်များကို ပိုမိုမှန်ကန်စွာ ကြည့်ရှုခြင်း&lt;/li&gt; &lt;li&gt;&amp;nbsp;အာရုံစိုက်နိုင်ရန် အရောင်များ ဖယ်ရှားခြင်း&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> မှ ကျော်၍ လုပ်ထားသည်။"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ဘက်ထရီကာကွယ်ရန် အားသွင်းခြင်းကို ခဏရပ်ထားသည်"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - အားသွင်းပစ္စည်း စစ်ဆေးနေသည်"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ခန့် ကျန်သည်"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ခန့် ကျန်သည် (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"သင်၏ အသုံးပြုမှု အပေါ် မူတည်၍ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ခန့် ကျန်သည်"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"ကြိုးမဲ့ အားသွင်းနေသည်"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"အားသွင်းနေသည်"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"အားသွင်းမနေပါ"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"ချိတ်ဆက်ထားသည်၊ အားသွင်းမနေပါ"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"ချိတ်ဆက်ထားသော်လည်း အားသွင်းမနေပါ"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"အားသွင်းပြီးပါပြီ"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"အားအပြည့်သွင်းထားသည်"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"အားသွင်းခြင်းကို ခဏရပ်ထားသည်"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"စီမံခန့်ခွဲသူမှ ထိန်းချုပ်ပါသည်"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"ကန့်သတ်ထားသော ဆက်တင်များဖြင့် ထိန်းချုပ်ထားသည်"</string>
     <string name="disabled" msgid="8017887509554714950">"ပိတ်ထားပြီး"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 0815268..b3c4295 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Bildefremviser"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Øretelefoner"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Inndata fra ytre utstyrsenheter"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Høreapparater"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wifi er av."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wifi er frakoblet."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Fargekorrigering kan være nyttig når du vil&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;se farger mer nøyaktig&lt;/li&gt; &lt;li&gt;&amp;nbsp;fjerne farger for å gjøre det enklere å fokusere&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overstyres av <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ladingen er satt på vent for å beskytte batteriet"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Sjekker ladetilbehøret"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Omtrent <xliff:g id="TIME_REMAINING">%1$s</xliff:g> igjen"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Omtrent <xliff:g id="TIME_REMAINING">%1$s</xliff:g> igjen (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Omtrent <xliff:g id="TIME_REMAINING">%1$s</xliff:g> igjen basert på bruken din"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Lader trådløst"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Lader"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Lader ikke"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Tilkoblet, lader ikke"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Tilkoblet, men lader ikke"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Ladet"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fulladet"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Ladingen er satt på vent"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Kontrollert av administratoren"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kontrollert av en begrenset innstilling"</string>
     <string name="disabled" msgid="8017887509554714950">"Slått av"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 6c4ddf2..377078d 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"छवि सम्बन्धी"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"हेडफोन"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"इनपुट सम्बन्धी बाह्य यन्त्र"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"श्रवण यन्त्रहरू"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"ब्लुटुथ"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi बन्द।"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi जडान विच्छेद भयो।"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"तपाईं रङ सच्याउने सुविधाका सहायताले निम्न कार्य गर्न सक्नुहुन्छ:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;अझ सटीक तरिकाले रङहरू हेर्न&lt;/li&gt; &lt;li&gt;&amp;nbsp;फोकस गर्नका लागि रङहरू हटाउन&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> द्वारा अधिरोहित"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ब्याट्री जोगाउन चार्जिङ होल्ड गरिएको छ"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिङ एक्सेसरीको जाँच गरिँदै छ"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"लगभग <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाँकी छ"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"लगभग <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाँकी छ (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"तपाईंको प्रयोगको आधारमा लगभग <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाँकी छ"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"वायरलेस तरिकाले चार्ज गरिँदै छ"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"चार्ज हुँदै छ"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"चार्ज भइरहेको छैन"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"कनेक्ट गरिएको छ, चार्ज भइरहेको छैन"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"जोडिएको छ तर चार्ज गरिराखिएको छैन"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"चार्ज भयो"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"पूर्ण रूपमा चार्ज भएको छ"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"चार्जिङ होल्ड गरिएको छ"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"प्रशासकद्वारा नियन्त्रित"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"प्रतिबन्धित सेटिङले नियन्त्रण गरेको"</string>
     <string name="disabled" msgid="8017887509554714950">"असक्षम पारियो"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index a65ff730..f576722 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Replicatieapparaat"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Hoofdtelefoon"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Randapparaat voor invoer"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Hoortoestellen"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wifi staat uit."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wifi-verbinding verbroken."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Kleurcorrectie kan handig zijn in de volgende situaties:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Je wilt kleuren nauwkeuriger zien.&lt;/li&gt; &lt;li&gt;&amp;nbsp;Je wilt kleuren verwijderen zodat je je beter kunt focussen.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overschreven door <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g>: opladen is in de wacht gezet om de batterij te beschermen"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g>: oplaadaccessoire checken"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Nog ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Nog ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Nog ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g> op basis van je gebruik"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Draadloos opladen"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Opladen"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Wordt niet opgeladen"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Verbonden, wordt niet opgeladen"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Verbonden, maar wordt niet opgeladen"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Opgeladen"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Volledig opgeladen"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Opladen in de wacht"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Ingesteld door beheerder"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Beheerd door beperkte instelling"</string>
     <string name="disabled" msgid="8017887509554714950">"Uitgezet"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index add9ff7..30bb050 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"ଇମେଜିଙ୍ଗ"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"ହେଡ୍‌ଫୋନ୍‌"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"ଇନ୍‌ପୁଟ୍‌ ଉପକରଣ"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"ଶ୍ରବଣ ଯନ୍ତ୍ର"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"ବ୍ଲୁଟୁଥ"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"ୱାଇ-ଫାଇ ବନ୍ଦ।"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"ୱାଇଫାଇ ବିଚ୍ଛିନ୍ନ କରାଗଲା।"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"ଆପଣ ଏସବୁ କରିବାକୁ ଚାହିଁଲେ ରଙ୍ଗ ସଂଶୋଧନ ଉପଯୋଗୀ ହୋଇପାରିବ:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;ଆହୁରି ସଠିକ୍ ଭାବେ ରଙ୍ଗଗୁଡ଼ିକ ଦେଖିବା&lt;/li&gt; &lt;li&gt;&amp;nbsp;ଆପଣଙ୍କୁ ଫୋକସ କରିବାରେ ସାହାଯ୍ୟ କରିବା ପାଇଁ ରଙ୍ଗଗୁଡ଼ିକୁ କାଢ଼ିବା&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ଦ୍ୱାରା ଓଭର୍‌ରାଇଡ୍‌ କରାଯାଇଛି"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ବେଟେରୀକୁ ସୁରକ୍ଷିତ ରଖିବା ପାଇଁ ଚାର୍ଜିଂ ହୋଲ୍ଡରେ ଅଛି"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - ଚାର୍ଜିଂ ଆକସେସୋରୀକୁ ଯାଞ୍ଚ କରାଯାଉଛି"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"ପାଖାପାଖି <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ବଳକା ଅଛି"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"ପାଖାପାଖି <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ପାଇଁ (<xliff:g id="LEVEL">%2$s</xliff:g>) ବଳକା ଅଛି"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"ଆପଣଙ୍କ ବ୍ୟବହାରକୁ ଆଧାର କରି ପାଖାପାଖି <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ବଳକା ଅଛି"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"ୱେୟରଲେସ ଭାବେ ଚାର୍ଜିଂ"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"ଚାର୍ଜ ହେଉଛି"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"ଚାର୍ଜ ହେଉନାହିଁ"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"ସଂଯୋଗ କରାଯାଇଛି, ଚାର୍ଜ ହେଉନାହିଁ"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"କନେକ୍ଟ ହୋଇଛି, କିନ୍ତୁ ଚାର୍ଜ ହେଉନାହିଁ"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"ଚାର୍ଜ ହୋଇଯାଇଛି"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"ସମ୍ପୂର୍ଣ୍ଣ ଭାବରେ ଚାର୍ଜ ହୋଇଛି"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"ଚାର୍ଜିଂ ହୋଲ୍ଡରେ ଅଛି"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"ଆଡ୍‌ମିନ୍‌ ଦ୍ୱାରା ନିୟନ୍ତ୍ରିତ"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"ପ୍ରତିବନ୍ଧିତ ସେଟିଂ ଦ୍ୱାରା ନିୟନ୍ତ୍ରଣ କରାଯାଇଛି"</string>
     <string name="disabled" msgid="8017887509554714950">"ଅକ୍ଷମ ହୋଇଛି"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 42d47f6..d25b921 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"ਇਮੇਜਿੰਗ"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"ਹੈੱਡਫੋਨ"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"ਇਨਪੁੱਟ ਪੈਰਿਫੈਰਲ"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"ਸੁਣਨ ਦੇ ਸਾਧਨ"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"ਬਲੂਟੁੱਥ"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wifi ਬੰਦ।"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wifi ਡਿਸਕਨੈਕਟ ਕੀਤਾ।"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"ਰੰਗ ਸੁਧਾਈ ਉਦੋਂ ਲਾਹੇਵੰਦ ਹੋ ਸਕਦੀ ਹੈ, ਜਦੋਂ ਤੁਸੀਂ:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;ਰੰਗਾਂ ਨੂੰ ਹੋਰ ਸਹੀ ਢੰਗ ਨਾਲ ਦੇਖਣਾ ਚਾਹੋ&lt;/li&gt; &lt;li&gt;&amp;nbsp;ਫੋਕਸ ਕਰਨ ਵਿੱਚ ਮਦਦ ਲਈ ਰੰਗ ਹਟਾਉਣਾ ਚਾਹੋ&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ਦੁਆਰਾ ਓਵਰਰਾਈਡ ਕੀਤਾ"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਬੈਟਰੀ ਦੀ ਸੁਰੱਖਿਆ ਲਈ ਚਾਰਜਿੰਗ ਨੂੰ ਰੋਕਿਆ ਗਿਆ ਹੈ"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਚਾਰਜਿੰਗ ਐਕਸੈਸਰੀ ਦੀ ਜਾਂਚ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"ਲਗਭਗ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ਬਾਕੀ"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"ਲਗਭਗ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ਬਾਕੀ (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"ਤੁਹਾਡੀ ਵਰਤੋਂ ਦੇ ਆਧਾਰ \'ਤੇ ਲਗਭਗ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ਬਾਕੀ"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"ਬਿਨਾਂ ਤਾਰ ਤੋਂ ਚਾਰਜ ਹੋ ਰਹੀ ਹੈ"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"ਚਾਰਜ ਨਹੀਂ ਹੋ ਰਿਹਾ"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"ਕਨੈਕਟ ਹੈ, ਚਾਰਜ ਨਹੀਂ ਹੋ ਰਹੀ"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"ਕਨੈਕਟ ਹੋ ਗਿਆ, ਪਰ ਚਾਰਜ ਨਹੀਂ ਹੋ ਰਿਹਾ ਹੈ"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"ਚਾਰਜ ਹੋ ਗਈ"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"ਪੂਰੀ ਚਾਰਜ ਹੋ ਗਈ ਹੈ"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"ਚਾਰਜਿੰਗ ਨੂੰ ਰੋਕਿਆ ਗਿਆ ਹੈ"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਕੰਟਰੋਲ ਕੀਤੀ ਗਈ"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"ਪ੍ਰਤਿਬੰਧਿਤ ਸੈਟਿੰਗ ਰਾਹੀਂ ਕੰਟਰੋਲ ਕੀਤੀ ਜਾਂਦੀ ਹੈ"</string>
     <string name="disabled" msgid="8017887509554714950">"ਅਯੋਗ ਬਣਾਇਆ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index b319cab..72a1b2c 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Obrazowanie"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Słuchawki"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Peryferyjne urządzenie wejściowe"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Aparaty słuchowe"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi wyłączone."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi odłączone."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Korekcja kolorów może być pomocna, gdy:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;chcesz wyraźniej widzieć kolory;&lt;/li&gt; &lt;li&gt;&amp;nbsp;chcesz usunąć kolory, aby łatwiej było się skupić.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Nadpisana przez <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – wstrzymano ładowanie, aby chronić baterię"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – sprawdzam akcesoria do ładowania"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Jeszcze około <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Jeszcze około <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Jeszcze około <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (na podstawie Twojego sposobu korzystania)"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Ładowanie bezprzewodowe"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Ładowanie"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nie podłączony"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Podłączono, brak ładowania"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Podłączono, ale nie ładuje się"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Naładowana"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Bateria w pełni naładowana"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Ładowanie wstrzymane"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Kontrolowane przez administratora"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kontrolowane przez ograniczone ustawienia"</string>
     <string name="disabled" msgid="8017887509554714950">"Wyłączone"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 32fa1b6..814215e 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Imagem"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Headphone"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Periférico de entrada"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Aparelhos auditivos"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi desligado."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi desconectado"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"A correção de cor pode ser útil caso você queira:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;ver cores mais nítidas;&lt;/li&gt; &lt;li&gt;&amp;nbsp;remover cores para que você possa se concentrar.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carregamento suspenso para proteger a bateria"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Verificando o acessório de carregamento"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Tempo restante aproximado, com base no seu uso: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Carregando sem fio"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Carregando"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Não está carregando"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Conectado sem carregar"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Conectado, mas não está carregando"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Carregada"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Carga completa"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Carregamento suspenso"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlada pelo admin"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlada pelas configurações restritas"</string>
     <string name="disabled" msgid="8017887509554714950">"Desativado"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 1a81f2d..1d7b609 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Dispositivo de imagem"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Auricular"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Periférico de entrada"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Aparelhos auditivos"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi desativado."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi desligado."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"A correção da cor pode ser útil quando quiser:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Ver cores com maior precisão&lt;/li&gt; &lt;li&gt;&amp;nbsp;Remover cores para ajudar a concentrar-se&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carregamento em espera para proteger a bateria"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – A verificar o acessório de carregamento"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Resta(m) cerca de <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Resta(m) cerca de <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Resta(m) cerca de <xliff:g id="TIME_REMAINING">%1$s</xliff:g> com base na sua utilização"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"A carregar sem fios"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"A carregar"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Não está a carregar"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Ligado, não está a carregar"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Ligado, mas não está a carregar"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Carregada"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Totalmente carregada"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Carregamento em espera"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlado pelo gestor"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlado por uma definição restrita"</string>
     <string name="disabled" msgid="8017887509554714950">"Desativada"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 32fa1b6..814215e 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Imagem"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Headphone"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Periférico de entrada"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Aparelhos auditivos"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi desligado."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi desconectado"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"A correção de cor pode ser útil caso você queira:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;ver cores mais nítidas;&lt;/li&gt; &lt;li&gt;&amp;nbsp;remover cores para que você possa se concentrar.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carregamento suspenso para proteger a bateria"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Verificando o acessório de carregamento"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Tempo restante aproximado, com base no seu uso: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Carregando sem fio"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Carregando"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Não está carregando"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Conectado sem carregar"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Conectado, mas não está carregando"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Carregada"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Carga completa"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Carregamento suspenso"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlada pelo admin"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlada pelas configurações restritas"</string>
     <string name="disabled" msgid="8017887509554714950">"Desativado"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 5956d63..def9d89 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Dispozitiv pentru imagini"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Căști"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Dispozitiv periferic de intrare"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Aparate auditive"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi dezactivat."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi deconectat."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Corecția culorii poate fi utilă dacă vrei:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;să vezi mai precis culorile;&lt;/li&gt; &lt;li&gt;&amp;nbsp;să elimini culorile pentru a te concentra mai bine.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Valoare înlocuită de <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Încărcarea s-a întrerupt pentru a proteja bateria"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Se verifică accesoriul de încărcare"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Timp aproximativ rămas: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Timp aproximativ rămas: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"În baza utilizării, timpul rămas este de aproximativ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Se încarcă wireless"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Se încarcă"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nu se încarcă"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Conectat, nu se încarcă"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Conectat, dar nu se încarcă"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Încărcată"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Complet încărcată"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Încărcare întreruptă"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlată de administrator"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlată de setarea restricționată"</string>
     <string name="disabled" msgid="8017887509554714950">"Dezactivată"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index fe65be5..5cc1a5b 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Камера"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Наушники"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Периферийное устройство ввода"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Слуховые аппараты"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi выключен"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi отключен"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Используйте коррекцию цвета, чтобы:&lt;br/&gt; &lt;ol&gt; &lt;li&gt; Добиться нужной цветопередачи.&lt;/li&gt; &lt;li&gt; Убрать цвета, которые мешают сосредоточиться.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Новая настройка: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"Уровень заряда – <xliff:g id="PERCENTAGE">%1$s</xliff:g>. <xliff:g id="TIME_STRING">%2$s</xliff:g>."</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g>, зарядка приостановлена для защиты батареи"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g>, проверяется зарядное устройство"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Заряда хватит примерно на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Заряда (<xliff:g id="LEVEL">%2$s</xliff:g>) хватит примерно на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Заряда хватит примерно на <xliff:g id="TIME_REMAINING">%1$s</xliff:g> при текущем уровне расхода"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Беспроводная зарядка"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Зарядка"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не заряжается"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Подключено, не заряжается"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Устройство подключено, но не заряжается"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Батарея заряжена"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Батарея заряжена"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Зарядка приостановлена"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Контролируется администратором"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Контролируется настройками с ограниченным доступом"</string>
     <string name="disabled" msgid="8017887509554714950">"Отключено"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 55e0778..0d9ca37 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"නිරූපණය"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"හෙඩ්ෆෝන්"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"ආදාන උපාංග"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"ශ්‍රවණාධාරක"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"බ්ලූටූත්"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wifi අක්‍රියයි."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wifi සම්බන්ධ කර නොමැත."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"ඔබට පහත දේවල් සිදු කිරීම අවශ්‍ය විට වර්ණ නිවැරදි කිරීම ප්‍රයෝජනවත් විය හැකිය:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;වඩාත් නිවැරදිව වර්ණ දැකීම&lt;/li&gt; &lt;li&gt;ඔබට අවධානය යොමු කිරීම‍ට උදවු වීමට වර්ණ ඉවත් කිරීම&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> මගින් ඉක්මවන ලදී"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - බැටරිය ආරක්ෂා කිරීම සඳහා ආරෝපණය රඳවා තබා ඇත"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - ආරෝපණ ආයිත්තම පරීක්ෂා කිරීම"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ක් පමණ ඉතිරියි"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ක් පමණ ඉතිරියි (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"ඔබේ භාවිතය මත පදනම්ව <xliff:g id="TIME_REMAINING">%1$s</xliff:g> පමණ ඉතිරිව ඇත"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"නොරැහැන්ව ආරෝපණය වේ"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"ආරෝපණය වේ"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"ආරෝපණය නොවේ"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"සම්බන්ධයි, ආරෝපණය නොවේ"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"සම්බන්ධයි, නමුත් ආරෝපණය නොවේ"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"අරෝපිතයි"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"සම්පූර්ණයෙන් ආරෝපණ වී ඇත"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"ආරෝපණය රදවාගෙන ඇත"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"පරිපාලක විසින් පාලනය කරන ලදී"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"සීමා කළ සැකසීම මගින් පාලනය වේ"</string>
     <string name="disabled" msgid="8017887509554714950">"අබල කර ඇත"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 4d03ddd..a9b518d 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Snímkovacie zariadenie"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Slúchadlá"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Periférne vstupné zariadenie"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Načúvadlá"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Sieť Wi‑Fi je vypnutá."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Sieť Wi‑Fi je odpojená."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Úprava farieb môže byť užitočná, keď chcete:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;zobrazovať farby presnejšie;&lt;/li&gt; &lt;li&gt;&amp;nbsp;odstrániť farby, aby ste sa mohli sústrediť.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Prekonané predvoľbou <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – nabíjanie je pozastavené, aby sa chránila batéria"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – kontroluje sa nabíjacie príslušenstvo"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ešte približne <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Zostáva približne <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Zostáva približne <xliff:g id="TIME_REMAINING">%1$s</xliff:g> – závisí to od intenzity využitia"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Nabíja sa bezdrôtovo"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Nabíja sa"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nenabíja sa"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Pripojené, nenabíja sa"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Pripojené, ale nenabíja sa"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Nabité"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Úplne nabitá"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Nabíjanie je pozastavené"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Ovládané správcom"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Ovládané obmedzeným nastavením"</string>
     <string name="disabled" msgid="8017887509554714950">"Deaktivované"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index ddf0f71..f739fbe0 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Naprava za zajem slik"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Slušalka"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Zunanja dodatna oprema"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Slušni aparati"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi je izklopljen."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Povezava Wi-Fi je prekinjena."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Popravljanje barv je lahko koristno, ko želite:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;natančneje videti barve;&lt;/li&gt; &lt;li&gt;&amp;nbsp;odstraniti barve, da se lažje osredotočite.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Preglasila nastavitev: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Zaradi zaščite baterije je polnjenje na čakanju"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Preverjanje pripomočka za polnjenje"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Še približno <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Še približno <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Glede na način uporabe še približno <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Brezžično polnjenje"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Polnjenje"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Se ne polni"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Povezano, se ne polni"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Naprava je povezana, vendar se ne polni"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Napolnjeno"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Popolnoma napolnjena"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Polnjenje je na čakanju"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Nadzira skrbnik"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Pod nadzorom omejene nastavitve"</string>
     <string name="disabled" msgid="8017887509554714950">"Onemogočeno"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index b627d09..a722ad1 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Imazhe"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Kufje"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Hyrje periferike"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Aparatet e dëgjimit"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth-i"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi është çaktivizuar."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi është i shkëputur."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Korrigjimi i ngjyrës mund të jetë i dobishëm kur dëshiron:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Të shohësh ngjyrat më saktë&lt;/li&gt; &lt;li&gt;&amp;nbsp;Të heqësh ngjyrat për të të ndihmuar të fokusohesh&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Mbivendosur nga <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Karikimi është vendosur në pritje për të mbrojtur baterinë"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Po kontrollohet aksesori i karikimit"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Rreth <xliff:g id="TIME_REMAINING">%1$s</xliff:g> të mbetura"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Rreth <xliff:g id="TIME_REMAINING">%1$s</xliff:g> të mbetura (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Rreth <xliff:g id="TIME_REMAINING">%1$s</xliff:g> të mbetura bazuar në përdorimin tënd"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Po karikohet wireless"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Po karikohet"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nuk po karikohet"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Lidhur, jo në karikim"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Është lidhur, por nuk po karikohet"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Karikuar"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Karikuar plotësisht"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Karikimi në pritje"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Kontrolluar nga administratori"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kontrollohet nga \"Cilësimet e kufizuara\""</string>
     <string name="disabled" msgid="8017887509554714950">"Çaktivizuar"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 224be52..66e448b 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Обрада слика"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Слушалице"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Периферни уређај за унос"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Слушни апарати"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"WiFi је искључен."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"WiFi веза је прекинута."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Корекција боја може да буде корисна када желите:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Прецизније да видите боје&lt;/li&gt; &lt;li&gt;&amp;nbsp;Да уклоните боје како бисте се фокусирали&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Замењује га <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>–<xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – пуњење је на чекању да би се заштитила батерија"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – провера додатне опреме за пуњење"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Преостало је око <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Преостало је око <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Преостало је око <xliff:g id="TIME_REMAINING">%1$s</xliff:g> на основу коришћења"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Бежично пуњење"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Пуњење"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не пуни се"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Повезано, не пуни се"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Уређај је повезан, али се не пуни"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Напуњено"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Напуњено до краја"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Пуњење је на чекању"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Контролише администратор"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Контролишу ограничена подешавања"</string>
     <string name="disabled" msgid="8017887509554714950">"Онемогућено"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 7ffad7c..82567bf 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Bild"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Hörlur"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Inmatning från kringutrustning"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Hörapparater"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wifi är inaktiverat."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Ingen wifi-anslutning."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Färgkorrigering kan vara bra för att&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;urskilja färger bättre&lt;/li&gt; &lt;li&gt;&amp;nbsp;ta bort färger som distraherar&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Har åsidosatts av <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Laddningen har pausats för att skydda batteriet"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Kontrollerar laddningstillbehöret"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Cirka <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kvar"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Cirka <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kvar (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Cirka <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kvar utifrån din användning"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Laddas trådlöst"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Laddas"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Laddar inte"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Ansluten, laddas inte"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Ansluten men laddas inte"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Laddat"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fulladdad"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Laddningen har pausats"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Strys av administratören"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Styrs av spärrad inställning"</string>
     <string name="disabled" msgid="8017887509554714950">"Inaktiverad"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index a94c313..0d1ef46 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Kupiga picha"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Kifaa cha sauti"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Vifaa vya Ziada vya Kuingiza Data"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Visaidizi vya Kusikia"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wifi imezimwa."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wifi imeondolewa."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Usahihishaji wa rangi unaweza kusaidia wakati unataka:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Kuona rangi kwa usahihi zaidi&lt;/li&gt; &lt;li&gt;&amp;nbsp;Kuondoa rangi ili ikusaidie kumakinika&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Imetanguliwa na <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Imesitisha kuchaji ili kulinda betri yako"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Inakagua kifaa cha kuchaji"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Zimesalia takribani <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Zimesalia takribani <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Zimesalia takribani <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kulingana na jinsi unavyoitumia"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Inachaji bila kutumia waya"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Inachaji"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Haichaji"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Imeunganishwa, haichaji"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Imeunganishwa, lakini haichaji"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Imechajiwa"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Imejaa Chaji"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Imesitisha kuchaji"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Imedhibitiwa na msimamizi"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Imedhibitiwa na Mpangilio wenye Mipaka"</string>
     <string name="disabled" msgid="8017887509554714950">"Imezimwa"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index b76f7e3..a5ce169 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"இமேஜிங்"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"ஹெட்ஃபோன்"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"இன்புட் பெரிபெரல்"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"செவித்துணைக் கருவிகள்"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"புளூடூத்"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"வைஃபை முடக்கப்பட்டது."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"வைஃபை துண்டிக்கப்பட்டது."</string>
@@ -178,7 +179,7 @@
     <string name="running_process_item_user_label" msgid="3988506293099805796">"பயனர்: <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
     <string name="launch_defaults_some" msgid="3631650616557252926">"சில இயல்புநிலைகள் அமைக்கப்பட்டன"</string>
     <string name="launch_defaults_none" msgid="8049374306261262709">"இயல்புநிலைகள் எதுவும் அமைக்கப்படவில்லை"</string>
-    <string name="tts_settings" msgid="8130616705989351312">"உரை வடிவத்திலிருந்து பேச்சுக்கான அமைப்பு"</string>
+    <string name="tts_settings" msgid="8130616705989351312">"\'எழுத்திலிருந்து பேச்சு\' அமைப்புகள்"</string>
     <string name="tts_settings_title" msgid="7602210956640483039">"உரையிலிருந்து பேச்சாக மாற்றுதல்"</string>
     <string name="tts_default_rate_title" msgid="3964187817364304022">"பேச்சு வீதம்"</string>
     <string name="tts_default_rate_summary" msgid="3781937042151716987">"பேசப்படும் உரையின் வேகம்"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"நீங்கள் இவற்றைச் செய்ய விரும்பும்போது கலர் கரெக்‌ஷன் உதவும்:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;வண்ணங்களை மிகத் துல்லியமாகப் பார்த்தல்&lt;/li&gt; &lt;li&gt;&amp;nbsp;கவனம் செலுத்துவதற்கு உதவ வண்ணங்களை அகற்றுதல்&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> மூலம் மேலெழுதப்பட்டது"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - பேட்டரியைப் பாதுகாப்பதற்காகச் சார்ஜிங் இடைநிறுத்தப்பட்டுள்ளது"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - சார்ஜிங் துணைக்கருவியைச் சரிபார்க்கிறது"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"கிட்டத்தட்ட <xliff:g id="TIME_REMAINING">%1$s</xliff:g> மீதமுள்ளது"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"கிட்டத்தட்ட <xliff:g id="TIME_REMAINING">%1$s</xliff:g> மீதமுள்ளது (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"உபயோகத்தின் அடிப்படையில் கிட்டத்தட்ட <xliff:g id="TIME_REMAINING">%1$s</xliff:g> மீதமுள்ளது"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"வயரின்றி சார்ஜாகிறது"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"சார்ஜாகிறது"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"சார்ஜ் செய்யப்படவில்லை"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"இணைக்கப்பட்டுள்ளது, சார்ஜாகவில்லை"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"இணைக்கப்பட்டது. ஆனால் சார்ஜ் ஆகவில்லை"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"சார்ஜாகிவிட்டது"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"முழுவதும் சார்ஜாகிவிட்டது"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"சார்ஜிங் இடைநிறுத்தப்பட்டுள்ளது"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"நிர்வாகி கட்டுப்படுத்துகிறார்"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"வரையறுக்கப்பட்ட அமைப்பால் கட்டுப்படுத்தப்படுகிறது"</string>
     <string name="disabled" msgid="8017887509554714950">"முடக்கப்பட்டது"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 23c00c1..ce432b6 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"ప్రతిబింబనం"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"హెడ్‌ఫోన్"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"ఇన్‌పుట్ అనుబంధ పరికరం"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"వినికిడి పరికరాలు"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"బ్లూటూత్"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wifi ఆఫ్‌లో ఉంది."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wifi డిస్‌కనెక్ట్ చేయబడింది."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"మీరు కింది వాటిని చేయాలనుకున్నప్పుడు కలర్ కరెక్షన్ సహాయకరంగా ఉంటుంది:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;రంగులను మరింత ఖచ్చితంగా చూసేందుకు సహాయపడుతుంది&lt;/li&gt; &lt;li&gt;&amp;nbsp;మీరు ఫోకస్ చేయడంలో సహాయపడటానికి రంగులను తీసివేయండి&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ద్వారా భర్తీ చేయబడింది"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - బ్యాటరీని రక్షించడానికి ఛార్జింగ్ హోల్డ్‌లో ఉంచబడింది"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - ఛార్జ్ చేసే పరికరాన్ని చెక్ చేయండి"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> సమయం మిగిలి ఉంది"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"దాదాపు <xliff:g id="TIME_REMAINING">%1$s</xliff:g> సమయం మిగిలి ఉంది (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"మీ వినియోగం ఆధారంగా దాదాపు <xliff:g id="TIME_REMAINING">%1$s</xliff:g> సమయం మిగిలి ఉంది"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"వైర్‌లెస్ ఛార్జింగ్"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"ఛార్జ్ అవుతోంది"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"ఛార్జ్ కావడం లేదు"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"కనెక్ట్ చేయబడింది, ఛార్జ్ చేయబడలేదు"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"కనెక్ట్ అయి ఉంది, కానీ ఛార్జ్ అవ్వడం లేదు"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"ఛార్జ్ చేయబడింది"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"పూర్తి ఛార్జ్ అయింది"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"ఛార్జింగ్ హోల్డ్‌లో ఉంది"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"నిర్వాహకుని ద్వారా నియంత్రించబడింది"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"పరిమితం చేసిన సెట్టింగ్ ద్వారా నియంత్రించబడుతుంది"</string>
     <string name="disabled" msgid="8017887509554714950">"డిజేబుల్ చేయబడింది"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 4d9d382..183130b 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"การถ่ายภาพ"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"หูฟัง"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"อุปกรณ์อินพุต"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"เครื่องช่วยฟัง"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"บลูทูธ"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi ปิดอยู่"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"ไม่ได้เชื่อมต่อ Wi-Fi"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"การแก้สีจะเป็นประโยชน์เมื่อคุณต้องการที่จะ&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;มองเห็นสีได้ถูกต้องยิ่งขึ้น&lt;/li&gt; &lt;li&gt;&amp;nbsp;นำสีออกเพื่อช่วยให้เห็นชัดเจนยิ่งขึ้น&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"แทนที่โดย <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - หยุดการชาร์จชั่วคราวเพื่อถนอมแบตเตอรี่"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - กำลังตรวจสอบอุปกรณ์เสริมสำหรับการชาร์จ"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"เหลืออีกประมาณ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"เหลืออีกประมาณ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"เหลืออีกประมาณ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ขึ้นอยู่กับการใช้งานของคุณ"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"กำลังชาร์จแบบไร้สาย"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"กำลังชาร์จ"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"ไม่ได้ชาร์จ"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"เชื่อมต่ออยู่ ไม่ได้ชาร์จ"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"เชื่อมต่อแล้ว แต่ยังไม่ชาร์จ"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"ชาร์จแล้ว"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"ชาร์จเต็มแล้ว"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"หยุดการชาร์จชั่วคราว"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"ผู้ดูแลระบบเป็นผู้ควบคุม"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"ควบคุมโดยการตั้งค่าที่จำกัด"</string>
     <string name="disabled" msgid="8017887509554714950">"ปิดอยู่"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index c9e3494..53f75cb 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Imaging"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Headphone"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Input Peripheral"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Mga Hearing Aid"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Naka-off ang Wifi."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Nakadiskonekta ang Wifi."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Puwedeng makatulong ang pagtatama ng kulay kapag gusto mong:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Makita nang mas tumpak ang mga kulay&lt;/li&gt; &lt;li&gt;&amp;nbsp;Alisin ang mga kulay para matulungan kang tumuon&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Na-override ng <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Naka-hold ang pag-charge para protektahan ang baterya"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Sinusuri ang accessory sa pag-charge"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Humigit-kumulang <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ang natitira"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Humigit-kumulang <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ang natitira (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Humigit-kumulang <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ang natitira batay sa iyong paggamit"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Wireless na nagcha-charge"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Nagcha-charge"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Hindi nagcha-charge"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Nakakonekta, hindi nagcha-charge"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Nakakonekta, pero hindi nagcha-charge"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Charged"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Puno ang Baterya"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Naka-hold ang pag-charge"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Pinapamahalaan ng admin"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kinokontrol ng Pinaghihigpitang Setting"</string>
     <string name="disabled" msgid="8017887509554714950">"Naka-disable"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 0f628b4..8e491a1 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Görüntüleme"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Kulaklık"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Giriş Çevre Birimi"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"İşitme Cihazları"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Kablosuz kapalı."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Kablosuz bağlantı kesildi."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Renk düzeltme aşağıdaki durumlarda faydalı olabilir:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Renkleri daha doğru görmek istediğinizde&lt;/li&gt; &lt;li&gt;&amp;nbsp;Odaklanmak için renkleri kaldırmak istediğinizde&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> tarafından geçersiz kılındı"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pili korumak için şarj beklemede"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj aksesuarı kontrol ediliyor"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Yaklaşık <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kaldı"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Yaklaşık <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kaldı (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Kullanımınıza dayalı olarak yaklaşık <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kaldı"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Kablosuz şarj oluyor"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Şarj Etme"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Şarj olmuyor"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Bağlandı, şarj olmuyor"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Bağlı ancak şarj olmuyor"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Şarj oldu"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Pilin Şarjı Tam"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Şarj işlemi beklemede"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Yönetici tarafından denetleniyor"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kısıtlanmış ayar tarafından kontrol ediliyor"</string>
     <string name="disabled" msgid="8017887509554714950">"Devre dışı"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index e00fbc9..45ed508 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Візуалізація"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Навушники"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Периферійне введення"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Слухові апарати"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi вимкнено."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi від’єднано."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Корекція кольору корисна, якщо ви хочете:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;точніше бачити кольори;&lt;/li&gt; &lt;li&gt;&amp;nbsp;вилучити кольори, щоб легше зосереджуватися.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Замінено на <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – заряджання призупинено, щоб захистити акумулятор"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – перевірка зарядного пристрою"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Залишилося приблизно <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Залишилося приблизно <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Згідно з даними про використання залишилося приблизно <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Бездротове заряджання"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Заряджання"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не заряджається"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Підключено, не заряджається"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Пристрій підключено, але він не заряджається"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Заряджено"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Повністю заряджено"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Заряджання призупинено"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Керується адміністратором"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Керується налаштуваннями з обмеженнями"</string>
     <string name="disabled" msgid="8017887509554714950">"Вимкнено"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 0dcb12f..020680e6 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"امیجنگ"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"ہیڈ فون"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"ان پٹ پیریفرل"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"سماعتی آلات"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"بلوٹوتھ"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"‏Wifi آف ہے۔"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"‏Wifi غیر منسلک ہو گیا۔"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"‏درج ذیل کے لیے رنگ کی اصلاح مددگار ثابت ہو سکتی ہے:‏‎&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;‎جب آپ رنگوں کو مزید درست طریقے سے دیکھنا چاہیں‎&lt;/li&gt; &lt;li&gt;&amp;nbsp;‎فوکس کرنے میں مدد کرنے کے لئے رنگوں کو ہٹانا چاہیں‎&lt;/li&gt; &lt;/ol&gt;‎"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> کے ذریعہ منسوخ کردیا گیا"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - بیٹری کی حفاظت کرنے کے لیے چارجنگ ہولڈ پر ہے"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - چارجنگ ایکسیسری کی جانچ کی جا رہی ہے"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> باقی ہے"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> باقی ہے (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"آپ کے استعمال کی بنیاد پر تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> باقی ہے"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"وائرلیس طریقے سے چارج ہو رہی ہے"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"چارج ہو رہی ہے"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"چارج نہیں ہو رہا ہے"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"منسلک ہے، چارج نہیں ہو رہی ہے"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"منسلک ہے، لیکن چارج نہیں ہو رہا ہے"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"چارج ہو گئی"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"مکمل طور پر چارج ہو گئی"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"چارجنگ ہولڈ پر ہے"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"کنٹرول کردہ بذریعہ منتظم"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"محدود کردہ ترتیب کے زیر انتظام ہے"</string>
     <string name="disabled" msgid="8017887509554714950">"غیر فعال"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index c94ff4d..b46fca5 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Kamera"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Quloqchin"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Kiritish qurilmasi"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Eshitish moslamalari"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi o‘chiq."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi o‘chiq."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Ranglarni tuzatishning foydasi:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Ranglar yanada aniqroq koʻrinadi&lt;/li&gt; &lt;li&gt;&amp;nbsp;Diqqatni qaratish uchun ortiqcha ranglarni olib tashlash imkonini beradi&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> bilan almashtirildi"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Batareyani himoyalash uchun quvvatlash toʻxtatildi"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Quvvatlash aksessuari tekshirilmoqda"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Taxminan <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qoldi"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Taxminan <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qoldi (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Quvvati tugashiga taxminan <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qoldi"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Simsiz quvvat olmoqda"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Quvvat olmoqda"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Quvvat olmayapti"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Ulangan, quvvat olmayapti"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Ulangan, lekin quvvat olmayapti"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Quvvat oldi"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Toʻliq quvvatlandi"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Quvvatlash toʻxtatildi"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Administrator tomonidan boshqariladi"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Cheklangan sozlama tomonidan boshqariladi"</string>
     <string name="disabled" msgid="8017887509554714950">"Oʻchiq"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 03f0770..c471546 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Tạo ảnh"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Tai nghe"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Thiết bị ngoại vi vào"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Thiết bị trợ thính"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Đã tắt Wi-Fi."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Đã ngắt kết nối Wi-Fi."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Tính năng chỉnh màu có thể giúp ích khi bạn muốn:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Thấy màu sắc chính xác hơn&lt;/li&gt; &lt;li&gt;&amp;nbsp;Loại bỏ bớt màu để tập trung&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Bị ghi đè bởi <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Đang tạm ngưng sạc để bảo vệ pin"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> – Đang kiểm tra phụ kiện sạc"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Còn khoảng <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Còn khoảng <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Còn khoảng <xliff:g id="TIME_REMAINING">%1$s</xliff:g> dựa trên mức sử dụng của bạn"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Đang sạc không dây"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Đang sạc"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Hiện không sạc"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Đã kết nối nhưng chưa sạc"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Đã kết nối, nhưng không sạc"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Đã sạc"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Đã sạc đầy"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Đang tạm ngưng sạc"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Do quản trị viên kiểm soát"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Do chế độ Cài đặt hạn chế kiểm soát"</string>
     <string name="disabled" msgid="8017887509554714950">"Đã tắt"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 30db033..e9774e1 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"成像设备"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"耳机"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"外围输入设备"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"助听器"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"蓝牙"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"WLAN 已关闭。"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"WLAN 连接已断开。"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"“色彩校正”功能适用于以下情况:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;您想更准确地查看颜色&lt;/li&gt; &lt;li&gt;您想移除颜色以提高专注程度&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"已被“<xliff:g id="TITLE">%1$s</xliff:g>”覆盖"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - 为保护电池,已暂停充电"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - 正在检查充电配件"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"大约还可使用<xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"大约还可使用<xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"根据您的使用情况,大约还可使用<xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"正在无线充电"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"正在充电"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"未在充电"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"已连接,未充电"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"已连接,但未充电"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"已充满电"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"已充满电"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"充电已暂停"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"由管理员控制"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"由受限设置控制"</string>
     <string name="disabled" msgid="8017887509554714950">"已停用"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 2ce4de9..1e3394d 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"映像設備"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"耳機"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"輸入周邊設備"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"助聽器"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"藍牙"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi 已關閉。"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi 連線已中斷。"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"「色彩校正」功能適用於以下情況::&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;你想讓裝置顯示更準確的色彩&lt;/li&gt; &lt;li&gt;&amp;nbsp;你想移除色彩以提高專注力&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"已由「<xliff:g id="TITLE">%1$s</xliff:g>」覆寫"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - 為保護電池,目前暫停充電"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - 正在檢查充電配件"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"還有大約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"還有大約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"根據你的使用情況,還有大約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"無線充電中"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"正在充電"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"非充電中"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"已連接,非充電中"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"已連接,但未充電"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"已充滿電"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"充電完成"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"目前暫停充電"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"已由管理員停用"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"由「受限設定」控制"</string>
     <string name="disabled" msgid="8017887509554714950">"已停用"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 2fac076..8643fde 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"顯像裝置"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"頭戴式耳機"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"周邊輸入裝置"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"助聽器"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"藍牙"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"已關閉 Wi-Fi。"</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi 連線已中斷。"</string>
@@ -369,7 +370,7 @@
     <string name="show_hw_screen_updates_summary" msgid="3539770072741435691">"繪圖時在視窗中閃爍顯示畫面"</string>
     <string name="show_hw_layers_updates" msgid="5268370750002509767">"顯示硬體層更新"</string>
     <string name="show_hw_layers_updates_summary" msgid="5850955890493054618">"在硬體層更新時閃綠燈"</string>
-    <string name="debug_hw_overdraw" msgid="8944851091008756796">"針對 GPU 重複繪圖進行偵錯"</string>
+    <string name="debug_hw_overdraw" msgid="8944851091008756796">"針對 GPU 過度繪製進行偵錯"</string>
     <string name="disable_overlays" msgid="4206590799671557143">"停用硬體重疊圖層"</string>
     <string name="disable_overlays_summary" msgid="1954852414363338166">"一律使用 GPU 進行畫面合成"</string>
     <string name="simulate_color_space" msgid="1206503300335835151">"模擬色彩空間"</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"「色彩校正」功能適用於以下情況:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;你想讓裝置顯示更準確的色彩&lt;/li&gt; &lt;li&gt;&amp;nbsp;你想移除色彩以提升專注力&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"已改為<xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - 為保護電池,目前暫停充電"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - 正在檢查充電配件"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"還能使用約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"目前電量為 <xliff:g id="LEVEL">%2$s</xliff:g>,還能使用約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"根據你的使用情形,還能使用約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"正在進行無線充電"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"充電中"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"非充電中"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"已連接,尚未充電"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"已連接,但未充電"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"充電完成"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"充電完成"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"目前暫停充電"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"已由管理員停用"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"由限制設定控管"</string>
     <string name="disabled" msgid="8017887509554714950">"已停用"</string>
@@ -507,7 +511,7 @@
     <string name="screen_zoom_summary_extremely_large" msgid="1438045624562358554">"最大"</string>
     <string name="screen_zoom_summary_custom" msgid="3468154096832912210">"自訂 (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string>
     <string name="content_description_menu_button" msgid="6254844309171779931">"選單"</string>
-    <string name="retail_demo_reset_message" msgid="5392824901108195463">"如要在示範模式中恢復原廠設定,請輸入密碼"</string>
+    <string name="retail_demo_reset_message" msgid="5392824901108195463">"如要在展示模式中恢復原廠設定,請輸入密碼"</string>
     <string name="retail_demo_reset_next" msgid="3688129033843885362">"下一步"</string>
     <string name="retail_demo_reset_title" msgid="1866911701095959800">"請輸入密碼"</string>
     <string name="active_input_method_subtypes" msgid="4232680535471633046">"啟用的輸入法"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 9a81808..af85952 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -154,6 +154,7 @@
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Ukwenza isithombe"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Amahedfoni"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Okokufaka okulawulwa yikhompuyutha"</string>
+    <string name="bluetooth_talkback_hearing_aids" msgid="3983279945542595479">"Imishini Yezindlebe"</string>
     <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"I-Bluetooth"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"I-Wifi ivaliwe."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"I-Wifi inqanyuliwe."</string>
@@ -455,6 +456,8 @@
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Ukulungiswa kombala kungasiza uma ufuna ukwenza lokhu:<br/><br/> <ol> <li>&amp;nbsp;Ukubona imibala ngokunembe kakhulu</li> <li>&amp;nbsp;Ukususa imibala ukukusiza ukuthi ugxile</li> </ol>"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Igitshezwe ngaphezulu yi-<xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
+    <string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ukushaja kumisiwe ukuze kuvikelwe ibhethri"</string>
+    <string name="power_incompatible_charging_settings_home_page" msgid="1261756225093962684">"<xliff:g id="LEVEL">%1$s</xliff:g> - Kuhlolwa okokushaja"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Cishe u-<xliff:g id="TIME_REMAINING">%1$s</xliff:g> osele"</string>
     <string name="power_discharging_duration" msgid="1076561255466053220">"Cishe u-<xliff:g id="TIME_REMAINING">%1$s</xliff:g> osele (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Cishe u-<xliff:g id="TIME_REMAINING">%1$s</xliff:g> osele ngokususelwe ekusebenziseni wakho"</string>
@@ -483,9 +486,10 @@
     <string name="battery_info_status_charging_wireless" msgid="8924722966861282197">"Iyashaja ngaphandle kwentambo"</string>
     <string name="battery_info_status_charging_dock" msgid="8573274094093364791">"Iyashaja"</string>
     <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ayishaji"</string>
-    <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Ixhunyiwe, ayishaji"</string>
+    <string name="battery_info_status_not_charging" msgid="1103084691314264664">"Ixhunyiwe, kodwa ayishaji"</string>
     <string name="battery_info_status_full" msgid="1339002294876531312">"Kushajiwe"</string>
     <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Ishaje Ngokuphelele"</string>
+    <string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Ukushaja kumisiwe"</string>
     <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Kulawulwa umqondisi"</string>
     <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kulawulwe Isethingi Elikhawulelwe"</string>
     <string name="disabled" msgid="8017887509554714950">"Akusebenzi"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 5aa2bfc..a4b3af9 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -338,6 +338,9 @@
     <!-- Message for telling the user the kind of BT device being displayed in list. [CHAR LIMIT=30 BACKUP_MESSAGE_ID=5165842622743212268] -->
     <string name="bluetooth_talkback_input_peripheral">Input Peripheral</string>
 
+    <!-- Message for telling the user the kind of BT device being displayed in list. [CHAR LIMIT=30 BACKUP_MESSAGE_ID=26580326066627664] -->
+    <string name="bluetooth_talkback_hearing_aids">Hearing Aids</string>
+
     <!-- Message for telling the user the kind of BT device being displayed in list. [CHAR LIMIT=30 BACKUP_MESSAGE_ID=5615463912185280812] -->
     <string name="bluetooth_talkback_bluetooth">Bluetooth</string>
 
@@ -1079,6 +1082,10 @@
 
     <!-- [CHAR_LIMIT=NONE] Label for battery on main page of settings -->
     <string name="power_remaining_settings_home_page"><xliff:g id="percentage" example="10%">%1$s</xliff:g> - <xliff:g id="time_string" example="1 hour left based on your usage">%2$s</xliff:g></string>
+    <!-- [CHAR_LIMIT=NONE] Label for charging on hold on main page of settings -->
+    <string name="power_charging_on_hold_settings_home_page"><xliff:g id="level">%1$s</xliff:g> - Charging on hold to protect battery</string>
+    <!-- [CHAR_LIMIT=NONE] Label for incompatible charging accessory on main page of settings -->
+    <string name="power_incompatible_charging_settings_home_page"><xliff:g id="level">%1$s</xliff:g> - Checking charging accessory</string>
     <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery discharging -->
     <string name="power_remaining_duration_only">About <xliff:g id="time_remaining">%1$s</xliff:g> left</string>
     <!-- [CHAR_LIMIT=40] Label for battery level chart when discharging with duration -->
@@ -1139,11 +1146,13 @@
     <!-- Battery Info screen. Value for a status item.  Used for diagnostic info screens, precise translation isn't needed -->
     <string name="battery_info_status_discharging">Not charging</string>
     <!-- Battery Info screen. Value for a status item. A state which device is connected with any charger(e.g. USB, Adapter or Wireless) but not charging yet. Used for diagnostic info screens, precise translation isn't needed -->
-    <string name="battery_info_status_not_charging">Connected, not charging</string>
+    <string name="battery_info_status_not_charging">Connected, but not charging</string>
     <!-- Battery Info screen. Value for a status item.  Used for diagnostic info screens, precise translation isn't needed -->
     <string name="battery_info_status_full">Charged</string>
     <!-- [CHAR_LIMIT=40] Battery Info screen. Value for a status item. A state which device is fully charged -->
     <string name="battery_info_status_full_charged">Fully Charged</string>
+    <!-- [CHAR_LIMIT=None] Battery Info screen. Value for a status item. A state which device charging on hold -->
+    <string name="battery_info_status_charging_on_hold">Charging on hold</string>
 
     <!-- Summary for settings preference disabled by administrator [CHAR LIMIT=50] -->
     <string name="disabled_by_admin_summary_text">Controlled by admin</string>
diff --git a/packages/SettingsLib/search/Android.bp b/packages/SettingsLib/search/Android.bp
index 3b14712..1f8d1dd 100644
--- a/packages/SettingsLib/search/Android.bp
+++ b/packages/SettingsLib/search/Android.bp
@@ -12,14 +12,14 @@
     visibility: ["//visibility:private"],
     srcs: ["interface-src/**/*.java"],
     host_supported: true,
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 android_library {
     name: "SettingsLib-search",
     use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
     static_libs: [
         "SettingsLib-search-interface",
     ],
diff --git a/packages/SettingsLib/search/lint-baseline.xml b/packages/SettingsLib/search/lint-baseline.xml
index 7ec512b..61cdb05 100644
--- a/packages/SettingsLib/search/lint-baseline.xml
+++ b/packages/SettingsLib/search/lint-baseline.xml
@@ -1,16 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 23 (current min is 21): `new android.provider.SearchIndexableData`"
-        errorLine1="        super(context);"
-        errorLine2="        ~~~~~">
-        <location
-            file="frameworks/base/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexableRaw.java"
-            line="62"
-            column="9"/>
-    </issue>
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NewApi"
@@ -23,4 +12,15 @@
             column="41"/>
     </issue>
 
+    <issue
+        id="NewApi"
+        message="Call requires API level 23 (current min is 21): `new android.provider.SearchIndexableData`"
+        errorLine1="        super(context);"
+        errorLine2="        ~~~~~">
+        <location
+            file="frameworks/base/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexableRaw.java"
+            line="62"
+            column="9"/>
+    </issue>
+
 </issues>
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 8e5396f..d902457 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -19,6 +19,7 @@
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE;
 import static android.app.admin.DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
 import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+import static android.app.role.RoleManager.ROLE_FINANCED_DEVICE_KIOSK;
 
 import static com.android.settingslib.Utils.getColorAttrDefaultColor;
 
@@ -27,6 +28,7 @@
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyManager;
+import android.app.role.RoleManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -70,6 +72,10 @@
     private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
     private static final Set<String> ECM_KEYS = new ArraySet<>();
 
+    // TODO(b/281701062): reference role name from role manager once its exposed.
+    private static final String ROLE_DEVICE_LOCK_CONTROLLER =
+            "android.app.role.SYSTEM_FINANCED_DEVICE_CONTROLLER";
+
     static {
         if (android.security.Flags.extendEcmToAllSettings()) {
             ECM_KEYS.add(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW);
@@ -476,16 +482,27 @@
     }
 
     /**
-     * Check if {@param packageName} is restricted by the profile or device owner from using
-     * metered data.
+     * Check if user control over metered data usage of {@code packageName} is disabled by the
+     * profile or device owner.
      *
      * @return EnforcedAdmin object containing the enforced admin component and admin user details,
-     * or {@code null} if the {@param packageName} is not restricted.
+     * or {@code null} if the user control is not disabled.
      */
-    public static EnforcedAdmin checkIfMeteredDataRestricted(Context context,
+    public static EnforcedAdmin checkIfMeteredDataUsageUserControlDisabled(Context context,
             String packageName, int userId) {
+        RoleManager roleManager = context.getSystemService(RoleManager.class);
+        UserHandle userHandle = getUserHandleOf(userId);
+        if (roleManager.getRoleHoldersAsUser(ROLE_FINANCED_DEVICE_KIOSK, userHandle)
+                .contains(packageName)
+                || roleManager.getRoleHoldersAsUser(ROLE_DEVICE_LOCK_CONTROLLER, userHandle)
+                .contains(packageName)) {
+            // There is no actual device admin for a financed device, but metered data usage
+            // control should still be disabled for both controller and kiosk apps.
+            return new EnforcedAdmin();
+        }
+
         final EnforcedAdmin enforcedAdmin = getProfileOrDeviceOwner(context,
-                getUserHandleOf(userId));
+                userHandle);
         if (enforcedAdmin == null) {
             return null;
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index c2be571..fb14a17 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -1,6 +1,7 @@
 package com.android.settingslib;
 
 import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USER_LABEL;
+import static android.webkit.Flags.updateServiceV2;
 
 import android.annotation.ColorInt;
 import android.app.admin.DevicePolicyManager;
@@ -34,6 +35,7 @@
 import android.net.wifi.WifiInfo;
 import android.os.BatteryManager;
 import android.os.Build;
+import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -44,6 +46,9 @@
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.util.Log;
+import android.webkit.IWebViewUpdateService;
+import android.webkit.WebViewFactory;
+import android.webkit.WebViewProviderInfo;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -65,6 +70,8 @@
 
 public class Utils {
 
+    private static final String TAG = "Utils";
+
     @VisibleForTesting
     static final String STORAGE_MANAGER_ENABLED_PROPERTY =
             "ro.storage_manager.enabled";
@@ -76,6 +83,7 @@
     private static String sPermissionControllerPackageName;
     private static String sServicesSystemSharedLibPackageName;
     private static String sSharedSystemSharedLibPackageName;
+    private static String sDefaultWebViewPackageName;
 
     static final int[] WIFI_PIE = {
         com.android.internal.R.drawable.ic_wifi_signal_0,
@@ -445,6 +453,7 @@
                 || packageName.equals(sServicesSystemSharedLibPackageName)
                 || packageName.equals(sSharedSystemSharedLibPackageName)
                 || packageName.equals(PrintManager.PRINT_SPOOLER_PACKAGE_NAME)
+                || (updateServiceV2() && packageName.equals(getDefaultWebViewPackageName()))
                 || isDeviceProvisioningPackage(resources, packageName);
     }
 
@@ -459,6 +468,29 @@
     }
 
     /**
+     * Fetch the package name of the default WebView provider.
+     */
+    @Nullable
+    private static String getDefaultWebViewPackageName() {
+        if (sDefaultWebViewPackageName != null) {
+            return sDefaultWebViewPackageName;
+        }
+
+        try {
+            IWebViewUpdateService service = WebViewFactory.getUpdateService();
+            if (service != null) {
+                WebViewProviderInfo provider = service.getDefaultWebViewPackage();
+                if (provider != null) {
+                    sDefaultWebViewPackageName = provider.packageName;
+                }
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException when trying to fetch default WebView package Name", e);
+        }
+        return sDefaultWebViewPackageName;
+    }
+
+    /**
      * Returns the Wifi icon resource for a given RSSI level.
      *
      * @param level The number of bars to show (0-4)
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
index 5d520ce..7e2d0af 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
@@ -21,6 +21,8 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 
+import androidx.annotation.NonNull;
+
 /**
  * A class for applying config changes and determing if doing so resulting in any "interesting"
  * changes.
@@ -48,8 +50,15 @@
      */
     @SuppressLint("NewApi")
     public boolean applyNewConfig(Resources res) {
+        return applyNewConfig(res.getConfiguration());
+    }
+
+    /**
+     * Applies the given config change and returns whether an "interesting" change happened.
+     */
+    public boolean applyNewConfig(@NonNull Configuration configuration) {
         int configChanges = mLastConfiguration.updateFrom(
-                Configuration.generateDelta(mLastConfiguration, res.getConfiguration()));
+                Configuration.generateDelta(mLastConfiguration, configuration));
         return (configChanges & (mFlags)) != 0;
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 0ffcc45..f7f0673 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -117,6 +117,12 @@
             }
         }
 
+        if (cachedDevice.isHearingAidDevice()) {
+            return new Pair<>(getBluetoothDrawable(context,
+                    com.android.internal.R.drawable.ic_bt_hearing_aid),
+                    context.getString(R.string.bluetooth_talkback_hearing_aids));
+        }
+
         List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles();
         int resId = 0;
         for (LocalBluetoothProfile profile : profiles) {
@@ -125,7 +131,8 @@
                 // The device should show hearing aid icon if it contains any hearing aid related
                 // profiles
                 if (profile instanceof HearingAidProfile || profile instanceof HapClientProfile) {
-                    return new Pair<>(getBluetoothDrawable(context, profileResId), null);
+                    return new Pair<>(getBluetoothDrawable(context, profileResId),
+                            context.getString(R.string.bluetooth_talkback_hearing_aids));
                 }
                 if (resId == 0) {
                     resId = profileResId;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 245fe6e..560bc46 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -410,8 +410,13 @@
         connectDevice();
     }
 
-    void setHearingAidInfo(HearingAidInfo hearingAidInfo) {
+    public void setHearingAidInfo(HearingAidInfo hearingAidInfo) {
         mHearingAidInfo = hearingAidInfo;
+        dispatchAttributesChanged();
+    }
+
+    public HearingAidInfo getHearingAidInfo() {
+        return mHearingAidInfo;
     }
 
     /**
@@ -1788,4 +1793,40 @@
     boolean getUnpairing() {
         return mUnpairing;
     }
+
+    ListenableFuture<Void> syncProfileForMemberDevice() {
+        return ThreadUtils.getBackgroundExecutor()
+            .submit(
+                () -> {
+                    List<Pair<LocalBluetoothProfile, Boolean>> toSync =
+                        Stream.of(
+                            mProfileManager.getA2dpProfile(),
+                            mProfileManager.getHeadsetProfile(),
+                            mProfileManager.getHearingAidProfile(),
+                            mProfileManager.getLeAudioProfile(),
+                            mProfileManager.getLeAudioBroadcastAssistantProfile())
+                        .filter(Objects::nonNull)
+                        .map(profile -> new Pair<>(profile, profile.isEnabled(mDevice)))
+                        .toList();
+
+                    for (var t : toSync) {
+                        LocalBluetoothProfile profile = t.first;
+                        boolean enabledForMain = t.second;
+
+                        for (var member : mMemberDevices) {
+                            BluetoothDevice btDevice = member.getDevice();
+
+                            if (enabledForMain != profile.isEnabled(btDevice)) {
+                                Log.d(TAG, "Syncing profile " + profile + " to "
+                                        + enabledForMain + " for member device "
+                                        + btDevice.getAnonymizedAddress() + " of main device "
+                                        + mDevice.getAnonymizedAddress());
+                                profile.setEnabled(btDevice, enabledForMain);
+                            }
+                        }
+                    }
+                    return null;
+                }
+            );
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index a6536a8c..89fe268 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -349,6 +349,7 @@
         if (profileId == BluetoothProfile.HEADSET
                 || profileId == BluetoothProfile.A2DP
                 || profileId == BluetoothProfile.LE_AUDIO
+                || profileId == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
                 || profileId == BluetoothProfile.CSIP_SET_COORDINATOR) {
             return mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice,
                 state);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index a49314a..e67ec48 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -379,6 +379,7 @@
         if (hasChanged) {
             log("addMemberDevicesIntoMainDevice: After changed, CachedBluetoothDevice list: "
                     + mCachedDevices);
+            preferredMainDevice.syncProfileForMemberDevice();
         }
         return hasChanged;
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
index cf4d6be..0613676 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
@@ -99,6 +99,8 @@
                 device.refresh();
             }
 
+            // Check current list of CachedDevices to see if any are hearing aid devices.
+            mDeviceManager.updateHearingAidsDevices();
             mIsProfileReady = true;
             mProfileManager.callServiceConnectedListeners();
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index 3a15b71..9fd174d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -15,8 +15,8 @@
  */
 package com.android.settingslib.bluetooth;
 
-import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.le.ScanFilter;
@@ -68,14 +68,9 @@
 
     void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice,
             List<ScanFilter> leScanFilters) {
-        long hiSyncId = getHiSyncId(newDevice.getDevice());
-        if (isValidHiSyncId(hiSyncId)) {
-            // Once hiSyncId is valid, assign hearing aid info
-            final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
-                    .setAshaDeviceSide(getDeviceSide(newDevice.getDevice()))
-                    .setAshaDeviceMode(getDeviceMode(newDevice.getDevice()))
-                    .setHiSyncId(hiSyncId);
-            newDevice.setHearingAidInfo(infoBuilder.build());
+        HearingAidInfo info = generateHearingAidInfo(newDevice);
+        if (info != null) {
+            newDevice.setHearingAidInfo(info);
         } else if (leScanFilters != null && !newDevice.isHearingAidDevice()) {
             // If the device is added with hearing aid scan filter during pairing, set an empty
             // hearing aid info to indicate it's a hearing aid device. The info will be updated
@@ -94,38 +89,6 @@
         }
     }
 
-    private long getHiSyncId(BluetoothDevice device) {
-        final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
-        final HearingAidProfile profileProxy = profileManager.getHearingAidProfile();
-        if (profileProxy == null) {
-            return BluetoothHearingAid.HI_SYNC_ID_INVALID;
-        }
-
-        return profileProxy.getHiSyncId(device);
-    }
-
-    private int getDeviceSide(BluetoothDevice device) {
-        final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
-        final HearingAidProfile profileProxy = profileManager.getHearingAidProfile();
-        if (profileProxy == null) {
-            Log.w(TAG, "HearingAidProfile is not supported and not ready to fetch device side");
-            return HearingAidProfile.DeviceSide.SIDE_INVALID;
-        }
-
-        return profileProxy.getDeviceSide(device);
-    }
-
-    private int getDeviceMode(BluetoothDevice device) {
-        final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
-        final HearingAidProfile profileProxy = profileManager.getHearingAidProfile();
-        if (profileProxy == null) {
-            Log.w(TAG, "HearingAidProfile is not supported and not ready to fetch device mode");
-            return HearingAidProfile.DeviceMode.MODE_INVALID;
-        }
-
-        return profileProxy.getDeviceMode(device);
-    }
-
     boolean setSubDeviceIfNeeded(CachedBluetoothDevice newDevice) {
         final long hiSyncId = newDevice.getHiSyncId();
         if (isValidHiSyncId(hiSyncId)) {
@@ -157,21 +120,17 @@
 
     // To collect all HearingAid devices and call #onHiSyncIdChanged to group device by HiSyncId
     void updateHearingAidsDevices() {
-        final Set<Long> newSyncIdSet = new HashSet<Long>();
+        final Set<Long> newSyncIdSet = new HashSet<>();
         for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
             // Do nothing if HiSyncId has been assigned
-            if (!isValidHiSyncId(cachedDevice.getHiSyncId())) {
-                final long newHiSyncId = getHiSyncId(cachedDevice.getDevice());
-                // Do nothing if there is no HiSyncId on Bluetooth device
-                if (isValidHiSyncId(newHiSyncId)) {
-                    // Once hiSyncId is valid, assign hearing aid info
-                    final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
-                            .setAshaDeviceSide(getDeviceSide(cachedDevice.getDevice()))
-                            .setAshaDeviceMode(getDeviceMode(cachedDevice.getDevice()))
-                            .setHiSyncId(newHiSyncId);
-                    cachedDevice.setHearingAidInfo(infoBuilder.build());
-
-                    newSyncIdSet.add(newHiSyncId);
+            if (isValidHiSyncId(cachedDevice.getHiSyncId())) {
+                continue;
+            }
+            HearingAidInfo info = generateHearingAidInfo(cachedDevice);
+            if (info != null) {
+                cachedDevice.setHearingAidInfo(info);
+                if (isValidHiSyncId(info.getHiSyncId())) {
+                    newSyncIdSet.add(info.getHiSyncId());
                 }
             }
         }
@@ -378,6 +337,54 @@
         return null;
     }
 
+    private boolean isLeAudioHearingAid(CachedBluetoothDevice cachedDevice) {
+        List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles();
+        boolean supportLeAudio = profiles.stream().anyMatch(p -> p instanceof LeAudioProfile);
+        boolean supportHapClient = profiles.stream().anyMatch(p -> p instanceof HapClientProfile);
+        return supportLeAudio && supportHapClient;
+    }
+
+    private boolean isAshaHearingAid(CachedBluetoothDevice cachedDevice) {
+        return cachedDevice.getProfiles().stream().anyMatch(p -> p instanceof HearingAidProfile);
+    }
+
+    private HearingAidInfo generateHearingAidInfo(CachedBluetoothDevice cachedDevice) {
+        final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
+        if (isAshaHearingAid(cachedDevice)) {
+            final HearingAidProfile asha = profileManager.getHearingAidProfile();
+            if (asha == null) {
+                Log.w(TAG, "HearingAidProfile is not supported on this device");
+            } else {
+                long hiSyncId = asha.getHiSyncId(cachedDevice.getDevice());
+                if (isValidHiSyncId(hiSyncId)) {
+                    final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+                            .setAshaDeviceSide(asha.getDeviceSide(cachedDevice.getDevice()))
+                            .setAshaDeviceMode(asha.getDeviceMode(cachedDevice.getDevice()))
+                            .setHiSyncId(hiSyncId);
+                    return infoBuilder.build();
+                }
+            }
+        }
+        if (isLeAudioHearingAid(cachedDevice)) {
+            final HapClientProfile hapClientProfile = profileManager.getHapClientProfile();
+            final LeAudioProfile leAudioProfile = profileManager.getLeAudioProfile();
+            if (hapClientProfile == null || leAudioProfile == null) {
+                Log.w(TAG, "HapClientProfile or LeAudioProfile is not supported on this device");
+            } else {
+                int audioLocation = leAudioProfile.getAudioLocation(cachedDevice.getDevice());
+                int hearingAidType = hapClientProfile.getHearingAidType(cachedDevice.getDevice());
+                if (audioLocation != BluetoothLeAudio.AUDIO_LOCATION_INVALID
+                        && hearingAidType != HapClientProfile.HearingAidType.TYPE_INVALID) {
+                    final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+                            .setLeAudioLocation(audioLocation)
+                            .setHapDeviceType(hearingAidType);
+                    return infoBuilder.build();
+                }
+            }
+        }
+        return null;
+    }
+
     private void log(String msg) {
         if (DEBUG) {
             Log.d(TAG, msg);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index 14fab16..f2450de 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -109,7 +109,7 @@
                 device.refresh();
             }
 
-            // Check current list of CachedDevices to see if any are Hearing Aid devices.
+            // Check current list of CachedDevices to see if any are hearing aid devices.
             mDeviceManager.updateHearingAidsDevices();
             mIsProfileReady = true;
             mProfileManager.callServiceConnectedListeners();
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
index 57012aa..6be4336 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
@@ -3,17 +3,17 @@
 */
 
 /* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-*     http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 package com.android.settingslib.bluetooth;
 
@@ -23,6 +23,7 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothProfile;
@@ -30,6 +31,7 @@
 import android.os.Build;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 
@@ -57,13 +59,12 @@
     private static final int ORDINAL = 1;
 
     // These callbacks run on the main thread.
-    private final class LeAudioServiceListener
-            implements BluetoothProfile.ServiceListener {
+    private final class LeAudioServiceListener implements BluetoothProfile.ServiceListener {
 
         @RequiresApi(Build.VERSION_CODES.S)
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
             if (DEBUG) {
-                Log.d(TAG,"Bluetooth service connected");
+                Log.d(TAG, "Bluetooth service connected");
             }
             mService = (BluetoothLeAudio) proxy;
             // We just bound to the service, so refresh the UI for any connected LeAudio devices.
@@ -78,18 +79,19 @@
                     }
                     device = mDeviceManager.addDevice(nextDevice);
                 }
-                device.onProfileStateChanged(LeAudioProfile.this,
-                        BluetoothProfile.STATE_CONNECTED);
+                device.onProfileStateChanged(LeAudioProfile.this, BluetoothProfile.STATE_CONNECTED);
                 device.refresh();
             }
 
+            // Check current list of CachedDevices to see if any are hearing aid devices.
+            mDeviceManager.updateHearingAidsDevices();
             mProfileManager.callServiceConnectedListeners();
             mIsProfileReady = true;
         }
 
         public void onServiceDisconnected(int profile) {
             if (DEBUG) {
-                 Log.d(TAG,"Bluetooth service disconnected");
+                Log.d(TAG, "Bluetooth service disconnected");
             }
             mProfileManager.callServiceDisconnectedListeners();
             mIsProfileReady = false;
@@ -105,7 +107,9 @@
         return BluetoothProfile.LE_AUDIO;
     }
 
-    LeAudioProfile(Context context, CachedBluetoothDeviceManager deviceManager,
+    LeAudioProfile(
+            Context context,
+            CachedBluetoothDeviceManager deviceManager,
             LocalBluetoothProfileManager profileManager) {
         mContext = context;
         mDeviceManager = deviceManager;
@@ -113,8 +117,7 @@
 
         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
         mBluetoothAdapter.getProfileProxy(
-                context, new LeAudioServiceListener(),
-                BluetoothProfile.LE_AUDIO);
+                context, new LeAudioServiceListener(), BluetoothProfile.LE_AUDIO);
     }
 
     public boolean accessProfileEnabled() {
@@ -126,18 +129,22 @@
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
-        return getDevicesByStates(new int[] {
-                BluetoothProfile.STATE_CONNECTED,
-                BluetoothProfile.STATE_CONNECTING,
-                BluetoothProfile.STATE_DISCONNECTING});
+        return getDevicesByStates(
+                new int[] {
+                    BluetoothProfile.STATE_CONNECTED,
+                    BluetoothProfile.STATE_CONNECTING,
+                    BluetoothProfile.STATE_DISCONNECTING
+                });
     }
 
     public List<BluetoothDevice> getConnectableDevices() {
-        return getDevicesByStates(new int[] {
-                BluetoothProfile.STATE_DISCONNECTED,
-                BluetoothProfile.STATE_CONNECTED,
-                BluetoothProfile.STATE_CONNECTING,
-                BluetoothProfile.STATE_DISCONNECTING});
+        return getDevicesByStates(
+                new int[] {
+                    BluetoothProfile.STATE_DISCONNECTED,
+                    BluetoothProfile.STATE_CONNECTED,
+                    BluetoothProfile.STATE_CONNECTING,
+                    BluetoothProfile.STATE_DISCONNECTING
+                });
     }
 
     private List<BluetoothDevice> getDevicesByStates(int[] states) {
@@ -148,8 +155,8 @@
     }
 
     /*
-    * @hide
-    */
+     * @hide
+     */
     public boolean connect(BluetoothDevice device) {
         if (mService == null) {
             return false;
@@ -158,8 +165,8 @@
     }
 
     /*
-    * @hide
-    */
+     * @hide
+     */
     public boolean disconnect(BluetoothDevice device) {
         if (mService == null) {
             return false;
@@ -174,6 +181,14 @@
         return mService.getConnectionState(device);
     }
 
+    /** Get group id for {@link BluetoothDevice}. */
+    public int getGroupId(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
+        }
+        return mService.getGroupId(device);
+    }
+
     public boolean setActiveDevice(BluetoothDevice device) {
         if (mBluetoothAdapter == null) {
             return false;
@@ -193,29 +208,28 @@
     /**
      * Get Lead device for the group.
      *
-     * Lead device is the device that can be used as an active device in the system.
-     * Active devices points to the Audio Device for the Le Audio group.
-     * This method returns the Lead devices for the connected LE Audio
-     * group and this device should be used in the setActiveDevice() method by other parts
-     * of the system, which wants to set to active a particular Le Audio group.
+     * <p>Lead device is the device that can be used as an active device in the system. Active
+     * devices points to the Audio Device for the Le Audio group. This method returns the Lead
+     * devices for the connected LE Audio group and this device should be used in the
+     * setActiveDevice() method by other parts of the system, which wants to set to active a
+     * particular Le Audio group.
      *
-     * Note: getActiveDevice() returns the Lead device for the currently active LE Audio group.
+     * <p>Note: getActiveDevice() returns the Lead device for the currently active LE Audio group.
      * Note: When Lead device gets disconnected while Le Audio group is active and has more devices
-     * in the group, then Lead device will not change. If Lead device gets disconnected, for the
-     * Le Audio group which is not active, a new Lead device will be chosen
+     * in the group, then Lead device will not change. If Lead device gets disconnected, for the Le
+     * Audio group which is not active, a new Lead device will be chosen
      *
      * @param groupId The group id.
      * @return group lead device.
-     *
      * @hide
      */
     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     public @Nullable BluetoothDevice getConnectedGroupLeadDevice(int groupId) {
         if (DEBUG) {
-            Log.d(TAG,"getConnectedGroupLeadDevice");
+            Log.d(TAG, "getConnectedGroupLeadDevice");
         }
         if (mService == null) {
-            Log.e(TAG,"No service.");
+            Log.e(TAG, "No service.");
             return null;
         }
         return mService.getConnectedGroupLeadDevice(groupId);
@@ -310,10 +324,10 @@
         }
         if (mService != null) {
             try {
-                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.LE_AUDIO,
-                        mService);
+                BluetoothAdapter.getDefaultAdapter()
+                        .closeProfileProxy(BluetoothProfile.LE_AUDIO, mService);
                 mService = null;
-            }catch (Throwable t) {
+            } catch (Throwable t) {
                 Log.w(TAG, "Error cleaning up LeAudio proxy", t);
             }
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index de21c54..1d2f790 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -84,6 +84,8 @@
                 Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO),
                 Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE),
                 Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME),
+                Settings.Secure.getUriFor(
+                        Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY),
             };
 
     private BluetoothLeBroadcast mServiceBroadcast;
@@ -96,6 +98,7 @@
     private String mNewAppSourceName = "";
     private boolean mIsBroadcastProfileReady = false;
     private boolean mIsBroadcastAssistantProfileReady = false;
+    private boolean mImproveCompatibility = false;
     private String mProgramInfo;
     private byte[] mBroadcastCode;
     private Executor mExecutor;
@@ -391,6 +394,52 @@
      * <p>If the system started the LE Broadcast, then the system calls the corresponding callback
      * {@link BluetoothLeBroadcast.Callback}.
      */
+    public void startPrivateBroadcast() {
+        mNewAppSourceName = "Sharing audio";
+        if (mServiceBroadcast == null) {
+            Log.d(TAG, "The BluetoothLeBroadcast is null when starting the private broadcast.");
+            return;
+        }
+        if (mServiceBroadcast.getAllBroadcastMetadata().size()
+                >= mServiceBroadcast.getMaximumNumberOfBroadcasts()) {
+            Log.d(TAG, "Skip starting the broadcast due to number limit.");
+            return;
+        }
+        String programInfo = getProgramInfo();
+        boolean improveCompatibility = getImproveCompatibility();
+        if (DEBUG) {
+            Log.d(
+                    TAG,
+                    "startBroadcast: language = null , programInfo = "
+                            + programInfo
+                            + ", improveCompatibility = "
+                            + improveCompatibility);
+        }
+        // Current broadcast framework only support one subgroup
+        BluetoothLeBroadcastSubgroupSettings subgroupSettings =
+                buildBroadcastSubgroupSettings(
+                        /* language= */ null, programInfo, improveCompatibility);
+        BluetoothLeBroadcastSettings settings =
+                buildBroadcastSettings(
+                        true, // TODO: set to false after framework fix
+                        TextUtils.isEmpty(programInfo) ? null : programInfo,
+                        (mBroadcastCode != null && mBroadcastCode.length > 0)
+                                ? mBroadcastCode
+                                : null,
+                        ImmutableList.of(subgroupSettings));
+        mServiceBroadcast.startBroadcast(settings);
+    }
+
+    /**
+     * Start the private Broadcast for personal audio sharing or qr code sharing.
+     *
+     * <p>The broadcast will use random string for both broadcast name and subgroup program info;
+     * The broadcast will use random string for broadcast code; The broadcast will only have one
+     * subgroup due to system limitation; The subgroup language will be null.
+     *
+     * <p>If the system started the LE Broadcast, then the system calls the corresponding callback
+     * {@link BluetoothLeBroadcast.Callback}.
+     */
     public void startPrivateBroadcast(int quality) {
         mNewAppSourceName = "Sharing audio";
         if (mServiceBroadcast == null) {
@@ -408,7 +457,11 @@
         }
         // Current broadcast framework only support one subgroup
         BluetoothLeBroadcastSubgroupSettings subgroupSettings =
-                buildBroadcastSubgroupSettings(/* language= */ null, programInfo, quality);
+                buildBroadcastSubgroupSettings(
+                        /* language= */ null,
+                        programInfo,
+                        /* improveCompatibility= */
+                        BluetoothLeBroadcastSubgroupSettings.QUALITY_STANDARD == quality);
         BluetoothLeBroadcastSettings settings =
                 buildBroadcastSettings(
                         true, // TODO: set to false after framework fix
@@ -437,7 +490,7 @@
     }
 
     private BluetoothLeBroadcastSubgroupSettings buildBroadcastSubgroupSettings(
-            @Nullable String language, @Nullable String programInfo, int quality) {
+            @Nullable String language, @Nullable String programInfo, boolean improveCompatibility) {
         BluetoothLeAudioContentMetadata metadata =
                 new BluetoothLeAudioContentMetadata.Builder()
                         .setLanguage(language)
@@ -447,7 +500,10 @@
         // metadata to keep legacy UI working.
         mBluetoothLeAudioContentMetadata = metadata;
         return new BluetoothLeBroadcastSubgroupSettings.Builder()
-                .setPreferredQuality(quality)
+                .setPreferredQuality(
+                        improveCompatibility
+                                ? BluetoothLeBroadcastSubgroupSettings.QUALITY_STANDARD
+                                : BluetoothLeBroadcastSubgroupSettings.QUALITY_HIGH)
                 .setContentMetadata(mBluetoothLeAudioContentMetadata)
                 .build();
     }
@@ -513,6 +569,36 @@
         }
     }
 
+    /** Get compatibility config for broadcast. */
+    public boolean getImproveCompatibility() {
+        return mImproveCompatibility;
+    }
+
+    /** Set compatibility config for broadcast. */
+    public void setImproveCompatibility(boolean improveCompatibility) {
+        setImproveCompatibility(improveCompatibility, /* updateContentResolver= */ true);
+    }
+
+    private void setImproveCompatibility(
+            boolean improveCompatibility, boolean updateContentResolver) {
+        if (mImproveCompatibility == improveCompatibility) {
+            Log.d(TAG, "setImproveCompatibility: improveCompatibility is not changed");
+            return;
+        }
+        mImproveCompatibility = improveCompatibility;
+        if (updateContentResolver) {
+            if (mContentResolver == null) {
+                Log.d(TAG, "mContentResolver is null");
+                return;
+            }
+            Log.d(TAG, "Set improveCompatibility to: " + improveCompatibility);
+            Settings.Secure.putString(
+                    mContentResolver,
+                    Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY,
+                    improveCompatibility ? "1" : "0");
+        }
+    }
+
     private void setLatestBroadcastId(int broadcastId) {
         Log.d(TAG, "setLatestBroadcastId: mBroadcastId is " + broadcastId);
         mBroadcastId = broadcastId;
@@ -600,6 +686,14 @@
                 Settings.Secure.getString(
                         mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME);
         setAppSourceName(appSourceName, /* updateContentResolver= */ false);
+
+        String improveCompatibility =
+                Settings.Secure.getString(
+                        mContentResolver,
+                        Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY);
+        setImproveCompatibility(
+                improveCompatibility == null ? false : improveCompatibility.equals("1"),
+                /* updateContentResolver= */ false);
     }
 
     private void updateBroadcastInfoFromBroadcastMetadata(
@@ -665,6 +759,20 @@
     }
 
     /**
+     * Update the LE Broadcast by calling {@link BluetoothLeBroadcast#updateBroadcast(int,
+     * BluetoothLeAudioContentMetadata)}, currently only updates programInfo.
+     */
+    public void updateBroadcast() {
+        if (mServiceBroadcast == null) {
+            Log.d(TAG, "The BluetoothLeBroadcast is null when updating the broadcast.");
+            return;
+        }
+        String programInfo = getProgramInfo();
+        mBluetoothLeAudioContentMetadata = mBuilder.setProgramInfo(programInfo).build();
+        mServiceBroadcast.updateBroadcast(mBroadcastId, mBluetoothLeAudioContentMetadata);
+    }
+
+    /**
      * Register Broadcast Callbacks to track its state and receivers
      *
      * @param executor Executor object for callback
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
index 5bc27195..943e3fc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
@@ -70,10 +70,8 @@
     @Override
     public void displayPreference(PreferenceScreen screen) {
         super.displayPreference(screen);
-        if (isAvailable()) {
-            mWifiMacAddress = screen.findPreference(KEY_WIFI_MAC_ADDRESS);
-            updateConnectivity();
-        }
+        mWifiMacAddress = screen.findPreference(KEY_WIFI_MAC_ADDRESS);
+        updateConnectivity();
     }
 
     @Override
@@ -84,16 +82,16 @@
     @SuppressLint("HardwareIds")
     @Override
     protected void updateConnectivity() {
+        if (mWifiManager == null || mWifiMacAddress == null) {
+            return;
+        }
+
         final String[] macAddresses = mWifiManager.getFactoryMacAddresses();
         String macAddress = null;
         if (macAddresses != null && macAddresses.length > 0) {
             macAddress = macAddresses[0];
         }
 
-        if (mWifiMacAddress == null) {
-            return;
-        }
-
         if (TextUtils.isEmpty(macAddress) || macAddress.equals(WifiInfo.DEFAULT_MAC_ADDRESS)) {
             mWifiMacAddress.setSummary(R.string.status_unavailable);
         } else {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
index cf224dc..3de4933 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
@@ -71,15 +71,15 @@
                         new Device(
                                 AudioDeviceInfo.TYPE_HDMI,
                                 MediaRoute2Info.TYPE_HDMI,
-                                mIsTv ? R.drawable.ic_tv : R.drawable.ic_headphone),
+                                mIsTv ? R.drawable.ic_tv : R.drawable.ic_external_display),
                         new Device(
                                 AudioDeviceInfo.TYPE_HDMI_ARC,
                                 MediaRoute2Info.TYPE_HDMI_ARC,
-                                mIsTv ? R.drawable.ic_hdmi : R.drawable.ic_headphone),
+                                mIsTv ? R.drawable.ic_hdmi : R.drawable.ic_external_display),
                         new Device(
                                 AudioDeviceInfo.TYPE_HDMI_EARC,
                                 MediaRoute2Info.TYPE_HDMI_EARC,
-                                mIsTv ? R.drawable.ic_hdmi : R.drawable.ic_headphone),
+                                mIsTv ? R.drawable.ic_hdmi : R.drawable.ic_external_display),
                         new Device(
                                 AudioDeviceInfo.TYPE_WIRED_HEADSET,
                                 MediaRoute2Info.TYPE_WIRED_HEADSET,
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index d6f1eab..15f33d2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -229,6 +229,17 @@
     @SuppressWarnings("NewApi")
     @Override
     public String getId() {
+        if (com.android.media.flags.Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
+            // Note: be careful when removing this flag. Instead of just removing it, you might want
+            // to replace it with SDK_INT >= 35. Explanation: The presence of SDK checks in settings
+            // lib suggests that a mainline component may depend on this code. Which means removing
+            // this "if" (and using always the route info id) could mean a regression on mainline
+            // code running on a device that's running API 34 or older. Unfortunately, we cannot
+            // check the API level at the moment of writing this code because the API level has not
+            // been bumped, yet.
+            return mRouteInfo.getId();
+        }
+
         String id;
         switch (mRouteInfo.getType()) {
             case TYPE_WIRED_HEADSET:
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index 8a122fc..aef09ac 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -26,11 +26,13 @@
 import android.media.RouteDiscoveryPreference;
 import android.media.RouteListingPreference;
 import android.media.RoutingSessionInfo;
+import android.os.Process;
 import android.text.TextUtils;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.media.flags.Flags;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 
 import java.util.ArrayList;
@@ -62,21 +64,33 @@
                 refreshDevices();
             };
 
-    // TODO: b/192657812 - Create factory method in InfoMediaManager to return
-    //      RouterInfoMediaManager or ManagerInfoMediaManager based on flag.
+    // TODO (b/321969740): Plumb target UserHandle between UMO and RouterInfoMediaManager.
     public RouterInfoMediaManager(
             Context context,
             String packageName,
             Notification notification,
-            LocalBluetoothManager localBluetoothManager) throws PackageNotAvailableException {
+            LocalBluetoothManager localBluetoothManager)
+            throws PackageNotAvailableException {
         super(context, packageName, notification, localBluetoothManager);
 
-        mRouter = MediaRouter2.getInstance(context, packageName);
+        MediaRouter2 router = null;
 
-        if (mRouter == null) {
+        if (Flags.enableCrossUserRoutingInMediaRouter2()) {
+            try {
+                router = MediaRouter2.getInstance(context, packageName, Process.myUserHandle());
+            } catch (IllegalArgumentException ex) {
+                // Do nothing
+            }
+        } else {
+            router = MediaRouter2.getInstance(context, packageName);
+        }
+        if (router == null) {
             throw new PackageNotAvailableException(
                     "Package name " + packageName + " does not exist.");
         }
+        // We have to defer initialization because mRouter is final.
+        mRouter = router;
+
         mRouterManager = MediaRouter2Manager.getInstance(context);
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
index cd5f597..b015b2b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
@@ -234,6 +234,6 @@
     EditUserPhotoController createEditUserPhotoController(Activity activity,
             ActivityStarter activityStarter, ImageView userPhotoView) {
         return new EditUserPhotoController(activity, activityStarter, userPhotoView,
-                mSavedPhoto, mSavedDrawable, mFileAuthority);
+                mSavedPhoto, mSavedDrawable, mFileAuthority, false);
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
index e83b9bc..b2de5a9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
@@ -62,6 +62,7 @@
 
     private static final String AVATAR_PICKER_ACTION = "com.android.avatarpicker"
             + ".FULL_SCREEN_ACTIVITY";
+    static final String EXTRA_IS_USER_NEW = "is_user_new";
 
     private final Activity mActivity;
     private final ActivityStarter mActivityStarter;
@@ -72,9 +73,13 @@
     private Bitmap mNewUserPhotoBitmap;
     private Drawable mNewUserPhotoDrawable;
     private String mCachedDrawablePath;
-
     public EditUserPhotoController(Activity activity, ActivityStarter activityStarter,
             ImageView view, Bitmap savedBitmap, Drawable savedDrawable, String fileAuthority) {
+        this(activity, activityStarter, view, savedBitmap, savedDrawable, fileAuthority, true);
+    }
+    public EditUserPhotoController(Activity activity, ActivityStarter activityStarter,
+            ImageView view, Bitmap savedBitmap, Drawable savedDrawable, String fileAuthority,
+            boolean isUserNew) {
         mActivity = activity;
         mActivityStarter = activityStarter;
         mFileAuthority = fileAuthority;
@@ -82,7 +87,7 @@
         mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR);
         mImagesDir.mkdir();
         mImageView = view;
-        mImageView.setOnClickListener(v -> showAvatarPicker());
+        mImageView.setOnClickListener(v -> showAvatarPicker(isUserNew));
 
         mNewUserPhotoBitmap = savedBitmap;
         mNewUserPhotoDrawable = savedDrawable;
@@ -117,11 +122,12 @@
         return mNewUserPhotoDrawable;
     }
 
-    private void showAvatarPicker() {
+    private void showAvatarPicker(boolean isUserNew) {
         Intent intent;
         if (Flags.avatarSync()) {
             intent = new Intent(AVATAR_PICKER_ACTION);
             intent.addCategory(Intent.CATEGORY_DEFAULT);
+            intent.putExtra(EXTRA_IS_USER_NEW, isUserNew);
         } else {
             intent = new Intent(mImageView.getContext(), AvatarPickerActivity.class);
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
new file mode 100644
index 0000000..3355fb3
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.data.repository
+
+import android.media.AudioManager
+import com.android.internal.util.ConcurrentUtils
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Provides audio managing functionality and data. */
+interface AudioRepository {
+
+    /** Current [AudioManager.getMode]. */
+    val mode: StateFlow<Int>
+}
+
+class AudioRepositoryImpl(
+    private val audioManager: AudioManager,
+    backgroundCoroutineContext: CoroutineContext,
+    coroutineScope: CoroutineScope,
+) : AudioRepository {
+
+    override val mode: StateFlow<Int> =
+        callbackFlow {
+                val listener =
+                    AudioManager.OnModeChangedListener { newMode -> launch { send(newMode) } }
+                audioManager.addOnModeChangedListener(ConcurrentUtils.DIRECT_EXECUTOR, listener)
+                awaitClose { audioManager.removeOnModeChangedListener(listener) }
+            }
+            .flowOn(backgroundCoroutineContext)
+            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), audioManager.mode)
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractor.kt
new file mode 100644
index 0000000..053c59b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractor.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.domain.interactor
+
+import android.media.AudioManager
+import com.android.settingslib.volume.data.repository.AudioRepository
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class AudioModeInteractor(repository: AudioRepository) {
+
+    private val ongoingCallModes =
+        setOf(
+            AudioManager.MODE_RINGTONE,
+            AudioManager.MODE_IN_CALL,
+            AudioManager.MODE_IN_COMMUNICATION,
+        )
+
+    /** Returns if current [AudioManager.getMode] call is an ongoing call */
+    val isOngoingCall: Flow<Boolean> = repository.mode.map { it in ongoingCallModes }
+}
diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp
index 644b72c..ce3a7ba 100644
--- a/packages/SettingsLib/tests/integ/Android.bp
+++ b/packages/SettingsLib/tests/integ/Android.bp
@@ -57,6 +57,7 @@
         "SettingsLibSettingsSpinner",
         "SettingsLibUsageProgressBarPreference",
         "settingslib_media_flags_lib",
+        "kotlinx_coroutines_test",
     ],
 
     dxflags: ["--multi-dex"],
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
new file mode 100644
index 0000000..686362f
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeAudioRepository : AudioRepository {
+
+    private val mutableMode = MutableStateFlow(0)
+    override val mode: StateFlow<Int>
+        get() = mutableMode.asStateFlow()
+
+    fun setMode(newMode: Int) {
+        mutableMode.value = newMode
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
new file mode 100644
index 0000000..3bc1edc
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.domain.interactor
+
+import android.media.AudioManager
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.settingslib.volume.data.repository.FakeAudioRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class AudioModeInteractorTest {
+
+    private val testScope = TestScope()
+    private val fakeAudioRepository = FakeAudioRepository()
+
+    private val underTest = AudioModeInteractor(fakeAudioRepository)
+
+    @Test
+    fun ongoingCallModes_isOnGoingCall() {
+        testScope.runTest {
+            for (mode in ongoingCallModes) {
+                var isOngoingCall = false
+                underTest.isOngoingCall.onEach { isOngoingCall = it }.launchIn(backgroundScope)
+
+                fakeAudioRepository.setMode(mode)
+                runCurrent()
+
+                assertThat(isOngoingCall).isTrue()
+            }
+        }
+    }
+
+    @Test
+    fun notOngoingCallModes_isNotOnGoingCall() {
+        testScope.runTest {
+            var isOngoingCall = true
+            underTest.isOngoingCall.onEach { isOngoingCall = it }.launchIn(backgroundScope)
+
+            fakeAudioRepository.setMode(AudioManager.MODE_CURRENT)
+            runCurrent()
+
+            assertThat(isOngoingCall).isFalse()
+        }
+    }
+
+    private companion object {
+        private val ongoingCallModes =
+            setOf(
+                AudioManager.MODE_RINGTONE,
+                AudioManager.MODE_IN_CALL,
+                AudioManager.MODE_IN_COMMUNICATION,
+            )
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 7409eea..f7ec80b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -16,6 +16,7 @@
 package com.android.settingslib.bluetooth;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -87,6 +88,14 @@
     }
 
     @Test
+    public void getBtClassDrawableWithDescription_typeHearingAid_returnHearingAidDrawable() {
+        when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
+        BluetoothUtils.getBtClassDrawableWithDescription(mContext, mCachedBluetoothDevice);
+
+        verify(mContext).getDrawable(com.android.internal.R.drawable.ic_bt_hearing_aid);
+    }
+
+    @Test
     public void getBtRainbowDrawableWithDescription_normalHeadset_returnAdaptiveIcon() {
         when(mBluetoothDevice.getMetadata(
                 BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn("false".getBytes());
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index ed545ab..9db8b47 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -18,7 +18,9 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -56,6 +58,8 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.shadow.api.Shadow;
 
+import java.util.concurrent.ExecutionException;
+
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowBluetoothAdapter.class})
 public class CachedBluetoothDeviceTest {
@@ -1815,6 +1819,52 @@
         assertThat(mCachedDevice.isConnectedHearingAidDevice()).isFalse();
     }
 
+    @Test
+    public void syncProfileForMemberDevice_hasDiff_shouldSync()
+            throws ExecutionException, InterruptedException {
+        mCachedDevice.addMemberDevice(mSubCachedDevice);
+        when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
+        when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
+        when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+
+        when(mA2dpProfile.isEnabled(mDevice)).thenReturn(true);
+        when(mHearingAidProfile.isEnabled(mDevice)).thenReturn(true);
+        when(mLeAudioProfile.isEnabled(mDevice)).thenReturn(true);
+
+        when(mA2dpProfile.isEnabled(mSubDevice)).thenReturn(true);
+        when(mHearingAidProfile.isEnabled(mSubDevice)).thenReturn(false);
+        when(mLeAudioProfile.isEnabled(mSubDevice)).thenReturn(false);
+
+        mCachedDevice.syncProfileForMemberDevice().get();
+
+        verify(mA2dpProfile, never()).setEnabled(any(BluetoothDevice.class), anyBoolean());
+        verify(mHearingAidProfile).setEnabled(any(BluetoothDevice.class), eq(true));
+        verify(mLeAudioProfile).setEnabled(any(BluetoothDevice.class), eq(true));
+    }
+
+    @Test
+    public void syncProfileForMemberDevice_noDiff_shouldNotSync()
+            throws ExecutionException, InterruptedException {
+        mCachedDevice.addMemberDevice(mSubCachedDevice);
+        when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
+        when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
+        when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+
+        when(mA2dpProfile.isEnabled(mDevice)).thenReturn(false);
+        when(mHearingAidProfile.isEnabled(mDevice)).thenReturn(false);
+        when(mLeAudioProfile.isEnabled(mDevice)).thenReturn(true);
+
+        when(mA2dpProfile.isEnabled(mSubDevice)).thenReturn(false);
+        when(mHearingAidProfile.isEnabled(mSubDevice)).thenReturn(false);
+        when(mLeAudioProfile.isEnabled(mSubDevice)).thenReturn(true);
+
+        mCachedDevice.syncProfileForMemberDevice().get();
+
+        verify(mA2dpProfile, never()).setEnabled(any(BluetoothDevice.class), anyBoolean());
+        verify(mHearingAidProfile, never()).setEnabled(any(BluetoothDevice.class), anyBoolean());
+        verify(mLeAudioProfile, never()).setEnabled(any(BluetoothDevice.class), anyBoolean());
+    }
+
     private HearingAidInfo getLeftAshaHearingAidInfo() {
         return new HearingAidInfo.Builder()
                 .setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index 24fd06e..aa5a298 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -15,10 +15,20 @@
  */
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothHearingAid.HI_SYNC_ID_INVALID;
+import static android.bluetooth.BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT;
+import static android.bluetooth.BluetoothLeAudio.AUDIO_LOCATION_INVALID;
+
+import static com.android.settingslib.bluetooth.HapClientProfile.HearingAidType.TYPE_BINAURAL;
+import static com.android.settingslib.bluetooth.HapClientProfile.HearingAidType.TYPE_INVALID;
+import static com.android.settingslib.bluetooth.HearingAidProfile.DeviceMode.MODE_BINAURAL;
+import static com.android.settingslib.bluetooth.HearingAidProfile.DeviceSide.SIDE_RIGHT;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
@@ -31,7 +41,6 @@
 
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.le.ScanFilter;
@@ -73,13 +82,13 @@
     private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22";
     private final BluetoothClass DEVICE_CLASS =
             createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE);
+    private final Context mContext = ApplicationProvider.getApplicationContext();
 
     private CachedBluetoothDevice mCachedDevice1;
     private CachedBluetoothDevice mCachedDevice2;
     private CachedBluetoothDeviceManager mCachedDeviceManager;
     private HearingAidDeviceManager mHearingAidDeviceManager;
     private AudioDeviceAttributes mHearingDeviceAttribute;
-    private final Context mContext = ApplicationProvider.getApplicationContext();
     @Spy
     private HearingAidAudioRoutingHelper mHelper = new HearingAidAudioRoutingHelper(mContext);
     @Mock
@@ -91,6 +100,10 @@
     @Mock
     private HearingAidProfile mHearingAidProfile;
     @Mock
+    private LeAudioProfile mLeAudioProfile;
+    @Mock
+    private HapClientProfile mHapClientProfile;
+    @Mock
     private AudioProductStrategy mAudioStrategy;
     @Mock
     private BluetoothDevice mDevice1;
@@ -122,6 +135,8 @@
         when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
         when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager);
         when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
+        when(mLocalProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+        when(mLocalProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
         when(mAudioStrategy.getAudioAttributesForLegacyStreamType(
                 AudioManager.STREAM_MUSIC))
                 .thenReturn((new AudioAttributes.Builder()).build());
@@ -139,34 +154,43 @@
     }
 
     /**
-     * Test initHearingAidDeviceIfNeeded, set HearingAid's information, including HiSyncId,
-     * deviceSide, deviceMode.
+     * Test initHearingAidDeviceIfNeeded
+     *
+     * Conditions:
+     *      1) ASHA hearing aid
+     *      2) Valid HiSyncId
+     * Result:
+     *      Set hearing aid info to the device.
      */
     @Test
-    public void initHearingAidDeviceIfNeeded_validHiSyncId_setHearingAidInfo() {
+    public void initHearingAidDeviceIfNeeded_asha_validHiSyncId_setHearingAidInfo() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
         when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
-        when(mHearingAidProfile.getDeviceMode(mDevice1)).thenReturn(
-                HearingAidProfile.DeviceMode.MODE_BINAURAL);
-        when(mHearingAidProfile.getDeviceSide(mDevice1)).thenReturn(
-                HearingAidProfile.DeviceSide.SIDE_RIGHT);
+        when(mHearingAidProfile.getDeviceMode(mDevice1)).thenReturn(MODE_BINAURAL);
+        when(mHearingAidProfile.getDeviceSide(mDevice1)).thenReturn(SIDE_RIGHT);
 
         assertThat(mCachedDevice1.getHiSyncId()).isNotEqualTo(HISYNCID1);
         mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
 
         assertThat(mCachedDevice1.getHiSyncId()).isEqualTo(HISYNCID1);
+        assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_RIGHT);
         assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
                 HearingAidInfo.DeviceMode.MODE_BINAURAL);
-        assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(
-                HearingAidInfo.DeviceSide.SIDE_RIGHT);
     }
 
     /**
-     * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId will not be assigned
+     * Test initHearingAidDeviceIfNeeded
+     *
+     * Conditions:
+     *      1) ASHA hearing aid
+     *      2) Invalid HiSyncId
+     * Result:
+     *      Do not set hearing aid info to the device.
      */
     @Test
-    public void initHearingAidDeviceIfNeeded_invalidHiSyncId_notToSetHearingAidInfo() {
-        when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
-                BluetoothHearingAid.HI_SYNC_ID_INVALID);
+    public void initHearingAidDeviceIfNeeded_asha_invalidHiSyncId_notToSetHearingAidInfo() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+        when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HI_SYNC_ID_INVALID);
 
         mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
 
@@ -174,34 +198,89 @@
     }
 
     /**
-     * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId and hearing aid scan filter, set an
-     * empty hearing aid info on the device.
+     * Test initHearingAidDeviceIfNeeded
+     *
+     * Conditions:
+     *      1) ASHA hearing aid
+     *      2) Invalid HiSyncId
+     *      3) ASHA uuid scan filter
+     * Result:
+     *      Set an empty hearing aid info to the device.
      */
     @Test
-    public void initHearingAidDeviceIfNeeded_hearingAidScanFilter_setHearingAidInfo() {
-        when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
-                BluetoothHearingAid.HI_SYNC_ID_INVALID);
+    public void initHearingAidDeviceIfNeeded_asha_scanFilterNotNull_setEmptyHearingAidInfo() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+        when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HI_SYNC_ID_INVALID);
         final ScanFilter scanFilter = new ScanFilter.Builder()
                 .setServiceData(BluetoothUuid.HEARING_AID, new byte[]{0}).build();
 
         mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, List.of(scanFilter));
 
-        assertThat(mCachedDevice1.isHearingAidDevice()).isTrue();
+        verify(mCachedDevice1).setHearingAidInfo(new HearingAidInfo.Builder().build());
     }
 
     /**
-     * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId and random scan filter, not to set
-     * hearing aid info on the device.
+     * Test initHearingAidDeviceIfNeeded
+     *
+     * Conditions:
+     *      1) Asha hearing aid
+     *      2) Invalid HiSyncId
+     *      3) Random scan filter
+     * Result:
+     *      Do not set hearing aid info to the device.
      */
     @Test
-    public void initHearingAidDeviceIfNeeded_randomScanFilter_setHearingAidInfo() {
-        when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
-                BluetoothHearingAid.HI_SYNC_ID_INVALID);
+    public void initHearingAidDeviceIfNeeded_asha_randomScanFilter_notToSetHearingAidInfo() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+        when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HI_SYNC_ID_INVALID);
         final ScanFilter scanFilter = new ScanFilter.Builder().build();
 
         mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, List.of(scanFilter));
 
-        assertThat(mCachedDevice1.isHearingAidDevice()).isFalse();
+        verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
+    }
+
+    /**
+     * Test initHearingAidDeviceIfNeeded
+     *
+     * Conditions:
+     *      1) LeAudio hearing aid
+     *      2) Valid audio location and device type
+     * Result:
+     *      Set hearing aid info to the device.
+     */
+    @Test
+    public void initHearingAidDeviceIfNeeded_leAudio_validInfo_setHearingAidInfo() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
+        when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_FRONT_LEFT);
+        when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_BINAURAL);
+
+        mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
+
+        verify(mCachedDevice1).setHearingAidInfo(any(HearingAidInfo.class));
+        assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_LEFT);
+        assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
+                HearingAidInfo.DeviceMode.MODE_BINAURAL);
+    }
+
+    /**
+     * Test initHearingAidDeviceIfNeeded
+     *
+     * Conditions:
+     *      1) LeAudio hearing aid
+     *      2) Invalid audio location and device type
+     * Result:
+     *      Do not set hearing aid info to the device.
+     */
+    @Test
+    public void initHearingAidDeviceIfNeeded_leAudio_invalidInfo_notToSetHearingAidInfo() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
+        when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_INVALID);
+        when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_INVALID);
+
+        mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
+
+        verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
     }
 
     /**
@@ -233,13 +312,20 @@
     }
 
     /**
-     * Test updateHearingAidsDevices, to link two devices with the same HiSyncId.
-     * When first paired devices is connected and second paired device is disconnected, first
-     * paired device would be set as main device and second device will be removed from
-     * CachedDevices list.
+     * Test updateHearingAidsDevices
+     *
+     * Conditions:
+     *      1) Two ASHA hearing aids with the same HiSyncId
+     *      2) First paired devices is connected
+     *      3) Second paired device is disconnected
+     * Result:
+     *      First paired device would be set as main device and second paired device will be set
+     *      as sub device and removed from CachedDevices list.
      */
     @Test
-    public void updateHearingAidsDevices_firstPairedDevicesConnected_verifySubDevice() {
+    public void updateHearingAidsDevices_asha_firstPairedDevicesConnected_verifySubDevice() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+        when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
         when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
         when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1);
         when(mCachedDevice1.isConnected()).thenReturn(true);
@@ -256,13 +342,20 @@
     }
 
     /**
-     * Test updateHearingAidsDevices, to link two devices with the same HiSyncId.
-     * When second paired devices is connected and first paired device is disconnected, second
-     * paired device would be set as main device and first device will be removed from
-     * CachedDevices list.
+     * Test updateHearingAidsDevices
+     *
+     * Conditions:
+     *      1) Two ASHA hearing aids with the same HiSyncId
+     *      2) First paired devices is disconnected
+     *      3) Second paired device is connected
+     * Result:
+     *      Second paired device would be set as main device and first paired device will be set
+     *      as sub device and removed from CachedDevices list.
      */
     @Test
-    public void updateHearingAidsDevices_secondPairedDeviceConnected_verifySubDevice() {
+    public void updateHearingAidsDevices_asha_secondPairedDeviceConnected_verifySubDevice() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+        when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
         when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
         when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1);
         when(mCachedDevice1.isConnected()).thenReturn(false);
@@ -279,12 +372,20 @@
     }
 
     /**
-     * Test updateHearingAidsDevices, to link two devices with the same HiSyncId.
-     * When both devices are connected, to build up main and sub relationship and to remove sub
-     * device from CachedDevices list.
+     * Test updateHearingAidsDevices
+     *
+     * Conditions:
+     *      1) Two ASHA hearing aids with the same HiSyncId
+     *      2) First paired devices is connected
+     *      3) Second paired device is connected
+     * Result:
+     *      First paired device would be set as main device and second paired device will be set
+     *      as sub device and removed from CachedDevices list.
      */
     @Test
-    public void updateHearingAidsDevices_BothConnected_verifySubDevice() {
+    public void updateHearingAidsDevices_asha_bothConnected_verifySubDevice() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+        when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
         when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
         when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1);
         when(mCachedDevice1.isConnected()).thenReturn(true);
@@ -301,46 +402,64 @@
     }
 
     /**
-     * Test updateHearingAidsDevices, dispatch callback
+     * Test updateHearingAidsDevices
+     *
+     * Conditions:
+     *      1) Two ASHA hearing aids with the same HiSyncId
+     * Result:
+     *      Dispatch device removed callback
      */
     @Test
-    public void updateHearingAidsDevices_dispatchDeviceRemovedCallback() {
+    public void updateHearingAidsDevices_asha_dispatchDeviceRemovedCallback() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+        when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
         when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
         when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1);
         mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
         mCachedDeviceManager.mCachedDevices.add(mCachedDevice2);
+
         mHearingAidDeviceManager.updateHearingAidsDevices();
 
         verify(mBluetoothEventManager).dispatchDeviceRemoved(mCachedDevice1);
     }
 
     /**
-     * Test updateHearingAidsDevices, do nothing when HiSyncId is invalid
+     * Test updateHearingAidsDevices
+     *
+     * Conditions:
+     *      1) Two ASHA hearing aids with invalid HiSyncId
+     * Result:
+     *      Do nothing
      */
     @Test
-    public void updateHearingAidsDevices_invalidHiSyncId_doNothing() {
-        when(mHearingAidProfile.getHiSyncId(mDevice1)).
-                thenReturn(BluetoothHearingAid.HI_SYNC_ID_INVALID);
-        when(mHearingAidProfile.getHiSyncId(mDevice2)).
-                thenReturn(BluetoothHearingAid.HI_SYNC_ID_INVALID);
+    public void updateHearingAidsDevices_asha_invalidHiSyncId_doNothing() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+        when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+        when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HI_SYNC_ID_INVALID);
+        when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HI_SYNC_ID_INVALID);
         mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
         mCachedDeviceManager.mCachedDevices.add(mCachedDevice2);
+
         mHearingAidDeviceManager.updateHearingAidsDevices();
 
         verify(mHearingAidDeviceManager, never()).onHiSyncIdChanged(anyLong());
     }
 
     /**
-     * Test updateHearingAidsDevices, set HearingAid's information, including HiSyncId, deviceSide,
-     * deviceMode.
+     * Test updateHearingAidsDevices
+     *
+     * Conditions:
+     *      1) ASHA hearing aids
+     *      2) Valid HiSync Id
+     * Result:
+     *      Set hearing aid info to the device.
      */
     @Test
-    public void updateHearingAidsDevices_validHiSyncId_setHearingAidInfos() {
+    public void updateHearingAidsDevices_asha_validHiSyncId_setHearingAidInfo() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
         when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
-        when(mHearingAidProfile.getDeviceMode(mDevice1)).thenReturn(
-                HearingAidProfile.DeviceMode.MODE_BINAURAL);
-        when(mHearingAidProfile.getDeviceSide(mDevice1)).thenReturn(
-                HearingAidProfile.DeviceSide.SIDE_RIGHT);
+        when(mHearingAidProfile.getDeviceMode(mDevice1)).thenReturn(MODE_BINAURAL);
+        when(mHearingAidProfile.getDeviceSide(mDevice1)).thenReturn(SIDE_RIGHT);
         mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
 
         mHearingAidDeviceManager.updateHearingAidsDevices();
@@ -354,6 +473,51 @@
     }
 
     /**
+     * Test updateHearingAidsDevices
+     *
+     * Conditions:
+     *      1) LeAudio hearing aid
+     *      2) Valid audio location and device type
+     * Result:
+     *      Set hearing aid info to the device.
+     */
+    @Test
+    public void updateHearingAidsDevices_leAudio_validInfo_setHearingAidInfo() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
+        when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_FRONT_LEFT);
+        when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_BINAURAL);
+        mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
+
+        mHearingAidDeviceManager.updateHearingAidsDevices();
+
+        verify(mCachedDevice1).setHearingAidInfo(any(HearingAidInfo.class));
+        assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_LEFT);
+        assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
+                HearingAidInfo.DeviceMode.MODE_BINAURAL);
+    }
+
+    /**
+     * Test updateHearingAidsDevices
+     *
+     * Conditions:
+     *      1) LeAudio hearing aid
+     *      2) Invalid audio location and device type
+     * Result:
+     *      Do not set hearing aid info to the device.
+     */
+    @Test
+    public void updateHearingAidsDevices_leAudio_invalidInfo_notToSetHearingAidInfo() {
+        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
+        when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_INVALID);
+        when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_INVALID);
+        mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
+
+        mHearingAidDeviceManager.updateHearingAidsDevices();
+
+        verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
+    }
+
+    /**
      * Test onProfileConnectionStateChangedIfProcessed.
      * When first hearing aid device is connected, to process it same as other generic devices.
      * No need to process it.
@@ -517,6 +681,8 @@
         when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
                 mHearingDeviceAttribute);
         when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
+        doReturn(true).when(mHelper).setPreferredDeviceRoutingStrategies(anyList(),
+                eq(mHearingDeviceAttribute), anyInt());
 
         mHearingAidDeviceManager.onActiveDeviceChanged(mCachedDevice1);
 
@@ -529,6 +695,8 @@
         when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
                 mHearingDeviceAttribute);
         when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(false);
+        doReturn(true).when(mHelper).setPreferredDeviceRoutingStrategies(anyList(), any(),
+                anyInt());
 
         mHearingAidDeviceManager.onActiveDeviceChanged(mCachedDevice1);
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
index 1746bef..ceba9be 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
@@ -31,10 +31,15 @@
 
 import android.content.Context;
 import android.media.MediaRoute2Info;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 
+import com.android.media.flags.Flags;
 import com.android.settingslib.R;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -45,6 +50,8 @@
 @RunWith(RobolectricTestRunner.class)
 public class PhoneMediaDeviceTest {
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock
     private MediaRoute2Info mInfo;
 
@@ -110,8 +117,18 @@
                 .isEqualTo(mContext.getString(R.string.media_transfer_this_device_name));
     }
 
+    @EnableFlags(Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
     @Test
-    public void getId_returnCorrectId() {
+    public void getId_whenAdvancedWiredRoutingEnabled_returnCorrectId() {
+        String fakeId = "foo";
+        when(mInfo.getId()).thenReturn(fakeId);
+
+        assertThat(mPhoneMediaDevice.getId()).isEqualTo(fakeId);
+    }
+
+    @DisableFlags(Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
+    @Test
+    public void getId_whenAdvancedWiredRoutingDisabled_returnCorrectId() {
         when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES);
 
         assertThat(mPhoneMediaDevice.getId())
diff --git a/packages/SettingsProvider/TEST_MAPPING b/packages/SettingsProvider/TEST_MAPPING
index 890510f..0eed2b7 100644
--- a/packages/SettingsProvider/TEST_MAPPING
+++ b/packages/SettingsProvider/TEST_MAPPING
@@ -11,5 +11,10 @@
                 }
             ]
         }
+    ],
+    "postsubmit": [
+        {
+            "name": "CtsDeviceConfigTestCases"
+        }
     ]
 }
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 89a8dd9..17d9f1b 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -335,4 +335,7 @@
     <!-- Default for Settings.BATTERY_CHARGING_STATE_ENFORCE_LEVEL.
         -1 means system internal default value is used. -->
     <integer name="def_battery_charging_state_enforce_level">-1</integer>
+
+    <!-- Value to use as default scale for fonts -->
+    <item name="def_device_font_scale" format="float" type="dimen">1.0</item>
 </resources>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 5004f25..8ad5f24 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -74,6 +74,7 @@
         Settings.Secure.TTS_DEFAULT_LOCALE,
         Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
         Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS,
+        Settings.Secure.ACCESSIBILITY_SLOW_KEYS,
         Settings.Secure.ACCESSIBILITY_STICKY_KEYS,
         Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,            // moved to global
         Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,               // moved to global
@@ -241,6 +242,8 @@
         Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO,
         Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE,
         Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME,
+        Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY,
+        Settings.Secure.BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_DEVICE_ADDRESS,
         Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP,
         Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER,
         Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index e7d7bb0..38ec931 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -48,6 +48,7 @@
                 Settings.System.WIFI_STATIC_DNS2,
                 Settings.System.BLUETOOTH_DISCOVERABILITY,
                 Settings.System.BLUETOOTH_DISCOVERABILITY_TIMEOUT,
+                Settings.System.DEFAULT_DEVICE_FONT_SCALE,
                 Settings.System.FONT_SCALE,
                 Settings.System.DIM_SCREEN,
                 Settings.System.SCREEN_OFF_TIMEOUT,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 0b0e182..d854df38 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -120,6 +120,7 @@
         VALIDATORS.put(Secure.TTS_DEFAULT_LOCALE, TTS_LIST_VALIDATOR);
         VALIDATORS.put(Secure.SHOW_IME_WITH_HARD_KEYBOARD, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_BOUNCE_KEYS, ANY_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.ACCESSIBILITY_SLOW_KEYS, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_STICKY_KEYS, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, NON_NEGATIVE_INTEGER_VALIDATOR);
@@ -387,6 +388,11 @@
         VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO, ANY_STRING_VALIDATOR);
         VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_CODE, ANY_STRING_VALIDATOR);
         VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME, ANY_STRING_VALIDATOR);
+        VALIDATORS.put(
+                Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY,
+                new DiscreteValueValidator(new String[] {"0", "1"}));
+        VALIDATORS.put(
+                Secure.BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_DEVICE_ADDRESS, ANY_STRING_VALIDATOR);
         VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_APP, ANY_STRING_VALIDATOR);
         VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_USER, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.LOCK_SCREEN_WEATHER_ENABLED, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
index a8a659e..677c81a 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
@@ -45,6 +45,9 @@
         }
     };
 
+    public static final Validator FONT_SCALE_VALIDATOR = new InclusiveFloatRangeValidator(0.25f,
+            5.0f);
+
     public static final Validator NON_NEGATIVE_INTEGER_VALIDATOR = new Validator() {
         @Override
         public boolean validate(@Nullable String value) {
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 572303a..98941c7 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -20,6 +20,7 @@
 import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.COMPONENT_NAME_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.FONT_SCALE_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.LENIENT_IP_ADDRESS_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_FLOAT_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR;
@@ -31,7 +32,6 @@
 import android.content.ComponentName;
 import android.hardware.display.ColorDisplayManager;
 import android.os.BatteryManager;
-import android.provider.Settings.Global;
 import android.provider.Settings.System;
 import android.util.ArrayMap;
 
@@ -93,7 +93,8 @@
                         return value == null || value.length() < MAX_LENGTH;
                     }
                 });
-        VALIDATORS.put(System.FONT_SCALE, new InclusiveFloatRangeValidator(0.25f, 5.0f));
+        VALIDATORS.put(System.DEFAULT_DEVICE_FONT_SCALE, FONT_SCALE_VALIDATOR);
+        VALIDATORS.put(System.FONT_SCALE, FONT_SCALE_VALIDATOR);
         VALIDATORS.put(System.DIM_SCREEN, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
                 System.DISPLAY_COLOR_MODE,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index b0abf92..febce97 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -406,7 +406,7 @@
                     Process.THREAD_PRIORITY_BACKGROUND);
             mHandlerThread.start();
             mHandler = new Handler(mHandlerThread.getLooper());
-            mSettingsRegistry = new SettingsRegistry();
+            mSettingsRegistry = new SettingsRegistry(mHandlerThread.getLooper());
         }
         SettingsState.cacheSystemPackageNamesAndSystemSignature(getContext());
         synchronized (mLock) {
@@ -1349,6 +1349,26 @@
             final int nameCount = names.size();
             HashMap<String, String> flagsToValues = new HashMap<>(names.size());
 
+            if (Flags.loadAconfigDefaults()) {
+                Map<String, Map<String, String>> allDefaults =
+                        settingsState.getAconfigDefaultValues();
+
+                if (allDefaults != null) {
+                    if (prefix != null) {
+                        String namespace = prefix.substring(0, prefix.length() - 1);
+
+                        Map<String, String> namespaceDefaults = allDefaults.get(namespace);
+                        if (namespaceDefaults != null) {
+                            flagsToValues.putAll(namespaceDefaults);
+                        }
+                    } else {
+                        for (Map<String, String> namespaceDefaults : allDefaults.values()) {
+                            flagsToValues.putAll(namespaceDefaults);
+                        }
+                    }
+                }
+            }
+
             for (int i = 0; i < nameCount; i++) {
                 String name = names.get(i);
                 Setting setting = settingsState.getSettingLocked(name);
@@ -2876,8 +2896,8 @@
 
         private String mSettingsCreationBuildId;
 
-        public SettingsRegistry() {
-            mHandler = new MyHandler(getContext().getMainLooper());
+        SettingsRegistry(Looper looper) {
+            mHandler = new MyHandler(looper);
             mGenerationRegistry = new GenerationRegistry(UserManager.getMaxSupportedUsers());
             mBackupManager = new BackupManager(getContext());
         }
@@ -3792,7 +3812,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 224;
+            private static final int SETTINGS_VERSION = 225;
 
             private final int mUserId;
 
@@ -5984,6 +6004,13 @@
                     currentVersion = 224;
                 }
 
+                // Version 224: Update the default font scale depending on the
+                //              R.dimen.def_device_font_scale configuration property.
+                if (currentVersion == 224) {
+                    handleDefaultFontScale(getSystemSettingsLocked(userId));
+                    currentVersion = 225;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
@@ -6001,6 +6028,32 @@
                 return currentVersion;
             }
 
+            @SuppressWarnings("GuardedBy")
+            @GuardedBy("mLock")
+            private void handleDefaultFontScale(@NonNull SettingsState systemSettings) {
+                final float defaultFontScale = getContext().getResources()
+                        .getFloat(R.dimen.def_device_font_scale);
+                // Persist the value for future use (e.g. Reset Settings option)
+                systemSettings.insertSettingLocked(
+                        Settings.System.DEFAULT_DEVICE_FONT_SCALE,
+                        String.valueOf(defaultFontScale),
+                        /* tag= */ null,
+                        /* makeDefault= */ false,
+                        SettingsState.SYSTEM_PACKAGE_NAME);
+                // We verify if there is a pre existing value for font_scale.
+                final Setting existingFontScale = systemSettings.getSettingLocked(
+                        Settings.System.FONT_SCALE);
+                if (existingFontScale == null || existingFontScale.isNull()) {
+                    // Set the default value only if it didn't exist before
+                    systemSettings.insertSettingLocked(
+                            Settings.System.FONT_SCALE,
+                            String.valueOf(defaultFontScale),
+                            /* tag= */ null,
+                            /* makeDefault= */ false,
+                            SettingsState.SYSTEM_PACKAGE_NAME);
+                }
+            }
+
             @GuardedBy("mLock")
             private void initGlobalSettingsDefaultValLocked(String key, boolean val) {
                 initGlobalSettingsDefaultValLocked(key, val ? "1" : "0");
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 73c2e22..6f3c88f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -69,6 +69,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -236,6 +237,10 @@
     @GuardedBy("mLock")
     private int mNextHistoricalOpIdx;
 
+    @GuardedBy("mLock")
+    @Nullable
+    private Map<String, Map<String, String>> mNamespaceDefaults;
+
     public static final int SETTINGS_TYPE_GLOBAL = 0;
     public static final int SETTINGS_TYPE_SYSTEM = 1;
     public static final int SETTINGS_TYPE_SECURE = 2;
@@ -331,25 +336,21 @@
             readStateSyncLocked();
 
             if (Flags.loadAconfigDefaults()) {
-                // Only load aconfig defaults if this is the first boot, the XML
-                // file doesn't exist yet, or this device is on its first boot after
-                // an OTA.
-                boolean shouldLoadAconfigValues = isConfigSettingsKey(mKey)
-                        && (!file.exists()
-                                || mContext.getPackageManager().isDeviceUpgrading());
-                if (shouldLoadAconfigValues) {
+                if (isConfigSettingsKey(mKey)) {
                     loadAconfigDefaultValuesLocked();
                 }
             }
+
         }
     }
 
     @GuardedBy("mLock")
     private void loadAconfigDefaultValuesLocked() {
+        mNamespaceDefaults = new HashMap<>();
+
         for (String fileName : sAconfigTextProtoFilesOnDevice) {
             try (FileInputStream inputStream = new FileInputStream(fileName)) {
-                byte[] contents = inputStream.readAllBytes();
-                loadAconfigDefaultValues(contents);
+                loadAconfigDefaultValues(inputStream.readAllBytes(), mNamespaceDefaults);
             } catch (IOException e) {
                 Slog.e(LOG_TAG, "failed to read protobuf", e);
             }
@@ -358,27 +359,21 @@
 
     @VisibleForTesting
     @GuardedBy("mLock")
-    public void loadAconfigDefaultValues(byte[] fileContents) {
+    public static void loadAconfigDefaultValues(byte[] fileContents,
+            @NonNull Map<String, Map<String, String>> defaultMap) {
         try {
-            parsed_flags parsedFlags = parsed_flags.parseFrom(fileContents);
-
-            if (parsedFlags == null) {
-                Slog.e(LOG_TAG, "failed to parse aconfig protobuf");
-                return;
-            }
-
+            parsed_flags parsedFlags =
+                    parsed_flags.parseFrom(fileContents);
             for (parsed_flag flag : parsedFlags.getParsedFlagList()) {
-                String flagName = flag.getNamespace() + "/"
-                        + flag.getPackage() + "." + flag.getName();
-                String value = flag.getState() == flag_state.ENABLED ? "true" : "false";
-
-                Setting existingSetting = getSettingLocked(flagName);
-                boolean isDefaultLoaded = existingSetting.getTag() != null
-                        && existingSetting.getTag().equals(BOOT_LOADED_DEFAULT_TAG);
-                if (existingSetting.getValue() == null || isDefaultLoaded) {
-                    insertSettingLocked(flagName, value, BOOT_LOADED_DEFAULT_TAG,
-                            false, flag.getPackage());
+                if (!defaultMap.containsKey(flag.getNamespace())) {
+                    Map<String, String> defaults = new HashMap<>();
+                    defaultMap.put(flag.getNamespace(), defaults);
                 }
+                String flagName = flag.getNamespace()
+                        + "/" + flag.getPackage() + "." + flag.getName();
+                String flagValue = flag.getState() == flag_state.ENABLED
+                        ? "true" : "false";
+                defaultMap.get(flag.getNamespace()).put(flagName, flagValue);
             }
         } catch (IOException e) {
             Slog.e(LOG_TAG, "failed to parse protobuf", e);
@@ -443,6 +438,13 @@
         return names;
     }
 
+    @Nullable
+    public Map<String, Map<String, String>> getAconfigDefaultValues() {
+        synchronized (mLock) {
+            return mNamespaceDefaults;
+        }
+    }
+
     // The settings provider must hold its lock when calling here.
     public Setting getSettingLocked(String name) {
         if (TextUtils.isEmpty(name)) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index ecac5ee..edbc0b3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -14,3 +14,11 @@
     bug: "311155098"
     is_fixed_read_only: true
 }
+
+flag {
+  name: "configurable_font_scale_default"
+  namespace: "large_screen_experiences_app_compat"
+  description: "Whether the font_scale is read from a device dependent configuration file"
+  bug: "319808237"
+  is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 1481d97..16de478 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -231,6 +231,7 @@
                     Settings.Global.ENABLE_ADB_INCREMENTAL_INSTALL_DEFAULT,
                     Settings.Global.ENABLE_MULTI_SLOT_TIMEOUT_MILLIS,
                     Settings.Global.ENHANCED_4G_MODE_ENABLED,
+                    Settings.Global.ENABLE_16K_PAGES, // Added for 16K developer option
                     Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES,
                     Settings.Global.ERROR_LOGCAT_PREFIX,
                     Settings.Global.EUICC_PROVISIONED,
@@ -867,6 +868,7 @@
                         Settings.Secure.NEARBY_SHARING_SLICE_URI,
                         Settings.Secure.NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES,
                         Settings.Secure.ONE_HANDED_TUTORIAL_SHOW_COUNT,
+                        Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
                         Settings.Secure.RELEASE_COMPRESS_BLOCKS_ON_INSTALL,
                         Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED,
                         Settings.Secure.SHOW_QR_CODE_SCANNER_SETTING,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 24625ea..e55bbec 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -30,6 +30,8 @@
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.PrintStream;
+import java.util.HashMap;
+import java.util.Map;
 
 public class SettingsStateTest extends AndroidTestCase {
     public static final String CRAZY_STRING =
@@ -93,7 +95,6 @@
         SettingsState settingsState = new SettingsState(
                 getContext(), lock, mSettingsFile, configKey,
                 SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
-
         parsed_flags flags = parsed_flags
                 .newBuilder()
                 .addParsedFlag(parsed_flag
@@ -117,18 +118,13 @@
                 .build();
 
         synchronized (lock) {
-            settingsState.loadAconfigDefaultValues(flags.toByteArray());
-            settingsState.persistSettingsLocked();
-        }
-        settingsState.waitForHandler();
+            Map<String, Map<String, String>> defaults = new HashMap<>();
+            settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
+            Map<String, String> namespaceDefaults = defaults.get("test_namespace");
+            assertEquals(2, namespaceDefaults.keySet().size());
 
-        synchronized (lock) {
-            assertEquals("false",
-                    settingsState.getSettingLocked(
-                        "test_namespace/com.android.flags.flag1").getValue());
-            assertEquals("true",
-                    settingsState.getSettingLocked(
-                        "test_namespace/com.android.flags.flag2").getValue());
+            assertEquals("false", namespaceDefaults.get("test_namespace/com.android.flags.flag1"));
+            assertEquals("true", namespaceDefaults.get("test_namespace/com.android.flags.flag2"));
         }
     }
 
@@ -150,21 +146,18 @@
                 .build();
 
         synchronized (lock) {
-            settingsState.loadAconfigDefaultValues(flags.toByteArray());
-            settingsState.persistSettingsLocked();
-        }
-        settingsState.waitForHandler();
+            Map<String, Map<String, String>> defaults = new HashMap<>();
+            settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
 
-        synchronized (lock) {
-            assertEquals(null,
-                    settingsState.getSettingLocked(
-                        "test_namespace/com.android.flags.flag1").getValue());
+            Map<String, String> namespaceDefaults = defaults.get("test_namespace");
+            assertEquals(null, namespaceDefaults);
         }
     }
 
     public void testInvalidAconfigProtoDoesNotCrash() {
+        Map<String, Map<String, String>> defaults = new HashMap<>();
         SettingsState settingsState = getSettingStateObject();
-        settingsState.loadAconfigDefaultValues("invalid protobuf".getBytes());
+        settingsState.loadAconfigDefaultValues("invalid protobuf".getBytes(), defaults);
     }
 
     public void testIsBinary() {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 507d9c4..95e0e1b 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -343,6 +343,7 @@
     <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_GLASSES" />
     <uses-permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
     <uses-permission android:name="android.permission.USE_COMPANION_TRANSPORTS" />
+    <uses-permission android:name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE" />
 
     <uses-permission android:name="android.permission.MANAGE_APPOPS" />
     <uses-permission android:name="android.permission.WATCH_APPOPS" />
@@ -559,6 +560,12 @@
     <!-- Permission required for CTS test - android.server.biometrics -->
     <uses-permission android:name="android.permission.TEST_BIOMETRIC" />
 
+    <!-- Permission required for CTS test - android.server.biometrics -->
+    <uses-permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" />
+
+    <!-- Permission required for CTS test - android.server.biometrics -->
+    <uses-permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
+
     <!-- Permissions required for CTS test - NotificationManagerTest -->
     <uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
 
@@ -748,6 +755,7 @@
     <uses-permission android:name="android.permission.BIND_TELECOM_CONNECTION_SERVICE" />
     <uses-permission android:name="android.permission.MODIFY_CELL_BROADCASTS" />
     <uses-permission android:name="android.permission.SATELLITE_COMMUNICATION" />
+    <uses-permission android:name="android.permission.ACCESS_LAST_KNOWN_CELL_ID" />
 
     <!-- Permission required for CTS test - CtsPersistentDataBlockManagerTestCases -->
     <uses-permission android:name="android.permission.ACCESS_PDB_STATE" />
@@ -891,6 +899,15 @@
     <!-- Permission required for Cts test - CtsNotificationTestCases -->
     <uses-permission android:name="android.permission.RECEIVE_SENSITIVE_NOTIFICATIONS" />
 
+    <!-- Permission required for Cts test - CtsWindowManagerJetpackTestCases -->
+    <uses-permission android:name="android.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE" />
+
+    <!-- Permission required for BinaryTransparencyService shell API and host test -->
+    <uses-permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" />
+
+    <!-- Permissions required for CTS test - CtsPermissionUiTestCases -->
+    <uses-permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES" />
+
     <application
         android:label="@string/app_label"
         android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index d3a89f4..900a2f8 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -214,6 +214,8 @@
 
     javacflags: [
         "-Adagger.fastInit=enabled",
+        "-Adagger.explicitBindingConflictsWithInject=ERROR",
+        "-Adagger.strictMultibindingValidation=enabled",
         "-Aroom.schemaLocation=frameworks/base/packages/SystemUI/schemas",
     ],
     kotlincflags: ["-Xjvm-default=all"],
@@ -330,6 +332,7 @@
         "androidx.core_core-animation-testing-nodeps",
         "androidx.compose.ui_ui",
         "flag-junit",
+        "ravenwood-junit",
         "platform-test-annotations",
         "notification_flags_lib",
     ],
@@ -361,6 +364,7 @@
         "androidx.test.ext.junit",
         "androidx.test.ext.truth",
         "kotlin-test",
+        "SystemUICustomizationTestUtils",
     ],
     libs: [
         "android.test.runner",
@@ -385,7 +389,8 @@
 
 android_app {
     name: "SystemUIRobo-stub",
-    use_resource_processor: true,
+    // SystemUiRavenTests references the .aapt.srcjar
+    use_resource_processor: false,
     defaults: [
         "platform_app_defaults",
         "SystemUI_optimized_defaults",
@@ -438,6 +443,8 @@
         "androidx.core_core-animation-testing",
         "androidx.test.ext.junit",
         "inline-mockito-robolectric-prebuilt",
+        "platform-parametric-runner-lib",
+        "SystemUICustomizationTestUtils",
     ],
     libs: [
         "android.test.runner",
@@ -455,6 +462,34 @@
     ],
 }
 
+android_ravenwood_test {
+    name: "SystemUiRavenTests",
+    srcs: [
+        ":SystemUI-tests-utils",
+        ":SystemUI-tests-multivalent",
+        // TODO(b/294256649): pivot to using {.aapt.jar} and re-enable
+        // use_resource_processor: true when better supported by soong
+        ":SystemUIRobo-stub{.aapt.srcjar}",
+    ],
+    static_libs: [
+        "SystemUI-core",
+        "SystemUI-res",
+        "SystemUI-tests-base",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.core_core-animation-testing",
+        "androidx.test.ext.junit",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+    ],
+    auto_gen_config: true,
+    plugins: [
+        "dagger2-compiler",
+    ],
+}
+
 // Opt-out config for optimizing the SystemUI target using R8.
 // Disabled via `export SYSTEMUI_OPTIMIZE_JAVA=false`, or explicitly in Make via
 // `SYSTEMUI_OPTIMIZE_JAVA := false`.
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 7443e4c..54ab5d1 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -355,6 +355,8 @@
 
     <uses-permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT" />
 
+    <uses-permission android:name="android.permission.MONITOR_STICKY_MODIFIER_STATE" />
+
     <!-- Listen to (dis-)connection of external displays and enable / disable them. -->
     <uses-permission android:name="android.permission.MANAGE_DISPLAYS" />
 
@@ -897,6 +899,14 @@
                   android:exported="true"
                   />
 
+        <activity
+            android:name=".volume.panel.ui.activity.VolumePanelActivity"
+            android:label="@string/sound_settings"
+            android:excludeFromRecents="true"
+            android:exported="false"
+            android:launchMode="singleInstance"
+            android:theme="@style/Theme.VolumePanelActivity" />
+
         <activity android:name=".wallet.ui.WalletActivity"
                   android:label="@string/wallet_title"
                   android:theme="@style/Wallet.Theme"
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
index 6c75b434..0df9bac 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
@@ -37,6 +37,7 @@
         "androidx.preference_preference",
         "androidx.viewpager_viewpager",
         "SettingsLibDisplayUtils",
+        "SettingsLibSettingsTheme",
         "com_android_a11y_menu_flags_lib",
     ],
 
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
index ca84265..648cc3b 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -40,7 +40,7 @@
             android:exported="true"
             android:label="@string/accessibility_menu_settings_name"
             android:launchMode="singleTop"
-            android:theme="@style/AccessibilityMenuSettings">
+            android:theme="@style/Theme.SettingsBase">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
 
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
index eadcd7c..f5db6a4 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
@@ -8,10 +8,3 @@
     description: "Hides the AccessibilityMenuService UI before taking action instead of after."
     bug: "292020123"
 }
-
-flag {
-    name: "a11y_menu_settings_back_button_fix_and_large_button_sizing"
-    namespace: "accessibility"
-    description: "Provides/restores back button functionality for the a11yMenu settings page. Also, fixes sizing problems with large shortcut buttons."
-    bug: "298467628"
-}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml
index 1f57654..81b3152 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml
@@ -16,10 +16,6 @@
 -->
 
 <resources>
-  <style name="AccessibilityMenuSettings" parent="android:Theme.DeviceDefault.DayNight">
-    <item name="android:windowLightStatusBar">false</item>
-  </style>
-
   <!--Adds the theme to support SnackBar component and user configurable theme. -->
   <style name="ServiceTheme" parent="android:Theme.DeviceDefault.DayNight">
     <item name="android:colorControlNormal">@color/colorControlNormal</item>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
index a2508cd..4169155 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
@@ -16,11 +16,6 @@
 -->
 
 <resources>
-  <!--The theme is for preference CollapsingToolbarBaseActivity settings-->
-  <style name="AccessibilityMenuSettings" parent="android:Theme.DeviceDefault.DayNight">
-    <item name="android:windowLightStatusBar">true</item>
-  </style>
-
   <!--Adds the theme to support SnackBar component and user configurable theme. -->
   <style name="ServiceTheme" parent="android:Theme.DeviceDefault.Light">
     <item name="android:colorControlNormal">@color/colorControlNormal</item>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
index bf51e23..ab8f97a 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
@@ -35,7 +35,6 @@
 import androidx.preference.PreferenceFragmentCompat;
 import androidx.preference.PreferenceManager;
 
-import com.android.systemui.accessibility.accessibilitymenu.Flags;
 import com.android.systemui.accessibility.accessibilitymenu.R;
 
 /**
@@ -56,28 +55,17 @@
 
         ActionBar actionBar = getActionBar();
         actionBar.setDisplayShowCustomEnabled(true);
-
-        if (Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
-            actionBar.setDisplayHomeAsUpEnabled(true);
-        }
+        actionBar.setDisplayHomeAsUpEnabled(true);
         actionBar.setCustomView(R.layout.preferences_action_bar);
         ((TextView) findViewById(R.id.action_bar_title)).setText(
                 getResources().getString(R.string.accessibility_menu_settings_name)
         );
-        actionBar.setDisplayOptions(
-                ActionBar.DISPLAY_TITLE_MULTIPLE_LINES
-                        | ActionBar.DISPLAY_SHOW_TITLE
-                        | ActionBar.DISPLAY_HOME_AS_UP);
     }
 
     @Override
     public boolean onNavigateUp() {
-        if (Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
-            mCallback.onBackInvoked();
-            return true;
-        } else {
-            return false;
-        }
+        mCallback.onBackInvoked();
+        return true;
     }
 
     /**
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
index c4f372c..c2cf6e1 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
@@ -28,7 +28,6 @@
 import android.widget.TextView;
 
 import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService;
-import com.android.systemui.accessibility.accessibilitymenu.Flags;
 import com.android.systemui.accessibility.accessibilitymenu.R;
 import com.android.systemui.accessibility.accessibilitymenu.activity.A11yMenuSettingsActivity.A11yMenuPreferenceFragment;
 import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut;
@@ -81,10 +80,8 @@
         if (convertView == null) {
             convertView = mInflater.inflate(R.layout.grid_item, parent, false);
 
-            if (Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
-                configureShortcutSize(convertView,
-                        A11yMenuPreferenceFragment.isLargeButtonsEnabled(mService));
-            }
+            configureShortcutSize(convertView,
+                    A11yMenuPreferenceFragment.isLargeButtonsEnabled(mService));
         }
 
         A11yMenuShortcut shortcutItem = (A11yMenuShortcut) getItem(position);
@@ -147,15 +144,6 @@
         ImageButton shortcutIconButton = convertView.findViewById(R.id.shortcutIconBtn);
         TextView shortcutLabel = convertView.findViewById(R.id.shortcutLabel);
 
-        if (!Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
-            if (A11yMenuPreferenceFragment.isLargeButtonsEnabled(mService)) {
-                ViewGroup.LayoutParams params = shortcutIconButton.getLayoutParams();
-                params.width = (int) (params.width * LARGE_BUTTON_SCALE);
-                params.height = (int) (params.height * LARGE_BUTTON_SCALE);
-                shortcutLabel.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, mLargeTextSize);
-            }
-        }
-
         if (shortcutItem.getId() == A11yMenuShortcut.ShortcutId.UNSPECIFIED_ID_VALUE.ordinal()) {
             // Sets empty shortcut icon and label when the shortcut is ADD_ITEM.
             shortcutIconButton.setImageResource(android.R.color.transparent);
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 21263a9..866aa89 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -10,6 +10,20 @@
 }
 
 flag {
+    name: "floating_menu_drag_to_hide"
+    namespace: "accessibility"
+    description: "Allows users to hide the FAB then use notification to dismiss or bring it back."
+    bug: "298718415"
+}
+
+flag {
+    name: "floating_menu_drag_to_edit"
+    namespace: "accessibility"
+    description: "adds a second drag button to allow the user edit the shortcut."
+    bug: "297583708"
+}
+
+flag {
     name: "floating_menu_ime_displacement_animation"
     namespace: "accessibility"
     description: "Adds an animation for when the FAB is displaced by an IME becoming visible."
@@ -28,4 +42,11 @@
     namespace: "accessibility"
     description: "Animates the floating menu's transition between curved and jagged edges."
     bug: "281140482"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "create_windowless_window_magnifier"
+    namespace: "accessibility"
+    description: "Uses SurfaceControlViewHost to create the magnifier for window magnification."
+    bug: "280992417"
+}
diff --git a/packages/SystemUI/aconfig/biometrics_framework.aconfig b/packages/SystemUI/aconfig/biometrics_framework.aconfig
index 5fd3b48..7cc0c83 100644
--- a/packages/SystemUI/aconfig/biometrics_framework.aconfig
+++ b/packages/SystemUI/aconfig/biometrics_framework.aconfig
@@ -7,4 +7,11 @@
     namespace: "biometrics_framework"
     description: "Adds talkback directional guidance when using UDFPS with biometric prompt"
     bug: "310044658"
+}
+
+flag {
+    name: "constraint_bp"
+    namespace: "biometrics_framework"
+    description: "Refactors Biometric Prompt to use a ConstraintLayout"
+    bug: "288175072"
 }
\ No newline at end of file
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 5b21854..375fe13 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -15,6 +15,13 @@
 }
 
 flag {
+    name: "notification_async_group_header_inflation"
+    namespace: "systemui"
+    description: "Inflates the notification group summary header views from the background thread."
+    bug: "217799515"
+}
+
+flag {
     name: "notification_async_hybrid_view_inflation"
     namespace: "systemui"
     description: "Inflates hybrid (single-line) notification views from the background thread."
@@ -82,6 +89,13 @@
 }
 
 flag {
+    name: "notification_avalanche_suppression"
+    namespace: "systemui"
+    description: "After notification avalanche floodgate event, suppress HUNs completely."
+    bug: "321089634"
+}
+
+flag {
     name: "notification_background_tint_optimization"
     namespace: "systemui"
     description: "Re-enable the codepath that removed tinting of notifications when the"
@@ -208,6 +222,13 @@
 }
 
 flag {
+    name: "compose_bouncer"
+    namespace: "systemui"
+    description: "Use the new compose bouncer in SystemUI"
+    bug: "310005730"
+}
+
+flag {
    name: "media_in_scene_container"
    namespace: "systemui"
    description: "Enable media in the scene container framework"
@@ -258,6 +279,15 @@
 }
 
 flag {
+   name: "centralized_status_bar_dimens_refactor"
+   namespace: "systemui"
+   description: "Refactors shade header and keyguard status bar to read status bar dimens from a"
+        " central place, instead of reading resources directly. This is to take into account display"
+        " cutouts and other special cases. "
+   bug: "317199366"
+}
+
+flag {
   name: "enable_layout_tracing"
   namespace: "systemui"
   description: "Enables detailed traversal slices during measure and layout in perfetto traces"
@@ -327,3 +357,24 @@
    description: "Relocate Smartspace to bottom of the Lock Screen"
    bug: "316212788"
 }
+
+flag {
+   name: "pin_input_field_styled_focus_state"
+   namespace: "systemui"
+   description: "Enables styled focus states on pin input field if keyboard is connected"
+   bug: "316106516"
+}
+
+flag {
+    name: "keyguard_wm_state_refactor"
+    namespace: "systemui"
+    description: "Enables refactored logic for SysUI+WM unlock/occlusion code paths"
+    bug: "278086361"
+}
+
+flag {
+   name: "enable_keyguard_compose"
+   namespace: "systemui"
+   description: "Enables the compose version of keyguard."
+   bug: "301968149"
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 9c46ebdc..8194055 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -644,7 +644,7 @@
             var candidate: RemoteAnimationTarget? = null
             for (it in apps) {
                 if (it.mode == RemoteAnimationTarget.MODE_OPENING) {
-                    if (it.taskInfo != null && !it.hasAnimatingParent) {
+                    if (!it.hasAnimatingParent) {
                         return it
                     }
                     if (candidate == null) {
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 bb2fbf7..a18b460 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
@@ -17,6 +17,7 @@
 
 package com.android.compose
 
+import androidx.annotation.DrawableRes
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.RowScope
@@ -24,8 +25,13 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.ButtonColors
 import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.IconButtonColors
+import androidx.compose.material3.IconButtonDefaults
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import com.android.compose.theme.LocalAndroidColorScheme
@@ -89,6 +95,29 @@
     )
 }
 
+@Composable
+fun PlatformIconButton(
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    colors: IconButtonColors = iconButtonColors(),
+    @DrawableRes iconResource: Int,
+    contentDescription: String?,
+) {
+    IconButton(
+        modifier = modifier,
+        onClick = onClick,
+        enabled = enabled,
+        colors = colors,
+    ) {
+        Icon(
+            painter = painterResource(id = iconResource),
+            contentDescription = contentDescription,
+            tint = colors.contentColor,
+        )
+    }
+}
+
 private val DefaultPlatformButtonVerticalPadding = 6.dp
 private val ButtonPaddings = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
 
@@ -109,6 +138,13 @@
 }
 
 @Composable
+private fun iconButtonColors(): IconButtonColors {
+    return IconButtonDefaults.filledIconButtonColors(
+        contentColor = LocalAndroidColorScheme.current.onSurface,
+    )
+}
+
+@Composable
 private fun outlineButtonBorder(): BorderStroke {
     return BorderStroke(
         width = 1.dp,
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index fd04b5ee0..3c32594 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -17,17 +17,24 @@
 
 package com.android.systemui.compose
 
+import android.app.Dialog
 import android.content.Context
 import android.view.View
 import android.view.WindowInsets
 import androidx.activity.ComponentActivity
 import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
+import com.android.systemui.communal.widgets.WidgetConfigurator
+import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.scene.shared.model.Scene
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.StateFlow
 
@@ -50,12 +57,21 @@
     override fun setCommunalEditWidgetActivityContent(
         activity: ComponentActivity,
         viewModel: BaseCommunalViewModel,
+        widgetConfigurator: WidgetConfigurator,
         onOpenWidgetPicker: () -> Unit,
         onEditDone: () -> Unit,
     ) {
         throwComposeUnavailableError()
     }
 
+    override fun setVolumePanelActivityContent(
+        activity: ComponentActivity,
+        viewModel: VolumePanelViewModel,
+        onDismissAnimationFinished: () -> Unit,
+    ) {
+        throwComposeUnavailableError()
+    }
+
     override fun createFooterActionsView(
         context: Context,
         viewModel: FooterActionsViewModel,
@@ -74,6 +90,13 @@
         throwComposeUnavailableError()
     }
 
+    override fun createStickyKeysDialog(
+        dialogFactory: SystemUIDialogFactory,
+        viewModel: StickyKeysIndicatorViewModel
+    ): Dialog {
+        throwComposeUnavailableError()
+    }
+
     override fun createCommunalView(
         context: Context,
         viewModel: BaseCommunalViewModel,
@@ -85,6 +108,12 @@
         throwComposeUnavailableError()
     }
 
+    override fun createBouncer(
+        context: Context,
+        viewModel: BouncerViewModel,
+        dialogFactory: BouncerDialogFactory,
+    ): View = throwComposeUnavailableError()
+
     private fun throwComposeUnavailableError(): Nothing {
         error(
             "Compose is not available. Make sure to check isComposeAvailable() before calling any" +
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt
new file mode 100644
index 0000000..c8dae76
--- /dev/null
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.bottombar
+
+import dagger.Module
+
+@Module interface BottomBarModule
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index d31547b..afb860e 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.compose
 
+import android.app.Dialog
 import android.content.Context
 import android.graphics.Point
 import android.view.View
@@ -28,12 +29,18 @@
 import androidx.lifecycle.LifecycleOwner
 import com.android.compose.theme.PlatformTheme
 import com.android.compose.ui.platform.DensityAwareComposeView
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
+import com.android.systemui.bouncer.ui.composable.BouncerContent
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
 import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
 import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
 import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
 import com.android.systemui.communal.ui.compose.CommunalContainer
 import com.android.systemui.communal.ui.compose.CommunalHub
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
+import com.android.systemui.communal.widgets.WidgetConfigurator
+import com.android.systemui.keyboard.stickykeys.ui.view.StickyKeysIndicator
+import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
 import com.android.systemui.people.ui.compose.PeopleScreen
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.compose.FooterActions
@@ -43,6 +50,10 @@
 import com.android.systemui.scene.ui.composable.ComposableScene
 import com.android.systemui.scene.ui.composable.SceneContainer
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.statusbar.phone.create
+import com.android.systemui.volume.panel.ui.composable.VolumePanelRoot
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -66,6 +77,7 @@
     override fun setCommunalEditWidgetActivityContent(
         activity: ComponentActivity,
         viewModel: BaseCommunalViewModel,
+        widgetConfigurator: WidgetConfigurator,
         onOpenWidgetPicker: () -> Unit,
         onEditDone: () -> Unit,
     ) {
@@ -74,12 +86,26 @@
                 CommunalHub(
                     viewModel = viewModel,
                     onOpenWidgetPicker = onOpenWidgetPicker,
+                    widgetConfigurator = widgetConfigurator,
                     onEditDone = onEditDone,
                 )
             }
         }
     }
 
+    override fun setVolumePanelActivityContent(
+        activity: ComponentActivity,
+        viewModel: VolumePanelViewModel,
+        onDismissAnimationFinished: () -> Unit,
+    ) {
+        activity.setContent {
+            VolumePanelRoot(
+                viewModel = viewModel,
+                onDismissAnimationFinished = onDismissAnimationFinished,
+            )
+        }
+    }
+
     override fun createFooterActionsView(
         context: Context,
         viewModel: FooterActionsViewModel,
@@ -114,6 +140,13 @@
         }
     }
 
+    override fun createStickyKeysDialog(
+        dialogFactory: SystemUIDialogFactory,
+        viewModel: StickyKeysIndicatorViewModel
+    ): Dialog {
+        return dialogFactory.create { StickyKeysIndicator(viewModel) }
+    }
+
     override fun createCommunalView(
         context: Context,
         viewModel: BaseCommunalViewModel,
@@ -171,4 +204,14 @@
     private fun Int.toDp(context: Context): Dp {
         return (this.toFloat() / context.resources.displayMetrics.density).dp
     }
+
+    override fun createBouncer(
+        context: Context,
+        viewModel: BouncerViewModel,
+        dialogFactory: BouncerDialogFactory,
+    ): View {
+        return ComposeView(context).apply {
+            setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
+        }
+    }
 }
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt
index 1860c9f..2b1268e 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt
@@ -16,34 +16,14 @@
 
 package com.android.systemui.scene
 
-import android.app.AlertDialog
-import android.content.Context
-import com.android.systemui.bouncer.ui.composable.BouncerDialogFactory
 import com.android.systemui.bouncer.ui.composable.BouncerScene
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.shared.model.Scene
-import com.android.systemui.statusbar.phone.SystemUIDialog
 import dagger.Binds
 import dagger.Module
-import dagger.Provides
 import dagger.multibindings.IntoSet
 
 @Module
 interface BouncerSceneModule {
 
     @Binds @IntoSet fun bouncerScene(scene: BouncerScene): Scene
-
-    companion object {
-
-        @Provides
-        @SysUISingleton
-        fun bouncerSceneDialogFactory(@Application context: Context): BouncerDialogFactory {
-            return object : BouncerDialogFactory {
-                override fun invoke(): AlertDialog {
-                    return SystemUIDialog(context)
-                }
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index 6591543..d949396 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -81,6 +81,7 @@
 import com.android.compose.modifiers.thenIf
 import com.android.compose.windowsizeclass.LocalWindowSizeClass
 import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
 import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
 import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
@@ -824,10 +825,6 @@
     }
 }
 
-interface BouncerDialogFactory {
-    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.
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 d638ffe..428bc39 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,6 +24,7 @@
 import androidx.compose.ui.Modifier
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.scene.shared.model.Direction
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index 0960811..f9b91cf 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalComposeUiApi::class)
+
 package com.android.systemui.bouncer.ui.composable
 
-import android.view.ViewTreeObserver
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.material3.LocalTextStyle
@@ -25,25 +26,25 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.State
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.produceState
 import androidx.compose.runtime.remember
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.input.key.key
+import androidx.compose.ui.input.key.onInterceptKeyBeforeSoftKeyboard
 import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.text.input.PasswordVisualTransformation
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
-import androidx.core.view.WindowInsetsCompat
 import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
 
 /** UI for the input part of a password-requiring version of the bouncer. */
@@ -64,9 +65,6 @@
     val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
     val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
 
-    val isImeVisible by isSoftwareKeyboardVisible()
-    LaunchedEffect(isImeVisible) { viewModel.onImeVisibilityChanged(isImeVisible) }
-
     DisposableEffect(Unit) {
         viewModel.onShown()
         onDispose { viewModel.onHidden() }
@@ -109,27 +107,14 @@
                         end = Offset(size.width, y = size.height - lineWidthPx),
                         strokeWidth = lineWidthPx,
                     )
+                }
+                .onInterceptKeyBeforeSoftKeyboard { keyEvent ->
+                    if (keyEvent.key == Key.Back) {
+                        viewModel.onImeDismissed()
+                        true
+                    } else {
+                        false
+                    }
                 },
     )
 }
-
-/** Returns a [State] with `true` when the IME/keyboard is visible and `false` when it's not. */
-@Composable
-fun isSoftwareKeyboardVisible(): State<Boolean> {
-    val view = LocalView.current
-    val viewTreeObserver = view.viewTreeObserver
-
-    return produceState(
-        initialValue = false,
-        key1 = viewTreeObserver,
-    ) {
-        val listener =
-            ViewTreeObserver.OnGlobalLayoutListener {
-                value = view.rootWindowInsets?.isVisible(WindowInsetsCompat.Type.ime()) ?: false
-            }
-
-        viewTreeObserver.addOnGlobalLayoutListener(listener)
-
-        awaitDispose { viewTreeObserver.removeOnGlobalLayoutListener(listener) }
-    }
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 249b3e1..ff5a698 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -1,30 +1,13 @@
 package com.android.systemui.communal.ui.compose
 
 import androidx.compose.animation.core.tween
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.offset
-import androidx.compose.foundation.layout.width
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Close
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.pointer.pointerInteropFilter
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.ElementKey
@@ -33,15 +16,14 @@
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.SceneTransitionLayout
-import com.android.compose.animation.scene.SceneTransitionLayoutState
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.observableTransitionState
 import com.android.compose.animation.scene.transitions
+import com.android.compose.animation.scene.updateSceneTransitionLayoutState
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.transform
 
@@ -66,7 +48,6 @@
  * This is a temporary container to allow the communal UI to use [SceneTransitionLayout] for gesture
  * handling and transitions before the full Flexiglass layout is ready.
  */
-@OptIn(ExperimentalComposeUiApi::class, ExperimentalCoroutinesApi::class)
 @Composable
 fun CommunalContainer(
     modifier: Modifier = Modifier,
@@ -76,16 +57,12 @@
         viewModel.currentScene
             .transform { value -> emit(value.toTransitionSceneKey()) }
             .collectAsState(TransitionSceneKey.Blank)
-    val sceneTransitionLayoutState = remember { SceneTransitionLayoutState(currentScene) }
-    // Don't show hub mode UI if keyguard is present. This is important since we're in the shade,
-    // which can be opened from many locations.
-    val isKeyguardShowing by viewModel.isKeyguardVisible.collectAsState(initial = false)
-
-    // Failsafe to hide the whole SceneTransitionLayout in case of bugginess.
-    var showSceneTransitionLayout by remember { mutableStateOf(true) }
-    if (!showSceneTransitionLayout || !isKeyguardShowing) {
-        return
-    }
+    val sceneTransitionLayoutState =
+        updateSceneTransitionLayoutState(
+            currentScene,
+            onChangeScene = { viewModel.onSceneChanged(it.toCommunalSceneKey()) },
+            transitions = sceneTransitions,
+        )
 
     // This effect exposes the SceneTransitionLayout's observable transition state to the rest of
     // the system, and unsets it when the view is disposed to avoid a memory leak.
@@ -96,83 +73,31 @@
         onDispose { viewModel.setTransitionState(null) }
     }
 
-    Box(modifier = modifier.fillMaxSize()) {
-        SceneTransitionLayout(
-            modifier = Modifier.fillMaxSize(),
-            currentScene = currentScene,
-            onChangeScene = { sceneKey -> viewModel.onSceneChanged(sceneKey.toCommunalSceneKey()) },
-            transitions = sceneTransitions,
-            state = sceneTransitionLayoutState,
-            edgeDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize)
+    SceneTransitionLayout(
+        state = sceneTransitionLayoutState,
+        modifier = modifier.fillMaxSize(),
+        swipeSourceDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize),
+    ) {
+        scene(
+            TransitionSceneKey.Blank,
+            userActions =
+                mapOf(
+                    Swipe(SwipeDirection.Left, fromSource = Edge.Right) to
+                        TransitionSceneKey.Communal
+                )
         ) {
-            scene(
-                TransitionSceneKey.Blank,
-                userActions =
-                    mapOf(
-                        Swipe(SwipeDirection.Left, fromEdge = Edge.Right) to
-                            TransitionSceneKey.Communal
-                    )
-            ) {
-                BlankScene { showSceneTransitionLayout = false }
-            }
-
-            scene(
-                TransitionSceneKey.Communal,
-                userActions =
-                    mapOf(
-                        Swipe(SwipeDirection.Right, fromEdge = Edge.Left) to
-                            TransitionSceneKey.Blank
-                    ),
-            ) {
-                CommunalScene(viewModel, modifier = modifier)
-            }
+            // This scene shows nothing only allowing for transitions to the communal scene.
+            Box(modifier = Modifier.fillMaxSize())
         }
 
-        // TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't
-        //  block touches anymore.
-        Box(
-            modifier =
-                Modifier.fillMaxSize()
-                    // Offsetting to the left so that edge swipe to open the hub still works. This
-                    // does mean that the very right edge of the hub won't refresh the screen
-                    // timeout, but should be good enough for a temporary solution.
-                    .offset(x = -ContainerDimensions.EdgeSwipeSize)
-                    .pointerInteropFilter {
-                        viewModel.onUserActivity()
-                        if (
-                            sceneTransitionLayoutState.transitionState.currentScene ==
-                                TransitionSceneKey.Blank
-                        ) {
-                            viewModel.onOuterTouch(it)
-                            return@pointerInteropFilter true
-                        }
-                        false
-                    }
-        )
-    }
-}
-
-/**
- * Blank scene that shows over keyguard/dream. This scene will eventually show nothing at all and is
- * only used to allow for transitions to the communal scene.
- */
-@Composable
-private fun BlankScene(
-    modifier: Modifier = Modifier,
-    hideSceneTransitionLayout: () -> Unit,
-) {
-    Box(modifier.fillMaxSize()) {
-        Column(
-            Modifier.fillMaxHeight()
-                .width(ContainerDimensions.EdgeSwipeSize)
-                .align(Alignment.CenterEnd)
-                .background(Color(0x55e9f2eb)),
-            verticalArrangement = Arrangement.Center,
-            horizontalAlignment = Alignment.CenterHorizontally
+        scene(
+            TransitionSceneKey.Communal,
+            userActions =
+                mapOf(
+                    Swipe(SwipeDirection.Right, fromSource = Edge.Left) to TransitionSceneKey.Blank
+                ),
         ) {
-            IconButton(onClick = hideSceneTransitionLayout) {
-                Icon(Icons.Filled.Close, contentDescription = "Close button")
-            }
+            CommunalScene(viewModel, modifier = modifier)
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index d201544..576596f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -20,6 +20,10 @@
 import android.os.Bundle
 import android.util.SizeF
 import android.widget.FrameLayout
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.background
@@ -38,36 +42,44 @@
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.lazy.grid.GridCells
 import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.foundation.lazy.grid.LazyGridState
 import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
 import androidx.compose.foundation.lazy.grid.rememberLazyGridState
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.icons.filled.Edit
 import androidx.compose.material.icons.outlined.Delete
+import androidx.compose.material.icons.outlined.Edit
+import androidx.compose.material.icons.outlined.TouchApp
 import androidx.compose.material.icons.outlined.Widgets
 import androidx.compose.material3.Button
 import androidx.compose.material3.ButtonColors
 import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.Card
 import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.FilledIconButton
 import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
+import androidx.compose.material3.IconButtonColors
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
 import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.boundsInWindow
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.layout.positionInWindow
@@ -76,47 +88,111 @@
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
+import androidx.compose.ui.window.Popup
+import androidx.core.view.setPadding
+import com.android.compose.modifiers.thenIf
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth
+import com.android.systemui.communal.ui.compose.extensions.allowGestures
+import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture
+import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
+import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
-import com.android.systemui.media.controls.ui.MediaHostState
+import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.res.R
+import kotlinx.coroutines.launch
 
 @Composable
 fun CommunalHub(
     modifier: Modifier = Modifier,
     viewModel: BaseCommunalViewModel,
+    widgetConfigurator: WidgetConfigurator? = null,
     onOpenWidgetPicker: (() -> Unit)? = null,
     onEditDone: (() -> Unit)? = null,
 ) {
     val communalContent by viewModel.communalContent.collectAsState(initial = emptyList())
+    val isPopupOnDismissCtaShowing by
+        viewModel.isPopupOnDismissCtaShowing.collectAsState(initial = false)
     var removeButtonCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
     var toolbarSize: IntSize? by remember { mutableStateOf(null) }
     var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
     var isDraggingToRemove by remember { mutableStateOf(false) }
+    val gridState = rememberLazyGridState()
+    val contentListState = rememberContentListState(widgetConfigurator, communalContent, viewModel)
+    val reorderingWidgets by viewModel.reorderingWidgets.collectAsState()
+    val selectedKey = viewModel.selectedKey.collectAsState()
+    val removeButtonEnabled by remember {
+        derivedStateOf { selectedKey.value != null || reorderingWidgets }
+    }
+    var isButtonToEditWidgetsShowing by remember { mutableStateOf(false) }
+
+    val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
+    val contentOffset = beforeContentPadding(contentPadding).toOffset()
 
     Box(
         modifier =
-            modifier.fillMaxSize().background(LocalAndroidColorScheme.current.outlineVariant),
+            modifier
+                .fillMaxSize()
+                .background(LocalAndroidColorScheme.current.outlineVariant)
+                .pointerInput(gridState, contentOffset, contentListState) {
+                    // If not in edit mode, don't allow selecting items.
+                    if (!viewModel.isEditMode) return@pointerInput
+                    observeTapsWithoutConsuming { offset ->
+                        val adjustedOffset = offset - contentOffset
+                        val index = firstIndexAtOffset(gridState, adjustedOffset)
+                        val key = index?.let { keyAtIndexIfEditable(contentListState.list, index) }
+                        viewModel.setSelectedKey(key)
+                    }
+                }
+                .thenIf(!viewModel.isEditMode) {
+                    Modifier.pointerInput(
+                        gridState,
+                        contentOffset,
+                        communalContent,
+                        gridCoordinates
+                    ) {
+                        detectLongPressGesture { offset ->
+                            isButtonToEditWidgetsShowing = true
+
+                            // Deduct both grid offset relative to its container and content offset.
+                            val adjustedOffset =
+                                gridCoordinates?.let {
+                                    offset - it.positionInWindow() - contentOffset
+                                }
+                            val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
+                            val key = index?.let { keyAtIndexIfEditable(communalContent, index) }
+                            viewModel.setSelectedKey(key)
+                        }
+                    }
+                },
     ) {
         CommunalHubLazyGrid(
             communalContent = communalContent,
             viewModel = viewModel,
-            contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize),
+            contentPadding = contentPadding,
+            contentOffset = contentOffset,
             setGridCoordinates = { gridCoordinates = it },
-            updateDragPositionForRemove = {
+            updateDragPositionForRemove = { offset ->
                 isDraggingToRemove =
-                    checkForDraggingToRemove(it, removeButtonCoordinates, gridCoordinates)
+                    isPointerWithinCoordinates(
+                        offset = gridCoordinates?.let { it.positionInWindow() + offset },
+                        containerToCheck = removeButtonCoordinates
+                    )
                 isDraggingToRemove
             },
             onOpenWidgetPicker = onOpenWidgetPicker,
+            gridState = gridState,
+            contentListState = contentListState,
+            selectedKey = selectedKey,
+            widgetConfigurator = widgetConfigurator,
         )
 
         if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
@@ -126,11 +202,33 @@
                 setRemoveButtonCoordinates = { removeButtonCoordinates = it },
                 onEditDone = onEditDone,
                 onOpenWidgetPicker = onOpenWidgetPicker,
+                onRemoveClicked = {
+                    val index =
+                        selectedKey.value?.let { key ->
+                            contentListState.list.indexOfFirst { it.key == key }
+                        }
+                    index?.let {
+                        contentListState.onRemove(it)
+                        contentListState.onSaveList()
+                        viewModel.setSelectedKey(null)
+                    }
+                },
+                removeEnabled = removeButtonEnabled
             )
-        } else {
-            IconButton(onClick = viewModel::onOpenWidgetEditor) {
-                Icon(Icons.Default.Edit, stringResource(R.string.button_to_open_widget_editor))
-            }
+        }
+
+        if (isPopupOnDismissCtaShowing) {
+            PopupOnDismissCtaTile(viewModel::onHidePopupAfterDismissCta)
+        }
+
+        if (isButtonToEditWidgetsShowing) {
+            ButtonToEditWidgets(
+                onClick = {
+                    isButtonToEditWidgetsShowing = false
+                    viewModel.onOpenWidgetEditor(selectedKey.value)
+                },
+                onHide = { isButtonToEditWidgetsShowing = false },
+            )
         }
 
         // This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving
@@ -151,16 +249,20 @@
     communalContent: List<CommunalContentModel>,
     viewModel: BaseCommunalViewModel,
     contentPadding: PaddingValues,
+    selectedKey: State<String?>,
+    contentOffset: Offset,
+    gridState: LazyGridState,
+    contentListState: ContentListState,
     setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit,
     updateDragPositionForRemove: (offset: Offset) -> Boolean,
     onOpenWidgetPicker: (() -> Unit)? = null,
+    widgetConfigurator: WidgetConfigurator?,
 ) {
-    var gridModifier = Modifier.align(Alignment.CenterStart)
-    val gridState = rememberLazyGridState()
+    var gridModifier =
+        Modifier.align(Alignment.CenterStart).onGloballyPositioned { setGridCoordinates(it) }
     var list = communalContent
     var dragDropState: GridDragDropState? = null
     if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
-        val contentListState = rememberContentListState(list, viewModel)
         list = contentListState.list
         // for drag & drop operations within the communal hub grid
         dragDropState =
@@ -170,10 +272,7 @@
                 updateDragPositionForRemove = updateDragPositionForRemove
             )
         gridModifier =
-            gridModifier
-                .fillMaxSize()
-                .dragContainer(dragDropState, beforeContentPadding(contentPadding))
-                .onGloballyPositioned { setGridCoordinates(it) }
+            gridModifier.fillMaxSize().dragContainer(dragDropState, contentOffset, viewModel)
         // for widgets dropped from other activities
         val dragAndDropTargetState =
             rememberDragAndDropTargetState(
@@ -211,26 +310,32 @@
                     list[index].size.dp().value,
                 )
             if (viewModel.isEditMode && dragDropState != null) {
+                val selected by
+                    remember(index) { derivedStateOf { list[index].key == selectedKey.value } }
                 DraggableItem(
                     dragDropState = dragDropState,
+                    selected = selected,
                     enabled = list[index] is CommunalContentModel.Widget,
                     index = index,
                     size = size
-                ) { _ ->
+                ) { isDragging ->
                     CommunalContent(
                         modifier = cardModifier,
                         model = list[index],
                         viewModel = viewModel,
                         size = size,
                         onOpenWidgetPicker = onOpenWidgetPicker,
+                        selected = selected && !isDragging,
+                        widgetConfigurator = widgetConfigurator,
                     )
                 }
             } else {
                 CommunalContent(
-                    modifier = cardModifier,
                     model = list[index],
                     viewModel = viewModel,
                     size = size,
+                    selected = false,
+                    modifier = cardModifier,
                 )
             }
         }
@@ -246,11 +351,19 @@
 @Composable
 private fun Toolbar(
     isDraggingToRemove: Boolean,
+    removeEnabled: Boolean,
+    onRemoveClicked: () -> Unit,
     setToolbarSize: (toolbarSize: IntSize) -> Unit,
     setRemoveButtonCoordinates: (coordinates: LayoutCoordinates) -> Unit,
     onOpenWidgetPicker: () -> Unit,
-    onEditDone: () -> Unit,
+    onEditDone: () -> Unit
 ) {
+    val removeButtonAlpha: Float by
+        animateFloatAsState(
+            targetValue = if (removeEnabled) 1f else 0.5f,
+            label = "RemoveButtonAlphaAnimation"
+        )
+
     Row(
         modifier =
             Modifier.fillMaxWidth()
@@ -269,7 +382,7 @@
             colors = filledButtonColors(),
             contentPadding = Dimensions.ButtonPadding
         ) {
-            Icon(Icons.Default.Add, stringResource(R.string.button_to_open_widget_editor))
+            Icon(Icons.Default.Add, stringResource(R.string.hub_mode_add_widget_button_text))
             Spacer(spacerModifier)
             Text(
                 text = stringResource(R.string.hub_mode_add_widget_button_text),
@@ -294,13 +407,18 @@
             }
         } else {
             OutlinedButton(
-                // Button is disabled to make it non-clickable
-                enabled = false,
-                onClick = {},
-                colors = ButtonDefaults.outlinedButtonColors(disabledContentColor = colors.primary),
+                enabled = removeEnabled,
+                onClick = onRemoveClicked,
+                colors =
+                    ButtonDefaults.outlinedButtonColors(
+                        contentColor = colors.primary,
+                        disabledContentColor = colors.primary
+                    ),
                 border = BorderStroke(width = 1.0.dp, color = colors.primary),
                 contentPadding = Dimensions.ButtonPadding,
-                modifier = Modifier.onGloballyPositioned { setRemoveButtonCoordinates(it) }
+                modifier =
+                    Modifier.graphicsLayer { alpha = removeButtonAlpha }
+                        .onGloballyPositioned { setRemoveButtonCoordinates(it) }
             ) {
                 RemoveButtonContent(spacerModifier)
             }
@@ -319,8 +437,68 @@
 }
 
 @Composable
+private fun ButtonToEditWidgets(
+    onClick: () -> Unit,
+    onHide: () -> Unit,
+) {
+    Popup(alignment = Alignment.TopCenter, offset = IntOffset(0, 40), onDismissRequest = onHide) {
+        val colors = LocalAndroidColorScheme.current
+        Button(
+            modifier =
+                Modifier.height(56.dp).background(colors.secondary, RoundedCornerShape(50.dp)),
+            onClick = onClick,
+        ) {
+            Icon(
+                imageVector = Icons.Outlined.Widgets,
+                contentDescription = stringResource(R.string.button_to_configure_widgets_text),
+                tint = colors.onSecondary,
+                modifier = Modifier.size(20.dp)
+            )
+            Spacer(modifier = Modifier.size(8.dp))
+            Text(
+                text = stringResource(R.string.button_to_configure_widgets_text),
+                style = MaterialTheme.typography.titleSmall,
+                color = colors.onSecondary,
+            )
+        }
+    }
+}
+
+@Composable
+private fun PopupOnDismissCtaTile(onHidePopupAfterDismissCta: () -> Unit) {
+    Popup(
+        alignment = Alignment.TopCenter,
+        offset = IntOffset(0, 40),
+        onDismissRequest = onHidePopupAfterDismissCta
+    ) {
+        val colors = LocalAndroidColorScheme.current
+        Row(
+            modifier =
+                Modifier.height(56.dp)
+                    .background(colors.secondary, RoundedCornerShape(50.dp))
+                    .padding(16.dp),
+            horizontalArrangement = Arrangement.Center,
+            verticalAlignment = Alignment.CenterVertically,
+        ) {
+            Icon(
+                imageVector = Icons.Outlined.TouchApp,
+                contentDescription = stringResource(R.string.popup_on_dismiss_cta_tile_text),
+                tint = colors.onSecondary,
+                modifier = Modifier.size(20.dp)
+            )
+            Spacer(modifier = Modifier.size(8.dp))
+            Text(
+                text = stringResource(R.string.popup_on_dismiss_cta_tile_text),
+                style = MaterialTheme.typography.titleSmall,
+                color = colors.onSecondary,
+            )
+        }
+    }
+}
+
+@Composable
 private fun RemoveButtonContent(spacerModifier: Modifier) {
-    Icon(Icons.Outlined.Delete, stringResource(R.string.button_to_open_widget_editor))
+    Icon(Icons.Outlined.Delete, stringResource(R.string.button_to_remove_widget))
     Spacer(spacerModifier)
     Text(
         text = stringResource(R.string.button_to_remove_widget),
@@ -341,12 +519,15 @@
     model: CommunalContentModel,
     viewModel: BaseCommunalViewModel,
     size: SizeF,
+    selected: Boolean,
     modifier: Modifier = Modifier,
     onOpenWidgetPicker: (() -> Unit)? = null,
+    widgetConfigurator: WidgetConfigurator? = null,
 ) {
     when (model) {
-        is CommunalContentModel.Widget -> WidgetContent(viewModel, model, size, modifier)
-        is CommunalContentModel.WidgetPlaceholder -> WidgetPlaceholderContent(size)
+        is CommunalContentModel.Widget ->
+            WidgetContent(viewModel, model, size, selected, widgetConfigurator, modifier)
+        is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(size)
         is CommunalContentModel.CtaTileInViewMode ->
             CtaTileInViewModeContent(viewModel, size, modifier)
         is CommunalContentModel.CtaTileInEditMode ->
@@ -357,13 +538,13 @@
     }
 }
 
-/** Presents a placeholder card for the new widget being dragged and dropping into the grid. */
+/** Creates an empty card used to highlight a particular spot on the grid. */
 @Composable
-fun WidgetPlaceholderContent(size: SizeF) {
+fun HighlightedItem(size: SizeF, modifier: Modifier = Modifier) {
     Card(
-        modifier = Modifier.size(Dp(size.width), Dp(size.height)),
+        modifier = modifier.size(Dp(size.width), Dp(size.height)),
         colors = CardDefaults.cardColors(containerColor = Color.Transparent),
-        border = BorderStroke(3.dp, LocalAndroidColorScheme.current.tertiaryFixed),
+        border = BorderStroke(CardOutlineWidth, LocalAndroidColorScheme.current.tertiaryFixed),
         shape = RoundedCornerShape(16.dp)
     ) {}
 }
@@ -377,7 +558,7 @@
 ) {
     val colors = LocalAndroidColorScheme.current
     Card(
-        modifier = modifier.height(size.height.dp),
+        modifier = modifier.height(size.height.dp).padding(CardOutlineWidth),
         colors =
             CardDefaults.cardColors(
                 containerColor = colors.primary,
@@ -449,7 +630,7 @@
     }
     val colors = LocalAndroidColorScheme.current
     Card(
-        modifier = modifier.height(size.height.dp),
+        modifier = modifier.height(size.height.dp).padding(CardOutlineWidth),
         colors = CardDefaults.cardColors(containerColor = Color.Transparent),
         border = BorderStroke(1.dp, colors.primary),
         shape = RoundedCornerShape(200.dp),
@@ -482,31 +663,80 @@
     viewModel: BaseCommunalViewModel,
     model: CommunalContentModel.Widget,
     size: SizeF,
+    selected: Boolean,
+    widgetConfigurator: WidgetConfigurator?,
     modifier: Modifier = Modifier,
 ) {
     Box(
         modifier = modifier.height(size.height.dp),
-        contentAlignment = Alignment.Center,
     ) {
+        val paddingInPx = with(LocalDensity.current) { CardOutlineWidth.toPx().toInt() }
         AndroidView(
-            modifier = modifier,
+            modifier =
+                modifier.align(Alignment.Center).allowGestures(allowed = !viewModel.isEditMode),
             factory = { context ->
-                // The AppWidgetHostView will inherit the interaction handler from the
-                // AppWidgetHost. So set the interaction handler here before creating the view, and
-                // then clear it after the view is created. This is a workaround due to the fact
-                // that the interaction handler cannot be specified when creating the view,
-                // and there are race conditions if it is set after the view is created.
-                model.appWidgetHost.setInteractionHandler(viewModel.getInteractionHandler())
                 val view =
                     model.appWidgetHost
-                        .createView(context, model.appWidgetId, model.providerInfo)
+                        .createViewForCommunal(context, model.appWidgetId, model.providerInfo)
                         .apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) }
-                model.appWidgetHost.setInteractionHandler(null)
+                // Remove the extra padding applied to AppWidgetHostView to allow widgets to
+                // occupy the entire box. The added padding is now adjusted to leave only sufficient
+                // space for displaying the outline around the box when the widget is selected.
+                view.setPadding(paddingInPx)
                 view
             },
             // For reusing composition in lazy lists.
             onReset = {},
         )
+        if (
+            viewModel is CommunalEditModeViewModel &&
+                model.reconfigurable &&
+                widgetConfigurator != null
+        ) {
+            WidgetConfigureButton(
+                visible = selected,
+                model = model,
+                widgetConfigurator = widgetConfigurator,
+                modifier = Modifier.align(Alignment.BottomEnd)
+            )
+        }
+    }
+}
+
+@Composable
+fun WidgetConfigureButton(
+    visible: Boolean,
+    model: CommunalContentModel.Widget,
+    modifier: Modifier = Modifier,
+    widgetConfigurator: WidgetConfigurator,
+) {
+    val colors = LocalAndroidColorScheme.current
+    val scope = rememberCoroutineScope()
+
+    AnimatedVisibility(
+        visible = visible,
+        enter = fadeIn(),
+        exit = fadeOut(),
+        modifier = modifier.padding(16.dp),
+    ) {
+        FilledIconButton(
+            shape = RoundedCornerShape(16.dp),
+            modifier = Modifier.size(48.dp),
+            colors =
+                IconButtonColors(
+                    containerColor = colors.primary,
+                    contentColor = colors.onPrimary,
+                    disabledContainerColor = Color.Transparent,
+                    disabledContentColor = Color.Transparent
+                ),
+            onClick = { scope.launch { widgetConfigurator.configureWidget(model.appWidgetId) } },
+        ) {
+            Icon(
+                imageVector = Icons.Outlined.Edit,
+                contentDescription = stringResource(id = R.string.edit_widget),
+                modifier = Modifier.padding(12.dp)
+            )
+        }
     }
 }
 
@@ -535,10 +765,6 @@
     AndroidView(
         modifier = modifier,
         factory = {
-            viewModel.mediaHost.expansion = MediaHostState.EXPANDED
-            viewModel.mediaHost.showsOnlyActiveMedia = false
-            viewModel.mediaHost.falsingProtectionNeeded = false
-            viewModel.mediaHost.init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
             viewModel.mediaHost.hostView.layoutParams =
                 FrameLayout.LayoutParams(
                     FrameLayout.LayoutParams.MATCH_PARENT,
@@ -581,8 +807,8 @@
 private fun beforeContentPadding(paddingValues: PaddingValues): ContentPaddingInPx {
     return with(LocalDensity.current) {
         ContentPaddingInPx(
-            startPadding = paddingValues.calculateLeftPadding(LayoutDirection.Ltr).toPx(),
-            topPadding = paddingValues.calculateTopPadding().toPx()
+            start = paddingValues.calculateLeftPadding(LayoutDirection.Ltr).toPx(),
+            top = paddingValues.calculateTopPadding().toPx()
         )
     }
 }
@@ -591,18 +817,15 @@
  * Check whether the pointer position that the item is being dragged at is within the coordinates of
  * the remove button in the toolbar. Returns true if the item is removable.
  */
-private fun checkForDraggingToRemove(
-    offset: Offset,
-    removeButtonCoordinates: LayoutCoordinates?,
-    gridCoordinates: LayoutCoordinates?,
+private fun isPointerWithinCoordinates(
+    offset: Offset?,
+    containerToCheck: LayoutCoordinates?
 ): Boolean {
-    if (removeButtonCoordinates == null || gridCoordinates == null) {
+    if (offset == null || containerToCheck == null) {
         return false
     }
-    val pointer = gridCoordinates.positionInWindow() + offset
-    val removeButton = removeButtonCoordinates.positionInWindow()
-    return pointer.x in removeButton.x..removeButton.x + removeButtonCoordinates.size.width &&
-        pointer.y in removeButton.y..removeButton.y + removeButtonCoordinates.size.height
+    val container = containerToCheck.boundsInWindow()
+    return container.contains(offset)
 }
 
 private fun CommunalContentSize.dp(): Dp {
@@ -613,13 +836,23 @@
     }
 }
 
-data class ContentPaddingInPx(val startPadding: Float, val topPadding: Float)
+private fun firstIndexAtOffset(gridState: LazyGridState, offset: Offset): Int? =
+    gridState.layoutInfo.visibleItemsInfo.firstItemAtOffset(offset)?.index
+
+/** Returns the key of item if it's editable at the given index. Only widget is editable. */
+private fun keyAtIndexIfEditable(list: List<CommunalContentModel>, index: Int): String? =
+    if (index in list.indices && list[index].isWidget()) list[index].key else null
+
+data class ContentPaddingInPx(val start: Float, val top: Float) {
+    fun toOffset(): Offset = Offset(start, top)
+}
 
 object Dimensions {
     val CardWidth = 464.dp
     val CardHeightFull = 630.dp
     val CardHeightHalf = 307.dp
     val CardHeightThird = 199.dp
+    val CardOutlineWidth = 3.dp
     val GridHeight = CardHeightFull
     val Spacing = 16.dp
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index 979991d..67b79a0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -21,17 +21,25 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.toMutableStateList
 import com.android.systemui.communal.domain.model.CommunalContentModel
-import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
+import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
+import com.android.systemui.communal.widgets.WidgetConfigurator
 
 @Composable
 fun rememberContentListState(
+    widgetConfigurator: WidgetConfigurator?,
     communalContent: List<CommunalContentModel>,
-    viewModel: CommunalEditModeViewModel,
+    viewModel: BaseCommunalViewModel,
 ): ContentListState {
     return remember(communalContent) {
         ContentListState(
             communalContent,
-            viewModel::onAddWidget,
+            { componentName, priority ->
+                viewModel.onAddWidget(
+                    componentName,
+                    priority,
+                    widgetConfigurator,
+                )
+            },
             viewModel::onDeleteWidget,
             viewModel::onReorderWidgets,
         )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index 1b40de4..a195953 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -17,6 +17,10 @@
 package com.android.systemui.communal.ui.compose
 
 import android.util.SizeF
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
 import androidx.compose.foundation.gestures.scrollBy
@@ -32,6 +36,7 @@
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
@@ -39,7 +44,9 @@
 import androidx.compose.ui.unit.toOffset
 import androidx.compose.ui.unit.toSize
 import androidx.compose.ui.zIndex
+import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
 import com.android.systemui.communal.ui.compose.extensions.plus
+import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.launch
@@ -108,13 +115,10 @@
 
     internal fun onDragStart(offset: Offset, contentOffset: Offset) {
         state.layoutInfo.visibleItemsInfo
-            .firstOrNull { item ->
-                // grid item offset is based off grid content container so we need to deduct
-                // before content padding from the initial pointer position
-                contentListState.isItemEditable(item.index) &&
-                    (offset.x - contentOffset.x).toInt() in item.offset.x..item.offsetEnd.x &&
-                    (offset.y - contentOffset.y).toInt() in item.offset.y..item.offsetEnd.y
-            }
+            .filter { item -> contentListState.isItemEditable(item.index) }
+            // grid item offset is based off grid content container so we need to deduct
+            // before content padding from the initial pointer position
+            .firstItemAtOffset(offset - contentOffset)
             ?.apply {
                 dragStartPointerOffset = offset - this.offset.toOffset()
                 draggingItemIndex = index
@@ -147,12 +151,11 @@
         val middleOffset = startOffset + (endOffset - startOffset) / 2f
 
         val targetItem =
-            state.layoutInfo.visibleItemsInfo.find { item ->
-                contentListState.isItemEditable(item.index) &&
-                    middleOffset.x.toInt() in item.offset.x..item.offsetEnd.x &&
-                    middleOffset.y.toInt() in item.offset.y..item.offsetEnd.y &&
-                    draggingItem.index != item.index
-            }
+            state.layoutInfo.visibleItemsInfo
+                .asSequence()
+                .filter { item -> contentListState.isItemEditable(item.index) }
+                .filter { item -> draggingItem.index != item.index }
+                .firstItemAtOffset(middleOffset)
 
         if (targetItem != null) {
             val scrollToIndex =
@@ -207,24 +210,31 @@
 
 fun Modifier.dragContainer(
     dragDropState: GridDragDropState,
-    beforeContentPadding: ContentPaddingInPx
+    contentOffset: Offset,
+    viewModel: BaseCommunalViewModel,
 ): Modifier {
-    return pointerInput(dragDropState, beforeContentPadding) {
-        detectDragGesturesAfterLongPress(
-            onDrag = { change, offset ->
-                change.consume()
-                dragDropState.onDrag(offset = offset)
-            },
-            onDragStart = { offset ->
-                dragDropState.onDragStart(
-                    offset,
-                    Offset(beforeContentPadding.startPadding, beforeContentPadding.topPadding)
-                )
-            },
-            onDragEnd = { dragDropState.onDragInterrupted() },
-            onDragCancel = { dragDropState.onDragInterrupted() }
-        )
-    }
+    return this.then(
+        pointerInput(dragDropState, contentOffset) {
+            detectDragGesturesAfterLongPress(
+                onDrag = { change, offset ->
+                    change.consume()
+                    dragDropState.onDrag(offset = offset)
+                },
+                onDragStart = { offset ->
+                    dragDropState.onDragStart(offset, contentOffset)
+                    viewModel.onReorderWidgetStart()
+                },
+                onDragEnd = {
+                    dragDropState.onDragInterrupted()
+                    viewModel.onReorderWidgetEnd()
+                },
+                onDragCancel = {
+                    dragDropState.onDragInterrupted()
+                    viewModel.onReorderWidgetCancel()
+                }
+            )
+        }
+    )
 }
 
 /** Wrap LazyGrid item with additional modifier needed for drag and drop. */
@@ -234,6 +244,7 @@
     dragDropState: GridDragDropState,
     index: Int,
     enabled: Boolean,
+    selected: Boolean,
     size: SizeF,
     modifier: Modifier = Modifier,
     content: @Composable (isDragging: Boolean) -> Unit
@@ -241,21 +252,31 @@
     if (!enabled) {
         return Box(modifier = modifier) { content(false) }
     }
+
     val dragging = index == dragDropState.draggingItemIndex
+    val itemAlpha: Float by
+        animateFloatAsState(
+            targetValue = if (dragDropState.isDraggingToRemove) 0.5f else 1f,
+            label = "DraggableItemAlpha"
+        )
     val draggingModifier =
         if (dragging) {
             Modifier.zIndex(1f).graphicsLayer {
                 translationX = dragDropState.draggingItemOffset.x
                 translationY = dragDropState.draggingItemOffset.y
-                alpha = if (dragDropState.isDraggingToRemove) 0.5f else 1f
+                alpha = itemAlpha
             }
         } else {
             Modifier.animateItemPlacement()
         }
 
     Box(modifier) {
-        if (dragging) {
-            WidgetPlaceholderContent(size)
+        AnimatedVisibility(
+            visible = (dragging || selected) && !dragDropState.isDraggingToRemove,
+            enter = fadeIn(),
+            exit = fadeOut()
+        ) {
+            HighlightedItem(size)
         }
         Box(modifier = draggingModifier, propagateMinConstraints = true) { content(dragging) }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/LazyGridStateExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/LazyGridStateExt.kt
new file mode 100644
index 0000000..132093f
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/LazyGridStateExt.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.compose.extensions
+
+import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.unit.toRect
+
+/**
+ * Determine the item at the specified offset, or null if none exist.
+ *
+ * @param offset The offset in pixels, relative to the top start of the grid.
+ */
+fun Iterable<LazyGridItemInfo>.firstItemAtOffset(offset: Offset): LazyGridItemInfo? =
+    firstOrNull { item ->
+        isItemAtOffset(item, offset)
+    }
+
+/**
+ * Determine the item at the specified offset, or null if none exist.
+ *
+ * @param offset The offset in pixels, relative to the top start of the grid.
+ */
+fun Sequence<LazyGridItemInfo>.firstItemAtOffset(offset: Offset): LazyGridItemInfo? =
+    firstOrNull { item ->
+        isItemAtOffset(item, offset)
+    }
+
+private fun isItemAtOffset(item: LazyGridItemInfo, offset: Offset): Boolean {
+    val boundingBox = IntRect(item.offset, item.size)
+    return boundingBox.toRect().contains(offset)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/ModifierExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/ModifierExt.kt
new file mode 100644
index 0000000..b31008e
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/ModifierExt.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.compose.extensions
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.pointerInput
+
+/** Sets whether gestures are allowed on children of this element. */
+fun Modifier.allowGestures(allowed: Boolean): Modifier =
+    if (allowed) {
+        this
+    } else {
+        this.then(pointerInput(Unit) { consumeAllGestures() })
+    }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
new file mode 100644
index 0000000..bc1e429
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.compose.extensions
+
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.waitForUpOrCancellation
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.AwaitPointerEventScope
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException
+import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.util.fastAny
+import androidx.compose.ui.util.fastForEach
+import kotlinx.coroutines.coroutineScope
+
+/**
+ * Observe taps without actually consuming them, so child elements can still respond to them. Long
+ * presses are excluded.
+ */
+suspend fun PointerInputScope.observeTapsWithoutConsuming(
+    pass: PointerEventPass = PointerEventPass.Initial,
+    onTap: ((Offset) -> Unit)? = null,
+) = coroutineScope {
+    if (onTap == null) return@coroutineScope
+    awaitEachGesture {
+        awaitFirstDown(pass = pass)
+        val tapTimeout = viewConfiguration.longPressTimeoutMillis
+        val up = withTimeoutOrNull(tapTimeout) { waitForUpOrCancellation(pass = pass) }
+        if (up != null) {
+            onTap(up.position)
+        }
+    }
+}
+
+/**
+ * Detect long press gesture and calls onLongPress when detected. The callback parameter receives an
+ * Offset representing the position relative to the containing element.
+ */
+suspend fun PointerInputScope.detectLongPressGesture(
+    pass: PointerEventPass = PointerEventPass.Initial,
+    onLongPress: ((Offset) -> Unit),
+) = coroutineScope {
+    awaitEachGesture {
+        val down = awaitFirstDown(pass = pass)
+        val longPressTimeout = viewConfiguration.longPressTimeoutMillis
+        // wait for first tap up or long press
+        try {
+            withTimeout(longPressTimeout) { waitForUpOrCancellation(pass = pass) }
+        } catch (_: PointerEventTimeoutCancellationException) {
+            // withTimeout throws exception if timeout has passed before block completes
+            onLongPress.invoke(down.position)
+            consumeUntilUp(pass)
+        }
+    }
+}
+
+/**
+ * Consumes all pointer events until nothing is pressed and then returns. This method assumes that
+ * something is currently pressed.
+ */
+private suspend fun AwaitPointerEventScope.consumeUntilUp(
+    pass: PointerEventPass = PointerEventPass.Initial
+) {
+    do {
+        val event = awaitPointerEvent(pass = pass)
+        event.changes.fastForEach { it.consume() }
+    } while (event.changes.fastAny { it.pressed })
+}
+
+/** Consume all gestures on the initial pass so that child elements do not receive them. */
+suspend fun PointerInputScope.consumeAllGestures() = coroutineScope {
+    awaitEachGesture {
+        awaitPointerEvent(pass = PointerEventPass.Initial)
+            .changes
+            .forEach(PointerInputChange::consume)
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
new file mode 100644
index 0000000..68e57b5
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.stickykeys.ui.view
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
+import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
+
+@Composable
+fun StickyKeysIndicator(viewModel: StickyKeysIndicatorViewModel) {
+    val stickyKeys by viewModel.indicatorContent.collectAsState(emptyMap())
+    StickyKeysIndicator(stickyKeys)
+}
+
+@Composable
+fun StickyKeysIndicator(stickyKeys: Map<ModifierKey, Locked>, modifier: Modifier = Modifier) {
+    Surface(
+        color = MaterialTheme.colorScheme.surface,
+        shape = MaterialTheme.shapes.medium,
+        modifier = modifier
+    ) {
+        Column(
+            horizontalAlignment = Alignment.CenterHorizontally,
+            modifier = Modifier.padding(16.dp)
+        ) {
+            stickyKeys.forEach { (key, isLocked) ->
+                key(key) {
+                    Text(
+                        text = key.text,
+                        fontWeight = if (isLocked.locked) FontWeight.Bold else FontWeight.Normal
+                    )
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 84d4246..bf02d8a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -17,13 +17,16 @@
 package com.android.systemui.keyguard.ui.composable.blueprint
 
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.unit.IntRect
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.padding
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
@@ -66,6 +69,7 @@
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
         val burnIn = rememberBurnIn(clockInteractor)
+        val resources = LocalContext.current.resources
 
         LockscreenLongPress(
             viewModel = viewModel.longPress,
@@ -88,13 +92,27 @@
                             SmartSpace(
                                 burnInParams = burnIn.parameters,
                                 onTopChanged = burnIn.onSmartspaceTopChanged,
-                                modifier = Modifier.fillMaxWidth(),
+                                modifier =
+                                    Modifier.fillMaxWidth()
+                                        .padding(
+                                            top = { viewModel.getSmartSpacePaddingTop(resources) }
+                                        ),
                             )
                         }
-                        with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
-                        with(notificationSection) {
-                            Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+
+                        if (viewModel.isLargeClockVisible) {
+                            Spacer(modifier = Modifier.weight(weight = 1f))
+                            with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
                         }
+
+                        if (viewModel.areNotificationsVisible) {
+                            with(notificationSection) {
+                                Notifications(
+                                    modifier = Modifier.fillMaxWidth().weight(weight = 1f)
+                                )
+                            }
+                        }
+
                         if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
                             with(ambientIndicationSectionOptional.get()) {
                                 AmbientIndication(modifier = Modifier.fillMaxWidth())
@@ -155,7 +173,8 @@
                 val belowLockIconPlaceable =
                     belowLockIconMeasurable.measure(
                         noMinConstraints.copy(
-                            maxHeight = constraints.maxHeight - lockIconBounds.bottom
+                            maxHeight =
+                                (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
                         )
                     )
                 val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index 4148462..d0aa444 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -17,13 +17,16 @@
 package com.android.systemui.keyguard.ui.composable.blueprint
 
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.unit.IntRect
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.padding
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
@@ -66,6 +69,7 @@
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
         val burnIn = rememberBurnIn(clockInteractor)
+        val resources = LocalContext.current.resources
 
         LockscreenLongPress(
             viewModel = viewModel.longPress,
@@ -88,13 +92,27 @@
                             SmartSpace(
                                 burnInParams = burnIn.parameters,
                                 onTopChanged = burnIn.onSmartspaceTopChanged,
-                                modifier = Modifier.fillMaxWidth(),
+                                modifier =
+                                    Modifier.fillMaxWidth()
+                                        .padding(
+                                            top = { viewModel.getSmartSpacePaddingTop(resources) }
+                                        ),
                             )
                         }
-                        with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
-                        with(notificationSection) {
-                            Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+
+                        if (viewModel.isLargeClockVisible) {
+                            Spacer(modifier = Modifier.weight(weight = 1f))
+                            with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
                         }
+
+                        if (viewModel.areNotificationsVisible) {
+                            with(notificationSection) {
+                                Notifications(
+                                    modifier = Modifier.fillMaxWidth().weight(weight = 1f)
+                                )
+                            }
+                        }
+
                         if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
                             with(ambientIndicationSectionOptional.get()) {
                                 AmbientIndication(modifier = Modifier.fillMaxWidth())
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
index fdf1166..616a7b4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -16,19 +16,42 @@
 
 package com.android.systemui.keyguard.ui.composable.blueprint
 
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.material3.Text
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.padding
+import com.android.systemui.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
+import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
+import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
+import com.android.systemui.keyguard.ui.composable.section.ClockSection
+import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
+import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
+import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import com.android.systemui.res.R
+import com.android.systemui.shade.LargeScreenHeaderHelper
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
+import java.util.Optional
 import javax.inject.Inject
 
 /**
@@ -39,22 +62,174 @@
 @Inject
 constructor(
     private val viewModel: LockscreenContentViewModel,
+    private val statusBarSection: StatusBarSection,
+    private val clockSection: ClockSection,
+    private val smartSpaceSection: SmartSpaceSection,
+    private val notificationSection: NotificationSection,
+    private val lockSection: LockSection,
+    private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
+    private val bottomAreaSection: BottomAreaSection,
+    private val settingsMenuSection: SettingsMenuSection,
+    private val clockInteractor: KeyguardClockInteractor,
+    private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
 ) : LockscreenSceneBlueprint {
 
     override val id: String = "split-shade"
 
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
+        val isUdfpsVisible = viewModel.isUdfpsVisible
+        val burnIn = rememberBurnIn(clockInteractor)
+        val resources = LocalContext.current.resources
+
         LockscreenLongPress(
             viewModel = viewModel.longPress,
             modifier = modifier,
-        ) { _ ->
-            Box(modifier.background(Color.Black)) {
-                Text(
-                    text = "TODO(b/316211368): split shade blueprint",
-                    color = Color.White,
-                    modifier = Modifier.align(Alignment.Center),
-                )
+        ) { onSettingsMenuPlaced ->
+            Layout(
+                content = {
+                    // Constrained to above the lock icon.
+                    Column(
+                        modifier = Modifier.fillMaxSize(),
+                    ) {
+                        with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+                        Row(
+                            modifier = Modifier.fillMaxSize(),
+                        ) {
+                            Column(
+                                modifier = Modifier.fillMaxHeight().weight(weight = 1f),
+                                horizontalAlignment = Alignment.CenterHorizontally,
+                            ) {
+                                with(smartSpaceSection) {
+                                    SmartSpace(
+                                        burnInParams = burnIn.parameters,
+                                        onTopChanged = burnIn.onSmartspaceTopChanged,
+                                        modifier =
+                                            Modifier.fillMaxWidth()
+                                                .padding(
+                                                    top = {
+                                                        viewModel.getSmartSpacePaddingTop(resources)
+                                                    }
+                                                ),
+                                    )
+                                }
+
+                                Spacer(modifier = Modifier.weight(weight = 1f))
+                                with(clockSection) { LargeClock() }
+                                Spacer(modifier = Modifier.weight(weight = 1f))
+                            }
+                            with(notificationSection) {
+                                val splitShadeTopMargin: Dp =
+                                    if (Flags.centralizedStatusBarDimensRefactor()) {
+                                        largeScreenHeaderHelper.getLargeScreenHeaderHeight().dp
+                                    } else {
+                                        dimensionResource(
+                                            id = R.dimen.large_screen_shade_header_height
+                                        )
+                                    }
+                                Notifications(
+                                    modifier =
+                                        Modifier.fillMaxHeight()
+                                            .weight(weight = 1f)
+                                            .padding(top = splitShadeTopMargin)
+                                )
+                            }
+                        }
+
+                        if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
+                            with(ambientIndicationSectionOptional.get()) {
+                                AmbientIndication(modifier = Modifier.fillMaxWidth())
+                            }
+                        }
+                    }
+
+                    with(lockSection) { LockIcon() }
+
+                    // Aligned to bottom and constrained to below the lock icon.
+                    Column(modifier = Modifier.fillMaxWidth()) {
+                        if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
+                            with(ambientIndicationSectionOptional.get()) {
+                                AmbientIndication(modifier = Modifier.fillMaxWidth())
+                            }
+                        }
+
+                        with(bottomAreaSection) {
+                            IndicationArea(modifier = Modifier.fillMaxWidth())
+                        }
+                    }
+
+                    // Aligned to bottom and NOT constrained by the lock icon.
+                    with(bottomAreaSection) {
+                        Shortcut(isStart = true, applyPadding = true)
+                        Shortcut(isStart = false, applyPadding = true)
+                    }
+                    with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
+                },
+                modifier = Modifier.fillMaxSize(),
+            ) { measurables, constraints ->
+                check(measurables.size == 6)
+                val aboveLockIconMeasurable = measurables[0]
+                val lockIconMeasurable = measurables[1]
+                val belowLockIconMeasurable = measurables[2]
+                val startShortcutMeasurable = measurables[3]
+                val endShortcutMeasurable = measurables[4]
+                val settingsMenuMeasurable = measurables[5]
+
+                val noMinConstraints =
+                    constraints.copy(
+                        minWidth = 0,
+                        minHeight = 0,
+                    )
+                val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+                val lockIconBounds =
+                    IntRect(
+                        left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+                        top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+                        right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+                        bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+                    )
+
+                val aboveLockIconPlaceable =
+                    aboveLockIconMeasurable.measure(
+                        noMinConstraints.copy(maxHeight = lockIconBounds.top)
+                    )
+                val belowLockIconPlaceable =
+                    belowLockIconMeasurable.measure(
+                        noMinConstraints.copy(
+                            maxHeight =
+                                (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
+                        )
+                    )
+                val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
+                val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
+                val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
+
+                layout(constraints.maxWidth, constraints.maxHeight) {
+                    aboveLockIconPlaceable.place(
+                        x = 0,
+                        y = 0,
+                    )
+                    lockIconPlaceable.place(
+                        x = lockIconBounds.left,
+                        y = lockIconBounds.top,
+                    )
+                    belowLockIconPlaceable.place(
+                        x = 0,
+                        y = constraints.maxHeight - belowLockIconPlaceable.height,
+                    )
+                    startShortcutPleaceable.place(
+                        x = 0,
+                        y = constraints.maxHeight - startShortcutPleaceable.height,
+                    )
+                    endShortcutPleaceable.place(
+                        x = constraints.maxWidth - endShortcutPleaceable.width,
+                        y = constraints.maxHeight - endShortcutPleaceable.height,
+                    )
+                    settingsMenuPlaceable.place(
+                        x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
+                        y = constraints.maxHeight - settingsMenuPlaceable.height,
+                    )
+                }
             }
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
index f021bb6..8f21879 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
@@ -16,7 +16,8 @@
 
 package com.android.systemui.keyguard.ui.composable.section
 
-import androidx.compose.foundation.layout.fillMaxWidth
+import android.view.ViewGroup
+import android.widget.FrameLayout
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
@@ -30,10 +31,10 @@
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.padding
 import com.android.keyguard.KeyguardClockSwitch
+import com.android.systemui.customization.R as customizationR
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.res.R
 import javax.inject.Inject
 
 class ClockSection
@@ -75,14 +76,26 @@
         ) {
             content {
                 AndroidView(
-                    factory = { checkNotNull(currentClock).smallClock.view },
+                    factory = { context ->
+                        FrameLayout(context).apply {
+                            val newClockView = checkNotNull(currentClock).smallClock.view
+                            (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+                            addView(newClockView)
+                        }
+                    },
                     modifier =
                         Modifier.padding(
                                 horizontal =
-                                    dimensionResource(R.dimen.keyguard_affordance_horizontal_offset)
+                                    dimensionResource(customizationR.dimen.clock_padding_start)
                             )
                             .padding(top = { viewModel.getSmallClockTopMargin(view.context) })
                             .onTopPlacementChanged(onTopChanged),
+                    update = {
+                        val newClockView = checkNotNull(currentClock).smallClock.view
+                        it.removeAllViews()
+                        (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+                        it.addView(newClockView)
+                    },
                 )
             }
         }
@@ -116,10 +129,19 @@
         ) {
             content {
                 AndroidView(
-                    factory = { checkNotNull(currentClock).largeClock.view },
-                    modifier =
-                        Modifier.fillMaxWidth()
-                            .padding(top = { viewModel.getLargeClockTopMargin(view.context) })
+                    factory = { context ->
+                        FrameLayout(context).apply {
+                            val newClockView = checkNotNull(currentClock).largeClock.view
+                            (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+                            addView(newClockView)
+                        }
+                    },
+                    update = {
+                        val newClockView = checkNotNull(currentClock).largeClock.view
+                        it.removeAllViews()
+                        (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+                        it.addView(newClockView)
+                    },
                 )
             }
         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
index 2a6bea7..be6f022 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
@@ -33,6 +33,7 @@
 import com.android.keyguard.LockIconViewController
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
@@ -47,10 +48,12 @@
 import com.android.systemui.statusbar.VibratorHelper
 import dagger.Lazy
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 
 class LockSection
 @Inject
 constructor(
+    @Application private val applicationScope: CoroutineScope,
     private val windowManager: WindowManager,
     private val authController: AuthController,
     private val featureFlags: FeatureFlagsClassic,
@@ -76,6 +79,7 @@
                         DeviceEntryIconView(context, null).apply {
                             id = R.id.device_entry_icon_view
                             DeviceEntryIconViewBinder.bind(
+                                applicationScope,
                                 this,
                                 deviceEntryIconViewModel.get(),
                                 deviceEntryForegroundViewModel.get(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 900616f..42fcd13 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -16,23 +16,55 @@
 
 package com.android.systemui.keyguard.ui.composable.section
 
+import android.content.Context
+import android.view.ViewGroup
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.statusbar.notification.stack.AmbientState
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackAppearanceViewBinder
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import javax.inject.Inject
 
 class NotificationSection
 @Inject
 constructor(
+    @Application context: Context,
     private val viewModel: NotificationsPlaceholderViewModel,
+    controller: NotificationStackScrollLayoutController,
+    sceneContainerFlags: SceneContainerFlags,
+    sharedNotificationContainer: SharedNotificationContainer,
+    stackScrollLayout: NotificationStackScrollLayout,
+    notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel,
+    ambientState: AmbientState,
 ) {
+    init {
+        if (sceneContainerFlags.flexiNotifsEnabled()) {
+            (stackScrollLayout.parent as? ViewGroup)?.removeView(stackScrollLayout)
+            sharedNotificationContainer.addNotificationStackScrollLayout(stackScrollLayout)
+
+            NotificationStackAppearanceViewBinder.bind(
+                context,
+                sharedNotificationContainer,
+                notificationStackAppearanceViewModel,
+                ambientState,
+                controller,
+            )
+        }
+    }
+
     @Composable
     fun SceneScope.Notifications(modifier: Modifier = Modifier) {
         NotificationStack(
             viewModel = viewModel,
-            isScrimVisible = false,
             modifier = modifier,
         )
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 0eec024..e835d3e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -22,6 +22,7 @@
 import androidx.compose.foundation.layout.Box
 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.shape.RoundedCornerShape
 import androidx.compose.material3.MaterialTheme
@@ -43,11 +44,10 @@
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.ValueKey
-import com.android.compose.animation.scene.animateElementFloatAsState
+import com.android.compose.modifiers.height
 import com.android.systemui.notifications.ui.composable.Notifications.Form
-import com.android.systemui.notifications.ui.composable.Notifications.SharedValues.SharedExpansionValue
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+import kotlin.math.roundToInt
 
 object Notifications {
     object Elements {
@@ -56,10 +56,6 @@
         val ShelfSpace = ElementKey("ShelfSpace")
     }
 
-    object SharedValues {
-        val SharedExpansionValue = ValueKey("SharedExpansionValue")
-    }
-
     enum class Form {
         HunFromTop,
         Stack,
@@ -84,32 +80,52 @@
     )
 }
 
-/** Adds the space where notification stack will appear in the scene. */
+/** Adds the space where notification stack should appear in the scene. */
 @Composable
 fun SceneScope.NotificationStack(
     viewModel: NotificationsPlaceholderViewModel,
-    isScrimVisible: Boolean,
+    modifier: Modifier = Modifier,
+) {
+    NotificationPlaceholder(
+        viewModel = viewModel,
+        form = Form.Stack,
+        modifier = modifier,
+    )
+}
+
+/**
+ * Adds the space where notification stack should appear in the scene, with a scrim and nested
+ * scrolling.
+ */
+@Composable
+fun SceneScope.NotificationScrollingStack(
+    viewModel: NotificationsPlaceholderViewModel,
     modifier: Modifier = Modifier,
 ) {
     val cornerRadius by viewModel.cornerRadiusDp.collectAsState()
 
-    Box(modifier = modifier) {
-        if (isScrimVisible) {
-            Box(
-                modifier =
-                    Modifier.element(Notifications.Elements.NotificationScrim)
-                        .fillMaxSize()
-                        .graphicsLayer {
-                            shape = RoundedCornerShape(cornerRadius.dp)
-                            clip = true
-                        }
-                        .background(MaterialTheme.colorScheme.surface)
-            )
-        }
+    val contentHeight by viewModel.intrinsicContentHeight.collectAsState()
+
+    val expansionFraction by viewModel.expandFraction.collectAsState(0f)
+
+    Box(
+        modifier =
+            modifier
+                .verticalNestedScrollToScene()
+                .fillMaxWidth()
+                .element(Notifications.Elements.NotificationScrim)
+                .graphicsLayer {
+                    shape = RoundedCornerShape(cornerRadius.dp)
+                    clip = true
+                    alpha = expansionFraction
+                }
+                .background(MaterialTheme.colorScheme.surface)
+                .debugBackground(viewModel, Color(0.5f, 0.5f, 0f, 0.2f))
+    ) {
         NotificationPlaceholder(
             viewModel = viewModel,
             form = Form.Stack,
-            modifier = Modifier.fillMaxSize(),
+            modifier = Modifier.fillMaxWidth().height { contentHeight.roundToInt() }
         )
     }
 }
@@ -166,6 +182,7 @@
                     debugLog(viewModel) { "STACK onSizeChanged: size=$size" }
                 }
                 .onPlaced { coordinates: LayoutCoordinates ->
+                    viewModel.onContentTopChanged(coordinates.positionInWindow().y)
                     debugLog(viewModel) {
                         "STACK onPlaced:" +
                             " size=${coordinates.size}" +
@@ -181,13 +198,6 @@
                     )
                 }
     ) {
-        val animatedExpansion by
-            animateElementFloatAsState(
-                value = if (form == Form.HunFromTop) 0f else 1f,
-                key = SharedExpansionValue
-            )
-        debugLog(viewModel) { "STACK composed: expansion=$animatedExpansion" }
-
         content {
             if (viewModel.isPlaceholderTextVisible) {
                 Box(Modifier.fillMaxSize()) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index 9778e53..c027c49 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -16,17 +16,16 @@
 
 package com.android.systemui.qs.ui.composable
 
-import android.view.ContextThemeWrapper
 import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.defaultMinSize
-import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.unit.dp
@@ -53,14 +52,6 @@
     }
 }
 
-@Composable
-private fun QuickSettingsTheme(content: @Composable () -> Unit) {
-    val context = LocalContext.current
-    val themedContext =
-        remember(context) { ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) }
-    CompositionLocalProvider(LocalContext provides themedContext) { content() }
-}
-
 private fun SceneScope.stateForQuickSettingsContent(): QSSceneAdapter.State {
     return when (val transitionState = layoutState.transitionState) {
         is TransitionState.Idle -> {
@@ -115,6 +106,7 @@
     modifier: Modifier = Modifier,
 ) {
     val qsView by qsSceneAdapter.qsView.collectAsState(null)
+    val isCustomizing by qsSceneAdapter.isCustomizing.collectAsState()
     QuickSettingsTheme {
         val context = LocalContext.current
 
@@ -124,14 +116,27 @@
             }
         }
         qsView?.let { view ->
-            AndroidView(
-                modifier = modifier.fillMaxSize().background(colorAttr(R.attr.underSurface)),
-                factory = { _ ->
-                    qsSceneAdapter.setState(state)
-                    view
-                },
-                update = { qsSceneAdapter.setState(state) }
-            )
+            Box(
+                modifier =
+                    modifier
+                        .fillMaxWidth()
+                        .then(
+                            if (isCustomizing) {
+                                Modifier.fillMaxHeight()
+                            } else {
+                                Modifier.wrapContentHeight()
+                            }
+                        )
+            ) {
+                AndroidView(
+                    modifier = Modifier.fillMaxWidth().background(colorAttr(R.attr.underSurface)),
+                    factory = { _ ->
+                        qsSceneAdapter.setState(state)
+                        view
+                    },
+                    update = { qsSceneAdapter.setState(state) }
+                )
+            }
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index d8c7290..bbfe0fd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -24,31 +24,44 @@
 import androidx.compose.animation.fadeOut
 import androidx.compose.animation.shrinkVertically
 import androidx.compose.foundation.background
+import androidx.compose.foundation.clipScrollableContainer
+import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
 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.wrapContentHeight
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.TransitionState
 import com.android.compose.windowsizeclass.LocalWindowSizeClass
 import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace
+import com.android.systemui.qs.footer.ui.compose.FooterActions
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.scene.ui.composable.toTransitionSceneKey
 import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
 import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
 import com.android.systemui.shade.ui.composable.Shade
@@ -105,57 +118,120 @@
 ) {
     // TODO(b/280887232): implement the real UI.
     Box(modifier = modifier.fillMaxSize()) {
-        Box(modifier = Modifier.fillMaxSize()) {
-            val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
-            val collapsedHeaderHeight =
-                with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() }
-            Spacer(
-                modifier =
-                    Modifier.element(Shade.Elements.ScrimBackground)
-                        .fillMaxSize()
-                        .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim)
-            )
-            Column(
-                horizontalAlignment = Alignment.CenterHorizontally,
-                modifier =
-                    Modifier.fillMaxSize().padding(start = 16.dp, end = 16.dp, bottom = 48.dp)
-            ) {
-                when (LocalWindowSizeClass.current.widthSizeClass) {
-                    WindowWidthSizeClass.Compact ->
-                        AnimatedVisibility(
-                            visible = !isCustomizing,
-                            enter =
-                                expandVertically(
-                                    animationSpec = tween(1000),
-                                    initialHeight = { collapsedHeaderHeight },
-                                ) + fadeIn(tween(1000)),
-                            exit =
-                                shrinkVertically(
-                                    animationSpec = tween(1000),
-                                    targetHeight = { collapsedHeaderHeight },
-                                    shrinkTowards = Alignment.Top,
-                                ) + fadeOut(tween(1000)),
-                        ) {
-                            ExpandedShadeHeader(
+        val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
+        val collapsedHeaderHeight =
+            with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() }
+        val lifecycleOwner = LocalLifecycleOwner.current
+        val footerActionsViewModel =
+            remember(lifecycleOwner, viewModel) {
+                viewModel.getFooterActionsViewModel(lifecycleOwner)
+            }
+        val scrollState = rememberScrollState()
+        // When animating into the scene, we don't want it to be able to scroll, as it could mess
+        // up with the expansion animation.
+        val isScrollable =
+            when (val state = layoutState.transitionState) {
+                is TransitionState.Idle -> true
+                is TransitionState.Transition -> {
+                    state.fromScene == SceneKey.QuickSettings.toTransitionSceneKey()
+                }
+            }
+
+        LaunchedEffect(isCustomizing, scrollState) {
+            if (isCustomizing) {
+                scrollState.scrollTo(0)
+            }
+        }
+
+        // This is the background for the whole scene, as the elements don't necessarily provide
+        // a background that extends to the edges.
+        Spacer(
+            modifier =
+                Modifier.element(Shade.Elements.ScrimBackground)
+                    .fillMaxSize()
+                    .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim)
+        )
+        Column(
+            horizontalAlignment = Alignment.CenterHorizontally,
+            modifier =
+                Modifier.fillMaxSize()
+                    // bottom should be tied to insets
+                    .padding(bottom = 16.dp)
+        ) {
+            Box(modifier = Modifier.fillMaxSize().weight(1f)) {
+                val shadeHeaderAndQuickSettingsModifier =
+                    if (isCustomizing) {
+                        Modifier.fillMaxHeight().align(Alignment.TopCenter)
+                    } else {
+                        Modifier.verticalNestedScrollToScene()
+                            .verticalScroll(
+                                scrollState,
+                                enabled = isScrollable,
+                            )
+                            .clipScrollableContainer(Orientation.Horizontal)
+                            .fillMaxWidth()
+                            .wrapContentHeight(unbounded = true)
+                            .align(Alignment.TopCenter)
+                    }
+
+                Column(
+                    modifier = shadeHeaderAndQuickSettingsModifier,
+                ) {
+                    when (LocalWindowSizeClass.current.widthSizeClass) {
+                        WindowWidthSizeClass.Compact ->
+                            AnimatedVisibility(
+                                visible = !isCustomizing,
+                                enter =
+                                    expandVertically(
+                                        animationSpec = tween(100),
+                                        initialHeight = { collapsedHeaderHeight },
+                                    ) + fadeIn(tween(100)),
+                                exit =
+                                    shrinkVertically(
+                                        animationSpec = tween(100),
+                                        targetHeight = { collapsedHeaderHeight },
+                                        shrinkTowards = Alignment.Top,
+                                    ) + fadeOut(tween(100)),
+                            ) {
+                                ExpandedShadeHeader(
+                                    viewModel = viewModel.shadeHeaderViewModel,
+                                    createTintedIconManager = createTintedIconManager,
+                                    createBatteryMeterViewController =
+                                        createBatteryMeterViewController,
+                                    statusBarIconController = statusBarIconController,
+                                    modifier = Modifier.padding(horizontal = 16.dp),
+                                )
+                            }
+                        else ->
+                            CollapsedShadeHeader(
                                 viewModel = viewModel.shadeHeaderViewModel,
                                 createTintedIconManager = createTintedIconManager,
                                 createBatteryMeterViewController = createBatteryMeterViewController,
                                 statusBarIconController = statusBarIconController,
+                                modifier = Modifier.padding(horizontal = 16.dp),
                             )
-                        }
-                    else ->
-                        CollapsedShadeHeader(
-                            viewModel = viewModel.shadeHeaderViewModel,
-                            createTintedIconManager = createTintedIconManager,
-                            createBatteryMeterViewController = createBatteryMeterViewController,
-                            statusBarIconController = statusBarIconController,
-                        )
+                    }
+                    Spacer(modifier = Modifier.height(16.dp))
+                    // This view has its own horizontal padding
+                    QuickSettings(
+                        modifier = Modifier.sysuiResTag("expanded_qs_scroll_view"),
+                        viewModel.qsSceneAdapter,
+                    )
                 }
-                Spacer(modifier = Modifier.height(16.dp))
-                QuickSettings(
-                    modifier = Modifier.fillMaxHeight(),
-                    viewModel.qsSceneAdapter,
-                )
+            }
+            AnimatedVisibility(
+                visible = !isCustomizing,
+                modifier = Modifier.align(Alignment.CenterHorizontally).fillMaxWidth()
+            ) {
+                QuickSettingsTheme {
+                    // This view has its own horizontal padding
+                    // TODO(b/321716470) This should use a lifecycle tied to the scene.
+                    FooterActions(
+                        viewModel = footerActionsViewModel,
+                        qsVisibilityLifecycleOwner = lifecycleOwner,
+                        modifier = Modifier.element(QuickSettings.Elements.FooterActions)
+                    )
+                }
             }
         }
         HeadsUpNotificationSpace(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsTheme.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsTheme.kt
new file mode 100644
index 0000000..87b6f95b
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsTheme.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.ui.composable
+
+import android.view.ContextThemeWrapper
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import com.android.systemui.res.R
+
+@Composable
+fun QuickSettingsTheme(content: @Composable () -> Unit) {
+    val context = LocalContext.current
+    val themedContext =
+        remember(context) { ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) }
+    CompositionLocalProvider(LocalContext provides themedContext) { content() }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index bded98d..747faab 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -25,6 +25,7 @@
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace
+import com.android.systemui.notifications.ui.composable.Notifications
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.Edge
 import com.android.systemui.scene.shared.model.SceneKey
@@ -66,6 +67,7 @@
         modifier: Modifier,
     ) {
         Box(modifier = modifier) {
+            Box(modifier = Modifier.fillMaxSize().element(Notifications.Elements.NotificationScrim))
             HeadsUpNotificationSpace(
                 viewModel = notificationsViewModel,
                 modifier = Modifier.padding(16.dp).fillMaxSize(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 4eb9089..9f9e1f5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -25,7 +25,6 @@
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
@@ -38,11 +37,11 @@
 import com.android.compose.animation.scene.ObservableTransitionState as SceneTransitionObservableTransitionState
 import com.android.compose.animation.scene.SceneKey as SceneTransitionSceneKey
 import com.android.compose.animation.scene.SceneTransitionLayout
-import com.android.compose.animation.scene.SceneTransitionLayoutState
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserAction as SceneTransitionUserAction
 import com.android.compose.animation.scene.observableTransitionState
+import com.android.compose.animation.scene.updateSceneTransitionLayoutState
 import com.android.systemui.ribbon.ui.composable.BottomRightCornerRibbon
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.Edge
@@ -82,7 +81,12 @@
     val currentScene = checkNotNull(sceneByKey[currentSceneKey])
     val currentDestinations: Map<UserAction, SceneModel> by
         currentScene.destinationScenes.collectAsState()
-    val state = remember { SceneTransitionLayoutState(currentSceneKey.toTransitionSceneKey()) }
+    val state =
+        updateSceneTransitionLayoutState(
+            currentSceneKey.toTransitionSceneKey(),
+            onChangeScene = viewModel::onSceneChanged,
+            transitions = SceneContainerTransitions,
+        )
 
     DisposableEffect(viewModel, state) {
         viewModel.setTransitionState(state.observableTransitionState().map { it.toModel() })
@@ -93,9 +97,6 @@
         modifier = Modifier.fillMaxSize(),
     ) {
         SceneTransitionLayout(
-            currentScene = currentSceneKey.toTransitionSceneKey(),
-            onChangeScene = viewModel::onSceneChanged,
-            transitions = SceneContainerTransitions,
             state = state,
             modifier =
                 modifier
@@ -182,7 +183,7 @@
         is UserAction.Swipe ->
             Swipe(
                 pointerCount = pointerCount,
-                fromEdge =
+                fromSource =
                     when (this.fromEdge) {
                         null -> null
                         Edge.LEFT -> SceneTransitionEdge.Left
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
index 6bb525a..0c2c519 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
@@ -3,12 +3,12 @@
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
-import com.android.systemui.notifications.ui.composable.Notifications
-import com.android.systemui.scene.ui.composable.Shade
+import com.android.systemui.qs.ui.composable.QuickSettings
+import com.android.systemui.shade.ui.composable.ShadeHeader
 
 fun TransitionBuilder.goneToShadeTransition() {
     spec = tween(durationMillis = 500)
 
-    translate(Shade.rootElementKey, Edge.Top, true)
-    fade(Notifications.Elements.NotificationScrim)
+    fractionRange(start = .58f) { fade(ShadeHeader.Elements.CollapsedContent) }
+    translate(QuickSettings.Elements.Content, Edge.Top, true)
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index e2beaee..b11edf7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -58,6 +58,8 @@
 import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
 import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
 import com.android.systemui.res.R
+import com.android.systemui.scene.ui.composable.QuickSettings
+import com.android.systemui.scene.ui.composable.Shade as ShadeKey
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager
@@ -348,7 +350,7 @@
 }
 
 @Composable
-private fun StatusIcons(
+private fun SceneScope.StatusIcons(
     viewModel: ShadeHeaderViewModel,
     createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
     statusBarIconController: StatusBarIconController,
@@ -358,7 +360,6 @@
     val carrierIconSlots =
         listOf(stringResource(id = com.android.internal.R.string.status_bar_mobile))
     val isSingleCarrier by viewModel.isSingleCarrier.collectAsState()
-    val isTransitioning by viewModel.isTransitioning.collectAsState()
 
     AndroidView(
         factory = { context ->
@@ -373,7 +374,9 @@
             iconContainer
         },
         update = { iconContainer ->
-            iconContainer.setQsExpansionTransitioning(isTransitioning)
+            iconContainer.setQsExpansionTransitioning(
+                layoutState.isTransitioningBetween(ShadeKey, QuickSettings)
+            )
             if (isSingleCarrier || !useExpandedFormat) {
                 iconContainer.removeIgnoredSlots(carrierIconSlots)
             } else {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 9c0f1fe..1545372 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -22,11 +22,9 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
-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.wrapContentHeight
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
@@ -44,10 +42,12 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.media.controls.ui.MediaCarouselController
+import com.android.systemui.media.controls.ui.MediaHierarchyManager
 import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.MediaHostState
 import com.android.systemui.media.controls.ui.composable.MediaCarousel
 import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
-import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.notifications.ui.composable.NotificationScrollingStack
 import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Direction
@@ -128,6 +128,12 @@
             modifier = modifier,
         )
 
+    init {
+        mediaHost.expansion = MediaHostState.EXPANDED
+        mediaHost.showsOnlyActiveMedia = true
+        mediaHost.init(MediaHierarchyManager.LOCATION_QQS)
+    }
+
     private fun destinationScenes(
         up: SceneKey,
     ): Map<UserAction, SceneModel> {
@@ -148,35 +154,27 @@
     mediaHost: MediaHost,
     modifier: Modifier = Modifier,
 ) {
+    val localDensity = LocalDensity.current
     val layoutWidth = remember { mutableStateOf(0) }
 
-    Box(modifier.element(Shade.Elements.Scrim)) {
-        Spacer(
-            modifier =
-                Modifier.element(Shade.Elements.ScrimBackground)
-                    .fillMaxSize()
-                    .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim)
-        )
+    Box(
+        modifier =
+            modifier.element(Shade.Elements.Scrim).background(MaterialTheme.colorScheme.scrim),
+    )
+    Box {
         Column(
             horizontalAlignment = Alignment.CenterHorizontally,
-            modifier =
-                Modifier.fillMaxSize()
-                    .clickable(onClick = { viewModel.onContentClicked() })
-                    .padding(
-                        start = Shade.Dimensions.HorizontalPadding,
-                        end = Shade.Dimensions.HorizontalPadding,
-                        bottom = 48.dp
-                    )
+            modifier = Modifier.fillMaxWidth().clickable(onClick = { viewModel.onContentClicked() })
         ) {
             CollapsedShadeHeader(
                 viewModel = viewModel.shadeHeaderViewModel,
                 createTintedIconManager = createTintedIconManager,
                 createBatteryMeterViewController = createBatteryMeterViewController,
                 statusBarIconController = statusBarIconController,
+                modifier = Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
             )
-            Spacer(modifier = Modifier.height(16.dp))
             QuickSettings(
-                modifier = Modifier.wrapContentHeight(),
+                modifier = Modifier.height(130.dp),
                 viewModel.qsSceneAdapter,
             )
 
@@ -202,16 +200,15 @@
                         },
                     mediaHost = mediaHost,
                     layoutWidth = layoutWidth.value,
-                    layoutHeight = with(LocalDensity.current) { mediaHeight.toPx() }.toInt(),
+                    layoutHeight = with(localDensity) { mediaHeight.toPx() }.toInt(),
                     carouselController = mediaCarouselController,
                 )
             }
 
             Spacer(modifier = Modifier.height(16.dp))
-            NotificationStack(
+            NotificationScrollingStack(
                 viewModel = viewModel.notifications,
-                isScrimVisible = true,
-                modifier = Modifier.weight(1f),
+                modifier = Modifier.fillMaxWidth().weight(1f),
             )
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt
new file mode 100644
index 0000000..43d5453
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.bottombar
+
+import com.android.systemui.volume.panel.component.bottombar.ui.BottomBarComponent
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.domain.AlwaysAvailableCriteria
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+@Module
+interface BottomBarModule {
+
+    @Binds
+    @IntoMap
+    @StringKey(VolumePanelComponents.BOTTOM_BAR)
+    fun bindMediaVolumeSliderComponent(component: BottomBarComponent): VolumePanelUiComponent
+
+    @Binds
+    @IntoMap
+    @StringKey(VolumePanelComponents.BOTTOM_BAR)
+    fun bindComponentAvailabilityCriteria(
+        defaultCriteria: AlwaysAvailableCriteria
+    ): ComponentAvailabilityCriteria
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
new file mode 100644
index 0000000..03c07f7
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.bottombar.ui
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.android.compose.PlatformButton
+import com.android.compose.PlatformOutlinedButton
+import com.android.systemui.res.R
+import com.android.systemui.volume.panel.component.bottombar.ui.viewmodel.BottomBarViewModel
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
+import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
+import javax.inject.Inject
+
+@VolumePanelScope
+class BottomBarComponent
+@Inject
+constructor(
+    private val viewModel: BottomBarViewModel,
+) : ComposeVolumePanelUiComponent {
+
+    @Composable
+    override fun VolumePanelComposeScope.Content(modifier: Modifier) {
+        Row(
+            modifier = modifier.height(48.dp).fillMaxWidth(),
+            horizontalArrangement = Arrangement.SpaceBetween,
+            verticalAlignment = Alignment.CenterVertically,
+        ) {
+            PlatformOutlinedButton(onClick = viewModel::onSettingsClicked) {
+                Text(text = stringResource(R.string.volume_panel_dialog_settings_button))
+            }
+            PlatformButton(onClick = viewModel::onDoneClicked) {
+                Text(text = stringResource(R.string.inline_done_button))
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/ComposeVolumePanelUiComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/ComposeVolumePanelUiComponent.kt
new file mode 100644
index 0000000..e1834ee
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/ComposeVolumePanelUiComponent.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.volume.panel.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+
+/**
+ * Compose implementation of [VolumePanelUiComponent]. Each new UI component should implement this
+ * interface.
+ */
+interface ComposeVolumePanelUiComponent : VolumePanelUiComponent {
+
+    @Composable fun VolumePanelComposeScope.Content(modifier: Modifier)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
new file mode 100644
index 0000000..dcd22fe
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.composable
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.systemui.volume.panel.ui.viewmodel.ComponentState
+
+@Composable
+fun VolumePanelComposeScope.VerticalVolumePanelContent(
+    components: List<ComponentState>,
+    modifier: Modifier = Modifier,
+) {
+    Column(
+        modifier = modifier,
+        verticalArrangement = Arrangement.spacedBy(20.dp),
+    ) {
+        for (component in components) {
+            AnimatedVisibility(component.isVisible) {
+                with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt
new file mode 100644
index 0000000..c70c6b1
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.composable
+
+import android.content.res.Configuration.Orientation
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
+
+class VolumePanelComposeScope(private val state: VolumePanelState) {
+
+    /**
+     * Layout orientation of the panel. It doesn't necessarily aligns with the device orientation,
+     * because in some cases we want to show bigger version of a portrait orientation when the
+     * device is in landscape.
+     */
+    @Orientation
+    val orientation: Int
+        get() = state.orientation
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
new file mode 100644
index 0000000..3487184
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.composable
+
+import android.content.res.Configuration
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.MutableTransitionState
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.dimensionResource
+import com.android.compose.theme.PlatformTheme
+import com.android.systemui.res.R
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+
+@Composable
+fun VolumePanelRoot(
+    viewModel: VolumePanelViewModel,
+    onDismissAnimationFinished: () -> Unit,
+    modifier: Modifier = Modifier,
+) {
+    PlatformTheme(isSystemInDarkTheme()) {
+        val state: VolumePanelState by viewModel.volumePanelState.collectAsState()
+        val components by viewModel.componentsLayout.collectAsState(null)
+
+        val transitionState =
+            remember { MutableTransitionState(false) }.apply { targetState = state.isVisible }
+
+        LaunchedEffect(transitionState.targetState, transitionState.isIdle) {
+            if (!transitionState.targetState && transitionState.isIdle) {
+                onDismissAnimationFinished()
+            }
+        }
+
+        Column(
+            modifier =
+                modifier
+                    .fillMaxSize()
+                    .statusBarsPadding()
+                    .clickable(onClick = { viewModel.dismissPanel() }),
+            verticalArrangement = Arrangement.Bottom,
+        ) {
+            AnimatedVisibility(
+                visibleState = transitionState,
+                enter = slideInVertically { it },
+                exit = slideOutVertically { it },
+            ) {
+                val radius = dimensionResource(R.dimen.volume_panel_corner_radius)
+                Surface(
+                    shape = RoundedCornerShape(topStart = radius, topEnd = radius),
+                    color = MaterialTheme.colorScheme.surfaceBright,
+                ) {
+                    Column {
+                        components?.let { componentsState ->
+                            with(VolumePanelComposeScope(state)) { Components(componentsState) }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+@Composable
+private fun VolumePanelComposeScope.Components(state: ComponentsLayout) {
+    if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+        VerticalVolumePanelContent(
+            components = state.contentComponents,
+            modifier = Modifier.padding(dimensionResource(R.dimen.volume_panel_content_padding)),
+        )
+    } else {
+        TODO("Add landscape layout")
+    }
+
+    val horizontalPadding = dimensionResource(R.dimen.volume_panel_bottom_bar_horizontal_padding)
+    if (state.bottomBarComponent.isVisible) {
+        with(state.bottomBarComponent.component as ComposeVolumePanelUiComponent) {
+            Content(
+                Modifier.navigationBarsPadding()
+                    .padding(
+                        start = horizontalPadding,
+                        end = horizontalPadding,
+                        bottom = dimensionResource(R.dimen.volume_panel_bottom_bar_bottom_padding),
+                    )
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/tests/AndroidManifest.xml b/packages/SystemUI/compose/features/tests/AndroidManifest.xml
index 8fe9656..fc337fb 100644
--- a/packages/SystemUI/compose/features/tests/AndroidManifest.xml
+++ b/packages/SystemUI/compose/features/tests/AndroidManifest.xml
@@ -30,11 +30,6 @@
             android:enabled="false"
             tools:replace="android:authorities"
             tools:node="remove" />
-        <provider android:name="com.google.android.systemui.keyguard.KeyguardSliceProviderGoogle"
-            android:authorities="com.android.systemui.test.keyguard.disabled"
-            android:enabled="false"
-            tools:replace="android:authorities"
-            tools:node="remove" />
         <provider android:name="com.android.systemui.keyguard.CustomizationProvider"
             android:authorities="com.android.systemui.test.keyguard.quickaffordance.disabled"
             android:enabled="false"
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index ba6d00e..7d3b0fb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -28,9 +28,9 @@
  * the currently running transition, if there is one.
  */
 internal fun CoroutineScope.animateToScene(
-    layoutState: SceneTransitionLayoutStateImpl,
+    layoutState: BaseSceneTransitionLayoutState,
     target: SceneKey,
-) {
+): TransitionState.Transition? {
     val transitionState = layoutState.transitionState
     if (transitionState.currentScene == target) {
         // This can happen in 3 different situations, for which there isn't anything else to do:
@@ -41,10 +41,10 @@
         //     a. didn't release their pointer yet.
         //     b. released their pointer such that the swipe gesture was cancelled and the
         //        transition is currently animating back to [target].
-        return
+        return null
     }
 
-    when (transitionState) {
+    return when (transitionState) {
         is TransitionState.Idle -> animate(layoutState, target)
         is TransitionState.Transition -> {
             // A transition is currently running: first check whether `transition.toScene` or
@@ -62,47 +62,43 @@
                     // finish the current transition early to make sure that the current state
                     // change is committed.
                     layoutState.finishTransition(transitionState, transitionState.currentScene)
+                    null
                 } else {
                     // The transition is in progress: start the canned animation at the same
                     // progress as it was in.
                     // TODO(b/290184746): Also take the current velocity into account.
                     animate(layoutState, target, startProgress = progress)
                 }
-
-                return
-            }
-
-            if (transitionState.fromScene == target) {
+            } else if (transitionState.fromScene == target) {
                 // There is a transition from [target] to another scene: simply animate the same
                 // transition progress to `0`.
-
                 check(transitionState.toScene == transitionState.currentScene)
+
                 val progress = transitionState.progress
                 if (progress.absoluteValue < ProgressVisibilityThreshold) {
                     // The transition is at progress ~= 0: no need to animate.We finish the current
                     // transition early to make sure that the current state change is committed.
                     layoutState.finishTransition(transitionState, transitionState.currentScene)
+                    null
                 } else {
                     // TODO(b/290184746): Also take the current velocity into account.
                     animate(layoutState, target, startProgress = progress, reversed = true)
                 }
-
-                return
+            } else {
+                // Generic interruption; the current transition is neither from or to [target].
+                // TODO(b/290930950): Better handle interruptions here.
+                animate(layoutState, target)
             }
-
-            // Generic interruption; the current transition is neither from or to [target].
-            // TODO(b/290930950): Better handle interruptions here.
-            animate(layoutState, target)
         }
     }
 }
 
 private fun CoroutineScope.animate(
-    layoutState: SceneTransitionLayoutStateImpl,
+    layoutState: BaseSceneTransitionLayoutState,
     target: SceneKey,
     startProgress: Float = 0f,
     reversed: Boolean = false,
-) {
+): TransitionState.Transition {
     val fromScene = layoutState.transitionState.currentScene
     val isUserInput =
         (layoutState.transitionState as? TransitionState.Transition)?.isInitiatedByUserInput
@@ -143,10 +139,15 @@
         }
 
     // Animate the progress to its target value.
-    launch {
-        animatable.animateTo(targetProgress, animationSpec)
-        layoutState.finishTransition(transition, target)
-    }
+    launch { animatable.animateTo(targetProgress, animationSpec) }
+        .invokeOnCompletion {
+            // Settle the state to Idle(target). Note that this will do nothing if this transition
+            // was replaced/interrupted by another one, and this also runs if this coroutine is
+            // cancelled, i.e. if [this] coroutine scope is cancelled.
+            layoutState.finishTransition(transition, target)
+        }
+
+    return transition
 }
 
 private class OneOffTransition(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
index 82d4239..b0dc3a1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
@@ -23,24 +23,19 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 
-interface EdgeDetector {
-    /**
-     * Return the [Edge] associated to [position] inside a layout of size [layoutSize], given
-     * [density] and [orientation].
-     */
-    fun edge(
-        layoutSize: IntSize,
-        position: IntOffset,
-        density: Density,
-        orientation: Orientation,
-    ): Edge?
+/** The edge of a [SceneTransitionLayout]. */
+enum class Edge : SwipeSource {
+    Left,
+    Right,
+    Top,
+    Bottom,
 }
 
 val DefaultEdgeDetector = FixedSizeEdgeDetector(40.dp)
 
-/** An [EdgeDetector] that detects edges assuming a fixed edge size of [size]. */
-class FixedSizeEdgeDetector(val size: Dp) : EdgeDetector {
-    override fun edge(
+/** An [SwipeSourceDetector] that detects edges assuming a fixed edge size of [size]. */
+class FixedSizeEdgeDetector(val size: Dp) : SwipeSourceDetector {
+    override fun source(
         layoutSize: IntSize,
         position: IntOffset,
         density: Density,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 280fbfb..a910bca 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -20,10 +20,10 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.SnapshotStateMap
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.isSpecified
 import androidx.compose.ui.geometry.isUnspecified
 import androidx.compose.ui.geometry.lerp
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
@@ -46,41 +46,18 @@
 /** An element on screen, that can be composed in one or more scenes. */
 @Stable
 internal class Element(val key: ElementKey) {
-    /**
-     * The last state of this element, coming from any scene. Note that this state will be unstable
-     * if this element is present in multiple scenes but the shared element animation is disabled,
-     * given that multiple instances of the element with different states will write to this state.
-     * You should prefer using [SceneState.lastState] in the current scene when it is defined.
-     */
-    val lastSharedState = State()
-
     /** The mapping between a scene and the state this element has in that scene, if any. */
-    val sceneStates = mutableMapOf<SceneKey, SceneState>()
+    // TODO(b/316901148): Make this a normal map instead once we can make sure that new transitions
+    // are first seen by composition then layout/drawing code. See 316901148#comment2 for details.
+    val sceneStates = SnapshotStateMap<SceneKey, SceneState>()
 
     override fun toString(): String {
         return "Element(key=$key)"
     }
 
-    /** The state of this element, either in a specific scene or in a shared context. */
-    class State {
-        /** The offset of the element, relative to the SceneTransitionLayout containing it. */
-        var offset = Offset.Unspecified
-
-        /** The size of this element. */
-        var size = SizeUnspecified
-
-        /** The draw scale of this element. */
-        var drawScale = Scale.Default
-
-        /** The alpha of this element. */
-        var alpha = AlphaUnspecified
-    }
-
     /** The last and target state of this element in a given scene. */
     @Stable
     class SceneState(val scene: SceneKey) {
-        val lastState = State()
-
         var targetSize by mutableStateOf(SizeUnspecified)
         var targetOffset by mutableStateOf(Offset.Unspecified)
 
@@ -94,7 +71,6 @@
 
     companion object {
         val SizeUnspecified = IntSize(Int.MAX_VALUE, Int.MAX_VALUE)
-        val AlphaUnspecified = Float.MIN_VALUE
     }
 }
 
@@ -219,7 +195,7 @@
     }
 
     override fun ContentDrawScope.draw() {
-        val drawScale = getDrawScale(layoutImpl, element, scene, sceneState)
+        val drawScale = getDrawScale(layoutImpl, element, scene)
         if (drawScale == Scale.Default) {
             drawContent()
         } else {
@@ -264,7 +240,6 @@
     // Always draw the element if there is no ongoing transition or if the element is not shared.
     if (
         transition == null ||
-            !layoutImpl.isTransitionReady(transition) ||
             transition.fromScene !in element.sceneStates ||
             transition.toScene !in element.sceneStates
     ) {
@@ -304,7 +279,7 @@
 }
 
 private fun isSharedElementEnabled(
-    layoutState: SceneTransitionLayoutStateImpl,
+    layoutState: BaseSceneTransitionLayoutState,
     transition: TransitionState.Transition,
     element: ElementKey,
 ): Boolean {
@@ -312,7 +287,7 @@
 }
 
 internal fun sharedElementTransformation(
-    layoutState: SceneTransitionLayoutStateImpl,
+    layoutState: BaseSceneTransitionLayoutState,
     transition: TransitionState.Transition,
     element: ElementKey,
 ): SharedElementTransformation? {
@@ -342,18 +317,9 @@
     layoutImpl: SceneTransitionLayoutImpl,
     element: Element,
     scene: Scene,
-    sceneState: Element.SceneState,
 ): Boolean {
     val transition = layoutImpl.state.currentTransition ?: return true
 
-    if (!layoutImpl.isTransitionReady(transition)) {
-        val lastValue =
-            sceneState.lastState.alpha.takeIf { it != Element.AlphaUnspecified }
-                ?: element.lastSharedState.alpha.takeIf { it != Element.AlphaUnspecified } ?: 1f
-
-        return lastValue == 1f
-    }
-
     val fromScene = transition.fromScene
     val toScene = transition.toScene
     val fromState = element.sceneStates[fromScene]
@@ -383,7 +349,6 @@
     layoutImpl: SceneTransitionLayoutImpl,
     element: Element,
     scene: Scene,
-    sceneState: Element.SceneState,
 ): Float {
     return computeValue(
             layoutImpl,
@@ -393,10 +358,7 @@
             transformation = { it.alpha },
             idleValue = 1f,
             currentValue = { 1f },
-            lastValue = {
-                sceneState.lastState.alpha.takeIf { it != Element.AlphaUnspecified }
-                    ?: element.lastSharedState.alpha.takeIf { it != Element.AlphaUnspecified } ?: 1f
-            },
+            isSpecified = { true },
             ::lerp,
         )
         .coerceIn(0f, 1f)
@@ -434,34 +396,23 @@
             transformation = { it.size },
             idleValue = lookaheadSize,
             currentValue = { measurable.measure(constraints).also { maybePlaceable = it }.size() },
-            lastValue = {
-                sceneState.lastState.size.takeIf { it != Element.SizeUnspecified }
-                    ?: element.lastSharedState.size.takeIf { it != Element.SizeUnspecified }
-                        ?: measurable.measure(constraints).also { maybePlaceable = it }.size()
-            },
+            isSpecified = { it != Element.SizeUnspecified },
             ::lerp,
         )
 
-    val placeable =
-        maybePlaceable
-            ?: measurable.measure(
-                Constraints.fixed(
-                    targetSize.width.coerceAtLeast(0),
-                    targetSize.height.coerceAtLeast(0),
-                )
+    return maybePlaceable
+        ?: measurable.measure(
+            Constraints.fixed(
+                targetSize.width.coerceAtLeast(0),
+                targetSize.height.coerceAtLeast(0),
             )
-
-    val size = placeable.size()
-    element.lastSharedState.size = size
-    sceneState.lastState.size = size
-    return placeable
+        )
 }
 
 private fun getDrawScale(
     layoutImpl: SceneTransitionLayoutImpl,
     element: Element,
-    scene: Scene,
-    sceneState: Element.SceneState
+    scene: Scene
 ): Scale {
     return computeValue(
         layoutImpl,
@@ -471,10 +422,7 @@
         transformation = { it.drawScale },
         idleValue = Scale.Default,
         currentValue = { Scale.Default },
-        lastValue = {
-            sceneState.lastState.drawScale.takeIf { it != Scale.Default }
-                ?: element.lastSharedState.drawScale
-        },
+        isSpecified = { true },
         ::lerp,
     )
 }
@@ -498,9 +446,12 @@
             sceneState.targetOffset = targetOffsetInScene
         }
 
+        // No need to place the element in this scene if we don't want to draw it anyways.
+        if (!shouldDrawElement(layoutImpl, scene, element)) {
+            return
+        }
+
         val currentOffset = lookaheadScopeCoordinates.localPositionOf(coords, Offset.Zero)
-        val lastSharedState = element.lastSharedState
-        val lastSceneState = sceneState.lastState
         val targetOffset =
             computeValue(
                 layoutImpl,
@@ -510,37 +461,19 @@
                 transformation = { it.offset },
                 idleValue = targetOffsetInScene,
                 currentValue = { currentOffset },
-                lastValue = {
-                    lastSceneState.offset.takeIf { it.isSpecified }
-                        ?: lastSharedState.offset.takeIf { it.isSpecified } ?: currentOffset
-                },
+                isSpecified = { it != Offset.Unspecified },
                 ::lerp,
             )
 
-        lastSharedState.offset = targetOffset
-        lastSceneState.offset = targetOffset
-
-        // No need to place the element in this scene if we don't want to draw it anyways. Note that
-        // it's still important to compute the target offset and update last(Shared|Scene)State,
-        // otherwise they will be out of date.
-        if (!shouldDrawElement(layoutImpl, scene, element)) {
-            return
-        }
-
         val offset = (targetOffset - currentOffset).round()
-        if (isElementOpaque(layoutImpl, element, scene, sceneState)) {
+        if (isElementOpaque(layoutImpl, element, scene)) {
             // TODO(b/291071158): Call placeWithLayer() if offset != IntOffset.Zero and size is not
             // animated once b/305195729 is fixed. Test that drawing is not invalidated in that
             // case.
             placeable.place(offset)
-            lastSharedState.alpha = 1f
-            lastSceneState.alpha = 1f
         } else {
             placeable.placeWithLayer(offset) {
-                val alpha = elementAlpha(layoutImpl, element, scene, sceneState)
-                this.alpha = alpha
-                lastSharedState.alpha = alpha
-                lastSceneState.alpha = alpha
+                this.alpha = elementAlpha(layoutImpl, element, scene)
             }
         }
     }
@@ -563,8 +496,6 @@
  *   different than [idleValue] even if the value is not transformed directly because it could be
  *   impacted by the transformations on other elements, like a parent that is being translated or
  *   resized.
- * @param lastValue the last value that was used. This should be equal to [currentValue] if this is
- *   the first time the value is set.
  * @param lerp the linear interpolation function used to interpolate between two values of this
  *   value type.
  */
@@ -576,7 +507,7 @@
     transformation: (ElementTransformations) -> PropertyTransformation<T>?,
     idleValue: T,
     currentValue: () -> T,
-    lastValue: () -> T,
+    isSpecified: (T) -> Boolean,
     lerp: (T, T, Float) -> T,
 ): T {
     val transition =
@@ -587,21 +518,16 @@
         // layout phase.
         ?: return currentValue()
 
-    // A transition was started but it's not ready yet (not all elements have been composed/laid
-    // out yet). Use the last value that was set, to make sure elements don't unexpectedly jump.
-    if (!layoutImpl.isTransitionReady(transition)) {
-        return lastValue()
-    }
-
     val fromScene = transition.fromScene
     val toScene = transition.toScene
+
     val fromState = element.sceneStates[fromScene]
     val toState = element.sceneStates[toScene]
 
     if (fromState == null && toState == null) {
         // TODO(b/311600838): Throw an exception instead once layers of disposed elements are not
         // run anymore.
-        return lastValue()
+        return idleValue
     }
 
     // The element is shared: interpolate between the value in fromScene and the value in toScene.
@@ -612,6 +538,11 @@
         val start = sceneValue(fromState!!)
         val end = sceneValue(toState!!)
 
+        // TODO(b/316901148): Remove checks to isSpecified() once the lookahead pass runs for all
+        // nodes before the intermediate layout pass.
+        if (!isSpecified(start)) return end
+        if (!isSpecified(end)) return start
+
         // Make sure we don't read progress if values are the same and we don't need to interpolate,
         // so we don't invalidate the phase where this is read.
         return if (start == end) start else lerp(start, end, transition.progress)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index 90f46bd..9d4b69c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
@@ -44,7 +44,7 @@
 class SceneKey(
     name: String,
     identity: Any = Object(),
-) : Key(name, identity) {
+) : Key(name, identity), UserActionResult {
     @VisibleForTesting
     // TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can
     // access internal members.
@@ -53,6 +53,10 @@
     /** The unique [ElementKey] identifying this scene's root element. */
     val rootElementKey = ElementKey(name, identity)
 
+    // Implementation of [UserActionResult].
+    override val toScene: SceneKey = this
+    override val distance: UserActionDistance? = null
+
     override fun toString(): String {
         return "SceneKey(debugName=$debugName)"
     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index af3c099..cdc4778 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -174,22 +174,6 @@
         // If we are idle, there is only one [scene] that is composed so we can compose our
         // movable content here.
         ?: return true
-    val fromScene = transition.fromScene
-    val toScene = transition.toScene
-
-    val fromReady = layoutImpl.isSceneReady(fromScene)
-    val toReady = layoutImpl.isSceneReady(toScene)
-
-    if (!fromReady && !toReady) {
-        // Neither of the scenes will be drawn, so where we compose it doesn't really matter. Note
-        // that we could have slightly more complicated logic here to optimize for this case, but
-        // it's not worth it given that readyScenes should disappear soon (b/316901148).
-        return scene == toScene
-    }
-
-    // If one of the scenes is not ready, compose it in the other one to make sure it is drawn.
-    if (!fromReady) return scene == toScene
-    if (!toReady) return scene == fromScene
 
     // Always compose movable elements in the scene picked by their scene picker.
     return shouldDrawOrComposeSharedElement(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 3873878..5f615fd 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -40,12 +40,15 @@
 import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
 import androidx.compose.ui.node.DelegatingNode
 import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.ObserverModifierNode
 import androidx.compose.ui.node.PointerInputModifierNode
 import androidx.compose.ui.node.currentValueOf
+import androidx.compose.ui.node.observeReads
 import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.util.fastForEach
+import kotlin.math.sign
 
 /**
  * Make an element draggable in the given [orientation].
@@ -64,8 +67,8 @@
 @Stable
 internal fun Modifier.multiPointerDraggable(
     orientation: Orientation,
-    enabled: Boolean,
-    startDragImmediately: Boolean,
+    enabled: () -> Boolean,
+    startDragImmediately: (startedPosition: Offset) -> Boolean,
     onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
     onDragDelta: (delta: Float) -> Unit,
     onDragStopped: (velocity: Float) -> Unit,
@@ -83,8 +86,8 @@
 
 private data class MultiPointerDraggableElement(
     private val orientation: Orientation,
-    private val enabled: Boolean,
-    private val startDragImmediately: Boolean,
+    private val enabled: () -> Boolean,
+    private val startDragImmediately: (startedPosition: Offset) -> Boolean,
     private val onDragStarted:
         (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
     private val onDragDelta: (Float) -> Unit,
@@ -110,19 +113,24 @@
     }
 }
 
-private class MultiPointerDraggableNode(
+internal class MultiPointerDraggableNode(
     orientation: Orientation,
-    enabled: Boolean,
-    var startDragImmediately: Boolean,
+    enabled: () -> Boolean,
+    var startDragImmediately: (startedPosition: Offset) -> Boolean,
     var onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
     var onDragDelta: (Float) -> Unit,
     var onDragStopped: (velocity: Float) -> Unit,
-) : PointerInputModifierNode, DelegatingNode(), CompositionLocalConsumerModifierNode {
+) :
+    PointerInputModifierNode,
+    DelegatingNode(),
+    CompositionLocalConsumerModifierNode,
+    ObserverModifierNode {
     private val pointerInputHandler: suspend PointerInputScope.() -> Unit = { pointerInput() }
     private val delegate = delegate(SuspendingPointerInputModifierNode(pointerInputHandler))
     private val velocityTracker = VelocityTracker()
+    private var previousEnabled: Boolean = false
 
-    var enabled: Boolean = enabled
+    var enabled: () -> Boolean = enabled
         set(value) {
             // Reset the pointer input whenever enabled changed.
             if (value != field) {
@@ -133,13 +141,28 @@
 
     var orientation: Orientation = orientation
         set(value) {
-            // Reset the pointer input whenever enabled orientation.
+            // Reset the pointer input whenever orientation changed.
             if (value != field) {
                 field = value
                 delegate.resetPointerInputHandler()
             }
         }
 
+    override fun onAttach() {
+        previousEnabled = enabled()
+        onObservedReadsChanged()
+    }
+
+    override fun onObservedReadsChanged() {
+        observeReads {
+            val newEnabled = enabled()
+            if (newEnabled != previousEnabled) {
+                delegate.resetPointerInputHandler()
+            }
+            previousEnabled = newEnabled
+        }
+    }
+
     override fun onCancelPointerInput() = delegate.onCancelPointerInput()
 
     override fun onPointerEvent(
@@ -149,7 +172,7 @@
     ) = delegate.onPointerEvent(pointerEvent, pass, bounds)
 
     private suspend fun PointerInputScope.pointerInput() {
-        if (!enabled) {
+        if (!enabled()) {
             return
         }
 
@@ -163,8 +186,7 @@
         val onDragEnd: () -> Unit = {
             val maxFlingVelocity =
                 currentValueOf(LocalViewConfiguration).maximumFlingVelocity.let { max ->
-                    val maxF = max.toFloat()
-                    Velocity(maxF, maxF)
+                    Velocity(max, max)
                 }
 
             val velocity = velocityTracker.calculateVelocity(maxFlingVelocity)
@@ -183,7 +205,7 @@
 
         detectDragGestures(
             orientation = orientation,
-            startDragImmediately = { startDragImmediately },
+            startDragImmediately = startDragImmediately,
             onDragStart = onDragStart,
             onDragEnd = onDragEnd,
             onDragCancel = onDragCancel,
@@ -202,7 +224,7 @@
  */
 private suspend fun PointerInputScope.detectDragGestures(
     orientation: Orientation,
-    startDragImmediately: () -> Boolean,
+    startDragImmediately: (startedPosition: Offset) -> Boolean,
     onDragStart: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
     onDragEnd: () -> Unit,
     onDragCancel: () -> Unit,
@@ -212,7 +234,7 @@
         val initialDown = awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial)
         var overSlop = 0f
         val drag =
-            if (startDragImmediately()) {
+            if (startDragImmediately(initialDown.position)) {
                 initialDown.consume()
                 initialDown
             } else {
@@ -224,12 +246,31 @@
 
                 // TODO(b/291055080): Replace by await[Orientation]PointerSlopOrCancellation once
                 // it is public.
-                when (orientation) {
-                    Orientation.Horizontal ->
-                        awaitHorizontalTouchSlopOrCancellation(down.id, onSlopReached)
-                    Orientation.Vertical ->
-                        awaitVerticalTouchSlopOrCancellation(down.id, onSlopReached)
+                val drag =
+                    when (orientation) {
+                        Orientation.Horizontal ->
+                            awaitHorizontalTouchSlopOrCancellation(down.id, onSlopReached)
+                        Orientation.Vertical ->
+                            awaitVerticalTouchSlopOrCancellation(down.id, onSlopReached)
+                    }
+
+                // Make sure that overSlop is not 0f. This can happen when the user drags by exactly
+                // the touch slop. However, the overSlop we pass to onDragStarted() is used to
+                // compute the direction we are dragging in, so overSlop should never be 0f unless
+                // we intercept an ongoing swipe transition (i.e. startDragImmediately() returned
+                // true).
+                if (drag != null && overSlop == 0f) {
+                    val deltaOffset = drag.position - initialDown.position
+                    val delta =
+                        when (orientation) {
+                            Orientation.Horizontal -> deltaOffset.y
+                            Orientation.Vertical -> deltaOffset.y
+                        }
+                    check(delta != 0f)
+                    overSlop = delta.sign
                 }
+
+                drag
             }
 
         if (drag != null) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt
index 454c0ec..b346a70 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt
@@ -16,96 +16,118 @@
 
 package com.android.compose.animation.scene
 
+import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.geometry.toRect
 import androidx.compose.ui.graphics.BlendMode
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.CompositingStrategy
 import androidx.compose.ui.graphics.Outline
-import androidx.compose.ui.graphics.Paint
 import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.drawOutline
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
 import androidx.compose.ui.graphics.drawscope.DrawScope
-import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
 import androidx.compose.ui.graphics.drawscope.translate
-import androidx.compose.ui.graphics.withSaveLayer
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.node.DelegatingNode
 import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.GlobalPositionAwareModifierNode
+import androidx.compose.ui.node.LayoutModifierNode
 import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.toSize
 
-internal fun Modifier.punchHole(
-    layoutImpl: SceneTransitionLayoutImpl,
-    element: ElementKey,
-    bounds: ElementKey,
-    shape: Shape,
-): Modifier = this.then(PunchHoleElement(layoutImpl, element, bounds, shape))
+/**
+ * Punch a hole in this node with the given [size], [offset] and [shape].
+ *
+ * Punching a hole in an element will "remove" any pixel drawn by that element in the hole area.
+ * This can be used to make content drawn below an opaque element visible. For example, if we have
+ * [this lockscreen scene](http://shortn/_VYySFnJDhN) drawn below
+ * [this shade scene](http://shortn/_fpxGUk0Rg7) and punch a hole in the latter using the big clock
+ * time bounds and a RoundedCornerShape(10dp), [this](http://shortn/_qt80IvORFj) would be the
+ * result.
+ */
+@Stable
+fun Modifier.punchHole(
+    size: () -> Size,
+    offset: () -> Offset,
+    shape: Shape = RectangleShape,
+): Modifier = this.then(PunchHoleElement(size, offset, shape))
+
+/**
+ * Punch a hole in this node using the bounds of [coords] and the given [shape].
+ *
+ * You can use [androidx.compose.ui.layout.onGloballyPositioned] to get the last coordinates of a
+ * node.
+ */
+@Stable
+fun Modifier.punchHole(
+    coords: () -> LayoutCoordinates?,
+    shape: Shape = RectangleShape,
+): Modifier = this.then(PunchHoleWithBoundsElement(coords, shape))
 
 private data class PunchHoleElement(
-    private val layoutImpl: SceneTransitionLayoutImpl,
-    private val element: ElementKey,
-    private val bounds: ElementKey,
+    private val size: () -> Size,
+    private val offset: () -> Offset,
     private val shape: Shape,
 ) : ModifierNodeElement<PunchHoleNode>() {
-    override fun create(): PunchHoleNode = PunchHoleNode(layoutImpl, element, bounds, shape)
+    override fun create(): PunchHoleNode = PunchHoleNode(size, offset, { shape })
 
     override fun update(node: PunchHoleNode) {
-        node.layoutImpl = layoutImpl
-        node.element = element
-        node.bounds = bounds
-        node.shape = shape
+        node.size = size
+        node.offset = offset
+        node.shape = { shape }
     }
 }
 
 private class PunchHoleNode(
-    var layoutImpl: SceneTransitionLayoutImpl,
-    var element: ElementKey,
-    var bounds: ElementKey,
-    var shape: Shape,
-) : Modifier.Node(), DrawModifierNode {
+    var size: () -> Size,
+    var offset: () -> Offset,
+    var shape: () -> Shape,
+) : Modifier.Node(), DrawModifierNode, LayoutModifierNode {
     private var lastSize: Size = Size.Unspecified
     private var lastLayoutDirection: LayoutDirection = LayoutDirection.Ltr
     private var lastOutline: Outline? = null
 
-    override fun ContentDrawScope.draw() {
-        val bounds = layoutImpl.elements[bounds]
-
-        if (
-            bounds == null ||
-                bounds.lastSharedState.size == Element.SizeUnspecified ||
-                bounds.lastSharedState.offset == Offset.Unspecified
-        ) {
-            drawContent()
-            return
-        }
-
-        val element = layoutImpl.elements.getValue(element)
-        drawIntoCanvas { canvas ->
-            canvas.withSaveLayer(size.toRect(), Paint()) {
-                drawContent()
-
-                val offset = bounds.lastSharedState.offset - element.lastSharedState.offset
-                translate(offset.x, offset.y) { drawHole(bounds) }
+    override fun MeasureScope.measure(
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult {
+        return measurable.measure(constraints).run {
+            layout(width, height) {
+                placeWithLayer(0, 0) { compositingStrategy = CompositingStrategy.Offscreen }
             }
         }
     }
 
-    private fun DrawScope.drawHole(bounds: Element) {
-        val boundsSize = bounds.lastSharedState.size.toSize()
+    override fun ContentDrawScope.draw() {
+        drawContent()
+
+        val holeSize = size()
+        if (holeSize != Size.Zero) {
+            val offset = offset()
+            translate(offset.x, offset.y) { drawHole(holeSize) }
+        }
+    }
+
+    private fun DrawScope.drawHole(size: Size) {
         if (shape == RectangleShape) {
-            drawRect(Color.Black, size = boundsSize, blendMode = BlendMode.DstOut)
+            drawRect(Color.Black, size = size, blendMode = BlendMode.DstOut)
             return
         }
 
         val outline =
-            if (boundsSize == lastSize && layoutDirection == lastLayoutDirection) {
+            if (size == lastSize && layoutDirection == lastLayoutDirection) {
                 lastOutline!!
             } else {
-                val newOutline = shape.createOutline(boundsSize, layoutDirection, this)
-                lastSize = boundsSize
+                val newOutline = shape().createOutline(size, layoutDirection, this)
+                lastSize = size
                 lastLayoutDirection = layoutDirection
                 lastOutline = newOutline
                 newOutline
@@ -118,3 +140,39 @@
         )
     }
 }
+
+private data class PunchHoleWithBoundsElement(
+    private val coords: () -> LayoutCoordinates?,
+    private val shape: Shape,
+) : ModifierNodeElement<PunchHoleWithBoundsNode>() {
+    override fun create(): PunchHoleWithBoundsNode = PunchHoleWithBoundsNode(coords, shape)
+
+    override fun update(node: PunchHoleWithBoundsNode) {
+        node.holeCoords = coords
+        node.shape = shape
+    }
+}
+
+private class PunchHoleWithBoundsNode(
+    var holeCoords: () -> LayoutCoordinates?,
+    var shape: Shape,
+) : DelegatingNode(), DrawModifierNode, GlobalPositionAwareModifierNode {
+    private val delegate = delegate(PunchHoleNode(::holeSize, ::holeOffset, ::shape))
+    private var lastCoords: LayoutCoordinates? = null
+
+    override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
+        this.lastCoords = coordinates
+    }
+
+    override fun ContentDrawScope.draw() = with(delegate) { draw() }
+
+    private fun holeSize(): Size {
+        return holeCoords()?.size?.toSize() ?: Size.Zero
+    }
+
+    private fun holeOffset(): Offset {
+        val holeCoords = holeCoords() ?: return Offset.Zero
+        val lastCoords = lastCoords ?: error("draw() was called before onGloballyPositioned()")
+        return lastCoords.localPositionOf(holeCoords, relativeToSource = Offset.Zero)
+    }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 3537b79..af51cee 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -26,7 +26,6 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.layout.intermediateLayout
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.unit.IntSize
@@ -39,7 +38,7 @@
     val key: SceneKey,
     layoutImpl: SceneTransitionLayoutImpl,
     content: @Composable SceneScope.() -> Unit,
-    actions: Map<UserAction, SceneKey>,
+    actions: Map<UserAction, UserActionResult>,
     zIndex: Float,
 ) {
     internal val scope = SceneScopeImpl(layoutImpl, this)
@@ -139,12 +138,6 @@
             bottomOrRightBehavior = bottomBehavior,
         )
 
-    override fun Modifier.punchHole(
-        element: ElementKey,
-        bounds: ElementKey,
-        shape: Shape
-    ): Modifier = punchHole(layoutImpl, element, bounds, shape)
-
     override fun Modifier.noResizeDuringTransitions(): Modifier {
         return noResizeDuringTransitions(layoutState = layoutImpl.state)
     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index 338557d..58c3be24 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -20,14 +20,15 @@
 
 import android.util.Log
 import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.SpringSpec
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.round
 import com.android.compose.nestedscroll.PriorityNestedScrollConnection
@@ -52,7 +53,20 @@
         }
 
     private fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) {
-        if (isDrivingTransition || force) layoutState.startTransition(newTransition)
+        if (isDrivingTransition || force) {
+            layoutState.startTransition(newTransition)
+
+            // Initialize SwipeTransition.swipeSpec. Note that this must be called right after
+            // layoutState.startTransition() is called, because it computes the
+            // layoutState.transformationSpec().
+            newTransition.swipeSpec =
+                layoutState.transformationSpec.swipeSpec ?: layoutState.transitions.defaultSwipeSpec
+        } else {
+            // We were not driving the transition and we don't force the update, so the spec won't
+            // be used and it doesn't matter which one we set here.
+            newTransition.swipeSpec = SceneTransitions.DefaultSwipeSpec
+        }
+
         swipeTransition = newTransition
     }
 
@@ -73,22 +87,48 @@
     private val positionalThreshold
         get() = with(layoutImpl.density) { 56.dp.toPx() }
 
-    internal var gestureWithPriority: Any? = null
+    internal var currentSource: Any? = null
 
-    /** The [UserAction]s associated to the current swipe. */
-    private var actionUpOrLeft: UserAction? = null
-    private var actionDownOrRight: UserAction? = null
-    private var actionUpOrLeftNoEdge: UserAction? = null
-    private var actionDownOrRightNoEdge: UserAction? = null
-    private var upOrLeftScene: SceneKey? = null
-    private var downOrRightScene: SceneKey? = null
+    /** The [Swipes] associated to the current gesture. */
+    private var swipes: Swipes? = null
+
+    /** The [UserActionResult] associated to up and down swipes. */
+    private var upOrLeftResult: UserActionResult? = null
+    private var downOrRightResult: UserActionResult? = null
+
+    /**
+     * Whether we should immediately intercept a gesture.
+     *
+     * Note: if this returns true, then [onDragStarted] will be called with overSlop equal to 0f,
+     * indicating that the transition should be intercepted.
+     */
+    internal fun shouldImmediatelyIntercept(startedPosition: Offset?): Boolean {
+        // We don't intercept the touch if we are not currently driving the transition.
+        if (!isDrivingTransition) {
+            return false
+        }
+
+        // Only intercept the current transition if one of the 2 swipes results is also a transition
+        // between the same pair of scenes.
+        val fromScene = swipeTransition._currentScene
+        val swipes = computeSwipes(fromScene, startedPosition, pointersDown = 1)
+        val (upOrLeft, downOrRight) = computeSwipesResults(fromScene, swipes)
+        return (upOrLeft != null &&
+            swipeTransition.isTransitioningBetween(fromScene.key, upOrLeft.toScene)) ||
+            (downOrRight != null &&
+                swipeTransition.isTransitioningBetween(fromScene.key, downOrRight.toScene))
+    }
 
     internal fun onDragStarted(pointersDown: Int, startedPosition: Offset?, overSlop: Float) {
-        if (isDrivingTransition) {
+        if (overSlop == 0f) {
+            check(isDrivingTransition) {
+                "onDragStarted() called while isDrivingTransition=false overSlop=0f"
+            }
+
             // This [transition] was already driving the animation: simply take over it.
             // Stop animating and start from where the current offset.
             swipeTransition.cancelOffsetAnimation()
-            updateTargetScenes(swipeTransition._fromScene)
+            updateSwipesResults(swipeTransition._fromScene)
             return
         }
 
@@ -104,18 +144,25 @@
         }
 
         val fromScene = layoutImpl.scene(transitionState.currentScene)
-        setCurrentActions(fromScene, startedPosition, pointersDown)
+        updateSwipes(fromScene, startedPosition, pointersDown)
 
         val (targetScene, distance) =
-            findTargetSceneAndDistance(fromScene, overSlop, updateScenes = true) ?: return
-
+            findTargetSceneAndDistance(fromScene, overSlop, updateSwipesResults = true) ?: return
         updateTransition(SwipeTransition(fromScene, targetScene, distance), force = true)
     }
 
-    private fun setCurrentActions(fromScene: Scene, startedPosition: Offset?, pointersDown: Int) {
-        val fromEdge =
+    private fun updateSwipes(fromScene: Scene, startedPosition: Offset?, pointersDown: Int) {
+        this.swipes = computeSwipes(fromScene, startedPosition, pointersDown)
+    }
+
+    private fun computeSwipes(
+        fromScene: Scene,
+        startedPosition: Offset?,
+        pointersDown: Int
+    ): Swipes {
+        val fromSource =
             startedPosition?.let { position ->
-                layoutImpl.edgeDetector.edge(
+                layoutImpl.swipeSourceDetector.source(
                     fromScene.targetSize,
                     position.round(),
                     layoutImpl.density,
@@ -131,7 +178,7 @@
                         Orientation.Vertical -> SwipeDirection.Up
                     },
                 pointerCount = pointersDown,
-                fromEdge = fromEdge,
+                fromSource = fromSource,
             )
 
         val downOrRight =
@@ -142,33 +189,31 @@
                         Orientation.Vertical -> SwipeDirection.Down
                     },
                 pointerCount = pointersDown,
-                fromEdge = fromEdge,
+                fromSource = fromSource,
             )
 
-        if (fromEdge == null) {
-            actionUpOrLeft = null
-            actionDownOrRight = null
-            actionUpOrLeftNoEdge = upOrLeft
-            actionDownOrRightNoEdge = downOrRight
+        return if (fromSource == null) {
+            Swipes(
+                upOrLeft = null,
+                downOrRight = null,
+                upOrLeftNoSource = upOrLeft,
+                downOrRightNoSource = downOrRight,
+            )
         } else {
-            actionUpOrLeft = upOrLeft
-            actionDownOrRight = downOrRight
-            actionUpOrLeftNoEdge = upOrLeft.copy(fromEdge = null)
-            actionDownOrRightNoEdge = downOrRight.copy(fromEdge = null)
+            Swipes(
+                upOrLeft = upOrLeft,
+                downOrRight = downOrRight,
+                upOrLeftNoSource = upOrLeft.copy(fromSource = null),
+                downOrRightNoSource = downOrRight.copy(fromSource = null),
+            )
         }
     }
 
-    /**
-     * Use the layout size in the swipe orientation for swipe distance.
-     *
-     * TODO(b/290184746): Also handle custom distances for transitions. With smaller distances, we
-     *   will also have to make sure that we correctly handle overscroll.
-     */
-    private fun Scene.getAbsoluteDistance(): Float {
-        return when (orientation) {
-            Orientation.Horizontal -> targetSize.width
-            Orientation.Vertical -> targetSize.height
-        }.toFloat()
+    private fun Scene.getAbsoluteDistance(distance: UserActionDistance?): Float {
+        val targetSize = this.targetSize
+        return with(distance ?: DefaultSwipeDistance) {
+            layoutImpl.density.absoluteDistance(targetSize, orientation)
+        }
     }
 
     internal fun onDrag(delta: Float) {
@@ -183,7 +228,7 @@
             findTargetSceneAndDistance(
                 fromScene,
                 swipeTransition.dragOffset,
-                updateScenes = isNewFromScene,
+                updateSwipesResults = isNewFromScene,
             )
                 ?: run {
                     onDragStopped(delta, true)
@@ -200,9 +245,31 @@
         }
     }
 
-    private fun updateTargetScenes(fromScene: Scene) {
-        upOrLeftScene = fromScene.upOrLeft()
-        downOrRightScene = fromScene.downOrRight()
+    private fun updateSwipesResults(fromScene: Scene) {
+        val (upOrLeftResult, downOrRightResult) =
+            computeSwipesResults(
+                fromScene,
+                this.swipes ?: error("updateSwipes() should be called before updateSwipesResults()")
+            )
+
+        this.upOrLeftResult = upOrLeftResult
+        this.downOrRightResult = downOrRightResult
+    }
+
+    private fun computeSwipesResults(
+        fromScene: Scene,
+        swipes: Swipes
+    ): Pair<UserActionResult?, UserActionResult?> {
+        val userActions = fromScene.userActions
+        fun sceneToSwipePair(swipe: Swipe?): UserActionResult? {
+            return userActions[swipe ?: return null]
+        }
+
+        val upOrLeftResult =
+            sceneToSwipePair(swipes.upOrLeft) ?: sceneToSwipePair(swipes.upOrLeftNoSource)
+        val downOrRightResult =
+            sceneToSwipePair(swipes.downOrRight) ?: sceneToSwipePair(swipes.downOrRightNoSource)
+        return Pair(upOrLeftResult, downOrRightResult)
     }
 
     /**
@@ -229,9 +296,9 @@
         // If the offset is past the distance then let's change fromScene so that the user can swipe
         // to the next screen or go back to the previous one.
         val offset = swipeTransition.dragOffset
-        return if (offset <= -absoluteDistance && upOrLeftScene == toScene.key) {
+        return if (offset <= -absoluteDistance && upOrLeftResult?.toScene == toScene.key) {
             Pair(toScene, absoluteDistance)
-        } else if (offset >= absoluteDistance && downOrRightScene == toScene.key) {
+        } else if (offset >= absoluteDistance && downOrRightResult?.toScene == toScene.key) {
             Pair(toScene, -absoluteDistance)
         } else {
             Pair(fromScene, 0f)
@@ -244,31 +311,41 @@
      * @param fromScene the scene from which we look for the target
      * @param directionOffset signed float that indicates the direction. Positive is down or right
      *   negative is up or left.
-     * @param updateScenes whether the target scenes should be updated to the current values held in
-     *   the Scenes map. Usually we don't want to update them while doing a drag, because this could
-     *   change the target scene (jump cutting) to a different scene, when some system state changed
-     *   the targets the background. However, an update is needed any time we calculate the targets
-     *   for a new fromScene.
+     * @param updateSwipesResults whether the target scenes should be updated to the current values
+     *   held in the Scenes map. Usually we don't want to update them while doing a drag, because
+     *   this could change the target scene (jump cutting) to a different scene, when some system
+     *   state changed the targets the background. However, an update is needed any time we
+     *   calculate the targets for a new fromScene.
      * @return null when there are no targets in either direction. If one direction is null and you
      *   drag into the null direction this function will return the opposite direction, assuming
      *   that the users intention is to start the drag into the other direction eventually. If
      *   [directionOffset] is 0f and both direction are available, it will default to
-     *   [upOrLeftScene].
+     *   [upOrLeftResult].
      */
     private inline fun findTargetSceneAndDistance(
         fromScene: Scene,
         directionOffset: Float,
-        updateScenes: Boolean,
+        updateSwipesResults: Boolean,
     ): Pair<Scene, Float>? {
-        if (updateScenes) updateTargetScenes(fromScene)
-        val absoluteDistance = fromScene.getAbsoluteDistance()
+        if (updateSwipesResults) updateSwipesResults(fromScene)
 
         // Compute the target scene depending on the current offset.
         return when {
-            upOrLeftScene == null && downOrRightScene == null -> null
-            (directionOffset < 0f && upOrLeftScene != null) || downOrRightScene == null ->
-                Pair(layoutImpl.scene(upOrLeftScene!!), -absoluteDistance)
-            else -> Pair(layoutImpl.scene(downOrRightScene!!), absoluteDistance)
+            upOrLeftResult == null && downOrRightResult == null -> null
+            (directionOffset < 0f && upOrLeftResult != null) || downOrRightResult == null ->
+                upOrLeftResult?.let { result ->
+                    Pair(
+                        layoutImpl.scene(result.toScene),
+                        -fromScene.getAbsoluteDistance(result.distance)
+                    )
+                }
+            else ->
+                downOrRightResult?.let { result ->
+                    Pair(
+                        layoutImpl.scene(result.toScene),
+                        fromScene.getAbsoluteDistance(result.distance)
+                    )
+                }
         }
     }
 
@@ -280,30 +357,36 @@
         fromScene: Scene,
         directionOffset: Float,
     ): Pair<Scene, Float>? {
-        val absoluteDistance = fromScene.getAbsoluteDistance()
         return when {
             directionOffset > 0f ->
-                upOrLeftScene?.let { Pair(layoutImpl.scene(it), -absoluteDistance) }
+                upOrLeftResult?.let { result ->
+                    Pair(
+                        layoutImpl.scene(result.toScene),
+                        -fromScene.getAbsoluteDistance(result.distance),
+                    )
+                }
             directionOffset < 0f ->
-                downOrRightScene?.let { Pair(layoutImpl.scene(it), absoluteDistance) }
+                downOrRightResult?.let { result ->
+                    Pair(
+                        layoutImpl.scene(result.toScene),
+                        fromScene.getAbsoluteDistance(result.distance),
+                    )
+                }
             else -> null
         }
     }
 
-    private fun Scene.upOrLeft(): SceneKey? {
-        return userActions[actionUpOrLeft] ?: userActions[actionUpOrLeftNoEdge]
-    }
-
-    private fun Scene.downOrRight(): SceneKey? {
-        return userActions[actionDownOrRight] ?: userActions[actionDownOrRightNoEdge]
-    }
-
     internal fun onDragStopped(velocity: Float, canChangeScene: Boolean) {
         // The state was changed since the drag started; don't do anything.
         if (!isDrivingTransition) {
             return
         }
 
+        // Important: Make sure that all the code here references the current transition when
+        // [onDragStopped] is called, otherwise the callbacks (like onAnimationCompleted()) might
+        // incorrectly finish a new transition that replaced this one.
+        val swipeTransition = this.swipeTransition
+
         fun animateTo(targetScene: Scene, targetOffset: Float) {
             // If the effective current scene changed, it should be reflected right now in the
             // current scene state, even before the settle animation is ongoing. That way all the
@@ -312,13 +395,16 @@
             // immediately go back B => A.
             if (targetScene != swipeTransition._currentScene) {
                 swipeTransition._currentScene = targetScene
-                layoutImpl.onChangeScene(targetScene.key)
+                with(layoutImpl.state) { coroutineScope.onChangeScene(targetScene.key) }
             }
 
-            animateOffset(
+            swipeTransition.animateOffset(
+                coroutineScope = coroutineScope,
                 initialVelocity = velocity,
                 targetOffset = targetOffset,
-                targetScene = targetScene.key
+                onAnimationCompleted = {
+                    layoutState.finishTransition(swipeTransition, idleScene = targetScene.key)
+                }
             )
         }
 
@@ -410,34 +496,6 @@
         }
     }
 
-    private fun animateOffset(
-        initialVelocity: Float,
-        targetOffset: Float,
-        targetScene: SceneKey,
-    ) {
-        swipeTransition.startOffsetAnimation {
-            coroutineScope.launch {
-                if (!swipeTransition.isAnimatingOffset) {
-                    swipeTransition.offsetAnimatable.snapTo(swipeTransition.dragOffset)
-                }
-                swipeTransition.isAnimatingOffset = true
-
-                swipeTransition.offsetAnimatable.animateTo(
-                    targetOffset,
-                    // TODO(b/290184746): Make this spring spec configurable.
-                    spring(
-                        stiffness = Spring.StiffnessMediumLow,
-                        visibilityThreshold = OffsetVisibilityThreshold
-                    ),
-                    initialVelocity = initialVelocity,
-                )
-
-                swipeTransition.finishOffsetAnimation()
-                layoutState.finishTransition(swipeTransition, targetScene)
-            }
-        }
-    }
-
     internal class SwipeTransition(
         val _fromScene: Scene,
         val _toScene: Scene,
@@ -445,7 +503,7 @@
          * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is
          * above or to the left of [toScene].
          */
-        val distance: Float
+        val distance: Float,
     ) : TransitionState.Transition(_fromScene.key, _toScene.key) {
         var _currentScene by mutableStateOf(_fromScene)
         override val currentScene: SceneKey
@@ -478,13 +536,18 @@
         /** Job to check that there is at most one offset animation in progress. */
         private var offsetAnimationJob: Job? = null
 
+        /** The spec to use when animating this transition to either [fromScene] or [toScene]. */
+        lateinit var swipeSpec: SpringSpec<Float>
+
         /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
-        fun startOffsetAnimation(job: () -> Job) {
+        private fun startOffsetAnimation(job: () -> Job) {
             cancelOffsetAnimation()
             offsetAnimationJob = job()
         }
 
         /** Cancel any ongoing offset animation. */
+        // TODO(b/317063114) This should be a suspended function to avoid multiple jobs running at
+        // the same time.
         fun cancelOffsetAnimation() {
             offsetAnimationJob?.cancel()
             finishOffsetAnimation()
@@ -496,30 +559,82 @@
                 dragOffset = offsetAnimatable.value
             }
         }
+
+        fun animateOffset(
+            // TODO(b/317063114) The CoroutineScope should be removed.
+            coroutineScope: CoroutineScope,
+            initialVelocity: Float,
+            targetOffset: Float,
+            onAnimationCompleted: () -> Unit,
+        ) {
+            startOffsetAnimation {
+                coroutineScope.launch {
+                    animateOffset(targetOffset, initialVelocity)
+                    onAnimationCompleted()
+                }
+            }
+        }
+
+        private suspend fun animateOffset(targetOffset: Float, initialVelocity: Float) {
+            if (!isAnimatingOffset) {
+                offsetAnimatable.snapTo(dragOffset)
+            }
+            isAnimatingOffset = true
+
+            offsetAnimatable.animateTo(
+                targetValue = targetOffset,
+                animationSpec = swipeSpec,
+                initialVelocity = initialVelocity,
+            )
+
+            finishOffsetAnimation()
+        }
     }
 
     companion object {
         private const val TAG = "SceneGestureHandler"
     }
+
+    private object DefaultSwipeDistance : UserActionDistance {
+        override fun Density.absoluteDistance(
+            fromSceneSize: IntSize,
+            orientation: Orientation,
+        ): Float {
+            return when (orientation) {
+                Orientation.Horizontal -> fromSceneSize.width
+                Orientation.Vertical -> fromSceneSize.height
+            }.toFloat()
+        }
+    }
+
+    /** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */
+    private class Swipes(
+        val upOrLeft: Swipe?,
+        val downOrRight: Swipe?,
+        val upOrLeftNoSource: Swipe?,
+        val downOrRightNoSource: Swipe?,
+    )
 }
 
 private class SceneDraggableHandler(
     private val gestureHandler: SceneGestureHandler,
 ) : DraggableHandler {
+    private val source = this
+
     override fun onDragStarted(startedPosition: Offset, overSlop: Float, pointersDown: Int) {
-        gestureHandler.gestureWithPriority = this
+        gestureHandler.currentSource = source
         gestureHandler.onDragStarted(pointersDown, startedPosition, overSlop)
     }
 
     override fun onDelta(pixels: Float) {
-        if (gestureHandler.gestureWithPriority == this) {
+        if (gestureHandler.currentSource == source) {
             gestureHandler.onDrag(delta = pixels)
         }
     }
 
     override fun onDragStopped(velocity: Float) {
-        if (gestureHandler.gestureWithPriority == this) {
-            gestureHandler.gestureWithPriority = null
+        if (gestureHandler.currentSource == source) {
+            gestureHandler.currentSource = null
             gestureHandler.onDragStopped(velocity = velocity, canChangeScene = true)
         }
     }
@@ -572,13 +687,18 @@
             return nextScene != null
         }
 
+        val source = this
+        var isIntercepting = false
+
         return PriorityNestedScrollConnection(
             orientation = orientation,
             canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
                 canChangeScene = offsetBeforeStart == 0f
 
                 val canInterceptSwipeTransition =
-                    canChangeScene && gestureHandler.isDrivingTransition && offsetAvailable != 0f
+                    canChangeScene &&
+                        offsetAvailable != 0f &&
+                        gestureHandler.shouldImmediatelyIntercept(startedPosition = null)
                 if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false
 
                 val swipeTransition = gestureHandler.swipeTransition
@@ -596,7 +716,12 @@
                 }
 
                 // Start only if we cannot consume this event
-                !shouldSnapToIdle
+                val canStart = !shouldSnapToIdle
+                if (canStart) {
+                    isIntercepting = true
+                }
+
+                canStart
             },
             canStartPostScroll = { offsetAvailable, offsetBeforeStart ->
                 val behavior: NestedScrollBehavior =
@@ -608,24 +733,31 @@
 
                 val isZeroOffset = offsetBeforeStart == 0f
 
-                when (behavior) {
-                    NestedScrollBehavior.DuringTransitionBetweenScenes -> {
-                        canChangeScene = false // unused: added for consistency
-                        false
+                val canStart =
+                    when (behavior) {
+                        NestedScrollBehavior.DuringTransitionBetweenScenes -> {
+                            canChangeScene = false // unused: added for consistency
+                            false
+                        }
+                        NestedScrollBehavior.EdgeNoPreview -> {
+                            canChangeScene = isZeroOffset
+                            isZeroOffset && hasNextScene(offsetAvailable)
+                        }
+                        NestedScrollBehavior.EdgeWithPreview -> {
+                            canChangeScene = isZeroOffset
+                            hasNextScene(offsetAvailable)
+                        }
+                        NestedScrollBehavior.EdgeAlways -> {
+                            canChangeScene = true
+                            hasNextScene(offsetAvailable)
+                        }
                     }
-                    NestedScrollBehavior.EdgeNoPreview -> {
-                        canChangeScene = isZeroOffset
-                        isZeroOffset && hasNextScene(offsetAvailable)
-                    }
-                    NestedScrollBehavior.EdgeWithPreview -> {
-                        canChangeScene = isZeroOffset
-                        hasNextScene(offsetAvailable)
-                    }
-                    NestedScrollBehavior.EdgeAlways -> {
-                        canChangeScene = true
-                        hasNextScene(offsetAvailable)
-                    }
+
+                if (canStart) {
+                    isIntercepting = false
                 }
+
+                canStart
             },
             canStartPostFling = { velocityAvailable ->
                 val behavior: NestedScrollBehavior =
@@ -637,20 +769,26 @@
 
                 // We could start an overscroll animation
                 canChangeScene = false
-                behavior.canStartOnPostFling && hasNextScene(velocityAvailable)
+
+                val canStart = behavior.canStartOnPostFling && hasNextScene(velocityAvailable)
+                if (canStart) {
+                    isIntercepting = false
+                }
+
+                canStart
             },
             canContinueScroll = { true },
             canScrollOnFling = false,
             onStart = { offsetAvailable ->
-                gestureHandler.gestureWithPriority = this
+                gestureHandler.currentSource = source
                 gestureHandler.onDragStarted(
                     pointersDown = 1,
                     startedPosition = null,
-                    overSlop = offsetAvailable,
+                    overSlop = if (isIntercepting) 0f else offsetAvailable,
                 )
             },
             onScroll = { offsetAvailable ->
-                if (gestureHandler.gestureWithPriority != this) {
+                if (gestureHandler.currentSource != source) {
                     return@PriorityNestedScrollConnection 0f
                 }
 
@@ -661,7 +799,7 @@
                 offsetAvailable
             },
             onStop = { velocityAvailable ->
-                if (gestureHandler.gestureWithPriority != this) {
+                if (gestureHandler.currentSource != source) {
                     return@PriorityNestedScrollConnection 0f
                 }
 
@@ -681,4 +819,6 @@
  * The number of pixels below which there won't be a visible difference in the transition and from
  * which the animation can stop.
  */
-private const val OffsetVisibilityThreshold = 0.5f
+// TODO(b/290184746): Have a better default visibility threshold which takes the swipe distance into
+// account instead.
+internal const val OffsetVisibilityThreshold = 0.5f
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 84fade89..7e0aa9c3 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -19,17 +19,53 @@
 import androidx.annotation.FloatRange
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.platform.LocalDensity
-import kotlinx.coroutines.channels.Channel
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+
+/**
+ * [SceneTransitionLayout] is a container that automatically animates its content whenever its state
+ * changes.
+ *
+ * Note: You should use [androidx.compose.animation.AnimatedContent] instead of
+ * [SceneTransitionLayout] if it fits your need. Use [SceneTransitionLayout] over AnimatedContent if
+ * you need support for swipe gestures, shared elements or transitions defined declaratively outside
+ * UI code.
+ *
+ * @param state the state of this layout.
+ * @param swipeSourceDetector the edge detector used to detect which edge a swipe is started from,
+ *   if any.
+ * @param transitionInterceptionThreshold used during a scene transition. For the scene to be
+ *   intercepted, the progress value must be above the threshold, and below (1 - threshold).
+ * @param scenes the configuration of the different scenes of this layout.
+ * @see updateSceneTransitionLayoutState
+ */
+@Composable
+fun SceneTransitionLayout(
+    state: SceneTransitionLayoutState,
+    modifier: Modifier = Modifier,
+    swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
+    @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f,
+    scenes: SceneTransitionLayoutScope.() -> Unit,
+) {
+    SceneTransitionLayoutForTesting(
+        state,
+        modifier,
+        swipeSourceDetector,
+        transitionInterceptionThreshold,
+        onLayoutImpl = null,
+        scenes,
+    )
+}
 
 /**
  * [SceneTransitionLayout] is a container that automatically animates its content whenever
@@ -45,8 +81,8 @@
  *   This is called when the user commits a transition to a new scene because of a [UserAction], for
  *   instance by triggering back navigation or by swiping to a new scene.
  * @param transitions the definition of the transitions used to animate a change of scene.
- * @param state the observable state of this layout.
- * @param edgeDetector the edge detector used to detect which edge a swipe is started from, if any.
+ * @param swipeSourceDetector the source detector used to detect which source a swipe is started
+ *   from, if any.
  * @param transitionInterceptionThreshold used during a scene transition. For the scene to be
  *   intercepted, the progress value must be above the threshold, and below (1 - threshold).
  * @param scenes the configuration of the different scenes of this layout.
@@ -57,20 +93,16 @@
     onChangeScene: (SceneKey) -> Unit,
     transitions: SceneTransitions,
     modifier: Modifier = Modifier,
-    state: SceneTransitionLayoutState = remember { SceneTransitionLayoutState(currentScene) },
-    edgeDetector: EdgeDetector = DefaultEdgeDetector,
+    swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
     @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f,
     scenes: SceneTransitionLayoutScope.() -> Unit,
 ) {
-    SceneTransitionLayoutForTesting(
-        currentScene,
-        onChangeScene,
-        modifier,
-        transitions,
+    val state = updateSceneTransitionLayoutState(currentScene, onChangeScene, transitions)
+    SceneTransitionLayout(
         state,
-        edgeDetector,
+        modifier,
+        swipeSourceDetector,
         transitionInterceptionThreshold,
-        onLayoutImpl = null,
         scenes,
     )
 }
@@ -87,7 +119,7 @@
      */
     fun scene(
         key: SceneKey,
-        userActions: Map<UserAction, SceneKey> = emptyMap(),
+        userActions: Map<UserAction, UserActionResult> = emptyMap(),
         content: @Composable SceneScope.() -> Unit,
     )
 }
@@ -203,18 +235,6 @@
     ): Modifier
 
     /**
-     * Punch a hole in this [element] using the bounds of [bounds] in [scene] and the given [shape].
-     *
-     * Punching a hole in an element will "remove" any pixel drawn by that element in the hole area.
-     * This can be used to make content drawn below an opaque element visible. For example, if we
-     * have [this lockscreen scene](http://shortn/_VYySFnJDhN) drawn below
-     * [this shade scene](http://shortn/_fpxGUk0Rg7) and punch a hole in the latter using the big
-     * clock time bounds and a RoundedCornerShape(10dp), [this](http://shortn/_qt80IvORFj) would be
-     * the result.
-     */
-    fun Modifier.punchHole(element: ElementKey, bounds: ElementKey, shape: Shape): Modifier
-
-    /**
      * Don't resize during transitions. This can for instance be used to make sure that scrollable
      * lists keep a constant size during transitions even if its elements are growing/shrinking.
      */
@@ -321,7 +341,7 @@
 data class Swipe(
     val direction: SwipeDirection,
     val pointerCount: Int = 1,
-    val fromEdge: Edge? = null,
+    val fromSource: SwipeSource? = null,
 ) : UserAction {
     companion object {
         val Left = Swipe(SwipeDirection.Left)
@@ -339,6 +359,95 @@
 }
 
 /**
+ * The source of a Swipe.
+ *
+ * Important: This can be anything that can be returned by any [SwipeSourceDetector], but this must
+ * implement [equals] and [hashCode]. Note that those can be trivially implemented using data
+ * classes.
+ */
+interface SwipeSource {
+    // Require equals() and hashCode() to be implemented.
+    override fun equals(other: Any?): Boolean
+
+    override fun hashCode(): Int
+}
+
+interface SwipeSourceDetector {
+    /**
+     * Return the [SwipeSource] associated to [position] inside a layout of size [layoutSize], given
+     * [density] and [orientation].
+     */
+    fun source(
+        layoutSize: IntSize,
+        position: IntOffset,
+        density: Density,
+        orientation: Orientation,
+    ): SwipeSource?
+}
+
+/**
+ * The result of performing a [UserAction].
+ *
+ * Note: [UserActionResult] is implemented by [SceneKey], and you can also use [withDistance] to
+ * easily create a [UserActionResult] with a fixed distance:
+ * ```
+ * SceneTransitionLayout(...) {
+ *     scene(
+ *         Scenes.Foo,
+ *         userActions =
+ *             mapOf(
+ *                 Swipe.Right to Scene.Bar,
+ *                 Swipe.Down to Scene.Doe withDistance 100.dp,
+ *             )
+ *         )
+ *     ) { ... }
+ * }
+ * ```
+ */
+interface UserActionResult {
+    /** The scene we should be transitioning to during the [UserAction]. */
+    val toScene: SceneKey
+
+    /**
+     * The distance the action takes to animate from 0% to 100%.
+     *
+     * If `null`, a default distance will be used that depends on the [UserAction] performed.
+     */
+    val distance: UserActionDistance?
+}
+
+interface UserActionDistance {
+    /**
+     * Return the **absolute** distance of the user action given the size of the scene we are
+     * animating from and the [orientation].
+     */
+    fun Density.absoluteDistance(fromSceneSize: IntSize, orientation: Orientation): Float
+}
+
+/**
+ * A utility function to make it possible to define user actions with a distance using the syntax
+ * `Swipe.Up to Scene.foo withDistance 100.dp`
+ */
+infix fun Pair<UserAction, SceneKey>.withDistance(
+    distance: Dp
+): Pair<UserAction, UserActionResult> {
+    val scene = second
+    val distance = FixedDistance(distance)
+    return first to
+        object : UserActionResult {
+            override val toScene: SceneKey = scene
+            override val distance: UserActionDistance = distance
+        }
+}
+
+/** The user action has a fixed [absoluteDistance]. */
+private class FixedDistance(private val distance: Dp) : UserActionDistance {
+    override fun Density.absoluteDistance(fromSceneSize: IntSize, orientation: Orientation): Float {
+        return distance.toPx()
+    }
+}
+
+/**
  * An internal version of [SceneTransitionLayout] to be used for tests.
  *
  * Important: You should use this only in tests and if you need to access the underlying
@@ -346,12 +455,9 @@
  */
 @Composable
 internal fun SceneTransitionLayoutForTesting(
-    currentScene: SceneKey,
-    onChangeScene: (SceneKey) -> Unit,
+    state: SceneTransitionLayoutState,
     modifier: Modifier = Modifier,
-    transitions: SceneTransitions = transitions {},
-    state: SceneTransitionLayoutState = remember { SceneTransitionLayoutState(currentScene) },
-    edgeDetector: EdgeDetector = DefaultEdgeDetector,
+    swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
     transitionInterceptionThreshold: Float = 0f,
     onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null,
     scenes: SceneTransitionLayoutScope.() -> Unit,
@@ -360,10 +466,9 @@
     val coroutineScope = rememberCoroutineScope()
     val layoutImpl = remember {
         SceneTransitionLayoutImpl(
-                state = state as SceneTransitionLayoutStateImpl,
-                onChangeScene = onChangeScene,
+                state = state as BaseSceneTransitionLayoutState,
                 density = density,
-                edgeDetector = edgeDetector,
+                swipeSourceDetector = swipeSourceDetector,
                 transitionInterceptionThreshold = transitionInterceptionThreshold,
                 builder = scenes,
                 coroutineScope = coroutineScope,
@@ -375,7 +480,6 @@
     // SnapshotStateMap anymore.
     layoutImpl.updateScenes(scenes)
 
-    val targetSceneChannel = remember { Channel<SceneKey>(Channel.CONFLATED) }
     SideEffect {
         if (state != layoutImpl.state) {
             error(
@@ -384,23 +488,8 @@
             )
         }
 
-        layoutImpl.onChangeScene = onChangeScene
-        (state as SceneTransitionLayoutStateImpl).transitions = transitions
         layoutImpl.density = density
-        layoutImpl.edgeDetector = edgeDetector
-
-        state.transitions = transitions
-
-        targetSceneChannel.trySend(currentScene)
-    }
-
-    LaunchedEffect(targetSceneChannel) {
-        for (newKey in targetSceneChannel) {
-            // Inspired by AnimateAsState.kt: let's poll the last value to avoid being one frame
-            // late.
-            val newKey = targetSceneChannel.tryReceive().getOrNull() ?: newKey
-            animateToScene(layoutImpl.state, newKey)
-        }
+        layoutImpl.swipeSourceDetector = swipeSourceDetector
     }
 
     layoutImpl.Content(modifier)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 0227aba..8c5a472 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -20,14 +20,11 @@
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.key
 import androidx.compose.runtime.snapshots.SnapshotStateMap
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawWithContent
 import androidx.compose.ui.layout.LookaheadScope
 import androidx.compose.ui.layout.intermediateLayout
 import androidx.compose.ui.unit.Density
@@ -48,13 +45,12 @@
 
 @Stable
 internal class SceneTransitionLayoutImpl(
-    internal val state: SceneTransitionLayoutStateImpl,
-    internal var onChangeScene: (SceneKey) -> Unit,
+    internal val state: BaseSceneTransitionLayoutState,
     internal var density: Density,
-    internal var edgeDetector: EdgeDetector,
+    internal var swipeSourceDetector: SwipeSourceDetector,
     internal var transitionInterceptionThreshold: Float,
     builder: SceneTransitionLayoutScope.() -> Unit,
-    coroutineScope: CoroutineScope,
+    private val coroutineScope: CoroutineScope,
 ) {
     /**
      * The map of [Scene]s.
@@ -100,16 +96,6 @@
                 ?: mutableMapOf<ValueKey, MutableMap<ElementKey?, SnapshotStateMap<SceneKey, *>>>()
                     .also { _sharedValues = it }
 
-    /**
-     * The scenes that are "ready", i.e. they were composed and fully laid-out at least once.
-     *
-     * Note that this map is *read* during composition, so it is a [SnapshotStateMap] to make sure
-     * that we recompose when modifications are made to this map.
-     *
-     * TODO(b/316901148): Remove this map.
-     */
-    private val readyScenes = SnapshotStateMap<SceneKey, Boolean>()
-
     private val horizontalGestureHandler: SceneGestureHandler
     private val verticalGestureHandler: SceneGestureHandler
 
@@ -154,7 +140,7 @@
         object : SceneTransitionLayoutScope {
                 override fun scene(
                     key: SceneKey,
-                    userActions: Map<UserAction, SceneKey>,
+                    userActions: Map<UserAction, UserActionResult>,
                     content: @Composable SceneScope.() -> Unit,
                 ) {
                     scenesToRemove.remove(key)
@@ -243,50 +229,22 @@
                 // Handle back events.
                 // TODO(b/290184746): Make sure that this works with SystemUI once we use
                 // SceneTransitionLayout in Flexiglass.
-                scene(state.transitionState.currentScene).userActions[Back]?.let { backScene ->
-                    BackHandler { onChangeScene(backScene) }
+                scene(state.transitionState.currentScene).userActions[Back]?.let { result ->
+                    // TODO(b/290184746): Handle predictive back and use result.distance if
+                    // specified.
+                    BackHandler { with(state) { coroutineScope.onChangeScene(result.toScene) } }
                 }
 
                 Box {
                     scenesToCompose.fastForEach { scene ->
                         val key = scene.key
-                        key(key) {
-                            // Mark this scene as ready once it has been composed, laid out and
-                            // drawn the first time. We have to do this in a LaunchedEffect here
-                            // because DisposableEffect runs between composition and layout.
-                            LaunchedEffect(key) { readyScenes[key] = true }
-                            DisposableEffect(key) { onDispose { readyScenes.remove(key) } }
-
-                            scene.Content(
-                                Modifier.drawWithContent {
-                                    if (state.currentTransition == null) {
-                                        drawContent()
-                                    } else {
-                                        // Don't draw scenes that are not ready yet.
-                                        if (readyScenes.containsKey(key)) {
-                                            drawContent()
-                                        }
-                                    }
-                                }
-                            )
-                        }
+                        key(key) { scene.Content() }
                     }
                 }
             }
         }
     }
 
-    /**
-     * Return whether [transition] is ready, i.e. the elements of both scenes of the transition were
-     * laid out at least once.
-     */
-    internal fun isTransitionReady(transition: TransitionState.Transition): Boolean {
-        return readyScenes.containsKey(transition.fromScene) &&
-            readyScenes.containsKey(transition.toScene)
-    }
-
-    internal fun isSceneReady(scene: SceneKey): Boolean = readyScenes.containsKey(scene)
-
     internal fun setScenesTargetSizeForTest(size: IntSize) {
         scenes.values.forEach { it.targetSize = size }
     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 0607aa1..956e326 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -16,12 +16,23 @@
 
 package com.android.compose.animation.scene
 
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.Channel
 
-/** The state of a [SceneTransitionLayout]. */
+/**
+ * The state of a [SceneTransitionLayout].
+ *
+ * @see MutableSceneTransitionLayoutState
+ * @see updateSceneTransitionLayoutState
+ */
 @Stable
 sealed interface SceneTransitionLayoutState {
     /**
@@ -36,6 +47,9 @@
     val currentTransition: TransitionState.Transition?
         get() = transitionState as? TransitionState.Transition
 
+    /** The [SceneTransitions] used when animating this state. */
+    val transitions: SceneTransitions
+
     /**
      * Whether we are transitioning. If [from] or [to] is empty, we will also check that they match
      * the scenes we are animating from and/or to.
@@ -46,9 +60,68 @@
     fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean
 }
 
-/** Create a new [SceneTransitionLayoutState] that is currently idle at scene [currentScene]. */
-fun SceneTransitionLayoutState(currentScene: SceneKey): SceneTransitionLayoutState {
-    return SceneTransitionLayoutStateImpl(currentScene, SceneTransitions.Empty)
+/** A [SceneTransitionLayoutState] whose target scene can be imperatively set. */
+sealed interface MutableSceneTransitionLayoutState : SceneTransitionLayoutState {
+    /** The [SceneTransitions] used when animating this state. */
+    override var transitions: SceneTransitions
+
+    /**
+     * Set the target scene of this state to [targetScene].
+     *
+     * If [targetScene] is the same as the [currentScene][TransitionState.currentScene] of
+     * [transitionState], then nothing will happen and this will return `null`. Note that this means
+     * that this will also do nothing if the user is currently swiping from [targetScene] to another
+     * scene, or if we were already animating to [targetScene].
+     *
+     * If [targetScene] is different than the [currentScene][TransitionState.currentScene] of
+     * [transitionState], then this will animate to [targetScene]. The associated
+     * [TransitionState.Transition] will be returned and will be set as the current
+     * [transitionState] of this [MutableSceneTransitionLayoutState].
+     *
+     * Note that because a non-null [TransitionState.Transition] is returned does not mean that the
+     * transition will finish and that we will settle to [targetScene]. The returned transition
+     * might still be interrupted, for instance by another call to [setTargetScene] or by a user
+     * gesture.
+     *
+     * If [this] [CoroutineScope] is cancelled during the transition and that the transition was
+     * still active, then the [transitionState] of this [MutableSceneTransitionLayoutState] will be
+     * set to `TransitionState.Idle(targetScene)`.
+     *
+     * TODO(b/318794193): Add APIs to await() and cancel() any [TransitionState.Transition].
+     */
+    fun setTargetScene(
+        targetScene: SceneKey,
+        coroutineScope: CoroutineScope,
+    ): TransitionState.Transition?
+}
+
+/** Return a [MutableSceneTransitionLayoutState] initially idle at [initialScene]. */
+fun MutableSceneTransitionLayoutState(
+    initialScene: SceneKey,
+    transitions: SceneTransitions = SceneTransitions.Empty,
+): MutableSceneTransitionLayoutState {
+    return MutableSceneTransitionLayoutStateImpl(initialScene, transitions)
+}
+
+/**
+ * Sets up a [SceneTransitionLayoutState] and keeps it synced with [currentScene], [onChangeScene]
+ * and [transitions]. New transitions will automatically be started whenever [currentScene] is
+ * changed.
+ *
+ * @param currentScene the current scene
+ * @param onChangeScene a mutator that should set [currentScene] to the given scene when called.
+ *   This is called when the user commits a transition to a new scene because of a [UserAction], for
+ *   instance by triggering back navigation or by swiping to a new scene.
+ * @param transitions the definition of the transitions used to animate a change of scene.
+ */
+@Composable
+fun updateSceneTransitionLayoutState(
+    currentScene: SceneKey,
+    onChangeScene: (SceneKey) -> Unit,
+    transitions: SceneTransitions = SceneTransitions.Empty,
+): SceneTransitionLayoutState {
+    return remember { HoistedSceneTransitionLayoutScene(currentScene, transitions, onChangeScene) }
+        .apply { update(currentScene, onChangeScene, transitions) }
 }
 
 @Stable
@@ -109,13 +182,11 @@
     }
 }
 
-internal class SceneTransitionLayoutStateImpl(
-    initialScene: SceneKey,
-    internal var transitions: SceneTransitions,
-) : SceneTransitionLayoutState {
+internal abstract class BaseSceneTransitionLayoutState(initialScene: SceneKey) :
+    SceneTransitionLayoutState {
     override var transitionState: TransitionState by
         mutableStateOf(TransitionState.Idle(initialScene))
-        private set
+        protected set
 
     /**
      * The current [transformationSpec] associated to [transitionState]. Accessing this value makes
@@ -123,6 +194,14 @@
      */
     internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty
 
+    /**
+     * Called when the [current scene][TransitionState.currentScene] should be changed to [scene].
+     *
+     * When this is called, the source of truth for the current scene should be changed so that
+     * [transitionState] will animate and settle to [scene].
+     */
+    internal abstract fun CoroutineScope.onChangeScene(scene: SceneKey)
+
     override fun isTransitioning(from: SceneKey?, to: SceneKey?): Boolean {
         val transition = currentTransition ?: return false
         return transition.isTransitioning(from, to)
@@ -154,3 +233,62 @@
         }
     }
 }
+
+/**
+ * A [SceneTransitionLayout] whose current scene/source of truth is hoisted (its current value comes
+ * from outside).
+ */
+internal class HoistedSceneTransitionLayoutScene(
+    initialScene: SceneKey,
+    override var transitions: SceneTransitions,
+    private var changeScene: (SceneKey) -> Unit,
+) : BaseSceneTransitionLayoutState(initialScene) {
+    private val targetSceneChannel = Channel<SceneKey>(Channel.CONFLATED)
+
+    override fun CoroutineScope.onChangeScene(scene: SceneKey) = changeScene(scene)
+
+    @Composable
+    fun update(
+        currentScene: SceneKey,
+        onChangeScene: (SceneKey) -> Unit,
+        transitions: SceneTransitions,
+    ) {
+        SideEffect {
+            this.changeScene = onChangeScene
+            this.transitions = transitions
+
+            targetSceneChannel.trySend(currentScene)
+        }
+
+        LaunchedEffect(targetSceneChannel) {
+            for (newKey in targetSceneChannel) {
+                // Inspired by AnimateAsState.kt: let's poll the last value to avoid being one frame
+                // late.
+                val newKey = targetSceneChannel.tryReceive().getOrNull() ?: newKey
+                animateToScene(layoutState = this@HoistedSceneTransitionLayoutScene, newKey)
+            }
+        }
+    }
+}
+
+/** A [MutableSceneTransitionLayoutState] that holds the value for the current scene. */
+internal class MutableSceneTransitionLayoutStateImpl(
+    initialScene: SceneKey,
+    override var transitions: SceneTransitions,
+) : MutableSceneTransitionLayoutState, BaseSceneTransitionLayoutState(initialScene) {
+    override fun setTargetScene(
+        targetScene: SceneKey,
+        coroutineScope: CoroutineScope
+    ): TransitionState.Transition? {
+        return with(this) {
+            coroutineScope.animateToScene(
+                layoutState = this@MutableSceneTransitionLayoutStateImpl,
+                target = targetScene,
+            )
+        }
+    }
+
+    override fun CoroutineScope.onChangeScene(scene: SceneKey) {
+        setTargetScene(scene, coroutineScope = this)
+    }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index 3a55567..ac423b7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -17,7 +17,10 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.SpringSpec
 import androidx.compose.animation.core.snap
+import androidx.compose.animation.core.spring
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.util.fastForEach
@@ -36,6 +39,7 @@
 /** The transitions configuration of a [SceneTransitionLayout]. */
 class SceneTransitions
 internal constructor(
+    internal val defaultSwipeSpec: SpringSpec<Float>,
     internal val transitionSpecs: List<TransitionSpecImpl>,
 ) {
     private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpecImpl>>()
@@ -91,7 +95,13 @@
         TransitionSpecImpl(from, to, TransformationSpec.EmptyProvider)
 
     companion object {
-        val Empty = SceneTransitions(transitionSpecs = emptyList())
+        internal val DefaultSwipeSpec =
+            spring(
+                stiffness = Spring.StiffnessMediumLow,
+                visibilityThreshold = OffsetVisibilityThreshold,
+            )
+
+        val Empty = SceneTransitions(DefaultSwipeSpec, transitionSpecs = emptyList())
     }
 }
 
@@ -125,15 +135,30 @@
 }
 
 interface TransformationSpec {
-    /** The [AnimationSpec] used to animate the associated transition progress. */
+    /**
+     * The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
+     * the transition is triggered (i.e. it is not gesture-based).
+     */
     val progressSpec: AnimationSpec<Float>
 
+    /**
+     * The [SpringSpec] used to animate the associated transition progress when the transition was
+     * started by a swipe and is now animating back to a scene because the user lifted their finger.
+     *
+     * If `null`, then the [SceneTransitions.defaultSwipeSpec] will be used.
+     */
+    val swipeSpec: SpringSpec<Float>?
+
     /** The list of [Transformation] applied to elements during this transition. */
     val transformations: List<Transformation>
 
     companion object {
         internal val Empty =
-            TransformationSpecImpl(progressSpec = snap(), transformations = emptyList())
+            TransformationSpecImpl(
+                progressSpec = snap(),
+                swipeSpec = null,
+                transformations = emptyList(),
+            )
         internal val EmptyProvider = { Empty }
     }
 }
@@ -151,6 +176,7 @@
                 val reverse = transformationSpec.invoke()
                 TransformationSpecImpl(
                     progressSpec = reverse.progressSpec,
+                    swipeSpec = reverse.swipeSpec,
                     transformations = reverse.transformations.map { it.reversed() }
                 )
             }
@@ -166,6 +192,7 @@
  */
 internal class TransformationSpecImpl(
     override val progressSpec: AnimationSpec<Float>,
+    override val swipeSpec: SpringSpec<Float>?,
     override val transformations: List<Transformation>,
 ) : TransformationSpec {
     private val cache = mutableMapOf<ElementKey, MutableMap<SceneKey, ElementTransformations>>()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 0d3bc7d..61f4978 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -17,40 +17,96 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.PointerEvent
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.PointerInputModifierNode
+import androidx.compose.ui.unit.IntSize
 
 /**
  * Configures the swipeable behavior of a [SceneTransitionLayout] depending on the current state.
  */
+@Stable
 internal fun Modifier.swipeToScene(gestureHandler: SceneGestureHandler): Modifier {
-    /** Whether swipe should be enabled in the given [orientation]. */
-    fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean =
-        userActions.keys.any { it is Swipe && it.direction.orientation == orientation }
+    return this.then(SwipeToSceneElement(gestureHandler))
+}
 
-    val layoutImpl = gestureHandler.layoutImpl
-    val currentScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
-    val orientation = gestureHandler.orientation
-    val canSwipe = currentScene.shouldEnableSwipes(orientation)
-    val canOppositeSwipe =
-        currentScene.shouldEnableSwipes(
-            when (orientation) {
+private data class SwipeToSceneElement(
+    val gestureHandler: SceneGestureHandler,
+) : ModifierNodeElement<SwipeToSceneNode>() {
+    override fun create(): SwipeToSceneNode = SwipeToSceneNode(gestureHandler)
+
+    override fun update(node: SwipeToSceneNode) {
+        node.gestureHandler = gestureHandler
+    }
+}
+
+private class SwipeToSceneNode(
+    gestureHandler: SceneGestureHandler,
+) : DelegatingNode(), PointerInputModifierNode {
+    private val delegate =
+        delegate(
+            MultiPointerDraggableNode(
+                orientation = gestureHandler.orientation,
+                enabled = ::enabled,
+                startDragImmediately = ::startDragImmediately,
+                onDragStarted = gestureHandler.draggable::onDragStarted,
+                onDragDelta = gestureHandler.draggable::onDelta,
+                onDragStopped = gestureHandler.draggable::onDragStopped,
+            )
+        )
+
+    var gestureHandler: SceneGestureHandler = gestureHandler
+        set(value) {
+            if (value != field) {
+                field = value
+
+                // Make sure to update the delegate orientation. Note that this will automatically
+                // reset the underlying pointer input handler, so previous gestures will be
+                // cancelled.
+                delegate.orientation = value.orientation
+            }
+        }
+
+    override fun onPointerEvent(
+        pointerEvent: PointerEvent,
+        pass: PointerEventPass,
+        bounds: IntSize,
+    ) = delegate.onPointerEvent(pointerEvent, pass, bounds)
+
+    override fun onCancelPointerInput() = delegate.onCancelPointerInput()
+
+    private fun enabled(): Boolean {
+        return gestureHandler.isDrivingTransition ||
+            currentScene().shouldEnableSwipes(gestureHandler.orientation)
+    }
+
+    private fun currentScene(): Scene {
+        val layoutImpl = gestureHandler.layoutImpl
+        return layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
+    }
+
+    /** Whether swipe should be enabled in the given [orientation]. */
+    private fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean {
+        return userActions.keys.any { it is Swipe && it.direction.orientation == orientation }
+    }
+
+    private fun startDragImmediately(startedPosition: Offset): Boolean {
+        // Immediately start the drag if the user can't swipe in the other direction and the gesture
+        // handler can intercept it.
+        return !canOppositeSwipe() && gestureHandler.shouldImmediatelyIntercept(startedPosition)
+    }
+
+    private fun canOppositeSwipe(): Boolean {
+        val oppositeOrientation =
+            when (gestureHandler.orientation) {
                 Orientation.Vertical -> Orientation.Horizontal
                 Orientation.Horizontal -> Orientation.Vertical
             }
-        )
-
-    return multiPointerDraggable(
-        orientation = orientation,
-        enabled = gestureHandler.isDrivingTransition || canSwipe,
-        // Immediately start the drag if this our [transition] is currently animating to a scene
-        // (i.e. the user released their input pointer after swiping in this orientation) and the
-        // user can't swipe in the other direction.
-        startDragImmediately =
-            gestureHandler.isDrivingTransition &&
-                gestureHandler.swipeTransition.isAnimatingOffset &&
-                !canOppositeSwipe,
-        onDragStarted = gestureHandler.draggable::onDragStarted,
-        onDragDelta = gestureHandler.draggable::onDelta,
-        onDragStopped = gestureHandler.draggable::onDragStopped,
-    )
+        return currentScene().shouldEnableSwipes(oppositeOrientation)
+    }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index dc8505c..04e0937 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -17,6 +17,7 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.SpringSpec
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
@@ -31,6 +32,12 @@
 @TransitionDsl
 interface SceneTransitionsBuilder {
     /**
+     * The default [AnimationSpec] used when after the user lifts their finger after starting a
+     * swipe to transition, to animate back into one of the 2 scenes we are transitioning to.
+     */
+    var defaultSwipeSpec: SpringSpec<Float>
+
+    /**
      * Define the default animation to be played when transitioning [to] the specified scene, from
      * any scene. For the animation specification to apply only when transitioning between two
      * specific scenes, use [from] instead.
@@ -64,12 +71,20 @@
 @TransitionDsl
 interface TransitionBuilder : PropertyTransformationBuilder {
     /**
-     * The [AnimationSpec] used to animate the progress of this transition from `0` to `1` when
-     * performing programmatic (not input pointer tracking) animations.
+     * The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
+     * the transition is triggered (i.e. it is not gesture-based).
      */
     var spec: AnimationSpec<Float>
 
     /**
+     * The [SpringSpec] used to animate the associated transition progress when the transition was
+     * started by a swipe and is now animating back to a scene because the user lifted their finger.
+     *
+     * If `null`, then the [SceneTransitionsBuilder.defaultSwipeSpec] will be used.
+     */
+    var swipeSpec: SpringSpec<Float>?
+
+    /**
      * Define a progress-based range for the transformations inside [builder].
      *
      * For instance, the following will fade `Foo` during the first half of the transition then it
@@ -320,11 +335,3 @@
         anchorHeight: Boolean = true,
     )
 }
-
-/** The edge of a [SceneTransitionLayout]. */
-enum class Edge {
-    Left,
-    Right,
-    Top,
-    Bottom,
-}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index b96f9be..df186a1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -19,6 +19,7 @@
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.DurationBasedAnimationSpec
 import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.SpringSpec
 import androidx.compose.animation.core.VectorConverter
 import androidx.compose.animation.core.spring
 import androidx.compose.ui.geometry.Offset
@@ -40,10 +41,12 @@
     builder: SceneTransitionsBuilder.() -> Unit,
 ): SceneTransitions {
     val impl = SceneTransitionsBuilderImpl().apply(builder)
-    return SceneTransitions(impl.transitionSpecs)
+    return SceneTransitions(impl.defaultSwipeSpec, impl.transitionSpecs)
 }
 
 private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
+    override var defaultSwipeSpec: SpringSpec<Float> = SceneTransitions.DefaultSwipeSpec
+
     val transitionSpecs = mutableListOf<TransitionSpecImpl>()
 
     override fun to(to: SceneKey, builder: TransitionBuilder.() -> Unit): TransitionSpec {
@@ -67,6 +70,7 @@
             val impl = TransitionBuilderImpl().apply(builder)
             return TransformationSpecImpl(
                 progressSpec = impl.spec,
+                swipeSpec = impl.swipeSpec,
                 transformations = impl.transformations,
             )
         }
@@ -80,6 +84,7 @@
 internal class TransitionBuilderImpl : TransitionBuilder {
     val transformations = mutableListOf<Transformation>()
     override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
+    override var swipeSpec: SpringSpec<Float>? = null
 
     private var range: TransformationRange? = null
     private var reversed = false
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
index 2c96d0e..8e35988 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
@@ -36,24 +36,25 @@
 fun LargeTopAppBarNestedScrollConnection(
     height: () -> Float,
     onHeightChanged: (Float) -> Unit,
-    heightRange: ClosedFloatingPointRange<Float>,
+    minHeight: () -> Float,
+    maxHeight: () -> Float,
 ): PriorityNestedScrollConnection {
-    val minHeight = heightRange.start
-    val maxHeight = heightRange.endInclusive
     return PriorityNestedScrollConnection(
         orientation = Orientation.Vertical,
         // When swiping up, the LargeTopAppBar will shrink (to [minHeight]) and the content will
         // expand. Then, you can then scroll down the content.
         canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
-            offsetAvailable < 0 && offsetBeforeStart == 0f && height() > minHeight
+            offsetAvailable < 0 && offsetBeforeStart == 0f && height() > minHeight()
         },
         // When swiping down, the content will scroll up until it reaches the top. Then, the
         // LargeTopAppBar will expand until it reaches its [maxHeight].
-        canStartPostScroll = { offsetAvailable, _ -> offsetAvailable > 0 && height() < maxHeight },
+        canStartPostScroll = { offsetAvailable, _ ->
+            offsetAvailable > 0 && height() < maxHeight()
+        },
         canStartPostFling = { false },
         canContinueScroll = {
             val currentHeight = height()
-            minHeight < currentHeight && currentHeight < maxHeight
+            minHeight() < currentHeight && currentHeight < maxHeight()
         },
         canScrollOnFling = true,
         onStart = { /* do nothing */},
@@ -61,10 +62,10 @@
             val currentHeight = height()
             val amountConsumed =
                 if (offsetAvailable > 0) {
-                    val amountLeft = maxHeight - currentHeight
+                    val amountLeft = maxHeight() - currentHeight
                     offsetAvailable.coerceAtMost(amountLeft)
                 } else {
-                    val amountLeft = minHeight - currentHeight
+                    val amountLeft = minHeight() - currentHeight
                     offsetAvailable.coerceAtLeast(amountLeft)
                 }
             onHeightChanged(currentHeight + amountConsumed)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index 2841bcf..ac11d30 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -22,6 +22,7 @@
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.unit.Velocity
 import com.android.compose.ui.util.SpaceVectorConverter
+import kotlin.math.sign
 
 /**
  * This [NestedScrollConnection] waits for a child to scroll ([onPreScroll] or [onPostScroll]), and
@@ -117,7 +118,12 @@
             return Velocity.Zero
         }
 
-        onPriorityStart(available = Offset.Zero)
+        // The offset passed to onPriorityStart() must be != 0f, so we create a small offset of 1px
+        // given the available velocity.
+        // TODO(b/291053278): Remove canStartPostFling() and instead make it possible to define the
+        // overscroll behavior on the Scene level.
+        val smallOffset = Offset(available.x.sign, available.y.sign)
+        onPriorityStart(available = smallOffset)
 
         // This is the last event of a scroll gesture.
         return onPriorityStop(available)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 35a5054..c0de87a 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.compose.animation.scene
 
-import androidx.compose.animation.core.LinearEasing
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.layout.Box
@@ -35,17 +34,11 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.layout.intermediateLayout
-import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.DpOffset
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.compose.test.subjects.DpOffsetSubject
-import com.android.compose.test.subjects.assertThat
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
@@ -263,8 +256,11 @@
 
         rule.setContent {
             SceneTransitionLayoutForTesting(
-                currentScene = currentScene,
-                onChangeScene = { currentScene = it },
+                state =
+                    updateSceneTransitionLayoutState(
+                        currentScene = currentScene,
+                        onChangeScene = { currentScene = it }
+                    ),
                 onLayoutImpl = { nullableLayoutImpl = it },
             ) {
                 scene(TestScenes.SceneA) { /* Nothing */}
@@ -428,8 +424,11 @@
 
         rule.setContent {
             SceneTransitionLayoutForTesting(
-                currentScene = TestScenes.SceneA,
-                onChangeScene = {},
+                state =
+                    updateSceneTransitionLayoutState(
+                        currentScene = TestScenes.SceneA,
+                        onChangeScene = {}
+                    ),
                 onLayoutImpl = { nullableLayoutImpl = it },
             ) {
                 scene(TestScenes.SceneA) { Box(Modifier.element(key)) }
@@ -478,8 +477,11 @@
             scrollScope = rememberCoroutineScope()
 
             SceneTransitionLayoutForTesting(
-                currentScene = TestScenes.SceneA,
-                onChangeScene = {},
+                state =
+                    updateSceneTransitionLayoutState(
+                        currentScene = TestScenes.SceneA,
+                        onChangeScene = {}
+                    ),
                 onLayoutImpl = { nullableLayoutImpl = it },
             ) {
                 scene(TestScenes.SceneA) {
@@ -565,86 +567,4 @@
             after { assertThat(fooCompositions).isEqualTo(1) }
         }
     }
-
-    @Test
-    fun sharedElementOffsetIsUpdatedEvenWhenNotPlaced() {
-        var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
-        var density: Density? = null
-
-        fun layoutImpl() = nullableLayoutImpl ?: error("nullableLayoutImpl was not set")
-
-        fun density() = density ?: error("density was not set")
-
-        fun Offset.toDpOffset() = with(density()) { DpOffset(x.toDp(), y.toDp()) }
-
-        fun foo() = layoutImpl().elements[TestElements.Foo] ?: error("Foo not in elements map")
-
-        fun Element.lastSharedOffset() = lastSharedState.offset.toDpOffset()
-
-        fun Element.lastOffsetIn(scene: SceneKey) =
-            (sceneStates[scene] ?: error("$scene not in sceneValues map"))
-                .lastState
-                .offset
-                .toDpOffset()
-
-        rule.testTransition(
-            from = TestScenes.SceneA,
-            to = TestScenes.SceneB,
-            transitionLayout = { currentScene, onChangeScene ->
-                density = LocalDensity.current
-
-                SceneTransitionLayoutForTesting(
-                    currentScene = currentScene,
-                    onChangeScene = onChangeScene,
-                    onLayoutImpl = { nullableLayoutImpl = it },
-                    transitions =
-                        transitions {
-                            from(TestScenes.SceneA, to = TestScenes.SceneB) {
-                                spec = tween(durationMillis = 4 * 16, easing = LinearEasing)
-                            }
-                        }
-                ) {
-                    scene(TestScenes.SceneA) { Box(Modifier.element(TestElements.Foo)) }
-                    scene(TestScenes.SceneB) {
-                        Box(Modifier.offset(x = 40.dp, y = 80.dp).element(TestElements.Foo))
-                    }
-                }
-            }
-        ) {
-            val tolerance = DpOffsetSubject.DefaultTolerance
-
-            before {
-                val expected = DpOffset(0.dp, 0.dp)
-                assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected)
-                assertThat(foo().lastOffsetIn(TestScenes.SceneA)).isWithin(tolerance).of(expected)
-            }
-
-            at(16) {
-                val expected = DpOffset(10.dp, 20.dp)
-                assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected)
-                assertThat(foo().lastOffsetIn(TestScenes.SceneA)).isWithin(tolerance).of(expected)
-                assertThat(foo().lastOffsetIn(TestScenes.SceneB)).isWithin(tolerance).of(expected)
-            }
-
-            at(32) {
-                val expected = DpOffset(20.dp, 40.dp)
-                assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected)
-                assertThat(foo().lastOffsetIn(TestScenes.SceneA)).isWithin(tolerance).of(expected)
-                assertThat(foo().lastOffsetIn(TestScenes.SceneB)).isWithin(tolerance).of(expected)
-            }
-
-            at(48) {
-                val expected = DpOffset(30.dp, 60.dp)
-                assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected)
-                assertThat(foo().lastOffsetIn(TestScenes.SceneA)).isWithin(tolerance).of(expected)
-                assertThat(foo().lastOffsetIn(TestScenes.SceneB)).isWithin(tolerance).of(expected)
-            }
-
-            after {
-                val expected = DpOffset(40.dp, 80.dp)
-                assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected)
-                assertThat(foo().lastOffsetIn(TestScenes.SceneB)).isWithin(tolerance).of(expected)
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt
index a68282a..cceaf57 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt
@@ -35,7 +35,7 @@
     @Test
     fun horizontalEdges() {
         fun horizontalEdge(position: Int): Edge? =
-            detector.edge(
+            detector.source(
                 layoutSize,
                 position = IntOffset(position, 0),
                 density,
@@ -53,7 +53,7 @@
     @Test
     fun verticalEdges() {
         fun verticalEdge(position: Int): Edge? =
-            detector.edge(
+            detector.source(
                 layoutSize,
                 position = IntOffset(0, position),
                 density,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
index 04b3f8a..0f9b024 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
@@ -32,7 +32,7 @@
 
     @Test
     fun testObservableTransitionState() = runTest {
-        val state = SceneTransitionLayoutState(TestScenes.SceneA)
+        lateinit var state: SceneTransitionLayoutState
 
         // Collect the current observable state into [observableState].
         // TODO(b/290184746): Use collectValues {} once it is extracted into a library that can be
@@ -58,12 +58,14 @@
             from = TestScenes.SceneA,
             to = TestScenes.SceneB,
             transitionLayout = { currentScene, onChangeScene ->
-                SceneTransitionLayout(
-                    currentScene,
-                    onChangeScene,
-                    EmptyTestTransitions,
-                    state = state,
-                ) {
+                state =
+                    updateSceneTransitionLayoutState(
+                        currentScene,
+                        onChangeScene,
+                        EmptyTestTransitions
+                    )
+
+                SceneTransitionLayout(state = state) {
                     scene(TestScenes.SceneA) {}
                     scene(TestScenes.SceneB) {}
                 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index d9ce519..e8cc0ec 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -18,13 +18,9 @@
 
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.material3.Text
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
-import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.Velocity
@@ -53,10 +49,8 @@
     private class TestGestureScope(
         val coroutineScope: MonotonicClockTestScope,
     ) {
-        private var internalCurrentScene: SceneKey by mutableStateOf(SceneA)
-
         private val layoutState =
-            SceneTransitionLayoutStateImpl(internalCurrentScene, EmptyTestTransitions)
+            MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
 
         val mutableUserActionsA: MutableMap<UserAction, SceneKey> =
             mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
@@ -82,7 +76,7 @@
                 userActions =
                     mapOf(
                         Swipe.Up to SceneB,
-                        Swipe(SwipeDirection.Up, fromEdge = Edge.Bottom) to SceneA
+                        Swipe(SwipeDirection.Up, fromSource = Edge.Bottom) to SceneA
                     ),
             ) {
                 Text("SceneC")
@@ -94,9 +88,8 @@
         private val layoutImpl =
             SceneTransitionLayoutImpl(
                     state = layoutState,
-                    onChangeScene = { internalCurrentScene = it },
                     density = Density(1f),
-                    edgeDetector = DefaultEdgeDetector,
+                    swipeSourceDetector = DefaultEdgeDetector,
                     transitionInterceptionThreshold = transitionInterceptionThreshold,
                     builder = scenesBuilder,
                     coroutineScope = coroutineScope,
@@ -124,9 +117,6 @@
         fun up(fractionOfScreen: Float) =
             if (fractionOfScreen < 0f) error("use down()") else -down(fractionOfScreen)
 
-        // Float tolerance for comparisons
-        val tolerance = 0.00001f
-
         // Offset y: 10% of the screen
         val offsetY10 = Offset(x = 0f, y = down(0.1f))
 
@@ -156,31 +146,42 @@
             fromScene: SceneKey? = null,
             toScene: SceneKey? = null,
             progress: Float? = null,
+            isUserInputOngoing: Boolean? = null
         ) {
+            val transition = transitionState
             assertWithMessage("transitionState must be Transition")
-                .that(transitionState is Transition)
+                .that(transition is Transition)
                 .isTrue()
+            transition as Transition
+
             if (currentScene != null)
                 assertWithMessage("currentScene does not match")
-                    .that(transitionState.currentScene)
+                    .that(transition.currentScene)
                     .isEqualTo(currentScene)
+
             if (fromScene != null)
                 assertWithMessage("fromScene does not match")
-                    .that((transitionState as? Transition)?.fromScene)
+                    .that(transition.fromScene)
                     .isEqualTo(fromScene)
+
             if (toScene != null)
                 assertWithMessage("toScene does not match")
-                    .that((transitionState as? Transition)?.toScene)
+                    .that(transition.toScene)
                     .isEqualTo(toScene)
+
             if (progress != null)
                 assertWithMessage("progress does not match")
-                    .that((transitionState as? Transition)?.progress)
-                    .isWithin(tolerance)
+                    .that(transition.progress)
+                    .isWithin(0f) // returns true when comparing 0.0f with -0.0f
                     .of(progress)
+
+            if (isUserInputOngoing != null)
+                assertWithMessage("isUserInputOngoing does not match")
+                    .that(transition.isUserInputOngoing)
+                    .isEqualTo(isUserInputOngoing)
         }
     }
 
-    @OptIn(ExperimentalTestApi::class)
     private fun runGestureTest(block: suspend TestGestureScope.() -> Unit) {
         runMonotonicClockTest { TestGestureScope(coroutineScope = this).block() }
     }
@@ -198,16 +199,14 @@
 
     @Test
     fun onDragStarted_shouldStartATransition() = runGestureTest {
-        draggable.onDragStarted()
+        draggable.onDragStarted(overSlop = down(0.1f))
         assertTransition(currentScene = SceneA)
     }
 
     @Test
     fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
-        draggable.onDragStarted()
+        draggable.onDragStarted(overSlop = down(0.1f))
         assertTransition(currentScene = SceneA)
-
-        draggable.onDelta(pixels = down(0.1f))
         assertThat(progress).isEqualTo(0.1f)
 
         draggable.onDelta(pixels = down(0.1f))
@@ -216,10 +215,7 @@
 
     @Test
     fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
-        draggable.onDragStarted()
-        assertTransition(currentScene = SceneA)
-
-        draggable.onDelta(pixels = down(0.1f))
+        draggable.onDragStarted(overSlop = down(0.1f))
         assertTransition(currentScene = SceneA)
 
         draggable.onDragStopped(
@@ -234,10 +230,7 @@
 
     @Test
     fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
-        draggable.onDragStarted()
-        assertTransition(currentScene = SceneA)
-
-        draggable.onDelta(pixels = down(0.1f))
+        draggable.onDragStarted(overSlop = down(0.1f))
         assertTransition(currentScene = SceneA)
 
         draggable.onDragStopped(velocity = velocityThreshold)
@@ -251,7 +244,7 @@
 
     @Test
     fun onDragStoppedAfterStarted_returnToIdle() = runGestureTest {
-        draggable.onDragStarted()
+        draggable.onDragStarted(overSlop = down(0.1f))
         assertTransition(currentScene = SceneA)
 
         draggable.onDragStopped(velocity = 0f)
@@ -262,8 +255,7 @@
     @Test
     fun onDragReversedDirection_changeToScene() = runGestureTest {
         // Drag A -> B with progress 0.6
-        draggable.onDragStarted()
-        draggable.onDelta(up(0.6f))
+        draggable.onDragStarted(overSlop = -60f)
         assertTransition(
             currentScene = SceneA,
             fromScene = SceneA,
@@ -272,7 +264,7 @@
         )
 
         // Reverse direction such that A -> C now with 0.4
-        draggable.onDelta(down(1f))
+        draggable.onDelta(100f)
         assertTransition(
             currentScene = SceneA,
             fromScene = SceneA,
@@ -302,7 +294,7 @@
         navigateToSceneC()
 
         // We are on SceneC which has no action in Down direction
-        draggable.onDragStarted(down(0.1f))
+        draggable.onDragStarted(10f)
         assertTransition(
             currentScene = SceneC,
             fromScene = SceneC,
@@ -311,7 +303,7 @@
         )
 
         // Reverse drag direction, it will consume the previous drag
-        draggable.onDelta(up(0.1f))
+        draggable.onDelta(-10f)
         assertTransition(
             currentScene = SceneC,
             fromScene = SceneC,
@@ -320,7 +312,7 @@
         )
 
         // Continue reverse drag direction, it should record progress to Scene B
-        draggable.onDelta(up(0.1f))
+        draggable.onDelta(-10f)
         assertTransition(
             currentScene = SceneC,
             fromScene = SceneC,
@@ -372,8 +364,7 @@
     @Test
     fun onAccelaratedScroll_scrollToThirdScene() = runGestureTest {
         // Drag A -> B with progress 0.2
-        draggable.onDragStarted()
-        draggable.onDelta(up(0.2f))
+        draggable.onDragStarted(overSlop = up(0.2f))
         assertTransition(
             currentScene = SceneA,
             fromScene = SceneA,
@@ -407,9 +398,7 @@
 
     @Test
     fun onAccelaratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest {
-        draggable.onDragStarted()
-        draggable.onDelta(up(0.2f))
-
+        draggable.onDragStarted(overSlop = up(0.2f))
         draggable.onDelta(up(0.2f))
         draggable.onDragStopped(velocity = -velocityThreshold)
         assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
@@ -465,16 +454,14 @@
         draggable.onDragStopped(down(0.1f))
 
         // now target changed to C for new drag that started before previous drag settled to Idle
-        draggable.onDragStarted(up(0.1f))
+        draggable.onDragStarted(overSlop = 0f)
+        draggable.onDelta(up(0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.3f)
     }
 
     @Test
     fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest {
-        draggable.onDragStarted()
-        assertTransition(currentScene = SceneA)
-
-        draggable.onDelta(pixels = down(0.1f))
+        draggable.onDragStarted(overSlop = down(0.1f))
         assertTransition(currentScene = SceneA)
 
         draggable.onDragStopped(
@@ -577,53 +564,53 @@
     ) {
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
         // start scene transition
-        nestedScroll.scroll(available = Offset(0f, SCREEN_SIZE * firstScroll))
+        nestedScroll.scroll(available = Offset(0f, firstScroll))
 
         // stop scene transition (start the "stop animation")
         nestedScroll.onPreFling(available = Velocity.Zero)
 
         // a pre scroll event, that could be intercepted by SceneGestureHandler
-        nestedScroll.onPreScroll(Offset(0f, SCREEN_SIZE * secondScroll), NestedScrollSource.Drag)
+        nestedScroll.onPreScroll(Offset(0f, secondScroll), NestedScrollSource.Drag)
     }
 
     @Test
     fun scrollAndFling_scrollLessThanInterceptable_goToIdleOnCurrentScene() = runGestureTest {
-        val first = transitionInterceptionThreshold - tolerance
-        val second = 0.01f
+        val firstScroll = (transitionInterceptionThreshold - 0.0001f) * SCREEN_SIZE
+        val secondScroll = 1f
 
-        preScrollAfterSceneTransition(firstScroll = first, secondScroll = second)
+        preScrollAfterSceneTransition(firstScroll = firstScroll, secondScroll = secondScroll)
 
         assertIdle(SceneA)
     }
 
     @Test
     fun scrollAndFling_scrollMinInterceptable_interceptPreScrollEvents() = runGestureTest {
-        val first = transitionInterceptionThreshold + tolerance
-        val second = 0.01f
+        val firstScroll = (transitionInterceptionThreshold + 0.0001f) * SCREEN_SIZE
+        val secondScroll = 1f
 
-        preScrollAfterSceneTransition(firstScroll = first, secondScroll = second)
+        preScrollAfterSceneTransition(firstScroll = firstScroll, secondScroll = secondScroll)
 
-        assertTransition(progress = first + second)
+        assertTransition(progress = (firstScroll + secondScroll) / SCREEN_SIZE)
     }
 
     @Test
     fun scrollAndFling_scrollMaxInterceptable_interceptPreScrollEvents() = runGestureTest {
-        val first = 1f - transitionInterceptionThreshold - tolerance
-        val second = 0.01f
+        val firstScroll = -(1f - transitionInterceptionThreshold - 0.0001f) * SCREEN_SIZE
+        val secondScroll = -1f
 
-        preScrollAfterSceneTransition(firstScroll = first, secondScroll = second)
+        preScrollAfterSceneTransition(firstScroll = firstScroll, secondScroll = secondScroll)
 
-        assertTransition(progress = first + second)
+        assertTransition(progress = -(firstScroll + secondScroll) / SCREEN_SIZE)
     }
 
     @Test
     fun scrollAndFling_scrollMoreThanInterceptable_goToIdleOnNextScene() = runGestureTest {
-        val first = 1f - transitionInterceptionThreshold + tolerance
-        val second = 0.01f
+        val firstScroll = -(1f - transitionInterceptionThreshold + 0.0001f) * SCREEN_SIZE
+        val secondScroll = -0.01f
 
-        preScrollAfterSceneTransition(firstScroll = first, secondScroll = second)
+        preScrollAfterSceneTransition(firstScroll = firstScroll, secondScroll = secondScroll)
 
-        assertIdle(SceneC)
+        assertIdle(SceneB)
     }
 
     @Test
@@ -765,31 +752,75 @@
     @Test
     fun startNestedScrollWhileDragging() = runGestureTest {
         val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
-        draggable.onDragStarted()
-        assertTransition(currentScene = SceneA)
 
-        draggable.onDelta(down(0.1f))
+        // Start a drag and then stop it, given that
+        draggable.onDragStarted(overSlop = up(0.1f))
+
+        assertTransition(currentScene = SceneA)
         assertThat(progress).isEqualTo(0.1f)
 
         // now we can intercept the scroll events
-        nestedScroll.scroll(available = offsetY10)
+        nestedScroll.scroll(available = -offsetY10)
         assertThat(progress).isEqualTo(0.2f)
 
         // this should be ignored, we are scrolling now!
-        draggable.onDragStopped(velocityThreshold)
+        draggable.onDragStopped(-velocityThreshold)
         assertTransition(currentScene = SceneA)
 
-        nestedScroll.scroll(available = offsetY10)
+        nestedScroll.scroll(available = -offsetY10)
         assertThat(progress).isEqualTo(0.3f)
 
-        nestedScroll.scroll(available = offsetY10)
+        nestedScroll.scroll(available = -offsetY10)
         assertThat(progress).isEqualTo(0.4f)
 
-        nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
-        assertTransition(currentScene = SceneC)
+        nestedScroll.onPreFling(available = Velocity(0f, -velocityThreshold))
+        assertTransition(currentScene = SceneB)
 
         // wait for the stop animation
         advanceUntilIdle()
-        assertIdle(currentScene = SceneC)
+        assertIdle(currentScene = SceneB)
+    }
+
+    @Test
+    fun interceptTransition() = runGestureTest {
+        // Start at scene C.
+        navigateToSceneC()
+
+        // Swipe up from the middle to transition to scene B.
+        val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+        draggable.onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+        assertTransition(
+            currentScene = SceneC,
+            fromScene = SceneC,
+            toScene = SceneB,
+            isUserInputOngoing = true,
+        )
+
+        val firstTransition = transitionState
+
+        // During the current gesture, start a new gesture, still in the middle of the screen. We
+        // should intercept it. Because it is intercepted, the overSlop passed to onDragStarted()
+        // should be 0f.
+        assertThat(sceneGestureHandler.shouldImmediatelyIntercept(middle)).isTrue()
+        draggable.onDragStarted(startedPosition = middle, overSlop = 0f)
+
+        // We should have intercepted the transition, so the transition should be the same object.
+        assertTransition(currentScene = SceneC, fromScene = SceneC, toScene = SceneB)
+        assertThat(transitionState).isSameInstanceAs(firstTransition)
+
+        // Start a new gesture from the bottom of the screen. Because swiping up from the bottom of
+        // C leads to scene A (and not B), the previous transitions is *not* intercepted and we
+        // instead animate from C to A.
+        val bottom = Offset(SCREEN_SIZE / 2, SCREEN_SIZE)
+        assertThat(sceneGestureHandler.shouldImmediatelyIntercept(bottom)).isFalse()
+        draggable.onDragStarted(startedPosition = bottom, overSlop = up(0.1f))
+
+        assertTransition(
+            currentScene = SceneC,
+            fromScene = SceneC,
+            toScene = SceneA,
+            isUserInputOngoing = true,
+        )
+        assertThat(transitionState).isNotSameInstanceAs(firstTransition)
     }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 75dee47..48825fb 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -18,7 +18,11 @@
 
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.test.runMonotonicClockTest
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -29,7 +33,7 @@
 
     @Test
     fun isTransitioningTo_idle() {
-        val state = SceneTransitionLayoutState(TestScenes.SceneA)
+        val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
 
         assertThat(state.isTransitioning()).isFalse()
         assertThat(state.isTransitioning(from = TestScenes.SceneA)).isFalse()
@@ -40,7 +44,7 @@
 
     @Test
     fun isTransitioningTo_transition() {
-        val state = SceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+        val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
         state.startTransition(transition(from = TestScenes.SceneA, to = TestScenes.SceneB))
 
         assertThat(state.isTransitioning()).isTrue()
@@ -50,4 +54,56 @@
         assertThat(state.isTransitioning(to = TestScenes.SceneA)).isFalse()
         assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue()
     }
+
+    @Test
+    fun setTargetScene_idleToSameScene() = runMonotonicClockTest {
+        val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
+        assertThat(state.setTargetScene(TestScenes.SceneA, coroutineScope = this)).isNull()
+    }
+
+    @Test
+    fun setTargetScene_idleToDifferentScene() = runMonotonicClockTest {
+        val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
+        val transition = state.setTargetScene(TestScenes.SceneB, coroutineScope = this)
+        assertThat(transition).isNotNull()
+        assertThat(state.transitionState).isEqualTo(transition)
+
+        testScheduler.advanceUntilIdle()
+        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+    }
+
+    @Test
+    fun setTargetScene_transitionToSameScene() = runMonotonicClockTest {
+        val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
+        assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
+        assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNull()
+        testScheduler.advanceUntilIdle()
+        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+    }
+
+    @Test
+    fun setTargetScene_transitionToDifferentScene() = runMonotonicClockTest {
+        val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
+        assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
+        assertThat(state.setTargetScene(TestScenes.SceneC, coroutineScope = this)).isNotNull()
+        testScheduler.advanceUntilIdle()
+        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneC))
+    }
+
+    @Test
+    fun setTargetScene_coroutineScopeCancelled() = runMonotonicClockTest {
+        val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
+
+        lateinit var transition: TransitionState.Transition
+        val job =
+            launch(start = CoroutineStart.UNDISPATCHED) {
+                transition = state.setTargetScene(TestScenes.SceneB, coroutineScope = this)!!
+            }
+        assertThat(state.transitionState).isEqualTo(transition)
+
+        // Cancelling the scope/job still sets the state to Idle(targetScene).
+        job.cancel()
+        testScheduler.advanceUntilIdle()
+        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 649e499..efaea71 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -63,7 +63,7 @@
     }
 
     private var currentScene by mutableStateOf(TestScenes.SceneA)
-    private val layoutState = SceneTransitionLayoutState(currentScene)
+    private lateinit var layoutState: SceneTransitionLayoutState
 
     // We use createAndroidComposeRule() here and not createComposeRule() because we need an
     // activity for testBack().
@@ -72,10 +72,14 @@
     /** The content under test. */
     @Composable
     private fun TestContent() {
+        layoutState =
+            updateSceneTransitionLayoutState(
+                currentScene,
+                { currentScene = it },
+                EmptyTestTransitions
+            )
+
         SceneTransitionLayout(
-            currentScene,
-            { currentScene = it },
-            EmptyTestTransitions,
             state = layoutState,
             modifier = Modifier.size(LayoutSize),
         ) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 58d853e..d81631a 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -17,6 +17,7 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.Composable
@@ -58,46 +59,53 @@
             get() = Offset(0f, (LayoutHeight / 2).toPx())
     }
 
-    private var currentScene by mutableStateOf(TestScenes.SceneA)
-    private val layoutState = SceneTransitionLayoutState(currentScene)
-
     @get:Rule val rule = createComposeRule()
 
+    private fun layoutState(initialScene: SceneKey = TestScenes.SceneA) =
+        MutableSceneTransitionLayoutState(initialScene, EmptyTestTransitions)
+
     /** The content under test. */
     @Composable
-    private fun TestContent() {
+    private fun TestContent(
+        layoutState: SceneTransitionLayoutState,
+        swipesEnabled: () -> Boolean = { true },
+    ) {
         SceneTransitionLayout(
-            currentScene,
-            { currentScene = it },
-            EmptyTestTransitions,
             state = layoutState,
             modifier = Modifier.size(LayoutWidth, LayoutHeight).testTag(TestElements.Foo.debugName),
         ) {
             scene(
                 TestScenes.SceneA,
                 userActions =
-                    mapOf(
-                        Swipe.Left to TestScenes.SceneB,
-                        Swipe.Down to TestScenes.SceneC,
-                    ),
+                    if (swipesEnabled())
+                        mapOf(
+                            Swipe.Left to TestScenes.SceneB,
+                            Swipe.Down to TestScenes.SceneC,
+                            Swipe.Up to TestScenes.SceneB,
+                        )
+                    else emptyMap(),
             ) {
                 Box(Modifier.fillMaxSize())
             }
             scene(
                 TestScenes.SceneB,
-                userActions = mapOf(Swipe.Right to TestScenes.SceneA),
+                userActions =
+                    if (swipesEnabled()) mapOf(Swipe.Right to TestScenes.SceneA) else emptyMap(),
             ) {
                 Box(Modifier.fillMaxSize())
             }
             scene(
                 TestScenes.SceneC,
                 userActions =
-                    mapOf(
-                        Swipe.Down to TestScenes.SceneA,
-                        Swipe(SwipeDirection.Down, pointerCount = 2) to TestScenes.SceneB,
-                        Swipe(SwipeDirection.Right, fromEdge = Edge.Left) to TestScenes.SceneB,
-                        Swipe(SwipeDirection.Down, fromEdge = Edge.Top) to TestScenes.SceneB,
-                    ),
+                    if (swipesEnabled())
+                        mapOf(
+                            Swipe.Down to TestScenes.SceneA,
+                            Swipe(SwipeDirection.Down, pointerCount = 2) to TestScenes.SceneB,
+                            Swipe(SwipeDirection.Right, fromSource = Edge.Left) to
+                                TestScenes.SceneB,
+                            Swipe(SwipeDirection.Down, fromSource = Edge.Top) to TestScenes.SceneB,
+                        )
+                    else emptyMap(),
             ) {
                 Box(Modifier.fillMaxSize())
             }
@@ -109,9 +117,11 @@
         // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
         // detected as a drag event.
         var touchSlop = 0f
+
+        val layoutState = layoutState()
         rule.setContent {
             touchSlop = LocalViewConfiguration.current.touchSlop
-            TestContent()
+            TestContent(layoutState)
         }
 
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
@@ -195,9 +205,10 @@
         // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
         // detected as a drag event.
         var touchSlop = 0f
+        val layoutState = layoutState()
         rule.setContent {
             touchSlop = LocalViewConfiguration.current.touchSlop
-            TestContent()
+            TestContent(layoutState)
         }
 
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
@@ -260,14 +271,14 @@
     @Test
     fun multiPointerSwipe() {
         // Start at scene C.
-        currentScene = TestScenes.SceneC
+        val layoutState = layoutState(TestScenes.SceneC)
 
         // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
         // detected as a drag event.
         var touchSlop = 0f
         rule.setContent {
             touchSlop = LocalViewConfiguration.current.touchSlop
-            TestContent()
+            TestContent(layoutState)
         }
 
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
@@ -299,14 +310,14 @@
     @Test
     fun defaultEdgeSwipe() {
         // Start at scene C.
-        currentScene = TestScenes.SceneC
+        val layoutState = layoutState(TestScenes.SceneC)
 
         // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
         // detected as a drag event.
         var touchSlop = 0f
         rule.setContent {
             touchSlop = LocalViewConfiguration.current.touchSlop
-            TestContent()
+            TestContent(layoutState)
         }
 
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
@@ -352,4 +363,116 @@
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
         assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
     }
+
+    @Test
+    fun swipeDistance() {
+        // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+        // detected as a drag event.
+        var touchSlop = 0f
+
+        val layoutState = layoutState()
+        val verticalSwipeDistance = 50.dp
+        assertThat(verticalSwipeDistance).isNotEqualTo(LayoutHeight)
+
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+
+            SceneTransitionLayout(
+                state = layoutState,
+                modifier = Modifier.size(LayoutWidth, LayoutHeight)
+            ) {
+                scene(
+                    TestScenes.SceneA,
+                    userActions =
+                        mapOf(Swipe.Down to TestScenes.SceneB withDistance verticalSwipeDistance),
+                ) {
+                    Spacer(Modifier.fillMaxSize())
+                }
+                scene(TestScenes.SceneB) { Spacer(Modifier.fillMaxSize()) }
+            }
+        }
+
+        assertThat(layoutState.currentTransition).isNull()
+
+        // Swipe by half of verticalSwipeDistance.
+        rule.onRoot().performTouchInput {
+            down(middleTop)
+            moveBy(Offset(0f, touchSlop + (verticalSwipeDistance / 2).toPx()), delayMillis = 1_000)
+        }
+
+        // We should be at 50%
+        val transition = layoutState.currentTransition
+        assertThat(transition).isNotNull()
+        assertThat(transition!!.progress).isEqualTo(0.5f)
+    }
+
+    @Test
+    fun swipeByTouchSlop() {
+        val layoutState = layoutState()
+        var touchSlop = 0f
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            TestContent(layoutState)
+        }
+
+        // Swipe down by exactly touchSlop, so that the drag overSlop is 0f.
+        rule.onRoot().performTouchInput {
+            down(middle)
+            moveBy(Offset(0f, touchSlop), delayMillis = 1_000)
+        }
+
+        // We should still correctly compute that we are swiping down to scene C.
+        var transition = layoutState.currentTransition
+        assertThat(transition).isNotNull()
+        assertThat(transition?.toScene).isEqualTo(TestScenes.SceneC)
+
+        // Release the finger, animating back to scene A.
+        rule.onRoot().performTouchInput { up() }
+        rule.waitForIdle()
+        assertThat(layoutState.currentTransition).isNull()
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+
+        // Swipe up by exactly touchSlop, so that the drag overSlop is 0f.
+        rule.onRoot().performTouchInput {
+            down(middle)
+            moveBy(Offset(0f, -touchSlop), delayMillis = 1_000)
+        }
+
+        // We should still correctly compute that we are swiping up to scene B.
+        transition = layoutState.currentTransition
+        assertThat(transition).isNotNull()
+        assertThat(transition?.toScene).isEqualTo(TestScenes.SceneB)
+    }
+
+    @Test
+    fun swipeEnabledLater() {
+        val layoutState = MutableSceneTransitionLayoutState(TestScenes.SceneA)
+        var swipesEnabled by mutableStateOf(false)
+        var touchSlop = 0f
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            TestContent(layoutState, swipesEnabled = { swipesEnabled })
+        }
+
+        // Drag down from the middle. This should not do anything, because swipes are disabled.
+        rule.onRoot().performTouchInput {
+            down(middle)
+            moveBy(Offset(0f, touchSlop), delayMillis = 1_000)
+        }
+        assertThat(layoutState.currentTransition).isNull()
+
+        // Release finger.
+        rule.onRoot().performTouchInput { up() }
+
+        // Enable swipes.
+        swipesEnabled = true
+        rule.waitForIdle()
+
+        // Drag down from the middle. Now it should start a transition.
+        rule.onRoot().performTouchInput {
+            down(middle)
+            moveBy(Offset(0f, touchSlop), delayMillis = 1_000)
+        }
+        assertThat(layoutState.currentTransition).isNotNull()
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index ef72992..140baca 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.animation.core.SpringSpec
 import androidx.compose.animation.core.TweenSpec
+import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.compose.animation.scene.transformation.Transformation
@@ -188,6 +189,40 @@
             )
     }
 
+    @Test
+    fun springSpec() {
+        val defaultSpec = spring<Float>(stiffness = 1f)
+        val specFromAToC = spring<Float>(stiffness = 2f)
+        val transitions = transitions {
+            defaultSwipeSpec = defaultSpec
+
+            from(TestScenes.SceneA, to = TestScenes.SceneB) {
+                // Default swipe spec.
+            }
+            from(TestScenes.SceneA, to = TestScenes.SceneC) { swipeSpec = specFromAToC }
+        }
+
+        assertThat(transitions.defaultSwipeSpec).isSameInstanceAs(defaultSpec)
+
+        // A => B does not have a custom spec.
+        assertThat(
+                transitions
+                    .transitionSpec(from = TestScenes.SceneA, to = TestScenes.SceneB)
+                    .transformationSpec()
+                    .swipeSpec
+            )
+            .isNull()
+
+        // A => C has a custom swipe spec.
+        assertThat(
+                transitions
+                    .transitionSpec(from = TestScenes.SceneA, to = TestScenes.SceneC)
+                    .transformationSpec()
+                    .swipeSpec
+            )
+            .isSameInstanceAs(specFromAToC)
+    }
+
     companion object {
         private val TRANSFORMATION_RANGE =
             Correspondence.transforming<Transformation, TransformationRange?>(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
index e2974cd..ac7717b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
@@ -34,7 +34,8 @@
         LargeTopAppBarNestedScrollConnection(
             height = { height },
             onHeightChanged = { height = it },
-            heightRange = heightRange,
+            minHeight = { heightRange.start },
+            maxHeight = { heightRange.endInclusive },
         )
 
     private fun NestedScrollConnection.scroll(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
index cb122dc..fbcd5b2 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
@@ -13,7 +13,7 @@
  *
  * The [TestCoroutineScheduler] is passed to provide the functionality to wait for idle.
  */
-@ExperimentalTestApi
+@OptIn(ExperimentalTestApi::class)
 fun runMonotonicClockTest(block: suspend MonotonicClockTestScope.() -> Unit) = runTest {
     // We need a CoroutineScope (like a TestScope) to create a TestMonotonicFrameClock.
     withContext(TestMonotonicFrameClock(this)) {
diff --git a/packages/SystemUI/customization/Android.bp b/packages/SystemUI/customization/Android.bp
index 1d18496..81b5bd4 100644
--- a/packages/SystemUI/customization/Android.bp
+++ b/packages/SystemUI/customization/Android.bp
@@ -34,15 +34,19 @@
         "PluginCoreLib",
         "SystemUIPluginLib",
         "SystemUIUnfoldLib",
-        "androidx.dynamicanimation_dynamicanimation",
+        "kotlinx_coroutines",
+        "dagger2",
+        "jsr330",
+    ],
+    libs: [
+        // Keep android-specific libraries as libs instead of static_libs, so that they don't break
+        // things when included as transitive dependencies in robolectric targets.
         "androidx.concurrent_concurrent-futures",
+        "androidx.dynamicanimation_dynamicanimation",
         "androidx.lifecycle_lifecycle-runtime-ktx",
         "androidx.lifecycle_lifecycle-viewmodel-ktx",
         "androidx.recyclerview_recyclerview",
         "kotlinx_coroutines_android",
-        "kotlinx_coroutines",
-        "dagger2",
-        "jsr330",
     ],
     resource_dirs: [
         "res",
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 2bfa7d9..cea49e1 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -27,6 +27,7 @@
 import android.text.format.DateFormat
 import android.util.AttributeSet
 import android.util.MathUtils.constrainedMap
+import android.view.View
 import android.widget.TextView
 import com.android.app.animation.Interpolators
 import com.android.internal.annotations.VisibleForTesting
@@ -51,7 +52,7 @@
     context: Context,
     attrs: AttributeSet? = null,
     defStyleAttr: Int = 0,
-    defStyleRes: Int = 0
+    defStyleRes: Int = 0,
 ) : TextView(context, attrs, defStyleAttr, defStyleRes) {
     // To protect us from issues from this being null while the TextView constructor is running, we
     // implement the get method and ensure a value is returned before initialization is complete.
@@ -61,6 +62,9 @@
         get() = logger.buffer
         set(value) { logger = Logger(value, TAG) }
 
+    var hasCustomPositionUpdatedAnimation: Boolean = false
+    var migratedClocks: Boolean = false
+
     private val time = Calendar.getInstance()
 
     private val dozingWeightInternal: Int
@@ -193,9 +197,18 @@
         } else {
             animator.updateLayout(layout)
         }
+        if (migratedClocks && hasCustomPositionUpdatedAnimation) {
+            // Expand width to avoid clock being clipped during stepping animation
+            setMeasuredDimension(measuredWidth +
+                    MeasureSpec.getSize(widthMeasureSpec) / 2, measuredHeight)
+        }
     }
 
     override fun onDraw(canvas: Canvas) {
+        if (migratedClocks && hasCustomPositionUpdatedAnimation) {
+            canvas.save()
+            canvas.translate((parent as View).measuredWidth / 4F, 0F)
+        }
         logger.d({ "onDraw($str1)"}) { str1 = text.toString() }
         // Use textAnimator to render text if animation is enabled.
         // Otherwise default to using standard draw functions.
@@ -205,6 +218,9 @@
         } else {
             super.onDraw(canvas)
         }
+        if (migratedClocks && hasCustomPositionUpdatedAnimation) {
+            canvas.restore()
+        }
     }
 
     override fun invalidate() {
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 41bde52..3dfe65a 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
@@ -22,6 +22,7 @@
 import android.os.UserHandle
 import android.provider.Settings
 import androidx.annotation.OpenForTesting
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.core.LogcatOnlyMessageBuffer
 import com.android.systemui.log.core.Logger
@@ -120,8 +121,9 @@
             override fun onPluginAttached(
                 manager: PluginLifecycleManager<ClockProviderPlugin>
             ): Boolean {
-                manager.isDebug = !keepAllLoaded
-
+                manager.setLogFunc({ tag, msg ->
+                    (clockBuffers?.infraMessageBuffer as LogBuffer?)?.log(tag, LogLevel.DEBUG, msg)
+                })
                 if (keepAllLoaded) {
                     // Always load new plugins if requested
                     return true
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 99d3216..001e3a5 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -193,6 +193,8 @@
             ClockFaceConfig(hasCustomPositionUpdatedAnimation = hasStepClockAnimation)
 
         init {
+            view.migratedClocks = migratedClocks
+            view.hasCustomPositionUpdatedAnimation = hasStepClockAnimation
             animations = LargeClockAnimations(view, 0f, 0f)
         }
 
diff --git a/packages/SystemUI/docs/imgs/ribbon.png b/packages/SystemUI/docs/imgs/ribbon.png
index 9f57652..3379d3d 100644
--- a/packages/SystemUI/docs/imgs/ribbon.png
+++ b/packages/SystemUI/docs/imgs/ribbon.png
Binary files differ
diff --git a/packages/SystemUI/docs/scene.md b/packages/SystemUI/docs/scene.md
index 3e4a1b4..105e438 100644
--- a/packages/SystemUI/docs/scene.md
+++ b/packages/SystemUI/docs/scene.md
@@ -20,14 +20,16 @@
     from one scene to another) are also pulled out and separated from the
     content of the UI.
 
-In addition to the above, some of the **secondary goals** are: 4. Make
-**customization easier**: by separating scenes to standalone pieces, it becomes
-possible for variant owners and OEMs to exclude or replace certain scenes or to
-add brand-new scenes. 5. **Enable modularization**: by separating scenes to
-standalone pieces, it becomes possible to break down System UI into smaller
-codebases, each one of which could be built on its own. Note: this isn't part of
-the scene framework itself but is something that can be done more easily once
-the scene framework is in place.
+In addition to the above, some of the **secondary goals** are:
+
+4. Make **customization easier**: by separating scenes to standalone pieces, it
+becomes possible for variant owners and OEMs to exclude or replace certain scenes
+or to add brand-new scenes.
+5. **Enable modularization**: by separating scenes to standalone pieces, it
+becomes possible to break down System UI into smaller codebases, each one of
+which could be built on its own. Note: this isn't part of the scene framework
+itself but is something that can be done more easily once the scene framework
+is in place.
 
 ## Terminology
 
@@ -70,15 +72,17 @@
     running: `console $ adb shell statusbar cmd migrate_keyguard_status_bar_view
     true`
 3.  Set a collection of **aconfig flags** to `true` by running the following
-    commands: `console $ adb shell device_config put systemui
-    com.android.systemui.scene_container true $ adb shell device_config put
-    systemui com.android.systemui.keyguard_bottom_area_refactor true $ adb shell
-    device_config put systemui
-    com.android.systemui.keyguard_shade_migration_nssl true $ adb shell
-    device_config put systemui com.android.systemui.media_in_scene_container
-    true`
-4.  **Restart** System UI by issuing the following command: `console $ adb shell
-    am crash com.android.systemui`
+    commands:
+    ```console
+    $ adb shell device_config put systemui com.android.systemui.scene_container true
+    $ adb shell device_config put systemui com.android.systemui.keyguard_bottom_area_refactor true
+    $ adb shell device_config put systemui com.android.systemui.keyguard_shade_migration_nssl true
+    $ adb shell device_config put systemui com.android.systemui.media_in_scene_container true
+    ```
+4.  **Restart** System UI by issuing the following command:
+    ```console
+    $ adb shell am crash com.android.systemui
+    ```
 5.  **Verify** that the scene framework was turned on. There are two ways to do
     this:
 
@@ -94,15 +98,29 @@
 
     $ adb shell cmd statusbar echo -b SceneFramework:verbose
 
-# Look for the log statements from the framework:
+### Checking if the framework is enabled
 
-$ adb logcat -v time SceneFramework:* *:S ```
+Look for the log statements from the framework:
 
-To **disable** the framework, simply turn off the main aconfig flag: `console $
-adb shell device_config put systemui com.android.systemui.scene_container false`
+```console
+$ adb logcat -v time SceneFramework:* *:S
+```
+
+### Disabling the framework
+
+To **disable** the framework, simply turn off the main aconfig flag:
+
+```console
+$ adb shell device_config put systemui com.android.systemui.scene_container false
+```
 
 ## Defining a scene
 
+By default, the framework ships with fully functional scenes as enumarated
+[here](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt).
+Should a variant owner or OEM want to replace or add a new scene, they could
+do so by defining their own scene. This section describes how to do that.
+
 Each scene is defined as an implementation of the
 [`ComposableScene`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt)
 interface, which has three parts: 1. The `key` property returns the
@@ -118,28 +136,28 @@
 content of your UI changes with and throughout a transition to learn more please
 see the [Scene transition animations](#Scene-transition-animations) section
 
-For example: ```kotlin @SysUISingleton class YourScene @Inject constructor( //
-your dependencies here ) : ComposableScene { override val key =
-SceneKey.YourScene
+For example:
 
-```
-override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> =
-    MutableStateFlow<Map<UserAction, SceneModel>>(
-        mapOf(
-            // This is where scene navigation is defined, more on that below.
-        )
-    ).asStateFlow()
+```kotlin
+@SysUISingleton class YourScene @Inject constructor( /* your dependencies here */ ) : ComposableScene {
+    override val key = SceneKey.YourScene
 
-@Composable
-override fun SceneScope.Content(
-    modifier: Modifier,
-) {
-    // This is where the UI is defined using Jetpack Compose.
+    override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> =
+        MutableStateFlow<Map<UserAction, SceneModel>>(
+            mapOf(
+                // This is where scene navigation is defined, more on that below.
+            )
+        ).asStateFlow()
+
+    @Composable
+    override fun SceneScope.Content(
+        modifier: Modifier,
+    ) {
+        // This is where the UI is defined using Jetpack Compose.
+    }
 }
 ```
 
-} ```
-
 ### Injecting scenes
 
 Scenes are injected into the Dagger dependency graph from the
@@ -200,20 +218,21 @@
 }
 ```
 
-Going through the example code: * The `spec` is the animation that should be
-invoked, in the example above, we use a `tween` animation with a duration of 500
-milliseconds * Then there's a series of function calls: `punchHole` applies a
-clip mask to the `Scrim` element in the destination scene (in this case it's the
-`Shade` scene) which has the position and size determined by the `bounds`
-parameter and the shape passed into the `shape` parameter. This lets the
-`Lockscreen` scene render "through" the `Shade` scene * The `translate` call
-shifts the `Scrim` element to/from the `Top` edge of the scene container * The
-first `fractionRange` wrapper tells the system to apply its contained functions
+Going through the example code:
+
+* The `spec` is the animation that should be invoked, in the example above, we use a `tween`
+animation with a duration of 500 milliseconds
+* Then there's a series of function calls: `punchHole` applies a clip mask to the `Scrim`
+element in the destination scene (in this case it's the `Shade` scene) which has the
+position and size determined by the `bounds` parameter and the shape passed into the `shape`
+parameter. This lets the `Lockscreen` scene render "through" the `Shade` scene
+* The `translate` call shifts the `Scrim` element to/from the `Top` edge of the scene container
+* The first `fractionRange` wrapper tells the system to apply its contained functions
 only during the first half of the transition. Inside of it, we see a `fade` of
 the `ScrimBackground` element and a `translate` o the `CollpasedGrid` element
-to/from the `Top` edge * The second `fractionRange` only starts at the second
-half of the transition (e.g. when the previous one ends) and applies a `fade` on
-the `Notifications` element
+to/from the `Top` edge
+* The second `fractionRange` only starts at the second half of the transition (e.g. when
+the previous one ends) and applies a `fade` on the `Notifications` element
 
 You can find the actual documentation for this API
 [here](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt).
@@ -295,3 +314,52 @@
 this puts together the scenes from `SceneModule`, the configuration from
 `SceneContainerConfigModule`, and the startable from
 `SceneContainerStartableModule`.
+
+## Integration Notes
+
+### Relationship to Jetpack Compose
+
+The scene framework depends on Jetpack Compose; therefore, compiling System UI with
+Jetpack Compose is required. However, because Jetpack Compose and Android Views
+[interoperate](https://developer.android.com/jetpack/compose/migrate/interoperability-apis/views-in-compose),
+the UI in each scene doesn't necessarily need to be a pure hierarchy of `@Composable`
+functions; instead, it's acceptable to use an `AndroidView` somewhere in the
+hierarchy of composable functions to include a `View` or `ViewGroup` subtree.
+
+#### Interoperability with Views
+The scene framework comes with built-in functionality to animate the entire scene and/or
+elements within the scene in-tandem with the actual scene transition progress.
+
+For example, as the user drags their finger down rom the top of the lockscreen,
+the shade scene becomes visible and gradually expands, the amount of expansion tracks
+the movement of the finger.
+
+That feature of the framework uses a custom `element(ElementKey)` Jetpack Compose
+`Modifier` to refer to elements within a scene.
+The transition builders then use the same `ElementKey` objects to refer to those elements
+and describe how they animate in-tandem with scene transitions. Because this is a
+Jetpack Compose `Modifier`, it means that, in order for an element in a scene to be
+animated automatically by the framework, that element must be nested within a pure
+`@Composable` hierarchy. The element itself is allowed to be a classic Android `View`
+(nested within a Jetpack Compose `AndroidView`) but all ancestors must be `@Composable`
+functions.
+
+### Notifications
+
+As of January 2024, the integration of notifications and heads-up notifications (HUNs)
+into the scene framework follows an unusual pattern. We chose this pattern due to migration
+risk and performance concerns but will eventually replace it with the more common element
+placement pattern that all other elements are following.
+
+The special pattern for notifications is that, instead of the notification list
+(`NotificationStackScrollLayout` or "NSSL", which also displays HUNs) being placed in the element
+hierarchy within the scenes that display notifications, the NSSL (which continues to be an Android View)
+"floats" above the scene container, rendering on top of everything. This is very similar to
+how NSSL is integrated with the legacy shade, prior to the scene framework.
+
+In order to render the NSSL as if it's part of the organic hierarchy of elements within its
+scenes, we control the NSSL's self-imposed effective bounds (e.g. position offsets, clip path,
+size) from `@Composable` elements within the normal scene hierarchy. These special
+"placeholder" elements can be found
+[here](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt).
+
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index e893169..c4bcb53 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -35,6 +35,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository;
 import com.android.systemui.res.R;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
@@ -107,7 +108,7 @@
                 mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
                 mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
                 mEmergencyButtonController, mFalsingCollector, featureFlags,
-                mSelectedUserInteractor) {
+                mSelectedUserInteractor, new FakeKeyboardRepository()) {
             @Override
             public void onResume(int reason) {
                 super.onResume(reason);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
deleted file mode 100644
index 78b854e..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard
-
-import android.testing.TestableLooper
-import android.view.View
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.internal.logging.UiEventLogger
-import com.android.internal.util.LatencyTracker
-import com.android.internal.widget.LockPatternUtils
-import com.android.internal.widget.LockscreenCredential
-import com.android.keyguard.KeyguardPinViewController.PinBouncerUiEvent
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.classifier.FalsingCollectorFake
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.policy.DevicePostureController
-import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
-import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.anyString
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.any
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-@TestableLooper.RunWithLooper
-class KeyguardPinViewControllerTest : SysuiTestCase() {
-
-    private lateinit var objectKeyguardPINView: KeyguardPINView
-
-    @Mock private lateinit var mockKeyguardPinView: KeyguardPINView
-
-    @Mock private lateinit var keyguardMessageArea: BouncerKeyguardMessageArea
-
-    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-
-    @Mock private lateinit var securityMode: SecurityMode
-
-    @Mock private lateinit var lockPatternUtils: LockPatternUtils
-
-    @Mock private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
-
-    @Mock
-    private lateinit var keyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
-
-    @Mock
-    private lateinit var keyguardMessageAreaController:
-        KeyguardMessageAreaController<BouncerKeyguardMessageArea>
-
-    @Mock private lateinit var mLatencyTracker: LatencyTracker
-
-    @Mock private lateinit var liftToActivateListener: LiftToActivateListener
-
-    @Mock private val mEmergencyButtonController: EmergencyButtonController? = null
-    private val falsingCollector: FalsingCollector = FalsingCollectorFake()
-    @Mock lateinit var postureController: DevicePostureController
-    @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
-
-    @Mock lateinit var featureFlags: FeatureFlags
-    @Mock lateinit var passwordTextView: PasswordTextView
-    @Mock lateinit var deleteButton: NumPadButton
-    @Mock lateinit var enterButton: View
-    @Mock lateinit var uiEventLogger: UiEventLogger
-
-    @Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback>
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        Mockito.`when`(mockKeyguardPinView.requireViewById<View>(R.id.bouncer_message_area))
-            .thenReturn(keyguardMessageArea)
-        Mockito.`when`(
-                keyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea::class.java))
-            )
-            .thenReturn(keyguardMessageAreaController)
-        `when`(mockKeyguardPinView.passwordTextViewId).thenReturn(R.id.pinEntry)
-        `when`(mockKeyguardPinView.findViewById<PasswordTextView>(R.id.pinEntry))
-            .thenReturn(passwordTextView)
-        `when`(mockKeyguardPinView.resources).thenReturn(context.resources)
-        `when`(mockKeyguardPinView.findViewById<NumPadButton>(R.id.delete_button))
-            .thenReturn(deleteButton)
-        `when`(mockKeyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton)
-        // For posture tests:
-        `when`(mockKeyguardPinView.buttons).thenReturn(arrayOf())
-        `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
-        `when`(featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)).thenReturn(false)
-
-        objectKeyguardPINView =
-            View.inflate(mContext, R.layout.keyguard_pin_view, null)
-                .requireViewById(R.id.keyguard_pin_view) as KeyguardPINView
-    }
-
-    private fun constructPinViewController(
-        mKeyguardPinView: KeyguardPINView
-    ): KeyguardPinViewController {
-        return KeyguardPinViewController(
-            mKeyguardPinView,
-            keyguardUpdateMonitor,
-            securityMode,
-            lockPatternUtils,
-            mKeyguardSecurityCallback,
-            keyguardMessageAreaControllerFactory,
-            mLatencyTracker,
-            liftToActivateListener,
-            mEmergencyButtonController,
-            falsingCollector,
-            postureController,
-            featureFlags,
-            mSelectedUserInteractor,
-            uiEventLogger
-        )
-    }
-
-    @Test
-    fun onViewAttached_deviceHalfFolded_propagatedToPatternView() {
-        val pinViewController = constructPinViewController(objectKeyguardPINView)
-        overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
-        whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
-
-        pinViewController.onViewAttached()
-
-        assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
-    }
-
-    @Test
-    fun onDevicePostureChanged_deviceOpened_propagatedToPatternView() {
-        val pinViewController = constructPinViewController(objectKeyguardPINView)
-        overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
-
-        whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
-        pinViewController.onViewAttached()
-
-        // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED
-        assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
-
-        // Simulate posture change to state DEVICE_POSTURE_OPENED with callback
-        verify(postureController).addCallback(postureCallbackCaptor.capture())
-        val postureCallback: DevicePostureController.Callback = postureCallbackCaptor.value
-        postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
-
-        // Verify view is now in posture state DEVICE_POSTURE_OPENED
-        assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
-
-        // Simulate posture change to same state with callback
-        postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
-
-        // Verify view is still in posture state DEVICE_POSTURE_OPENED
-        assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
-    }
-
-    private fun getPinTopGuideline(): Float {
-        val cs = ConstraintSet()
-        val container =
-            objectKeyguardPINView.requireViewById(R.id.pin_container) as ConstraintLayout
-        cs.clone(container)
-        return cs.getConstraint(R.id.pin_pad_top_guideline).layout.guidePercent
-    }
-
-    private fun getHalfOpenedBouncerHeightRatio(): Float {
-        return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio)
-    }
-
-    @Test
-    fun testOnViewAttached() {
-        val pinViewController = constructPinViewController(mockKeyguardPinView)
-
-        pinViewController.onViewAttached()
-
-        verify(keyguardMessageAreaController)
-            .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
-    }
-
-    @Test
-    fun testOnViewAttached_withExistingMessage() {
-        val pinViewController = constructPinViewController(mockKeyguardPinView)
-        Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.")
-
-        pinViewController.onViewAttached()
-
-        verify(keyguardMessageAreaController, Mockito.never()).setMessage(anyString(), anyBoolean())
-    }
-
-    @Test
-    fun testOnViewAttached_withAutoPinConfirmationFailedPasswordAttemptsLessThan5() {
-        val pinViewController = constructPinViewController(mockKeyguardPinView)
-        `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
-        `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
-        `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
-        `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3)
-        `when`(passwordTextView.text).thenReturn("")
-
-        pinViewController.onViewAttached()
-
-        verify(deleteButton).visibility = View.INVISIBLE
-        verify(enterButton).visibility = View.INVISIBLE
-        verify(passwordTextView).setUsePinShapes(true)
-        verify(passwordTextView).setIsPinHinting(true)
-    }
-
-    @Test
-    fun testOnViewAttached_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5() {
-        val pinViewController = constructPinViewController(mockKeyguardPinView)
-        `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
-        `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
-        `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
-        `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6)
-        `when`(passwordTextView.text).thenReturn("")
-
-        pinViewController.onViewAttached()
-
-        verify(deleteButton).visibility = View.VISIBLE
-        verify(enterButton).visibility = View.VISIBLE
-        verify(passwordTextView).setUsePinShapes(true)
-        verify(passwordTextView).setIsPinHinting(false)
-    }
-
-    @Test
-    fun handleLockout_readsNumberOfErrorAttempts() {
-        val pinViewController = constructPinViewController(mockKeyguardPinView)
-
-        pinViewController.handleAttemptLockout(0)
-
-        verify(lockPatternUtils).getCurrentFailedPasswordAttempts(anyInt())
-    }
-
-    @Test
-    fun onUserInput_autoConfirmation_attemptsUnlock() {
-        val pinViewController = constructPinViewController(mockKeyguardPinView)
-        whenever(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
-        whenever(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
-        whenever(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
-        whenever(passwordTextView.text).thenReturn("000000")
-        whenever(enterButton.visibility).thenReturn(View.INVISIBLE)
-        whenever(mockKeyguardPinView.enteredCredential)
-            .thenReturn(LockscreenCredential.createPin("000000"))
-
-        pinViewController.onUserInput()
-
-        verify(uiEventLogger).log(PinBouncerUiEvent.ATTEMPT_UNLOCK_WITH_AUTO_CONFIRM_FEATURE)
-        verify(keyguardUpdateMonitor).setCredentialAttempted()
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index c8461d2..c82688c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -45,18 +45,23 @@
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.classifier.FalsingA11yDelegate
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.SessionTracker
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -66,6 +71,7 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.testKosmos
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.kotlin.JavaAdapter
 import com.android.systemui.util.mockito.any
@@ -156,7 +162,7 @@
     private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
     private lateinit var keyguardPasswordView: KeyguardPasswordView
     private lateinit var testableResources: TestableResources
-    private lateinit var sceneTestUtils: SceneTestUtils
+    private lateinit var kosmos: Kosmos
     private lateinit var sceneInteractor: SceneInteractor
     private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     private lateinit var deviceEntryInteractor: DeviceEntryInteractor
@@ -197,12 +203,17 @@
         whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(true)
 
         featureFlags = FakeFeatureFlags()
-        featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
-        featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
         featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
         featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
 
-        mSetFlagsRule.enableFlags(AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES)
+        mSetFlagsRule.enableFlags(
+            AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES,
+        )
+        mSetFlagsRule.disableFlags(
+            FLAG_SIDEFPS_CONTROLLER_REFACTOR,
+            AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR
+        )
+
         keyguardPasswordViewController =
             KeyguardPasswordViewController(
                 keyguardPasswordView,
@@ -223,21 +234,16 @@
                 mSelectedUserInteractor,
             )
 
-        sceneTestUtils = SceneTestUtils(this)
-        sceneInteractor = sceneTestUtils.sceneInteractor()
+        kosmos = testKosmos()
+        sceneInteractor = kosmos.sceneInteractor
         keyguardTransitionInteractor =
-            KeyguardTransitionInteractorFactory.create(sceneTestUtils.testScope.backgroundScope)
+            KeyguardTransitionInteractorFactory.create(kosmos.testScope.backgroundScope)
                 .keyguardTransitionInteractor
         sceneTransitionStateFlow =
             MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
         sceneInteractor.setTransitionState(sceneTransitionStateFlow)
-        deviceEntryInteractor =
-            sceneTestUtils.deviceEntryInteractor(
-                authenticationInteractor = sceneTestUtils.authenticationInteractor(),
-                sceneInteractor = sceneInteractor,
-            )
+        deviceEntryInteractor = kosmos.deviceEntryInteractor
 
-        mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
         underTest =
             KeyguardSecurityContainerController(
                 view,
@@ -254,7 +260,7 @@
                 falsingManager,
                 userSwitcherController,
                 featureFlags,
-                sceneTestUtils.sceneContainerFlags,
+                kosmos.fakeSceneContainerFlags,
                 globalSettings,
                 sessionTracker,
                 Optional.of(sideFpsController),
@@ -264,7 +270,7 @@
                 audioManager,
                 faceAuthInteractor,
                 mock(),
-                { JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
+                { JavaAdapter(kosmos.testScope.backgroundScope) },
                 mSelectedUserInteractor,
                 deviceProvisionedController,
                 faceAuthAccessibilityDelegate,
@@ -791,7 +797,8 @@
 
     @Test
     fun dismissesKeyguard_whenSceneChangesToGone() =
-        sceneTestUtils.testScope.runTest {
+        kosmos.testScope.runTest {
+            kosmos.fakeSceneContainerFlags.enabled = true
             // Upon init, we have never dismisses the keyguard.
             underTest.onInit()
             runCurrent()
@@ -818,6 +825,8 @@
 
             // While listening, going from the bouncer scene to the gone scene, does dismiss the
             // keyguard.
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+            runCurrent()
             sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason")
             sceneTransitionStateFlow.value =
                 ObservableTransitionState.Transition(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
deleted file mode 100644
index cbcca55..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard
-
-import android.telephony.PinResult
-import android.telephony.TelephonyManager
-import android.testing.TestableLooper
-import android.view.LayoutInflater
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.internal.util.LatencyTracker
-import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.Flags
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.res.R
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.util.mockito.any
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyString
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-@TestableLooper.RunWithLooper
-class KeyguardSimPinViewControllerTest : SysuiTestCase() {
-    private lateinit var simPinView: KeyguardSimPinView
-    private lateinit var underTest: KeyguardSimPinViewController
-    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    @Mock private lateinit var securityMode: KeyguardSecurityModel.SecurityMode
-    @Mock private lateinit var lockPatternUtils: LockPatternUtils
-    @Mock private lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
-    @Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
-    @Mock private lateinit var latencyTracker: LatencyTracker
-    @Mock private lateinit var liftToActivateListener: LiftToActivateListener
-    @Mock private lateinit var telephonyManager: TelephonyManager
-    @Mock private lateinit var falsingCollector: FalsingCollector
-    @Mock private lateinit var emergencyButtonController: EmergencyButtonController
-    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
-    @Mock
-    private lateinit var keyguardMessageAreaController:
-        KeyguardMessageAreaController<BouncerKeyguardMessageArea>
-    private val updateMonitorCallbackArgumentCaptor =
-        ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        `when`(messageAreaControllerFactory.create(Mockito.any(KeyguardMessageArea::class.java)))
-            .thenReturn(keyguardMessageAreaController)
-        `when`(telephonyManager.createForSubscriptionId(anyInt())).thenReturn(telephonyManager)
-        `when`(telephonyManager.supplyIccLockPin(anyString()))
-            .thenReturn(mock(PinResult::class.java))
-        simPinView =
-            LayoutInflater.from(context).inflate(R.layout.keyguard_sim_pin_view, null)
-                as KeyguardSimPinView
-        val fakeFeatureFlags = FakeFeatureFlags()
-
-        mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
-        underTest =
-            KeyguardSimPinViewController(
-                simPinView,
-                keyguardUpdateMonitor,
-                securityMode,
-                lockPatternUtils,
-                keyguardSecurityCallback,
-                messageAreaControllerFactory,
-                latencyTracker,
-                liftToActivateListener,
-                telephonyManager,
-                falsingCollector,
-                emergencyButtonController,
-                fakeFeatureFlags,
-                mSelectedUserInteractor
-            )
-        underTest.init()
-        underTest.onResume(0)
-        verify(keyguardUpdateMonitor)
-            .registerCallback(updateMonitorCallbackArgumentCaptor.capture())
-    }
-
-    @Test
-    fun onViewAttached() {
-        underTest.onViewAttached()
-        verify(keyguardMessageAreaController)
-            .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
-    }
-
-    @Test
-    fun onViewDetached() {
-        underTest.onViewDetached()
-    }
-
-    @Test
-    fun onResume() {
-        reset(keyguardUpdateMonitor)
-        underTest.onResume(KeyguardSecurityView.VIEW_REVEALED)
-        verify(keyguardUpdateMonitor)
-            .registerCallback(any(KeyguardUpdateMonitorCallback::class.java))
-    }
-
-    @Test
-    fun onPause() {
-        underTest.onPause()
-        verify(keyguardUpdateMonitor).removeCallback(any(KeyguardUpdateMonitorCallback::class.java))
-    }
-
-    @Test
-    fun startAppearAnimation() {
-        underTest.startAppearAnimation()
-    }
-
-    @Test
-    fun startDisappearAnimation() {
-        underTest.startDisappearAnimation {}
-    }
-
-    @Test
-    fun resetState() {
-        underTest.resetState()
-        verify(keyguardMessageAreaController).setMessage("")
-    }
-
-    @Test
-    fun onSimStateChangedFromPinToPuk_showsCurrentSecurityScreen() {
-        updateMonitorCallbackArgumentCaptor.value.onSimStateChanged(
-            /* subId= */ 0,
-            /* slotId= */ 0,
-            TelephonyManager.SIM_STATE_PIN_REQUIRED
-        )
-        verify(keyguardSecurityCallback, never()).showCurrentSecurityScreen()
-
-        updateMonitorCallbackArgumentCaptor.value.onSimStateChanged(
-            /* subId= */ 0,
-            /* slotId= */ 0,
-            TelephonyManager.SIM_STATE_PUK_REQUIRED
-        )
-
-        verify(keyguardSecurityCallback).showCurrentSecurityScreen()
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
deleted file mode 100644
index 45a60199..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard
-
-import android.telephony.PinResult
-import android.telephony.TelephonyManager
-import android.testing.TestableLooper
-import android.view.LayoutInflater
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.internal.util.LatencyTracker
-import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.Flags
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.res.R
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.util.mockito.any
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyString
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-@TestableLooper.RunWithLooper
-class KeyguardSimPukViewControllerTest : SysuiTestCase() {
-    private lateinit var simPukView: KeyguardSimPukView
-    private lateinit var underTest: KeyguardSimPukViewController
-    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    @Mock private lateinit var securityMode: KeyguardSecurityModel.SecurityMode
-    @Mock private lateinit var lockPatternUtils: LockPatternUtils
-    @Mock private lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
-    @Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
-    @Mock private lateinit var latencyTracker: LatencyTracker
-    @Mock private lateinit var liftToActivateListener: LiftToActivateListener
-    @Mock private lateinit var telephonyManager: TelephonyManager
-    @Mock private lateinit var falsingCollector: FalsingCollector
-    @Mock private lateinit var emergencyButtonController: EmergencyButtonController
-    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
-    @Mock
-    private lateinit var keyguardMessageAreaController:
-        KeyguardMessageAreaController<BouncerKeyguardMessageArea>
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        Mockito.`when`(
-                messageAreaControllerFactory.create(Mockito.any(KeyguardMessageArea::class.java))
-            )
-            .thenReturn(keyguardMessageAreaController)
-        Mockito.`when`(telephonyManager.createForSubscriptionId(Mockito.anyInt()))
-            .thenReturn(telephonyManager)
-        Mockito.`when`(telephonyManager.supplyIccLockPuk(anyString(), anyString()))
-            .thenReturn(Mockito.mock(PinResult::class.java))
-        simPukView =
-            LayoutInflater.from(context).inflate(R.layout.keyguard_sim_puk_view, null)
-                as KeyguardSimPukView
-        val fakeFeatureFlags = FakeFeatureFlags()
-        mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
-        underTest =
-            KeyguardSimPukViewController(
-                simPukView,
-                keyguardUpdateMonitor,
-                securityMode,
-                lockPatternUtils,
-                keyguardSecurityCallback,
-                messageAreaControllerFactory,
-                latencyTracker,
-                liftToActivateListener,
-                telephonyManager,
-                falsingCollector,
-                emergencyButtonController,
-                fakeFeatureFlags,
-                mSelectedUserInteractor,
-            )
-        underTest.init()
-    }
-
-    @Test
-    fun onViewAttached() {
-        underTest.onViewAttached()
-        Mockito.verify(keyguardUpdateMonitor)
-            .registerCallback(any(KeyguardUpdateMonitorCallback::class.java))
-        Mockito.verify(keyguardMessageAreaController)
-            .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
-    }
-
-    @Test
-    fun onViewDetached() {
-        underTest.onViewDetached()
-        Mockito.verify(keyguardUpdateMonitor)
-            .removeCallback(any(KeyguardUpdateMonitorCallback::class.java))
-    }
-
-    @Test
-    fun onResume() {
-        underTest.onResume(KeyguardSecurityView.VIEW_REVEALED)
-    }
-
-    @Test
-    fun onPause() {
-        underTest.onPause()
-    }
-
-    @Test
-    fun startAppearAnimation() {
-        underTest.startAppearAnimation()
-    }
-
-    @Test
-    fun startDisappearAnimation() {
-        underTest.startDisappearAnimation {}
-    }
-
-    @Test
-    fun resetState() {
-        underTest.resetState()
-        Mockito.verify(keyguardMessageAreaController)
-            .setMessage(context.resources.getString(R.string.kg_puk_enter_puk_hint))
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
new file mode 100644
index 0000000..9287edf
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+    private val secureSettings = FakeSettings()
+
+    private val userA11yQsShortcutsRepositoryFactory =
+        object : UserA11yQsShortcutsRepository.Factory {
+            override fun create(userId: Int): UserA11yQsShortcutsRepository {
+                return UserA11yQsShortcutsRepository(
+                    userId,
+                    secureSettings,
+                    testScope.backgroundScope,
+                    testDispatcher,
+                )
+            }
+        }
+
+    private val underTest =
+        AccessibilityQsShortcutsRepositoryImpl(userA11yQsShortcutsRepositoryFactory)
+
+    @Test
+    fun a11yQsShortcutTargetsForCorrectUsers() =
+        testScope.runTest {
+            val user0 = 0
+            val targetsForUser0 = setOf("a", "b", "c")
+            val user1 = 1
+            val targetsForUser1 = setOf("A")
+            val targetsFromUser0 by collectLastValue(underTest.a11yQsShortcutTargets(user0))
+            val targetsFromUser1 by collectLastValue(underTest.a11yQsShortcutTargets(user1))
+
+            storeA11yQsShortcutTargetsForUser(targetsForUser0, user0)
+            storeA11yQsShortcutTargetsForUser(targetsForUser1, user1)
+
+            assertThat(targetsFromUser0).isEqualTo(targetsForUser0)
+            assertThat(targetsFromUser1).isEqualTo(targetsForUser1)
+        }
+
+    private fun storeA11yQsShortcutTargetsForUser(a11yQsTargets: Set<String>, forUser: Int) {
+        secureSettings.putStringForUser(
+            SETTING_NAME,
+            a11yQsTargets.joinToString(separator = ":"),
+            forUser
+        )
+    }
+
+    companion object {
+        private const val SETTING_NAME = Settings.Secure.ACCESSIBILITY_QS_TARGETS
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
index 74f50d8..c86c747 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
@@ -17,14 +17,15 @@
 package com.android.systemui.accessibility.data.repository
 
 import android.os.UserHandle
+import android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
@@ -37,11 +38,9 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
-    companion object {
-        val TEST_USER_1 = UserHandle.of(1)!!
-        val TEST_USER_2 = UserHandle.of(2)!!
-    }
 
+    private val testUser1 = UserHandle.of(1)!!
+    private val testUser2 = UserHandle.of(2)!!
     private val testDispatcher = StandardTestDispatcher()
     private val scope = TestScope(testDispatcher)
     private val settings: FakeSettings = FakeSettings()
@@ -53,6 +52,7 @@
         underTest =
             ColorCorrectionRepositoryImpl(
                 testDispatcher,
+                scope.backgroundScope,
                 settings,
             )
     }
@@ -60,112 +60,113 @@
     @Test
     fun isEnabled_initiallyGetsSettingsValue() =
         scope.runTest {
+            val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+
             settings.putIntForUser(
-                ColorCorrectionRepositoryImpl.SETTING_NAME,
-                1,
-                TEST_USER_1.identifier
+                SETTING_NAME,
+                ENABLED,
+                testUser1.identifier
             )
-
-            underTest =
-                ColorCorrectionRepositoryImpl(
-                    testDispatcher,
-                    settings,
-                )
-
-            underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
             runCurrent()
 
-            val actualValue: Boolean = underTest.isEnabled(TEST_USER_1).first()
             Truth.assertThat(actualValue).isTrue()
         }
 
     @Test
     fun isEnabled_settingUpdated_valueUpdated() =
         scope.runTest {
-            underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
+            val flowValues: List<Boolean> by collectValues(underTest.isEnabled(testUser1))
 
             settings.putIntForUser(
-                ColorCorrectionRepositoryImpl.SETTING_NAME,
-                ColorCorrectionRepositoryImpl.DISABLED,
-                TEST_USER_1.identifier
+                SETTING_NAME,
+                DISABLED,
+                testUser1.identifier
             )
             runCurrent()
-            Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
 
             settings.putIntForUser(
-                ColorCorrectionRepositoryImpl.SETTING_NAME,
-                ColorCorrectionRepositoryImpl.ENABLED,
-                TEST_USER_1.identifier
+                SETTING_NAME,
+                ENABLED,
+                testUser1.identifier
             )
             runCurrent()
-            Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isTrue()
 
             settings.putIntForUser(
-                ColorCorrectionRepositoryImpl.SETTING_NAME,
-                ColorCorrectionRepositoryImpl.DISABLED,
-                TEST_USER_1.identifier
+                SETTING_NAME,
+                DISABLED,
+                testUser1.identifier
             )
             runCurrent()
-            Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
+
+            Truth.assertThat(flowValues.size).isEqualTo(3)
+            Truth.assertThat(flowValues).containsExactly(false, true, false).inOrder()
         }
 
     @Test
     fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() =
         scope.runTest {
-            underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
-            settings.putIntForUser(
-                ColorCorrectionRepositoryImpl.SETTING_NAME,
-                ColorCorrectionRepositoryImpl.DISABLED,
-                TEST_USER_1.identifier
-            )
-            underTest.isEnabled(TEST_USER_2).launchIn(backgroundScope)
-            settings.putIntForUser(
-                ColorCorrectionRepositoryImpl.SETTING_NAME,
-                ColorCorrectionRepositoryImpl.DISABLED,
-                TEST_USER_2.identifier
-            )
-
-            runCurrent()
-            Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
-            Truth.assertThat(underTest.isEnabled(TEST_USER_2).first()).isFalse()
+            val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1))
+            val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2))
 
             settings.putIntForUser(
-                ColorCorrectionRepositoryImpl.SETTING_NAME,
-                ColorCorrectionRepositoryImpl.ENABLED,
-                TEST_USER_1.identifier
+                SETTING_NAME,
+                DISABLED,
+                testUser1.identifier
+            )
+            settings.putIntForUser(
+                SETTING_NAME,
+                DISABLED,
+                testUser2.identifier
             )
             runCurrent()
-            Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isTrue()
-            Truth.assertThat(underTest.isEnabled(TEST_USER_2).first()).isFalse()
+
+            Truth.assertThat(lastValueUser1).isFalse()
+            Truth.assertThat(lastValueUser2).isFalse()
+
+            settings.putIntForUser(
+                SETTING_NAME,
+                ENABLED,
+                testUser1.identifier
+            )
+            runCurrent()
+
+            Truth.assertThat(lastValueUser1).isTrue()
+            Truth.assertThat(lastValueUser2).isFalse()
         }
 
     @Test
     fun setEnabled() =
         scope.runTest {
-            val success = underTest.setIsEnabled(true, TEST_USER_1)
+            val success = underTest.setIsEnabled(true, testUser1)
             runCurrent()
             Truth.assertThat(success).isTrue()
 
             val actualValue =
                 settings.getIntForUser(
-                    ColorCorrectionRepositoryImpl.SETTING_NAME,
-                    TEST_USER_1.identifier
+                    SETTING_NAME,
+                    testUser1.identifier
                 )
-            Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.ENABLED)
+            Truth.assertThat(actualValue).isEqualTo(ENABLED)
         }
 
     @Test
     fun setDisabled() =
         scope.runTest {
-            val success = underTest.setIsEnabled(false, TEST_USER_1)
+            val success = underTest.setIsEnabled(false, testUser1)
             runCurrent()
             Truth.assertThat(success).isTrue()
 
             val actualValue =
                 settings.getIntForUser(
-                    ColorCorrectionRepositoryImpl.SETTING_NAME,
-                    TEST_USER_1.identifier
+                    SETTING_NAME,
+                    testUser1.identifier
                 )
-            Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.DISABLED)
+            Truth.assertThat(actualValue).isEqualTo(DISABLED)
         }
+
+    companion object {
+        private const val SETTING_NAME = ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED
+        private const val DISABLED = 0
+        private const val ENABLED = 1
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
index 3f05fef..4853529 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
@@ -21,11 +21,11 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
@@ -39,6 +39,8 @@
 @RunWith(AndroidJUnit4::class)
 class ColorInversionRepositoryImplTest : SysuiTestCase() {
 
+    private val testUser1 = UserHandle.of(1)!!
+    private val testUser2 = UserHandle.of(2)!!
     private val testDispatcher = StandardTestDispatcher()
     private val scope = TestScope(testDispatcher)
     private val settings: FakeSettings = FakeSettings()
@@ -50,6 +52,7 @@
         underTest =
             ColorInversionRepositoryImpl(
                 testDispatcher,
+                scope.backgroundScope,
                 settings,
             )
     }
@@ -57,76 +60,68 @@
     @Test
     fun isEnabled_initiallyGetsSettingsValue() =
         scope.runTest {
-            settings.putIntForUser(SETTING_NAME, 1, TEST_USER_1.identifier)
+            val actualValue by collectLastValue(underTest.isEnabled(testUser1))
 
-            underTest =
-                ColorInversionRepositoryImpl(
-                    testDispatcher,
-                    settings,
-                )
-
-            underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
+            settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
             runCurrent()
 
-            val actualValue: Boolean = underTest.isEnabled(TEST_USER_1).first()
             assertThat(actualValue).isTrue()
         }
 
     @Test
     fun isEnabled_settingUpdated_valueUpdated() =
         scope.runTest {
-            underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
+            val flowValues: List<Boolean> by
+                collectValues(underTest.isEnabled(testUser1))
 
-            settings.putIntForUser(SETTING_NAME, DISABLED, TEST_USER_1.identifier)
+            settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
             runCurrent()
-            assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
+            settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
+            runCurrent()
+            settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
+            runCurrent()
 
-            settings.putIntForUser(SETTING_NAME, ENABLED, TEST_USER_1.identifier)
-            runCurrent()
-            assertThat(underTest.isEnabled(TEST_USER_1).first()).isTrue()
-
-            settings.putIntForUser(SETTING_NAME, DISABLED, TEST_USER_1.identifier)
-            runCurrent()
-            assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
+            assertThat(flowValues.size).isEqualTo(3)
+            assertThat(flowValues).containsExactly(false, true, false).inOrder()
         }
 
     @Test
     fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() =
         scope.runTest {
-            underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
-            settings.putIntForUser(SETTING_NAME, DISABLED, TEST_USER_1.identifier)
-            underTest.isEnabled(TEST_USER_2).launchIn(backgroundScope)
-            settings.putIntForUser(SETTING_NAME, DISABLED, TEST_USER_2.identifier)
+            val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1))
+            val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2))
 
+            settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
+            settings.putIntForUser(SETTING_NAME, DISABLED, testUser2.identifier)
             runCurrent()
-            assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
-            assertThat(underTest.isEnabled(TEST_USER_2).first()).isFalse()
+            assertThat(lastValueUser1).isFalse()
+            assertThat(lastValueUser2).isFalse()
 
-            settings.putIntForUser(SETTING_NAME, ENABLED, TEST_USER_1.identifier)
+            settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
             runCurrent()
-            assertThat(underTest.isEnabled(TEST_USER_1).first()).isTrue()
-            assertThat(underTest.isEnabled(TEST_USER_2).first()).isFalse()
+            assertThat(lastValueUser1).isTrue()
+            assertThat(lastValueUser2).isFalse()
         }
 
     @Test
     fun setEnabled() =
         scope.runTest {
-            val success = underTest.setIsEnabled(true, TEST_USER_1)
+            val success = underTest.setIsEnabled(true, testUser1)
             runCurrent()
             assertThat(success).isTrue()
 
-            val actualValue = settings.getIntForUser(SETTING_NAME, TEST_USER_1.identifier)
+            val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
             assertThat(actualValue).isEqualTo(ENABLED)
         }
 
     @Test
     fun setDisabled() =
         scope.runTest {
-            val success = underTest.setIsEnabled(false, TEST_USER_1)
+            val success = underTest.setIsEnabled(false, testUser1)
             runCurrent()
             assertThat(success).isTrue()
 
-            val actualValue = settings.getIntForUser(SETTING_NAME, TEST_USER_1.identifier)
+            val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
             assertThat(actualValue).isEqualTo(DISABLED)
         }
 
@@ -134,7 +129,5 @@
         private const val SETTING_NAME = ACCESSIBILITY_DISPLAY_INVERSION_ENABLED
         private const val DISABLED = 0
         private const val ENABLED = 1
-        private val TEST_USER_1 = UserHandle.of(1)!!
-        private val TEST_USER_2 = UserHandle.of(2)!!
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
new file mode 100644
index 0000000..ce22e28
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UserA11yQsShortcutsRepositoryTest : SysuiTestCase() {
+    private val secureSettings = FakeSettings()
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private val underTest =
+        UserA11yQsShortcutsRepository(
+            USER_ID,
+            secureSettings,
+            testScope.backgroundScope,
+            testDispatcher
+        )
+
+    @Test
+    fun targetsMatchesSetting() =
+        testScope.runTest {
+            val observedTargets by collectLastValue(underTest.targets)
+            val a11yQsTargets = setOf("a", "b", "c")
+            secureSettings.putStringForUser(
+                SETTING_NAME,
+                a11yQsTargets.joinToString(SEPARATOR),
+                USER_ID
+            )
+
+            assertThat(observedTargets).isEqualTo(a11yQsTargets)
+        }
+
+    companion object {
+        private const val USER_ID = 0
+        private const val SEPARATOR = ":"
+        private const val SETTING_NAME = Settings.Secure.ACCESSIBILITY_QS_TARGETS
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index b9759cc..caf9219 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -29,10 +29,13 @@
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
@@ -59,8 +62,8 @@
     @Mock private lateinit var tableLogger: TableLogBuffer
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
 
-    private val testUtils = SceneTestUtils(this)
-    private val testScope = testUtils.testScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
     private val clock = FakeSystemClock()
     private val userRepository = FakeUserRepository()
     private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
@@ -82,8 +85,8 @@
         underTest =
             AuthenticationRepositoryImpl(
                 applicationScope = testScope.backgroundScope,
-                backgroundDispatcher = testUtils.testDispatcher,
-                flags = testUtils.sceneContainerFlags,
+                backgroundDispatcher = kosmos.testDispatcher,
+                flags = kosmos.sceneContainerFlags,
                 clock = clock,
                 getSecurityMode = getSecurityMode,
                 userRepository = userRepository,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 10c16bd..cb8cebf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -22,6 +22,7 @@
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.None
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
@@ -29,7 +30,8 @@
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.authentication.shared.model.AuthenticationWipeModel
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,9 +47,9 @@
 @RunWith(AndroidJUnit4::class)
 class AuthenticationInteractorTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val underTest = utils.authenticationInteractor()
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val underTest = kosmos.authenticationInteractor
 
     private val onAuthenticationResult by
         testScope.collectLastValue(underTest.onAuthenticationResult)
@@ -62,7 +64,7 @@
             assertThat(authMethod).isEqualTo(Pin)
             assertThat(underTest.getAuthenticationMethod()).isEqualTo(Pin)
 
-            utils.authenticationRepository.setAuthenticationMethod(Password)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
 
             assertThat(authMethod).isEqualTo(Password)
             assertThat(underTest.getAuthenticationMethod()).isEqualTo(Password)
@@ -74,7 +76,7 @@
             val authMethod by collectLastValue(underTest.authenticationMethod)
             runCurrent()
 
-            utils.authenticationRepository.setAuthenticationMethod(None)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(None)
 
             assertThat(authMethod).isEqualTo(None)
             assertThat(underTest.getAuthenticationMethod()).isEqualTo(None)
@@ -83,7 +85,7 @@
     @Test
     fun authenticate_withCorrectPin_succeeds() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
 
             assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
         }
@@ -91,7 +93,7 @@
     @Test
     fun authenticate_withIncorrectPin_fails() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
 
             assertFailed(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4)))
         }
@@ -99,7 +101,7 @@
     @Test(expected = IllegalArgumentException::class)
     fun authenticate_withEmptyPin_throwsException() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             underTest.authenticate(listOf())
         }
 
@@ -107,7 +109,7 @@
     fun authenticate_withCorrectMaxLengthPin_succeeds() =
         testScope.runTest {
             val correctMaxLengthPin = List(16) { 9 }
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 overrideCredential(correctMaxLengthPin)
             }
@@ -124,7 +126,7 @@
             // If the policy changes, there is work to do in SysUI.
             assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17)
 
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
 
             assertFailed(underTest.authenticate(List(17) { 9 }))
         }
@@ -132,7 +134,7 @@
     @Test
     fun authenticate_withCorrectPassword_succeeds() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(Password)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
 
             assertSucceeded(underTest.authenticate("password".toList()))
         }
@@ -140,7 +142,7 @@
     @Test
     fun authenticate_withIncorrectPassword_fails() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(Password)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
 
             assertFailed(underTest.authenticate("alohomora".toList()))
         }
@@ -148,7 +150,7 @@
     @Test
     fun authenticate_withCorrectPattern_succeeds() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(Pattern)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern)
 
             assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.PATTERN))
         }
@@ -156,7 +158,7 @@
     @Test
     fun authenticate_withIncorrectPattern_fails() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(Pattern)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern)
             val wrongPattern =
                 listOf(
                     AuthenticationPatternCoordinate(x = 2, y = 0),
@@ -172,7 +174,7 @@
     fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNull() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(true)
             }
@@ -182,14 +184,14 @@
 
             assertSkipped(underTest.authenticate(shorterPin, tryAutoConfirm = true))
             assertThat(underTest.lockoutEndTimestamp).isNull()
-            assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
+            assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(0)
         }
 
     @Test
     fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalse() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(true)
             }
@@ -207,7 +209,7 @@
     fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalse() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(true)
             }
@@ -225,7 +227,7 @@
     fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrue() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(true)
             }
@@ -241,7 +243,7 @@
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(true)
                 reportLockoutStarted(42)
@@ -258,7 +260,7 @@
     @Test
     fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNull() =
         testScope.runTest {
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(false)
             }
@@ -271,7 +273,7 @@
     @Test
     fun tryAutoConfirm_withoutCorrectPassword_returnsNull() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(Password)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
 
             assertSkipped(underTest.authenticate("password".toList(), tryAutoConfirm = true))
         }
@@ -280,7 +282,7 @@
     fun isAutoConfirmEnabled_featureDisabled_returnsFalse() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
-            utils.authenticationRepository.setAutoConfirmFeatureEnabled(false)
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(false)
 
             assertThat(isAutoConfirmEnabled).isFalse()
         }
@@ -289,7 +291,7 @@
     fun isAutoConfirmEnabled_featureEnabled_returnsTrue() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
-            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
 
             assertThat(isAutoConfirmEnabled).isTrue()
         }
@@ -298,7 +300,7 @@
     fun isAutoConfirmEnabled_featureEnabledButDisabledByLockout() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
-            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
 
             // The feature is enabled.
             assertThat(isAutoConfirmEnabled).isTrue()
@@ -308,7 +310,7 @@
                 assertFailed(underTest.authenticate(listOf(5, 6, 7))) // Wrong PIN
             }
             assertThat(underTest.lockoutEndTimestamp).isNotNull()
-            assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
+            assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(1)
 
             // Lockout disabled auto-confirm.
             assertThat(isAutoConfirmEnabled).isFalse()
@@ -336,7 +338,7 @@
             val failedAuthenticationAttempts by
                 collectLastValue(underTest.failedAuthenticationAttempts)
 
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
 
             assertSucceeded(underTest.authenticate(correctPin))
@@ -366,7 +368,7 @@
     @Test
     fun lockoutEndTimestamp() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
 
             underTest.authenticate(correctPin)
@@ -384,7 +386,7 @@
             val expectedLockoutEndTimestamp =
                 testScope.currentTime + FakeAuthenticationRepository.LOCKOUT_DURATION_MS
             assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp)
-            assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
+            assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(1)
 
             // Correct PIN, but locked out, so doesn't attempt it:
             assertSkipped(underTest.authenticate(correctPin), assertNoResultEvents = false)
@@ -409,7 +411,7 @@
     fun upcomingWipe() =
         testScope.runTest {
             val upcomingWipe by collectLastValue(underTest.upcomingWipe)
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
             val wrongPin = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 }
 
@@ -418,7 +420,7 @@
 
             var expectedFailedAttempts = 0
             var remainingFailedAttempts =
-                utils.authenticationRepository.getMaxFailedUnlockAttemptsForWipe()
+                kosmos.fakeAuthenticationRepository.getMaxFailedUnlockAttemptsForWipe()
             assertThat(remainingFailedAttempts)
                 .isGreaterThan(LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE)
 
@@ -458,7 +460,7 @@
     fun hintedPinLength_withoutAutoConfirm_isNull() =
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(false)
             }
@@ -470,11 +472,13 @@
     fun hintedPinLength_withAutoConfirmPinTooShort_isNull() =
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 overrideCredential(
                     buildList {
-                        repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) }
+                        repeat(kosmos.fakeAuthenticationRepository.hintedPinLength - 1) {
+                            add(it + 1)
+                        }
                     }
                 )
                 setAutoConfirmFeatureEnabled(true)
@@ -487,28 +491,31 @@
     fun hintedPinLength_withAutoConfirmPinAtRightLength_isSameLength() =
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(true)
                 overrideCredential(
                     buildList {
-                        repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) }
+                        repeat(kosmos.fakeAuthenticationRepository.hintedPinLength) { add(it + 1) }
                     }
                 )
             }
 
-            assertThat(hintedPinLength).isEqualTo(utils.authenticationRepository.hintedPinLength)
+            assertThat(hintedPinLength)
+                .isEqualTo(kosmos.fakeAuthenticationRepository.hintedPinLength)
         }
 
     @Test
     fun hintedPinLength_withAutoConfirmPinTooLong_isNull() =
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            utils.authenticationRepository.apply {
+            kosmos.fakeAuthenticationRepository.apply {
                 setAuthenticationMethod(Pin)
                 overrideCredential(
                     buildList {
-                        repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) }
+                        repeat(kosmos.fakeAuthenticationRepository.hintedPinLength + 1) {
+                            add(it + 1)
+                        }
                     }
                 )
                 setAutoConfirmFeatureEnabled(true)
@@ -520,10 +527,10 @@
     @Test
     fun authenticate_withTooShortPassword() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(Password)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
 
             val tooShortPassword = buildList {
-                repeat(utils.authenticationRepository.minPasswordLength - 1) { time ->
+                repeat(kosmos.fakeAuthenticationRepository.minPasswordLength - 1) { time ->
                     add("$time")
                 }
             }
@@ -534,7 +541,7 @@
         assertThat(authenticationResult).isEqualTo(AuthenticationResult.SUCCEEDED)
         assertThat(onAuthenticationResult).isTrue()
         assertThat(underTest.lockoutEndTimestamp).isNull()
-        assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
+        assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(0)
         assertThat(failedAuthenticationAttempts).isEqualTo(0)
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 1c1335f..343280d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -801,6 +801,20 @@
     }
 
     @Test
+    public void testOnBiometricPromptDismissedCallback_hideAuthenticationDialog() {
+        // GIVEN a callback is registered
+        AuthController.Callback callback = mock(AuthController.Callback.class);
+        mAuthController.addCallback(callback);
+
+        // WHEN dialog is shown and then dismissed
+        showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
+        mAuthController.hideAuthenticationDialog(mAuthController.mCurrentDialog.getRequestId());
+
+        // THEN callback should be received
+        verify(callback).onBiometricPromptDismissed();
+    }
+
+    @Test
     public void testSubscribesToLogContext() {
         mAuthController.setBiometricContextListener(mContextListener);
         verify(mLogContextInteractor).addBiometricContextListener(same(mContextListener));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
index 15633d1..72e884e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -16,8 +16,10 @@
 
 package com.android.systemui.biometrics
 
+import android.graphics.Bitmap
 import android.hardware.biometrics.BiometricManager.Authenticators
 import android.hardware.biometrics.ComponentInfoInternal
+import android.hardware.biometrics.PromptContentView
 import android.hardware.biometrics.PromptInfo
 import android.hardware.biometrics.SensorProperties
 import android.hardware.biometrics.SensorPropertiesInternal
@@ -116,18 +118,24 @@
 }
 
 internal fun promptInfo(
+    logoRes: Int = -1,
+    logoBitmap: Bitmap? = null,
     title: String = "title",
     subtitle: String = "sub",
     description: String = "desc",
+    contentView: PromptContentView? = null,
     credentialTitle: String? = "cred title",
     credentialSubtitle: String? = "cred sub",
     credentialDescription: String? = "cred desc",
     negativeButton: String = "neg",
 ): PromptInfo {
     val info = PromptInfo()
+    info.logoRes = logoRes
+    info.logoBitmap = logoBitmap
     info.title = title
     info.subtitle = subtitle
     info.description = description
+    info.contentView = contentView
     credentialTitle?.let { info.deviceCredentialTitle = it }
     credentialSubtitle?.let { info.deviceCredentialSubtitle = it }
     credentialDescription?.let { info.deviceCredentialDescription = it }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index b0beab9..f7743e2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.biometrics.ui.viewmodel.DefaultUdfpsTouchOverlayViewModel
 import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel
@@ -118,6 +119,7 @@
     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock private lateinit var shadeInteractor: ShadeInteractor
     @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
+    @Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
 
     private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
     private var overlayParams: UdfpsOverlayParams = UdfpsOverlayParams()
@@ -174,7 +176,8 @@
                 mSelectedUserInteractor,
                 { deviceEntryUdfpsTouchOverlayViewModel },
                 { defaultUdfpsTouchOverlayViewModel },
-                shadeInteractor
+                shadeInteractor,
+                udfpsOverlayInteractor,
             )
         block()
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index cec2d74..90c3c14 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -76,6 +76,7 @@
 import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
 import com.android.systemui.biometrics.udfps.InteractionEvent;
 import com.android.systemui.biometrics.udfps.NormalizedTouchData;
@@ -214,6 +215,8 @@
     @Mock
     private AlternateBouncerInteractor mAlternateBouncerInteractor;
     @Mock
+    private UdfpsOverlayInteractor mUdfpsOverlayInteractor;
+    @Mock
     private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
     @Mock
     private SelectedUserInteractor mSelectedUserInteractor;
@@ -342,8 +345,8 @@
                 mFpsUnlockTracker,
                 mKeyguardTransitionInteractor,
                 mDeviceEntryUdfpsTouchOverlayViewModel,
-                mDefaultUdfpsTouchOverlayViewModel
-
+                mDefaultUdfpsTouchOverlayViewModel,
+                mUdfpsOverlayInteractor
         );
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
@@ -1219,6 +1222,40 @@
     }
 
     @Test
+    public void onDownTouchReceivedWithoutPreviousUp() throws RemoteException {
+        final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
+                0L);
+        final TouchProcessorResult processorResultDown =
+                new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN,
+                        -1 /* pointerId */, touchData);
+
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+
+        // WHEN ACTION_DOWN is received and touch is within sensor
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                processorResultDown);
+        MotionEvent firstDownEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, firstDownEvent);
+        mBiometricExecutor.runAllReady();
+        firstDownEvent.recycle();
+
+        // And another ACTION_DOWN is received without an ACTION_UP before
+        MotionEvent secondDownEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, secondDownEvent);
+        mBiometricExecutor.runAllReady();
+        secondDownEvent.recycle();
+
+        // THEN the touch is still processed
+        verify(mFingerprintManager, times(2)).onPointerDown(anyLong(), anyInt(), anyInt(),
+                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(),
+                anyBoolean());
+    }
+
+    @Test
     public void onTouch_pilferPointerWhenAltBouncerShowing()
             throws RemoteException {
         final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index 13b53a8..7d9c2f9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -27,6 +27,7 @@
 import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.dump.DumpManager;
@@ -76,6 +77,7 @@
     protected @Mock UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
     protected @Mock SelectedUserInteractor mSelectedUserInteractor;
     protected @Mock KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    protected @Mock UdfpsOverlayInteractor mUdfpsOverlayInteractor;
 
     protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
 
@@ -152,7 +154,8 @@
                 mUdfpsKeyguardAccessibilityDelegate,
                 mSelectedUserInteractor,
                 mKeyguardTransitionInteractor,
-                mShadeInteractor);
+                mShadeInteractor,
+                mUdfpsOverlayInteractor);
         return controller;
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index 335ac9d..0e257bc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF
 import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
@@ -32,6 +33,7 @@
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
@@ -45,9 +47,11 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.time.SystemClock
+import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
@@ -130,6 +134,13 @@
                     repository = transitionRepository,
                 )
                 .keyguardTransitionInteractor
+        mUdfpsOverlayInteractor =
+            UdfpsOverlayInteractor(
+                context,
+                mock(AuthController::class.java),
+                mock(SelectedUserInteractor::class.java),
+                testScope.backgroundScope,
+            )
         return createUdfpsKeyguardViewController(/* useModernBouncer */ true)
     }
 
@@ -239,6 +250,31 @@
         }
 
     @Test
+    fun shouldHandleTouchesChange() =
+        testScope.runTest {
+            val shouldHandleTouches by collectLastValue(mUdfpsOverlayInteractor.shouldHandleTouches)
+
+            // GIVEN view is attached + on the keyguard
+            mController.onViewAttached()
+            captureStatusBarStateListeners()
+            sendStatusBarStateChanged(StatusBarState.KEYGUARD)
+            whenever(mView.setPauseAuth(true)).thenReturn(true)
+            whenever(mView.unpausedAlpha).thenReturn(0)
+
+            // WHEN panelViewExpansion changes to expanded
+            val job = mController.listenForBouncerExpansion(this)
+            keyguardBouncerRepository.setPrimaryShow(true)
+            keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
+            runCurrent()
+
+            // THEN UDFPS auth is paused and should not handle touches
+            assertThat(mController.shouldPauseAuth()).isTrue()
+            assertThat(shouldHandleTouches!!).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
     fun fadeFromDialogSuggestedAlpha() =
         testScope.runTest {
             // GIVEN view is attached and status bar expansion is 1f
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt
new file mode 100644
index 0000000..97c407c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import android.hardware.biometrics.SensorLocationInternal
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FingerprintPropertyInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val underTest by lazy { kosmos.fingerprintPropertyInteractor }
+    private val repository by lazy { kosmos.fingerprintPropertyRepository }
+    private val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
+    private val displayRepository by lazy { kosmos.displayRepository }
+
+    @Test
+    fun sensorLocation_resolution1f() =
+        testScope.runTest {
+            val currSensorLocation by collectLastValue(underTest.sensorLocation)
+
+            displayRepository.emitDisplayChangeEvent(0)
+            runCurrent()
+            repository.setProperties(
+                sensorId = 0,
+                strength = SensorStrength.STRONG,
+                sensorType = FingerprintSensorType.UDFPS_OPTICAL,
+                sensorLocations =
+                    mapOf(
+                        Pair("", SensorLocationInternal("", 4, 4, 2)),
+                        Pair("otherDisplay", SensorLocationInternal("", 1, 1, 1))
+                    )
+            )
+            runCurrent()
+            configurationRepository.setScaleForResolution(1f)
+            runCurrent()
+
+            assertThat(currSensorLocation?.centerX).isEqualTo(4)
+            assertThat(currSensorLocation?.centerY).isEqualTo(4)
+            assertThat(currSensorLocation?.radius).isEqualTo(2)
+        }
+
+    @Test
+    fun sensorLocation_resolution2f() =
+        testScope.runTest {
+            val currSensorLocation by collectLastValue(underTest.sensorLocation)
+
+            displayRepository.emitDisplayChangeEvent(0)
+            runCurrent()
+            repository.setProperties(
+                sensorId = 0,
+                strength = SensorStrength.STRONG,
+                sensorType = FingerprintSensorType.UDFPS_OPTICAL,
+                sensorLocations =
+                    mapOf(
+                        Pair("", SensorLocationInternal("", 4, 4, 2)),
+                        Pair("otherDisplay", SensorLocationInternal("", 1, 1, 1))
+                    )
+            )
+            runCurrent()
+            configurationRepository.setScaleForResolution(2f)
+            runCurrent()
+
+            assertThat(currSensorLocation?.centerX).isEqualTo(4 * 2)
+            assertThat(currSensorLocation?.centerY).isEqualTo(4 * 2)
+            assertThat(currSensorLocation?.radius).isEqualTo(2 * 2)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
index 8d6d052..a862112 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
@@ -37,6 +37,8 @@
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -112,6 +114,7 @@
                 windowManager,
                 displayStateInteractor,
                 Optional.of(fingerprintInteractiveToAuthProvider),
+                kosmos.biometricSettingsRepository,
                 kosmos.keyguardTransitionInteractor,
                 SideFpsLogger(logcatLogBuffer("SfpsLogger"))
             )
@@ -420,6 +423,7 @@
     @Test
     fun isProlongedTouchRequiredForAuthentication_dependsOnSettingsToggle() =
         testScope.runTest {
+            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
             val isEnabled by collectLastValue(underTest.isProlongedTouchRequiredForAuthentication)
             setupFingerprint(FingerprintSensorType.POWER_BUTTON)
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
index c2117ae..a67b093 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
@@ -20,8 +20,11 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
@@ -36,8 +39,8 @@
 @RunWith(AndroidJUnit4::class)
 class EmergencyServicesRepositoryImplTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
     private lateinit var underTest: EmergencyServicesRepository
 
@@ -52,7 +55,7 @@
             EmergencyServicesRepository(
                 resources = context.resources,
                 applicationScope = testScope.backgroundScope,
-                configurationRepository = utils.configurationRepository,
+                configurationRepository = kosmos.configurationRepository,
             )
     }
 
@@ -71,7 +74,7 @@
 
     private fun TestScope.setEmergencyCallWhileSimLocked(isEnabled: Boolean) {
         overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, isEnabled)
-        utils.configurationRepository.onConfigurationChange()
+        kosmos.fakeConfigurationRepository.onConfigurationChange()
         runCurrent()
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
index 67ce86b..741cde8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
@@ -16,27 +16,32 @@
 
 package com.android.systemui.bouncer.domain.interactor
 
-import android.app.ActivityTaskManager
 import android.telecom.TelecomManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.app.activityTaskManager
 import com.android.internal.R
+import com.android.internal.logging.fakeMetricsLogger
 import com.android.internal.logging.nano.MetricsProto
-import com.android.internal.logging.testing.FakeMetricsLogger
-import com.android.internal.util.EmergencyAffordanceManager
+import com.android.internal.util.emergencyAffordanceManager
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.whenever
+import com.android.telecom.telecomManager
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -53,27 +58,26 @@
 @RunWith(AndroidJUnit4::class)
 class BouncerActionButtonInteractorTest : SysuiTestCase() {
 
-    @Mock private lateinit var activityTaskManager: ActivityTaskManager
-    @Mock private lateinit var emergencyAffordanceManager: EmergencyAffordanceManager
     @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
-    @Mock private lateinit var tableLogger: TableLogBuffer
     @Mock private lateinit var telecomManager: TelecomManager
 
-    private lateinit var utils: SceneTestUtils
-    private lateinit var testScope: TestScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val metricsLogger = kosmos.fakeMetricsLogger
+    private val activityTaskManager = kosmos.activityTaskManager
+    private val emergencyAffordanceManager = kosmos.emergencyAffordanceManager
+
     private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
 
-    private val metricsLogger = FakeMetricsLogger()
     private var currentUserId: Int = 0
     private var needsEmergencyAffordance = true
 
-    private lateinit var underTest: BouncerActionButtonInteractor
-
     @Before
     fun setUp() {
-        utils = SceneTestUtils(this)
-        testScope = utils.testScope
         MockitoAnnotations.initMocks(this)
+        kosmos.fakeSceneContainerFlags.enabled = true
+
+        mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
 
         overrideResource(R.string.lockscreen_emergency_call, MESSAGE_EMERGENCY_CALL)
         overrideResource(R.string.lockscreen_return_to_call, MESSAGE_RETURN_TO_CALL)
@@ -86,34 +90,18 @@
             .thenReturn(needsEmergencyAffordance)
         whenever(telecomManager.isInCall).thenReturn(false)
 
-        utils.featureFlags.set(REFACTOR_GETCURRENTUSER, true)
+        kosmos.fakeFeatureFlagsClassic.set(REFACTOR_GETCURRENTUSER, true)
 
-        mobileConnectionsRepository =
-            FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogger)
+        kosmos.fakeTelephonyRepository.setHasTelephonyRadio(true)
 
-        utils.telephonyRepository.setHasTelephonyRadio(true)
-
-        underTest =
-            utils.bouncerActionButtonInteractor(
-                mobileConnectionsRepository = mobileConnectionsRepository,
-                activityTaskManager = activityTaskManager,
-                telecomManager = telecomManager,
-                emergencyAffordanceManager = emergencyAffordanceManager,
-                metricsLogger = metricsLogger,
-            )
+        kosmos.telecomManager = telecomManager
     }
 
     @Test
     fun noTelephonyRadio_noButton() =
         testScope.runTest {
-            utils.telephonyRepository.setHasTelephonyRadio(false)
-            underTest =
-                utils.bouncerActionButtonInteractor(
-                    mobileConnectionsRepository = mobileConnectionsRepository,
-                    activityTaskManager = activityTaskManager,
-                    telecomManager = telecomManager,
-                )
-
+            kosmos.fakeTelephonyRepository.setHasTelephonyRadio(false)
+            val underTest = kosmos.bouncerActionButtonInteractor
             val actionButton by collectLastValue(underTest.actionButton)
             assertThat(actionButton).isNull()
         }
@@ -121,12 +109,8 @@
     @Test
     fun noTelecomManager_noButton() =
         testScope.runTest {
-            underTest =
-                utils.bouncerActionButtonInteractor(
-                    mobileConnectionsRepository = mobileConnectionsRepository,
-                    activityTaskManager = activityTaskManager,
-                    telecomManager = null,
-                )
+            kosmos.telecomManager = null
+            val underTest = kosmos.bouncerActionButtonInteractor
             val actionButton by collectLastValue(underTest.actionButton)
             assertThat(actionButton).isNull()
         }
@@ -134,8 +118,9 @@
     @Test
     fun duringCall_returnToCallButton() =
         testScope.runTest {
+            val underTest = kosmos.bouncerActionButtonInteractor
             val actionButton by collectLastValue(underTest.actionButton)
-            utils.telephonyRepository.setIsInCall(true)
+            kosmos.fakeTelephonyRepository.setIsInCall(true)
 
             assertThat(actionButton).isNotNull()
             assertThat(actionButton?.label).isEqualTo(MESSAGE_RETURN_TO_CALL)
@@ -143,6 +128,7 @@
             assertThat(actionButton?.onLongClick).isNull()
 
             actionButton?.onClick?.invoke()
+            runCurrent()
 
             assertThat(metricsLogger.logs.size).isEqualTo(1)
             assertThat(metricsLogger.logs.element().category)
@@ -154,10 +140,13 @@
     @Test
     fun noCall_secureAuthMethod_emergencyCallButton() =
         testScope.runTest {
+            val underTest = kosmos.bouncerActionButtonInteractor
             val actionButton by collectLastValue(underTest.actionButton)
             mobileConnectionsRepository.isAnySimSecure.value = false
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.telephonyRepository.setIsInCall(false)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeTelephonyRepository.setIsInCall(false)
 
             assertThat(actionButton).isNotNull()
             assertThat(actionButton?.label).isEqualTo(MESSAGE_EMERGENCY_CALL)
@@ -165,6 +154,7 @@
             assertThat(actionButton?.onLongClick).isNotNull()
 
             actionButton?.onClick?.invoke()
+            runCurrent()
 
             assertThat(metricsLogger.logs.size).isEqualTo(1)
             assertThat(metricsLogger.logs.element().category)
@@ -182,10 +172,14 @@
     @Test
     fun noCall_insecureAuthMethodButSecureSim_emergencyCallButton() =
         testScope.runTest {
+            val underTest = kosmos.bouncerActionButtonInteractor
             val actionButton by collectLastValue(underTest.actionButton)
             mobileConnectionsRepository.isAnySimSecure.value = true
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
-            utils.telephonyRepository.setIsInCall(false)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
+            kosmos.fakeTelephonyRepository.setIsInCall(false)
+            runCurrent()
 
             assertThat(actionButton).isNotNull()
             assertThat(actionButton?.label).isEqualTo(MESSAGE_EMERGENCY_CALL)
@@ -196,10 +190,13 @@
     @Test
     fun noCall_insecure_noButton() =
         testScope.runTest {
+            val underTest = kosmos.bouncerActionButtonInteractor
             val actionButton by collectLastValue(underTest.actionButton)
             mobileConnectionsRepository.isAnySimSecure.value = false
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
-            utils.telephonyRepository.setIsInCall(false)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
+            kosmos.fakeTelephonyRepository.setIsInCall(false)
 
             assertThat(actionButton).isNull()
         }
@@ -207,12 +204,15 @@
     @Test
     fun noCall_simSecureButEmergencyNotSupported_noButton() =
         testScope.runTest {
+            val underTest = kosmos.bouncerActionButtonInteractor
             val actionButton by collectLastValue(underTest.actionButton)
             mobileConnectionsRepository.isAnySimSecure.value = true
             overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, false)
-            utils.configurationRepository.onConfigurationChange()
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
-            utils.telephonyRepository.setIsInCall(false)
+            kosmos.fakeConfigurationRepository.onConfigurationChange()
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
+            kosmos.fakeTelephonyRepository.setIsInCall(false)
             runCurrent()
 
             assertThat(actionButton).isNull()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 93ba6a4..707777b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -20,14 +20,20 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
 import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -37,8 +43,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -46,17 +50,16 @@
 @RunWith(AndroidJUnit4::class)
 class BouncerInteractorTest : SysuiTestCase() {
 
-    @Mock private lateinit var mDeviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor
-
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val authenticationInteractor = utils.authenticationInteractor()
+    private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+    private val testScope = kosmos.testScope
+    private val authenticationInteractor = kosmos.authenticationInteractor
 
     private lateinit var underTest: BouncerInteractor
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+
         overrideResource(R.string.keyguard_enter_your_pin, MESSAGE_ENTER_YOUR_PIN)
         overrideResource(R.string.keyguard_enter_your_password, MESSAGE_ENTER_YOUR_PASSWORD)
         overrideResource(R.string.keyguard_enter_your_pattern, MESSAGE_ENTER_YOUR_PATTERN)
@@ -64,11 +67,7 @@
         overrideResource(R.string.kg_wrong_password, MESSAGE_WRONG_PASSWORD)
         overrideResource(R.string.kg_wrong_pattern, MESSAGE_WRONG_PATTERN)
 
-        underTest =
-            utils.bouncerInteractor(
-                authenticationInteractor = authenticationInteractor,
-                deviceEntryFaceAuthInteractor = mDeviceEntryFaceAuthInteractor,
-            )
+        underTest = kosmos.bouncerInteractor
     }
 
     @Test
@@ -76,7 +75,9 @@
         testScope.runTest {
             val message by collectLastValue(underTest.message)
 
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
             runCurrent()
             underTest.clearMessage()
             assertThat(message).isNull()
@@ -100,7 +101,9 @@
     @Test
     fun pinAuthMethod_sim_skipsAuthentication() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Sim
+            )
             runCurrent()
 
             // We rely on TelephonyManager to authenticate the sim card.
@@ -115,9 +118,11 @@
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
 
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
             runCurrent()
-            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
             assertThat(isAutoConfirmEnabled).isTrue()
 
             // Incomplete input.
@@ -143,7 +148,9 @@
         testScope.runTest {
             val message by collectLastValue(underTest.message)
 
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
             runCurrent()
 
             // Incomplete input.
@@ -166,7 +173,7 @@
     fun passwordAuthMethod() =
         testScope.runTest {
             val message by collectLastValue(underTest.message)
-            utils.authenticationRepository.setAuthenticationMethod(
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
             runCurrent()
@@ -186,7 +193,8 @@
             assertThat(
                     underTest.authenticate(
                         buildList {
-                            repeat(utils.authenticationRepository.minPasswordLength - 1) { time ->
+                            repeat(kosmos.fakeAuthenticationRepository.minPasswordLength - 1) { time
+                                ->
                                 add("$time")
                             }
                         }
@@ -204,7 +212,7 @@
     fun patternAuthMethod() =
         testScope.runTest {
             val message by collectLastValue(underTest.message)
-            utils.authenticationRepository.setAuthenticationMethod(
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pattern
             )
             runCurrent()
@@ -220,7 +228,8 @@
                     AuthenticationPatternCoordinate(0, 1),
                 )
             assertThat(wrongPattern).isNotEqualTo(FakeAuthenticationRepository.PATTERN)
-            assertThat(wrongPattern.size).isAtLeast(utils.authenticationRepository.minPatternLength)
+            assertThat(wrongPattern.size)
+                .isAtLeast(kosmos.fakeAuthenticationRepository.minPatternLength)
             assertThat(underTest.authenticate(wrongPattern)).isEqualTo(AuthenticationResult.FAILED)
             assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN)
 
@@ -231,7 +240,7 @@
             val tooShortPattern =
                 FakeAuthenticationRepository.PATTERN.subList(
                     0,
-                    utils.authenticationRepository.minPatternLength - 1
+                    kosmos.fakeAuthenticationRepository.minPatternLength - 1
                 )
             assertThat(underTest.authenticate(tooShortPattern))
                 .isEqualTo(AuthenticationResult.SKIPPED)
@@ -251,7 +260,9 @@
             val lockoutStartedEvents by collectValues(underTest.onLockoutStarted)
             val message by collectLastValue(underTest.message)
 
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
             assertThat(lockoutStartedEvents).isEmpty()
 
             // Try the wrong PIN repeatedly, until lockout is triggered:
@@ -297,16 +308,24 @@
     @Test
     fun intentionalUserInputEvent_registersTouchEvent() =
         testScope.runTest {
-            assertThat(utils.powerRepository.userTouchRegistered).isFalse()
+            assertThat(kosmos.fakePowerRepository.userTouchRegistered).isFalse()
             underTest.onIntentionalUserInput()
-            assertThat(utils.powerRepository.userTouchRegistered).isTrue()
+            assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue()
         }
 
     @Test
     fun intentionalUserInputEvent_notifiesFaceAuthInteractor() =
         testScope.runTest {
+            val isFaceAuthRunning by
+                collectLastValue(kosmos.fakeDeviceEntryFaceAuthRepository.isAuthRunning)
+            kosmos.deviceEntryFaceAuthInteractor.onDeviceLifted()
+            runCurrent()
+            assertThat(isFaceAuthRunning).isTrue()
+
             underTest.onIntentionalUserInput()
-            verify(mDeviceEntryFaceAuthInteractor).onPrimaryBouncerUserInput()
+            runCurrent()
+
+            assertThat(isFaceAuthRunning).isFalse()
         }
 
     companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
new file mode 100644
index 0000000..63f6c20
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -0,0 +1,512 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.bouncer.domain.interactor
+
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.TestableResources
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.DejankUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
+import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
+import com.android.systemui.bouncer.ui.BouncerView
+import com.android.systemui.bouncer.ui.BouncerViewDelegate
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.os.FakeHandler
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidJUnit4::class)
+class PrimaryBouncerInteractorTest : SysuiTestCase() {
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private lateinit var repository: KeyguardBouncerRepository
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView
+    @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+    @Mock private lateinit var mPrimaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
+    @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
+    private lateinit var mainHandler: FakeHandler
+    private lateinit var underTest: PrimaryBouncerInteractor
+    private lateinit var resources: TestableResources
+    private lateinit var trustRepository: FakeTrustRepository
+    private lateinit var testScope: TestScope
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(keyguardSecurityModel.getSecurityMode(anyInt()))
+            .thenReturn(KeyguardSecurityModel.SecurityMode.PIN)
+
+        DejankUtils.setImmediate(true)
+        testScope = TestScope()
+        mainHandler = FakeHandler(android.os.Looper.getMainLooper())
+        trustRepository = FakeTrustRepository()
+        underTest =
+            PrimaryBouncerInteractor(
+                repository,
+                bouncerView,
+                mainHandler,
+                keyguardStateController,
+                keyguardSecurityModel,
+                mPrimaryBouncerCallbackInteractor,
+                falsingCollector,
+                dismissCallbackRegistry,
+                context,
+                keyguardUpdateMonitor,
+                trustRepository,
+                testScope.backgroundScope,
+                mSelectedUserInteractor,
+                faceAuthInteractor,
+            )
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        whenever(repository.primaryBouncerShow.value).thenReturn(false)
+        whenever(bouncerView.delegate).thenReturn(bouncerViewDelegate)
+        resources = context.orCreateTestableResources
+    }
+
+    @Test
+    fun show_nullDelegate() {
+        testScope.run {
+            whenever(bouncerView.delegate).thenReturn(null)
+            mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+            // WHEN bouncer show is requested
+            underTest.show(true)
+
+            // WHEN all queued messages are dispatched
+            mainHandler.dispatchQueuedMessages()
+
+            // THEN primary bouncer state doesn't update to show since delegate was null
+            verify(repository, never()).setPrimaryShow(true)
+            verify(repository, never()).setPrimaryShowingSoon(false)
+            verify(mPrimaryBouncerCallbackInteractor, never()).dispatchStartingToShow()
+            verify(mPrimaryBouncerCallbackInteractor, never())
+                .dispatchVisibilityChanged(View.VISIBLE)
+        }
+    }
+
+    @Test
+    fun testShow_isScrimmed() {
+        underTest.show(true)
+        verify(repository).setKeyguardAuthenticatedBiometrics(null)
+        verify(repository).setPrimaryStartingToHide(false)
+        verify(repository).setPrimaryScrimmed(true)
+        verify(repository).setPanelExpansion(EXPANSION_VISIBLE)
+        verify(repository).setPrimaryShowingSoon(true)
+        verify(keyguardStateController).notifyPrimaryBouncerShowing(true)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToShow()
+        verify(repository).setPrimaryShow(true)
+        verify(repository).setPrimaryShowingSoon(false)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.VISIBLE)
+    }
+
+    @Test
+    fun testShow_isNotScrimmed() {
+        verify(repository, never()).setPanelExpansion(EXPANSION_VISIBLE)
+    }
+
+    @Test
+    fun testShow_keyguardIsDone() {
+        whenever(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true)
+        verify(keyguardStateController, never()).notifyPrimaryBouncerShowing(true)
+        verify(mPrimaryBouncerCallbackInteractor, never()).dispatchStartingToShow()
+    }
+
+    @Test
+    fun testShow_isResumed() {
+        whenever(repository.primaryBouncerShow.value).thenReturn(true)
+        whenever(keyguardSecurityModel.getSecurityMode(anyInt()))
+            .thenReturn(KeyguardSecurityModel.SecurityMode.SimPuk)
+
+        underTest.show(true)
+        verify(repository).setPrimaryShow(false)
+        verify(repository).setPrimaryShow(true)
+    }
+
+    @Test
+    fun testHide() {
+        underTest.hide()
+        verify(falsingCollector).onBouncerHidden()
+        verify(keyguardStateController).notifyPrimaryBouncerShowing(false)
+        verify(repository).setPrimaryShowingSoon(false)
+        verify(repository).setPrimaryShow(false)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.INVISIBLE)
+        verify(repository).setPrimaryStartDisappearAnimation(null)
+        verify(repository).setPanelExpansion(EXPANSION_HIDDEN)
+    }
+
+    @Test
+    fun testExpansion() {
+        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        underTest.setPanelExpansion(0.6f)
+        verify(repository).setPanelExpansion(0.6f)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
+    }
+
+    @Test
+    fun testExpansion_fullyShown() {
+        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        underTest.setPanelExpansion(EXPANSION_VISIBLE)
+        verify(falsingCollector).onBouncerShown()
+        verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown()
+    }
+
+    @Test
+    fun testExpansion_fullyHidden() {
+        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        underTest.setPanelExpansion(EXPANSION_HIDDEN)
+        verify(repository).setPrimaryShow(false)
+        verify(falsingCollector).onBouncerHidden()
+        verify(mPrimaryBouncerCallbackInteractor).dispatchReset()
+        verify(mPrimaryBouncerCallbackInteractor).dispatchFullyHidden()
+    }
+
+    @Test
+    fun testExpansion_startingToHide() {
+        whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+        underTest.setPanelExpansion(0.1f)
+        verify(repository).setPrimaryStartingToHide(true)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToHide()
+    }
+
+    @Test
+    fun testShowMessage() {
+        val argCaptor = ArgumentCaptor.forClass(BouncerShowMessageModel::class.java)
+        underTest.showMessage("abc", null)
+        verify(repository).setShowMessage(argCaptor.capture())
+        assertThat(argCaptor.value.message).isEqualTo("abc")
+    }
+
+    @Test
+    fun testDismissAction() {
+        val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
+        val cancelAction = mock(Runnable::class.java)
+        underTest.setDismissAction(onDismissAction, cancelAction)
+        verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
+    }
+
+    @Test
+    fun testUpdateResources() {
+        underTest.updateResources()
+        verify(repository).setResourceUpdateRequests(true)
+    }
+
+    @Test
+    fun testNotifyKeyguardAuthenticated() {
+        underTest.notifyKeyguardAuthenticatedBiometrics(true)
+        verify(repository).setKeyguardAuthenticatedBiometrics(true)
+    }
+
+    @Test
+    fun testNotifyShowedMessage() {
+        underTest.onMessageShown()
+        verify(repository).setShowMessage(null)
+    }
+
+    @Test
+    fun testSetKeyguardPosition() {
+        underTest.setKeyguardPosition(0f)
+        verify(repository).setKeyguardPosition(0f)
+    }
+
+    @Test
+    fun testNotifyKeyguardAuthenticatedHandled() {
+        underTest.notifyKeyguardAuthenticatedHandled()
+        verify(repository).setKeyguardAuthenticatedBiometrics(null)
+    }
+
+    @Test
+    fun testNotifyUpdatedResources() {
+        underTest.notifyUpdatedResources()
+        verify(repository).setResourceUpdateRequests(false)
+    }
+
+    @Test
+    fun testSetBackButtonEnabled() {
+        underTest.setBackButtonEnabled(true)
+        verify(repository).setIsBackButtonEnabled(true)
+    }
+
+    @Test
+    fun testStartDisappearAnimation_willRunDismissFromKeyguard() {
+        whenever(bouncerViewDelegate.willRunDismissFromKeyguard()).thenReturn(true)
+
+        val runnable = mock(Runnable::class.java)
+        underTest.startDisappearAnimation(runnable)
+        // End runnable should run immediately
+        verify(runnable).run()
+        // ... while the disappear animation should never be run
+        verify(repository, never()).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
+    }
+
+    @Test
+    fun testStartDisappearAnimation_willNotRunDismissFromKeyguard_() {
+        whenever(bouncerViewDelegate.willRunDismissFromKeyguard()).thenReturn(false)
+
+        val runnable = mock(Runnable::class.java)
+        underTest.startDisappearAnimation(runnable)
+        verify(repository).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
+    }
+
+    @Test
+    fun testIsFullShowing() {
+        whenever(repository.primaryBouncerShow.value).thenReturn(true)
+        whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        assertThat(underTest.isFullyShowing()).isTrue()
+        whenever(repository.primaryBouncerShow.value).thenReturn(false)
+        assertThat(underTest.isFullyShowing()).isFalse()
+    }
+
+    @Test
+    fun testIsScrimmed() {
+        whenever(repository.primaryBouncerScrimmed.value).thenReturn(true)
+        assertThat(underTest.isScrimmed()).isTrue()
+        whenever(repository.primaryBouncerScrimmed.value).thenReturn(false)
+        assertThat(underTest.isScrimmed()).isFalse()
+    }
+
+    @Test
+    fun testIsInTransit() {
+        whenever(repository.primaryBouncerShowingSoon.value).thenReturn(true)
+        assertThat(underTest.isInTransit()).isTrue()
+        whenever(repository.primaryBouncerShowingSoon.value).thenReturn(false)
+        assertThat(underTest.isInTransit()).isFalse()
+        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        assertThat(underTest.isInTransit()).isTrue()
+    }
+
+    @Test
+    fun testIsAnimatingAway() {
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
+        assertThat(underTest.isAnimatingAway()).isTrue()
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        assertThat(underTest.isAnimatingAway()).isFalse()
+    }
+
+    @Test
+    fun testWillDismissWithAction() {
+        whenever(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
+        assertThat(underTest.willDismissWithAction()).isTrue()
+        whenever(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
+        assertThat(underTest.willDismissWithAction()).isFalse()
+    }
+
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+    @Test
+    fun testSideFpsVisibility() {
+        updateSideFpsVisibilityParameters(
+            isVisible = true,
+            sfpsEnabled = true,
+            fpsDetectionRunning = true,
+            isUnlockingWithFpAllowed = true,
+            isAnimatingAway = false
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(true)
+    }
+
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+    @Test
+    fun testSideFpsVisibility_notVisible() {
+        updateSideFpsVisibilityParameters(
+            isVisible = false,
+            sfpsEnabled = true,
+            fpsDetectionRunning = true,
+            isUnlockingWithFpAllowed = true,
+            isAnimatingAway = false
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(false)
+    }
+
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+    @Test
+    fun testSideFpsVisibility_sfpsNotEnabled() {
+        updateSideFpsVisibilityParameters(
+            isVisible = true,
+            sfpsEnabled = false,
+            fpsDetectionRunning = true,
+            isUnlockingWithFpAllowed = true,
+            isAnimatingAway = false
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(false)
+    }
+
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+    @Test
+    fun testSideFpsVisibility_fpsDetectionNotRunning() {
+        updateSideFpsVisibilityParameters(
+            isVisible = true,
+            sfpsEnabled = true,
+            fpsDetectionRunning = false,
+            isUnlockingWithFpAllowed = true,
+            isAnimatingAway = false
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(false)
+    }
+
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+    @Test
+    fun testSideFpsVisibility_UnlockingWithFpNotAllowed() {
+        updateSideFpsVisibilityParameters(
+            isVisible = true,
+            sfpsEnabled = true,
+            fpsDetectionRunning = true,
+            isUnlockingWithFpAllowed = false,
+            isAnimatingAway = false
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(false)
+    }
+
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+    @Test
+    fun testSideFpsVisibility_AnimatingAway() {
+        updateSideFpsVisibilityParameters(
+            isVisible = true,
+            sfpsEnabled = true,
+            fpsDetectionRunning = true,
+            isUnlockingWithFpAllowed = true,
+            isAnimatingAway = true
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(false)
+    }
+
+    @Test
+    fun delayBouncerWhenFaceAuthPossible() {
+        mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+        // GIVEN bouncer should be delayed due to face auth
+        whenever(faceAuthInteractor.canFaceAuthRun()).thenReturn(true)
+
+        // WHEN bouncer show is requested
+        underTest.show(true)
+
+        // THEN primary show & primary showing soon aren't updated immediately
+        verify(repository, never()).setPrimaryShow(true)
+        verify(repository, never()).setPrimaryShowingSoon(false)
+
+        // WHEN all queued messages are dispatched
+        mainHandler.dispatchQueuedMessages()
+
+        // THEN primary show & primary showing soon are updated
+        verify(repository).setPrimaryShow(true)
+        verify(repository).setPrimaryShowingSoon(false)
+    }
+
+    @Test
+    fun noDelayBouncer_faceAuthNotAllowed() {
+        mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+        // GIVEN bouncer should not be delayed because device isn't in the right posture for
+        // face auth
+        whenever(faceAuthInteractor.canFaceAuthRun()).thenReturn(false)
+
+        // WHEN bouncer show is requested
+        underTest.show(true)
+
+        // THEN primary show & primary showing soon are updated immediately
+        verify(repository).setPrimaryShow(true)
+        verify(repository).setPrimaryShowingSoon(false)
+    }
+
+    @Test
+    fun delayBouncerWhenActiveUnlockPossible() {
+        testScope.run {
+            mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+            // GIVEN bouncer should be delayed due to active unlock
+            trustRepository.setCurrentUserActiveUnlockAvailable(true)
+            whenever(keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState())
+                .thenReturn(true)
+            runCurrent()
+
+            // WHEN bouncer show is requested
+            underTest.show(true)
+
+            // THEN primary show & primary showing soon were scheduled to update
+            verify(repository, never()).setPrimaryShow(true)
+            verify(repository, never()).setPrimaryShowingSoon(false)
+
+            // WHEN all queued messages are dispatched
+            mainHandler.dispatchQueuedMessages()
+
+            // THEN primary show & primary showing soon are updated
+            verify(repository).setPrimaryShow(true)
+            verify(repository).setPrimaryShowingSoon(false)
+        }
+    }
+
+    private fun updateSideFpsVisibilityParameters(
+        isVisible: Boolean,
+        sfpsEnabled: Boolean,
+        fpsDetectionRunning: Boolean,
+        isUnlockingWithFpAllowed: Boolean,
+        isAnimatingAway: Boolean
+    ) {
+        mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
+        whenever(repository.primaryBouncerShow.value).thenReturn(isVisible)
+        resources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer, sfpsEnabled)
+        whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning)
+            .thenReturn(fpsDetectionRunning)
+        whenever(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
+            .thenReturn(isUnlockingWithFpAllowed)
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value)
+            .thenReturn(if (isAnimatingAway) Runnable {} else null)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
index c8560c3..c300e0a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -55,7 +55,9 @@
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
     @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
-    private val mainHandler = FakeHandler(Looper.getMainLooper())
+    private val mainHandler by lazy {
+        FakeHandler(Looper.getMainLooper())
+    }
     private lateinit var underTest: PrimaryBouncerInteractor
 
     @Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
index 8c53c0e..09fdd11 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
@@ -28,8 +28,11 @@
 import com.android.systemui.bouncer.data.repository.FakeSimBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor.Companion.INVALID_SUBSCRIPTION_ID
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -54,10 +57,10 @@
     @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock lateinit var euiccManager: EuiccManager
 
-    private val utils = SceneTestUtils(this)
+    private val kosmos = testKosmos()
     private val bouncerSimRepository = FakeSimBouncerRepository()
     private val resources: Resources = context.resources
-    private val testScope = utils.testScope
+    private val testScope = kosmos.testScope
 
     private lateinit var underTest: SimBouncerInteractor
 
@@ -68,13 +71,13 @@
             SimBouncerInteractor(
                 context,
                 testScope.backgroundScope,
-                utils.testDispatcher,
+                kosmos.testDispatcher,
                 bouncerSimRepository,
                 telephonyManager,
                 resources,
                 keyguardUpdateMonitor,
                 euiccManager,
-                utils.mobileConnectionsRepository,
+                kosmos.mobileConnectionsRepository,
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index 2f0843b..d30e333 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -20,9 +20,13 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.runTest
@@ -33,27 +37,27 @@
 @RunWith(AndroidJUnit4::class)
 class AuthMethodBouncerViewModelTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val bouncerInteractor =
-        utils.bouncerInteractor(
-            authenticationInteractor = utils.authenticationInteractor(),
-        )
-    private val underTest =
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
+    private val underTest by lazy {
         PinBouncerViewModel(
             applicationContext = context,
             viewModelScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
             isInputEnabled = MutableStateFlow(true),
-            simBouncerInteractor = utils.simBouncerInteractor,
+            simBouncerInteractor = kosmos.simBouncerInteractor,
             authenticationMethod = AuthenticationMethodModel.Pin,
         )
+    }
 
     @Test
     fun animateFailure() =
         testScope.runTest {
             val animateFailure by collectLastValue(underTest.animateFailure)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
             assertThat(animateFailure).isFalse()
 
             // Wrong PIN:
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 47bbe6f4..73db175 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -20,15 +20,21 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.None
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Sim
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import kotlin.time.Duration.Companion.seconds
@@ -41,6 +47,7 @@
 import kotlinx.coroutines.test.currentTime
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -49,18 +56,17 @@
 @RunWith(AndroidJUnit4::class)
 class BouncerViewModelTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val authenticationInteractor = utils.authenticationInteractor()
-    private val bouncerInteractor =
-        utils.bouncerInteractor(
-            authenticationInteractor = authenticationInteractor,
-        )
-    private val underTest =
-        utils.bouncerViewModel(
-            bouncerInteractor = bouncerInteractor,
-            authenticationInteractor = authenticationInteractor,
-        )
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
+    private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
+    private lateinit var underTest: BouncerViewModel
+
+    @Before
+    fun setUp() {
+        kosmos.fakeSceneContainerFlags.enabled = true
+        underTest = kosmos.bouncerViewModel
+    }
 
     @Test
     fun authMethod_nonNullForSecureMethods_nullForNotSecureMethods() =
@@ -68,7 +74,7 @@
             var authMethodViewModel: AuthMethodBouncerViewModel? = null
 
             authMethodsToTest().forEach { authMethod ->
-                utils.authenticationRepository.setAuthenticationMethod(authMethod)
+                kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
                 val job =
                     underTest.authMethodViewModel.onEach { authMethodViewModel = it }.launchIn(this)
                 runCurrent()
@@ -98,13 +104,13 @@
 
             // First pass, populate our "seen" map:
             authMethodsToTest().forEach { authMethod ->
-                utils.authenticationRepository.setAuthenticationMethod(authMethod)
+                kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
                 authMethodViewModel?.let { seen[authMethod] = it }
             }
 
             // Second pass, assert same instances are not reused:
             authMethodsToTest().forEach { authMethod ->
-                utils.authenticationRepository.setAuthenticationMethod(authMethod)
+                kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
                 authMethodViewModel?.let {
                     assertThat(it.authenticationMethod).isEqualTo(authMethod)
                     assertThat(it).isNotSameInstanceAs(seen[authMethod])
@@ -116,11 +122,11 @@
     fun authMethodUnchanged_reusesInstances() =
         testScope.runTest {
             authMethodsToTest().forEach { authMethod ->
-                utils.authenticationRepository.setAuthenticationMethod(authMethod)
+                kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
                 val firstInstance: AuthMethodBouncerViewModel? =
                     collectLastValue(underTest.authMethodViewModel).invoke()
 
-                utils.authenticationRepository.setAuthenticationMethod(authMethod)
+                kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
                 val secondInstance: AuthMethodBouncerViewModel? =
                     collectLastValue(underTest.authMethodViewModel).invoke()
 
@@ -139,7 +145,7 @@
     fun message() =
         testScope.runTest {
             val message by collectLastValue(underTest.message)
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             assertThat(message?.isUpdateAnimated).isTrue()
 
             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
@@ -157,8 +163,8 @@
         testScope.runTest {
             val authMethodViewModel by collectLastValue(underTest.authMethodViewModel)
             val message by collectLastValue(underTest.message)
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
-            assertThat(utils.authenticationRepository.lockoutEndTimestamp).isNull()
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
+            assertThat(kosmos.fakeAuthenticationRepository.lockoutEndTimestamp).isNull()
             assertThat(authMethodViewModel?.lockoutMessageId).isNotNull()
 
             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times ->
@@ -192,7 +198,7 @@
                         authViewModel?.isInputEnabled ?: emptyFlow()
                     }
                 )
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             assertThat(isInputEnabled).isTrue()
 
             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
@@ -210,7 +216,7 @@
         testScope.runTest {
             val authMethodViewModel by collectLastValue(underTest.authMethodViewModel)
             val dialogViewModel by collectLastValue(underTest.dialogViewModel)
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             assertThat(authMethodViewModel?.lockoutMessageId).isNotNull()
 
             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
@@ -228,17 +234,17 @@
     fun isSideBySideSupported() =
         testScope.runTest {
             val isSideBySideSupported by collectLastValue(underTest.isSideBySideSupported)
-            utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             assertThat(isSideBySideSupported).isTrue()
-            utils.authenticationRepository.setAuthenticationMethod(Password)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
             assertThat(isSideBySideSupported).isTrue()
 
-            utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             assertThat(isSideBySideSupported).isTrue()
 
-            utils.authenticationRepository.setAuthenticationMethod(Password)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
             assertThat(isSideBySideSupported).isFalse()
         }
 
@@ -246,12 +252,12 @@
     fun isFoldSplitRequired() =
         testScope.runTest {
             val isFoldSplitRequired by collectLastValue(underTest.isFoldSplitRequired)
-            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
             assertThat(isFoldSplitRequired).isTrue()
-            utils.authenticationRepository.setAuthenticationMethod(Password)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
             assertThat(isFoldSplitRequired).isFalse()
 
-            utils.authenticationRepository.setAuthenticationMethod(Pattern)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern)
             assertThat(isFoldSplitRequired).isTrue()
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index a0c2acc..cddbd1f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -66,7 +66,9 @@
     @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
 
     lateinit var bouncerInteractor: PrimaryBouncerInteractor
-    private val mainHandler = FakeHandler(Looper.getMainLooper())
+    private val mainHandler by lazy {
+        FakeHandler(Looper.getMainLooper())
+    }
     val repository = FakeKeyguardBouncerRepository()
 
     lateinit var underTest: KeyguardBouncerViewModel
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 64e6e57..c193d14 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -19,13 +19,19 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -43,28 +49,21 @@
 @RunWith(AndroidJUnit4::class)
 class PasswordBouncerViewModelTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val authenticationInteractor = utils.authenticationInteractor()
-    private val sceneInteractor = utils.sceneInteractor()
-    private val bouncerInteractor =
-        utils.bouncerInteractor(
-            authenticationInteractor = authenticationInteractor,
-        )
-    private val bouncerViewModel =
-        utils.bouncerViewModel(
-            bouncerInteractor = bouncerInteractor,
-            authenticationInteractor = authenticationInteractor,
-            actionButtonInteractor = utils.bouncerActionButtonInteractor(),
-        )
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val authenticationInteractor = kosmos.authenticationInteractor
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
+    private val bouncerViewModel by lazy { kosmos.bouncerViewModel }
     private val isInputEnabled = MutableStateFlow(true)
 
-    private val underTest =
+    private val underTest by lazy {
         PasswordBouncerViewModel(
             viewModelScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
             isInputEnabled.asStateFlow(),
         )
+    }
 
     @Before
     fun setUp() {
@@ -148,10 +147,10 @@
         testScope.runTest {
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            utils.authenticationRepository.setAuthenticationMethod(
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
-            utils.deviceEntryRepository.setUnlocked(false)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(false)
             switchToScene(SceneKey.Bouncer)
 
             // No input entered.
@@ -209,45 +208,13 @@
         }
 
     @Test
-    fun onImeVisibilityChanged_false_doesNothing() =
+    fun onImeDismissed() =
         testScope.runTest {
             val events by collectValues(bouncerInteractor.onImeHiddenByUser)
             assertThat(events).isEmpty()
 
-            underTest.onImeVisibilityChanged(isVisible = false)
-            assertThat(events).isEmpty()
-        }
-
-    @Test
-    fun onImeVisibilityChanged_falseAfterTrue_emitsOnImeHiddenByUserEvent() =
-        testScope.runTest {
-            val events by collectValues(bouncerInteractor.onImeHiddenByUser)
-            assertThat(events).isEmpty()
-
-            underTest.onImeVisibilityChanged(isVisible = true)
-            assertThat(events).isEmpty()
-
-            underTest.onImeVisibilityChanged(isVisible = false)
+            underTest.onImeDismissed()
             assertThat(events).hasSize(1)
-
-            underTest.onImeVisibilityChanged(isVisible = true)
-            assertThat(events).hasSize(1)
-
-            underTest.onImeVisibilityChanged(isVisible = false)
-            assertThat(events).hasSize(2)
-        }
-
-    @Test
-    fun onImeVisibilityChanged_falseAfterTrue_whileLockedOut_doesNothing() =
-        testScope.runTest {
-            val events by collectValues(bouncerInteractor.onImeHiddenByUser)
-            assertThat(events).isEmpty()
-            underTest.onImeVisibilityChanged(isVisible = true)
-            setLockout(true)
-
-            underTest.onImeVisibilityChanged(isVisible = false)
-
-            assertThat(events).isEmpty()
         }
 
     @Test
@@ -317,8 +284,10 @@
     }
 
     private fun TestScope.lockDeviceAndOpenPasswordBouncer() {
-        utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Password)
-        utils.deviceEntryRepository.setUnlocked(false)
+        kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+            AuthenticationMethodModel.Password
+        )
+        kosmos.fakeDeviceEntryRepository.setUnlocked(false)
         switchToScene(SceneKey.Bouncer)
     }
 
@@ -328,13 +297,13 @@
     ) {
         if (isLockedOut) {
             repeat(failedAttemptCount) {
-                utils.authenticationRepository.reportAuthenticationAttempt(false)
+                kosmos.fakeAuthenticationRepository.reportAuthenticationAttempt(false)
             }
-            utils.authenticationRepository.reportLockoutStarted(
+            kosmos.fakeAuthenticationRepository.reportLockoutStarted(
                 30.seconds.inWholeMilliseconds.toInt()
             )
         } else {
-            utils.authenticationRepository.reportAuthenticationAttempt(true)
+            kosmos.fakeAuthenticationRepository.reportAuthenticationAttempt(true)
         }
         isInputEnabled.value = !isLockedOut
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 83d1938..725bdbd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -20,13 +20,20 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.authenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -44,27 +51,20 @@
 @RunWith(AndroidJUnit4::class)
 class PatternBouncerViewModelTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val authenticationInteractor = utils.authenticationInteractor()
-    private val sceneInteractor = utils.sceneInteractor()
-    private val bouncerInteractor =
-        utils.bouncerInteractor(
-            authenticationInteractor = authenticationInteractor,
-        )
-    private val bouncerViewModel =
-        utils.bouncerViewModel(
-            bouncerInteractor = bouncerInteractor,
-            authenticationInteractor = authenticationInteractor,
-            actionButtonInteractor = utils.bouncerActionButtonInteractor(),
-        )
-    private val underTest =
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
+    private val bouncerViewModel by lazy { kosmos.bouncerViewModel }
+    private val underTest by lazy {
         PatternBouncerViewModel(
             applicationContext = context,
             viewModelScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
             isInputEnabled = MutableStateFlow(true).asStateFlow(),
         )
+    }
 
     private val containerSize = 90 // px
     private val dotSize = 30 // px
@@ -313,7 +313,7 @@
                 underTest.onDragStart()
                 CORRECT_PATTERN.subList(
                         0,
-                        utils.authenticationRepository.minPatternLength - 1,
+                        kosmos.authenticationRepository.minPatternLength - 1,
                     )
                     .forEach { coordinate ->
                         underTest.onDrag(
@@ -382,8 +382,10 @@
     }
 
     private fun TestScope.lockDeviceAndOpenPatternBouncer() {
-        utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pattern)
-        utils.deviceEntryRepository.setUnlocked(false)
+        kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+            AuthenticationMethodModel.Pattern
+        )
+        kosmos.fakeDeviceEntryRepository.setUnlocked(false)
         switchToScene(SceneKey.Bouncer)
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index db98d76..06e1258 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -20,12 +20,20 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -43,32 +51,26 @@
 @RunWith(AndroidJUnit4::class)
 class PinBouncerViewModelTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val sceneInteractor = utils.sceneInteractor()
-    private val authenticationInteractor = utils.authenticationInteractor()
-    private val bouncerInteractor =
-        utils.bouncerInteractor(
-            authenticationInteractor = authenticationInteractor,
-        )
-    private val bouncerViewModel =
-        utils.bouncerViewModel(
-            bouncerInteractor = bouncerInteractor,
-            authenticationInteractor = authenticationInteractor,
-            actionButtonInteractor = utils.bouncerActionButtonInteractor(),
-        )
-    private val underTest =
-        PinBouncerViewModel(
-            applicationContext = context,
-            viewModelScope = testScope.backgroundScope,
-            interactor = bouncerInteractor,
-            isInputEnabled = MutableStateFlow(true).asStateFlow(),
-            simBouncerInteractor = utils.simBouncerInteractor,
-            authenticationMethod = AuthenticationMethodModel.Pin,
-        )
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
+    private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
+    private val bouncerViewModel by lazy { kosmos.bouncerViewModel }
+    private lateinit var underTest: PinBouncerViewModel
 
     @Before
     fun setUp() {
+        underTest =
+            PinBouncerViewModel(
+                    applicationContext = context,
+                    viewModelScope = testScope.backgroundScope,
+                    interactor = bouncerInteractor,
+                    isInputEnabled = MutableStateFlow(true).asStateFlow(),
+                    simBouncerInteractor = kosmos.simBouncerInteractor,
+                    authenticationMethod = AuthenticationMethodModel.Pin,
+            )
+
         overrideResource(R.string.keyguard_enter_your_pin, ENTER_YOUR_PIN)
         overrideResource(R.string.kg_wrong_pin, WRONG_PIN)
     }
@@ -94,7 +96,7 @@
                     viewModelScope = testScope.backgroundScope,
                     interactor = bouncerInteractor,
                     isInputEnabled = MutableStateFlow(true).asStateFlow(),
-                    simBouncerInteractor = utils.simBouncerInteractor,
+                    simBouncerInteractor = kosmos.simBouncerInteractor,
                     authenticationMethod = AuthenticationMethodModel.Sim,
                 )
 
@@ -105,7 +107,7 @@
     fun onErrorDialogDismissed_clearsDialogMessage() =
         testScope.runTest {
             val dialogMessage by collectLastValue(underTest.errorDialogMessage)
-            utils.simBouncerRepository.setSimVerificationErrorMessage("abc")
+            kosmos.fakeSimBouncerRepository.setSimVerificationErrorMessage("abc")
             assertThat(dialogMessage).isEqualTo("abc")
 
             underTest.onErrorDialogDismissed()
@@ -122,10 +124,10 @@
                     viewModelScope = testScope.backgroundScope,
                     interactor = bouncerInteractor,
                     isInputEnabled = MutableStateFlow(true).asStateFlow(),
-                    simBouncerInteractor = utils.simBouncerInteractor,
+                    simBouncerInteractor = kosmos.simBouncerInteractor,
                     authenticationMethod = AuthenticationMethodModel.Sim,
                 )
-            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
 
             assertThat(hintedPinLength).isNull()
@@ -262,7 +264,7 @@
     @Test
     fun onAutoConfirm_whenCorrect() =
         testScope.runTest {
-            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
             val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
             lockDeviceAndOpenPinBouncer()
 
@@ -277,7 +279,7 @@
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
-            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
             lockDeviceAndOpenPinBouncer()
 
             FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit ->
@@ -317,7 +319,9 @@
         testScope.runTest {
             val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
 
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
 
             assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Shown)
         }
@@ -326,8 +330,10 @@
     fun backspaceButtonAppearance_withAutoConfirmButNoInput_isHidden() =
         testScope.runTest {
             val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
 
             assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
         }
@@ -336,8 +342,10 @@
     fun backspaceButtonAppearance_withAutoConfirmAndInput_isShownQuiet() =
         testScope.runTest {
             val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
 
             underTest.onPinButtonClicked(1)
 
@@ -349,7 +357,9 @@
         testScope.runTest {
             val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
 
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
 
             assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Shown)
         }
@@ -358,8 +368,10 @@
     fun confirmButtonAppearance_withAutoConfirm_isHidden() =
         testScope.runTest {
             val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
 
             assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
         }
@@ -369,10 +381,10 @@
         testScope.runTest {
             val isAnimationEnabled by collectLastValue(underTest.isDigitButtonAnimationEnabled)
 
-            utils.authenticationRepository.setPinEnhancedPrivacyEnabled(true)
+            kosmos.fakeAuthenticationRepository.setPinEnhancedPrivacyEnabled(true)
             assertThat(isAnimationEnabled).isFalse()
 
-            utils.authenticationRepository.setPinEnhancedPrivacyEnabled(false)
+            kosmos.fakeAuthenticationRepository.setPinEnhancedPrivacyEnabled(false)
             assertThat(isAnimationEnabled).isTrue()
         }
 
@@ -390,8 +402,8 @@
     }
 
     private fun TestScope.lockDeviceAndOpenPinBouncer() {
-        utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-        utils.deviceEntryRepository.setUnlocked(false)
+        kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+        kosmos.fakeDeviceEntryRepository.setUnlocked(false)
         switchToScene(SceneKey.Bouncer)
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
new file mode 100644
index 0000000..820bfbf
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.repository
+
+import android.content.SharedPreferences
+import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryImpl.Companion.FILE_NAME
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.FakeSharedPreferences
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
+    private lateinit var underTest: CommunalPrefsRepositoryImpl
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private lateinit var userRepository: FakeUserRepository
+    private lateinit var userFileManager: UserFileManager
+
+    @Before
+    fun setUp() {
+        userRepository = kosmos.fakeUserRepository
+        userRepository.setUserInfos(USER_INFOS)
+
+        userFileManager =
+            FakeUserFileManager(
+                mapOf(
+                    USER_INFOS[0].id to FakeSharedPreferences(),
+                    USER_INFOS[1].id to FakeSharedPreferences()
+                )
+            )
+        underTest =
+            CommunalPrefsRepositoryImpl(
+                testScope.backgroundScope,
+                kosmos.testDispatcher,
+                userRepository,
+                userFileManager,
+            )
+    }
+
+    @Test
+    fun isCtaDismissedValue_byDefault_isFalse() =
+        testScope.runTest {
+            val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
+            assertThat(isCtaDismissed).isFalse()
+        }
+
+    @Test
+    fun isCtaDismissedValue_onSet_isTrue() =
+        testScope.runTest {
+            val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
+
+            underTest.setCtaDismissedForCurrentUser()
+            assertThat(isCtaDismissed).isTrue()
+        }
+
+    @Test
+    fun isCtaDismissedValue_whenSwitchUser() =
+        testScope.runTest {
+            val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
+            underTest.setCtaDismissedForCurrentUser()
+
+            // dismissed true for primary user
+            assertThat(isCtaDismissed).isTrue()
+
+            // switch to secondary user
+            userRepository.setSelectedUserInfo(USER_INFOS[1])
+
+            // dismissed is false for secondary user
+            assertThat(isCtaDismissed).isFalse()
+
+            // switch back to primary user
+            userRepository.setSelectedUserInfo(USER_INFOS[0])
+
+            // dismissed is true for primary user
+            assertThat(isCtaDismissed).isTrue()
+        }
+
+    private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :
+        UserFileManager {
+        override fun getFile(fileName: String, userId: Int): File {
+            throw UnsupportedOperationException()
+        }
+
+        override fun getSharedPreferences(
+            fileName: String,
+            mode: Int,
+            userId: Int
+        ): SharedPreferences {
+            if (fileName != FILE_NAME) {
+                throw IllegalArgumentException("Preference files must be $FILE_NAME")
+            }
+            return sharedPrefs.getValue(userId)
+        }
+    }
+
+    companion object {
+        val USER_INFOS =
+            listOf(
+                UserInfo(/* id= */ 0, "zero", /* flags= */ 0),
+                UserInfo(/* id= */ 1, "secondary", /* flags= */ 0),
+            )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
index 65176e1..bd9ca30 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
@@ -16,23 +16,28 @@
 
 package com.android.systemui.communal.data.repository
 
+import android.content.pm.UserInfo
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
-import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.data.repository.SceneContainerRepository
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -43,19 +48,23 @@
 class CommunalRepositoryImplTest : SysuiTestCase() {
     private lateinit var underTest: CommunalRepositoryImpl
 
-    private val testDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
+    private lateinit var secureSettings: FakeSettings
+    private lateinit var userRepository: FakeUserRepository
 
-    private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic
-    private lateinit var sceneContainerRepository: SceneContainerRepository
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sceneContainerRepository = kosmos.sceneContainerRepository
 
     @Before
     fun setUp() {
-        val sceneTestUtils = SceneTestUtils(this)
-        sceneContainerRepository = sceneTestUtils.fakeSceneContainerRepository()
-        featureFlagsClassic = FakeFeatureFlagsClassic()
+        secureSettings = FakeSettings()
+        userRepository = kosmos.fakeUserRepository
 
-        featureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
+        val listOfUserInfo = listOf(MAIN_USER_INFO)
+        userRepository.setUserInfos(listOfUserInfo)
+
+        kosmos.fakeFeatureFlagsClassic.apply { set(Flags.COMMUNAL_SERVICE_ENABLED, true) }
+        mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
 
         underTest = createRepositoryImpl(false)
     }
@@ -63,9 +72,13 @@
     private fun createRepositoryImpl(sceneContainerEnabled: Boolean): CommunalRepositoryImpl {
         return CommunalRepositoryImpl(
             testScope.backgroundScope,
-            featureFlagsClassic,
-            FakeSceneContainerFlags(enabled = sceneContainerEnabled),
+            testScope.backgroundScope,
+            kosmos.testDispatcher,
+            kosmos.fakeFeatureFlagsClassic,
+            kosmos.fakeSceneContainerFlags.apply { enabled = sceneContainerEnabled },
             sceneContainerRepository,
+            kosmos.fakeUserRepository,
+            secureSettings,
         )
     }
 
@@ -146,4 +159,29 @@
             assertThat(transitionState)
                 .isEqualTo(ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT))
         }
+
+    @Test
+    fun communalEnabledState_false_whenGlanceableHubSettingFalse() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(MAIN_USER_INFO)
+            secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 0, MAIN_USER_INFO.id)
+
+            val communalEnabled by collectLastValue(underTest.communalEnabledState)
+            assertThat(communalEnabled).isFalse()
+        }
+
+    @Test
+    fun communalEnabledState_true_whenGlanceableHubSettingTrue() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(MAIN_USER_INFO)
+            secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 1, MAIN_USER_INFO.id)
+
+            val communalEnabled by collectLastValue(underTest.communalEnabledState)
+            assertThat(communalEnabled).isTrue()
+        }
+
+    companion object {
+        private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled"
+        private val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
new file mode 100644
index 0000000..c4a8582
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.communal.data.repository
+
+import android.content.pm.UserInfo
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryImpl.Companion.CURRENT_TUTORIAL_VERSION
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalTutorialRepositoryImplTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private lateinit var secureSettings: FakeSettings
+    private lateinit var userRepository: FakeUserRepository
+
+    private lateinit var underTest: CommunalTutorialRepositoryImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        secureSettings = FakeSettings()
+        userRepository = FakeUserRepository()
+        val listOfUserInfo = listOf(MAIN_USER_INFO)
+        userRepository.setUserInfos(listOfUserInfo)
+
+        underTest =
+            CommunalTutorialRepositoryImpl(
+                kosmos.applicationCoroutineScope,
+                kosmos.testDispatcher,
+                userRepository,
+                secureSettings,
+                logcatLogBuffer("CommunalTutorialRepositoryImplTest"),
+            )
+    }
+
+    @Test
+    fun tutorialSettingState_defaultToNotStarted() =
+        testScope.runTest {
+            val tutorialSettingState by collectLastValue(underTest.tutorialSettingState)
+            assertThat(tutorialSettingState)
+                .isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED)
+        }
+
+    @Test
+    fun tutorialSettingState_whenTutorialSettingsUpdatedToStarted() =
+        testScope.runTest {
+            underTest.setTutorialState(Settings.Secure.HUB_MODE_TUTORIAL_STARTED)
+            val tutorialSettingState by collectLastValue(underTest.tutorialSettingState)
+            assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_STARTED)
+        }
+
+    @Test
+    fun tutorialSettingState_whenTutorialSettingsUpdatedToCompleted() =
+        testScope.runTest {
+            underTest.setTutorialState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+            val tutorialSettingState by collectLastValue(underTest.tutorialSettingState)
+            assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+        }
+
+    @Test
+    fun tutorialVersion_userCompletedCurrentVersion_stateCompleted() =
+        testScope.runTest {
+            // User completed the current version.
+            setTutorialStateSetting(CURRENT_TUTORIAL_VERSION)
+
+            // Verify tutorial state is completed.
+            val tutorialSettingState by collectLastValue(underTest.tutorialSettingState)
+            assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+        }
+
+    @Test
+    fun tutorialVersion_userCompletedPreviousVersion_stateNotStarted() =
+        testScope.runTest {
+            // User completed the previous version.
+            setTutorialStateSetting(CURRENT_TUTORIAL_VERSION - 1)
+
+            // Verify tutorial state is not started.
+            val tutorialSettingState by collectLastValue(underTest.tutorialSettingState)
+            assertThat(tutorialSettingState)
+                .isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED)
+        }
+
+    @Test
+    fun tutorialVersion_uponTutorialCompletion_writeCurrentVersion() =
+        testScope.runTest {
+            // Tutorial not started.
+            setTutorialStateSetting(Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED)
+
+            // Tutorial completed.
+            underTest.setTutorialState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+
+            // Verify tutorial setting state is updated to current version.
+            val settingState = getTutorialStateSetting()
+            assertThat(settingState).isEqualTo(CURRENT_TUTORIAL_VERSION)
+        }
+
+    private fun setTutorialStateSetting(
+        @Settings.Secure.HubModeTutorialState state: Int,
+        user: UserInfo = MAIN_USER_INFO
+    ) {
+        secureSettings.putIntForUser(Settings.Secure.HUB_MODE_TUTORIAL_STATE, state, user.id)
+    }
+
+    private fun getTutorialStateSetting(user: UserInfo = MAIN_USER_INFO): Int {
+        return secureSettings.getIntForUser(Settings.Secure.HUB_MODE_TUTORIAL_STATE, user.id)
+    }
+
+    companion object {
+        private val MAIN_USER_INFO =
+            UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 4079f12..c979ca6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -16,14 +16,11 @@
 
 package com.android.systemui.communal.data.repository
 
-import android.appwidget.AppWidgetHost
 import android.appwidget.AppWidgetManager
 import android.appwidget.AppWidgetProviderInfo
+import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL
+import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
 import android.content.ComponentName
-import android.content.Intent
-import android.content.Intent.ACTION_USER_UNLOCKED
-import android.os.UserHandle
-import android.os.UserManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -32,20 +29,22 @@
 import com.android.systemui.communal.data.db.CommunalWidgetItem
 import com.android.systemui.communal.shared.CommunalWidgetHost
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
+import com.android.systemui.communal.widgets.widgetConfiguratorFail
+import com.android.systemui.communal.widgets.widgetConfiguratorSuccess
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.res.R
-import com.android.systemui.settings.UserTracker
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -61,33 +60,18 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
-    @Mock private lateinit var appWidgetManagerOptional: Optional<AppWidgetManager>
-
     @Mock private lateinit var appWidgetManager: AppWidgetManager
-
-    @Mock private lateinit var appWidgetHost: AppWidgetHost
-
-    @Mock private lateinit var userManager: UserManager
-
-    @Mock private lateinit var userHandle: UserHandle
-
-    @Mock private lateinit var userTracker: UserTracker
-
+    @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
     @Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo
-
     @Mock private lateinit var providerInfoA: AppWidgetProviderInfo
-
     @Mock private lateinit var communalWidgetHost: CommunalWidgetHost
-
     @Mock private lateinit var communalWidgetDao: CommunalWidgetDao
 
-    private lateinit var communalRepository: FakeCommunalRepository
-
     private lateinit var logBuffer: LogBuffer
+    private lateinit var fakeWidgets: MutableStateFlow<Map<CommunalItemRank, CommunalWidgetItem>>
 
-    private val testDispatcher = StandardTestDispatcher()
-
-    private val testScope = TestScope(testDispatcher)
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
     private val fakeAllowlist =
         listOf(
@@ -96,68 +80,44 @@
             "com.android.fake/WidgetProviderC",
         )
 
+    private lateinit var underTest: CommunalWidgetRepositoryImpl
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        fakeWidgets = MutableStateFlow(emptyMap())
+        logBuffer = logcatLogBuffer(name = "CommunalWidgetRepoImplTest")
 
-        logBuffer = FakeLogBuffer.Factory.create()
-        communalRepository = FakeCommunalRepository()
-
-        communalEnabled(true)
         setAppWidgetIds(emptyList())
 
         overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())
 
         whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch")
-        whenever(userTracker.userHandle).thenReturn(userHandle)
-        whenever(communalWidgetDao.getWidgets()).thenReturn(flowOf(emptyMap()))
-        whenever(appWidgetManagerOptional.isPresent).thenReturn(true)
-        whenever(appWidgetManagerOptional.get()).thenReturn(appWidgetManager)
+        whenever(communalWidgetDao.getWidgets()).thenReturn(fakeWidgets)
+
+        underTest =
+            CommunalWidgetRepositoryImpl(
+                Optional.of(appWidgetManager),
+                appWidgetHost,
+                testScope.backgroundScope,
+                kosmos.testDispatcher,
+                communalWidgetHost,
+                communalWidgetDao,
+                logBuffer,
+            )
     }
 
     @Test
-    fun neverQueryDbForWidgets_whenFeatureIsDisabled() =
+    fun communalWidgets_queryWidgetsFromDb() =
         testScope.runTest {
-            communalEnabled(false)
-            val repository = initCommunalWidgetRepository()
-            repository.communalWidgets.launchIn(backgroundScope)
-            runCurrent()
-
-            verify(communalWidgetDao, never()).getWidgets()
-        }
-
-    @Test
-    fun neverQueryDbForWidgets_whenFeatureEnabled_andUserLocked() =
-        testScope.runTest {
-            userUnlocked(false)
-            val repository = initCommunalWidgetRepository()
-            repository.communalWidgets.launchIn(backgroundScope)
-            runCurrent()
-
-            verify(communalWidgetDao, never()).getWidgets()
-        }
-
-    @Test
-    fun communalWidgets_whenUserUnlocked_queryWidgetsFromDb() =
-        testScope.runTest {
-            userUnlocked(false)
-            val repository = initCommunalWidgetRepository()
-            val communalWidgets by collectLastValue(repository.communalWidgets)
-            runCurrent()
             val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1)
             val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L)
-            whenever(communalWidgetDao.getWidgets())
-                .thenReturn(flowOf(mapOf(communalItemRankEntry to communalWidgetItemEntry)))
+            fakeWidgets.value = mapOf(communalItemRankEntry to communalWidgetItemEntry)
             whenever(appWidgetManager.getAppWidgetInfo(anyInt())).thenReturn(providerInfoA)
 
-            userUnlocked(true)
             installedProviders(listOf(stopwatchProviderInfo))
-            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
-                context,
-                Intent(ACTION_USER_UNLOCKED)
-            )
-            runCurrent()
 
+            val communalWidgets by collectLastValue(underTest.communalWidgets)
             verify(communalWidgetDao).getWidgets()
             assertThat(communalWidgets)
                 .containsExactly(
@@ -172,17 +132,14 @@
     @Test
     fun addWidget_allocateId_bindWidget_andAddToDb() =
         testScope.runTest {
-            userUnlocked(true)
-            val repository = initCommunalWidgetRepository()
-            runCurrent()
-
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
             val priority = 1
-            whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
+            whenever(communalWidgetHost.getAppWidgetInfo(id))
+                .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
             whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>()))
                 .thenReturn(id)
-            repository.addWidget(provider, priority) { true }
+            underTest.addWidget(provider, priority, kosmos.widgetConfiguratorSuccess)
             runCurrent()
 
             verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -192,16 +149,13 @@
     @Test
     fun addWidget_configurationFails_doNotAddWidgetToDb() =
         testScope.runTest {
-            userUnlocked(true)
-            val repository = initCommunalWidgetRepository()
-            runCurrent()
-
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
             val priority = 1
-            whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
+            whenever(communalWidgetHost.getAppWidgetInfo(id))
+                .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
             whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
-            repository.addWidget(provider, priority) { false }
+            underTest.addWidget(provider, priority, kosmos.widgetConfiguratorFail)
             runCurrent()
 
             verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -212,16 +166,13 @@
     @Test
     fun addWidget_configurationThrowsError_doNotAddWidgetToDb() =
         testScope.runTest {
-            userUnlocked(true)
-            val repository = initCommunalWidgetRepository()
-            runCurrent()
-
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
             val priority = 1
-            whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
+            whenever(communalWidgetHost.getAppWidgetInfo(id))
+                .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
             whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
-            repository.addWidget(provider, priority) { throw IllegalStateException("some error") }
+            underTest.addWidget(provider, priority) { throw IllegalStateException("some error") }
             runCurrent()
 
             verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -232,37 +183,25 @@
     @Test
     fun addWidget_configurationNotRequired_doesNotConfigure_addWidgetToDb() =
         testScope.runTest {
-            userUnlocked(true)
-            val repository = initCommunalWidgetRepository()
-            runCurrent()
-
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
             val priority = 1
-            whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(false)
+            whenever(communalWidgetHost.getAppWidgetInfo(id))
+                .thenReturn(PROVIDER_INFO_CONFIGURATION_OPTIONAL)
             whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>()))
                 .thenReturn(id)
-            var configured = false
-            repository.addWidget(provider, priority) {
-                configured = true
-                true
-            }
+            underTest.addWidget(provider, priority, kosmos.widgetConfiguratorFail)
             runCurrent()
 
             verify(communalWidgetHost).allocateIdAndBindWidget(provider)
             verify(communalWidgetDao).addWidget(id, provider, priority)
-            assertThat(configured).isFalse()
         }
 
     @Test
     fun deleteWidget_removeWidgetId_andDeleteFromDb() =
         testScope.runTest {
-            userUnlocked(true)
-            val repository = initCommunalWidgetRepository()
-            runCurrent()
-
             val id = 1
-            repository.deleteWidget(id)
+            underTest.deleteWidget(id)
             runCurrent()
 
             verify(communalWidgetDao).deleteWidgetById(id)
@@ -272,108 +211,13 @@
     @Test
     fun reorderWidgets_queryDb() =
         testScope.runTest {
-            userUnlocked(true)
-            val repository = initCommunalWidgetRepository()
-            runCurrent()
-
             val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3)
-            repository.updateWidgetOrder(widgetIdToPriorityMap)
+            underTest.updateWidgetOrder(widgetIdToPriorityMap)
             runCurrent()
 
             verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap)
         }
 
-    @Test
-    fun broadcastReceiver_communalDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
-        testScope.runTest {
-            communalEnabled(false)
-            val repository = initCommunalWidgetRepository()
-            repository.communalWidgets.launchIn(backgroundScope)
-            runCurrent()
-            assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(0)
-        }
-
-    @Test
-    fun broadcastReceiver_featureEnabledAndUserLocked_registerBroadcastReceiver() =
-        testScope.runTest {
-            userUnlocked(false)
-            val repository = initCommunalWidgetRepository()
-            repository.communalWidgets.launchIn(backgroundScope)
-            runCurrent()
-            assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(1)
-        }
-
-    @Test
-    fun appWidgetHost_userUnlocked_startListening() =
-        testScope.runTest {
-            userUnlocked(false)
-            val repository = initCommunalWidgetRepository()
-            repository.communalWidgets.launchIn(backgroundScope)
-            runCurrent()
-            verify(appWidgetHost, never()).startListening()
-
-            userUnlocked(true)
-            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
-                context,
-                Intent(ACTION_USER_UNLOCKED)
-            )
-            runCurrent()
-
-            verify(appWidgetHost).startListening()
-        }
-
-    @Test
-    fun appWidgetHost_userLockedAgain_stopListening() =
-        testScope.runTest {
-            userUnlocked(false)
-            val repository = initCommunalWidgetRepository()
-            repository.communalWidgets.launchIn(backgroundScope)
-            runCurrent()
-
-            userUnlocked(true)
-            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
-                context,
-                Intent(ACTION_USER_UNLOCKED)
-            )
-            runCurrent()
-
-            verify(appWidgetHost).startListening()
-            verify(appWidgetHost, never()).stopListening()
-
-            userUnlocked(false)
-            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
-                context,
-                Intent(ACTION_USER_UNLOCKED)
-            )
-            runCurrent()
-
-            verify(appWidgetHost).stopListening()
-        }
-
-    private fun initCommunalWidgetRepository(): CommunalWidgetRepositoryImpl {
-        return CommunalWidgetRepositoryImpl(
-            appWidgetManagerOptional,
-            appWidgetHost,
-            testScope.backgroundScope,
-            testDispatcher,
-            fakeBroadcastDispatcher,
-            communalRepository,
-            communalWidgetHost,
-            communalWidgetDao,
-            userManager,
-            userTracker,
-            logBuffer,
-        )
-    }
-
-    private fun communalEnabled(enabled: Boolean) {
-        communalRepository.setIsCommunalEnabled(enabled)
-    }
-
-    private fun userUnlocked(userUnlocked: Boolean) {
-        whenever(userManager.isUserUnlockingOrUnlocked(userHandle)).thenReturn(userUnlocked)
-    }
-
     private fun installedProviders(providers: List<AppWidgetProviderInfo>) {
         whenever(appWidgetManager.installedProviders).thenReturn(providers)
     }
@@ -381,4 +225,15 @@
     private fun setAppWidgetIds(ids: List<Int>) {
         whenever(appWidgetHost.appWidgetIds).thenReturn(ids.toIntArray())
     }
+
+    private companion object {
+        val PROVIDER_INFO_REQUIRES_CONFIGURATION =
+            AppWidgetProviderInfo().apply { configure = ComponentName("test.pkg", "test.cmp") }
+        val PROVIDER_INFO_CONFIGURATION_OPTIONAL =
+            AppWidgetProviderInfo().apply {
+                configure = ComponentName("test.pkg", "test.cmp")
+                widgetFeatures =
+                    WIDGET_FEATURE_CONFIGURATION_OPTIONAL or WIDGET_FEATURE_RECONFIGURABLE
+            }
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
new file mode 100644
index 0000000..6a3fc2a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.communal.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This class is a variation of the [CommunalInteractorTest] for cases where communal is disabled.
+ */
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class CommunalInteractorCommunalDisabledTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private lateinit var communalRepository: FakeCommunalRepository
+    private lateinit var widgetRepository: FakeCommunalWidgetRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+
+    private lateinit var underTest: CommunalInteractor
+
+    @Before
+    fun setUp() {
+        communalRepository = kosmos.fakeCommunalRepository
+        widgetRepository = kosmos.fakeCommunalWidgetRepository
+        keyguardRepository = kosmos.fakeKeyguardRepository
+
+        communalRepository.setIsCommunalEnabled(false)
+
+        underTest = kosmos.communalInteractor
+    }
+
+    @Test
+    fun isCommunalEnabled_false() =
+        testScope.runTest { assertThat(underTest.isCommunalEnabled).isFalse() }
+
+    @Test
+    fun isCommunalAvailable_whenStorageUnlock_false() =
+        testScope.runTest {
+            val isCommunalAvailable by collectLastValue(underTest.isCommunalAvailable)
+
+            assertThat(isCommunalAvailable).isFalse()
+
+            keyguardRepository.setIsEncryptedOrLockdown(false)
+            runCurrent()
+
+            assertThat(isCommunalAvailable).isFalse()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 744b65f..ee01bf9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -18,49 +18,75 @@
 package com.android.systemui.communal.domain.interactor
 
 import android.app.smartspace.SmartspaceTarget
+import android.content.pm.UserInfo
 import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
 import android.widget.RemoteViews
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
 import com.android.systemui.communal.data.repository.FakeCommunalRepository
 import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
 import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
+import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.fakeCommunalPrefsRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
+import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
+import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mock
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
+/**
+ * This class of test cases assume that communal is enabled. For disabled cases, see
+ * [CommunalInteractorCommunalDisabledTest].
+ */
 @SmallTest
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class CommunalInteractorTest : SysuiTestCase() {
-    private lateinit var testScope: TestScope
+    @Mock private lateinit var mainUser: UserInfo
+    @Mock private lateinit var secondaryUser: UserInfo
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
     private lateinit var tutorialRepository: FakeCommunalTutorialRepository
     private lateinit var communalRepository: FakeCommunalRepository
     private lateinit var mediaRepository: FakeCommunalMediaRepository
     private lateinit var widgetRepository: FakeCommunalWidgetRepository
     private lateinit var smartspaceRepository: FakeSmartspaceRepository
+    private lateinit var userRepository: FakeUserRepository
     private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository
     private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter
 
     private lateinit var underTest: CommunalInteractor
@@ -69,33 +95,95 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        testScope = TestScope()
+        tutorialRepository = kosmos.fakeCommunalTutorialRepository
+        communalRepository = kosmos.fakeCommunalRepository
+        mediaRepository = kosmos.fakeCommunalMediaRepository
+        widgetRepository = kosmos.fakeCommunalWidgetRepository
+        smartspaceRepository = kosmos.fakeSmartspaceRepository
+        userRepository = kosmos.fakeUserRepository
+        keyguardRepository = kosmos.fakeKeyguardRepository
+        editWidgetsActivityStarter = kosmos.editWidgetsActivityStarter
+        communalPrefsRepository = kosmos.fakeCommunalPrefsRepository
 
-        val withDeps = CommunalInteractorFactory.create()
+        whenever(mainUser.isMain).thenReturn(true)
+        whenever(secondaryUser.isMain).thenReturn(false)
+        userRepository.setUserInfos(listOf(mainUser, secondaryUser))
 
-        tutorialRepository = withDeps.tutorialRepository
-        communalRepository = withDeps.communalRepository
-        mediaRepository = withDeps.mediaRepository
-        widgetRepository = withDeps.widgetRepository
-        smartspaceRepository = withDeps.smartspaceRepository
-        keyguardRepository = withDeps.keyguardRepository
-        editWidgetsActivityStarter = withDeps.editWidgetsActivityStarter
-
-        underTest = withDeps.communalInteractor
+        underTest = kosmos.communalInteractor
     }
 
     @Test
-    fun communalEnabled() =
+    fun communalEnabled_true() =
+        testScope.runTest { assertThat(underTest.isCommunalEnabled).isTrue() }
+
+    @Test
+    fun isCommunalAvailable_storageUnlockedAndMainUser_true() =
         testScope.runTest {
-            communalRepository.setIsCommunalEnabled(true)
-            assertThat(underTest.isCommunalEnabled).isTrue()
+            val isAvailable by collectLastValue(underTest.isCommunalAvailable)
+            assertThat(isAvailable).isFalse()
+
+            keyguardRepository.setIsEncryptedOrLockdown(false)
+            userRepository.setSelectedUserInfo(mainUser)
+            keyguardRepository.setKeyguardShowing(true)
+            communalRepository.setCommunalEnabledState(true)
+
+            assertThat(isAvailable).isTrue()
         }
 
     @Test
-    fun communalDisabled() =
+    fun isCommunalAvailable_storageLockedAndMainUser_false() =
         testScope.runTest {
-            communalRepository.setIsCommunalEnabled(false)
-            assertThat(underTest.isCommunalEnabled).isFalse()
+            val isAvailable by collectLastValue(underTest.isCommunalAvailable)
+            assertThat(isAvailable).isFalse()
+
+            keyguardRepository.setIsEncryptedOrLockdown(true)
+            userRepository.setSelectedUserInfo(mainUser)
+            keyguardRepository.setKeyguardShowing(true)
+            communalRepository.setCommunalEnabledState(true)
+
+            assertThat(isAvailable).isFalse()
+        }
+
+    @Test
+    fun isCommunalAvailable_storageUnlockedAndSecondaryUser_false() =
+        testScope.runTest {
+            val isAvailable by collectLastValue(underTest.isCommunalAvailable)
+            assertThat(isAvailable).isFalse()
+
+            keyguardRepository.setIsEncryptedOrLockdown(false)
+            userRepository.setSelectedUserInfo(secondaryUser)
+            keyguardRepository.setKeyguardShowing(true)
+            communalRepository.setCommunalEnabledState(true)
+
+            assertThat(isAvailable).isFalse()
+        }
+
+    @Test
+    fun isCommunalAvailable_whenDreaming_true() =
+        testScope.runTest {
+            val isAvailable by collectLastValue(underTest.isCommunalAvailable)
+            assertThat(isAvailable).isFalse()
+
+            keyguardRepository.setIsEncryptedOrLockdown(false)
+            userRepository.setSelectedUserInfo(mainUser)
+            keyguardRepository.setDreaming(true)
+            communalRepository.setCommunalEnabledState(true)
+
+            assertThat(isAvailable).isTrue()
+        }
+
+    @Test
+    fun isCommunalAvailable_communalDisabled_false() =
+        testScope.runTest {
+            val isAvailable by collectLastValue(underTest.isCommunalAvailable)
+            assertThat(isAvailable).isFalse()
+
+            keyguardRepository.setIsEncryptedOrLockdown(false)
+            userRepository.setSelectedUserInfo(mainUser)
+            keyguardRepository.setKeyguardShowing(true)
+            communalRepository.setCommunalEnabledState(false)
+
+            assertThat(isAvailable).isFalse()
         }
 
     @Test
@@ -327,10 +415,9 @@
         }
 
     @Test
-    fun cta_visibilityTrue_shows() =
+    fun ctaTile_showsByDefault() =
         testScope.runTest {
             tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
-            communalRepository.setCtaTileInViewModeVisibility(true)
 
             val ctaTileContent by collectLastValue(underTest.ctaTileContent)
 
@@ -342,10 +429,10 @@
         }
 
     @Test
-    fun ctaTile_visibilityFalse_doesNotShow() =
+    fun ctaTile_afterDismiss_doesNotShow() =
         testScope.runTest {
             tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
-            communalRepository.setCtaTileInViewModeVisibility(false)
+            communalPrefsRepository.setCtaDismissedForCurrentUser()
 
             val ctaTileContent by collectLastValue(underTest.ctaTileContent)
 
@@ -379,6 +466,131 @@
         }
 
     @Test
+    fun transitionProgress_onTargetScene_fullProgress() =
+        testScope.runTest {
+            val targetScene = CommunalSceneKey.Blank
+            val transitionProgressFlow = underTest.transitionProgressToScene(targetScene)
+            val transitionProgress by collectLastValue(transitionProgressFlow)
+
+            val transitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(
+                    ObservableCommunalTransitionState.Idle(targetScene)
+                )
+            underTest.setTransitionState(transitionState)
+
+            // We're on the target scene.
+            assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(targetScene))
+        }
+
+    @Test
+    fun transitionProgress_notOnTargetScene_noProgress() =
+        testScope.runTest {
+            val targetScene = CommunalSceneKey.Blank
+            val currentScene = CommunalSceneKey.Communal
+            val transitionProgressFlow = underTest.transitionProgressToScene(targetScene)
+            val transitionProgress by collectLastValue(transitionProgressFlow)
+
+            val transitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(
+                    ObservableCommunalTransitionState.Idle(currentScene)
+                )
+            underTest.setTransitionState(transitionState)
+
+            // Transition progress is still idle, but we're not on the target scene.
+            assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(currentScene))
+        }
+
+    @Test
+    fun transitionProgress_transitioningToTrackedScene() =
+        testScope.runTest {
+            val currentScene = CommunalSceneKey.Communal
+            val targetScene = CommunalSceneKey.Blank
+            val transitionProgressFlow = underTest.transitionProgressToScene(targetScene)
+            val transitionProgress by collectLastValue(transitionProgressFlow)
+
+            var transitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(
+                    ObservableCommunalTransitionState.Idle(currentScene)
+                )
+            underTest.setTransitionState(transitionState)
+
+            // Progress starts at 0.
+            assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(currentScene))
+
+            val progress = MutableStateFlow(0f)
+            transitionState =
+                MutableStateFlow(
+                    ObservableCommunalTransitionState.Transition(
+                        fromScene = currentScene,
+                        toScene = targetScene,
+                        progress = progress,
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            underTest.setTransitionState(transitionState)
+
+            // Partially transition.
+            progress.value = .4f
+            assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Transition(.4f))
+
+            // Transition is at full progress.
+            progress.value = 1f
+            assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Transition(1f))
+
+            // Transition finishes.
+            transitionState = MutableStateFlow(ObservableCommunalTransitionState.Idle(targetScene))
+            underTest.setTransitionState(transitionState)
+            assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(targetScene))
+        }
+
+    @Test
+    fun transitionProgress_transitioningAwayFromTrackedScene() =
+        testScope.runTest {
+            val currentScene = CommunalSceneKey.Blank
+            val targetScene = CommunalSceneKey.Communal
+            val transitionProgressFlow = underTest.transitionProgressToScene(currentScene)
+            val transitionProgress by collectLastValue(transitionProgressFlow)
+
+            var transitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(
+                    ObservableCommunalTransitionState.Idle(currentScene)
+                )
+            underTest.setTransitionState(transitionState)
+
+            // Progress starts at 0.
+            assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(currentScene))
+
+            val progress = MutableStateFlow(0f)
+            transitionState =
+                MutableStateFlow(
+                    ObservableCommunalTransitionState.Transition(
+                        fromScene = currentScene,
+                        toScene = targetScene,
+                        progress = progress,
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            underTest.setTransitionState(transitionState)
+
+            // Partially transition.
+            progress.value = .4f
+
+            // This is a transition we don't care about the progress of.
+            assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.OtherTransition)
+
+            // Transition is at full progress.
+            progress.value = 1f
+            assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.OtherTransition)
+
+            // Transition finishes.
+            transitionState = MutableStateFlow(ObservableCommunalTransitionState.Idle(targetScene))
+            underTest.setTransitionState(transitionState)
+            assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(targetScene))
+        }
+
+    @Test
     fun isCommunalShowing() =
         testScope.runTest {
             var isCommunalShowing = collectLastValue(underTest.isCommunalShowing)
@@ -393,12 +605,57 @@
         }
 
     @Test
+    fun isIdleOnCommunal() =
+        testScope.runTest {
+            val transitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(
+                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Blank)
+                )
+            communalRepository.setTransitionState(transitionState)
+
+            // isIdleOnCommunal is false when not on communal.
+            val isIdleOnCommunal by collectLastValue(underTest.isIdleOnCommunal)
+            runCurrent()
+            assertThat(isIdleOnCommunal).isEqualTo(false)
+
+            // Transition to communal.
+            transitionState.value =
+                ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+            runCurrent()
+
+            // isIdleOnCommunal is now true since we're on communal.
+            assertThat(isIdleOnCommunal).isEqualTo(true)
+
+            // Start transition away from communal.
+            transitionState.value =
+                ObservableCommunalTransitionState.Transition(
+                    fromScene = CommunalSceneKey.Communal,
+                    toScene = CommunalSceneKey.Blank,
+                    progress = flowOf(0f),
+                    isInitiatedByUserInput = false,
+                    isUserInputOngoing = flowOf(false),
+                )
+            runCurrent()
+
+            // isIdleOnCommunal turns false as soon as transition away starts.
+            assertThat(isIdleOnCommunal).isEqualTo(false)
+        }
+
+    @Test
     fun testShowWidgetEditorStartsActivity() =
         testScope.runTest {
             underTest.showWidgetEditor()
             verify(editWidgetsActivityStarter).startActivity()
         }
 
+    @Test
+    fun showWidgetEditor_withPreselectedKey_startsActivity() =
+        testScope.runTest {
+            val widgetKey = CommunalContentModel.KEY.widget(123)
+            underTest.showWidgetEditor(preselectedKey = widgetKey)
+            verify(editWidgetsActivityStarter).startActivity(widgetKey)
+        }
+
     private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget {
         val timer = mock(SmartspaceTarget::class.java)
         whenever(timer.smartspaceTargetId).thenReturn(id)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
index 9a3129f..6c87e0f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal.domain.interactor
 
+import android.content.pm.UserInfo
 import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
 import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
 import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_STARTED
@@ -24,52 +25,52 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.FakeCommunalRepository
 import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CommunalTutorialInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
-    @Mock private lateinit var userTracker: UserTracker
-
-    private lateinit var testScope: TestScope
     private lateinit var underTest: CommunalTutorialInteractor
     private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var communalTutorialRepository: FakeCommunalTutorialRepository
     private lateinit var communalRepository: FakeCommunalRepository
+    private lateinit var communalInteractor: CommunalInteractor
+    private lateinit var userRepository: FakeUserRepository
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
+        keyguardRepository = kosmos.fakeKeyguardRepository
+        communalTutorialRepository = kosmos.fakeCommunalTutorialRepository
+        communalRepository = kosmos.fakeCommunalRepository
+        communalInteractor = kosmos.communalInteractor
+        userRepository = kosmos.fakeUserRepository
 
-        testScope = TestScope()
+        userRepository.setUserInfos(listOf(MAIN_USER_INFO))
 
-        val withDeps = CommunalTutorialInteractorFactory.create(testScope)
-        keyguardRepository = withDeps.keyguardRepository
-        communalTutorialRepository = withDeps.communalTutorialRepository
-        communalRepository = withDeps.communalRepository
-
-        underTest = withDeps.communalTutorialInteractor
-
-        whenever(userTracker.userHandle).thenReturn(mock())
+        underTest = kosmos.communalTutorialInteractor
     }
 
     @Test
     fun tutorialUnavailable_whenKeyguardNotVisible() =
         testScope.runTest {
             val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+            setCommunalAvailable(true)
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
             keyguardRepository.setKeyguardShowing(false)
             assertThat(isTutorialAvailable).isFalse()
@@ -79,6 +80,7 @@
     fun tutorialUnavailable_whenTutorialIsCompleted() =
         testScope.runTest {
             val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+            setCommunalAvailable(true)
             keyguardRepository.setKeyguardShowing(true)
             keyguardRepository.setKeyguardOccluded(false)
             communalRepository.setIsCommunalHubShowing(false)
@@ -87,9 +89,20 @@
         }
 
     @Test
+    fun tutorialUnavailable_whenCommunalNotAvailable() =
+        testScope.runTest {
+            val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+            setCommunalAvailable(false)
+            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
+            keyguardRepository.setKeyguardShowing(true)
+            assertThat(isTutorialAvailable).isFalse()
+        }
+
+    @Test
     fun tutorialAvailable_whenTutorialNotStarted() =
         testScope.runTest {
             val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+            setCommunalAvailable(true)
             keyguardRepository.setKeyguardShowing(true)
             keyguardRepository.setKeyguardOccluded(false)
             communalRepository.setIsCommunalHubShowing(false)
@@ -101,6 +114,7 @@
     fun tutorialAvailable_whenTutorialIsStarted() =
         testScope.runTest {
             val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+            setCommunalAvailable(true)
             keyguardRepository.setKeyguardShowing(true)
             keyguardRepository.setKeyguardOccluded(false)
             communalRepository.setIsCommunalHubShowing(true)
@@ -181,4 +195,21 @@
 
             assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
         }
+
+    private suspend fun setCommunalAvailable(available: Boolean) {
+        if (available) {
+            communalRepository.setIsCommunalEnabled(true)
+            communalRepository.setCommunalEnabledState(true)
+            keyguardRepository.setIsEncryptedOrLockdown(false)
+            userRepository.setSelectedUserInfo(MAIN_USER_INFO)
+            keyguardRepository.setKeyguardShowing(true)
+        } else {
+            communalRepository.setIsCommunalEnabled(false)
+            communalRepository.setCommunalEnabledState(false)
+        }
+    }
+
+    private companion object {
+        val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt
new file mode 100644
index 0000000..6b1b937
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.log
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.shared.log.CommunalUiEvent
+import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class CommunalLoggerStartableTest : SysuiTestCase() {
+    @Mock private lateinit var uiEventLogger: UiEventLogger
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private lateinit var communalInteractor: CommunalInteractor
+    private lateinit var underTest: CommunalLoggerStartable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        communalInteractor = kosmos.communalInteractor
+
+        underTest =
+            CommunalLoggerStartable(
+                testScope.backgroundScope,
+                communalInteractor,
+                uiEventLogger,
+            )
+        underTest.start()
+    }
+
+    @Test
+    fun transitionStateLogging_enterCommunalHub() =
+        testScope.runTest {
+            // Transition state is default (non-communal)
+            val transitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(idle(CommunalSceneKey.DEFAULT))
+            communalInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // Verify nothing is logged from the default state
+            verify(uiEventLogger, never()).log(any())
+
+            // Start transition to communal
+            transitionState.value = transition(to = CommunalSceneKey.Communal)
+            runCurrent()
+
+            // Verify UiEvent logged
+            verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_START)
+
+            // Finish transition to communal
+            transitionState.value = idle(CommunalSceneKey.Communal)
+            runCurrent()
+
+            // Verify UiEvent logged
+            verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_FINISH)
+            verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
+        }
+
+    @Test
+    fun transitionStateLogging_enterCommunalHub_canceled() =
+        testScope.runTest {
+            // Transition state is default (non-communal)
+            val transitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(idle(CommunalSceneKey.DEFAULT))
+            communalInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // Verify nothing is logged from the default state
+            verify(uiEventLogger, never()).log(any())
+
+            // Start transition to communal
+            transitionState.value = transition(to = CommunalSceneKey.Communal)
+            runCurrent()
+
+            // Verify UiEvent logged
+            verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_START)
+
+            // Cancel the transition
+            transitionState.value = idle(CommunalSceneKey.DEFAULT)
+            runCurrent()
+
+            // Verify UiEvent logged
+            verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_CANCEL)
+
+            // Verify neither SHOWN nor GONE is logged
+            verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
+            verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_GONE)
+        }
+
+    @Test
+    fun transitionStateLogging_exitCommunalHub() =
+        testScope.runTest {
+            // Transition state is communal
+            val transitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(idle(CommunalSceneKey.Communal))
+            communalInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // Verify SHOWN is logged when it's the default state
+            verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
+
+            // Start transition from communal
+            transitionState.value = transition(from = CommunalSceneKey.Communal)
+            runCurrent()
+
+            // Verify UiEvent logged
+            verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_START)
+
+            // Finish transition to communal
+            transitionState.value = idle(CommunalSceneKey.DEFAULT)
+            runCurrent()
+
+            // Verify UiEvent logged
+            verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_FINISH)
+            verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_GONE)
+        }
+
+    @Test
+    fun transitionStateLogging_exitCommunalHub_canceled() =
+        testScope.runTest {
+            // Transition state is communal
+            val transitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(idle(CommunalSceneKey.Communal))
+            communalInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // Clear the initial SHOWN event from the logger
+            clearInvocations(uiEventLogger)
+
+            // Start transition from communal
+            transitionState.value = transition(from = CommunalSceneKey.Communal)
+            runCurrent()
+
+            // Verify UiEvent logged
+            verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_START)
+
+            // Cancel the transition
+            transitionState.value = idle(CommunalSceneKey.Communal)
+            runCurrent()
+
+            // Verify UiEvent logged
+            verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_CANCEL)
+
+            // Verify neither SHOWN nor GONE is logged
+            verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
+            verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_GONE)
+        }
+
+    private fun transition(
+        from: CommunalSceneKey = CommunalSceneKey.DEFAULT,
+        to: CommunalSceneKey = CommunalSceneKey.DEFAULT,
+    ): ObservableCommunalTransitionState.Transition {
+        return ObservableCommunalTransitionState.Transition(
+            fromScene = from,
+            toScene = to,
+            progress = emptyFlow(),
+            isInitiatedByUserInput = true,
+            isUserInputOngoing = emptyFlow(),
+        )
+    }
+
+    private fun idle(sceneKey: CommunalSceneKey): ObservableCommunalTransitionState.Idle {
+        return ObservableCommunalTransitionState.Idle(sceneKey)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
new file mode 100644
index 0000000..3aa99c4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.widgets
+
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
+import com.android.systemui.communal.widgets.CommunalAppWidgetHostView
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidJUnit4::class)
+class CommunalAppWidgetHostTest : SysuiTestCase() {
+
+    private lateinit var testableLooper: TestableLooper
+    private lateinit var underTest: CommunalAppWidgetHost
+
+    @Before
+    fun setUp() {
+        testableLooper = TestableLooper.get(this)
+        underTest =
+            CommunalAppWidgetHost(
+                context = context,
+                hostId = 116,
+                interactionHandler = mock(),
+                looper = testableLooper.looper
+            )
+    }
+
+    @Test
+    fun createViewForCommunal_returnCommunalAppWidgetView() {
+        val appWidgetId = 789
+        val view =
+            underTest.createViewForCommunal(
+                context = context,
+                appWidgetId = appWidgetId,
+                appWidget = null
+            )
+        testableLooper.processAllMessages()
+
+        assertThat(view).isInstanceOf(CommunalAppWidgetHostView::class.java)
+        assertThat(view).isNotNull()
+        assertThat(view.appWidgetId).isEqualTo(appWidgetId)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index c638e1e..273d1cd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -16,60 +16,51 @@
 
 package com.android.systemui.communal.view.viewmodel
 
-import android.app.Activity.RESULT_CANCELED
-import android.app.Activity.RESULT_OK
 import android.app.smartspace.SmartspaceTarget
-import android.appwidget.AppWidgetHost
-import android.content.ComponentName
-import android.os.PowerManager
 import android.provider.Settings
 import android.widget.RemoteViews
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
 import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
 import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
+import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.log.CommunalUiEvent
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.controls.ui.MediaHost
-import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
+import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import javax.inject.Provider
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CommunalEditModeViewModelTest : SysuiTestCase() {
     @Mock private lateinit var mediaHost: MediaHost
-    @Mock private lateinit var shadeViewController: ShadeViewController
-    @Mock private lateinit var powerManager: PowerManager
-    @Mock private lateinit var appWidgetHost: AppWidgetHost
+    @Mock private lateinit var uiEventLogger: UiEventLogger
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
 
-    private lateinit var keyguardRepository: FakeKeyguardRepository
-    private lateinit var communalRepository: FakeCommunalRepository
     private lateinit var tutorialRepository: FakeCommunalTutorialRepository
     private lateinit var widgetRepository: FakeCommunalWidgetRepository
     private lateinit var smartspaceRepository: FakeSmartspaceRepository
@@ -81,21 +72,16 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        val withDeps = CommunalInteractorFactory.create(testScope)
-        keyguardRepository = withDeps.keyguardRepository
-        communalRepository = withDeps.communalRepository
-        tutorialRepository = withDeps.tutorialRepository
-        widgetRepository = withDeps.widgetRepository
-        smartspaceRepository = withDeps.smartspaceRepository
-        mediaRepository = withDeps.mediaRepository
+        tutorialRepository = kosmos.fakeCommunalTutorialRepository
+        widgetRepository = kosmos.fakeCommunalWidgetRepository
+        smartspaceRepository = kosmos.fakeSmartspaceRepository
+        mediaRepository = kosmos.fakeCommunalMediaRepository
 
         underTest =
             CommunalEditModeViewModel(
-                withDeps.communalInteractor,
-                appWidgetHost,
-                Provider { shadeViewController },
-                powerManager,
+                kosmos.communalInteractor,
                 mediaHost,
+                uiEventLogger,
             )
     }
 
@@ -143,64 +129,33 @@
         }
 
     @Test
-    fun interactionHandlerIgnoresClicks() {
-        val interactionHandler = underTest.getInteractionHandler()
-        assertThat(
-                interactionHandler.onInteraction(
-                    /* view = */ mock(),
-                    /* pendingIntent = */ mock(),
-                    /* response = */ mock()
-                )
-            )
-            .isEqualTo(false)
+    fun selectedKey_onReorderWidgets_isCleared() =
+        testScope.runTest {
+            val selectedKey by collectLastValue(underTest.selectedKey)
+
+            val key = CommunalContentModel.KEY.widget(123)
+            underTest.setSelectedKey(key)
+            assertThat(selectedKey).isEqualTo(key)
+
+            underTest.onReorderWidgetStart()
+            assertThat(selectedKey).isNull()
+        }
+
+    @Test
+    fun reorderWidget_uiEventLogging_start() {
+        underTest.onReorderWidgetStart()
+        verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START)
     }
 
     @Test
-    fun addingWidgetTriggersConfiguration() =
-        testScope.runTest {
-            val provider = ComponentName("pkg.test", "testWidget")
-            val widgetToConfigure by collectLastValue(underTest.widgetsToConfigure)
-            assertThat(widgetToConfigure).isNull()
-            underTest.onAddWidget(componentName = provider, priority = 0)
-            assertThat(widgetToConfigure).isEqualTo(1)
-        }
-
-    @Test
-    fun settingResultOkAddsWidget() =
-        testScope.runTest {
-            val provider = ComponentName("pkg.test", "testWidget")
-            val widgetAdded by collectLastValue(widgetRepository.widgetAdded)
-            assertThat(widgetAdded).isNull()
-            underTest.onAddWidget(componentName = provider, priority = 0)
-            assertThat(widgetAdded).isNull()
-            underTest.setConfigurationResult(RESULT_OK)
-            assertThat(widgetAdded).isEqualTo(1)
-        }
-
-    @Test
-    fun settingResultCancelledDoesNotAddWidget() =
-        testScope.runTest {
-            val provider = ComponentName("pkg.test", "testWidget")
-            val widgetAdded by collectLastValue(widgetRepository.widgetAdded)
-            assertThat(widgetAdded).isNull()
-            underTest.onAddWidget(componentName = provider, priority = 0)
-            assertThat(widgetAdded).isNull()
-            underTest.setConfigurationResult(RESULT_CANCELED)
-            assertThat(widgetAdded).isNull()
-        }
-
-    @Test(expected = IllegalStateException::class)
-    fun settingResultBeforeWidgetAddedThrowsException() {
-        underTest.setConfigurationResult(RESULT_OK)
+    fun reorderWidget_uiEventLogging_end() {
+        underTest.onReorderWidgetEnd()
+        verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_FINISH)
     }
 
-    @Test(expected = IllegalStateException::class)
-    fun addingWidgetWhileConfigurationActiveFails() =
-        testScope.runTest {
-            val providerOne = ComponentName("pkg.test", "testWidget")
-            underTest.onAddWidget(componentName = providerOne, priority = 0)
-            runCurrent()
-            val providerTwo = ComponentName("pkg.test", "testWidget2")
-            underTest.onAddWidget(componentName = providerTwo, priority = 0)
-        }
+    @Test
+    fun reorderWidget_uiEventLogging_cancel() {
+        underTest.onReorderWidgetCancel()
+        verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 16e0bc0..0723e83 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -17,54 +17,66 @@
 package com.android.systemui.communal.view.viewmodel
 
 import android.app.smartspace.SmartspaceTarget
-import android.os.PowerManager
+import android.content.pm.UserInfo
 import android.provider.Settings
 import android.widget.RemoteViews
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
 import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
 import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
+import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalTutorialInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
-import com.android.systemui.communal.widgets.WidgetInteractionHandler
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.ui.MediaHierarchyManager
 import com.android.systemui.media.controls.ui.MediaHost
-import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
+import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import javax.inject.Provider
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CommunalViewModelTest : SysuiTestCase() {
     @Mock private lateinit var mediaHost: MediaHost
-    @Mock private lateinit var shadeViewController: ShadeViewController
-    @Mock private lateinit var powerManager: PowerManager
+    @Mock private lateinit var user: UserInfo
 
-    private lateinit var testScope: TestScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
     private lateinit var keyguardRepository: FakeKeyguardRepository
-    private lateinit var communalRepository: FakeCommunalRepository
     private lateinit var tutorialRepository: FakeCommunalTutorialRepository
     private lateinit var widgetRepository: FakeCommunalWidgetRepository
     private lateinit var smartspaceRepository: FakeSmartspaceRepository
     private lateinit var mediaRepository: FakeCommunalMediaRepository
+    private lateinit var userRepository: FakeUserRepository
 
     private lateinit var underTest: CommunalViewModel
 
@@ -72,33 +84,39 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        testScope = TestScope()
+        keyguardRepository = kosmos.fakeKeyguardRepository
+        tutorialRepository = kosmos.fakeCommunalTutorialRepository
+        widgetRepository = kosmos.fakeCommunalWidgetRepository
+        smartspaceRepository = kosmos.fakeSmartspaceRepository
+        mediaRepository = kosmos.fakeCommunalMediaRepository
+        userRepository = kosmos.fakeUserRepository
 
-        val withDeps = CommunalInteractorFactory.create()
-        keyguardRepository = withDeps.keyguardRepository
-        communalRepository = withDeps.communalRepository
-        tutorialRepository = withDeps.tutorialRepository
-        widgetRepository = withDeps.widgetRepository
-        smartspaceRepository = withDeps.smartspaceRepository
-        mediaRepository = withDeps.mediaRepository
+        kosmos.fakeCommunalRepository.setCommunalEnabledState(true)
 
         underTest =
             CommunalViewModel(
-                withDeps.communalInteractor,
-                WidgetInteractionHandler(mock()),
-                withDeps.tutorialInteractor,
-                Provider { shadeViewController },
-                powerManager,
+                testScope,
+                kosmos.communalInteractor,
+                kosmos.communalTutorialInteractor,
                 mediaHost,
             )
     }
 
     @Test
+    fun init_initsMediaHost() =
+        testScope.runTest {
+            // MediaHost is initialized as soon as the class is created.
+            verify(mediaHost).init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
+        }
+
+    @Test
     fun tutorial_tutorialNotCompletedAndKeyguardVisible_showTutorialContent() =
         testScope.runTest {
-            // Keyguard showing, and tutorial not started.
+            // Keyguard showing, storage unlocked, main user, and tutorial not started.
             keyguardRepository.setKeyguardShowing(true)
             keyguardRepository.setKeyguardOccluded(false)
+            keyguardRepository.setIsEncryptedOrLockdown(false)
+            setIsMainUser(true)
             tutorialRepository.setTutorialSettingState(
                 Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
             )
@@ -142,9 +160,6 @@
             // Media playing.
             mediaRepository.mediaActive()
 
-            // CTA Tile not dismissed.
-            communalRepository.setCtaTileInViewModeVisibility(true)
-
             val communalContent by collectLastValue(underTest.communalContent)
 
             // Order is smart space, then UMO, widget content and cta tile.
@@ -159,4 +174,48 @@
             assertThat(communalContent?.get(4))
                 .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
         }
+
+    @Test
+    fun dismissCta_hidesCtaTileAndShowsPopup_thenHidesPopupAfterTimeout() =
+        testScope.runTest {
+            tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+
+            val communalContent by collectLastValue(underTest.communalContent)
+            val isPopupOnDismissCtaShowing by collectLastValue(underTest.isPopupOnDismissCtaShowing)
+
+            assertThat(communalContent?.size).isEqualTo(1)
+            assertThat(communalContent?.get(0))
+                .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
+
+            underTest.onDismissCtaTile()
+
+            // hide CTA tile and show the popup
+            assertThat(communalContent).isEmpty()
+            assertThat(isPopupOnDismissCtaShowing).isEqualTo(true)
+
+            // hide popup after time elapsed
+            advanceTimeBy(POPUP_AUTO_HIDE_TIMEOUT_MS)
+            assertThat(isPopupOnDismissCtaShowing).isEqualTo(false)
+        }
+
+    @Test
+    fun popup_onDismiss_hidesImmediately() =
+        testScope.runTest {
+            tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+
+            val isPopupOnDismissCtaShowing by collectLastValue(underTest.isPopupOnDismissCtaShowing)
+
+            underTest.onDismissCtaTile()
+            assertThat(isPopupOnDismissCtaShowing).isEqualTo(true)
+
+            // dismiss the popup directly
+            underTest.onHidePopupAfterDismissCta()
+            assertThat(isPopupOnDismissCtaShowing).isEqualTo(false)
+        }
+
+    private suspend fun setIsMainUser(isMainUser: Boolean) {
+        whenever(user.isMain).thenReturn(isMainUser)
+        userRepository.setUserInfos(listOf(user))
+        userRepository.setSelectedUserInfo(user)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
new file mode 100644
index 0000000..a3654b6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalAppWidgetHostStartableTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
+    @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
+
+    private lateinit var underTest: CommunalAppWidgetHostStartable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
+
+        underTest =
+            CommunalAppWidgetHostStartable(
+                appWidgetHost,
+                kosmos.communalInteractor,
+                kosmos.applicationCoroutineScope,
+                kosmos.testDispatcher,
+            )
+    }
+
+    @Test
+    fun editModeShowingStartsAppWidgetHost() =
+        with(kosmos) {
+            testScope.runTest {
+                setCommunalAvailable(false)
+                communalInteractor.setEditModeOpen(true)
+                verify(appWidgetHost, never()).startListening()
+
+                underTest.start()
+                runCurrent()
+
+                verify(appWidgetHost).startListening()
+                verify(appWidgetHost, never()).stopListening()
+
+                communalInteractor.setEditModeOpen(false)
+                runCurrent()
+
+                verify(appWidgetHost).stopListening()
+            }
+        }
+
+    @Test
+    fun communalShowingStartsAppWidgetHost() =
+        with(kosmos) {
+            testScope.runTest {
+                setCommunalAvailable(true)
+                communalInteractor.setEditModeOpen(false)
+                verify(appWidgetHost, never()).startListening()
+
+                underTest.start()
+                runCurrent()
+
+                verify(appWidgetHost).startListening()
+                verify(appWidgetHost, never()).stopListening()
+
+                setCommunalAvailable(false)
+                runCurrent()
+
+                verify(appWidgetHost).stopListening()
+            }
+        }
+
+    @Test
+    fun communalAndEditModeNotShowingNeverStartListening() =
+        with(kosmos) {
+            testScope.runTest {
+                setCommunalAvailable(false)
+                communalInteractor.setEditModeOpen(false)
+
+                underTest.start()
+                runCurrent()
+
+                verify(appWidgetHost, never()).startListening()
+                verify(appWidgetHost, never()).stopListening()
+            }
+        }
+
+    private suspend fun setCommunalAvailable(available: Boolean) =
+        with(kosmos) {
+            fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
+            fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
+            fakeKeyguardRepository.setKeyguardShowing(true)
+            fakeCommunalRepository.setCommunalEnabledState(available)
+        }
+
+    private companion object {
+        val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt
new file mode 100644
index 0000000..55fafdf
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.app.Activity
+import android.content.ActivityNotFoundException
+import androidx.activity.ComponentActivity
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.async
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WidgetConfigurationControllerTest : SysuiTestCase() {
+    @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
+    @Mock private lateinit var ownerActivity: ComponentActivity
+
+    private val kosmos = testKosmos()
+
+    private lateinit var underTest: WidgetConfigurationController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest =
+            WidgetConfigurationController(ownerActivity, appWidgetHost, kosmos.testDispatcher)
+    }
+
+    @Test
+    fun configurationFailsWhenActivityNotFound() =
+        with(kosmos) {
+            testScope.runTest {
+                whenever(
+                        appWidgetHost.startAppWidgetConfigureActivityForResult(
+                            eq(ownerActivity),
+                            eq(123),
+                            anyInt(),
+                            eq(WidgetConfigurationController.REQUEST_CODE),
+                            any()
+                        )
+                    )
+                    .thenThrow(ActivityNotFoundException())
+
+                assertThat(underTest.configureWidget(123)).isFalse()
+            }
+        }
+
+    @Test
+    fun configurationFails() =
+        with(kosmos) {
+            testScope.runTest {
+                val result = async { underTest.configureWidget(123) }
+                runCurrent()
+                assertThat(result.isCompleted).isFalse()
+
+                underTest.setConfigurationResult(Activity.RESULT_CANCELED)
+                runCurrent()
+
+                assertThat(result.await()).isFalse()
+                result.cancel()
+            }
+        }
+
+    @Test
+    fun configurationSuccessful() =
+        with(kosmos) {
+            testScope.runTest {
+                val result = async { underTest.configureWidget(123) }
+                runCurrent()
+                assertThat(result.isCompleted).isFalse()
+
+                underTest.setConfigurationResult(Activity.RESULT_OK)
+                runCurrent()
+
+                assertThat(result.await()).isTrue()
+                result.cancel()
+            }
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
new file mode 100644
index 0000000..69ff5ab
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.RemoteViews.RemoteResponse
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.util.mockito.eq
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.isNull
+import org.mockito.Mockito.notNull
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WidgetInteractionHandlerTest : SysuiTestCase() {
+    @Mock private lateinit var activityStarter: ActivityStarter
+
+    private lateinit var underTest: WidgetInteractionHandler
+
+    private val testIntent =
+        PendingIntent.getActivity(
+            context,
+            /* requestCode = */ 0,
+            Intent("action"),
+            PendingIntent.FLAG_IMMUTABLE
+        )
+    private val testResponse = RemoteResponse.fromPendingIntent(testIntent)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest = WidgetInteractionHandler(activityStarter)
+    }
+
+    @Test
+    fun launchAnimatorIsUsedForWidgetView() {
+        val parent = FrameLayout(context)
+        val view = CommunalAppWidgetHostView(context)
+        parent.addView(view)
+
+        underTest.onInteraction(view, testIntent, testResponse)
+
+        verify(activityStarter)
+            .startPendingIntentMaybeDismissingKeyguard(
+                eq(testIntent),
+                isNull(),
+                notNull(),
+            )
+    }
+
+    @Test
+    fun launchAnimatorIsNotUsedForRegularView() {
+        val parent = FrameLayout(context)
+        val view = View(context)
+        parent.addView(view)
+
+        underTest.onInteraction(view, testIntent, testResponse)
+
+        verify(activityStarter)
+            .startPendingIntentMaybeDismissingKeyguard(eq(testIntent), isNull(), isNull())
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 6a14220..6808f5d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -38,6 +38,7 @@
 import com.android.internal.logging.InstanceId.fakeInstanceId
 import com.android.internal.logging.UiEventLogger
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
 import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
@@ -62,7 +63,6 @@
 import com.android.systemui.display.data.repository.display
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.keyguard.data.repository.BiometricType
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
@@ -194,7 +194,7 @@
         biometricSettingsRepository = FakeBiometricSettingsRepository()
         deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
         trustRepository = FakeTrustRepository()
-        featureFlags = FakeFeatureFlags().apply { set(KEYGUARD_WM_STATE_REFACTOR, false) }
+        featureFlags = FakeFeatureFlags()
 
         powerRepository = FakePowerRepository()
         powerInteractor =
@@ -252,6 +252,10 @@
             .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true)))
         whenever(bypassController.bypassEnabled).thenReturn(true)
         underTest = createDeviceEntryFaceAuthRepositoryImpl(faceManager, bypassController)
+
+        mSetFlagsRule.disableFlags(
+            AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+        )
     }
 
     private fun createDeviceEntryFaceAuthRepositoryImpl(
@@ -301,7 +305,6 @@
             faceAuthBuffer,
             keyguardTransitionInteractor,
             displayStateInteractor,
-            featureFlags,
             dumpManager,
         )
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
index 565049b..b54c5bd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
@@ -7,9 +7,11 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.whenever
@@ -36,8 +38,8 @@
     @Mock private lateinit var keyguardBypassController: KeyguardBypassController
     @Mock private lateinit var keyguardStateController: KeyguardStateController
 
-    private val testUtils = SceneTestUtils(this)
-    private val testScope = testUtils.testScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
     private val userRepository = FakeUserRepository()
     private val keyguardRepository = FakeKeyguardRepository()
 
@@ -52,7 +54,7 @@
         underTest =
             DeviceEntryRepositoryImpl(
                 applicationScope = testScope.backgroundScope,
-                backgroundDispatcher = testUtils.testDispatcher,
+                backgroundDispatcher = kosmos.testDispatcher,
                 userRepository = userRepository,
                 lockPatternUtils = lockPatternUtils,
                 keyguardBypassController = keyguardBypassController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt
new file mode 100644
index 0000000..88ad3f3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AuthRippleInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val deviceEntrySourceInteractor = kosmos.deviceEntrySourceInteractor
+    private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+    private val underTest = kosmos.authRippleInteractor
+
+    @Test
+    fun enteringDeviceFromDeviceEntryIcon_udfpsNotSupported_doesNotShowAuthRipple() =
+        testScope.runTest {
+            val showUnlockRipple by collectLastValue(underTest.showUnlockRipple)
+            fingerprintPropertyRepository.supportsRearFps()
+            keyguardRepository.setKeyguardDismissible(true)
+            runCurrent()
+            deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()
+            assertThat(showUnlockRipple).isNull()
+        }
+
+    @Test
+    fun enteringDeviceFromDeviceEntryIcon_udfpsSupported_showsAuthRipple() =
+        testScope.runTest {
+            val showUnlockRipple by collectLastValue(underTest.showUnlockRipple)
+            fingerprintPropertyRepository.supportsUdfps()
+            keyguardRepository.setKeyguardDismissible(true)
+            runCurrent()
+            deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()
+            assertThat(showUnlockRipple).isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
+        }
+
+    @Test
+    fun faceUnlocked_showsAuthRipple() =
+        testScope.runTest {
+            val showUnlockRipple by collectLastValue(underTest.showUnlockRipple)
+            keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR)
+            keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+            assertThat(showUnlockRipple).isEqualTo(BiometricUnlockSource.FACE_SENSOR)
+        }
+
+    @Test
+    fun fingerprintUnlocked_showsAuthRipple() =
+        testScope.runTest {
+            val showUnlockRippleFromBiometricUnlock by collectLastValue(underTest.showUnlockRipple)
+            keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
+            keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+            assertThat(showUnlockRippleFromBiometricUnlock)
+                .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index ea19cb7..05b5891 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -20,19 +20,25 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
-import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeTrustRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -41,21 +47,19 @@
 @RunWith(AndroidJUnit4::class)
 class DeviceEntryInteractorTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val repository: FakeDeviceEntryRepository = utils.deviceEntryRepository
-    private val faceAuthRepository = FakeDeviceEntryFaceAuthRepository()
-    private val trustRepository = FakeTrustRepository()
-    private val sceneInteractor = utils.sceneInteractor()
-    private val authenticationInteractor = utils.authenticationInteractor()
-    private val underTest =
-        utils.deviceEntryInteractor(
-            repository = repository,
-            authenticationInteractor = authenticationInteractor,
-            sceneInteractor = sceneInteractor,
-            faceAuthRepository = faceAuthRepository,
-            trustRepository = trustRepository,
-        )
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository }
+    private val trustRepository by lazy { kosmos.fakeTrustRepository }
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
+    private lateinit var underTest: DeviceEntryInteractor
+
+    @Before
+    fun setUp() {
+        kosmos.fakeSceneContainerFlags.enabled = true
+        underTest = kosmos.deviceEntryInteractor
+    }
 
     @Test
     fun canSwipeToEnter_startsNull() =
@@ -67,8 +71,10 @@
     @Test
     fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
-            utils.deviceEntryRepository.apply {
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
+            kosmos.fakeDeviceEntryRepository.apply {
                 setLockscreenEnabled(false)
 
                 // Toggle isUnlocked, twice.
@@ -101,8 +107,10 @@
     @Test
     fun isUnlocked_whenAuthMethodIsSimAndUnlocked_isFalse() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Sim
+            )
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
 
             val isUnlocked by collectLastValue(underTest.isUnlocked)
             assertThat(isUnlocked).isFalse()
@@ -159,10 +167,10 @@
     @Test
     fun isDeviceEntered_onBouncer_isFalse() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pattern
             )
-            utils.deviceEntryRepository.setLockscreenEnabled(true)
+            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
             switchToScene(SceneKey.Lockscreen)
             runCurrent()
             switchToScene(SceneKey.Bouncer)
@@ -184,8 +192,10 @@
     @Test
     fun canSwipeToEnter_onLockscreenWithPin_isFalse() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.deviceEntryRepository.setLockscreenEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
             switchToScene(SceneKey.Lockscreen)
 
             val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
@@ -205,15 +215,15 @@
         }
 
     private fun setupSwipeDeviceEntryMethod() {
-        utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
-        utils.deviceEntryRepository.setLockscreenEnabled(true)
+        kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+        kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
     }
 
     @Test
     fun canSwipeToEnter_whenTrustedByTrustManager_isTrue() =
         testScope.runTest {
             val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
-            utils.authenticationRepository.setAuthenticationMethod(
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
             switchToScene(SceneKey.Lockscreen)
@@ -230,7 +240,7 @@
     fun canSwipeToEnter_whenAuthenticatedByFace_isTrue() =
         testScope.runTest {
             val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
-            utils.authenticationRepository.setAuthenticationMethod(
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
             switchToScene(SceneKey.Lockscreen)
@@ -246,9 +256,9 @@
     @Test
     fun isAuthenticationRequired_lockedAndSecured_true() =
         testScope.runTest {
-            utils.deviceEntryRepository.setUnlocked(false)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(false)
             runCurrent()
-            utils.authenticationRepository.setAuthenticationMethod(
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
 
@@ -258,9 +268,11 @@
     @Test
     fun isAuthenticationRequired_lockedAndNotSecured_false() =
         testScope.runTest {
-            utils.deviceEntryRepository.setUnlocked(false)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(false)
             runCurrent()
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
 
             assertThat(underTest.isAuthenticationRequired()).isFalse()
         }
@@ -268,9 +280,9 @@
     @Test
     fun isAuthenticationRequired_unlockedAndSecured_false() =
         testScope.runTest {
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
             runCurrent()
-            utils.authenticationRepository.setAuthenticationMethod(
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
 
@@ -280,9 +292,11 @@
     @Test
     fun isAuthenticationRequired_unlockedAndNotSecured_false() =
         testScope.runTest {
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
             runCurrent()
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
 
             assertThat(underTest.isAuthenticationRequired()).isFalse()
         }
@@ -290,7 +304,7 @@
     @Test
     fun isBypassEnabled_enabledInRepository_true() =
         testScope.runTest {
-            utils.deviceEntryRepository.setBypassEnabled(true)
+            kosmos.fakeDeviceEntryRepository.setBypassEnabled(true)
             assertThat(underTest.isBypassEnabled.value).isTrue()
         }
 
@@ -301,8 +315,10 @@
             switchToScene(SceneKey.Lockscreen)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
             runCurrent()
 
             underTest.attemptDeviceEntry()
@@ -317,7 +333,10 @@
             switchToScene(SceneKey.Lockscreen)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
+            runCurrent()
 
             underTest.attemptDeviceEntry()
 
@@ -331,8 +350,11 @@
             switchToScene(SceneKey.Lockscreen)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
-            utils.deviceEntryRepository.setLockscreenEnabled(true)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
+            runCurrent()
 
             underTest.attemptDeviceEntry()
 
@@ -342,7 +364,7 @@
     @Test
     fun isBypassEnabled_disabledInRepository_false() =
         testScope.runTest {
-            utils.deviceEntryRepository.setBypassEnabled(false)
+            kosmos.fakeDeviceEntryRepository.setBypassEnabled(false)
             assertThat(underTest.isBypassEnabled.value).isFalse()
         }
 
@@ -350,8 +372,10 @@
     fun successfulAuthenticationChallengeAttempt_updatesIsUnlockedState() =
         testScope.runTest {
             val isUnlocked by collectLastValue(underTest.isUnlocked)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.deviceEntryRepository.setLockscreenEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
             assertThat(isUnlocked).isFalse()
 
             authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt
new file mode 100644
index 0000000..d216fa0
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntrySourceInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+    private val underTest = kosmos.deviceEntrySourceInteractor
+
+    @Test
+    fun deviceEntryFromFaceUnlock() =
+        testScope.runTest {
+            val deviceEntryFromBiometricAuthentication by
+                collectLastValue(underTest.deviceEntryFromBiometricSource)
+            keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR)
+            keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+            runCurrent()
+            assertThat(deviceEntryFromBiometricAuthentication)
+                .isEqualTo(BiometricUnlockSource.FACE_SENSOR)
+        }
+
+    @Test
+    fun deviceEntryFromFingerprintUnlock() = runTest {
+        val deviceEntryFromBiometricAuthentication by
+            collectLastValue(underTest.deviceEntryFromBiometricSource)
+        keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
+        keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+        runCurrent()
+        assertThat(deviceEntryFromBiometricAuthentication)
+            .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
+    }
+
+    @Test
+    fun noDeviceEntry() = runTest {
+        val deviceEntryFromBiometricAuthentication by
+            collectLastValue(underTest.deviceEntryFromBiometricSource)
+        keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
+        // doesn't dismiss keyguard:
+        keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.ONLY_WAKE)
+        runCurrent()
+        assertThat(deviceEntryFromBiometricAuthentication).isNull()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
new file mode 100644
index 0000000..32943a1
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceUnlockedInteractorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val authenticationRepository = kosmos.fakeAuthenticationRepository
+    private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
+
+    val underTest =
+        DeviceUnlockedInteractor(
+            applicationScope = testScope.backgroundScope,
+            authenticationInteractor = kosmos.authenticationInteractor,
+            deviceEntryRepository = deviceEntryRepository,
+        )
+
+    @Test
+    fun isDeviceUnlocked_whenUnlockedAndAuthMethodIsNone_isTrue() =
+        testScope.runTest {
+            val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+
+            deviceEntryRepository.setUnlocked(true)
+            authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+
+            assertThat(isUnlocked).isTrue()
+        }
+
+    @Test
+    fun isDeviceUnlocked_whenUnlockedAndAuthMethodIsPin_isTrue() =
+        testScope.runTest {
+            val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+
+            deviceEntryRepository.setUnlocked(true)
+            authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+
+            assertThat(isUnlocked).isTrue()
+        }
+
+    @Test
+    fun isDeviceUnlocked_whenUnlockedAndAuthMethodIsSim_isFalse() =
+        testScope.runTest {
+            val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+
+            deviceEntryRepository.setUnlocked(true)
+            authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
+
+            assertThat(isUnlocked).isFalse()
+        }
+
+    @Test
+    fun isDeviceUnlocked_whenLockedAndAuthMethodIsNone_isTrue() =
+        testScope.runTest {
+            val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+
+            deviceEntryRepository.setUnlocked(false)
+            authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+
+            assertThat(isUnlocked).isTrue()
+        }
+
+    @Test
+    fun isDeviceUnlocked_whenLockedAndAuthMethodIsPin_isFalse() =
+        testScope.runTest {
+            val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+
+            deviceEntryRepository.setUnlocked(false)
+            authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+
+            assertThat(isUnlocked).isFalse()
+        }
+
+    @Test
+    fun isDeviceUnlocked_whenLockedAndAuthMethodIsSim_isFalse() =
+        testScope.runTest {
+            val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+
+            deviceEntryRepository.setUnlocked(false)
+            authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
+
+            assertThat(isUnlocked).isFalse()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 562f96c..c143468 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -93,7 +93,7 @@
     @Rule
     public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
 
-    WindowManager.LayoutParams mWindowParams = new WindowManager.LayoutParams();
+    WindowManager.LayoutParams mWindowParams;
 
     @Mock
     IDreamOverlayCallback mDreamOverlayCallback;
@@ -184,6 +184,7 @@
         when(mDreamOverlayContainerViewController.getContainerView())
                 .thenReturn(mDreamOverlayContainerView);
 
+        mWindowParams = new WindowManager.LayoutParams();
         mService = new DreamOverlayService(
                 mContext,
                 mLifecycleOwner,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
index 3d1efa5..5827671 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -293,8 +293,8 @@
     }
 
     /**
-     * Verifies that swiping up when the lock pattern is not secure does not consume the scroll
-     * gesture or expand.
+     * Verifies that swiping up when the lock pattern is not secure dismissed dream and consumes
+     * the gesture.
      */
     @Test
     public void testSwipeUp_keyguardNotSecure_doesNotExpand() {
@@ -314,11 +314,44 @@
 
         reset(mScrimController);
 
-        // Scroll gesture is not consumed.
+        // Scroll gesture is consumed.
         assertThat(gestureListener.onScroll(event1, event2, 0, distanceY))
-                .isFalse();
+                .isTrue();
         // We should not expand since the keyguard is not secure
         verify(mScrimController, never()).expand(any());
+        // Since we are swiping up, we should wake from dreams.
+        verify(mCentralSurfaces).awakenDreams();
+    }
+
+    /**
+     * Verifies that swiping down when the lock pattern is not secure does not dismiss the dream.
+     */
+    @Test
+    public void testSwipeDown_keyguardNotSecure_doesNotExpand() {
+        when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(false);
+        mTouchHandler.onSessionStart(mTouchSession);
+        ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
+                ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+        verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+
+        final OnGestureListener gestureListener = gestureListenerCaptor.getValue();
+
+        final float distanceY = SCREEN_HEIGHT_PX * 0.3f;
+        // Swiping down near the bottom of the screen where the touch initiation region is.
+        final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+                0, SCREEN_HEIGHT_PX - distanceY, 0);
+        final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+                0, SCREEN_HEIGHT_PX, 0);
+
+        reset(mScrimController);
+
+        // Scroll gesture is not consumed.
+        assertThat(gestureListener.onScroll(event1, event2, 0, distanceY))
+                .isTrue();
+        // We should not expand since the keyguard is not secure
+        verify(mScrimController, never()).expand(any());
+        // Since we are swiping down, we should not dismiss the dream.
+        verify(mCentralSurfaces, never()).awakenDreams();
     }
 
     private void verifyScroll(float percent, Direction direction,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
new file mode 100644
index 0000000..74c1970
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.touch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class CommunalTouchHandlerTest extends SysuiTestCase {
+    @Mock
+    CentralSurfaces mCentralSurfaces;
+    @Mock
+    NotificationShadeWindowController mNotificationShadeWindowController;
+    @Mock
+    DreamTouchHandler.TouchSession mTouchSession;
+    CommunalTouchHandler mTouchHandler;
+
+    private static final int INITIATION_WIDTH = 20;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mTouchHandler = new CommunalTouchHandler(
+                Optional.of(mCentralSurfaces),
+                mNotificationShadeWindowController,
+                INITIATION_WIDTH);
+    }
+
+    @Test
+    public void testSessionStartForcesShadeOpen() {
+        mTouchHandler.onSessionStart(mTouchSession);
+        verify(mNotificationShadeWindowController).setForcePluginOpen(true, mTouchHandler);
+    }
+
+    @Test
+    public void testEventPropagation() {
+        final MotionEvent motionEvent = Mockito.mock(MotionEvent.class);
+
+        final ArgumentCaptor<InputChannelCompat.InputEventListener>
+                inputEventListenerArgumentCaptor =
+                ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+
+        mTouchHandler.onSessionStart(mTouchSession);
+        verify(mTouchSession).registerInputListener(inputEventListenerArgumentCaptor.capture());
+        inputEventListenerArgumentCaptor.getValue().onInputEvent(motionEvent);
+        verify(mCentralSurfaces).handleDreamTouch(motionEvent);
+    }
+
+    @Test
+    public void testTouchPilferingOnScroll() {
+        final MotionEvent motionEvent1 = Mockito.mock(MotionEvent.class);
+        final MotionEvent motionEvent2 = Mockito.mock(MotionEvent.class);
+
+        final ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerArgumentCaptor =
+                ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+
+        mTouchHandler.onSessionStart(mTouchSession);
+        verify(mTouchSession).registerGestureListener(gestureListenerArgumentCaptor.capture());
+
+        assertThat(gestureListenerArgumentCaptor.getValue()
+                .onScroll(motionEvent1, motionEvent2, 1, 1))
+                .isTrue();
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt
new file mode 100644
index 0000000..ea766f8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.slider
+
+import android.widget.SeekBar
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.fakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class SeekableSliderHapticPluginTest : SysuiTestCase() {
+
+    private val kosmos = Kosmos()
+
+    @Rule @JvmField val mMockitoRule: MockitoRule = MockitoJUnit.rule()
+    @Mock private lateinit var vibratorHelper: VibratorHelper
+    private val seekBar = SeekBar(mContext)
+    private lateinit var plugin: SeekableSliderHapticPlugin
+
+    @Before
+    fun setup() {
+        whenever(vibratorHelper.getPrimitiveDurations(anyInt())).thenReturn(intArrayOf(0))
+    }
+
+    @Test
+    fun start_beginsTrackingSlider() = runOnStartedPlugin { assertThat(plugin.isTracking).isTrue() }
+
+    @Test
+    fun stop_stopsTrackingSlider() = runOnStartedPlugin {
+        // WHEN called to stop
+        plugin.stop()
+
+        // THEN stops tracking
+        assertThat(plugin.isTracking).isFalse()
+    }
+
+    @Test
+    fun start_afterStop_startsTheTrackingAgain() = runOnStartedPlugin {
+        // WHEN the plugin is restarted
+        plugin.stop()
+        plugin.start()
+
+        // THEN the tracking begins again
+        assertThat(plugin.isTracking).isTrue()
+    }
+
+    @Test
+    fun onKeyDown_startsWaiting() = runOnStartedPlugin {
+        // WHEN a keyDown event is recorded
+        plugin.onKeyDown()
+
+        // THEN the timer starts waiting
+        assertThat(plugin.isKeyUpTimerWaiting).isTrue()
+    }
+
+    @Test
+    fun keyUpWaitComplete_triggersOnArrowUp() = runOnStartedPlugin {
+        // GIVEN an onKeyDown that starts the wait and a program progress change that advances the
+        // slider state to ARROW_HANDLE_MOVED_ONCE
+        plugin.onKeyDown()
+        plugin.onProgressChanged(seekBar, 50, false)
+        testScheduler.runCurrent()
+        assertThat(plugin.trackerState).isEqualTo(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+        // WHEN the key-up wait completes after the timeout plus a small buffer
+        advanceTimeBy(KEY_UP_TIMEOUT + 10L)
+
+        // THEN the onArrowUp event is delivered causing the slider tracker to move to IDLE
+        assertThat(plugin.trackerState).isEqualTo(SliderState.IDLE)
+        assertThat(plugin.isKeyUpTimerWaiting).isFalse()
+    }
+
+    @Test
+    fun onKeyDown_whileWaiting_restartsWait() = runOnStartedPlugin {
+        // GIVEN an onKeyDown that starts the wait and a program progress change that advances the
+        // slider state to ARROW_HANDLE_MOVED_ONCE
+        plugin.onKeyDown()
+        plugin.onProgressChanged(seekBar, 50, false)
+        testScheduler.runCurrent()
+        assertThat(plugin.trackerState).isEqualTo(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+        // WHEN half the timeout period has elapsed and a new keyDown event occurs
+        advanceTimeBy(KEY_UP_TIMEOUT / 2)
+        plugin.onKeyDown()
+
+        // AFTER advancing by a period of time that should have complete the original wait
+        advanceTimeBy(KEY_UP_TIMEOUT / 2 + 10L)
+
+        // THEN the timer is still waiting and the slider tracker remains on ARROW_HANDLE_MOVED_ONCE
+        assertThat(plugin.isKeyUpTimerWaiting).isTrue()
+        assertThat(plugin.trackerState).isEqualTo(SliderState.ARROW_HANDLE_MOVED_ONCE)
+    }
+
+    private fun runOnStartedPlugin(test: suspend TestScope.() -> Unit) =
+        with(kosmos) {
+            testScope.runTest {
+                createPlugin(this, UnconfinedTestDispatcher(testScheduler))
+                // GIVEN that the plugin is started
+                plugin.start()
+
+                // THEN run the test
+                test()
+            }
+        }
+
+    private fun createPlugin(scope: CoroutineScope, dispatcher: CoroutineDispatcher) {
+        plugin =
+            SeekableSliderHapticPlugin(
+                vibratorHelper,
+                kosmos.fakeSystemClock,
+                dispatcher,
+                scope,
+            )
+    }
+
+    companion object {
+        private const val KEY_UP_TIMEOUT = 100L
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index c4ebbdc..c01c79d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
@@ -52,6 +53,8 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.verify
@@ -68,6 +71,8 @@
     @Mock private lateinit var authController: AuthController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
+    @Mock private lateinit var userTracker: UserTracker
+    @Captor private lateinit var updateCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
     private val mainDispatcher = StandardTestDispatcher()
     private val testDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testDispatcher)
@@ -93,6 +98,7 @@
                 testScope.backgroundScope,
                 systemClock,
                 facePropertyRepository,
+                userTracker,
             )
     }
 
@@ -133,6 +139,18 @@
         }
 
     @Test
+    fun topClippingBounds() =
+        testScope.runTest {
+            assertThat(underTest.topClippingBounds.value).isNull()
+
+            underTest.topClippingBounds.value = 50
+            assertThat(underTest.topClippingBounds.value).isEqualTo(50)
+
+            underTest.topClippingBounds.value = 500
+            assertThat(underTest.topClippingBounds.value).isEqualTo(500)
+        }
+
+    @Test
     fun clockPosition() =
         testScope.runTest {
             assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
@@ -220,10 +238,10 @@
         }
 
     @Test
-    fun isKeyguardUnlocked() =
+    fun isKeyguardDismissible() =
         testScope.runTest {
             whenever(keyguardStateController.isUnlocked).thenReturn(false)
-            val isKeyguardUnlocked by collectLastValue(underTest.isKeyguardUnlocked)
+            val isKeyguardUnlocked by collectLastValue(underTest.isKeyguardDismissible)
 
             runCurrent()
             assertThat(isKeyguardUnlocked).isFalse()
@@ -553,4 +571,25 @@
 
             job.cancel()
         }
+
+    @Test
+    fun isEncryptedOrLockdown() =
+        TestScope(mainDispatcher).runTest {
+            whenever(userTracker.userId).thenReturn(0)
+            whenever(keyguardUpdateMonitor.isEncryptedOrLockdown(0)).thenReturn(true)
+
+            // Default value for isEncryptedOrLockdown is true
+            val isEncryptedOrLockdown by collectLastValue(underTest.isEncryptedOrLockdown)
+            assertThat(isEncryptedOrLockdown).isTrue()
+
+            verify(keyguardUpdateMonitor).registerCallback(updateCallbackCaptor.capture())
+            val updateCallback = updateCallbackCaptor.value
+
+            // Strong auth state updated
+            whenever(keyguardUpdateMonitor.isEncryptedOrLockdown(0)).thenReturn(false)
+            updateCallback.onStrongAuthStateChanged(0)
+
+            // Verify no longer encrypted or lockdown
+            assertThat(isEncryptedOrLockdown).isFalse()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 11939c1..0b320a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -26,12 +26,16 @@
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -48,27 +52,30 @@
 @RunWith(AndroidJUnit4::class)
 class KeyguardInteractorTest : SysuiTestCase() {
 
-    private val testUtils = SceneTestUtils(this)
-    private val testScope = testUtils.testScope
-    private val repository = testUtils.keyguardRepository
-    private val sceneInteractor = testUtils.sceneInteractor()
-    private val commandQueue = FakeCommandQueue()
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val repository by lazy { kosmos.fakeKeyguardRepository }
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val commandQueue by lazy {
+        FakeCommandQueue()
+    }
     private val bouncerRepository = FakeKeyguardBouncerRepository()
     private val shadeRepository = FakeShadeRepository()
     private val transitionState: MutableStateFlow<ObservableTransitionState> =
         MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Gone))
 
-    private val underTest =
+    private val underTest by lazy {
         KeyguardInteractor(
             repository = repository,
             commandQueue = commandQueue,
             powerInteractor = PowerInteractorFactory.create().powerInteractor,
-            sceneContainerFlags = testUtils.sceneContainerFlags,
+            sceneContainerFlags = kosmos.fakeSceneContainerFlags,
             bouncerRepository = bouncerRepository,
             configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
             shadeRepository = shadeRepository,
             sceneInteractorProvider = { sceneInteractor },
         )
+    }
 
     @Before
     fun setUp() {
@@ -183,6 +190,7 @@
     @Test
     fun animationDozingTransitions() =
         testScope.runTest {
+            kosmos.fakeSceneContainerFlags.enabled = true
             val isAnimate by collectLastValue(underTest.animateDozingTransitions)
 
             underTest.setAnimateDozingTransitions(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 34f703b..db414b7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dock.DockManagerFake
 import com.android.systemui.flags.FakeFeatureFlags
@@ -49,16 +50,17 @@
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.testKosmos
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
@@ -82,6 +84,7 @@
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var launchAnimator: DialogLaunchAnimator
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock private lateinit var shadeInteractor: ShadeInteractor
     @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
 
     private lateinit var underTest: KeyguardQuickAffordanceInteractor
@@ -95,8 +98,6 @@
     private lateinit var dockManager: DockManagerFake
     private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
 
-    private val kosmos = testKosmos()
-
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -183,7 +184,7 @@
         underTest =
             KeyguardQuickAffordanceInteractor(
                 keyguardInteractor = withDeps.keyguardInteractor,
-                shadeInteractor = kosmos.shadeInteractor,
+                shadeInteractor = shadeInteractor,
                 lockPatternUtils = lockPatternUtils,
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
@@ -198,6 +199,8 @@
                 backgroundDispatcher = testDispatcher,
                 appContext = context,
             )
+
+        whenever(shadeInteractor.anyExpansion).thenReturn(MutableStateFlow(0f))
     }
 
     @Test
@@ -344,6 +347,25 @@
         }
 
     @Test
+    fun quickAffordance_updateOncePerShadeExpansion() =
+        testScope.runTest {
+            val shadeExpansion = MutableStateFlow(0f)
+            whenever(shadeInteractor.anyExpansion).thenReturn(shadeExpansion)
+
+            val collectedValue by
+                collectValues(
+                    underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+                )
+
+            val initialSize = collectedValue.size
+            for (i in 0..10) {
+                shadeExpansion.value = i / 10f
+            }
+
+            assertThat(collectedValue.size).isEqualTo(initialSize + 1)
+        }
+
+    @Test
     fun quickAffordanceAlwaysVisible_notVisible_restrictedByPolicyManager() =
         testScope.runTest {
             whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 4f7d944..6828041 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -21,23 +21,24 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
 import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertEquals
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -46,18 +47,11 @@
 @kotlinx.coroutines.ExperimentalCoroutinesApi
 class KeyguardTransitionInteractorTest : SysuiTestCase() {
 
-    private lateinit var underTest: KeyguardTransitionInteractor
-    private lateinit var repository: FakeKeyguardTransitionRepository
-    private val testScope = TestScope()
+    val kosmos = testKosmos()
 
-    @Before
-    fun setUp() {
-        repository = FakeKeyguardTransitionRepository()
-        underTest = KeyguardTransitionInteractorFactory.create(
-                scope = testScope.backgroundScope,
-                repository = repository,
-        ).keyguardTransitionInteractor
-    }
+    val underTest = kosmos.keyguardTransitionInteractor
+    val repository = kosmos.fakeKeyguardTransitionRepository
+    val testScope = kosmos.testScope
 
     @Test
     fun transitionCollectorsReceivesOnlyAppropriateEvents() = runTest {
@@ -114,48 +108,50 @@
     }
 
     @Test
-    fun finishedKeyguardStateTests() = testScope.runTest {
-        val finishedSteps by collectValues(underTest.finishedKeyguardState)
-        runCurrent()
-        val steps = mutableListOf<TransitionStep>()
-
-        steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
-        steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
-        steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
-        steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
-        steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
-        steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
-        steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
-
-        steps.forEach {
-            repository.sendTransitionStep(it)
+    fun finishedKeyguardStateTests() =
+        testScope.runTest {
+            val finishedSteps by collectValues(underTest.finishedKeyguardState)
             runCurrent()
-        }
+            val steps = mutableListOf<TransitionStep>()
 
-        assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD))
-    }
+            steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
+            steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
+            steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
+            steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
+            steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
+            steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
+            steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+            steps.forEach {
+                repository.sendTransitionStep(it)
+                runCurrent()
+            }
+
+            assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD))
+        }
 
     @Test
-    fun startedKeyguardStateTests() = testScope.runTest {
-        val startedStates by collectValues(underTest.startedKeyguardState)
-        runCurrent()
-        val steps = mutableListOf<TransitionStep>()
-
-        steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
-        steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
-        steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
-        steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
-        steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
-        steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
-        steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
-
-        steps.forEach {
-            repository.sendTransitionStep(it)
+    fun startedKeyguardStateTests() =
+        testScope.runTest {
+            val startedStates by collectValues(underTest.startedKeyguardState)
             runCurrent()
-        }
+            val steps = mutableListOf<TransitionStep>()
 
-        assertThat(startedStates).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD, GONE))
-    }
+            steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
+            steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
+            steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
+            steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
+            steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
+            steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
+            steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+            steps.forEach {
+                repository.sendTransitionStep(it)
+                runCurrent()
+            }
+
+            assertThat(startedStates).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD, GONE))
+        }
 
     @Test
     fun finishedKeyguardTransitionStepTests() = runTest {
@@ -178,7 +174,7 @@
 
         // Ignore the default state.
         assertThat(finishedSteps.subList(1, finishedSteps.size))
-                .isEqualTo(listOf(steps[2], steps[5]))
+            .isEqualTo(listOf(steps[2], steps[5]))
     }
 
     @Test
@@ -233,500 +229,1067 @@
     }
 
     @Test
-    fun isInTransitionToState() = testScope.runTest {
-        val results by collectValues(underTest.isInTransitionToState(GONE))
+    fun isInTransitionToAnyState() =
+        testScope.runTest {
+            val inTransition by collectValues(underTest.isInTransitionToAnyState)
 
-        sendSteps(
+            assertEquals(
+                listOf(
+                    true, // The repo is seeded with a transition from OFF to LOCKSCREEN.
+                    false,
+                ),
+                inTransition
+            )
+
+            sendSteps(
+                TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
+            )
+
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                    true,
+                ),
+                inTransition
+            )
+
+            sendSteps(
+                TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
+            )
+
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                    true,
+                ),
+                inTransition
+            )
+
+            sendSteps(
+                TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
+            )
+
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                    true,
+                    false,
+                ),
+                inTransition
+            )
+        }
+
+    @Test
+    fun isInTransitionToAnyState_finishedStateIsStartedStateAfterCancels() =
+        testScope.runTest {
+            val inTransition by collectValues(underTest.isInTransitionToAnyState)
+
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                ),
+                inTransition
+            )
+
+            // Start FINISHED in GONE.
+            sendSteps(
+                TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
+                TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
+                TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
+            )
+
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                    true,
+                    false,
+                ),
+                inTransition
+            )
+
+            sendSteps(
+                TransitionStep(GONE, DOZING, 0f, STARTED),
+            )
+
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                    true,
+                    false,
+                    true,
+                ),
+                inTransition
+            )
+
+            sendSteps(
+                TransitionStep(GONE, DOZING, 0.5f, RUNNING),
+                TransitionStep(GONE, DOZING, 0.6f, CANCELED),
+                TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
+                TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING),
+                TransitionStep(DOZING, LOCKSCREEN, 0.6f, CANCELED),
+                TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
+            )
+
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                    true,
+                    false,
+                    // We should have been in transition throughout the entire transition, including
+                    // both cancellations, and we should still be in transition despite now
+                    // transitioning to GONE, the state we're also FINISHED in.
+                    true,
+                ),
+                inTransition
+            )
+
+            sendSteps(
+                TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
+                TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
+            )
+
+            assertEquals(
+                listOf(
+                    true,
+                    false,
+                    true,
+                    false,
+                    true,
+                    false,
+                ),
+                inTransition
+            )
+        }
+
+    @Test
+    fun isInTransitionToState() =
+        testScope.runTest {
+            val results by collectValues(underTest.isInTransitionToState(GONE))
+
+            sendSteps(
                 TransitionStep(AOD, DOZING, 0f, STARTED),
                 TransitionStep(AOD, DOZING, 0.5f, RUNNING),
                 TransitionStep(AOD, DOZING, 1f, FINISHED),
-        )
+            )
 
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                    )
+                )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-        ))
-
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, STARTED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, FINISHED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(GONE, DOZING, 0f, STARTED),
                 TransitionStep(GONE, DOZING, 0f, RUNNING),
                 TransitionStep(GONE, DOZING, 1f, FINISHED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, STARTED),
                 TransitionStep(DOZING, GONE, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-                true,
-        ))
-    }
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                        true,
+                    )
+                )
+        }
 
     @Test
-    fun isInTransitionFromState() = testScope.runTest {
-        val results by collectValues(underTest.isInTransitionFromState(DOZING))
+    fun isInTransitionFromState() =
+        testScope.runTest {
+            val results by collectValues(underTest.isInTransitionFromState(DOZING))
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(AOD, DOZING, 0f, STARTED),
                 TransitionStep(AOD, DOZING, 0.5f, RUNNING),
                 TransitionStep(AOD, DOZING, 1f, FINISHED),
-        )
+            )
 
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                    )
+                )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-        ))
-
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, STARTED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, FINISHED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(GONE, DOZING, 0f, STARTED),
                 TransitionStep(GONE, DOZING, 0f, RUNNING),
                 TransitionStep(GONE, DOZING, 1f, FINISHED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, STARTED),
                 TransitionStep(DOZING, GONE, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-                true,
-        ))
-    }
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                        true,
+                    )
+                )
+        }
 
     @Test
-    fun isInTransitionFromStateWhere() = testScope.runTest {
-        val results by collectValues(underTest.isInTransitionFromStateWhere {
-            it == DOZING
-        })
+    fun isInTransitionFromStateWhere() =
+        testScope.runTest {
+            val results by collectValues(underTest.isInTransitionFromStateWhere { it == DOZING })
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(AOD, DOZING, 0f, STARTED),
                 TransitionStep(AOD, DOZING, 0.5f, RUNNING),
                 TransitionStep(AOD, DOZING, 1f, FINISHED),
-        )
+            )
 
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                    )
+                )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-        ))
-
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, STARTED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, FINISHED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(GONE, DOZING, 0f, STARTED),
                 TransitionStep(GONE, DOZING, 0f, RUNNING),
                 TransitionStep(GONE, DOZING, 1f, FINISHED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, STARTED),
                 TransitionStep(DOZING, GONE, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-                true,
-        ))
-    }
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                        true,
+                    )
+                )
+        }
 
     @Test
-    fun isInTransitionWhere() = testScope.runTest {
-        val results by collectValues(underTest.isInTransitionWhere(
-            fromStatePredicate = { it == DOZING },
-            toStatePredicate = { it == GONE },
-        ))
+    fun isInTransitionWhere() =
+        testScope.runTest {
+            val results by
+                collectValues(
+                    underTest.isInTransitionWhere(
+                        fromStatePredicate = { it == DOZING },
+                        toStatePredicate = { it == GONE },
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(AOD, DOZING, 0f, STARTED),
                 TransitionStep(AOD, DOZING, 0.5f, RUNNING),
                 TransitionStep(AOD, DOZING, 1f, FINISHED),
-        )
+            )
 
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                    )
+                )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-        ))
-
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, STARTED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, FINISHED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(GONE, DOZING, 0f, STARTED),
                 TransitionStep(GONE, DOZING, 0f, RUNNING),
                 TransitionStep(GONE, DOZING, 1f, FINISHED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, STARTED),
                 TransitionStep(DOZING, GONE, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-                true,
-        ))
-    }
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                        true,
+                    )
+                )
+        }
 
     @Test
-    fun isFinishedInStateWhere() = testScope.runTest {
-        val results by collectValues(underTest.isFinishedInStateWhere { it == GONE } )
+    fun isInTransitionWhere_withCanceledStep() =
+        testScope.runTest {
+            val results by
+                collectValues(
+                    underTest.isInTransitionWhere(
+                        fromStatePredicate = { it == DOZING },
+                        toStatePredicate = { it == GONE },
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(AOD, DOZING, 0f, STARTED),
                 TransitionStep(AOD, DOZING, 0.5f, RUNNING),
                 TransitionStep(AOD, DOZING, 1f, FINISHED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false, // Finished in DOZING, not GONE.
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                    )
+                )
 
-        sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED))
+            sendSteps(
+                TransitionStep(DOZING, GONE, 0f, STARTED),
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING))
+            sendSteps(
+                TransitionStep(DOZING, GONE, 0f, RUNNING),
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
+            sendSteps(
+                TransitionStep(DOZING, GONE, 0f, CANCELED),
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(GONE, DOZING, 0f, STARTED),
                 TransitionStep(GONE, DOZING, 0f, RUNNING),
-        )
+                TransitionStep(GONE, DOZING, 1f, FINISHED),
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED))
-
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
-
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, STARTED),
                 TransitionStep(DOZING, GONE, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
-
-        sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
-
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-                true,
-        ))
-    }
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                        true,
+                    )
+                )
+        }
 
     @Test
-    fun isFinishedInState() = testScope.runTest {
-        val results by collectValues(underTest.isFinishedInState(GONE))
+    fun isFinishedInStateWhere() =
+        testScope.runTest {
+            val results by collectValues(underTest.isFinishedInStateWhere { it == GONE })
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(AOD, DOZING, 0f, STARTED),
                 TransitionStep(AOD, DOZING, 0.5f, RUNNING),
                 TransitionStep(AOD, DOZING, 1f, FINISHED),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false, // Finished in DOZING, not GONE.
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false, // Finished in DOZING, not GONE.
+                    )
+                )
 
-        sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED))
+            sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED))
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                    )
+                )
 
-        sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING))
+            sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING))
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                    )
+                )
 
-        sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
+            sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(GONE, DOZING, 0f, STARTED),
                 TransitionStep(GONE, DOZING, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
 
-        sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED))
+            sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED))
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(DOZING, GONE, 0f, STARTED),
                 TransitionStep(DOZING, GONE, 0f, RUNNING),
-        )
+            )
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-        ))
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
 
-        sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
+            sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
 
-        assertThat(results).isEqualTo(listOf(
-                false,
-                true,
-                false,
-                true,
-        ))
-    }
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                        true,
+                    )
+                )
+        }
 
     @Test
-    fun finishedKeyguardState_emitsAgainIfCancelledAndReversed() = testScope.runTest {
-        val finishedStates by collectValues(underTest.finishedKeyguardState)
+    fun isFinishedInState() =
+        testScope.runTest {
+            val results by collectValues(underTest.isFinishedInState(GONE))
 
-        // We default FINISHED in LOCKSCREEN.
-        assertEquals(listOf(
-                LOCKSCREEN
-        ), finishedStates)
+            sendSteps(
+                TransitionStep(AOD, DOZING, 0f, STARTED),
+                TransitionStep(AOD, DOZING, 0.5f, RUNNING),
+                TransitionStep(AOD, DOZING, 1f, FINISHED),
+            )
 
-        sendSteps(
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false, // Finished in DOZING, not GONE.
+                    )
+                )
+
+            sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED))
+
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                    )
+                )
+
+            sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING))
+
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                    )
+                )
+
+            sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
+
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
+
+            sendSteps(
+                TransitionStep(GONE, DOZING, 0f, STARTED),
+                TransitionStep(GONE, DOZING, 0f, RUNNING),
+            )
+
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                    )
+                )
+
+            sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED))
+
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
+
+            sendSteps(
+                TransitionStep(DOZING, GONE, 0f, STARTED),
+                TransitionStep(DOZING, GONE, 0f, RUNNING),
+            )
+
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                    )
+                )
+
+            sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
+
+            assertThat(results)
+                .isEqualTo(
+                    listOf(
+                        false,
+                        true,
+                        false,
+                        true,
+                    )
+                )
+        }
+
+    @Test
+    fun finishedKeyguardState_emitsAgainIfCancelledAndReversed() =
+        testScope.runTest {
+            val finishedStates by collectValues(underTest.finishedKeyguardState)
+
+            // We default FINISHED in LOCKSCREEN.
+            assertEquals(listOf(LOCKSCREEN), finishedStates)
+
+            sendSteps(
                 TransitionStep(LOCKSCREEN, AOD, 0f, STARTED),
                 TransitionStep(LOCKSCREEN, AOD, 0.5f, RUNNING),
                 TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED),
-        )
+            )
 
-        // We're FINISHED in AOD.
-        assertEquals(listOf(
-                LOCKSCREEN,
-                AOD,
-        ), finishedStates)
+            // We're FINISHED in AOD.
+            assertEquals(
+                listOf(
+                    LOCKSCREEN,
+                    AOD,
+                ),
+                finishedStates
+            )
 
-        // Transition back to LOCKSCREEN.
-        sendSteps(
+            // Transition back to LOCKSCREEN.
+            sendSteps(
                 TransitionStep(AOD, LOCKSCREEN, 0f, STARTED),
                 TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING),
                 TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED),
-        )
+            )
 
-        // We're FINISHED in LOCKSCREEN.
-        assertEquals(listOf(
-                LOCKSCREEN,
-                AOD,
-                LOCKSCREEN,
-        ), finishedStates)
+            // We're FINISHED in LOCKSCREEN.
+            assertEquals(
+                listOf(
+                    LOCKSCREEN,
+                    AOD,
+                    LOCKSCREEN,
+                ),
+                finishedStates
+            )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
                 TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
-        )
+            )
 
-        // We've STARTED a transition to GONE but not yet finished it so we're still FINISHED in
-        // LOCKSCREEN.
-        assertEquals(listOf(
-                LOCKSCREEN,
-                AOD,
-                LOCKSCREEN,
-        ), finishedStates)
+            // We've STARTED a transition to GONE but not yet finished it so we're still FINISHED in
+            // LOCKSCREEN.
+            assertEquals(
+                listOf(
+                    LOCKSCREEN,
+                    AOD,
+                    LOCKSCREEN,
+                ),
+                finishedStates
+            )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(LOCKSCREEN, GONE, 0.6f, CANCELED),
-        )
+            )
 
-        // We've CANCELED a transition to GONE, we're still FINISHED in LOCKSCREEN.
-        assertEquals(listOf(
-                LOCKSCREEN,
-                AOD,
-                LOCKSCREEN,
-        ), finishedStates)
+            // We've CANCELED a transition to GONE, we're still FINISHED in LOCKSCREEN.
+            assertEquals(
+                listOf(
+                    LOCKSCREEN,
+                    AOD,
+                    LOCKSCREEN,
+                ),
+                finishedStates
+            )
 
-        sendSteps(
+            sendSteps(
                 TransitionStep(GONE, LOCKSCREEN, 0.6f, STARTED),
                 TransitionStep(GONE, LOCKSCREEN, 0.9f, RUNNING),
                 TransitionStep(GONE, LOCKSCREEN, 1f, FINISHED),
-        )
+            )
 
-        // Expect another emission of LOCKSCREEN, as we have FINISHED a second transition to
-        // LOCKSCREEN after the cancellation.
-        assertEquals(listOf(
-                LOCKSCREEN,
-                AOD,
-                LOCKSCREEN,
-                LOCKSCREEN,
-        ), finishedStates)
-    }
+            // Expect another emission of LOCKSCREEN, as we have FINISHED a second transition to
+            // LOCKSCREEN after the cancellation.
+            assertEquals(
+                listOf(
+                    LOCKSCREEN,
+                    AOD,
+                    LOCKSCREEN,
+                    LOCKSCREEN,
+                ),
+                finishedStates
+            )
+        }
+
+    @Test
+    fun testCurrentState() =
+        testScope.runTest {
+            val currentStates by collectValues(underTest.currentKeyguardState)
+
+            // We init the repo with a transition from OFF -> LOCKSCREEN.
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(LOCKSCREEN, AOD, 0f, STARTED),
+            )
+
+            // The current state should continue to be LOCKSCREEN as we transition to AOD.
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(LOCKSCREEN, AOD, 0.5f, RUNNING),
+            )
+
+            // The current state should continue to be LOCKSCREEN as we transition to AOD.
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(LOCKSCREEN, AOD, 0.6f, CANCELED),
+            )
+
+            // Once CANCELED, we're still currently in LOCKSCREEN...
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(AOD, LOCKSCREEN, 0.6f, STARTED),
+            )
+
+            // ...until STARTING back to LOCKSCREEN, at which point the "current" state should be
+            // the
+            // one we're transitioning from, despite never FINISHING in that state.
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                    AOD,
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(AOD, LOCKSCREEN, 0.8f, RUNNING),
+                TransitionStep(AOD, LOCKSCREEN, 0.8f, FINISHED),
+            )
+
+            // FINSHING in LOCKSCREEN should update the current state to LOCKSCREEN.
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                    AOD,
+                    LOCKSCREEN,
+                ),
+                currentStates
+            )
+        }
+
+    @Test
+    fun testCurrentState_multipleCancellations_backToLastFinishedState() =
+        testScope.runTest {
+            val currentStates by collectValues(underTest.currentKeyguardState)
+
+            // We init the repo with a transition from OFF -> LOCKSCREEN.
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
+                TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
+                TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
+            )
+
+            assertEquals(
+                listOf(
+                    // Default transition from OFF -> LOCKSCREEN
+                    OFF,
+                    LOCKSCREEN,
+                    // Transitioned to GONE
+                    GONE,
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(GONE, DOZING, 0f, STARTED),
+                TransitionStep(GONE, DOZING, 0.5f, RUNNING),
+                TransitionStep(GONE, DOZING, 0.6f, CANCELED),
+            )
+
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                    GONE,
+                    // Current state should not be DOZING until the post-cancelation transition is
+                    // STARTED
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
+            )
+
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                    GONE,
+                    // DOZING -> LS STARTED, DOZING is now the current state.
+                    DOZING,
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING),
+                TransitionStep(DOZING, LOCKSCREEN, 0.6f, CANCELED),
+            )
+
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                    GONE,
+                    DOZING,
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
+            )
+
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                    GONE,
+                    DOZING,
+                    // LS -> GONE STARTED, LS is now the current state.
+                    LOCKSCREEN,
+                ),
+                currentStates
+            )
+
+            sendSteps(
+                TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
+                TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
+            )
+
+            assertEquals(
+                listOf(
+                    OFF,
+                    LOCKSCREEN,
+                    GONE,
+                    DOZING,
+                    LOCKSCREEN,
+                    // FINISHED in GONE, GONE is now the current state.
+                    GONE,
+                ),
+                currentStates
+            )
+        }
 
     private suspend fun sendSteps(vararg steps: TransitionStep) {
         steps.forEach {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index b483085..ce43d4e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -38,12 +38,12 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito
 import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
-import org.mockito.Spy
 
 @SmallTest
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -51,16 +51,19 @@
 class LightRevealScrimInteractorTest : SysuiTestCase() {
     private val fakeKeyguardTransitionRepository = FakeKeyguardTransitionRepository()
 
-    @Spy private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository()
+    private val fakeLightRevealScrimRepository by lazy {
+        Mockito.spy(FakeLightRevealScrimRepository())
+    }
 
     private val testScope = TestScope()
 
-    private val keyguardTransitionInteractor =
+    private val keyguardTransitionInteractor by lazy {
         KeyguardTransitionInteractorFactory.create(
                 scope = testScope.backgroundScope,
                 repository = fakeKeyguardTransitionRepository,
             )
             .keyguardTransitionInteractor
+    }
 
     private lateinit var underTest: LightRevealScrimInteractor
 
@@ -82,6 +85,7 @@
                 keyguardTransitionInteractor,
                 fakeLightRevealScrimRepository,
                 testScope.backgroundScope,
+                mock(),
                 mock()
             )
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
index e7037a6..199ffa6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
@@ -20,11 +20,14 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -36,6 +39,7 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -45,10 +49,18 @@
 class AlternateBouncerToAodTransitionViewModelTest : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
-    private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
-    private val biometricSettingsRepository = kosmos.biometricSettingsRepository
-    private val underTest = kosmos.alternateBouncerToAodTransitionViewModel
+    private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+    private lateinit var underTest: AlternateBouncerToAodTransitionViewModel
+
+    @Before
+    fun setUp() {
+        keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+        fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+        biometricSettingsRepository = kosmos.biometricSettingsRepository
+        underTest = kosmos.alternateBouncerToAodTransitionViewModel
+    }
 
     @Test
     fun deviceEntryParentViewAppear() =
@@ -94,7 +106,7 @@
                 testScope,
             )
 
-            assertThat(values.size).isEqualTo(5)
+            assertThat(values.size).isEqualTo(6)
             values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
index 3f7e0df..d443851 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
@@ -49,7 +49,9 @@
         }
     private val testScope = kosmos.testScope
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
-    private val underTest = kosmos.alternateBouncerToGoneTransitionViewModel
+    private val underTest by lazy {
+        kosmos.alternateBouncerToGoneTransitionViewModel
+    }
 
     @Test
     fun deviceEntryParentViewDisappear() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
new file mode 100644
index 0000000..ff41ea2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AlternateBouncerToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() {
+    private val kosmos =
+        testKosmos().apply {
+            fakeFeatureFlagsClassic.apply {
+                set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
+                set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+            }
+        }
+    private val testScope = kosmos.testScope
+    private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+    private val underTest by lazy { kosmos.alternateBouncerToPrimaryBouncerTransitionViewModel }
+
+    @Test
+    fun deviceEntryParentViewDisappear() =
+        testScope.runTest {
+            val values by collectValues(underTest.deviceEntryParentViewAlpha)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                listOf(
+                    step(0f, TransitionState.STARTED),
+                    step(0f),
+                    step(0.1f),
+                    step(0.2f),
+                    step(0.3f),
+                    step(1f),
+                ),
+                testScope,
+            )
+
+            values.forEach { assertThat(it).isEqualTo(0f) }
+        }
+
+    private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
+        return TransitionStep(
+            from = KeyguardState.ALTERNATE_BOUNCER,
+            to = KeyguardState.PRIMARY_BOUNCER,
+            value = value,
+            transitionState = state,
+            ownerName = "AlternateBouncerToPrimaryBouncerTransitionViewModelTest"
+        )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index e141c2b..89e29cf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -20,10 +20,12 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
@@ -41,6 +43,7 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -50,9 +53,16 @@
 class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
-    private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
-    private val underTest = kosmos.dreamingToLockscreenTransitionViewModel
+    private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+    private lateinit var underTest: DreamingToLockscreenTransitionViewModel
+
+    @Before
+    fun setUp() {
+        keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+        fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+        underTest = kosmos.dreamingToLockscreenTransitionViewModel
+    }
 
     @Test
     fun shortcutsAlpha_bothShortcutsReceiveLastValue() =
@@ -95,7 +105,7 @@
                 testScope,
             )
 
-            assertThat(values.size).isEqualTo(6)
+            assertThat(values.size).isEqualTo(7)
             values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
         }
 
@@ -117,7 +127,7 @@
                 testScope,
             )
 
-            assertThat(values.size).isEqualTo(3)
+            assertThat(values.size).isEqualTo(4)
             values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
         }
 
@@ -232,7 +242,7 @@
                 testScope,
             )
 
-            assertThat(values.size).isEqualTo(4)
+            assertThat(values.size).isEqualTo(5)
             values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
index 897ce6d..36b26a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -29,6 +30,7 @@
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -37,8 +39,14 @@
 class GoneToDreamingTransitionViewModelTest : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val repository = kosmos.fakeKeyguardTransitionRepository
-    private val underTest = kosmos.goneToDreamingTransitionViewModel
+    private lateinit var repository: FakeKeyguardTransitionRepository
+    private lateinit var underTest: GoneToDreamingTransitionViewModel
+
+    @Before
+    fun setUp() {
+        repository = kosmos.fakeKeyguardTransitionRepository
+        underTest = kosmos.goneToDreamingTransitionViewModel
+    }
 
     @Test
     fun runTest() =
@@ -59,9 +67,9 @@
                 testScope,
             )
 
-            // Only three values should be present, since the dream overlay runs for a small
+            // Only five values should be present, since the dream overlay runs for a small
             // fraction of the overall animation time
-            assertThat(values.size).isEqualTo(4)
+            assertThat(values.size).isEqualTo(5)
             values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
         }
 
@@ -84,7 +92,7 @@
                 testScope,
             )
 
-            assertThat(values.size).isEqualTo(4)
+            assertThat(values.size).isEqualTo(5)
             values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 5b88ebe6..6cc680b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -25,9 +25,14 @@
 import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.communalRepository
+import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -42,6 +47,7 @@
 import com.android.systemui.util.ui.value
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -54,11 +60,14 @@
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val keyguardInteractor = kosmos.keyguardInteractor
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+    private val communalRepository = kosmos.communalRepository
     private val screenOffAnimationController = kosmos.screenOffAnimationController
     private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
     private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor
     private val dozeParameters = kosmos.dozeParameters
-    private val underTest = kosmos.keyguardRootViewModel
+    private val underTest by lazy { kosmos.keyguardRootViewModel }
 
     @Before
     fun setUp() {
@@ -203,4 +212,69 @@
 
             assertThat(isVisible?.isAnimating).isEqualTo(false)
         }
+
+    @Test
+    fun topClippingBounds() =
+        testScope.runTest {
+            val topClippingBounds by collectLastValue(underTest.topClippingBounds)
+            assertThat(topClippingBounds).isNull()
+
+            keyguardRepository.topClippingBounds.value = 50
+            assertThat(topClippingBounds).isEqualTo(50)
+
+            keyguardRepository.topClippingBounds.value = 1000
+            assertThat(topClippingBounds).isEqualTo(1000)
+        }
+
+    @Test
+    fun alpha_idleOnHub_isZero() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.alpha)
+
+            // Hub transition state is idle with hub open.
+            communalRepository.setTransitionState(
+                flowOf(ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal))
+            )
+            runCurrent()
+
+            // Set keyguard alpha to 1.0f.
+            keyguardInteractor.setAlpha(1.0f)
+
+            // Alpha property remains 0 regardless.
+            assertThat(alpha).isEqualTo(0f)
+        }
+
+    @Test
+    fun alpha_transitionToHub_isZero() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.alpha)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GLANCEABLE_HUB,
+                testScope,
+            )
+
+            assertThat(alpha).isEqualTo(0f)
+        }
+
+    @Test
+    fun alpha_transitionFromHubToLockscreen_isOne() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.alpha)
+
+            // Transition to the glanceable hub and back.
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GLANCEABLE_HUB,
+                testScope,
+            )
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GLANCEABLE_HUB,
+                to = KeyguardState.LOCKSCREEN,
+                testScope,
+            )
+
+            assertThat(alpha).isEqualTo(1.0f)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
new file mode 100644
index 0000000..d4dd2ac
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardClockSwitch
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.authController
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LockscreenContentViewModelTest : SysuiTestCase() {
+
+    private val kosmos: Kosmos = testKosmos()
+
+    lateinit var underTest: LockscreenContentViewModel
+
+    @Before
+    fun setup() {
+        with(kosmos) {
+            fakeFeatureFlagsClassic.set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, true)
+            underTest = lockscreenContentViewModel
+        }
+    }
+
+    @Test
+    fun isUdfpsVisible_withUdfps_true() =
+        with(kosmos) {
+            testScope.runTest {
+                whenever(kosmos.authController.isUdfpsSupported).thenReturn(true)
+                assertThat(underTest.isUdfpsVisible).isTrue()
+            }
+        }
+
+    @Test
+    fun isUdfpsVisible_withoutUdfps_false() =
+        with(kosmos) {
+            testScope.runTest {
+                whenever(kosmos.authController.isUdfpsSupported).thenReturn(false)
+                assertThat(underTest.isUdfpsVisible).isFalse()
+            }
+        }
+
+    @Test
+    fun isLargeClockVisible_withLargeClock_true() =
+        with(kosmos) {
+            testScope.runTest {
+                kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
+                assertThat(underTest.isLargeClockVisible).isTrue()
+            }
+        }
+
+    @Test
+    fun isLargeClockVisible_withSmallClock_false() =
+        with(kosmos) {
+            testScope.runTest {
+                kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL)
+                assertThat(underTest.isLargeClockVisible).isFalse()
+            }
+        }
+
+    @Test
+    fun areNotificationsVisible_withSmallClock_true() =
+        with(kosmos) {
+            testScope.runTest {
+                kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL)
+                assertThat(underTest.areNotificationsVisible).isTrue()
+            }
+        }
+
+    @Test
+    fun areNotificationsVisible_withLargeClock_false() =
+        with(kosmos) {
+            testScope.runTest {
+                kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
+                assertThat(underTest.areNotificationsVisible).isFalse()
+            }
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 74d309c..4595fbf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -21,11 +21,19 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -37,19 +45,23 @@
 @RunWith(AndroidJUnit4::class)
 class LockscreenSceneViewModelTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val sceneInteractor = utils.sceneInteractor()
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
 
-    private val underTest = createLockscreenSceneViewModel()
+    private val underTest by lazy {
+        createLockscreenSceneViewModel()
+    }
 
     @Test
     fun upTransitionSceneKey_canSwipeToUnlock_gone() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
-            utils.deviceEntryRepository.setLockscreenEnabled(true)
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
+            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
             sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
@@ -59,8 +71,10 @@
     fun upTransitionSceneKey_cannotSwipeToUnlock_bouncer() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.deviceEntryRepository.setUnlocked(false)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeDeviceEntryRepository.setUnlocked(false)
             sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
@@ -69,7 +83,7 @@
     @Test
     fun leftTransitionSceneKey_communalIsEnabled_communal() =
         testScope.runTest {
-            utils.communalRepository.setIsCommunalEnabled(true)
+            kosmos.fakeCommunalRepository.setIsCommunalEnabled(true)
             val underTest = createLockscreenSceneViewModel()
 
             assertThat(underTest.leftDestinationSceneKey).isEqualTo(SceneKey.Communal)
@@ -78,7 +92,7 @@
     @Test
     fun leftTransitionSceneKey_communalIsDisabled_null() =
         testScope.runTest {
-            utils.communalRepository.setIsCommunalEnabled(false)
+            kosmos.fakeCommunalRepository.setIsCommunalEnabled(false)
             val underTest = createLockscreenSceneViewModel()
 
             assertThat(underTest.leftDestinationSceneKey).isNull()
@@ -87,17 +101,13 @@
     private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
         return LockscreenSceneViewModel(
             applicationScope = testScope.backgroundScope,
-            deviceEntryInteractor =
-                utils.deviceEntryInteractor(
-                    authenticationInteractor = utils.authenticationInteractor(),
-                    sceneInteractor = utils.sceneInteractor(),
-                ),
-            communalInteractor = utils.communalInteractor(),
+            deviceEntryInteractor = kosmos.deviceEntryInteractor,
+            communalInteractor = kosmos.communalInteractor,
             longPress =
                 KeyguardLongPressViewModel(
                     interactor = mock(),
                 ),
-            notifications = utils.notificationsPlaceholderViewModel(),
+            notifications = kosmos.notificationsPlaceholderViewModel,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index 4843f8b..8f04ec38 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -27,18 +27,22 @@
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.testKosmos
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -51,10 +55,18 @@
             fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         }
     private val testScope = kosmos.testScope
-    private val repository = kosmos.fakeKeyguardTransitionRepository
-    private val shadeRepository = kosmos.shadeRepository
-    private val keyguardRepository = kosmos.fakeKeyguardRepository
-    private val underTest = kosmos.lockscreenToDreamingTransitionViewModel
+    private lateinit var repository: FakeKeyguardTransitionRepository
+    private lateinit var shadeRepository: ShadeRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var underTest: LockscreenToDreamingTransitionViewModel
+
+    @Before
+    fun setUp() {
+        repository = kosmos.fakeKeyguardTransitionRepository
+        shadeRepository = kosmos.shadeRepository
+        keyguardRepository = kosmos.fakeKeyguardRepository
+        underTest = kosmos.lockscreenToDreamingTransitionViewModel
+    }
 
     @Test
     fun lockscreenFadeOut() =
@@ -73,9 +85,9 @@
                 testScope = testScope,
             )
 
-            // Only three values should be present, since the dream overlay runs for a small
+            // Only five values should be present, since the dream overlay runs for a small
             // fraction of the overall animation time
-            assertThat(values.size).isEqualTo(4)
+            assertThat(values.size).isEqualTo(5)
             values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
         }
 
@@ -98,10 +110,10 @@
                 testScope = testScope,
             )
 
-            assertThat(values.size).isEqualTo(5)
+            assertThat(values.size).isEqualTo(6)
             values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
             // Validate finished value
-            assertThat(values[4]).isEqualTo(0f)
+            assertThat(values[5]).isEqualTo(0f)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index a1b8aab..b120f87 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -22,12 +22,15 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -35,12 +38,14 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.testKosmos
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -52,11 +57,20 @@
             fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         }
     private val testScope = kosmos.testScope
-    private val repository = kosmos.fakeKeyguardTransitionRepository
-    private val shadeRepository = kosmos.shadeRepository
-    private val keyguardRepository = kosmos.fakeKeyguardRepository
-    private val configurationRepository = kosmos.fakeConfigurationRepository
-    private val underTest = kosmos.lockscreenToOccludedTransitionViewModel
+    private lateinit var repository: FakeKeyguardTransitionRepository
+    private lateinit var shadeRepository: ShadeRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var configurationRepository: FakeConfigurationRepository
+    private lateinit var underTest: LockscreenToOccludedTransitionViewModel
+
+    @Before
+    fun setUp() {
+        repository = kosmos.fakeKeyguardTransitionRepository
+        shadeRepository = kosmos.shadeRepository
+        keyguardRepository = kosmos.fakeKeyguardRepository
+        configurationRepository = kosmos.fakeConfigurationRepository
+        underTest = kosmos.lockscreenToOccludedTransitionViewModel
+    }
 
     @Test
     fun lockscreenFadeOut() =
@@ -74,9 +88,9 @@
                     ),
                 testScope = testScope,
             )
-            // Only 3 values should be present, since the dream overlay runs for a small fraction
+            // Only 5 values should be present, since the dream overlay runs for a small fraction
             // of the overall animation time
-            assertThat(values.size).isEqualTo(4)
+            assertThat(values.size).isEqualTo(5)
             values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
index 2111ad5..dddf648 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
@@ -33,6 +33,7 @@
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -48,12 +49,15 @@
     val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
     val configurationRepository = kosmos.fakeConfigurationRepository
-    val underTest = kosmos.occludedToLockscreenTransitionViewModel
+    val underTest by lazy {
+        kosmos.occludedToLockscreenTransitionViewModel
+    }
 
     @Test
     fun lockscreenFadeIn() =
         testScope.runTest {
             val values by collectValues(underTest.lockscreenAlpha)
+            runCurrent()
 
             keyguardTransitionRepository.sendTransitionSteps(
                 listOf(
@@ -83,6 +87,7 @@
                 100
             )
             val values by collectValues(underTest.lockscreenTranslationY)
+            runCurrent()
 
             keyguardTransitionRepository.sendTransitionSteps(
                 listOf(
@@ -95,7 +100,7 @@
                 testScope,
             )
 
-            assertThat(values.size).isEqualTo(4)
+            assertThat(values.size).isEqualTo(5)
             values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
         }
 
@@ -107,6 +112,7 @@
                 100
             )
             val values by collectValues(underTest.lockscreenTranslationY)
+            runCurrent()
 
             keyguardTransitionRepository.sendTransitionStep(step(0.5f, TransitionState.CANCELED))
 
@@ -117,6 +123,7 @@
     fun deviceEntryParentViewFadeIn() =
         testScope.runTest {
             val values by collectValues(underTest.deviceEntryParentViewAlpha)
+            runCurrent()
 
             keyguardTransitionRepository.sendTransitionSteps(
                 listOf(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 90b8362..30ac344 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.util.mockito.whenever
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -53,7 +54,9 @@
     val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     val primaryBouncerInteractor = kosmos.primaryBouncerInteractor
     val sysuiStatusBarStateController = kosmos.sysuiStatusBarStateController
-    val underTest = kosmos.primaryBouncerToGoneTransitionViewModel
+    val underTest by lazy {
+        kosmos.primaryBouncerToGoneTransitionViewModel
+    }
 
     @Before
     fun setUp() {
@@ -65,6 +68,7 @@
     fun bouncerAlpha() =
         testScope.runTest {
             val values by collectValues(underTest.bouncerAlpha)
+            runCurrent()
 
             keyguardTransitionRepository.sendTransitionSteps(
                 listOf(
@@ -83,6 +87,7 @@
     fun bouncerAlpha_runDimissFromKeyguard() =
         testScope.runTest {
             val values by collectValues(underTest.bouncerAlpha)
+            runCurrent()
 
             whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
 
@@ -95,7 +100,7 @@
                 testScope,
             )
 
-            assertThat(values.size).isEqualTo(1)
+            assertThat(values.size).isEqualTo(2)
             values.forEach { assertThat(it).isEqualTo(0f) }
         }
 
@@ -103,11 +108,12 @@
     fun lockscreenAlpha() =
         testScope.runTest {
             val values by collectValues(underTest.lockscreenAlpha)
+            runCurrent()
 
             keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
             keyguardTransitionRepository.sendTransitionStep(step(1f))
 
-            assertThat(values.size).isEqualTo(1)
+            assertThat(values.size).isEqualTo(2)
             values.forEach { assertThat(it).isEqualTo(0f) }
         }
 
@@ -115,13 +121,14 @@
     fun lockscreenAlpha_runDimissFromKeyguard() =
         testScope.runTest {
             val values by collectValues(underTest.lockscreenAlpha)
+            runCurrent()
 
             sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(true)
 
             keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
             keyguardTransitionRepository.sendTransitionStep(step(1f))
 
-            assertThat(values.size).isEqualTo(1)
+            assertThat(values.size).isEqualTo(2)
             values.forEach { assertThat(it).isEqualTo(1f) }
         }
 
@@ -129,13 +136,14 @@
     fun lockscreenAlpha_leaveShadeOpen() =
         testScope.runTest {
             val values by collectValues(underTest.lockscreenAlpha)
+            runCurrent()
 
             sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(true)
 
             keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
             keyguardTransitionRepository.sendTransitionStep(step(1f))
 
-            assertThat(values.size).isEqualTo(1)
+            assertThat(values.size).isEqualTo(2)
             values.forEach { assertThat(it).isEqualTo(1f) }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt
new file mode 100644
index 0000000..2fe4ef78
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.content.applicationContext
+import android.hardware.biometrics.BiometricFingerprintConstants
+import android.os.PowerManager
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.sideFpsSensorInteractor
+import com.android.systemui.biometrics.fakeFingerprintInteractiveToAuthProvider
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.dozeServiceHost
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidJUnit4::class)
+class SideFpsProgressBarViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private lateinit var underTest: SideFpsProgressBarViewModel
+    private val testScope = kosmos.testScope
+    private lateinit var mTestableLooper: TestableLooper
+
+    @Before
+    fun setup() {
+        mTestableLooper = TestableLooper.get(this)
+        allowTestableLooperAsMainThread()
+    }
+
+    private suspend fun setupRestToUnlockEnabled() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_REST_TO_UNLOCK)
+        overrideResource(R.bool.config_restToUnlockSupported, true)
+        kosmos.fakeFingerprintPropertyRepository.setProperties(
+            1,
+            SensorStrength.STRONG,
+            FingerprintSensorType.POWER_BUTTON,
+            mutableMapOf(Pair("sensor", mock()))
+        )
+        kosmos.fakeFingerprintInteractiveToAuthProvider.enabledForCurrentUser.value = true
+        kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
+            TransitionStep(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                value = 0.0f,
+                transitionState = TransitionState.STARTED
+            )
+        )
+        kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
+            TransitionStep(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.AOD,
+                value = 1.0f,
+                transitionState = TransitionState.FINISHED
+            )
+        )
+        kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+    }
+
+    @Test
+    fun whenConfigDisabled_featureIsDisabled() =
+        testScope.runTest {
+            overrideResource(R.bool.config_restToUnlockSupported, false)
+            underTest = createViewModel()
+            val enabled by collectLastValue(underTest.isProlongedTouchRequiredForAuthentication)
+
+            assertThat(enabled).isFalse()
+        }
+
+    @Test
+    fun whenConfigEnabledSensorIsPowerButtonAndSettingsToggleIsEnabled_featureIsEnabled() =
+        testScope.runTest {
+            overrideResource(R.bool.config_restToUnlockSupported, true)
+            underTest = createViewModel()
+            val enabled by collectLastValue(underTest.isProlongedTouchRequiredForAuthentication)
+
+            assertThat(enabled).isFalse()
+            kosmos.fakeFingerprintPropertyRepository.setProperties(
+                1,
+                SensorStrength.STRONG,
+                FingerprintSensorType.POWER_BUTTON,
+                mutableMapOf(Pair("sensor", mock()))
+            )
+            assertThat(enabled).isFalse()
+
+            kosmos.fakeFingerprintInteractiveToAuthProvider.enabledForCurrentUser.value = true
+            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+
+            runCurrent()
+            assertThat(enabled).isTrue()
+        }
+
+    @Test
+    fun whenFingerprintAcquiredStartsWhenNotDozing_wakesUpDevice() =
+        testScope.runTest {
+            setupRestToUnlockEnabled()
+            underTest = createViewModel()
+
+            kosmos.fakeKeyguardRepository.setIsDozing(false)
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+                AcquiredFingerprintAuthenticationStatus(
+                    BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+                )
+            )
+
+            runCurrent()
+
+            assertThat(kosmos.fakePowerRepository.lastWakeReason)
+                .isEqualTo(PowerManager.WAKE_REASON_BIOMETRIC)
+        }
+
+    @Test
+    fun whenFingerprintAcquiredStartsWhenDozing_pulsesAod() =
+        testScope.runTest {
+            setupRestToUnlockEnabled()
+            underTest = createViewModel()
+
+            kosmos.fakeKeyguardRepository.setIsDozing(true)
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+                AcquiredFingerprintAuthenticationStatus(
+                    BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+                )
+            )
+
+            runCurrent()
+
+            verify(kosmos.dozeServiceHost).fireSideFpsAcquisitionStarted()
+        }
+
+    private fun createViewModel() =
+        SideFpsProgressBarViewModel(
+            kosmos.applicationContext,
+            kosmos.deviceEntryFingerprintAuthInteractor,
+            kosmos.sideFpsSensorInteractor,
+            kosmos.dozeServiceHost,
+            kosmos.keyguardInteractor,
+            kosmos.displayStateInteractor,
+            kosmos.testDispatcher,
+            kosmos.applicationCoroutineScope,
+            kosmos.powerInteractor,
+        )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt
index d277fca..1545e74 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.pipeline.data.repository
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -38,6 +39,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class AutoAddSettingsRepositoryTest : SysuiTestCase() {
     private val secureSettings = FakeSettings()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt
index 3db676d..ee7a97a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt
@@ -18,6 +18,7 @@
 
 import android.content.ComponentName
 import android.content.SharedPreferences
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -29,6 +30,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class CustomTileAddedSharedPreferencesRepositoryTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
index 070e07a..d9f24b3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
@@ -17,11 +17,9 @@
 package com.android.systemui.qs.pipeline.data.repository
 
 import android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
-import android.content.BroadcastReceiver
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
-import android.content.IntentFilter
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.content.pm.PackageManager.ResolveInfoFlags
@@ -33,44 +31,38 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.data.repository.fakePackageChangeRepository
+import com.android.systemui.common.data.repository.packageChangeRepository
+import com.android.systemui.common.data.shared.model.PackageChangeModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argThat
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
-@OptIn(ExperimentalCoroutinesApi::class)
 class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
-    private val testDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
     @Mock private lateinit var context: Context
     @Mock private lateinit var packageManager: PackageManager
-    @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
 
     private lateinit var underTest: InstalledTilesComponentRepositoryImpl
 
@@ -92,63 +84,12 @@
         underTest =
             InstalledTilesComponentRepositoryImpl(
                 context,
-                testDispatcher,
+                testScope.backgroundScope,
+                kosmos.packageChangeRepository
             )
     }
 
     @Test
-    fun registersAndUnregistersBroadcastReceiver() =
-        testScope.runTest {
-            val user = 10
-            val job = launch { underTest.getInstalledTilesComponents(user).collect {} }
-            runCurrent()
-
-            verify(context)
-                .registerReceiverAsUser(
-                    capture(receiverCaptor),
-                    eq(UserHandle.of(user)),
-                    any(),
-                    nullable(),
-                    nullable(),
-                )
-
-            verify(context, never()).unregisterReceiver(receiverCaptor.value)
-
-            job.cancel()
-            runCurrent()
-            verify(context).unregisterReceiver(receiverCaptor.value)
-        }
-
-    @Test
-    fun intentFilterForCorrectActionsAndScheme() =
-        testScope.runTest {
-            val filterCaptor = argumentCaptor<IntentFilter>()
-
-            backgroundScope.launch { underTest.getInstalledTilesComponents(0).collect {} }
-            runCurrent()
-
-            verify(context)
-                .registerReceiverAsUser(
-                    any(),
-                    any(),
-                    capture(filterCaptor),
-                    nullable(),
-                    nullable(),
-                )
-
-            with(filterCaptor.value) {
-                assertThat(matchAction(Intent.ACTION_PACKAGE_CHANGED)).isTrue()
-                assertThat(matchAction(Intent.ACTION_PACKAGE_ADDED)).isTrue()
-                assertThat(matchAction(Intent.ACTION_PACKAGE_REMOVED)).isTrue()
-                assertThat(matchAction(Intent.ACTION_PACKAGE_REPLACED)).isTrue()
-                assertThat(countActions()).isEqualTo(4)
-
-                assertThat(hasDataScheme("package")).isTrue()
-                assertThat(countDataSchemes()).isEqualTo(1)
-            }
-        }
-
-    @Test
     fun componentsLoadedOnStart() =
         testScope.runTest {
             val userId = 0
@@ -164,18 +105,21 @@
                 .thenReturn(listOf(resolveInfo))
 
             val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+            runCurrent()
 
             assertThat(componentNames).containsExactly(TEST_COMPONENT)
         }
 
     @Test
-    fun componentAdded_foundAfterBroadcast() =
+    fun componentAdded_foundAfterPackageChange() =
         testScope.runTest {
             val userId = 0
             val resolveInfo =
                 ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = true)
 
             val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+            runCurrent()
+
             assertThat(componentNames).isEmpty()
 
             whenever(
@@ -186,7 +130,8 @@
                     )
                 )
                 .thenReturn(listOf(resolveInfo))
-            getRegisteredReceiver().onReceive(context, Intent(Intent.ACTION_PACKAGE_ADDED))
+            kosmos.fakePackageChangeRepository.notifyChange(PackageChangeModel.Empty)
+            runCurrent()
 
             assertThat(componentNames).containsExactly(TEST_COMPONENT)
         }
@@ -207,6 +152,8 @@
                 .thenReturn(listOf(resolveInfo))
 
             val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+            runCurrent()
+
             assertThat(componentNames).isEmpty()
         }
 
@@ -226,6 +173,8 @@
                 .thenReturn(listOf(resolveInfo))
 
             val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+            runCurrent()
+
             assertThat(componentNames).isEmpty()
         }
 
@@ -271,22 +220,30 @@
                 .thenReturn(listOf(resolveInfo))
 
             val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+            runCurrent()
 
             assertThat(componentNames).containsExactly(TEST_COMPONENT)
         }
 
-    private fun getRegisteredReceiver(): BroadcastReceiver {
-        verify(context)
-            .registerReceiverAsUser(
-                capture(receiverCaptor),
-                any(),
-                any(),
-                nullable(),
-                nullable(),
-            )
+    @Test
+    fun loadComponentsForSameUserTwice_returnsSameFlow() =
+        testScope.runTest {
+            val flowForUser1 = underTest.getInstalledTilesComponents(1)
+            val flowForUser1TheSecondTime = underTest.getInstalledTilesComponents(1)
+            runCurrent()
 
-        return receiverCaptor.value
-    }
+            assertThat(flowForUser1TheSecondTime).isEqualTo(flowForUser1)
+        }
+
+    @Test
+    fun loadComponentsForDifferentUsers_returnsDifferentFlow() =
+        testScope.runTest {
+            val flowForUser1 = underTest.getInstalledTilesComponents(1)
+            val flowForUser2 = underTest.getInstalledTilesComponents(2)
+            runCurrent()
+
+            assertThat(flowForUser2).isNotEqualTo(flowForUser1)
+        }
 
     companion object {
         private val INTENT = Intent(TileService.ACTION_QS_TILE)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index f7c3b21..3418977 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.pipeline.data.repository
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -39,6 +40,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalCoroutinesApi::class)
 class TileSpecSettingsRepositoryTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt
index 9516c21..9e99fc0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.qs.pipeline.data.repository
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -9,6 +10,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class TilesSettingConverterTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt
index 36e860e..1ca3c06 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.qs.pipeline.data.repository
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -24,6 +25,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class UserAutoAddRepositoryTest : SysuiTestCase() {
     private val secureSettings = FakeSettings()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
index d4a9fab..58fc109 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.qs.pipeline.data.repository
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -23,6 +24,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@EnabledOnRavenwood
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class UserTileSpecRepositoryTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessorTest.kt
index 30d1822..57bb77f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessorTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.pipeline.data.restoreprocessors
 
 import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -31,6 +32,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalCoroutinesApi::class)
 class WorkTileRestoreProcessorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt
new file mode 100644
index 0000000..311122d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.accessibility.Flags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.accessibility.AccessibilityShortcutController
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.ColorCorrectionTile
+import com.android.systemui.qs.tiles.ColorInversionTile
+import com.android.systemui.qs.tiles.OneHandedModeTile
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class A11yShortcutAutoAddableListTest : SysuiTestCase() {
+
+    private val factory =
+        object : A11yShortcutAutoAddable.Factory {
+            override fun create(
+                spec: TileSpec,
+                componentName: ComponentName
+            ): A11yShortcutAutoAddable {
+                return A11yShortcutAutoAddable(mock(), mock(), spec, componentName)
+            }
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+    fun getA11yShortcutAutoAddables_withA11yQsShortcutFlagOff_emptyResult() {
+        val autoAddables = A11yShortcutAutoAddableList.getA11yShortcutAutoAddables(factory)
+
+        assertThat(autoAddables).isEmpty()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+    fun getA11yShortcutAutoAddables_withA11yQsShortcutFlagOn_correctAutoAddables() {
+        val expected =
+            setOf(
+                factory.create(
+                    TileSpec.create(ColorCorrectionTile.TILE_SPEC),
+                    AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME
+                ),
+                factory.create(
+                    TileSpec.create(ColorInversionTile.TILE_SPEC),
+                    AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME
+                ),
+                factory.create(
+                    TileSpec.create(OneHandedModeTile.TILE_SPEC),
+                    AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME
+                ),
+                factory.create(
+                    TileSpec.create(ReduceBrightColorsTile.TILE_SPEC),
+                    AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME
+                ),
+            )
+
+        val autoAddables = A11yShortcutAutoAddableList.getA11yShortcutAutoAddables(factory)
+
+        assertThat(autoAddables).isNotEmpty()
+        assertThat(autoAddables).containsExactlyElementsIn(expected)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableTest.kt
new file mode 100644
index 0000000..3b33a43
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableTest.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.FakeAccessibilityQsShortcutsRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class A11yShortcutAutoAddableTest : SysuiTestCase() {
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private val a11yQsShortcutsRepository = FakeAccessibilityQsShortcutsRepository()
+    private val underTest =
+        A11yShortcutAutoAddable(a11yQsShortcutsRepository, testDispatcher, SPEC, TARGET_COMPONENT)
+
+    @Test
+    fun settingNotSet_noSignal() =
+        testScope.runTest {
+            val signal by collectLastValue(underTest.autoAddSignal(USER_ID))
+
+            assertThat(signal).isNull() // null means no emitted value
+        }
+
+    @Test
+    fun settingSetWithTarget_addSignal() =
+        testScope.runTest {
+            val signal by collectLastValue(underTest.autoAddSignal(USER_ID))
+            assertThat(signal).isNull()
+
+            a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+                USER_ID,
+                setOf(TARGET_COMPONENT_FLATTEN)
+            )
+
+            assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+        }
+
+    @Test
+    fun settingSetWithoutTarget_removeSignal() =
+        testScope.runTest {
+            val signal by collectLastValue(flow = underTest.autoAddSignal(USER_ID))
+            assertThat(signal).isNull()
+
+            a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+                USER_ID,
+                setOf(OTHER_COMPONENT_FLATTEN)
+            )
+
+            assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+        }
+
+    @Test
+    fun settingSetWithMultipleComponents_containsTarget_addSignal() =
+        testScope.runTest {
+            val signal by collectLastValue(underTest.autoAddSignal(USER_ID))
+            assertThat(signal).isNull()
+
+            a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+                USER_ID,
+                setOf(OTHER_COMPONENT_FLATTEN, TARGET_COMPONENT_FLATTEN)
+            )
+
+            assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+        }
+
+    @Test
+    fun settingSetWithMultipleComponents_doesNotContainTarget_removeSignal() =
+        testScope.runTest {
+            val signal by collectLastValue(underTest.autoAddSignal(USER_ID))
+            assertThat(signal).isNull()
+
+            a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+                USER_ID,
+                setOf(OTHER_COMPONENT_FLATTEN, OTHER_COMPONENT_FLATTEN)
+            )
+
+            assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+        }
+
+    @Test
+    fun multipleChangesWithTarget_onlyOneAddSignal() =
+        testScope.runTest {
+            val signals by collectValues(underTest.autoAddSignal(USER_ID))
+            assertThat(signals).isEmpty()
+
+            repeat(3) {
+                a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+                    USER_ID,
+                    setOf(TARGET_COMPONENT_FLATTEN)
+                )
+            }
+
+            assertThat(signals.size).isEqualTo(1)
+            assertThat(signals[0]).isEqualTo(AutoAddSignal.Add(SPEC))
+        }
+
+    @Test
+    fun multipleChangesWithoutTarget_onlyOneRemoveSignal() =
+        testScope.runTest {
+            val signals by collectValues(underTest.autoAddSignal(USER_ID))
+            assertThat(signals).isEmpty()
+
+            repeat(3) {
+                a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+                    USER_ID,
+                    setOf("$OTHER_COMPONENT_FLATTEN$it")
+                )
+            }
+
+            assertThat(signals.size).isEqualTo(1)
+            assertThat(signals[0]).isEqualTo(AutoAddSignal.Remove(SPEC))
+        }
+
+    @Test
+    fun settingSetWithTargetForUsers_onlySignalInThatUser() =
+        testScope.runTest {
+            val otherUserId = USER_ID + 1
+            val signalTargetUser by collectLastValue(underTest.autoAddSignal(USER_ID))
+            val signalOtherUser by collectLastValue(underTest.autoAddSignal(otherUserId))
+            assertThat(signalTargetUser).isNull()
+            assertThat(signalOtherUser).isNull()
+
+            a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+                USER_ID,
+                setOf(TARGET_COMPONENT_FLATTEN)
+            )
+
+            assertThat(signalTargetUser).isEqualTo(AutoAddSignal.Add(SPEC))
+            assertThat(signalOtherUser).isNull()
+        }
+
+    @Test
+    fun strategyAlways() {
+        assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create("spec")
+        private val TARGET_COMPONENT = ComponentName("FakePkgName", "FakeClassName")
+        private val TARGET_COMPONENT_FLATTEN = TARGET_COMPONENT.flattenToString()
+        private val OTHER_COMPONENT_FLATTEN =
+            ComponentName("FakePkgName", "OtherClassName").flattenToString()
+        private const val USER_ID = 0
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
index 4454a3c..f185ed5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
 import android.content.ComponentName
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.res.R
@@ -28,6 +29,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class AutoAddableSettingListTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
index d153e9d..cfb84a7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -35,6 +36,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class AutoAddableSettingTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
index ec139e4..ea8d873 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -35,6 +36,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class CallbackControllerAutoAddableTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
index 4fae532..9bb591e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -41,6 +42,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class CastAutoAddableTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
index 9e2d1f8..b925b27 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -40,6 +41,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class DataSaverAutoAddableTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
index 0116bd9..188c6a9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -43,6 +44,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class DeviceControlsAutoAddableTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
index e7ea9a6..02699dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -40,6 +41,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class HotspotAutoAddableTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
index 19ac63c..6d6fd75 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -43,6 +44,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class ReduceBrightColorsAutoAddableTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
index d645ee3..633e494 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
@@ -18,6 +18,7 @@
 
 import android.content.ComponentName
 import android.content.pm.PackageManager
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.res.R
@@ -51,6 +52,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class SafetyCenterAutoAddableTest : SysuiTestCase() {
     private val testDispatcher = StandardTestDispatcher()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
index 83ff35d..c5c76eb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -37,6 +38,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class WalletAutoAddableTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
index 54b03a9..2ea12ef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.pipeline.domain.interactor
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -46,6 +47,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalCoroutinesApi::class)
 class AutoAddInteractorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
index 0d97115..d38c19b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.qs.pipeline.domain.interactor
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -28,6 +29,7 @@
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
+@EnabledOnRavenwood
 class PanelInteractorImplTest : SysuiTestCase() {
 
     @Mock private lateinit var shadeController: ShadeController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
index b2a9783..0b3144a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.qs.pipeline.domain.interactor
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -26,6 +27,7 @@
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
+@EnabledOnRavenwood
 class RestoreReconciliationInteractorTest : SysuiTestCase() {
 
     private val tileSpecRepository = FakeTileSpecRepository()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
index 96d5774..4207a9c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
@@ -54,7 +54,9 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 class WorkProfileAutoAddedAfterRestoreTest : SysuiTestCase() {
 
-    private val kosmos = Kosmos().apply { fakeUserTracker.set(listOf(USER_0_INFO), 0) }
+    private val kosmos by lazy {
+        Kosmos().apply { fakeUserTracker.set(listOf(USER_0_INFO), 0) }
+    }
     // Getter here so it can change when there is a managed profile.
     private val workTileAvailable: Boolean
         get() = hasManagedProfile()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt
index 558e769..869ab6c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.pipeline.shared
 
 import android.content.ComponentName
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -25,6 +26,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class TileSpecTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
index bd1c310..bf48784 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
@@ -16,32 +16,47 @@
 
 package com.android.systemui.qs.tiles.base.actions
 
+import android.app.PendingIntent
+import android.content.ComponentName
 import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.content.pm.ResolveInfo
+import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.util.mockito.argThat
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatcher
 import org.mockito.Mock
 import org.mockito.Mockito.any
 import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class QSTileIntentUserInputHandlerTest : SysuiTestCase() {
-
-    @Mock private lateinit var activityStarted: ActivityStarter
+    @Mock private lateinit var packageManager: PackageManager
+    @Mock private lateinit var activityStarter: ActivityStarter
 
     lateinit var underTest: QSTileIntentUserInputHandler
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        underTest = QSTileIntentUserInputHandlerImpl(activityStarted)
+        underTest = QSTileIntentUserInputHandlerImpl(activityStarter, packageManager, user)
     }
 
     @Test
@@ -50,6 +65,103 @@
 
         underTest.handle(null, intent)
 
-        verify(activityStarted).postStartActivityDismissingKeyguard(eq(intent), eq(0), any())
+        verify(activityStarter).postStartActivityDismissingKeyguard(eq(intent), eq(0), any())
+    }
+
+    @Test
+    fun testPassesActivityPendingIntentToStarterAsPendingIntent() {
+        val pendingIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }
+
+        underTest.handle(null, pendingIntent, true)
+
+        verify(activityStarter).postStartActivityDismissingKeyguard(eq(pendingIntent), any())
+    }
+
+    @Test
+    fun testPassesActivityPendingIntentToStarterAsPendingIntentWhenNotRequestingActivityStart() {
+        val pendingIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }
+
+        underTest.handle(null, pendingIntent, false)
+
+        verify(activityStarter).postStartActivityDismissingKeyguard(eq(pendingIntent), any())
+    }
+
+    @Test
+    fun testPassNonActivityPendingIntentAndRequestStartingActivity_findsIntentAndStarts() {
+        val pendingIntent =
+            mock<PendingIntent> {
+                whenever(isActivity).thenReturn(false)
+                whenever(creatorPackage).thenReturn(ORIGINAL_PACKAGE)
+            }
+        setUpQueryResult(listOf(createActivityInfo(testResolvedComponent, exported = true)))
+
+        underTest.handle(null, pendingIntent, true)
+
+        val expectedIntent =
+            Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_LAUNCHER)
+                .setPackage(null)
+                .addFlags(
+                    Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+                )
+                .setComponent(testResolvedComponent)
+
+        verify(activityStarter)
+            .postStartActivityDismissingKeyguard(
+                argThat(IntentMatcher(expectedIntent)),
+                eq(0),
+                any()
+            )
+    }
+
+    @Test
+    fun testPassNonActivityPendingIntentAndDoNotRequestStartingActivity_doesNotStartActivity() {
+        val pendingIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(false) }
+
+        underTest.handle(null, pendingIntent, false)
+
+        verify(activityStarter, never())
+            .postStartActivityDismissingKeyguard(any(Intent::class.java), eq(0), any())
+    }
+
+    private fun createActivityInfo(
+        componentName: ComponentName,
+        exported: Boolean = false,
+    ): ActivityInfo {
+        return ActivityInfo().apply {
+            packageName = componentName.packageName
+            name = componentName.className
+            this.exported = exported
+        }
+    }
+
+    private fun setUpQueryResult(infos: List<ActivityInfo>) {
+        `when`(
+                packageManager.queryIntentActivitiesAsUser(
+                    any(Intent::class.java),
+                    any(ResolveInfoFlags::class.java),
+                    eq(user.identifier)
+                )
+            )
+            .thenReturn(infos.map { ResolveInfo().apply { activityInfo = it } })
+    }
+
+    private class IntentMatcher(intent: Intent) : ArgumentMatcher<Intent> {
+        private val expectedIntent = intent
+        override fun matches(argument: Intent?): Boolean {
+            return argument?.action.equals(expectedIntent.action) &&
+                argument?.`package`.equals(expectedIntent.`package`) &&
+                argument?.component?.equals(expectedIntent.component)!! &&
+                argument?.categories?.equals(expectedIntent.categories)!! &&
+                argument?.flags?.equals(expectedIntent.flags)!!
+        }
+    }
+
+    companion object {
+        private const val ORIGINAL_PACKAGE = "original_pkg"
+        private const val TEST_PACKAGE = "test_pkg"
+        private const val TEST_COMPONENT_CLASS_NAME = "test_component_class_name"
+        private val testResolvedComponent = ComponentName(TEST_PACKAGE, TEST_COMPONENT_CLASS_NAME)
+        private val user = UserHandle.of(0)
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt
index 9861606..fd09e3c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.base.analytics
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.InstanceId
@@ -33,6 +34,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class QSTileAnalyticsTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
index 2bdc154..a1f885c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.content.Intent
 import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.RestrictedLockUtils
@@ -45,6 +46,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class DisabledByPolicyInteractorTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt
index 937744d..89b9b7f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.airplate.domain.interactor
 
 import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -36,6 +37,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class AirplaneModeTileDataInteractorTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
index 81bde81..8982d81 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.impl.airplate.domain.interactor
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import android.provider.Settings
 import android.telephony.TelephonyManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -37,6 +38,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class AirplaneModeTileUserActionInteractorTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractorTest.kt
index e44c849..be2da17 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractorTest.kt
@@ -18,42 +18,25 @@
 
 import android.app.AlarmManager.AlarmClockInfo
 import android.app.PendingIntent
-import android.content.Intent
 import android.provider.AlarmClock
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
 import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
 import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.nullable
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Mockito.verify
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class AlarmTileUserActionInteractorTest : SysuiTestCase() {
-    private lateinit var activityStarter: ActivityStarter
-    private lateinit var intentCaptor: ArgumentCaptor<Intent>
-    private lateinit var pendingIntentCaptor: ArgumentCaptor<PendingIntent>
-
-    lateinit var underTest: AlarmTileUserActionInteractor
-
-    @Before
-    fun setup() {
-        activityStarter = mock<ActivityStarter>()
-        intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
-        pendingIntentCaptor = ArgumentCaptor.forClass(PendingIntent::class.java)
-        underTest = AlarmTileUserActionInteractor(activityStarter)
-    }
+    private val inputHandler = FakeQSTileIntentUserInputHandler()
+    private val underTest = AlarmTileUserActionInteractor(inputHandler)
 
     @Test
     fun handleClickWithDefaultIntent() = runTest {
@@ -62,21 +45,21 @@
 
         underTest.handleInput(click(inputModel))
 
-        verify(activityStarter)
-            .postStartActivityDismissingKeyguard(capture(intentCaptor), eq(0), nullable())
-        assertThat(intentCaptor.value.action).isEqualTo(AlarmClock.ACTION_SHOW_ALARMS)
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            assertThat(it.intent.action).isEqualTo(AlarmClock.ACTION_SHOW_ALARMS)
+        }
     }
 
     @Test
     fun handleClickWithPendingIntent() = runTest {
-        val expectedIntent: PendingIntent = mock<PendingIntent>()
+        val expectedIntent = mock<PendingIntent>()
         val alarmInfo = AlarmClockInfo(1L, expectedIntent)
         val inputModel = AlarmTileModel.NextAlarmSet(true, alarmInfo)
 
         underTest.handleInput(click(inputModel))
 
-        verify(activityStarter)
-            .postStartActivityDismissingKeyguard(capture(pendingIntentCaptor), nullable())
-        assertThat(pendingIntentCaptor.value).isEqualTo(expectedIntent)
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOnePendingIntentInput {
+            assertThat(it.pendingIntent).isEqualTo(expectedIntent)
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
index 8ee6d20..d05e98f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
@@ -36,8 +36,9 @@
 class ColorCorrectionTileMapperTest : SysuiTestCase() {
     private val kosmos = Kosmos()
     private val colorCorrectionTileConfig = kosmos.qsColorCorrectionTileConfig
-    private val subtitleArray =
+    private val subtitleArray by lazy {
         context.resources.getStringArray(R.array.tile_states_color_correction)
+    }
     // Using lazy (versus =) to make sure we override the right context -- see b/311612168
     private val mapper by lazy {
         ColorCorrectionTileMapper(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractorTest.kt
index 8c612ac..abaf808 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractorTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor
 
 import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -35,6 +36,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class ColorCorrectionTileDataInteractorTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileUserActionInteractorTest.kt
index 3049cc0..3bc53b27 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileUserActionInteractorTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor
 
 import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -32,6 +33,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class ColorCorrectionTileUserActionInteractorTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
index a84b9fa..da60c18 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
@@ -308,21 +308,27 @@
         val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
 
         val TEST_USER_1 = UserHandle.of(1)!!
-        val TEST_TILE_1 =
+        val TEST_TILE_1 by lazy {
             Tile().apply {
                 label = "test_tile_1"
                 icon = Icon.createWithContentUri("file://test_1")
             }
+        }
         val TEST_TILE_KEY_1 = TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier)
-        val TEST_DEFAULTS_1 = CustomTileDefaults.Result(TEST_TILE_1.icon, TEST_TILE_1.label)
+        val TEST_DEFAULTS_1 by lazy {
+            CustomTileDefaults.Result(TEST_TILE_1.icon, TEST_TILE_1.label)
+        }
 
         val TEST_USER_2 = UserHandle.of(2)!!
-        val TEST_TILE_2 =
+        val TEST_TILE_2 by lazy {
             Tile().apply {
                 label = "test_tile_2"
                 icon = Icon.createWithContentUri("file://test_2")
             }
+        }
         val TEST_TILE_KEY_2 = TileServiceKey(TEST_COMPONENT, TEST_USER_2.identifier)
-        val TEST_DEFAULTS_2 = CustomTileDefaults.Result(TEST_TILE_2.icon, TEST_TILE_2.label)
+        val TEST_DEFAULTS_2 by lazy {
+            CustomTileDefaults.Result(TEST_TILE_2.icon, TEST_TILE_2.label)
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
index 20653ca..995d6ac 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
@@ -180,11 +180,14 @@
 
         val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
         val TEST_USER = UserHandle.of(1)!!
-        val TEST_TILE =
+        val TEST_TILE by lazy {
             Tile().apply {
                 label = "test_tile_1"
                 icon = Icon.createWithContentUri("file://test_1")
             }
-        val TEST_DEFAULTS = CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label)
+        }
+        val TEST_DEFAULTS by lazy {
+            CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label)
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
index 7f7490d..a9e39354 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.flashlight.domain.interactor
 
 import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
 import android.testing.LeakCheck
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -37,6 +38,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class FlashlightTileDataInteractorTest : SysuiTestCase() {
     private lateinit var controller: FakeFlashlightController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
index f3c3579..ccd7ed9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
@@ -39,7 +39,9 @@
     private val colorInversionTileConfig = kosmos.qsColorInversionTileConfig
     private val subtitleArrayId =
         SubtitleArrayMapping.getSubtitleId(colorInversionTileConfig.tileSpec.spec)
-    private val subtitleArray = context.resources.getStringArray(subtitleArrayId)
+    private val subtitleArray by lazy {
+        context.resources.getStringArray(subtitleArrayId)
+    }
     // Using lazy (versus =) to make sure we override the right context -- see b/311612168
     private val mapper by lazy {
         ColorInversionTileMapper(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionTileDataInteractorTest.kt
index 24c7bfb..75b07ee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionTileDataInteractorTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.inversion.domain.interactor
 
 import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -35,6 +36,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class ColorInversionTileDataInteractorTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractorTest.kt
index 99bae18..f574f79 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractorTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.inversion.domain.interactor
 
 import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -32,6 +33,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class ColorInversionUserActionInteractorTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileDataInteractorTest.kt
index 8fdc93b..9adf57a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileDataInteractorTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.location.interactor
 
 import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
 import android.testing.LeakCheck
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -38,6 +39,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class LocationTileDataInteractorTest : SysuiTestCase() {
     private lateinit var controller: FakeLocationController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.kt
index 0fb8ae6..8b21cb4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.impl.location.interactor
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -42,6 +43,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class LocationTileUserActionInteractorTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt
index 4b96251..f24723a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.saver.domain
 
 import android.content.SharedPreferences
+import android.platform.test.annotations.EnabledOnRavenwood
 import android.testing.LeakCheck
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -37,6 +38,7 @@
 
 /** Test [DataSaverDialogDelegate]. */
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class DataSaverDialogDelegateTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt
index 819bd03..daee22d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.saver.domain.interactor
 
 import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
 import android.testing.LeakCheck
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -36,6 +37,7 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class DataSaverTileDataInteractorTest : SysuiTestCase() {
     private val controller: FakeDataSaverController = FakeDataSaverController(LeakCheck())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileDataInteractorTest.kt
index 7497ebd..46e1609 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileDataInteractorTest.kt
@@ -53,8 +53,9 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class UiModeNightTileDataInteractorTest : SysuiTestCase() {
-    private val configurationController: ConfigurationController =
+    private val configurationController: ConfigurationController by lazy {
         ConfigurationControllerImpl(context)
+    }
     private val batteryController = FakeBatteryController(LeakCheck())
     private val locationController = FakeLocationController(LeakCheck())
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileUserActionInteractorTest.kt
index 004ec62..eea6d16 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileUserActionInteractorTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.uimodenight.domain
 
 import android.app.UiModeManager
+import android.platform.test.annotations.EnabledOnRavenwood
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -39,6 +40,7 @@
 import org.mockito.Mockito.verify
 
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class UiModeNightTileUserActionInteractorTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt
index 5eca8ca..40971a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.viewmodel
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -27,6 +28,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class QSTileConfigProviderTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
index 3a0ebdb..a8bc8d6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.viewmodel
 
 import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.android.systemui.SysuiTestCase
@@ -50,6 +51,7 @@
 import org.mockito.MockitoAnnotations
 
 @MediumTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalCoroutinesApi::class)
 class QSTileViewModelTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
index 22fb152..18cdd71 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.viewmodel
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.android.settingslib.RestrictedLockUtils
@@ -53,6 +54,7 @@
 
 /** Tests all possible [QSTileUserAction]s. If you need */
 @MediumTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalCoroutinesApi::class)
 class QSTileViewModelUserInputTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index d9b1ea1..cae20d0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -16,12 +16,16 @@
 
 package com.android.systemui.qs.ui.adapter
 
+import android.content.res.Configuration
 import android.os.Bundle
+import android.view.Surface
 import android.view.View
 import androidx.asynclayoutinflater.view.AsyncLayoutInflater
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.qs.QSImpl
 import com.android.systemui.qs.dagger.QSComponent
@@ -34,6 +38,7 @@
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import java.util.Locale
 import javax.inject.Provider
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
@@ -81,11 +86,17 @@
                     .also { components.add(it) }
             }
         }
+    private val configuration = Configuration(context.resources.configuration)
+
+    private val fakeConfigurationRepository =
+        FakeConfigurationRepository().apply { onConfigurationChange(configuration) }
+    private val configurationInteractor = ConfigurationInteractor(fakeConfigurationRepository)
 
     private val mockAsyncLayoutInflater =
         mock<AsyncLayoutInflater>() {
             whenever(inflate(anyInt(), nullable(), any())).then { invocation ->
                 val mockView = mock<View>()
+                whenever(mockView.context).thenReturn(context)
                 invocation
                     .getArgument<AsyncLayoutInflater.OnInflateFinishedListener>(2)
                     .onInflateFinished(
@@ -102,6 +113,7 @@
             qsImplProvider,
             testDispatcher,
             testScope.backgroundScope,
+            configurationInteractor,
             { mockAsyncLayoutInflater },
         )
 
@@ -297,6 +309,9 @@
     @Test
     fun reinflation_previousStateDestroyed() =
         testScope.runTest {
+            // Run all flows... In particular, initial configuration propagation that could cause
+            // QSImpl to re-inflate.
+            runCurrent()
             val qsImpl by collectLastValue(underTest.qsImpl)
 
             underTest.inflate(context)
@@ -322,4 +337,81 @@
                     bundleArgCaptor.value,
                 )
         }
+
+    @Test
+    fun changeInLocale_reinflation() =
+        testScope.runTest {
+            val qsImpl by collectLastValue(underTest.qsImpl)
+
+            underTest.inflate(context)
+            runCurrent()
+
+            val oldQsImpl = qsImpl!!
+
+            val newLocale =
+                if (configuration.locales[0] == Locale("en-US")) {
+                    Locale("es-UY")
+                } else {
+                    Locale("en-US")
+                }
+            configuration.setLocale(newLocale)
+            fakeConfigurationRepository.onConfigurationChange(configuration)
+            runCurrent()
+
+            assertThat(oldQsImpl).isNotSameInstanceAs(qsImpl!!)
+        }
+
+    @Test
+    fun changeInFontSize_reinflation() =
+        testScope.runTest {
+            val qsImpl by collectLastValue(underTest.qsImpl)
+
+            underTest.inflate(context)
+            runCurrent()
+
+            val oldQsImpl = qsImpl!!
+
+            configuration.fontScale *= 2
+            fakeConfigurationRepository.onConfigurationChange(configuration)
+            runCurrent()
+
+            assertThat(oldQsImpl).isNotSameInstanceAs(qsImpl!!)
+        }
+
+    @Test
+    fun changeInAssetPath_reinflation() =
+        testScope.runTest {
+            val qsImpl by collectLastValue(underTest.qsImpl)
+
+            underTest.inflate(context)
+            runCurrent()
+
+            val oldQsImpl = qsImpl!!
+
+            configuration.assetsSeq += 1
+            fakeConfigurationRepository.onConfigurationChange(configuration)
+            runCurrent()
+
+            assertThat(oldQsImpl).isNotSameInstanceAs(qsImpl!!)
+        }
+
+    @Test
+    fun otherChangesInConfiguration_noReinflation_configurationChangeDispatched() =
+        testScope.runTest {
+            val qsImpl by collectLastValue(underTest.qsImpl)
+
+            underTest.inflate(context)
+            runCurrent()
+
+            val oldQsImpl = qsImpl!!
+            configuration.densityDpi *= 2
+            configuration.windowConfiguration.maxBounds.scale(2f)
+            configuration.windowConfiguration.rotation = Surface.ROTATION_270
+            fakeConfigurationRepository.onConfigurationChange(configuration)
+            runCurrent()
+
+            assertThat(oldQsImpl).isSameInstanceAs(qsImpl!!)
+            verify(qsImpl!!).onConfigurationChanged(configuration)
+            verify(qsImpl!!.view).dispatchConfigurationChanged(configuration)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
index 18a7320..d1bc686 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.ui.adapter
 
+import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -25,6 +26,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
+@EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class QSSceneAdapterTest : SysuiTestCase() {
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 6403ed1..42200a3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -22,13 +22,17 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.scene.shared.model.UserAction
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
@@ -36,23 +40,34 @@
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class QuickSettingsSceneViewModelTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val sceneInteractor = utils.sceneInteractor()
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
     private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
     private val qsFlexiglassAdapter = FakeQSSceneAdapter { mock() }
+    private val footerActionsViewModel = mock<FooterActionsViewModel>()
+    private val footerActionsViewModelFactory =
+        mock<FooterActionsViewModel.Factory> {
+            whenever(create(any())).thenReturn(footerActionsViewModel)
+        }
+    private val footerActionsController = mock<FooterActionsController>()
 
     private var mobileIconsViewModel: MobileIconsViewModel =
         MobileIconsViewModel(
@@ -90,7 +105,9 @@
             QuickSettingsSceneViewModel(
                 shadeHeaderViewModel = shadeHeaderViewModel,
                 qsSceneAdapter = qsFlexiglassAdapter,
-                notifications = utils.notificationsPlaceholderViewModel(),
+                notifications = kosmos.notificationsPlaceholderViewModel,
+                footerActionsViewModelFactory,
+                footerActionsController,
             )
     }
 
@@ -122,4 +139,12 @@
                     )
                 )
         }
+
+    @Test
+    fun gettingViewModelInitializesControllerOnlyOnce() {
+        underTest.getFooterActionsViewModel(mock())
+        underTest.getFooterActionsViewModel(mock())
+
+        verify(footerActionsController, times(1)).init()
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 530d127d..504ded3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -24,26 +24,42 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.R
 import com.android.internal.util.EmergencyAffordanceManager
+import com.android.internal.util.emergencyAffordanceManager
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
+import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel
+import com.android.systemui.classifier.domain.interactor.falsingInteractor
+import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
-import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.controls.pipeline.MediaDataManager
-import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.model.SysUiState
+import com.android.systemui.model.sceneContainerPlugin
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.domain.startable.SceneContainerStartable
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -51,16 +67,21 @@
 import com.android.systemui.settings.FakeDisplayTracker
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
 import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.telecom.telecomManager
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -101,50 +122,34 @@
 @RunWith(AndroidJUnit4::class)
 class SceneFrameworkIntegrationTest : SysuiTestCase() {
 
-    @Mock private lateinit var emergencyAffordanceManager: EmergencyAffordanceManager
-    @Mock private lateinit var tableLogger: TableLogBuffer
-    @Mock private lateinit var telecomManager: TelecomManager
+    private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+    private val testScope = kosmos.testScope
+    private val sceneContainerConfig by lazy { kosmos.sceneContainerConfig }
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
+    private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
+    private val communalInteractor by lazy { kosmos.communalInteractor }
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val sceneContainerConfig = utils.fakeSceneContainerConfig()
-    private val sceneRepository =
-        utils.fakeSceneContainerRepository(
-            containerConfig = sceneContainerConfig,
-        )
-    private val sceneInteractor =
-        utils.sceneInteractor(
-            repository = sceneRepository,
-        )
-    private val authenticationInteractor = utils.authenticationInteractor()
-    private val deviceEntryInteractor =
-        utils.deviceEntryInteractor(
-            authenticationInteractor = authenticationInteractor,
-            sceneInteractor = sceneInteractor,
-        )
-    private val communalInteractor = utils.communalInteractor()
-
-    private val transitionState =
+    private val transitionState by lazy {
         MutableStateFlow<ObservableTransitionState>(
             ObservableTransitionState.Idle(sceneContainerConfig.initialSceneKey)
         )
-    private val sceneContainerViewModel =
+    }
+    private val sceneContainerViewModel by lazy {
         SceneContainerViewModel(
                 sceneInteractor = sceneInteractor,
-                falsingInteractor = utils.falsingInteractor(),
+                falsingInteractor = kosmos.falsingInteractor,
             )
             .apply { setTransitionState(transitionState) }
+    }
 
-    private val bouncerInteractor =
-        utils.bouncerInteractor(
-            authenticationInteractor = authenticationInteractor,
-        )
+    private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
 
     private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
     private lateinit var bouncerActionButtonInteractor: BouncerActionButtonInteractor
     private lateinit var bouncerViewModel: BouncerViewModel
 
-    private val lockscreenSceneViewModel =
+    private val lockscreenSceneViewModel by lazy {
         LockscreenSceneViewModel(
             applicationScope = testScope.backgroundScope,
             deviceEntryInteractor = deviceEntryInteractor,
@@ -153,8 +158,9 @@
                 KeyguardLongPressViewModel(
                     interactor = mock(),
                 ),
-            notifications = utils.notificationsPlaceholderViewModel(),
+            notifications = kosmos.notificationsPlaceholderViewModel,
         )
+    }
 
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
 
@@ -170,61 +176,51 @@
                     FakeMobileConnectionsRepository(),
                 ),
             constants = mock(),
-            utils.featureFlags,
+            flags = kosmos.fakeFeatureFlagsClassic,
             scope = testScope.backgroundScope,
         )
 
     private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
     private lateinit var shadeSceneViewModel: ShadeSceneViewModel
 
-    private val keyguardRepository = utils.keyguardRepository
-    private val keyguardInteractor =
-        utils.keyguardInteractor(
-            repository = keyguardRepository,
-        )
-    private val powerInteractor = PowerInteractorFactory.create().powerInteractor
+    private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
+    private val powerInteractor by lazy { kosmos.powerInteractor }
 
     private var bouncerSceneJob: Job? = null
 
     private val qsFlexiglassAdapter = FakeQSSceneAdapter(inflateDelegate = { mock() })
 
     @Mock private lateinit var mediaDataManager: MediaDataManager
-    @Mock private lateinit var mediaHost: MediaHost
+
+    private lateinit var emergencyAffordanceManager: EmergencyAffordanceManager
+    private lateinit var telecomManager: TelecomManager
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+
         overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, true)
+        telecomManager = checkNotNull(kosmos.telecomManager)
         whenever(telecomManager.isInCall).thenReturn(false)
+        emergencyAffordanceManager = kosmos.emergencyAffordanceManager
         whenever(emergencyAffordanceManager.needsEmergencyAffordance()).thenReturn(true)
 
-        utils.featureFlags.apply {
+        kosmos.fakeFeatureFlagsClassic.apply {
             set(Flags.NEW_NETWORK_SLICE_UI, false)
             set(Flags.REFACTOR_GETCURRENTUSER, true)
         }
 
-        mobileConnectionsRepository =
-            FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogger)
-        mobileConnectionsRepository.isAnySimSecure.value = true
+        mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
+        mobileConnectionsRepository.isAnySimSecure.value = false
 
-        utils.telephonyRepository.apply {
+        kosmos.fakeTelephonyRepository.apply {
             setHasTelephonyRadio(true)
             setCallState(TelephonyManager.CALL_STATE_IDLE)
             setIsInCall(false)
         }
 
-        bouncerActionButtonInteractor =
-            utils.bouncerActionButtonInteractor(
-                mobileConnectionsRepository = mobileConnectionsRepository,
-                telecomManager = telecomManager,
-                emergencyAffordanceManager = emergencyAffordanceManager,
-            )
-        bouncerViewModel =
-            utils.bouncerViewModel(
-                bouncerInteractor = bouncerInteractor,
-                authenticationInteractor = authenticationInteractor,
-                actionButtonInteractor = bouncerActionButtonInteractor,
-            )
+        bouncerActionButtonInteractor = kosmos.bouncerActionButtonInteractor
+        bouncerViewModel = kosmos.bouncerViewModel
 
         shadeHeaderViewModel =
             ShadeHeaderViewModel(
@@ -242,30 +238,29 @@
                 deviceEntryInteractor = deviceEntryInteractor,
                 shadeHeaderViewModel = shadeHeaderViewModel,
                 qsSceneAdapter = qsFlexiglassAdapter,
-                notifications = utils.notificationsPlaceholderViewModel(),
+                notifications = kosmos.notificationsPlaceholderViewModel,
                 mediaDataManager = mediaDataManager,
-                mediaHost = mediaHost,
             )
 
-        utils.deviceEntryRepository.setUnlocked(false)
+        kosmos.fakeDeviceEntryRepository.setUnlocked(false)
 
         val displayTracker = FakeDisplayTracker(context)
-        val sysUiState = SysUiState(displayTracker)
+        val sysUiState = SysUiState(displayTracker, kosmos.sceneContainerPlugin)
         val startable =
             SceneContainerStartable(
                 applicationScope = testScope.backgroundScope,
                 sceneInteractor = sceneInteractor,
                 deviceEntryInteractor = deviceEntryInteractor,
                 keyguardInteractor = keyguardInteractor,
-                flags = utils.sceneContainerFlags,
+                flags = kosmos.fakeSceneContainerFlags,
                 sysUiState = sysUiState,
                 displayId = displayTracker.defaultDisplayId,
                 sceneLogger = mock(),
-                falsingCollector = utils.falsingCollector(),
+                falsingCollector = kosmos.falsingCollector,
                 powerInteractor = powerInteractor,
                 bouncerInteractor = bouncerInteractor,
-                simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor },
-                authenticationInteractor = dagger.Lazy { utils.authenticationInteractor() },
+                simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
+                authenticationInteractor = dagger.Lazy { kosmos.authenticationInteractor },
                 windowController = mock(),
             )
         startable.start()
@@ -561,15 +556,15 @@
         // Set the lockscreen enabled bit _before_ set the auth method as the code picks up on the
         // lockscreen enabled bit _after_ the auth method is changed and the lockscreen enabled bit
         // is not an observable that can trigger a new evaluation.
-        utils.deviceEntryRepository.setLockscreenEnabled(enableLockscreen)
-        utils.authenticationRepository.setAuthenticationMethod(authMethod)
+        kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(enableLockscreen)
+        kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
         runCurrent()
     }
 
     /** Emulates a phone call in progress. */
     private fun TestScope.startPhoneCall() {
         whenever(telecomManager.isInCall).thenReturn(true)
-        utils.telephonyRepository.apply {
+        kosmos.fakeTelephonyRepository.apply {
             setHasTelephonyRadio(true)
             setIsInCall(true)
             setCallState(TelephonyManager.CALL_STATE_OFFHOOK)
@@ -678,7 +673,7 @@
             .that(authMethod.isSecure)
             .isTrue()
 
-        utils.deviceEntryRepository.setUnlocked(false)
+        kosmos.fakeDeviceEntryRepository.setUnlocked(false)
         runCurrent()
     }
 
@@ -692,7 +687,7 @@
         enterPin()
         // This repository state is not changed by the AuthInteractor, it relies on
         // KeyguardStateController.
-        utils.deviceEntryRepository.setUnlocked(true)
+        kosmos.fakeDeviceEntryRepository.setUnlocked(true)
         emulateUiSceneTransition(
             expectedVisible = false,
         )
@@ -748,7 +743,7 @@
         }
         pinBouncerViewModel.onAuthenticateButtonClicked()
         setAuthMethod(authMethodAfterSimUnlock)
-        utils.mobileConnectionsRepository.isAnySimSecure.value = false
+        kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false
         runCurrent()
     }
 
@@ -781,21 +776,16 @@
     }
 
     /** Emulates the dismissal of the IME (soft keyboard). */
-    private suspend fun TestScope.dismissIme(
-        showImeBeforeDismissing: Boolean = true,
-    ) {
+    private fun TestScope.dismissIme() {
         (bouncerViewModel.authMethodViewModel.value as? PasswordBouncerViewModel)?.let {
-            if (showImeBeforeDismissing) {
-                it.onImeVisibilityChanged(true)
-            }
-            it.onImeVisibilityChanged(false)
+            it.onImeDismissed()
             runCurrent()
         }
     }
 
     private fun TestScope.introduceLockedSim() {
         setAuthMethod(AuthenticationMethodModel.Sim)
-        utils.mobileConnectionsRepository.isAnySimSecure.value = true
+        kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true
         runCurrent()
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index ddeb05b..b267720 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -22,11 +22,14 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.sceneContainerConfig
 import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -39,12 +42,12 @@
 @RunWith(AndroidJUnit4::class)
 class SceneContainerRepositoryTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
+    private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+    private val testScope = kosmos.testScope
 
     @Test
     fun allSceneKeys() {
-        val underTest = utils.fakeSceneContainerRepository()
+        val underTest = kosmos.sceneContainerRepository
         assertThat(underTest.allSceneKeys())
             .isEqualTo(
                 listOf(
@@ -61,7 +64,7 @@
     @Test
     fun desiredScene() =
         testScope.runTest {
-            val underTest = utils.fakeSceneContainerRepository()
+            val underTest = kosmos.sceneContainerRepository
             val currentScene by collectLastValue(underTest.desiredScene)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
@@ -71,15 +74,15 @@
 
     @Test(expected = IllegalStateException::class)
     fun setDesiredScene_noSuchSceneInContainer_throws() {
-        utils.kosmos.sceneKeys = listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
-        val underTest = utils.fakeSceneContainerRepository(utils.fakeSceneContainerConfig())
+        kosmos.sceneKeys = listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
+        val underTest = kosmos.sceneContainerRepository
         underTest.setDesiredScene(SceneModel(SceneKey.Shade))
     }
 
     @Test
     fun isVisible() =
         testScope.runTest {
-            val underTest = utils.fakeSceneContainerRepository()
+            val underTest = kosmos.sceneContainerRepository
             val isVisible by collectLastValue(underTest.isVisible)
             assertThat(isVisible).isTrue()
 
@@ -93,19 +96,19 @@
     @Test
     fun transitionState_defaultsToIdle() =
         testScope.runTest {
-            val underTest = utils.fakeSceneContainerRepository()
+            val underTest = kosmos.sceneContainerRepository
             val transitionState by collectLastValue(underTest.transitionState)
 
             assertThat(transitionState)
                 .isEqualTo(
-                    ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+                    ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
                 )
         }
 
     @Test
     fun transitionState_reflectsUpdates() =
         testScope.runTest {
-            val underTest = utils.fakeSceneContainerRepository()
+            val underTest = kosmos.sceneContainerRepository
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Idle(SceneKey.Lockscreen)
@@ -134,7 +137,7 @@
             underTest.setTransitionState(null)
             assertThat(reflectedTransitionState)
                 .isEqualTo(
-                    ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+                    ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
                 )
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 7f4bbbe..942fbc2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -20,14 +20,23 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.sceneContainerConfig
+import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -35,14 +44,20 @@
 @RunWith(AndroidJUnit4::class)
 class SceneInteractorTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val repository = utils.fakeSceneContainerRepository()
-    private val underTest = utils.sceneInteractor(repository = repository)
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private lateinit var underTest: SceneInteractor
+
+    @Before
+    fun setUp() {
+        kosmos.fakeSceneContainerFlags.enabled = true
+        underTest = kosmos.sceneInteractor
+    }
 
     @Test
     fun allSceneKeys() {
-        assertThat(underTest.allSceneKeys()).isEqualTo(utils.fakeSceneKeys())
+        assertThat(underTest.allSceneKeys()).isEqualTo(kosmos.sceneKeys)
     }
 
     @Test
@@ -56,6 +71,27 @@
         }
 
     @Test
+    fun changeScene_toGoneWhenUnl_doesNotThrow() =
+        testScope.runTest {
+            val desiredScene by collectLastValue(underTest.desiredScene)
+            assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
+
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+            runCurrent()
+
+            underTest.changeScene(SceneModel(SceneKey.Gone), "reason")
+            assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Gone))
+        }
+
+    @Test(expected = IllegalStateException::class)
+    fun changeScene_toGoneWhenStillLocked_throws() =
+        testScope.runTest {
+            kosmos.fakeDeviceEntryRepository.setUnlocked(false)
+
+            underTest.changeScene(SceneModel(SceneKey.Gone), "reason")
+        }
+
+    @Test
     fun onSceneChanged() =
         testScope.runTest {
             val desiredScene by collectLastValue(underTest.desiredScene)
@@ -68,7 +104,7 @@
     @Test
     fun transitionState() =
         testScope.runTest {
-            val underTest = utils.fakeSceneContainerRepository()
+            val underTest = kosmos.sceneContainerRepository
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Idle(SceneKey.Lockscreen)
@@ -97,7 +133,7 @@
             underTest.setTransitionState(null)
             assertThat(reflectedTransitionState)
                 .isEqualTo(
-                    ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+                    ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
                 )
         }
 
@@ -138,105 +174,6 @@
         }
 
     @Test
-    fun transitioning_idle_false() =
-        testScope.runTest {
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(SceneKey.Shade)
-                )
-            val transitioning by
-                collectLastValue(underTest.transitioning(SceneKey.Shade, SceneKey.Lockscreen))
-            underTest.setTransitionState(transitionState)
-
-            assertThat(transitioning).isFalse()
-        }
-
-    @Test
-    fun transitioning_wrongFromScene_false() =
-        testScope.runTest {
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Gone,
-                        toScene = SceneKey.Lockscreen,
-                        progress = flowOf(0.5f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            val transitioning by
-                collectLastValue(underTest.transitioning(SceneKey.Shade, SceneKey.Lockscreen))
-            underTest.setTransitionState(transitionState)
-
-            assertThat(transitioning).isFalse()
-        }
-
-    @Test
-    fun transitioning_wrongToScene_false() =
-        testScope.runTest {
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Shade,
-                        toScene = SceneKey.QuickSettings,
-                        progress = flowOf(0.5f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            underTest.setTransitionState(transitionState)
-
-            assertThat(underTest.transitioning(SceneKey.Shade, SceneKey.Lockscreen).value).isFalse()
-        }
-
-    @Test
-    fun transitioning_correctFromAndToScenes_true() =
-        testScope.runTest {
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Shade,
-                        toScene = SceneKey.Lockscreen,
-                        progress = flowOf(0.5f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            val transitioning by
-                collectLastValue(underTest.transitioning(SceneKey.Shade, SceneKey.Lockscreen))
-            underTest.setTransitionState(transitionState)
-
-            assertThat(transitioning).isTrue()
-        }
-
-    @Test
-    fun transitioning_updates() =
-        testScope.runTest {
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(SceneKey.Shade)
-                )
-            val transitioning by
-                collectLastValue(underTest.transitioning(SceneKey.Shade, SceneKey.Lockscreen))
-            underTest.setTransitionState(transitionState)
-
-            assertThat(transitioning).isFalse()
-
-            transitionState.value =
-                ObservableTransitionState.Transition(
-                    fromScene = SceneKey.Shade,
-                    toScene = SceneKey.Lockscreen,
-                    progress = flowOf(0.5f),
-                    isInitiatedByUserInput = false,
-                    isUserInputOngoing = flowOf(false),
-                )
-            assertThat(transitioning).isTrue()
-
-            transitionState.value = ObservableTransitionState.Idle(SceneKey.Lockscreen)
-            assertThat(transitioning).isFalse()
-        }
-
-    @Test
     fun isTransitionUserInputOngoing_idle_false() =
         testScope.runTest {
             val transitionState =
@@ -341,8 +278,8 @@
     @Test
     fun userInput() =
         testScope.runTest {
-            assertThat(utils.powerRepository.userTouchRegistered).isFalse()
+            assertThat(kosmos.fakePowerRepository.userTouchRegistered).isFalse()
             underTest.onUserInput()
-            assertThat(utils.powerRepository.userTouchRegistered).isTrue()
+            assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue()
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
index 8be4eeb..d5e43f4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.scene.domain.interactor
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.statusbar.IStatusBarService
@@ -23,12 +25,19 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.init.NotificationsController
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
@@ -37,7 +46,7 @@
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -49,7 +58,9 @@
 @RunWith(AndroidJUnit4::class)
 class WindowRootViewVisibilityInteractorTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+    private val testDispatcher = StandardTestDispatcher()
     private val iStatusBarService = mock<IStatusBarService>()
     private val executor = FakeExecutor(FakeSystemClock())
     private val windowRootViewVisibilityRepository =
@@ -59,6 +70,9 @@
     private val notificationPresenter = mock<NotificationPresenter>()
     private val notificationsController = mock<NotificationsController>()
     private val powerInteractor = PowerInteractorFactory.create().powerInteractor
+    private val activeNotificationsRepository = ActiveNotificationListRepository()
+    private val activeNotificationsInteractor =
+        ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher)
 
     private val underTest =
         WindowRootViewVisibilityInteractor(
@@ -67,6 +81,9 @@
                 keyguardRepository,
                 headsUpManager,
                 powerInteractor,
+                activeNotificationsInteractor,
+                kosmos.sceneContainerFlags,
+                kosmos::sceneInteractor,
             )
             .apply { setUp(notificationPresenter, notificationsController) }
 
@@ -257,7 +274,8 @@
         }
 
     @Test
-    fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_notifCountOne() =
+    @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+    fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_flagOff_notifCountOne() =
         testScope.runTest {
             underTest.start()
 
@@ -273,6 +291,23 @@
         }
 
     @Test
+    @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+    fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_flagOn_notifCountOne() =
+        testScope.runTest {
+            underTest.start()
+
+            whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
+            whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
+            activeNotificationsRepository.setActiveNotifs(4)
+
+            makeLockscreenShadeVisible()
+
+            val notifCount = argumentCaptor<Int>()
+            verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+            assertThat(notifCount.value).isEqualTo(1)
+        }
+
+    @Test
     fun lockscreenShadeInteractive_hasHeadsUpAndNullPresenter_notifCountOne() =
         testScope.runTest {
             underTest.start()
@@ -288,7 +323,8 @@
         }
 
     @Test
-    fun lockscreenShadeInteractive_noHeadsUp_notifCountMatchesNotifController() =
+    @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+    fun lockscreenShadeInteractive_noHeadsUp_flagOff_notifCountMatchesNotifController() =
         testScope.runTest {
             underTest.start()
             whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
@@ -304,7 +340,25 @@
         }
 
     @Test
-    fun lockscreenShadeInteractive_notifPresenterNotCollapsed_notifCountMatchesNotifController() =
+    @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+    fun lockscreenShadeInteractive_noHeadsUp_flagOn_notifCountMatchesNotifController() =
+        testScope.runTest {
+            underTest.start()
+            whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
+
+            whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false)
+            activeNotificationsRepository.setActiveNotifs(9)
+
+            makeLockscreenShadeVisible()
+
+            val notifCount = argumentCaptor<Int>()
+            verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+            assertThat(notifCount.value).isEqualTo(9)
+        }
+
+    @Test
+    @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+    fun lockscreenShadeInteractive_notifPresenterNotCollapsed_flagOff_notifCountMatchesNotifController() =
         testScope.runTest {
             underTest.start()
             whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
@@ -320,6 +374,23 @@
         }
 
     @Test
+    @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+    fun lockscreenShadeInteractive_notifPresenterNotCollapsed_flagOn_notifCountMatchesNotifController() =
+        testScope.runTest {
+            underTest.start()
+            whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
+
+            whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false)
+            activeNotificationsRepository.setActiveNotifs(8)
+
+            makeLockscreenShadeVisible()
+
+            val notifCount = argumentCaptor<Int>()
+            verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+            assertThat(notifCount.value).isEqualTo(8)
+        }
+
+    @Test
     fun lockscreenShadeInteractive_noHeadsUp_noNotifController_notifCountZero() =
         testScope.runTest {
             underTest.start()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index dd22976..16cb623 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -25,19 +25,31 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags as AconfigFlags
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.model.SysUiState
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -65,21 +77,15 @@
 
     @Mock private lateinit var windowController: NotificationShadeWindowController
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val sceneInteractor = utils.sceneInteractor()
-    private val sceneContainerFlags = utils.sceneContainerFlags
-    private val authenticationInteractor = utils.authenticationInteractor()
-    private val bouncerInteractor =
-        utils.bouncerInteractor(authenticationInteractor = authenticationInteractor)
-    private val faceAuthRepository = FakeDeviceEntryFaceAuthRepository()
-    private val deviceEntryInteractor =
-        utils.deviceEntryInteractor(
-            faceAuthRepository = faceAuthRepository,
-            authenticationInteractor = authenticationInteractor,
-            sceneInteractor = sceneInteractor,
-        )
-    private val keyguardInteractor = utils.keyguardInteractor()
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val sceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags }
+    private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
+    private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
+    private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository }
+    private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
+    private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
     private val sysUiState: SysUiState = mock()
     private val falsingCollector: FalsingCollector = mock()
     private val powerInteractor = PowerInteractorFactory.create().powerInteractor
@@ -103,7 +109,7 @@
                 falsingCollector = falsingCollector,
                 powerInteractor = powerInteractor,
                 bouncerInteractor = bouncerInteractor,
-                simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor },
+                simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
                 authenticationInteractor = dagger.Lazy { authenticationInteractor },
                 windowController = windowController,
             )
@@ -178,7 +184,7 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
             underTest.start()
 
-            utils.deviceEntryRepository.setUnlocked(false)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(false)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
         }
@@ -194,7 +200,7 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
             underTest.start()
 
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
         }
@@ -210,7 +216,7 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
             underTest.start()
 
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
         }
@@ -228,7 +234,7 @@
 
             // Authenticate using a passive auth method like face auth while bypass is disabled.
             faceAuthRepository.isAuthenticated.value = true
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
         }
@@ -250,7 +256,7 @@
             transitionStateFlowValue.value = ObservableTransitionState.Idle(SceneKey.Shade)
             assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
 
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
             runCurrent()
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
@@ -269,7 +275,7 @@
 
             // Authenticate using a passive auth method like face auth while bypass is disabled.
             faceAuthRepository.isAuthenticated.value = true
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
         }
@@ -305,6 +311,10 @@
                     SceneKey.QuickSettings,
                 )
                 .forEachIndexed { index, sceneKey ->
+                    if (sceneKey == SceneKey.Gone) {
+                        kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+                        runCurrent()
+                    }
                     sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
                     runCurrent()
                     verify(sysUiState, times(index)).commitUpdate(Display.DEFAULT_DISPLAY)
@@ -379,7 +389,7 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
             underTest.start()
 
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
             runCurrent()
             powerInteractor.setAwakeForTest()
             runCurrent()
@@ -414,6 +424,8 @@
                 }
 
             // Changing to the Gone scene should report a successful unlock.
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+            runCurrent()
             sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
             runCurrent()
             verify(falsingCollector).onSuccessfulUnlock()
@@ -469,11 +481,11 @@
             runCurrent()
             verify(falsingCollector).setShowingAod(false)
 
-            utils.keyguardRepository.setIsDozing(true)
+            kosmos.fakeKeyguardRepository.setIsDozing(true)
             runCurrent()
             verify(falsingCollector).setShowingAod(true)
 
-            utils.keyguardRepository.setIsDozing(false)
+            kosmos.fakeKeyguardRepository.setIsDozing(false)
             runCurrent()
             verify(falsingCollector, times(2)).setShowingAod(false)
         }
@@ -499,7 +511,7 @@
     @Test
     fun collectFalsingSignals_screenOnAndOff_aodUnavailable() =
         testScope.runTest {
-            utils.keyguardRepository.setAodAvailable(false)
+            kosmos.fakeKeyguardRepository.setAodAvailable(false)
             runCurrent()
             prepareState(
                 initialSceneKey = SceneKey.Lockscreen,
@@ -547,7 +559,7 @@
     @Test
     fun collectFalsingSignals_screenOnAndOff_aodAvailable() =
         testScope.runTest {
-            utils.keyguardRepository.setAodAvailable(true)
+            kosmos.fakeKeyguardRepository.setAodAvailable(true)
             runCurrent()
             prepareState(
                 initialSceneKey = SceneKey.Lockscreen,
@@ -607,6 +619,8 @@
             runCurrent()
             verify(falsingCollector).onBouncerShown()
 
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+            runCurrent()
             sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
             runCurrent()
             verify(falsingCollector, times(2)).onBouncerHidden()
@@ -625,7 +639,7 @@
             underTest.start()
             runCurrent()
 
-            utils.mobileConnectionsRepository.isAnySimSecure.value = true
+            kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true
             runCurrent()
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
@@ -634,7 +648,7 @@
     @Test
     fun switchesToLockscreen_whenSimBecomesUnlocked() =
         testScope.runTest {
-            utils.mobileConnectionsRepository.isAnySimSecure.value = true
+            kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true
             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
 
             prepareState(
@@ -644,7 +658,7 @@
             )
             underTest.start()
             runCurrent()
-            utils.mobileConnectionsRepository.isAnySimSecure.value = false
+            kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false
             runCurrent()
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
@@ -653,7 +667,7 @@
     @Test
     fun switchesToGone_whenSimBecomesUnlocked_ifDeviceUnlockedAndLockscreenDisabled() =
         testScope.runTest {
-            utils.mobileConnectionsRepository.isAnySimSecure.value = true
+            kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true
             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
 
             prepareState(
@@ -664,7 +678,7 @@
             )
             underTest.start()
             runCurrent()
-            utils.mobileConnectionsRepository.isAnySimSecure.value = false
+            kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false
             runCurrent()
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
@@ -735,9 +749,15 @@
                 "Lockscreen cannot be disabled while having a secure authentication method"
             }
         }
+
+        check(initialSceneKey != SceneKey.Gone || isDeviceUnlocked) {
+            "Cannot start on the Gone scene and have the device be locked at the same time."
+        }
+
         sceneContainerFlags.enabled = true
-        utils.deviceEntryRepository.setUnlocked(isDeviceUnlocked)
-        utils.deviceEntryRepository.setBypassEnabled(isBypassEnabled)
+        kosmos.fakeDeviceEntryRepository.setUnlocked(isDeviceUnlocked)
+        kosmos.fakeDeviceEntryRepository.setBypassEnabled(isBypassEnabled)
+        runCurrent()
         val transitionStateFlow =
             MutableStateFlow<ObservableTransitionState>(
                 ObservableTransitionState.Idle(SceneKey.Lockscreen)
@@ -749,8 +769,8 @@
             sceneInteractor.onSceneChanged(SceneModel(it), "reason")
         }
         authenticationMethod?.let {
-            utils.authenticationRepository.setAuthenticationMethod(authenticationMethod)
-            utils.deviceEntryRepository.setLockscreenEnabled(
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authenticationMethod)
+            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(
                 isLockscreenEnabled = isLockscreenEnabled
             )
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index c89cd9e..a16ce43 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -21,13 +21,18 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.domain.interactor.falsingInteractor
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -35,13 +40,19 @@
 @RunWith(AndroidJUnit4::class)
 class SceneContainerViewModelTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val interactor = utils.sceneInteractor()
-    private val underTest =
-        SceneContainerViewModel(
-            sceneInteractor = interactor,
-            falsingInteractor = utils.falsingInteractor(),
-        )
+    private val kosmos = testKosmos()
+    private val interactor by lazy { kosmos.sceneInteractor }
+    private lateinit var underTest: SceneContainerViewModel
+
+    @Before
+    fun setUp() {
+        kosmos.fakeSceneContainerFlags.enabled = true
+        underTest =
+            SceneContainerViewModel(
+                sceneInteractor = interactor,
+                falsingInteractor = kosmos.falsingInteractor,
+            )
+    }
 
     @Test
     fun isVisible() = runTest {
@@ -57,7 +68,7 @@
 
     @Test
     fun allSceneKeys() {
-        assertThat(underTest.allSceneKeys).isEqualTo(utils.fakeSceneKeys())
+        assertThat(underTest.allSceneKeys).isEqualTo(kosmos.sceneKeys)
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
new file mode 100644
index 0000000..51b8342
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadeControllerSceneImplTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+    private val sceneInteractor = kosmos.sceneInteractor
+    private val deviceEntryInteractor = kosmos.deviceEntryInteractor
+
+    private lateinit var shadeInteractor: ShadeInteractor
+    private lateinit var underTest: ShadeControllerSceneImpl
+
+    @Before
+    fun setup() {
+        kosmos.testCase = this
+        kosmos.fakeSceneContainerFlags.enabled = true
+        kosmos.fakeFeatureFlagsClassic.apply {
+            set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+            set(Flags.NSSL_DEBUG_LINES, false)
+            set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+        }
+        kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+        testScope.runCurrent()
+        shadeInteractor = kosmos.shadeInteractor
+        underTest = kosmos.shadeControllerSceneImpl
+    }
+
+    @Test
+    fun animateCollapseShade_noForceNoExpansion() =
+        testScope.runTest {
+            // GIVEN shade is collapsed and a post-collapse action is enqueued
+            val testRunnable = mock<Runnable>()
+            setDeviceEntered(true)
+            setCollapsed()
+            underTest.addPostCollapseAction(testRunnable)
+
+            // WHEN a collapse is requested
+            underTest.animateCollapseShade(0, force = false, delayed = false, 1f)
+            runCurrent()
+
+            // THEN the shade remains collapsed and the post-collapse action ran
+            assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Gone)
+            verify(testRunnable, times(1)).run()
+        }
+
+    @Test
+    fun animateCollapseShade_expandedExcludeFlagOn() =
+        testScope.runTest {
+            // GIVEN shade is fully expanded and a post-collapse action is enqueued
+            val testRunnable = mock<Runnable>()
+            underTest.addPostCollapseAction(testRunnable)
+            setDeviceEntered(true)
+            setShadeFullyExpanded()
+
+            // WHEN a collapse is requested with FLAG_EXCLUDE_NOTIFICATION_PANEL
+            underTest.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL)
+            runCurrent()
+
+            // THEN the shade remains expanded and the post-collapse action did not run
+            assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Shade)
+            assertThat(shadeInteractor.isAnyFullyExpanded.value).isTrue()
+            verify(testRunnable, never()).run()
+        }
+
+    @Test
+    fun animateCollapseShade_locked() =
+        testScope.runTest {
+            // GIVEN shade is fully expanded on lockscreen
+            setDeviceEntered(false)
+            setShadeFullyExpanded()
+
+            // WHEN a collapse is requested
+            underTest.animateCollapseShade()
+            runCurrent()
+
+            // THEN the shade collapses back to lockscreen and the post-collapse action ran
+            assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun animateCollapseShade_unlocked() =
+        testScope.runTest {
+            // GIVEN shade is fully expanded on an unlocked device
+            setDeviceEntered(true)
+            setShadeFullyExpanded()
+
+            // WHEN a collapse is requested
+            underTest.animateCollapseShade()
+            runCurrent()
+
+            // THEN the shade collapses back to lockscreen and the post-collapse action ran
+            assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Gone)
+        }
+
+    @Test
+    fun onCollapseShade_runPostCollapseActionsCalled() =
+        testScope.runTest {
+            // GIVEN shade is expanded and a post-collapse action is enqueued
+            val testRunnable = mock<Runnable>()
+            setShadeFullyExpanded()
+            underTest.addPostCollapseAction(testRunnable)
+
+            // WHEN shade collapses
+            setCollapsed()
+
+            // THEN post-collapse action ran
+            verify(testRunnable, times(1)).run()
+        }
+
+    @Test
+    fun postOnShadeExpanded() =
+        testScope.runTest {
+            // GIVEN shade is collapsed and a post-collapse action is enqueued
+            val testRunnable = mock<Runnable>()
+            setCollapsed()
+            underTest.postOnShadeExpanded(testRunnable)
+
+            // WHEN shade expands
+            setShadeFullyExpanded()
+
+            // THEN post-collapse action ran
+            verify(testRunnable, times(1)).run()
+        }
+
+    private fun setScene(key: SceneKey) {
+        sceneInteractor.changeScene(SceneModel(key), "test")
+        sceneInteractor.setTransitionState(
+            MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+        )
+        testScope.runCurrent()
+    }
+
+    private fun setDeviceEntered(isEntered: Boolean) {
+        setScene(
+            if (isEntered) {
+                SceneKey.Gone
+            } else {
+                SceneKey.Lockscreen
+            }
+        )
+        assertThat(deviceEntryInteractor.isDeviceEntered.value).isEqualTo(isEntered)
+    }
+
+    private fun setCollapsed() {
+        setScene(SceneKey.Gone)
+        assertThat(shadeInteractor.isAnyExpanded.value).isFalse()
+    }
+
+    private fun setShadeFullyExpanded() {
+        setScene(SceneKey.Shade)
+        assertThat(shadeInteractor.isAnyFullyExpanded.value).isTrue()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt
new file mode 100644
index 0000000..1ef07fa
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadeAnimationInteractorSceneContainerImplTest : SysuiTestCase() {
+    val kosmos = testKosmos()
+    val testScope = kosmos.testScope
+    val sceneInteractor = kosmos.sceneInteractor
+
+    val underTest = kosmos.shadeAnimationInteractorSceneContainerImpl
+
+    @Test
+    fun isAnyCloseAnimationRunning_qsToShade() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isAnyCloseAnimationRunning)
+
+            // WHEN transitioning from QS to Shade
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = SceneKey.QuickSettings,
+                        toScene = SceneKey.Shade,
+                        progress = MutableStateFlow(.1f),
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // THEN qs is animating closed
+            Truth.assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isAnyCloseAnimationRunning_qsToGone_userInputNotOngoing() =
+        testScope.runTest() {
+            val actual by collectLastValue(underTest.isAnyCloseAnimationRunning)
+
+            // WHEN transitioning from QS to Gone lwith no ongoing user input
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = SceneKey.QuickSettings,
+                        toScene = SceneKey.Gone,
+                        progress = MutableStateFlow(.1f),
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // THEN qs is animating closed
+            Truth.assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isAnyCloseAnimationRunning_qsToGone_userInputOngoing() =
+        testScope.runTest() {
+            val actual by collectLastValue(underTest.isAnyCloseAnimationRunning)
+
+            // WHEN transitioning from QS to Gone with user input ongoing
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = SceneKey.QuickSettings,
+                        toScene = SceneKey.Gone,
+                        progress = MutableStateFlow(.1f),
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(true),
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // THEN qs is not animating closed
+            Truth.assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun updateIsLaunchingActivity() =
+        testScope.runTest {
+            Truth.assertThat(underTest.isLaunchingActivity.value).isEqualTo(false)
+
+            underTest.setIsLaunchingActivity(true)
+            Truth.assertThat(underTest.isLaunchingActivity.value).isEqualTo(true)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
new file mode 100644
index 0000000..4e82feb
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
@@ -0,0 +1,734 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import android.app.StatusBarManager.DISABLE2_NONE
+import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
+import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
+import com.android.systemui.statusbar.phone.dozeParameters
+import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.data.repository.fakeUserSetupRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadeInteractorImplTest : SysuiTestCase() {
+    val kosmos = testKosmos()
+    val testScope = kosmos.testScope
+    val configurationRepository = kosmos.fakeConfigurationRepository
+    val deviceProvisioningRepository = kosmos.fakeDeviceProvisioningRepository
+    val disableFlagsRepository = kosmos.fakeDisableFlagsRepository
+    val keyguardRepository = kosmos.fakeKeyguardRepository
+    val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    val powerRepository = kosmos.fakePowerRepository
+    val sceneInteractor = kosmos.sceneInteractor
+    val shadeRepository = kosmos.fakeShadeRepository
+    val userRepository = kosmos.fakeUserRepository
+    val userSetupRepository = kosmos.fakeUserSetupRepository
+    val dozeParameters = kosmos.dozeParameters
+
+    val underTest = kosmos.shadeInteractorImpl
+
+    @Test
+    fun isShadeEnabled_matchesDisableFlagsRepo() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isShadeEnabled)
+
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(disable2 = DISABLE2_NOTIFICATION_SHADE)
+            assertThat(actual).isFalse()
+
+            disableFlagsRepository.disableFlags.value = DisableFlagsModel(disable2 = DISABLE2_NONE)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_deviceNotProvisioned_false() =
+        testScope.runTest {
+            deviceProvisioningRepository.setDeviceProvisioned(false)
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_userNotSetupAndSimpleUserSwitcher_false() =
+        testScope.runTest {
+            deviceProvisioningRepository.setDeviceProvisioned(true)
+
+            userSetupRepository.setUserSetUp(false)
+            userRepository.setSettings(UserSwitcherSettingsModel(isSimpleUserSwitcher = true))
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_shadeNotEnabled_false() =
+        testScope.runTest {
+            deviceProvisioningRepository.setDeviceProvisioned(true)
+            userSetupRepository.setUserSetUp(true)
+
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NOTIFICATION_SHADE,
+                )
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_quickSettingsNotEnabled_false() =
+        testScope.runTest {
+            deviceProvisioningRepository.setDeviceProvisioned(true)
+            userSetupRepository.setUserSetUp(true)
+
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_QUICK_SETTINGS,
+                )
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_dozing_false() =
+        testScope.runTest {
+            deviceProvisioningRepository.setDeviceProvisioned(true)
+            userSetupRepository.setUserSetUp(true)
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NONE,
+                )
+
+            keyguardRepository.setIsDozing(true)
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_userSetup_true() =
+        testScope.runTest {
+            deviceProvisioningRepository.setDeviceProvisioned(true)
+            keyguardRepository.setIsDozing(false)
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NONE,
+                )
+
+            userSetupRepository.setUserSetUp(true)
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_notSimpleUserSwitcher_true() =
+        testScope.runTest {
+            deviceProvisioningRepository.setDeviceProvisioned(true)
+            keyguardRepository.setIsDozing(false)
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NONE,
+                )
+
+            userRepository.setSettings(UserSwitcherSettingsModel(isSimpleUserSwitcher = false))
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_respondsToDozingUpdates() =
+        testScope.runTest {
+            deviceProvisioningRepository.setDeviceProvisioned(true)
+            keyguardRepository.setIsDozing(false)
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NONE,
+                )
+            userSetupRepository.setUserSetUp(true)
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isTrue()
+
+            // WHEN dozing starts
+            keyguardRepository.setIsDozing(true)
+
+            // THEN expand is disabled
+            assertThat(actual).isFalse()
+
+            // WHEN dozing stops
+            keyguardRepository.setIsDozing(false)
+
+            // THEN expand is enabled
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_respondsToDisableUpdates() =
+        testScope.runTest {
+            deviceProvisioningRepository.setDeviceProvisioned(true)
+            keyguardRepository.setIsDozing(false)
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NONE,
+                )
+            userSetupRepository.setUserSetUp(true)
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isTrue()
+
+            // WHEN QS is disabled
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_QUICK_SETTINGS,
+                )
+            // THEN expand is disabled
+            assertThat(actual).isFalse()
+
+            // WHEN QS is enabled
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NONE,
+                )
+            // THEN expand is enabled
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_respondsToUserUpdates() =
+        testScope.runTest {
+            deviceProvisioningRepository.setDeviceProvisioned(true)
+            keyguardRepository.setIsDozing(false)
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NONE,
+                )
+            userSetupRepository.setUserSetUp(true)
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isTrue()
+
+            // WHEN the user is no longer setup
+            userSetupRepository.setUserSetUp(false)
+            userRepository.setSettings(UserSwitcherSettingsModel(isSimpleUserSwitcher = true))
+
+            // THEN expand is disabled
+            assertThat(actual).isFalse()
+
+            // WHEN the user is setup again
+            userSetupRepository.setUserSetUp(true)
+
+            // THEN expand is enabled
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun fullShadeExpansionWhenShadeLocked() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.shadeExpansion)
+
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+            shadeRepository.setLockscreenShadeExpansion(0.5f)
+
+            assertThat(actual).isEqualTo(1f)
+        }
+
+    @Test
+    fun fullShadeExpansionWhenStatusBarStateIsNotShadeLocked() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.shadeExpansion)
+
+            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+
+            shadeRepository.setLockscreenShadeExpansion(0.5f)
+            assertThat(actual).isEqualTo(0.5f)
+
+            shadeRepository.setLockscreenShadeExpansion(0.8f)
+            assertThat(actual).isEqualTo(0.8f)
+        }
+
+    @Test
+    fun shadeExpansionWhenInSplitShadeAndQsExpanded() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.shadeExpansion)
+
+            // WHEN split shade is enabled and QS is expanded
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            configurationRepository.onAnyConfigurationChange()
+            shadeRepository.setQsExpansion(.5f)
+            shadeRepository.setLegacyShadeExpansion(.7f)
+            runCurrent()
+
+            // THEN legacy shade expansion is passed through
+            assertThat(actual).isEqualTo(.7f)
+        }
+
+    @Test
+    fun shadeExpansionWhenNotInSplitShadeAndQsExpanded() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.shadeExpansion)
+
+            // WHEN split shade is not enabled and QS is expanded
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            overrideResource(R.bool.config_use_split_notification_shade, false)
+            shadeRepository.setQsExpansion(.5f)
+            shadeRepository.setLegacyShadeExpansion(1f)
+            runCurrent()
+
+            // THEN shade expansion is zero
+            assertThat(actual).isEqualTo(0f)
+        }
+
+    @Test
+    fun shadeExpansionWhenNotInSplitShadeAndQsCollapsed() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.shadeExpansion)
+
+            // WHEN split shade is not enabled and QS is expanded
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            shadeRepository.setQsExpansion(0f)
+            shadeRepository.setLegacyShadeExpansion(.6f)
+
+            // THEN shade expansion is zero
+            assertThat(actual).isEqualTo(.6f)
+        }
+
+    @Test
+    fun anyExpansion_shadeGreater() =
+        testScope.runTest() {
+            // WHEN shade is more expanded than QS
+            shadeRepository.setLegacyShadeExpansion(.5f)
+            shadeRepository.setQsExpansion(0f)
+            runCurrent()
+
+            // THEN anyExpansion is .5f
+            assertThat(underTest.anyExpansion.value).isEqualTo(.5f)
+        }
+
+    @Test
+    fun anyExpansion_qsGreater() =
+        testScope.runTest() {
+            // WHEN qs is more expanded than shade
+            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeRepository.setQsExpansion(.5f)
+            runCurrent()
+
+            // THEN anyExpansion is .5f
+            assertThat(underTest.anyExpansion.value).isEqualTo(.5f)
+        }
+
+    @Test
+    fun userInteractingWithShade_shadeDraggedUpAndDown() =
+        testScope.runTest() {
+            val actual by collectLastValue(underTest.isUserInteractingWithShade)
+            // GIVEN shade collapsed and not tracking input
+            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+
+            // WHEN shade tracking starts
+            shadeRepository.setLegacyShadeTracking(true)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade dragged down halfway
+            shadeRepository.setLegacyShadeExpansion(.5f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade fully expanded but tracking is not stopped
+            shadeRepository.setLegacyShadeExpansion(1f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade fully collapsed but tracking is not stopped
+            shadeRepository.setLegacyShadeExpansion(0f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade dragged halfway and tracking is stopped
+            shadeRepository.setLegacyShadeExpansion(.6f)
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade completes expansion stopped
+            shadeRepository.setLegacyShadeExpansion(1f)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun userInteractingWithShade_shadeExpanded() =
+        testScope.runTest() {
+            val actual by collectLastValue(underTest.isUserInteractingWithShade)
+            // GIVEN shade collapsed and not tracking input
+            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+
+            // WHEN shade tracking starts
+            shadeRepository.setLegacyShadeTracking(true)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade dragged down halfway
+            shadeRepository.setLegacyShadeExpansion(.5f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade fully expanded and tracking is stopped
+            shadeRepository.setLegacyShadeExpansion(1f)
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun userInteractingWithShade_shadePartiallyExpanded() =
+        testScope.runTest() {
+            val actual by collectLastValue(underTest.isUserInteractingWithShade)
+            // GIVEN shade collapsed and not tracking input
+            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+
+            // WHEN shade tracking starts
+            shadeRepository.setLegacyShadeTracking(true)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade partially expanded
+            shadeRepository.setLegacyShadeExpansion(.4f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN tracking is stopped
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade goes back to collapsed
+            shadeRepository.setLegacyShadeExpansion(0f)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun userInteractingWithShade_shadeCollapsed() =
+        testScope.runTest() {
+            val actual by collectLastValue(underTest.isUserInteractingWithShade)
+            // GIVEN shade expanded and not tracking input
+            shadeRepository.setLegacyShadeExpansion(1f)
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+
+            // WHEN shade tracking starts
+            shadeRepository.setLegacyShadeTracking(true)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade dragged up halfway
+            shadeRepository.setLegacyShadeExpansion(.5f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade fully collapsed and tracking is stopped
+            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun userInteractingWithQs_qsDraggedUpAndDown() =
+        testScope.runTest() {
+            val actual by collectLastValue(underTest.isUserInteractingWithQs)
+            // GIVEN qs collapsed and not tracking input
+            shadeRepository.setQsExpansion(0f)
+            shadeRepository.setLegacyQsTracking(false)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+
+            // WHEN qs tracking starts
+            shadeRepository.setLegacyQsTracking(true)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN qs dragged down halfway
+            shadeRepository.setQsExpansion(.5f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN qs fully expanded but tracking is not stopped
+            shadeRepository.setQsExpansion(1f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN qs fully collapsed but tracking is not stopped
+            shadeRepository.setQsExpansion(0f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN qs dragged halfway and tracking is stopped
+            shadeRepository.setQsExpansion(.6f)
+            shadeRepository.setLegacyQsTracking(false)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN qs completes expansion stopped
+            shadeRepository.setQsExpansion(1f)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isShadeTouchable_isFalse_whenFrpIsActive() =
+        testScope.runTest {
+            deviceProvisioningRepository.setFactoryResetProtectionActive(true)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                )
+            )
+            val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
+            runCurrent()
+            assertThat(isShadeTouchable).isFalse()
+        }
+
+    @Test
+    fun isShadeTouchable_isFalse_whenDeviceAsleepAndNotPulsing() =
+        testScope.runTest {
+            powerRepository.updateWakefulness(
+                rawState = WakefulnessState.ASLEEP,
+                lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                lastSleepReason = WakeSleepReason.OTHER,
+            )
+            // goingToSleep == false
+            // TODO: remove?
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.LOCKSCREEN,
+                    transitionState = TransitionState.STARTED,
+                )
+            )
+            keyguardRepository.setDozeTransitionModel(
+                DozeTransitionModel(
+                    to = DozeStateModel.DOZE_AOD,
+                )
+            )
+            val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
+            runCurrent()
+            assertThat(isShadeTouchable).isFalse()
+        }
+
+    @Test
+    fun isShadeTouchable_isTrue_whenDeviceAsleepAndPulsing() =
+        testScope.runTest {
+            powerRepository.updateWakefulness(
+                rawState = WakefulnessState.ASLEEP,
+                lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                lastSleepReason = WakeSleepReason.OTHER,
+            )
+            // goingToSleep == false
+            // TODO: remove?
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.LOCKSCREEN,
+                    transitionState = TransitionState.STARTED,
+                )
+            )
+            keyguardRepository.setDozeTransitionModel(
+                DozeTransitionModel(
+                    to = DozeStateModel.DOZE_PULSING,
+                )
+            )
+            val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
+            runCurrent()
+            assertThat(isShadeTouchable).isTrue()
+        }
+
+    @Test
+    fun isShadeTouchable_isFalse_whenStartingToSleepAndNotControlScreenOff() =
+        testScope.runTest {
+            powerRepository.updateWakefulness(
+                rawState = WakefulnessState.STARTING_TO_SLEEP,
+                lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                lastSleepReason = WakeSleepReason.OTHER,
+            )
+            // goingToSleep == true
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    transitionState = TransitionState.STARTED,
+                )
+            )
+            whenever(dozeParameters.shouldControlScreenOff()).thenReturn(false)
+            val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
+            runCurrent()
+            assertThat(isShadeTouchable).isFalse()
+        }
+
+    @Test
+    fun isShadeTouchable_isTrue_whenStartingToSleepAndControlScreenOff() =
+        testScope.runTest {
+            powerRepository.updateWakefulness(
+                rawState = WakefulnessState.STARTING_TO_SLEEP,
+                lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                lastSleepReason = WakeSleepReason.OTHER,
+            )
+            // goingToSleep == true
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    transitionState = TransitionState.STARTED,
+                )
+            )
+            whenever(dozeParameters.shouldControlScreenOff()).thenReturn(true)
+            val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
+            runCurrent()
+            assertThat(isShadeTouchable).isTrue()
+        }
+
+    @Test
+    fun isShadeTouchable_isTrue_whenNotAsleep() =
+        testScope.runTest {
+            powerRepository.updateWakefulness(
+                rawState = WakefulnessState.AWAKE,
+                lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                lastSleepReason = WakeSleepReason.OTHER,
+            )
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                )
+            )
+            val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
+            runCurrent()
+            assertThat(isShadeTouchable).isTrue()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt
new file mode 100644
index 0000000..682c4ef
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadeInteractorLegacyImplTest : SysuiTestCase() {
+    val kosmos = testKosmos()
+    val testScope = kosmos.testScope
+    val configurationRepository = kosmos.fakeConfigurationRepository
+    val keyguardRepository = kosmos.fakeKeyguardRepository
+    val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    val sceneInteractor = kosmos.sceneInteractor
+    val shadeRepository = kosmos.fakeShadeRepository
+    val userRepository = kosmos.fakeUserRepository
+
+    val underTest = kosmos.shadeInteractorLegacyImpl
+
+    @Test
+    fun fullShadeExpansionWhenShadeLocked() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.shadeExpansion)
+
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+            shadeRepository.setLockscreenShadeExpansion(0.5f)
+
+            assertThat(actual).isEqualTo(1f)
+        }
+
+    @Test
+    fun fullShadeExpansionWhenStatusBarStateIsNotShadeLocked() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.shadeExpansion)
+
+            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+
+            shadeRepository.setLockscreenShadeExpansion(0.5f)
+            assertThat(actual).isEqualTo(0.5f)
+
+            shadeRepository.setLockscreenShadeExpansion(0.8f)
+            assertThat(actual).isEqualTo(0.8f)
+        }
+
+    @Test
+    fun shadeExpansionWhenInSplitShadeAndQsExpanded() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.shadeExpansion)
+
+            // WHEN split shade is enabled and QS is expanded
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            configurationRepository.onAnyConfigurationChange()
+            shadeRepository.setQsExpansion(.5f)
+            shadeRepository.setLegacyShadeExpansion(.7f)
+            runCurrent()
+
+            // THEN legacy shade expansion is passed through
+            assertThat(actual).isEqualTo(.7f)
+        }
+
+    @Test
+    fun shadeExpansionWhenNotInSplitShadeAndQsExpanded() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.shadeExpansion)
+
+            // WHEN split shade is not enabled and QS is expanded
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            overrideResource(R.bool.config_use_split_notification_shade, false)
+            shadeRepository.setQsExpansion(.5f)
+            shadeRepository.setLegacyShadeExpansion(1f)
+            runCurrent()
+
+            // THEN shade expansion is zero
+            assertThat(actual).isEqualTo(0f)
+        }
+
+    @Test
+    fun shadeExpansionWhenNotInSplitShadeAndQsCollapsed() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.shadeExpansion)
+
+            // WHEN split shade is not enabled and QS is expanded
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            shadeRepository.setQsExpansion(0f)
+            shadeRepository.setLegacyShadeExpansion(.6f)
+
+            // THEN shade expansion is zero
+            assertThat(actual).isEqualTo(.6f)
+        }
+
+    @Test
+    fun userInteractingWithShade_shadeDraggedUpAndDown() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isUserInteractingWithShade)
+            // GIVEN shade collapsed and not tracking input
+            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+
+            // WHEN shade tracking starts
+            shadeRepository.setLegacyShadeTracking(true)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade dragged down halfway
+            shadeRepository.setLegacyShadeExpansion(.5f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade fully expanded but tracking is not stopped
+            shadeRepository.setLegacyShadeExpansion(1f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade fully collapsed but tracking is not stopped
+            shadeRepository.setLegacyShadeExpansion(0f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade dragged halfway and tracking is stopped
+            shadeRepository.setLegacyShadeExpansion(.6f)
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade completes expansion stopped
+            shadeRepository.setLegacyShadeExpansion(1f)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun userInteractingWithShade_shadeExpanded() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isUserInteractingWithShade)
+            // GIVEN shade collapsed and not tracking input
+            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+
+            // WHEN shade tracking starts
+            shadeRepository.setLegacyShadeTracking(true)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade dragged down halfway
+            shadeRepository.setLegacyShadeExpansion(.5f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade fully expanded and tracking is stopped
+            shadeRepository.setLegacyShadeExpansion(1f)
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun userInteractingWithShade_shadePartiallyExpanded() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isUserInteractingWithShade)
+            // GIVEN shade collapsed and not tracking input
+            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+
+            // WHEN shade tracking starts
+            shadeRepository.setLegacyShadeTracking(true)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade partially expanded
+            shadeRepository.setLegacyShadeExpansion(.4f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN tracking is stopped
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade goes back to collapsed
+            shadeRepository.setLegacyShadeExpansion(0f)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun userInteractingWithShade_shadeCollapsed() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isUserInteractingWithShade)
+            // GIVEN shade expanded and not tracking input
+            shadeRepository.setLegacyShadeExpansion(1f)
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+
+            // WHEN shade tracking starts
+            shadeRepository.setLegacyShadeTracking(true)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade dragged up halfway
+            shadeRepository.setLegacyShadeExpansion(.5f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade fully collapsed and tracking is stopped
+            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun userInteractingWithQs_qsDraggedUpAndDown() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isUserInteractingWithQs)
+            // GIVEN qs collapsed and not tracking input
+            shadeRepository.setQsExpansion(0f)
+            shadeRepository.setLegacyQsTracking(false)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+
+            // WHEN qs tracking starts
+            shadeRepository.setLegacyQsTracking(true)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN qs dragged down halfway
+            shadeRepository.setQsExpansion(.5f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN qs fully expanded but tracking is not stopped
+            shadeRepository.setQsExpansion(1f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN qs fully collapsed but tracking is not stopped
+            shadeRepository.setQsExpansion(0f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN qs dragged halfway and tracking is stopped
+            shadeRepository.setQsExpansion(.6f)
+            shadeRepository.setLegacyQsTracking(false)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN qs completes expansion stopped
+            shadeRepository.setQsExpansion(1f)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
new file mode 100644
index 0000000..bf136cd
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
@@ -0,0 +1,560 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.userRepository
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadeInteractorSceneContainerImplTest : SysuiTestCase() {
+
+    val kosmos = testKosmos()
+    val testComponent = kosmos.testScope
+    val configurationRepository = kosmos.fakeConfigurationRepository
+    val keyguardRepository = kosmos.fakeKeyguardRepository
+    val keyguardTransitionRepository = kosmos.keyguardTransitionRepository
+    val sceneInteractor = kosmos.sceneInteractor
+    val userRepository = kosmos.userRepository
+
+    val underTest = kosmos.shadeInteractorSceneContainerImpl
+
+    @Test
+    fun qsExpansionWhenInSplitShadeAndQsExpanded() =
+        testComponent.runTest {
+            val actual by collectLastValue(underTest.qsExpansion)
+
+            // WHEN split shade is enabled and QS is expanded
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            configurationRepository.onAnyConfigurationChange()
+            runCurrent()
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = SceneKey.QuickSettings,
+                        toScene = SceneKey.Shade,
+                        progress = MutableStateFlow(.3f),
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+
+            // THEN legacy shade expansion is passed through
+            Truth.assertThat(actual).isEqualTo(.3f)
+        }
+
+    @Test
+    fun qsExpansionWhenNotInSplitShadeAndQsExpanded() =
+        testComponent.runTest {
+            val actual by collectLastValue(underTest.qsExpansion)
+
+            // WHEN split shade is not enabled and QS is expanded
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            overrideResource(R.bool.config_use_split_notification_shade, false)
+            configurationRepository.onAnyConfigurationChange()
+            runCurrent()
+            val progress = MutableStateFlow(.3f)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = SceneKey.QuickSettings,
+                        toScene = SceneKey.Shade,
+                        progress = progress,
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // THEN shade expansion is zero
+            Truth.assertThat(actual).isEqualTo(.7f)
+        }
+
+    @Test
+    fun qsFullscreen_falseWhenTransitioning() =
+        testComponent.runTest {
+            val actual by collectLastValue(underTest.isQsFullscreen)
+
+            // WHEN scene transition active
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = SceneKey.QuickSettings,
+                        toScene = SceneKey.Shade,
+                        progress = MutableStateFlow(.3f),
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // THEN QS is not fullscreen
+            Truth.assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun qsFullscreen_falseWhenIdleNotQS() =
+        testComponent.runTest {
+            val actual by collectLastValue(underTest.isQsFullscreen)
+
+            // WHEN Idle but not on QuickSettings scene
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(SceneKey.Shade)
+                )
+            sceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // THEN QS is not fullscreen
+            Truth.assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun qsFullscreen_trueWhenIdleQS() =
+        testComponent.runTest {
+            val actual by collectLastValue(underTest.isQsFullscreen)
+
+            // WHEN Idle on QuickSettings scene
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(SceneKey.QuickSettings)
+                )
+            sceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // THEN QS is fullscreen
+            Truth.assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun lockscreenShadeExpansion_idle_onScene() =
+        testComponent.runTest {
+            // GIVEN an expansion flow based on transitions to and from a scene
+            val key = SceneKey.Shade
+            val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
+            val expansionAmount by collectLastValue(expansion)
+
+            // WHEN transition state is idle on the scene
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+            sceneInteractor.setTransitionState(transitionState)
+
+            // THEN expansion is 1
+            Truth.assertThat(expansionAmount).isEqualTo(1f)
+        }
+
+    @Test
+    fun lockscreenShadeExpansion_idle_onDifferentScene() =
+        testComponent.runTest {
+            // GIVEN an expansion flow based on transitions to and from a scene
+            val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.Shade)
+            val expansionAmount by collectLastValue(expansion)
+
+            // WHEN transition state is idle on a different scene
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(SceneKey.Lockscreen)
+                )
+            sceneInteractor.setTransitionState(transitionState)
+
+            // THEN expansion is 0
+            Truth.assertThat(expansionAmount).isEqualTo(0f)
+        }
+
+    @Test
+    fun lockscreenShadeExpansion_transitioning_toScene() =
+        testComponent.runTest {
+            // GIVEN an expansion flow based on transitions to and from a scene
+            val key = SceneKey.QuickSettings
+            val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
+            val expansionAmount by collectLastValue(expansion)
+
+            // WHEN transition state is starting to move to the scene
+            val progress = MutableStateFlow(0f)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = SceneKey.Lockscreen,
+                        toScene = key,
+                        progress = progress,
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+
+            // THEN expansion is 0
+            Truth.assertThat(expansionAmount).isEqualTo(0f)
+
+            // WHEN transition state is partially to the scene
+            progress.value = .4f
+
+            // THEN expansion matches the progress
+            Truth.assertThat(expansionAmount).isEqualTo(.4f)
+
+            // WHEN transition completes
+            progress.value = 1f
+
+            // THEN expansion is 1
+            Truth.assertThat(expansionAmount).isEqualTo(1f)
+        }
+
+    @Test
+    fun lockscreenShadeExpansion_transitioning_fromScene() =
+        testComponent.runTest {
+            // GIVEN an expansion flow based on transitions to and from a scene
+            val key = SceneKey.QuickSettings
+            val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
+            val expansionAmount by collectLastValue(expansion)
+
+            // WHEN transition state is starting to move to the scene
+            val progress = MutableStateFlow(0f)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = key,
+                        toScene = SceneKey.Lockscreen,
+                        progress = progress,
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+
+            // THEN expansion is 1
+            Truth.assertThat(expansionAmount).isEqualTo(1f)
+
+            // WHEN transition state is partially to the scene
+            progress.value = .4f
+
+            // THEN expansion reflects the progress
+            Truth.assertThat(expansionAmount).isEqualTo(.6f)
+
+            // WHEN transition completes
+            progress.value = 1f
+
+            // THEN expansion is 0
+            Truth.assertThat(expansionAmount).isEqualTo(0f)
+        }
+
+    fun isQsBypassingShade_goneToQs() =
+        testComponent.runTest {
+            val actual by collectLastValue(underTest.isQsBypassingShade)
+
+            // WHEN transitioning from QS directly to Gone
+            configurationRepository.onAnyConfigurationChange()
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = SceneKey.Gone,
+                        toScene = SceneKey.QuickSettings,
+                        progress = MutableStateFlow(.1f),
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // THEN qs is bypassing shade
+            Truth.assertThat(actual).isTrue()
+        }
+
+    fun isQsBypassingShade_shadeToQs() =
+        testComponent.runTest {
+            val actual by collectLastValue(underTest.isQsBypassingShade)
+
+            // WHEN transitioning from QS to Shade
+            configurationRepository.onAnyConfigurationChange()
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = SceneKey.Shade,
+                        toScene = SceneKey.QuickSettings,
+                        progress = MutableStateFlow(.1f),
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // THEN qs is not bypassing shade
+            Truth.assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun lockscreenShadeExpansion_transitioning_toAndFromDifferentScenes() =
+        testComponent.runTest {
+            // GIVEN an expansion flow based on transitions to and from a scene
+            val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.QuickSettings)
+            val expansionAmount by collectLastValue(expansion)
+
+            // WHEN transition state is starting to between different scenes
+            val progress = MutableStateFlow(0f)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = SceneKey.Lockscreen,
+                        toScene = SceneKey.Shade,
+                        progress = progress,
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+
+            // THEN expansion is 0
+            Truth.assertThat(expansionAmount).isEqualTo(0f)
+
+            // WHEN transition state is partially complete
+            progress.value = .4f
+
+            // THEN expansion is still 0
+            Truth.assertThat(expansionAmount).isEqualTo(0f)
+
+            // WHEN transition completes
+            progress.value = 1f
+
+            // THEN expansion is still 0
+            Truth.assertThat(expansionAmount).isEqualTo(0f)
+        }
+
+    @Test
+    fun userInteracting_idle() =
+        testComponent.runTest {
+            // GIVEN an interacting flow based on transitions to and from a scene
+            val key = SceneKey.Shade
+            val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
+            val interacting by collectLastValue(interactingFlow)
+
+            // WHEN transition state is idle
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+            sceneInteractor.setTransitionState(transitionState)
+
+            // THEN interacting is false
+            Truth.assertThat(interacting).isFalse()
+        }
+
+    @Test
+    fun userInteracting_transitioning_toScene_programmatic() =
+        testComponent.runTest {
+            // GIVEN an interacting flow based on transitions to and from a scene
+            val key = SceneKey.QuickSettings
+            val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
+            val interacting by collectLastValue(interactingFlow)
+
+            // WHEN transition state is starting to move to the scene
+            val progress = MutableStateFlow(0f)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = SceneKey.Lockscreen,
+                        toScene = key,
+                        progress = progress,
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+
+            // THEN interacting is false
+            Truth.assertThat(interacting).isFalse()
+
+            // WHEN transition state is partially to the scene
+            progress.value = .4f
+
+            // THEN interacting is false
+            Truth.assertThat(interacting).isFalse()
+
+            // WHEN transition completes
+            progress.value = 1f
+
+            // THEN interacting is false
+            Truth.assertThat(interacting).isFalse()
+        }
+
+    @Test
+    fun userInteracting_transitioning_toScene_userInputDriven() =
+        testComponent.runTest {
+            // GIVEN an interacting flow based on transitions to and from a scene
+            val key = SceneKey.QuickSettings
+            val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
+            val interacting by collectLastValue(interactingFlow)
+
+            // WHEN transition state is starting to move to the scene
+            val progress = MutableStateFlow(0f)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = SceneKey.Lockscreen,
+                        toScene = key,
+                        progress = progress,
+                        isInitiatedByUserInput = true,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+
+            // THEN interacting is true
+            Truth.assertThat(interacting).isTrue()
+
+            // WHEN transition state is partially to the scene
+            progress.value = .4f
+
+            // THEN interacting is true
+            Truth.assertThat(interacting).isTrue()
+
+            // WHEN transition completes
+            progress.value = 1f
+
+            // THEN interacting is true
+            Truth.assertThat(interacting).isTrue()
+        }
+
+    @Test
+    fun userInteracting_transitioning_fromScene_programmatic() =
+        testComponent.runTest {
+            // GIVEN an interacting flow based on transitions to and from a scene
+            val key = SceneKey.QuickSettings
+            val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
+            val interacting by collectLastValue(interactingFlow)
+
+            // WHEN transition state is starting to move to the scene
+            val progress = MutableStateFlow(0f)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = key,
+                        toScene = SceneKey.Lockscreen,
+                        progress = progress,
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+
+            // THEN interacting is false
+            Truth.assertThat(interacting).isFalse()
+
+            // WHEN transition state is partially to the scene
+            progress.value = .4f
+
+            // THEN interacting is false
+            Truth.assertThat(interacting).isFalse()
+
+            // WHEN transition completes
+            progress.value = 1f
+
+            // THEN interacting is false
+            Truth.assertThat(interacting).isFalse()
+        }
+
+    @Test
+    fun userInteracting_transitioning_fromScene_userInputDriven() =
+        testComponent.runTest {
+            // GIVEN an interacting flow based on transitions to and from a scene
+            val key = SceneKey.QuickSettings
+            val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
+            val interacting by collectLastValue(interactingFlow)
+
+            // WHEN transition state is starting to move to the scene
+            val progress = MutableStateFlow(0f)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = key,
+                        toScene = SceneKey.Lockscreen,
+                        progress = progress,
+                        isInitiatedByUserInput = true,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+
+            // THEN interacting is true
+            Truth.assertThat(interacting).isTrue()
+
+            // WHEN transition state is partially to the scene
+            progress.value = .4f
+
+            // THEN interacting is true
+            Truth.assertThat(interacting).isTrue()
+
+            // WHEN transition completes
+            progress.value = 1f
+
+            // THEN interacting is true
+            Truth.assertThat(interacting).isTrue()
+        }
+
+    @Test
+    fun userInteracting_transitioning_toAndFromDifferentScenes() =
+        testComponent.runTest {
+            // GIVEN an interacting flow based on transitions to and from a scene
+            val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, SceneKey.Shade)
+            val interacting by collectLastValue(interactingFlow)
+
+            // WHEN transition state is starting to between different scenes
+            val progress = MutableStateFlow(0f)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = SceneKey.Lockscreen,
+                        toScene = SceneKey.QuickSettings,
+                        progress = MutableStateFlow(0f),
+                        isInitiatedByUserInput = true,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+
+            // THEN interacting is false
+            Truth.assertThat(interacting).isFalse()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index 77ddf15..c0aaab3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -7,9 +7,8 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
-import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -18,10 +17,9 @@
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -31,9 +29,9 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class ShadeHeaderViewModelTest : SysuiTestCase() {
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val sceneInteractor = utils.sceneInteractor()
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
 
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
     private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
@@ -72,74 +70,6 @@
     }
 
     @Test
-    fun isTransitioning_idle_false() =
-        testScope.runTest {
-            val isTransitioning by collectLastValue(underTest.isTransitioning)
-            sceneInteractor.setTransitionState(
-                MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Shade))
-            )
-
-            assertThat(isTransitioning).isFalse()
-        }
-
-    @Test
-    fun isTransitioning_shadeToQs_true() =
-        testScope.runTest {
-            val isTransitioning by collectLastValue(underTest.isTransitioning)
-            sceneInteractor.setTransitionState(
-                MutableStateFlow(
-                    ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Shade,
-                        toScene = SceneKey.QuickSettings,
-                        progress = MutableStateFlow(0.5f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            )
-
-            assertThat(isTransitioning).isTrue()
-        }
-
-    @Test
-    fun isTransitioning_qsToShade_true() =
-        testScope.runTest {
-            val isTransitioning by collectLastValue(underTest.isTransitioning)
-            sceneInteractor.setTransitionState(
-                MutableStateFlow(
-                    ObservableTransitionState.Transition(
-                        fromScene = SceneKey.QuickSettings,
-                        toScene = SceneKey.Shade,
-                        progress = MutableStateFlow(0.5f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            )
-
-            assertThat(isTransitioning).isTrue()
-        }
-
-    @Test
-    fun isTransitioning_otherTransition_false() =
-        testScope.runTest {
-            val isTransitioning by collectLastValue(underTest.isTransitioning)
-            sceneInteractor.setTransitionState(
-                MutableStateFlow(
-                    ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Gone,
-                        toScene = SceneKey.Shade,
-                        progress = MutableStateFlow(0.5f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            )
-
-            assertThat(isTransitioning).isFalse()
-        }
-
-    @Test
     fun mobileSubIds_update() =
         testScope.runTest {
             val mobileSubIds by collectLastValue(underTest.mobileSubIds)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 1d2497d..5ef095f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -19,16 +19,20 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.controls.pipeline.MediaDataManager
-import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
@@ -36,6 +40,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -53,15 +58,10 @@
 @RunWith(AndroidJUnit4::class)
 class ShadeSceneViewModelTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val sceneInteractor = utils.sceneInteractor()
-    private val authenticationInteractor = utils.authenticationInteractor()
-    private val deviceEntryInteractor =
-        utils.deviceEntryInteractor(
-            authenticationInteractor = authenticationInteractor,
-            sceneInteractor = sceneInteractor,
-        )
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
 
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
     private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
@@ -89,7 +89,6 @@
     private lateinit var underTest: ShadeSceneViewModel
 
     @Mock private lateinit var mediaDataManager: MediaDataManager
-    @Mock private lateinit var mediaHost: MediaHost
 
     @Before
     fun setUp() {
@@ -110,9 +109,8 @@
                 deviceEntryInteractor = deviceEntryInteractor,
                 shadeHeaderViewModel = shadeHeaderViewModel,
                 qsSceneAdapter = qsFlexiglassAdapter,
-                notifications = utils.notificationsPlaceholderViewModel(),
+                notifications = kosmos.notificationsPlaceholderViewModel,
                 mediaDataManager = mediaDataManager,
-                mediaHost = mediaHost,
             )
     }
 
@@ -120,8 +118,10 @@
     fun upTransitionSceneKey_deviceLocked_lockScreen() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.deviceEntryRepository.setUnlocked(false)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeDeviceEntryRepository.setUnlocked(false)
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
         }
@@ -130,8 +130,10 @@
     fun upTransitionSceneKey_deviceUnlocked_gone() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
         }
@@ -140,8 +142,10 @@
     fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            utils.deviceEntryRepository.setLockscreenEnabled(true)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
             sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
 
@@ -152,8 +156,12 @@
     fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            utils.deviceEntryRepository.setLockscreenEnabled(true)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
+            runCurrent()
             sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason")
 
@@ -164,8 +172,10 @@
     fun onContentClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.deviceEntryRepository.setUnlocked(true)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeDeviceEntryRepository.setUnlocked(true)
             runCurrent()
 
             underTest.onContentClicked()
@@ -177,8 +187,10 @@
     fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.deviceEntryRepository.setUnlocked(false)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+            kosmos.fakeDeviceEntryRepository.setUnlocked(false)
             runCurrent()
 
             underTest.onContentClicked()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
index ef2046d..ad4b98b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
@@ -69,7 +69,9 @@
     private lateinit var controller: CommunalSmartspaceController
 
     // TODO(b/272811280): Remove usage of real view
-    private val fakeParent = FrameLayout(context)
+    private val fakeParent by lazy {
+        FrameLayout(context)
+    }
 
     /**
      * A class which implements SmartspaceView and extends View. This is mocked to provide the right
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index e093859..3a38631 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -73,8 +73,9 @@
     @Mock
     private lateinit var weatherViewComponent: SmartspaceViewComponent
 
-    @Spy
-    private var weatherSmartspaceView: SmartspaceView = TestView(context)
+    private val weatherSmartspaceView: SmartspaceView by lazy {
+        Mockito.spy(TestView(context))
+    }
 
     @Mock
     private lateinit var targetFilter: SmartspaceTargetFilter
@@ -88,8 +89,9 @@
     @Mock
     private lateinit var precondition: SmartspacePrecondition
 
-    @Spy
-    private var smartspaceView: SmartspaceView = TestView(context)
+    private val smartspaceView: SmartspaceView by lazy {
+        Mockito.spy(TestView(context))
+    }
 
     @Mock
     private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener
@@ -100,7 +102,9 @@
     private lateinit var controller: DreamSmartspaceController
 
     // TODO(b/272811280): Remove usage of real view
-    private val fakeParent = FrameLayout(context)
+    private val fakeParent by lazy {
+        FrameLayout(context)
+    }
 
     /**
      * A class which implements SmartspaceView and extends View. This is mocked to provide the right
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java
new file mode 100644
index 0000000..d0e05fa
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java
@@ -0,0 +1,614 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
+import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.KeyguardManager;
+import android.app.Notification;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.SparseArray;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.settings.FakeSettings;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NotificationLockscreenUserManagerMainThreadTest extends SysuiTestCase {
+    @Mock
+    private NotificationPresenter mPresenter;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private UserTracker mUserTracker;
+
+    // Dependency mocks:
+    @Mock
+    private NotificationVisibilityProvider mVisibilityProvider;
+    @Mock
+    private CommonNotifCollection mNotifCollection;
+    @Mock
+    private DevicePolicyManager mDevicePolicyManager;
+    @Mock
+    private NotificationClickNotifier mClickNotifier;
+    @Mock
+    private OverviewProxyService mOverviewProxyService;
+    @Mock
+    private KeyguardManager mKeyguardManager;
+    @Mock
+    private DeviceProvisionedController mDeviceProvisionedController;
+    @Mock
+    private StatusBarStateController mStatusBarStateController;
+    @Mock
+    private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    private KeyguardStateController mKeyguardStateController;
+
+    private UserInfo mCurrentUser;
+    private UserInfo mSecondaryUser;
+    private UserInfo mWorkUser;
+    private UserInfo mCommunalUser;
+    private FakeSettings mSettings;
+    private TestNotificationLockscreenUserManager mLockscreenUserManager;
+    private NotificationEntry mCurrentUserNotif;
+    private NotificationEntry mSecondaryUserNotif;
+    private NotificationEntry mWorkProfileNotif;
+    private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic();
+    private Executor mMainExecutor = Runnable::run; // Direct executor
+    private Executor mBackgroundExecutor = Runnable::run; // Direct executor
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
+
+        int currentUserId = ActivityManager.getCurrentUser();
+        when(mUserTracker.getUserId()).thenReturn(currentUserId);
+        mSettings = new FakeSettings();
+        mSettings.setUserId(ActivityManager.getCurrentUser());
+        mCurrentUser = new UserInfo(currentUserId, "", 0);
+        mSecondaryUser = new UserInfo(currentUserId + 1, "", 0);
+        mWorkUser = new UserInfo(currentUserId + 2, "" /* name */, null /* iconPath */, 0,
+                UserManager.USER_TYPE_PROFILE_MANAGED);
+        mCommunalUser = new UserInfo(currentUserId + 3, "" /* name */, null /* iconPath */, 0,
+                UserManager.USER_TYPE_PROFILE_COMMUNAL);
+
+        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true);
+        when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList(
+                mCurrentUser, mWorkUser));
+        when(mUserManager.getProfilesIncludingCommunal(currentUserId)).thenReturn(
+                Lists.newArrayList(mCurrentUser, mWorkUser, mCommunalUser));
+        when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList(
+                mSecondaryUser));
+        when(mUserManager.getProfilesIncludingCommunal(mSecondaryUser.id)).thenReturn(
+                Lists.newArrayList(mSecondaryUser, mCommunalUser));
+
+        Notification notifWithPrivateVisibility = new Notification();
+        notifWithPrivateVisibility.visibility = Notification.VISIBILITY_PRIVATE;
+        mCurrentUserNotif = new NotificationEntryBuilder()
+                .setNotification(notifWithPrivateVisibility)
+                .setUser(new UserHandle(mCurrentUser.id))
+                .build();
+        mSecondaryUserNotif = new NotificationEntryBuilder()
+                .setNotification(notifWithPrivateVisibility)
+                .setUser(new UserHandle(mSecondaryUser.id))
+                .build();
+        mWorkProfileNotif = new NotificationEntryBuilder()
+                .setNotification(notifWithPrivateVisibility)
+                .setUser(new UserHandle(mWorkUser.id))
+                .build();
+
+        mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
+        mLockscreenUserManager.setUpWithPresenter(mPresenter);
+    }
+
+    private void changeSetting(String setting) {
+        final Collection<Uri> lockScreenUris = new ArrayList<>();
+        lockScreenUris.add(Settings.Secure.getUriFor(setting));
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false,
+            lockScreenUris, 0);
+    }
+
+    @Test
+    public void testGetCurrentProfiles() {
+        final SparseArray<UserInfo> expectedCurProfiles = new SparseArray<>();
+        expectedCurProfiles.put(mCurrentUser.id, mCurrentUser);
+        expectedCurProfiles.put(mWorkUser.id, mWorkUser);
+        if (android.multiuser.Flags.supportCommunalProfile()) {
+            expectedCurProfiles.put(mCommunalUser.id, mCommunalUser);
+        }
+        assertTrue(mLockscreenUserManager.getCurrentProfiles().contentEquals(expectedCurProfiles));
+
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+
+        final SparseArray<UserInfo> expectedSecProfiles = new SparseArray<>();
+        expectedSecProfiles.put(mSecondaryUser.id, mSecondaryUser);
+        if (android.multiuser.Flags.supportCommunalProfile()) {
+            expectedSecProfiles.put(mCommunalUser.id, mCommunalUser);
+        }
+        assertTrue(mLockscreenUserManager.getCurrentProfiles().contentEquals(expectedSecProfiles));
+    }
+
+    @Test
+    public void testLockScreenShowNotificationsFalse() {
+        mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+        assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
+    }
+
+    @Test
+    public void testLockScreenShowNotificationsTrue() {
+        mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+        assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
+    }
+
+    @Test
+    public void testLockScreenAllowPrivateNotificationsTrue() {
+        mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testLockScreenAllowPrivateNotificationsFalse() {
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testLockScreenAllowsWorkPrivateNotificationsFalse() {
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mWorkUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
+    }
+
+    @Test
+    public void testLockScreenAllowsWorkPrivateNotificationsTrue() {
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mWorkUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
+    }
+
+    @Test
+    public void testCurrentUserPrivateNotificationsNotRedacted() {
+        // GIVEN current user doesn't allow private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN current user's notification is redacted
+        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+    }
+
+    @Test
+    public void testCurrentUserPrivateNotificationsRedacted() {
+        // GIVEN current user allows private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN current user's notification isn't redacted
+        assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+    }
+
+    @Test
+    public void testWorkPrivateNotificationsRedacted() {
+        // GIVEN work profile doesn't private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mWorkUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN work profile notification is redacted
+        assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+        assertFalse(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
+    }
+
+    @Test
+    public void testWorkPrivateNotificationsNotRedacted() {
+        // GIVEN work profile allows private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mWorkUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN work profile notification isn't redacted
+        assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+        assertTrue(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
+    }
+
+    @Test
+    public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() {
+        // GIVEN work profile allows private notifications to show but the other users don't
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mWorkUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mCurrentUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN the work profile notification doesn't need to be redacted
+        assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+
+        // THEN the current user and secondary user notifications do need to be redacted
+        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+    }
+
+    @Test
+    public void testWorkProfileRedacted_otherUsersNotRedacted() {
+        // GIVEN work profile doesn't allow private notifications to show but the other users do
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mWorkUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mCurrentUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN the work profile notification needs to be redacted
+        assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+
+        // THEN the current user and secondary user notifications don't need to be redacted
+        assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+        assertFalse(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+    }
+
+    @Test
+    public void testSecondaryUserNotRedacted_currentUserRedacted() {
+        // GIVEN secondary profile allows private notifications to show but the current user
+        // doesn't allow private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mCurrentUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN the secondary profile notification still needs to be redacted because the current
+        // user's setting takes precedence
+        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+    }
+
+    @Test
+    public void testUserSwitchedCallsOnUserSwitching() {
+        mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanging(mSecondaryUser.id,
+                mContext);
+        verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id);
+    }
+
+    @Test
+    public void testIsLockscreenPublicMode() {
+        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
+        mLockscreenUserManager.setLockscreenPublicMode(true, mCurrentUser.id);
+        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
+    }
+
+    @Test
+    public void testUpdateIsPublicMode() {
+        when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
+
+        NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class);
+        mLockscreenUserManager.addNotificationStateChangedListener(listener);
+        mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+
+        // first call explicitly sets user 0 to not public; notifies
+        mLockscreenUserManager.updatePublicMode();
+        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
+        verify(listener).onNotificationStateChanged();
+        clearInvocations(listener);
+
+        // calling again has no changes; does not notify
+        mLockscreenUserManager.updatePublicMode();
+        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
+        verify(listener, never()).onNotificationStateChanged();
+
+        // Calling again with keyguard now showing makes user 0 public; notifies
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        mLockscreenUserManager.updatePublicMode();
+        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
+        verify(listener).onNotificationStateChanged();
+        clearInvocations(listener);
+
+        // calling again has no changes; does not notify
+        mLockscreenUserManager.updatePublicMode();
+        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
+        verify(listener, never()).onNotificationStateChanged();
+    }
+
+    @Test
+    public void testDevicePolicyDoesNotAllowNotifications() {
+        // User allows them
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides notifs on lockscreen
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testDevicePolicyDoesNotAllowNotifications_secondary() {
+        Mockito.clearInvocations(mDevicePolicyManager);
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides notifications
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
+                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+    }
+
+    @Test
+    public void testDevicePolicy_noPrivateNotifications() {
+        Mockito.clearInvocations(mDevicePolicyManager);
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides sensitive content
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+    }
+
+    @Test
+    public void testDevicePolicy_noPrivateNotifications_userAll() {
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides sensitive content
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder()
+                .setNotification(new Notification())
+                .setUser(UserHandle.ALL)
+                .build()));
+    }
+
+    @Test
+    public void testDevicePolicyPrivateNotifications_secondary() {
+        Mockito.clearInvocations(mDevicePolicyManager);
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides sensitive content
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
+                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+    }
+
+    @Test
+    public void testHideNotifications_primary() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testHideNotifications_secondary() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+    }
+
+    @Test
+    public void testHideNotifications_secondary_userSwitch() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+    }
+
+    @Test
+    public void testShowNotifications_secondary_userSwitch() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+
+        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+    }
+
+    @Test
+    public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() {
+        // DevicePolicy allows notifications
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(0);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        // KeyguardManager does not allow notifications
+        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
+
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no
+        // callback, so it's only updated when the setting is
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testUserAllowsNotificationsInPublic_settingsChange() {
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+
+        // User disables
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    private class TestNotificationLockscreenUserManager
+            extends NotificationLockscreenUserManagerImpl {
+        public TestNotificationLockscreenUserManager(Context context) {
+            super(
+                    context,
+                    mBroadcastDispatcher,
+                    mDevicePolicyManager,
+                    mUserManager,
+                    mUserTracker,
+                    (() -> mVisibilityProvider),
+                    (() -> mNotifCollection),
+                    mClickNotifier,
+                    (() -> mOverviewProxyService),
+                    NotificationLockscreenUserManagerMainThreadTest.this.mKeyguardManager,
+                    mStatusBarStateController,
+                    mMainExecutor,
+                    mBackgroundExecutor,
+                    mDeviceProvisionedController,
+                    mKeyguardStateController,
+                    mSettings,
+                    mock(DumpManager.class),
+                    mock(LockPatternUtils.class),
+                    mFakeFeatureFlags);
+        }
+
+        public BroadcastReceiver getBaseBroadcastReceiverForTest() {
+            return mBaseBroadcastReceiver;
+        }
+
+        public UserTracker.Callback getUserTrackerCallbackForTest() {
+            return mUserChangedCallback;
+        }
+
+        public ContentObserver getLockscreenSettingsObserverForTest() {
+            return mLockscreenSettingsObserver;
+        }
+
+        public ContentObserver getSettingsObserverForTest() {
+            return mSettingsObserver;
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
new file mode 100644
index 0000000..bcc0710
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -0,0 +1,975 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import static android.app.Notification.VISIBILITY_PRIVATE;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
+import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
+import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
+import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS;
+import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
+import static android.os.UserHandle.USER_ALL;
+import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
+import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.KeyguardManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
+import android.provider.Settings;
+import android.util.SparseArray;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.log.LogWtfHandlerRule;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import com.google.android.collect.Lists;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4.class)
+public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
+
+    @Parameters(name = "{0}")
+    public static List<FlagsParameterization> getParams() {
+        return FlagsParameterization.allCombinationsOf(
+                FLAG_ALLOW_PRIVATE_PROFILE,
+                FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS);
+    }
+
+    public NotificationLockscreenUserManagerTest(FlagsParameterization flags) {
+        mSetFlagsRule.setFlagsParameterization(flags);
+    }
+
+    private static final int TEST_PROFILE_USERHANDLE = 12;
+    @Mock
+    private NotificationPresenter mPresenter;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private UserTracker mUserTracker;
+
+    // Dependency mocks:
+    @Mock
+    private NotificationVisibilityProvider mVisibilityProvider;
+    @Mock
+    private CommonNotifCollection mNotifCollection;
+    @Mock
+    private DevicePolicyManager mDevicePolicyManager;
+    @Mock
+    private NotificationClickNotifier mClickNotifier;
+    @Mock
+    private OverviewProxyService mOverviewProxyService;
+    @Mock
+    private KeyguardManager mKeyguardManager;
+    @Mock
+    private DeviceProvisionedController mDeviceProvisionedController;
+    @Mock
+    private StatusBarStateController mStatusBarStateController;
+    @Mock
+    private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    private KeyguardStateController mKeyguardStateController;
+
+    private UserInfo mCurrentUser;
+    private UserInfo mSecondaryUser;
+    private UserInfo mWorkUser;
+    private UserInfo mCommunalUser;
+    private FakeSettings mSettings;
+    private TestNotificationLockscreenUserManager mLockscreenUserManager;
+    private NotificationEntry mCurrentUserNotif;
+    private NotificationEntry mSecondaryUserNotif;
+    private NotificationEntry mWorkProfileNotif;
+    private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic();
+    private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+    private final FakeExecutor mBackgroundExecutor = new FakeExecutor(mFakeSystemClock);
+    private final Executor mMainExecutor = Runnable::run; // Direct executor
+
+    @Rule public final LogWtfHandlerRule wtfHandlerRule = new LogWtfHandlerRule();
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
+
+        int currentUserId = ActivityManager.getCurrentUser();
+        when(mUserTracker.getUserId()).thenReturn(currentUserId);
+        mSettings = new FakeSettings();
+        mSettings.setUserId(ActivityManager.getCurrentUser());
+        mCurrentUser = new UserInfo(currentUserId, "", 0);
+        mSecondaryUser = new UserInfo(currentUserId + 1, "", 0);
+        mWorkUser = new UserInfo(currentUserId + 2, "" /* name */, null /* iconPath */, 0,
+                UserManager.USER_TYPE_PROFILE_MANAGED);
+        mCommunalUser = new UserInfo(currentUserId + 3, "" /* name */, null /* iconPath */, 0,
+                UserManager.USER_TYPE_PROFILE_COMMUNAL);
+
+        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true);
+        when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList(
+                mCurrentUser, mWorkUser));
+        when(mUserManager.getProfilesIncludingCommunal(currentUserId)).thenReturn(
+                Lists.newArrayList(mCurrentUser, mWorkUser, mCommunalUser));
+        when(mUserManager.getUsers()).thenReturn(Lists.newArrayList(
+                mCurrentUser, mWorkUser, mSecondaryUser, mCommunalUser));
+        when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList(
+                mSecondaryUser));
+        when(mUserManager.getProfilesIncludingCommunal(mSecondaryUser.id)).thenReturn(
+                Lists.newArrayList(mSecondaryUser, mCommunalUser));
+
+        Notification notifWithPrivateVisibility = new Notification();
+        notifWithPrivateVisibility.visibility = VISIBILITY_PRIVATE;
+        mCurrentUserNotif = new NotificationEntryBuilder()
+                .setNotification(notifWithPrivateVisibility)
+                .setUser(new UserHandle(mCurrentUser.id))
+                .build();
+        NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
+        channel.setLockscreenVisibility(VISIBILITY_NO_OVERRIDE);
+        mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking())
+                .setChannel(channel)
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+        when(mNotifCollection.getEntry(mCurrentUserNotif.getKey())).thenReturn(mCurrentUserNotif);
+        mSecondaryUserNotif = new NotificationEntryBuilder()
+                .setNotification(notifWithPrivateVisibility)
+                .setUser(new UserHandle(mSecondaryUser.id))
+                .build();
+        mSecondaryUserNotif.setRanking(new RankingBuilder(mSecondaryUserNotif.getRanking())
+                .setChannel(channel)
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+        when(mNotifCollection.getEntry(
+                mSecondaryUserNotif.getKey())).thenReturn(mSecondaryUserNotif);
+        mWorkProfileNotif = new NotificationEntryBuilder()
+                .setNotification(notifWithPrivateVisibility)
+                .setUser(new UserHandle(mWorkUser.id))
+                .build();
+        mWorkProfileNotif.setRanking(new RankingBuilder(mWorkProfileNotif.getRanking())
+                .setChannel(channel)
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+        when(mNotifCollection.getEntry(mWorkProfileNotif.getKey())).thenReturn(mWorkProfileNotif);
+
+        mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
+        mLockscreenUserManager.setUpWithPresenter(mPresenter);
+
+        mBackgroundExecutor.runAllReady();
+    }
+
+    @After
+    public void tearDown() {
+        // Validate that all tests processed all background posted code
+        assertEquals(0, mBackgroundExecutor.numPending());
+    }
+
+    private void changeSetting(String setting) {
+        final Collection<Uri> lockScreenUris = new ArrayList<>();
+        lockScreenUris.add(Settings.Secure.getUriFor(setting));
+        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false,
+            lockScreenUris, 0);
+    }
+
+    @Test
+    @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
+    public void testInit() {
+        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
+        mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
+        mLockscreenUserManager.setUpWithPresenter(mPresenter);
+
+        mBackgroundExecutor.runAllReady();
+
+        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+    }
+
+    @Test
+    public void testGetCurrentProfiles() {
+        final SparseArray<UserInfo> expectedCurProfiles = new SparseArray<>();
+        expectedCurProfiles.put(mCurrentUser.id, mCurrentUser);
+        expectedCurProfiles.put(mWorkUser.id, mWorkUser);
+        if (android.multiuser.Flags.supportCommunalProfile()) {
+            expectedCurProfiles.put(mCommunalUser.id, mCommunalUser);
+        }
+        assertTrue(mLockscreenUserManager.getCurrentProfiles().contentEquals(expectedCurProfiles));
+
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+
+        final SparseArray<UserInfo> expectedSecProfiles = new SparseArray<>();
+        expectedSecProfiles.put(mSecondaryUser.id, mSecondaryUser);
+        if (android.multiuser.Flags.supportCommunalProfile()) {
+            expectedSecProfiles.put(mCommunalUser.id, mCommunalUser);
+        }
+        assertTrue(mLockscreenUserManager.getCurrentProfiles().contentEquals(expectedSecProfiles));
+    }
+
+    @Test
+    public void testLockScreenShowNotificationsFalse() {
+        mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+        assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
+    }
+
+    @Test
+    public void testLockScreenShowNotificationsTrue() {
+        mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+        assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
+    }
+
+    @Test
+    public void testLockScreenAllowPrivateNotificationsTrue() {
+        mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testLockScreenAllowPrivateNotificationsFalse() {
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testLockScreenAllowsWorkPrivateNotificationsFalse() {
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mWorkUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
+    }
+
+    @Test
+    public void testLockScreenAllowsWorkPrivateNotificationsTrue() {
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mWorkUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
+    }
+
+    @Test
+    public void testCurrentUserPrivateNotificationsRedacted() {
+        // GIVEN current user doesn't allow private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN current user's notification is redacted
+        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+    }
+
+    @Test
+    public void testCurrentUserPrivateNotificationsNotRedacted() {
+        // GIVEN current user allows private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN current user's notification isn't redacted
+        assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+    }
+
+    @Test
+    public void testCurrentUserPrivateNotificationsRedactedChannel() {
+        // GIVEN current user allows private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // but doesn't allow it at the channel level
+        NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
+        channel.setLockscreenVisibility(VISIBILITY_PRIVATE);
+        mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking())
+                .setChannel(channel)
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+        // THEN the notification is redacted
+        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+    }
+
+    @Test
+    public void testCurrentUserPrivateNotificationsNullChannel() {
+        // GIVEN current user allows private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking())
+                .setChannel(null)
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+        // THEN the notification is not redacted
+        assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+    }
+
+    @Test
+    public void testWorkPrivateNotificationsRedacted() {
+        // GIVEN work profile doesn't private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mWorkUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN work profile notification is redacted
+        assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+        assertFalse(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
+    }
+
+    @Test
+    public void testWorkPrivateNotificationsNotRedacted() {
+        // GIVEN work profile allows private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mWorkUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN work profile notification isn't redacted
+        assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+        assertTrue(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
+    }
+
+    @Test
+    public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() {
+        // GIVEN work profile allows private notifications to show but the other users don't
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mWorkUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mCurrentUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN the work profile notification doesn't need to be redacted
+        assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+
+        // THEN the current user and secondary user notifications do need to be redacted
+        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+    }
+
+    @Test
+    public void testWorkProfileRedacted_otherUsersNotRedacted() {
+        // GIVEN work profile doesn't allow private notifications to show but the other users do
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mWorkUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mCurrentUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN the work profile notification needs to be redacted
+        assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+
+        // THEN the current user and secondary user notifications don't need to be redacted
+        assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+        assertFalse(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+    }
+
+    @Test
+    public void testSecondaryUserNotRedacted_currentUserRedacted() {
+        // GIVEN secondary profile allows private notifications to show but the current user
+        // doesn't allow private notifications to show
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+                mCurrentUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+                mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+        // THEN the secondary profile notification still needs to be redacted because the current
+        // user's setting takes precedence
+        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+    }
+
+    @Test
+    public void testUserSwitchedCallsOnUserSwitching() {
+        mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanging(mSecondaryUser.id,
+                mContext);
+        verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id);
+    }
+
+    @Test
+    public void testIsLockscreenPublicMode() {
+        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
+        mLockscreenUserManager.setLockscreenPublicMode(true, mCurrentUser.id);
+        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
+    }
+
+    @Test
+    public void testUpdateIsPublicMode() {
+        when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
+
+        NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class);
+        mLockscreenUserManager.addNotificationStateChangedListener(listener);
+        mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+
+        // first call explicitly sets user 0 to not public; notifies
+        mLockscreenUserManager.updatePublicMode();
+        mBackgroundExecutor.runAllReady();
+        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
+        verify(listener).onNotificationStateChanged();
+        clearInvocations(listener);
+
+        // calling again has no changes; does not notify
+        mLockscreenUserManager.updatePublicMode();
+        mBackgroundExecutor.runAllReady();
+        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
+        verify(listener, never()).onNotificationStateChanged();
+
+        // Calling again with keyguard now showing makes user 0 public; notifies
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        mLockscreenUserManager.updatePublicMode();
+        mBackgroundExecutor.runAllReady();
+        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
+        verify(listener).onNotificationStateChanged();
+        clearInvocations(listener);
+
+        // calling again has no changes; does not notify
+        mLockscreenUserManager.updatePublicMode();
+        mBackgroundExecutor.runAllReady();
+        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
+        verify(listener, never()).onNotificationStateChanged();
+    }
+
+    @Test
+    public void testDevicePolicyDoesNotAllowNotifications() {
+        // User allows them
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides notifs on lockscreen
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testDevicePolicyDoesNotAllowNotifications_deviceOwnerSetsForUserAll() {
+        // User allows them
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides notifs on lockscreen
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
+                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, USER_ALL, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+    }
+
+    @Test
+    public void testDevicePolicyDoesNotAllowNotifications_userAll() {
+        // User allows them
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides notifications
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(USER_ALL));
+    }
+
+    @Test
+    public void testDevicePolicyDoesNotAllowNotifications_secondary() {
+        Mockito.clearInvocations(mDevicePolicyManager);
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides notifications
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
+                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+
+        verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
+    }
+
+    @Test
+    @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
+    public void testEarlyUserSwitch() {
+        mLockscreenUserManager =
+                new TestNotificationLockscreenUserManager(mContext);
+        mBackgroundExecutor.runAllReady();
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(
+                mCurrentUser.id, mContext);
+        // no crash!
+    }
+
+    @Test
+    @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
+    public void testKeyguardManager_noPrivateNotifications() {
+        Mockito.clearInvocations(mDevicePolicyManager);
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED)
+                        .putExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, true));
+
+        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+        // it's a global field, confirm secondary too
+        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
+        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
+                mSecondaryUser.id));
+    }
+
+    @Test
+    public void testDevicePolicy_noPrivateNotifications() {
+        Mockito.clearInvocations(mDevicePolicyManager);
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides sensitive content
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+
+        verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
+    }
+
+    @Test
+    public void testDevicePolicy_noPrivateNotifications_userAll() {
+        NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
+        channel.setLockscreenVisibility(VISIBILITY_NO_OVERRIDE);
+        NotificationEntry notifEntry = new NotificationEntryBuilder()
+                .setNotification(new Notification())
+                .setUser(UserHandle.ALL)
+                .build();
+        notifEntry.setRanking(new RankingBuilder(notifEntry.getRanking())
+                .setChannel(channel)
+                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+        when(mNotifCollection.getEntry(notifEntry.getKey())).thenReturn(notifEntry);
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides sensitive content
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertTrue(mLockscreenUserManager.needsRedaction(notifEntry));
+    }
+
+    @Test
+    public void testDevicePolicyPrivateNotifications_secondary() {
+        Mockito.clearInvocations(mDevicePolicyManager);
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        // DevicePolicy hides sensitive content
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
+                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+
+        verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
+    }
+
+    @Test
+    public void testHideNotifications_primary() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testHideNotifications_secondary() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+    }
+
+    @Test
+    public void testHideNotifications_workProfile() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mWorkUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mWorkUser.id));
+    }
+
+    @Test
+    public void testHideNotifications_secondary_userSwitch() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+    }
+
+    @Test
+    public void testShowNotifications_secondary_userSwitch() {
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+
+        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+    }
+
+    @Test
+    @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
+    public void testShouldShowLockscreenNotifications_keyguardManagerNoPrivateNotifications_show() {
+        // KeyguardManager does not allow notifications
+        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+        // DevicePolicy allows notifications
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(0);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mKeyguardReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mKeyguardReceiver.onReceive(mContext,
+                new Intent(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED)
+                        .putExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, false));
+
+        assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
+        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    @DisableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
+    public void testShouldShowLockscreenNotifications_keyguardManagerNoPrivateNotifications() {
+        // KeyguardManager does not allow notifications
+        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+        // DevicePolicy allows notifications
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(0);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
+    }
+
+    @Test
+    @DisableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
+    public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() {
+        // DevicePolicy allows notifications
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(0);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        // KeyguardManager does not allow notifications
+        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
+
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no
+        // callback, so it's only updated when the setting is
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testUserAllowsNotificationsInPublic_settingsChange() {
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+
+        // User disables
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testUserAllowsNotificationsInPublic_devicePolicyChange() {
+        // User allows notifications
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+
+        // DevicePolicy disables notifications
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+    }
+
+    @Test
+    public void testNewUserAdded() {
+        int newUserId = 14;
+        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, newUserId);
+        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, newUserId);
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, newUserId))
+                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+        BroadcastReceiver broadcastReceiver =
+                mLockscreenUserManager.getBaseBroadcastReceiverForTest();
+        final Bundle extras = new Bundle();
+        final Intent intent = new Intent(Intent.ACTION_USER_ADDED);
+        intent.putExtras(extras);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
+        broadcastReceiver.onReceive(mContext, intent);
+
+        // One background task to run which will setup the new user
+        assertEquals(1, mBackgroundExecutor.runAllReady());
+
+        verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), eq(newUserId));
+
+        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(newUserId));
+        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(newUserId));
+    }
+
+    @Test
+    @EnableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+    public void testProfileAvailabilityIntent() {
+        mLockscreenUserManager.mCurrentProfiles.clear();
+        assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
+        mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+        simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_AVAILABLE);
+        int numProfiles = android.multiuser.Flags.supportCommunalProfile() ? 3 : 2;
+        assertEquals(numProfiles, mLockscreenUserManager.mCurrentProfiles.size());
+    }
+
+    @Test
+    @EnableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+    public void testProfileUnAvailabilityIntent() {
+        mLockscreenUserManager.mCurrentProfiles.clear();
+        assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
+        mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+        simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_UNAVAILABLE);
+        int numProfiles = android.multiuser.Flags.supportCommunalProfile() ? 3 : 2;
+        assertEquals(numProfiles, mLockscreenUserManager.mCurrentProfiles.size());
+    }
+
+    @Test
+    @DisableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+    public void testManagedProfileAvailabilityIntent() {
+        mLockscreenUserManager.mCurrentProfiles.clear();
+        mLockscreenUserManager.mCurrentManagedProfiles.clear();
+        assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
+        assertEquals(0, mLockscreenUserManager.mCurrentManagedProfiles.size());
+        mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+        simulateProfileAvailabilityActions(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
+        int numProfiles = android.multiuser.Flags.supportCommunalProfile() ? 3 : 2;
+        assertEquals(numProfiles, mLockscreenUserManager.mCurrentProfiles.size());
+        assertEquals(1, mLockscreenUserManager.mCurrentManagedProfiles.size());
+    }
+
+    @Test
+    @DisableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+    public void testManagedProfileUnAvailabilityIntent() {
+        mLockscreenUserManager.mCurrentProfiles.clear();
+        mLockscreenUserManager.mCurrentManagedProfiles.clear();
+        assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
+        assertEquals(0, mLockscreenUserManager.mCurrentManagedProfiles.size());
+        mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+        simulateProfileAvailabilityActions(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+        int numProfiles = android.multiuser.Flags.supportCommunalProfile() ? 3 : 2;
+        assertEquals(numProfiles, mLockscreenUserManager.mCurrentProfiles.size());
+        assertEquals(1, mLockscreenUserManager.mCurrentManagedProfiles.size());
+    }
+
+    private void simulateProfileAvailabilityActions(String intentAction) {
+        BroadcastReceiver broadcastReceiver =
+                mLockscreenUserManager.getBaseBroadcastReceiverForTest();
+        final Intent intent = new Intent(intentAction);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, TEST_PROFILE_USERHANDLE);
+        broadcastReceiver.onReceive(mContext, intent);
+    }
+
+    private class TestNotificationLockscreenUserManager
+            extends NotificationLockscreenUserManagerImpl {
+        public TestNotificationLockscreenUserManager(Context context) {
+            super(
+                    context,
+                    mBroadcastDispatcher,
+                    mDevicePolicyManager,
+                    mUserManager,
+                    mUserTracker,
+                    (() -> mVisibilityProvider),
+                    (() -> mNotifCollection),
+                    mClickNotifier,
+                    (() -> mOverviewProxyService),
+                    NotificationLockscreenUserManagerTest.this.mKeyguardManager,
+                    mStatusBarStateController,
+                    mMainExecutor,
+                    mBackgroundExecutor,
+                    mDeviceProvisionedController,
+                    mKeyguardStateController,
+                    mSettings,
+                    mock(DumpManager.class),
+                    mock(LockPatternUtils.class),
+                    mFakeFeatureFlags);
+        }
+
+        public BroadcastReceiver getBaseBroadcastReceiverForTest() {
+            return mBaseBroadcastReceiver;
+        }
+
+        public UserTracker.Callback getUserTrackerCallbackForTest() {
+            return mUserChangedCallback;
+        }
+
+        public ContentObserver getLockscreenSettingsObserverForTest() {
+            return mLockscreenSettingsObserver;
+        }
+
+        public ContentObserver getSettingsObserverForTest() {
+            return mSettingsObserver;
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt
new file mode 100644
index 0000000..8a0400d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RemoteInputRepositoryImplTest : SysuiTestCase() {
+    @Mock private lateinit var remoteInputManager: NotificationRemoteInputManager
+
+    private lateinit var testScope: TestScope
+    private lateinit var underTest: RemoteInputRepositoryImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        testScope = TestScope()
+        underTest = RemoteInputRepositoryImpl(remoteInputManager)
+    }
+
+    @Test
+    fun isRemoteInputActive_updatesOnChange() =
+        testScope.runTest {
+            val active by collectLastValue(underTest.isRemoteInputActive)
+            runCurrent()
+            assertThat(active).isFalse()
+
+            val callback = withArgCaptor {
+                verify(remoteInputManager).addControllerCallback(capture())
+            }
+
+            callback.onRemoteInputActive(true)
+            runCurrent()
+            assertThat(active).isTrue()
+
+            callback.onRemoteInputActive(false)
+            runCurrent()
+            assertThat(active).isFalse()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt
new file mode 100644
index 0000000..12469dd
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.data.repository.fakeRemoteInputRepository
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RemoteInputInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val fakeRemoteInputRepository = kosmos.fakeRemoteInputRepository
+    private val underTest = kosmos.remoteInputInteractor
+
+    @Test
+    fun isRemoteInputActive_true() =
+        testScope.runTest {
+            val active by collectLastValue(underTest.isRemoteInputActive)
+
+            fakeRemoteInputRepository.isRemoteInputActive.value = true
+            runCurrent()
+
+            assertThat(active).isTrue()
+        }
+
+    @Test
+    fun isRemoteInputActive_false() =
+        testScope.runTest {
+            val active by collectLastValue(underTest.isRemoteInputActive)
+
+            fakeRemoteInputRepository.isRemoteInputActive.value = false
+            runCurrent()
+
+            assertThat(active).isFalse()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index 4cdb08a..6a2e317 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -27,8 +27,7 @@
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -50,16 +49,16 @@
 
     private val kosmos =
         testKosmos().apply {
-            sceneContainerFlags = FakeSceneContainerFlags(enabled = true)
+            fakeSceneContainerFlags.enabled = true
             fakeFeatureFlagsClassic.apply {
                 set(Flags.FULL_SCREEN_USER_SWITCHER, false)
                 set(Flags.NSSL_DEBUG_LINES, false)
             }
         }
     private val testScope = kosmos.testScope
-    private val placeholderViewModel = kosmos.notificationsPlaceholderViewModel
-    private val appearanceViewModel = kosmos.notificationStackAppearanceViewModel
-    private val sceneInteractor = kosmos.sceneInteractor
+    private val placeholderViewModel by lazy { kosmos.notificationsPlaceholderViewModel }
+    private val appearanceViewModel by lazy { kosmos.notificationStackAppearanceViewModel }
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
 
     @Test
     fun updateBounds() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
new file mode 100644
index 0000000..0c7ce97
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.render
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager.OnGroupExpansionChangeListener
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GroupExpansionManagerTest : SysuiTestCase() {
+    private lateinit var underTest: GroupExpansionManagerImpl
+
+    private val dumpManager: DumpManager = mock()
+    private val groupMembershipManager: GroupMembershipManager = mock()
+
+    private val pipeline: NotifPipeline = mock()
+    private lateinit var beforeRenderListListener: OnBeforeRenderListListener
+
+    private val summary1 = notificationEntry("foo", 1)
+    private val summary2 = notificationEntry("bar", 1)
+    private val entries =
+        listOf<ListEntry>(
+            GroupEntryBuilder()
+                .setSummary(summary1)
+                .setChildren(
+                    listOf(
+                        notificationEntry("foo", 2),
+                        notificationEntry("foo", 3),
+                        notificationEntry("foo", 4)
+                    )
+                )
+                .build(),
+            GroupEntryBuilder()
+                .setSummary(summary2)
+                .setChildren(
+                    listOf(
+                        notificationEntry("bar", 2),
+                        notificationEntry("bar", 3),
+                        notificationEntry("bar", 4)
+                    )
+                )
+                .build(),
+            notificationEntry("baz", 1)
+        )
+
+    private fun notificationEntry(pkg: String, id: Int) =
+        NotificationEntryBuilder().setPkg(pkg).setId(id).build().apply { row = mock() }
+
+    @Before
+    fun setUp() {
+        whenever(groupMembershipManager.getGroupSummary(summary1)).thenReturn(summary1)
+        whenever(groupMembershipManager.getGroupSummary(summary2)).thenReturn(summary2)
+
+        underTest = GroupExpansionManagerImpl(dumpManager, groupMembershipManager)
+    }
+
+    @Test
+    fun notifyOnlyOnChange() {
+        var listenerCalledCount = 0
+        underTest.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
+
+        underTest.setGroupExpanded(summary1, false)
+        assertThat(listenerCalledCount).isEqualTo(0)
+        underTest.setGroupExpanded(summary1, true)
+        assertThat(listenerCalledCount).isEqualTo(1)
+        underTest.setGroupExpanded(summary2, true)
+        assertThat(listenerCalledCount).isEqualTo(2)
+        underTest.setGroupExpanded(summary1, true)
+        assertThat(listenerCalledCount).isEqualTo(2)
+        underTest.setGroupExpanded(summary2, false)
+        assertThat(listenerCalledCount).isEqualTo(3)
+    }
+
+    @Test
+    fun expandUnattachedEntry() {
+        // First, expand the entry when it is attached.
+        underTest.setGroupExpanded(summary1, true)
+        assertThat(underTest.isGroupExpanded(summary1)).isTrue()
+
+        // Un-attach it, and un-expand it.
+        NotificationEntryBuilder.setNewParent(summary1, null)
+        underTest.setGroupExpanded(summary1, false)
+
+        // Expanding again should throw.
+        assertThrows(IllegalArgumentException::class.java) {
+            underTest.setGroupExpanded(summary1, true)
+        }
+    }
+
+    @Test
+    fun syncWithPipeline() {
+        underTest.attach(pipeline)
+        beforeRenderListListener = withArgCaptor {
+            verify(pipeline).addOnBeforeRenderListListener(capture())
+        }
+
+        val listener: OnGroupExpansionChangeListener = mock()
+        underTest.registerGroupExpansionChangeListener(listener)
+
+        beforeRenderListListener.onBeforeRenderList(entries)
+        verify(listener, never()).onGroupExpansionChange(any(), any())
+
+        // Expand one of the groups.
+        underTest.setGroupExpanded(summary1, true)
+        verify(listener).onGroupExpansionChange(summary1.row, true)
+
+        // Empty the pipeline list and verify that the group is no longer expanded.
+        beforeRenderListListener.onBeforeRenderList(emptyList())
+        verify(listener).onGroupExpansionChange(summary1.row, false)
+        verifyNoMoreInteractions(listener)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
new file mode 100644
index 0000000..2cbcc5a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.render
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GroupMembershipManagerTest : SysuiTestCase() {
+    private var underTest = GroupMembershipManagerImpl()
+
+    @Test
+    fun isChildInGroup_topLevel() {
+        val topLevelEntry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+        assertThat(underTest.isChildInGroup(topLevelEntry)).isFalse()
+    }
+
+    @Test
+    fun isChildInGroup_noParent() {
+        val noParentEntry = NotificationEntryBuilder().setParent(null).build()
+        assertThat(underTest.isChildInGroup(noParentEntry)).isFalse()
+    }
+
+    @Test
+    fun isChildInGroup_summary() {
+        val groupKey = "group"
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, groupKey)
+                .setGroupSummary(mContext, true)
+                .build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+        assertThat(underTest.isChildInGroup(summary)).isFalse()
+    }
+
+    @Test
+    fun isGroupSummary_topLevelEntry() {
+        val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+        assertThat(underTest.isGroupSummary(entry)).isFalse()
+    }
+
+    @Test
+    fun isGroupSummary_summary() {
+        val groupKey = "group"
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, groupKey)
+                .setGroupSummary(mContext, true)
+                .build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+        assertThat(underTest.isGroupSummary(summary)).isTrue()
+    }
+
+    @Test
+    fun isGroupSummary_child() {
+        val groupKey = "group"
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, groupKey)
+                .setGroupSummary(mContext, true)
+                .build()
+        val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
+
+        assertThat(underTest.isGroupSummary(entry)).isFalse()
+    }
+
+    @Test
+    fun getGroupSummary_topLevelEntry() {
+        val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+        assertThat(underTest.getGroupSummary(entry)).isNull()
+    }
+
+    @Test
+    fun getGroupSummary_summary() {
+        val groupKey = "group"
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, groupKey)
+                .setGroupSummary(mContext, true)
+                .build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+        assertThat(underTest.getGroupSummary(summary)).isEqualTo(summary)
+    }
+
+    @Test
+    fun getGroupSummary_child() {
+        val groupKey = "group"
+        val summary =
+            NotificationEntryBuilder()
+                .setGroup(mContext, groupKey)
+                .setGroupSummary(mContext, true)
+                .build()
+        val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
+        GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
+
+        assertThat(underTest.getGroupSummary(entry)).isEqualTo(summary)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
new file mode 100644
index 0000000..cc4ebd4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.os.RemoteException
+import android.os.UserHandle
+import android.view.View
+import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.LaunchableView
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.data.repository.ShadeAnimationRepository
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ActivityStarterImplTest : SysuiTestCase() {
+    @Mock private lateinit var centralSurfaces: CentralSurfaces
+    @Mock private lateinit var assistManager: AssistManager
+    @Mock private lateinit var dozeServiceHost: DozeServiceHost
+    @Mock private lateinit var biometricUnlockController: BiometricUnlockController
+    @Mock private lateinit var keyguardViewMediator: KeyguardViewMediator
+    @Mock private lateinit var shadeController: ShadeController
+    @Mock private lateinit var shadeViewController: ShadeViewController
+    @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+    @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
+    @Mock private lateinit var lockScreenUserManager: NotificationLockscreenUserManager
+    @Mock private lateinit var statusBarWindowController: StatusBarWindowController
+    @Mock private lateinit var notifShadeWindowController: NotificationShadeWindowController
+    @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var activityIntentHelper: ActivityIntentHelper
+    private lateinit var underTest: ActivityStarterImpl
+    private val mainExecutor = FakeExecutor(FakeSystemClock())
+    private val shadeAnimationInteractor =
+        ShadeAnimationInteractorLegacyImpl(ShadeAnimationRepository(), FakeShadeRepository())
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest =
+            ActivityStarterImpl(
+                Lazy { Optional.of(centralSurfaces) },
+                Lazy { assistManager },
+                Lazy { dozeServiceHost },
+                Lazy { biometricUnlockController },
+                Lazy { keyguardViewMediator },
+                Lazy { shadeController },
+                Lazy { shadeViewController },
+                shadeAnimationInteractor,
+                Lazy { statusBarKeyguardViewManager },
+                Lazy { notifShadeWindowController },
+                activityLaunchAnimator,
+                context,
+                DISPLAY_ID,
+                lockScreenUserManager,
+                statusBarWindowController,
+                wakefulnessLifecycle,
+                keyguardStateController,
+                statusBarStateController,
+                keyguardUpdateMonitor,
+                deviceProvisionedController,
+                userTracker,
+                activityIntentHelper,
+                mainExecutor,
+            )
+        whenever(userTracker.userHandle).thenReturn(UserHandle.OWNER)
+    }
+
+    @Test
+    fun startPendingIntentDismissingKeyguard_keyguardShowing_dismissWithAction() {
+        val pendingIntent = mock(PendingIntent::class.java)
+        whenever(pendingIntent.isActivity).thenReturn(true)
+        whenever(keyguardStateController.isShowing).thenReturn(true)
+        whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+
+        underTest.startPendingIntentDismissingKeyguard(pendingIntent)
+        mainExecutor.runAllReady()
+
+        verify(statusBarKeyguardViewManager)
+            .dismissWithAction(any(OnDismissAction::class.java), eq(null), anyBoolean(), eq(null))
+    }
+
+    @Test
+    fun startPendingIntentMaybeDismissingKeyguard_keyguardShowing_showOverLs_launchAnimator() {
+        val pendingIntent = mock(PendingIntent::class.java)
+        val parent = FrameLayout(context)
+        val view =
+            object : View(context), LaunchableView {
+                override fun setShouldBlockVisibilityChanges(block: Boolean) {}
+            }
+        parent.addView(view)
+        val controller = ActivityLaunchAnimator.Controller.fromView(view)
+        whenever(pendingIntent.isActivity).thenReturn(true)
+        whenever(keyguardStateController.isShowing).thenReturn(true)
+        whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+        whenever(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt()))
+            .thenReturn(true)
+
+        underTest.startPendingIntentMaybeDismissingKeyguard(
+            intent = pendingIntent,
+            animationController = controller,
+            intentSentUiThreadCallback = null,
+        )
+        mainExecutor.runAllReady()
+
+        verify(activityLaunchAnimator)
+            .startPendingIntentWithAnimation(
+                nullable(),
+                eq(true),
+                nullable(),
+                eq(true),
+                any(),
+            )
+    }
+
+    @Test
+    fun startPendingIntentDismissingKeyguard_associatedView_getAnimatorController() {
+        val pendingIntent = mock(PendingIntent::class.java)
+        val associatedView = mock(ExpandableNotificationRow::class.java)
+
+        underTest.startPendingIntentDismissingKeyguard(
+            intent = pendingIntent,
+            intentSentUiThreadCallback = null,
+            associatedView = associatedView,
+        )
+
+        verify(centralSurfaces).getAnimatorControllerFromNotification(associatedView)
+    }
+
+    @Test
+    fun startActivity_noUserHandleProvided_getUserHandle() {
+        val intent = mock(Intent::class.java)
+
+        underTest.startActivity(intent, false)
+
+        verify(userTracker).userHandle
+    }
+
+    @Test
+    fun postStartActivityDismissingKeyguard_pendingIntent_postsOnMain() {
+        val intent = mock(PendingIntent::class.java)
+
+        underTest.postStartActivityDismissingKeyguard(intent)
+
+        assertThat(mainExecutor.numPending()).isEqualTo(1)
+    }
+
+    @Test
+    fun postStartActivityDismissingKeyguard_intent_postsOnMain() {
+        whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+        val intent = mock(Intent::class.java)
+
+        underTest.postStartActivityDismissingKeyguard(intent, 0)
+
+        assertThat(mainExecutor.numPending()).isEqualTo(1)
+        mainExecutor.runAllReady()
+
+        verify(deviceProvisionedController).isDeviceProvisioned
+        verify(shadeController).collapseShadeForActivityStart()
+    }
+
+    @Test
+    fun postStartActivityDismissingKeyguard_intent_notDeviceProvisioned_doesNotProceed() {
+        whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(false)
+        val intent = mock(Intent::class.java)
+
+        underTest.postStartActivityDismissingKeyguard(intent, 0)
+        mainExecutor.runAllReady()
+
+        verify(deviceProvisionedController).isDeviceProvisioned
+        verify(shadeController, never()).collapseShadeForActivityStart()
+    }
+
+    @Test
+    fun dismissKeyguardThenExecute_startWakeAndUnlock() {
+        whenever(wakefulnessLifecycle.wakefulness)
+            .thenReturn(WakefulnessLifecycle.WAKEFULNESS_ASLEEP)
+        whenever(keyguardStateController.canDismissLockScreen()).thenReturn(true)
+        whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
+        whenever(dozeServiceHost.isPulsing).thenReturn(true)
+
+        underTest.dismissKeyguardThenExecute({ true }, {}, false)
+
+        verify(biometricUnlockController)
+            .startWakeAndUnlock(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
+    }
+
+    @Test
+    fun dismissKeyguardThenExecute_keyguardIsShowing_dismissWithAction() {
+        val customMessage = "Enter your pin."
+        whenever(keyguardStateController.isShowing).thenReturn(true)
+
+        underTest.dismissKeyguardThenExecute({ true }, {}, false, customMessage)
+
+        verify(statusBarKeyguardViewManager)
+            .dismissWithAction(
+                any(OnDismissAction::class.java),
+                any(Runnable::class.java),
+                eq(false),
+                eq(customMessage)
+            )
+    }
+
+    @Test
+    fun dismissKeyguardThenExecute_awakeDreams() {
+        val customMessage = "Enter your pin."
+        var dismissActionExecuted = false
+        whenever(keyguardStateController.isShowing).thenReturn(false)
+        whenever(keyguardUpdateMonitor.isDreaming).thenReturn(true)
+
+        underTest.dismissKeyguardThenExecute(
+            {
+                dismissActionExecuted = true
+                true
+            },
+            {},
+            false,
+            customMessage
+        )
+
+        verify(centralSurfaces).awakenDreams()
+        assertThat(dismissActionExecuted).isTrue()
+    }
+
+    @Test
+    @Throws(RemoteException::class)
+    fun executeRunnableDismissingKeyguard_dreaming_notShowing_awakenDreams() {
+        whenever(keyguardStateController.isShowing).thenReturn(false)
+        whenever(keyguardStateController.isOccluded).thenReturn(false)
+        whenever(keyguardUpdateMonitor.isDreaming).thenReturn(true)
+
+        underTest.executeRunnableDismissingKeyguard(
+            runnable = {},
+            cancelAction = null,
+            dismissShade = false,
+            afterKeyguardGone = false,
+            deferred = false
+        )
+
+        verify(centralSurfaces, times(1)).awakenDreams()
+    }
+
+    @Test
+    @Throws(RemoteException::class)
+    fun executeRunnableDismissingKeyguard_notDreaming_notShowing_doNotAwakenDreams() {
+        whenever(keyguardStateController.isShowing).thenReturn(false)
+        whenever(keyguardStateController.isOccluded).thenReturn(false)
+        whenever(keyguardUpdateMonitor.isDreaming).thenReturn(false)
+
+        underTest.executeRunnableDismissingKeyguard(
+            runnable = {},
+            cancelAction = null,
+            dismissShade = false,
+            afterKeyguardGone = false,
+            deferred = false
+        )
+
+        verify(centralSurfaces, never()).awakenDreams()
+    }
+
+    @Test
+    fun postQSRunnableDismissingKeyguard_leaveOpenStatusBarState() {
+        underTest.postQSRunnableDismissingKeyguard {}
+
+        assertThat(mainExecutor.numPending()).isEqualTo(1)
+        mainExecutor.runAllReady()
+        verify(statusBarStateController).setLeaveOpenOnKeyguardHide(true)
+    }
+
+    private companion object {
+        private const val DISPLAY_ID = 0
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
index 7274c0c..4528957 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
@@ -31,6 +31,7 @@
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.testing.TestableLooper.RunWithLooper;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -66,8 +67,11 @@
     @Mock
     private SystemUIDialog.Delegate mDelegate;
 
+    // TODO(b/292141694): build out Ravenwood support for DeviceFlagsValueProvider
+    // Ravenwood already has solid support for SetFlagsRule, but CheckFlagsRule will be added soon
     @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+    public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood() ? null
+            : DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Before
     public void setup() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
deleted file mode 100644
index 5f4d7bf..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
-
-import com.android.settingslib.mobile.TelephonyIcons
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
-import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
-import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
-import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import kotlinx.coroutines.flow.MutableStateFlow
-
-class FakeMobileIconInteractor(
-    override val tableLogBuffer: TableLogBuffer,
-) : MobileIconInteractor {
-    override val alwaysShowDataRatIcon = MutableStateFlow(false)
-
-    override val activity =
-        MutableStateFlow(
-            DataActivityModel(
-                hasActivityIn = false,
-                hasActivityOut = false,
-            )
-        )
-
-    override val carrierNetworkChangeActive = MutableStateFlow(false)
-
-    override val mobileIsDefault = MutableStateFlow(true)
-
-    override val isSingleCarrier = MutableStateFlow(true)
-
-    override val networkTypeIconGroup =
-        MutableStateFlow<NetworkTypeIconModel>(
-            NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
-        )
-
-    override val showSliceAttribution = MutableStateFlow(false)
-
-    override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo mode"))
-
-    override val carrierName = MutableStateFlow("demo mode")
-
-    override val isRoaming = MutableStateFlow(false)
-
-    override val isDataConnected = MutableStateFlow(true)
-
-    override val isInService = MutableStateFlow(true)
-
-    private val _isDataEnabled = MutableStateFlow(true)
-    override val isDataEnabled = _isDataEnabled
-
-    override val isForceHidden = MutableStateFlow(false)
-
-    override val isAllowedDuringAirplaneMode = MutableStateFlow(false)
-
-    override val signalLevelIcon: MutableStateFlow<SignalIconModel> =
-        MutableStateFlow(
-            SignalIconModel(
-                level = 0,
-                numberOfLevels = 4,
-                showExclamationMark = false,
-                carrierNetworkChange = false,
-            )
-        )
-
-    fun setIsDataEnabled(enabled: Boolean) {
-        _isDataEnabled.value = enabled
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
deleted file mode 100644
index a9ee405..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
-
-import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO
-import android.telephony.TelephonyManager.NETWORK_TYPE_GSM
-import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
-import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS
-import com.android.settingslib.SignalIcon.MobileIconGroup
-import com.android.settingslib.mobile.TelephonyIcons
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
-import kotlinx.coroutines.flow.MutableStateFlow
-
-class FakeMobileIconsInteractor(
-    mobileMappings: MobileMappingsProxy,
-    val tableLogBuffer: TableLogBuffer,
-) : MobileIconsInteractor {
-    val THREE_G_KEY = mobileMappings.toIconKey(THREE_G)
-    val LTE_KEY = mobileMappings.toIconKey(LTE)
-    val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G)
-    val FIVE_G_OVERRIDE_KEY = mobileMappings.toIconKeyOverride(FIVE_G_OVERRIDE)
-
-    /**
-     * To avoid a reliance on [MobileMappings], we'll build a simpler map from network type to
-     * mobile icon. See TelephonyManager.NETWORK_TYPES for a list of types and [TelephonyIcons] for
-     * the exhaustive set of icons
-     */
-    val TEST_MAPPING: Map<String, MobileIconGroup> =
-        mapOf(
-            THREE_G_KEY to TelephonyIcons.THREE_G,
-            LTE_KEY to TelephonyIcons.LTE,
-            FOUR_G_KEY to TelephonyIcons.FOUR_G,
-            FIVE_G_OVERRIDE_KEY to TelephonyIcons.NR_5G,
-        )
-
-    private val interactorCache: MutableMap<Int, FakeMobileIconInteractor> = mutableMapOf()
-
-    override val isDefaultConnectionFailed = MutableStateFlow(false)
-
-    override val filteredSubscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
-
-    private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
-    override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
-
-    override val activeDataIconInteractor = MutableStateFlow(null)
-
-    override val alwaysShowDataRatIcon = MutableStateFlow(false)
-
-    override val alwaysUseCdmaLevel = MutableStateFlow(false)
-
-    override val mobileIsDefault = MutableStateFlow(false)
-
-    override val isSingleCarrier = MutableStateFlow(true)
-
-    override val icons: MutableStateFlow<List<MobileIconInteractor>> = MutableStateFlow(emptyList())
-
-    private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
-    override val defaultMobileIconMapping = _defaultMobileIconMapping
-
-    private val _defaultMobileIconGroup = MutableStateFlow(DEFAULT_ICON)
-    override val defaultMobileIconGroup = _defaultMobileIconGroup
-
-    private val _isUserSetup = MutableStateFlow(true)
-    override val isUserSetup = _isUserSetup
-
-    override val isForceHidden = MutableStateFlow(false)
-
-    /** Always returns a new fake interactor */
-    override fun getMobileConnectionInteractorForSubId(subId: Int): FakeMobileIconInteractor {
-        return FakeMobileIconInteractor(tableLogBuffer).also {
-            interactorCache[subId] = it
-            // Also update the icons
-            icons.value = interactorCache.values.toList()
-        }
-    }
-
-    /**
-     * Returns the most recently created interactor for the given subId, or null if an interactor
-     * has never been created for that sub.
-     */
-    fun getInteractorForSubId(subId: Int): FakeMobileIconInteractor? {
-        return interactorCache[subId]
-    }
-
-    companion object {
-        val DEFAULT_ICON = TelephonyIcons.G
-
-        const val DEFAULT_DATA_SUB_ID = 1
-
-        // Use [MobileMappings] to define some simple definitions
-        const val THREE_G = NETWORK_TYPE_GSM
-        const val LTE = NETWORK_TYPE_LTE
-        const val FOUR_G = NETWORK_TYPE_UMTS
-        const val FIVE_G_OVERRIDE = OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryTest.kt
new file mode 100644
index 0000000..ebc81be
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.policy.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener
+import com.android.systemui.statusbar.policy.deviceProvisionedController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UserSetupRepositoryTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private val testScope = kosmos.testScope
+    private val deviceProvisionedController : DeviceProvisionedController = mock()
+
+    private val underTest = UserSetupRepositoryImpl(
+            deviceProvisionedController,
+            kosmos.testDispatcher,
+            kosmos.applicationCoroutineScope,
+    )
+
+    @Test
+    fun userSetup_defaultFalse() =
+            testScope.runTest {
+                val latest by collectLastValue(underTest.isUserSetUp)
+
+                assertThat(latest).isFalse()
+            }
+
+    @Test
+    fun userSetup_updatesOnChange() =
+            testScope.runTest {
+                val latest by collectLastValue(underTest.isUserSetUp)
+                runCurrent()
+
+                whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
+                val callback = getDeviceProvisionedListener()
+                callback.onUserSetupChanged()
+
+                assertThat(latest).isTrue()
+            }
+
+    private fun getDeviceProvisionedListener(): DeviceProvisionedListener {
+        val captor = argumentCaptor<DeviceProvisionedListener>()
+        verify(deviceProvisionedController).addCallback(captor.capture())
+        return captor.value!!
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorTest.kt
new file mode 100644
index 0000000..26c0f80
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.policy.data.repository.fakeUserSetupRepository
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UserSetupInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val fakeUserSetupRepository = kosmos.fakeUserSetupRepository
+    private val underTest = kosmos.userSetupInteractor
+
+    @Test
+    fun isUserSetup_false() =
+        testScope.runTest {
+            val setup by collectLastValue(underTest.isUserSetUp)
+
+            fakeUserSetupRepository.setUserSetUp(false)
+
+            assertThat(setup).isFalse()
+        }
+
+    @Test
+    fun isUserSetup_true() =
+        testScope.runTest {
+            val setup by collectLastValue(underTest.isUserSetUp)
+
+            fakeUserSetupRepository.setUserSetUp(true)
+
+            assertThat(setup).isTrue()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
index 262795f..8e8e510 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
@@ -24,12 +24,13 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.telephony.TelephonyListenerManager
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.kotlinArgumentCaptor
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -39,7 +40,6 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class TelephonyRepositoryImplTest : SysuiTestCase() {
@@ -47,8 +47,8 @@
     @Mock private lateinit var manager: TelephonyListenerManager
     @Mock private lateinit var telecomManager: TelecomManager
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
     private lateinit var underTest: TelephonyRepositoryImpl
 
@@ -61,7 +61,7 @@
             TelephonyRepositoryImpl(
                 applicationScope = testScope.backgroundScope,
                 applicationContext = context,
-                backgroundDispatcher = utils.testDispatcher,
+                backgroundDispatcher = kosmos.testDispatcher,
                 manager = manager,
                 telecomManager = telecomManager,
             )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt
new file mode 100644
index 0000000..b4a0a37
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.kotlin.BooleanFlowOperators.and
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
+import com.android.systemui.util.kotlin.BooleanFlowOperators.or
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BooleanFlowOperatorsTest : SysuiTestCase() {
+
+    val kosmos = testKosmos()
+    val testScope = kosmos.testScope
+
+    @Test
+    fun and_allTrue_returnsTrue() =
+        testScope.runTest {
+            val result by collectLastValue(and(TRUE, TRUE))
+            assertThat(result).isTrue()
+        }
+
+    @Test
+    fun and_anyFalse_returnsFalse() =
+        testScope.runTest {
+            val result by collectLastValue(and(TRUE, FALSE, TRUE))
+            assertThat(result).isFalse()
+        }
+
+    @Test
+    fun and_allFalse_returnsFalse() =
+        testScope.runTest {
+            val result by collectLastValue(and(FALSE, FALSE, FALSE))
+            assertThat(result).isFalse()
+        }
+
+    @Test
+    fun or_allTrue_returnsTrue() =
+        testScope.runTest {
+            val result by collectLastValue(or(TRUE, TRUE))
+            assertThat(result).isTrue()
+        }
+
+    @Test
+    fun or_anyTrue_returnsTrue() =
+        testScope.runTest {
+            val result by collectLastValue(or(FALSE, TRUE, FALSE))
+            assertThat(result).isTrue()
+        }
+
+    @Test
+    fun or_allFalse_returnsFalse() =
+        testScope.runTest {
+            val result by collectLastValue(or(FALSE, FALSE, FALSE))
+            assertThat(result).isFalse()
+        }
+
+    @Test
+    fun not_true_returnsFalse() =
+        testScope.runTest {
+            val result by collectLastValue(not(TRUE))
+            assertThat(result).isFalse()
+        }
+
+    @Test
+    fun not_false_returnsFalse() =
+        testScope.runTest {
+            val result by collectLastValue(not(FALSE))
+            assertThat(result).isTrue()
+        }
+
+    private companion object {
+        val TRUE: Flow<Boolean>
+            get() = flowOf(true)
+        val FALSE: Flow<Boolean>
+            get() = flowOf(false)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
new file mode 100644
index 0000000..471c8d8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.bottombar.ui.viewmodel
+
+import android.content.Intent
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.volume.panel.volumePanelViewModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BottomBarViewModelTest : SysuiTestCase() {
+
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+    @Captor private lateinit var intentCaptor: ArgumentCaptor<Intent>
+
+    private val kosmos = testKosmos()
+
+    private lateinit var underTest: BottomBarViewModel
+
+    private fun initUnderTest() {
+        underTest = with(kosmos) { BottomBarViewModel(activityStarter, volumePanelViewModel) }
+    }
+
+    @Test
+    fun onDoneClicked_hidesPanel() {
+        with(kosmos) {
+            testScope.runTest {
+                initUnderTest()
+                underTest.onDoneClicked()
+                runCurrent()
+
+                val volumePanelState by collectLastValue(volumePanelViewModel.volumePanelState)
+                assertThat(volumePanelState!!.isVisible).isFalse()
+            }
+        }
+    }
+
+    @Test
+    fun onSettingsClicked_dismissesPanelAndNavigatesToSettings() {
+        with(kosmos) {
+            testScope.runTest {
+                initUnderTest()
+                underTest.onSettingsClicked()
+
+                runCurrent()
+
+                val volumePanelState by collectLastValue(volumePanelViewModel.volumePanelState)
+                assertThat(volumePanelState!!.isVisible).isFalse()
+                verify(activityStarter).startActivity(capture(intentCaptor), eq(true))
+                assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_SOUND_SETTINGS)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt
new file mode 100644
index 0000000..6256eec
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.volume.panel.availableCriteria
+import com.android.systemui.volume.panel.criteriaByKey
+import com.android.systemui.volume.panel.defaultCriteria
+import com.android.systemui.volume.panel.domain.model.ComponentModel
+import com.android.systemui.volume.panel.enabledComponents
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.unavailableCriteria
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ComponentsInteractorImplTest : SysuiTestCase() {
+
+    private val kosmos = Kosmos()
+
+    private lateinit var underTest: ComponentsInteractor
+
+    private fun initUnderTest() {
+        underTest =
+            with(kosmos) {
+                ComponentsInteractorImpl(
+                    enabledComponents,
+                    defaultCriteria,
+                    testScope.backgroundScope,
+                    criteriaByKey,
+                )
+            }
+    }
+
+    @Test
+    fun componentsAvailability_checked() {
+        with(kosmos) {
+            testScope.runTest {
+                enabledComponents =
+                    setOf(
+                        BOTTOM_BAR,
+                        COMPONENT_1,
+                        COMPONENT_2,
+                    )
+                criteriaByKey =
+                    mapOf(
+                        BOTTOM_BAR to availableCriteria,
+                        COMPONENT_1 to unavailableCriteria,
+                        COMPONENT_2 to availableCriteria,
+                    )
+                initUnderTest()
+
+                val components by collectLastValue(underTest.components)
+
+                assertThat(components)
+                    .containsExactly(
+                        ComponentModel(BOTTOM_BAR, true),
+                        ComponentModel(COMPONENT_1, false),
+                        ComponentModel(COMPONENT_2, true),
+                    )
+            }
+        }
+    }
+
+    @Test
+    fun noCriteria_fallbackToDefaultCriteria() {
+        with(kosmos) {
+            testScope.runTest {
+                enabledComponents =
+                    setOf(
+                        BOTTOM_BAR,
+                        COMPONENT_1,
+                        COMPONENT_2,
+                    )
+                criteriaByKey =
+                    mapOf(
+                        BOTTOM_BAR to availableCriteria,
+                        COMPONENT_2 to availableCriteria,
+                    )
+                defaultCriteria = unavailableCriteria
+                initUnderTest()
+
+                val components by collectLastValue(underTest.components)
+
+                assertThat(components)
+                    .containsExactly(
+                        ComponentModel(BOTTOM_BAR, true),
+                        ComponentModel(COMPONENT_1, false),
+                        ComponentModel(COMPONENT_2, true),
+                    )
+            }
+        }
+    }
+
+    private companion object {
+        const val BOTTOM_BAR: VolumePanelComponentKey = "test_bottom_bar"
+        const val COMPONENT_1: VolumePanelComponentKey = "test_component:1"
+        const val COMPONENT_2: VolumePanelComponentKey = "test_component:2"
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorTest.kt
deleted file mode 100644
index dbff63f..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorTest.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume.panel.domain.interactor
-
-class ComponentsInteractorTest {
-
-    // TODO(b/318080198) Write tests
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt
new file mode 100644
index 0000000..3dbf23e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.composable
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.panel.componentByKey
+import com.android.systemui.volume.panel.mockVolumePanelUiComponentProvider
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ComponentsFactoryTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private lateinit var underTest: ComponentsFactory
+
+    private fun initUnderTest() {
+        underTest = ComponentsFactory(kosmos.componentByKey)
+    }
+
+    @Test
+    fun existingComponent_created() {
+        kosmos.componentByKey = mapOf(TEST_COMPONENT to kosmos.mockVolumePanelUiComponentProvider)
+        initUnderTest()
+
+        val component = underTest.createComponent(TEST_COMPONENT)
+
+        Truth.assertThat(component).isNotNull()
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun componentAbsence_throws() {
+        kosmos.componentByKey = emptyMap()
+        initUnderTest()
+
+        underTest.createComponent(TEST_COMPONENT)
+    }
+
+    private companion object {
+        const val TEST_COMPONENT: VolumePanelComponentKey = "test_component"
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
index e5fb942..35d9698 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
@@ -16,7 +16,78 @@
 
 package com.android.systemui.volume.panel.ui.viewmodel
 
-class DefaultComponentsLayoutManagerTest {
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.panel.mockVolumePanelUiComponent
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
 
-    // TODO(b/318080198) Write tests
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DefaultComponentsLayoutManagerTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val underTest: ComponentsLayoutManager = DefaultComponentsLayoutManager()
+
+    @Test
+    fun bottomBar_isSet() {
+        val bottomBarComponentState =
+            ComponentState(BOTTOM_BAR, kosmos.mockVolumePanelUiComponent, false)
+        val layout =
+            underTest.layout(
+                VolumePanelState(0, false),
+                setOf(
+                    bottomBarComponentState,
+                    ComponentState(COMPONENT_1, kosmos.mockVolumePanelUiComponent, false),
+                    ComponentState(COMPONENT_2, kosmos.mockVolumePanelUiComponent, false),
+                )
+            )
+
+        Truth.assertThat(layout.bottomBarComponent).isEqualTo(bottomBarComponentState)
+    }
+
+    @Test
+    fun componentsAreInOrder() {
+        val bottomBarComponentState =
+            ComponentState(BOTTOM_BAR, kosmos.mockVolumePanelUiComponent, false)
+        val component1State = ComponentState(COMPONENT_1, kosmos.mockVolumePanelUiComponent, false)
+        val component2State = ComponentState(COMPONENT_2, kosmos.mockVolumePanelUiComponent, false)
+        val layout =
+            underTest.layout(
+                VolumePanelState(0, false),
+                setOf(
+                    bottomBarComponentState,
+                    component1State,
+                    component2State,
+                )
+            )
+
+        Truth.assertThat(layout.contentComponents[0]).isEqualTo(component1State)
+        Truth.assertThat(layout.contentComponents[1]).isEqualTo(component2State)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun bottomBarAbsence_throwsException() {
+        val component1State = ComponentState(COMPONENT_1, kosmos.mockVolumePanelUiComponent, false)
+        val component2State = ComponentState(COMPONENT_2, kosmos.mockVolumePanelUiComponent, false)
+        underTest.layout(
+            VolumePanelState(0, false),
+            setOf(
+                component1State,
+                component2State,
+            )
+        )
+    }
+
+    private companion object {
+        const val BOTTOM_BAR: VolumePanelComponentKey = "bottom_bar"
+        const val COMPONENT_1: VolumePanelComponentKey = "test_component:1"
+        const val COMPONENT_2: VolumePanelComponentKey = "test_component:2"
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
index 9795237..c4c9cc6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
@@ -16,7 +16,120 @@
 
 package com.android.systemui.volume.panel.ui.viewmodel
 
-class VolumePanelViewModelTest {
+import android.content.res.Configuration
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.policy.fakeConfigurationController
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.panel.componentByKey
+import com.android.systemui.volume.panel.componentsLayoutManager
+import com.android.systemui.volume.panel.criteriaByKey
+import com.android.systemui.volume.panel.dagger.factory.KosmosVolumePanelComponentFactory
+import com.android.systemui.volume.panel.mockVolumePanelUiComponentProvider
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.ui.layout.FakeComponentsLayoutManager
+import com.android.systemui.volume.panel.unavailableCriteria
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
 
-    // TODO(b/318080198) Write tests
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class VolumePanelViewModelTest : SysuiTestCase() {
+
+    private val kosmos =
+        testKosmos().apply {
+            componentsLayoutManager = FakeComponentsLayoutManager { it.key == BOTTOM_BAR }
+        }
+
+    private val testableResources = context.orCreateTestableResources
+
+    private lateinit var underTest: VolumePanelViewModel
+
+    private fun initUnderTest() {
+        underTest =
+            VolumePanelViewModel(
+                testableResources.resources,
+                KosmosVolumePanelComponentFactory(kosmos),
+                kosmos.fakeConfigurationController,
+            )
+    }
+
+    @Test
+    fun dismissingPanel_changesVisibility() {
+        with(kosmos) {
+            testScope.runTest {
+                initUnderTest()
+                assertThat(underTest.volumePanelState.value.isVisible).isTrue()
+
+                underTest.dismissPanel()
+                runCurrent()
+
+                assertThat(underTest.volumePanelState.value.isVisible).isFalse()
+            }
+        }
+    }
+
+    @Test
+    fun orientationChanges_panelOrientationChanges() {
+        with(kosmos) {
+            testScope.runTest {
+                initUnderTest()
+                val volumePanelState by collectLastValue(underTest.volumePanelState)
+                testableResources.overrideConfiguration(
+                    Configuration().apply { orientation = Configuration.ORIENTATION_PORTRAIT }
+                )
+                assertThat(volumePanelState!!.orientation)
+                    .isEqualTo(Configuration.ORIENTATION_PORTRAIT)
+
+                fakeConfigurationController.onConfigurationChanged(
+                    Configuration().apply { orientation = Configuration.ORIENTATION_LANDSCAPE }
+                )
+                runCurrent()
+
+                assertThat(volumePanelState!!.orientation)
+                    .isEqualTo(Configuration.ORIENTATION_LANDSCAPE)
+            }
+        }
+    }
+
+    @Test
+    fun components_areReturned() {
+        with(kosmos) {
+            testScope.runTest {
+                componentByKey =
+                    mapOf(
+                        COMPONENT_1 to mockVolumePanelUiComponentProvider,
+                        COMPONENT_2 to mockVolumePanelUiComponentProvider,
+                        BOTTOM_BAR to mockVolumePanelUiComponentProvider,
+                    )
+                criteriaByKey = mapOf(COMPONENT_2 to unavailableCriteria)
+                initUnderTest()
+
+                val componentsLayout by collectLastValue(underTest.componentsLayout)
+                runCurrent()
+
+                assertThat(componentsLayout!!.contentComponents).hasSize(2)
+                assertThat(componentsLayout!!.contentComponents[0].key).isEqualTo(COMPONENT_1)
+                assertThat(componentsLayout!!.contentComponents[0].isVisible).isTrue()
+                assertThat(componentsLayout!!.contentComponents[1].key).isEqualTo(COMPONENT_2)
+                assertThat(componentsLayout!!.contentComponents[1].isVisible).isFalse()
+                assertThat(componentsLayout!!.bottomBarComponent.key).isEqualTo(BOTTOM_BAR)
+                assertThat(componentsLayout!!.bottomBarComponent.isVisible).isTrue()
+            }
+        }
+    }
+
+    private companion object {
+        const val BOTTOM_BAR: VolumePanelComponentKey = "test_bottom_bar"
+        const val COMPONENT_1: VolumePanelComponentKey = "test_component:1"
+        const val COMPONENT_2: VolumePanelComponentKey = "test_component:2"
+    }
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
index 3d9645a..b1736b1 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
@@ -227,5 +227,10 @@
         void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkBeforeSwitch);
         // requires version 2
         void onShowCsdWarning(@AudioManager.CsdWarning int csdWarning, int durationMs);
+
+        /**
+         * Callback function for when the volume changed due to a physical key press.
+         */
+        void onVolumeChangedFromKey();
     }
 }
diff --git a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
index 3e5e8a0..f0ce460 100644
--- a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
+++ b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
@@ -18,6 +18,8 @@
 
 import android.content.ComponentName;
 
+import java.util.function.BiConsumer;
+
 /**
  * Provides the ability for consumers to control plugin lifecycle.
  *
@@ -33,11 +35,8 @@
     /** Returns the currently loaded plugin instance (if plugin is loaded) */
     T getPlugin();
 
-    /** Returns true if the lifecycle manager should log debug messages */
-    boolean getIsDebug();
-
-    /** Sets whether or not hte lifecycle manager should log debug messages */
-    void setIsDebug(boolean debug);
+    /** Log tag and messages will be sent to the provided Consumer */
+    void setLogFunc(BiConsumer<String, String> logConsumer);
 
     /** returns true if the plugin is currently loaded */
     default boolean isLoaded() {
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml
new file mode 100644
index 0000000..84b89ca
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true">
+        <shape android:shape="rectangle">
+            <corners android:radius="16dp" />
+            <!--By default this outline will not show hence 0 width.
+             width is set programmatically when needed and is gated by the flag:
+             com.android.systemui.Flags.pinInputFieldStyledFocusState-->
+            <stroke android:width="0dp"
+                android:color="@color/bouncer_password_focus_color" />
+        </shape>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
index 66c54f2..0b35559 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
@@ -23,7 +23,7 @@
         android:layout_marginTop="@dimen/keyguard_lock_padding"
         android:importantForAccessibility="no"
         android:ellipsize="marquee"
-        android:focusable="true"
+        android:focusable="false"
         android:gravity="center"
         android:singleLine="true" />
 </merge>
diff --git a/packages/SystemUI/res-keyguard/values-af/strings.xml b/packages/SystemUI/res-keyguard/values-af/strings.xml
index a6a8122..3b2eb15 100644
--- a/packages/SystemUI/res-keyguard/values-af/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-af/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Laai tans"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Laai tans vinnig"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Laai tans stadig"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Laaiproses word geoptimeer om battery te beskerm"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Kwessie met laaibykomstigheid"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Laaiproses is onderbreek om battery te beskerm"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Gaan die laaibykomstigheid na"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Netwerk is gesluit"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Geen SIM nie"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Onbruikbare SIM."</string>
diff --git a/packages/SystemUI/res-keyguard/values-am/strings.xml b/packages/SystemUI/res-keyguard/values-am/strings.xml
index fb84414..c3d141c 100644
--- a/packages/SystemUI/res-keyguard/values-am/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-am/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ኃይል በመሙላት ላይ"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • በፍጥነት ኃይልን በመሙላት ላይ"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • በዝግታ ኃይልን በመሙላት ላይ"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ባትሪን ለመጠበቅ ኃይል መሙላት ተብቷል"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ተለዋዋጭን ኃይል በመሙላት ላይ ችግር"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> - ባትሪን ለመጠበቅ ኃይል መሙላት በይቆይ ላይ"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • የኃይል መሙላት መለዋወጫን ይፈትሹ"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"አውታረ መረብ ተቆልፏል"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"ምንም SIM የለም"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ጥቅም ላይ የማይውል ሲም።"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml
index fb33092..cf8341d 100644
--- a/packages/SystemUI/res-keyguard/values-ar/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • جارٍ الشحن"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • جارٍ الشحن سريعًا"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • جارٍ الشحن ببطء"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • تم تحسين الشحن لحماية البطارية"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"‫<xliff:g id="PERCENTAGE">%s</xliff:g> • مشكلة متعلّقة بجهاز الشحن الملحق"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"‫<xliff:g id="PERCENTAGE">%s</xliff:g> • الشحن معلّق لحماية البطارية"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"‫<xliff:g id="PERCENTAGE">%s</xliff:g> • يُرجى فحص ملحق الشحن."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"الشبكة مؤمّنة"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"‏لا تتوفر شريحة SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"‏شريحة SIM غير قابلة للاستخدام."</string>
diff --git a/packages/SystemUI/res-keyguard/values-as/strings.xml b/packages/SystemUI/res-keyguard/values-as/strings.xml
index a123bb7..d11a9c1 100644
--- a/packages/SystemUI/res-keyguard/values-as/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-as/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • চ্চার্জ কৰি থকা হৈছে"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • দ্ৰুত গতিৰে চাৰ্জ কৰি থকা হৈছে"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • লাহে লাহে চাৰ্জ কৰি থকা হৈছে"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • বেটাৰী সুৰক্ষিত কৰিবলৈ চাৰ্জিং অপ্টিমাইজ কৰা হৈছে"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • চাৰ্জিঙৰ আনুষংগিক সামগ্ৰীত সমস্যা হৈছে"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • বেটাৰী সুৰক্ষিত কৰিবলৈ চাৰ্জিং স্থগিত ৰখা হৈছে"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • চাৰ্জিঙৰ সৈতে জড়িত আনুষংগিক সামগ্ৰী পৰীক্ষা কৰক"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"নেটৱর্ক লক কৰা অৱস্থাত আছে"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"কোনো ছিম নাই"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ব্যৱহাৰ কৰিব নোৱৰা ছিম।"</string>
diff --git a/packages/SystemUI/res-keyguard/values-az/strings.xml b/packages/SystemUI/res-keyguard/values-az/strings.xml
index b133b30a..4de32d9 100644
--- a/packages/SystemUI/res-keyguard/values-az/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-az/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Enerji yığır"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Sürətlə enerji yığır"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Yavaş enerji yığır"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Batareyanı qorumaq üçün şarj optimallaşdırılıb"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Şarj aksesuarı ilə bağlı problem"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Batareyanı qorumaq üçün şarj gözlədilir"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Şarj aksesuarını yoxlayın"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Şəbəkə kilidlidir"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM yoxdur"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"İstifadəyə yararsız SIM."</string>
diff --git a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
index 9a91962..d23ff41 100644
--- a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Puni se"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Brzo se puni"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Sporo se puni"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Punjenje je optimizovano da bi se zaštitila baterija"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problem sa dodatnim priborom za punjenje"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Punjenje je na čekanju da bi se zaštitila baterija"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Proverite dodatnu opremu za punjenje"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Mreža je zaključana"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Nema SIM-a"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Neupotrebljiv SIM."</string>
diff --git a/packages/SystemUI/res-keyguard/values-be/strings.xml b/packages/SystemUI/res-keyguard/values-be/strings.xml
index 5e46b715..e704c3c8 100644
--- a/packages/SystemUI/res-keyguard/values-be/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-be/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ідзе зарадка"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ідзе хуткая зарадка"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ідзе павольная зарадка"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • У мэтах зберажэння акумулятара зарадка аптымізавана"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Праблема з зараднай прыладай"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Зарадка прыпынена, каб абараніць акумулятар"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Праверце зарадную прыладу"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Сетка заблакіравана"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Няма SIM-карты"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Непрыдатная для выкарыстання SIM-карта."</string>
diff --git a/packages/SystemUI/res-keyguard/values-bg/strings.xml b/packages/SystemUI/res-keyguard/values-bg/strings.xml
index ab931ed..795d4b83 100644
--- a/packages/SystemUI/res-keyguard/values-bg/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bg/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Зарежда се"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Зарежда се бързо"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Зарежда се бавно"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Зареждането е оптимизирано с цел запазване на батерията"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Проблем със зарядното устройство"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Зареждането е поставено на пауза с цел запазване на батерията"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Проверете аксесоара за зареждане"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Мрежата е заключена"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Няма SIM карта"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Неизползваема SIM карта."</string>
diff --git a/packages/SystemUI/res-keyguard/values-bn/strings.xml b/packages/SystemUI/res-keyguard/values-bn/strings.xml
index e25de93..e333ddd 100644
--- a/packages/SystemUI/res-keyguard/values-bn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bn/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • চার্জ হচ্ছে"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • দ্রুত চার্জ হচ্ছে"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ধীরে চার্জ হচ্ছে"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ব্যাটারি ভাল রাখতে চার্জিং অপ্টিমাইজ করা হয়েছে"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • চার্জিং অ্যাক্সেসরিতে সমস্যা রয়েছে"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ব্যাটারিকে সুরক্ষিত রাখতে চার্জিং হোল্ড করা হয়েছে"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • চার্জিং অ্যাক্সেসরি চেক করুন"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"নেটওয়ার্ক লক করা আছে"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"কোনও সিম নেই"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ব্যবহারযোগ্য নয় এমন সিম।"</string>
diff --git a/packages/SystemUI/res-keyguard/values-bs/strings.xml b/packages/SystemUI/res-keyguard/values-bs/strings.xml
index cd7aaeb..75fe286 100644
--- a/packages/SystemUI/res-keyguard/values-bs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bs/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Punjenje"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Brzo punjenje"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Sporo punjenje"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Punjenje je optimizirano radi zaštite baterije"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problem s opremom za punjenje"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Punjenje je na čekanju radi zaštite baterije"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Provjera dodatne opreme za punjenje"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Mreža je zaključana"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Nema SIM-a"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Neupotrebljiv SIM."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ca/strings.xml b/packages/SystemUI/res-keyguard/values-ca/strings.xml
index bf8a592..4047d7c 100644
--- a/packages/SystemUI/res-keyguard/values-ca/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ca/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • S\'està carregant"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • S\'està carregant ràpidament"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • S\'està carregant lentament"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Càrrega optimitzada per protegir la bateria"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problema relacionat amb l\'accessori de càrrega"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • La càrrega s\'ha posat en espera per protegir la bateria"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Revisa l\'accessori de càrrega"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"La xarxa està bloquejada"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"No hi ha cap SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"La SIM no es pot utilitzar."</string>
diff --git a/packages/SystemUI/res-keyguard/values-cs/strings.xml b/packages/SystemUI/res-keyguard/values-cs/strings.xml
index bedafd8..ea9a683 100644
--- a/packages/SystemUI/res-keyguard/values-cs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-cs/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Nabíjení"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Rychlé nabíjení"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Pomalé nabíjení"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Optimalizované nabíjení za účelem ochrany baterie"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problém s nabíjecím příslušenstvím"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Nabíjení je odloženo za účelem ochrany baterie"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Zkontrolujte nabíjecí příslušenství"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Síť je blokována"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Žádná SIM karta"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM kartu nelze použít."</string>
diff --git a/packages/SystemUI/res-keyguard/values-da/strings.xml b/packages/SystemUI/res-keyguard/values-da/strings.xml
index 93f505e..8794bc0 100644
--- a/packages/SystemUI/res-keyguard/values-da/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-da/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Oplader"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Oplader hurtigt"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Oplader langsomt"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Opladning er optimeret for at beskytte batteriet"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problem med opladertilbehør"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Opladningen er sat på pause for at beskytte batteriet"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Tjek opladningstilbehøret"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Netværket er låst"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Intet SIM-kort"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Deaktiveret SIM-kort."</string>
diff --git a/packages/SystemUI/res-keyguard/values-de/strings.xml b/packages/SystemUI/res-keyguard/values-de/strings.xml
index 01e166e..9e80b74 100644
--- a/packages/SystemUI/res-keyguard/values-de/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-de/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Wird geladen"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Wird schnell geladen"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Wird langsam geladen"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Optimiertes Laden zur Akkuschonung"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problem mit dem Ladezubehör"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ladevorgang zum Schutz des Akkus angehalten"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ladezubehör prüfen"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Netzwerk gesperrt"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Keine SIM-Karte"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM-Karte ist nicht nutzbar."</string>
diff --git a/packages/SystemUI/res-keyguard/values-el/strings.xml b/packages/SystemUI/res-keyguard/values-el/strings.xml
index 9769242..b7d3250 100644
--- a/packages/SystemUI/res-keyguard/values-el/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-el/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Φόρτιση"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Γρήγορη φόρτιση"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Αργή φόρτιση"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Η φόρτιση βελτιστοποιήθηκε για την προστασία της μπαταρίας"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Πρόβλημα αξεσουάρ φόρτισης"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Η φόρτιση τέθηκε σε αναμονή για την προστασία της μπαταρίας"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ελέγξτε το αξεσουάρ φόρτισης"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Κλειδωμένο δίκτυο"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Δεν υπάρχει SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Η SIM δεν μπορεί να χρησιμοποιηθεί."</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
index 087ab3a..d0461c1 100644
--- a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging rapidly"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging slowly"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging optimised to protect battery"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Issue with charging accessory"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging on hold to protect battery"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Check charging accessory"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Network locked"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"No SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Unusable SIM."</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
index 7297cf9..cb13fd5 100644
--- a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging rapidly"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging slowly"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging optimized to protect battery"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Issue with charging accessory"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging on hold to protect battery"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Check charging accessory"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Network locked"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"No SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Unusable SIM."</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
index 087ab3a..d0461c1 100644
--- a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging rapidly"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging slowly"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging optimised to protect battery"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Issue with charging accessory"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging on hold to protect battery"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Check charging accessory"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Network locked"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"No SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Unusable SIM."</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
index 087ab3a..d0461c1 100644
--- a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging rapidly"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging slowly"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging optimised to protect battery"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Issue with charging accessory"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging on hold to protect battery"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Check charging accessory"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Network locked"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"No SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Unusable SIM."</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
index ead8bce..188acd6 100644
--- a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎‎‎‎‏‎‏‏‎‏‎‎‎‏‏‎‏‎‎‏‎‏‏‏‏‏‎‎‎‏‎‎‎‎‎‎‎‎‏‏‏‏‎‏‎‏‎‏‏‏‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%s</xliff:g>‎‏‎‎‏‏‏‎ • Charging‎‏‎‎‏‎"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‏‎‎‎‎‎‎‏‎‏‎‎‎‎‎‎‎‏‎‏‎‏‏‎‏‎‏‏‏‎‎‏‎‎‎‏‏‏‏‏‎‏‎‎‎‎‏‎‎‎‏‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%s</xliff:g>‎‏‎‎‏‏‏‎ • Charging rapidly‎‏‎‎‏‎"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‎‎‎‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‎‏‎‏‏‎‎‎‏‎‎‏‎‎‏‎‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%s</xliff:g>‎‏‎‎‏‏‏‎ • Charging slowly‎‏‎‎‏‎"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‏‏‎‏‎‏‏‏‏‎‎‎‏‎‏‎‎‎‎‎‏‎‏‏‎‎‏‎‎‏‎‏‎‎‎‏‎‎‎‏‏‏‎‎‎‎‎‏‎‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%s</xliff:g>‎‏‎‎‏‏‏‎ • Charging optimized to protect battery‎‏‎‎‏‎"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‎‏‏‏‎‎‏‎‎‎‏‎‏‏‎‏‏‏‎‎‎‎‎‎‏‏‏‏‎‎‏‎‎‏‏‏‏‎‎‏‎‏‏‎‎‎‏‎‎‎‏‎‎‎‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%s</xliff:g>‎‏‎‎‏‏‏‎ • Issue with charging accessory‎‏‎‎‏‎"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‎‎‎‏‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%s</xliff:g>‎‏‎‎‏‏‏‎ • Charging on hold to protect battery‎‏‎‎‏‎"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‎‏‏‎‎‏‎‎‏‏‏‏‏‏‎‎‎‏‏‎‏‏‎‏‎‎‎‏‏‎‏‎‎‎‏‎‏‎‎‏‏‎‏‎‏‏‎‎‎‏‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%s</xliff:g>‎‏‎‎‏‏‏‎ • Check charging accessory‎‏‎‎‏‎"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‎‏‎‎‏‏‎‎‏‎‎‏‎‏‏‏‏‏‎‏‎‏‏‏‏‏‎‎‏‎‎‎‏‏‎‎‏‎‏‏‎‎‏‎‏‎‎‎‎‎‎‎‎‎‎‎Network locked‎‏‎‎‏‎"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‎‎‎‎‎‎‏‏‎‏‏‎‏‏‎‏‏‎‎‎‏‏‏‎‏‏‏‏‎‎‎‏‎‎‎‎‎‎‎‎‏‏‏‏‎‎‏‎‎‏‎‏‎‎‎‎No SIM‎‏‎‎‏‎"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‏‏‎‎‏‎‏‎‏‏‎‏‎‎‎‎‎‎‏‎‎‏‏‎‎‏‏‎‎‏‎‏‎‏‏‎‎‏‎‏‏‎‎‎‎‏‎‏‎‎‎Unusable SIM.‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
index 5b82c44..43aea8d 100644
--- a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Cargando"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Cargando rápidamente"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Cargando lentamente"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carga optimizada para proteger la batería"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problema con el accesorio de carga"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Se detuvo la carga para proteger la batería"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Verifica el accesorio de carga"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Bloqueada para la red"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"No hay ninguna tarjeta SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Tarjeta SIM inutilizable."</string>
diff --git a/packages/SystemUI/res-keyguard/values-es/strings.xml b/packages/SystemUI/res-keyguard/values-es/strings.xml
index cf7f3d2..9022a3d 100644
--- a/packages/SystemUI/res-keyguard/values-es/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Cargando"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Cargando rápidamente"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Cargando lentamente"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carga optimizada para proteger la batería"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problema con el accesorio de carga"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carga pausada para proteger la batería"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Comprueba el accesorio de carga"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Bloqueada para la red"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"No hay ninguna SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"No se puede usar la SIM."</string>
diff --git a/packages/SystemUI/res-keyguard/values-et/strings.xml b/packages/SystemUI/res-keyguard/values-et/strings.xml
index 6335ca8..c0d5e2f2 100644
--- a/packages/SystemUI/res-keyguard/values-et/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-et/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Laadimine"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Kiirlaadimine"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Aeglane laadimine"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Laadimine on aku kaitsmiseks optimeeritud"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • probleem laadimistarvikuga"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • laadimine on aku kaitsmiseks ootele pandud"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • kontrollige laadimistarvikut"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Võrk on lukus"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM-i pole"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM-i ei saa kasutada."</string>
diff --git a/packages/SystemUI/res-keyguard/values-eu/strings.xml b/packages/SystemUI/res-keyguard/values-eu/strings.xml
index b47c58a..c9ac7e9 100644
--- a/packages/SystemUI/res-keyguard/values-eu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-eu/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Kargatzen"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Bizkor kargatzen"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Mantso kargatzen"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Bateria ez kaltetzeko, kargatzeko modua optimizatu da"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Arazo bat dago kargatzeko osagarriarekin"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • kargatze-prozesua zain dago bateria babesteko"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Eman begiratu bat kargatzeko osagarriari"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Sarea blokeatuta dago"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Ez dago SIMik"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Ezin da erabili SIMa."</string>
diff --git a/packages/SystemUI/res-keyguard/values-fa/strings.xml b/packages/SystemUI/res-keyguard/values-fa/strings.xml
index f274f5f..b1b1f8a 100644
--- a/packages/SystemUI/res-keyguard/values-fa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fa/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • درحال شارژ شدن"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • درحال شارژ سریع"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • آهسته‌آهسته شارژ می‌شود"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • برای محافظت از باتری، شارژ بهینه می‌شود"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • در شارژ وسیله جانبی مشکلی وجود دارد"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • برای محافظت از باتری، شارژ موقتاً متوقف شده است"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • لوازم شارژ را بررسی کنید"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"شبکه قفل شد"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"سیم‌کارتی وجود ندارد"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"سیم‌کارت قابل‌استفاده نیست."</string>
diff --git a/packages/SystemUI/res-keyguard/values-fi/strings.xml b/packages/SystemUI/res-keyguard/values-fi/strings.xml
index dd9ce2e4..f5a6759 100644
--- a/packages/SystemUI/res-keyguard/values-fi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fi/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ladataan"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ladataan nopeasti"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ladataan hitaasti"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Lataus optimoitu akun suojaamiseksi"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ongelma laturin kanssa"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Lataus on keskeytetty akun suojaamiseksi"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Tarkista latauslisävaruste"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Verkko lukittu"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Ei SIM-korttia"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM-korttia ei voi käyttää."</string>
diff --git a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
index 742f56e..bca8dd7 100644
--- a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"En recharge : <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"En recharge rapide : <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"En recharge lente : <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Recharge optimisée pour protéger la pile"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problème concernant l\'accessoire de recharge"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • La recharge a été mise en pause pour protéger la pile"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Vérifier l\'accessoire de recharge"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Réseau verrouillé"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Aucune carte SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"La carte SIM est inutilisable."</string>
diff --git a/packages/SystemUI/res-keyguard/values-fr/strings.xml b/packages/SystemUI/res-keyguard/values-fr/strings.xml
index 92d24c4..97d2081 100644
--- a/packages/SystemUI/res-keyguard/values-fr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Recharge…"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Recharge rapide…"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Recharge lente"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Recharge optimisée pour protéger la batterie"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problème de recharge de l\'accessoire"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Recharge en pause pour protéger la batterie"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Vérifier l\'accessoire de recharge"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Réseau verrouillé"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Aucune SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM inutilisable."</string>
diff --git a/packages/SystemUI/res-keyguard/values-gl/strings.xml b/packages/SystemUI/res-keyguard/values-gl/strings.xml
index 4837de2..12b6c2c 100644
--- a/packages/SystemUI/res-keyguard/values-gl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gl/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Cargando"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Cargando rapidamente"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Cargando lentamente"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carga optimizada para protexer a batería"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problema co accesorio de carga"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • A carga púxose en pausa para protexer a batería"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Comproba o accesorio de carga"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Bloqueada pola rede"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Non hai ningunha SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"A SIM non se pode usar."</string>
diff --git a/packages/SystemUI/res-keyguard/values-gu/strings.xml b/packages/SystemUI/res-keyguard/values-gu/strings.xml
index 7f8c6d8..9e2fd53 100644
--- a/packages/SystemUI/res-keyguard/values-gu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gu/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ચાર્જિંગ"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ઝડપથી ચાર્જિંગ"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ધીમેથી ચાર્જિંગ"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • બૅટરીની સુરક્ષા કરવા માટે, ચાર્જિંગ ઑપ્ટિમાઇઝ કરવામાં આવ્યું છે"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ચાર્જિંગ ઍક્સેસરીમાં સમસ્યા"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • બૅટરીને સુરક્ષિત રાખવા માટે, ચાર્જિંગ હોલ્ડ પર રાખવામાં આવ્યું છે"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ચાર્જિંગ ઍક્સેસરી ચેક કરો"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"નેટવર્ક લૉક થયું"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"કોઈ સિમ કાર્ડ નથી"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ઉપયોગમાં ન લઈ શકાતું સિમ કાર્ડ."</string>
diff --git a/packages/SystemUI/res-keyguard/values-hi/strings.xml b/packages/SystemUI/res-keyguard/values-hi/strings.xml
index 18d63ab..12cb7e3 100644
--- a/packages/SystemUI/res-keyguard/values-hi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hi/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • चार्ज हो रहा है"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • तेज़ चार्ज हो रहा है"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • धीरे चार्ज हो रहा है"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • बैटरी को नुकसान से बचाने के लिए, चार्जिंग को ऑप्टिमाइज़ किया गया"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • चार्जर ऐक्सेसरी से जुड़ी समस्या"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • बैटरी को सुरक्षित रखने के लिए, चार्जिंग को रोका गया है"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • चार्जिंग ऐक्सेसरी की जांच करें"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"नेटवर्क लॉक किया हुआ है"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"कोई सिम नहीं है"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"सिम को हमेशा के लिए बंद कर दिया गया है."</string>
@@ -104,7 +104,7 @@
     <string name="kg_password_pin_failed" msgid="5136259126330604009">"SIM पिन की कार्यवाही विफल रही!"</string>
     <string name="kg_password_puk_failed" msgid="6778867411556937118">"SIM PUK की कार्यवाही विफल रही!"</string>
     <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"इनपुट का तरीका बदलें"</string>
-    <string name="airplane_mode" msgid="2528005343938497866">"हवाई जहाज़ मोड"</string>
+    <string name="airplane_mode" msgid="2528005343938497866">"फ़्लाइट मोड"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="3321211830602827742">"डिवाइस रीस्टार्ट करने पर, पैटर्न ड्रॉ करना ज़रूरी है"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="2672166323886110512">"डिवाइस रीस्टार्ट करने पर, पिन डालना ज़रूरी है"</string>
     <string name="kg_prompt_reason_restart_password" msgid="3967993994418885887">"डिवाइस रीस्टार्ट करने पर, पासवर्ड डालना ज़रूरी है"</string>
diff --git a/packages/SystemUI/res-keyguard/values-hr/strings.xml b/packages/SystemUI/res-keyguard/values-hr/strings.xml
index 0206faf..fad222d 100644
--- a/packages/SystemUI/res-keyguard/values-hr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hr/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • punjenje"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • brzo punjenje"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • sporo punjenje"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Punjenje se optimizira radi zaštite baterije"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problem s priborom za punjenje"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Punjenje je pauzirano radi zaštite baterije"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Provjerite dodatak za punjenje"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Mreža je zaključana"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Nema SIM-a"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM je neupotrebljiv."</string>
diff --git a/packages/SystemUI/res-keyguard/values-hu/strings.xml b/packages/SystemUI/res-keyguard/values-hu/strings.xml
index 8575e10..a22fbce 100644
--- a/packages/SystemUI/res-keyguard/values-hu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hu/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Töltés"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Gyors töltés"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Lassú töltés"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Optimalizált töltés az akkumulátor védelme érdekében"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Probléma van a töltőtartozékkal"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Az akkumulátor védelme érdekében a töltés szünetel"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ellenőrizze a töltőtartozékot"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Hálózat zárolva"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Nincs SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Nem használható SIM."</string>
diff --git a/packages/SystemUI/res-keyguard/values-hy/strings.xml b/packages/SystemUI/res-keyguard/values-hy/strings.xml
index a7c3aba..119928a 100644
--- a/packages/SystemUI/res-keyguard/values-hy/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hy/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Լիցքավորում"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Արագ լիցքավորում"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Դանդաղ լիցքավորում"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Մարտկոցը պաշտպանելու համար լիցքավորումն օպտիմալացվել է"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Լիցքավորիչի հետ կապված խնդիր"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Լիցքավորումը դադարեցվել է՝ մարտկոցը պաշտպանելու համար"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ստուգեք լիցքավորիչը"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Ցանցը կողպված է"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM քարտ չկա"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Անվավեր SIM քարտ։"</string>
diff --git a/packages/SystemUI/res-keyguard/values-in/strings.xml b/packages/SystemUI/res-keyguard/values-in/strings.xml
index f9a840f..f4db035 100644
--- a/packages/SystemUI/res-keyguard/values-in/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-in/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Mengisi daya"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Mengisi daya dengan cepat"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Mengisi daya dengan lambat"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Pengisian daya dioptimalkan untuk melindungi baterai"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Masalah dengan aksesori pengisian daya"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Pengisian daya dihentikan sementara untuk melindungi baterai"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Periksa aksesori pengisi daya"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Jaringan terkunci"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Tidak ada SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM tidak dapat digunakan."</string>
diff --git a/packages/SystemUI/res-keyguard/values-is/strings.xml b/packages/SystemUI/res-keyguard/values-is/strings.xml
index b7147c2..c364f60 100644
--- a/packages/SystemUI/res-keyguard/values-is/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-is/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Í hleðslu"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Hröð hleðsla"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Hæg hleðsla"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Hleðsla fínstillt til að vernda rafhlöðuna"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Vandamál með hleðslubúnað"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Hleðsla í bið til að vernda rafhlöðuna"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Athugaðu hleðslubúnaðinn"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Net læst"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Ekkert SIM-kort"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Ónothæft SIM-kort."</string>
diff --git a/packages/SystemUI/res-keyguard/values-it/strings.xml b/packages/SystemUI/res-keyguard/values-it/strings.xml
index 9e1b187..659928f 100644
--- a/packages/SystemUI/res-keyguard/values-it/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-it/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • In carica"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ricarica veloce"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ricarica lenta"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ricarica ottimizzata per proteggere la batteria"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problema relativo all\'accessorio di ricarica"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ricarica in sospeso per proteggere la batteria"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Controlla l\'accessorio di ricarica"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Rete bloccata"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Nessuna SIM presente"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM inutilizzabile."</string>
diff --git a/packages/SystemUI/res-keyguard/values-iw/strings.xml b/packages/SystemUI/res-keyguard/values-iw/strings.xml
index 16316ce..0021d0a 100644
--- a/packages/SystemUI/res-keyguard/values-iw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-iw/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • בטעינה"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • בטעינה מהירה"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • בטעינה איטית"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • הטעינה עברה אופטימיזציה כדי להגן על הסוללה"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • יש בעיה עם אביזר הטעינה"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • הטעינה הושהתה כדי להגן על הסוללה"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • צריך לבדוק את אביזר הטעינה"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"הרשת נעולה"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"‏אין כרטיס SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"‏לא ניתן להשתמש בכרטיס ה-SIM הזה."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ja/strings.xml b/packages/SystemUI/res-keyguard/values-ja/strings.xml
index 6e8f423..c94d6b1 100644
--- a/packages/SystemUI/res-keyguard/values-ja/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ja/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 充電中"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 急速充電中"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 低速充電中"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • バッテリーを保護するために、充電が最適化されています"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 充電用アクセサリに関する問題"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • バッテリーを保護するため、充電を一時停止しています"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 充電用アクセサリを確認してください"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"ネットワークがロックされました"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM がありません"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM が使用できません。"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ka/strings.xml b/packages/SystemUI/res-keyguard/values-ka/strings.xml
index a31243d..c36b471 100644
--- a/packages/SystemUI/res-keyguard/values-ka/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ka/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • იტენება"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • სწრაფად იტენება"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ნელა იტენება"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • დატენვა ოპტიმიზირებულია ბატარეის დასაცავად"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • დამტენი დამხმარე მოწყობილობის პრობლემა"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • დატენვა შეჩერებულია ბატარეის დასაცავად"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • შეამოწმეთ დამტენი აქსესუარი"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"ქსელი ჩაკეტილია"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM არ არის"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"გამოუყენებელი SIM."</string>
diff --git a/packages/SystemUI/res-keyguard/values-kk/strings.xml b/packages/SystemUI/res-keyguard/values-kk/strings.xml
index 6a77783..fef02be 100644
--- a/packages/SystemUI/res-keyguard/values-kk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kk/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Зарядталуда"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Жылдам зарядтау"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Баяу зарядталуда"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Батареяны қорғау үшін зарядтау оңтайландырылды"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Зарядтау құрылғысына қатысты мәселе туындады."</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Батареяны қорғау мақсатында зарядтау кідіртілді."</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Зарядтау құрылғысын тексеріңіз."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Желі құлыптаулы"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM картасы жоқ."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM картасын пайдалану мүмкін емес."</string>
diff --git a/packages/SystemUI/res-keyguard/values-km/strings.xml b/packages/SystemUI/res-keyguard/values-km/strings.xml
index cda9520..f82defd 100644
--- a/packages/SystemUI/res-keyguard/values-km/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-km/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • កំពុង​សាកថ្ម"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • កំពុង​សាកថ្មយ៉ាង​ឆាប់រហ័ស"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • កំពុង​សាកថ្មយឺត"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • បានបង្កើនប្រសិទ្ធភាពនៃការសាក ដើម្បីការពារថ្ម"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • បញ្ហាពាក់ព័ន្ធនឹងគ្រឿងសាកថ្ម"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • កំពុងផ្អាកការសាកថ្ម ដើម្បីការពារថ្ម"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ពិនិត្យមើលគ្រឿងសាកថ្ម"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"បណ្ដាញ​ជាប់​សោ"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"គ្មានស៊ីមទេ"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ស៊ីមមិនអាចប្រើបាន។"</string>
diff --git a/packages/SystemUI/res-keyguard/values-kn/strings.xml b/packages/SystemUI/res-keyguard/values-kn/strings.xml
index e24005a..4ab035e5 100644
--- a/packages/SystemUI/res-keyguard/values-kn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kn/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ಚಾರ್ಜ್‌ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ವೇಗವಾಗಿ ಚಾರ್ಜ್‌ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ನಿಧಾನವಾಗಿ ಚಾರ್ಜ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ಬ್ಯಾಟರಿಯನ್ನು ರಕ್ಷಿಸಲು ಚಾರ್ಜಿಂಗ್ ಅನ್ನು ಆಪ್ಟಿಮೈಸ್ ಮಾಡಲಾಗಿದೆ"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ಚಾರ್ಜಿಂಗ್ ಪರಿಕರ ಕುರಿತು ಸಮಸ್ಯೆ ಇದೆ"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ಬ್ಯಾಟರಿಯನ್ನು ರಕ್ಷಿಸಲು ಚಾರ್ಜಿಂಗ್ ಅನ್ನು ಹೋಲ್ಡ್‌ ಮಾಡಲಾಗಿದೆ"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ಚಾರ್ಜಿಂಗ್ ಪರಿಕರವನ್ನು ಪರಿಶೀಲಿಸಿ"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"ನೆಟ್‌ವರ್ಕ್ ಲಾಕ್ ಆಗಿದೆ"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM ಇಲ್ಲ"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM ನಿಷ್ಪ್ರಯೋಜಕವಾಗಿದೆ."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ko/strings.xml b/packages/SystemUI/res-keyguard/values-ko/strings.xml
index 7378cc78..d456366 100644
--- a/packages/SystemUI/res-keyguard/values-ko/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ko/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 충전 중"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 고속 충전 중"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 저속 충전 중"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 배터리 보호를 위해 충전 최적화됨"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 충전 액세서리 문제"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 배터리 보호를 위해 충전 일시중지"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 충전 액세서리 확인"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"네트워크 잠김"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM 없음"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM을 사용할 수 없습니다."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ky/strings.xml b/packages/SystemUI/res-keyguard/values-ky/strings.xml
index 88f0b97..e8640ae5 100644
--- a/packages/SystemUI/res-keyguard/values-ky/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ky/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Кубатталууда"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Тез кубатталууда"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Жай кубатталууда"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Батареяны коргоо үчүн кубаттоо процесси оптималдаштырылды"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Кубаттоочу шайманда көйгөй бар"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Батареяны коргоо үчүн кубаттоо тындырылды"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Кубаттоо шайманын текшериңиз"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Тармак кулпуланган"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM карта жок"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Жараксыз SIM карта."</string>
diff --git a/packages/SystemUI/res-keyguard/values-lo/strings.xml b/packages/SystemUI/res-keyguard/values-lo/strings.xml
index 00a382a..6f6c1d1 100644
--- a/packages/SystemUI/res-keyguard/values-lo/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lo/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ກຳລັງສາກ"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ກຳລັງສາກແບບດ່ວນ"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ກຳລັງສາກແບບຊ້າ"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ການສາກຖືກປັບໃຫ້ເໝາະສົມເພື່ອປົກປ້ອງແບັດເຕີຣີ"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ບັນຫາກັບອຸປະກອນເສີມໃນການສາກ"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ຢຸດການສາກຊົ່ວຄາວເພື່ອປົກປ້ອງແບັດເຕີຣີ"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ກວດສອບອຸປະກອນເສີມສຳລັບການສາກ"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"ເຄືອຂ່າຍຖືກລັອກ"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"ບໍ່ມີຊິມ"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ຊິມໃຊ້ບໍ່ໄດ້."</string>
diff --git a/packages/SystemUI/res-keyguard/values-lt/strings.xml b/packages/SystemUI/res-keyguard/values-lt/strings.xml
index 31c4107..9d13485 100644
--- a/packages/SystemUI/res-keyguard/values-lt/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lt/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Įkraunama"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Greitai įkraunama"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Lėtai įkraunama"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Įkrovimas optimizuotas siekiant apsaugoti akumuliatorių"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Su įkrovimo priedu susijusi problema"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • įkrovimas pristabdytas, siekiant apsaugoti akumuliatorių"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • patikrinkite įkrovimo priedą"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Tinklas užrakintas"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Nėra SIM kortelės"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Nenaudojama SIM kortelė."</string>
diff --git a/packages/SystemUI/res-keyguard/values-lv/strings.xml b/packages/SystemUI/res-keyguard/values-lv/strings.xml
index ecf2233..10d657b 100644
--- a/packages/SystemUI/res-keyguard/values-lv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lv/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Notiek uzlāde"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Notiek ātrā uzlāde"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Notiek lēnā uzlāde"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Uzlāde optimizēta, lai saudzētu akumulatoru"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problēma ar uzlādes ierīci"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Uzlāde apturēta, lai aizsargātu akumulatoru"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Pārbaudiet uzlādes piederumu"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Tīkls ir bloķēts."</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Nav SIM kartes"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM karte nav izmantojama."</string>
diff --git a/packages/SystemUI/res-keyguard/values-mk/strings.xml b/packages/SystemUI/res-keyguard/values-mk/strings.xml
index 3f089b9..9aafb86 100644
--- a/packages/SystemUI/res-keyguard/values-mk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mk/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Се полни"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Брзо полнење"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Бавно полнење"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Полнењето е оптимизирано за да се заштити батеријата"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Проблем со додатокот за полнење"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Полнењето е паузирано за да се заштити батеријата"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Проверете го додатокот за полнење"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Мрежата е заклучена"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Нема SIM-картичка"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM-картичката е неупотреблива."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ml/strings.xml b/packages/SystemUI/res-keyguard/values-ml/strings.xml
index be1ea89..f690ce5 100644
--- a/packages/SystemUI/res-keyguard/values-ml/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ml/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ചാർജ് ചെയ്യുന്നു"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • വേഗത്തിൽ ചാർജ് ചെയ്യുന്നു"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • പതുക്കെ ചാർജ് ചെയ്യുന്നു"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ബാറ്ററി പരിരക്ഷിക്കാൻ ചാർജിംഗ് ഒപ്റ്റിമൈസ് ചെയ്തു"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ചാർജിംഗ് ആക്സസറിയുമായി ബന്ധപ്പെട്ട പ്രശ്നം"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ബാറ്ററി പരിരക്ഷിക്കാൻ ചാർജിംഗ് ഹോൾഡിലാണ്"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ചാർജിംഗ് ആക്സസറി പരിശോധിക്കുക"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"നെറ്റ്‌വർക്ക് ലോക്കുചെയ്‌തു"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"സിം ഇല്ല"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ഉപയോഗശൂന്യമായ സിം."</string>
diff --git a/packages/SystemUI/res-keyguard/values-mn/strings.xml b/packages/SystemUI/res-keyguard/values-mn/strings.xml
index 54fdecd..70cfa37 100644
--- a/packages/SystemUI/res-keyguard/values-mn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mn/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Цэнэглэж байна"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Хурдан цэнэглэж байна"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Удаан цэнэглэж байна"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Батарейг хамгаалахын тулд цэнэглэх явцыг оновчилсон"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Цэнэглэх хэрэгсэлд асуудал гарлаа"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Батарейг хамгаалахын тулд цэнэглэхийг хүлээлгэсэн"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Цэнэглэх нэмэлт хэрэгслийг шалгах"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Сүлжээ түгжигдсэн"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM байхгүй"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Ашиглах боломжгүй SIM."</string>
diff --git a/packages/SystemUI/res-keyguard/values-mr/strings.xml b/packages/SystemUI/res-keyguard/values-mr/strings.xml
index eff4c7a..9da4fb0 100644
--- a/packages/SystemUI/res-keyguard/values-mr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mr/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • चार्ज होत आहे"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • वेगाने चार्ज होत आहे"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • सावकाश चार्ज होत आहे"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • बॅटरीचे संरक्षण करण्यासाठी चार्जिंग ऑप्टिमाइझ केले आहे"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • चार्जिंगच्या ॲक्सेसरीसंबंधित समस्या"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • बॅटरीचे संरक्षण करण्यासाठी चार्जिंग थांबवले आहे"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • चार्जिंगसंबंधित ॲक्सेसरी तपासा"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"नेटवर्क लॉक केले"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"सिम नाही"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"वापरण्यायोग्य नसलेले सिम."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ms/strings.xml b/packages/SystemUI/res-keyguard/values-ms/strings.xml
index d9eb4ca..76de213 100644
--- a/packages/SystemUI/res-keyguard/values-ms/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ms/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Mengecas"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Mengecas dengan cepat"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Mengecas dengan perlahan"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Pengecasan dioptimumkan untuk melindungi bateri"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Isu berkaitan aksesori pengecasan"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Pengecasan ditunda untuk melindungi bateri"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Periksa aksesori pengecasan"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Rangkaian dikunci"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Tiada SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM tidak boleh digunakan."</string>
diff --git a/packages/SystemUI/res-keyguard/values-my/strings.xml b/packages/SystemUI/res-keyguard/values-my/strings.xml
index afbce26..bedabb8 100644
--- a/packages/SystemUI/res-keyguard/values-my/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-my/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • အားသွင်းနေသည်"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • အမြန်အားသွင်းနေသည်"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • နှေးကွေးစွာ အားသွင်းနေသည်"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ဘက်ထရီကာကွယ်ရန် အားသွင်းခြင်းကို အကောင်းဆုံးပြင်ဆင်ထားသည်"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • အားသွင်းပစ္စည်းတွင် ပြဿနာရှိသည်"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ဘက်ထရီကို ကာကွယ်ရန် အားသွင်းခြင်းကို ခဏရပ်ထားသည်"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • အားသွင်းပစ္စည်းကို စစ်ပါ"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"ကွန်ရက်ကို လော့ခ်ချထားသည်"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"ဆင်းမ်ကတ် မရှိပါ"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ဆင်းမ်ကတ်ကို သုံး၍မရပါ။"</string>
diff --git a/packages/SystemUI/res-keyguard/values-nb/strings.xml b/packages/SystemUI/res-keyguard/values-nb/strings.xml
index 3098e87..04b567b 100644
--- a/packages/SystemUI/res-keyguard/values-nb/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nb/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Lader"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Lader raskt"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Lader sakte"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ladingen er optimalisert for å beskytte batteriet"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problem med ladetilbehøret"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ladingen er satt på vent for å beskytte batteriet"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> · Sjekk ladetilbehøret"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Nettverket er låst"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Ingen SIM-kort"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM-kortet kan ikke brukes."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ne/strings.xml b/packages/SystemUI/res-keyguard/values-ne/strings.xml
index 45b8819..692e693 100644
--- a/packages/SystemUI/res-keyguard/values-ne/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ne/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • चार्ज गरिँदै"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • द्रुत गतिमा चार्ज गरिँदै छ"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • मन्द गतिमा चार्ज गरिँदै"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ब्याट्री जोगाउन चार्ज गर्ने प्रक्रिया अप्टिमाइज गरिएको छ"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • चार्ज गर्ने एक्सेसरीमा कुनै समस्या आयो"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ब्याट्रीको सुरक्षा गर्न चार्जिङ होल्ड गरिएको छ"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • चार्जिङ एक्सेसरी जाँच्नुहोस्"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"नेटवर्क लक भएको छ"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM कार्ड हालिएको छैन"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"यो SIM कार्ड प्रयोग गर्न मिल्दैन।"</string>
diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml
index af24d40..9833505 100644
--- a/packages/SystemUI/res-keyguard/values-nl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Opladen"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Snel opladen"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Langzaam opladen"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Opladen geoptimaliseerd om de batterij te beschermen"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Probleem met oplaadaccessoire"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Opladen in de wacht gezet om batterij te beschermen"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Check je oplaadaccessoire"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Netwerk vergrendeld"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Geen simkaart"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Onbruikbare simkaart."</string>
diff --git a/packages/SystemUI/res-keyguard/values-or/strings.xml b/packages/SystemUI/res-keyguard/values-or/strings.xml
index 8cae987..5ed2f11 100644
--- a/packages/SystemUI/res-keyguard/values-or/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-or/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ଚାର୍ଜ ହେଉଛି"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ଦ୍ରୁତ ଭାବେ ଚାର୍ଜ ହେଉଛି"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ଧୀରେ ଚାର୍ଜ ହେଉଛି"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ବେଟେରୀକୁ ସୁରକ୍ଷିତ ରଖିବା ପାଇଁ ଚାର୍ଜିଂକୁ ଅପ୍ଟିମାଇଜ କରାଯାଇଛି"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ଚାର୍ଜିଂ ଆକସେସୋରୀ ସହ ସମସ୍ୟା"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ବେଟେରୀକୁ ସୁରକ୍ଷିତ ରଖିବା ପାଇଁ ଚାର୍ଜିଂ ହୋଲ୍ଡରେ ଅଛି"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ଚାର୍ଜିଂ ଆକସେସୋରୀକୁ ଯାଞ୍ଚ କରନ୍ତୁ"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"ନେଟୱର୍କକୁ ଲକ୍‌ କରାଯାଇଛି"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"କୌଣସି SIM ନାହିଁ"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ବ୍ୟବହାର ଅଯୋଗ୍ୟ ଥିବା SIM।"</string>
diff --git a/packages/SystemUI/res-keyguard/values-pa/strings.xml b/packages/SystemUI/res-keyguard/values-pa/strings.xml
index 18959c8..e591e5d2 100644
--- a/packages/SystemUI/res-keyguard/values-pa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pa/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ਤੇਜ਼ੀ ਨਾਲ ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ਹੌਲੀ-ਹੌਲੀ ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ਬੈਟਰੀ ਦੀ ਸੁਰੱਖਿਆ ਲਈ ਚਾਰਜਿੰਗ ਨੂੰ ਸੁਯੋਗ ਬਣਾਇਆ ਗਿਆ"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ਚਾਰਜ ਕਰਨ ਵਾਲੀ ਐਕਸੈਸਰੀ ਸੰਬੰਧੀ ਸਮੱਸਿਆ"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ਬੈਟਰੀ ਦੀ ਸੁਰੱਖਿਆ ਲਈ ਚਾਰਜਿੰਗ ਨੂੰ ਰੋਕਿਆ ਗਿਆ ਹੈ"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ਚਾਰਜਿੰਗ ਐਕਸੈਸਰੀ ਦੀ ਜਾਂਚ ਕਰੋ"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"ਨੈੱਟਵਰਕ  ਲਾਕ  ਕੀਤਾ ਗਿਆ"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"ਕੋਈ ਸਿਮ ਨਹੀਂ ਹੈ"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"ਬੇਕਾਰ ਸਿਮ।"</string>
diff --git a/packages/SystemUI/res-keyguard/values-pl/strings.xml b/packages/SystemUI/res-keyguard/values-pl/strings.xml
index bd00ba9..396fa79 100644
--- a/packages/SystemUI/res-keyguard/values-pl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pl/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ładowanie"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Szybkie ładowanie"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Wolne ładowanie"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ładowanie zoptymalizowane w celu ochrony baterii"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problem z akcesoriami do ładowania"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> – wstrzymano ładowanie, aby chronić baterię"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> – sprawdź akcesoria do ładowania"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Sieć zablokowana"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Brak karty SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Nie można użyć karty SIM."</string>
diff --git a/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
index 54e270f..3de54f9 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carregando"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carregando rapidamente"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carregando lentamente"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carregamento otimizado para proteger a bateria"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problema com o acessório de carregamento"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carregamento suspenso para proteger a bateria"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Verifique o acessório de carregamento"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Rede bloqueada"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Sem chip"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Chip inutilizável."</string>
diff --git a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
index 2e37bde..3d8f7e6 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • A carregar…"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • A carregar rapidamente…"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • A carregar lentamente…"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carregamento otimizado para proteger a bateria"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problema com o acessório de carregamento"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carregamento em espera para proteger a bateria"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Verificar acessório de carregamento"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Rede bloqueada"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Sem SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM inutilizável."</string>
diff --git a/packages/SystemUI/res-keyguard/values-pt/strings.xml b/packages/SystemUI/res-keyguard/values-pt/strings.xml
index 54e270f..3de54f9 100644
--- a/packages/SystemUI/res-keyguard/values-pt/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carregando"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carregando rapidamente"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carregando lentamente"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carregamento otimizado para proteger a bateria"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problema com o acessório de carregamento"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Carregamento suspenso para proteger a bateria"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Verifique o acessório de carregamento"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Rede bloqueada"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Sem chip"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Chip inutilizável."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ro/strings.xml b/packages/SystemUI/res-keyguard/values-ro/strings.xml
index ead09209..053f862 100644
--- a/packages/SystemUI/res-keyguard/values-ro/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ro/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Se încarcă"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Se încarcă rapid"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Se încarcă lent"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Încărcarea este optimizată pentru a proteja bateria"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problemă legată de accesoriul de încărcare"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Încărcarea s-a întrerupt pentru a proteja bateria"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Verifică accesoriul de încărcare"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Rețea blocată"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Niciun card SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Cardul SIM nu se poate folosi."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ru/strings.xml b/packages/SystemUI/res-keyguard/values-ru/strings.xml
index 595fba5..6be1489 100644
--- a/packages/SystemUI/res-keyguard/values-ru/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ru/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"Идет зарядка (<xliff:g id="PERCENTAGE">%s</xliff:g>)"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"Идет быстрая зарядка (<xliff:g id="PERCENTAGE">%s</xliff:g>)"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"Идет медленная зарядка (<xliff:g id="PERCENTAGE">%s</xliff:g>)"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Зарядка оптимизирована для защиты батареи"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Проблема с зарядным устройством"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Зарядка приостановлена для защиты батареи."</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Проверьте зарядное устройство."</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Сеть заблокирована"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM-карта отсутствует"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM-карту невозможно использовать."</string>
diff --git a/packages/SystemUI/res-keyguard/values-si/strings.xml b/packages/SystemUI/res-keyguard/values-si/strings.xml
index b6a7422..401e147 100644
--- a/packages/SystemUI/res-keyguard/values-si/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-si/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ආරෝපණය වෙමින්"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • වේගයෙන් ආරෝපණය වෙමින්"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • සෙමින් ආරෝපණය වෙමින්"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • බැටරිය ආරක්ෂා කිරීම සඳහා ආරෝපණය ප්‍රශස්ත කර ඇත"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ආරෝපණ උපාංගයේ ගැටලුව"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • බැටරිය ආරක්ෂා කිරීම සඳහා ආරෝපණය රඳවා තබා ඇත"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ආරෝපණ ආයිත්තම පරීක්ෂා කරන්න"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"ජාලය අගුළු දමා ඇත"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM නැත"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"භාවිත කළ නොහැකි SIM."</string>
diff --git a/packages/SystemUI/res-keyguard/values-sk/strings.xml b/packages/SystemUI/res-keyguard/values-sk/strings.xml
index 5e34a94..2b65466 100644
--- a/packages/SystemUI/res-keyguard/values-sk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sk/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Nabíja sa"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Nabíja sa rýchlo"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Nabíja sa pomaly"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Nabíjanie je optimalizované, aby sa chránila batéria"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problém s nabíjacím príslušenstvom"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Nabíjanie je pozastavené, aby sa chránila batéria"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Skontrolujte nabíjacie príslušenstvo"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Sieť je zablokovaná"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Žiadna SIM karta"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Nepoužiteľná SIM karta."</string>
diff --git a/packages/SystemUI/res-keyguard/values-sl/strings.xml b/packages/SystemUI/res-keyguard/values-sl/strings.xml
index 3508f3b..dbb34c9 100644
--- a/packages/SystemUI/res-keyguard/values-sl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sl/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • polnjenje"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • hitro polnjenje"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • počasno polnjenje"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Polnjenje je optimizirano zaradi zaščite baterije"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • težava s pripomočkom za polnjenje"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Zaradi zaščite baterije je polnjenje na čakanju"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Preverite pripomoček za polnjenje"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Omrežje je zaklenjeno"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Ni kartice SIM."</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Kartica SIM je neuporabna."</string>
diff --git a/packages/SystemUI/res-keyguard/values-sq/strings.xml b/packages/SystemUI/res-keyguard/values-sq/strings.xml
index 8d71b0f..00c4999 100644
--- a/packages/SystemUI/res-keyguard/values-sq/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sq/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Po karikohet"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Po karikohet me shpejtësi"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Po karikohet ngadalë"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Karikimi u optimizua për të mbrojtur baterinë"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Problem me aksesorin e karikimit"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Karikimi është vendosur në pritje për të mbrojtur baterinë"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Kontrollo aksesorin e karikimit"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Rrjeti është i kyçur"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Nuk ka kartë SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Kartë SIM e papërdorshme."</string>
diff --git a/packages/SystemUI/res-keyguard/values-sr/strings.xml b/packages/SystemUI/res-keyguard/values-sr/strings.xml
index 4093952..feac583 100644
--- a/packages/SystemUI/res-keyguard/values-sr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sr/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Пуни се"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Брзо се пуни"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Споро се пуни"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Пуњење је оптимизовано да би се заштитила батерија"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Проблем са додатним прибором за пуњење"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Пуњење је на чекању да би се заштитила батерија"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Проверите додатну опрему за пуњење"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Мрежа је закључана"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Нема SIM-а"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Неупотребљив SIM."</string>
diff --git a/packages/SystemUI/res-keyguard/values-sv/strings.xml b/packages/SystemUI/res-keyguard/values-sv/strings.xml
index 5b01f39..67fa2f7 100644
--- a/packages/SystemUI/res-keyguard/values-sv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sv/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Laddas"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Laddas snabbt"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Laddas långsamt"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Laddningen har optimerats för att skydda batteriet"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ett problem uppstod med att ladda tillbehöret"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Laddningen har pausats för att skydda batteriet"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Kontrollera laddningstillbehöret"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Nätverk låst"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Inget SIM-kort"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM-kortet går inte att använda."</string>
diff --git a/packages/SystemUI/res-keyguard/values-sw/strings.xml b/packages/SystemUI/res-keyguard/values-sw/strings.xml
index 9bc6c22..527e73e 100644
--- a/packages/SystemUI/res-keyguard/values-sw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sw/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Inachaji"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Inachaji kwa kasi"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Inachaji pole pole"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Hali ya kuchaji imeboreshwa ili kulinda betri"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Kifuasi cha kuchaji kina hitilafu"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Imesitisha kuchaji ili kulinda betri yako"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Kagua kifaa cha kuchaji"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Mtandao umefungwa"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Hakuna SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM haiwezi kutumika."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ta/strings.xml b/packages/SystemUI/res-keyguard/values-ta/strings.xml
index 20eb8ef..05ac0c0 100644
--- a/packages/SystemUI/res-keyguard/values-ta/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ta/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • சார்ஜாகிறது"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • வேகமாகச் சார்ஜாகிறது"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • மெதுவாகச் சார்ஜாகிறது"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • பேட்டரியைப் பாதுகாக்க சார்ஜிங் மேம்படுத்தப்பட்டுள்ளது"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • சார்ஜரில் சிக்கல் உள்ளது"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • பேட்டரியைப் பாதுகாப்பதற்காகச் சார்ஜிங் ஹோல்டு செய்யப்பட்டுள்ளது"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • சார்ஜிங் துணைக்கருவியைச் சரிபார்க்கவும்"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"நெட்வொர்க் பூட்டப்பட்டது"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"சிம் இல்லை"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"பயன்படுத்த முடியாத சிம்."</string>
diff --git a/packages/SystemUI/res-keyguard/values-te/strings.xml b/packages/SystemUI/res-keyguard/values-te/strings.xml
index d496944..9d37fc1 100644
--- a/packages/SystemUI/res-keyguard/values-te/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-te/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ఛార్జ్ అవుతోంది"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • వేగంగా ఛార్జ్ అవుతోంది"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • నెమ్మదిగా ఛార్జ్ అవుతోంది"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • బ్యాటరీని రక్షించడానికి ఛార్జింగ్ ఆప్టిమైజ్ చేయబడింది"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ఛార్జింగ్ యాక్సెసరీతో సమస్య ఉంది"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • బ్యాటరీని రక్షించడానికి ఛార్జింగ్ హోల్డ్‌లో ఉంచబడింది"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ఛార్జింగ్ యాక్సెసరీని చెక్ చేయండి"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"నెట్‌వర్క్ లాక్ చేయబడింది"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM లేదు"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"వినియోగించలేని SIM."</string>
diff --git a/packages/SystemUI/res-keyguard/values-th/strings.xml b/packages/SystemUI/res-keyguard/values-th/strings.xml
index 605d077..57aadea 100644
--- a/packages/SystemUI/res-keyguard/values-th/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-th/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • กำลังชาร์จ"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • กำลังชาร์จอย่างเร็ว"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • กำลังชาร์จอย่างช้าๆ"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ปรับการชาร์จให้เหมาะสมเพื่อถนอมแบตเตอรี่"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ปัญหาเกี่ยวกับอุปกรณ์เสริมสำหรับการชาร์จ"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • หยุดการชาร์จชั่วคราวเพื่อถนอมแบตเตอรี่"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • ตรวจสอบอุปกรณ์เสริมสำหรับการชาร์จ"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"เครือข่ายถูกล็อก"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"ไม่มี SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM ใช้งานไม่ได้"</string>
diff --git a/packages/SystemUI/res-keyguard/values-tl/strings.xml b/packages/SystemUI/res-keyguard/values-tl/strings.xml
index 040ec9e..bb7c344 100644
--- a/packages/SystemUI/res-keyguard/values-tl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tl/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Nagcha-charge"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Mabilis na nagcha-charge"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Mabagal na nagcha-charge"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Naka-optimize ang pag-charge para protektahan ang baterya"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Isyu sa pag-charge ng accessory"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Naka-hold ang pag-charge para protektahan ang baterya"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Suriin ang accessory sa pag-charge"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Naka-lock ang network"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Walang SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Hindi magagamit na SIM."</string>
diff --git a/packages/SystemUI/res-keyguard/values-tr/strings.xml b/packages/SystemUI/res-keyguard/values-tr/strings.xml
index 750ba11..f401629 100644
--- a/packages/SystemUI/res-keyguard/values-tr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tr/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Şarj oluyor"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Hızlı şarj oluyor"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Yavaş şarj oluyor"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Şarj işlemi pili korumak üzere optimize edildi"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Şarj aksesuarı ile ilgili sorun"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Pili korumak için şarj beklemede"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Şarj aksesuarını kontrol edin"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Ağ kilitli"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM yok"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Kullanılamayan SIM."</string>
diff --git a/packages/SystemUI/res-keyguard/values-uk/strings.xml b/packages/SystemUI/res-keyguard/values-uk/strings.xml
index 169ea1f..4330b32 100644
--- a/packages/SystemUI/res-keyguard/values-uk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uk/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Заряджання"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Швидке заряджання"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Повільне заряджання"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Заряджання оптимізовано, щоб захистити акумулятор"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Проблема із зарядним пристроєм"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Заряджання призупинено, щоб захистити акумулятор"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Перевірте зарядний пристрій"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Мережу заблоковано"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Немає SIM-карти"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Непридатна SIM-карта."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ur/strings.xml b/packages/SystemUI/res-keyguard/values-ur/strings.xml
index d7f7b65..e466807 100644
--- a/packages/SystemUI/res-keyguard/values-ur/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ur/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • چارج ہو رہا ہے"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • تیزی سے چارج ہو رہا ہے"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • آہستہ چارج ہو رہا ہے"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • بیٹری کی حفاظت کے لیے چارجنگ کو بہتر بنایا گیا"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • چارجنگ ایکسیسری کے ساتھ مسئلہ"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • بیٹری کی حفاظت کرنے کے لیے چارجنگ ہولڈ پر ہے"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • چارجنگ ایکسیسری چیک کریں"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"نیٹ ورک مقفل ہو گیا"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"‏کوئی SIM نہیں ہے"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"‏ناقابل استعمال SIM۔"</string>
diff --git a/packages/SystemUI/res-keyguard/values-uz/strings.xml b/packages/SystemUI/res-keyguard/values-uz/strings.xml
index 40dbaf3..f3cdf26 100644
--- a/packages/SystemUI/res-keyguard/values-uz/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uz/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Quvvat olmoqda"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Tezkor quvvat olmoqda"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Sekin quvvat olmoqda"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Batareyani himoyalash uchun quvvatlash optimallashtirildi"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Quvvatlash aksessuari bilan muammo"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Batareyani himoyalash uchun quvvatlash toʻxtatildi"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Quvvatlash aksessuarini tekshiring"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Tarmoq qulflangan"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"SIM kartasiz"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"Ishlamaydigan SIM."</string>
diff --git a/packages/SystemUI/res-keyguard/values-vi/strings.xml b/packages/SystemUI/res-keyguard/values-vi/strings.xml
index d5a33d3..3bad8f1 100644
--- a/packages/SystemUI/res-keyguard/values-vi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-vi/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Đang sạc"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Đang sạc nhanh"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Đang sạc chậm"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Quá trình sạc được tối ưu hoá để bảo vệ pin"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Có vấn đề với phụ kiện sạc"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Đang tạm ngưng sạc để bảo vệ pin"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Hãy kiểm tra phụ kiện sạc"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Mạng đã bị khóa"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Không có SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM không sử dụng được."</string>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
index 6de9ff9..2818ffb 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 正在充电"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 正在快速充电"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 正在慢速充电"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 为保护电池,充电方式已优化"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 充电配件有问题"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 为保护电池,充电已暂停"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 请检查充电配件"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"网络已锁定"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"没有 SIM 卡"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM 卡无法使用。"</string>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
index 11966ca..1b196d4 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 正在充電"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 快速充電中"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 慢速充電中"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 為保護電池,系統已優化充電"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 充電配件發生問題"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> - 為保護電池,目前暫停充電"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 檢查充電配件"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"網絡已鎖定"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"沒有 SIM 卡"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM 卡無法使用。"</string>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
index e4f868a..9b7e431 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 充電中"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 快速充電中"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 慢速充電中"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 為保護電池,充電效能已最佳化"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 充電配件有問題"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 為保護電池,目前暫停充電"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • 請檢查充電配件"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"網路已鎖定"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"沒有 SIM 卡"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"SIM 卡無法使用。"</string>
diff --git a/packages/SystemUI/res-keyguard/values-zu/strings.xml b/packages/SystemUI/res-keyguard/values-zu/strings.xml
index 4fadc2e..cd36f95 100644
--- a/packages/SystemUI/res-keyguard/values-zu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zu/strings.xml
@@ -33,8 +33,8 @@
     <string name="keyguard_plugged_in" msgid="8169926454348380863">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Iyashaja"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="4386594091107340426">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ishaja kaningi"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="217655355424210">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ishaja kancane"</string>
-    <string name="keyguard_plugged_in_charging_limited" msgid="1053130519456324630">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ukushaja kuthuthukisiwe ukuze kuvikelwe ibhethri"</string>
-    <string name="keyguard_plugged_in_incompatible_charger" msgid="3687961801947819076">"<xliff:g id="PERCENTAGE">%s</xliff:g> • • Inkinga ngesisekeli sokushaja"</string>
+    <string name="keyguard_plugged_in_charging_limited" msgid="5369697538556777542">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Ukushaja kumisiwe ukuze kuvikelwe ibhethri"</string>
+    <string name="keyguard_plugged_in_incompatible_charger" msgid="6384203333154532125">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Hlola insiza yokushaja"</string>
     <string name="keyguard_network_locked_message" msgid="407096292844868608">"Inethiwekhi ivaliwe"</string>
     <string name="keyguard_missing_sim_message_short" msgid="685029586173458728">"Ayikho i-SIM"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="3955052454216046100">"I-SIM engasebenziseki."</string>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 0628c3e..ddad1e3 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -25,6 +25,12 @@
     <!-- Maximum width of the sliding KeyguardSecurityContainer -->
     <dimen name="keyguard_security_width">420dp</dimen>
 
+    <!-- Width for the keyguard pin input field -->
+    <dimen name="keyguard_pin_field_width">292dp</dimen>
+
+    <!-- Width for the keyguard pin input field -->
+    <dimen name="keyguard_pin_field_height">48dp</dimen>
+
     <!-- Height of the sliding KeyguardSecurityContainer
          (includes 2x keyguard_security_view_top_margin) -->
     <dimen name="keyguard_security_height">420dp</dimen>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 565ed10..f51e109 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -62,10 +62,10 @@
     <string name="keyguard_plugged_in_charging_slowly"><xliff:g id="percentage">%s</xliff:g> • Charging slowly</string>
 
     <!-- When the lock screen is showing and the phone plugged in, and the defend mode is triggered, say that charging is temporarily limited.  -->
-    <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging optimized to protect battery</string>
+    <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging on hold to protect battery</string>
 
     <!-- When the lock screen is showing and the phone plugged in with incompatible charger. -->
-    <string name="keyguard_plugged_in_incompatible_charger"><xliff:g id="percentage">%s</xliff:g> • Issue with charging accessory</string>
+    <string name="keyguard_plugged_in_incompatible_charger"><xliff:g id="percentage">%s</xliff:g> • Check charging accessory</string>
 
     <!-- SIM messages --><skip />
     <!-- When the user inserts a sim card from an unsupported network, it becomes network locked -->
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 2cca951..4789a22 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -76,6 +76,7 @@
     </style>
     <style name="Widget.TextView.Password" parent="@android:style/Widget.TextView">
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:background">@drawable/bouncer_password_text_view_focused_background</item>
         <item name="android:gravity">center</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml
new file mode 100644
index 0000000..045c19e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#fff"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml
new file mode 100644
index 0000000..5e012ab
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z"
+        android:fillAlpha="0.3"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z"
+        android:fillColor="#fff"/>
+</vector>
+
diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml
new file mode 100644
index 0000000..d8a9a70
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z"
+        android:fillColor="#fff"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml b/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
new file mode 100644
index 0000000..dec9930
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    >
+    <path
+        android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
+        android:fillColor="#fff"/>
+    <path
+        android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
+        android:fillColor="#fff"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_no_internet_branded_vpn.xml b/packages/SystemUI/res/drawable/stat_sys_no_internet_branded_vpn.xml
new file mode 100644
index 0000000..2161a62
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_no_internet_branded_vpn.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="17dp"
+    android:height="17dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M12.09,9C11.11,7.5 9.43,6.5 7.5,6.5C4.46,6.5 2,8.96 2,12c0,3.04 2.46,5.5 5.5,5.5c1.93,0 3.61,-1 4.59,-2.5H14v3h4V9H12.09zM18,13hv3h-2v-3h-5.16c-0.43,1.44 -1.76,2.5 -3.34,2.5C5.57,15.5 4,13.93 4,12c0,-1.93 1.57,-3.5 3.5,-3.5c1.58,0 2.9,1.06 3.34,2.5H18V13z"/>
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M7.5,12m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/>
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M22,10h-2v8h2V10z"/>
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M22,20h-2v2h2V20z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_no_internet_vpn_ic.xml b/packages/SystemUI/res/drawable/stat_sys_no_internet_vpn_ic.xml
new file mode 100644
index 0000000..2161a62
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_no_internet_vpn_ic.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="17dp"
+    android:height="17dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M12.09,9C11.11,7.5 9.43,6.5 7.5,6.5C4.46,6.5 2,8.96 2,12c0,3.04 2.46,5.5 5.5,5.5c1.93,0 3.61,-1 4.59,-2.5H14v3h4V9H12.09zM18,13hv3h-2v-3h-5.16c-0.43,1.44 -1.76,2.5 -3.34,2.5C5.57,15.5 4,13.93 4,12c0,-1.93 1.57,-3.5 3.5,-3.5c1.58,0 2.9,1.06 3.34,2.5H18V13z"/>
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M7.5,12m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/>
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M22,10h-2v8h2V10z"/>
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M22,20h-2v2h2V20z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/bindable_status_bar_icon.xml b/packages/SystemUI/res/layout/bindable_status_bar_icon.xml
new file mode 100644
index 0000000..ee4d05c
--- /dev/null
+++ b/packages/SystemUI/res/layout/bindable_status_bar_icon.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!-- Base layout that provides a single bindable icon_view id image view -->
+<com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarIconView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical"
+    >
+
+    <ImageView
+        android:id="@+id/icon_view"
+        android:layout_height="@dimen/status_bar_bindable_icon_size"
+        android:layout_width="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:padding="@dimen/status_bar_bindable_icon_padding"
+        android:scaleType="fitCenter"
+        />
+
+</com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarIconView>
diff --git a/packages/SystemUI/res/layout/biometric_prompt_content_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_content_layout.xml
new file mode 100644
index 0000000..3908757
--- /dev/null
+++ b/packages/SystemUI/res/layout/biometric_prompt_content_layout.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/customized_view"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical"
+    android:orientation="vertical"
+    style="@style/AuthCredentialContentLayoutStyle">
+
+    <TextView
+        android:id="@+id/customized_view_title"
+        style="@style/TextAppearance.AuthCredential.ContentViewTitle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:ellipsize="marquee"
+        android:marqueeRepeatLimit="1"
+        android:singleLine="true" />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/biometric_prompt_content_row_item_text_view.xml b/packages/SystemUI/res/layout/biometric_prompt_content_row_item_text_view.xml
new file mode 100644
index 0000000..e39f60f
--- /dev/null
+++ b/packages/SystemUI/res/layout/biometric_prompt_content_row_item_text_view.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/TextAppearance.AuthCredential.ContentViewListItem"
+    android:layout_width="0dp"
+    android:layout_height="match_parent"
+    android:layout_weight="1.0" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/biometric_prompt_content_row_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_content_row_layout.xml
new file mode 100644
index 0000000..6c86736
--- /dev/null
+++ b/packages/SystemUI/res/layout/biometric_prompt_content_row_layout.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/biometric_prompt_content_list_row_height"
+    android:gravity="center_vertical|start"
+    android:orientation="horizontal" />
diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
index bea0e13..10f7113 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
@@ -20,6 +20,13 @@
     android:layout_height="wrap_content"
     android:orientation="vertical">
 
+    <ImageView
+        android:id="@+id/logo"
+        android:layout_width="@dimen/biometric_auth_icon_size"
+        android:layout_height="@dimen/biometric_auth_icon_size"
+        android:layout_gravity="center"
+        android:scaleType="fitXY"/>
+
     <TextView
         android:id="@+id/title"
         android:layout_width="match_parent"
@@ -48,6 +55,23 @@
         android:importantForAccessibility="no"
         style="@style/TextAppearance.AuthCredential.Description"/>
 
+    <Space
+        android:id="@+id/space_above_content"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/biometric_prompt_space_above_content"
+        android:visibility="gone" />
+
+    <ScrollView
+        android:id="@+id/customized_view_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fadeScrollbars="false"
+        android:gravity="center_vertical"
+        android:orientation="vertical"
+        android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal"
+        android:scrollbars="vertical"
+        android:visibility="gone" />
+
     <Space android:id="@+id/space_above_icon"
         android:layout_width="match_parent"
         android:layout_height="48dp" />
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index 0534c6e..a0f916c 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -17,6 +17,7 @@
 <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:id="@+id/root"
     style="@style/Widget.SliceView.Panel"
     android:layout_width="wrap_content"
@@ -53,17 +54,40 @@
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_title" />
 
+    <View
+        android:id="@+id/bluetooth_tile_dialog_progress_background"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintEnd_toEndOf="@id/bluetooth_tile_dialog_progress_animation"
+        app:layout_constraintStart_toStartOf="@id/bluetooth_tile_dialog_progress_animation"
+        app:layout_constraintTop_toTopOf="@id/bluetooth_tile_dialog_progress_animation"
+        app:layout_constraintBottom_toBottomOf="@id/bluetooth_tile_dialog_progress_animation"
+        android:background="?androidprv:attr/colorSurfaceVariant" />
+
+    <ProgressBar
+        android:id="@+id/bluetooth_tile_dialog_progress_animation"
+        android:layout_width="152dp"
+        android:layout_height="4dp"
+        android:layout_marginTop="16dp"
+        style="@style/TrimmedHorizontalProgressBar"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle"
+        android:visibility="invisible"
+        android:indeterminate="true" />
+
     <androidx.core.widget.NestedScrollView
         android:id="@+id/scroll_view"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginTop="21dp"
+        android:minHeight="145dp"
         android:fillViewport="true"
         app:layout_constrainedHeight="true"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle">
+        app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_progress_animation">
 
         <androidx.constraintlayout.widget.ConstraintLayout
             android:id="@+id/scroll_layout"
@@ -81,7 +105,7 @@
                 android:paddingStart="36dp"
                 android:text="@string/turn_on_bluetooth"
                 android:clickable="false"
-                android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+                android:textAppearance="@style/TextAppearance.Dialog.Title"
                 android:textSize="16sp"
                 app:layout_constraintEnd_toStartOf="@+id/bluetooth_toggle"
                 app:layout_constraintStart_toStartOf="parent"
@@ -90,8 +114,7 @@
             <Switch
                 android:id="@+id/bluetooth_toggle"
                 android:layout_width="wrap_content"
-                android:layout_height="48dp"
-                android:paddingTop="10dp"
+                android:layout_height="64dp"
                 android:gravity="start|center_vertical"
                 android:paddingEnd="40dp"
                 android:contentDescription="@string/turn_on_bluetooth"
@@ -107,7 +130,6 @@
                 android:id="@+id/device_list"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_marginTop="20dp"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintTop_toBottomOf="@+id/bluetooth_toggle"
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index 16eba22..1365a11 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -17,6 +17,7 @@
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:id="@+id/internet_connectivity_dialog"
     android:layout_width="@dimen/large_dialog_width"
@@ -386,9 +387,8 @@
                 </LinearLayout>
             </LinearLayout>
 
-            <LinearLayout
+            <androidx.constraintlayout.widget.ConstraintLayout
                 android:id="@+id/button_layout"
-                android:orientation="horizontal"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="8dp"
@@ -398,53 +398,58 @@
                 android:clickable="false"
                 android:focusable="false">
 
-                <LinearLayout
+                <Button
+                    android:id="@+id/apm_button"
+                    style="@style/Widget.Dialog.Button.BorderButton"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:layout_weight="1"
-                    android:layout_gravity="start|center_vertical"
-                    android:orientation="horizontal">
-                    <Button
-                        android:id="@+id/apm_button"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:text="@string/turn_off_airplane_mode"
-                        android:ellipsize="end"
-                        android:maxLines="1"
-                        style="@style/Widget.Dialog.Button.BorderButton"
-                        android:clickable="true"
-                        android:focusable="true"/>
+                    android:layout_marginEnd="10dp"
+                    android:clickable="true"
+                    android:ellipsize="end"
+                    android:focusable="true"
+                    android:maxLines="1"
+                    android:text="@string/turn_off_airplane_mode"
+                    app:layout_constrainedWidth="true"
+                    app:layout_constraintHorizontal_bias="0"
+                    app:layout_constraintBottom_toBottomOf="parent"
+                    app:layout_constraintEnd_toStartOf="@id/share_wifi_button"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toTopOf="parent" />
 
-                    <Button
-                        android:id="@+id/share_wifi_button"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:text="@string/share_wifi_button_text"
-                        style="?android:attr/buttonBarNeutralButtonStyle"
-                        android:maxLines="1"
-                        android:ellipsize="end"
-                        android:clickable="true"
-                        android:focusable="true"
-                        android:visibility="gone"/>
-                </LinearLayout>
-
-                <LinearLayout
+                <Button
+                    android:id="@+id/share_wifi_button"
+                    style="?android:attr/buttonBarNeutralButtonStyle"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:layout_marginStart="16dp"
-                    android:layout_gravity="end|center_vertical">
-                    <Button
-                        android:id="@+id/done_button"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:text="@string/inline_done_button"
-                        style="@style/Widget.Dialog.Button"
-                        android:maxLines="1"
-                        android:ellipsize="end"
-                        android:clickable="true"
-                        android:focusable="true"/>
-                </LinearLayout>
-            </LinearLayout>
+                    android:layout_marginEnd="10dp"
+                    android:clickable="true"
+                    android:ellipsize="end"
+                    android:focusable="true"
+                    android:maxLines="1"
+                    android:visibility="gone"
+                    app:layout_constraintHorizontal_bias="0"
+                    android:text="@string/share_wifi_button_text"
+                    app:layout_constrainedWidth="true"
+                    app:layout_constraintBottom_toBottomOf="parent"
+                    app:layout_constraintEnd_toStartOf="@id/done_button"
+                    app:layout_constraintStart_toEndOf="@id/apm_button"
+                    app:layout_constraintTop_toTopOf="parent" />
+
+                <Button
+                    android:id="@+id/done_button"
+                    style="@style/Widget.Dialog.Button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:clickable="true"
+                    android:ellipsize="end"
+                    android:focusable="true"
+                    android:maxLines="1"
+                    android:text="@string/inline_done_button"
+                    app:layout_constrainedWidth="true"
+                    app:layout_constraintBottom_toBottomOf="parent"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintTop_toTopOf="parent" />
+            </androidx.constraintlayout.widget.ConstraintLayout>
 
         </LinearLayout>
     </androidx.core.widget.NestedScrollView>
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 6d7ce06..e602d6c 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -107,4 +107,13 @@
 
     <include layout="@layout/ambient_indication"
              android:id="@id/ambient_indication_container" />
+
+    <FrameLayout
+        android:id="@+id/smartspace_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom"
+        android:layout_marginBottom="@dimen/ambient_indication_margin_bottom"
+        android:visibility="gone">
+    </FrameLayout>
 </com.android.systemui.statusbar.phone.KeyguardBottomAreaView>
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index fc0bf24..4cb7591 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -23,7 +23,6 @@
     android:layout_width="match_parent"
     android:layout_height="@dimen/status_bar_header_height_keyguard"
     android:baselineAligned="false"
-    android:gravity="center_vertical"
     >
 
     <LinearLayout
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 85b6e8d..5db9eee 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -50,7 +50,10 @@
         app:layout_constraintStart_toStartOf="@id/album_art"
         app:layout_constraintEnd_toEndOf="@id/album_art"
         app:layout_constraintTop_toTopOf="@id/album_art"
-        app:layout_constraintBottom_toBottomOf="@id/album_art" />
+        app:layout_constraintBottom_toBottomOf="@id/album_art"
+        android:clipToOutline="true"
+        android:background="@drawable/qs_media_outline_layout_bg"
+        />
 
     <com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
         android:id="@+id/turbulence_noise_view"
@@ -59,7 +62,10 @@
         app:layout_constraintStart_toStartOf="@id/album_art"
         app:layout_constraintEnd_toEndOf="@id/album_art"
         app:layout_constraintTop_toTopOf="@id/album_art"
-        app:layout_constraintBottom_toBottomOf="@id/album_art" />
+        app:layout_constraintBottom_toBottomOf="@id/album_art"
+        android:clipToOutline="true"
+        android:background="@drawable/qs_media_outline_layout_bg"
+        />
 
     <!-- Guideline for output switcher -->
     <androidx.constraintlayout.widget.Guideline
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
index 2c7467d..fab7840 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
@@ -27,7 +27,7 @@
     tools:parentTag="com.android.systemui.privacy.OngoingPrivacyChip">
     >
 
-        <LinearLayout
+        <com.android.systemui.animation.view.LaunchableLinearLayout
             android:id="@+id/icons_container"
             android:layout_height="@dimen/ongoing_appops_chip_height"
             android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/power_notification_controls_settings.xml b/packages/SystemUI/res/layout/power_notification_controls_settings.xml
deleted file mode 100644
index 83c8a51..0000000
--- a/packages/SystemUI/res/layout/power_notification_controls_settings.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
-
-    <include layout="@layout/switch_bar" />
-
-    <TextView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:padding="16dp"
-            android:text="@string/power_notification_controls_description"/>
-
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_customize_panel_content.xml b/packages/SystemUI/res/layout/qs_customize_panel_content.xml
index 3be9993..156c983 100644
--- a/packages/SystemUI/res/layout/qs_customize_panel_content.xml
+++ b/packages/SystemUI/res/layout/qs_customize_panel_content.xml
@@ -37,6 +37,7 @@
             android:layout_height="wrap_content"
             android:background="@drawable/qs_customizer_toolbar"
             android:navigationContentDescription="@*android:string/action_bar_up_description"
+            android:titleTextAppearance="@*android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"
             style="@style/QSCustomizeToolbar"
             />
 
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 73874a0..b3f32a2 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -19,7 +19,7 @@
 <com.android.systemui.qs.QSFooterView xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/qs_footer"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
+    android:layout_height="@dimen/qs_footer_height"
     android:layout_marginStart="@dimen/qs_footer_margin"
     android:layout_marginEnd="@dimen/qs_footer_margin"
     android:layout_marginBottom="@dimen/qs_footers_margin_bottom"
@@ -31,7 +31,7 @@
 
         <LinearLayout
             android:layout_width="match_parent"
-            android:layout_height="@dimen/qs_footer_height"
+            android:layout_height="match_parent"
             android:layout_gravity="center_vertical">
 
             <TextView
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index dca84b9..b792acc 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -27,10 +27,11 @@
     android:fitsSystemWindows="true">
 
     <!-- Placeholder for the communal UI that will be replaced if the feature is enabled. -->
-    <ViewStub
+    <View
         android:id="@+id/communal_ui_stub"
         android:layout_width="match_parent"
-        android:layout_height="match_parent" />
+        android:layout_height="match_parent"
+        android:visibility="gone" />
 
     <com.android.systemui.scrim.ScrimView
         android:id="@+id/scrim_behind"
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index db4cca3..32ea2ce 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Kon nie Gesigslot opstel nie. Gaan na Instellings toe om weer te probeer."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Raak die vingerafdruksensor"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Druk die ontsluitikoon om voort te gaan"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Kan nie gesig herken nie. Gebruik eerder vingerafdruk."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Gesig word nie herken nie. Gebruik eerder vingerafdruk."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Kan nie gesig herken nie"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Gesig word nie herken nie"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Gebruik eerder vingerafdruk"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Gesigslot is onbeskikbaar"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth gekoppel."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laai tans • Vol oor <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swiep links om die gemeenskaplike tutoriaal te begin"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Maak die legstukredigeerder oop"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Pasmaak"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Maak toe"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Voeg by, verwyder en herrangskik jou legstukke in hierdie spasie"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Voeg meer legstukke by"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Langdruk om legstukke te pasmaak"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Verwyder"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Voeg legstuk by"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Klaar"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Skakel Bluetooth aan?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Jy moet Bluetooth aanskakel om jou sleutelbord aan jou tablet te koppel."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Skakel aan"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Kragkennisgewingkontroles"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Aan – gesiggegrond"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Met kragkennisgewingkontroles kan jy \'n belangrikheidvlak van 0 tot 5 vir \'n program se kennisgewings stel. \n\n"<b>"Vlak 5"</b>" \n- Wys aan die bokant van die kennisgewinglys \n- Laat volskermonderbreking toe \n- Wys altyd opspringkennisgewings \n\n"<b>"Vlak 4"</b>" \n- Verhoed volskermonderbreking \n- Wys altyd opspringkennisgewings \n\n"<b>"Vlak 3"</b>" \n- Verhoed volskermonderbreking \n- Verhoed opspringkennisgewings \n\n"<b>"Vlak 2"</b>" \n- Verhoed volskermonderbreking \n- Verhoed opspringkennisgewings \n- Moet nooit \'n klank maak of vibreer nie \n\n"<b>"Vlak 1"</b>" \n- Verhoed volskermonderbreking \n- Verhoed opspringkennisgewings \n- Moet nooit \'n klank maak of vibreer nie \n- Versteek van sluitskerm en statusbalk \n- Wys aan die onderkant van die kennisgewinglys \n\n"<b>"Vlak 0"</b>" \n- Blokkeer alle kennisgewings van die program af"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Klaar"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Pas toe"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Skakel kennisgewings af"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Opstelling"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Berging"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Wenke"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Toeganklikheid"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Kitsprogramme"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> loop tans"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Program is oopgemaak sonder dat dit geïnstalleer is."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tik om toeganklikheidkenmerke oop te maak Pasmaak of vervang knoppie in Instellings.\n\n"<annotation id="link">"Bekyk instellings"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Skuif knoppie na kant om dit tydelik te versteek"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Ontdoen"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Toeganklikheidknoppie is versteek"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Tik om toeganklikheidknoppie te wys"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g>-kortpad is verwyder"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# kortpad is verwyder}other{# kortpaaie is verwyder}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Beweeg na links bo"</string>
@@ -1207,9 +1217,10 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Gebruikerteenwoordigheid is bespeur"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Stel versteknotasapp in Instellings"</string>
     <string name="install_app" msgid="5066668100199613936">"Installeer app"</string>
-    <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Sinkroniseer wedersyds na eksterne skerm?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
     <skip />
+    <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Sinkroniseer wedersyds na eksterne skerm?"</string>
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Jou binneste skerm sal weerspieël word. Jou boonste skerm sal afgeskakel word."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Sinkroniseer skerm wedersyds"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Maak toe"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Skerm is gekoppel"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 40fddc8..12bca7d 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"በመልክ መክፈትን ማዋቀር አልተቻለም። እንደገና ለመሞከር ወደ ቅንብሮች ይሂዱ።"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"የጣት አሻራ ዳሳሹን ይንኩ"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"ለመቀጠል የክፈት አዶውን ይጫኑ"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"መልክን መለየት አልተቻለም። በምትኩ የጣት አሻራ ይጠቀሙ።"</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"ፊቱ አልታወቀም። በምትኩ የጣት አሻራ ይጠቀሙ።"</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"መልክን መለየት አልተቻለም"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"ፊቱ አልታወቀም።"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"በምትኩ የጣት አሻራን ይጠቀሙ"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"በመልክ መክፈት አይገኝም"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ብሉቱዝ ተያይዟል።"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ኃይል በመሙላት ላይ • በ<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ውስጥ ይሞላል"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"የጋራ አጋዥ ሥልጠናውን ለመጀመር ወደ ግራ ያንሸራትቱ።"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"የምግብር አርታዒውን ይክፈቱ"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"አብጅ"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"አሰናብት"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"በዚህ ቦታ ላይ የእርስዎን ምግብሮች ያክሉ፣ ያስወግዱ እና እንደገና ቅደም ተከተል ያስይዙ"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ተጨማሪ ምግብሮችን ያክሉ"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ምግብሮችን ለማበጀት በረጅሙ ይጫኑ"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"አስወግድ"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ምግብር አክል"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"ተከናውኗል"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"ብሉቱዝ ይብራ?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"የቁልፍ ሰሌዳዎን ከእርስዎ ጡባዊ ጋር ለማገናኘት በመጀመሪያ ብሉቱዝን ማብራት አለብዎት።"</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"አብራ"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"የኃይል ማሳወቂያ መቆጣጠሪያዎች"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"በርቷል - መልክ ላይ የተመሠረተ"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"በኃይል ማሳወቂያ መቆጣጠሪያዎች አማካኝነት የአንድ መተግበሪያ ማሳወቂያዎች የአስፈላጊነት ደረጃ ከ0 እስከ 5 ድረስ ማዘጋጀት ይችላሉ። \n\n"<b>"ደረጃ 5"</b>" \n- በማሳወቂያ ዝርዝሩ አናት ላይ አሳይ \n- የሙሉ ማያ ገፅ ማቋረጥን ፍቀድ \n- ሁልጊዜ አጮልቀው ይመልከቱ \n\n"<b>"ደረጃ 4"</b>" \n- የሙሉ ማያ ገፅ ማቋረጥን ከልክል \n- ሁልጊዜ አጮልቀው ይመልከቱ \n\n"<b>"ደረጃ 3"</b>" \n- የሙሉ ማያ ገፅ ማቋረጥን ከልክል \n- በፍጹም አጮልቀው አይምልከቱ \n\n"<b>"ደረጃ 2"</b>" \n- የሙሉ ማያ ገፅ ማቋረጥን ይከልክሉ \n- በፍጹም አጮልቀው አይመልከቱ \n- ድምፅ እና ንዝረትን በፍጹም አይኑር \n\n"<b>"ደረጃ 1"</b>" \n- የሙሉ ማያ ገፅ ማቋረጥን ይከልክሉ \n- በፍጹም አጮልቀው አይመልከቱ \n- ድምፅ ወይም ንዝረትን በፍጹም አያደርጉ \n- ከመቆለፊያ ገፅ እና የሁኔታ አሞሌ ይደብቁ \n- በማሳወቂያ ዝርዝር ግርጌ ላይ አሳይ \n\n"<b>"ደረጃ 0"</b>" \n- ሁሉንም የመተግበሪያው ማሳወቂያዎች ያግዱ"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"ተከናውኗል"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"ተግብር"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"ማሳወቂያዎችን አጥፋ"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"ውቅረት"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"ማከማቻ"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"ፍንጮች"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"ተደራሽነት"</string>
     <string name="instant_apps" msgid="8337185853050247304">"የቅጽበት መተግበሪያዎች"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> አሂድ"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"መተግበሪያ ሳይጫን ተከፍቷል።"</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"የተደራሽነት ባህሪያትን ለመክፈት መታ ያድርጉ። ይህንን አዝራር በቅንብሮች ውስጥ ያብጁ ወይም ይተኩ።\n\n"<annotation id="link">"ቅንብሮችን አሳይ"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ለጊዜው ለመደበቅ አዝራሩን ወደ ጠርዝ ያንቀሳቅሱ"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"ቀልብስ"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"የተደራሽነት አዝራር ተደብቋል"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"የተደራሽነት አዝራርን ለማሳየት መታ ያድርጉ"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> አቋራጭ ተወግዷል"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# አቋራጭ ተወግዷል}one{# አቋራጭ ተወግዷል}other{# አቋራጮች ተወግደዋል}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ወደ ላይኛው ግራ አንቀሳቅስ"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"የተጠቃሚ ተገኝነት ታውቋል"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"በቅንብሮች ውስጥ ነባሪ የማስታወሻዎች መተግበሪያን ያቀናብሩ"</string>
     <string name="install_app" msgid="5066668100199613936">"መተግበሪያን ጫን"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ወደ ውጫዊ ማሳያ ይንጸባረቅ?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"የውስጥ ማሳያዎ ይንጸባረቃል። የፊት ማሳያዎ ይጠፋል።"</string>
     <string name="mirror_display" msgid="2515262008898122928">"ማሳያን አንጸባርቅ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index d19c77b..1101e20 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"تعذّر إعداد ميزة \"فتح الجهاز بالتعرّف على الوجه\". انتقِل إلى \"الإعدادات\" لإعادة المحاولة."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"المس أداة استشعار بصمة الإصبع"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"للمتابعة، اضغط على رمز فتح القفل."</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"يتعذّر التعرّف على الوجه. استخدِم بصمة الإصبع بدلاً من ذلك."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"يتعذّر التعرّف على الوجه. يمكنك استخدام بصمة إصبعك."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"يتعذّر التعرّف على الوجه."</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"يتعذّر التعرّف على الوجه."</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"يمكنك استخدام بصمة إصبعك."</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"ميزة \"فتح الجهاز بالتعرف على الوجه\" غير متاحة."</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"تم توصيل البلوتوث."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • جارٍ الشحن • ستمتلئ البطارية خلال <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"مرِّر سريعًا لليمين لبدء الدليل التوجيهي العام."</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"فتح محرِّر التطبيقات المصغّرة"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"تخصيص"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"إغلاق"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"يمكنك في هذه المساحة إضافة التطبيقات المصغّرة وإزالتها وإعادة ترتيبها."</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"إضافة المزيد من التطبيقات المصغّرة"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"اضغط مع الاستمرار لتخصيص التطبيقات المصغّرة."</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"إزالة"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"إضافة تطبيق مصغّر"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"تم"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"تفعيل البلوتوث؟"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"لتوصيل لوحة المفاتيح بالجهاز اللوحي، يلزمك تفعيل بلوتوث أولاً."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"تفعيل"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"عناصر التحكم في إشعارات التشغيل"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"تفعيل - استنادًا للوجه"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"باستخدام عناصر التحكم في إشعار التشغيل، يمكنك ضبط مستوى الأهمية من 0 إلى 5 لإشعارات التطبيق. \n\n"<b>"المستوى 5"</b>" \n- العرض أعلى قائمة الإشعارات \n- يسمح بمقاطعة ملء الشاشة \n- الظهور الخاطف دائمًا \n\n"<b>"المستوى 4"</b>" \n- منع مقاطعة ملء الشاشة \n- الظهور الخاطف دائمًا \n\n"<b>"المستوى 3"</b>" \n- منع مقاطعة ملء الشاشة \n- عدم الظهور الخاطف أبدًا \n\n"<b>"المستوى 2"</b>" \n- منع مقاطعة ملء الشاشة \n- عدم الظهور الخاطف أبدًا \n- عدم إصدار أصوات واهتزاز \n\n"<b>"المستوى 1"</b>" \n- منع مقاطعة ملء الشاشة \n- عدم الظهور الخاطف أبدًا \n- عدم إصدار أصوات أو اهتزاز أبدًا \n- الإخفاء من شاشة القفل وشريط الحالة \n- العرض أسفل قائمة الإشعارات \n\n"<b>"المستوى 0"</b>" \n- حظر جميع الإشعارات من التطبيق"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"تمّ"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"تطبيق"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"إيقاف الإشعارات"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"عملية الإعداد"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"مساحة التخزين"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"تلميحات"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"أدوات تسهيل الاستخدام"</string>
     <string name="instant_apps" msgid="8337185853050247304">"التطبيقات الفورية"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"التطبيق <xliff:g id="APP">%1$s</xliff:g> قيد التشغيل"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"تمّ فتح التطبيق بدون تثبيته."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"انقر لفتح ميزات تسهيل الاستخدام. يمكنك تخصيص هذا الزر أو استبداله من الإعدادات.\n\n"<annotation id="link">"عرض الإعدادات"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"يمكنك نقل الزر إلى الحافة لإخفائه مؤقتًا."</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"تراجع"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"زر أدوات تسهيل الاستخدام مخفي"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"انقر لإظهار زر أدوات تسهيل الاستخدام."</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"تمت إزالة اختصار <xliff:g id="FEATURE_NAME">%s</xliff:g>."</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{تمت إزالة اختصار واحد.}zero{تمت إزالة # اختصار.}two{تمت إزالة اختصارَين.}few{تمت إزالة # اختصارات.}many{تمت إزالة # اختصارًا.}other{تمت إزالة # اختصار.}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"النقل إلى أعلى يمين الشاشة"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"تم رصد تواجد المستخدم."</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"يمكنك ضبط تطبيق تدوين الملاحظات التلقائي في \"الإعدادات\"."</string>
     <string name="install_app" msgid="5066668100199613936">"تثبيت التطبيق"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"هل تريد بث محتوى جهازك على الشاشة الخارجية؟"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"سيتم النسخ المطابق لمحتوى الشاشة الداخلية، وإيقاف الشاشة الأمامية."</string>
     <string name="mirror_display" msgid="2515262008898122928">"بث المحتوى على الشاشة"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 4e78f55..3050685 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"ফে’চ আনলক ছেট আপ কৰিব পৰা নগ’ল। পুনৰ চেষ্টা কৰিবলৈ ছেটিঙলৈ যাওক।"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"ফিংগাৰপ্ৰিণ্ট ছেন্সৰটো স্পৰ্শ কৰক"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"অব্যাহত ৰাখিবলৈ আনলক কৰক চিহ্নটোত টিপক"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"মুখাৱয়ব চিনিব নোৱাৰি। ফিংগাৰপ্ৰিণ্ট ব্যৱহাৰ কৰক।"</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"মুখাৱয়ব চিনাক্ত কৰিব পৰা নাই। ইয়াৰ পৰিৱৰ্তে ফিংগাৰপ্ৰিণ্ট ব্যৱহাৰ কৰক।"</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"মুখাৱয়ব চিনিব নোৱাৰি"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"মুখাৱয়ব চিনাক্ত কৰিব পৰা নাই"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ইয়াৰ সলনি ফিংগাৰপ্ৰিণ্ট ব্যৱহাৰ কৰক"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"ফেচ আনলক সুবিধা উপলব্ধ নহয়"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ব্লুটুথ সংযোগ হ’ল।"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • চাৰ্জ হৈ আছে • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ত সম্পূৰ্ণ হ’ব"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"সম্প্ৰদায় সম্পৰ্কীয় নিৰ্দেশনা আৰম্ভ কৰিবলৈ বাওঁফালে ছোৱাইপ কৰক"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ৱিজেট সম্পাদকটো খোলক"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"কাষ্টমাইজ কৰক"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"অগ্ৰাহ্য কৰক"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"এই স্পেচটোত আপোনাৰ ৱিজেটসমূহ যোগ দিয়ক, আঁতৰাওক আৰু সেইসমূহৰ ক্ৰম সলনি কৰক"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"অধিক ৱিজেট যোগ দিয়ক"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ৱিজেট কাষ্টমাইজ কৰিবলৈ দীঘলীয়াকৈ টিপক"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"আঁতৰাওক"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ৱিজেট যোগ দিয়ক"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"কৰা হ’ল"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"ব্লুটুথ অন কৰিবনে?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"আপোনাৰ টেবলেটত আপোনাৰ কীব\'ৰ্ড সংযোগ কৰিবলৈ আপুনি প্ৰথমে ব্লুটুথ অন কৰিব লাগিব।"</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"অন কৰক"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"জাননী নিয়ন্ত্ৰণৰ অধিক কৰ্তৃত্ব"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"অন আছে - মুখাৱয়ব ভিত্তিক"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"জাননী নিয়ন্ত্ৰণৰ অধিক কৰ্তৃত্বৰ সৈতে আপুনি এটা এপৰ জাননীৰ গুৰুত্বৰ স্তৰ ০ৰ পৰা ৫লৈ ছেট কৰিব পাৰে।\n\n"<b>"স্তৰ ৫"</b>" \n- জাননী তালিকাৰ একেবাৰে ওপৰত দেখুৱাওক \n- সম্পূৰ্ণ স্ক্ৰীনত থাকোঁতে ব্যাঘাত জন্মাবলৈ অনুমতি দিয়ক\n- সদায় ভুমুকি মাৰিবলৈ দিয়ক\n\n"<b>"স্তৰ ৪"</b>" \n- সম্পূৰ্ণ স্ক্ৰীনত থাকোঁতে ব্যাঘাত জন্মাবলৈ নিদিব\n- সদায় ভুমুকি মাৰিবলৈ দিয়ক\n\n"<b>"স্তৰ ৩"</b>" \n- সম্পূৰ্ণ স্ক্ৰীনত থাকোঁতে ব্যাঘাত জন্মাবলৈ নিদিব\n- কেতিয়াও ভুমুকি মাৰিবলৈ নিদিব\n\n"<b>"স্তৰ ২"</b>" \n- সম্পূর্ণ স্ক্ৰীনত থাকোঁতে ব্যাঘাত জন্মাবলৈ নিদিব \n- কেতিয়াও ভুমুকি মাৰিবলৈ নিদিব\n- কেতিয়াও শব্দ আৰু কম্পন কৰিবলৈ নিদিব\n\n"<b>" স্তৰ ১"</b>" \n- সম্পূৰ্ণ স্ক্ৰীনত থাকোঁতে ব্যাঘাত জন্মাবলৈ নিদিব\n- কেতিয়াও ভুমুকি মাৰিবলৈ নিদিব\n-কেতিয়াও শব্দ আৰু কম্পন কৰিবলৈ নিদিব \n- লক স্ক্ৰীন আৰু স্থিতি দণ্ডৰ পৰা লুকুৱাই ৰাখক \n- জাননী তালিকাৰ একেবাৰে তলত দেখুৱাওক\n\n"<b>"স্তৰ ০"</b>" \n- এই এপৰ আটাইবোৰ জাননী অৱৰোধ কৰক"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"কৰা হ’ল"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"প্ৰয়োগ কৰক"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"জাননী অফ কৰক"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"ছেটআপ"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"ষ্ট\'ৰেজ"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"ইংগিতবোৰ"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"সাধ্য সুবিধা"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> চলি আছে"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"এপ্‌টো ইনষ্ট\'ল নকৰাকৈ খোলা হৈছে।"</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"সাধ্য সুবিধাসমূহ খুলিবলৈ টিপক। ছেটিঙত এই বুটামটো কাষ্টমাইজ অথবা সলনি কৰক।\n\n"<annotation id="link">"ছেটিং চাওক"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"বুটামটোক সাময়িকভাৱে লুকুৱাবলৈ ইয়াক একেবাৰে কাষলৈ লৈ যাওক"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"আনডু কৰক"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"সাধ্য-সুবিধা বুটাম লুকুৱাই ৰখা হৈছে"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"সাধ্য-সুবিধা বুটাম দেখুৱাবলৈ টিপক"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g>ৰ শ্বৰ্টকাট আঁতৰোৱা হ’ল"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# টা শ্বৰ্টকাট আঁতৰোৱা হ’ল}one{# টা শ্বৰ্টকাট আঁতৰোৱা হ’ল}other{# টা শ্বৰ্টকাট আঁতৰোৱা হ’ল}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"শীৰ্ষৰ বাওঁফালে নিয়ক"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"ব্যৱহাৰকাৰীৰ উপস্থিতি চিনাক্ত কৰা হৈছে"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ছেটিঙত টোকাৰ ডিফ’ল্ট এপ্ ছেট কৰক"</string>
     <string name="install_app" msgid="5066668100199613936">"এপ্‌টো ইনষ্টল কৰক"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"বাহ্যিক ডিছপ্লে’লৈ মিৰ’ৰ কৰিবনে?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"আপোনাৰ ইনাৰ ডিছপ্লে’ প্ৰতিবিম্বিত কৰা হ’ব। আপোনাৰ ফ্ৰণ্ট ডিছপ্লে’ অফ কৰা হ’ব।"</string>
     <string name="mirror_display" msgid="2515262008898122928">"ডিছপ্লে’ মিৰ’ৰ কৰক"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index bf32c5e..3c3462b 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Üz ilə Kiliddən Açma ayarlanmadı. Yenidən cəhd etmək üçün Ayarlara keçin."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Barmaq izi sensoruna klikləyin"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"\"Kiliddən çıxarın\" ikonasını basaraq davam edin"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Tanımaq olmur. Barmaq izini işlədin."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Üz tanınmadı. Barmaq izindən istifadə edin."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Üzü tanımaq olmur"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Üz tanınmadı"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Barmaq izi istifadə edin"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Üz ilə kiliddən çıxarma əlçatan deyil"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth qoşulub."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Şarj edilir • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> sonra dolacaq"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"İcma təlimatını başlatmaq üçün sola sürüşdürün"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Vidcet redaktorunu açın"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Fərdiləşdirin"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Bağlayın"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Burada vidcetlər əlavə edin, silin və sırasını dəyişin"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Vidcetlər əlavə edin"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Basıb saxlayaraq vidcetləri fərdiləşdirin"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Silin"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Vidcet əlavə edin"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Hazırdır"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Bluetooth aktivləşsin?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Tabletinizlə klaviaturaya bağlanmaq üçün ilk olaraq Bluetooth\'u aktivləşdirməlisiniz."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Aktivləşdirin"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Enerji bildiriş nəzarəti"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Aktiv - Üz əsaslı"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Enerji bildiriş nəzarəti ilə, tətbiq bildirişləri üçün əhəmiyyət səviyyəsini 0-dan 5-ə kimi ayarlaya bilərsiniz. \n\n"<b>"Səviyyə 5"</b>" \n- Bildiriş siyahısının yuxarı hissəsində göstərin \n- Tam ekran kəsintisinə icazə verin \n- Hər zaman izləyin \n\n"<b>"Səviyyə 4"</b>" \n- Tam ekran kəsintisinin qarşısını alın \n- Hər zaman izləyin \n\n"<b>"Level 3"</b>" \n- Tam ekran kəsintisinin qarşısını alın \n- Heç vaxt izləməyin \n\n"<b>"Level 2"</b>" \n- Tam ekran kəsintisinin qarşısını alın \n- Heç vaxt izləməyin \n- Heç vaxt səsliyə və ya vibrasiyaya qoymayın \n\n"<b>"Səviyyə 1"</b>" \n- Prevent full screen interruption \n- Heç vaxt izləməyin \n- Heç vaxt səsliyə və ya vibrasiyaya qoymayın \n- Ekran kilidi və ya status panelindən gizlədin \n- Bildiriş siyahısının yuxarı hissəsində göstərin \n\n"<b>"Səviyyə 0"</b>" \n- Bütün bildirişləri tətbiqdən blok edin"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Hazırdır"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Tətbiq edin"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Bildirişləri deaktiv edin"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Ayarlama"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Yaddaş"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Məsləhətlər"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Əlçatımlıq"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Ani Tətbiqlər"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> işləyir"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Quraşdırılmadan açılan tətbiq."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Əlçatımlılıq funksiyalarını açmaq üçün toxunun. Ayarlarda bu düyməni fərdiləşdirin və ya dəyişdirin.\n\n"<annotation id="link">"Ayarlara baxın"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Düyməni müvəqqəti gizlətmək üçün kənara çəkin"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Geri qaytarın"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Xüsusi imkanlar düyməsi gizlədildi"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Toxunaraq xüsusi imkanlar düyməsini göstərin"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> qısayol silindi"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# qısayol silindi}other{# qısayol silindi}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Yuxarıya sola köçürün"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"İstifadəçi mövcudluğu aşkarlandı"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ayarlarda defolt qeydlər tətbiqi ayarlayın"</string>
     <string name="install_app" msgid="5066668100199613936">"Tətbiqi quraşdırın"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Xarici displeyə əks etdirilsin?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"İç displey əks etdiriləcək. Ön ekran deaktiv ediləcək."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Displeyi əks etdirin"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 56c3e45..33e7921 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Podešavanje otključavanja licem nije uspelo. Idite u Podešavanja da biste probali ponovo."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Dodirnite senzor za otisak prsta"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Pritisnite ikonu otključavanja za nastavak"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Lice nije prepoznato. Koristite otisak prsta."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Lice nije prepoznato. Koristite otisak prsta."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Lice nije prepoznato"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Lice nije prepoznato"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Koristite otisak prsta"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Otključavanje licem nije dostupno"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth je priključen."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Puni se • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do kraja punjenja"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Prevucite ulevo da biste započeli zajednički vodič"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otvori uređivač vidžeta"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Prilagodite"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Odbaci"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Dodajte, uklonite i preuredite vidžete u ovom prostoru"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Dodajte još vidžeta"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Dugi pritisak za prilagođavanje vidžeta"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Ukloni"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodaj vidžet"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Gotovo"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Želite li da uključite Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Da biste povezali tastaturu sa tabletom, prvo morate da uključite Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Uključi"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Napredne kontrole za obaveštenja"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Uključeno – na osnovu lica"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Pomoću naprednih kontrola za obaveštenja možete da podesite nivo važnosti od 0. do 5. za obaveštenja aplikacije. \n\n"<b>"5. nivo"</b>" \n– Prikazuju se u vrhu liste obaveštenja \n- Dozvoli prekid režima celog ekrana \n– Uvek zaviruj \n\n"<b>"4. nivo"</b>" \n– Spreči prekid režima celog ekrana \n– Uvek zaviruj \n\n"<b>"3. nivo"</b>" \n– Spreči prekid režima celog ekrana \n– Nikada ne zaviruj \n\n"<b>"2. nivo"</b>" \n– Spreči prekid režima celog ekrana \n– Nikada ne zaviruj \n– Nikada ne proizvodi zvuk ili vibraciju \n\n"<b>"1. nivo"</b>" \n– Spreči prekid režima celog ekrana \n– Nikada ne zaviruj \n– Nikada ne proizvodi zvuk ili vibraciju \n– Sakrij na zaključanom ekranu i statusnoj traci \n– Prikazuju se u dnu liste obaveštenja \n\n"<b>"0. nivo"</b>" \n– Blokiraj sva obaveštenja iz aplikacije"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Gotovo"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Primeni"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Isključi obaveštenja"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Podešavanje"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Memorijski prostor"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Saveti"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Pristupačnost"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant aplikacije"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"Aplikacija <xliff:g id="APP">%1$s</xliff:g> je pokrenuta"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Aplikacija se otvorila bez instaliranja."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Dodirnite za funkcije pristupačnosti. Prilagodite ili zamenite ovo dugme u Podešavanjima.\n\n"<annotation id="link">"Podešavanja"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Pomerite dugme do ivice da biste ga privremeno sakrili"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Opozovi"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Dugme Pristupačnost je skriveno"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Dodirnite za prikaz dugmeta Pristupačnost"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Prečica funkcije <xliff:g id="FEATURE_NAME">%s</xliff:g> je uklonjena"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# prečica je uklonjena}one{# prečica je uklonjena}few{# prečice su uklonjene}other{# prečica je uklonjeno}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Premesti gore levo"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Prisustvo korisnika može da se otkrije"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Podesite podrazumevanu aplikaciju za beleške u Podešavanjima"</string>
     <string name="install_app" msgid="5066668100199613936">"Instaliraj aplikaciju"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Želite li da preslikate na spoljnji ekran?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Unutrašnji ekran će se preslikati. Prednji ekran će se isključiti."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Preslikaj ekran"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 3bc6269..b7e3b1b 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Не ўдалося наладзіць функцыю распазнавання твару. Каб паўтарыць, перайдзіце ў Налады."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Дакраніцеся да сканера адбіткаў пальцаў"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Каб працягнуць, націсніце на значок разблакіроўкі"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Твар не распазнаны. Скарыстайце адбітак пальца."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Твар не распазнаны. Выкарыстайце адбітак пальца."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Твар не распазнаны"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Твар не распазнаны"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Скарыстайце адбітак пальца"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Распазнаванне твару не працуе"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-сувязь."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ідзе зарадка • Поўны зарад праз <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Правядзіце пальцам па экране ўлева, каб азнаёміцца з дапаможнікам"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Адкрыць рэдактар віджэтаў"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Наладзіць"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Закрыць"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Дадаць ці выдаліць віджэты ў гэтай вобласці або змяніць іх парадак"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Дадаць іншыя віджэты"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Доўга націскайце, каб наладзіць віджэты"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Выдаліць"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Дадаць віджэт"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Гатова"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Уключыць Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Для падлучэння клавіятуры да планшэта трэба спачатку ўключыць Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Уключыць"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Пашыранае кіраванне апавяшчэннямі"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Уключана – З улікам паставы галавы"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"З дапамогай пашыранага кіравання апавяшчэннямі вы можаце задаваць узровень важнасці апавяшчэнняў праграмы ад 0 да 5. \n\n"<b>"Узровень 5"</b>" \n- Паказваць уверсе спіса апавяшчэнняў \n- Дазваляць перапыняць рэжым поўнага экрана \n- Заўсёды дазваляць кароткі паказ \n\n"<b>"Узровень 4"</b>" \n- Забараняць перапыняць рэжым поўнага экрана \n- Заўсёды дазваляць кароткі паказ \n\n"<b>"Узровень 3"</b>" \n- Забараняць перапыняць рэжым поўнага экрана \n- Ніколі не дазваляць кароткі паказ \n\n"<b>"Узровень 2"</b>" \n- Забараняць перапыняць рэжым поўнага экрана \n- Ніколі не дазваляць кароткі паказ \n- Ніколі не прайграваць гук і не вібрыраваць \n\n"<b>"Узровень 1"</b>" \n- Забараняць перапыняць рэжым поўнага экрана \n- Ніколі не дазваляць кароткі паказ \n- Ніколі не прайграваць гук і не вібрыраваць \n- Хаваць з экрана блакіроўкі і панэлі стану \n- Паказваць унізе спіса апавяшчэнняў \n\n"<b>"Узровень 0"</b>" \n- Блакіраваць усе апавяшчэнні ад праграмы"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Гатова"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Прымяніць"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Выключыць апавяшчэнні"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Наладжванне"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Захоўванне"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Падказкі"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Спецыяльныя магчымасці"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Імгненныя праграмы"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"Праграма \"<xliff:g id="APP">%1$s</xliff:g>\" запушчана"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Праграма адкрыта без усталёўкі."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Націсніце, каб адкрыць спецыяльныя магчымасці. Рэгулюйце ці замяняйце кнопку ў Наладах.\n\n"<annotation id="link">"Прагляд налад"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Каб часова схаваць кнопку, перамясціце яе на край"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Адрабіць"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Кнопка спецыяльных магчымасцей схавана"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Калі вы хочаце, каб яна з\'явілася, націсніце на апавяшчэнне"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Выдалены ярлык <xliff:g id="FEATURE_NAME">%s</xliff:g>"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Выдалены # ярлык}one{Выдалены # ярлык}few{Выдалена # ярлыкі}many{Выдалена # ярлыкоў}other{Выдалена # ярлыка}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Перамясціць лявей і вышэй"</string>
@@ -1207,9 +1217,10 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Выяўлена прысутнасць карыстальніка"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Задайце ў Наладах стандартную праграму для нататак"</string>
     <string name="install_app" msgid="5066668100199613936">"Усталяваць праграму"</string>
-    <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Адлюстраваць на знешнім дысплэі?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
     <skip />
+    <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Адлюстраваць на знешнім дысплэі?"</string>
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Будзе ўключана дубліраванне ўнутранага дысплэя. Пярэдні дысплэй будзе выключаны."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Адлюстраваць дысплэй"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Закрыць"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Дысплэй падключаны"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 22d167e..c1a27d2 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Функцията „Отключване с лице“ не бе настроена. Отворете настройките, за да опитате отново."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Докоснете сензора за отпечатъци"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Натиснете иконата за отключване, за да продължите"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Лицето не е разпознато. Използвайте отпечатък."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Лицето не е разпознато. Използвайте отпечатък."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Лицето не е разпознато"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Лицето не е разпознато"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Използвайте отпечатък"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"„Отключване с лице“ не е налице"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth е включен."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарежда се • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до пълно зареждане"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Прекарайте пръст наляво, за да стартирате общия урок"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Отваряне на редактора на приспособлението"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Персонализиране"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Отхвърляне"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Добавяйте, премахвайте и пренареждайте приспособленията си в тази област"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Добавете още приспособления"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Натиснете продължително за персонализ. на приспос."</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Премахване"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Добавяне на приспособление"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Готово"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Да се включи ли Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"За да свържете клавиатурата с таблета си, първо трябва да включите Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Включване"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Контроли за известията"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Вкл. – въз основа на лицето"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"С помощта на контролите за известията можете да зададете ниво на важност от 0 до 5 за известията от дадено приложение. \n\n"<b>"Ниво 5"</b>" \n– Показване най-горе в списъка с известия. \n– Разрешаване на прекъсването на цял екран. \n– Известията винаги се показват мимолетно. \n\n"<b>"Ниво 4"</b>" \n– Предотвратяване на прекъсването на цял екран. \n– Известията винаги се показват мимолетно. \n\n"<b>"Ниво 3"</b>" \n– Предотвратяване на прекъсването на цял екран. \n– Известията никога не се показват мимолетно. \n\n"<b>"Ниво 2"</b>" \n– Предотвратяване на прекъсването на цял екран. \n– Известията никога не се показват мимолетно. \n– Без издаване на звуков сигнал и вибриране. \n\n"<b>"Ниво 1"</b>" \n– Предотвратяване на прекъсването на цял екран. \n– Известията никога не се показват мимолетно. \n– Без издаване на звуков сигнал и вибриране. \n– Скриване от заключения екран и лентата на състоянието. \n– Показване най-долу в списъка с известия. \n\n"<b>"Ниво 0"</b>" \n– Блокиране на всички известия от приложението."</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Готово"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Прилагане"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Изключване на известията"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Настройване"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Хранилище"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Съвети"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Достъпност"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Мигновени приложения"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> работи"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Приложението се отвори, без да бъде инсталирано."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Докоснете, за да отворите функциите за достъпност. Персон./заменете бутона от настройките.\n\n"<annotation id="link">"Преглед на настройките"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Преместете бутона до края, за да го скриете временно"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Отмяна"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Бутонът за достъпност е скрит"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Докоснете за показване на бутона за достъпност"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Прекият път за „<xliff:g id="FEATURE_NAME">%s</xliff:g>“ бе премахнат"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# пряк път бе премахнат}other{# преки пътя бяха премахнати}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Преместване горе вляво"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Установено е присъствие на потребител"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Задайте стандартно приложение за бележки от настройките"</string>
     <string name="install_app" msgid="5066668100199613936">"Инсталиране на приложението"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Да се дублира ли на външния екран?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Съдържанието на вътрешния ви дисплей ще бъде дублирано. Предният ви дисплей ще бъде изключен."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Дублиране на дисплея"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 045af93..a694f5e 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"\'ফেস আনলক\' সেট-আপ করা যায়নি। আবার চেষ্টা করতে সেটিংসে যান।"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"আঙ্গুলের ছাপের সেন্সর স্পর্শ করুন"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"চালিয়ে যেতে \'আনলক করুন\' আইকনে প্রেস করুন"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"মুখ শনাক্ত করতে পারছি না। পরিবর্তে আঙ্গুলের ছাপ ব্যবহার করুন।"</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"মুখ চেনা যায়নি। পরিবর্তে ফিঙ্গারপ্রিন্ট ব্যবহার করুন।"</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"ফেস শনাক্ত করা যায়নি"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"মুখ চেনা যায়নি"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"পরিবর্তে ফিঙ্গারপ্রিন্ট ব্যবহার করুন"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"\'ফেস আনলক\' উপলভ্য নেই"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ব্লুটুথ সংযুক্ত হয়েছে৷"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • চার্জ হচ্ছে • পুরো চার্জ হতে আরও <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> সময় লাগবে"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"কমিউনিটি টিউটোরিয়াল চালু করতে বাঁদিকে সোয়াইপ করুন"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"উইজেট এডিটর খুলুন"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"কাস্টমাইজ করুন"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"বাতিল করুন"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"এই স্পেসে আপনার উইজেট যোগ করুন, সরান ও আবার সাজান"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"আরও উইজেট যোগ করুন"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"উইজেট কাস্টমাইজ করতে বেশিক্ষণ প্রেস করুন"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"সরান"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"উইজেট যোগ করুন"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"হয়ে গেছে"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"ব্লুটুথ চালু করবেন?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"আপনার ট্যাবলেটের সাথে আপনার কীবোর্ড সংযুক্ত করতে, আপনাকে প্রথমে ব্লুটুথ চালু করতে হবে।"</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"চালু করুন"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"পাওয়ার বিজ্ঞপ্তির নিয়ন্ত্রণগুলি"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"চালু আছে - মুখের হিসেবে"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"পাওয়ার বিজ্ঞপ্তির নিয়ন্ত্রণগুলি ব্যহবার করে, আপনি কোনও অ্যাপ্লিকেশনের বিজ্ঞপ্তির জন্য ০ থেকে ৫ পর্যন্ত একটি গুরুত্বের লেভেলকে সেট করতে পারবেন৷ \n\n"<b>"লেভেল ৫"</b>" \n- বিজ্ঞপ্তি তালিকার শীর্ষে দেখায় \n- পূর্ণ স্ক্রিনের বাধাকে অনুমতি দেয় \n- সর্বদা স্ক্রিনে উপস্থিত হয় \n\n"<b>"লেভেল ৪"</b>" \n- পূর্ণ স্ক্রিনের বাধাকে আটকায় \n- সর্বদা স্ক্রিনে উপস্থিত হয় \n\n"<b>"লেভেল ৩"</b>" \n- পূর্ণ স্ক্রিনের বাধাকে আটকায় \n- কখনওই স্ক্রিনে উপস্থিত হয় না \n\n"<b>"লেভেল ২"</b>" \n- পূর্ণ স্ক্রিনের বাধাকে আটকায় \n- কখনওই স্ক্রিনে উপস্থিত হয় না \n- কখনওই শব্দ এবং কম্পন করে না \n\n"<b>"লেভেল ১"</b>" \n- পূর্ণ স্ক্রিনের বাধাকে আটকায় \n- কখনওই স্ক্রিনে উপস্থিত হয় না \n- কখনওই শব্দ এবং কম্পন করে না \n- লক স্ক্রিন এবং স্ট্যাটাস বার থেকে লুকায় \n- বিজ্ঞপ্তি তালিকার নীচের দিকে দেখায় \n\n"<b>"লেভেল ০"</b>" \n- অ্যাপ্লিকেশন থেকে সমস্ত বিজ্ঞপ্তিকে অবরূদ্ধ করে"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"হয়ে গেছে"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"প্রয়োগ করুন"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"বিজ্ঞপ্তি বন্ধ করুন"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"সেট-আপ"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"স্টোরেজ"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"হিন্ট"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"অ্যাক্সেসিবিলিটি"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> চলছে"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"অ্যাপটি ইনস্টল না করে চালু করা হয়েছে।"</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"অ্যাক্সেসিবিলিটি ফিচার খুলতে ট্যাপ করুন। কাস্টমাইজ করুন বা সেটিংসে এই বোতামটি সরিয়ে দিন।\n\n"<annotation id="link">"সেটিংস দেখুন"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"এটি অস্থায়ীভাবে লুকাতে বোতামটি কোণে সরান"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"আগের অবস্থায় ফিরুন"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"অ্যাক্সেসিবিলিটি বোতাম লুকানো আছে"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"অ্যাক্সেসিবিলিটি বোতাম দেখাতে ট্যাপ করুন"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g>-এর শর্টকাট সরানো হয়েছে"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{#টি শর্টকাট সরানো হয়েছে}one{#টি শর্টকাট সরানো হয়েছে}other{#টি শর্টকাট সরানো হয়েছে}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"উপরে বাঁদিকে সরান"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"ব্যবহারকারীর উপস্থিতি শনাক্ত করা হয়েছে"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"\'সেটিংস\' থেকে ডিফল্ট নোট নেওয়ার অ্যাপ সেট করুন"</string>
     <string name="install_app" msgid="5066668100199613936">"অ্যাপ ইনস্টল করুন"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"এক্সটার্নাল ডিসপ্লেতে মিরর করবেন?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"আপনার ইনার ডিসপ্লে মিরর করা হবে। আপনার ফ্রন্ট ডিসপ্লে বন্ধ করা হবে।"</string>
     <string name="mirror_display" msgid="2515262008898122928">"ডিসপ্লে দেখান"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index ae962ce..1c7198a 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Postavljanje otključavanja licem nije uspjelo. Idite u Postavke da pokušate ponovo."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Dodirnite senzor za otisak prsta"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Nastavak pritiskanjem ikone za otključavanje"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Nije moguće prepoznati lice. Koristite otisak prsta."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Lice nije prepoznato. Upotrijebite otisak prsta."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Nije moguće prepoznati lice"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Lice nije prepoznato"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Koristite otisak prsta"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Otključavanje licem je nedostupno"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth je povezan."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Punjenje • Potpuna napunjenost za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Prevucite ulijevo da pokrenete zajednički vodič"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otvaranje uređivača vidžeta"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Prilagodite"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Odbaci"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Dodajte, uklonite i promijenite raspored vidžeta u ovom prostoru"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Dodajte još vidžeta"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pritisnite i zadržite da prilagodite vidžete"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Uklanjanje"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodajte vidžet"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Gotovo"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Želiti li uključiti Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Da povežete tastaturu sa tabletom, prvo morate uključiti Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Uključi"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Kontrole obavještenja o napajanju"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Uključeno – na osnovu lica"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Uz kontrolu obavještenja o napajanju, možete postaviti nivo značaja obavještenja iz aplikacije, i to od nivoa 0 do 5. \n\n"<b>"Nivo 5"</b>" \n- Prikaži na vrhu liste obavještenja \n- Dopusti prekid prikaza cijelog ekrana \n- Uvijek izviruj \n\n"<b>"Nvio 4"</b>" \n- Spriječi prekid prikaza cijelog ekrana \n- Uvijek izviruj \n\n"<b>"Nivo 3"</b>" \n- Spriječi prekid prikaza cijelog ekrana \n- Nikad ne izviruj \n\n"<b>"Nivo 2"</b>" \n- Spriječi prekid prikaza cijelog ekrana \n- Nikad ne izviruj \n- Nikada ne puštaj zvuk ili vibraciju \n\n"<b>"Nivo 1"</b>" \n- Spriječi prekid prikaza cijelog ekrana \n- Nikada ne izviruj \n- Nikada ne puštaj zvuk ili vibraciju \n- Sakrij sa ekrana za zaključavanje i statusne trake \n- Prikaži na dnu liste obavještenja \n\n"<b>"Nivo 0"</b>" \n- Blokiraj sva obavještenja iz aplikacije"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Gotovo"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Primijeni"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Isključi obavještenja"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Postavljanje"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Pohrana"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Savjeti"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Pristupačnost"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant aplikacije"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"Pokrenuta je aplikacija <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Aplikacija je otvorena bez prethodne instalacije."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Dodirnite da otvorite funkcije pristupačnosti. Prilagodite ili zamijenite dugme u Postavkama.\n\n"<annotation id="link">"Postavke"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Premjestite dugme do ivice da ga privremeno sakrijete"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Opozovi"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Dugme za pristupačnost je sakriveno"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Prikazivanje dugmeta za pristupačnost dodirom"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Prečica <xliff:g id="FEATURE_NAME">%s</xliff:g> je uklonjena"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# prečica je uklonjena}one{# prečica je uklonjena}few{# prečice su uklonjene}other{# prečica je uklonjeno}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Pomjeranje gore lijevo"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Otkriveno je prisustvo korisnika"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Postavite zadanu aplikaciju za bilješke u Postavkama"</string>
     <string name="install_app" msgid="5066668100199613936">"Instaliraj aplikaciju"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Preslikati na vanjski ekran?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Unutrašnji ekran će se preslikavati. Prednji ekran će se isključiti."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Preslikaj ekran"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 8cf8828..948fb5c 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"No s\'ha pogut configurar el desbloqueig facial. Ves a Configuració per tornar-ho a provar."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Toca el sensor d\'empremtes digitals"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Prem la icona de desbloqueig per continuar"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"No podem detectar la cara. Usa l\'empremta digital."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"La cara no s\'ha reconegut. Usa l\'empremta digital."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"No es reconeix la cara"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"No s\'ha reconegut la cara"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Utilitza l\'empremta digital"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Desbloqueig facial no està disponible"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connectat."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • S\'està carregant • Es completarà d\'aquí a <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Llisca cap a l\'esquerra per iniciar el tutorial de la comunitat"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Obre l\'editor de widgets"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Personalitza"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Ignora"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Afegeix, suprimeix i reordena widgets en aquest espai"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Afegeix més widgets"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantén premut per personalitzar els widgets"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Suprimeix"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Afegeix un widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Fet"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Vols activar el Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Per connectar el teclat amb la tauleta, primer has d\'activar el Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Activa"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Controls millorats per a notificacions"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Activat: basat en cares"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Amb els controls de notificació millorats, pots establir un nivell d\'importància d\'entre 0 i 5 per a les notificacions d\'una aplicació. \n\n"<b>"Nivell 5"</b>" \n- Mostra les notificacions a la part superior de la llista \n- Permet la interrupció de la pantalla completa \n- Permet sempre la previsualització \n\n"<b>"Nivell 4"</b>" \n- No permet la interrupció de la pantalla completa \n- Permet sempre la previsualització \n\n"<b>"Nivell 3"</b>" \n- No permet la interrupció de la pantalla completa \n- No permet mai la previsualització \n\n"<b>"Nivell 2"</b>" \n- No permet la interrupció de la pantalla completa \n- No permet mai la previsualització \n- Les notificacions no poden emetre sons ni vibracions \n\n"<b>"Nivell 1"</b>" \n- No permet la interrupció de la pantalla completa \n- No permet mai la previsualització \n- No activa mai el so ni la vibració \n- Amaga les notificacions de la pantalla de bloqueig i de la barra d\'estat \n- Mostra les notificacions a la part inferior de la llista \n\n"<b>"Nivell 0"</b>" \n- Bloqueja totes les notificacions de l\'aplicació"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Fet"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Aplica"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Desactiva les notificacions"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Configuració"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Emmagatzematge"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Suggeriments"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Accessibilitat"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Aplicacions instantànies"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"S\'està executant <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"L\'aplicació s\'ha obert sense instal·lar-se."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toca per obrir funcions d\'accessibilitat. Personalitza o substitueix el botó a Configuració.\n\n"<annotation id="link">"Mostra"</annotation>"."</string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mou el botó a l\'extrem per amagar-lo temporalment"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Desfés"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"El botó d\'accessibilitat està amagat"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Toca per mostrar el botó d\'accessibilitat"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"S\'ha suprimit la drecera a <xliff:g id="FEATURE_NAME">%s</xliff:g>"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{S\'ha suprimit # drecera}many{S\'han suprimit # dreceres}other{S\'han suprimit # dreceres}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mou a dalt a l\'esquerra"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"S\'ha detectat la presència d\'usuaris"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defineix l\'aplicació de notes predeterminada a Configuració"</string>
     <string name="install_app" msgid="5066668100199613936">"Instal·la l\'aplicació"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Duplicar a la pantalla externa?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"La pantalla interior es duplicarà. La pantalla frontal es desactivarà."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Duplica la pantalla"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 17084dc..33397fe 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Odemknutí obličejem se nepodařilo nastavit. Pokud to chcete zkusit znovu, přejděte do Nastavení."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Dotkněte se snímače otisků prstů"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Klepněte na ikonu odemknutí"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Obličej se nepodařilo rozpoznat. Použijte místo něj otisk prstu."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Obličej nebyl rozpoznán. Použijte místo něj otisk prstu."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Obličej nelze rozpoznat"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Obličej nebyl rozpoznán"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Použijte otisk prstu"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Odemknutí obličejem není k dispozici"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Rozhraní Bluetooth je připojeno."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíjení • Plně nabito za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Přejetím doleva spustíte komunitní výukový program"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otevřít editor widgetů"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Přizpůsobit"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Zavřít"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"V tomto prostoru můžete přidávat a odstraňovat widgety a měnit jejich pořadí"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Přidat další widgety"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Dlouhým stisknutím můžete přizpůsobit widgety"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Odstranit"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Přidat widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Hotovo"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Zapnout Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Chcete-li klávesnici připojit k tabletu, nejdříve musíte zapnout Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Zapnout"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Rozšířené ovládací prvky oznámení"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Zapnuto – podle obličeje"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Rozšířené ovládací prvky oznámení umožňují nastavit úroveň důležitosti oznámení aplikace od 0 do 5. \n\n"<b>"Úroveň 5"</b>" \n– Zobrazit na začátku seznamu oznámení \n– Povolit vyrušení na celou obrazovku \n– Vždy zobrazit náhled \n\n"<b>"Úroveň 4"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Vždy zobrazit náhled \n\n"<b>"Úroveň 3"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n\n"<b>"Úroveň 2"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n– Nikdy nevydávat žádný zvukový signál ani nevibrovat \n\n"<b>"Úroveň 1"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n– Nikdy nevydávat zvukový signál ani nevibrovat \n– Skrýt na obrazovce uzamčení a stavového řádku \n– Zobrazovat na konci seznamu oznámení \n\n"<b>";Úroveň 0"</b>" \n– Blokovat všechna oznámení z aplikace"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Hotovo"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Použít"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Vypnout oznámení"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Nastavit"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Úložiště"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Tipy"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Přístupnost"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Okamžité aplikace"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"Aplikace <xliff:g id="APP">%1$s</xliff:g> je spuštěna"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Aplikace byla otevřena bez instalace."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Klepnutím otevřete funkce přístupnosti. Tlačítko lze upravit nebo nahradit v Nastavení.\n\n"<annotation id="link">"Nastavení"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Přesunutím tlačítka k okraji ho dočasně skryjete"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Vrátit zpět"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Tlačítko přístupnosti je skryté"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Klepnutím zobrazíte tlačítko přístupnosti"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Zkratka pro <xliff:g id="FEATURE_NAME">%s</xliff:g> byla odstraněna"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Byla odstraněna # zkratka}few{Byly odstraněny # zkratky}many{Bylo odstraněno # zkratky}other{Bylo odstraněno # zkratek}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Přesunout vlevo nahoru"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Je zjištěna přítomnost uživatele"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Výchozí aplikaci pro poznámky nastavíte v Nastavení"</string>
     <string name="install_app" msgid="5066668100199613936">"Nainstalovat aplikaci"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Zrcadlit na externí displej?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Vnitřní displej bude zrcadlen. Přední displej bude vypnutý."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Zrcadlit displej"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 41abea3..35142da 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Ansigtsoplåsning kunne ikke konfigureres. Gå til Indstillinger for at prøve igen."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Sæt fingeren på fingeraftrykssensoren"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Tryk på oplåsningsikonet for at fortsætte"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Ansigtet kan ikke genkendes. Brug fingeraftryk i stedet."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Ansigtet blev ikke genkendt. Brug fingeraftryk i stedet."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Ansigt kan ikke genkendes"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Ansigt blev ikke genkendt"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Brug fingeraftryk i stedet"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Ansigtsoplåsning er utilgængelig"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth tilsluttet."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Oplader • Fuldt opladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Stryg mod venstre for at starte den fælles vejledning"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Åbn redigeringsværktøjet til widgets"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Tilpas"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Luk"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Tilføj, fjern og omorganiser widgets i dette rum"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Tilføj flere widgets"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Hold fingeren nede for at tilpasse widgets"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Fjern"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Tilføj widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Udfør"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Vil du slå Bluetooth til?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Bluetooth skal være slået til, før du kan knytte dit tastatur til din tablet."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Slå til"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Kontrolelementer til notifikation om strøm"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Til – ansigtsbaseret"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Med kontrolelementer til notifikationer om strøm kan du konfigurere et vigtighedsniveau fra 0 til 5 for en apps notifikationer. \n\n"<b>"Niveau 5"</b>\n"- Vis øverst på listen over notifikationer \n- Tillad afbrydelse af fuld skærm \n- Se altid smugkig \n\n"<b>"Niveau 4"</b>\n"- Ingen afbrydelse af fuld skærm \n- Se altid smugkig \n\n"<b>"Niveau 3"</b>\n"- Ingen afbrydelse af fuld skærm \n- Se aldrig smugkig \n\n"<b>"Niveau 2"</b>\n"- Ingen afbrydelse af fuld skærm \n Se aldrig smugkig \n- Ingen lyd og vibration \n\n"<b>"Niveau 1"</b>\n"- Ingen afbrydelse af fuld skærm \n- Se aldrig smugkig \n- Ingen lyd eller vibration \n- Skjul fra låseskærm og statusbjælke \n- Vis nederst på listen over notifikationer \n\n"<b>"Niveau 0"</b>\n"- Bloker alle notifikationer fra appen."</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Udfør"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Anvend"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Deaktiver notifikationer"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Konfiguration"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Lagerplads"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Tips"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Hjælpefunktioner"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> kører"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"En app blev åbnet uden at blive installeret."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tryk for at åbne hjælpefunktioner. Tilpas eller erstat denne knap i Indstillinger.\n\n"<annotation id="link">"Se indstillingerne"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Flyt knappen til kanten for at skjule den midlertidigt"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Fortryd"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Knappen til hjælpefunktioner er skjult"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Tryk for at se knappen til hjælpefunktioner"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Genvejen til <xliff:g id="FEATURE_NAME">%s</xliff:g> er fjernet"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# genvej er fjernet}one{# genvej er fjernet}other{# genveje er fjernet}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Flyt op til venstre"</string>
@@ -1033,7 +1043,7 @@
     <string name="media_output_dialog_launch_app_text" msgid="1527413319632586259">"Åbn appen for at caste denne session."</string>
     <string name="media_output_dialog_unknown_launch_app_name" msgid="1084899329829371336">"Ukendt app"</string>
     <string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Stop med at caste"</string>
-    <string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Enheder, der er tilgængelige for lydoutput."</string>
+    <string name="media_output_dialog_accessibility_title" msgid="4681741064190167888">"Enheder, der er tilgængelige for lydudgang."</string>
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Lydstyrke"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Højttalere og skærme"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Brugertilstedeværelse er registreret"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Angiv standardapp til noter i Indstillinger"</string>
     <string name="install_app" msgid="5066668100199613936">"Installer app"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vil du spejle til ekstern skærm?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Din indre skærm spejles. Din skærm på forsiden slukkes."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Spejl skærm"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index d95e229..e100b90 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Die Entsperrung per Gesichtserkennung konnte nicht eingerichtet werden. Gehe zu den Einstellungen und versuche es noch einmal."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Berühre den Fingerabdrucksensor"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Tippe zum Fortfahren auf das Symbol „Entsperren“"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Gesicht wurde nicht erkannt. Verwende stattdessen den Fingerabdruck."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Gesicht nicht erkannt. Verwende den Fingerabdruck."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Gesicht nicht erkannt"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Gesicht nicht erkannt"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Fingerabdruck verwenden"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Entsperrung per Gesichtserkennung nicht verfügbar"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Mit Bluetooth verbunden"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wird geladen • Voll in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Wische nach links, um das gemeinsame Tutorial zu starten"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Widget-Editor öffnen"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Anpassen"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Schließen"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Hier kannst du Widgets hinzufügen, entfernen und neu anordnen"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Weitere Widgets hinzufügen"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Lange drücken, um Widgets anzupassen"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Entfernen"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Widget hinzufügen"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Fertig"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Bluetooth aktivieren?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Zum Verbinden von Tastatur und Tablet muss Bluetooth aktiviert sein."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Aktivieren"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Erweiterte Benachrichtigungseinstellungen"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"An – gesichtsbasiert"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Mit den erweiterten Benachrichtigungseinstellungen kannst du für App-Benachrichtigungen eine Wichtigkeitsstufe von 0 bis 5 festlegen. \n\n"<b>"Stufe 5"</b>" \n- Auf der Benachrichtigungsleiste ganz oben anzeigen \n- Vollbildunterbrechung zulassen \n- Immer kurz einblenden \n\n"<b>"Stufe 4"</b>" \n- Keine Vollbildunterbrechung \n- Immer kurz einblenden \n\n"<b>"Stufe 3"</b>" \n- Keine Vollbildunterbrechung \n- Nie kurz einblenden \n\n"<b>"Stufe 2"</b>" \n- Keine Vollbildunterbrechung \n- Nie kurz einblenden \n- Weder Ton noch Vibration \n\n"<b>"Stufe 1"</b>" \n- Keine Vollbildunterbrechung \n- Nie kurz einblenden \n- Weder Ton noch Vibration \n- Auf Sperrbildschirm und Statusleiste verbergen \n- Auf der Benachrichtigungsleiste ganz unten anzeigen \n\n"<b>"Stufe 0"</b>" \n- Alle Benachrichtigungen der App sperren"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Fertig"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Anwenden"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Benachrichtigungen deaktivieren"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Einrichtung"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Speicher"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Hinweise"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Bedienungshilfen"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> wird ausgeführt"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"App wurde geöffnet, ohne vorher installiert zu werden."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tippe, um die Bedienungshilfen aufzurufen. Du kannst diese Schaltfläche in den Einstellungen anpassen oder ersetzen.\n\n"<annotation id="link">"Zu den Einstellungen"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Durch Ziehen an den Rand wird die Schaltfläche zeitweise ausgeblendet"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Rückgängig machen"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Schaltfläche „Bedienungshilfen“ ausgeblendet"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Tippen, um die Schaltfläche „Bedienungshilfen“ aufzurufen"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Verknüpfung für „<xliff:g id="FEATURE_NAME">%s</xliff:g>“ entfernt"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# Verknüpfung entfernt}other{# Verknüpfungen entfernt}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Nach oben links verschieben"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Anwesenheit des Nutzers wurde erkannt"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standard-Notizen-App in den Einstellungen einrichten"</string>
     <string name="install_app" msgid="5066668100199613936">"App installieren"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Auf externen Bildschirm spiegeln?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Dein inneres Display wird gespiegelt. Das Frontdisplay wird ausgeschaltet."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Bildschirm spiegeln"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 5848e4f..ed3a678 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Δεν ήταν δυνατή η ρύθμιση για το Ξεκλείδωμα με το πρόσωπο. Μεταβείτε στις Ρυθμίσεις και δοκιμάστε ξανά."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Αγγίξτε τον αισθητήρα δακτυλικού αποτυπώματος"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Πατήστε το εικονίδιο ξεκλειδώματος για συνέχεια"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Το πρόσωπο δεν αναγνωρίζεται. Χρησιμ. δακτ. αποτ."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Δεν αναγνωρίστηκε. Χρήση δακτυλικού αποτυπώματος."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Αδύνατη η αναγν. προσώπου"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Δεν αναγνωρίστηκε"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Χρησιμ. δακτυλ. αποτύπ."</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Ξεκλ. με πρόσωπο μη διαθ."</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Το Bluetooth είναι συνδεδεμένο."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Φόρτιση • Πλήρης φόρτιση σε <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Σύρετε προς τα αριστερά για να ξεκινήσετε τον κοινόχρηστο οδηγό"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Άνοιγμα προγράμ. επεξεργασίας γραφικών στοιχείων"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Προσαρμογή"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Παράβλεψη"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Προσθήκη, κατάργηση και αναδιάταξη των γραφικών στοιχείων σε αυτόν τον χώρο"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Προσθήκη περισσότερων γραφικών στοιχείων"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Παρατεταμένο πάτημα για προσαρμογή γραφ. στοιχείων"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Κατάργηση"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Προσθήκη γραφικού στοιχείου"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Τέλος"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Ενεργοποίηση Bluetooth;"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Για να συνδέσετε το πληκτρολόγιο με το tablet σας, θα πρέπει πρώτα να ενεργοποιήσετε το Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Ενεργοποίηση"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Στοιχεία ελέγχου ειδοποίησης ισχύος"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Ενεργό - Βάσει προσώπου"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Με τα στοιχεία ελέγχου ειδοποίησης ισχύος, μπορείτε να ορίσετε ένα επίπεδο βαρύτητας από 0 έως 5 για τις ειδοποιήσεις μιας εφαρμογής. \n\n"<b>"Επίπεδο 5"</b>" \n- Εμφάνιση στην κορυφή της λίστας ειδοποιήσεων \n- Να επιτρέπεται η διακοπή πλήρους οθόνης \n- Να γίνεται πάντα σύντομη προβολή \n\n"<b>"Επίπεδο 4"</b>" \n- Αποτροπή διακοπής πλήρους οθόνης \n- Να γίνεται πάντα σύντομη προβολή \n\n"<b>"Επίπεδο 3"</b>" \n- Αποτροπή διακοπής πλήρους οθόνης \n- Να μην γίνεται ποτέ σύντομη προβολή \n\n"<b>"Επίπεδο 2"</b>" \n- Αποτροπή διακοπής πλήρους οθόνης \n- Να μην γίνεται ποτέ σύντομη προβολή \n- Να μην χρησιμοποιείται ποτέ ήχος και δόνηση \n\n"<b>"Επίπεδο 1"</b>" \n- Αποτροπή διακοπής πλήρους οθόνης \n- Να μην γίνεται ποτέ σύντομη προβολή \n- Να μην χρησιμοποιείται ποτέ ήχος και δόνηση \n- Απόκρυψη από την οθόνη κλειδώματος και τη γραμμή κατάστασης \n- Εμφάνιση στο κάτω μέρος της λίστας ειδοποιήσεων \n\n"<b>"Επίπεδο 0"</b>" \n- Αποκλεισμός όλων των ειδοποιήσεων από την εφαρμογή"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Τέλος"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Εφαρμογή"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Απενεργοποίηση ειδοποιήσεων"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Ρύθμιση"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Αποθηκευτικός χώρος"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Συμβουλές"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Προσβασιμότητα"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Εφαρμογές"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"Η εφαρμογή <xliff:g id="APP">%1$s</xliff:g> εκτελείται"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Η εφαρμογή άνοιξε χωρίς να έχει εγκατασταθεί."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Πατήστε για άνοιγμα των λειτουργιών προσβασιμότητας. Προσαρμόστε ή αντικαταστήστε το κουμπί στις Ρυθμίσεις.\n\n"<annotation id="link">"Προβολή ρυθμίσεων"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Μετακινήστε το κουμπί στο άκρο για προσωρινή απόκρυψη"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Αναίρεση"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Έχει γίνει απόκρυψη του κουμπιού προσβασιμότητας"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Πατήστε για εμφάνιση του κουμπιού προσβασιμότητας"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Η συντόμευση <xliff:g id="FEATURE_NAME">%s</xliff:g> καταργήθηκε"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Καταργήθηκε # συντόμευση}other{Καταργήθηκαν # συντομεύσεις}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Μετακίνηση επάνω αριστερά"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Εντοπίστηκε παρουσία χρήστη"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ορίστε την προεπιλεγμένη εφαρμογή σημειώσεων στις Ρυθμίσεις"</string>
     <string name="install_app" msgid="5066668100199613936">"Εγκατάσταση εφαρμογής"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Κατοπτρισμός σε εξωτερική οθόνη;"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Θα γίνει κατοπτρισμός της εσωτερικής προβολής. Η μπροστινή οθόνη θα απενεργοποιηθεί."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Κατοπτρισμός οθόνης"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 870e4dd..31b4b6e 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Couldn\'t set up Face Unlock. Go to Settings to try again."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Touch the fingerprint sensor"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Press the unlock icon to continue"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Can’t recognise face. Use fingerprint instead."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Face not recognised. Use fingerprint instead."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognise face"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Face not recognised"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Face Unlock unavailable"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe left to start the communal tutorial"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Open the widget editor"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Customise"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Dismiss"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Add, remove and reorder your widgets in this space"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Add more widgets"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customise widgets"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Done"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Turn on Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"To connect your keyboard with your tablet, you first have to turn on Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Turn on"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Power notification controls"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"On – Face-based"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications. \n\n"<b>"Level 5"</b>" \n- Show at the top of the notification list \n- Allow full screen interruption \n- Always peek \n\n"<b>"Level 4"</b>" \n- Prevent full screen interruption \n- Always peek \n\n"<b>"Level 3"</b>" \n- Prevent full screen interruption \n- Never peek \n\n"<b>"Level 2"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound and vibration \n\n"<b>"Level 1"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound or vibrate \n- Hide from lock screen and status bar \n- Show at the bottom of the notification list \n\n"<b>"Level 0"</b>" \n- Block all notifications from the app"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Done"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Apply"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Turn off notifications"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Setup"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Storage"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Hints"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Accessibility"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> running"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"App opened without being installed."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customise or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Undo"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Accessibility button hidden"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Tap to show Accessibility button"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> shortcut removed"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# shortcut removed}other{# shortcuts removed}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"User presence is detected"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
     <string name="install_app" msgid="5066668100199613936">"Install app"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Your inner display will be mirrored. Your front display will be turned off."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index f25baf2..23cd700 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Couldn’t set up face unlock. Go to Settings to try again."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Touch the fingerprint sensor"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Press the unlock icon to continue"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Can’t recognize face. Use fingerprint instead."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Face not recognized. Use fingerprint instead."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognize face"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Face not recognized"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Face Unlock unavailable"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
@@ -413,6 +413,14 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe left to start the communal tutorial"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Open the widget editor"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Customize"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Dismiss"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Add, remove, and reorder your widgets in this space"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Add more widgets"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customize widgets"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Done"</string>
@@ -599,9 +607,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Turn on Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"To connect your keyboard with your tablet, you first have to turn on Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Turn on"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Power notification controls"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"On - Face-based"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications. \n\n"<b>"Level 5"</b>" \n- Show at the top of the notification list \n- Allow full screen interruption \n- Always peek \n\n"<b>"Level 4"</b>" \n- Prevent full screen interruption \n- Always peek \n\n"<b>"Level 3"</b>" \n- Prevent full screen interruption \n- Never peek \n\n"<b>"Level 2"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound and vibration \n\n"<b>"Level 1"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound or vibrate \n- Hide from lock screen and status bar \n- Show at the bottom of the notification list \n\n"<b>"Level 0"</b>" \n- Block all notifications from the app"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Done"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Apply"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Turn off notifications"</string>
@@ -834,6 +840,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Setup"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Storage"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Hints"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Accessibility"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> running"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"App opened without being installed."</string>
@@ -929,6 +936,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customize or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Undo"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Accessibility button hidden"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Tap to show accessibility button"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> shortcut removed"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# shortcut removed}other{# shortcuts removed}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string>
@@ -1207,6 +1216,7 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"User presence is detected"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
     <string name="install_app" msgid="5066668100199613936">"Install app"</string>
+    <string name="dismissible_keyguard_swipe" msgid="8377597870094949432">"Swipe up to continue"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Your inner display will be mirrored. Your front display will be turned off."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 870e4dd..31b4b6e 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Couldn\'t set up Face Unlock. Go to Settings to try again."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Touch the fingerprint sensor"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Press the unlock icon to continue"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Can’t recognise face. Use fingerprint instead."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Face not recognised. Use fingerprint instead."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognise face"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Face not recognised"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Face Unlock unavailable"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe left to start the communal tutorial"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Open the widget editor"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Customise"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Dismiss"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Add, remove and reorder your widgets in this space"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Add more widgets"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customise widgets"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Done"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Turn on Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"To connect your keyboard with your tablet, you first have to turn on Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Turn on"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Power notification controls"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"On – Face-based"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications. \n\n"<b>"Level 5"</b>" \n- Show at the top of the notification list \n- Allow full screen interruption \n- Always peek \n\n"<b>"Level 4"</b>" \n- Prevent full screen interruption \n- Always peek \n\n"<b>"Level 3"</b>" \n- Prevent full screen interruption \n- Never peek \n\n"<b>"Level 2"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound and vibration \n\n"<b>"Level 1"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound or vibrate \n- Hide from lock screen and status bar \n- Show at the bottom of the notification list \n\n"<b>"Level 0"</b>" \n- Block all notifications from the app"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Done"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Apply"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Turn off notifications"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Setup"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Storage"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Hints"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Accessibility"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> running"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"App opened without being installed."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customise or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Undo"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Accessibility button hidden"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Tap to show Accessibility button"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> shortcut removed"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# shortcut removed}other{# shortcuts removed}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"User presence is detected"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
     <string name="install_app" msgid="5066668100199613936">"Install app"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Your inner display will be mirrored. Your front display will be turned off."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 870e4dd..31b4b6e 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Couldn\'t set up Face Unlock. Go to Settings to try again."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Touch the fingerprint sensor"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Press the unlock icon to continue"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Can’t recognise face. Use fingerprint instead."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Face not recognised. Use fingerprint instead."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognise face"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Face not recognised"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Face Unlock unavailable"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe left to start the communal tutorial"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Open the widget editor"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Customise"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Dismiss"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Add, remove and reorder your widgets in this space"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Add more widgets"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customise widgets"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Done"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Turn on Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"To connect your keyboard with your tablet, you first have to turn on Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Turn on"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Power notification controls"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"On – Face-based"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications. \n\n"<b>"Level 5"</b>" \n- Show at the top of the notification list \n- Allow full screen interruption \n- Always peek \n\n"<b>"Level 4"</b>" \n- Prevent full screen interruption \n- Always peek \n\n"<b>"Level 3"</b>" \n- Prevent full screen interruption \n- Never peek \n\n"<b>"Level 2"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound and vibration \n\n"<b>"Level 1"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound or vibrate \n- Hide from lock screen and status bar \n- Show at the bottom of the notification list \n\n"<b>"Level 0"</b>" \n- Block all notifications from the app"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Done"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Apply"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Turn off notifications"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Setup"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Storage"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Hints"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Accessibility"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> running"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"App opened without being installed."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customise or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Undo"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Accessibility button hidden"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Tap to show Accessibility button"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> shortcut removed"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# shortcut removed}other{# shortcuts removed}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"User presence is detected"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
     <string name="install_app" msgid="5066668100199613936">"Install app"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Your inner display will be mirrored. Your front display will be turned off."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index b3ed714..2970880 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎‏‏‏‎‏‏‎‏‎‏‏‎‎‏‏‏‏‏‎‎‎‎‏‎‏‎‏‎‎‏‎‎‎‏‏‎‎‏‎‏‏‎‏‏‎‎‎‏‏‏‎Couldn’t set up face unlock. Go to Settings to try again.‎‏‎‎‏‎"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‎‎‎‏‏‎‏‏‎‎‏‎‎‏‎‏‎‏‏‎‎‏‎‏‎‎‎‏‏‏‎‎‎‏‎‏‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‏‏‏‏‏‎Touch the fingerprint sensor‎‏‎‎‏‎"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‎‎‎‏‎‎‎‏‏‎‎‏‎‎‎‏‏‎‎‏‏‎‎‏‏‏‏‎‏‏‎‏‏‏‎‎‎‏‏‎‏‎‏‏‏‏‏‏‎‏‏‏‎‎Press the unlock icon to continue‎‏‎‎‏‎"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‏‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‎‎‏‏‎‎‎‏‎‎‎‏‎‎‎Can’t recognize face. Use fingerprint instead.‎‏‎‎‏‎"</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‏‎‏‎‏‎‏‎‏‎‏‎‏‏‎‎‏‏‏‏‏‎‏‎‏‏‎‎‎‏‎‏‎‎‏‏‏‏‎‎‏‎‏‎‏‎‏‏‎‎‏‎‏‏‎Face not recognized. Use fingerprint instead.‎‏‎‎‏‎"</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‏‏‏‏‎‎‎‏‎‎‏‎‎‏‏‏‎‎‏‏‎‎‎‎‏‎‎‎‎‏‏‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‎Can’t recognize face‎‏‎‎‏‎"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‎‎‎‎‎‎‏‏‎‎‏‎‏‏‏‎‏‎‏‎‏‏‎‏‏‏‏‏‎‎‏‏‎‏‏‏‎‏‎‏‎‎‎‏‎‎Face not recognized‎‏‎‎‏‎"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‎‎‏‏‏‏‎‎‎‎‏‎‎‏‏‎‎‏‏‎‏‎‎‎‎‎‏‏‎‎‏‎‏‎‎‏‏‏‎‏‎‎‎‎‏‏‏‎‎‎‎‎Use fingerprint instead‎‏‎‎‏‎"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‏‎‏‎‎‎‎‏‏‎‏‏‎‎‏‎‎‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‏‎‏‏‏‏‏‎‎‎‎‎Face Unlock unavailable‎‏‎‎‏‎"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‏‏‎‏‎‎‏‎‏‎‎‏‏‎‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‎‏‎‎‏‏‎‏‏‏‏‎‏‏‏‎‎‎‏‎‏‎Bluetooth connected.‎‏‎‎‏‎"</string>
@@ -413,6 +413,14 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‏‏‎‏‎‎‏‎‏‎‏‎‎‏‏‏‏‏‎‎‏‎‏‎‎‏‏‏‎‏‏‎‏‏‎‎‏‎‏‏‎‏‎‎‏‎‎‏‎‏‎‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%2$s</xliff:g>‎‏‎‎‏‏‏‎ • Charging • Full in ‎‏‎‎‏‏‎<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‎‎‎‎‏‏‏‏‎‎‎‎‎‎‎‏‏‎‏‏‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎Swipe left to start the communal tutorial‎‏‎‎‏‎"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‎‎‎‎‏‎‎‏‎‏‎‏‎‏‎‏‎‎‏‏‎‎‏‎‏‎‎‎‎‏‎‏‎‎‎‏‎‎‏‎‎‎‎‎‎‎Open the widget editor‎‏‎‎‏‎"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‏‏‎‏‎‏‎‎‎‏‏‎‏‎‏‏‏‎‏‎‎‏‏‎‎‎‏‏‏‏‎‎‏‏‎‏‏‏‏‎‏‎‏‎‎‏‎‏‎‎‏‏‎‎Customize‎‏‎‎‏‎"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‎‏‏‏‏‏‏‎‏‎‎‎‏‏‎‏‎‏‏‏‏‏‎‏‏‏‏‎‎‎‏‏‏‏‎‏‏‏‎‏‏‎‎‏‏‏‎‏‏‏‏‎‏‎‎Dismiss‎‏‎‎‏‎"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‏‎‏‎‎‏‏‎‎‏‎‎‏‎‏‎‎‎‏‏‎‏‏‎‎‏‎‎‏‎‏‏‎‎‎‏‏‎‏‏‏‎‎‏‏‎‎‎‏‎‏‏‎‎‎Add, remove, and reorder your widgets in this space‎‏‎‎‏‎"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‎‎‎‏‏‎‏‎‎‏‎‎‏‏‏‎‎‎‏‎‏‏‏‎‎‎‏‏‎‏‎‎‏‎‎‎‎‎‏‎‏‏‏‏‎‎‏‏‏‏‏‎‏‏‎Add more widgets‎‏‎‎‏‎"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‎‏‎‎‏‏‎‏‏‏‏‎‎‏‎‏‎‏‏‏‎‎‏‎‎‎‎‏‎‎‏‏‎‏‎‏‎‎‎‎‎‏‏‎Long press to customize widgets‎‏‎‎‏‎"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <string name="edit_widget" msgid="9030848101135393954">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‎‎‎‎‏‎‎‎‎‎‏‎‏‎‏‏‏‏‏‎‏‎‎‏‎‎‏‎‏‎‎‎‏‎‎Edit widget‎‏‎‎‏‎"</string>
     <string name="button_to_remove_widget" msgid="3948204829181214098">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‎‎‏‎‏‎‏‏‎‏‎‏‏‏‎‏‎‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎Remove‎‏‎‎‏‎"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‎‎‏‏‎‎‏‏‎‎‏‏‏‏‏‎‎‎‎‎‏‎‏‏‎‏‏‏‎‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‎‏‎Add widget‎‏‎‎‏‎"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‏‎‏‎‎‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‎‎‏‏‎‏‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‎‎‏‏‎‎‏‎‎‎‎‏‏‎Done‎‏‎‎‏‎"</string>
@@ -599,9 +607,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‏‎‎‎‏‏‎‎‏‏‎‏‎‏‏‏‎‎‎‎‏‎‎‎‏‏‏‏‏‏‎‎‏‎‎‎‏‎‎‎Turn on Bluetooth?‎‏‎‎‏‎"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‏‏‎‎‏‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‏‎‏‎‏‎‏‎‏‎‏‏‏‎‎‎‏‎‎‎‎‏‏‎‎‎‏‎‏‎To connect your keyboard with your tablet, you first have to turn on Bluetooth.‎‏‎‎‏‎"</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‎‎‏‏‏‏‎‎‎‎‏‏‏‎‎‎‎‏‏‏‎‎‏‎‎‏‏‎‎‎‏‎‎‏‏‏‏‏‎‎‏‎‎‎‏‎‎‏‎‏‏‎‎‎Turn on‎‏‎‎‏‎"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‎‏‎‎‎‎‏‏‎‏‎‎‎‎‏‎‎‎‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‎‏‏‏‏‎‏‏‎‎‎‏‏‎‏‎‏‏‏‎‎‏‏‎Power notification controls‎‏‎‎‏‎"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‏‏‏‎‏‎‎‏‎‏‎‎‎‏‎‏‏‎‎‎‏‏‎‎‏‏‎‏‎‏‏‏‎‏‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎On - Face-based‎‏‎‎‏‎"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‎‏‏‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎‎‎‏‏‏‎‏‎‎‎‎‎‏‏‎‏‏‏‏‎‏‎‎‏‎‏‏‎‎‎‎With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications. ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎"<b>"‎‏‎‎‏‏‏‎Level 5‎‏‎‎‏‏‎"</b>"‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Show at the top of the notification list ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Allow full screen interruption ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Always peek ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎"<b>"‎‏‎‎‏‏‏‎Level 4‎‏‎‎‏‏‎"</b>"‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Prevent full screen interruption ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Always peek ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎"<b>"‎‏‎‎‏‏‏‎Level 3‎‏‎‎‏‏‎"</b>"‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Prevent full screen interruption ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Never peek ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎"<b>"‎‏‎‎‏‏‏‎Level 2‎‏‎‎‏‏‎"</b>"‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Prevent full screen interruption ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Never peek ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Never make sound and vibration ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎"<b>"‎‏‎‎‏‏‏‎Level 1‎‏‎‎‏‏‎"</b>"‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Prevent full screen interruption ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Never peek ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Never make sound or vibrate ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Hide from lock screen and status bar ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Show at the bottom of the notification list ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎"<b>"‎‏‎‎‏‏‏‎Level 0‎‏‎‎‏‏‎"</b>"‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Block all notifications from the app‎‏‎‎‏‎"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‏‏‏‎‏‎‏‏‎‎‎‏‎‏‏‎‏‏‏‏‏‎‎‏‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‎‎‎‎‏‎‎‎‎‎Done‎‏‎‎‏‎"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‏‎‏‏‏‏‎‏‎‎‎‏‏‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‎‎‏‏‎‏‎‏‏‏‎‏‏‏‎‏‏‎‎‏‎‏‎‏‏‏‎Apply‎‏‎‎‏‎"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‎‎‏‎‎‏‎‏‏‎‎‏‎‏‎‎‎‎‏‎‎‏‎‎‏‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎Turn off notifications‎‏‎‎‏‎"</string>
@@ -834,6 +840,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‎‎‏‏‏‎‎‎‎‎‏‎‎‎‎‏‏‎‏‎‎‏‎‏‎‎‎‏‎‎‏‏‏‏‎‎Setup‎‏‎‎‏‎"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‎‎‎‎‎‏‏‏‏‏‎‏‎‏‏‎‎‏‏‏‎‏‏‎‏‏‎‏‏‎‎‏‏‏‎‎‏‎‎‎‏‎‎‏‏‎‎‎‎‎‎‎‎‏‎Storage‎‏‎‎‏‎"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‏‎‎‏‎‏‎‏‎‏‏‎‎‎‎‎‎‎‏‎‎‏‏‎‏‏‎‎‎‏‎‎‎‏‎‏‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎‎Hints‎‏‎‎‏‎"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‎‏‎‏‏‎‏‎‏‎‎‎‎‎‏‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‎‎‎‎‎‏‎‎‎‎‎‏‏‏‏‎‎‎Accessibility‎‏‎‎‏‎"</string>
     <string name="instant_apps" msgid="8337185853050247304">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‎‏‏‏‎‏‎‎‎‏‎‎‏‎‏‏‏‎‎‎‏‏‎‏‏‎‏‎‏‏‏‏‎‎‎‏‏‎‏‏‎‎‎‏‎‎‎‏‎‎‎‎Instant Apps‎‏‎‎‏‎"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‏‎‏‏‏‎‎‎‎‎‎‏‏‏‎‎‎‎‎‎‎‏‎‏‎‏‏‎‏‏‏‎‏‎‎‏‏‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="APP">%1$s</xliff:g>‎‏‎‎‏‏‏‎ running‎‏‎‎‏‎"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‎‎‏‏‏‎‏‏‎‏‎‏‏‏‎‎‎‎‏‏‏‏‎‎‏‏‎‎‏‏‏‎‏‎‎‏‏‎‏‎‏‏‏‎‎‎‏‎‏‎‏‎‎App opened without being installed.‎‏‎‎‏‎"</string>
@@ -929,6 +936,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‏‏‎‏‏‎‎‎‏‏‎‏‎‎‏‏‎‎‎‎‏‎‎‏‏‏‏‎‎‏‏‏‏‏‎‏‎‎‏‏‏‎‎Tap to open accessibility features. Customize or replace this button in Settings.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎"<annotation id="link">"‎‏‎‎‏‏‏‎View settings‎‏‎‎‏‏‎"</annotation>"‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‏‎‎‏‏‎‏‏‎‎‎‎‏‎‎‏‎‏‎‏‎‏‎‎‏‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‏‎‏‎Move button to the edge to hide it temporarily‎‏‎‎‏‎"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‎‎‎‎‏‏‎‎‎‎‎‎‏‏‏‎‏‎‏‎‎‎‏‎Undo‎‏‎‎‏‎"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‎‎‎‏‏‎‏‏‏‎‎‎‏‏‎‎‎‏‎‏‏‎‎‎‏‏‎‎‎‏‎‎‎‎‎‎‏‎‎‎‏‎‏‏‎‎‎‏‎‏‏‎‏‏‏‏‎Accessibility button hidden‎‏‎‎‏‎"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‎‏‏‏‎‎‎‎‏‏‎‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‏‏‎‏‏‎‏‎‏‎‎‎‏‏‎‎‏‎‏‎‎Tap to show accessibility button‎‏‎‎‏‎"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‏‎‎‏‎‏‎‎‎‎‎‏‏‎‎‎‎‎‎‏‏‎‏‏‏‎‎‎‎‏‎‏‎‎‎‏‏‎‎‏‎‎‏‎‏‎‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="FEATURE_NAME">%s</xliff:g>‎‏‎‎‏‏‏‎ shortcut removed‎‏‎‎‏‎"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‎‏‎‎‎‎‏‎‎‏‏‏‎‏‏‎‏‎‏‎‎‎‏‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‎‎‏‎‎‎‏‏‎‎‏‎‏‏‎‏‏‎# shortcut removed‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‎‏‎‎‎‎‏‎‎‏‏‏‎‏‏‎‏‎‏‎‎‎‏‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‎‎‏‎‎‎‏‏‎‎‏‎‏‏‎‏‏‎# shortcuts removed‎‏‎‎‏‎}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‎‎‏‎‎‎‏‏‏‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‏‏‎‏‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‎‎‏‎Move top left‎‏‎‎‏‎"</string>
@@ -1207,6 +1216,7 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‏‎‎‎‏‏‏‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‎‎‏‎‏‏‎‎‎‎‎‏‏‎‎‎‏‎‎‏‏‎‏‏‎‎‎User presence is detected‎‏‎‎‏‎"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‎‎‎‎‎‏‏‏‏‎‎‎‏‏‏‏‏‎‎‎‏‏‎‏‎‏‎‏‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‎‏‎‎‎‎‎‏‎Set default notes app in Settings‎‏‎‎‏‎"</string>
     <string name="install_app" msgid="5066668100199613936">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‎‎‎‏‏‎‏‎‏‏‏‏‎‎‏‎‏‎‏‏‏‏‎‎‏‏‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‎‎‎‎‎Install app‎‏‎‎‏‎"</string>
+    <string name="dismissible_keyguard_swipe" msgid="8377597870094949432">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‎‎‏‏‎‎‏‏‎‏‎‎‏‏‏‎‎‎‎‎‎‏‎‏‏‏‏‎‎‎‎‏‎‏‎‎‎‎‎‏‏‏‎‎‎‎‏‏‏‎‎‎‎Swipe up to continue‎‏‎‎‏‎"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‎‎‏‎‏‎‏‏‏‏‎‏‎‎‎‎‎‎‏‎‎‎‏‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‏‎‎Mirror to external display?‎‏‎‎‏‎"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‎‏‏‏‏‏‎‎‎‏‎‏‎‎‏‎‏‏‎‏‎‎‎‎‎‏‎‎‎‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‎‏‏‎‎‏‎‏‏‎Your inner display will be mirrored. Your front display will be turned off.‎‏‎‎‏‎"</string>
     <string name="mirror_display" msgid="2515262008898122928">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‎‏‎‎‎‎‎‎‎‎‎‎‏‎‏‏‏‏‎‎‎‎‏‏‏‏‏‎‏‎‏‏‎‏‎‎‎‎‏‏‏‎‏‎‎‏‎‏‏‎‎‎‎‎Mirror display‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index c535560..8f2d0e7 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"No se pudo configurar el desbloqueo facial. Ve a Configuración para volver a intentarlo."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Toca el sensor de huellas dactilares"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Presiona el ícono de desbloqueo para continuar"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"No se reconoce el rostro. Usa la huella dactilar."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"No se reconoció el rostro. Usa la huella dactilar."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"No se reconoce el rostro"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"No se reconoció el rostro"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usa la huella dactilar"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Desbloqueo facial no disponible"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando • Se completará en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Desliza el dedo a la izquierda para iniciar el instructivo comunal"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir el editor de widget"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Personalizar"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Descartar"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Agrega, quita y reordena tus widgets en este espacio"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Agregar más widgets"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantén presionado para personalizar los widgets"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Quitar"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Agregar widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Listo"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"¿Activar Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Para conectar el teclado con la tablet, primero debes activar Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Activar"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Controles de activación de notificaciones"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Activa - En función del rostro"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Con los controles de activación de notificaciones, puedes establecer un nivel de importancia para las notificaciones de una app. \n\n"<b>"Nivel 5"</b>" \n- Mostrar en la parte superior de la lista de notificaciones. \n- Permitir interrupción en la pantalla completa. \n- Mostrar siempre. \n\n"<b>"Nivel 4"</b>" \n- No permitir interrupción en la pantalla completa. \n- Mostrar siempre. \n\n"<b>"Nivel 3"</b>" \n- No permitir interrupción en la pantalla completa. \n- No mostrar. \n\n"<b>"Nivel 2"</b>" \n- No permitir interrupción en la pantalla completa. \n- No mostrar. \n- No sonar ni vibrar. \n\n"<b>"Nivel 1"</b>" \n- No permitir interrupción en la pantalla completa. \n- No mostrar. \n- No sonar ni vibrar. \n- Ocultar de la pantalla bloqueada y la barra de estado. \n- Mostrar al final de la lista de notificaciones. \n\n"<b>"Nivel 0"</b>" \n- Bloquear todas las notificaciones de la app."</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Listo"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Aplicar"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Desactivar notificaciones"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Configuración"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Almacenamiento"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Sugerencias"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Accesibilidad"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Apps instantáneas"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> en ejecución"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"La app se abrió sin instalarse."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Presiona para abrir las funciones de accesibilidad. Personaliza o cambia botón en Config.\n\n"<annotation id="link">"Ver config"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mueve el botón hacia el borde para ocultarlo temporalmente"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Deshacer"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Botón de accesibilidad oculto"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Presiona para ver el botón de accesibilidad"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Se quitó el acceso directo a <xliff:g id="FEATURE_NAME">%s</xliff:g>"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Se quitó # acceso directo}many{Se quitaron # accesos directos}other{Se quitaron # accesos directos}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover arriba a la izquierda"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Se detectó la presencia del usuario"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Configura la app de notas predeterminada en Configuración"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar app"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"¿Quieres duplicar en la pantalla externa?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Se duplicará la pantalla interior. Se apagará la pantalla frontal."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Duplicar pantalla"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 1157ff1..7848e87 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"No se ha podido configurar Desbloqueo facial. Ve a Ajustes e inténtalo de nuevo."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Toca el sensor de huellas digitales"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Pulsa el icono de desbloquear para continuar"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"No se reconoce la cara. Usa la huella digital."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Cara no reconocida. Usa la huella digital."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"No se reconoce la cara"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Cara no reconocida"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usa la huella digital"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Desbloqueo facial no disponible"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando • Carga completa en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Desliza hacia la izquierda para iniciar el tutorial de la comunidad"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir editor de widgets"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Personalizar"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Cerrar"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Añade, elimina y reordena tus widgets en este espacio"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Añade más widgets"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantén pulsado para personalizar los widgets"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Quitar"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Añadir widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Hecho"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"¿Activar Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Para poder conectar tu teclado a tu tablet, debes activar el Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Activar"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Controles de energía de las notificaciones"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Activado: basado en caras"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Los controles de energía de las notificaciones permiten establecer un nivel de importancia de 0 a 5 para las notificaciones de las aplicaciones. \n\n"<b>"Nivel 5"</b>" \n- Mostrar en la parte superior de la lista de notificaciones \n- Permitir interrumpir en el modo de pantalla completa \n- Mostrar siempre \n\n"<b>"Nivel 4"</b>" \n- Evitar interrumpir en el modo de pantalla completa \n- Mostrar siempre \n\n"<b>"Nivel 3"</b>" \n- Evitar interrumpir en el modo de pantalla completa \n- No mostrar nunca \n\n"<b>"Nivel 2"</b>" \n- Evitar interrumpir en el modo de pantalla completa\n- No mostrar nunca \n- No emitir sonido ni vibrar nunca \n\n"<b>"Nivel 1"</b>" \n- Evitar interrumpir en el modo de pantalla completa \n- No mostrar nunca \n- No emitir sonido ni vibrar nunca \n- Ocultar de la pantalla de bloqueo y de la barra de estado \n- Mostrar en la parte inferior de la lista de notificaciones \n\n"<b>"Nivel 0"</b>" \n- Bloquear todas las notificaciones de la aplicación"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Hecho"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Aplicar"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Desactivar notificaciones"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Configuración"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Almacenamiento"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Sugerencias"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Accesibilidad"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Aplicaciones Instantáneas"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> se está ejecutando"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"La aplicación se ha abierto sin instalarse."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toca para abrir funciones de accesibilidad. Personaliza o sustituye este botón en Ajustes.\n\n"<annotation id="link">"Ver ajustes"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mueve el botón hacia el borde para ocultarlo temporalmente"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Deshacer"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Botón de accesibilidad oculto"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Toca para que se muestre el botón de accesibilidad"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Acceso directo a <xliff:g id="FEATURE_NAME">%s</xliff:g> quitado"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# acceso directo eliminado}many{# accesos directos eliminados}other{# accesos directos eliminados}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover arriba a la izquierda"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Se ha detectado la presencia de usuarios"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Configura la aplicación de notas predeterminada en Ajustes"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar aplicación"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"¿Proyectar a pantalla externa?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Se proyectará tu pantalla interior. Se apagará tu pantalla frontal."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Proyectar pantalla"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 36b3f1e..6591799 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Näoga avamist ei õnnestunud seadistada. Avage seaded ja proovige uuesti."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Puudutage sõrmejäljeandurit"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Jätkamiseks vajutage avamise ikooni"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Nägu ei õnnestu tuvastada. Kasutage sõrmejälge."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Nägu ei tuvastatud. Kasutage sõrmejälge."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Nägu ei õnnestu tuvastada"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Nägu ei tuvastatud"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Kasutage sõrmejälge"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Näoga avamine pole saadaval"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth on ühendatud."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laadimine • Täis <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> pärast"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Ühise õpetuse käivitamiseks pühkige vasakule"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Vidina redaktori avamine"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Kohandage"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Loobuge"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Lisage ja eemaldage selles ruumis oma vidinaid ning muutke nende järjestust"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Lisage rohkem vidinaid"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Vajutage pikalt vidinate kohandamiseks"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Eemalda"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Lisa vidin"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Valmis"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Kas lülitada Bluetooth sisse?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Klaviatuuri ühendamiseks tahvelarvutiga peate esmalt Bluetoothi sisse lülitama."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Lülita sisse"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Toite märguannete juhtnupud"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Sees – näopõhine"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Toite märguannete juhtnuppudega saate määrata rakenduse märguannete tähtsuse taseme vahemikus 0–5. \n\n"<b>"5. tase"</b>" \n- Kuva märguannete loendi ülaosas\n- Luba täisekraanil häirimine \n- Kuva alati ekraani servas \n\n"<b>"4. tase"</b>" \n- Keela täisekraanil häirimine \n- Kuva alati ekraani servas \n\n"<b>"3. tase"</b>" \n- Keela täisekraanil häirimine \n- Ära kunagi kuva ekraani servas \n\n"<b>"2. tase"</b>" \n- Keela täisekraanil häirimine \n- Ära kunagi kuva ekraani servas \n- Ära kunagi helise ega vibreeri \n\n"<b>"1. tase"</b>" \n- Keela täisekraanil häirimine \n- Ära kunagi kuva ekraani servas \n- Ära kunagi helise ega vibreeri \n- Peida lukustuskuval ja olekuribal \n- Kuva märguannete loendi allosas \n\n"<b>"Tase 0"</b>" \n- Blokeeri kõik rakenduse märguanded"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Valmis"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Rakenda"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Lülita märguanded välja"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Seadistamine"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Salvestusruum"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Vihjed"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Juurdepääsetavus"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Installimata avatavad rakendused"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"Rakendus <xliff:g id="APP">%1$s</xliff:g> töötab"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Rakendus avati installimata."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Puudutage juurdepääsufunktsioonide avamiseks. Kohandage nuppu või asendage see seadetes.\n\n"<annotation id="link">"Kuva seaded"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Teisaldage nupp serva, et see ajutiselt peita"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Võta tagasi"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Juurdepääsetavuse nupp on peidetud"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Puudutage juurdepääsetavuse nupu kuvamiseks"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Funktsiooni <xliff:g id="FEATURE_NAME">%s</xliff:g> otsetee eemaldati"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# otsetee eemaldati}other{# otseteed eemaldati}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Teisalda üles vasakule"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Tuvastati kasutaja kohalolu"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Määrake seadetes märkmete vaikerakendus."</string>
     <string name="install_app" msgid="5066668100199613936">"Installi rakendus"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Kas peegeldada välisekraanile?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Teie siseekraani peegeldatakse. Teie esiekraan lülitatakse välja."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Peegelda ekraani"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 5ea02d1..937aa24 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Ezin izan da konfiguratu aurpegi bidez desblokeatzeko eginbidea. Berriro saiatzeko, joan ezarpenetara."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Sakatu hatz-marken sentsorea"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Aurrera egiteko, sakatu desblokeatzeko ikonoa"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Ezin da hauteman aurpegia. Erabili hatz-marka."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Ez da ezagutu aurpegia. Erabili hatz-marka."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Ezin da ezagutu aurpegia"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Ez da ezagutu aurpegia"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Erabili hatz-marka"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Aurpegi bidez desblokeatzeko eginbidea ez dago erabilgarri"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetootha konektatuta."</string>
@@ -366,8 +366,8 @@
     <string name="sensor_privacy_mic_unblocked_dialog_content" msgid="4889961886199270224">"Aplikazio eta zerbitzu guztiek mikrofonoa erabil dezakete."</string>
     <string name="sensor_privacy_mic_blocked_no_exception_dialog_content" msgid="5864898470772965394">"Mikrofonoa erabiltzeko baimena desgaituta dago aplikazio eta zerbitzu guztietarako. Mikrofonoa erabiltzeko baimena gaitzeko, joan Ezarpenak &gt; Pribatutasuna &gt; Mikrofonoa atalera."</string>
     <string name="sensor_privacy_mic_blocked_with_exception_dialog_content" msgid="810289713700437896">"Mikrofonoa erabiltzeko baimena desgaituta dago aplikazio eta zerbitzu guztietarako. Baimen hori aldatzeko, joan Ezarpenak &gt; Pribatutasuna &gt; Mikrofonoa atalera."</string>
-    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Piztu da kamera"</string>
-    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Itzali da kamera"</string>
+    <string name="sensor_privacy_camera_turned_on_dialog_title" msgid="8039095295100075952">"Aktibatu da kamera"</string>
+    <string name="sensor_privacy_camera_turned_off_dialog_title" msgid="1936603903120742696">"Desaktibatu da kamera"</string>
     <string name="sensor_privacy_camera_unblocked_dialog_content" msgid="7847190103011782278">"Aplikazio eta zerbitzu guztiek kamera erabil dezakete."</string>
     <string name="sensor_privacy_camera_blocked_dialog_content" msgid="3182428709314874616">"Kamera erabiltzeko baimena desgaituta dago aplikazio eta zerbitzu guztietarako."</string>
     <string name="sensor_privacy_htt_blocked_dialog_content" msgid="3333321592997666441">"Mikrofonoaren botoia erabiltzeko, gaitu mikrofonoa erabiltzeko baimena ezarpenetan."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Kargatzen • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> guztiz kargatu arte"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Tutorial komuna hasteko, pasatu hatza ezkerrera"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Ireki widget-editorea"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Pertsonalizatu"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Baztertu"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Gehitu eta kendu eremu honetako widgetak, edo aldatu haien ordena"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Gehitu widget gehiago"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Widgetak pertsonalizatzeko, sakatu luze"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Kendu"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Gehitu widget bat"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Eginda"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Bluetooth eginbidea aktibatu nahi duzu?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Teklatua tabletara konektatzeko, Bluetooth eginbidea aktibatu behar duzu."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Aktibatu"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Bateria-mailaren arabera jakinarazpenak kontrolatzeko aukerak"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Aktibatuta: aurpegian oinarrituta"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Bateria-mailaren arabera jakinarazpenak kontrolatzeko aukerekin, 0 eta 5 bitarteko garrantzi-mailetan sailka ditzakezu aplikazioen jakinarazpenak. \n\n"<b>"5. maila"</b>" \n- Erakutsi jakinarazpenen zerrendaren goialdean. \n- Baimendu etetea pantaila osoko moduan zaudenean. \n- Agerrarazi beti jakinarazpenak. \n\n"<b>"4. maila"</b>" \n- Galarazi etetea pantaila osoko moduan zaudenean. \n- Agerrarazi beti jakinarazpenak. \n\n"<b>"3. maila"</b>" \n- Galarazi etetea pantaila osoko moduan zaudenean. \n- Ez agerrarazi jakinarazpenik inoiz. \n\n"<b>"2. maila"</b>" \n- Galarazi etetea pantaila osoko moduan zaudenean. \n- Ez agerrarazi jakinarazpenik inoiz. \n- Ez egin soinurik edo dardararik inoiz. \n\n"<b>"1. maila"</b>" \n- Galarazi etetea pantaila osoko moduan zaudenean. \n- Ez agerrarazi jakinarazpenik inoiz. \n- Ez egin soinurik edo dardararik inoiz. \n- Ezkutatu pantaila blokeatutik eta egoera-barratik. \n- Erakutsi jakinarazpenen zerrendaren behealdean. \n\n"<b>"0. maila"</b>" \n- Blokeatu aplikazioaren jakinarazpen guztiak."</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Eginda"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Aplikatu"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Desaktibatu jakinarazpenak"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Konfigurazioa"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Memoria"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Aholkuak"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Erabilerraztasuna"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Zuzeneko aplikazioak"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> abian da"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Ezer instalatu gabe ireki da aplikazioa."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Erabilerraztasun-eginbideak irekitzeko, sakatu hau. Ezarpenetan pertsonalizatu edo ordez dezakezu botoia.\n\n"<annotation id="link">"Ikusi ezarpenak"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Eraman botoia ertzera aldi baterako ezkutatzeko"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Desegin"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Erabilerraztasuna botoia ezkutatuta dago"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Erabilerraztasuna botoia erakusteko, sakatu hau"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Kendu da lasterbidea (<xliff:g id="FEATURE_NAME">%s</xliff:g>)"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# lasterbide kendu da}other{# lasterbide kendu dira}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Eraman goialdera, ezkerretara"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Erabiltzailearen presentzia hauteman da"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ezarri oharren aplikazio lehenetsia ezarpenetan"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalatu aplikazioa"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Kanpoko pantailan islatu nahi duzu?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Barneko pantaila islatuko da. Aurreko pantaila desaktibatu egingo da."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Islatu pantaila"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index aa77b4b..9948f5f 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"«قفل‌گشایی با چهره» راه‌اندازی نشد. برای امتحان مجدد، به «تنظیمات» بروید."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"حسگر اثر انگشت را لمس کنید"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"برای ادامه، نماد قفل‌گشایی را فشار دهید"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"چهره شناسایی نشد. درعوض از اثر انگشت استفاده کنید."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"چهره شناسایی نشد. درعوض از اثر انگشت استفاده کنید."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"چهره شناسایی نشد"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"چهره شناسایی نشد"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"از اثر انگشت استفاده کنید"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"«قفل‌گشایی با چهره» دردسترس نیست"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"بلوتوث متصل است."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • درحال شارژ شدن • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> تا شارژ کامل"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"برای شروع آموزش گام‌به‌گام عمومی، تند به‌چپ بکشید"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"باز کردن ویرایشگر ابزارک"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"سفارشی‌سازی"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"بستن"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"افزودن، برداشتن، و تغییر ترتیب ابزارک‌ها در این فضا"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"افزودن ابزارک‌های بیشتر"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"برای سفارشی‌سازی ابزارک‌ها، فشار طولانی دهید"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"برداشتن"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"افزودن ابزارک"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"تمام"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"بلوتوث روشن شود؟"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"برای مرتبط کردن صفحه‌کلید با رایانه لوحی، ابتدا باید بلوتوث را روشن کنید."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"روشن کردن"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"کنترل‌های قدرتمند اعلان"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"روشن - براساس چهره"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"با کنترل‌های قدرتمند اعلان می‌توانید سطح اهمیت اعلان‌های هر برنامه را از ۰ تا ۵ تعیین کنید. \n\n"<b>"سطح ۵"</b>" \n- در صدر فهرست اعلان‌ها نشان داده می‌شود \n- وقفه برای نمایش تمام‌صفحه مجاز است \n- همیشه اجمالی نشان داده می‌شود \n\n"<b>"سطح ۴"</b>" \n- وقفه برای نمایش تمام‌صفحه مجاز نیست \n- همیشه اجمالی نشان داده می‌شود \n\n"<b>"سطح ۳"</b>" \n- وقفه برای نمایش تمام‌صفحه مجاز نیست \n- هیچ‌وقت اجمالی نشان داده نمی‌شود \n\n"<b>"سطح ۲"</b>" \n- وقفه برای نمایش تمام‌صفحه مجاز نیست \n- هیچ‌وقت اجمالی نشان داده نمی‌شود \n- هیچ‌وقت صدا و لرزش ایجاد نمی‌کند \n\n"<b>"سطح ۱"</b>" \n- نمایش تمام صفحه مجاز نیست \n- هیچ‌وقت اجمالی نشان داده نمی‌شود \n- هیچ‌وقت صدا یا لرزش ایجاد نمی‌کند \n- در صفحه قفل و نوار وضعیت پنهان است \n- در پایین فهرست اعلان‌ها نشان داده می‌شود \n\n"<b>"سطح ۰"</b>" \n- همه اعلان‌های این برنامه مسدود است"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"تمام"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"اعمال"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"خاموش کردن اعلان‌ها"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"راه‌اندازی"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"فضای ذخیره‌سازی"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"نکات"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"دسترس‌پذیری"</string>
     <string name="instant_apps" msgid="8337185853050247304">"برنامه‌های فوری"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"‫‫<xliff:g id="APP">%1$s</xliff:g> درحال اجرا"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"برنامه بدون نصب شدن باز شد."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"برای باز کردن ویژگی‌های دسترس‌پذیری ضربه بزنید. در تنظیمات این دکمه را سفارشی یا جایگزین کنید\n\n"<annotation id="link">"تنظیمات"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"برای پنهان کردن موقتی دکمه، آن را به لبه ببرید"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"واگرد"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"دکمه دسترس‌پذیری پنهان شده است"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"برای نمایش دکمه دسترس‌پذیری ضربه بزنید"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"میان‌بر «<xliff:g id="FEATURE_NAME">%s</xliff:g>» برداشته شد"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{میان‌بر «#» برداشته شد}one{میان‌بر «#» برداشته شد}other{میان‌بر «#» برداشته شد}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"انتقال به بالا سمت راست"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"حضور کاربر شناسایی می‌شود"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"برنامه پیش‌فرض یادداشت را در «تنظیمات» تنظیم کنید"</string>
     <string name="install_app" msgid="5066668100199613936">"نصب برنامه"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"روی نمایشگر خارجی قرینه‌سازی شود؟"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"نمایشگر داخلی شما قرینه‌سازی می‌شود. نمایشگر جلو خاموش می‌شود."</string>
     <string name="mirror_display" msgid="2515262008898122928">"قرینه‌سازی نمایشگر"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 882c42c..d4bb6bd 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Kasvojentunnistusavauksen käyttöönotto epäonnistui. Siirry asetuksiin ja yritä uudelleen."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Kosketa sormenjälkitunnistinta"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Jatka lukituksen avauskuvakkeella"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Kasvoja ei voi tunnistaa. Käytä sormenjälkeä."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Kasvoja ei tunnistettu. Käytä sormenjälkeä."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Kasvoja ei voi tunnistaa"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Kasvoja ei tunnistettu"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Käytä sormenjälkeä"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Kasvojentunnistusavaus ei ole saatavilla"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth yhdistetty."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Latautuu • Täynnä <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> päästä"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Aloita yhteisöesittely pyyhkäisemällä vasemmalle"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Avaa widgetien muokkaaja"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Muokkaa"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Hylkää"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Lisää, poista ja järjestä widgetejäsi uudelleen tässä tilassa"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Lisää widgetejä"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Yksilöi widgetit pitkällä painalluksella"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Poista"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Lisää widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Valmis"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Otetaanko Bluetooth käyttöön?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Jotta voit yhdistää näppäimistön tablettiisi, sinun on ensin otettava Bluetooth käyttöön."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Ota käyttöön"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Ilmoitusten tehohallinta"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Päällä – kasvojen perusteella"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Ilmoitusten tehohallinnan avulla voit määrittää sovelluksen ilmoituksille tärkeystason väliltä 0–5. \n\n"<b>"Taso 5"</b>" \n– Ilmoitukset näytetään ilmoitusluettelon yläosassa \n– Näkyminen koko näytön tilassa sallitaan \n– Ilmoitukset kurkistavat aina näytölle\n\n"<b>"Taso 4"</b>" \n– Näkyminen koko näytön tilassa estetään \n– Ilmoitukset kurkistavat aina näytölle \n\n"<b>"Taso 3"</b>" \n– Näkyminen koko näytön tilassa estetään \n– Ei kurkistamista \n\n"<b>"Taso 2"</b>" \n– Näkyminen koko näytön tilassa estetään \n– Ei kurkistamista \n– Ei ääniä eikä värinää \n\n"<b>"Taso 1"</b>" \n– Näkyminen koko näytön tilassa estetään \n– Ei kurkistamista \n– Ei ääniä eikä värinää \n– Ilmoitukset piilotetaan lukitusnäytöltä ja tilapalkista \n– Ilmoitukset näytetään ilmoitusluettelon alaosassa \n\n"<b>"Taso 0"</b>" \n– Kaikki sovelluksen ilmoitukset estetään"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Valmis"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Käytä"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Poista ilmoitukset käytöstä"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Määritys"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Tallennustila"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Vihjeet"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Saavutettavuus"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> on käynnissä"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Sovellus avattiin ilman asennusta."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Avaa esteettömyysominaisuudet napauttamalla. Yksilöi tai vaihda painike asetuksista.\n\n"<annotation id="link">"Avaa asetukset"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Piilota painike tilapäisesti siirtämällä se reunaan"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Kumoa"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Saavutettavuuspainike piilotettu"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Tuo saavutettavuuspainike esiin napauttamalla"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Pikanäppäin (<xliff:g id="FEATURE_NAME">%s</xliff:g>) poistettu"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# pikanäppäin poistettu}other{# pikanäppäintä poistettu}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Siirrä vasempaan yläreunaan"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Käyttäjän läsnäolo havaittu"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Aseta oletusmuistiinpanosovellus Asetuksista"</string>
     <string name="install_app" msgid="5066668100199613936">"Asenna sovellus"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Peilataanko ulkoiselle näytölle?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Sisänäyttö peilataan. Etunäyttö laitetaan pois päältä."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Peilaa näyttö"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 8370b01..ea7b0b0 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Impossible de configurer le Déverrouillage par reconnaissance faciale. Accédez au menu Paramètres pour réessayer."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Touchez le capteur d\'empreintes digitales"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Appuyez sur l\'icône Déverrouiller pour continuer"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Visage non reconnu. Utilisez plutôt l\'empreinte digitale."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Visage non reconnu. Utilisez l\'empreinte digitale."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Visage non reconnu"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Visage non reconnu"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Utiliser l\'empreinte digitale"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Déverrouillage par reconnaissance faciale inaccessible."</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connecté"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge en cours… • Se terminera dans <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Balayer l\'écran vers la gauche pour démarrer le tutoriel communautaire"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Ouvrir l\'éditeur de widget"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Personnaliser"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Fermer"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Ajouter, retirer et réorganiser vos widgets dans cet espace"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Ajouter plus de widgets"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Maintenez le doigt pour personnaliser les widgets"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Retirer"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ajouter un widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Terminé"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Activer Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Pour connecter votre clavier à votre tablette, vous devez d\'abord activer la connectivité Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Activer"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Réglages avancés des notifications"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Activé : en fonction du visage"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Avec les réglages avancés des notifications, vous pouvez définir un degré d\'importance de 0 à 5 pour les notifications d\'une application. \n\n"<b>"Niveau 5"</b>" \n- Afficher dans le haut de la liste des notifications \n- Autoriser les interruptions en mode plein écran \n- Toujours afficher les aperçus \n\n"<b>"Niveau 4"</b>" \n- Empêcher les interruptions en mode plein écran \n- Toujours afficher les aperçus \n\n"<b>"Niveau 3"</b>" \n- Empêcher les interruptions en mode plein écran \n- Ne jamais afficher les aperçus \n\n"<b>"Niveau 2"</b>" \n- Empêcher les interruptions en mode plein écran \n- Ne jamais afficher les aperçus \n- Ne pas autoriser les sons et les vibrations \n\n"<b>"Niveau 1"</b>" \n- Empêcher les interruptions en mode plein écran \n- Ne jamais afficher les aperçus \n- Ne pas autoriser les sons et les vibrations \n- Masquer de l\'écran de verrouillage et de la barre d\'état status bar \n- Afficher dans le bas de la liste des notifications \n\n"<b>"Level 0"</b>" \n- Bloquer toutes les notifications de l\'application"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Terminé"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Appliquer"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Désactiver les notifications"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Configuration"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Stockage"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Conseils"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Accessibilité"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Applications instantanées"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> en cours d\'exécution"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Application ouverte sans avoir été installée."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Touchez pour ouvrir fonction. d\'access. Personnalisez ou remplacez bouton dans Param.\n\n"<annotation id="link">"Afficher param."</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Déplacez le bouton vers le bord pour le masquer temporairement"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Annuler"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Bouton d\'accessibilité masqué"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Touchez pour afficher le bouton d\'accessibilité"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Le raccourci <xliff:g id="FEATURE_NAME">%s</xliff:g> a été retiré"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# raccourci retiré}one{# raccourci retiré}many{# de raccourcis retirés}other{# raccourcis retirés}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Déplacer dans coin sup. gauche"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"La présence d\'un utilisateur est détectée"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Définir l\'application de prise de notes par défaut dans les Paramètres"</string>
     <string name="install_app" msgid="5066668100199613936">"Installer l\'application"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Dupliquer l\'écran sur un moniteur externe?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Votre écran intérieur sera dupliqué. Votre écran frontal sera désactivé."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Dupliquer l\'écran"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index af81a31..acaa43e 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Impossible de configurer le déverrouillage par reconnaissance faciale. Accédez aux paramètres pour réessayer."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Appuyez sur le lecteur d\'empreinte digitale"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Appuyez sur l\'icône de déverrouillage pour continuer"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Visage non reconnu. Utilisez votre empreinte."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Visage non reconnu. Utilisez votre empreinte."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Visage non reconnu"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Visage non reconnu"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Utilisez empreinte digit."</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Déverrouillage par reconnaissance faciale indisponible"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connecté"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Balayer vers la gauche pour démarrer le tutoriel collectif"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Ouvrir l\'éditeur de widgets"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Personnaliser"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Fermer"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Dans cet espace, ajoutez, supprimez et réorganisez vos widgets"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Ajouter des widgets"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Appuyez de manière prolongée pour personnaliser les widgets"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Supprimer"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ajouter un widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"OK"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Activer le Bluetooth ?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Pour connecter un clavier à votre tablette, vous devez avoir activé le Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Activer"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Commandes de gestion des notifications"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Active - En fonction du visage"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Grâce aux commandes de gestion des notifications, vous pouvez définir le niveau d\'importance (compris entre 0 et 5) des notifications d\'une application. \n\n"<b>"Niveau 5"</b>" \n- Afficher en haut de la liste des notifications \n- Autoriser l\'interruption en plein écran \n- Toujours en aperçu \n\n"<b>"Niveau 4"</b>" \n- Empêcher l\'interruption en plein écran \n- Toujours en aperçu \n\n"<b>"Niveau 3"</b>" \n- Empêcher l\'interruption en plein écran \n- Jamais en aperçu \n\n"<b>"Niveau 2"</b>" \n- Empêcher l\'interruption en plein écran \n- Jamais en aperçu \n- Ne jamais émettre de signal sonore ni déclencher le vibreur \n\n"<b>"Niveau 1"</b>" \n- Empêcher l\'interruption en plein écran \n- Jamais en aperçu \n- Ne jamais émettre de signal sonore ni déclencher le vibreur \n- Masquer les notifications dans l\'écran de verrouillage et la barre d\'état \n- Afficher au bas de la liste des notifications \n\n"<b>"Niveau 0"</b>" \n- Bloquer toutes les notifications de l\'application"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"OK"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Appliquer"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Désactiver les notifications"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Configurer"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Espace de stockage"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Astuces"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Accessibilité"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Applis instantanées"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> en cours d\'exécution"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Vous pouvez ouvrir cette application sans l\'installer."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Appuyez pour ouvrir fonctionnalités d\'accessibilité. Personnalisez ou remplacez bouton dans paramètres.\n\n"<annotation id="link">"Voir paramètres"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Déplacer le bouton vers le bord pour le masquer temporairement"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Annuler"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Bouton Accessibilité masqué"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Appuyez pour afficher le bouton Accessibilité"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Raccourci vers <xliff:g id="FEATURE_NAME">%s</xliff:g> supprimé"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# raccourci supprimé}one{# raccourci supprimé}many{# raccourcis supprimés}other{# raccourcis supprimés}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Déplacer en haut à gauche"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"La présence de l\'utilisateur est détectée"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Définir une appli de notes par défaut dans les paramètres"</string>
     <string name="install_app" msgid="5066668100199613936">"Installer l\'appli"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Dupliquer sur l\'écran externe ?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Votre écran intérieur sera dupliqué. Votre écran frontal sera éteint."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Dupliquer l\'écran"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index ef0751b..6b23868 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Non se puido configurar o desbloqueo facial. Para tentalo de novo, vai a Configuración."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Toca o sensor de impresión dixital"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Preme a icona de desbloquear para continuar"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Non se recoñeceu a cara. Usa a impresión dixital."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Non se recoñeceu a cara. Usa a impresión dixital."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Non se recoñeceu a cara"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Non se recoñeceu a cara"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usa a impresión dixital"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"O desbloqueo facial non está dispoñible"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando • A carga completarase en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Pasa o dedo cara á esquerda para iniciar o titorial comunitario"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir o editor de widgets"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Personalizar"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Pechar"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Engadir, quitar e reordenar widgets neste espazo"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Engadir máis widgets"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pulsación longa para personalizar os widgets"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Quitar"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Engadir widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Feito"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Queres activar o Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Para conectar o teu teclado coa tableta, primeiro tes que activar o Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Activar"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Controis de notificacións mellorados"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Activada: baseada na cara"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Cos controis de notificacións mellorados, podes asignarlles un nivel de importancia comprendido entre 0 e 5 ás notificacións dunha aplicación determinada. \n\n"<b>"Nivel 5"</b>" \n- Mostrar na parte superior da lista de notificacións. \n- Permitir interrupcións no modo de pantalla completa. \n- Mostrar sempre. \n\n"<b>"Nivel 4"</b>" \n- Impedir interrupcións no modo de pantalla completa. \n- Mostrar sempre. \n\n"<b>"Nivel 3"</b>" \n- Impedir interrupcións no modo de pantalla completa. \n- Non mostrar nunca. \n\n"<b>"Nivel 2"</b>" \n- Impedir interrupcións no modo de pantalla completa. \n- Non mostrar nunca. \n- Non soar nin vibrar nunca. \n\n"<b>"Nivel 1"</b>" \n- Impedir interrupcións no modo de pantalla completa. \n- Non mostrar nunca. \n- Non soar nin vibrar nunca. \n- Ocultar na pantalla de bloqueo e na barra de estado. \n- Mostrar na parte inferior da lista de notificacións. \n\n"<b>"Nivel 0"</b>" \n- Bloquear todas as notificacións da aplicación."</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Feito"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Aplicar"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Desactivar notificacións"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Configurar"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Almacenamento"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Consellos"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Accesibilidade"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Aplicacións Instantáneas"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"Estase executando <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Abriuse a aplicación sen ter que instalala."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toca para abrir as funcións de accesibilidade. Cambia este botón en Configuración.\n\n"<annotation id="link">"Ver configuración"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Para ocultar temporalmente o botón, móveo ata o bordo"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Desfacer"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"O botón Accesibilidade está oculto"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Toca para mostrar o botón Accesibilidade"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Quitouse o atallo de <xliff:g id="FEATURE_NAME">%s</xliff:g>"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Quitouse # atallo}other{Quitáronse # atallos}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover á parte super. esquerda"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Detectouse a presenza de usuarios"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Establece a aplicación de notas predeterminada en Configuración"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar aplicación"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Queres proxectar contido nunha pantalla externa?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Proxectarase a pantalla interior. Desactivarase a pantalla frontal."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Proxectar pantalla"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index d92231c..6ea1f0e 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"ફેસ અનલૉક સુવિધાનું સેટઅપ કરી શક્યા નથી. ફરી પ્રયાસ કરવા માટે સેટિંગ પર જાઓ."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"ફિંગરપ્રિન્ટના સેન્સરને સ્પર્શ કરો"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"ચાલુ રાખવા \'અનલૉક કરો\' આઇકન દબાવો"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"ચહેરો ઓળખી શકતા નથી. તેને બદલે ફિંગરપ્રિન્ટ વાપરો."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"ચહેરો ઓળખાયો નથી. તેને બદલે ફિંગરપ્રિન્ટ વાપરો."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"ચહેરો ઓળખાતો નથી"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"ચહેરો ઓળખાયો નથી"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"તો ફિંગરપ્રિન્ટ વાપરો"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"ફેસ અનલૉક સુવિધા ઉપલબ્ધ નથી"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"બ્લૂટૂથ કનેક્ટ થયું."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ચાર્જ થઈ રહ્યું છે • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>માં પૂરું ચાર્જ થઈ જશે"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"કૉમ્યુનલ ટ્યૂટૉરિઅલ શરૂ કરવા માટે ડાબે સ્વાઇપ કરો"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"વિજેટ એડિટર ખોલો"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"કસ્ટમાઇઝ કરો"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"છોડી દો"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"આ સ્પેસમાં તમારા વિજેટ ઉમેરો, તેને કાઢી નાખો અને ફરી તેને ક્રમમાં ગોઠવો"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"વધુ વિજેટ ઉમેરો"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"વિજેટ કસ્ટમાઇઝ કરવા માટે થોડીવાર દબાવી રાખો"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"કાઢી નાખો"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"વિજેટ ઉમેરો"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"થઈ ગયું"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"બ્લૂટૂથ ચાલુ કરવુ છે?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"તમારા ટેબ્લેટ સાથે કીબોર્ડ કનેક્ટ કરવા માટે, તમારે પહેલાં બ્લૂટૂથ ચાલુ કરવાની જરૂર પડશે."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"ચાલુ કરો"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"પાવર સૂચના નિયંત્રણો"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ચાલુ છે - ચહેરા આધારિત રોટેશન"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"પાવર સૂચના નિયંત્રણો સાથે, તમે ઍપની સૂચનાઓ માટે 0 થી 5 સુધીના મહત્વના સ્તરને સેટ કરી શકો છો. \n\n"<b>"સ્તર 5"</b>" \n- સૂચના સૂચિની ટોચ પર બતાવો \n- પૂર્ણ સ્ક્રીન અવરોધની મંજૂરી આપો \n- હંમેશાં ત્વરિત દૃષ્ટિ કરો \n\n"<b>"સ્તર 4"</b>" \n- પૂર્ણ સ્ક્રીન અવરોધ અટકાવો \n- હંમેશાં ત્વરિત દૃષ્ટિ કરો \n\n"<b>"સ્તર 3"</b>" \n- પૂર્ણ સ્ક્રીન અવરોધ અટકાવો \n- ક્યારેય ત્વરિત દૃષ્ટિ કરશો નહીં \n\n"<b>"સ્તર 2"</b>" \n- પૂર્ણ સ્ક્રીન અવરોધ અટકાવો \n- ક્યારેય ત્વરિત દૃષ્ટિ કરશો નહીં \n- ક્યારેય અવાજ અથવા વાઇબ્રેટ કરશો નહીં \n\n"<b>"સ્તર 1"</b>" \n- પૂર્ણ સ્ક્રીન અવરોધની મંજૂરી આપો \n- ક્યારેય ત્વરિત દૃષ્ટિ કરશો નહીં \n- ક્યારેય અવાજ અથવા વાઇબ્રેટ કરશો નહીં \n- લૉક સ્ક્રીન અને સ્ટેટસ બારથી છુપાવો \n- સૂચના સૂચિના તળિયા પર બતાવો \n\n"<b>"સ્તર 0"</b>" \n- ઍપની તમામ સૂચનાઓને બ્લૉક કરો"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"થઈ ગયું"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"લાગુ કરો"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"નોટિફિકેશન બંધ કરો"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"સેટઅપ કરો"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"સ્ટોરેજ"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"હિન્ટ"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"ઍક્સેસિબિલિટી"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> ચાલી રહી છે"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"ઍપ ઇન્સ્ટૉલ કર્યા વિના ખુલી જાય છે."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ઍક્સેસિબિલિટી સુવિધાઓ ખોલવા માટે ટૅપ કરો. સેટિંગમાં આ બટનને કસ્ટમાઇઝ કરો અથવા બદલો.\n\n"<annotation id="link">"સેટિંગ જુઓ"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"તેને હંગામી રૂપે ખસેડવા માટે બટનને કિનારી પર ખસેડો"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"છેલ્લો ફેરફાર રદ કરો"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"ઍક્સેસિબિલિટી બટન છુપાવેલું છે"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"ઍક્સેસિબિલિટી બટન બતાવવા માટે ટૅપ કરો"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> શૉર્ટકટ કાઢી નાખ્યો"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# શૉર્ટકટ કાઢી નાખ્યો}one{# શૉર્ટકટ કાઢી નાખ્યો}other{# શૉર્ટકટ કાઢી નાખ્યા}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ઉપર ડાબે ખસેડો"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"વપરાશકર્તાની હાજરીની ભાળ મળી છે"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"સેટિંગમાં નોંધની ડિફૉલ્ટ ઍપ સેટ કરો"</string>
     <string name="install_app" msgid="5066668100199613936">"ઍપ ઇન્સ્ટૉલ કરો"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"શું બાહ્ય ડિસ્પ્લે પર મિરર કરીએ?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"તમારું ઇનર ડિસ્પ્લે મિરર કરવામાં આવશે. તમારું ફ્રન્ટ ડિસ્પ્લે બંધ કરવામાં આવશે."</string>
     <string name="mirror_display" msgid="2515262008898122928">"મિરર ડિસ્પ્લે"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index f8c78a9..db3549a 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"फ़ेस अनलॉक की सुविधा सेट अप नहीं की जा सकी. सेटिंग पर जाकर दोबारा कोशिश करें."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"फ़िंगरप्रिंट सेंसर को छुएं"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"जारी रखने के लिए अनलॉक आइकॉन पर टैप करें"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"चेहरे की पहचान नहीं हुई. फ़िंगरप्रिंट इस्तेमाल करें."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"चेहरा नहीं पहचाना गया. फ़िंगरप्रिंट इस्तेमाल करें."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"चेहरे की पहचान नहीं हुई"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"चेहरा नहीं पहचाना गया"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"फ़िंगरप्रिंट इस्तेमाल करें"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"फ़ेस अनलॉक की सुविधा उपलब्ध नहीं है"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लूटूथ कनेक्ट किया गया."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • चार्ज हो रहा है • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> में पूरा चार्ज हो जाएगा"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"कम्यूनिटी ट्यूटोरियल शुरू करने के लिए, बाईं ओर स्वाइप करें"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"विजेट एडिटर खोलें"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"पसंद के मुताबिक बनाएं"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"खारिज करें"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"इस स्पेस में विजेट जोड़ें, हटाएं, और उन्हें फिर से क्रम में लगाएं"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ज़्यादा विजेट जोड़ें"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"विजेट पसंद के मुताबिक बनाने के लिए उसे दबाकर रखें"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"हटाएं"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"विजेट जोड़ें"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"हो गया"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"ब्लूटूथ चालू करें?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"अपने कीबोर्ड को अपने टैबलेट से कनेक्ट करने के लिए, आपको पहले ब्लूटूथ चालू करना होगा."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"चालू करें"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"पावर सूचना नियंत्रण"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"चालू है - चेहरे की गतिविधि के हिसाब से कैमरे को घुमाने की सुविधा"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"पावर सूचना नियंत्रण के ज़रिये, आप किसी ऐप की सूचना को उसकी अहमियत के हिसाब से 0 से 5 के लेवल पर सेट कर सकते हैं.\n\n"<b>"लेवल 5"</b>" \n- सूचना सूची में सबसे ऊपर दिखाएं \n- पूरे स्क्रीन को ढंकने की अनुमति दें \n- लगातार देखते रहें \n\n"<b>" लेवल 4"</b>" \n- पूरे स्क्रीन को ढंकें \n- लगातार देखते रहें \n\n"<b>"लेवल 3"</b>" \n- पूरे स्क्रीन को ढंकने से रोकें \n-कभी भी न देखें \n\n"<b>"लेवल 2"</b>" \n- पूरे स्क्रीन को ढंकने से रोकें \n- कभी भी देखें \n- कभी भी आवाज़ या कंपन (वाइब्रेशन) न करें \n\n"<b>"लेवल 1"</b>" \n- पूरे स्क्रीन को ढंकने से रोकें \n- कभी भी न देखें \n- कभी भी आवाज़ या कंपन (वाइब्रेशन) न करें \n- लॉक स्क्रीन और स्टेटस बार से छिपाएं \n- सूचना सूची के नीचे दिखाएं \n\n"<b>"लेवल 0"</b>" \n- ऐप्लिकेशन की सभी सूचनाएं रोक दें"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"हो गया"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"लागू करें"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"सूचनाएं बंद करें"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"सेट अप"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"स्टोरेज"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"संकेत"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"सुलभता"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> चल रहा है"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"ऐप्लिकेशन इंस्टॉल किए बिना ही खुल गया है."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"सुलभता सुविधाएं खोलने के लिए टैप करें. सेटिंग में, इस बटन को बदलें या अपने हिसाब से सेट करें.\n\n"<annotation id="link">"सेटिंग देखें"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"बटन को कुछ समय छिपाने के लिए, उसे किनारे पर ले जाएं"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"पहले जैसा करें"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"सुलभता बटन छिपा है"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"सुलभता बटन दिखाने के लिए टैप करें"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> का शॉर्टकट हटाया गया"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# शॉर्टकट हटाया गया}one{# शॉर्टकट हटाया गया}other{# शॉर्टकट हटाए गए}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"सबसे ऊपर बाईं ओर ले जाएं"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"उपयोगकर्ता की मौजूदगी का पता लगाया गया"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"सेटिंग में जाकर, नोट लेने की सुविधा देने वाले ऐप्लिकेशन को डिफ़ॉल्ट के तौर पर सेट करें"</string>
     <string name="install_app" msgid="5066668100199613936">"ऐप्लिकेशन इंस्टॉल करें"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"क्या आपको किसी बाहरी डिवाइस पर डिसप्ले करना है?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"आपके फ़ोन के इनर डिसप्ले की स्क्रीन शेयर की जाएगी. फ़्रंट डिसप्ले को बंद कर दिया जाएगा."</string>
     <string name="mirror_display" msgid="2515262008898122928">"डिसप्ले करें"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 0803aeb..27eb8e9 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Postavljanje otključavanja licem nije uspjelo. Pokušajte ponovo u postavkama."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Dodirnite senzor otiska prsta"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Pritisnite ikonu otključavanja da biste nastavili"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Prepoznavanje lica nije uspjelo. Upotrijebite otisak prsta."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Lice nije prepoznato. Upotrijebite otisak prsta."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Lice nije prepoznato"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Lice nije prepoznato"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Upotrijebite otisak prsta"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Otključavanje licem nije dostupno"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth povezan."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • punjenje • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do napunjenosti"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Prijeđite prstom ulijevo da biste pokrenuli zajednički vodič"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otvaranje alata za uređivanje widgeta"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Prilagodi"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Odbaci"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Dodavanje, uklanjanje i promjena redoslijeda widgeta u ovom prostoru"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Dodavanje još widgeta"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Dugo pritisnite za prilagodbu widgeta"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Ukloni"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodaj widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Gotovo"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Želite li uključiti Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Da biste povezali tipkovnicu s tabletom, morate uključiti Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Uključi"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Napredne kontrole obavijesti"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Uključeno – na temelju lica"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Napredne kontrole obavijesti omogućuju vam da postavite razinu važnosti za obavijesti aplikacije od 0 do 5. \n\n"<b>"Razina 5"</b>" \n– prikaži na vrhu popisa obavijesti \n– dopusti prekide prikaza na cijelom zaslonu \n– uvijek dopusti brzi pregled \n\n"<b>"Razina 4"</b>" \n– onemogući prekid prikaza na cijelom zaslonu \n– uvijek dopusti brzi pregled \n\n"<b>"Razina 3"</b>" \n– onemogući prekid prikaza na cijelom zaslonu \n– nikad ne dopusti brzi pregled\n\n"<b>"Razina 2"</b>" \n– onemogući prekid prikaza na cijelom zaslonu \n– nikad ne dopusti brzi pregled \n– nikad ne emitiraj zvuk ni vibraciju \n\n"<b>"Razina 1"</b>" \n– onemogući prekid prikaza na cijelom zaslonu \n– nikad ne dopusti brzi pregled \n– nikad ne emitiraj zvuk ni vibraciju \n– ne prikazuj na zaključanom zaslonu i traci statusa \n– prikaži na dnu popisa obavijesti \n\n"<b>"Razina 0"</b>" \n– blokiraj sve obavijesti aplikacije"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Gotovo"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Primijeni"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Isključi obavijesti"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Postavljanje"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Pohrana"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Savjeti"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Pristupačnost"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant aplikacije"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"Izvodi se aplikacija <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Aplikacija je otvorena bez instaliranja."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Dodirnite za otvaranje značajki pristupačnosti. Prilagodite ili zamijenite taj gumb u postavkama.\n\n"<annotation id="link">"Pregledajte postavke"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Pomaknite gumb do ruba da biste ga privremeno sakrili"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Poništi"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Gumb za pristupačnost je skriven"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Dodirnite za prikaz gumba za pristupačnost"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Uklonjen je prečac za uslugu <xliff:g id="FEATURE_NAME">%s</xliff:g>"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Uklonjen je # prečac}one{Uklonjen je # prečac}few{Uklonjena su # prečaca}other{Uklonjeno je # prečaca}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Premjesti u gornji lijevi kut"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Otkrivena je prisutnost korisnika"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Postavite zadanu aplikaciju za bilješke u postavkama"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalacija"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Želite li zrcaliti na vanjski zaslon?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Unutarnji zaslon bit će zrcaljen. Prednji zaslon bit će isključen."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Zrcaljenje zaslona"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index fd9d832..7a16d74 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Nem sikerült beállítani az arcalapú feloldást. Próbálkozzon újra a Beállításokban."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Érintse meg az ujjlenyomat-érzékelőt"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"A folytatáshoz koppintson a Feloldás ikonra"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Az arc nem felismerhető. Használjon ujjlenyomatot."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Sikertelen arcfelismerés. Használjon ujjlenyomatot."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Az arc nem ismerhető fel"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Sikertelen arcfelismerés"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Használjon ujjlenyomatot"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Nem áll rendelkezésre az Arcalapú feloldás"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth csatlakoztatva."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Töltés • A teljes töltöttségig: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Csúsztasson gyorsan balra a közösségi útmutató elindításához"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"A modulszerkesztő megnyitása"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Személyre szabás"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Elvetés"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Modulok hozzáadása, eltávolítása és átrendezése ezen a területen"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"További modulok hozzáadása"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Nyomja meg hosszan a modulok személyre szabásához"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Eltávolítás"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Modul hozzáadása"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Kész"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Engedélyezi a Bluetooth-kapcsolatot?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Ha a billentyűzetet csatlakoztatni szeretné táblagépéhez, először engedélyeznie kell a Bluetooth-kapcsolatot."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Bekapcsolás"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Teljes körű értesítésvezérlők"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Be: Arcalapú"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Az értesítési beállítások révén 0-tól 5-ig állíthatja be a fontossági szintet az alkalmazás értesítéseinél. \n\n"<b>"5. szint"</b>" \n– Megjelenítés az értesítési lista tetején \n– Teljes képernyő megszakításának engedélyezése \n– Mindig felugrik \n\n"<b>"4. szint"</b>" \n– Teljes képernyő megszakításának megakadályozása \n– Mindig felugrik \n\n"<b>"3. szint"</b>" \n– Teljes képernyő megszakításának megakadályozása \n– Soha nem ugrik fel \n\n"<b>"2. szint"</b>" \n– Teljes képernyő megszakításának megakadályozása \n– Soha nem ugrik fel \n– Soha nincs hangjelzés és rezgés \n\n"<b>"1. szint"</b>" \n– Teljes képernyő megszakításának megakadályozása \n– Soha nem ugrik fel \n– Soha nincs hangjelzés vagy rezgés \n– Elrejtés a lezárási képernyőről és az állapotsávról \n– Megjelenítés az értesítési lista alján \n\n"<b>"0. szint"</b>" \n– Az alkalmazás összes értesítésének letiltása"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Kész"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Alkalmaz"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Az értesítések kikapcsolása"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Beállítás"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Tárhely"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Tippek"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Kisegítő lehetőségek"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Azonnali alkalmazások"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"A(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazás jelenleg fut"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Az alkalmazás telepítés nélkül lett megnyitva."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Koppintson a kisegítő lehetőségek megnyitásához. A gombot a Beállításokban módosíthatja.\n\n"<annotation id="link">"Beállítások"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"A gombot a szélre áthelyezve ideiglenesen elrejtheti"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Visszavonás"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Kisegítő lehetőségek gomb elrejtve"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Koppintson a Kisegítő lehetőségek gomb megjelenítéséhez"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> gyorsparancs eltávolítva"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# gyorsparancs eltávolítva}other{# gyorsparancs eltávolítva}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Áthelyezés fel és balra"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Felhasználói jelenlét észlelve"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Állítson be alapértelmezett jegyzetkészítő alkalmazást a Beállításokban"</string>
     <string name="install_app" msgid="5066668100199613936">"Alkalmazás telepítése"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Tükrözi a kijelzőt a külső képernyőre?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"A belső kijelző tükrözve lesz. Az elülső kijelző ki lesz kapcsolva."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Kijelző tükrözése"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index a9252e2..f3e883f 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Չհաջողվեց կարգավորել դեմքով ապակողպումը։ Անցեք Կարգավորումներ և նորից փորձեք։"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Հպեք մատնահետքի սկաներին"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Շարունակելու համար սեղմեք ապակողպման պատկերակը"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Դեմքը չի հաջողվում ճանաչել։ Օգտագործեք մատնահետքը։"</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Դեմքը չի ճանաչվել։ Օգտագործեք մատնահետքը։"</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Դեմքը չի ճանաչվել"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Դեմքը չի ճանաչվել"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Օգտագործեք մատնահետք"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Դեմքով ապակողպումն անհասանելի է"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-ը միացված է:"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Լիցքավորում • Մնացել է <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Թերթեք ձախ՝ ուղեցույցը գործարկելու համար"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Բացել վիջեթների խմբագրիչը"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Անհատականացնել"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Փակել"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Ավելացնել վիջեթներ, ինչպես նաև հեռացնել և վերադասավորել դրանք այս տարածքում"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Ավելացնել վիջեթներ"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Երկար սեղմեք՝ վիջեթները հարմարեցնելու համար"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Հեռացնել"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ավելացնել վիջեթ"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Պատրաստ է"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Միացնե՞լ Bluetooth-ը:"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Ստեղնաշարը ձեր պլանշետին միացնելու համար նախ անհրաժեշտ է միացնել Bluetooth-ը:"</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Միացնել"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Ծանուցումների ընդլայնված կառավարում"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Միաց․ – Դիմաճանաչման հիման վրա"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Ծանուցումների ընդլայնված կառավարման օգնությամբ կարող եք յուրաքանչյուր հավելվածի ծանուցումների համար նշանակել կարևորության աստիճան՝ 0-5 սահմաններում: \n\n"<b>"5-րդ աստիճան"</b>" \n- Ցուցադրել ծանուցումների ցանկի վերևում \n- Թույլատրել լիաէկրան ընդհատումները \n- Միշտ ցուցադրել կարճ ծանուցումները \n\n"<b>"4-րդ աստիճան"</b>" \n- Արգելել լիաէկրան ընդհատումները \n- Միշտ ցուցադրել կարճ ծանուցումները \n\n"<b>"3-րդ աստիճան"</b>" \n- Արգելել լիաէկրան ընդհատումները \n- Արգելել կարճ ծանուցումների ցուցադրումը \n\n"<b>"2-րդ աստիճան"</b>" \n- Արգելել լիաէկրան ընդհատումները \n- Արգելել կարճ ծանուցումների ցուցադրումը \n- Անջատել ձայնը և թրթռումը \n\n"<b>"1-ին աստիճան"</b>" \n- Արգելել լիաէկրան ընդհատումները \n- Արգելել կարճ ծանուցումների ցուցադրումը \n- Անջատել ձայնը և թրթռումը \n- Չցուցադրել կողպէկրանում և կարգավիճակի գոտում \n- Ցուցադրել ծանուցումների ցանկի ներքևում \n\n"<b>"0-րդ աստիճան"</b>\n"- Արգելափակել հավելվածի բոլոր ծանուցումները"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Փակել"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Կիրառել"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Անջատել ծանուցումները"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Կարգավորում"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Տարածք"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Հուշումներ"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Հատուկ գործառույթներ"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Ակնթարթային հավելվածներ"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> հավելվածն աշխատում է"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Հավելվածը բացվել է առանց տեղադրման։"</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Հատուկ գործառույթները բացելու համար հպեք։ Անհատականացրեք այս կոճակը կարգավորումներում։\n\n"<annotation id="link">"Կարգավորումներ"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Կոճակը ժամանակավորապես թաքցնելու համար այն տեղափոխեք էկրանի եզր"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Հետարկել"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"«Հատուկ գործառույթներ» կոճակը թաքցված է"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Հպեք՝ «Հատուկ գործառույթներ» կոճակը ցուցադրելու համար"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"«<xliff:g id="FEATURE_NAME">%s</xliff:g>» դյուրանցումը հեռացվեց"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# դյուրանցում հեռացվեց}one{# դյուրանցում հեռացվեց}other{# դյուրանցում հեռացվեց}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Տեղափոխել վերև՝ ձախ"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Հայտնաբերվել է օգտատեր"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Կարգավորեք նշումների կանխադրված հավելված Կարգավորումներում"</string>
     <string name="install_app" msgid="5066668100199613936">"Տեղադրել հավելվածը"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Հայելապատճենե՞լ արտաքին էկրանին"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ներքին էկրանը կհայելապատճենվի։ Առջևի էկրանը կանջատվի։"</string>
     <string name="mirror_display" msgid="2515262008898122928">"Հայելապատճենել էկրանը"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 8ef809a..fdd111f 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Tidak dapat menyiapkan buka dengan wajah. Buka Setelan untuk mencoba lagi."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Sentuh sensor sidik jari"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Tekan ikon buka kunci untuk melanjutkan"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Tidak dapat mengenali wajah. Gunakan sidik jari."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Wajah tidak dikenali. Gunakan sidik jari."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Tidak mengenali wajah"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Wajah tidak dikenali"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Gunakan sidik jari"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Buka dengan Wajah tidak tersedia"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth terhubung."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengisi daya • Penuh dalam waktu <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Geser ke kiri untuk memulai tutorial komunal"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Buka editor widget"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Sesuaikan"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Tutup"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Tambahkan, hapus, dan susun ulang widget Anda di ruang ini"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Tambahkan widget lainnya"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Tekan lama untuk menyesuaikan widget"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Hapus"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Tambahkan widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Selesai"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Aktifkan Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Untuk menghubungkan keyboard dengan tablet, terlebih dahulu aktifkan Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Aktifkan"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Kontrol notifikasi daya"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Aktif - Berbasis deteksi wajah"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Dengan kontrol notifikasi daya, Anda dapt menyetel level kepentingan notifikasi aplikasi dari 0 sampai 5. \n\n"<b>"Level 5"</b>" \n- Muncul di atas daftar notifikasi \n- Izinkan interupsi layar penuh \n- Selalu intip pesan \n\n"<b>"Level 4"</b>" \n- Jangan interupsi layar penuh \n- Selalu intip pesan \n\n"<b>"Level 3"</b>" \n- Jangan interupsi layar penuh \n- Tak pernah intip pesan \n\n"<b>"Level 2"</b>" \n- Jangan interupsi layar penuh \n- Tak pernah intip pesan \n- Tanpa suara dan getaran \n\n"<b>"Level 1"</b>" \n- Jangan interupsi layar penuh \n- Tak pernah intip pesan \n- Tanpa suara atau getaran \n- Sembunyikan dari layar kunci dan bilah status \n- Muncul di bawah daftar notifikasi \n\n"<b>"Level 0"</b>" \n- Blokir semua notifikasi dari aplikasi"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Selesai"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Terapkan"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Nonaktifkan notifikasi"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Penyiapan"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Penyimpanan"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Petunjuk"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Aksesibilitas"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Aplikasi Instan"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> sedang berjalan"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Aplikasi dapat dibuka tanpa perlu diinstal."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Ketuk untuk membuka fitur aksesibilitas. Sesuaikan atau ganti tombol ini di Setelan.\n\n"<annotation id="link">"Lihat setelan"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Pindahkan tombol ke tepi agar tersembunyi untuk sementara"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Urungkan"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Tombol aksesibilitas tersembunyi"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Ketuk untuk menampilkan tombol aksesibilitas"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Pintasan <xliff:g id="FEATURE_NAME">%s</xliff:g> dihapus"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# pintasan dihapus}other{# pintasan dihapus}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Pindahkan ke kiri atas"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Kehadiran pengguna terdeteksi"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setel aplikasi catatan default di Setelan"</string>
     <string name="install_app" msgid="5066668100199613936">"Instal aplikasi"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Cerminkan ke layar eksternal?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Layar dalam akan dicerminkan. Layar depan akan dinonaktifkan."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Cerminkan layar"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index cef3285..cdbc055c 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Ekki var hægt að setja upp andlitskenni. Farðu í stillingar og reyndu aftur."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Snertu fingrafaralesarann"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Ýttu á táknið taka úr lás til að halda áfram"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Andlit þekkist ekki. Notaðu fingrafar í staðinn."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Ekki tókst að bera kennsl á andlit. Notaðu fingrafar í staðinn."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Andlit þekkist ekki"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Ekki tókst að bera kennsl á andlit"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Nota fingrafar í staðinn"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Andlitskenni ekki í boði"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth tengt."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Í hleðslu • Full hleðsla eftir <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Strjúktu til vinstri til að hefja samfélagsleiðsögnina"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Opna græjuritilinn"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Sérsníða"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Hunsa"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Bættu við, fjarlægðu og endurraðaðu græjunum þínum í þessu rými"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Bæta við fleiri græjum"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Haltu inni til að sérsníða græjur"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Fjarlægja"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Bæta græju við"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Lokið"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Kveikja á Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Til að geta tengt lyklaborðið við spjaldtölvuna þarftu fyrst að kveikja á Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Kveikja"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Orkustillingar tilkynninga"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Kveikt – út frá andliti"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Með orkutilkynningastýringum geturðu stillt mikilvægi frá 0 upp í 5 fyrir tilkynningar forrita. \n\n"<b>"Stig 5"</b>" \n- Sýna efst á tilkynningalista \n- Leyfa truflun þegar birt er á öllum skjánum \n- Kíkja alltaf \n\n"<b>"Stig 4"</b>" \n- Hindra truflun við birtingu á öllum skjánum \n- Kíkja alltaf \n\n"<b>"Stig 3"</b>" \n- Hindra truflun við birtingu á öllum skjánum \n- Kíkja aldrei \n\n"<b>"Stig 2"</b>" \n- Hindra truflun við birtingu á öllum skjánum \n- Kíkja aldrei \n- Slökkva á hljóði og titringi \n\n"<b>"Stig 1"</b>" \n- Hindra truflun við birtingu á öllum skjánum \n- Kíkja aldrei \n- Slökkva á hljóði og titringi \n- Fela á lásskjá og stöðustiku \n- Sýna neðst á tilkynningalista \n\n"<b>"Stig 0"</b>" \n- Setja allar tilkynningar frá forriti á bannlista"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Lokið"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Nota"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Slökkva á tilkynningum"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Uppsetning"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Geymslurými"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Vísbendingar"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Aðgengileiki"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Skyndiforrit"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> er í gangi"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Forrit opnað án þess að vera uppsett."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Ýttu til að opna aðgengiseiginleika. Sérsníddu eða skiptu hnappinum út í stillingum.\n\n"<annotation id="link">"Skoða stillingar"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Færðu hnappinn að brúninni til að fela hann tímabundið"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Afturkalla"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Aðgengishnappur falinn"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Ýttu til að sjá aðgengishnappinn"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Flýtileiðin <xliff:g id="FEATURE_NAME">%s</xliff:g> var fjarlægð"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# flýtileið var fjarlægð}one{# flýtileið var fjarlægð}other{# flýtileiðir voru fjarlægðar}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Færa efst til vinstri"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Viðvera notanda greindist"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Stilltu sjálfgefið glósuforrit í stillingunum"</string>
     <string name="install_app" msgid="5066668100199613936">"Setja upp forrit"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Spegla yfir á ytri skjá?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Innri skjárinn þinn verður speglaður. Slökkt verður á framskjánum þínum."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Spegla skjá"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index c5079fe..ec74e40 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Impossibile configurare lo Sblocco con il Volto. Vai alle Impostazioni e riprova."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Tocca il sensore di impronte"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Premi l\'icona Sblocca per continuare"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Impossibile riconoscere il volto. Usa l\'impronta."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Volto non riconosciuto. Usa l\'impronta."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Volto non riconosciuto"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Volto non riconosciuto"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usa l\'impronta"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Sblocco con il Volto non disponibile"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth collegato."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • In carica • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> alla ricarica completa"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Scorri a sinistra per iniziare il tutorial della community"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Apri l\'editor del widget"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Personalizza"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Chiudi"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Aggiungi, rimuovi e riordina i tuoi widget in questo spazio"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Aggiungi altri widget"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Premi a lungo per personalizzare i widget"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Rimuovi"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Aggiungi widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Fine"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Attivare il Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Per connettere la tastiera al tablet, devi prima attivare il Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Attiva"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Controlli di gestione delle notifiche"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"On - Rotazione basata sul viso"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"I controlli di gestione delle notifiche ti consentono di impostare un livello di importanza compreso tra 0 e 5 per le notifiche di un\'app. \n\n"<b>"Livello 5"</b>" \n- Mostra in cima all\'elenco di notifiche \n- Consenti l\'interruzione a schermo intero \n- Visualizza sempre \n\n"<b>"Livello 4"</b>" \n- Impedisci l\'interruzione a schermo intero \n- Visualizza sempre \n\n"<b>"Livello 3"</b>" \n- Impedisci l\'interruzione a schermo intero \n- Non visualizzare mai \n\n"<b>"Livello 2"</b>" \n- Impedisci l\'interruzione a schermo intero \n- Non visualizzare mai \n- Non emettere mai suoni e vibrazioni \n\n"<b>"Livello 1"</b>" \n- Impedisci l\'interruzione a schermo intero \n- Non visualizzare mai \n- Non emettere mai suoni e vibrazioni \n- Nascondi da schermata di blocco e barra di stato \n- Mostra in fondo all\'elenco di notifiche \n\n"<b>"Livello 0"</b>" \n- Blocca tutte le notifiche dell\'app"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Fine"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Applica"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Disattiva notifiche"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Configurazione"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Spazio di archiviazione"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Suggerimenti"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Accessibilità"</string>
     <string name="instant_apps" msgid="8337185853050247304">"App istantanee"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"App <xliff:g id="APP">%1$s</xliff:g> in esecuzione"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"App aperta senza essere stata installata."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tocca per aprire funzioni di accessibilità. Personalizza o sostituisci il pulsante in Impostazioni.\n\n"<annotation id="link">"Vedi impostazioni"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Sposta il pulsante fino al bordo per nasconderlo temporaneamente"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Elimina"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Pulsante Accessibilità nascosto"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Tocca per mostrare il pulsante Accessibilità"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Scorciatoia <xliff:g id="FEATURE_NAME">%s</xliff:g> rimossa"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# scorciatoia rimossa}many{# scorciatoie rimosse}other{# scorciatoie rimosse}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Sposta in alto a sinistra"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Viene rilevata la presenza dell\'utente"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Imposta l\'app per le note predefinita nelle Impostazioni"</string>
     <string name="install_app" msgid="5066668100199613936">"Installa app"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vuoi eseguire il mirroring al display esterno?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Verrà eseguito il mirroring del tuo display interno. Il tuo display frontale verrà spento."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Esegui il mirroring del display"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 486b22f..ee1d0bd 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"לא ניתן להגדיר פתיחה ע\"י זיהוי הפנים. צריך לעבור להגדרות כדי לנסות שוב."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"יש לגעת בחיישן טביעות האצבע"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"להמשך יש ללחוץ על סמל ביטול הנעילה"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"לא ניתן לזהות את הפנים. יש להשתמש בטביעת אצבע במקום."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"הפנים לא זוהו. צריך להשתמש בטביעת אצבע במקום."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"לא ניתן לזהות את הפנים"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"הפנים לא זוהו"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"שימוש בטביעת אצבע במקום זאת"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"אי אפשר לפתוח בזיהוי פנים"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"‏Bluetooth מחובר."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> עד לסיום"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"אפשר להחליק שמאלה כדי להפעיל את המדריך המשותף"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"פתיחה של הכלי לעריכת ווידג\'טים"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"התאמה אישית"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"סגירה"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"הוספה, הסרה, וסידור מחדש של הווידג\'טים במרחב הזה"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"הוספת ווידג\'טים"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"לוחצים לחיצה ארוכה כדי להתאים אישית את הווידג\'טים"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"הסרה"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"הוספת ווידג\'ט"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"סיום"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"‏האם להפעיל את ה-Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"‏כדי לחבר את המקלדת לטאבלט, תחילה עליך להפעיל את ה-Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"הפעלה"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"פקדים של הודעות הפעלה"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"פועל – מבוסס על זיהוי פנים"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"בעזרת פקדים של התראות הפעלה, אפשר להגדיר רמת חשיבות מ-0 עד 5 להתראות אפליקציה. \n\n"<b>"רמה 5"</b>" \n- הצגה בראש רשימת ההתראות \n- לאפשר הפרעה במסך מלא \n- תמיד לאפשר הצצה \n\n"<b>"רמה 4"</b>" \n- מניעת הפרעה במסך מלא \n- תמיד לאפשר הצצה \n\n"<b>"רמה 3"</b>" \n- מניעת הפרעה במסך מלא \n- אף פעם לא לאפשר הצצה \n\n"<b>"רמה 2"</b>" \n- מניעת הפרעה במסך מלא \n- אף פעם לא לאפשר הצצה \n- אף פעם לא לאפשר קול ורטט \n\n"<b>"רמה 1"</b>" \n- מניעת הפרעה במסך מלא \n- אף פעם לא לאפשר הצצה \n- אף פעם לא לאפשר קול ורטט \n- הסתרה ממסך הנעילה ומשורת הסטטוס \n- הצגה בתחתית רשימת ההתראות \n\n"<b>"רמה 0"</b>" \n- חסימת כל ההתראות מהאפליקציה"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"סיום"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"אישור"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"השבתת ההתראות"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"הגדרה"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"אחסון"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"טיפים"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"נגישות"</string>
     <string name="instant_apps" msgid="8337185853050247304">"אפליקציות ללא התקנה"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"האפליקציה <xliff:g id="APP">%1$s</xliff:g> פועלת"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"האפליקציה נפתחת בלי התקנה."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"מקישים כדי לפתוח את תכונות הנגישות. אפשר להחליף את הלחצן או להתאים אותו אישית בהגדרות.\n\n"<annotation id="link">"הצגת ההגדרות"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"כדי להסתיר זמנית את הלחצן, יש להזיז אותו לקצה"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"ביטול"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"לחצן הנגישות מוסתר"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"אפשר להקיש כדי לראות את לחצן הנגישות"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"קיצור הדרך אל <xliff:g id="FEATURE_NAME">%s</xliff:g> הוסר"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{קיצור הדרך הוסר}one{# קיצורי דרך הוסרו}two{# קיצורי דרך הוסרו}other{# קיצורי דרך הוסרו}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"העברה לפינה השמאלית העליונה"</string>
@@ -939,7 +949,7 @@
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"העברה מהשוליים והצגה"</string>
     <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"הסרה"</string>
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"החלפת מצב"</string>
-    <string name="quick_controls_title" msgid="6839108006171302273">"לחצני מכשירים"</string>
+    <string name="quick_controls_title" msgid="6839108006171302273">"ממשק השליטה במכשירים"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"יש לבחור אפליקציה כדי להוסיף פקדים"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{נוסף אמצעי בקרה אחד (#).}one{נוספו # אמצעי בקרה.}two{נוספו # אמצעי בקרה.}other{נוספו # אמצעי בקרה.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"הוסר"</string>
@@ -953,7 +963,7 @@
     <string name="accessibility_control_change_unfavorite" msgid="6997408061750740327">"להסיר מהמועדפים"</string>
     <string name="accessibility_control_move" msgid="8980344493796647792">"העברה למיקום <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="controls_favorite_default_title" msgid="967742178688938137">"פקדים"</string>
-    <string name="controls_favorite_subtitle" msgid="5818709315630850796">"יש לבחור פקדי מכשירים כדי לקבל גישה מהירה"</string>
+    <string name="controls_favorite_subtitle" msgid="5818709315630850796">"יש לבחור מכשירים כדי לגשת אליהם במהירות מממשק השליטה במכשירים"</string>
     <string name="controls_favorite_rearrange" msgid="5616952398043063519">"כדי לארגן מחדש את הפקדים, צריך ללחוץ לחיצה ארוכה ולגרור"</string>
     <string name="controls_favorite_removed" msgid="5276978408529217272">"כל הפקדים הוסרו"</string>
     <string name="controls_favorite_toast_no_changes" msgid="7094494210840877931">"השינויים לא נשמרו"</string>
@@ -964,7 +974,7 @@
     <string name="controls_favorite_load_error" msgid="5126216176144877419">"לא ניתן היה לטעון את הפקדים. יש לבדוק את האפליקציה <xliff:g id="APP">%s</xliff:g> כדי לוודא שהגדרות האפליקציה לא השתנו."</string>
     <string name="controls_favorite_load_none" msgid="7687593026725357775">"פקדים תואמים לא זמינים"</string>
     <string name="controls_favorite_other_zone_header" msgid="9089613266575525252">"אחר"</string>
-    <string name="controls_dialog_title" msgid="2343565267424406202">"הוספה לפקדי המכשירים"</string>
+    <string name="controls_dialog_title" msgid="2343565267424406202">"הוספה לממשק השליטה במכשירים"</string>
     <string name="controls_dialog_ok" msgid="2770230012857881822">"הוספה"</string>
     <string name="controls_dialog_remove" msgid="3775288002711561936">"הסרה"</string>
     <string name="controls_dialog_message" msgid="342066938390663844">"הוצע על-ידי <xliff:g id="APP">%s</xliff:g>"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"נוכחות המשתמש זוהתה"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"צריך להגדיר את אפליקציית ברירת המחדל לפתקים ב\'הגדרות\'"</string>
     <string name="install_app" msgid="5066668100199613936">"התקנת האפליקציה"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"לשקף למסך חיצוני?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"המסך הפנימי שלך ישוכפל. המסך החיצוני שלך יכובה."</string>
     <string name="mirror_display" msgid="2515262008898122928">"תצוגת מראה"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index c742f93..416a61d 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"顔認証を設定できませんでした。[設定] に移動してもう一度お試しください。"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"指紋認証センサーをタッチ"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"ロック解除アイコンを押して続行してください"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"顔を認識できません。指紋認証を使用してください。"</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"顔を認識できません。指紋認証を使用してください。"</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"顔を認識できません"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"顔を認識できません"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"指紋認証をお使いください"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"顔認証を利用できません"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetoothに接続済み。"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 充電中 • フル充電まで <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"左にスワイプすると、コミュニティ チュートリアルが開始します"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ウィジェット エディタを開く"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"カスタマイズ"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"閉じる"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"このスペースでのウィジェットの追加、削除、並べ替え"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ウィジェットの追加"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"長押ししてウィジェットをカスタマイズ"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"削除"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ウィジェットを追加"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"完了"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"BluetoothをONにしますか?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"タブレットでキーボードに接続するには、最初にBluetoothをONにする必要があります。"</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"ONにする"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"電源通知管理"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ON - 顔ベース"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"電源通知管理では、アプリの通知の重要度をレベル 0~5 で設定できます。\n\n"<b>"レベル 5"</b>" \n- 通知リストの一番上に表示する \n- 全画面表示を許可する \n- 常にポップアップする \n\n"<b>"レベル 4"</b>" \n- 全画面表示しない \n- 常にポップアップする \n\n"<b>"レベル 3"</b>" \n- 全画面表示しない \n- ポップアップしない \n\n"<b>"レベル 2"</b>" \n- 全画面表示しない \n- ポップアップしない \n- 音やバイブレーションを使用しない \n\n"<b>"レベル 1"</b>" \n- 全画面表示しない \n- ポップアップしない \n- 音やバイブレーションを使用しない \n- ロック画面やステータスバーに表示しない \n- 通知リストの一番下に表示する \n\n"<b>"レベル 0"</b>" \n- アプリからのすべての通知をブロックする"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"完了"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"適用"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"通知を OFF にする"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"セットアップ"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"ストレージ"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"ヒント"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"ユーザー補助"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> を実行中"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"アプリをインストールせずに開きました。"</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"タップしてユーザー補助機能を開きます。ボタンのカスタマイズや入れ替えを [設定] で行えます。\n\n"<annotation id="link">"設定を表示"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ボタンを一時的に非表示にするには、端に移動させてください"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"元に戻す"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"ユーザー補助機能ボタンは非表示です"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"ユーザー補助機能ボタンを表示するにはタップしてください"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> のショートカットを削除しました"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# 個のショートカットを削除}other{# 個のショートカットを削除}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"左上に移動"</string>
@@ -1207,8 +1217,10 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"会話を始められます"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"[設定] でデフォルトのメモアプリを設定してください"</string>
     <string name="install_app" msgid="5066668100199613936">"アプリをインストール"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"外部ディスプレイにミラーリングしますか?"</string>
-    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"インナー ディスプレイがミラーリングされます。フロント ディスプレイは OFF になります。"</string>
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"インナー ディスプレイがミラーリングされます。フロント ディスプレイはオフになります。"</string>
     <string name="mirror_display" msgid="2515262008898122928">"ディスプレイをミラーリングする"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"閉じる"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"ディスプレイに接続しました"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 9d386ef..db57e7e 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"სახით განბლოკვის დაყენება ვერ მოხერხდა. გადადით პარამეტრებზე და ცადეთ ხელახლა."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"შეეხეთ თითის ანაბეჭდის სენსორს"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"გასაგრძელებლად დააჭირეთ განბლოკვის ხატულას"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"სახის ამოცნობა ვერ ხერხდება. სანაცვლოდ თითის ანაბეჭდი გამოიყენეთ."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"სახის ამოცნობა ვერ მოხერხდა. ცადეთ თითის ანაბეჭდი."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"სახის ამოცნობა შეუძლებ."</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"სახის ამოცნობა ვერ მოხერხდა"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"გამოიყენეთ თითის ანაბეჭდი"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"სახით განბლოკვა მიუწვდომელია."</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth დაკავშირებულია."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • იტენება • სრულ დატენვამდე <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"გადაფურცლეთ მარცხნივ, რათა დაიწყოთ საერთო სახელმძღვანელო"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"გახსენით ვიჯეტის რედაქტორი"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"მორგება"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"უარყოფა"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"ამ სივრცეში შეძლებთ თქვენი ვიჯეტების დამატებას, ამოშლასა და გადაწყობას"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ვიჯეტების დამატება"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ხანგრძლივად დააჭირეთ ვიჯეტების მოსარგებად"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ამოშლა"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ვიჯეტის დამატება"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"მზადაა"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"გსურთ Bluetooth-ის ჩართვა?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"კლავიატურის ტაბლეტთან დასაკავშირებლად, ჯერ უნდა ჩართოთ Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"ჩართვა"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"შეტყობინებების მართვის საშუალებები"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ჩართული — სახის მიხედვით"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"შეტყობინებების მართვის საშუალებების მეშვეობით, შეგიძლიათ განსაზღვროთ აპის შეტყობინებების მნიშვნელობის დონე 0-დან 5-მდე დიაპაზონში. \n\n"<b>"დონე 5"</b>" \n— შეტყობინებათა სიის თავში ჩვენება \n— სრულეკრანიანი რეჟიმის შეფერხების დაშვება \n— ეკრანზე ყოველთვის გამოჩენა \n\n"<b>"დონე 4"</b>" \n— სრულეკრანიანი რეჟიმის შეფერხების აღკვეთა \n— ეკრანზე ყოველთვის გამოჩენა \n\n"<b>"დონე 3"</b>" \n— სრულეკრანიანი რეჟიმის შეფერხების აღკვეთა \n— ეკრანზე გამოჩენის აღკვეთა \n\n"<b>"დონე 2"</b>" \n— სრულეკრანიანი რეჟიმის შეფერხების აღკვეთა \n— ეკრანზე გამოჩენის აღკვეთა \n— ხმისა და ვიბრაციის აღკვეთა \n\n"<b>"დონე 1"</b>" \n— სრულეკრანიანი რეჟიმის შეფერხების აღკვეთა \n— ეკრანზე გამოჩენის აღკვეთა \n— ხმისა და ვიბრაციის აღკვეთა \n— ჩაკეტილი ეკრანიდან და სტატუსის ზოლიდან დამალვა \n— შეტყობინებათა სიის ბოლოში ჩვენება \n\n"<b>"დონე 0"</b>" \n— აპის ყველა შეტყობინების დაბლოკვა"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"მზადაა"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"მისადაგება"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"შეტყობინებების გამორთვა"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"დაყენება"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"მეხსიერება"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"მინიშნებები"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"მარტივი წვდომა"</string>
     <string name="instant_apps" msgid="8337185853050247304">"მყისიერი აპები"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> გაშვებულია"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"აპი გაიხსნა ინსტალაციის გარეშე."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"შეეხეთ მარტივი წვდომის ფუნქციების გასახსნელად. მოარგეთ ან შეცვალეთ ეს ღილაკი პარამეტრებში.\n\n"<annotation id="link">"პარამეტრების ნახვა"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"გადაიტანეთ ღილაკი კიდეში, რათა დროებით დამალოთ ის"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"მოქმედების გაუქმება"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"მარტივი წვდომის ღილაკი დამალულია"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"შეეხეთ მარტივი წვდომის ღილაკის საჩვენებლად"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> მალსახმობი ამოშლილია"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# მალსახმობი ამოშლილია}other{# მალსახმობი ამოშლილია}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ზევით და მარცხნივ გადატანა"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"აღმოჩენილია მომხმარებლის ყოფნა"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"დააყენეთ ნაგულისხმევი შენიშვნების აპი პარამეტრებში"</string>
     <string name="install_app" msgid="5066668100199613936">"აპის ინსტალაცია"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"აირეკლოს გარე ეკრანზე?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"თქვენი შიდა ეკრანი აირეკლება. თქვენი წინა ეკრანი გამოირთვება."</string>
     <string name="mirror_display" msgid="2515262008898122928">"ეკრანის არეკვლა"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index d2c60f1..a90c6e8 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Бет тану функциясы реттелмеді. \"Параметрлер\" бөліміне өтіп, әрекетті қайталап көріңіз."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Саусақ ізін оқу сканерін түртіңіз"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Жалғастыру үшін құлыпты ашу белгішесін басыңыз."</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Бет танылмады. Орнына саусақ ізін пайдаланыңыз."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Бет танылмады. Орнына саусақ ізін пайдаланыңыз."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Бет танылмады."</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Бет танылмады."</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Орнына саусақ ізін пайдаланыңыз."</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Бет тану функциясы қолжетімсіз."</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth қосылған."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарядталып жатыр. • Толуына <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> қалды."</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Ортақ оқулықты ашу үшін солға қарай сырғытыңыз."</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Виджет редакторын ашу"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Бейімдеу"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Жабу"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Осы бөлмеде виджеттер қосыңыз, оларды өшіріңіз және ретін өзгертіңіз."</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Басқа виджеттер қосыңыз."</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Виджеттерді бейімдеу үшін ұзақ басып тұрыңыз."</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Өшіру"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Виджет қосу"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Дайын"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Bluetooth функциясын қосу керек пе?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Пернетақтаны планшетке қосу үшін алдымен Bluetooth функциясын қосу керек."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Қосу"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Қуат хабарландыруының басқару элементтері"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Қосулы – бет негізінде"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Қуат хабарландыруының басқару элементтерімен қолданбаның хабарландырулары үшін 0-ден бастап 5-ке дейін маңыздылық деңгейін орнатуға болады. \n\n"<b>"5-деңгей"</b>" \n- Хабарландыру тізімінің ең басында көрсету \n- Толық экранға ашылуын рұқсат ету \n- Әрдайым қалқымалы хабарландыру түрінде көрсету \n\n"<b>"4-деңгей"</b>" \n- Толық экранға шығармау \n- Әрдайым қалқымалы хабарландыру түрінде көрсету \n\n"<b>"3-деңгей"</b>" \n- Толық экранға шығармау \n- Ешқашан қалқымалы хабарландыру түрінде көрсетпеу \n\n"<b>"2-деңгей"</b>" \n- Толық экранға шығармау \n- Ешқашан қалқымалы хабарландыру түрінде көрсетпеу \n- Ешқашан дыбыс және діріл шығармау \n\n"<b>"1-деңгей"</b>" \n- Толық экранға шығармау \n- Ешқашан қалқымалы хабарландыру түрінде көрсетпеу \n- Ешқашан дыбыс немесе діріл шығармау \n- Құлыпталған экраннан және күйін көрсету жолағынан жасыру \n- Хабарландыру тізімінің ең астында көрсету \n\n"<b>"0-деңгей"</b>" \n- Қолданбадағы барлық хабарландыруларға тыйым салу"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Дайын"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Қолдану"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Хабарландыруларды өшіру"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Реттеу"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Жад"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Кеңестер"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Арнайы мүмкіндіктер"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> іске қосулы"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Қолданба орнатылмай-ақ ашылды."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Арнайы мүмкіндікті ашу үшін түртіңіз. Түймені параметрден реттеңіз не ауыстырыңыз.\n\n"<annotation id="link">"Параметрді көру"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Түймені уақытша жасыру үшін оны шетке қарай жылжытыңыз."</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Қайтару"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Арнайы мүмкіндіктер түймесі жасырылған"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Арнайы мүмкіндіктер түймесін көрсету үшін түртіңіз."</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> жылдам пәрмені өшірілді."</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# таңбаша өшірілді.}other{# таңбаша өшірілді.}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Жоғарғы сол жаққа жылжыту"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Пайдаланушы анықталды."</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Параметрлерден әдепкі жазба қолданбасын орнатыңыз."</string>
     <string name="install_app" msgid="5066668100199613936">"Қолданбаны орнату"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Сыртқы экран арқылы да көрсету керек пе?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ішкі экран көшірмесі көрсетіледі. Алдыңғы экран өшіріледі."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Көрсету"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index ec81523..dfa26a5 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"មិនអាច​រៀបចំ​ការដោះសោតាមទម្រង់មុខបានទេ។ សូមចូលទៅកាន់​ការកំណត់​ ដើម្បីព្យាយាមម្ដងទៀត។"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"ប៉ះ​ឧបករណ៍​ចាប់ស្នាម​ម្រាមដៃ"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"សូមចុចរូបដោះសោ ដើម្បីបន្ត"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"មិនអាចសម្គាល់មុខបានទេ។ សូមប្រើស្នាមម្រាមដៃជំនួសវិញ។"</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"មិន​ស្គាល់​មុខទេ។ សូមប្រើស្នាមម្រាមដៃជំនួសវិញ។"</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"មិនអាចសម្គាល់មុខបានទេ"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"មិន​ស្គាល់​មុខ"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ប្រើស្នាមម្រាមដៃជំនួសវិញ​"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"មិនអាចប្រើការដោះសោតាមទម្រង់មុខបានទេ"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"បាន​តភ្ជាប់​ប៊្លូធូស។"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • កំពុងសាកថ្ម • ពេញក្នុងរយៈពេល <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"អូសទៅឆ្វេង ដើម្បីចាប់ផ្ដើមមេរៀនសហគមន៍"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"បើកកម្មវិធីកែធាតុ​ក្រាហ្វិក"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"ប្ដូរតាមបំណង"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"ច្រានចោល"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"បញ្ចូល ដកចេញ និងតម្រៀបធាតុ​ក្រាហ្វិករបស់អ្នកឡើងវិញនៅក្នុងលំហនេះ"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"បញ្ចូលធាតុ​ក្រាហ្វិកច្រើនទៀត"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ចុច​ឱ្យ​យូរ ដើម្បីប្ដូរធាតុ​ក្រាហ្វិកតាមបំណង"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ដកចេញ"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"បញ្ចូលធាតុ​ក្រាហ្វិក"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"រួចរាល់"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"បើកប៊្លូធូសឬ?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"ដើម្បីភ្ជាប់ក្តារចុចរបស់អ្នកជាមួយនឹងថេប្លេតរបស់អ្នក អ្នកត្រូវតែបើកប៊្លូធូសជាមុនសិន។"</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"បើក"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"អង្គគ្រប់គ្រងការជូនដំណឹងថាមពល"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"បើក - ផ្អែកលើមុខ"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"ជាមួយអង្គគ្រប់គ្រងការជូនដំណឹងថាមពល អ្នកអាចកំណត់កម្រិតសំខាន់ពី 0 ទៅ 5 សម្រាប់ការជូនដំណឹងរបស់កម្មវិធី។ \n\n"<b>"កម្រិត 5"</b>" \n- បង្ហាញនៅផ្នែកខាងលើបញ្ជីជូនដំណឹង \n- អនុញ្ញាតការរំខានលើអេក្រង់ពេញ \n- លោតឡើងជានិច្ច \n\n"<b>"កម្រិត 4"</b>" \n- រារាំងការរំខានលើអេក្រង់ពេញ \n- លោតឡើងជានិច្ច \n\n"<b>"កម្រិត 3"</b>" \n- រារាំងការរំខានលើអេក្រង់ពេញ \n- លោតឡើងជានិច្ច \n\n"<b>"កម្រិត 2"</b>" \n- រារាំងការរំខានលើអេក្រង់ពេញ \n- លោតឡើងជានិច្ច \n- មិនបន្លឺសំឡេង ឬញ័រ \n\n"<b>"កម្រិត 1"</b>" \n- រារាំងការរំខានលើអេក្រង់ពេញ \n- លោតឡើងជានិច្ច \n- មិនបន្លឺសំឡេង ឬញ័រ \n- លាក់ពីអេក្រង់ចាក់សោ និងរបារស្ថានភាព \n- បង្ហាញនៅផ្នែកខាងក្រោមបញ្ជីជូនដំណឹង \n\n"<b>"កម្រិត 0"</b>" \n- រារាំងការជូនដំណឹងទាំងអស់ពីកម្មវិធី"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"រួចរាល់"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"ប្រើ"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"បិទ​ការជូន​ដំណឹង"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"ការរៀបចំ"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"ទំហំផ្ទុក"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"ការ​សម្រួល"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"ភាពងាយស្រួល"</string>
     <string name="instant_apps" msgid="8337185853050247304">"កម្មវិធី​ប្រើ​ភ្លាមៗ"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> កំពុង​ដំណើរការ"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"កម្មវិធីត្រូវ​បាន​បើក​ដោយ​មិនចាំបាច់ដំឡើង។"</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ចុចដើម្បីបើក​មុខងារ​ភាពងាយស្រួល។ ប្ដូរ ឬប្ដូរ​ប៊ូតុងនេះ​តាមបំណង​នៅក្នុង​ការកំណត់។\n\n"<annotation id="link">"មើល​ការកំណត់"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ផ្លាស់ទី​ប៊ូតុង​ទៅគែម ដើម្បីលាក់វា​ជាបណ្ដោះអាសន្ន"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"ត្រឡប់វិញ"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"បានលាក់ប៊ូតុង​ភាពងាយស្រួល"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"ចុច ដើម្បីបង្ហាញប៊ូតុង​ភាពងាយស្រួល"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"បានដក​ផ្លូវកាត់ <xliff:g id="FEATURE_NAME">%s</xliff:g> ចេញ"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{បានដក​ផ្លូវកាត់ # ចេញ}other{បាន​ដក​ផ្លូវ​កាត់ # ចេញ}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ផ្លាស់ទីទៅខាងលើផ្នែកខាងឆ្វេង"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"វត្តមានអ្នកប្រើប្រាស់ត្រូវបានចាប់ដឹង"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"កំណត់កម្មវិធីកំណត់ចំណាំលំនាំដើមនៅក្នុងការកំណត់"</string>
     <string name="install_app" msgid="5066668100199613936">"ដំឡើង​កម្មវិធី"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"បញ្ចាំងទៅ​ផ្ទាំងអេក្រង់​ខាងក្រៅឬ?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"អេក្រង់ខាងក្នុងរបស់អ្នកនឹងត្រូវបានធ្វើ​សមកាលកម្មទៅវិញទៅមក។ អេក្រង់មុខរបស់អ្នកនឹងត្រូវបានបិទ។"</string>
     <string name="mirror_display" msgid="2515262008898122928">"បញ្ចាំងទៅផ្ទាំងអេក្រង់"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 33f4528..51d74cc 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -74,7 +74,7 @@
     <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"ಚಿತ್ರವನ್ನು ಕಳುಹಿಸಲಾಗಿದೆ"</string>
     <string name="screenshot_saving_title" msgid="2298349784913287333">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್ ಉಳಿಸಲಾಗುತ್ತಿದೆ…"</string>
     <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ಗೆ ಸ್ಕ್ರೀನ್‌ಶಾಟ್‌ ಉಳಿಸಲಾಗುತ್ತಿದೆ…"</string>
-    <string name="screenshot_saved_title" msgid="8893267638659083153">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್‌ ಅನ್ನು ಉಳಿಸಲಾಗಿದೆ"</string>
+    <string name="screenshot_saved_title" msgid="8893267638659083153">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್‌ ಅನ್ನು ಸೇವ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="screenshot_failed_title" msgid="3259148215671936891">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್ ಅನ್ನು ಉಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
     <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"ಬಾಹ್ಯ ಡಿಸ್‌ಪ್ಲೇ"</string>
     <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್ ಉಳಿಸುವ ಮೊದಲು ಸಾಧನವನ್ನು ಅನ್‌ಲಾಕ್ ಮಾಡಬೇಕು"</string>
@@ -116,7 +116,7 @@
     <string name="screenrecord_taps_label" msgid="1595690528298857649">"ಸ್ಪರ್ಶಗಳನ್ನು ಸ್ಕ್ರೀನ್ ಮೇಲೆ ತೋರಿಸಿ"</string>
     <string name="screenrecord_stop_label" msgid="72699670052087989">"ನಿಲ್ಲಿಸಿ"</string>
     <string name="screenrecord_share_label" msgid="5025590804030086930">"ಹಂಚಿಕೊಳ್ಳಿ"</string>
-    <string name="screenrecord_save_title" msgid="1886652605520893850">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಉಳಿಸಲಾಗಿದೆ"</string>
+    <string name="screenrecord_save_title" msgid="1886652605520893850">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಸೇವ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="screenrecord_save_text" msgid="3008973099800840163">"ವೀಕ್ಷಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಸೇವ್‌ ಮಾಡುವಾಗ ದೋಷ ಎದುರಾಗಿದೆ"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭಿಸುವಾಗ ದೋಷ ಕಂಡುಬಂದಿದೆ"</string>
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"ಫೇಸ್ ಅನ್‌ಲಾಕ್ ಅನ್ನು ಸೆಟಪ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ. ಮತ್ತೊಮ್ಮೆ ಪ್ರಯತ್ನಿಸಲು, ಸೆಟ್ಟಿಂಗ್‌ಗಳಿಗೆ ಹೋಗಿ."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"ಫಿಂಗರ್‌ಪ್ರಿಂಟ್ ಸೆನ್ಸರ್‌‌ ಅನ್ನು ಸ್ಪರ್ಶಿಸಿ"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"ಮುಂದುವರಿಯಲು ಅನ್‌ಲಾಕ್ ಐಕಾನ್ ಅನ್ನು ಒತ್ತಿರಿ"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"ಮುಖ ಗುರುತಿಸಲಾಗುತ್ತಿಲ್ಲ ಬದಲಿಗೆ ಫಿಂಗರ್‌ಪ್ರಿಂಟ್ ಬಳಸಿ."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"ಮುಖ ಗುರುತಿಸಲಾಗಿಲ್ಲ. ಬದಲಿಗೆ ಫಿಂಗರ್‌ಪ್ರಿಂಟ್ ಬಳಸಿ."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"ಮುಖ ಗುರುತಿಸಲಾಗುತ್ತಿಲ್ಲ"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"ಮುಖವನ್ನು ಗುರುತಿಸಲಾಗಿಲ್ಲ"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ಬದಲಿಗೆ ಫಿಂಗರ್‌ಪ್ರಿಂಟ್ ಬಳಸಿ"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"ಫೇಸ್ ಅನ್‌ಲಾಕ್ ಲಭ್ಯವಿಲ್ಲ"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ಬ್ಲೂಟೂತ್‌‌ ಸಂಪರ್ಕಗೊಂಡಿದೆ."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ದಲ್ಲಿ ಪೂರ್ಣಗೊಳ್ಳುತ್ತದೆ"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"ಸಮುದಾಯದ ಟ್ಯುಟೋರಿಯಲ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಲು ಎಡಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ವಿಜೆಟ್ ಎಡಿಟರ್ ಅನ್ನು ತೆರೆಯಿರಿ"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"ಕಸ್ಟಮೈಸ್ ಮಾಡಿ"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"ವಜಾಗೊಳಿಸಿ"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"ಈ ಜಾಗದಲ್ಲಿ ನಿಮ್ಮ ವಿಜೆಟ್‌ಗಳನ್ನು ಸೇರಿಸಿ, ತೆಗೆದುಹಾಕಿ ಮತ್ತು ಮರುಕ್ರಮಗೊಳಿಸಿ"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ಹೆಚ್ಚಿನ ವಿಜೆಟ್‌ಗಳನ್ನು ಸೇರಿಸಿ"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ವಿಜೆಟ್‌ಗಳನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಲು ದೀರ್ಘಕಾಲ ಒತ್ತಿರಿ"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ತೆಗೆದುಹಾಕಿ"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ವಿಜೆಟ್ ಅನ್ನು ಸೇರಿಸಿ"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"ಮುಗಿದಿದೆ"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"ಬ್ಲೂಟೂತ್ ಆನ್ ಮಾಡಬೇಕೆ?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"ನಿಮ್ಮ ಕೀಬೋರ್ಡ್ ಅನ್ನು ಟ್ಯಾಬ್ಲೆಟ್‌ಗೆ ಸಂಪರ್ಕಿಸಲು, ನೀವು ಮೊದಲು ಬ್ಲೂಟೂತ್ ಆನ್ ಮಾಡಬೇಕಾಗುತ್ತದೆ."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"ಆನ್‌ ಮಾಡಿ"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"ಪವರ್ ನೋಟಿಫಿಕೇಶನ್ ನಿಯಂತ್ರಣಗಳು"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ಆನ್ ಆಗಿದೆ - ಮುಖ-ಆಧಾರಿತ"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"ಪವರ್ ನೋಟಿಫಿಕೇಶನ್ ನಿಯಂತ್ರಣಗಳ ಮೂಲಕ, ನೀವು ಆ್ಯಪ್‍ಗಳ ನೋಟಿಫಿಕೇಶನ್‍ಗಳನ್ನು 0 ರಿಂದ 5 ರವರೆಗಿನ ಹಂತಗಳ ಪ್ರಾಮುಖ್ಯತೆಯನ್ನು ಹೊಂದಿಸಬಹುದು. \n\n"<b>"ಹಂತ 5"</b>" \n- ಮೇಲಿನ ನೋಟಿಫಿಕೇಶನ್ ಪಟ್ಟಿಯನ್ನು ತೋರಿಸಿ \n- ಪೂರ್ಣ ಪರದೆ ಅಡಚಣೆಯನ್ನು ಅನುಮತಿಸಿ \n- ಯಾವಾಗಲು ಇಣುಕು ನೋಟ \n\n"<b>"ಹಂತ 4"</b>" \n- ಪೂರ್ಣ ಪರದೆ ಅಡಚಣೆಯನ್ನು ತಡೆಯಿರಿ \n- ಯಾವಾಗಲು ಇಣುಕು ನೋಟ\n\n"<b>"ಹಂತ 3"</b>" \n- ಪೂರ್ಣ ಪರದೆ ಅಡಚಣೆಯನ್ನು ತಡೆಯಿರಿ \n- ಎಂದಿಗೂ ಇಣುಕು ನೋಟ ಬೇಡ \n\n"<b>"ಹಂತ 2"</b>" \n- ಪೂರ್ಣ ಪರದೆ ಅಡಚಣೆಯನ್ನು ತಡೆಯಿರಿ \n- ಎಂದಿಗೂ ಇಣುಕು ನೋಟ ಬೇಡ \n- ಶಬ್ದ ಮತ್ತು ವೈಬ್ರೇಷನ್ ಎಂದಿಗೂ ಮಾಡಬೇಡಿ \n\n"<b>"ಹಂತ 1"</b>" \n- ಪೂರ್ಣ ಪರದೆ ಅಡಚಣೆಯನ್ನು ತಡೆಯಿರಿ \n- ಎಂದಿಗೂ ಇಣುಕು ನೋಟ ಬೇಡ \n- ಶಬ್ದ ಮತ್ತು ವೈಬ್ರೇಷನ್ ಎಂದಿಗೂ ಮಾಡಬೇಡಿ \n- ಸ್ಥಿತಿ ಪಟ್ಟಿ ಮತ್ತು ಲಾಕ್ ಪರದೆಯಿಂದ ಮರೆಮಾಡಿ \n- ಕೆಳಗಿನ ನೋಟಿಫಿಕೇಶನ್ ಪಟ್ಟಿಯನ್ನು ತೋರಿಸಿ \n\n"<b>"ಹಂತ 0"</b>" \n- ಆ್ಯಪ್‍ನಿಂದ ಎಲ್ಲಾ ನೋಟಿಫಿಕೇಶನ್‍ಗಳನ್ನು ನಿರ್ಬಂಧಿಸಿ"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"ಪೂರ್ಣಗೊಂಡಿದೆ"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"ಅನ್ವಯಿಸಿ"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"ಅಧಿಸೂಚನೆಗಳನ್ನು ಆಫ್ ಮಾಡಿ"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"ಸೆಟಪ್"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"ಸಂಗ್ರಹಣೆ"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"ಸುಳಿವುಗಳು"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> ರನ್ ಆಗುತ್ತಿದೆ"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"ಇನ್‌ಸ್ಟಾಲ್‌ ಮಾಡದೆ ಆ್ಯಪ್‌ ತೆರೆಯಲಾಗಿದೆ."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ತೆರೆಯಲು ಟ್ಯಾಪ್ ಮಾಡಿ. ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ಈ ಬಟನ್ ಅನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಿ ಅಥವಾ ಬದಲಾಯಿಸಿ.\n\n"<annotation id="link">"ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ಅದನ್ನು ತಾತ್ಕಾಲಿಕವಾಗಿ ಮರೆಮಾಡಲು ಅಂಚಿಗೆ ಬಟನ್ ಸರಿಸಿ"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"ರದ್ದುಗೊಳಿಸಿ"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ ಬಟನ್ ಅನ್ನು ಮರೆಮಾಡಲಾಗಿದೆ"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ ಬಟನ್ ತೋರಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> ಶಾರ್ಟ್‌ಕಟ್ ತೆಗೆದುಹಾಕಲಾಗಿದೆ"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# ಶಾರ್ಟ್‌ಕಟ್ ತೆಗೆದುಹಾಕಲಾಗಿದೆ}one{# ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ}other{# ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ಎಡ ಮೇಲ್ಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"ಬಳಕೆದಾರರ ಉಪಸ್ಥಿತಿಯನ್ನು ಪತ್ತೆಹಚ್ಚಲಾಗಿದೆ"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ಡೀಫಾಲ್ಟ್ ಟಿಪ್ಪಣಿಗಳ ಆ್ಯಪ್ ಅನ್ನು ಸೆಟ್ ಮಾಡಿ"</string>
     <string name="install_app" msgid="5066668100199613936">"ಆ್ಯಪ್ ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ಬಾಹ್ಯ ಡಿಸ್‌ಪ್ಲೇಗೆ ಪ್ರತಿಬಿಂಬಿಸಬೇಕೆ?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ನಿಮ್ಮ ಒಳಗಿನ ಡಿಸ್‌ಪ್ಲೇ ಅನ್ನು ಪ್ರತಿಬಿಂಬಿಸಲಾಗುತ್ತದೆ. ನಿಮ್ಮ ಫ್ರಂಟ್ ಡಿಸ್‌ಪ್ಲೇ ಅನ್ನು ಆಫ್ ಮಾಡಲಾಗುತ್ತದೆ."</string>
     <string name="mirror_display" msgid="2515262008898122928">"ಮಿರರ್ ಡಿಸ್‌ಪ್ಲೇ"</string>
diff --git a/packages/SystemUI/res/values-kn/tiles_states_strings.xml b/packages/SystemUI/res/values-kn/tiles_states_strings.xml
index 876562d..250eb5a 100644
--- a/packages/SystemUI/res/values-kn/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-kn/tiles_states_strings.xml
@@ -34,147 +34,147 @@
   <string-array name="tile_states_internet">
     <item msgid="5499482407653291407">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="3048856902433862868">"ಆಫ್"</item>
-    <item msgid="6877982264300789870">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="6877982264300789870">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_wifi">
     <item msgid="8054147400538405410">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="4293012229142257455">"ಆಫ್"</item>
-    <item msgid="6221288736127914861">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="6221288736127914861">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_cell">
     <item msgid="1235899788959500719">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="2074416252859094119">"ಆಫ್"</item>
-    <item msgid="287997784730044767">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="287997784730044767">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_battery">
     <item msgid="6311253873330062961">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="7838121007534579872">"ಆಫ್"</item>
-    <item msgid="1578872232501319194">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="1578872232501319194">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_dnd">
     <item msgid="467587075903158357">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="5376619709702103243">"ಆಫ್"</item>
-    <item msgid="4875147066469902392">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="4875147066469902392">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_flashlight">
     <item msgid="3465257127433353857">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="5044688398303285224">"ಆಫ್"</item>
-    <item msgid="8527389108867454098">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="8527389108867454098">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_rotation">
     <item msgid="4578491772376121579">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="5776427577477729185">"ಆಫ್"</item>
-    <item msgid="7105052717007227415">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="7105052717007227415">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_bt">
     <item msgid="5330252067413512277">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="5315121904534729843">"ಆಫ್"</item>
-    <item msgid="503679232285959074">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="503679232285959074">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_airplane">
     <item msgid="1985366811411407764">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="4801037224991420996">"ಆಫ್"</item>
-    <item msgid="1982293347302546665">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="1982293347302546665">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_location">
     <item msgid="3316542218706374405">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="4813655083852587017">"ಆಫ್"</item>
-    <item msgid="6744077414775180687">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="6744077414775180687">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_hotspot">
     <item msgid="3145597331197351214">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="5715725170633593906">"ಆಫ್"</item>
-    <item msgid="2075645297847971154">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="2075645297847971154">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_color_correction">
     <item msgid="2840507878437297682">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="1909756493418256167">"ಆಫ್"</item>
-    <item msgid="4531508423703413340">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="4531508423703413340">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_inversion">
     <item msgid="3638187931191394628">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="9103697205127645916">"ಆಫ್"</item>
-    <item msgid="8067744885820618230">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="8067744885820618230">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_saver">
     <item msgid="39714521631367660">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="6983679487661600728">"ಆಫ್"</item>
-    <item msgid="7520663805910678476">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="7520663805910678476">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_dark">
     <item msgid="2762596907080603047">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="400477985171353">"ಆಫ್"</item>
-    <item msgid="630890598801118771">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="630890598801118771">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_work">
     <item msgid="389523503690414094">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="8045580926543311193">"ಆಫ್"</item>
-    <item msgid="4913460972266982499">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="4913460972266982499">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_cast">
     <item msgid="6032026038702435350">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="1488620600954313499">"ಆಫ್"</item>
-    <item msgid="588467578853244035">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="588467578853244035">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_night">
     <item msgid="7857498964264855466">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="2744885441164350155">"ಆಫ್"</item>
-    <item msgid="151121227514952197">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="151121227514952197">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_screenrecord">
     <item msgid="1085836626613341403">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="8259411607272330225">"ಆಫ್"</item>
-    <item msgid="578444932039713369">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="578444932039713369">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="8707481475312432575">"ಆಫ್"</item>
-    <item msgid="8031106212477483874">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="8031106212477483874">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_reduce_brightness">
     <item msgid="1839836132729571766">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="4572245614982283078">"ಆಫ್"</item>
-    <item msgid="6536448410252185664">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="6536448410252185664">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_cameratoggle">
     <item msgid="6680671247180519913">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="4765607635752003190">"ಆಫ್"</item>
-    <item msgid="1697460731949649844">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="1697460731949649844">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_mictoggle">
     <item msgid="6895831614067195493">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="3296179158646568218">"ಆಫ್"</item>
-    <item msgid="8998632451221157987">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="8998632451221157987">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_controls">
     <item msgid="8199009425335668294">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="4544919905196727508">"ಆಫ್"</item>
-    <item msgid="3422023746567004609">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="3422023746567004609">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_wallet">
     <item msgid="4177615438710836341">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="7571394439974244289">"ಆಫ್"</item>
-    <item msgid="6866424167599381915">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="6866424167599381915">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_qr_code_scanner">
     <item msgid="7435143266149257618">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="3301403109049256043">"ಆಫ್"</item>
-    <item msgid="8878684975184010135">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="8878684975184010135">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_alarm">
     <item msgid="4936533380177298776">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="2710157085538036590">"ಆಫ್"</item>
-    <item msgid="7809470840976856149">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="7809470840976856149">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_onehanded">
     <item msgid="8189342855739930015">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="146088982397753810">"ಆಫ್"</item>
-    <item msgid="460891964396502657">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="460891964396502657">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_dream">
     <item msgid="6184819793571079513">"ಲಭ್ಯವಿಲ್ಲ"</item>
     <item msgid="8014986104355098744">"ಆಫ್"</item>
-    <item msgid="5966994759929723339">"ಆನ್ ಮಾಡಿ"</item>
+    <item msgid="5966994759929723339">"ಆನ್"</item>
   </string-array>
   <string-array name="tile_states_font_scaling">
     <item msgid="3173069902082305985">"ಲಭ್ಯವಿಲ್ಲ"</item>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 897b266..316fea7 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"얼굴 인식 잠금 해제를 설정할 수 없습니다. 설정으로 이동하여 다시 시도해 보세요."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"지문 센서를 터치하세요."</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"계속하려면 잠금 해제 아이콘을 누르세요."</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"얼굴을 인식할 수 없습니다. 대신 지문을 사용하세요."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"얼굴을 인식할 수 없습니다. 대신 지문을 사용하세요."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"얼굴을 인식할 수 없습니다."</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"얼굴을 인식할 수 없습니다."</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"대신 지문을 사용하세요."</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"얼굴 인식 잠금 해제를 사용할 수 없습니다."</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"블루투스가 연결되었습니다."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 충전 중 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> 후 충전 완료"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"공동 튜토리얼을 시작하려면 왼쪽으로 스와이프하세요"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"위젯 편집기 열기"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"맞춤설정"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"닫기"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"이 공간에서 위젯 추가, 삭제 또는 다시 정렬"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"더 많은 위젯 추가"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"위젯을 맞춤설정하려면 길게 누르기"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"삭제"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"위젯 추가"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"완료"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"블루투스를 켜시겠습니까?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"키보드를 태블릿에 연결하려면 먼저 블루투스를 켜야 합니다."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"사용"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"전원 알림 컨트롤"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"켜짐 - 얼굴 기준"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"전원 알림 컨트롤을 사용하면 앱 알림 관련 중요도를 0부터 5까지로 설정할 수 있습니다. \n\n"<b>"레벨 5"</b>" \n- 알림 목록 상단에 표시 \n- 전체 화면일 경우 알림 표시 허용 \n- 항상 엿보기 표시 \n\n"<b>"레벨 4"</b>" \n- 전체 화면에 알림 표시 금지 \n- 항상 엿보기 표시 \n\n"<b>"레벨 3"</b>" \n- 전체 화면에 알림 표시 금지 \n- 엿보기 표시 안함 \n\n"<b>"레벨 2"</b>" \n- 전체 화면에 알림 표시 금지 \n- 엿보기 표시 안함 \n- 소리나 진동으로 알리지 않음 \n\n"<b>"레벨 1"</b>" \n- 전체 화면에 알림 표시 금지 \n- 엿보기 표시 안함 \n- 소리나 진동으로 알리지 않음 \n- 잠금 화면 및 상태 표시줄에서 숨김 \n- 알림 목록 하단에 표시 \n\n"<b>"레벨 0"</b>" \n- 앱의 모든 알림 차단"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"완료"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"적용"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"알림 사용 중지"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"설정"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"저장용량"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"힌트"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"접근성"</string>
     <string name="instant_apps" msgid="8337185853050247304">"인스턴트 앱"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> 실행 중"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"설치 없이 앱이 실행되었습니다."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"접근성 기능을 열려면 탭하세요. 설정에서 이 버튼을 맞춤설정하거나 교체할 수 있습니다.\n\n"<annotation id="link">"설정 보기"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"버튼을 가장자리로 옮겨서 일시적으로 숨기세요."</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"실행취소"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"접근성 버튼 숨김"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"탭하여 접근성 버튼 표시"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> 바로가기 삭제됨"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{바로가기 #개 삭제됨}other{바로가기 #개 삭제됨}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"왼쪽 상단으로 이동"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"사용자 정보가 감지되었습니다."</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"설정에서 기본 메모 앱 설정"</string>
     <string name="install_app" msgid="5066668100199613936">"앱 설치"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"외부 디스플레이로 미러링하시겠습니까?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"내부 디스플레이가 미러링됩니다. 전면 디스플레이는 꺼집니다."</string>
     <string name="mirror_display" msgid="2515262008898122928">"디스플레이 미러링"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 2f091ec..d7b13a6 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Жүзүнөн таанып ачуу функциясы кошулган жок. Параметрлерге өтүп, кайталап көрүңүз."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Манжа изинин сенсорун басыңыз"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Улантуу үчүн кулпусун ачуу сүрөтчөсүн басыңыз"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Жүз таанылбай жатат. Манжа изин колдонуңуз."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Жүз таанылган жок. Манжа изин колдонуңуз."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Жүз таанылбай жатат"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Жүз таанылган жок"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Манжа изин колдонуңуз"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"\"Жүзүнөн таанып ачуу\" жеткиликсиз"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth байланышта"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Кубатталууда • Толгонго чейин <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> калды"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Жалпы үйрөткүчтү иштетүү үчүн солго сүрүңүз"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Виджет түзөткүчтү ачуу"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Ыңгайлаштыруу"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Жабуу"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Бул иш чөйрөсүнө виджеттерди кошуп, өчүрүп жана иретин өзгөртүңүз"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Көбүрөөк виджеттерди кошуу"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Виджеттерди ыңгайлаштыруу үчүн кое бербей басып туруңуз"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Өчүрүү"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Виджет кошуу"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Бүттү"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Bluetooth күйгүзүлсүнбү?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Баскычтобуңузду планшетиңизге туташтыруу үчүн, адегенде Bluetooth\'ту күйгүзүшүңүз керек."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Күйгүзүү"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Эскертмелерди башкаруу каражаттары"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Күйүк – Жүздүн негизинде"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Бул функциянын жардамы менен, ар бир колдонмо үчүн билдирменин маанилүүлүгүн 0дон 5ке чейин бааласаңыз болот. \n\n"<b>"5-деңгээл"</b>" \n- Билдирмелер тизмесинин өйдө жагында көрүнөт \n- Билдирмелер толук экранда көрүнөт \n- Калкып чыгуучу билдирмелерге уруксат берилет \n\n"<b>"4-деңгээл"</b>" \n- Билдирмелер толук экранда көрүнбөйт \n- Калкып чыгуучу билдирмелерге уруксат берилет \n\n"<b>"3-деңгээл"</b>" \n- Билдирмелер толук экранда көрүнбөйт \n- Калкып чыгуучу билдирмелерге тыюу салынат \n\n"<b>"2-деңгээл"</b>" \n- Билдирмелер толук экранда көрүнбөйт \n- Калкып чыгуучу билдирмелерге тыюу салынат \n- Эч качан үн чыкпайт же дирилдебейт \n\n"<b>"1-деңгээл"</b>" \n- Билдирмелер толук экранда көрүнбөйт \n- Калкып чыгуучу билдирмелерге тыюу салынат \n- Эч качан үн чыкпайт же дирилдебейт \n- Кулпуланган экрандан жана абал тилкесинен жашырылат \n- Билдирмелер тизмесинин ылдый жагында көрүнөт \n\n"<b>"0-деңгээл"</b>" \n- Колдонмодон алынган бардык билдирмелер бөгөттөлөт"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Бүттү"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Колдонуу"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Билдирмелерди өчүрүү"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Тууралоо"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Сактагыч"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Кеңештер"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Атайын мүмкүнчүлүктөр"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Ыкчам ачылуучу колдонмолор"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> иштеп жатат"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Колдонмо орнотулбастан ачылды."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Атайын мүмкүнчүлүктөрдү ачуу үчүн басыңыз. Бул баскычты Параметрлерден өзгөртүңүз.\n\n"<annotation id="link">"Параметрлерди көрүү"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Баскычты убактылуу жашыра туруу үчүн экрандын четине жылдырыңыз"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Кайтаруу"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Атайын мүмкүнчүлүктөр баскычы жашырылган"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Атайын мүмкүнчүлүктөр баскычын көрсөтүү үчүн таптаңыз"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> ыкчам баскычы өчүрүлдү"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# ыкчам баскыч өчүрүлдү}other{# ыкчам баскыч өчүрүлдү}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Жогорку сол жакка жылдыруу"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Колдонуучу аныкталды"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Параметрлерден демейки кыска жазуулар колдонмосун тууралаңыз"</string>
     <string name="install_app" msgid="5066668100199613936">"Колдонмону орнотуу"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Тышкы экранга чыгарасызбы?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ички экраныңыз башка экранга чыгарылат. Алдыңкы экраныңыз өчүрүлөт."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Тышкы экран"</string>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index cfb4017..55606aa 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -86,8 +86,8 @@
     <!-- Power Menu Lite -->
     <!-- These values are for small screen landscape. For larger landscape screens, they are
          overlaid -->
-    <dimen name="global_actions_button_size">72dp</dimen>
-    <dimen name="global_actions_button_padding">26dp</dimen>
+    <dimen name="global_actions_button_size">64dp</dimen>
+    <dimen name="global_actions_button_padding">20dp</dimen>
 
     <!-- scroll view the size of 2 channel rows -->
     <dimen name="notification_blocker_channel_list_height">128dp</dimen>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index c0c5d44..1f0fbe4 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"ບໍ່ສາມາດຕັ້ງຄ່າການປົດລັອກດ້ວຍໜ້າໄດ້. ກະລຸນາເຂົ້າໄປການຕັ້ງຄ່າເພື່ອລອງໃໝ່."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"ແຕະໃສ່ເຊັນເຊີລາຍນິ້ວມື"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"ກົດໄອຄອນປົດລັອກເພື່ອສືບຕໍ່"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"ບໍ່ສາມາດຈຳແນກໜ້າໄດ້. ກະລຸນາໃຊ້ລາຍນິ້ວມືແທນ."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"ບໍ່ສາມາດຈຳແນກໜ້າໄດ້. ໃຊ້ລາຍນິ້ວມືແທນ."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"ບໍ່ສາມາດຈຳແນກໃບໜ້າໄດ້"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"ບໍ່ສາມາດຈຳແນກໜ້າໄດ້"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ກະລຸນາໃຊ້ລາຍນິ້ວມືແທນ"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"ໃຊ້ການປົດລັອກດ້ວຍໜ້າບໍ່ໄດ້"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ເຊື່ອມຕໍ່ Bluetooth ແລ້ວ."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ກຳລັງສາກໄຟ • ຈະເຕັມໃນອີກ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"ປັດຊ້າຍເພື່ອເລີ່ມບົດແນະນຳສ່ວນກາງ"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ເປີດຕົວແກ້ໄຂວິດເຈັດ"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"ປັບແຕ່ງ"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"ປ່ອຍ​ໄປ"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"ເພີ່ມ, ລຶບອອກ ແລະ ຈັດຮຽງວິດເຈັດຂອງທ່ານຄືນໃໝ່ໃນພື້ນທີ່ນີ້"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ເພີ່ມວິດເຈັດເພີ່ມເຕີມ"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ກົດຄ້າງໄວ້ເພື່ອປັບແຕ່ງວິດເຈັດ"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ລຶບອອກ"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ເພີ່ມວິດເຈັດ"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"ແລ້ວໆ"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"ເປີດ​ໃຊ້ Bluetooth ບໍ່?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"ເພື່ອ​ເຊື່ອມ​ຕໍ່​ແປ້ນ​ພິມ​ຂອງ​ທ່ານ​ກັບ​ແທັບ​ເລັດ​ຂອງ​ທ່ານ, ກ່ອນ​ອື່ນ​ໝົດ​ທ່ານ​ຕ້ອງ​ເປີດ Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"ເປີດ​"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"ການຄວບຄຸມການແຈ້ງເຕືອນ"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ເປີດ - ອ້າງອີງໃບໜ້າ"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"ດ້ວຍການຄວບຄຸມການແຈ້ງເຕືອນ, ທ່ານສາມາດຕັ້ງລະດັບຄວາມສຳຄັນຈາກ 0 ຮອດ 5 ໃຫ້ກັບການແຈ້ງເຕືອນແອັບໃດໜຶ່ງໄດ້. \n\n"<b>"ລະດັບ 5"</b>" \n- ສະແດງຢູ່ເທິງສຸດຂອງລາຍການແຈ້ງເຕືອນ \n- ອະນຸຍາດໃຫ້ຂັດຈັງຫວະຕອນເປີດເຕັມຈໍ \n- ແນມເບິ່ງທຸກເທື່ອ \n\n"<b>"ລະດັບ 4"</b>" \n- ກັນບໍ່ໃຫ້ຂັດຈັງຫວະຕອນເປີດເຕັມຈໍ \n- ແນມເບິ່ງທຸກເທື່ອ \n\n"<b>"ລະດັບ 3"</b>" \n- ກັນບໍ່ໃຫ້ຂັດຈັງຫວະຕອນເປີດເຕັມຈໍ \n- ບໍ່ແນມເບິ່ງ \n\n"<b>"ລະດັບ 2"</b>" \n- ກັນບໍ່ໃຫ້ຂັດຈັງຫວະຕອນເປີດເຕັມຈໍ \n- ບໍ່ແນມເບິ່ງ \n- ບໍ່ມີສຽງ ແລະ ບໍ່ມີການສັ່ນເຕືອນ \n\n"<b>"ລະດັບ 1"</b>" \n- ກັນບໍ່ໃຫ້ຂັດຈັງຫວະຕອນເປີດເຕັມຈໍ \n- ບໍ່ແນມເບິ່ງ \n- ບໍ່ມີສຽງ ແລະ ບໍ່ມີການສັ່ນເຕືອນ \n- ເຊື່ອງຈາກໜ້າຈໍລັອກ ແລະ ແຖບສະຖານະ \n- ສະແດງຢູ່ລຸ່ມສຸດຂອງລາຍການແຈ້ງເຕືອນ \n\n"<b>"ລະດັບ 0"</b>" \n- ປິດກັ້ນການແຈ້ງເຕືອນທັງໝົດຈາກແອັບ"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"ແລ້ວໆ"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"ນຳໃຊ້"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"ປິດການແຈ້ງເຕືອນ"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"ຕັ້ງຄ່າ"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"ບ່ອນເກັບຂໍ້ມູນ"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"ຄຳໃບ້"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"ການຊ່ວຍເຂົ້າເຖິງ"</string>
     <string name="instant_apps" msgid="8337185853050247304">"ອິນສະແຕນແອັບ"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> ກຳລັງເຮັດວຽກຢູ່"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"ເປີດແອັບໂດຍບໍ່ມີການຕິດຕັ້ງແລ້ວ."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ແຕະເພື່ອເປີດຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງ. ປັບແຕ່ງ ຫຼື ປ່ຽນປຸ່ມນີ້ໃນການຕັ້ງຄ່າ.\n\n"<annotation id="link">"ເບິ່ງການຕັ້ງຄ່າ"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ຍ້າຍປຸ່ມໄປໃສ່ຂອບເພື່ອເຊື່ອງມັນຊົ່ວຄາວ"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"ຍົກເລີກ"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"ປຸ່ມການຊ່ວຍເຂົ້າເຖິງຖືກເຊື່ອງໄວ້"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"ແຕະເພື່ອສະແດງປຸ່ມການຊ່ວຍເຂົ້າເຖິງ"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"ລຶບທາງລັດ <xliff:g id="FEATURE_NAME">%s</xliff:g> ອອກແລ້ວ"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{ລຶບ # ທາງລັດອອກແລ້ວ}other{ລຶບ # ທາງລັດອອກແລ້ວ}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ຍ້າຍຊ້າຍເທິງ"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"ກວດພົບຕົວຕົນຜູ້ໃຊ້"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ຕັ້ງຄ່າແອັບຈົດບັນທຶກເລີ່ມຕົ້ນໃນການຕັ້ງຄ່າ"</string>
     <string name="install_app" msgid="5066668100199613936">"ຕິດຕັ້ງແອັບ"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ສາຍໃສ່ຈໍສະແດງຜົນພາຍນອກບໍ?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ການສະແດງຜົນທາງໃນຂອງທ່ານຈະຖືກສະທ້ອນ. ການສະແດງຜົນທາງໜ້າຂອງທ່ານຈະຖືກປິດໄວ້."</string>
     <string name="mirror_display" msgid="2515262008898122928">"ຈໍສະແດງຜົນແບບສະທ້ອນ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 83d2724..df88f0a 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Nepavyko nustatyti atrakinimo pagal veidą. Eikite į skiltį „Nustatymai“ ir bandykite dar kartą."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Palieskite piršto antspaudo jutiklį"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Tęskite paspaudę atrakinimo piktogramą"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Veidas neatpažintas. Naudokite kontrolinį kodą."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Veidas neatpažintas. Naudokite piršto antspaudą."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Veidas neatpažintas"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Veidas neatpažintas"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Naudoti piršto antspaudą"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Atrakinimo pagal veidą funkcija nepasiekiama"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"„Bluetooth“ prijungtas."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Įkraunama • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> iki visiško įkrovimo"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Perbraukite kairėn, paleistumėte bendruomenės mokomąją medžiagą"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Atidaryti valdiklio redagavimo programą"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Tinkinti"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Atsisakyti"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Šioje erdvėje pridėkite valdiklių, juos pašalinkite ir keiskite jų tvarką"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Pridėkite daugiau valdiklių"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Ilgai paspauskite, kad tinkintumėte valdiklius"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Pašalinti"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Pridėti valdiklį"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Atlikta"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Įjungti „Bluetooth“?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Kad galėtumėte prijungti klaviatūrą prie planšetinio kompiuterio, pirmiausia turite įjungti „Bluetooth“."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Įjungti"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Galingi pranešimų valdikliai"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Įjungta – pagal veidą"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Naudodami pranešimų valdiklius galite nustatyti programos pranešimų svarbos lygį nuo 0 iki 5. \n\n"<b>"5 lygis"</b>" \n– Rodyti pranešimų sąrašo viršuje \n– Leisti pertraukti, kai veikia viso ekrano režimas \n– Visada rodyti pranešimus \n\n"<b>"4 lygis"</b>" \n– Neleisti pertraukti viso ekrano režimo \n– Visada rodyti pranešimus \n\n"<b>"3 lygis"</b>" \n– Neleisti pertraukti viso ekrano režimo \n– Niekada nerodyti pranešimų \n\n"<b>"2 lygis"</b>" \n– Neleisti pertraukti viso ekrano režimo \n– Niekada nerodyti pranešimų \n– Niekada neleisti garso ir nevibruoti \n\n"<b>"1 lygis"</b>" \n– Neleisti pertraukti viso ekrano režimo \n– Niekada nerodyti pranešimų \n– Niekada neleisti garso ir nevibruoti \n– Slėpti užrakinimo ekrane ir būsenos juostoje \n– Rodyti pranešimų sąrašo apačioje \n\n"<b>"0 lygis"</b>" \n– Blokuoti visus programos pranešimus"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Atlikta"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Taikyti"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Išjungti pranešimus"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Sąranka"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Saugykla"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Užuominos"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Pritaikomumas"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Akimirksniu įkeliamos programos"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"Programa „<xliff:g id="APP">%1$s</xliff:g>“ paleista"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Programa atidaryta jos neįdiegus."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Palietę atidarykite pritaikymo neįgaliesiems funkcijas. Tinkinkite arba pakeiskite šį mygtuką nustatymuose.\n\n"<annotation id="link">"Žr. nustatymus"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Perkelkite mygtuką prie krašto, kad laikinai jį paslėptumėte"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Anuliuoti"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Pritaikomumo mygtukas paslėptas"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Palieskite, kad būtų rodomas pritaikomumo mygtukas"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Pašalintas spart. klavišas „<xliff:g id="FEATURE_NAME">%s</xliff:g>“"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Pašalintas # spartusis klavišas}one{Pašalintas # spartusis klavišas}few{Pašalinti # spartieji klavišai}many{Pašalinta # sparčiojo klavišo}other{Pašalinta # sparčiųjų klavišų}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Perkelti į viršų kairėje"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Aptikta naudotojo veikla"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nustatykite numatytąją užrašų programą Nustatymuose"</string>
     <string name="install_app" msgid="5066668100199613936">"Įdiegti programą"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Bendrinti ekrano vaizdą išoriniame ekrane?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Bus bendrinamas vidinio rodinio ekrano vaizdas. Priekinis rodinys bus išjungtas."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Bendrinti ekrano vaizdą"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 4bc71c3..7139fdb 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Nevarēja iestatīt autorizāciju pēc sejas. Atveriet iestatījumus, lai mēģinātu vēlreiz."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Pieskarieties pirksta nospieduma sensoram"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Lai turpinātu, nospiediet atbloķēšanas ikonu."</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Nevar atpazīt seju. Lietojiet pirksta nospiedumu."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Seja netika atpazīta. Lietojiet pirksta nospiedumu."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Nevar atpazīt seju"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Seja netika atpazīta."</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Lietot pirksta nospiedumu"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Autorizācija pēc sejas nav pieejama"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth savienojums ir izveidots."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Notiek uzlāde • Laiks līdz pilnai uzlādei: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Velciet pa kreisi, lai palaistu kopienas pamācību."</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Atvērt logrīku redaktoru"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Pielāgot"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Nerādīt"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Šajā vietā varat pievienot, noņemt un pārkārtot logrīkus"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Pievienot citus logrīkus"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Nospiediet un turiet, lai pielāgotu logrīkus."</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Noņemt"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Pievienot logrīku"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Gatavs"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Vai ieslēgt Bluetooth savienojumu?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Lai pievienotu tastatūru planšetdatoram, vispirms ir jāieslēdz Bluetooth savienojums."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Ieslēgt"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Barošanas paziņojumu vadīklas"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Ieslēgta — ar sejas noteikšanu"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Izmantojot barošanas paziņojumu vadīklas, varat lietotnes paziņojumiem iestatīt svarīguma līmeni (no 0 līdz 5). \n\n"<b>"5. līmenis"</b>" \n- Tiek rādīts paziņojumu saraksta augšdaļā \n- Tiek atļauta pilnekrāna režīma pārtraukšana \n- Ieskats vienmēr atļauts \n\n"<b>"4. līmenis"</b>" \n- Tiek novērsta pilnekrāna režīma pārtraukšana \n- Ieskats vienmēr atļauts \n\n"<b>"3. līmenis"</b>" \n- Tiek novērsta pilnekrāna režīma pārtraukšana \n- Ieskats nav atļauts \n\n"<b>"2. līmenis"</b>" \n- Tiek novērsta pilnekrāna režīma pārtraukšana \n- Ieskats nav atļauts \n- Nav atļautas skaņas un vibrosignāls \n\n"<b>"1. līmenis"</b>" \n- Tiek novērsta pilnekrāna režīma pārtraukšana \n- Ieskats nav atļauts \n- Nav atļautas skaņas un vibrosignāls \n- Paziņojumi tiek paslēpti bloķēšanas ekrānā un statusa joslā \n- Paziņojumi tiek rādīti paziņojumu saraksta apakšdaļā \n\n"<b>"0. līmenis"</b>" \n- Visi lietotnes paziņojumi tiek bloķēti"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Gatavs"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Lietot"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Izslēgt paziņojumus"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Iestatīšana"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Krātuve"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Padomi"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Pieejamība"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Tūlītējās lietotnes"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"Lietotne <xliff:g id="APP">%1$s</xliff:g> darbojas"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Lai atvērtu šo lietotni, tā nav jāinstalē."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Atveriet pieejamības funkcijas. Pielāgojiet vai aizstājiet šo pogu iestatījumos.\n\n"<annotation id="link">"Skatīt iestatījumus"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Lai īslaicīgi paslēptu pogu, pārvietojiet to uz malu"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Atsaukt"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Pieejamības poga ir paslēpta"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Pieskarieties, lai tiktu parādīta pieejamības poga."</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Noņemts īsinājumtaustiņš <xliff:g id="FEATURE_NAME">%s</xliff:g>"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Noņemts # īsinājumtaustiņš}zero{Noņemti # īsinājumtaustiņi}one{Noņemts # īsinājumtaustiņš}other{Noņemti # īsinājumtaustiņi}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Pārvietot augšpusē pa kreisi"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Konstatēta lietotāja klātbūtne"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Iestatījumos iestatiet noklusējuma piezīmju lietotni."</string>
     <string name="install_app" msgid="5066668100199613936">"Instalēt lietotni"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vai spoguļot ārējā displejā?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Jūsu iekšējais displejs tiks spoguļots. Jūsu priekšējais displejs tiks izslēgts."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Spoguļot displeju"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 28a2a02..26e4caa 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Не можеше да се постави „Отклучување со лик“. Отворете „Поставки“ за да се обидете повторно."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Допрете го сензорот за отпечатоци"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Притиснете ја иконата за отклучување за да продолжите"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Не се препознава ликот. Користете отпечаток."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Ликот не е препознаен. Користете отпечаток."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Не се препознава ликот"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Ликот не е препознаен"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Користи отпечаток"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"„Отклучувањето со лик“ е недостапно"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth е поврзан."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Се полни • Полна по <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Повлечете налево за да го започнете заедничкото упатство"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Го отвора уредникот на виџети"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Приспособете"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Отфрли"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Додајте, отстранете и прераспоредете ги виџетите во просторов"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Додајте повеќе виџети"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Притиснете долго за да ги приспособите виџетите"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Отстранува"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Додајте виџет"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Готово"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Да се вклучи Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"За да ја поврзете тастатурата со таблетот, најпрво треба да вклучите Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Вклучи"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Контроли за известувањата за напојување"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Вклучено - според лице"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Со контролите за известувањата за напојување, може да поставите ниво на важност од 0 до 5 за известувањата на која било апликација. \n\n"<b>"Ниво 5"</b>" \n- Прикажувај на врвот на списокот со известувања \n- Дозволи прекин во цел екран \n- Секогаш користи појавување \n\n"<b>"Ниво 4"</b>" \n- Спречи прекин во цел екран \n- Секогаш користи појавување \n\n"<b>"Ниво 3"</b>" \n- Спречи прекин во цел екран \n- Без појавување \n\n"<b>"Ниво 2"</b>" \n- Спречи прекин во цел екран \n- Без појавување \n- Без звук и вибрации \n\n"<b>"Ниво 1"</b>" \n- Спречи прекин во цел екран \n- Без појавување \n- Без звук и вибрации \n- Скриј од заклучен екран и статусна лента \n- Прикажувај на дното на списокот со известувања \n\n"<b>"Ниво 0"</b>" \n- Блокирај ги сите известувања од апликацијата"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Готово"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Примени"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Исклучи известувања"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Поставување"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Капацитет"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Совети"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Пристапност"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Инстант апликации"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"Се извршува <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Апликацијата беше отворена без да се инсталира."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Допрете за функциите за пристапност. Приспособете или заменете го копчево во „Поставки“.\n\n"<annotation id="link">"Прикажи поставки"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Преместете го копчето до работ за да го сокриете привремено"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Врати"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Копчето за пристапност е скриено"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Допрете за да се прикаже копчето за пристапност"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Кратенката за „<xliff:g id="FEATURE_NAME">%s</xliff:g>“ е отстранета"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Отстранета е # кратенка}one{Отстранети се # кратенка}other{Отстранети се # кратенки}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Премести горе лево"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Откриено е присуство на корисник"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Поставете стандардна апликација за белешки во „Поставки“"</string>
     <string name="install_app" msgid="5066668100199613936">"Инсталирајте ја апликацијата"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Да се преслика на надворешниот екран?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Вашиот внатрешен екран ќе се отслика. Вашиот преден екран ќе се исклучи."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Пресликај екран"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 057f0b0..181e2fd 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"ഫെയ്‌സ് അൺലോക്ക് സജ്ജീകരിക്കാനായില്ല. വീണ്ടും ശ്രമിക്കാൻ ക്രമീകരണത്തിലേക്ക് പോകുക."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"ഫിംഗർപ്രിന്റ് സെൻസർ സ്‌പർശിക്കുക"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"തുടരാൻ അൺലോക്ക് ഐക്കൺ അമർത്തുക"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"മുഖം തിരിച്ചറിയാനായില്ല. പകരം ഫിംഗർപ്രിന്റ് ഉപയോഗിക്കൂ."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"മുഖം തിരിച്ചറിഞ്ഞിട്ടില്ല. പകരം ഫിംഗർപ്രിന്റ് ഉപയോഗിക്കൂ."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"മുഖം തിരിച്ചറിയാനാകുന്നില്ല"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"മുഖം തിരിച്ചറിഞ്ഞിട്ടില്ല"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"പകരം ഫിംഗർപ്രിന്റ് ഉപയോഗിക്കൂ"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"ഫെയ്‌സ് അൺലോക്ക് ലഭ്യമല്ല"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ബ്ലൂടൂത്ത് കണക്‌റ്റുചെയ്തു."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ചാർജ് ചെയ്യുന്നു • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-ൽ പൂർത്തിയാകും"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"കമ്മ്യൂണൽ ട്യൂട്ടോറിയൽ ആരംഭിക്കാൻ ഇടത്തോട്ട് സ്വൈപ്പ് ചെയ്യുക"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"വിജറ്റ് എഡിറ്റർ തുറക്കുക"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"ഇഷ്‌ടാനുസൃതമാക്കുക"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"ഡിസ്‌മിസ് ചെയ്യുക"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"ഈ സ്‌പെയ്‌സിൽ നിങ്ങളുടെ വിജറ്റുകൾ ചേർക്കുക, നീക്കം ചെയ്യുക, പുനഃക്രമീകരിക്കുക"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"കൂടുതൽ വിജറ്റുകൾ ചേർക്കുക"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"വിജറ്റുകൾ ഇഷ്ടാനുസൃതമാക്കാൻ ദീർഘനേരം അമർത്തുക"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"നീക്കം ചെയ്യുക"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"വിജറ്റ് ചേർക്കുക"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"പൂർത്തിയായി"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Bluetooth ഓണാക്കണോ?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"നിങ്ങളുടെ ടാബ്‌ലെറ്റുമായി കീബോർഡ് കണക്റ്റുചെയ്യുന്നതിന്, ആദ്യം Bluetooth ഓണാക്കേണ്ടതുണ്ട്."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"ഓണാക്കുക"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"പവർ അറിയിപ്പ് നിയന്ത്രണങ്ങൾ"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ഓണാണ് - ഫേസ് ബേസ്‌ഡ്"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"പവർ അറിയിപ്പ് നിയന്ത്രണം ഉപയോഗിച്ച്, ഒരു ആപ്പിനായുള്ള അറിയിപ്പുകൾക്ക് 0 മുതൽ 5 വരെയുള്ള പ്രാധാന്യ ലെവലുകളിലൊന്ന് നിങ്ങൾക്ക് സജ്ജമാക്കാവുന്നതാണ്. \n\n"<b>"ലെവൽ 5"</b>" \n- അറിയിപ്പ് ലിസ്റ്റിന്റെ മുകളിൽ കാണിക്കുക \n- മുഴുവൻ സ്ക്രീൻ തടസ്സം അനുവദിക്കുക \n- എല്ലായ്പ്പോഴും ദൃശ്യമാക്കുക \n\n"<b>"ലെവൽ 4"</b>" \n- മുഴുവൻ സ്ക്രീൻ തടസ്സം തടയുക \n- എല്ലായ്പ്പോഴും ദൃശ്യമാക്കുക \n\n"<b>"ലെവൽ 3"</b>" \n- മുഴുവൻ സ്ക്രീൻ തടസ്സം തടയുക \n- ഒരിക്കലും സൃശ്യമാക്കരുത് \n\n"<b>"ലെവൽ 2"</b>" \n- മുഴുവൻ സ്ക്രീൻ തടസ്സം തടയുക \n- ഒരിക്കലും ദൃശ്യമാക്കരുത് \n- ഒരിക്കലും ശബ്ദവും വൈബ്രേഷനും ഉണ്ടാക്കരുത് \n\n"<b>"ലെവൽ 1"</b>" \n- മുഴുവൻ സ്ക്രീൻ തടസ്സം തടയുക \n- ഒരിക്കലും ദൃശ്യമാക്കരുത് \n- ഒരിക്കലും ശബ്ദവും വൈബ്രേഷനും ഉണ്ടാക്കരുത് \n- ലോക്ക് സ്ക്രീനിൽ നിന്നും സ്റ്റാറ്റസ് ബാറിൽ നിന്നും മറയ്ക്കുക \n- അറിയിപ്പ് ലിസ്റ്റിന്റെ അടിയിൽ കാണിക്കുക \n\n"<b>"ലെവൽ 0"</b>" \n- ആപ്പിൽ നിന്നുള്ള എല്ലാ അറിയിപ്പുകളും ബ്ലോക്കുചെയ്യുക"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"പൂർത്തിയായി"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"ബാധകമാക്കുക"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"അറിയിപ്പുകൾ ഓഫാക്കുക"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"സജ്ജീകരണം"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"സ്റ്റോറേജ്"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"സൂചനകൾ"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"ഉപയോഗസഹായി"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> റണ്‍ ചെയ്യുന്നു"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"ഇൻസ്‌റ്റാൾ ചെയ്യാതെ ആപ്പ് തുറന്നു."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ഉപയോഗസഹായി ഫീച്ചർ തുറക്കാൻ ടാപ്പ് ചെയ്യൂ. ക്രമീകരണത്തിൽ ഈ ബട്ടൺ ഇഷ്ടാനുസൃതമാക്കാം, മാറ്റാം.\n\n"<annotation id="link">"ക്രമീകരണം കാണൂ"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"തൽക്കാലം മറയ്‌ക്കുന്നതിന് ബട്ടൺ അരുകിലേക്ക് നീക്കുക"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"പഴയപടിയാക്കുക"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"ഉപയോഗസഹായി ബട്ടൺ മറച്ചിരിക്കുന്നു"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"ഉപയോഗസഹായി ബട്ടൺ കാണിക്കുന്നതിന് ടാപ്പ് ചെയ്യുക"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> കുറുക്കുവഴി നീക്കം ചെയ്‌തു"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# കുറുക്കുവഴി നീക്കം ചെയ്‌തു}other{# കുറുക്കുവഴികൾ നീക്കം ചെയ്‌തു}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"മുകളിൽ ഇടതുഭാഗത്തേക്ക് നീക്കുക"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"ഉപയോക്താവിന്റെ സാന്നിധ്യം കണ്ടെത്തി"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ക്രമീകരണത്തിൽ കുറിപ്പുകൾക്കുള്ള ഡിഫോൾട്ട് ആപ്പ് സജ്ജീകരിക്കുക"</string>
     <string name="install_app" msgid="5066668100199613936">"ആപ്പ് ഇൻസ്റ്റാൾ ചെയ്യൂ"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ബാഹ്യ ഡിസ്‌പ്ലേയിലേക്ക് മിറർ ചെയ്യണോ?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"നിങ്ങളുടെ ഇന്നർ ഡിസ്പ്ലേ മിറർ ചെയ്യും. നിങ്ങളുടെ ഫ്രണ്ട് ഡിസ്പ്ലേ ഓഫാകും."</string>
     <string name="mirror_display" msgid="2515262008898122928">"മിറർ ഡിസ്‌പ്ലേ"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 933652b..8b8265d 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Царайгаар түгжээ тайлахыг тохируулж чадсангүй. Дахин оролдохын тулд Тохиргоо руу очно уу."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Хурууны хээ мэдрэгчид хүрэх"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Үргэлжлүүлэхийн тулд түгжээг тайлах дүрс тэмдгийг дарна уу"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Царай таних боломжгүй. Оронд нь хурууны хээ ашигла"</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Царайг таньсангүй. Оронд нь хурууны хээ ашигла."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Царайг танихгүй байна"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Царайг таньсангүй"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Оронд нь хурууны хээ ашиглах"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Царайгаар түгжээ тайлах боломжгүй"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth холбогдсон."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Цэнэглэж байна • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-н дараа дүүрнэ"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Нийтийн практик хичээлийг эхлүүлэхийн тулд зүүн тийш шударна уу"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Виджет засварлагчийг нээх"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Өөрчлөх"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Хаах"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Энэ орон зайд виджетүүдээ нэмэх, хасах болон дахин эрэмбэлэх"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Илүү олон виджет нэмэх"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Виджетүүдийг өөрчлөхийн тулд удаан дарна уу"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Хасах"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Виджет нэмэх"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Болсон"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Bluetooth-г асаах уу?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Компьютерийн гараа таблетад холбохын тулд эхлээд Bluetooth-г асаана уу."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Асаах"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Тэжээлийн мэдэгдлийн удирдлага"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Асаалттай - Царайнд суурилсан"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Тэжээлийн мэдэгдлийн удирдлагын тусламжтайгаар та апп-н мэдэгдэлд 0-5 хүртэлх ач холбогдлын түвшин тогтоох боломжтой. \n\n"<b>"5-р түвшин"</b>" \n- Мэдэгдлийн жагсаалтын хамгийн дээр харуулна \n- Бүтэн дэлгэцэд саад болно \n- Дэлгэцэд тогтмол гарч ирнэ \n\n"<b>"4-р түвшин"</b>" \n- Бүтэн дэлгэцэд саад болохоос сэргийлнэ \n- Дэлгэцэд тогтмол гарч ирнэ \n\n"<b>"3-р түвшин"</b>" \n- Бүтэн дэлгэцэд саад болохоос сэргийлнэ \n- Дэлгэцэд хэзээ ч гарч ирэхгүй \n\n"<b>"2-р түвшин"</b>" \n- Бүтэн дэлгэцэд саад болохоос сэргийлнэ \n- Дэлгэцэд хэзээ ч гарч ирэхгүй \n- Дуу болон чичиргээ хэзээ ч гаргахгүй \n\n"<b>"1-р түвшин"</b>" \n- Бүтэн дэлгэцэд саад болохоос сэргийлнэ \n- Дэлгэцэд хэзээ ч гарч ирэхгүй \n- Дуу болон чичиргээ хэзээ ч гаргахгүй \n- Түгжигдсэн дэлгэц болон статусын самбараас нууна \n- Мэдэгдлийн жагсаалтын доор харуулна \n\n"<b>"0-р түвшин"</b>" \n- Энэ апп-н бүх мэдэгдлийг блоклоно"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Болсон"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Ашиглах"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Мэдэгдлийг унтраах"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Тохируулга"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Хадгалах сан"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Заавар"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Хандалт"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Шуурхай апп"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g>-г ажиллуулж байна"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Аппыг суулгахгүйгээр нээсэн."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Хандалтын онцлогуудыг нээхийн тулд товшино уу. Энэ товчлуурыг Тохиргоо хэсэгт өөрчилж эсвэл солиорой.\n\n"<annotation id="link">"Тохиргоог харах"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Үүнийг түр нуухын тулд товчлуурыг зах руу зөөнө үү"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Болих"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Хандалтын товчлуурыг нуусан"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Хандалтын товчлуурыг харуулахын тулд товшино уу"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g>-н товчлолыг хассан"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# товчлолыг хассан}other{# товчлолыг хассан}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Зүүн дээш зөөх"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Хэрэглэгч байгааг илрүүлсэн"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Тохиргоонд тэмдэглэлийн өгөгдмөл апп тохируулна уу"</string>
     <string name="install_app" msgid="5066668100199613936">"Аппыг суулгах"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Гадны дэлгэцэд тусгал үүсгэх үү?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Таны дотоод дэлгэцийн тусгалыг үүсгэнэ. Таны урд талын дэлгэцийг унтраана."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Дэлгэцийн тусгал үүсгэх"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index aad269b..b61d408 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"फेस अनलॉक सेट करता आले नाही. सेटिंग्ज वर जा आणि पुन्हा प्रयत्न करा."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"फिंगरप्रिंट सेन्सरला स्पर्श करा"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"पुढे सुरू ठेवण्यासाठी, अनलॉक करा चा आयकन प्रेस करा"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"चेहरा ओळखू शकत नाही. त्याऐवजी फिंगरप्रिंट वापरा."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"चेहरा ओळखता आला नाही. त्याऐवजी फिंगरप्रिंट वापरा."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"चेहरा ओळखू शकत नाही"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"चेहरा ओळखता आला नाही"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"त्याऐवजी फिंगरप्रिंट वापरा"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"फेस अनलॉक उपलब्ध नाही"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लूटूथ कनेक्‍ट केले."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • चार्ज होत आहे • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मध्ये पूर्ण होईल"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"सामुदायिक ट्यूटोरियल सुरू करण्यासाठी डावीकडे स्वाइप करा"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"विजेट संपादक उघडा"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"कस्टमाइझ करा"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"डिसमिस करा"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"या स्पेसमध्ये तुमची विजेट जोडा, काढून टाका आणि पुन्हा क्रमाने लावा"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"आणखी विजेट जोडा"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"विजेट कस्टमाइझ करण्यासाठी प्रेस करून ठेवा"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"काढून टाका"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"विजेट जोडा"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"पूर्ण झाले"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"ब्लूटूथ सुरू करायचे?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"तुमचा कीबोर्ड तुमच्या टॅबलेटसह कनेक्ट करण्यासाठी, तुम्ही प्रथम ब्लूटूथ सुरू करणे आवश्यक आहे."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"सुरू करा"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"पॉवर सूचना नियंत्रणे"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"सुरू - चेहऱ्यावर आधारित"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"पॉवर सूचना नियंत्रणांच्या साहाय्याने तुम्ही अ‍ॅप सूचनांसाठी 0 ते 5 असे महत्त्व स्तर सेट करू शकता. \n\n"<b>"स्तर 5"</b>" \n- सूचना सूचीच्या शीर्षस्थानी दाखवा \n- फुल स्क्रीन व्यत्ययास अनुमती द्या \n- नेहमी डोकावून पहा \n\n"<b>"स्तर 4"</b>\n" - फुल स्क्रीन व्यत्ययास प्रतिबंधित करा \n- नेहमी डोकावून पहा \n\n"<b>"स्तर 3"</b>" \n- फुल स्क्रीन व्यत्ययास प्रतिबंधित करा \n- कधीही डोकावून पाहू नका \n\n"<b>"स्तर 2"</b>" \n- फुल स्क्रीन व्यत्ययास प्रतिबंधित करा \n- कधीही डोकावून पाहू नका \n- कधीही ध्वनी किंवा व्हायब्रेट करू नका \n\n"<b>"स्तर 1"</b>\n"- फुल स्क्रीन व्यत्ययास प्रतिबंधित करा \n- कधीही डोकावून पाहू नका \n- कधीही ध्वनी किंवा व्हायब्रेट करू नका \n- लॉक स्क्रीन आणि स्टेटस बार मधून लपवा \n- सूचना सूचीच्या तळाशी दर्शवा \n\n"<b>"स्तर 0"</b>" \n- अ‍ॅपमधील सर्व सूचना ब्लॉक करा"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"पूर्ण झाले"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"लागू करा"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"सूचना बंद करा"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"सेटअप"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"स्टोरेज"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"सूचना"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"अ‍ॅक्सेसिबिलिटी"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> रन होत आहे"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"इंस्टॉल केल्याशिवाय अ‍ॅप उघडले."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"अ‍ॅक्सेसिबिलिटी वैशिष्ट्ये उघडण्यासाठी, टॅप करा. सेटिंग्जमध्ये हे बटण कस्टमाइझ करा किंवा बदला.\n\n"<annotation id="link">"सेटिंग्ज पहा"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"बटण तात्पुरते लपवण्यासाठी ते कोपर्‍यामध्ये हलवा"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"पहिल्यासारखे करा"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"अ‍ॅक्सेसिबिलिटी बटण लपवलेले आहे"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"अ‍ॅक्सेसिबिलिटी बटण दाखवण्यासाठी टॅप करा"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> शॉर्टकट काढून टाकला"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# शॉर्टकट काढून टाकला}other{# शॉर्टकट काढून टाकले}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"वर डावीकडे हलवा"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"वापरकर्त्याची उपस्थिती डिटेक्ट केली"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"सेटिंग्ज मध्ये डीफॉल्ट टिपा अ‍ॅप सेट करा"</string>
     <string name="install_app" msgid="5066668100199613936">"अ‍ॅप इंस्टॉल करा"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"बाह्य डिस्प्लेवर मिरर करायचे आहे का?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"तुमचा अंतर्गत डिस्प्ले मिरर केला जाईल. तुमचा पुढील डिस्प्ले बंद केला जाईल."</string>
     <string name="mirror_display" msgid="2515262008898122928">"डिस्प्ले मिरर करा"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 8287d2b..86c0dd9 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Tidak dapat menyediakan buka kunci wajah. Akses Tetapan untuk mencuba lagi."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Sentuh penderia cap jari"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Tekan ikon buka kunci untuk meneruskan proses"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Tidak mengenali wajah. Gunakan cap jari."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Wajah tidak dikenali. Gunakan cap jari."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Tak dapat mengecam wajah"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Wajah tidak dikenali"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Gunakan cap jari"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Buka Kunci Wajah tidak tersedia"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth disambungkan."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengecas • Penuh dalam masa <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Leret ke kiri untuk memulakan tutorial umum"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Buka editor widget"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Sesuaikan"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Ketepikan"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Tambahkan, alih keluar dan susun semula widget anda dalam ruang ini"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Tambahkan lagi widget"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Tekan lama untuk menyesuaikan widget"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Alih keluar"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Tambahkan widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Selesai"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Hidupkan Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Untuk menyambungkan papan kekunci anda dengan tablet, anda perlu menghidupkan Bluetooth terlebih dahulu."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Hidupkan"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Kawalan pemberitahuan berkuasa"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Hidup - Berasaskan wajah"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Dengan kawalan pemberitahuan berkuasa, anda boleh menetapkan tahap kepentingan dari 0 hingga 5 untuk pemberitahuan apl. \n\n"<b>"Tahap 5"</b>" \n- Tunjukkan pada bahagian atas senarai pemberitahuan \n- Benarkan gangguan skrin penuh \n- Sentiasa intai \n\n"<b>"Tahap 4"</b>" \n- Halang gangguan skrin penuh \n- Sentiasa intai \n\n"<b>"Tahap 3"</b>" \n- Halang gangguan skrin penuh \n- Jangan intai \n\n"<b>"Tahap 2"</b>" \n- Halang gangguan skrin penuh \n- Jangan intai \n- Jangan berbunyi dan bergetar \n\n"<b>"Tahap 1"</b>" \n- Halang gangguan skrin penuh \n- Jangan intai \n- Jangan berbunyi atau bergetar \n- Sembunyikan daripada skrin kunci dan bar status \n- Tunjukkan di bahagian bawah senarai pemberitahuan \n\n"<b>"Tahap 0"</b>" \n- Sekat semua pemberitahuan daripada apl"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Selesai"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Guna"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Matikan pemberitahuan"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Persediaan"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Storan"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Pembayang"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Kebolehaksesan"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Apl Segera"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> sedang berjalan"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Apl dibuka tanpa dipasang."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Ketik untuk membuka ciri kebolehaksesan. Sesuaikan/gantikan butang ini dalam Tetapan.\n\n"<annotation id="link">"Lihat tetapan"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Gerakkan butang ke tepi untuk disembunyikan buat sementara waktu"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Buat asal"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Butang kebolehaksesan disembunyikan"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Ketik untuk memaparkan butang kebolehaksesan"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Pintasan <xliff:g id="FEATURE_NAME">%s</xliff:g> dialih keluar"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# pintasan dialih keluar}other{# pintasan dialih keluar}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Alihkan ke atas sebelah kiri"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Kehadiran pengguna dikesan"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Tetapkan apl nota lalai dalam Tetapan"</string>
     <string name="install_app" msgid="5066668100199613936">"Pasang apl"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Paparkan pada paparan luaran?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Paparan dalaman anda akan dicerminkan. Paparan depan anda akan dimatikan."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Segerakkan paparan"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 25afad4..19c9c04 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"မျက်နှာပြ လော့ခ်ဖွင့်ခြင်းကို စနစ်ထည့်သွင်း၍မရပါ။ ဆက်တင်များသို့သွားပြီး ထပ်စမ်းကြည့်ပါ။"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"လက်ဗွေအာရုံခံကိရိယာကို တို့ပါ"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"ရှေ့ဆက်ရန် လော့ခ်ဖွင့်သင်္ကေတကို နှိပ်ပါ"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"မျက်နှာကို မမှတ်မိပါ။ လက်ဗွေကို အစားထိုးသုံးပါ။"</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"မျက်နှာကို မသိရှိပါ။ လက်ဗွေကို အစားထိုးသုံးပါ။"</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"မျက်နှာကို မမှတ်မိပါ"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"မျက်နှာကို မသိရှိပါ"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"လက်ဗွေကို အစားထိုးသုံးပါ"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"မျက်နှာပြ လော့ခ်ဖွင့်ခြင်း မရနိုင်ပါ"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ဘလူးတုသ်ချိတ်ဆက်ထားမှု"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • အားသွင်းနေသည် • အားပြည့်ရန် <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> လိုသည်"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"အများသုံးရှင်းလင်းပို့ချချက် စတင်ရန် ဘယ်သို့ပွတ်ဆွဲပါ"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ဝိဂျက်တည်းဖြတ်စနစ် ဖွင့်ရန်"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"စိတ်ကြိုက်လုပ်ရန်"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"ပယ်ရန်"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"ဤနေရာတွင် သင့်ဝိဂျက်များကို ထည့်ရန်၊ ဖယ်ရှားရန်၊ ပြန်စီရန်"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"နောက်ထပ်ဝိဂျက်များ ထည့်ရန်"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ဝိဂျက်များ စိတ်ကြိုက်လုပ်ရန် ကြာကြာနှိပ်ထားပါ"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ဖယ်ရှားရန်"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ဝိဂျက်ထည့်ရန်"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"ပြီးပြီ"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"ဘလူးတုသ် ဖွင့်ရမလား။"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"ကီးဘုတ်ကို တပ်ဘလက်နှင့် ချိတ်ဆက်ရန်၊ ပမထဦးစွာ ဘလူးတုသ်ကို ဖွင့်ပါ။"</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"ဖွင့်ပါ"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"ပါဝါအကြောင်းကြားချက် ထိန်းချုပ်မှုများ"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ဖွင့် - မျက်နှာအခြေခံ"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"ပါဝါအကြောင်းကြားချက် ထိန်းချုပ်မှုများကိုအသုံးပြုပြီး အက်ပ်တစ်ခု၏ အကြောင်းကြားချက် အရေးပါမှု ၀ မှ ၅ အထိသတ်မှတ်ပေးနိုင်သည်။ \n\n"<b>"အဆင့် ၅"</b>" \n- အကြောင်းကြားချက်စာရင်း၏ ထိပ်ဆုံးတွင် ပြသည် \n- မျက်နှာပြင်အပြည့် ကြားဖြတ်ဖော်ပြခြင်းကို ခွင့်ပြုသည် \n- အမြဲတမ်း ခေတ္တပြပါမည် \n\n"<b>"အဆင့် ၄"</b>" \n- မျက်နှာပြင်အပြည့် ကြားဖြတ်ဖော်ပြခြင်း မရှိစေရန် ကာကွယ်ပေးသည် \n- အမြဲတမ်း ခေတ္တပြပါမည် \n\n"<b>"အဆင့် ၃"</b>" \n- မျက်နှာပြင်အပြည့် ကြားဖြတ်ဖော်ပြခြင်း မရှိစေရန် ကာကွယ်ပေးသည် \n- ဘယ်တော့မှ ခေတ္တပြခြင်း မရှိပါ \n\n"<b>"အဆင့် ၂"</b>" \n- မျက်နှာပြင်အပြည့် ကြားဖြတ်ဖော်ပြခြင်း မရှိစေရန် ကာကွယ်ပေးသည် \n- ဘယ်တော့မှ ခေတ္တပြခြင်း မရှိပါ \n- အသံမြည်ခြင်းနှင့် တုန်ခါခြင်းများ ဘယ်တော့မှ မပြုလုပ်ပါ \n\n"<b>"အဆင့် ၁"</b>" \n- မျက်နှာပြင်အပြည့် ကြားဖြတ်ဖော်ပြခြင်း မရှိစေရန် ကာကွယ်ပေးသည် \n- ဘယ်တော့မှ ခေတ္တပြခြင်း မရှိပါ \n- အသံမြည်ခြင်းနှင့် တုန်ခါခြင်းများ ဘယ်တော့မှ မပြုလုပ်ပါ \n- လော့ခ်ချထားသည့် မျက်နှာပြင်နှင့် အခြေအနေဘားတန်းတို့တွင် မပြပါ \n- အကြောင်းကြားချက်စာရင်း အောက်ဆုံးတွင်ပြသည် \n\n"<b>"အဆင့် ၀"</b>" \n- အက်ပ်မှ အကြောင်းကြားချက်များ အားလုံးကို ပိတ်ဆို့သည်"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"ပြီးပြီ"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"အသုံးပြုရန်"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"အကြောင်းကြားချက်များ ပိတ်ရန်"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"စနစ်ထည့်ရန်"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"သိုလှောင်ခန်း"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"အရိပ်အမြွက်များ"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"အများသုံးနိုင်မှု"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> လုပ်ဆောင်နေသည်"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"အက်ပ်ကိုမထည့်သွင်းဘဲ ဖွင့်ထားသည်။"</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှုများ ဖွင့်ရန် တို့ပါ။ ဆက်တင်များတွင် ဤခလုတ်ကို စိတ်ကြိုက်ပြင်ပါ (သို့) လဲပါ။\n\n"<annotation id="link">"ဆက်တင်များ ကြည့်ရန်"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ခလုတ်ကို ယာယီဝှက်ရန် အစွန်းသို့ရွှေ့ပါ"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"နောက်ပြန်ရန်"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"အများသုံးနိုင်မှုခလုတ်ကို ဝှက်ထားသည်"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"အများသုံးနိုင်မှုခလုတ်ပြရန် တို့ပါ"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> ဖြတ်လမ်းလင့်ခ် ဖယ်ရှားထားသည်"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{ဖြတ်လမ်းလင့်ခ် # ခု ဖယ်ရှားထားသည်}other{ဖြတ်လမ်းလင့်ခ် # ခု ဖယ်ရှားထားသည်}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ဘယ်ဘက်ထိပ်သို့ ရွှေ့ရန်"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"အသုံးပြုသူရှိကြောင်း တွေ့ရသည်"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ဆက်တင်များတွင် မူရင်းမှတ်စုများအက်ပ် သတ်မှတ်ပါ"</string>
     <string name="install_app" msgid="5066668100199613936">"အက်ပ် ထည့်သွင်းရန်"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ပြင်ပဖန်သားပြင်သို့ စကရင်ပွားမလား။"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"အတွင်းပြကွက်ကို စကရင်ပွားပါမည်။ ရှေ့မျက်နှာပြင်ပြကွက်ကို ပိတ်မည်။"</string>
     <string name="mirror_display" msgid="2515262008898122928">"ဖန်သားပြင်ကို စကရင်ပွားရန်"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index d915743..18fba66 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Kunne ikke konfigurere ansiktslåsen. Gå til innstillingene for å prøve på nytt."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Trykk på fingeravtrykkssensoren"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Trykk på lås opp-ikonet for å fortsette"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Ansiktet gjenkjennes ikke. Bruk fingeravtrykk."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Ansiktet ble ikke gjenkjent. Bruk fingeravtrykk."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Ansiktet gjenkjennes ikke"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Ansikt ikke gjenkjent"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Bruk fingeravtrykk"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Ansiktslås er utilgjengelig"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth er tilkoblet."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lader • Fulladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Sveip til venstre for å starte fellesveiledningen"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Åpne redigeringsverktøyet for moduler"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Tilpass"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Lukk"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Legg til, fjern og omorganiser modulene i dette området"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Legg til flere moduler"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Trykk lenge for å tilpasse modulene"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Fjern"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Legg til modul"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Ferdig"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Vil du slå på Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"For å koble tastaturet til nettbrettet ditt må du først slå på Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Slå på"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Effektive varselinnstillinger"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"På – ansiktsbasert"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Med effektive varselinnstillinger kan du angi viktighetsnivåer fra 0 til 5 for appvarsler. \n\n"<b>"Nivå 5"</b>" \n– Vis øverst på varsellisten \n– Tillat forstyrrelser ved fullskjermmodus \n– Vis alltid raskt \n\n"<b>"Nivå 4"</b>" \n– Forhindre forstyrrelser ved fullskjermmodus \n– Vis alltid raskt \n\n"<b>"Nivå 3"</b>" \n– Forhindre forstyrrelser ved fullskjermmodus \n– Vis aldri raskt \n\n"<b>"Nivå 2"</b>" \n– Forhindre forstyrrelser ved fullskjermmodus \n– Vis aldri fort \n– Tillat aldri lyder eller vibrering \n\n"<b>"Nivå 1"</b>" \n– Forhindre forstyrrelser ved fullskjermmodus \n– Vis aldri raskt \n– Tillat aldri lyder eller vibrering \n– Skjul fra låseskjermen og statusfeltet \n– Vis nederst på varsellisten \n\n"<b>"Nivå 0"</b>" \n– Blokkér alle varsler fra appen"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Ferdig"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Bruk"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Slå av varsler"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Konfigurering"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Lagring"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Hint"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Tilgjengelighet"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> kjører"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Appen ble åpnet uten at den ble installert."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Trykk for å åpne tilgj.funksjoner. Tilpass eller bytt knappen i Innstillinger.\n\n"<annotation id="link">"Se innstillingene"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Flytt knappen til kanten for å skjule den midlertidig"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Angre"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Tilgjengelighet-knappen er skjult"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Trykk for å vise Tilgjengelighet-knappen"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g>-snarveien er fjernet"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# snarvei er fjernet}other{# snarveier er fjernet}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Flytt til øverst til venstre"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Det er registrert at brukeren er til stede"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Du kan velge en standardapp for notater i Innstillinger"</string>
     <string name="install_app" msgid="5066668100199613936">"Installer appen"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vil du speile til en ekstern skjerm?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Den indre skjermen speiles. Den ytre skjermen slås av."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Speil skjermen"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 4abb4a4..a192c93 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"फेस अनलक सेटअप गर्न सकिएन। फेरि प्रयास गर्न सेटिङमा जानुहोस्।"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"फिंगरप्रिन्ट सेन्सरमा छुनुहोस्‌"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"जारी राख्न अनलक आइकनमा थिच्नुहोस्"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"अनुहार पहिचान गर्न सकिएन। बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्।"</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"अनुहार पहिचान गर्न सकिएन। बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्।"</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"अनुहार पहिचान गर्न सकिएन"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"अनुहार पहिचान गर्न सकिएन"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"फेस अनलक उपलब्ध छैन"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लुटुथ जडान भयो।"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • चार्ज हुँदै छ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मा फुल चार्ज हुने छ"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"कम्युनल ट्युटोरियल सुरु गर्न बायाँतिर स्वाइप गर्नुहोस्"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"विजेट एडिटर खोल्नुहोस्"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"कस्टमाइज गर्नुहोस्"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"खारेज गर्नुहोस्"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"यो स्पेसमा आफ्ना विजेटहरू हाल्नुहोस्, हटाउनुहोस् र तिनका क्रम फेरि मिलाउनुहोस्"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"थप विजेटहरू हाल्नुहोस्"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"विजेटहरू कस्टमाइज गर्न केही बेरसम्म थिच्नुहोस्"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"हटाउनुहोस्"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"विजेट हाल्नुहोस्"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"पूरा भयो"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"ब्लुटुथ सक्रिय पार्ने हो?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"आफ्नो ट्याब्लेटसँग किबोर्ड जोड्न, पहिले तपाईँले ब्लुटुथ सक्रिय गर्नुपर्छ।"</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"सक्रिय पार्नुहोस्"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"सशक्त सूचना नियन्त्रण"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"अन छ - अनुहारमा आधारित"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"सशक्त सूचना नियन्त्रणहरू मार्फत तपाईं अनुप्रयाेगका सूचनाहरूका लागि ० देखि ५ सम्मको महत्व सम्बन्धी स्तर सेट गर्न सक्नुहुन्छ। \n\n"<b>"स्तर ५"</b>" \n- सूचनाको सूचीको माथिल्लो भागमा देखाउने \n- पूर्ण स्क्रिनमा अवरोधका लागि अनुमति दिने \n- सधैँ चियाउने \n\n"<b>"स्तर ४"</b>" \n- पूर्ण स्क्रिनमा अवरोधलाई रोक्ने \n- सधैँ चियाउने \n\n"<b>"स्तर ३"</b>" \n- पूर्ण स्क्रिनमा अवरोधलाई रोक्ने \n- कहिल्यै नचियाउने \n\n"<b>"स्तर २"</b>" \n- पूर्ण स्क्रिनमा अवरोधलाई रोक्ने \n- कहिल्यै नचियाउने \n- कहिल्यै पनि आवाज ननिकाल्ने र कम्पन नगर्ने \n\n"<b>"स्तर १"</b>" \n- पूर्ण स्क्रिनमा अवरोध रोक्ने \n- कहिल्यै नचियाउने \n- कहिल्यै पनि आवाज ननिकाल्ने वा कम्पन नगर्ने \n- लक स्क्रिन र वस्तुस्थिति पट्टीबाट लुकाउने \n- सूचनाको सूचीको तल्लो भागमा देखाउने \n\n"<b>"स्तर ०"</b>" \n- एपका सबै सूचनाहरूलाई रोक्ने"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"सम्पन्न भयो"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"लागू गर्नुहोस्"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"सूचनाहरू अफ गर्नुहोस्"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"सेटअप गर्नुहोस्"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"भण्डारण"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"सङ्केतहरू"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"एक्सेसिबिलिटी"</string>
     <string name="instant_apps" msgid="8337185853050247304">"तात्कालिक एपहरू"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> चलिरहेको छ"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"स्थापना नगरिकनै एप खोलियो।"</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"सर्वसुलभता कायम गर्ने सुविधा खोल्न ट्याप गर्नुहोस्। सेटिङमा गई यो बटन कस्टमाइज गर्नुहोस् वा बदल्नुहोस्।\n\n"<annotation id="link">"सेटिङ हेर्नुहोस्"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"यो बटन केही बेर नदेखिने पार्न किनारातिर सार्नुहोस्"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"अन्डू गर्नुहोस्"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"एक्सेसिबिलिटी बटन लुकाइएको छ"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"एक्सेसिबिलिटी बटन देखाउन ट्याप गर्नुहोस्"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> सर्टकट हटाइएको छ"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# सर्टकट हटाइएको छ}other{# वटा सर्टकट हटाइएका छन्}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"सिरानको बायाँतिर सार्नुहोस्"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"प्रयोगकर्ता उपस्थित भएको कुरा पत्ता लागेको छ"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"सेटिङमा गई नोट बनाउने डिफल्ट एप तोक्नुहोस्"</string>
     <string name="install_app" msgid="5066668100199613936">"एप इन्स्टल गर्नुहोस्"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"बाह्य डिस्प्लेमा मिरर गर्ने हो?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"तपाईंको भित्री डिस्प्ले मिरर गरिने छ। तपाईंको अगाडिको डिस्प्ले अफ गरिने छ।"</string>
     <string name="mirror_display" msgid="2515262008898122928">"डिस्प्ले मिरर गर्नुहोस्"</string>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index bcc3c83..61a323d4 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -93,6 +93,8 @@
     <color name="qs_user_switcher_selected_avatar_icon_color">#202124</color>
     <!-- Color of background circle of user avatars in quick settings user switcher -->
     <color name="qs_user_switcher_avatar_background">#3C4043</color>
+    <!-- Color of border for keyguard password input when focused -->
+    <color name="bouncer_password_focus_color">@*android:color/system_secondary_dark</color>
 
     <!-- Accessibility floating menu -->
     <color name="accessibility_floating_menu_background">#B3000000</color> <!-- 70% -->
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 0576730..d518a87 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Kan ontgrendelen via gezichtsherkenning niet instellen. Ga naar Instellingen om het opnieuw te proberen."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Raak de vingerafdruksensor aan"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Druk op het ontgrendelicoon om door te gaan"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Gezicht niet herkend. Gebruik je vingerafdruk."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Gezicht niet herkend. Gebruik je vingerafdruk."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Gezicht niet herkend"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Gezicht niet herkend"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Vingerafdruk gebruiken"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Ontgrendelen via gezichtsherkenning niet beschikbaar"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-verbinding ingesteld."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Opladen • Vol over <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swipe naar links om de communitytutorial te starten"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"De widget-editor openen"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Aanpassen"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Sluiten"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Je widgets aan deze ruimte toevoegen, eruit verwijderen of opnieuw ordenen"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Meer widgets toevoegen"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Houd lang ingedrukt om widgets aan te passen"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Verwijderen"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Widget toevoegen"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Klaar"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Bluetooth aanzetten?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Als je je toetsenbord wilt verbinden met je tablet, moet je eerst Bluetooth aanzetten."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Aanzetten"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Beheeropties voor meldingen met betrekking tot stroomverbruik"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Aan: op basis van gezicht"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Met beheeropties voor meldingen met betrekking tot stroomverbruik kun je een belangrijkheidsniveau van 0 tot 5 instellen voor de meldingen van een app. \n\n"<b>"Niveau 5"</b>" \n- Bovenaan de lijst met meldingen tonen \n- Onderbreking op volledig scherm toestaan \n- Altijd korte weergave \n\n"<b>"Niveau 4"</b>" \n- Geen onderbreking op volledig scherm \n- Altijd korte weergave \n\n"<b>"Niveau 3"</b>" \n- Geen onderbreking op volledig scherm \n- Nooit korte weergave \n\n"<b>"Niveau 2"</b>" \n- Geen onderbreking op volledig scherm \n- Nooit korte weergave \n- Nooit geluid laten horen of trillen \n\n"<b>"Niveau 1"</b>" \n- Geen onderbreking op volledig scherm \n- Nooit korte weergave \n- Nooit geluid laten horen of trillen \n- Verbergen op vergrendelscherm en statusbalk \n- Onderaan de lijst met meldingen tonen \n\n"<b>"Niveau 0"</b>" \n- Alle meldingen van de app blokkeren"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Klaar"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Toepassen"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Meldingen uitzetten"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Instellen"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Opslag"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Hints"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Toegankelijkheid"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant-apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> actief"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"App geopend zonder dat deze is geïnstalleerd."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tik voor toegankelijkheidsfuncties. Wijzig of vervang deze knop via Instellingen.\n\n"<annotation id="link">"Naar Instellingen"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Knop naar de rand verplaatsen om deze tijdelijk te verbergen"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Ongedaan maken"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Knop Toegankelijkheid verborgen"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Tik om de knop Toegankelijkheid te tonen"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Snelkoppeling voor <xliff:g id="FEATURE_NAME">%s</xliff:g> verwijderd"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# snelkoppeling verwijderd}other{# snelkoppelingen verwijderd}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Naar linksboven verplaatsen"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Gebruikersaanwezigheid is waargenomen"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standaard notitie-app instellen in Instellingen"</string>
     <string name="install_app" msgid="5066668100199613936">"App installeren"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Spiegelen naar extern scherm?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Het scherm aan de binnenkant wordt gemirrord. Het scherm aan de voorkant wordt uitgezet."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Scherm spiegelen"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index cb58e64..38d0bd9 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"ଫେସ ଅନଲକ ସେଟ ଅପ କରାଯାଇପାରିଲା ନାହିଁ। ପୁଣି ଚେଷ୍ଟା କରିବା ପାଇଁ ସେଟିଂସକୁ ଯାଆନ୍ତୁ।"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"ଟିପଚିହ୍ନ ସେନସର୍‌କୁ ଛୁଅଁନ୍ତୁ"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"ଜାରି ରଖିବାକୁ ଅନଲକ ଆଇକନ ଦବାନ୍ତୁ"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"ଫେସ୍ ଚିହ୍ନଟ କରିହେବ ନାହିଁ। ଟିପଚିହ୍ନ ବ୍ୟବହାର କରନ୍ତୁ।"</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"ଫେସ ଚିହ୍ନଟ କରାଯାଇନାହିଁ। ଟିପଚିହ୍ନ ବ୍ୟବହାର କରନ୍ତୁ।"</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"ଫେସ ଚିହ୍ନଟ ହୋଇପାରିବ ନାହିଁ"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"ଫେସ ଚିହ୍ନଟ କରାଯାଇନାହିଁ"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ଟିପଚିହ୍ନ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"ଫେସ ଅନଲକ ଉପଲବ୍ଧ ନାହିଁ"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ବ୍ଲୁଟୂଥ୍‍‌ ସଂଯୋଗ କରାଯାଇଛି।"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ଚାର୍ଜ ହେଉଛି • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ରେ ସମ୍ପୂର୍ଣ୍ଣ ହେବ"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"କମ୍ୟୁନାଲ ଟ୍ୟୁଟୋରିଆଲ ଆରମ୍ଭ କରିବା ପାଇଁ ବାମକୁ ସ୍ୱାଇପ କରନ୍ତୁ"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ୱିଜେଟ ଏଡିଟର ଖୋଲନ୍ତୁ"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"କଷ୍ଟମାଇଜ କରନ୍ତୁ"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"ଖାରଜ କରନ୍ତୁ"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"ଏହି ସ୍ପେସରେ ଆପଣଙ୍କ ୱିଜେଟଗୁଡ଼ିକୁ ଯୋଗ କରନ୍ତୁ, କାଢ଼ି ଦିଅନ୍ତୁ ଏବଂ ରିଅର୍ଡର କରନ୍ତୁ"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ଅଧିକ ୱିଜେଟ ଯୋଗ କରନ୍ତୁ"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ୱିଜେଟଗୁଡ଼ିକୁ କଷ୍ଟମାଇଜ କରିବା ପାଇଁ ଅଧିକ ସମୟ ଦବାନ୍ତୁ"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"କାଢ଼ି ଦିଅନ୍ତୁ"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ୱିଜେଟ ଯୋଗ କରନ୍ତୁ"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"ହୋଇଗଲା"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"ବ୍ଲୁଟୂଥ୍‍‍ ଚାଲୁ କରିବେ?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"ଆପଣଙ୍କ ଟାବଲେଟ୍‌ରେ କୀ’ବୋର୍ଡ ସଂଯୋଗ କରିବା ପାଇଁ ଆପଣଙ୍କୁ ପ୍ରଥମେ ବ୍ଲୁଟୂଥ୍‍‍ ଅନ୍‍ କରିବାକୁ ହେବ।"</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"ଚାଲୁ କରନ୍ତୁ"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"ପାୱାର୍‍ ବିଜ୍ଞପ୍ତି କଣ୍ଟ୍ରୋଲ୍‌"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ଚାଲୁ ଅଛି - ଫେସ-ଆଧାରିତ"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"ପାୱାର୍‍ ବିଜ୍ଞପ୍ତି କଣ୍ଟ୍ରୋଲ୍‌ରେ, ଆପଣ ଏକ ଆପ୍‍ ବିଜ୍ଞପ୍ତି ପାଇଁ 0 ରୁ 5 ଗୁରୁତ୍ୱ ସ୍ତର ସେଟ୍‍ କରିହେବେ। \n\n"<b>"ସ୍ତର 5"</b>" \n- ବିଜ୍ଞପ୍ତି ତାଲିକାର ଶୀର୍ଷରେ ଦେଖାନ୍ତୁ \n- ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନରେ ବାଧା ଦେବାକୁ ଅନୁମତି ଦିଅନ୍ତୁ \n- ସର୍ବଦା ପିକ୍‍ କରନ୍ତୁ \n\n"<b>"ସ୍ତର 4"</b>" \n- ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନରେ ବାଧା ଦେବା ବ୍ଲକ୍‌ କରନ୍ତୁ \n- ସର୍ବଦା ପିକ୍‍ କରନ୍ତୁ \n\n"<b>"ସ୍ତର 3"</b>" \n- ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନରେ ବାଧା ଦେବା ବ୍ଲକ୍‌ କରନ୍ତୁ \n- କଦାପି ପିକ୍‍ କରନ୍ତୁ ନାହିଁ \n\n"<b>"ସ୍ତର 2"</b>" \n- ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନରେ ବାଧା ଦେବା ବ୍ଲକ୍‌ କରନ୍ତୁ \n- କଦାପି ପିକ୍‍ କରନ୍ତୁ ନାହିଁ \n- କଦାପି ସାଉଣ୍ଡ ଓ ଭାଇବ୍ରେଟ୍‍ କରନ୍ତୁ ନାହିଁ \n\n"<b>"ସ୍ତର 1"</b>" \n- ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନରେ ବାଧା ଦେବା ବ୍ଲକ୍‌ କରନ୍ତୁ \n- କଦାପି ପିକ୍‍ କରନ୍ତୁ ନାହିଁ \n- କଦାପି ସାଉଣ୍ଡ ଓ ଭାଇବ୍ରେଟ୍‍ କରନ୍ତୁ ନାହିଁ \n- ଲକ୍‍ ସ୍କ୍ରୀନ୍‍ ଓ ଷ୍ଟାଟସ୍‍ ବାର୍‌ରୁ ଲୁଚାନ୍ତୁ \n- ବିଜ୍ଞପ୍ତି ତାଲିକାର ନିମ୍ନରେ ଦେଖାନ୍ତୁ \n\n"<b>"ସ୍ତର 0"</b>" \n- ଆପରୁ ସମସ୍ତ ବିଜ୍ଞପ୍ତି ବ୍ଲକ୍‌ କରନ୍ତୁ"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"ହୋଇଗଲା"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"ଲାଗୁ କରନ୍ତୁ"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"ବିଜ୍ଞପ୍ତି ବନ୍ଦ କରନ୍ତୁ"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"ସେଟଅପ"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"ଷ୍ଟୋରେଜ"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"ହିଣ୍ଟ"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"ଆକ୍ସେସିବିଲିଟୀ"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> ଚାଲୁଛି"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"ଇନ୍‍ଷ୍ଟଲ୍‍ ନହୋଇ ଆପ୍‍ ଖୋଲିଛି।"</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ଆକ୍ସେସିବିଲିଟୀ ଫିଚର ଖୋଲିବାକୁ ଟାପ କରନ୍ତୁ। ସେଟିଂସରେ ଏହି ବଟନକୁ କଷ୍ଟମାଇଜ କର କିମ୍ବା ବଦଳାଅ।\n\n"<annotation id="link">"ସେଟିଂସ ଦେଖନ୍ତୁ"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ବଟନକୁ ଅସ୍ଥାୟୀ ଭାବେ ଲୁଚାଇବା ପାଇଁ ଏହାକୁ ଗୋଟିଏ ଧାରକୁ ମୁଭ୍ କରନ୍ତୁ"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"ପୂର୍ବବତ୍ କରନ୍ତୁ"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"ଆକ୍ସେସିବିଲିଟୀ ବଟନକୁ ଲୁଚାଯାଇଛି"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"ଆକ୍ସେସିବିଲିଟୀ ବଟନ ଦେଖାଇବାକୁ ଟାପ କରନ୍ତୁ"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> ସର୍ଟକଟକୁ କାଢ଼ି ଦିଆଯାଇଛି"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{#ଟି ସର୍ଟକଟକୁ କାଢ଼ି ଦିଆଯାଇଛି}other{#ଟି ସର୍ଟକଟକୁ କାଢ଼ି ଦିଆଯାଇଛି}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ଶୀର୍ଷ ବାମକୁ ମୁଭ୍ କରନ୍ତୁ"</string>
@@ -1082,7 +1092,7 @@
     <string name="empty_user_name" msgid="3389155775773578300">"ସାଙ୍ଗମାନେ"</string>
     <string name="empty_status" msgid="5938893404951307749">"ଆଜି ରାତି ଚାଟ କରିବା!"</string>
     <string name="status_before_loading" msgid="1500477307859631381">"ବିଷୟବସ୍ତୁ ଶୀଘ୍ର ଦେଖାଯିବ"</string>
-    <string name="missed_call" msgid="4228016077700161689">"ମିସ୍ଡ କଲ୍"</string>
+    <string name="missed_call" msgid="4228016077700161689">"ମିସ୍ଡ କଲ"</string>
     <string name="messages_count_overflow_indicator" msgid="7850934067082006043">"<xliff:g id="NUMBER">%d</xliff:g>+"</string>
     <string name="people_tile_description" msgid="8154966188085545556">"ବର୍ତ୍ତମାନର ମେସେଜ, ମିସ୍ଡ କଲ ଏବଂ ସ୍ଥିତି ଅପଡେଟଗୁଡ଼ିକୁ ଦେଖନ୍ତୁ"</string>
     <string name="people_tile_title" msgid="6589377493334871272">"ବାର୍ତ୍ତାଳାପ"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"ୟୁଜରଙ୍କ ଉପସ୍ଥିତି ଚିହ୍ନଟ କରାଯାଇଛି"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ସେଟିଂସରେ ଡିଫଲ୍ଟ ନୋଟ୍ସ ଆପ ସେଟ କରନ୍ତୁ"</string>
     <string name="install_app" msgid="5066668100199613936">"ଆପ ଇନଷ୍ଟଲ କରନ୍ତୁ"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ଏକ୍ସଟର୍ନଲ ଡିସପ୍ଲେକୁ ମିରର କରିବେ?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ଆପଣଙ୍କ ଇନର ଡିସପ୍ଲେକୁ ମିରର କରାଯିବ। ଆପଣଙ୍କ ଫ୍ରଣ୍ଟ ଡିସପ୍ଲେକୁ ବନ୍ଦ କରାଯିବ।"</string>
     <string name="mirror_display" msgid="2515262008898122928">"ଡିସପ୍ଲେ ମିରର କରନ୍ତୁ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 0340bd1..fdbc0e5 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"ਫ਼ੇਸ ਅਣਲਾਕ ਦਾ ਸੈੱਟਅੱਪ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰਨ ਲਈ ਸੈਟਿੰਗਾਂ \'ਤੇ ਜਾਓ।"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"ਫਿੰਗਰਪ੍ਰਿੰਟ ਸੈਂਸਰ ਨੂੰ ਸਪੱਰਸ਼ ਕਰੋ"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"ਜਾਰੀ ਰੱਖਣ ਲਈ \'ਅਣਲਾਕ ਕਰੋ\' ਪ੍ਰਤੀਕ ਨੂੰ ਦਬਾਓ"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"ਚਿਹਰਾ ਨਹੀਂ ਪਛਾਣ ਸਕਦੇ। ਇਸਦੀ ਬਜਾਏ ਫਿੰਗਰਪ੍ਰਿੰਟ ਵਰਤੋ।"</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"ਚਿਹਰੇ ਦੀ ਪਛਾਣ ਨਹੀਂ ਹੋਈ। ਇਸਦੀ ਬਜਾਏ ਫਿੰਗਰਪ੍ਰਿੰਟ ਵਰਤੋ।"</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"ਚਿਹਰੇ ਦੀ ਪਛਾਣ ਨਹੀਂ ਹੋਈ"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"ਚਿਹਰੇ ਦੀ ਪਛਾਣ ਨਹੀਂ ਹੋਈ"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ਇਸਦੀ ਬਜਾਏ ਫਿੰਗਰਪ੍ਰਿੰਟ ਵਰਤੋ"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"ਫ਼ੇਸ ਅਣਲਾਕ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ਕਨੈਕਟ ਕੀਤੀ।"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ਵਿੱਚ ਪੂਰਾ ਚਾਰਜ ਹੋਵੇਗਾ"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"ਭਾਈਚਾਰਕ ਟਿਊਟੋਰੀਅਲ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਖੱਬੇ ਪਾਸੇ ਵੱਲ ਸਵਾਈਪ ਕਰੋ"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ਵਿਜੇਟ ਸੰਪਾਦਕ ਖੋਲ੍ਹੋ"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"ਵਿਉਂਤਬੱਧ ਕਰੋ"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"ਖਾਰਜ ਕਰੋ"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"ਇਸ ਜਗ੍ਹਾ ਵਿੱਚ ਆਪਣੇ ਵਿਜੇਟ ਸ਼ਾਮਲ ਕਰੋ, ਹਟਾਓ ਅਤੇ ਮੁੜ-ਕ੍ਰਮਬੱਧ ਕਰੋ"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ਹੋਰ ਵਿਜੇਟ ਸ਼ਾਮਲ ਕਰੋ"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ਵਿਜੇਟਾਂ ਨੂੰ ਵਿਉਂਤਬੱਧ ਕਰਨ ਲਈ ਦਬਾਈ ਰੱਖੋ"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ਹਟਾਓ"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ਵਿਜੇਟ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"ਹੋ ਗਿਆ"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Bluetooth ਚਾਲੂ ਕਰੋ?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"ਆਪਣੇ ਟੈਬਲੈੱਟ ਨਾਲ ਆਪਣਾ ਕੀ-ਬੋਰਡ ਕਨੈਕਟ ਕਰਨ ਲਈ, ਤੁਹਾਨੂੰ ਪਹਿਲਾਂ ਬਲੂਟੁੱਥ ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ।"</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"ਚਾਲੂ ਕਰੋ"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"ਪਾਵਰ ਸੂਚਨਾ ਕੰਟਰੋਲ"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ਚਾਲੂ ਹੈ - ਚਿਹਰਾ-ਆਧਾਰਿਤ"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"ਪਾਵਰ ਸੂਚਨਾ ਕੰਟਰੋਲਾਂ ਨਾਲ, ਤੁਸੀਂ ਕਿਸੇ ਐਪ ਦੀਆਂ ਸੂਚਨਾਵਾਂ ਲਈ ਮਹੱਤਤਾ ਪੱਧਰ ਨੂੰ 0 ਤੋਂ 5 ਤੱਕ ਸੈੱਟ ਕਰ ਸਕਦੇ ਹੋ। \n\n"<b>"ਪੱਧਰ 5"</b>" \n- ਸੂਚਨਾ ਸੂਚੀ ਦੇ ਸਿਖਰ \'ਤੇ ਦਿਖਾਓ \n- ਪੂਰੀ ਸਕ੍ਰੀਨ ਰੁਕਾਵਟ ਦੀ ਆਗਿਆ ਦਿਓ \n- ਹਮੇਸ਼ਾਂ ਝਲਕ ਦਿਖਾਓ \n\n"<b>"ਪੱਧਰ 4"</b>" \n- ਪੂਰੀ ਸਕ੍ਰੀਨ ਰੁਕਾਵਟ ਨੂੰ ਰੋਕੋ \n- ਹਮੇਸ਼ਾਂ ਝਲਕ ਦਿਖਾਓ \n\n"<b>"ਪੱਧਰ 3"</b>" \n- ਪੂਰੀ ਸਕ੍ਰੀਨ ਰੁਕਾਵਟ ਨੂੰ ਰੋਕੋ \n- ਕਦੇ ਝਲਕ ਨਾ ਦਿਖਾਓ \n\n"<b>"ਪੱਧਰ 2"</b>" \n- ਪੂਰੀ ਸਕ੍ਰੀਨ ਰੁਕਾਵਟ ਨੂੰ ਰੋਕੋ \n- ਕਦੇ ਝਲਕ ਨਾ ਦਿਖਾਓ \n- ਕਦੇ ਵੀ ਧੁਨੀ ਜਾਂ ਥਰਥਰਾਹਟ ਨਾ ਕਰੋ \n\n"<b>"ਪੱਧਰ 1"</b>" \n- ਪੂਰੀ ਸਕ੍ਰੀਨ ਰੁਕਾਵਟ ਨੂੰ ਰੋਕੋ \n- ਕਦੇ ਝਲਕ ਨਾ ਦਿਖਾਓ \n- ਕਦੇ ਧੁਨੀ ਜਾਂ ਥਰਥਰਾਹਟ ਨਾ ਕਰੋ \n- ਲਾਕ ਸਕ੍ਰੀਨ ਅਤੇ ਸਥਿਤੀ ਪੱਟੀ ਤੋਂ ਲੁਕਾਓ \n- ਸੂਚਨਾ ਸੂਚੀ ਦੇ ਹੇਠਾਂ ਦਿਖਾਓ \n\n"<b>"ਪੱਧਰ 0"</b>" \n- ਐਪ ਤੋਂ ਸਾਰੀਆਂ ਸੂਚਨਾਵਾਂ ਨੂੰ ਬਲਾਕ ਕਰੋ"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"ਹੋ ਗਿਆ"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"ਲਾਗੂ ਕਰੋ"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"ਸੂਚਨਾਵਾਂ ਬੰਦ ਕਰੋ"</string>
@@ -827,13 +834,14 @@
     <string name="tuner_right" msgid="8247571132790812149">"ਸੱਜਾ"</string>
     <string name="tuner_menu" msgid="363690665924769420">"ਮੀਨੂ"</string>
     <string name="tuner_app" msgid="6949280415826686972">"<xliff:g id="APP">%1$s</xliff:g> ਐਪ"</string>
-    <string name="notification_channel_alerts" msgid="3385787053375150046">"ਸੁਚੇਤਨਾਵਾਂ"</string>
+    <string name="notification_channel_alerts" msgid="3385787053375150046">"ਅਲਰਟ"</string>
     <string name="notification_channel_battery" msgid="9219995638046695106">"ਬੈਟਰੀ"</string>
     <string name="notification_channel_screenshot" msgid="7665814998932211997">"ਸਕ੍ਰੀਨਸ਼ਾਟ"</string>
     <string name="notification_channel_instant" msgid="7556135423486752680">"Instant Apps"</string>
     <string name="notification_channel_setup" msgid="7660580986090760350">"ਸੈੱਟਅੱਪ ਕਰੋ"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"ਸਟੋਰੇਜ"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"ਸੰਕੇਤ"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"ਪਹੁੰਚਯੋਗਤਾ"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> ਚੱਲ ਰਹੀ ਹੈ"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"ਸਥਾਪਤ ਕੀਤੇ ਬਿਨਾਂ ਐਪ ਖੋਲ੍ਹੀ ਗਈ।"</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਖੋਲ੍ਹਣ ਲਈ ਟੈਪ ਕਰੋ। ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਇਹ ਬਟਨ ਵਿਉਂਤਬੱਧ ਕਰੋ ਜਾਂ ਬਦਲੋ।\n\n"<annotation id="link">"ਸੈਟਿੰਗਾਂ ਦੇਖੋ"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ਬਟਨ ਨੂੰ ਅਸਥਾਈ ਤੌਰ \'ਤੇ ਲੁਕਾਉਣ ਲਈ ਕਿਨਾਰੇ \'ਤੇ ਲਿਜਾਓ"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"ਅਣਕੀਤਾ ਕਰੋ"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"ਪਹੁੰਚਯੋਗਤਾ ਬਟਨ ਨੂੰ ਲੁਕਾਇਆ ਗਿਆ"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"ਪਹੁੰਚਯੋਗਤਾ ਬਟਨ ਦਿਖਾਉਣ ਲਈ ਟੈਪ ਕਰੋ"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਹਟਾਇਆ ਗਿਆ"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਹਟਾਇਆ ਗਿਆ}one{# ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਹਟਾਇਆ ਗਿਆ}other{# ਸ਼ਾਰਟਕੱਟਾਂ ਨੂੰ ਹਟਾਇਆ ਗਿਆ}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ਉੱਪਰ ਵੱਲ ਖੱਬੇ ਲਿਜਾਓ"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"ਵਰਤੋਂਕਾਰ ਦੀ ਮੌਜੂਦਗੀ ਦਾ ਪਤਾ ਲਗਾਇਆ ਜਾਂਦਾ ਹੈ"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਜਾ ਕੇ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਨੋਟ ਐਪ ਨੂੰ ਸੈੱਟ ਕਰੋ"</string>
     <string name="install_app" msgid="5066668100199613936">"ਐਪ ਸਥਾਪਤ ਕਰੋ"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ਕੀ ਬਾਹਰੀ ਡਿਸਪਲੇ \'ਤੇ ਪ੍ਰਤਿਬਿੰਬਿਤ ਕਰਨਾ ਹੈ?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ਤੁਹਾਡੀ ਅੰਦਰੂਨੀ ਡਿਸਪਲੇ ਪ੍ਰਤੀਬਿੰਬਤ ਕੀਤੀ ਜਾਵੇਗੀ। ਤੁਹਾਡੀ ਅਗਲੀ ਡਿਸਪਲੇ ਬੰਦ ਕਰ ਦਿੱਤੀ ਜਾਵੇਗੀ।"</string>
     <string name="mirror_display" msgid="2515262008898122928">"ਡਿਸਪਲੇ ਨੂੰ ਪ੍ਰਤਿਬਿੰਬਿਤ ਕਰੋ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index aa24818..53a1a67c 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Nie udało się skonfigurować rozpoznawania twarzy. Przejdź do ustawień, aby spróbować jeszcze raz."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Dotknij czytnika linii papilarnych"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Aby kontynuować, kliknij ikonę odblokowywania"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Nie rozpoznaję twarzy. Użyj odcisku palca."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Nie rozpoznano twarzy. Użyj odcisku palca."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Nie można rozpoznać twarzy"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Nie rozpoznano twarzy"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Użyj odcisku palca"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Rozpoznawanie twarzy niedostępne"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth połączony."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ładowanie • Pełne naładowanie za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Aby uruchomić wspólny samouczek, przeciągnij palcem w lewo"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otwórz edytor widżetów"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Dostosuj"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Zamknij"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Dodawaj widżety, usuwaj je i zmieniaj ich kolejność w tym obszarze"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Dodaj więcej widżetów"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Przytrzymaj, aby dostosować widżety"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Usuń"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodaj widżet"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Gotowe"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Włączyć Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Aby połączyć klawiaturę z tabletem, musisz najpierw włączyć Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Włącz"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Zaawansowane ustawienia powiadomień"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Włączono – na podstawie twarzy"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Dzięki zaawansowanym ustawieniom możesz określić poziom ważności powiadomień z aplikacji w skali od 0 do 5. \n\n"<b>"Poziom 5"</b>" \n– Pokazuj u góry listy powiadomień \n– Zezwalaj na powiadomienia na pełnym ekranie \n– Zawsze pokazuj podgląd \n\n"<b>"Poziom 4"</b>" \n– Wyłącz powiadomienia na pełnym ekranie \n– Zawsze pokazuj podgląd \n\n"<b>"Poziom 3"</b>" \n– Wyłącz powiadomienia na pełnym ekranie \n– Nigdy nie pokazuj podglądu \n\n"<b>"Poziom 2"</b>" \n– Wyłącz powiadomienia na pełnym ekranie \n– Nigdy nie pokazuj podglądu \n– NIgdy nie powiadamiaj dźwiękiem ani wibracjami \n\n"<b>"Poziom 1"</b>" \n– Wyłącz powiadomienia na pełnym ekranie \n– Nigdy nie pokazuj podglądu \n– NIgdy nie powiadamiaj dźwiękiem ani wibracjami \n– Ukrywaj na ekranie blokady i pasku stanu \n– Pokazuj u dołu listy powiadomień \n\n"<b>"Poziom 0"</b>" \n– Blokuj wszystkie powiadomienia aplikacji"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Gotowe"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Zastosuj"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Wyłącz powiadomienia"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Konfiguracja"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Pamięć wewnętrzna"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Wskazówki"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Ułatwienia dostępu"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Aplikacje błyskawiczne"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"Aplikacja <xliff:g id="APP">%1$s</xliff:g> działa"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Aplikacja została otwarta bez zainstalowania."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Kliknij, aby otworzyć ułatwienia dostępu. Dostosuj lub zmień ten przycisk w Ustawieniach.\n\n"<annotation id="link">"Wyświetl ustawienia"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Przesuń przycisk do krawędzi, aby ukryć go tymczasowo"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Cofnij"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Ukryto przycisk ułatwień dostępu"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Kliknij, aby wyświetlić przycisk ułatwień dostępu"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> – skrót został usunięty"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# skrót został usunięty}few{# skróty zostały usunięte}many{# skrótów zostało usuniętych}other{# skrótu zostało usunięte}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Przenieś w lewy górny róg"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Wykryto obecność użytkownika"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ustaw domyślną aplikację do obsługi notatek w Ustawieniach"</string>
     <string name="install_app" msgid="5066668100199613936">"Zainstaluj aplikację"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Powielić na wyświetlaczu zewnętrznym?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Powstanie odbicie lustrzane Twojego wewnętrznego ekranu. Przedni ekran zostanie wyłączony."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Powielaj obraz"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index c65c56e..3508558 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Não foi possível configurar o Desbloqueio facial. Acesse as Configurações e tente de novo."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Toque no sensor de impressão digital"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Pressione o ícone de desbloqueio para continuar"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Não foi possível reconhecer o rosto Use a impressão digital."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Rosto não reconhecido. Use a impressão digital."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Rosto não reconhecido"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Rosto não reconhecido"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use a impressão digital"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"O Desbloqueio facial não está disponível"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregando • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Deslize para a esquerda para iniciar o tutorial compartilhado"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir o editor de widgets"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Personalizar"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Dispensar"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Adicione, remova e reorganize seus widgets neste espaço"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Adicione mais widgets"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantenha pressionado para personalizar widgets"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Remover"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adicionar widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Concluído"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Ativar o Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Para conectar o teclado ao tablet, é preciso primeiro ativar o Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Ativar"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Controles de ativação/desativação de notificações"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Ativada (reconhecimento facial)"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Com controles de ativação de notificações, é possível definir o nível de importância de 0 a 5 para as notificações de um app. \n\n"<b>"Nível 5"</b>" \n- Exibir na parte superior da lista de notificações \n- Permitir interrupção em tela cheia \n- Sempre exibir \n\n"<b>"Nível 4"</b>" \n- Impedir interrupções em tela cheia \n- Sempre exibir \n\n"<b>"Nível 3"</b>" \n- Impedir interrupções em tela cheia \n- Nunca exibir \n\n"<b>"Nível 2"</b>" \n- Impedir interrupções em tela cheia \n- Nunca exibir \n- Nunca emitir som ou vibrar \n\n"<b>"Nível 1"</b>" \n- Impedir interrupções em tela cheia \n- Nunca exibir \n- Nunca emitir som ou vibrar \n- Ocultar da tela de bloqueio e barra de status \n- Exibir na parte inferior da lista de notificações \n\n"<b>"Nível 0"</b>" \n- Bloquear todas as notificações do app"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Concluído"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Aplicar"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Desativar notificações"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Configurar"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Armazenamento"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Dicas"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Acessibilidade"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> em execução"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"O app é aberto sem precisar ser instalado."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toque para abrir os recursos de acessibilidade. Personalize ou substitua o botão nas Configurações.\n\n"<annotation id="link">"Ver configurações"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mova o botão para a borda para ocultá-lo temporariamente"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Desfazer"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Botão de acessibilidade oculto"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Toque para mostrar o botão de acessibilidade"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Atalho de <xliff:g id="FEATURE_NAME">%s</xliff:g> removido"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# atalho removido}one{# atalho removido}many{# de atalhos removidos}other{# atalhos removidos}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover para o canto superior esquerdo"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Presença do usuário detectada"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defina o app de notas padrão nas Configurações"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar o app"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Espelhar para a tela externa?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Seu display interno será espelhado. O display frontal será desligado."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Espelhar tela"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 0b9580d..3ac26f0 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Não foi possível configurar o Desbloqueio facial. Aceda às Definições para tentar novamente."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Toque no sensor de impressões digitais."</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Prima o ícone de desbloqueio para continuar"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Impos. reconh. rosto. Utilize a impressão digital."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Rosto não reconhecido. Use a impressão digital."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Imposs. reconhecer rosto"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Rosto não reconhecido"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usar impressão digital"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Desbloqueio facial indisponível"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ligado."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • A carregar • Carga completa em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Deslize rapidamente para a esquerda para iniciar o tutorial coletivo"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir editor de widgets"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Personalizar"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Ignorar"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Adicionar, remover e reordenar widgets neste espaço"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Adicionar mais widgets"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantenha premido para personalizar os widgets"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Remover"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adicionar widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Concluir"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Ativar o Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Para ligar o teclado ao tablet, tem de ativar primeiro o Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Ativar"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Controlos de notificações do consumo de energia"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Ativada – Com base no rosto"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Com os controlos de notificações do consumo de energia, pode definir um nível de importância de 0 a 5 para as notificações de aplicações. \n\n"<b>"Nível 5"</b>" \n- Mostrar no início da lista de notificações \n- Permitir a interrupção do ecrã inteiro \n- Aparecer rapidamente sempre \n\n"<b>"Nível 4"</b>" \n- Impedir a interrupção do ecrã inteiro \n- Aparecer rapidamente sempre\n\n"<b>"Nível 3"</b>" \n- Impedir a interrupção do ecrã inteiro \n- Nunca aparecer rapidamente \n\n"<b>"Nível 2"</b>" \n- Impedir a interrupção do ecrã inteiro \n- Nunca aparecer rapidamente \n- Nunca tocar nem vibrar \n\n"<b>"Nível 1"</b>" \n- Impedir a interrupção do ecrã inteiro \n- Nunca aparecer rapidamente \n- Nunca tocar nem vibrar \n- Ocultar do ecrã de bloqueio e da barra de estado \n- Mostrar no fim da lista de notificações \n\n"<b>"Nível 0"</b>" \n- Bloquear todas as notificações da app"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Concluído"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Aplicar"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Desativar notificações"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Configuração"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Armazenamento"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Sugestões"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Acessibilidade"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Apps instantâneas"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> em execução"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"A app é aberta sem ser instalada."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toque para abrir funcionalidades de acessibilidade. Personal. ou substitua botão em Defin.\n\n"<annotation id="link">"Ver defin."</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mova o botão para a extremidade para o ocultar temporariamente"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Anular"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Botão Acessibilidade oculto"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Toque para mostrar o botão Acessibilidade"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Atalho de <xliff:g id="FEATURE_NAME">%s</xliff:g> removido"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# atalho removido}many{# atalhos removidos}other{# atalhos removidos}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover p/ parte sup. esquerda"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Quando deteta a presença do utilizador"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Predefina a app de notas nas Definições"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar app"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Espelhar para o ecrã externo?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"O seu ecrã interior vai ser espelhado. O seu ecrã frontal vai ser desativado."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Espelhar ecrã"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index c65c56e..3508558 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Não foi possível configurar o Desbloqueio facial. Acesse as Configurações e tente de novo."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Toque no sensor de impressão digital"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Pressione o ícone de desbloqueio para continuar"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Não foi possível reconhecer o rosto Use a impressão digital."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Rosto não reconhecido. Use a impressão digital."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Rosto não reconhecido"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Rosto não reconhecido"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use a impressão digital"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"O Desbloqueio facial não está disponível"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregando • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Deslize para a esquerda para iniciar o tutorial compartilhado"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Abrir o editor de widgets"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Personalizar"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Dispensar"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Adicione, remova e reorganize seus widgets neste espaço"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Adicione mais widgets"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantenha pressionado para personalizar widgets"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Remover"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adicionar widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Concluído"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Ativar o Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Para conectar o teclado ao tablet, é preciso primeiro ativar o Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Ativar"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Controles de ativação/desativação de notificações"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Ativada (reconhecimento facial)"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Com controles de ativação de notificações, é possível definir o nível de importância de 0 a 5 para as notificações de um app. \n\n"<b>"Nível 5"</b>" \n- Exibir na parte superior da lista de notificações \n- Permitir interrupção em tela cheia \n- Sempre exibir \n\n"<b>"Nível 4"</b>" \n- Impedir interrupções em tela cheia \n- Sempre exibir \n\n"<b>"Nível 3"</b>" \n- Impedir interrupções em tela cheia \n- Nunca exibir \n\n"<b>"Nível 2"</b>" \n- Impedir interrupções em tela cheia \n- Nunca exibir \n- Nunca emitir som ou vibrar \n\n"<b>"Nível 1"</b>" \n- Impedir interrupções em tela cheia \n- Nunca exibir \n- Nunca emitir som ou vibrar \n- Ocultar da tela de bloqueio e barra de status \n- Exibir na parte inferior da lista de notificações \n\n"<b>"Nível 0"</b>" \n- Bloquear todas as notificações do app"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Concluído"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Aplicar"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Desativar notificações"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Configurar"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Armazenamento"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Dicas"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Acessibilidade"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> em execução"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"O app é aberto sem precisar ser instalado."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toque para abrir os recursos de acessibilidade. Personalize ou substitua o botão nas Configurações.\n\n"<annotation id="link">"Ver configurações"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mova o botão para a borda para ocultá-lo temporariamente"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Desfazer"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Botão de acessibilidade oculto"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Toque para mostrar o botão de acessibilidade"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Atalho de <xliff:g id="FEATURE_NAME">%s</xliff:g> removido"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# atalho removido}one{# atalho removido}many{# de atalhos removidos}other{# atalhos removidos}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover para o canto superior esquerdo"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Presença do usuário detectada"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defina o app de notas padrão nas Configurações"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar o app"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Espelhar para a tela externa?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Seu display interno será espelhado. O display frontal será desligado."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Espelhar tela"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index b2fdef3..7cdc121 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Nu s-a putut configura deblocarea facială. Accesează Setările pentru a încerca din nou."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Atinge senzorul de amprente"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Apasă pe pictograma de deblocare pentru a continua"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Chipul nu a fost recunoscut. Folosește amprenta."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Fața nu a fost recunoscută. Folosește amprenta."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Chip nerecunoscut"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Față nerecunoscută"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Folosește amprenta"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Deblocarea facială nu este disponibilă"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Conectat prin Bluetooth."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Glisează spre stânga pentru a începe tutorialul pentru comunitate"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Deschide editorul de widgeturi"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Personalizează"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Respinge"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Adaugă, elimină și reordonează widgeturile în acest spațiu"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Adaugă mai multe widgeturi"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Apasă lung pentru a personaliza widgeturi"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Elimină"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adaugă un widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Gata"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Activezi Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Pentru a conecta tastatura la tabletă, mai întâi trebuie să activezi Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Activează"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Comenzi de gestionare a notificărilor"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Activată – În funcție de chip"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Folosind comenzile de gestionare a notificărilor, poți seta un nivel de importanță de la 0 la 5 pentru notificările unei aplicații. \n\n"<b>"Nivelul 5"</b>" \n– Se afișează la începutul listei de notificări \n– Se permite întreruperea pe ecranul complet \n– Se afișează întotdeauna scurt \n\n"<b>"Nivelul 4"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Se afișează întotdeauna scurt \n\n"<b>"Nivelul 3"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Nu se afișează niciodată scurt \n\n"<b>"Nivelul 2"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Nu se afișează niciodată scurt \n– Nu se emit sunete și nu vibrează niciodată \n\n"<b>"Nivelul 1"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Nu se afișează niciodată scurt \n– Nu se emit sunete și nu vibrează niciodată \n– Se ascunde în ecranul de blocare și în bara de stare \n– Se afișează la finalul listei de notificări \n\n"<b>"Nivelul 0"</b>" \n– Se blochează toate notificările din aplicație"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Gata"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Aplică"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Dezactivează notificările"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Configurarea"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Stocare"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Indicii"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Accesibilitate"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Aplicații instantanee"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> rulează"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Aplicația a fost deschisă fără a fi instalată."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Atinge ca să deschizi funcțiile de accesibilitate. Personalizează sau înlocuiește butonul în setări.\n\n"<annotation id="link">"Vezi setările"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mută butonul spre margine pentru a-l ascunde temporar"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Anulează"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Butonul de accesibilitate a fost ascuns"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Atinge pentru a afișa butonul de accesibilitate"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Comandă rapidă <xliff:g id="FEATURE_NAME">%s</xliff:g> eliminată"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# comandă rapidă eliminată}few{# comenzi rapide eliminate}other{# de comenzi rapide eliminate}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mută în stânga sus"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"S-a detectat prezența utilizatorului"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setează aplicația prestabilită de note din Setări"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalează aplicația"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Oglindești pe ecranul extern?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ecranul interior va fi oglindit. Ecranul frontal va fi dezactivat."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Afișare în oglindă"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index ff4bd45..a059e55 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Не удалось настроить фейсконтроль. Перейдите в настройки и повторите попытку."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Прикоснитесь к сканеру отпечатков пальцев."</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Нажмите на значок разблокировки."</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Не удалось распознать лицо. Используйте отпечаток."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Лицо не распознано. Сканируйте отпечаток пальца."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Лицо не распознано."</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Лицо не распознано."</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Используйте отпечаток."</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Фейсконтроль недоступен"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-соединение установлено."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарядка • Осталось <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Чтобы ознакомиться с руководством, проведите по экрану влево"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Открыть редактор виджетов"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Настроить"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Закрыть"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Добавление, удаление и упорядочивание виджетов в этом пространстве"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Добавить виджеты"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Нажмите и удерживайте, чтобы настроить виджеты."</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Удалить"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Добавить виджет"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Готово"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Подключение по Bluetooth"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Чтобы подключить клавиатуру к планшету, включите Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Включить"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Расширенное управление уведомлениями"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Включить (на основе распознавания лиц)"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"С помощью этой функции вы можете устанавливать уровень важности уведомлений от 0 до 5 для каждого приложения.\n\n"<b>"Уровень 5"</b>\n"‒ Помещать уведомления в начало списка.\n‒ Показывать полноэкранные уведомления.\n‒ Показывать всплывающие уведомления.\nУровень 4\n"<b></b>\n"‒ Не показывать полноэкранные уведомления.\n‒ Показывать всплывающие уведомления.\nУровень 3\n"<b></b>\n"‒ Не показывать полноэкранные уведомления.\n‒ Не показывать всплывающие уведомления.\nУровень 2\n"<b></b>\n"‒ Не показывать полноэкранные уведомления.\n‒ Не показывать всплывающие уведомления.\n‒ Не использовать звук и вибрацию.\nУровень 1\n"<b></b>\n"‒ Не показывать полноэкранные уведомления.\n‒ Не показывать всплывающие уведомления.\n‒ Не использовать звук и вибрацию.\n‒ Не показывать на экране блокировки и в строке состояния.\n‒ Помещать уведомления в конец списка.\nУровень 0\n"<b></b>\n"‒ Блокировать все уведомления приложения."</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Готово"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Применить"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Выключить уведомления"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Настройка"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Хранилище"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Подсказки"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Специальные возможности"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Приложения с мгновенным запуском"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> уже здесь!"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Приложение готово к работе, установка не требуется."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Нажмите, чтобы открыть спец. возможности. Настройте или замените эту кнопку в настройках.\n\n"<annotation id="link">"Настройки"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Чтобы временно скрыть кнопку, переместите ее к краю экрана"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Отменить"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Кнопка специальных возможностей скрыта"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Чтобы вернуть ее, нажмите на уведомление."</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g>: сочетание клавиш удалено."</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# сочетание клавиш удалено}one{# сочетание клавиш удалено}few{# сочетания клавиш удалено}many{# сочетаний клавиш удалено}other{# сочетания клавиш удалено}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Перенести в левый верхний угол"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Обнаружен пользователь"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Задайте стандартное приложение для заметок в настройках."</string>
     <string name="install_app" msgid="5066668100199613936">"Установить приложение"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Дублировать на внешний дисплей?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Для внутреннего экрана включится дублирование. Передний экран будет отключен."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Дублировать"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 4b4d08b..ad7b241 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"මුහුණෙන් අගුළු හැරීම පිහිටුවිය නොහැකි විය. නැවත උත්සාහ කිරීමට සැකසීම් වෙත යන්න."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"ඇඟිලි සලකුණු සංවේදකය ස්පර්ශ කරන්න"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"ඉදිරියට යාමට අගුළු ඇරීමේ නිරූපකය ඔබන්න"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"මුහුණ හැඳිනිය නොහැක. ඒ වෙනුවට ඇඟිලි සලකුණ භාවිත ක."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"මුහුණ හඳුනා නොගැනිණි. ඒ වෙනුවට ඇඟිලි සලකුණ භාවිත කරන්න."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"මුහුණ හඳුනා ගත නොහැක"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"මුහුණ හඳුනා නොගන්නා ලදි"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ඒ වෙනුවට ඇඟිලි සලකුණ භාවිත කරන්න"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"මුහුණෙන් අගුළු ඇරීම නැත"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"බ්ලූටූත් සම්බන්ධිතයි."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ආරෝපණය වෙමින් • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>කින් සම්පූර්ණ වේ"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"පොදු නිබන්ධනය ආරම්භ කිරීමට වමට ස්වයිප් කරන්න"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"විජට් සංස්කාරකය විවෘත කරන්න"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"අභිරුචිකරණය කරන්න"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"අස් කරන්න"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"මෙම අවකාශය තුළ ඔබේ විජට් එක් කරන්න, ඉවත් කරන්න, සහ නැවත අනුපිළිවෙල කරන්න"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"තවත් විජට් එක් කරන්න"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"විජට් අභිරුචිකරණය කිරීමට දිගු ඔබන්න"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ඉවත් කරන්න"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"විජට්ටුව එක් කරන්න"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"නිමයි"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"බ්ලූටූත් ක්‍රියාත්මක කරන්නද?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"ඔබේ යතුරු පුවරුව ඔබේ ටැබ්ලට් පරිගණකයට සම්බන්ධ කිරීමට, ඔබ පළමුව බ්ලූටූත් ක්‍රියාත්මක කළ යුතුය."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"ක්‍රියාත්මක කරන්න"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"බල දැනුම්දීම් පාලන"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ක්‍රියාත්මකයි - මුහුණ-පදනම්ව"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"බල දැනුම්දීම් පාලන සමගින්, ඔබට යෙදුමක දැනුම්දීම් සඳහා වැදගත්කම 0 සිට 5 දක්වා සැකසිය හැකිය. \n\n"<b>"5 මට්ටම"</b>" \n- දැනුම්දීම් ලැයිස්තුවේ ඉහළින්ම පෙන්වන්න \n- පූර්ණ තිර බාධාවට ඉඩ දෙන්න \n- සැම විට එබී බලන්න \n\n"<b>"4 මට්ටම"</b>" \n- පූර්ණ තිර බාධාව වළක්වන්න \n- සැම විට එබී බලන්න \n\n"<b>"3 මට්ටම"</b>" \n- පූර්ණ තිර බාධාව වළක්වන්න \n- කිසි විටක එබී නොබලන්න \n\n"<b>"2 මට්ටම"</b>" \n- පූර්ණ තිර බාධාව වළක්වන්න \n- කිසි විටක එබී නොබලන්න \n- කිසි විටක හඬ සහ කම්පනය සිදු නොකරන්න \n\n"<b>"1 මට්ටම"</b>" \n- පූර්ණ තිර බාධාව වළක්වන්න \n- කිසි විටක එබී නොබලන්න \n- කිසි විටක හඬ සහ කම්පනය සිදු නොකරන්න \n- අගුලු තිරය සහ තත්ත්ව තීරුව වෙතින් සඟවන්න \n- දැනුම්දීම් ලැයිස්තුවේ පහළින්ම පෙන්වන්න \n\n"<b>"0 මට්ටම"</b>" \n- යෙදුම වෙතින් වන සියලු දැනුම් දීම් සඟවන්න."</string>
     <string name="inline_done_button" msgid="6043094985588909584">"නිමයි"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"යොදන්න"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"දැනුම්දීම් අක්‍රිය කරන්න"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"පිහිටුවීම"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"ගබඩාව"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"ඉඟි"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"ප්‍රවේශ්‍යතාව"</string>
     <string name="instant_apps" msgid="8337185853050247304">"ක්ෂණික යෙදුම්"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> ධාවනය වෙමින්"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"ස්ථාපනය නොකර යෙදුම විවෘත කර ඇත."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ප්‍රවේශ්‍යතා විශේෂාංග විවෘත කිරීමට තට්ටු කරන්න. සැකසීම් තුළ මෙම බොත්තම අභිරුචිකරණය හෝ ප්‍රතිස්ථාපනය කරන්න.\n\n"<annotation id="link">"සැකසීම් බලන්න"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"එය තාවකාලිකව සැඟවීමට බොත්තම දාරයට ගෙන යන්න"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"අස් කරන්න"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"ප්‍රවේශ්‍යතා බොත්තම සඟවා ඇත"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"ප්‍රවේශ්‍යතා බොත්තම පෙන්වීමට තට්ටු කරන්න"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> කෙටිමඟ ඉවත් කළා"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# කෙටිමඟක් ඉවත් කළා}one{කෙටිමං #ක් ඉවත් කළා}other{කෙටිමං #ක් ඉවත් කළා}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ඉහළ වමට ගෙන යන්න"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"පරිශීලක රූපාකාරය අනාවරණය වේ"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"සැකසීම් තුළ පෙරනිමි සටහන් යෙදුම සකසන්න"</string>
     <string name="install_app" msgid="5066668100199613936">"යෙදුම ස්ථාපනය කරන්න"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"බාහිර සංදර්ශකයට දර්පණය කරන්න ද?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ඔබේ අභ්‍යන්තර සංදර්ශකය පිළිබිඹු වනු ඇත. ඔබේ ඉදිරිපස සංදර්ශකය ක්‍රියාවිරහිත වනු ඇත."</string>
     <string name="mirror_display" msgid="2515262008898122928">"සංදර්ශකය දර්පණය කරන්න"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 9e9507e..e4162db 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Odomknutie tvárou sa nepodarilo nastaviť. Prejdite do Nastavení a skúste to znova."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Dotknite sa senzora odtlačkov prstov"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Pokračujte stlačením ikony odomknutia"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Tvár sa nedá rozpoznať. Použite odtlačok prsta."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Tvár nebola rozpoznaná. Použite odtlačok prsta."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Tvár sa nedá rozpoznať"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Tvár nebola rozpoznaná"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Používať radšej odtlačok"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Odomknutie tvárou nie je k dispozícii"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth pripojené."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíja sa • Do úplného nabitia zostáva <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Potiahnutím doľava spustite komunitný návod"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Otvoriť editor miniaplikácií"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Prispôsobiť"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Zavrieť"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Pridávajte aj odstraňujte miniaplikácie a meňte ich poradie v tomto priestore"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Pridať ďalšie miniaplikácie"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Miniaplikácie prispôsobíte dlhým stlačením"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Odstrániť"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Pridať miniaplikáciu"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Hotovo"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Zapnúť Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Ak chcete klávesnicu pripojiť k tabletu, najprv musíte zapnúť Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Zapnúť"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Ovládacie prvky zobrazovania upozornení"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Zapnuté – podľa tváre"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Pomocou ovládacích prvkov zobrazovania upozornení môžete nastaviť pre upozornenia aplikácie úroveň dôležitosti od 0 do 5. \n\n"<b>"Úroveň 5"</b>" \n– Zobrazovať v hornej časti zoznamu upozornení. \n– Povoliť prerušenia na celú obrazovku. \n– Vždy zobrazovať čiastočne. \n\n"<b>"Úroveň 4"</b>" \n– Zabrániť prerušeniam na celú obrazovku. \n– Vždy zobrazovať čiastočne. \n\n"<b>"Úroveň 3"</b>" \n– Zabrániť prerušeniam na celú obrazovku. \n– Nikdy nezobrazovať čiastočne. \n\n"<b>"Úroveň 2"</b>" \n– Zabrániť prerušeniam na celú obrazovku. \n– Nikdy nezobrazovať čiastočne. \n– Nikdy nespúšťať zvuk ani vibrácie. \n\n"<b>"Úroveň 1"</b>" \n– Zabrániť prerušeniam na celú obrazovku. \n– Nikdy nezobrazovať čiastočne. \n– Nikdy nespúšťať zvuk ani vibrácie. \n– Skryť na uzamknutej obrazovke a v stavovom riadku. \n– Zobraziť v dolnej časti zoznamu upozornení. \n\n"<b>"Úroveň 0"</b>" \n– Blokovať všetky upozornenia z aplikácie."</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Hotovo"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Použiť"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Vypnúť upozornenia"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Nastavenie"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Úložisko"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Tipy"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Dostupnosť"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Okamžité aplikácie"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"Aplikácia <xliff:g id="APP">%1$s</xliff:g> je spustená"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Aplikácia bola otvorená bez inštalácie."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Funkcie dostupnosti otvoríte klepnutím. Tlačidlo prispôsobte alebo nahraďte v Nastav.\n\n"<annotation id="link">"Zobraz. nast."</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Ak chcete tlačidlo dočasne skryť, presuňte ho k okraju"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Späť"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Tlačidlo dostupnosti je skryté"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Klepnutím zobrazíte tlačidlo dostupnosti"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Bola odstránená skratka <xliff:g id="FEATURE_NAME">%s</xliff:g>"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Bola odstránená # skratka}few{Boli odstránené # skratky}many{# shortcuts removed}other{Bolo odstránených # skratiek}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Presunúť doľava nahor"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Bola rozpoznaná prítomnosť používateľa"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nastavte predvolenú aplikáciu na poznámky v Nastaveniach"</string>
     <string name="install_app" msgid="5066668100199613936">"Inštalovať aplikáciu"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Chcete zrkadliť na externú obrazovku?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Vnútorná obrazovka bude zrkadlená. Predná obrazovka bude vypnutá."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Zrkadliť obrazovku"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index cba5416..c8f7dc1 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Odklepanja z obrazom ni bilo mogoče nastaviti. Odprite nastavitve in poskusite znova."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Dotaknite se tipala prstnih odtisov"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Za nadaljevanje pritisnite ikono za odklepanje"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Obraza ni mogoče prepoznati. Uporabite prstni odtis."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Obraz ni prepoznan. Uporabite prstni odtis."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Obraz ni bil prepoznan."</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Obraz ni prepoznan"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Uporabite prstni odtis."</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Odklepanje z obrazom ni na voljo."</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Povezava Bluetooth vzpostavljena."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Polnjenje • Napolnjeno čez <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Povlecite levo, da zaženete vadnico za skupnost"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Odpiranje urejevalnika pripomočkov"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Prilagodi"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Opusti"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Dodajajte, odstranjujte in prerazporejajte pripomočke v tem prostoru"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Dodajte več pripomočkov"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pridržite za prilagajanje pripomočkov"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Odstrani"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodajanje pripomočka"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Končano"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Želite vklopiti Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Če želite povezati tipkovnico in tablični računalnik, vklopite Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Vklop"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Kontrolniki za pomembnost obvestil"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Vklopljeno – na podlagi obraza"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"S kontrolniki za pomebnost obvestila je mogoče za obvestila aplikacije nastaviti stopnjo pomembnosti od 0 do 5. \n\n"<b>"Stopnja 5"</b>" \n– Prikaz na vrhu seznama obvestil \n– Omogočanje prekinitev v celozaslonskem načinu \n– Vedno prikaži hitre predoglede \n\n"<b>"Stopnja 4"</b>" \n– Preprečevanje prekinitev v celozaslonskem načinu \n– Vedno prikaži hitre predoglede \n\n"<b>"Stopnja 3"</b>" \n– Preprečevanje prekinitev v celozaslonskem načinu \n– Nikoli ne prikaži hitrih predogledov \n\n"<b>"Stopnja 2"</b>" \n– Preprečevanje prekinitev v celozaslonskem načinu \n– Nikoli ne prikaži hitrih predogledov \n– Nikoli ne uporabi zvoka in vibriranja \n\n"<b>"Stopnja 1"</b>" \n– Preprečevanje prekinitev v celozaslonskem načinu \n– Nikoli ne prikaži hitrih predogledov \n– Nikoli ne uporabi zvoka in vibriranja \n– Skrivanje na zaklenjenem zaslonu in v vrstici stanja \n– Prikaz na dnu seznama obvestil \n\n"<b>"Stopnja 0"</b>" \n– Blokiranje vseh obvestil aplikacije"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Končano"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Uporabi"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Izklopi obvestila"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Nastavitev"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Shramba"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Namigi"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Dostopnost"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Nenamestljive aplikacije"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"Aplikacija <xliff:g id="APP">%1$s</xliff:g> se izvaja"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Aplikacija je odprta brez namestitve."</string>
@@ -897,10 +905,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Premakni navzdol"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Premakni levo"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Premakni desno"</string>
-    <string name="accessibility_control_increase_window_width" msgid="6992249470832493283">"Povečanje širine povečevalnika"</string>
-    <string name="accessibility_control_decrease_window_width" msgid="5740401560105929681">"Zmanjšanje širine povečevalnika"</string>
-    <string name="accessibility_control_increase_window_height" msgid="2200966116612324260">"Povečanje višine povečevalnika"</string>
-    <string name="accessibility_control_decrease_window_height" msgid="2054479949445332761">"Zmanjšanje višine povečevalnika"</string>
+    <string name="accessibility_control_increase_window_width" msgid="6992249470832493283">"Povečanje širine lupe"</string>
+    <string name="accessibility_control_decrease_window_width" msgid="5740401560105929681">"Zmanjšanje širine lupe"</string>
+    <string name="accessibility_control_increase_window_height" msgid="2200966116612324260">"Povečanje višine lupe"</string>
+    <string name="accessibility_control_decrease_window_height" msgid="2054479949445332761">"Zmanjšanje višine lupe"</string>
     <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Stikalo za povečavo"</string>
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Povečanje celotnega zaslona"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Povečava dela zaslona"</string>
@@ -917,7 +925,7 @@
     <string name="accessibility_magnification_right_handle" msgid="9055988237319397605">"Ročica desno"</string>
     <string name="accessibility_magnification_bottom_handle" msgid="6531646968813821258">"Ročica spodaj"</string>
     <string name="accessibility_magnification_settings_panel_description" msgid="8174187340747846953">"Nastavitve povečave"</string>
-    <string name="accessibility_magnifier_size" msgid="3038755600030422334">"Velikost povečevalnika"</string>
+    <string name="accessibility_magnifier_size" msgid="3038755600030422334">"Velikost lupe"</string>
     <string name="accessibility_magnification_zoom" msgid="4222088982642063979">"Povečava/pomanjšava"</string>
     <string name="accessibility_magnification_medium" msgid="6994632616884562625">"Srednja"</string>
     <string name="accessibility_magnification_small" msgid="8144502090651099970">"Majhna"</string>
@@ -925,10 +933,12 @@
     <string name="accessibility_magnification_fullscreen" msgid="5043514702759201964">"Celozaslonski način"</string>
     <string name="accessibility_magnification_done" msgid="263349129937348512">"Končano"</string>
     <string name="accessibility_magnifier_edit" msgid="1522877239671820636">"Uredi"</string>
-    <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Nastavitve okna povečevalnika"</string>
+    <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Nastavitve okna lupe"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Dotaknite se za funkcije dostopnosti. Ta gumb lahko prilagodite ali zamenjate v nastavitvah.\n\n"<annotation id="link">"Ogled nastavitev"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Če želite gumb začasno skriti, ga premaknite ob rob."</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Razveljavi"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Gumb za dostopnost je skrit"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Dotaknite se za prikaz gumba za dostopnost"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Odstranjena bližnjica za fun. <xliff:g id="FEATURE_NAME">%s</xliff:g>"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Odstranjena # bližnjica}one{Odstranjena # bližnjica}two{Odstranjeni # bližnjici}few{Odstranjene # bližnjice}other{Odstranjenih # bližnjic}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Premakni zgoraj levo"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Zaznana je prisotnost uporabnika"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nastavite privzeto aplikacijo za zapiske v nastavitvah."</string>
     <string name="install_app" msgid="5066668100199613936">"Namesti aplikacijo"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Želite zrcaliti na zunanji zaslon?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Notranji zaslon bo zrcaljen. Sprednji zaslon bo izklopljen."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Zrcali zaslon"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index b35668f..7dd89a1 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Shkyçja me fytyrë nuk mund të konfigurohej. Shko te \"Cilësimet\" për të provuar përsëri."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Prek sensorin e gjurmës së gishtit"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Shtyp ikonën e shkyçjes për të vazhduar"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Nuk mund ta dallojë fytyrën. Përdor më mirë gjurmën e gishtit."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Fytyra nuk njihet. Përdor më mirë gjurmën e gishtit."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Fytyra nuk mund të njihet"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Fytyra nuk njihet"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Përdor më mirë gjurmën e gishtit"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"\"Shkyçja me fytyrë\" nuk ofrohet"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Pajisja është lidhur me \"bluetooth\"."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Po karikohet • Plot për <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Rrëshqit shpejt majtas për të filluar udhëzuesin e përbashkët"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Hap modifikuesin e miniaplikacionit"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Personalizo"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Hiq"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Shto, hiq dhe rirendit miniaplikacionet e tua në këtë hapësirë"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Shto miniaplikacione të tjera"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Shtyp gjatë për të personalizuar miniaplikacionet"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Hiq"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Shto miniaplikacionin"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"U krye"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Të aktivizohet \"bluetooth-i\"?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Për të lidhur tastierën me tabletin, në fillim duhet të aktivizosh \"bluetooth-in\"."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Aktivizo"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Kontrollet e njoftimit të energjisë"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Aktiv - Në bazë të fytyrës"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Me kontrollet e njoftimit të energjisë, mund të caktosh një nivel rëndësie nga 0 në 5 për njoftimet e një aplikacioni. \n\n"<b>"Niveli 5"</b>" \n- Shfaq në krye të listës së njoftimeve \n- Lejo ndërprerjen e ekranit të plotë \n- Gjithmonë shfaq shpejt \n\n"<b>"Niveli 4"</b>" \n- Parandalo ndërprerjen e ekranit të plotë \n- Gijthmonë shfaq shpejt \n\n"<b>"Niveli 3"</b>" \n- Parandalo ndërprerjen e ekranit të plotë \n- Asnjëherë mos shfaq shpejt \n\n"<b>"Niveli 2"</b>" \n- Parandalo ndërprerjen e ekranit të plotë \n- Asnjëherë mos shfaq shpejt \n- Asnjëherë mos lësho tingull dhe dridhje \n\n"<b>"Niveli 1"</b>" \n- Parandalo ndërprerjen e ekranit të plotë \n- Asnjëherë mos shfaq shpejt \n- Asnjëherë mos lësho tingull ose dridhje \n- Fshih nga ekrani i kyçjes dhe shiriti i statusit \n- Shfaq në fund të listës së njoftimeve \n\n"<b>"Niveli 0"</b>" \n- Blloko të gjitha njoftimet nga aplikacioni"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"U krye"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Zbato"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Çaktivizo njoftimet"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Konfigurimi"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Hapësira ruajtëse"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Sugjerimet"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Qasshmëria"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Aplikacionet e çastit"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"Po ekzekutohet <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Aplikacioni u hap pa u instaluar."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Trokit dhe hap veçoritë e qasshmërisë. Modifiko ose ndërro butonin te \"Cilësimet\".\n\n"<annotation id="link">"Shih cilësimet"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Zhvendose butonin në skaj për ta fshehur përkohësisht"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Zhbëj"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Butoni i qasshmërisë u fsheh"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Trokit për të shfaqur butonin e qasshmërisë"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Shkurtorja për \"<xliff:g id="FEATURE_NAME">%s</xliff:g>\" u hoq"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# shkurtore u hoq}other{# shkurtore u hoqën}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Zhvendos lart majtas"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Është zbuluar prania e përdoruesit"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Cakto aplikacionin e parazgjedhur të shënimeve te \"Cilësimet\""</string>
     <string name="install_app" msgid="5066668100199613936">"Instalo aplikacionin"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Të pasqyrohet në ekranin e jashtëm?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ekrani i brendshëm do të pasqyrohet. Ekrani i parmë do të çaktivizohet."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Pasqyro ekranin"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 1d45fbd..ca43617 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Подешавање откључавања лицем није успело. Идите у Подешавања да бисте пробали поново."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Додирните сензор за отисак прста"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Притисните икону откључавања за наставак"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Лице није препознато. Користите отисак прста."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Лице није препознато. Користите отисак прста."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Лице није препознато"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Лице није препознато"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Користите отисак прста"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Откључавање лицем није доступно"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth је прикључен."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Пуни се • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до краја пуњења"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Превуците улево да бисте започели заједнички водич"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Отвори уређивач виџета"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Прилагодите"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Одбаци"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Додајте, уклоните и преуредите виџете у овом простору"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Додајте још виџета"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Дуги притисак за прилагођавање виџета"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Уклони"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Додај виџет"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Готово"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Желите ли да укључите Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Да бисте повезали тастатуру са таблетом, прво морате да укључите Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Укључи"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Напредне контроле за обавештења"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Укључено – на основу лица"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Помоћу напредних контрола за обавештења можете да подесите ниво важности од 0. до 5. за обавештења апликације. \n\n"<b>"5. ниво"</b>" \n– Приказују се у врху листе обавештења \n- Дозволи прекид режима целог екрана \n– Увек завируј \n\n"<b>"4. ниво"</b>" \n– Спречи прекид режима целог екрана \n– Увек завируј \n\n"<b>"3. ниво"</b>" \n– Спречи прекид режима целог екрана \n– Никада не завируј \n\n"<b>"2. ниво"</b>" \n– Спречи прекид режима целог екрана \n– Никада не завируј \n– Никада не производи звук или вибрацију \n\n"<b>"1. ниво"</b>" \n– Спречи прекид режима целог екрана \n– Никада не завируј \n– Никада не производи звук или вибрацију \n– Сакриј на закључаном екрану и статусној траци \n– Приказују се у дну листе обавештења \n\n"<b>"0. ниво"</b>" \n– Блокирај сва обавештења из апликације"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Готово"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Примени"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Искључи обавештења"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Подешавање"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Меморијски простор"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Савети"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Приступачност"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Инстант апликације"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"Апликација <xliff:g id="APP">%1$s</xliff:g> је покренута"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Апликација се отворила без инсталирања."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Додирните за функције приступачности. Прилагодите или замените ово дугме у Подешавањима.\n\n"<annotation id="link">"Подешавања"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Померите дугме до ивице да бисте га привремено сакрили"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Опозови"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Дугме Приступачност је скривено"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Додирните за приказ дугмета Приступачност"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Пречица функције <xliff:g id="FEATURE_NAME">%s</xliff:g> је уклоњена"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# пречица је уклоњена}one{# пречица је уклоњена}few{# пречице су уклоњене}other{# пречица је уклоњено}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Премести горе лево"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Присуство корисника може да се открије"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Подесите подразумевану апликацију за белешке у Подешавањима"</string>
     <string name="install_app" msgid="5066668100199613936">"Инсталирај апликацију"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Желите ли да пресликате на спољњи екран?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Унутрашњи екран ће се пресликати. Предњи екран ће се искључити."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Пресликај екран"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 0d6272f..078942d 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Det gick inte att konfigurera ansiktslåset. Öppna inställningarna och försök igen."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Tryck på fingeravtryckssensorn"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Tryck på ikonen lås upp för att fortsätta"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Ansiktet kändes inte igen. Använd fingeravtryck."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Ansiktet känns inte igen. Använd fingeravtryck."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Ansiktet kändes inte igen"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Ansiktet känns inte igen"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Använd fingeravtryck"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Ansiktslås är otillgängligt"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ansluten."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laddas • Fulladdat om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Svep åt vänster för att börja med gruppguiden"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Öppna widgetredigeraren"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Anpassa"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Ignorera"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Lägg till, ta bort och ordna om dina widgetar i det här rummet"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Lägg till fler widgetar"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Tryck länge för att anpassa widgetar"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Ta bort"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Lägg till widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Klar"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Vill du aktivera Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Om du vill ansluta tangentbordet till surfplattan måste du först aktivera Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Aktivera"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Prioritetsinställningar för aviseringar"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"På – ansiktsbaserad"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Med aviseringsinställningarna kan du ange prioritetsnivå från 0 till 5 för aviseringar från en app. \n\n"<b>"Nivå 5"</b>" \n– Visa högst upp i aviseringslistan\n– Tillåt avbrott i helskärmsläge \n– Snabbvisa alltid \n\n"<b>"Nivå 4"</b>" \n– Tillåt inte avbrott i helskärmsläge \n– Snabbvisa alltid \n\n"<b>"Nivå 3"</b>" \n- Tillåt inte avbrott i helskärmsläge \n– Snabbvisa aldrig \n\n"<b>"Nivå 2"</b>" \n– Tillåt inte avbrott i helskärmsläge \n– Snabbvisa aldrig \n– Aldrig med ljud eller vibration \n\n"<b>"Nivå 1"</b>" \n– Tillåt inte avbrott i helskärmsläge \n– Snabbvisa aldrig \n– Aldrig med ljud eller vibration \n– Visa inte på låsskärmen och i statusfältet \n– Visa längst ned i aviseringslistan \n\n"<b>"Nivå 0"</b>" \n– Blockera alla aviseringar från appen"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Klart"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Tillämpa"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Inaktivera aviseringar"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Konfigurering"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Lagring"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Tips"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Tillgänglighet"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Snabbappar"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> körs"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Appen öppnades utan installation."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tryck för att öppna tillgänglighetsfunktioner. Anpassa/ersätt knappen i Inställningar.\n\n"<annotation id="link">"Inställningar"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Flytta knappen till kanten för att dölja den tillfälligt"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Ångra"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Tillgänglighetsknappen är dold"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Tryck för att visa tillgänglighetsknapp"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Genväg till <xliff:g id="FEATURE_NAME">%s</xliff:g> har tagits bort"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# genväg har tagits bort}other{# genvägar har tagits bort}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Flytta högst upp till vänster"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Användarnärvaro har upptäckts"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ställ in en standardapp för anteckningar i inställningarna"</string>
     <string name="install_app" msgid="5066668100199613936">"Installera appen"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vill du spegla till extern skärm?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Den inre skärmen speglas. Den främre skärmen stängs av."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Spegla skärm"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 1bd2ed7..b056cf7 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Imeshindwa kuweka mipangilio ya kufungua kwa uso. Nenda kwenye Mipangilio ili ujaribu tena."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Gusa kitambua alama ya kidole"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Bonyeza aikoni ya kufungua ili uendelee"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Imeshindwa kutambua uso. Tumia alama ya kidole."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Uso hautambuliki. Tumia alama ya kidole."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Imeshindwa kutambua uso"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Uso hautambuliki"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Badala yake, tumia alama ya kidole"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Kipengele cha Kufungua kwa Uso hakipatikani"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth imeunganishwa."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Inachaji • Itajaa baada ya <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Telezesha kidole kushoto ili uanze mafunzo ya pamoja"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Fungua kihariri cha wijeti"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Weka mapendeleo"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Funga"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Ongeza, ondoa na upange upya wijeti zako katika nafasi hii"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Weka wijeti zingine"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Bonyeza kwa muda mrefu uweke mapendeleo ya wijeti"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Ondoa"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ongeza wijeti"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Nimemaliza"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Je, ungependa kuwasha Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Ili uunganishe Kibodi yako kwenye kompyuta yako kibao, lazima kwanza uwashe Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Washa"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Udhibiti wa arifa"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Imewashwa - Inayolenga nyuso"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Ukiwa na udhibiti wa arifa, unaweza kuweka kiwango cha umuhimu wa arifa za programu kuanzia 0 hadi 5. \n\n"<b>"Kiwango cha 5"</b>" \n- Onyesha katika sehemu ya juu ya orodha ya arifa \n- Ruhusu ukatizaji wa skrini nzima \n- Ruhusu arifa za kuchungulia kila wakati\n\n"<b>"Kiwango cha 4"</b>" \n- Zuia ukatizaji wa skrini nzima\n- Ruhusu arifa za kuchungulia kila wakati \n\n"<b>"Kiwango cha 3"</b>" \n- Zuia ukatizaji wa skrini nzima\n- Usiruhusu kamwe arifa za kuchungulia\n\n"<b>"Kiwango cha 2"</b>" \n- Zuia ukatizaji wa skrini nzima\n- Usiruhusu kamwe arifa za kuchungulia \n- Usiruhusu kamwe sauti au mtetemo \n\n"<b>"Kiwango cha 1"</b>" \n- Zuia ukatizaji wa skrini nzima \n- Usiruhusu kamwe arifa za kuchungulia \n- Usiruhusu kamwe sauti na mtetemo \n- Usionyeshe skrini iliyofungwa na sehemu ya arifa \n- Onyesha katika sehemu ya chini ya orodha ya arifa \n\n"<b>"Kiwango cha 0"</b>" \n- Zuia arifa zote kutoka programu"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Nimemaliza"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Tumia"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Zima arifa"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Weka mipangilio"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Hifadhi"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Vidokezo"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Ufikivu"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Programu Zinazofunguka Papo Hapo"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"Programu ya <xliff:g id="APP">%1$s</xliff:g> inatumika"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Programu inafunguka bila kusakinishwa."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Gusa ili ufungue vipengele vya ufikivu. Weka mapendeleo au ubadilishe kitufe katika Mipangilio.\n\n"<annotation id="link">"Angalia mipangilio"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Sogeza kitufe kwenye ukingo ili ukifiche kwa muda"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Tendua"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Kitufe cha zana za ufikivu kimefichwa"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Gusa ili uone kitufe cha zana za ufikivu"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Njia ya mkato ya <xliff:g id="FEATURE_NAME">%s</xliff:g> imeondolewa"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Njia # ya mkato imeondolewa}other{Njia # za mkato zimeondolewa}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Sogeza juu kushoto"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Imetambua uwepo wa mtumiaji"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Teua programu chaguomsingi ya madokezo katika Mipangilio"</string>
     <string name="install_app" msgid="5066668100199613936">"Sakinisha programu"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Ungependa kuonyesha kwenye skrini ya nje?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Mwonekano wa ndani wa kifaa chako utaakisiwa. Mwonekano wa mbele wa kifaa chako utazimwa."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Akisi skrini"</string>
diff --git a/packages/SystemUI/res/values-sw720dp-land/config.xml b/packages/SystemUI/res/values-sw720dp-land/config.xml
index be34a48..0955f67 100644
--- a/packages/SystemUI/res/values-sw720dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/config.xml
@@ -24,9 +24,6 @@
     <!-- The maximum number of tiles in the QuickQSPanel -->
     <integer name="quick_qs_panel_max_tiles">6</integer>
 
-    <!-- Whether to use the split 2-column notification shade -->
-    <bool name="config_use_split_notification_shade">true</bool>
-
     <!-- The number of columns in the QuickSettings -->
     <integer name="quick_settings_num_columns">3</integer>
 
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 0cd076f..42db082 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"\'முகம் காட்டித் திறத்தல்\' அம்சத்தை அமைக்க முடியவில்லை. அமைப்புகளுக்குச் சென்று மீண்டும் முயலவும்."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"கைரேகை சென்சாரைத் தொடவும்"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"தொடர, அன்லாக் ஐகானை அழுத்துங்கள்"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"முகத்தை அடையாளம் காண முடியவில்லை. கைரேகையைப் பயன்படுத்தவும்."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"முகத்தைக் கண்டறிய முடியவில்லை. கைரேகை பயன்படுத்துக"</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"முகத்தை கண்டறிய இயலவில்லை"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"முகம் கண்டறிய முடியவில்லை"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"கைரேகையை உபயோகிக்கவும்"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"முகம் காட்டித் திறத்தல் அம்சம் கிடைக்கவில்லை"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"புளூடூத் இணைக்கப்பட்டது."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • சார்ஜாகிறது • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> இல் முழுவதும் சார்ஜாகும்"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"சமூகப் பயிற்சியைத் தொடங்க இடதுபுறம் ஸ்வைப் செய்யுங்கள்"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"விட்ஜெட் எடிட்டரைத் திறக்கும்"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"பிரத்தியேகமாக்குங்கள்"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"மூடுக"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"இந்த இடத்தில் உங்கள் விட்ஜெட்களைச் சேர்க்கலாம், அகற்றலாம், மறுவரிசைப்படுத்தலாம்"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"கூடுதல் விட்ஜெட்களைச் சேருங்கள்"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"விட்ஜெட்களைப் பிரத்தியேகமாக்க நீண்ட நேரம் அழுத்துக"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"அகற்றும்"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"விட்ஜெட்டைச் சேர்"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"முடிந்தது"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"புளூடூத்தை இயக்கவா?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"உங்கள் டேப்லெட்டுடன் கீபோர்டை இணைக்க, முதலில் புளூடூத்தை இயக்க வேண்டும்."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"இயக்கு"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"ஆற்றல்மிக்க அறிவிப்புக் கட்டுப்பாடுகள்"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ஆன் - முகம் அடிப்படையிலானது"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"ஆற்றல்மிக்க அறிவிப்புக் கட்டுப்பாடுகள் மூலம், ஆப்ஸின் அறிவிப்புகளுக்கு முக்கியத்துவ நிலையை (0-5) அமைக்கலாம். \n\n"<b>"நிலை 5"</b>" \n- அறிவிப்புப் பட்டியலின் மேலே காட்டும் \n- முழுத் திரைக் குறுக்கீட்டை அனுமதிக்கும் \n- எப்போதும் நடப்புத் திரையின் மேல் பகுதியில் அறிவிப்புகளைக் காட்டும் \n\n"<b>"நிலை 4"</b>" \n- முழுத் திரைக் குறுக்கீட்டைத் தடுக்கும் \n- எப்போதும் நடப்புத் திரையின் மேல் பகுதியில் அறிவிப்புகளைக் காட்டும் \n\n"<b>"நிலை 3"</b>" \n- முழுத் திரைக் குறுக்கீட்டைத் தடுக்கும் \n- ஒருபோதும் நடப்புத் திரையின் மேல் பகுதியில் அறிவிப்புகளைக் காட்டாது \n\n"<b>"நிலை 2"</b>" \n- முழுத் திரைக் குறுக்கீட்டைத் தடுக்கும் \n- ஒருபோதும் நடப்புத் திரையின் மேல் பகுதியில் அறிவிப்புகளைக் காட்டாது \n- ஒருபோதும் ஒலி எழுப்பாது, அதிர்வுறாது \n\n"<b>"நிலை 1"</b>" \n- முழுத் திரைக் குறுக்கீட்டைத் தடுக்கும் \n- ஒருபோதும் நடப்புத் திரையின் மேல் பகுதியில் அறிவிப்புகளைக் காட்டாது \n- ஒருபோதும் ஒலி எழுப்பாது அல்லது அதிர்வுறாது \n- லாக் ஸ்கிரீன் மற்றும் நிலைப்பட்டியிலிருந்து மறைக்கும் \n- அறிவிப்புகள் பட்டியலின் கீழே காட்டும் \n\n"<b>"நிலை 0"</b>" \n- ஆப்ஸின் எல்லா அறிவிப்புகளையும் தடுக்கும்"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"முடிந்தது"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"பயன்படுத்து"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"அறிவிப்புகளை முடக்கு"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"அமைவு"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"சேமிப்பிடம்"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"குறிப்புகள்"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"அணுகல்தன்மை"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> இயங்குகிறது"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"நிறுவ வேண்டிய தேவையில்லாமல் ஆப்ஸ் திறக்கப்பட்டது."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"அணுகல்தன்மை அம்சத்தை திறக்க தட்டவும். அமைப்பில் பட்டனை பிரத்தியேகமாக்கலாம்/மாற்றலாம்.\n\n"<annotation id="link">"அமைப்பில் காண்க"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"பட்டனைத் தற்காலிகமாக மறைக்க ஓரத்திற்கு நகர்த்தும்"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"செயல்தவிர்"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"அணுகல்தன்மை பட்டன் மறைக்கப்பட்டுள்ளது"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"அணுகல்தன்மை பட்டனைக் காட்ட தட்டவும்"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> ஷார்ட்கட் அகற்றப்பட்டது"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# ஷார்ட்கட் அகற்றப்பட்டது}other{# ஷார்ட்கட்கள் அகற்றப்பட்டன}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"மேலே இடதுபுறத்திற்கு நகர்த்து"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"பயனர் கண்டறியப்பட்டுள்ளார்"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"குறிப்பு எடுப்பதற்கான இயல்புநிலை ஆப்ஸை அமைப்புகளில் அமையுங்கள்"</string>
     <string name="install_app" msgid="5066668100199613936">"ஆப்ஸை நிறுவுங்கள்"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"வெளிப்புறக் காட்சிக்கு மிரர் செய்யவா?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"உங்கள் உட்புற டிஸ்பிளே பிரதிபலிக்கப்படும். உங்கள் முன்புற டிஸ்பிளே முடக்கப்படும்."</string>
     <string name="mirror_display" msgid="2515262008898122928">"டிஸ்பிளேயை மிரர் செய்"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 6a59812..de866a0 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"ఫేస్ అన్‌లాక్‌ను సెటప్ చేయడం సాధ్యపడలేదు. సెట్టింగ్‌లకు వెళ్లి, ఆపై మళ్లీ ట్రై చేయండి."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"వేలిముద్ర సెన్సార్‌ను తాకండి"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"కొనసాగించడానికి అన్‌లాక్ చిహ్నాన్ని నొక్కండి"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"ముఖం గుర్తించలేము. బదులుగా వేలిముద్ర ఉపయోగించండి."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"ముఖం గుర్తించలేదు. బదులుగా వేలిముద్ర ఉపయోగించండి."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"ముఖం గుర్తించడం కుదరలేదు"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"ముఖం గుర్తించబడలేదు"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"బదులుగా వేలిముద్రను ఉపయోగించండి"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"ఫేస్ అన్‌లాక్ అందుబాటులో లేదు"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"బ్లూటూత్ కనెక్ట్ చేయబడింది."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ఛార్జ్ అవుతోంది • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>లో పూర్తిగా ఛార్జ్ అవుతుంది"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"కమ్యూనల్ ట్యుటోరియల్‌ను ప్రారంభించడానికి ఎడమ వైపునకు స్వైప్ చేయండి"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"విడ్జెట్ ఎడిటర్‌ను తెరవండి"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"అనుకూలంగా మార్చండి"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"విస్మరించండి"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"ఈ స్పేస్‌లో మీ విడ్జెట్‌లను జోడించండి, తీసివేయండి, క్రమపద్ధతిలో అమర్చండి"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"మరిన్ని విడ్జెట్‌లను జోడించండి"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"విడ్జెట్‌లను అనుకూలీకరించడానికి, నొక్కి, ఉంచండి"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"తీసివేయండి"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"విడ్జెట్‌ను జోడించండి"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"పూర్తయింది"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"బ్లూటూత్ ఆన్ చేయాలా?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"మీ కీబోర్డ్‌ను మీ టాబ్లెట్‌తో కనెక్ట్ చేయడానికి, మీరు ముందుగా బ్లూటూత్ ఆన్ చేయాలి."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"ఆన్ చేయి"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"పవర్ నోటిఫికేషన్ నియంత్రణలు"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"\'ముఖం ఆధారం\'ను - ఆన్ చేయండి"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"పవర్ నోటిఫికేషన్ కంట్రోల్స్ సాయంతో, మీరు యాప్ నోటిఫికేషన్‌లకు ప్రాముఖ్యతా స్థాయిని 0 నుండి 5 వరకు సెట్ చేయవచ్చు. \n\n"<b>"స్థాయి 5"</b>" \n- నోటిఫికేషన్ లిస్ట్‌ పైభాగంలో చూపబడతాయి \n- ఫుల్-స్క్రీన్ అంతరాయం అనుమతించబడుతుంది \n- ఎల్లప్పుడూ క్విక్ వీక్షణ అందించబడుతుంది \n\n"<b>"స్థాయి 4"</b>\n"- ఫుల్-స్క్రీన్ అంతరాయం నిరోధించబడుతుంది \n- ఎల్లప్పుడూ క్విక్ వీక్షణ అందించబడుతుంది \n\n"<b>"స్థాయి 3"</b>" \n- ఫుల్-స్క్రీన్ అంతరాయం నిరోధించబడుతుంది \n- ఎప్పుడూ క్విక్ వీక్షణ అందించబడదు \n\n"<b>"స్థాయి 2"</b>" \n- ఫుల్-స్క్రీన్ అంతరాయం నిరోధించబడుతుంది \n- ఎప్పుడూ క్విక్ వీక్షణ అందించబడదు \n- ఎప్పుడూ శబ్దం మరియు వైబ్రేషన్ చేయవు \n\n"<b>"స్థాయి 1"</b>" \n- ఫుల్-స్క్రీన్ అంతరాయం నిరోధించబడుతుంది \n- ఎప్పుడూ క్విక్ వీక్షణ అందించబడదు \n- ఎప్పుడూ శబ్దం లేదా వైబ్రేట్ చేయవు \n- లాక్ స్క్రీన్, స్టేటస్ బార్‌ల నుండి దాచబడతాయి \n- నోటిఫికేషన్ లిస్ట్‌ దిగువ భాగంలో చూపబడతాయి \n\n"<b>"స్థాయి 0"</b>" \n- యాప్ నుండి అన్ని నోటిఫికేషన్‌లు బ్లాక్ చేయబడతాయి"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"పూర్తయింది"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"అప్లయి చేయి"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"నోటిఫికేషన్‌లను ఆఫ్ చేయి"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"సెటప్ చేయండి"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"స్టోరేజ్"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"సూచనలు"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"యాక్సెసిబిలిటీ"</string>
     <string name="instant_apps" msgid="8337185853050247304">"ఇన్‌స్టంట్ యాప్‌లు"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> అమలవుతోంది"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"ఇన్‌స్టాల్ చేయకుండా యాప్ తెరవబడింది."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"యాక్సెసిబిలిటీ ఫీచర్‌లను తెరవడానికి ట్యాప్ చేయండి. సెట్టింగ్‌లలో ఈ బటన్‌ను అనుకూలంగా మార్చండి లేదా రీప్లేస్ చేయండి.\n\n"<annotation id="link">"వీక్షణ సెట్టింగ్‌లు"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"తాత్కాలికంగా దానిని దాచడానికి బటన్‌ను చివరకు తరలించండి"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"చర్య రద్దు చేయండి"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"యాక్సెసిబిలిటీ బటన్ దాచబడింది"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"యాక్సెసిబిలిటీ బటన్‌ను చూడటానికి ట్యాప్ చేయండి"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> షార్ట్‌కట్ తీసివేయబడింది"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# షార్ట్‌కట్ తీసివేయబడింది}other{# షార్ట్‌కట్‌లు తీసివేయబడ్డాయి}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ఎగువ ఎడమ వైపునకు తరలించు"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"యూజర్ ఉనికి గుర్తించబడింది"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"సెట్టింగ్‌లలో ఆటోమేటిక్‌గా ఉండేలా ఒక నోట్స్ యాప్‌ను సెట్ చేసుకోండి"</string>
     <string name="install_app" msgid="5066668100199613936">"యాప్‌ను ఇన్‌స్టాల్ చేయండి"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ఎక్స్‌టర్నల్ డిస్‌ప్లే‌కి మిర్రర్‌ చేయాలా?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"మీ లోపలి డిస్‌ప్లే మిర్రర్ చేయబడుతుంది. మీ ముందు వైపు డిస్‌ప్లే ఆఫ్ చేయబడుతుంది."</string>
     <string name="mirror_display" msgid="2515262008898122928">"మిర్రర్ డిస్‌ప్లే"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index dc4c6cf..341a462 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"ตั้งค่าการปลดล็อกด้วยใบหน้าไม่ได้ ไปที่การตั้งค่าเพื่อลองอีกครั้ง"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"แตะเซ็นเซอร์ลายนิ้วมือ"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"กดไอคอนปลดล็อกเพื่อดำเนินการต่อ"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"ไม่รู้จักใบหน้า ใช้ลายนิ้วมือแทน"</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"ไม่รู้จักใบหน้า ใช้ลายนิ้วมือแทน"</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"ไม่รู้จักใบหน้า"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"ไม่รู้จักใบหน้า"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ใช้ลายนิ้วมือแทน"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"การปลดล็อกด้วยใบหน้าไม่พร้อมใช้งาน"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"เชื่อมต่อบลูทูธแล้ว"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • กำลังชาร์จ • จะเต็มในอีก <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"ปัดไปทางซ้ายเพื่อเริ่มบทแนะนำส่วนกลาง"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"เปิดเครื่องมือแก้ไขวิดเจ็ต"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"ปรับแต่ง"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"ปิด"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"เพิ่ม นำออก และจัดลำดับวิดเจ็ตในพื้นที่นี้ใหม่"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"เพิ่มวิดเจ็ตอีก"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"กดค้างเพื่อปรับแต่งวิดเจ็ต"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"นำออก"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"เพิ่มวิดเจ็ต"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"เสร็จสิ้น"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"เปิดบลูทูธไหม"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"หากต้องการเชื่อมต่อแป้นพิมพ์กับแท็บเล็ต คุณต้องเปิดบลูทูธก่อน"</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"เปิด"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"ส่วนควบคุมการแจ้งเตือนแบบเปิด/ปิด"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"เปิด - ตามใบหน้า"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"ส่วนควบคุมการแจ้งเตือนแบบเปิด/ปิดช่วยให้คุณตั้งค่าระดับความสำคัญสำหรับการแจ้งเตือนของแอปได้ตั้งแต่ระดับ 0-5 \n\n"<b>"ระดับ 5"</b>" \n- แสดงที่ด้านบนของรายการแจ้งเตือน \n- อนุญาตให้รบกวนแบบเต็มหน้าจอ \n- อนุญาตให้แสดงชั่วครู่ \n\n"<b>"ระดับ 4"</b>" \n- ป้องกันการรบกวนแบบเต็มหน้าจอ \n- แสดงชั่วครู่เสมอ \n\n"<b>"ระดับ 3"</b>" \n- ป้องกันการรบกวนแบบเต็มหน้าจอ \n- ไม่แสดงชั่วครู่เลย \n\n"<b>"ระดับ 2"</b>" \n- ป้องกันการรบกวนแบบเต็มหน้าจอ \n- ไม่แสดงชั่วครู่เลย \n- ไม่ส่งเสียงหรือสั่นเลย \n\n"<b>"ระดับ 1"</b>" \n- ป้องกันการรบกวนแบบเต็มหน้าจอ \n- ไม่แสดงชั่วครู่เลย \n- ไม่ส่งเสียงหรือสั่นเลย \n- ซ่อนจากหน้าจอล็อกและแถบสถานะ \n- แสดงที่ด้านล่างของรายการแจ้งเตือน \n\n"<b>"ระดับ 0"</b>" \n- บล็อกการแจ้งเตือนทั้งหมดจากแอป"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"เสร็จสิ้น"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"ใช้"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"ปิดการแจ้งเตือน"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"ตั้งค่า"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"พื้นที่เก็บข้อมูล"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"คำแนะนำ"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"การช่วยเหลือพิเศษ"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant App"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> ทำงานอยู่"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"เปิดแอปได้โดยไม่ต้องติดตั้ง"</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"แตะเพื่อเปิดฟีเจอร์การช่วยเหลือพิเศษ ปรับแต่งหรือแทนที่ปุ่มนี้ในการตั้งค่า\n\n"<annotation id="link">"ดูการตั้งค่า"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ย้ายปุ่มไปที่ขอบเพื่อซ่อนชั่วคราว"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"เลิกทำ"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"ปุ่มการช่วยเหลือพิเศษซ่อนอยู่"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"แตะเพื่อแสดงปุ่มการช่วยเหลือพิเศษ"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"นำทางลัด <xliff:g id="FEATURE_NAME">%s</xliff:g> ออกแล้ว"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{นำทางลัด # รายการออกแล้ว}other{นำทางลัด # รายการออกแล้ว}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ย้ายไปด้านซ้ายบน"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"ตรวจพบการแสดงข้อมูลของผู้ใช้"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"กำหนดแอปการจดบันทึกเริ่มต้นในการตั้งค่า"</string>
     <string name="install_app" msgid="5066668100199613936">"ติดตั้งแอป"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"มิเรอร์ไปยังจอแสดงผลภายนอกไหม"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ระบบจะมิเรอร์หน้าจอด้านใน และจะปิดหน้าจอด้านหน้า"</string>
     <string name="mirror_display" msgid="2515262008898122928">"มิเรอร์จอแสดงผล"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index c09ac97..3ac3d3a 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Hindi na-set up ang pag-unlock gamit ang mukha. Pumunta sa Mga Setting para subukan ulit."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Pindutin ang fingerprint sensor"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Pindutin ang icon ng pag-unlock para magpatuloy"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Hindi makilala ang mukha. Gumamit ng fingerprint."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Hindi nakilala ang mukha. Gumamit ng fingerprint."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Hindi makilala ang mukha"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Hindi nakilala ang mukha"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Gumamit ng fingerprint"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Hindi available ang Pag-unlock Gamit ang Mukha"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Nakakonekta ang Bluetooth."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nagcha-charge • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> na lang para mapuno"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Mag-swipe pakaliwa para simulan ang communal na tutorial"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Buksan ang editor ng widget"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"I-customize"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"I-dismiss"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Magdagdag, mag-alis, at baguhin ang ayos ng iyong mga widget sa space na ito"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Magdagdag ng higit pang widget"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pindutin nang matagal para i-customize ang mga widget"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Alisin"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Magdagdag ng widget"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Tapos na"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"I-on ang Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Upang ikonekta ang iyong keyboard sa iyong tablet, kailangan mo munang i-on ang Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"I-on"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Mga kontrol sa notification ng power"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Naka-on - Batay sa mukha"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Sa pamamagitan ng mga kontrol sa notification ng power, magagawa mong itakda ang antas ng kahalagahan ng mga notification ng isang app mula 0 hanggang 5. \n\n"<b>"Antas 5"</b>" \n- Ipakita sa itaas ng listahan ng notification \n- Payagan ang pag-istorbo kapag full screen \n- Palaging sumilip \n\n"<b>"Antas 4"</b>" \n- Pigilan ang pag-istorbo kapag full screen \n- Palaging sumilip \n\n"<b>"Antas 3"</b>" \n- Pigilan ang pag-istorbo kapag full screen \n- Huwag kailanman sumilip \n\n"<b>"Antas 2"</b>" \n- Pigilan ang pag-istorbo kapag full screen \n- Huwag kailanman sumilip \n- Huwag kailanman tumunog o mag-vibrate \n\n"<b>"Antas 1"</b>" \n- Pigilan ang pag-istorbo kapag full screen \n- Huwag kailanman sumilip \n- Huwag kailanman tumunog o mag-vibrate \n- Itago sa lock screen at status bar \n- Ipakita sa ibaba ng listahan ng notification \n\n"<b>"Antas 0"</b>" \n- I-block ang lahat ng notification mula sa app"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Tapos na"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Ilapat"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"I-off ang mga notification"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Setup"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Storage"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Mga Hint"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Accessibility"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"Tumatakbo ang <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Nabuksan ang app nang hindi ini-install."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"I-tap, buksan mga feature ng accessibility. I-customize o palitan button sa Mga Setting.\n\n"<annotation id="link">"Tingnan ang mga setting"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Ilipat ang button sa gilid para pansamantala itong itago"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"I-undo"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Nakatago ang button ng accessibility"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"I-tap para ipakita ang button ng accessibility"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> shortcut ang naalis"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# shortcut ang naalis}one{# shortcut ang naalis}other{# na shortcut ang naalis}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Ilipat sa kaliwa sa itaas"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Na-detect ang presensya ng user"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Magtakda ng default na app sa pagtatala sa Mga Setting"</string>
     <string name="install_app" msgid="5066668100199613936">"I-install ang app"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"I-mirror sa external na display?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Imi-mirror ang inner display mo. Io-off ang iyong front display."</string>
     <string name="mirror_display" msgid="2515262008898122928">"I-mirror ang display"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index ee1909b..9a09961 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Yüz tanıma kilidi kurulamadı. Tekrar denemek için Ayarlar\'a gidin."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Parmak izi sensörüne dokunun"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Devam etmek için kilit açma simgesine basın"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Yüz tanınamadı. Bunun yerine parmak izi kullanın."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Yüz tanınmadı. Bunun yerine parmak izi kullanın."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Yüz tanınamadı"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Yüz tanınmadı"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Bunun yerine parmak izi kullanın"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Yüz Tanıma Kilidi kullanılamıyor"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth bağlandı."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Şarj oluyor • Dolmasına <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> kaldı"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Ortak eğitimi başlatmak için sola kaydırın"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Widget düzenleyiciyi açın"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Özelleştir"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Kapat"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Bu alanda widget\'larınızı ekleyin, kaldırın ve yeniden sıralayın"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Daha fazla widget ekle"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Widget\'ları özelleştirmek için uzun basın"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Kaldır"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Widget ekle"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Bitti"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Bluetooth açılsın mı?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Klavyenizi tabletinize bağlamak için önce Bluetooth\'u açmanız gerekir."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Aç"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Güç bildirim kontrolleri"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Açık - Yüze göre"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Güç bildirim kontrolleriyle, bir uygulamanın bildirimleri için 0 ile 5 arasında bir önem düzeyi ayarlayabilirsiniz. \n\n"<b>"5. Düzey"</b>" \n- Bildirim listesinin en üstünde gösterilsin \n- Tam ekran kesintisine izin verilsin \n- Ekranda her zaman kısaca belirsin \n\n"<b>"4. Düzey"</b>" \n- Tam ekran kesintisi engellensin \n- Ekranda her zaman kısaca belirsin \n\n"<b>"3. Düzey"</b>" \n- Tam ekran kesintisi engellensin \n- Ekranda hiçbir zaman kısaca belirmesin \n\n"<b>"2. Düzey"</b>" \n- Tam ekran kesintisi engellensin \n- Ekranda hiçbir zaman belirmesin \n- Hiçbir zaman ses çıkarmasın ve titreştirmesin \n\n"<b>"1. Düzey"</b>" \n- Tam ekran kesintisi engellensin \n- Ekranda hiçbir zaman kısaca belirmesin \n- Hiçbir zaman ses çıkarmasın veya titreştirmesin \n- Kilit ekranından ve durum çubuğundan gizlensin \n- Bildirim listesinin en altında gösterilsin \n\n"<b>"0. Düzey"</b>" \n- Uygulamadan gelen tüm bildirimler engellensin"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Bitti"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Uygula"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Bildirimleri kapat"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Kurulum"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Depolama alanı"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"İpuçları"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Erişilebilirlik"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Hazır Uygulamalar"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> çalışıyor"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Uygulama yüklenmeden açıldı."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Erişilebilirlik özelliklerini açmak için dokunun. Bu düğmeyi Ayarlar\'dan özelleştirin veya değiştirin.\n\n"<annotation id="link">"Ayarları göster"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Düğmeyi geçici olarak gizlemek için kenara taşıyın"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Geri al"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Erişilebilirlik düğmesi gizlendi"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Erişilebilirlik düğmesini göstermek için dokunun"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> kısayol kaldırıldı"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# kısayol kaldırıldı}other{# kısayol kaldırıldı}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Sol üste taşı"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Kullanıcı varlığı algılandı"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ayarlar\'ı kullanarak varsayılan notlar ayarlayın"</string>
     <string name="install_app" msgid="5066668100199613936">"Uygulamayı yükle"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Harici ekrana yansıtılsın mı?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"İç ekranınız yansıtılacak. Ön ekranınız kapatılacak."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Ekranı yansıt"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 04a97bc..152d93c 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Не вдалося налаштувати фейс-контроль. Перейдіть у налаштування, щоб повторити спробу."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Торкніться сканера відбитків пальців"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Щоб продовжити, натисніть значок розблокування"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Обличчя не розпізнано. Скористайтеся відбитком пальця."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Обличчя не розпізнано. Скористайтеся відб. пальця."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Обличчя не розпізнано"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Обличчя не розпізнано"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Скористайтеся відбитком"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Фейс-контроль недоступний"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth під’єднано."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Заряджання • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до повного заряду"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Проведіть пальцем уліво, щоб відкрити спільний навчальний посібник"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Відкрити редактор віджетів"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Налаштувати"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Закрити"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Додати, вилучити чи впорядкувати віджети в цьому просторі"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Додати більше віджетів"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Утримуйте, щоб налаштувати віджети"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Видалити"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Додати віджет"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Готово"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Увімкнути Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Щоб під’єднати клавіатуру до планшета, спершу потрібно ввімкнути Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Увімкнути"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Елементи керування сповіщеннями"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Увімкнути (за обличчям)"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"За допомогою елементів керування сповіщеннями ви можете налаштувати пріоритет сповіщень додатка – від 0 до 5 рівня. \n\n"<b>"Рівень 5"</b>\n"- Показувати сповіщення вгорі списку \n- Виводити на весь екран \n- Завжди показувати короткі сповіщення \n\n"<b>"Рівень 4"</b>\n"- Не виводити на весь екран \n- Завжди показувати короткі сповіщення \n\n"<b>"Рівень 3"</b>\n"- Не виводити на весь екран \n- Не показувати короткі сповіщення \n\n"<b>"Рівень 2"</b>\n"- Не виводити на весь екран \n- Не показувати короткі сповіщення \n- Вимкнути звук і вібросигнал \n\n"<b>"Рівень 1"</b>\n"- Не виводити на весь екран \n- Не показувати короткі сповіщення \n- Вимкнути звук і вібросигнал \n- Не показувати на заблокованому екрані та в рядку стану \n- Показувати сповіщення внизу списку \n\n"<b>"Рівень 0"</b>\n"- Блокувати всі сповіщення з додатка"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Готово"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Застосувати"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Вимкнути сповіщення"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Налаштування"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Пам’ять"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Поради"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Доступність"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Додатки з миттєвим запуском"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> працює"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Додаток відкрито без встановлення."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Торкніться, щоб відкрити функції доступності. Змінити або замінити цю кнопку можна в Налаштуваннях.\n\n"<annotation id="link">"Налаштування"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Щоб тимчасово сховати кнопку, перемістіть її на край екрана"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Відмінити"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Кнопку функцій доступності приховано"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Натисніть, щоб відобразити кнопку функцій доступності"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g>: швидкий запуск вилучено"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# ярлик вилучено}one{# ярлик вилучено}few{# ярлики вилучено}many{# ярликів вилучено}other{# ярлика вилучено}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Перемістити ліворуч угору"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Виявлено присутність користувача"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Призначте стандартний додаток для нотаток у налаштуваннях"</string>
     <string name="install_app" msgid="5066668100199613936">"Установити додаток"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Дублювати на зовнішньому екрані?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ваш внутрішній екран буде продубльовано. Передній екран буде вимкнено."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Дублювати екран"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index fd984b9..2ec5007 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"فیس اَن لاک کو سیٹ اپ نہیں کیا جا سکا۔ دوبارہ کوشش کرنے کیلئے ترتیبات پر جائیں۔"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"فنگر پرنٹ سینسر پر ٹچ کریں"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"جاری رکھنے کیلئے غیر مقفل کرنے کا آئیکن دبائیں"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"چہرے کی شناخت نہیں ہو سکی۔ اس کے بجائے فنگر پرنٹ استعمال کریں۔"</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"چہرے کی شناخت نہیں ہو سکی۔ اس کے بجائے فنگر پرنٹ استعمال کریں۔"</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"چہرے کی پہچان نہیں ہو سکی"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"چہرے کی شناخت نہیں ہو سکی"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"اس کے بجائے فنگر پرنٹ استعمال کریں"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"فیس اَنلاک غیر دستیاب ہے"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"بلوٹوتھ مربوط ہے۔"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • چارج ہو رہا ہے • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> میں مکمل"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"کمیونل ٹیوٹوریل شروع کرنے کے لیے بائیں سوائپ کریں"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"ویجیٹ ایڈیٹر کو کھولیں"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"حسب ضرورت بنائیں"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"برخاست کریں"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"اس اسپیس میں اپنے ویجٹس شامل کریں، ہٹائیں اور دوبارہ ترتیب دیں"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"مزید ویجٹس شامل کریں"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ویجٹس کو حسب ضرورت بنانے کے لیے لانگ پریس کریں"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"ہٹائیں"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ویجیٹ شامل کریں"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"ہو گیا"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"بلوٹوتھ آن کریں؟"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"اپنے کی بورڈ کو اپنے ٹیبلٹ کے ساتھ منسلک کرنے کیلئے پہلے آپ کو اپنا بلو ٹوتھ آن کرنا ہو گا۔"</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"آن کریں"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"پاور اطلاع کے کنٹرولز"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"آن - چہرے پر مبنی"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"پاور اطلاع کنٹرولز کے ساتھ آپ کسی ایپ کی اطلاعات کیلئے 0 سے 5 تک اہمیت کی سطح سیٹ کر سکتے ہیں۔ \n\n"<b>"سطح 5"</b>\n"- اطلاعات کی فہرست کے اوپر دکھائیں \n- پوری اسکرین کی مداخلت کی اجازت دیں \n- ہمیشہ جھانکنا\n\n"<b>"سطح 4"</b>\n"- پوری اسکرین کی مداخلت کو روکیں \n- ہمیشہ جھانکنا\n\n"<b>"سطح 3"</b>\n"- پوری اسکرین کی مداخلت کو روکیں \n- کبھی نہ جھانکنا \n\n"<b>"سطح 2"</b>\n"- پوری اسکرین کی مداخلت کو روکیں \n- کبھی نہ جھانکنا \n- کبھی آواز اور ارتعاش پیدا نہ کرنا \n\n"<b>" سطح 1"</b>\n"- پوری اسکرین کی مداخلت کو روکنا \n- کبھی نہ جھانکنا \n- کبھی بھی آواز یا ارتعاش پیدا نہ کرنا\n- مقفل اسکرین اور اسٹیٹس بار سے چھپانا \n - اطلاع کی فہرست کی نیچے دکھانا \n\n"<b>"سطح 0"</b>\n"- ایپ سے تمام اطلاعات مسدود کریں"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"ہو گیا"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"لاگو کریں"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"اطلاعات کو آف کریں"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"سیٹ اپ"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"اسٹوریج"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"اشارات"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"ایکسیسبیلٹی"</string>
     <string name="instant_apps" msgid="8337185853050247304">"فوری ایپس"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> چل رہی ہے"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"انسٹال کیے بغیر کھلنے والی ایپ۔"</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ایکسیسبیلٹی خصوصیات کھولنے کے لیے تھپتھپائیں۔ ترتیبات میں اس بٹن کو حسب ضرورت بنائیں یا تبدیل کریں۔\n\n"<annotation id="link">"ترتیبات ملاحظہ کریں"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"عارضی طور پر بٹن کو چھپانے کے لئے اسے کنارے پر لے جائیں"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"کالعدم کریں"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"ایکسیسبیلٹی بٹن پوشیدہ ہے"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"ایکسیسبیلٹی بٹن دکھانے کے لیے تھپتھپائیں"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"‫<xliff:g id="FEATURE_NAME">%s</xliff:g> شارٹ کٹ ہٹا دیا گیا"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# شارٹ کٹ ہٹا دیا گیا}other{# شارٹ کٹس ہٹا دیے گئے}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"اوپر بائیں جانب لے جائیں"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"صارف کی موجودگی کا پتہ چلا ہے"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ترتیبات میں ڈیفالٹ نوٹس ایپ سیٹ کریں"</string>
     <string name="install_app" msgid="5066668100199613936">"ایپ انسٹال کریں"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"بیرونی ڈسپلے پر مرر کریں؟"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"آپ کے اندرونی ڈسپلے کو دو طرفہ مطابقت پذیر بنایا جائے گا۔ آپ کا فرنٹ ڈسپلے آف ہو جائے گا۔"</string>
     <string name="mirror_display" msgid="2515262008898122928">"ڈسپلے کو مرر کریں"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index b9a9832..8bcda40 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Yuz bilan ochish sozlanmadimi. Sozlamalarni ochib, qaytadan urining."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Barmoq izi skaneriga tegining"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Davom etish uchun qulfni ochish belgisini bosing"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Bu yuz notanish. Barmoq izi orqali urining."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Yuz aniqlanmadi. Barmoq izi orqali urining."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Yuz aniqlanmadi"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Yuz aniqlanmadi"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Barmoq izi orqali urining"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Yuz bilan ochilmaydi."</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ulandi."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Quvvat olmoqda • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> qoldi"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Qoʻllanma bilan tanishish uchun chapga suring"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Vidjet muharririni ochish"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Moslash"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Yopish"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Bu xonadagi vidjetlaringizni olib tashlang, tartibini oʻzgartiring va yangisini qoʻshing"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Koʻproq vidjetlar qoʻshish"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Vidjetlarni sozlash uchun bosib turing"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Olib tashlash"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Vidjet kiritish"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Tayyor"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Bluetooth yoqilsinmi?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Klaviaturani planshetingizga ulash uchun Bluetooth xizmatini yoqishingiz kerak."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Yoqish"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Bildirishnomalar uchun kengaytirilgan boshqaruv"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Yoqish - Yuz asosida"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Bildirishnomalar uchun kengaytirilgan boshqaruv yordamida ilova bildirishnomalarining muhimlik darajasini (0-5) sozlash mumkin. \n\n"<b>"5-daraja"</b>" \n- Bildirishnomani ro‘yxatning boshida ko‘rsatish \n- To‘liq ekranli bildirishnomalarni ko‘rsatish \n- Qalqib chiquvchi bildirishnomalarni ko‘rsatish \n\n"<b>"4-daraja"</b>" \n- To‘liq ekranli bildirishnomalarni ko‘rsatmaslik \n- Qalqib chiquvchi bildirishnomalarni ko‘rsatish \n\n"<b>"3-daraja"</b>" \n- To‘liq ekranli bildirishnomalarni ko‘rsatmaslik \n- Qalqib chiquvchi bildirishnomalarni ko‘rsatmaslik \n\n"<b>"2-daraja"</b>" \n- To‘liq ekranli bildirishnomalarni ko‘rsatmaslik \n- Qalqib chiquvchi bildirishnomalarni ko‘rsatmaslik \n- Ovoz va tebranishdan foydalanmaslik \n\n"<b>"1-daraja"</b>" \n- To‘liq ekranli bildirishnomalarni ko‘rsatmaslik \n- Qalqib chiquvchi bildirishnomalarni ko‘rsatmaslik \n- Ovoz va tebranishdan foydalanmaslik \n- Ekran qulfi va holat qatorida ko‘rsatmaslik \n- Bildirishnomani ro‘yxatning oxirida ko‘rsatish \n\n"<b>"0-daraja"</b>" \n- Ilovadan keladigan barcha bildirishnomalarni bloklash"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Tayyor"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Tatbiq etish"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Bildirishnoma kelmasin"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Sozlash"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Xotira"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Maslahatlar"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Qulayliklar"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Darhol ochiladigan ilovalar"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> ishlayapti"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Ilova o‘rnatilmasdan ochildi."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Maxsus imkoniyatlarni ochish uchun bosing Sozlamalardan moslay yoki almashtira olasiz.\n\n"<annotation id="link">"Sozlamalar"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Vaqtinchalik berkitish uchun tugmani qirra tomon suring"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Bekor qilish"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Qulayliklar tugmasi yashirilgan"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Qulayliklar tugmasini koʻrsatish uchun bosing"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"<xliff:g id="FEATURE_NAME">%s</xliff:g> ta yorliq olindi"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# ta yorliq olindi}other{# ta yorliq olindi}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Yuqori chapga surish"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Foydalanuvchi aniqlandi"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standart qaydlar ilovasini Sozlamalar orqali tanlang"</string>
     <string name="install_app" msgid="5066668100199613936">"Ilovani oʻrnatish"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Tashqi displeyda aks ettirilsinmi?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ichki ekran uchun aks ettirish yoqiladi. Old ekran oʻchiriladi."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Displeyni aks ettirish"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index b1ff9a8..51d9c48 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Không thiết lập được tính năng Mở khoá bằng khuôn mặt. Hãy chuyển đến phần Cài đặt để thử lại."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Chạm vào cảm biến vân tay"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Nhấn vào biểu tượng mở khoá để tiếp tục"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Không thể nhận dạng khuôn mặt. Hãy dùng vân tay."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Không nhận dạng được khuôn mặt. Hãy dùng vân tay."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Không nhận ra khuôn mặt"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Không thể nhận dạng mặt"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Hãy dùng vân tay"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Không dùng được tính năng Mở khoá bằng khuôn mặt"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Đã kết nối bluetooth."</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Đang sạc • Sẽ đầy sau <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Vuốt sang trái để bắt đầu xem hướng dẫn chung"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Mở trình chỉnh sửa tiện ích"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Tuỳ chỉnh"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Đóng"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Thêm, xoá và sắp xếp lại các tiện ích trong không gian này."</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Thêm tiện ích khác"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Nhấn và giữ để tuỳ chỉnh tiện ích"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Xoá"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Thêm tiện ích"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Xong"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Bật Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Để kết nối bàn phím với máy tính bảng, trước tiên, bạn phải bật Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Bật"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Điều khiển thông báo nguồn"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Đang bật – Dựa trên khuôn mặt"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Với các kiểm soát thông báo nguồn, bạn có thể đặt cấp độ quan trọng từ 0 đến 5 cho các thông báo của ứng dụng. \n\n"<b>"Cấp 5"</b>" \n- Hiển thị ở đầu danh sách thông báo \n- Cho phép gián đoạn ở chế độ toàn màn hình \n- Luôn xem nhanh \n\n"<b>"Cấp 4"</b>" \n- Ngăn gián đoạn ở chế độ toàn màn hình \n- Luôn xem nhanh \n\n"<b>"Cấp 3"</b>" \n- Ngăn gián đoạn ở chế độ toàn màn hình \n- Không bao giờ xem nhanh \n\n"<b>"Cấp 2"</b>" \n- Ngăn gián đoạn ở chế độ toàn màn hình \n- Không bao giờ xem nhanh \n- Không bao giờ có âm báo và rung \n\n"<b>"Cấp 1"</b>" \n- Ngăn gián đoạn ở chế độ toàn màn hình \n- Không bao giờ xem nhanh \n- Không bao giờ có âm báo và rung \n- Ẩn khỏi màn hình khóa và thanh trạng thái \n- Hiển thị ở cuối danh sách thông báo \n\n"<b>"Cấp 0"</b>" \n- Chặn tất cả các thông báo từ ứng dụng"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Xong"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Áp dụng"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Tắt thông báo"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Thiết lập"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Bộ nhớ"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Gợi ý"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Hỗ trợ tiếp cận"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Ứng dụng tức thì"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> đang chạy"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Ứng dụng được mở mà không cần cài đặt."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Nhấn để mở bộ tính năng hỗ trợ tiếp cận. Tuỳ chỉnh/thay thế nút này trong phần Cài đặt.\n\n"<annotation id="link">"Xem chế độ cài đặt"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Di chuyển nút sang cạnh để ẩn nút tạm thời"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Huỷ"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Đã ẩn nút hỗ trợ tiếp cận"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Nhấn để hiện nút hỗ trợ tiếp cận"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Đã xoá phím tắt dành cho <xliff:g id="FEATURE_NAME">%s</xliff:g>"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Đã xoá # lối tắt}other{Đã xoá # lối tắt}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Chuyển lên trên cùng bên trái"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Phát hiện thấy người dùng đang hiện diện"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Đặt ứng dụng ghi chú mặc định trong phần Cài đặt"</string>
     <string name="install_app" msgid="5066668100199613936">"Cài đặt ứng dụng"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Phản chiếu sang màn hình ngoài?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Màn hình trong của bạn sẽ được phản chiếu. Màn hình ngoài của bạn sẽ tắt."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Phản chiếu màn hình"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 237fd57..847418a 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"无法设置“人脸解锁”功能。请前往“设置”重试。"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"请触摸指纹传感器"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"按下解锁图标即可继续"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"无法识别人脸。请改用指纹。"</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"无法识别面孔。请改用指纹。"</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"人脸识别失败"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"无法识别面孔"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"改用指纹"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"无法使用人脸解锁功能"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"蓝牙已连接。"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 正在充电 • 将于 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>后充满"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"向左滑动即可启动公共教程"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"打开微件编辑器"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"自定义"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"关闭"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"在此空间内添加、移除和重新排列您的微件"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"添加更多微件"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"长按即可自定义微件"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"移除"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"添加微件"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"完成"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"要开启蓝牙吗?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"要将您的键盘连接到平板电脑,您必须先开启蓝牙。"</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"开启"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"高级通知设置"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"已开启 - 基于人脸"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"利用高级通知设置,您可以为应用通知设置从 0 级到 5 级的重要程度等级。\n\n"<b>"5 级"</b>" \n- 在通知列表顶部显示 \n- 允许全屏打扰 \n- 一律短暂显示通知 \n\n"<b>"4 级"</b>" \n- 禁止全屏打扰 \n- 一律短暂显示通知 \n\n"<b>"3 级"</b>" \n- 禁止全屏打扰 \n- 一律不短暂显示通知 \n\n"<b>"2 级"</b>" \n- 禁止全屏打扰 \n- 一律不短暂显示通知 \n- 一律不发出声音或振动 \n\n"<b>"1 级"</b>" \n- 禁止全屏打扰 \n- 一律不短暂显示通知 \n- 一律不发出声音或振动 \n- 不在锁定屏幕和状态栏中显示 \n- 在通知列表底部显示 \n\n"<b>"0 级"</b>" \n- 屏蔽应用的所有通知"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"完成"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"应用"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"关闭通知"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"设置"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"存储空间"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"提示"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"无障碍"</string>
     <string name="instant_apps" msgid="8337185853050247304">"免安装应用"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"正在运行<xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"已打开免安装应用。"</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"点按即可打开无障碍功能。您可在“设置”中自定义或更换此按钮。\n\n"<annotation id="link">"查看设置"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"将按钮移到边缘,即可暂时将其隐藏"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"撤消"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"“无障碍”按钮已隐藏"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"点按即可显示“无障碍”按钮"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"已移除“<xliff:g id="FEATURE_NAME">%s</xliff:g>”快捷方式"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{已移除 # 个快捷方式}other{已移除 # 个快捷方式}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"移至左上角"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"检测到用户存在"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"在设置中设置默认记事应用"</string>
     <string name="install_app" msgid="5066668100199613936">"安装应用"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"镜像到外接显示屏?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"系统将镜像您的内屏,而关闭外屏。"</string>
     <string name="mirror_display" msgid="2515262008898122928">"镜像到显示屏"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 313af30..b5a6089 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"無法設定「面孔解鎖」功能,請前往「設定」再試一次。"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"請輕觸指紋感應器"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"按解鎖圖示即可繼續"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"無法辨識面孔,請改用指紋完成驗證。"</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"無法辨識面孔,請改用指紋完成驗證。"</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"無法辨識面孔"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"無法辨識面孔"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"請改用指紋"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"無法使用面孔解鎖"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"藍牙連線已建立。"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 充電中 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充滿電"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"向左滑動即可開始共用教學課程"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"開啟小工具編輯器"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"自訂"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"關閉"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"增、移除小工具,以及調整小工具在此空間中的位置"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"新增更多小工具"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"長按即可自訂小工具"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"移除"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"新增小工具"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"完成"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"要開啟藍牙嗎?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"如要將鍵盤連接至平板電腦,請先開啟藍牙。"</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"開啟"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"通知控制項"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"已開啟 - 根據面孔偵測"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"通知控制項讓你設定應用程式通知的重要性 (0 至 5 級)。\n\n"<b>"第 5 級"</b>" \n- 在通知清單頂部顯示 \n- 允許全螢幕騷擾 \n- 一律顯示通知 \n\n"<b>"第 4 級"</b>" \n- 阻止全螢幕騷擾 \n- 一律顯示通知 \n\n"<b>"第 3 級"</b>" \n- 阻止全螢幕騷擾 \n- 永不顯示通知 \n\n"<b>"第 2 級"</b>" \n- 阻止全螢幕騷擾 \n- 永不顯示通知 \n- 永不發出聲響和震動 \n\n"<b>"第 1 級"</b>" \n- 阻止全螢幕騷擾 \n- 永不顯示通知 \n- 永不發出聲響和震動 \n- 從上鎖畫面和狀態列中隱藏 \n- 在通知清單底部顯示 \n\n"<b>"第 0 級"</b>" \n- 封鎖所有應用程式通知"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"完成"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"套用"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"關閉通知"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"設定"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"儲存空間"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"提示"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"無障礙功能"</string>
     <string name="instant_apps" msgid="8337185853050247304">"免安裝應用程式"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> 運作中"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"已開啟免安裝應用程式。"</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"㩒一下就可以開無障礙功能。喺「設定」度自訂或者取代呢個按鈕。\n\n"<annotation id="link">"查看設定"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"將按鈕移到邊緣即可暫時隱藏"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"復原"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"已隱藏無障礙功能按鈕"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"輕按即可顯示無障礙功能按鈕"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"已移除「<xliff:g id="FEATURE_NAME">%s</xliff:g>」捷徑"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{已移除 # 個捷徑}other{已移除 # 個捷徑}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"移去左上方"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"偵測到使用者動態"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"在「設定」中指定預設筆記應用程式"</string>
     <string name="install_app" msgid="5066668100199613936">"安裝應用程式"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"要鏡像投射至外部顯示屏嗎?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"鏡像畫面將顯示在內部螢幕,前方螢幕則會關閉。"</string>
     <string name="mirror_display" msgid="2515262008898122928">"鏡像顯示"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 6a13d3d..417b70e 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"無法設定人臉解鎖功能,請前往「設定」再試一次。"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"請輕觸指紋感應器"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"按下「解鎖」圖示即可繼續操作"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"無法辨識臉孔,請改用指紋完成驗證。"</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"無法辨識臉孔,請改用指紋完成驗證。"</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"無法辨識臉孔"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"無法辨識臉孔"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"請改用指紋"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"無法使用人臉解鎖功能"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"藍牙連線已建立。"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 充電中 • 將於 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充飽"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"向左滑動即可啟動通用教學課程"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"開啟小工具編輯器"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"自訂"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"關閉"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"新增、移除小工具,以及調整小工具在這個空間中的位置"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"新增更多小工具"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"長按即可自訂小工具"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"移除"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"新增小工具"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"完成"</string>
@@ -567,9 +576,9 @@
     <string name="volume_dialog_ringer_guidance_ring" msgid="9143194270463146858">"有來電和通知時會響鈴 (<xliff:g id="VOLUME_LEVEL">%1$s</xliff:g>)"</string>
     <string name="system_ui_tuner" msgid="1471348823289954729">"系統使用者介面調整精靈"</string>
     <string name="status_bar" msgid="4357390266055077437">"狀態列"</string>
-    <string name="demo_mode" msgid="263484519766901593">"系統 UI 示範模式"</string>
-    <string name="enable_demo_mode" msgid="3180345364745966431">"啟用示範模式"</string>
-    <string name="show_demo_mode" msgid="3677956462273059726">"顯示示範模式"</string>
+    <string name="demo_mode" msgid="263484519766901593">"系統 UI 展示模式"</string>
+    <string name="enable_demo_mode" msgid="3180345364745966431">"啟用展示模式"</string>
+    <string name="show_demo_mode" msgid="3677956462273059726">"顯示展示模式"</string>
     <string name="status_bar_ethernet" msgid="5690979758988647484">"乙太網路"</string>
     <string name="status_bar_alarm" msgid="87160847643623352">"鬧鐘"</string>
     <string name="wallet_title" msgid="5369767670735827105">"錢包"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"要開啟藍牙功能嗎?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"如要將鍵盤連線到平板電腦,你必須先開啟藍牙。"</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"開啟"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"電源通知控制項"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"已開啟 - 依臉部方向旋轉"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"只要使用電源通知控制項,你就能為應用程式通知設定從 0 到 5 的重要性等級。\n\n"<b>"等級 5"</b>" \n- 顯示在通知清單頂端 \n- 允許全螢幕通知 \n- 一律允許短暫顯示通知 \n\n"<b>"等級 4"</b>" \n- 禁止全螢幕通知 \n- 一律允許短暫顯示通知 \n\n"<b>"等級 3"</b>" \n- 禁止全螢幕通知 \n- 一律不允許短暫顯示通知 \n\n"<b>"等級 2"</b>" \n- 禁止全螢幕通知 \n- 一律不允許短暫顯示通知 \n- 一律不發出音效或震動 \n\n"<b>"等級 1"</b>" \n- 禁止全螢幕通知 \n- 一律不允許短暫顯示通知 \n- 一律不發出音效或震動 \n- 在鎖定畫面和狀態列中隱藏 \n- 顯示在通知清單底端 \n\n"<b>"等級 0"</b>" \n- 封鎖應用程式的所有通知"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"完成"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"套用"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"關閉通知"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"設定"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"儲存空間"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"提示"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"無障礙功能"</string>
     <string name="instant_apps" msgid="8337185853050247304">"免安裝應用程式"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"正在執行「<xliff:g id="APP">%1$s</xliff:g>」"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"已開啟免安裝應用程式。"</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"輕觸即可開啟無障礙功能。你可以前往「設定」自訂或更換這個按鈕。\n\n"<annotation id="link">"查看設定"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"將按鈕移到邊緣處即可暫時隱藏"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"復原"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"無障礙工具按鈕已隱藏"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"輕觸即可顯示無障礙工具按鈕"</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"已移除「<xliff:g id="FEATURE_NAME">%s</xliff:g>」捷徑"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{已移除 # 個捷徑}other{已移除 # 個捷徑}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"移到左上方"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"偵測到使用者"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"在「設定」中指定預設記事應用程式"</string>
     <string name="install_app" msgid="5066668100199613936">"安裝應用程式"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"要以鏡像方式投放至外部螢幕嗎?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"鏡像畫面將顯示在內螢幕,封面螢幕則會關閉。"</string>
     <string name="mirror_display" msgid="2515262008898122928">"鏡像顯示"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 23862a7..795ed38 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -188,10 +188,10 @@
     <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Ayikwazanga ukusetha ukuvula ngobuso. Iya Kumasethingi ukuze uzame futhi."</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Thinta inzwa yesigxivizo zeminwe"</string>
     <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Cindezela isithonjana sokuvula ukuze uqhubeke"</string>
-    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Ayibazi ubuso. Sebenzisa izigxivizo zeminwe kunalokho."</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"Ubuso abaziwa. Sebenzisa izigxivizo zeminwe kunalokho."</string>
     <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
     <skip />
-    <string name="keyguard_face_failed" msgid="9044619102286917151">"Ayikwazi ukubona ubuso"</string>
+    <string name="keyguard_face_failed" msgid="2346762871330729634">"Ubuso abaziwa"</string>
     <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Kunalokho sebenzisa isigxivizo somunwe"</string>
     <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"Ukuvula ngobuso akutholakali"</string>
     <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ixhunyiwe"</string>
@@ -413,6 +413,15 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Iyashaja • Izogcwala ngo-<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"Swayiphela kwesokunxele ukuze uqale okokufundisa komphakathi"</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"Vula isihleli sewijethi"</string>
+    <string name="cta_tile_button_to_open_widget_editor" msgid="3871562362382963878">"Enza ngendlela oyifisayo"</string>
+    <string name="cta_tile_button_to_dismiss" msgid="3377597875997861754">"Chitha"</string>
+    <string name="cta_label_to_edit_widget" msgid="6496885074209203756">"Engeza, susa, futhi uhlele kabusha amawijethi akho kulesi sikhala"</string>
+    <string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Engeza amawijethi engeziwe"</string>
+    <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Cindezela isikhathi eside ukuze wenze ngokwezifiso amawijethi"</string>
+    <!-- no translation found for button_to_configure_widgets_text (4191862850185256901) -->
+    <skip />
+    <!-- no translation found for edit_widget (9030848101135393954) -->
+    <skip />
     <string name="button_to_remove_widget" msgid="3948204829181214098">"Susa"</string>
     <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Engeza iwijethi"</string>
     <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"Kwenziwe"</string>
@@ -599,9 +608,7 @@
     <string name="enable_bluetooth_title" msgid="866883307336662596">"Vula i-Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="6740938333772779717">"Ukuze uxhume ikhibhodi yakho nethebhulethi yakho, kufanele uqale ngokuvula i-Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Vula"</string>
-    <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Izilawuli zesaziso zamandla"</string>
     <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Vuliwe - Kususelwe kubuso"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Ngezilawuli zesaziso zamandla, ungasetha ileveli ebalulekile kusuka ku-0 kuya ku-5 kusuka kuzaziso zohlelo lokusebenza. \n\n"<b>"Ileveli 5"</b>" \n- Ibonisa phezulu kuhlu lwesaziso \n- Vumela ukuphazamiseka kwesikrini esigcwele \n- Ukuhlola njalo \n\n"<b>"Ileveli 4"</b>" \n- Gwema ukuphazamiseka kwesikrini esigcwele \n- Ukuhlola njalo \n\n"<b>"Ileveli 3"</b>" \n- Gwema ukuphazamiseka kwesikrini esigcwele \n- Ukungahloli \n\n"<b>"Ileveli 2"</b>" \n- Gwema ukuphazamiseka kwesikrini esigcwele \n- Ukungahloli \n- Ungenzi umsindo nokudlidliza \n\n"<b>"Ileveli 1"</b>" \n- Gwema ukuphazamiseka kwesikrini esigcwele \n- Ukungahloli \n- Ungenzi umsindo noma ukudlidliza \n- Fihla kusuka kusikrini sokukhiya nebha yesimo \n- Bonisa phansi kohlu lwesaziso \n\n"<b>"Ileveli 0"</b>" \n- Vimbela zonke izaziso kusuka kuhlelo lokusebenza"</string>
     <string name="inline_done_button" msgid="6043094985588909584">"Kwenziwe"</string>
     <string name="inline_ok_button" msgid="603075490581280343">"Faka"</string>
     <string name="inline_turn_off_notifications" msgid="8543989584403106071">"Vala izaziso"</string>
@@ -834,6 +841,7 @@
     <string name="notification_channel_setup" msgid="7660580986090760350">"Ukusetha"</string>
     <string name="notification_channel_storage" msgid="2720725707628094977">"Isitoreji"</string>
     <string name="notification_channel_hints" msgid="7703783206000346876">"Ukubonisa"</string>
+    <string name="notification_channel_accessibility" msgid="8956203986976245820">"Ukufinyeleleka"</string>
     <string name="instant_apps" msgid="8337185853050247304">"Izinhlelo zokusebenza ezisheshayo"</string>
     <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> esebenzayo"</string>
     <string name="instant_apps_message" msgid="6112428971833011754">"Uhlelo lokusebenza luvulwe ngaphndle kokufakwa."</string>
@@ -929,6 +937,8 @@
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Thepha ukuze uvule izakhi zokufinyelela. Enza ngendlela oyifisayo noma shintsha le nkinobho Kumasethingi.\n\n"<annotation id="link">"Buka amasethingi"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Hambisa inkinobho onqenqemeni ukuze uyifihle okwesikhashana"</string>
     <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Hlehlisa"</string>
+    <string name="accessibility_floating_button_hidden_notification_title" msgid="4115036997406994799">"Inkinobho yokufinyeleleka ifihliwe"</string>
+    <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Thepha ukuze ubonise inkinobho yokufinyeleleka."</string>
     <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Isinqamuleli se-<xliff:g id="FEATURE_NAME">%s</xliff:g> sisusiwe"</string>
     <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{Isinqamuleli esingu-# sisusiwe}one{Izinqamuleli ezingu-# zisusiwe}other{Izinqamuleli ezingu-# zisusiwe}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Hamba phezulu kwesokunxele"</string>
@@ -1207,6 +1217,8 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"Ubukhona bomsebenzisi butholakele"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setha i-app yamanothi azenzakalelayo Kumsethingi"</string>
     <string name="install_app" msgid="5066668100199613936">"Faka i-app"</string>
+    <!-- no translation found for dismissible_keyguard_swipe (8377597870094949432) -->
+    <skip />
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Fanisa nesibonisi sangaphandle?"</string>
     <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Isibonisi sakho sangaphakathi sizoboniswa. Isibonisi sakho sangaphambili sizovalwa."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Isibonisi sokufanisa"</string>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 5f6a39a..307a619 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -29,6 +29,9 @@
     <color name="status_bar_icons_hover_color_light">#38FFFFFF</color> <!-- 22% white -->
     <color name="status_bar_icons_hover_color_dark">#38000000</color> <!-- 22% black -->
 
+    <!-- The dark background color behind the shade -->
+    <color name="shade_scrim_background_dark">@*android:color/black</color>
+
     <!-- The color of the background in the separated list of the Global Actions menu -->
     <color name="global_actions_separated_background">#F5F5F5</color>
 
@@ -56,6 +59,8 @@
     <color name="kg_user_switcher_restricted_avatar_icon_color">@color/GM2_grey_600</color>
     <!-- Color of background circle of user avatars in keyguard user switcher -->
     <color name="user_avatar_color_bg">?android:attr/colorBackgroundFloating</color>
+    <!-- Color of border for keyguard password input when focused -->
+    <color name="bouncer_password_focus_color">@*android:color/system_secondary_light</color>
 
     <!-- Icon color for user avatars in user switcher quick settings -->
     <color name="qs_user_switcher_avatar_icon_color">#3C4043</color>
@@ -135,6 +140,9 @@
     <color name="biometric_dialog_gray">#ff757575</color>
     <color name="biometric_dialog_accent">@color/material_dynamic_primary40</color>
     <color name="biometric_dialog_error">#ffd93025</color>                  <!-- red 600 -->
+    <!-- Color for biometric prompt content view -->
+    <color name="biometric_prompt_content_background_color">#8AB4F8</color>
+    <color name="biometric_prompt_content_list_item_bullet_color">#1d873b</color>
 
     <!-- SFPS colors -->
     <color name="sfps_chevron_fill">@color/material_dynamic_primary90</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 64a1d24..17719d1 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -1002,4 +1002,7 @@
         <item>com.android.switchaccess.SwitchAccessService</item>
         <item>com.google.android.apps.accessibility.voiceaccess.JustSpeakService</item>
     </string-array>
+
+    <!--  Whether to use a machine learning model for back gesture falsing. -->
+    <bool name="config_useBackGestureML">true</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 90d8cdb..4209c1f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -151,6 +151,8 @@
     <dimen name="status_bar_icon_size_sp">@*android:dimen/status_bar_icon_size_sp</dimen>
     <!-- Original dp height of notification icons in the status bar -->
     <dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>
+    <dimen name="status_bar_bindable_icon_size">20sp</dimen>
+    <dimen name="status_bar_bindable_icon_padding">2sp</dimen>
 
     <!-- Default horizontal drawable padding for status bar icons. -->
     <dimen name="status_bar_horizontal_padding">2.5sp</dimen>
@@ -897,6 +899,10 @@
     <dimen name="communal_tutorial_indicator_padding">24dp</dimen>
     <dimen name="communal_tutorial_indicator_horizontal_offset">32dp</dimen>
 
+    <!-- Size of the maximum radius for the enforced rounded rectangles on communal hub.
+        Keep it the same as in Launcher-->
+    <dimen name="communal_enforced_rounded_corner_max_radius">16dp</dimen>
+
     <!-- The width/height of the unlock icon view on keyguard. -->
     <dimen name="keyguard_lock_height">42dp</dimen>
     <dimen name="keyguard_lock_padding">20dp</dimen>
@@ -1092,6 +1098,16 @@
     <dimen name="biometric_dialog_width">240dp</dimen>
     <dimen name="biometric_dialog_height">240dp</dimen>
 
+    <!-- Dimensions for biometric prompt content view. -->
+    <dimen name="biometric_prompt_space_above_content">48dp</dimen>
+    <dimen name="biometric_prompt_content_container_padding_horizontal">24dp</dimen>
+    <dimen name="biometric_prompt_content_padding_horizontal">10dp</dimen>
+    <dimen name="biometric_prompt_content_list_row_height">24dp</dimen>
+    <dimen name="biometric_prompt_content_list_item_padding_horizontal">10dp</dimen>
+    <dimen name="biometric_prompt_content_list_item_text_size">14sp</dimen>
+    <dimen name="biometric_prompt_content_list_item_bullet_gap_width">10dp</dimen>
+    <dimen name="biometric_prompt_content_list_item_bullet_radius">5dp</dimen>
+
     <!-- Biometric Auth Credential values -->
     <dimen name="biometric_auth_icon_size">48dp</dimen>
 
@@ -1712,6 +1728,12 @@
     <dimen name="communal_grid_height">630dp</dimen>
     <!-- Number of columns for each communal card -->
     <integer name="communal_grid_columns_per_card">6</integer>
+    <!-- Width of area on right edge of screen in which swipes will open the communal hub -->
+    <dimen name="communal_right_edge_swipe_region_width">16dp</dimen>
+    <!-- Height of area at top of communal hub where swipes should open the notification shade -->
+    <dimen name="communal_top_edge_swipe_region_height">32dp</dimen>
+    <!-- Height of area at bottom of communal hub where swipes should open the bouncer -->
+    <dimen name="communal_bottom_edge_swipe_region_height">32dp</dimen>
 
     <dimen name="drag_and_drop_icon_size">70dp</dimen>
 
@@ -1783,6 +1805,9 @@
     <dimen name="dream_overlay_complication_smartspace_padding">24dp</dimen>
     <dimen name="dream_overlay_complication_smartspace_max_width">408dp</dimen>
 
+    <!-- The width of the swipe target to initiate opening communal hub over dreams. -->
+    <dimen name="communal_gesture_initiation_width">48dp</dimen>
+
     <!-- The position of the end guide, which dream overlay complications can align their start with
          if their end is aligned with the parent end. Represented as the percentage over from the
          start of the parent container. -->
@@ -1916,5 +1941,9 @@
     <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
 
     <!-- UDFPS view attributes -->
-    <dimen name="udfps_icon_size">6mm</dimen>
+    <!-- UDFPS icon size in microns/um -->
+    <dimen name="udfps_icon_size" format="float">6000</dimen>
+    <!-- Microns/ums (1000 um = 1mm) per pixel for the given device. If unspecified, UI that
+         relies on this value will not be sized correctly. -->
+    <item name="pixel_pitch" format="float" type="dimen">-1</item>
 </resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 6e035e8..2ab0813 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -184,6 +184,7 @@
     <item type="id" name="action_move_to_edge_and_hide"/>
     <item type="id" name="action_move_out_edge_and_show"/>
     <item type="id" name="action_remove_menu"/>
+    <item type="id" name="action_edit"/>
 
     <!-- rounded corner view id -->
     <item type="id" name="rounded_corner_top_left"/>
@@ -227,6 +228,7 @@
     <item type="id" name="ambient_indication_container" />
     <item type="id" name="status_view_media_container" />
     <item type="id" name="smart_space_barrier_bottom" />
+    <item type="id" name="weather_clock_date_and_icons_barrier_bottom" />
 
     <!-- Privacy dialog -->
     <item type="id" name="privacy_dialog_close_app_button" />
@@ -249,6 +251,9 @@
     -->
     <item type="id" name="tag_smartspace_view" />
 
+    <!-- ID of the Scene Container root Composable view -->
+    <item type='id' name="scene_container_root_composable" />
+
     <!-- Tag set on the Compose implementation of the QS footer actions. -->
     <item type="id" name="tag_compose_qs_footer_actions" />
 
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index c925b26..fad4d4f 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -16,6 +16,7 @@
   -->
 <resources>
     <integer name="biometric_dialog_text_gravity">8388611</integer> <!-- gravity start -->
+    <integer name="biometric_prompt_content_list_item_max_lines_if_two_column">3</integer>
 
     <integer name="qs_security_footer_maxLines">2</integer>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3f11fae..7943588 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -449,11 +449,11 @@
     <!-- Content description after successful auth when confirmation required -->
     <string name="fingerprint_dialog_authenticated_confirmation">Press the unlock icon to continue</string>
     <!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] -->
-    <string name="fingerprint_dialog_use_fingerprint_instead">Can\u2019t recognize face. Use fingerprint instead.</string>
+    <string name="fingerprint_dialog_use_fingerprint_instead">Face not recognized. Use fingerprint instead.</string>
     <!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] -->
     <string name="keyguard_face_failed_use_fp">@string/fingerprint_dialog_use_fingerprint_instead</string>
     <!-- Message shown to inform the user a face cannot be recognized. [CHAR LIMIT=25] -->
-    <string name="keyguard_face_failed">Can\u2019t recognize face</string>
+    <string name="keyguard_face_failed">Face not recognized</string>
     <!-- Message shown to suggest using fingerprint sensor to authenticate after another biometric failed. [CHAR LIMIT=25] -->
     <string name="keyguard_suggest_fingerprint">Use fingerprint instead</string>
     <!-- Message shown to inform the user that face unlock is not available. [CHAR LIMIT=59] -->
@@ -1077,8 +1077,6 @@
     <!-- Indicator on keyguard to start the communal tutorial. [CHAR LIMIT=100] -->
     <string name="communal_tutorial_indicator_text">Swipe left to start the communal tutorial</string>
 
-    <!-- Description for the button that opens the widget editor on click. [CHAR LIMIT=50] -->
-    <string name="button_to_open_widget_editor">Open the widget editor</string>
     <!-- Text for CTA button that launches the hub mode widget editor on click. [CHAR LIMIT=50] -->
     <string name="cta_tile_button_to_open_widget_editor">Customize</string>
     <!-- Text for CTA button that dismisses the tile on click. [CHAR LIMIT=50] -->
@@ -1087,6 +1085,12 @@
     <string name="cta_label_to_edit_widget">Add, remove, and reorder your widgets in this space</string>
     <!-- Label for CTA tile that opens widget picker on click in edit mode [CHAR LIMIT=50] -->
     <string name="cta_label_to_open_widget_picker">Add more widgets</string>
+    <!-- Text for the popup to be displayed after dismissing the CTA tile. [CHAR LIMIT=50] -->
+    <string name="popup_on_dismiss_cta_tile_text">Long press to customize widgets</string>
+    <!-- Text for the button to configure widgets after long press. [CHAR LIMIT=50] -->
+    <string name="button_to_configure_widgets_text">Customize widgets</string>
+    <!-- Label for the button which configures widgets [CHAR LIMIT=NONE] -->
+    <string name="edit_widget">Edit widget</string>
     <!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] -->
     <string name="button_to_remove_widget">Remove</string>
     <!-- Text for the button that launches the hub mode widget picker. [CHAR LIMIT=50] -->
@@ -1610,37 +1614,9 @@
     <!-- Bluetooth enablement ok text [CHAR LIMIT=40] -->
     <string name="enable_bluetooth_confirmation_ok">Turn on</string>
 
-    <!-- [CHAR LIMIT=NONE] Importance Tuner setting title -->
-    <string name="tuner_full_importance_settings">Power notification controls</string>
-
     <!-- [CHAR LIMIT=NONE] Notification camera based rotation enabled description -->
     <string name="rotation_lock_camera_rotation_on">On - Face-based</string>
 
-    <string name="power_notification_controls_description">With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications.
-        \n\n<b>Level 5</b>
-        \n- Show at the top of the notification list
-        \n- Allow full screen interruption
-        \n- Always peek
-        \n\n<b>Level 4</b>
-        \n- Prevent full screen interruption
-        \n- Always peek
-        \n\n<b>Level 3</b>
-        \n- Prevent full screen interruption
-        \n- Never peek
-        \n\n<b>Level 2</b>
-        \n- Prevent full screen interruption
-        \n- Never peek
-        \n- Never make sound and vibration
-        \n\n<b>Level 1</b>
-        \n- Prevent full screen interruption
-        \n- Never peek
-        \n- Never make sound or vibrate
-        \n- Hide from lock screen and status bar
-        \n- Show at the bottom of the notification list
-        \n\n<b>Level 0</b>
-        \n- Block all notifications from the app
-    </string>
-
     <!-- Notification Inline controls: button to dismiss the blocking helper [CHAR_LIMIT=20] -->
     <string name="inline_done_button">Done</string>
 
@@ -1772,6 +1748,9 @@
     <!-- Notification: Snooze panel: Snooze undo button label. [CHAR LIMIT=50]-->
     <string name="snooze_undo">Undo</string>
 
+    <!-- Notification: Snooze panel: Snooze undo content description for a11y. [CHAR LIMIT=NONE]-->
+    <string name="snooze_undo_content_description">Undo notification snooze</string>
+
     <!-- Notification: Snooze panel: message indicating how long the notification was snoozed for. [CHAR LIMIT=100]-->
     <string name="snoozed_for_time">Snoozed for <xliff:g id="time_amount" example="15 minutes">%1$s</xliff:g></string>
 
@@ -2282,6 +2261,8 @@
     <string name="notification_channel_storage">Storage</string>
     <!-- Title for the notification channel for hints and suggestions. [CHAR LIMIT=NONE] -->
     <string name="notification_channel_hints">Hints</string>
+    <!-- Title for the notification channel for accessibility related (i.e. accessibility floating menu). [CHAR LIMIT=NONE] -->
+    <string name="notification_channel_accessibility">Accessibility</string>
 
     <!-- App label of the instant apps notification [CHAR LIMIT=60] -->
     <string name="instant_apps">Instant Apps</string>
@@ -2552,6 +2533,11 @@
     <string name="accessibility_floating_button_docking_tooltip">Move button to the edge to hide it temporarily</string>
     <!-- Text for the undo action button of the message view of the accessibility floating menu to perform undo operation. [CHAR LIMIT=30]-->
     <string name="accessibility_floating_button_undo">Undo</string>
+    <!-- Notification title shown when accessibility floating button is in hidden state. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_floating_button_hidden_notification_title">Accessibility button hidden</string>
+    <!-- Notification content text to explain user can tap notification to bring back accessibility floating button. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_floating_button_hidden_notification_text">Tap to show accessibility button</string>
+
     <!-- Text for the message view with undo action of the accessibility floating menu to show which feature shortcut was removed. [CHAR LIMIT=30]-->
     <string name="accessibility_floating_button_undo_message_label_text"><xliff:g id="feature name" example="Magnification">%s</xliff:g> shortcut removed</string>
     <!-- Text for the message view with undo action of the accessibility floating menu to show how many features shortcuts were removed. [CHAR LIMIT=30]-->
@@ -2575,6 +2561,8 @@
     <string name="accessibility_floating_button_action_remove_menu">Remove</string>
     <!-- Action in accessibility menu to toggle on/off the accessibility feature. [CHAR LIMIT=30]-->
     <string name="accessibility_floating_button_action_double_tap_to_toggle">toggle</string>
+    <!-- Action in accessibility menu to open the shortcut edit menu" [CHAR LIMIT=30]-->
+    <string name="accessibility_floating_button_action_edit">Edit</string>
 
     <!-- Device Controls strings -->
     <!-- Device Controls, Quick Settings tile title [CHAR LIMIT=30] -->
@@ -3271,7 +3259,7 @@
     <string name="install_app">Install app</string>
 
     <!-- Instructions informing the user they can swipe up on the lockscreen to dismiss [CHAR LIMIT=48]-->
-    <string name="dismissible_keyguard_swipe">Swipe to continue</string>
+    <string name="dismissible_keyguard_swipe">Swipe up to continue</string>
 
     <!--- Title of the dialog appearing when an external display is connected, asking whether to start mirroring [CHAR LIMIT=NONE]-->
     <string name="connected_display_dialog_start_mirroring">Mirror to external display?</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 3f026a4..ab3cacb 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -195,6 +195,24 @@
         <item name="android:textSize">14sp</item>
     </style>
 
+    <style name="TextAppearance.AuthCredential.ContentViewTitle">
+        <item name="android:fontFamily">google-sans</item>
+        <item name="android:paddingTop">8dp</item>
+        <item name="android:paddingHorizontal">24dp</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:gravity">start</item>
+    </style>
+
+    <style name="TextAppearance.AuthCredential.ContentViewListItem">
+        <item name="android:fontFamily">google-sans</item>
+        <item name="android:paddingTop">8dp</item>
+        <item name="android:paddingHorizontal">
+            @dimen/biometric_prompt_content_list_item_padding_horizontal
+        </item>
+        <item name="android:textSize">@dimen/biometric_prompt_content_list_item_text_size</item>
+        <item name="android:gravity">start</item>
+    </style>
+
     <style name="TextAppearance.AuthCredential.Error">
         <item name="android:paddingTop">6dp</item>
         <item name="android:paddingHorizontal">24dp</item>
@@ -294,6 +312,11 @@
         <item name="android:textSize">16sp</item>
     </style>
 
+    <style name="AuthCredentialContentLayoutStyle">
+        <item name="android:background">@color/biometric_prompt_content_background_color</item>
+        <item name="android:paddingHorizontal">@dimen/biometric_prompt_content_padding_horizontal</item>
+    </style>
+
     <style name="DeviceManagementDialogTitle">
         <item name="android:gravity">center</item>
         <item name="android:textAppearance">@style/TextAppearance.Dialog.Title</item>
@@ -921,7 +944,7 @@
     <style name="Theme.ControlsActivity" parent="@android:style/Theme.DeviceDefault.NoActionBar">
         <item name="android:windowActivityTransitions">true</item>
         <item name="android:windowContentTransitions">false</item>
-        <item name="android:windowIsTranslucent">false</item>
+        <item name="android:windowIsTranslucent">true</item>
         <item name="android:windowBackground">@android:color/black</item>
         <item name="android:windowAnimationStyle">@null</item>
         <item name="android:statusBarColor">@android:color/black</item>
diff --git a/packages/SystemUI/res/xml/other_settings.xml b/packages/SystemUI/res/xml/other_settings.xml
deleted file mode 100644
index 7719d5e..0000000
--- a/packages/SystemUI/res/xml/other_settings.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
-                  xmlns:sysui="http://schemas.android.com/apk/res-auto"
-                  android:title="@string/other">
-
-    <!-- importance -->
-    <Preference
-            android:key="power_notification_controls"
-            android:title="@string/tuner_full_importance_settings"
-            android:fragment="com.android.systemui.tuner.PowerNotificationControlsFragment"/>
-
-</PreferenceScreen>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 05106c9..326c7ef 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -34,9 +34,6 @@
     srcs: [
         ":statslog-SystemUI-java-gen",
     ],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 android_library {
@@ -74,9 +71,6 @@
     min_sdk_version: "current",
     plugins: ["dagger2-compiler"],
     kotlincflags: ["-Xjvm-default=all"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 java_library {
@@ -88,9 +82,6 @@
     static_kotlin_stdlib: false,
     java_version: "1.8",
     min_sdk_version: "current",
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 java_library {
@@ -110,7 +101,4 @@
     },
     java_version: "1.8",
     min_sdk_version: "current",
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
index a00cdfa..7c674c8 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
@@ -47,3 +47,14 @@
         FingerprintSensorProperties.TYPE_HOME_BUTTON -> FingerprintSensorType.HOME_BUTTON
         else -> throw IllegalArgumentException("Invalid SensorType value: $this")
     }
+
+/** Convert [this] to corresponding [Int] */
+fun FingerprintSensorType.toInt(): Int =
+    when (this) {
+        FingerprintSensorType.UNKNOWN -> FingerprintSensorProperties.TYPE_UNKNOWN
+        FingerprintSensorType.REAR -> FingerprintSensorProperties.TYPE_REAR
+        FingerprintSensorType.UDFPS_ULTRASONIC -> FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC
+        FingerprintSensorType.UDFPS_OPTICAL -> FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
+        FingerprintSensorType.POWER_BUTTON -> FingerprintSensorProperties.TYPE_POWER_BUTTON
+        FingerprintSensorType.HOME_BUTTON -> FingerprintSensorProperties.TYPE_HOME_BUTTON
+    }
diff --git a/packages/SystemUI/shared/lint-baseline.xml b/packages/SystemUI/shared/lint-baseline.xml
deleted file mode 100644
index 4bd6729..0000000
--- a/packages/SystemUI/shared/lint-baseline.xml
+++ /dev/null
@@ -1,708 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" name="" variant="all" version="7.1.0-dev">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `android.os.RemoteException#rethrowFromSystemServer`"
-        errorLine1="            throw e.rethrowFromSystemServer();"
-        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java"
-            line="90"
-            column="21"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 26): `android.graphics.Bitmap#getHardwareBuffer`"
-        errorLine1="                mBuffer != null ? mBuffer.getHardwareBuffer() : null, mRect);"
-        errorLine2="                                          ~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecCompat.java"
-            line="39"
-            column="43"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 26): `new android.graphics.ParcelableColorSpace`"
-        errorLine1="                        ? new ParcelableColorSpace(ColorSpace.get(ColorSpace.Named.SRGB))"
-        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java"
-            line="57"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 26): `new android.graphics.ParcelableColorSpace`"
-        errorLine1="                        : new ParcelableColorSpace(bitmap.getColorSpace());"
-        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java"
-            line="58"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 26): `android.graphics.Bitmap#getHardwareBuffer`"
-        errorLine1="        bundle.putParcelable(KEY_BUFFER, bitmap.getHardwareBuffer());"
-        errorLine2="                                                ~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java"
-            line="61"
-            column="49"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Cast from `ParcelableColorSpace` to `Parcelable` requires API level 31 (current min is 26)"
-        errorLine1="        bundle.putParcelable(KEY_COLOR_SPACE, colorSpace);"
-        errorLine2="                                              ~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java"
-            line="62"
-            column="47"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.graphics.Bitmap#wrapHardwareBuffer`"
-        errorLine1="        return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer),"
-        errorLine2="                      ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java"
-            line="84"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 26): `android.graphics.ParcelableColorSpace#getColorSpace`"
-        errorLine1="                colorSpace.getColorSpace());"
-        errorLine2="                           ~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java"
-            line="85"
-            column="28"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `new android.view.SurfaceControl.Transaction`"
-        errorLine1="        final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();"
-        errorLine2="                                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java"
-            line="122"
-            column="47"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `new android.util.ArraySet`"
-        errorLine1="        mPluginActions = new ArraySet&lt;>(mSharedPrefs.getStringSet(PLUGIN_ACTIONS, null));"
-        errorLine2="                         ~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginPrefs.java"
-            line="41"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `new android.util.ArraySet`"
-        errorLine1="        return new ArraySet&lt;>(mPluginActions);"
-        errorLine2="               ~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginPrefs.java"
-            line="45"
-            column="16"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 28 (current min is 26): `android.graphics.Bitmap#createBitmap`"
-        errorLine1="        return Bitmap.createBitmap(picture);"
-        errorLine2="                      ~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java"
-            line="113"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `new android.view.SurfaceControl.Builder`"
-        errorLine1="            mSurface = new SurfaceControl.Builder()"
-        errorLine2="                       ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="116"
-            column="24"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Builder#setName`"
-        errorLine1="                    .setName(&quot;Transition Unrotate&quot;)"
-        errorLine2="                     ~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="117"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Builder#setParent`"
-        errorLine1="                    .setParent(parent)"
-        errorLine2="                     ~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="119"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Builder#build`"
-        errorLine1="                    .build();"
-        errorLine2="                     ~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="120"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#reparent`"
-        errorLine1="            t.reparent(child, mSurface);"
-        errorLine2="              ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="137"
-            column="15"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `new android.view.SurfaceControl.Transaction`"
-        errorLine1="            SurfaceControl.Transaction t = new SurfaceControl.Transaction();"
-        errorLine2="                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="143"
-            column="44"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#reparent`"
-        errorLine1="                t.reparent(mRotateChildren.get(i), rootLeash);"
-        errorLine2="                  ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="145"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#apply`"
-        errorLine1="            t.apply();"
-        errorLine2="              ~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="148"
-            column="15"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setLayer`"
-        errorLine1="                        t.setLayer(counterLauncher.mSurface, launcherLayer);"
-        errorLine2="                          ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="200"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setLayer`"
-        errorLine1="                        t.setLayer(counterLauncher.mSurface, info.getChanges().size() * 3);"
-        errorLine2="                          ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="206"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setLayer`"
-        errorLine1="                            t.setLayer(leash, info.getChanges().size() * 3 - i);"
-        errorLine2="                              ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="216"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setAlpha`"
-        errorLine1="                        t.setAlpha(wallpapersCompat[i].leash.getSurfaceControl(), 1.f);"
-        errorLine2="                          ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="223"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setLayer`"
-        errorLine1="                            t.setLayer(counterWallpaper.mSurface, -1);"
-        errorLine2="                              ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="233"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#apply`"
-        errorLine1="                t.apply();"
-        errorLine2="                  ~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java"
-            line="238"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.ActivityManager.RunningTaskInfo#taskId`"
-        errorLine1="        taskId = change.getTaskInfo() != null ? change.getTaskInfo().taskId : -1;"
-        errorLine2="                                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java"
-            line="101"
-            column="49"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#taskId`"
-        errorLine1="        taskId = change.getTaskInfo() != null ? change.getTaskInfo().taskId : -1;"
-        errorLine2="                                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java"
-            line="192"
-            column="49"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.ActivityManager.RunningTaskInfo#isRunning`"
-        errorLine1="            isNotInRecents = !change.getTaskInfo().isRunning;"
-        errorLine2="                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java"
-            line="116"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#isRunning`"
-        errorLine1="            isNotInRecents = !change.getTaskInfo().isRunning;"
-        errorLine2="                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java"
-            line="210"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl#release`"
-        errorLine1="        leash.mSurfaceControl.release();"
-        errorLine2="                              ~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java"
-            line="159"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl#release`"
-        errorLine1="            mStartLeash.release();"
-        errorLine2="                        ~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java"
-            line="161"
-            column="25"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setLayer`"
-        errorLine1="                        t.setLayer(change.getLeash(), info.getChanges().size() * 3 - i);"
-        errorLine2="                          ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java"
-            line="97"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setAlpha`"
-        errorLine1="                    t.setAlpha(wallpapers[i].leash.mSurfaceControl, 1);"
-        errorLine2="                      ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java"
-            line="105"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#apply`"
-        errorLine1="                t.apply();"
-        errorLine2="                  ~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java"
-            line="107"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl#isValid`"
-        errorLine1="        return mSurfaceControl != null &amp;&amp; mSurfaceControl.isValid();"
-        errorLine2="                                                          ~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceControlCompat.java"
-            line="41"
-            column="59"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `android.view.SurfaceControlViewHost#release`"
-        errorLine1="            mSurfaceControlViewHost.release();"
-        errorLine2="                                    ~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java"
-            line="61"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 26): `android.view.SurfaceView#getHostToken`"
-        errorLine1="        bundle.putBinder(KEY_HOST_TOKEN, surfaceView.getHostToken());"
-        errorLine2="                                                     ~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestUtils.java"
-            line="34"
-            column="54"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceView#getSurfaceControl`"
-        errorLine1="        bundle.putParcelable(KEY_SURFACE_CONTROL, surfaceView.getSurfaceControl());"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestUtils.java"
-            line="35"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Cast from `SurfaceControl` to `Parcelable` requires API level 29 (current min is 26)"
-        errorLine1="        bundle.putParcelable(KEY_SURFACE_CONTROL, surfaceView.getSurfaceControl());"
-        errorLine2="                                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestUtils.java"
-            line="35"
-            column="51"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl#isValid`"
-        errorLine1="                if (mBarrierSurfaceControl == null || !mBarrierSurfaceControl.isValid()) {"
-        errorLine2="                                                                              ~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java"
-            line="107"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `new android.view.SurfaceControl.Transaction`"
-        errorLine1="                Transaction t = new Transaction();"
-        errorLine2="                                ~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java"
-            line="113"
-            column="33"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#apply`"
-        errorLine1="                    t.apply();"
-        errorLine2="                      ~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java"
-            line="122"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setAlpha`"
-        errorLine1="                t.setAlpha(surface, alpha);"
-        errorLine2="                  ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java"
-            line="361"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setLayer`"
-        errorLine1="                t.setLayer(surface, layer);"
-        errorLine2="                  ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java"
-            line="364"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#origActivity`"
-        errorLine1="            ComponentName sourceComponent = t.origActivity != null"
-        errorLine2="                                            ~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java"
-            line="73"
-            column="45"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#origActivity`"
-        errorLine1="                    ? t.origActivity"
-        errorLine2="                      ~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java"
-            line="75"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#taskId`"
-        errorLine1="            this.id = t.taskId;"
-        errorLine2="                      ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java"
-            line="78"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#baseIntent`"
-        errorLine1="            this.baseIntent = t.baseIntent;"
-        errorLine2="                              ~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java"
-            line="80"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#taskDescription`"
-        errorLine1="        ActivityManager.TaskDescription td = taskInfo.taskDescription;"
-        errorLine2="                                             ~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java"
-            line="242"
-            column="46"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#topActivity`"
-        errorLine1="                taskInfo.supportsSplitScreenMultiWindow, isLocked, td, taskInfo.topActivity);"
-        errorLine2="                                                                       ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java"
-            line="246"
-            column="72"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#topActivity`"
-        errorLine1="        return info.topActivity;"
-        errorLine2="               ~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java"
-            line="49"
-            column="16"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#taskDescription`"
-        errorLine1="        return info.taskDescription;"
-        errorLine2="               ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskInfoCompat.java"
-            line="53"
-            column="16"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.ActivityManager.RunningTaskInfo#taskId`"
-        errorLine1="        onTaskMovedToFront(taskInfo.taskId);"
-        errorLine2="                           ~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java"
-            line="70"
-            column="28"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 29 (current min is 26): `android.app.TaskInfo#taskId`"
-        errorLine1="        onTaskMovedToFront(taskInfo.taskId);"
-        errorLine2="                           ~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java"
-            line="70"
-            column="28"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.graphics.Bitmap#wrapHardwareBuffer`"
-        errorLine1="                thumbnail = Bitmap.wrapHardwareBuffer(buffer, snapshot.getColorSpace());"
-        errorLine2="                                   ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java"
-            line="69"
-            column="36"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 31 (current min is 26): `android.app.WallpaperColors#getColorHints`"
-        errorLine1="                    (colors.getColorHints() &amp; WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0;"
-        errorLine2="                            ~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TonalCompat.java"
-            line="42"
-            column="29"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `new android.view.SurfaceControl.Transaction`"
-        errorLine1="        mTransaction = new Transaction();"
-        errorLine2="                       ~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java"
-            line="31"
-            column="24"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#apply`"
-        errorLine1="        mTransaction.apply();"
-        errorLine2="                     ~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java"
-            line="35"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setBufferSize`"
-        errorLine1="        mTransaction.setBufferSize(surfaceControl.mSurfaceControl, w, h);"
-        errorLine2="                     ~~~~~~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java"
-            line="54"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setLayer`"
-        errorLine1="        mTransaction.setLayer(surfaceControl.mSurfaceControl, z);"
-        errorLine2="                     ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java"
-            line="59"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#setAlpha`"
-        errorLine1="        mTransaction.setAlpha(surfaceControl.mSurfaceControl, alpha);"
-        errorLine2="                     ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java"
-            line="64"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.view.SurfaceControl.Transaction#apply`"
-        errorLine1="            t.apply();"
-        errorLine2="              ~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewRootImplCompat.java"
-            line="64"
-            column="15"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 29 (current min is 26): `android.content.res.Resources#getFloat`"
-        errorLine1="                .getFloat(Resources.getSystem().getIdentifier("
-        errorLine2="                 ~~~~~~~~">
-        <location
-            file="frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperManagerCompat.java"
-            line="46"
-            column="18"/>
-    </issue>
-
-</issues>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
index 387f2e1..87cc86f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
@@ -38,6 +38,7 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.BiConsumer;
 import java.util.function.Supplier;
 
 /**
@@ -57,7 +58,7 @@
     private final PluginFactory<T> mPluginFactory;
     private final String mTag;
 
-    private boolean mIsDebug = false;
+    private BiConsumer<String, String> mLogConsumer = null;
     private Context mPluginContext;
     private T mPlugin;
 
@@ -86,17 +87,13 @@
         return mTag;
     }
 
-    public boolean getIsDebug() {
-        return mIsDebug;
+    public void setLogFunc(BiConsumer logConsumer) {
+        mLogConsumer = logConsumer;
     }
 
-    public void setIsDebug(boolean debug) {
-        mIsDebug = debug;
-    }
-
-    private void logDebug(String message) {
-        if (mIsDebug) {
-            Log.i(mTag, message);
+    private void log(String message) {
+        if (mLogConsumer != null) {
+            mLogConsumer.accept(mTag, message);
         }
     }
 
@@ -105,19 +102,19 @@
         boolean loadPlugin = mListener.onPluginAttached(this);
         if (!loadPlugin) {
             if (mPlugin != null) {
-                logDebug("onCreate: auto-unload");
+                log("onCreate: auto-unload");
                 unloadPlugin();
             }
             return;
         }
 
         if (mPlugin == null) {
-            logDebug("onCreate auto-load");
+            log("onCreate auto-load");
             loadPlugin();
             return;
         }
 
-        logDebug("onCreate: load callbacks");
+        log("onCreate: load callbacks");
         mPluginFactory.checkVersion(mPlugin);
         if (!(mPlugin instanceof PluginFragment)) {
             // Only call onCreate for plugins that aren't fragments, as fragments
@@ -129,7 +126,7 @@
 
     /** Alerts listener and plugin that the plugin is being shutdown. */
     public synchronized void onDestroy() {
-        logDebug("onDestroy");
+        log("onDestroy");
         unloadPlugin();
         mListener.onPluginDetached(this);
     }
@@ -145,7 +142,7 @@
      */
     public synchronized void loadPlugin() {
         if (mPlugin != null) {
-            logDebug("Load request when already loaded");
+            log("Load request when already loaded");
             return;
         }
 
@@ -157,7 +154,7 @@
             return;
         }
 
-        logDebug("Loaded plugin; running callbacks");
+        log("Loaded plugin; running callbacks");
         mPluginFactory.checkVersion(mPlugin);
         if (!(mPlugin instanceof PluginFragment)) {
             // Only call onCreate for plugins that aren't fragments, as fragments
@@ -174,11 +171,11 @@
      */
     public synchronized void unloadPlugin() {
         if (mPlugin == null) {
-            logDebug("Unload request when already unloaded");
+            log("Unload request when already unloaded");
             return;
         }
 
-        logDebug("Unloading plugin, running callbacks");
+        log("Unloading plugin, running callbacks");
         mListener.onPluginUnloaded(mPlugin, this);
         if (!(mPlugin instanceof PluginFragment)) {
             // Only call onDestroy for plugins that aren't fragments, as fragments
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 823bd2d..d191a3c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -20,7 +20,6 @@
 import android.graphics.Region;
 import android.os.Bundle;
 import android.view.MotionEvent;
-import android.view.SurfaceControl;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 
 // Next ID: 29
@@ -88,18 +87,18 @@
      */
     void onNavButtonsDarkIntensityChanged(float darkIntensity) = 22;
 
-     /**
-      * Sent when split keyboard shortcut is triggered to enter stage split.
-      */
-     void enterStageSplitFromRunningApp(boolean leftOrTop) = 25;
+    /**
+     * Sent when when navigation bar luma sampling is enabled or disabled.
+     */
+    void onNavigationBarLumaSamplingEnabled(int displayId, boolean enable) = 23;
 
-     /**
-      * Sent when the surface for navigation bar is created or changed
-      */
-     void onNavigationBarSurface(in SurfaceControl surface) = 26;
+    /**
+     * Sent when split keyboard shortcut is triggered to enter stage split.
+     */
+    void enterStageSplitFromRunningApp(boolean leftOrTop) = 25;
 
-     /**
-      * Sent when the task bar stash state is toggled.
-      */
-     void onTaskbarToggled() = 27;
+    /**
+     * Sent when the task bar stash state is toggled.
+     */
+    void onTaskbarToggled() = 27;
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java
index 259cca8..9e92c93 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java
@@ -16,8 +16,11 @@
 
 package com.android.systemui.shared.system;
 
-import android.graphics.Matrix;
+import static android.os.Trace.TRACE_TAG_INPUT;
+
 import android.os.Looper;
+import android.os.Trace;
+import android.util.Log;
 import android.view.BatchedInputEventReceiver;
 import android.view.Choreographer;
 import android.view.InputChannel;
@@ -52,23 +55,24 @@
         return target.addBatch(src);
     }
 
-    /** @see MotionEvent#createRotateMatrix */
-    public static Matrix createRotationMatrix(
-            /*@Surface.Rotation*/ int rotation, int displayW, int displayH) {
-        return MotionEvent.createRotateMatrix(rotation, displayW, displayH);
-    }
-
     /**
      * @see BatchedInputEventReceiver
      */
     public static class InputEventReceiver {
 
+        private final String mName;
         private final BatchedInputEventReceiver mReceiver;
 
+        @Deprecated
         public InputEventReceiver(InputChannel inputChannel, Looper looper,
                 Choreographer choreographer, final InputEventListener listener) {
-            mReceiver = new BatchedInputEventReceiver(inputChannel, looper, choreographer) {
+            this("unknown", inputChannel, looper, choreographer, listener);
+        }
 
+        public InputEventReceiver(String name, InputChannel inputChannel, Looper looper,
+                Choreographer choreographer, final InputEventListener listener) {
+            mName = name;
+            mReceiver = new BatchedInputEventReceiver(inputChannel, looper, choreographer) {
                 @Override
                 public void onInputEvent(InputEvent event) {
                     listener.onInputEvent(event);
@@ -89,6 +93,9 @@
          */
         public void dispose() {
             mReceiver.dispose();
+            Trace.instant(TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " receiver disposed");
+            Log.d(InputMonitorCompat.TAG, "Input event receiver for monitor (" + mName
+                    + ") disposed");
         }
     }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java
index c4aac11..78beaf7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java
@@ -17,8 +17,13 @@
 
 import android.hardware.input.InputManagerGlobal;
 import android.os.Looper;
+import android.os.Trace;
+import android.util.Log;
 import android.view.Choreographer;
 import android.view.InputMonitor;
+import android.view.SurfaceControl;
+
+import androidx.annotation.NonNull;
 
 import com.android.systemui.shared.system.InputChannelCompat.InputEventListener;
 import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
@@ -27,14 +32,20 @@
  * @see android.view.InputMonitor
  */
 public class InputMonitorCompat {
+    static final String TAG = "InputMonitorCompat";
     private final InputMonitor mInputMonitor;
+    private final String mName;
 
     /**
      * Monitor input on the specified display for gestures.
      */
-    public InputMonitorCompat(String name, int displayId) {
+    public InputMonitorCompat(@NonNull String name, int displayId) {
+        mName = name + "-disp" + displayId;
         mInputMonitor = InputManagerGlobal.getInstance()
                 .monitorGestureInput(name, displayId);
+        Trace.instant(Trace.TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " created");
+        Log.d(TAG, "Input monitor (" + mName + ") created");
+
     }
 
     /**
@@ -45,10 +56,19 @@
     }
 
     /**
+     * @see InputMonitor#getSurface()
+     */
+    public SurfaceControl getSurface() {
+        return mInputMonitor.getSurface();
+    }
+
+    /**
      * @see InputMonitor#dispose()
      */
     public void dispose() {
         mInputMonitor.dispose();
+        Trace.instant(Trace.TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " disposed");
+        Log.d(TAG, "Input monitor (" + mName + ") disposed");
     }
 
     /**
@@ -56,7 +76,9 @@
      */
     public InputEventReceiver getInputReceiver(Looper looper, Choreographer choreographer,
             InputEventListener listener) {
-        return new InputEventReceiver(mInputMonitor.getInputChannel(), looper, choreographer,
+        Trace.instant(Trace.TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " receiver created");
+        Log.d(TAG, "Input event receiver for monitor (" + mName + ") created");
+        return new InputEventReceiver(mName, mInputMonitor.getInputChannel(), looper, choreographer,
                 listener);
     }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index f094102..d4ac195 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -40,7 +40,15 @@
      */
     public static RemoteAnimationTarget[] wrapApps(TransitionInfo info,
             SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
-        return wrap(info, t, leashMap, new TransitionUtil.LeafTaskFilter());
+        // LeafTaskFilter is order-dependent, so the same object needs to be used for all Change
+        // objects. That's why it's constructed here and captured by the lambda instead of building
+        // a new one ad hoc every time.
+        TransitionUtil.LeafTaskFilter taskFilter = new TransitionUtil.LeafTaskFilter();
+        return wrap(info, t, leashMap, (change) -> {
+            // Intra-task activity -> activity transitions should be categorized as apps.
+            if (change.getActivityComponent() != null) return true;
+            return taskFilter.test(change);
+        });
     }
 
     /**
@@ -53,8 +61,12 @@
      */
     public static RemoteAnimationTarget[] wrapNonApps(TransitionInfo info, boolean wallpapers,
             SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
-        return wrap(info, t, leashMap, (change) -> (wallpapers
-                ? TransitionUtil.isWallpaper(change) : TransitionUtil.isNonApp(change)));
+        return wrap(info, t, leashMap, (change) -> {
+            // Intra-task activity -> activity transitions should be categorized as apps.
+            if (change.getActivityComponent() != null) return false;
+            return wallpapers
+                    ? TransitionUtil.isWallpaper(change) : TransitionUtil.isNonApp(change);
+        });
     }
 
     private static RemoteAnimationTarget[] wrap(TransitionInfo info,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index a5a545a..ad30317 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -3,6 +3,7 @@
 import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_X_CLOCK_DESIGN;
 import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_DESIGN;
 import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_SIZE;
+import static com.android.systemui.Flags.migrateClocksToBlueprint;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -191,11 +192,11 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-
-        mSmallClockFrame = findViewById(R.id.lockscreen_clock_view);
-        mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
-        mStatusArea = findViewById(R.id.keyguard_status_area);
-
+        if (!migrateClocksToBlueprint()) {
+            mSmallClockFrame = findViewById(R.id.lockscreen_clock_view);
+            mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
+            mStatusArea = findViewById(R.id.keyguard_status_area);
+        }
         onConfigChanged();
     }
 
@@ -476,9 +477,13 @@
     public void dump(PrintWriter pw, String[] args) {
         pw.println("KeyguardClockSwitch:");
         pw.println("  mSmallClockFrame = " + mSmallClockFrame);
-        pw.println("  mSmallClockFrame.alpha = " + mSmallClockFrame.getAlpha());
+        if (mSmallClockFrame != null) {
+            pw.println("  mSmallClockFrame.alpha = " + mSmallClockFrame.getAlpha());
+        }
         pw.println("  mLargeClockFrame = " + mLargeClockFrame);
-        pw.println("  mLargeClockFrame.alpha = " + mLargeClockFrame.getAlpha());
+        if (mLargeClockFrame != null) {
+            pw.println("  mLargeClockFrame.alpha = " + mLargeClockFrame.getAlpha());
+        }
         pw.println("  mStatusArea = " + mStatusArea);
         pw.println("  mDisplayedClockSize = " + mDisplayedClockSize);
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 74b975c..de7c12d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -22,6 +22,7 @@
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
 import static com.android.systemui.Flags.migrateClocksToBlueprint;
+import static com.android.systemui.Flags.smartspaceRelocateToBottom;
 import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
@@ -421,6 +422,10 @@
             return;
         }
 
+        if (smartspaceRelocateToBottom()) {
+            return;
+        }
+
         mSmartspaceView = mSmartspaceController.buildAndConnectView(mView);
         LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                 MATCH_PARENT, WRAP_CONTENT);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 66f965a..efd8f7f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -37,6 +37,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyboard.data.repository.KeyboardRepository;
 import com.android.systemui.log.BouncerLogger;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.DevicePostureController;
@@ -210,6 +211,7 @@
         private final FeatureFlags mFeatureFlags;
         private final SelectedUserInteractor mSelectedUserInteractor;
         private final UiEventLogger mUiEventLogger;
+        private final KeyboardRepository mKeyboardRepository;
 
         @Inject
         public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -223,7 +225,8 @@
                 DevicePostureController devicePostureController,
                 KeyguardViewController keyguardViewController,
                 FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor,
-                UiEventLogger uiEventLogger) {
+                UiEventLogger uiEventLogger,
+                KeyboardRepository keyboardRepository) {
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
             mLockPatternUtils = lockPatternUtils;
             mLatencyTracker = latencyTracker;
@@ -240,6 +243,7 @@
             mFeatureFlags = featureFlags;
             mSelectedUserInteractor = selectedUserInteractor;
             mUiEventLogger = uiEventLogger;
+            mKeyboardRepository = keyboardRepository;
         }
 
         /** Create a new {@link KeyguardInputViewController}. */
@@ -268,19 +272,22 @@
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mLiftToActivateListener, emergencyButtonController, mFalsingCollector,
                         mDevicePostureController, mFeatureFlags, mSelectedUserInteractor,
-                        mUiEventLogger);
+                        mUiEventLogger, mKeyboardRepository
+                );
             } else if (keyguardInputView instanceof KeyguardSimPinView) {
                 return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
-                        emergencyButtonController, mFeatureFlags, mSelectedUserInteractor);
+                        emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
+                        mKeyboardRepository);
             } else if (keyguardInputView instanceof KeyguardSimPukView) {
                 return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
-                        emergencyButtonController, mFeatureFlags, mSelectedUserInteractor);
+                        emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
+                        mKeyboardRepository);
             }
 
             throw new RuntimeException("Unable to find controller for " + keyguardInputView);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 36fe75f..fcff0db 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -25,6 +25,7 @@
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
+import static com.android.systemui.Flags.pinInputFieldStyledFocusState;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -168,7 +169,9 @@
 
         // Set selected property on so the view can send accessibility events.
         mPasswordEntry.setSelected(true);
-        mPasswordEntry.setDefaultFocusHighlightEnabled(false);
+        if (!pinInputFieldStyledFocusState()) {
+            mPasswordEntry.setDefaultFocusHighlightEnabled(false);
+        }
 
         mOkButton = findViewById(R.id.key_enter);
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 376933d..60dd568 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -16,17 +16,25 @@
 
 package com.android.keyguard;
 
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import static com.android.systemui.Flags.pinInputFieldStyledFocusState;
+
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.StateListDrawable;
+import android.util.TypedValue;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnKeyListener;
 import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
 
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyboard.data.repository.KeyboardRepository;
 import com.android.systemui.res.R;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
@@ -35,6 +43,7 @@
 
     private final LiftToActivateListener mLiftToActivateListener;
     private final FalsingCollector mFalsingCollector;
+    private final KeyboardRepository mKeyboardRepository;
     protected PasswordTextView mPasswordEntry;
 
     private final OnKeyListener mOnKeyListener = (v, keyCode, event) -> {
@@ -65,12 +74,14 @@
             EmergencyButtonController emergencyButtonController,
             FalsingCollector falsingCollector,
             FeatureFlags featureFlags,
-            SelectedUserInteractor selectedUserInteractor) {
+            SelectedUserInteractor selectedUserInteractor,
+            KeyboardRepository keyboardRepository) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, falsingCollector,
                 emergencyButtonController, featureFlags, selectedUserInteractor);
         mLiftToActivateListener = liftToActivateListener;
         mFalsingCollector = falsingCollector;
+        mKeyboardRepository = keyboardRepository;
         mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
     }
 
@@ -120,6 +131,35 @@
             });
             okButton.setOnHoverListener(mLiftToActivateListener);
         }
+        if (pinInputFieldStyledFocusState()) {
+            collectFlow(mPasswordEntry, mKeyboardRepository.isAnyKeyboardConnected(),
+                    this::setKeyboardBasedFocusOutline);
+
+            /**
+             * new UI Specs for PIN Input field have new dimensions go/pin-focus-states.
+             * However we want these changes behind a flag, and resource files cannot be flagged
+             * hence the dimension change in code. When the flags are removed these dimensions
+             * should be set in resources permanently and the code below removed.
+             */
+            ViewGroup.LayoutParams layoutParams = mPasswordEntry.getLayoutParams();
+            layoutParams.width = (int) getResources().getDimension(
+                    R.dimen.keyguard_pin_field_width);
+            layoutParams.height = (int) getResources().getDimension(
+                    R.dimen.keyguard_pin_field_height);
+        }
+    }
+
+    private void setKeyboardBasedFocusOutline(boolean isAnyKeyboardConnected) {
+        StateListDrawable background = (StateListDrawable) mPasswordEntry.getBackground();
+        GradientDrawable stateDrawable = (GradientDrawable) background.getStateDrawable(0);
+        int color = getResources().getColor(R.color.bouncer_password_focus_color);
+        if (!isAnyKeyboardConnected) {
+            stateDrawable.setStroke(0, color);
+        } else {
+            int strokeWidthInDP = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3,
+                    getResources().getDisplayMetrics());
+            stateDrawable.setStroke(strokeWidthInDP, color);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 2aab1f1..b958f55 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -28,6 +28,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyboard.data.repository.KeyboardRepository;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -58,12 +59,13 @@
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
             EmergencyButtonController emergencyButtonController,
             FalsingCollector falsingCollector,
-            DevicePostureController postureController,
-            FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor,
-            UiEventLogger uiEventLogger) {
+            DevicePostureController postureController, FeatureFlags featureFlags,
+            SelectedUserInteractor selectedUserInteractor, UiEventLogger uiEventLogger,
+            KeyboardRepository keyboardRepository) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor);
+                emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
+                keyboardRepository);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mPostureController = postureController;
         mLockPatternUtils = lockPatternUtils;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index e457ca1..25d7713 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -19,9 +19,9 @@
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 
+import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISSIBLE_KEYGUARD;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS;
-import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISSIBLE_KEYGUARD;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_PASSWORD;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_SIM;
@@ -37,6 +37,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.Intent;
 import android.content.res.ColorStateList;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.hardware.biometrics.BiometricRequestConstants;
 import android.media.AudioManager;
@@ -83,6 +84,7 @@
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.KeyguardWmStateRefactor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.ActivityStarter;
@@ -99,8 +101,6 @@
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.GlobalSettings;
 
-import dagger.Lazy;
-
 import java.io.File;
 import java.util.Arrays;
 import java.util.Optional;
@@ -108,6 +108,7 @@
 import javax.inject.Inject;
 import javax.inject.Provider;
 
+import dagger.Lazy;
 import kotlinx.coroutines.Job;
 
 /** Controller for {@link KeyguardSecurityContainer} */
@@ -329,7 +330,7 @@
                 }
             }
 
-            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            if (KeyguardWmStateRefactor.isEnabled()) {
                 mKeyguardTransitionInteractor.startDismissKeyguardTransition();
             }
         }
@@ -390,6 +391,11 @@
                         mSecurityViewFlipperController.updateConstraints(useSplitBouncer);
                     }
                 }
+
+                @Override
+                public void onConfigChanged(Configuration newConfig) {
+                    configureMode();
+                }
             };
     private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
             new KeyguardUpdateMonitorCallback() {
@@ -1111,7 +1117,7 @@
     }
 
     private boolean canDisplayUserSwitcher() {
-        return mFeatureFlags.isEnabled(Flags.BOUNCER_USER_SWITCHER);
+        return getContext().getResources().getBoolean(R.bool.config_enableBouncerUserSwitcher);
     }
 
     private void configureMode() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index c5e7070..1cdcbd0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -44,6 +44,7 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyboard.data.repository.KeyboardRepository;
 import com.android.systemui.res.R;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
@@ -93,10 +94,11 @@
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
             TelephonyManager telephonyManager, FalsingCollector falsingCollector,
             EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
-            SelectedUserInteractor selectedUserInteractor) {
+            SelectedUserInteractor selectedUserInteractor, KeyboardRepository keyboardRepository) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor);
+                emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
+                keyboardRepository);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
@@ -105,6 +107,13 @@
     @Override
     protected void onViewAttached() {
         super.onViewAttached();
+        mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
+    }
+
+    @Override
+    protected void onViewDetached() {
+        super.onViewDetached();
+        mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
     }
 
     @Override
@@ -128,14 +137,12 @@
     @Override
     public void onResume(int reason) {
         super.onResume(reason);
-        mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
         mView.resetState();
     }
 
     @Override
     public void onPause() {
         super.onPause();
-        mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
 
         // dismiss the dialog.
         if (mSimUnlockProgressDialog != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 05fb5fa..f019d61 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -39,6 +39,7 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyboard.data.repository.KeyboardRepository;
 import com.android.systemui.res.R;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
@@ -90,10 +91,11 @@
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
             TelephonyManager telephonyManager, FalsingCollector falsingCollector,
             EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
-            SelectedUserInteractor selectedUserInteractor) {
+            SelectedUserInteractor selectedUserInteractor, KeyboardRepository keyboardRepository) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor);
+                emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
+                keyboardRepository);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 2a54a4e..1758831 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -20,6 +20,7 @@
 import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION;
+import static com.android.systemui.Flags.migrateClocksToBlueprint;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.animation.Animator;
@@ -492,7 +493,12 @@
             boolean splitShadeEnabled,
             boolean shouldBeCentered,
             boolean animate) {
-        mKeyguardClockSwitchController.setSplitShadeCentered(splitShadeEnabled && shouldBeCentered);
+        if (migrateClocksToBlueprint()) {
+            mKeyguardInteractor.setClockShouldBeCentered(shouldBeCentered);
+        } else {
+            mKeyguardClockSwitchController.setSplitShadeCentered(
+                    splitShadeEnabled && shouldBeCentered);
+        }
         if (mStatusViewCentered == shouldBeCentered) {
             return;
         }
@@ -548,8 +554,9 @@
         ClockController clock = mKeyguardClockSwitchController.getClock();
         boolean customClockAnimation = clock != null
                 && clock.getLargeClock().getConfig().getHasCustomPositionUpdatedAnimation();
-
-        if (customClockAnimation) {
+        // When migrateClocksToBlueprint is on, customized clock animation is conducted in
+        // KeyguardClockViewBinder
+        if (customClockAnimation && !migrateClocksToBlueprint()) {
             // Find the clock, so we can exclude it from this transition.
             FrameLayout clockContainerView = mView.findViewById(R.id.lockscreen_clock_view_large);
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index fe96099..536f3af 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -114,7 +114,6 @@
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.Dumpable;
-import com.android.systemui.Flags;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -383,7 +382,6 @@
     private List<SubscriptionInfo> mSubscriptionInfo;
     @VisibleForTesting
     protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
-    private boolean mFingerprintDetectRunning;
     private boolean mIsDreaming;
     private boolean mLogoutEnabled;
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -1005,7 +1003,6 @@
         final boolean wasCancellingRestarting = mFingerprintRunningState
                 == BIOMETRIC_STATE_CANCELLING_RESTARTING;
         mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
-        mFingerprintDetectRunning = false;
         if (wasCancellingRestarting) {
             KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         } else {
@@ -1114,9 +1111,6 @@
         boolean wasRunning = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
         boolean isRunning = fingerprintRunningState == BIOMETRIC_STATE_RUNNING;
         mFingerprintRunningState = fingerprintRunningState;
-        if (mFingerprintRunningState == BIOMETRIC_STATE_STOPPED) {
-            mFingerprintDetectRunning = false;
-        }
         mLogger.logFingerprintRunningState(mFingerprintRunningState);
         // Clients of KeyguardUpdateMonitor don't care about the internal state about the
         // asynchronousness of the cancel cycle. So only notify them if the actually running state
@@ -1644,11 +1638,11 @@
     void setAssistantVisible(boolean assistantVisible) {
         mAssistantVisible = assistantVisible;
         mLogger.logAssistantVisible(mAssistantVisible);
-        if (getFaceAuthInteractor() != null) {
-            getFaceAuthInteractor().onAssistantTriggeredOnLockScreen();
-        }
         updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         if (mAssistantVisible) {
+            if (getFaceAuthInteractor() != null) {
+                getFaceAuthInteractor().onAssistantTriggeredOnLockScreen();
+            }
             requestActiveUnlock(
                     ActiveUnlockConfig.ActiveUnlockRequestOrigin.ASSISTANT,
                     "assistant",
@@ -2105,7 +2099,6 @@
     @VisibleForTesting
     void resetBiometricListeningState() {
         mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
-        mFingerprintDetectRunning = false;
     }
 
     @VisibleForTesting
@@ -2544,10 +2537,8 @@
             return;
         }
         final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsSupported());
-        final boolean running = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
-        final boolean runningOrRestarting = running
+        final boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING
                 || mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING;
-        final boolean runDetect = shouldRunFingerprintDetect();
         if (runningOrRestarting && !shouldListenForFingerprint) {
             if (action == BIOMETRIC_ACTION_START) {
                 mLogger.v("Ignoring stopListeningForFingerprint()");
@@ -2559,24 +2550,10 @@
                 mLogger.v("Ignoring startListeningForFingerprint()");
                 return;
             }
-            startListeningForFingerprint(runDetect);
-        } else if (running && runDetect && !mFingerprintDetectRunning) {
-            if (action == BIOMETRIC_ACTION_STOP) {
-                mLogger.v("Ignoring startListeningForFingerprint(detect)");
-                return;
-            }
-            // stop running authentication and start running fingerprint detection
-            stopListeningForFingerprint();
-            startListeningForFingerprint(true);
+            startListeningForFingerprint();
         }
     }
 
-    private boolean shouldRunFingerprintDetect() {
-        return !isUnlockingWithFingerprintAllowed()
-                || (Flags.runFingerprintDetectOnDismissibleKeyguard()
-                && getUserCanSkipBouncer(mSelectedUserInteractor.getSelectedUserId()));
-    }
-
     /**
      * If a user is encrypted or not.
      * This is NOT related to the lock screen being visible or not.
@@ -2832,6 +2809,7 @@
                         && biometricEnabledForUser
                         && !isUserInLockdown(user);
         final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed();
+        final boolean isSideFps = isSfpsSupported() && isSfpsEnrolled();
         final boolean shouldListenBouncerState =
                 !strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing;
 
@@ -2894,7 +2872,7 @@
         }
     }
 
-    private void startListeningForFingerprint(boolean runDetect) {
+    private void startListeningForFingerprint() {
         final int userId = mSelectedUserInteractor.getSelectedUserId();
         final boolean unlockPossible = isUnlockWithFingerprintPossible(userId);
         if (mFingerprintCancelSignal != null) {
@@ -2924,20 +2902,18 @@
                         mFingerprintInteractiveToAuthProvider.getVendorExtension(userId));
             }
 
-            if (runDetect) {
+            if (!isUnlockingWithFingerprintAllowed()) {
                 mLogger.v("startListeningForFingerprint - detect");
                 mFpm.detectFingerprint(
                         mFingerprintCancelSignal,
                         mFingerprintDetectionCallback,
                         fingerprintAuthenticateOptions);
-                mFingerprintDetectRunning = true;
             } else {
                 mLogger.v("startListeningForFingerprint");
                 mFpm.authenticate(null /* crypto */, mFingerprintCancelSignal,
                         mFingerprintAuthenticationCallback,
                         null /* handler */,
                         fingerprintAuthenticateOptions);
-                mFingerprintDetectRunning = false;
             }
             setFingerprintRunningState(BIOMETRIC_STATE_RUNNING);
         }
@@ -3962,7 +3938,6 @@
                 mSelectedUserInteractor.getSelectedUserId()));
         pw.println("  getUserUnlockedWithBiometric()="
                 + getUserUnlockedWithBiometric(mSelectedUserInteractor.getSelectedUserId()));
-        pw.println("  mFingerprintDetectRunning=" + mFingerprintDetectRunning);
         pw.println("  SIM States:");
         for (SimData data : mSimDatas.values()) {
             pw.println("    " + data.toString());
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index d5dc85c..8e98150 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -63,6 +63,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -87,6 +88,8 @@
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
 /**
  * Controls when to show the LockIcon affordance (lock/unlocked icon or circle) on lock screen.
  *
@@ -717,6 +720,7 @@
         return mDownDetected;
     }
 
+    @ExperimentalCoroutinesApi
     @VisibleForTesting
     protected void onLongPress() {
         cancelTouches();
@@ -727,7 +731,8 @@
 
         // pre-emptively set to true to hide view
         mIsBouncerShowing = true;
-        if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) {
+        if (!DeviceEntryUdfpsRefactor.isEnabled()
+                && mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) {
             mAuthRippleController.showUnlockRipple(FINGERPRINT);
         }
         updateVisibility();
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 76f93e1..a81c1b0 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -26,6 +26,7 @@
 import android.graphics.drawable.VectorDrawable;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.annotation.Nullable;
 
@@ -145,4 +146,10 @@
             mAnimator = null;
         }
     }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        info.setTextEntryKey(true);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index 871d57d..dcfa775 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -30,7 +30,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.TextView;
 
 import androidx.annotation.Nullable;
@@ -105,8 +105,6 @@
         }
 
         setOnClickListener(mListener);
-        setOnHoverListener(new LiftToActivateListener(
-                (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE)));
 
         mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
@@ -240,4 +238,10 @@
     public void setAnimationEnabled(boolean enabled) {
         mAnimationsEnabled = enabled;
     }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        info.setTextEntryKey(true);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
index 0f5f869..4372826 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -20,11 +20,12 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.pm.UserInfo;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.UserHandle;
 
 import androidx.annotation.NonNull;
 
-import com.android.systemui.res.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.GuestResetOrExitSessionReceiver.ResetSessionDialogFactory;
@@ -32,6 +33,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.qs.QSUserSwitcherEvent;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -61,6 +63,7 @@
     private final SecureSettings mSecureSettings;
     private final ResetSessionDialogFactory mResetSessionDialogFactory;
     private final GuestSessionNotification mGuestSessionNotification;
+    private final HandlerThread mHandlerThread;
 
     @VisibleForTesting
     public final UserTracker.Callback mUserChangedCallback =
@@ -111,13 +114,16 @@
         mSecureSettings = secureSettings;
         mGuestSessionNotification = guestSessionNotification;
         mResetSessionDialogFactory = resetSessionDialogFactory;
+        mHandlerThread = new HandlerThread("GuestResumeSessionReceiver");
+        mHandlerThread.start();
     }
 
     /**
      * Register this receiver with the {@link BroadcastDispatcher}
      */
     public void register() {
-        mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
+        mUserTracker.addCallback(mUserChangedCallback,
+                  new HandlerExecutor(mHandlerThread.getThreadHandler()));
     }
 
     private void cancelDialog() {
diff --git a/packages/SystemUI/src/com/android/systemui/NoOpCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/NoOpCoreStartable.kt
new file mode 100644
index 0000000..9f54c26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/NoOpCoreStartable.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+/** A [CoreStartable] that does nothing. */
+class NoOpCoreStartable : CoreStartable {
+    override fun start() {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
index b15aaaf..e88aaf01 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
@@ -91,7 +91,7 @@
         return app
     }
 
-    @UsesReflection(KeepTarget(extendsClassConstant = SysUIComponent::class, methodName = "inject"))
+    @UsesReflection(KeepTarget(instanceOfClassConstant = SysUIComponent::class, methodName = "inject"))
     override fun instantiateProviderCompat(cl: ClassLoader, className: String): ContentProvider {
         val contentProvider = super.instantiateProviderCompat(cl, className)
         if (contentProvider is ContextInitializer) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
index 8c2d221..35f9344 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.accessibility
 
+import com.android.systemui.accessibility.data.repository.AccessibilityQsShortcutsRepository
+import com.android.systemui.accessibility.data.repository.AccessibilityQsShortcutsRepositoryImpl
 import com.android.systemui.accessibility.data.repository.ColorCorrectionRepository
 import com.android.systemui.accessibility.data.repository.ColorCorrectionRepositoryImpl
 import com.android.systemui.accessibility.data.repository.ColorInversionRepository
@@ -31,4 +33,9 @@
 
     @Binds
     fun colorInversionRepository(impl: ColorInversionRepositoryImpl): ColorInversionRepository
+
+    @Binds
+    fun accessibilityQsShortcutsRepository(
+        impl: AccessibilityQsShortcutsRepositoryImpl
+    ): AccessibilityQsShortcutsRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt
new file mode 100644
index 0000000..401ac0f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import android.util.SparseArray
+import androidx.annotation.GuardedBy
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.SharedFlow
+
+/** Provides data related to accessibility quick setting shortcut option. */
+interface AccessibilityQsShortcutsRepository {
+    /**
+     * Observable for the a11y features the user chooses in the Settings app to use the quick
+     * setting option.
+     */
+    fun a11yQsShortcutTargets(userId: Int): SharedFlow<Set<String>>
+}
+
+@SysUISingleton
+class AccessibilityQsShortcutsRepositoryImpl
+@Inject
+constructor(
+    private val userA11yQsShortcutsRepositoryFactory: UserA11yQsShortcutsRepository.Factory,
+) : AccessibilityQsShortcutsRepository {
+
+    @GuardedBy("userA11yQsShortcutsRepositories")
+    private val userA11yQsShortcutsRepositories = SparseArray<UserA11yQsShortcutsRepository>()
+
+    override fun a11yQsShortcutTargets(userId: Int): SharedFlow<Set<String>> {
+        return synchronized(userA11yQsShortcutsRepositories) {
+            if (userId !in userA11yQsShortcutsRepositories) {
+                val userA11yQsShortcutsRepository =
+                    userA11yQsShortcutsRepositoryFactory.create(userId)
+                userA11yQsShortcutsRepositories.put(userId, userA11yQsShortcutsRepository)
+            }
+            userA11yQsShortcutsRepositories.get(userId).targets
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
index 6483ae4..c7e5b64 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
@@ -19,16 +19,20 @@
 import android.os.UserHandle
 import android.provider.Settings.Secure
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
 import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.withContext
 
 /** Provides data related to color correction. */
@@ -45,22 +49,24 @@
 @Inject
 constructor(
     @Background private val bgCoroutineContext: CoroutineContext,
+    @Application private val scope: CoroutineScope,
     private val secureSettings: SecureSettings,
 ) : ColorCorrectionRepository {
 
-    companion object {
-        const val SETTING_NAME = Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED
-        const val DISABLED = 0
-        const val ENABLED = 1
-    }
+    private val userMap = mutableMapOf<Int, Flow<Boolean>>()
 
     override fun isEnabled(userHandle: UserHandle): Flow<Boolean> =
-        secureSettings
-            .observerFlow(userHandle.identifier, SETTING_NAME)
-            .onStart { emit(Unit) }
-            .map { secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED }
-            .distinctUntilChanged()
-            .flowOn(bgCoroutineContext)
+        userMap.getOrPut(userHandle.identifier) {
+            secureSettings
+                .observerFlow(userHandle.identifier, SETTING_NAME)
+                .onStart { emit(Unit) }
+                .map {
+                    secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED
+                }
+                .distinctUntilChanged()
+                .flowOn(bgCoroutineContext)
+                .shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1)
+        }
 
     override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean =
         withContext(bgCoroutineContext) {
@@ -70,4 +76,10 @@
                 userHandle.identifier
             )
         }
+
+    companion object {
+        private const val SETTING_NAME = Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED
+        private const val DISABLED = 0
+        private const val ENABLED = 1
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
index bbf10c5..419eada 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
@@ -19,16 +19,20 @@
 import android.os.UserHandle
 import android.provider.Settings.Secure
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
 import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.withContext
 
 /** Provides data related to color inversion. */
@@ -45,16 +49,24 @@
 @Inject
 constructor(
     @Background private val bgCoroutineContext: CoroutineContext,
+    @Application private val scope: CoroutineScope,
     private val secureSettings: SecureSettings,
 ) : ColorInversionRepository {
 
+    private val userMap = mutableMapOf<Int, Flow<Boolean>>()
+
     override fun isEnabled(userHandle: UserHandle): Flow<Boolean> =
-        secureSettings
-            .observerFlow(userHandle.identifier, SETTING_NAME)
-            .onStart { emit(Unit) }
-            .map { secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED }
-            .distinctUntilChanged()
-            .flowOn(bgCoroutineContext)
+        userMap.getOrPut(userHandle.identifier) {
+            secureSettings
+                .observerFlow(userHandle.identifier, SETTING_NAME)
+                .onStart { emit(Unit) }
+                .map {
+                    secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED
+                }
+                .distinctUntilChanged()
+                .flowOn(bgCoroutineContext)
+                .shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1)
+        }
 
     override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean =
         withContext(bgCoroutineContext) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepository.kt
new file mode 100644
index 0000000..ed91f03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepository.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import android.provider.Settings
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+
+/**
+ * Single user version of [AccessibilityQsShortcutsRepository]. It provides a similar interface as
+ * [TileSpecRepository], but focusing solely on the user it was created for. It observes the changes
+ * on the [Settings.Secure.ACCESSIBILITY_QS_TARGETS] for a given user
+ */
+class UserA11yQsShortcutsRepository
+@AssistedInject
+constructor(
+    @Assisted private val userId: Int,
+    private val secureSettings: SecureSettings,
+    @Application private val applicationScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+    val targets =
+        secureSettings
+            .observerFlow(userId, SETTING_NAME)
+            // Force an update
+            .onStart { emit(Unit) }
+            .map { getA11yQsShortcutTargets(userId) }
+            .flowOn(backgroundDispatcher)
+            .shareIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                replay = 1
+            )
+
+    private fun getA11yQsShortcutTargets(userId: Int): Set<String> {
+        val settingValue = secureSettings.getStringForUser(SETTING_NAME, userId) ?: ""
+        return settingValue.split(SETTING_SEPARATOR).filterNot { it.isEmpty() }.toSet()
+    }
+
+    companion object {
+        const val SETTING_NAME = Settings.Secure.ACCESSIBILITY_QS_TARGETS
+        const val SETTING_SEPARATOR = ":"
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            userId: Int,
+        ): UserA11yQsShortcutsRepository
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
index 49e0df6..7fd72ec 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
@@ -16,121 +16,138 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
+import static android.R.id.empty;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.util.ArrayMap;
+import android.util.Pair;
 import android.view.MotionEvent;
 
 import androidx.annotation.NonNull;
 import androidx.dynamicanimation.animation.DynamicAnimation;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Flags;
+import com.android.wm.shell.common.bubbles.DismissCircleView;
 import com.android.wm.shell.common.bubbles.DismissView;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 
+import java.util.Map;
+import java.util.Objects;
+
 /**
  * Controls the interaction between {@link MagnetizedObject} and
  * {@link MagnetizedObject.MagneticTarget}.
  */
 class DragToInteractAnimationController {
-    private static final boolean ENABLE_FLING_TO_DISMISS_MENU = false;
     private static final float COMPLETELY_OPAQUE = 1.0f;
     private static final float COMPLETELY_TRANSPARENT = 0.0f;
     private static final float CIRCLE_VIEW_DEFAULT_SCALE = 1.0f;
     private static final float ANIMATING_MAX_ALPHA = 0.7f;
 
+    private final DragToInteractView mInteractView;
     private final DismissView mDismissView;
     private final MenuView mMenuView;
-    private final ValueAnimator mDismissAnimator;
-    private final MagnetizedObject<?> mMagnetizedObject;
-    private float mMinDismissSize;
+
+    /**
+     * MagnetizedObject cannot differentiate between its MagnetizedTargets,
+     * so we need an object & an animator for every interactable.
+     */
+    private final ArrayMap<Integer, Pair<MagnetizedObject<MenuView>, ValueAnimator>> mInteractMap;
+
+    private float mMinInteractSize;
     private float mSizePercent;
 
+    DragToInteractAnimationController(DragToInteractView interactView, MenuView menuView) {
+        mDismissView = null;
+        mInteractView = interactView;
+        mInteractView.setPivotX(interactView.getWidth() / 2.0f);
+        mInteractView.setPivotY(interactView.getHeight() / 2.0f);
+        mMenuView = menuView;
+
+        updateResources();
+
+        mInteractMap = new ArrayMap<>();
+        interactView.getInteractMap().forEach((viewId, pair) -> {
+            DismissCircleView circleView = pair.getFirst();
+            createMagnetizedObjectAndAnimator(circleView);
+        });
+    }
+
     DragToInteractAnimationController(DismissView dismissView, MenuView menuView) {
         mDismissView = dismissView;
+        mInteractView = null;
         mDismissView.setPivotX(dismissView.getWidth() / 2.0f);
         mDismissView.setPivotY(dismissView.getHeight() / 2.0f);
         mMenuView = menuView;
 
         updateResources();
 
-        mDismissAnimator = ValueAnimator.ofFloat(COMPLETELY_OPAQUE, COMPLETELY_TRANSPARENT);
-        mDismissAnimator.addUpdateListener(dismissAnimation -> {
-            final float animatedValue = (float) dismissAnimation.getAnimatedValue();
-            final float scaleValue = Math.max(animatedValue, mSizePercent);
-            dismissView.getCircle().setScaleX(scaleValue);
-            dismissView.getCircle().setScaleY(scaleValue);
-
-            menuView.setAlpha(Math.max(animatedValue, ANIMATING_MAX_ALPHA));
-        });
-
-        mDismissAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
-                super.onAnimationEnd(animation, isReverse);
-
-                if (isReverse) {
-                    mDismissView.getCircle().setScaleX(CIRCLE_VIEW_DEFAULT_SCALE);
-                    mDismissView.getCircle().setScaleY(CIRCLE_VIEW_DEFAULT_SCALE);
-                    mMenuView.setAlpha(COMPLETELY_OPAQUE);
-                }
-            }
-        });
-
-        mMagnetizedObject =
-                new MagnetizedObject<MenuView>(mMenuView.getContext(), mMenuView,
-                        new MenuAnimationController.MenuPositionProperty(
-                                DynamicAnimation.TRANSLATION_X),
-                        new MenuAnimationController.MenuPositionProperty(
-                                DynamicAnimation.TRANSLATION_Y)) {
-                    @Override
-                    public void getLocationOnScreen(MenuView underlyingObject, int[] loc) {
-                        underlyingObject.getLocationOnScreen(loc);
-                    }
-
-                    @Override
-                    public float getHeight(MenuView underlyingObject) {
-                        return underlyingObject.getHeight();
-                    }
-
-                    @Override
-                    public float getWidth(MenuView underlyingObject) {
-                        return underlyingObject.getWidth();
-                    }
-                };
-
-        final MagnetizedObject.MagneticTarget magneticTarget = new MagnetizedObject.MagneticTarget(
-                dismissView.getCircle(), (int) mMinDismissSize);
-        mMagnetizedObject.addTarget(magneticTarget);
-        mMagnetizedObject.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_MENU);
+        mInteractMap = new ArrayMap<>();
+        createMagnetizedObjectAndAnimator(dismissView.getCircle());
     }
 
-    void showDismissView(boolean show) {
-        if (show) {
-            mDismissView.show();
-        } else {
-            mDismissView.hide();
+    void showInteractView(boolean show) {
+        if (Flags.floatingMenuDragToEdit() && mInteractView != null) {
+            if (show) {
+                mInteractView.show();
+            } else {
+                mInteractView.hide();
+            }
+        } else if (mDismissView != null) {
+            if (show) {
+                mDismissView.show();
+            } else {
+                mDismissView.hide();
+            }
         }
     }
 
     void setMagnetListener(MagnetizedObject.MagnetListener magnetListener) {
-        mMagnetizedObject.setMagnetListener(magnetListener);
+        mInteractMap.forEach((viewId, pair) -> {
+            MagnetizedObject<?> magnetizedObject = pair.first;
+            magnetizedObject.setMagnetListener(magnetListener);
+        });
+    }
+
+    @VisibleForTesting
+    MagnetizedObject.MagnetListener getMagnetListener(int id) {
+        return Objects.requireNonNull(mInteractMap.get(id)).first.getMagnetListener();
     }
 
     void maybeConsumeDownMotionEvent(MotionEvent event) {
-        mMagnetizedObject.maybeConsumeMotionEvent(event);
+        mInteractMap.forEach((viewId, pair) -> {
+            MagnetizedObject<?> magnetizedObject = pair.first;
+            magnetizedObject.maybeConsumeMotionEvent(event);
+        });
+    }
+
+    private int maybeConsumeMotionEvent(MotionEvent event) {
+        for (Map.Entry<Integer, Pair<MagnetizedObject<MenuView>, ValueAnimator>> set:
+                mInteractMap.entrySet()) {
+            MagnetizedObject<MenuView> magnetizedObject = set.getValue().first;
+            if (magnetizedObject.maybeConsumeMotionEvent(event)) {
+                return set.getKey();
+            }
+        }
+        return empty;
     }
 
     /**
-     * This used to pass {@link MotionEvent#ACTION_DOWN} to the magnetized object to check if it was
-     * within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}.
+     * This used to pass {@link MotionEvent#ACTION_DOWN} to the magnetized objects
+     * to check if it was within a magnetic field.
+     * It should be used in the {@link MenuListViewTouchHandler}.
      *
      * @param event that move the magnetized object which is also the menu list view.
-     * @return true if the location of the motion events moves within the magnetic field of a
-     * target, but false if didn't set
+     * @return id of a target if the location of the motion events moves
+     * within the field of the target, otherwise it returns{@link android.R.id#empty}.
+     * <p>
      * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
      */
-    boolean maybeConsumeMoveMotionEvent(MotionEvent event) {
-        return mMagnetizedObject.maybeConsumeMotionEvent(event);
+    int maybeConsumeMoveMotionEvent(MotionEvent event) {
+        return maybeConsumeMotionEvent(event);
     }
 
     /**
@@ -138,31 +155,93 @@
      * within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}.
      *
      * @param event that move the magnetized object which is also the menu list view.
-     * @return true if the location of the motion events moves within the magnetic field of a
-     * target, but false if didn't set
+     * @return id of a target if the location of the motion events moves
+     * within the field of the target, otherwise it returns{@link android.R.id#empty}.
      * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
      */
-    boolean maybeConsumeUpMotionEvent(MotionEvent event) {
-        return mMagnetizedObject.maybeConsumeMotionEvent(event);
+    int maybeConsumeUpMotionEvent(MotionEvent event) {
+        return maybeConsumeMotionEvent(event);
     }
 
-    void animateDismissMenu(boolean scaleUp) {
+    void animateInteractMenu(int targetViewId, boolean scaleUp) {
+        Pair<MagnetizedObject<MenuView>, ValueAnimator> value = mInteractMap.get(targetViewId);
+        if (value == null) {
+            return;
+        }
+        ValueAnimator animator = value.second;
         if (scaleUp) {
-            mDismissAnimator.start();
+            animator.start();
         } else {
-            mDismissAnimator.reverse();
+            animator.reverse();
         }
     }
 
     void updateResources() {
-        final float maxDismissSize = mDismissView.getResources().getDimensionPixelSize(
+        final float maxInteractSize = mMenuView.getResources().getDimensionPixelSize(
                 com.android.wm.shell.R.dimen.dismiss_circle_size);
-        mMinDismissSize = mDismissView.getResources().getDimensionPixelSize(
+        mMinInteractSize = mMenuView.getResources().getDimensionPixelSize(
                 com.android.wm.shell.R.dimen.dismiss_circle_small);
-        mSizePercent = mMinDismissSize / maxDismissSize;
+        mSizePercent = mMinInteractSize / maxInteractSize;
     }
 
-    interface DismissCallback {
-        void onDismiss();
+    /**
+     * Creates a magnetizedObject & valueAnimator pair for the provided circleView,
+     * and adds them to the interactMap.
+     *
+     * @param circleView circleView to create objects for.
+     */
+    private void createMagnetizedObjectAndAnimator(DismissCircleView circleView) {
+        MagnetizedObject<MenuView> magnetizedObject = new MagnetizedObject<MenuView>(
+                mMenuView.getContext(), mMenuView,
+                new MenuAnimationController.MenuPositionProperty(
+                        DynamicAnimation.TRANSLATION_X),
+                new MenuAnimationController.MenuPositionProperty(
+                        DynamicAnimation.TRANSLATION_Y)) {
+            @Override
+            public void getLocationOnScreen(MenuView underlyingObject, @NonNull int[] loc) {
+                underlyingObject.getLocationOnScreen(loc);
+            }
+
+            @Override
+            public float getHeight(MenuView underlyingObject) {
+                return underlyingObject.getHeight();
+            }
+
+            @Override
+            public float getWidth(MenuView underlyingObject) {
+                return underlyingObject.getWidth();
+            }
+        };
+        // Avoid unintended selection of an object / option
+        magnetizedObject.setFlingToTargetEnabled(false);
+        magnetizedObject.addTarget(new MagnetizedObject.MagneticTarget(
+                circleView, (int) mMinInteractSize));
+
+        final ValueAnimator animator =
+                ValueAnimator.ofFloat(COMPLETELY_OPAQUE, COMPLETELY_TRANSPARENT);
+
+        animator.addUpdateListener(dismissAnimation -> {
+            final float animatedValue = (float) dismissAnimation.getAnimatedValue();
+            final float scaleValue = Math.max(animatedValue, mSizePercent);
+            circleView.setScaleX(scaleValue);
+            circleView.setScaleY(scaleValue);
+
+            mMenuView.setAlpha(Math.max(animatedValue, ANIMATING_MAX_ALPHA));
+        });
+
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+                super.onAnimationEnd(animation, isReverse);
+
+                if (isReverse) {
+                    circleView.setScaleX(CIRCLE_VIEW_DEFAULT_SCALE);
+                    circleView.setScaleY(CIRCLE_VIEW_DEFAULT_SCALE);
+                    mMenuView.setAlpha(COMPLETELY_OPAQUE);
+                }
+            }
+        });
+
+        mInteractMap.put(circleView.getId(), new Pair<>(magnetizedObject, animator));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
new file mode 100644
index 0000000..0ef3d20
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.floatingmenu
+
+import android.animation.ObjectAnimator
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.GradientDrawable
+import android.util.ArrayMap
+import android.util.IntProperty
+import android.util.Log
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import android.widget.Space
+import androidx.annotation.ColorRes
+import androidx.annotation.DimenRes
+import androidx.annotation.DrawableRes
+import androidx.core.content.ContextCompat
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
+import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
+import com.android.wm.shell.R
+import com.android.wm.shell.animation.PhysicsAnimator
+import com.android.wm.shell.common.bubbles.DismissCircleView
+import com.android.wm.shell.common.bubbles.DismissView
+
+/**
+ * View that handles interactions between DismissCircleView and BubbleStackView.
+ *
+ * @note [setup] method should be called after initialisation
+ */
+class DragToInteractView(context: Context) : FrameLayout(context) {
+    /**
+     * The configuration is used to provide module specific resource ids
+     *
+     * @see [setup] method
+     */
+    data class Config(
+        /** dimen resource id of the dismiss target circle view size */
+        @DimenRes val targetSizeResId: Int,
+        /** dimen resource id of the icon size in the dismiss target */
+        @DimenRes val iconSizeResId: Int,
+        /** dimen resource id of the bottom margin for the dismiss target */
+        @DimenRes var bottomMarginResId: Int,
+        /** dimen resource id of the height for dismiss area gradient */
+        @DimenRes val floatingGradientHeightResId: Int,
+        /** color resource id of the dismiss area gradient color */
+        @ColorRes val floatingGradientColorResId: Int,
+        /** drawable resource id of the dismiss target background */
+        @DrawableRes val backgroundResId: Int,
+        /** drawable resource id of the icon for the dismiss target */
+        @DrawableRes val iconResId: Int
+    )
+
+    companion object {
+        private const val SHOULD_SETUP = "The view isn't ready. Should be called after `setup`"
+        private val TAG = DragToInteractView::class.simpleName
+    }
+
+    // START DragToInteractView modification
+    // We could technically access each DismissCircleView from their Animator,
+    // but the animators only store a weak reference to their targets. This is safer.
+    var interactMap = ArrayMap<Int, Pair<DismissCircleView, PhysicsAnimator<DismissCircleView>>>()
+    // END DragToInteractView modification
+    var isShowing = false
+    var config: Config? = null
+
+    private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY)
+    private val INTERACT_SCRIM_FADE_MS = 200L
+    private var wm: WindowManager =
+        context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+    private var gradientDrawable: GradientDrawable? = null
+
+    private val GRADIENT_ALPHA: IntProperty<GradientDrawable> =
+        object : IntProperty<GradientDrawable>("alpha") {
+            override fun setValue(d: GradientDrawable, percent: Int) {
+                d.alpha = percent
+            }
+            override fun get(d: GradientDrawable): Int {
+                return d.alpha
+            }
+        }
+
+    init {
+        clipToPadding = false
+        clipChildren = false
+        visibility = View.INVISIBLE
+
+        // START DragToInteractView modification
+        // Resources included within implementation as we aren't concerned with decoupling them.
+        setup(
+            Config(
+                targetSizeResId = R.dimen.dismiss_circle_size,
+                iconSizeResId = R.dimen.dismiss_target_x_size,
+                bottomMarginResId = R.dimen.floating_dismiss_bottom_margin,
+                floatingGradientHeightResId = R.dimen.floating_dismiss_gradient_height,
+                floatingGradientColorResId = android.R.color.system_neutral1_900,
+                backgroundResId = R.drawable.dismiss_circle_background,
+                iconResId = R.drawable.pip_ic_close_white
+            )
+        )
+        // END DragToInteractView modification
+    }
+
+    /**
+     * Sets up view with the provided resource ids.
+     *
+     * Decouples resource dependency in order to be used externally (e.g. Launcher). Usually called
+     * with default params in module specific extension:
+     *
+     * @see [DismissView.setup] in DismissViewExt.kt
+     */
+    fun setup(config: Config) {
+        this.config = config
+
+        // Setup layout
+        layoutParams =
+            LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                resources.getDimensionPixelSize(config.floatingGradientHeightResId),
+                Gravity.BOTTOM
+            )
+        updatePadding()
+
+        // Setup gradient
+        gradientDrawable = createGradient(color = config.floatingGradientColorResId)
+        background = gradientDrawable
+
+        // START DragToInteractView modification
+
+        // Setup LinearLayout. Added to organize multiple circles.
+        val linearLayout = LinearLayout(context)
+        linearLayout.layoutParams =
+            LinearLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT
+            )
+        linearLayout.weightSum = 0f
+        addView(linearLayout)
+
+        // Setup DismissCircleView. Code block replaced with repeatable functions
+        addSpace(linearLayout)
+        addCircle(
+            config,
+            com.android.systemui.res.R.id.action_remove_menu,
+            R.drawable.pip_ic_close_white,
+            linearLayout
+        )
+        addCircle(
+            config,
+            com.android.systemui.res.R.id.action_edit,
+            com.android.systemui.res.R.drawable.ic_screenshot_edit,
+            linearLayout
+        )
+        // END DragToInteractView modification
+    }
+
+    /** Animates this view in. */
+    fun show() {
+        if (isShowing) return
+        val gradientDrawable = checkExists(gradientDrawable) ?: return
+        isShowing = true
+        visibility = View.VISIBLE
+        val alphaAnim =
+            ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, gradientDrawable.alpha, 255)
+        alphaAnim.duration = INTERACT_SCRIM_FADE_MS
+        alphaAnim.start()
+
+        // START DragToInteractView modification
+        interactMap.forEach {
+            val animator = it.value.second
+            animator.cancel()
+            animator.spring(DynamicAnimation.TRANSLATION_Y, 0f, spring).start()
+        }
+        // END DragToInteractView modification
+    }
+
+    /**
+     * Animates this view out, as well as the circle that encircles the bubbles, if they were
+     * dragged into the target and encircled.
+     */
+    fun hide() {
+        if (!isShowing) return
+        val gradientDrawable = checkExists(gradientDrawable) ?: return
+        isShowing = false
+        val alphaAnim =
+            ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, gradientDrawable.alpha, 0)
+        alphaAnim.duration = INTERACT_SCRIM_FADE_MS
+        alphaAnim.start()
+
+        // START DragToInteractView modification
+        interactMap.forEach {
+            val animator = it.value.second
+            animator
+                .spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(), spring)
+                .withEndActions({ visibility = View.INVISIBLE })
+                .start()
+        }
+        // END DragToInteractView modification
+    }
+
+    /** Cancels the animator for the dismiss target. */
+    fun cancelAnimators() {
+        // START DragToInteractView modification
+        interactMap.forEach {
+            val animator = it.value.second
+            animator.cancel()
+        }
+        // END DragToInteractView modification
+    }
+
+    fun updateResources() {
+        val config = checkExists(config) ?: return
+        updatePadding()
+        layoutParams.height = resources.getDimensionPixelSize(config.floatingGradientHeightResId)
+        val targetSize = resources.getDimensionPixelSize(config.targetSizeResId)
+
+        // START DragToInteractView modification
+        interactMap.forEach {
+            val circle = it.value.first
+            circle.layoutParams.width = targetSize
+            circle.layoutParams.height = targetSize
+            circle.requestLayout()
+        }
+        // END DragToInteractView modification
+    }
+
+    private fun createGradient(@ColorRes color: Int): GradientDrawable {
+        val gradientColor = ContextCompat.getColor(context, color)
+        val alpha = 0.7f * 255
+        val gradientColorWithAlpha =
+            Color.argb(
+                alpha.toInt(),
+                Color.red(gradientColor),
+                Color.green(gradientColor),
+                Color.blue(gradientColor)
+            )
+        val gd =
+            GradientDrawable(
+                GradientDrawable.Orientation.BOTTOM_TOP,
+                intArrayOf(gradientColorWithAlpha, Color.TRANSPARENT)
+            )
+        gd.setDither(true)
+        gd.alpha = 0
+        return gd
+    }
+
+    private fun updatePadding() {
+        val config = checkExists(config) ?: return
+        val insets: WindowInsets = wm.currentWindowMetrics.windowInsets
+        val navInset = insets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars())
+        setPadding(
+            0,
+            0,
+            0,
+            navInset.bottom + resources.getDimensionPixelSize(config.bottomMarginResId)
+        )
+    }
+
+    /**
+     * Checks if the value is set up and exists, if not logs an exception. Used for convenient
+     * logging in case `setup` wasn't called before
+     *
+     * @return value provided as argument
+     */
+    private fun <T> checkExists(value: T?): T? {
+        if (value == null) Log.e(TAG, SHOULD_SETUP)
+        return value
+    }
+
+    // START DragToInteractView modification
+    private fun addSpace(parent: LinearLayout) {
+        val space = Space(context)
+        // Spaces are weighted equally to space out circles evenly
+        space.layoutParams =
+            LinearLayout.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                1f
+            )
+        parent.addView(space)
+        parent.weightSum = parent.weightSum + 1f
+    }
+
+    private fun addCircle(config: Config, id: Int, iconResId: Int, parent: LinearLayout) {
+        val targetSize = resources.getDimensionPixelSize(config.targetSizeResId)
+        val circleLayoutParams = LinearLayout.LayoutParams(targetSize, targetSize, 0f)
+        circleLayoutParams.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+        val circle = DismissCircleView(context)
+        circle.id = id
+        circle.setup(config.backgroundResId, iconResId, config.iconSizeResId)
+        circle.layoutParams = circleLayoutParams
+
+        // Initial position with circle offscreen so it's animated up
+        circle.translationY =
+            resources.getDimensionPixelSize(config.floatingGradientHeightResId).toFloat()
+
+        interactMap[circle.id] = Pair(circle, PhysicsAnimator.getInstance(circle))
+        parent.addView(circle)
+        addSpace(parent)
+    }
+    // END DragToInteractView modification
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index a270558..d3e85e0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -37,7 +37,6 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
 import com.android.systemui.Flags;
 
 import java.util.HashMap;
@@ -73,7 +72,6 @@
     private final ValueAnimator mFadeOutAnimator;
     private final Handler mHandler;
     private boolean mIsFadeEffectEnabled;
-    private DragToInteractAnimationController.DismissCallback mDismissCallback;
     private Runnable mSpringAnimationsEndAction;
 
     // Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link
@@ -170,11 +168,6 @@
         mSpringAnimationsEndAction = runnable;
     }
 
-    void setDismissCallback(
-            DragToInteractAnimationController.DismissCallback dismissCallback) {
-        mDismissCallback = dismissCallback;
-    }
-
     void moveToTopLeftPosition() {
         mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
         final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
@@ -205,13 +198,6 @@
         constrainPositionAndUpdate(position, /* writeToPosition = */ true);
     }
 
-    void removeMenu() {
-        Preconditions.checkArgument(mDismissCallback != null,
-                "The dismiss callback should be initialized first.");
-
-        mDismissCallback.onDismiss();
-    }
-
     void flingMenuThenSpringToEdge(float x, float velocityX, float velocityY) {
         final boolean shouldMenuFlingLeft = isOnLeftSide()
                 ? velocityX < ESCAPE_VELOCITY
@@ -334,8 +320,6 @@
             moveToEdgeAndHide();
             return true;
         }
-
-        fadeOutIfEnabled();
         return false;
     }
 
@@ -453,8 +437,6 @@
         mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
         constrainPositionAndUpdate(position, writeToPosition);
 
-        fadeOutIfEnabled();
-
         if (mSpringAnimationsEndAction != null) {
             mSpringAnimationsEndAction.run();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
index 0538e7d..4a06ae9 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
@@ -21,7 +21,6 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY;
 import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE;
 import static android.provider.Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
 
 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
 import static com.android.systemui.accessibility.floatingmenu.MenuFadeEffectInfoKt.DEFAULT_FADE_EFFECT_IS_ENABLED;
@@ -47,6 +46,7 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.dialog.AccessibilityTarget;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Prefs;
@@ -182,7 +182,7 @@
     }
 
     void loadMenuTargetFeatures(OnInfoReady<List<AccessibilityTarget>> callback) {
-        callback.onReady(getTargets(mContext, ACCESSIBILITY_BUTTON));
+        callback.onReady(getTargets(mContext, ShortcutConstants.UserShortcutType.SOFTWARE));
     }
 
     void loadMenuSizeType(OnInfoReady<Integer> callback) {
@@ -223,7 +223,7 @@
 
     private void onTargetFeaturesChanged() {
         mSettingsContentsCallback.onTargetFeaturesChanged(
-                getTargets(mContext, ACCESSIBILITY_BUTTON));
+                getTargets(mContext, ShortcutConstants.UserShortcutType.SOFTWARE));
     }
 
     private Position getStartPosition() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
index 9c22a77..975a602 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
@@ -27,6 +27,7 @@
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
 import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
 
+import com.android.systemui.Flags;
 import com.android.systemui.res.R;
 
 /**
@@ -35,15 +36,18 @@
  */
 class MenuItemAccessibilityDelegate extends RecyclerViewAccessibilityDelegate.ItemDelegate {
     private final MenuAnimationController mAnimationController;
+    private final MenuViewLayer mMenuViewLayer;
 
     MenuItemAccessibilityDelegate(@NonNull RecyclerViewAccessibilityDelegate recyclerViewDelegate,
-            MenuAnimationController animationController) {
+            MenuAnimationController animationController, MenuViewLayer menuViewLayer) {
         super(recyclerViewDelegate);
         mAnimationController = animationController;
+        mMenuViewLayer = menuViewLayer;
     }
 
     @Override
-    public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
+    public void onInitializeAccessibilityNodeInfo(
+            @NonNull View host, @NonNull AccessibilityNodeInfoCompat info) {
         super.onInitializeAccessibilityNodeInfo(host, info);
 
         final Resources res = host.getResources();
@@ -90,6 +94,15 @@
                         R.id.action_remove_menu,
                         res.getString(R.string.accessibility_floating_button_action_remove_menu));
         info.addAction(removeMenu);
+
+        if (Flags.floatingMenuDragToEdit()) {
+            final AccessibilityNodeInfoCompat.AccessibilityActionCompat edit =
+                    new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+                            R.id.action_edit,
+                            res.getString(
+                                    R.string.accessibility_floating_button_action_remove_menu));
+            info.addAction(edit);
+        }
     }
 
     @Override
@@ -132,8 +145,8 @@
             return true;
         }
 
-        if (action == R.id.action_remove_menu) {
-            mAnimationController.removeMenu();
+        if (action == R.id.action_remove_menu || action == R.id.action_edit) {
+            mMenuViewLayer.dispatchAccessibilityAction(action);
             return true;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
index 52e7b91..7519168 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
+import static android.R.id.empty;
+
 import android.graphics.PointF;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
@@ -78,10 +80,9 @@
                         mMenuAnimationController.onDraggingStart();
                     }
 
-                    mDragToInteractAnimationController.showDismissView(/* show= */ true);
-
-                    if (!mDragToInteractAnimationController.maybeConsumeMoveMotionEvent(
-                            motionEvent)) {
+                    mDragToInteractAnimationController.showInteractView(/* show= */ true);
+                    if (mDragToInteractAnimationController.maybeConsumeMoveMotionEvent(motionEvent)
+                            == empty) {
                         mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
                         mMenuAnimationController.moveToPositionYIfNeeded(
                                 mMenuTranslationDown.y + dy);
@@ -94,21 +95,19 @@
                     final float endX = mMenuTranslationDown.x + dx;
                     mIsDragging = false;
 
-                    if (mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
-                        mDragToInteractAnimationController.showDismissView(/* show= */ false);
-                        mMenuAnimationController.fadeOutIfEnabled();
+                    mDragToInteractAnimationController.showInteractView(/* show= */ false);
 
+                    if (mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
+                        mMenuAnimationController.fadeOutIfEnabled();
                         return true;
                     }
 
-                    if (!mDragToInteractAnimationController.maybeConsumeUpMotionEvent(
-                            motionEvent)) {
+                    if (mDragToInteractAnimationController.maybeConsumeUpMotionEvent(motionEvent)
+                            == empty) {
                         mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS);
                         mMenuAnimationController.flingMenuThenSpringToEdge(endX,
                                 mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
-                        mDragToInteractAnimationController.showDismissView(/* show= */ false);
                     }
-
                     // Avoid triggering the listener of the item.
                     return true;
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuNotificationFactory.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuNotificationFactory.java
new file mode 100644
index 0000000..b5eeaa1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuNotificationFactory.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.floatingmenu;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+
+import com.android.systemui.res.R;
+import com.android.systemui.util.NotificationChannels;
+
+class MenuNotificationFactory {
+    public static final String ACTION_UNDO =
+            "com.android.systemui.accessibility.floatingmenu.action.UNDO";
+    public static final String ACTION_DELETE =
+            "com.android.systemui.accessibility.floatingmenu.action.DELETE";
+
+    private final Context mContext;
+
+    MenuNotificationFactory(Context context) {
+        mContext = context;
+    }
+
+    public Notification createHiddenNotification() {
+        final CharSequence title = mContext.getText(
+                R.string.accessibility_floating_button_hidden_notification_title);
+        final CharSequence content = mContext.getText(
+                R.string.accessibility_floating_button_hidden_notification_text);
+
+        return new Notification.Builder(mContext, NotificationChannels.ALERTS)
+                .setContentTitle(title)
+                .setContentText(content)
+                .setSmallIcon(R.drawable.ic_settings_24dp)
+                .setContentIntent(buildUndoIntent())
+                .setDeleteIntent(buildDeleteIntent())
+                .setColor(mContext.getResources().getColor(
+                        com.android.internal.R.color.system_notification_accent_color))
+                .setLocalOnly(true)
+                .setCategory(Notification.CATEGORY_SYSTEM)
+                .build();
+    }
+
+    private PendingIntent buildUndoIntent() {
+        final Intent intent = new Intent(ACTION_UNDO);
+
+        return PendingIntent.getBroadcast(mContext, /* requestCode= */ 0, intent,
+                PendingIntent.FLAG_IMMUTABLE);
+
+    }
+
+    private PendingIntent buildDeleteIntent() {
+        final Intent intent = new Intent(ACTION_DELETE);
+
+        return PendingIntent.getBroadcastAsUser(mContext, /* requestCode= */ 0, intent,
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
+                        | PendingIntent.FLAG_IMMUTABLE, UserHandle.CURRENT);
+
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 76808cb..035ccbd 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -21,24 +21,28 @@
 import android.annotation.SuppressLint;
 import android.content.ComponentCallbacks;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.GradientDrawable;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.SettingsStringUtil;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
-import androidx.core.view.AccessibilityDelegateCompat;
 import androidx.lifecycle.Observer;
 import androidx.recyclerview.widget.DiffUtil;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
 
 import com.android.internal.accessibility.dialog.AccessibilityTarget;
 import com.android.systemui.Flags;
+import com.android.systemui.util.settings.SecureSettings;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -72,26 +76,20 @@
     private final MenuAnimationController mMenuAnimationController;
     private OnTargetFeaturesChangeListener mFeaturesChangeListener;
     private OnMoveToTuckedListener mMoveToTuckedListener;
+    private SecureSettings mSecureSettings;
 
-    MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) {
+    MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance,
+            SecureSettings secureSettings) {
         super(context);
 
         mMenuViewModel = menuViewModel;
         mMenuViewAppearance = menuViewAppearance;
+        mSecureSettings = secureSettings;
         mMenuAnimationController = new MenuAnimationController(this, menuViewAppearance);
         mAdapter = new AccessibilityTargetAdapter(mTargetFeatures);
         mTargetFeaturesView = new RecyclerView(context);
         mTargetFeaturesView.setAdapter(mAdapter);
         mTargetFeaturesView.setLayoutManager(new LinearLayoutManager(context));
-        mTargetFeaturesView.setAccessibilityDelegateCompat(
-                new RecyclerViewAccessibilityDelegate(mTargetFeaturesView) {
-                    @NonNull
-                    @Override
-                    public AccessibilityDelegateCompat getItemDelegate() {
-                        return new MenuItemAccessibilityDelegate(/* recyclerViewDelegate= */ this,
-                                mMenuAnimationController);
-                    }
-                });
         setLayoutParams(new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
         // Avoid drawing out of bounds of the parent view
         setClipToOutline(true);
@@ -228,15 +226,15 @@
             // onArrivalAtPosition() is called at the end of the animation.
         } else {
             mMenuAnimationController.moveToPosition(position);
-            onArrivalAtPosition(); // no animation, so we call this immediately.
+            onArrivalAtPosition(true); // no animation, so we call this immediately.
         }
     }
 
-    void onArrivalAtPosition() {
+    void onArrivalAtPosition(boolean moveToEdgeIfTucked) {
         final PointF position = getMenuPosition();
         onBoundsInParentChanged((int) position.x, (int) position.y);
 
-        if (isMoveToTucked()) {
+        if (isMoveToTucked() && moveToEdgeIfTucked) {
             mMenuAnimationController.moveToEdgeAndHide();
         }
     }
@@ -278,6 +276,7 @@
         if (mFeaturesChangeListener != null) {
             mFeaturesChangeListener.onChange(newTargetFeatures);
         }
+
         mMenuAnimationController.fadeOutIfEnabled();
     }
 
@@ -306,6 +305,10 @@
         return mMenuViewAppearance.getMenuPosition();
     }
 
+    RecyclerView getTargetFeaturesView() {
+        return mTargetFeaturesView;
+    }
+
     void persistPositionAndUpdateEdge(Position percentagePosition) {
         mMenuViewModel.updateMenuSavingPosition(percentagePosition);
         mMenuViewAppearance.setPercentagePosition(percentagePosition);
@@ -424,6 +427,35 @@
         onPositionChanged();
     }
 
+    void gotoEditScreen() {
+        if (!Flags.floatingMenuDragToEdit()) {
+            return;
+        }
+        mMenuAnimationController.flingMenuThenSpringToEdge(
+                getMenuPosition().x, 100f, 0f);
+        mContext.startActivity(getIntentForEditScreen());
+    }
+
+    Intent getIntentForEditScreen() {
+        List<String> targets = new SettingsStringUtil.ColonDelimitedSet.OfStrings(
+                mSecureSettings.getStringForUser(
+                        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+                        UserHandle.USER_CURRENT)).stream().toList();
+
+        Intent intent = new Intent(
+                Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
+        Bundle args = new Bundle();
+        Bundle fragmentArgs = new Bundle();
+        fragmentArgs.putStringArray("targets", targets.toArray(new String[0]));
+        args.putBundle(":settings:show_fragment_args", fragmentArgs);
+        // TODO: b/318748373 - The fragment should set its own title using the targets
+        args.putString(
+                ":settings:show_fragment_title", "Accessibility Shortcut");
+        intent.replaceExtras(args);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        return intent;
+    }
+
     private InstantInsetLayerDrawable getContainerViewInsetLayer() {
         return (InstantInsetLayerDrawable) getBackground();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 62d5feb..f2e9531 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -17,7 +17,6 @@
 package com.android.systemui.accessibility.floatingmenu;
 
 import static android.view.WindowInsets.Type.ime;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 
 import static androidx.core.view.WindowInsetsCompat.Type;
 
@@ -26,16 +25,21 @@
 import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
 import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
 import static com.android.systemui.accessibility.floatingmenu.MenuMessageView.Index;
+import static com.android.systemui.accessibility.floatingmenu.MenuNotificationFactory.ACTION_DELETE;
+import static com.android.systemui.accessibility.floatingmenu.MenuNotificationFactory.ACTION_UNDO;
 import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.IntDef;
 import android.annotation.StringDef;
 import android.annotation.SuppressLint;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
 import android.content.ComponentCallbacks;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Rect;
@@ -54,10 +58,15 @@
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.core.view.AccessibilityDelegateCompat;
 import androidx.lifecycle.Observer;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
 
+import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.dialog.AccessibilityTarget;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.util.Preconditions;
 import com.android.systemui.Flags;
 import com.android.systemui.res.R;
@@ -88,9 +97,13 @@
     private final MenuListViewTouchHandler mMenuListViewTouchHandler;
     private final MenuMessageView mMessageView;
     private final DismissView mDismissView;
+    private final DragToInteractView mDragToInteractView;
+
     private final MenuViewAppearance mMenuViewAppearance;
     private final MenuAnimationController mMenuAnimationController;
     private final AccessibilityManager mAccessibilityManager;
+    private final NotificationManager mNotificationManager;
+    private final MenuNotificationFactory mNotificationFactory;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final IAccessibilityFloatingMenu mFloatingMenu;
     private final SecureSettings mSecureSettings;
@@ -103,7 +116,9 @@
     private final Rect mImeInsetsRect = new Rect();
     private boolean mIsMigrationTooltipShowing;
     private boolean mShouldShowDockTooltip;
+    private boolean mIsNotificationShown;
     private Optional<MenuEduTooltipView> mEduTooltipView = Optional.empty();
+    private BroadcastReceiver mNotificationActionReceiver;
 
     @IntDef({
             LayerIndex.MENU_VIEW,
@@ -139,7 +154,7 @@
 
             final List<ComponentName> hardwareKeyShortcutComponents =
                     mAccessibilityManager.getAccessibilityShortcutTargets(
-                                    ACCESSIBILITY_SHORTCUT_KEY)
+                                    ShortcutConstants.UserShortcutType.HARDWARE)
                             .stream()
                             .map(ComponentName::unflattenFromString)
                             .toList();
@@ -168,7 +183,10 @@
     };
 
     MenuViewLayer(@NonNull Context context, WindowManager windowManager,
-            AccessibilityManager accessibilityManager, IAccessibilityFloatingMenu floatingMenu,
+            AccessibilityManager accessibilityManager,
+            MenuViewModel menuViewModel,
+            MenuViewAppearance menuViewAppearance, MenuView menuView,
+            IAccessibilityFloatingMenu floatingMenu,
             SecureSettings secureSettings) {
         super(context);
 
@@ -180,33 +198,52 @@
         mFloatingMenu = floatingMenu;
         mSecureSettings = secureSettings;
 
-        mMenuViewModel = new MenuViewModel(context, accessibilityManager, secureSettings);
-        mMenuViewAppearance = new MenuViewAppearance(context, windowManager);
-        mMenuView = new MenuView(context, mMenuViewModel, mMenuViewAppearance);
+        mMenuViewModel = menuViewModel;
+        mMenuViewAppearance = menuViewAppearance;
+        mMenuView = menuView;
+        RecyclerView targetFeaturesView = mMenuView.getTargetFeaturesView();
+        targetFeaturesView.setAccessibilityDelegateCompat(
+                new RecyclerViewAccessibilityDelegate(targetFeaturesView) {
+                    @NonNull
+                    @Override
+                    public AccessibilityDelegateCompat getItemDelegate() {
+                        return new MenuItemAccessibilityDelegate(/* recyclerViewDelegate= */ this,
+                                mMenuAnimationController, MenuViewLayer.this);
+                    }
+                });
         mMenuAnimationController = mMenuView.getMenuAnimationController();
-        mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
         mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction);
         mDismissView = new DismissView(context);
+        mDragToInteractView = new DragToInteractView(context);
         DismissViewUtils.setup(mDismissView);
-        mDragToInteractAnimationController = new DragToInteractAnimationController(
-                mDismissView, mMenuView);
+        mDismissView.getCircle().setId(R.id.action_remove_menu);
+        mNotificationFactory = new MenuNotificationFactory(context);
+        mNotificationManager = context.getSystemService(NotificationManager.class);
+
+        if (Flags.floatingMenuDragToEdit()) {
+            mDragToInteractAnimationController = new DragToInteractAnimationController(
+                    mDragToInteractView, mMenuView);
+        } else {
+            mDragToInteractAnimationController = new DragToInteractAnimationController(
+                    mDismissView, mMenuView);
+        }
         mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
             @Override
             public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
-                mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ true);
+                mDragToInteractAnimationController.animateInteractMenu(
+                        target.getTargetView().getId(), /* scaleUp= */ true);
             }
 
             @Override
             public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
                     float velocityX, float velocityY, boolean wasFlungOut) {
-                mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
+                mDragToInteractAnimationController.animateInteractMenu(
+                        target.getTargetView().getId(), /* scaleUp= */ false);
             }
 
             @Override
             public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
-                hideMenuAndShowMessage();
-                mDismissView.hide();
-                mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
+                dispatchAccessibilityAction(target.getTargetView().getId());
             }
         });
 
@@ -218,22 +255,35 @@
         mMessageView = new MenuMessageView(context);
 
         mMenuView.setOnTargetFeaturesChangeListener(newTargetFeatures -> {
-            if (newTargetFeatures.size() < 1) {
-                return;
-            }
+            if (Flags.floatingMenuDragToHide()) {
+                dismissNotification();
+                if (newTargetFeatures.size() > 0) {
+                    undo();
+                }
+            } else {
+                if (newTargetFeatures.size() < 1) {
+                    return;
+                }
 
-            // During the undo action period, the pending action will be canceled and undo back
-            // to the previous state if users did any action related to the accessibility features.
-            if (mMessageView.getVisibility() == VISIBLE) {
-                undo();
-            }
+                // During the undo action period, the pending action will be canceled and undo back
+                // to the previous state if users did any action related to the accessibility
+                // features.
+                if (mMessageView.getVisibility() == VISIBLE) {
+                    undo();
+                }
 
-            final TextView messageText = (TextView) mMessageView.getChildAt(Index.TEXT_VIEW);
-            messageText.setText(getMessageText(newTargetFeatures));
+
+                final TextView messageText = (TextView) mMessageView.getChildAt(Index.TEXT_VIEW);
+                messageText.setText(getMessageText(newTargetFeatures));
+            }
         });
 
         addView(mMenuView, LayerIndex.MENU_VIEW);
-        addView(mDismissView, LayerIndex.DISMISS_VIEW);
+        if (Flags.floatingMenuDragToEdit()) {
+            addView(mDragToInteractView, LayerIndex.DISMISS_VIEW);
+        } else {
+            addView(mDismissView, LayerIndex.DISMISS_VIEW);
+        }
         addView(mMessageView, LayerIndex.MESSAGE_VIEW);
 
         if (Flags.floatingMenuAnimatedTuck()) {
@@ -243,6 +293,7 @@
 
     @Override
     public void onConfigurationChanged(@NonNull Configuration newConfig) {
+        mDragToInteractView.updateResources();
         mDismissView.updateResources();
         mDragToInteractAnimationController.updateResources();
     }
@@ -395,10 +446,27 @@
             }
         }
         if (Flags.floatingMenuImeDisplacementAnimation()) {
-            mMenuView.onArrivalAtPosition();
+            mMenuView.onArrivalAtPosition(false);
         }
     }
 
+    void dispatchAccessibilityAction(int id) {
+        if (id == R.id.action_remove_menu) {
+            if (Flags.floatingMenuDragToHide()) {
+                hideMenuAndShowNotification();
+            } else {
+                hideMenuAndShowMessage();
+            }
+        } else if (id == R.id.action_edit
+                && Flags.floatingMenuDragToEdit()) {
+            mMenuView.gotoEditScreen();
+        }
+        mDismissView.hide();
+        mDragToInteractView.hide();
+        mDragToInteractAnimationController.animateInteractMenu(
+                id, /* scaleUp= */ false);
+    }
+
     private CharSequence getMigrationMessage() {
         final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -446,7 +514,8 @@
         mEduTooltipView = Optional.empty();
     }
 
-    private void hideMenuAndShowMessage() {
+    @VisibleForTesting
+    void hideMenuAndShowMessage() {
         final int delayTime = mAccessibilityManager.getRecommendedTimeoutMillis(
                 SHOW_MESSAGE_DELAY_MS,
                 AccessibilityManager.FLAG_CONTENT_TEXT
@@ -456,6 +525,51 @@
         mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE));
     }
 
+    @VisibleForTesting
+    void hideMenuAndShowNotification() {
+        mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE));
+        showNotification();
+    }
+
+    private void showNotification() {
+        registerReceiverIfNeeded();
+        if (!mIsNotificationShown) {
+            mNotificationManager.notify(
+                    SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN,
+                    mNotificationFactory.createHiddenNotification());
+            mIsNotificationShown = true;
+        }
+    }
+
+    private void dismissNotification() {
+        unregisterReceiverIfNeeded();
+        if (mIsNotificationShown) {
+            mNotificationManager.cancel(
+                    SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN);
+            mIsNotificationShown = false;
+        }
+    }
+
+    private void registerReceiverIfNeeded() {
+        if (mNotificationActionReceiver != null) {
+            return;
+        }
+        mNotificationActionReceiver = new MenuNotificationActionReceiver();
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(ACTION_UNDO);
+        intentFilter.addAction(ACTION_DELETE);
+        getContext().registerReceiver(mNotificationActionReceiver, intentFilter,
+                Context.RECEIVER_EXPORTED);
+    }
+
+    private void unregisterReceiverIfNeeded() {
+        if (mNotificationActionReceiver == null) {
+            return;
+        }
+        getContext().unregisterReceiver(mNotificationActionReceiver);
+        mNotificationActionReceiver = null;
+    }
+
     private void undo() {
         mHandler.removeCallbacksAndMessages(/* token= */ null);
         mMessageView.setVisibility(GONE);
@@ -464,4 +578,23 @@
         mMenuView.setVisibility(VISIBLE);
         mMenuAnimationController.startGrowAnimation();
     }
+
+    @VisibleForTesting
+    DragToInteractAnimationController getDragToInteractAnimationController() {
+        return mDragToInteractAnimationController;
+    }
+
+    private class MenuNotificationActionReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (ACTION_UNDO.equals(action)) {
+                dismissNotification();
+                undo();
+            } else if (ACTION_DELETE.equals(action)) {
+                dismissNotification();
+                mDismissMenuAction.run();
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index 1f54952..bc9d1ff 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -39,7 +39,16 @@
     MenuViewLayerController(Context context, WindowManager windowManager,
             AccessibilityManager accessibilityManager, SecureSettings secureSettings) {
         mWindowManager = windowManager;
-        mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager, this,
+
+        MenuViewModel menuViewModel = new MenuViewModel(
+                context, accessibilityManager, secureSettings);
+        MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context, windowManager);
+
+        mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager,
+                menuViewModel,
+                menuViewAppearance,
+                new MenuView(context, menuViewModel, menuViewAppearance, secureSettings),
+                this,
                 secureSettings);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index fdccad1..8ca083f 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -30,11 +30,16 @@
 import com.android.systemui.authentication.shared.model.AuthenticationWipeModel.WipeTarget
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 import kotlin.math.max
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharedFlow
@@ -44,6 +49,7 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 /**
  * Hosts application business logic related to user authentication.
@@ -57,6 +63,7 @@
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val repository: AuthenticationRepository,
     private val selectedUserInteractor: SelectedUserInteractor,
 ) {
@@ -227,6 +234,11 @@
         if (authenticationResult.isSuccessful) {
             repository.reportAuthenticationAttempt(isSuccessful = true)
             _onAuthenticationResult.emit(true)
+
+            // Force a garbage collection in an attempt to erase any credentials left in memory.
+            // Do it after a 5-sec delay to avoid making the bouncer dismiss animation janky.
+            initiateGarbageCollection(delay = 5.seconds)
+
             return AuthenticationResult.SUCCEEDED
         }
 
@@ -312,6 +324,15 @@
         }
     }
 
+    private suspend fun initiateGarbageCollection(delay: Duration) {
+        applicationScope.launch(backgroundDispatcher) {
+            delay(delay)
+            System.gc()
+            System.runFinalization()
+            System.gc()
+        }
+    }
+
     companion object {
         const val TAG = "AuthenticationInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index ab23564..57e308f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -399,7 +399,8 @@
                     config.mPromptInfo,
                     config.mUserId,
                     config.mOperationId,
-                    new BiometricModalities(fpProps, faceProps));
+                    new BiometricModalities(fpProps, faceProps),
+                    config.mOpPackageName);
 
             final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
                     R.layout.biometric_prompt_layout, null, false);
@@ -470,7 +471,8 @@
         mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
 
         mPromptSelectorInteractorProvider.get().useCredentialsForAuthentication(
-                mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId);
+                mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId,
+                mConfig.mOpPackageName);
         final CredentialViewModel vm = mCredentialViewModelProvider.get();
         vm.setAnimateContents(animateContents);
         ((CredentialView) mCredentialView).init(vm, this, mPanelController, animatePanel);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 093a1ff..a40b4d7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -47,6 +47,7 @@
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
 import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
 import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
@@ -1127,6 +1128,9 @@
         }
 
         mCurrentDialog.dismissFromSystemServer();
+        for (Callback cb : mCallbacks) {
+            cb.onBiometricPromptDismissed();
+        }
 
         // BiometricService will have already sent the callback to the client in this case.
         // This avoids a round trip to SystemUI. So, just dismiss the dialog and we're done.
@@ -1156,6 +1160,15 @@
     }
 
     /**
+     * Does the provided user have at least one optical udfps fingerprint enrolled?
+     */
+    public boolean isOpticalUdfpsEnrolled(int userId) {
+        return isUdfpsEnrolled(userId)
+                && mUdfpsProps != null
+                && mUdfpsProps.get(0).sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
+    }
+
+    /**
      * Whether the passed userId has enrolled UDFPS.
      */
     public boolean isUdfpsEnrolled(int userId) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 86f372a..d2c6227 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -25,6 +25,7 @@
 import android.hardware.biometrics.BiometricSourceType
 import android.util.DisplayMetrics
 import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.animation.Interpolators
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
@@ -35,8 +36,11 @@
 import com.android.systemui.biometrics.data.repository.FacePropertyRepository
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.AuthRippleInteractor
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
@@ -51,7 +55,6 @@
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.ViewController
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import java.io.PrintWriter
 import javax.inject.Inject
 import javax.inject.Provider
@@ -64,7 +67,6 @@
  *
  * The ripple uses the accent color of the current theme.
  */
-@ExperimentalCoroutinesApi
 @SysUISingleton
 class AuthRippleController @Inject constructor(
     private val sysuiContext: Context,
@@ -81,6 +83,7 @@
     private val logger: KeyguardLogger,
     private val biometricUnlockController: BiometricUnlockController,
     private val lightRevealScrim: LightRevealScrim,
+    private val authRippleInteractor: AuthRippleInteractor,
     private val facePropertyRepository: FacePropertyRepository,
     rippleView: AuthRippleView?
 ) :
@@ -103,6 +106,22 @@
         init()
     }
 
+    init {
+        if (DeviceEntryUdfpsRefactor.isEnabled) {
+            rippleView?.repeatWhenAttached {
+                repeatOnLifecycle(androidx.lifecycle.Lifecycle.State.CREATED) {
+                    authRippleInteractor.showUnlockRipple.collect { biometricUnlockSource ->
+                        if (biometricUnlockSource == BiometricUnlockSource.FINGERPRINT_SENSOR) {
+                            showUnlockRippleInternal(BiometricSourceType.FINGERPRINT)
+                        } else {
+                            showUnlockRippleInternal(BiometricSourceType.FACE)
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     @VisibleForTesting
     public override fun onViewAttached() {
         authController.addCallback(authControllerCallback)
@@ -114,7 +133,9 @@
         keyguardStateController.addCallback(this)
         wakefulnessLifecycle.addObserver(this)
         commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() }
-        biometricUnlockController.addListener(biometricModeListener)
+        if (!DeviceEntryUdfpsRefactor.isEnabled) {
+            biometricUnlockController.addListener(biometricModeListener)
+        }
     }
 
     private val biometricModeListener =
@@ -122,8 +143,9 @@
             override fun onBiometricUnlockedWithKeyguardDismissal(
                     biometricSourceType: BiometricSourceType?
             ) {
+                DeviceEntryUdfpsRefactor.assertInLegacyMode()
                 if (biometricSourceType != null) {
-                    showUnlockRipple(biometricSourceType)
+                    showUnlockRippleInternal(biometricSourceType)
                 } else {
                     logger.log(TAG,
                             LogLevel.ERROR,
@@ -146,7 +168,13 @@
         notificationShadeWindowController.setForcePluginOpen(false, this)
     }
 
-    fun showUnlockRipple(biometricSourceType: BiometricSourceType) {
+     @Deprecated("Update authRippleInteractor.showUnlockRipple instead of calling this.")
+     fun showUnlockRipple(biometricSourceType: BiometricSourceType) {
+         DeviceEntryUdfpsRefactor.assertInLegacyMode()
+         showUnlockRippleInternal(biometricSourceType)
+     }
+
+    private fun showUnlockRippleInternal(biometricSourceType: BiometricSourceType) {
         val keyguardNotShowing = !keyguardStateController.isShowing
         val unlockNotAllowed = !keyguardUpdateMonitor
                 .isUnlockingWithBiometricAllowed(biometricSourceType)
@@ -316,18 +344,6 @@
                 mView.fadeDwellRipple()
             }
         }
-
-        override fun onBiometricDetected(
-                userId: Int,
-                biometricSourceType: BiometricSourceType,
-                isStrongBiometric: Boolean
-        ) {
-            // TODO (b/309804148): add support detect auth ripple for deviceEntryUdfpsRefactor
-            if (!DeviceEntryUdfpsRefactor.isEnabled &&
-                    keyguardUpdateMonitor.getUserCanSkipBouncer(userId)) {
-                showUnlockRipple(biometricSourceType)
-            }
-        }
     }
 
     private val configurationChangedListener =
@@ -392,12 +408,12 @@
                     }
                     "fingerprint" -> {
                         pw.println("fingerprint ripple sensorLocation=$fingerprintSensorLocation")
-                        showUnlockRipple(BiometricSourceType.FINGERPRINT)
+                        showUnlockRippleInternal(BiometricSourceType.FINGERPRINT)
                     }
                     "face" -> {
                         // note: only shows when about to proceed to the home screen
                         pw.println("face ripple sensorLocation=$faceSensorLocation")
-                        showUnlockRipple(BiometricSourceType.FACE)
+                        showUnlockRippleInternal(BiometricSourceType.FACE)
                     }
                     "custom" -> {
                         if (args.size != 3 ||
@@ -424,7 +440,7 @@
             pw.println("  custom <x-location: int> <y-location: int>")
         }
 
-        fun invalidCommand(pw: PrintWriter) {
+        private fun invalidCommand(pw: PrintWriter) {
             pw.println("invalid command")
             help(pw)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt
index e9b6372..ca479f5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt
@@ -21,6 +21,7 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 import android.os.Handler
 import android.view.DisplayInfo
+import com.android.app.tracing.traceSection
 import com.android.systemui.biometrics.BiometricDisplayListener.SensorType.Generic
 
 /**
@@ -42,13 +43,15 @@
     override fun onDisplayAdded(displayId: Int) {}
     override fun onDisplayRemoved(displayId: Int) {}
     override fun onDisplayChanged(displayId: Int) {
-        val rotationChanged = didRotationChange()
+        traceSection({ "BiometricDisplayListener($sensorType)#onDisplayChanged" }) {
+            val rotationChanged = didRotationChange()
 
-        when (sensorType) {
-            is SensorType.SideFingerprint -> onChanged()
-            else -> {
-                if (rotationChanged) {
-                    onChanged()
+            when (sensorType) {
+                is SensorType.SideFingerprint -> onChanged()
+                else -> {
+                    if (rotationChanged) {
+                        onChanged()
+                    }
                 }
             }
         }
@@ -82,8 +85,8 @@
      * biometric prompt (and this object will likely change as multi-mode auth is added).
      */
     sealed class SensorType {
-        object Generic : SensorType()
-        object UnderDisplayFingerprint : SensorType()
+        data object Generic : SensorType()
+        data object UnderDisplayFingerprint : SensorType()
         data class SideFingerprint(
             val properties: FingerprintSensorPropertiesInternal
         ) : SensorType()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
index 7d9ec08..f5603ed 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
@@ -23,6 +23,7 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.animation.Interpolators
 import com.android.systemui.Dumpable
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -49,7 +50,8 @@
     protected val statusBarStateController: StatusBarStateController,
     protected val shadeInteractor: ShadeInteractor,
     protected val dialogManager: SystemUIDialogManager,
-    private val dumpManager: DumpManager
+    private val dumpManager: DumpManager,
+    private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) : ViewController<T>(view), Dumpable {
 
     protected abstract val tag: String
@@ -130,11 +132,13 @@
     override fun onViewAttached() {
         dialogManager.registerListener(dialogListener)
         dumpManager.registerDumpable(dumpTag, this)
+        udfpsOverlayInteractor.setHandleTouches(shouldHandle = true)
     }
 
     override fun onViewDetached() {
         dialogManager.unregisterListener(dialogListener)
         dumpManager.unregisterDumpable(dumpTag)
+        udfpsOverlayInteractor.setHandleTouches(shouldHandle = true)
     }
 
     /**
@@ -165,6 +169,7 @@
     fun updatePauseAuth() {
         if (view.setPauseAuth(shouldPauseAuth())) {
             view.postInvalidate()
+            udfpsOverlayInteractor.setHandleTouches(shouldHandle = !shouldPauseAuth())
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
index e7b0d9f..e0455b5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.biometrics
 
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -28,13 +29,15 @@
     statusBarStateController: StatusBarStateController,
     shadeInteractor: ShadeInteractor,
     systemUIDialogManager: SystemUIDialogManager,
-    dumpManager: DumpManager
+    dumpManager: DumpManager,
+    udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) : UdfpsAnimationViewController<UdfpsBpView>(
     view,
     statusBarStateController,
     shadeInteractor,
     systemUIDialogManager,
-    dumpManager
+    dumpManager,
+    udfpsOverlayInteractor,
 ) {
     override val tag = "UdfpsBpViewController"
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index d664637..66fe4b3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -68,6 +68,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.biometrics.dagger.BiometricsBackground;
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
 import com.android.systemui.biometrics.udfps.InteractionEvent;
 import com.android.systemui.biometrics.udfps.NormalizedTouchData;
@@ -171,6 +172,7 @@
     @NonNull private final Lazy<DefaultUdfpsTouchOverlayViewModel>
             mDefaultUdfpsTouchOverlayViewModel;
     @NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor;
+    @NonNull private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
     @NonNull private final InputManager mInputManager;
     @NonNull private final UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
     @NonNull private final SelectedUserInteractor mSelectedUserInteractor;
@@ -187,7 +189,7 @@
     @Nullable private UdfpsDisplayModeProvider mUdfpsDisplayMode;
 
     // The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active.
-    private int mActivePointerId = -1;
+    private int mActivePointerId = MotionEvent.INVALID_POINTER_ID;
     // Whether a pointer has been pilfered for current gesture
     private boolean mPointerPilfered = false;
     // The timestamp of the most recent touch log.
@@ -293,7 +295,8 @@
                         mSelectedUserInteractor,
                         mDeviceEntryUdfpsTouchOverlayViewModel,
                         mDefaultUdfpsTouchOverlayViewModel,
-                        mShadeInteractor
+                        mShadeInteractor,
+                        mUdfpsOverlayInteractor
                     )));
         }
 
@@ -510,7 +513,16 @@
                     + mOverlay.getRequestId());
             return false;
         }
-        if (!DeviceEntryUdfpsRefactor.isEnabled()) {
+        if (event.getAction() == MotionEvent.ACTION_DOWN
+                || event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
+            // Reset on ACTION_DOWN, start of new gesture
+            mPointerPilfered = false;
+            if (mActivePointerId != MotionEvent.INVALID_POINTER_ID) {
+                Log.w(TAG, "onTouch down received without a preceding up");
+            }
+            mActivePointerId = MotionEvent.INVALID_POINTER_ID;
+            mOnFingerDown = false;
+        } else if (!DeviceEntryUdfpsRefactor.isEnabled()) {
             if ((mLockscreenShadeTransitionController.getQSDragProgress() != 0f
                     && !mAlternateBouncerInteractor.isVisibleState())
                     || mPrimaryBouncerInteractor.isInTransit()) {
@@ -518,11 +530,6 @@
                 return false;
             }
         }
-        if (event.getAction() == MotionEvent.ACTION_DOWN
-                || event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
-            // Reset on ACTION_DOWN, start of new gesture
-            mPointerPilfered = false;
-        }
 
         final TouchProcessorResult result = mTouchProcessor.processTouch(event, mActivePointerId,
                 mOverlayParams);
@@ -670,7 +677,8 @@
             @NonNull FpsUnlockTracker fpsUnlockTracker,
             @NonNull KeyguardTransitionInteractor keyguardTransitionInteractor,
             Lazy<DeviceEntryUdfpsTouchOverlayViewModel> deviceEntryUdfpsTouchOverlayViewModel,
-            Lazy<DefaultUdfpsTouchOverlayViewModel> defaultUdfpsTouchOverlayViewModel) {
+            Lazy<DefaultUdfpsTouchOverlayViewModel> defaultUdfpsTouchOverlayViewModel,
+            @NonNull UdfpsOverlayInteractor udfpsOverlayInteractor) {
         mContext = context;
         mExecution = execution;
         mVibrator = vibrator;
@@ -711,6 +719,7 @@
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
         mShadeInteractor = shadeInteractor;
         mAlternateBouncerInteractor = alternateBouncerInteractor;
+        mUdfpsOverlayInteractor = udfpsOverlayInteractor;
         mInputManager = inputManager;
         mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate;
         mSelectedUserInteractor = selectedUserInteractor;
@@ -1080,7 +1089,7 @@
             long gestureStart,
             boolean isAod) {
         mExecution.assertIsMainThread();
-        mActivePointerId = -1;
+        mActivePointerId = MotionEvent.INVALID_POINTER_ID;
         mAcquiredReceived = false;
         if (mOnFingerDown) {
             mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId, pointerId, x,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index b94a177..4176083 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -45,6 +45,7 @@
 import androidx.annotation.VisibleForTesting
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.biometrics.ui.binder.UdfpsTouchOverlayBinder
 import com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay
@@ -109,6 +110,7 @@
     private val deviceEntryUdfpsTouchOverlayViewModel: Lazy<DeviceEntryUdfpsTouchOverlayViewModel>,
     private val defaultUdfpsTouchOverlayViewModel: Lazy<DefaultUdfpsTouchOverlayViewModel>,
     private val shadeInteractor: ShadeInteractor,
+    private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) {
     private var overlayViewLegacy: UdfpsView? = null
         private set
@@ -281,7 +283,8 @@
                     statusBarStateController,
                     shadeInteractor,
                     dialogManager,
-                    dumpManager
+                    dumpManager,
+                    udfpsOverlayInteractor,
                 )
             }
             REASON_AUTH_KEYGUARD -> {
@@ -306,6 +309,7 @@
                     selectedUserInteractor,
                     transitionInteractor,
                     shadeInteractor,
+                    udfpsOverlayInteractor,
                 )
             }
             REASON_AUTH_BP -> {
@@ -315,7 +319,8 @@
                     statusBarStateController,
                     shadeInteractor,
                     dialogManager,
-                    dumpManager
+                    dumpManager,
+                    udfpsOverlayInteractor,
                 )
             }
             REASON_AUTH_OTHER,
@@ -325,7 +330,8 @@
                     statusBarStateController,
                     shadeInteractor,
                     dialogManager,
-                    dumpManager
+                    dumpManager,
+                    udfpsOverlayInteractor,
                 )
             }
             else -> {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
index abbfa01..02eae9ced 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
@@ -131,25 +131,29 @@
                 icon.measure(
                         MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST),
                         MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST));
-            } else if (child.getId() == R.id.space_above_icon) {
+            } else if (child.getId() == R.id.space_above_icon
+                    || child.getId() == R.id.space_above_content
+                    || child.getId() == R.id.button_bar) {
                 child.measure(
                         MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                         MeasureSpec.makeMeasureSpec(
                                 child.getLayoutParams().height, MeasureSpec.EXACTLY));
-            } else if (child.getId() == R.id.button_bar) {
-                child.measure(
-                        MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
-                        MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
-                                MeasureSpec.EXACTLY));
             } else if (child.getId() == R.id.space_below_icon) {
                 // Set the spacer height so the fingerprint icon is on the physical sensor area
                 final int clampedSpacerHeight = Math.max(mBottomSpacerHeight, 0);
                 child.measure(
                         MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                         MeasureSpec.makeMeasureSpec(clampedSpacerHeight, MeasureSpec.EXACTLY));
-            } else if (child.getId() == R.id.description) {
+            } else if (child.getId() == R.id.description
+                    || child.getId() == R.id.customized_view_container) {
                 //skip description view and compute later
                 continue;
+            } else if (child.getId() == R.id.logo) {
+                child.measure(
+                        MeasureSpec.makeMeasureSpec(child.getLayoutParams().width,
+                                MeasureSpec.EXACTLY),
+                        MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
+                                MeasureSpec.EXACTLY));
             } else {
                 child.measure(
                         MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
@@ -161,27 +165,28 @@
             }
         }
 
-        //re-calculate the height of description
+        //re-calculate the height of body content
         View description = mView.findViewById(R.id.description);
+        View contentView = mView.findViewById(R.id.customized_view_container);
         if (description != null && description.getVisibility() != View.GONE) {
             totalHeight += measureDescription(description, displayHeight, width, totalHeight);
+        } else if (contentView != null && contentView.getVisibility() != View.GONE) {
+            totalHeight += measureDescription(contentView, displayHeight, width, totalHeight);
         }
 
         return new AuthDialog.LayoutParams(width, totalHeight);
     }
 
-    private int measureDescription(View description, int displayHeight, int currWidth,
+    private int measureDescription(View bodyContent, int displayHeight, int currWidth,
                                    int currHeight) {
-        //description view should be measured in AuthBiometricFingerprintView#onMeasureInternal
-        //so we could getMeasuredHeight in onMeasureInternalPortrait directly.
-        int newHeight = description.getMeasuredHeight() + currHeight;
+        int newHeight = bodyContent.getMeasuredHeight() + currHeight;
         int limit = (int) (displayHeight * 0.75);
         if (newHeight > limit) {
-            description.measure(
+            bodyContent.measure(
                     MeasureSpec.makeMeasureSpec(currWidth, MeasureSpec.EXACTLY),
                     MeasureSpec.makeMeasureSpec(limit - currHeight, MeasureSpec.EXACTLY));
         }
-        return description.getMeasuredHeight();
+        return bodyContent.getMeasuredHeight();
     }
 
     @NonNull
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
index ab3fbb1..cfbbc26 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.biometrics
 
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -30,13 +31,15 @@
     statusBarStateController: StatusBarStateController,
     shadeInteractor: ShadeInteractor,
     systemUIDialogManager: SystemUIDialogManager,
-    dumpManager: DumpManager
+    dumpManager: DumpManager,
+    udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) : UdfpsAnimationViewController<UdfpsFpmEmptyView>(
     view,
     statusBarStateController,
     shadeInteractor,
     systemUIDialogManager,
-    dumpManager
+    dumpManager,
+    udfpsOverlayInteractor,
 ) {
     override val tag = "UdfpsFpmOtherViewController"
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 9f17024..7020d05 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -27,6 +27,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
@@ -75,6 +76,7 @@
     private val selectedUserInteractor: SelectedUserInteractor,
     private val transitionInteractor: KeyguardTransitionInteractor,
     shadeInteractor: ShadeInteractor,
+    udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) :
     UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>(
         view,
@@ -82,6 +84,7 @@
         shadeInteractor,
         systemUIDialogManager,
         dumpManager,
+        udfpsOverlayInteractor,
     ) {
     private val uniqueIdentifier = this.toString()
     private var showingUdfpsBouncer = false
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
index ad2136a..d28dbc0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
@@ -94,6 +94,10 @@
                         override fun onAuthenticationStopped() {
                             updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
                         }
+
+                        override fun onAuthenticationSucceeded(requestReason: Int, userId: Int) {}
+
+                        override fun onAuthenticationFailed(requestReason: Int, userId: Int) {}
                     }
 
                 updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
index aaccbc1..792a7ef 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
@@ -24,6 +24,7 @@
 import android.os.Handler
 import android.util.Size
 import android.view.DisplayInfo
+import com.android.app.tracing.traceSection
 import com.android.internal.util.ArrayUtils
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.toDisplayRotation
@@ -130,12 +131,17 @@
                         override fun onDisplayAdded(displayId: Int) {}
 
                         override fun onDisplayChanged(displayId: Int) {
-                            val displayInfo = getDisplayInfo()
-                            trySendWithFailureLogging(
-                                displayInfo,
-                                TAG,
-                                "Error sending displayInfo to $displayInfo"
-                            )
+                            traceSection(
+                                "DisplayStateRepository" +
+                                    ".currentRotationDisplayListener#onDisplayChanged"
+                            ) {
+                                val displayInfo = getDisplayInfo()
+                                trySendWithFailureLogging(
+                                    displayInfo,
+                                    TAG,
+                                    "Error sending displayInfo to $displayInfo"
+                                )
+                            }
                         }
                     }
                 displayManager.registerDisplayListener(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
index b35fbbc..ad7bb0e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -55,6 +55,9 @@
     /** The kind of credential to use (biometric, pin, pattern, etc.). */
     val kind: StateFlow<PromptKind>
 
+    /** The package name that the prompt is called from. */
+    val opPackageName: StateFlow<String?>
+
     /**
      * If explicit confirmation is required.
      *
@@ -68,6 +71,7 @@
         userId: Int,
         gatekeeperChallenge: Long?,
         kind: PromptKind,
+        opPackageName: String,
     )
 
     /** Unset the prompt info. */
@@ -108,6 +112,9 @@
     private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.Biometric())
     override val kind = _kind.asStateFlow()
 
+    private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null)
+    override val opPackageName = _opPackageName.asStateFlow()
+
     private val _faceSettings =
         _userId.map { id -> faceSettings.forUser(id) }.distinctUntilChanged()
     private val _faceSettingAlwaysRequireConfirmation =
@@ -127,11 +134,13 @@
         userId: Int,
         gatekeeperChallenge: Long?,
         kind: PromptKind,
+        opPackageName: String,
     ) {
         _kind.value = kind
         _userId.value = userId
         _challenge.value = gatekeeperChallenge
         _promptInfo.value = promptInfo
+        _opPackageName.value = opPackageName
     }
 
     override fun unsetPrompt() {
@@ -139,6 +148,7 @@
         _userId.value = null
         _challenge.value = null
         _kind.value = PromptKind.Biometric()
+        _opPackageName.value = null
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
new file mode 100644
index 0000000..e6939f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import android.content.Context
+import android.hardware.biometrics.SensorLocationInternal
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.SensorLocation
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class FingerprintPropertyInteractor
+@Inject
+constructor(
+    @Application private val context: Context,
+    repository: FingerprintPropertyRepository,
+    configurationInteractor: ConfigurationInteractor,
+    displayStateInteractor: DisplayStateInteractor,
+) {
+    /**
+     * Devices with multiple physical displays use unique display ids to determine which sensor is
+     * on the active physical display. This value represents a unique physical display id.
+     */
+    private val uniqueDisplayId: Flow<String> =
+        displayStateInteractor.displayChanges
+            .map { context.display?.uniqueId }
+            .filterNotNull()
+            .distinctUntilChanged()
+
+    /**
+     * Sensor location for the:
+     * - current physical display
+     * - device's natural screen resolution
+     * - device's natural orientation
+     */
+    private val unscaledSensorLocation: Flow<SensorLocationInternal> =
+        combine(
+            repository.sensorLocations,
+            uniqueDisplayId,
+        ) { locations, displayId ->
+            // Devices without multiple physical displays do not use the display id as the key;
+            // instead, the key is an empty string.
+            locations.getOrDefault(
+                displayId,
+                locations.getOrDefault("", SensorLocationInternal.DEFAULT)
+            )
+        }
+
+    /**
+     * Sensor location for the:
+     * - current physical display
+     * - current screen resolution
+     * - device's natural orientation
+     */
+    val sensorLocation: Flow<SensorLocation> =
+        combine(
+            unscaledSensorLocation,
+            configurationInteractor.scaleForResolution,
+        ) { unscaledSensorLocation, scale ->
+            val sensorLocation =
+                SensorLocation(
+                    unscaledSensorLocation.sensorLocationX,
+                    unscaledSensorLocation.sensorLocationY,
+                    unscaledSensorLocation.sensorRadius,
+                )
+            sensorLocation.scale = scale
+            sensorLocation
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
index 863ba8d..70be0f2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
@@ -63,6 +63,9 @@
     /** Current display state, defined as [AuthenticateOptions.DisplayState] */
     val displayState: Flow<Int>
 
+    /** If touches on the fingerprint sensor should be ignored by the HAL. */
+    val isHardwareIgnoringTouches: Flow<Boolean>
+
     /**
      * Add a permanent context listener.
      *
@@ -79,12 +82,11 @@
     @Application private val applicationScope: CoroutineScope,
     private val foldProvider: FoldStateProvider,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) : LogContextInteractor {
 
     init {
-        applicationScope.launch {
-            foldProvider.start()
-        }
+        applicationScope.launch { foldProvider.start() }
     }
 
     override val displayState =
@@ -102,6 +104,9 @@
             }
         }
 
+    override val isHardwareIgnoringTouches: Flow<Boolean> =
+        udfpsOverlayInteractor.shouldHandleTouches.map { shouldHandle -> !shouldHandle }
+
     override val isAod =
         displayState.map { it == AuthenticateOptions.DISPLAY_STATE_AOD }.distinctUntilChanged()
 
@@ -159,6 +164,12 @@
                 .catch { t -> Log.w(TAG, "failed to notify new display state", t) }
                 .launchIn(this)
 
+            isHardwareIgnoringTouches
+                .distinctUntilChanged()
+                .onEach { state -> listener.onHardwareIgnoreTouchesChanged(state) }
+                .catch { t -> Log.w(TAG, "failed to notify new set ignore state", t) }
+                .launchIn(this)
+
             listener.asBinder().linkToDeath({ cancel() }, 0)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
index ac4b717..359e2e7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
@@ -115,12 +115,14 @@
         @Utils.CredentialType kind: Int,
         userId: Int,
         challenge: Long,
+        opPackageName: String,
     ) {
         biometricPromptRepository.setPrompt(
             promptInfo,
             userId,
             challenge,
-            kind.asBiometricPromptCredential()
+            kind.asBiometricPromptCredential(),
+            opPackageName,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index 65a2c0a..b3f9574 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -76,6 +76,7 @@
         userId: Int,
         challenge: Long,
         modalities: BiometricModalities,
+        opPackageName: String,
     )
 
     /** Use credential-based authentication instead of biometrics. */
@@ -84,6 +85,7 @@
         @Utils.CredentialType kind: Int,
         userId: Int,
         challenge: Long,
+        opPackageName: String,
     )
 
     /** Unset the current authentication request. */
@@ -104,9 +106,12 @@
             promptRepository.promptInfo,
             promptRepository.challenge,
             promptRepository.userId,
-            promptRepository.kind
-        ) { promptInfo, challenge, userId, kind ->
-            if (promptInfo == null || userId == null || challenge == null) {
+            promptRepository.kind,
+            promptRepository.opPackageName,
+        ) { promptInfo, challenge, userId, kind, opPackageName ->
+            if (
+                promptInfo == null || userId == null || challenge == null || opPackageName == null
+            ) {
                 return@combine null
             }
 
@@ -117,6 +122,7 @@
                         userInfo = BiometricUserInfo(userId = userId),
                         operationInfo = BiometricOperationInfo(gatekeeperChallenge = challenge),
                         modalities = kind.activeModalities,
+                        opPackageName = opPackageName,
                     )
                 else -> null
             }
@@ -152,13 +158,15 @@
         promptInfo: PromptInfo,
         userId: Int,
         challenge: Long,
-        modalities: BiometricModalities
+        modalities: BiometricModalities,
+        opPackageName: String,
     ) {
         promptRepository.setPrompt(
             promptInfo = promptInfo,
             userId = userId,
             gatekeeperChallenge = challenge,
             kind = PromptKind.Biometric(modalities),
+            opPackageName = opPackageName,
         )
     }
 
@@ -167,12 +175,14 @@
         @Utils.CredentialType kind: Int,
         userId: Int,
         challenge: Long,
+        opPackageName: String,
     ) {
         promptRepository.setPrompt(
             promptInfo = promptInfo,
             userId = userId,
             gatekeeperChallenge = challenge,
             kind = kind.asBiometricPromptCredential(),
+            opPackageName = opPackageName,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
index 348b54e..c3dc2d4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
@@ -26,20 +26,24 @@
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.isDefaultOrientation
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.log.SideFpsLogger
 import com.android.systemui.res.R
 import java.util.Optional
 import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 
+@ExperimentalCoroutinesApi
 @SysUISingleton
 class SideFpsSensorInteractor
 @Inject
@@ -49,6 +53,7 @@
     windowManager: WindowManager,
     displayStateInteractor: DisplayStateInteractor,
     fingerprintInteractiveToAuthProvider: Optional<FingerprintInteractiveToAuthProvider>,
+    biometricSettingsRepository: BiometricSettingsRepository,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val logger: SideFpsLogger,
 ) {
@@ -84,13 +89,24 @@
             .map { it ?: 0L }
             .onEach { logger.authDurationChanged(it) }
 
+    private val isSettingEnabled: Flow<Boolean> =
+        biometricSettingsRepository.isFingerprintEnrolledAndEnabled
+            .flatMapLatest { enabledAndEnrolled ->
+                if (!enabledAndEnrolled || fingerprintInteractiveToAuthProvider.isEmpty) {
+                    flowOf(false)
+                } else {
+                    fingerprintInteractiveToAuthProvider.get().enabledForCurrentUser
+                }
+            }
+            .onEach { logger.restToUnlockSettingEnabledChanged(it) }
+
     val isProlongedTouchRequiredForAuthentication: Flow<Boolean> =
-        if (fingerprintInteractiveToAuthProvider.isEmpty || !isProlongedTouchEnabledForDevice) {
+        if (!isProlongedTouchEnabledForDevice) {
             flowOf(false)
         } else {
             combine(
                 isAvailable,
-                fingerprintInteractiveToAuthProvider.get().enabledForCurrentUser
+                isSettingEnabled,
             ) { sfpsAvailable, isSettingEnabled ->
                 sfpsAvailable && isSettingEnabled
             }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index f4a2811..a77cc1f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.biometrics.domain.interactor
 
 import android.content.Context
+import android.util.Log
 import android.view.MotionEvent
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
@@ -30,8 +31,10 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
@@ -40,12 +43,23 @@
 class UdfpsOverlayInteractor
 @Inject
 constructor(
-    @Application context: Context,
+    @Application private val context: Context,
     private val authController: AuthController,
     private val selectedUserInteractor: SelectedUserInteractor,
     @Application scope: CoroutineScope
 ) {
-    private val iconSize: Int = context.resources.getDimensionPixelSize(R.dimen.udfps_icon_size)
+    private fun calculateIconSize(): Int {
+        val pixelPitch = context.resources.getFloat(R.dimen.pixel_pitch)
+        if (pixelPitch <= 0) {
+            Log.e(
+                "UdfpsOverlayInteractor",
+                "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device."
+            )
+        }
+        return (context.resources.getFloat(R.dimen.udfps_icon_size) / pixelPitch).toInt()
+    }
+
+    private var iconSize: Int = calculateIconSize()
 
     /** Whether a touch is within the under-display fingerprint sensor area */
     fun isTouchWithinUdfpsArea(ev: MotionEvent): Boolean {
@@ -56,6 +70,16 @@
         return isUdfpsEnrolled && isWithinOverlayBounds
     }
 
+    /** Sets whether Udfps overlay should handle touches */
+    fun setHandleTouches(shouldHandle: Boolean = true) {
+        _shouldHandleTouches.value = shouldHandle
+    }
+
+    private var _shouldHandleTouches = MutableStateFlow(true)
+
+    /** Whether Udfps overlay should handle touches */
+    val shouldHandleTouches: StateFlow<Boolean> = _shouldHandleTouches.asStateFlow()
+
     /** Returns the current udfpsOverlayParams */
     val udfpsOverlayParams: StateFlow<UdfpsOverlayParams> =
         ConflatedCallbackFlow.conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
index 8fbb250..c17c8dc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
@@ -1,5 +1,7 @@
 package com.android.systemui.biometrics.domain.model
 
+import android.graphics.Bitmap
+import android.hardware.biometrics.PromptContentView
 import android.hardware.biometrics.PromptInfo
 import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricUserInfo
@@ -25,6 +27,7 @@
         userInfo: BiometricUserInfo,
         operationInfo: BiometricOperationInfo,
         val modalities: BiometricModalities,
+        val opPackageName: String,
     ) :
         BiometricPromptRequest(
             title = info.title?.toString() ?: "",
@@ -34,6 +37,9 @@
             operationInfo = operationInfo,
             showEmergencyCallButton = info.isShowEmergencyCallButton
         ) {
+        val contentView: PromptContentView? = info.contentView
+        val logoRes: Int = info.logoRes
+        val logoBitmap: Bitmap? = info.logoBitmap
         val negativeButtonText: String = info.negativeButtonText?.toString() ?: ""
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt
new file mode 100644
index 0000000..dddadbd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.shared.model
+
+/** Provides current sensor location information in the current screen resolution [scale]. */
+data class SensorLocation(
+    private val naturalCenterX: Int,
+    private val naturalCenterY: Int,
+    private val naturalRadius: Int
+) {
+    /**
+     * Scale to apply to the sensor location's natural parameters to support different screen
+     * resolutions.
+     */
+    var scale: Float = 1f
+
+    val centerX: Float
+        get() {
+            return naturalCenterX * scale
+        }
+    val centerY: Float
+        get() {
+            return naturalCenterY * scale
+        }
+    val radius: Float
+        get() {
+            return naturalRadius * scale
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
index 0d72b9c..b450896 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
@@ -101,6 +101,7 @@
             final View child = getChildAt(i);
 
             if (child.getId() == R.id.space_above_icon
+                    || child.getId() == R.id.space_above_content
                     || child.getId() == R.id.space_below_icon
                     || child.getId() == R.id.button_bar) {
                 child.measure(
@@ -114,6 +115,12 @@
                                 MeasureSpec.EXACTLY),
                         MeasureSpec.makeMeasureSpec(iconView.getLayoutParams().height,
                                 MeasureSpec.EXACTLY));
+            } else if (child.getId() == R.id.logo) {
+                child.measure(
+                        MeasureSpec.makeMeasureSpec(child.getLayoutParams().width,
+                                MeasureSpec.EXACTLY),
+                        MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
+                                MeasureSpec.EXACTLY));
             } else if (child.getId() == R.id.biometric_icon) {
                 child.measure(
                         MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
new file mode 100644
index 0000000..96582cb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.ui.binder
+
+import android.content.Context
+import android.content.res.Resources
+import android.content.res.Resources.Theme
+import android.graphics.Paint
+import android.hardware.biometrics.PromptContentItem
+import android.hardware.biometrics.PromptContentItemBulletedText
+import android.hardware.biometrics.PromptContentItemPlainText
+import android.hardware.biometrics.PromptContentView
+import android.hardware.biometrics.PromptVerticalListContentView
+import android.text.SpannableString
+import android.text.Spanned
+import android.text.style.BulletSpan
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.LinearLayout
+import android.widget.ScrollView
+import android.widget.Space
+import android.widget.TextView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.biometrics.ui.BiometricPromptLayout
+import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import kotlin.math.ceil
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+
+/** Sub-binder for [BiometricPromptLayout.customized_view_container]. */
+object BiometricCustomizedViewBinder {
+    fun bind(customizedViewContainer: ScrollView, spaceAbove: Space, viewModel: PromptViewModel) {
+        customizedViewContainer.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.CREATED) {
+                launch {
+                    val contentView: PromptContentView? = viewModel.contentView.first()
+
+                    if (contentView != null) {
+                        val context = customizedViewContainer.context
+                        customizedViewContainer.addView(contentView.toView(context))
+                        customizedViewContainer.visibility = View.VISIBLE
+                        spaceAbove.visibility = View.VISIBLE
+                    } else {
+                        customizedViewContainer.visibility = View.GONE
+                        spaceAbove.visibility = View.GONE
+                    }
+                }
+            }
+        }
+    }
+}
+
+private fun PromptContentView.toView(context: Context): View {
+    val resources = context.resources
+    val inflater = LayoutInflater.from(context)
+    when (this) {
+        is PromptVerticalListContentView -> {
+            val contentView =
+                inflater.inflate(R.layout.biometric_prompt_content_layout, null) as LinearLayout
+
+            val descriptionView = contentView.requireViewById<TextView>(R.id.customized_view_title)
+            if (!description.isNullOrEmpty()) {
+                descriptionView.text = description
+            } else {
+                descriptionView.visibility = View.GONE
+            }
+
+            // Show two column by default, once there is an item exceeding max lines, show single
+            // item instead.
+            val showTwoColumn = listItems.all { !it.doesExceedMaxLinesIfTwoColumn(resources) }
+            var currRowView = createNewRowLayout(inflater)
+            for (item in listItems) {
+                val itemView = item.toView(resources, inflater, context.theme)
+                currRowView.addView(itemView)
+
+                if (!showTwoColumn || currRowView.childCount == 2) {
+                    contentView.addView(currRowView)
+                    currRowView = createNewRowLayout(inflater)
+                }
+            }
+            if (currRowView.childCount > 0) {
+                contentView.addView(currRowView)
+            }
+
+            return contentView
+        }
+        else -> {
+            throw IllegalStateException("No such PromptContentView: $this")
+        }
+    }
+}
+
+private fun createNewRowLayout(inflater: LayoutInflater): LinearLayout {
+    return inflater.inflate(R.layout.biometric_prompt_content_row_layout, null) as LinearLayout
+}
+
+private fun PromptContentItem.doesExceedMaxLinesIfTwoColumn(
+    resources: Resources,
+): Boolean {
+    val passedInText: String =
+        when (this) {
+            is PromptContentItemPlainText -> text
+            is PromptContentItemBulletedText -> text
+            else -> {
+                throw IllegalStateException("No such PromptContentItem: $this")
+            }
+        }
+
+    when (this) {
+        is PromptContentItemPlainText,
+        is PromptContentItemBulletedText -> {
+            val dialogMargin =
+                resources.getDimensionPixelSize(R.dimen.biometric_dialog_border_padding)
+            val halfDialogWidth =
+                Resources.getSystem().displayMetrics.widthPixels / 2 - dialogMargin
+            val containerPadding =
+                resources.getDimensionPixelSize(
+                    R.dimen.biometric_prompt_content_container_padding_horizontal
+                )
+            val contentPadding =
+                resources.getDimensionPixelSize(R.dimen.biometric_prompt_content_padding_horizontal)
+            val listItemPadding = getListItemPadding(resources)
+            val maxWidth = halfDialogWidth - containerPadding - contentPadding - listItemPadding
+
+            val text = "$passedInText"
+            val textSize =
+                resources.getDimensionPixelSize(
+                    R.dimen.biometric_prompt_content_list_item_text_size
+                )
+            val paint = Paint()
+            paint.textSize = textSize.toFloat()
+
+            val maxLines =
+                resources.getInteger(
+                    R.integer.biometric_prompt_content_list_item_max_lines_if_two_column
+                )
+            val numLines = ceil(paint.measureText(text).toDouble() / maxWidth).toInt()
+            return numLines > maxLines
+        }
+        else -> {
+            throw IllegalStateException("No such PromptContentItem: $this")
+        }
+    }
+}
+
+private fun PromptContentItem.toView(
+    resources: Resources,
+    inflater: LayoutInflater,
+    theme: Theme,
+): TextView {
+    val textView =
+        inflater.inflate(R.layout.biometric_prompt_content_row_item_text_view, null) as TextView
+    val lp = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1f)
+    textView.layoutParams = lp
+
+    when (this) {
+        is PromptContentItemPlainText -> {
+            textView.text = text
+        }
+        is PromptContentItemBulletedText -> {
+            val bulletedText = SpannableString(text)
+            val span =
+                BulletSpan(
+                    getListItemBulletGapWidth(resources),
+                    getListItemBulletColor(resources, theme),
+                    getListItemBulletRadius(resources)
+                )
+            bulletedText.setSpan(span, 0 /* start */, text.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+            textView.text = bulletedText
+        }
+        else -> {
+            throw IllegalStateException("No such PromptContentItem: $this")
+        }
+    }
+    return textView
+}
+
+private fun PromptContentItem.getListItemPadding(resources: Resources): Int {
+    var listItemPadding =
+        resources.getDimensionPixelSize(
+            R.dimen.biometric_prompt_content_list_item_padding_horizontal
+        ) * 2
+    when (this) {
+        is PromptContentItemPlainText -> {}
+        is PromptContentItemBulletedText -> {
+            listItemPadding +=
+                getListItemBulletRadius(resources) * 2 + getListItemBulletGapWidth(resources)
+        }
+        else -> {
+            throw IllegalStateException("No such PromptContentItem: $this")
+        }
+    }
+    return listItemPadding
+}
+
+private fun getListItemBulletRadius(resources: Resources): Int =
+    resources.getDimensionPixelSize(R.dimen.biometric_prompt_content_list_item_bullet_radius)
+
+private fun getListItemBulletGapWidth(resources: Resources): Int =
+    resources.getDimensionPixelSize(R.dimen.biometric_prompt_content_list_item_bullet_gap_width)
+
+private fun getListItemBulletColor(resources: Resources, theme: Theme): Int =
+    resources.getColor(R.color.biometric_prompt_content_list_item_bullet_color, theme)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 7b8cb82..285ab4a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -31,6 +31,8 @@
 import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
 import android.view.accessibility.AccessibilityManager
 import android.widget.Button
+import android.widget.ImageView
+import android.widget.ScrollView
 import android.widget.TextView
 import androidx.lifecycle.DefaultLifecycleObserver
 import androidx.lifecycle.Lifecycle
@@ -91,11 +93,16 @@
         val textColorHint =
             view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme)
 
+        val logoView = view.requireViewById<ImageView>(R.id.logo)
         val titleView = view.requireViewById<TextView>(R.id.title)
         val subtitleView = view.requireViewById<TextView>(R.id.subtitle)
         val descriptionView = view.requireViewById<TextView>(R.id.description)
+        val customizedViewContainer =
+            view.requireViewById<ScrollView>(R.id.customized_view_container)
 
         // set selected to enable marquee unless a screen reader is enabled
+        logoView.isSelected =
+            !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
         titleView.isSelected =
             !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
         subtitleView.isSelected =
@@ -149,9 +156,15 @@
                 }
             }
 
+            logoView.setImageDrawable(viewModel.logo.first())
             titleView.text = viewModel.title.first()
-            descriptionView.text = viewModel.description.first()
             subtitleView.text = viewModel.subtitle.first()
+            descriptionView.text = viewModel.description.first()
+            BiometricCustomizedViewBinder.bind(
+                customizedViewContainer,
+                view.requireViewById(R.id.space_above_content),
+                viewModel
+            )
 
             // set button listeners
             negativeButton.setOnClickListener { legacyCallback.onButtonNegative() }
@@ -175,15 +188,19 @@
                     viewModel = viewModel,
                     viewsToHideWhenSmall =
                         listOf(
+                            logoView,
                             titleView,
                             subtitleView,
                             descriptionView,
+                            customizedViewContainer,
                         ),
                     viewsToFadeInOnSizeChange =
                         listOf(
+                            logoView,
                             titleView,
                             subtitleView,
                             descriptionView,
+                            customizedViewContainer,
                             indicatorMessageView,
                             negativeButton,
                             cancelButton,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 1a7b6c9..d5695f3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -25,6 +25,7 @@
 import android.view.WindowInsets
 import android.view.WindowManager
 import android.view.accessibility.AccessibilityManager
+import android.widget.ImageView
 import android.widget.TextView
 import androidx.core.animation.addListener
 import androidx.core.view.doOnLayout
@@ -55,7 +56,7 @@
     fun bind(
         view: BiometricPromptLayout,
         viewModel: PromptViewModel,
-        viewsToHideWhenSmall: List<TextView>,
+        viewsToHideWhenSmall: List<View>,
         viewsToFadeInOnSizeChange: List<View>,
         panelViewController: AuthPanelController,
         jankListener: BiometricJankListener,
@@ -110,7 +111,7 @@
 
                         // prepare for animated size transitions
                         for (v in viewsToHideWhenSmall) {
-                            v.showTextOrHide(forceHide = size.isSmall)
+                            v.showContentOrHide(forceHide = size.isSmall)
                         }
                         if (currentSize == null && size.isSmall) {
                             iconHolderView.alpha = 0f
@@ -119,6 +120,10 @@
                             viewsToFadeInOnSizeChange.forEach { it.alpha = 0f }
                         }
 
+                        // TODO(b/302735104): Fix wrong height due to the delay of
+                        // PromptContentView. addOnLayoutChangeListener() will cause crash when
+                        // showing credential view, since |PromptIconViewModel| won't release the
+                        // flow.
                         // propagate size changes to legacy panel controller and animate transitions
                         view.doOnLayout {
                             val width = view.measuredWidth
@@ -228,8 +233,15 @@
     return r == Surface.ROTATION_90 || r == Surface.ROTATION_270
 }
 
-private fun TextView.showTextOrHide(forceHide: Boolean = false) {
-    visibility = if (forceHide || text.isBlank()) View.GONE else View.VISIBLE
+private fun View.showContentOrHide(forceHide: Boolean = false) {
+    val isTextViewWithBlankText = this is TextView && this.text.isBlank()
+    val isImageViewWithoutImage = this is ImageView && this.drawable == null
+    visibility =
+        if (forceHide || isTextViewWithBlankText || isImageViewWithoutImage) {
+            View.GONE
+        } else {
+            View.VISIBLE
+        }
 }
 
 private fun View.asVerticalAnimator(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index c36e0e2..80d37b4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -121,11 +121,13 @@
             if (it.isAttachedToWindow) {
                 lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
                 lottie?.pauseAnimation()
+                lottie?.removeAllLottieOnCompositionLoadedListener()
                 windowManager.get().removeView(it)
             }
         }
 
         overlayView = layoutInflater.get().inflate(R.layout.sidefps_view, null, false)
+
         val overlayViewModel =
             SideFpsOverlayViewModel(
                 applicationContext,
@@ -163,8 +165,10 @@
 
                 val lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
                 lottie.addLottieOnCompositionLoadedListener { composition: LottieComposition ->
-                    viewModel.setLottieBounds(composition.bounds)
-                    overlayView.visibility = View.VISIBLE
+                    if (overlayView.visibility != View.VISIBLE) {
+                        viewModel.setLottieBounds(composition.bounds)
+                        overlayView.visibility = View.VISIBLE
+                    }
                 }
                 it.alpha = 0f
                 val overlayShowAnimator =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index d899827e..0f1340a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -13,11 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.systemui.biometrics.ui.viewmodel
 
 import android.content.Context
 import android.graphics.Rect
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
 import android.hardware.biometrics.BiometricPrompt
+import android.hardware.biometrics.Flags.customBiometricPrompt
+import android.hardware.biometrics.PromptContentView
 import android.util.Log
 import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
@@ -231,6 +236,19 @@
             }
         }
 
+    /** Logo for the prompt. */
+    val logo: Flow<Drawable?> =
+        promptSelectorInteractor.prompt
+            .map {
+                when {
+                    !customBiometricPrompt() || it == null -> null
+                    it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme)
+                    it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
+                    else -> context.packageManager.getApplicationIcon(it.opPackageName)
+                }
+            }
+            .distinctUntilChanged()
+
     /** Title for the prompt. */
     val title: Flow<String> =
         promptSelectorInteractor.prompt.map { it?.title ?: "" }.distinctUntilChanged()
@@ -239,9 +257,22 @@
     val subtitle: Flow<String> =
         promptSelectorInteractor.prompt.map { it?.subtitle ?: "" }.distinctUntilChanged()
 
-    /** Description for the prompt. */
-    val description: Flow<String> =
+    /** Custom content view for the prompt. */
+    val contentView: Flow<PromptContentView?> =
+        promptSelectorInteractor.prompt
+            .map { if (customBiometricPrompt()) it?.contentView else null }
+            .distinctUntilChanged()
+
+    private val originalDescription =
         promptSelectorInteractor.prompt.map { it?.description ?: "" }.distinctUntilChanged()
+    /**
+     * Description for the prompt. Description view and contentView is mutually exclusive. Pass
+     * description down only when contentView is null.
+     */
+    val description: Flow<String> =
+        combine(contentView, originalDescription) { contentView, description ->
+            if (contentView == null) description else ""
+        }
 
     /** If the indicator (help, error) message should be shown. */
     val isIndicatorMessageVisible: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
index c2a1d8f..d0ff185 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
@@ -20,6 +20,7 @@
 import android.util.Log
 import com.android.systemui.biometrics.shared.SideFpsControllerRefactor
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
+import com.android.systemui.bouncer.shared.model.BouncerDismissActionModel
 import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -90,6 +91,9 @@
     val alternateBouncerUIAvailable: StateFlow<Boolean>
     val sideFpsShowing: StateFlow<Boolean>
 
+    /** Action that should be run right after the bouncer is dismissed. */
+    var bouncerDismissActionModel: BouncerDismissActionModel?
+
     var lastAlternateBouncerVisibleTime: Long
 
     fun setPrimaryScrimmed(isScrimmed: Boolean)
@@ -134,6 +138,8 @@
     @Application private val applicationScope: CoroutineScope,
     @BouncerTableLog private val buffer: TableLogBuffer,
 ) : KeyguardBouncerRepository {
+    override var bouncerDismissActionModel: BouncerDismissActionModel? = null
+
     /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
     private val _primaryBouncerShow = MutableStateFlow(false)
     override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 654fa22..8c87b0c 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -28,10 +28,12 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.DejankUtils
+import com.android.systemui.Flags
 import com.android.systemui.biometrics.shared.SideFpsControllerRefactor
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
+import com.android.systemui.bouncer.shared.model.BouncerDismissActionModel
 import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
 import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.classifier.FalsingCollector
@@ -154,12 +156,12 @@
     /** Show the bouncer if necessary and set the relevant states. */
     @JvmOverloads
     fun show(isScrimmed: Boolean) {
-        if (primaryBouncerView.delegate == null) {
+        if (primaryBouncerView.delegate == null && !Flags.composeBouncer()) {
             Log.d(
                 TAG,
                 "PrimaryBouncerInteractor#show is being called before the " +
-                    "primaryBouncerDelegate is set. Let's exit early so we don't set the wrong " +
-                    "primaryBouncer state."
+                    "primaryBouncerDelegate is set. Let's exit early so we don't " +
+                    "set the wrong primaryBouncer state."
             )
             return
         }
@@ -272,15 +274,24 @@
         repository.setShowMessage(BouncerShowMessageModel(message, colorStateList))
     }
 
+    val bouncerDismissAction: BouncerDismissActionModel?
+        get() = repository.bouncerDismissActionModel
+
     /**
      * Sets actions to the bouncer based on how the bouncer is dismissed. If the bouncer is
-     * unlocked, we will run the onDismissAction. If the bouncer is existed before unlocking, we
-     * call cancelAction.
+     * unlocked, we will run the onDismissAction. If the bouncer is exited before unlocking, we call
+     * cancelAction.
      */
     fun setDismissAction(
         onDismissAction: ActivityStarter.OnDismissAction?,
         cancelAction: Runnable?
     ) {
+        repository.bouncerDismissActionModel =
+            if (onDismissAction != null && cancelAction != null) {
+                BouncerDismissActionModel(onDismissAction, cancelAction)
+            } else {
+                null
+            }
         primaryBouncerView.delegate?.setDismissAction(onDismissAction, cancelAction)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerDismissActionModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerDismissActionModel.kt
new file mode 100644
index 0000000..02b444f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerDismissActionModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.shared.model
+
+import com.android.systemui.plugins.ActivityStarter
+
+/** Represents the action that needs to be performed after bouncer is dismissed. */
+data class BouncerDismissActionModel(
+    /** If the bouncer is unlocked, [onDismissAction] will be run. */
+    val onDismissAction: ActivityStarter.OnDismissAction?,
+    /** If the bouncer is exited before unlocking, [onCancel] will be invoked. */
+    val onCancel: Runnable?
+)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerDialogFactory.kt
new file mode 100644
index 0000000..5defe475
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerDialogFactory.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.ui
+
+import android.app.AlertDialog
+
+/** Factory to create alert dialogs for use in bouncer component. */
+interface BouncerDialogFactory {
+    operator fun invoke(): AlertDialog
+}
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 7f3b794..f3903de 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
@@ -16,9 +16,15 @@
 
 package com.android.systemui.bouncer.ui
 
+import android.app.AlertDialog
+import android.content.Context
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModelModule
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.phone.SystemUIDialog
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
 
 @Module(
     includes =
@@ -29,4 +35,17 @@
 interface BouncerViewModule {
     /** Binds BouncerView to BouncerViewImpl and makes it injectable. */
     @Binds fun bindBouncerView(bouncerViewImpl: BouncerViewImpl): BouncerView
+
+    companion object {
+
+        @Provides
+        @SysUISingleton
+        fun bouncerDialogFactory(@Application context: Context): BouncerDialogFactory {
+            return object : BouncerDialogFactory {
+                override fun invoke(): AlertDialog {
+                    return SystemUIDialog(context)
+                }
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
new file mode 100644
index 0000000..dd253a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
@@ -0,0 +1,89 @@
+package com.android.systemui.bouncer.ui.binder
+
+import android.view.ViewGroup
+import com.android.keyguard.KeyguardMessageAreaController
+import com.android.keyguard.ViewMediatorCallback
+import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.Flags
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.Flags.COMPOSE_BOUNCER_ENABLED
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
+import com.android.systemui.log.BouncerLogger
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import dagger.Lazy
+import javax.inject.Inject
+
+/** Helper data class that allows to lazy load all the dependencies of the legacy bouncer. */
+@SysUISingleton
+data class LegacyBouncerDependencies
+@Inject
+constructor(
+    val viewModel: KeyguardBouncerViewModel,
+    val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
+    val componentFactory: KeyguardBouncerComponent.Factory,
+    val messageAreaControllerFactory: KeyguardMessageAreaController.Factory,
+    val bouncerMessageInteractor: BouncerMessageInteractor,
+    val bouncerLogger: BouncerLogger,
+    val selectedUserInteractor: SelectedUserInteractor,
+)
+
+/** Helper data class that allows to lazy load all the dependencies of the compose based bouncer. */
+@SysUISingleton
+data class ComposeBouncerDependencies
+@Inject
+constructor(
+    val legacyInteractor: PrimaryBouncerInteractor,
+    val viewModel: BouncerViewModel,
+    val dialogFactory: BouncerDialogFactory,
+    val authenticationInteractor: AuthenticationInteractor,
+    val viewMediatorCallback: ViewMediatorCallback?,
+    val selectedUserInteractor: SelectedUserInteractor,
+)
+
+/**
+ * Toggles between the compose and non compose version of the bouncer, instantiating only the
+ * dependencies required for each.
+ */
+@SysUISingleton
+class BouncerViewBinder
+@Inject
+constructor(
+    private val legacyBouncerDependencies: Lazy<LegacyBouncerDependencies>,
+    private val composeBouncerDependencies: Lazy<ComposeBouncerDependencies>,
+) {
+    fun bind(view: ViewGroup) {
+        if (
+            ComposeFacade.isComposeAvailable() && Flags.composeBouncer() && COMPOSE_BOUNCER_ENABLED
+        ) {
+            val deps = composeBouncerDependencies.get()
+            ComposeBouncerViewBinder.bind(
+                view,
+                deps.legacyInteractor,
+                deps.viewModel,
+                deps.dialogFactory,
+                deps.authenticationInteractor,
+                deps.selectedUserInteractor,
+                deps.viewMediatorCallback,
+            )
+        } else {
+            val deps = legacyBouncerDependencies.get()
+            KeyguardBouncerViewBinder.bind(
+                view,
+                deps.viewModel,
+                deps.primaryBouncerToGoneTransitionViewModel,
+                deps.componentFactory,
+                deps.messageAreaControllerFactory,
+                deps.bouncerMessageInteractor,
+                deps.bouncerLogger,
+                deps.selectedUserInteractor,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
new file mode 100644
index 0000000..7b05395
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
@@ -0,0 +1,75 @@
+package com.android.systemui.bouncer.ui.binder
+
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.ViewMediatorCallback
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+/** View binder responsible for binding the compose version of the bouncer. */
+object ComposeBouncerViewBinder {
+    fun bind(
+        view: ViewGroup,
+        legacyInteractor: PrimaryBouncerInteractor,
+        viewModel: BouncerViewModel,
+        dialogFactory: BouncerDialogFactory,
+        authenticationInteractor: AuthenticationInteractor,
+        selectedUserInteractor: SelectedUserInteractor,
+        viewMediatorCallback: ViewMediatorCallback?,
+    ) {
+        view.addView(
+            ComposeFacade.createBouncer(
+                view.context,
+                viewModel,
+                dialogFactory,
+            )
+        )
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.CREATED) {
+                launch {
+                    legacyInteractor.isShowing.collectLatest { bouncerShowing ->
+                        view.isVisible = bouncerShowing
+                    }
+                }
+
+                launch {
+                    authenticationInteractor.onAuthenticationResult.collectLatest {
+                        authenticationSucceeded ->
+                        if (authenticationSucceeded) {
+                            // Some dismiss actions require that keyguard be dismissed right away or
+                            // deferred until something else later on dismisses keyguard (eg. end of
+                            // a hide animation).
+                            val deferKeyguardDone =
+                                legacyInteractor.bouncerDismissAction?.onDismissAction?.onDismiss()
+                            legacyInteractor.setDismissAction(null, null)
+
+                            viewMediatorCallback?.let {
+                                val selectedUserId = selectedUserInteractor.getSelectedUserId()
+                                if (deferKeyguardDone == true) {
+                                    it.keyguardDonePending(selectedUserId)
+                                } else {
+                                    it.keyguardDone(selectedUserId)
+                                }
+                            }
+                        }
+                    }
+                }
+                launch {
+                    legacyInteractor.startingDisappearAnimation.collectLatest {
+                        it.run()
+                        legacyInteractor.hide()
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 8e14778..5c9c997 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -26,6 +26,7 @@
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the password bouncer UI. */
 class PasswordBouncerViewModel(
@@ -48,9 +49,6 @@
 
     override val lockoutMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message
 
-    /** Whether the input method editor (for example, the software keyboard) is visible. */
-    private var isImeVisible: Boolean = false
-
     /** Whether the text field element currently has focus. */
     private val isTextFieldFocused = MutableStateFlow(false)
 
@@ -65,7 +63,6 @@
 
     override fun onHidden() {
         super.onHidden()
-        isImeVisible = false
         isTextFieldFocused.value = false
     }
 
@@ -97,16 +94,9 @@
         }
     }
 
-    /**
-     * Notifies that the input method editor (for example, the software keyboard) has been shown or
-     * hidden.
-     */
-    suspend fun onImeVisibilityChanged(isVisible: Boolean) {
-        if (isImeVisible && !isVisible && isInputEnabled.value) {
-            interactor.onImeHiddenByUser()
-        }
-
-        isImeVisible = isVisible
+    /** Notifies that the user has dismissed the software keyboard (IME). */
+    fun onImeDismissed() {
+        viewModelScope.launch { interactor.onImeHiddenByUser() }
     }
 
     /** Notifies that the password text field has gained or lost focus. */
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateLogger.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateLogger.kt
index adbb37c..4184ef7 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateLogger.kt
@@ -31,6 +31,7 @@
         is PackageChangeModel.UpdateStarted -> "started updating"
         is PackageChangeModel.UpdateFinished -> "finished updating"
         is PackageChangeModel.Changed -> "changed"
+        is PackageChangeModel.Empty -> throw IllegalStateException("Unexpected empty value: $model")
     }
 
 /** A debug logger for [PackageChangeRepository]. */
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/shared/model/PackageChangeModel.kt b/packages/SystemUI/src/com/android/systemui/common/data/shared/model/PackageChangeModel.kt
index 853eff7..3ae87c3 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/shared/model/PackageChangeModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/shared/model/PackageChangeModel.kt
@@ -23,6 +23,14 @@
     val packageName: String
     val packageUid: Int
 
+    /** Empty change, provided for convenience when a sensible default value is needed. */
+    data object Empty : PackageChangeModel {
+        override val packageName: String
+            get() = ""
+        override val packageUid: Int
+            get() = 0
+    }
+
     /**
      * An existing application package was uninstalled.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
index 1353985..5f6ff82 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
@@ -72,6 +72,9 @@
     val onAnyConfigurationChange: Flow<Unit> =
         repository.onAnyConfigurationChange.onStart { emit(Unit) }
 
+    /** Emits the new configuration on any configuration change */
+    val configurationValues: Flow<Configuration> = repository.configurationValues
+
     /** Emits the current resolution scaling factor */
     val scaleForResolution: Flow<Float> = repository.scaleForResolution
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
index 8d04e3d..ce798ba 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
@@ -26,3 +26,20 @@
     importantForAccessibility =
         if (value) View.IMPORTANT_FOR_ACCESSIBILITY_YES else View.IMPORTANT_FOR_ACCESSIBILITY_NO
 }
+
+/**
+ * Can be used to find the nearest parent of a view of a particular type.
+ *
+ * Usage:
+ * ```
+ * val textView = view.getNearestParent<TextView>()
+ * ```
+ */
+inline fun <reified T : View> View.getNearestParent(): T? {
+    var view: Any? = this
+    while (view is View) {
+        if (view is T) return view
+        view = view.parent
+    }
+    return null
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index 10768ea..dc07c1b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.communal.data.db.CommunalDatabaseModule
 import com.android.systemui.communal.data.repository.CommunalMediaRepositoryModule
+import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
@@ -34,6 +35,7 @@
             CommunalTutorialRepositoryModule::class,
             CommunalWidgetRepositoryModule::class,
             CommunalDatabaseModule::class,
+            CommunalPrefsRepositoryModule::class,
         ]
 )
 interface CommunalModule {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
new file mode 100644
index 0000000..c2ea2e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.repository
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.content.pm.UserInfo
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserFileManagerExt.observeSharedPreferences
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Stores simple preferences for the current user in communal hub. For use cases like "has the CTA
+ * tile been dismissed?"
+ */
+interface CommunalPrefsRepository {
+
+    /** Whether the CTA tile has been dismissed. */
+    val isCtaDismissed: Flow<Boolean>
+
+    /** Save the CTA tile dismissed state for the current user. */
+    suspend fun setCtaDismissedForCurrentUser()
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class CommunalPrefsRepositoryImpl
+@Inject
+constructor(
+    @Background private val backgroundScope: CoroutineScope,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    private val userRepository: UserRepository,
+    private val userFileManager: UserFileManager,
+) : CommunalPrefsRepository {
+
+    override val isCtaDismissed: Flow<Boolean> =
+        userRepository.selectedUserInfo
+            .flatMapLatest(::observeCtaDismissState)
+            .stateIn(
+                scope = backgroundScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    override suspend fun setCtaDismissedForCurrentUser() =
+        withContext(bgDispatcher) {
+            getSharedPrefsForUser(userRepository.getSelectedUserInfo())
+                .edit()
+                .putBoolean(CTA_DISMISSED_STATE, true)
+                .apply()
+        }
+
+    private fun observeCtaDismissState(user: UserInfo): Flow<Boolean> =
+        userFileManager
+            .observeSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, user.id)
+            // Emit at the start of collection to ensure we get an initial value
+            .onStart { emit(Unit) }
+            .map { getCtaDismissedState() }
+            .flowOn(bgDispatcher)
+
+    private suspend fun getCtaDismissedState(): Boolean =
+        withContext(bgDispatcher) {
+            getSharedPrefsForUser(userRepository.getSelectedUserInfo())
+                .getBoolean(CTA_DISMISSED_STATE, false)
+        }
+
+    private fun getSharedPrefsForUser(user: UserInfo): SharedPreferences {
+        return userFileManager.getSharedPreferences(
+            FILE_NAME,
+            Context.MODE_PRIVATE,
+            user.id,
+        )
+    }
+
+    companion object {
+        const val TAG = "CommunalRepository"
+        const val FILE_NAME = "communal_hub_prefs"
+        const val CTA_DISMISSED_STATE = "cta_dismissed"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.kt
new file mode 100644
index 0000000..a4ff6d3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.communal.data.repository
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface CommunalPrefsRepositoryModule {
+    @Binds fun communalPrefsRepository(impl: CommunalPrefsRepositoryImpl): CommunalPrefsRepository
+}
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 553b3eb..addd880 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
@@ -20,13 +20,18 @@
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.scene.data.repository.SceneContainerRepository
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -34,16 +39,26 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
 
 /** Encapsulates the state of communal mode. */
 interface CommunalRepository {
     /** Whether communal features are enabled. */
     val isCommunalEnabled: Boolean
 
+    /**
+     * A {@link StateFlow} that tracks whether communal hub is enabled (it can be disabled in
+     * settings).
+     */
+    val communalEnabledState: StateFlow<Boolean>
+
     /** Whether the communal hub is showing. */
     val isCommunalHubShowing: Flow<Boolean>
 
@@ -56,9 +71,6 @@
     /** Exposes the transition state of the communal [SceneTransitionLayout]. */
     val transitionState: StateFlow<ObservableCommunalTransitionState>
 
-    /** Whether the CTA tile is visible in the hub under view mode. */
-    val isCtaTileInViewModeVisible: Flow<Boolean>
-
     /** Updates the requested scene. */
     fun setDesiredScene(desiredScene: CommunalSceneKey)
 
@@ -68,9 +80,6 @@
      * Note that you must call is with `null` when the UI is done or risk a memory leak.
      */
     fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?)
-
-    /** Updates whether to display the CTA tile in the hub under view mode. */
-    fun setCtaTileInViewModeVisibility(isVisible: Boolean)
 }
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -78,13 +87,36 @@
 class CommunalRepositoryImpl
 @Inject
 constructor(
+    @Application private val applicationScope: CoroutineScope,
     @Background backgroundScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val featureFlagsClassic: FeatureFlagsClassic,
     sceneContainerFlags: SceneContainerFlags,
     sceneContainerRepository: SceneContainerRepository,
+    userRepository: UserRepository,
+    private val secureSettings: SecureSettings
 ) : CommunalRepository {
+
+    private val communalEnabledSettingState: Flow<Boolean> =
+        userRepository.selectedUserInfo
+            .flatMapLatest { userInfo -> observeSettings(userInfo.id) }
+            .shareIn(scope = applicationScope, started = SharingStarted.WhileSubscribed())
+
+    override val communalEnabledState: StateFlow<Boolean> =
+        if (featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()) {
+            communalEnabledSettingState
+                .filterNotNull()
+                .stateIn(
+                    scope = applicationScope,
+                    started = SharingStarted.Eagerly,
+                    initialValue = true
+                )
+        } else {
+            MutableStateFlow(false)
+        }
+
     override val isCommunalEnabled: Boolean
-        get() = featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()
+        get() = communalEnabledState.value
 
     private val _desiredScene: MutableStateFlow<CommunalSceneKey> =
         MutableStateFlow(CommunalSceneKey.DEFAULT)
@@ -102,16 +134,6 @@
                 initialValue = defaultTransitionState,
             )
 
-    // TODO(b/313462210) - persist the value in local storage, so the tile won't show up again
-    //  once dismissed.
-    private val _isCtaTileInViewModeVisible: MutableStateFlow<Boolean> = MutableStateFlow(true)
-    override val isCtaTileInViewModeVisible: Flow<Boolean> =
-        _isCtaTileInViewModeVisible.asStateFlow()
-
-    override fun setCtaTileInViewModeVisibility(isVisible: Boolean) {
-        _isCtaTileInViewModeVisible.value = isVisible
-    }
-
     override fun setDesiredScene(desiredScene: CommunalSceneKey) {
         _desiredScene.value = desiredScene
     }
@@ -131,4 +153,26 @@
         } else {
             desiredScene.map { sceneKey -> sceneKey == CommunalSceneKey.Communal }
         }
+
+    private fun observeSettings(userId: Int): Flow<Boolean> =
+        secureSettings
+            .observerFlow(
+                userId = userId,
+                names =
+                    arrayOf(
+                        GLANCEABLE_HUB_ENABLED,
+                    )
+            )
+            // Force an update
+            .onStart { emit(Unit) }
+            .map { readFromSettings(userId) }
+
+    private suspend fun readFromSettings(userId: Int): Boolean =
+        withContext(backgroundDispatcher) {
+            secureSettings.getIntForUser(GLANCEABLE_HUB_ENABLED, 1, userId) == 1
+        }
+
+    companion object {
+        private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
index 9a9b0e2..046aaaa 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.communal.data.repository
 
 import android.provider.Settings
+import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
 import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
 import android.provider.Settings.Secure.HubModeTutorialState
 import com.android.systemui.dagger.SysUISingleton
@@ -24,7 +25,6 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
-import com.android.systemui.settings.UserTracker
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -35,6 +35,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
@@ -62,14 +63,19 @@
 constructor(
     @Application private val applicationScope: CoroutineScope,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
-    userRepository: UserRepository,
+    private val userRepository: UserRepository,
     private val secureSettings: SecureSettings,
-    private val userTracker: UserTracker,
     @CommunalLog logBuffer: LogBuffer,
 ) : CommunalTutorialRepository {
 
     companion object {
         private const val TAG = "CommunalTutorialRepository"
+
+        const val MIN_TUTORIAL_VERSION = HUB_MODE_TUTORIAL_COMPLETED
+
+        // A version number which ensures that users, regardless of their completion of previous
+        // versions, see the updated tutorial when this number is bumped.
+        const val CURRENT_TUTORIAL_VERSION = MIN_TUTORIAL_VERSION + 1
     }
 
     private data class SettingsState(
@@ -80,7 +86,7 @@
 
     private val settingsState: Flow<SettingsState> =
         userRepository.selectedUserInfo
-            .flatMapLatest { observeSettings() }
+            .flatMapLatest { userInfo -> observeSettings(userInfo.id) }
             .shareIn(scope = applicationScope, started = SharingStarted.WhileSubscribed())
 
     /** Emits the state of tutorial state in settings */
@@ -91,31 +97,37 @@
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = HUB_MODE_TUTORIAL_NOT_STARTED
+                initialValue = HUB_MODE_TUTORIAL_NOT_STARTED,
             )
 
-    private fun observeSettings(): Flow<SettingsState> =
+    private fun observeSettings(userId: Int): Flow<SettingsState> =
         secureSettings
             .observerFlow(
-                userId = userTracker.userId,
-                names =
-                    arrayOf(
-                        Settings.Secure.HUB_MODE_TUTORIAL_STATE,
-                    )
+                userId = userId,
+                names = arrayOf(Settings.Secure.HUB_MODE_TUTORIAL_STATE),
             )
             // Force an update
             .onStart { emit(Unit) }
-            .map { readFromSettings() }
+            .map { readFromSettings(userId) }
 
-    private suspend fun readFromSettings(): SettingsState =
+    private suspend fun readFromSettings(userId: Int): SettingsState =
         withContext(backgroundDispatcher) {
-            val userId = userTracker.userId
-            val hubModeTutorialState =
+            var hubModeTutorialState =
                 secureSettings.getIntForUser(
                     Settings.Secure.HUB_MODE_TUTORIAL_STATE,
                     HUB_MODE_TUTORIAL_NOT_STARTED,
                     userId,
                 )
+
+            if (hubModeTutorialState >= CURRENT_TUTORIAL_VERSION) {
+                // Tutorial is considered "completed" if the user has completed the current or a
+                // newer version.
+                hubModeTutorialState = HUB_MODE_TUTORIAL_COMPLETED
+            } else if (hubModeTutorialState >= MIN_TUTORIAL_VERSION) {
+                // Tutorial is considered "not started" if the user completed a version older than
+                // the current.
+                hubModeTutorialState = HUB_MODE_TUTORIAL_NOT_STARTED
+            }
             val settingsState = SettingsState(hubModeTutorialState)
             logger.d({ "Communal tutorial state for user $int1 in settings: $str1" }) {
                 int1 = userId
@@ -127,18 +139,40 @@
 
     override suspend fun setTutorialState(state: Int): Unit =
         withContext(backgroundDispatcher) {
-            val userId = userTracker.userId
+            val userId = userRepository.getSelectedUserInfo().id
             if (tutorialSettingState.value == state) {
                 return@withContext
             }
+            val newState =
+                if (state == HUB_MODE_TUTORIAL_COMPLETED) CURRENT_TUTORIAL_VERSION else state
             logger.d({ "Update communal tutorial state to $int1 for user $int2" }) {
-                int1 = state
+                int1 = newState
                 int2 = userId
             }
             secureSettings.putIntForUser(
                 Settings.Secure.HUB_MODE_TUTORIAL_STATE,
-                state,
+                newState,
                 userId,
             )
         }
 }
+
+// TODO(b/320769333): delete me and use the real repo above when tutorial is ready.
+@SysUISingleton
+class CommunalTutorialDisabledRepositoryImpl
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+) : CommunalTutorialRepository {
+    override val tutorialSettingState: StateFlow<Int> =
+        emptyFlow<Int>()
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = HUB_MODE_TUTORIAL_COMPLETED,
+            )
+
+    override suspend fun setTutorialState(state: Int) {
+        // Do nothing
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt
index 69b0a27..b4f838a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt
@@ -22,6 +22,9 @@
 
 @Module
 interface CommunalTutorialRepositoryModule {
+    // TODO(b/320769333): use [CommunalTutorialRepositoryImpl] when tutorial is ready.
     @Binds
-    fun communalTutorialRepository(impl: CommunalTutorialRepositoryImpl): CommunalTutorialRepository
+    fun communalTutorialRepository(
+        impl: CommunalTutorialDisabledRepositoryImpl
+    ): CommunalTutorialRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index e6816e9..f36547b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -16,43 +16,31 @@
 
 package com.android.systemui.communal.data.repository
 
-import android.appwidget.AppWidgetHost
 import android.appwidget.AppWidgetManager
 import android.content.ComponentName
-import android.content.Intent
-import android.content.IntentFilter
-import android.os.UserManager
 import androidx.annotation.WorkerThread
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.communal.data.db.CommunalItemRank
 import com.android.systemui.communal.data.db.CommunalWidgetDao
 import com.android.systemui.communal.data.db.CommunalWidgetItem
 import com.android.systemui.communal.shared.CommunalWidgetHost
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
+import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
-import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.kotlin.getValue
 import java.util.Optional
 import javax.inject.Inject
 import kotlin.coroutines.cancellation.CancellationException
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.emptyFlow
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
 
 /** Encapsulates the state of widgets for communal mode. */
 interface CommunalWidgetRepository {
@@ -63,7 +51,7 @@
     fun addWidget(
         provider: ComponentName,
         priority: Int,
-        configureWidget: suspend (id: Int) -> Boolean
+        configurator: WidgetConfigurator? = null
     ) {}
 
     /** Delete a widget by id from app widget service and the database. */
@@ -77,21 +65,16 @@
     fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {}
 }
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class CommunalWidgetRepositoryImpl
 @Inject
 constructor(
-    private val appWidgetManager: Optional<AppWidgetManager>,
-    private val appWidgetHost: AppWidgetHost,
-    @Application private val applicationScope: CoroutineScope,
+    appWidgetManagerOptional: Optional<AppWidgetManager>,
+    private val appWidgetHost: CommunalAppWidgetHost,
+    @Background private val bgScope: CoroutineScope,
     @Background private val bgDispatcher: CoroutineDispatcher,
-    broadcastDispatcher: BroadcastDispatcher,
-    communalRepository: CommunalRepository,
     private val communalWidgetHost: CommunalWidgetHost,
     private val communalWidgetDao: CommunalWidgetDao,
-    private val userManager: UserManager,
-    private val userTracker: UserTracker,
     @CommunalLog logBuffer: LogBuffer,
 ) : CommunalWidgetRepository {
     companion object {
@@ -100,98 +83,68 @@
 
     private val logger = Logger(logBuffer, TAG)
 
-    // Whether the [AppWidgetHost] is listening for updates.
-    private var isHostListening = false
-
-    private suspend fun isUserUnlockingOrUnlocked(): Boolean =
-        withContext(bgDispatcher) { userManager.isUserUnlockingOrUnlocked(userTracker.userHandle) }
-
-    private val isUserUnlocked: Flow<Boolean> =
-        flowOf(communalRepository.isCommunalEnabled)
-            .flatMapLatest { enabled ->
-                if (enabled) {
-                    broadcastDispatcher
-                        .broadcastFlow(
-                            filter = IntentFilter(Intent.ACTION_USER_UNLOCKED),
-                            user = userTracker.userHandle
-                        )
-                        .onStart { emit(Unit) }
-                        .mapLatest { isUserUnlockingOrUnlocked() }
-                } else {
-                    emptyFlow()
-                }
-            }
-            .distinctUntilChanged()
-
-    private val isHostActive: Flow<Boolean> =
-        isUserUnlocked.map {
-            if (it) {
-                startListening()
-                true
-            } else {
-                stopListening()
-                false
-            }
-        }
+    private val appWidgetManager by appWidgetManagerOptional
 
     override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
-        isHostActive.flatMapLatest { isHostActive ->
-            if (!isHostActive || !appWidgetManager.isPresent) {
-                return@flatMapLatest flowOf(emptyList())
-            }
-            communalWidgetDao
-                .getWidgets()
-                .map { it.map(::mapToContentModel) }
-                // As this reads from a database and triggers IPCs to AppWidgetManager,
-                // it should be executed in the background.
-                .flowOn(bgDispatcher)
-        }
+        communalWidgetDao
+            .getWidgets()
+            .map { it.mapNotNull(::mapToContentModel) }
+            // As this reads from a database and triggers IPCs to AppWidgetManager,
+            // it should be executed in the background.
+            .flowOn(bgDispatcher)
 
     override fun addWidget(
         provider: ComponentName,
         priority: Int,
-        configureWidget: suspend (id: Int) -> Boolean
+        configurator: WidgetConfigurator?
     ) {
-        applicationScope.launch(bgDispatcher) {
+        bgScope.launch {
             val id = communalWidgetHost.allocateIdAndBindWidget(provider)
-            if (id != null) {
-                val configured =
-                    if (communalWidgetHost.requiresConfiguration(id)) {
-                        logger.i("Widget ${provider.flattenToString()} requires configuration.")
-                        try {
-                            configureWidget.invoke(id)
-                        } catch (ex: Exception) {
-                            // Cleanup the app widget id if an error happens during configuration.
-                            logger.e("Error during widget configuration, cleaning up id $id", ex)
-                            if (ex is CancellationException) {
-                                appWidgetHost.deleteAppWidgetId(id)
-                                // Re-throw cancellation to ensure the parent coroutine also gets
-                                // cancelled.
-                                throw ex
-                            } else {
-                                false
-                            }
+            if (id == null) {
+                logger.e("Failed to allocate widget id to ${provider.flattenToString()}")
+                return@launch
+            }
+            val info = communalWidgetHost.getAppWidgetInfo(id)
+            val configured =
+                if (
+                    configurator != null &&
+                        info != null &&
+                        CommunalWidgetHost.requiresConfiguration(info)
+                ) {
+                    logger.i("Widget ${provider.flattenToString()} requires configuration.")
+                    try {
+                        configurator.configureWidget(id)
+                    } catch (ex: Exception) {
+                        // Cleanup the app widget id if an error happens during configuration.
+                        logger.e("Error during widget configuration, cleaning up id $id", ex)
+                        if (ex is CancellationException) {
+                            appWidgetHost.deleteAppWidgetId(id)
+                            // Re-throw cancellation to ensure the parent coroutine also gets
+                            // cancelled.
+                            throw ex
+                        } else {
+                            false
                         }
-                    } else {
-                        logger.i("Skipping configuration for ${provider.flattenToString()}")
-                        true
                     }
-                if (configured) {
-                    communalWidgetDao.addWidget(
-                        widgetId = id,
-                        provider = provider,
-                        priority = priority,
-                    )
                 } else {
-                    appWidgetHost.deleteAppWidgetId(id)
+                    logger.i("Skipping configuration for ${provider.flattenToString()}")
+                    true
                 }
+            if (configured) {
+                communalWidgetDao.addWidget(
+                    widgetId = id,
+                    provider = provider,
+                    priority = priority,
+                )
+            } else {
+                appWidgetHost.deleteAppWidgetId(id)
             }
             logger.i("Added widget ${provider.flattenToString()} at position $priority.")
         }
     }
 
     override fun deleteWidget(widgetId: Int) {
-        applicationScope.launch(bgDispatcher) {
+        bgScope.launch {
             communalWidgetDao.deleteWidgetById(widgetId)
             appWidgetHost.deleteAppWidgetId(widgetId)
             logger.i("Deleted widget with id $widgetId.")
@@ -199,7 +152,7 @@
     }
 
     override fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
-        applicationScope.launch(bgDispatcher) {
+        bgScope.launch {
             communalWidgetDao.updateWidgetOrder(widgetIdToPriorityMap)
             logger.i({ "Updated the order of widget list with ids: $str1." }) {
                 str1 = widgetIdToPriorityMap.toString()
@@ -210,30 +163,13 @@
     @WorkerThread
     private fun mapToContentModel(
         entry: Map.Entry<CommunalItemRank, CommunalWidgetItem>
-    ): CommunalWidgetContentModel {
+    ): CommunalWidgetContentModel? {
         val (_, widgetId) = entry.value
+        val providerInfo = appWidgetManager?.getAppWidgetInfo(widgetId) ?: return null
         return CommunalWidgetContentModel(
             appWidgetId = widgetId,
-            providerInfo = appWidgetManager.get().getAppWidgetInfo(widgetId),
+            providerInfo = providerInfo,
             priority = entry.key.rank,
         )
     }
-
-    private fun startListening() {
-        if (isHostListening) {
-            return
-        }
-
-        appWidgetHost.startListening()
-        isHostListening = true
-    }
-
-    private fun stopListening() {
-        if (!isHostListening) {
-            return
-        }
-
-        appWidgetHost.stopListening()
-        isHostListening = false
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
index d0d9e3f..ab0a2d0d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
@@ -17,11 +17,13 @@
 
 package com.android.systemui.communal.data.repository
 
-import android.appwidget.AppWidgetHost
 import android.appwidget.AppWidgetManager
 import android.content.Context
 import android.content.res.Resources
+import android.os.Looper
 import com.android.systemui.communal.shared.CommunalWidgetHost
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
+import com.android.systemui.communal.widgets.WidgetInteractionHandler
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
@@ -48,15 +50,19 @@
 
         @SysUISingleton
         @Provides
-        fun provideAppWidgetHost(@Application context: Context): AppWidgetHost {
-            return AppWidgetHost(context, APP_WIDGET_HOST_ID)
+        fun provideCommunalAppWidgetHost(
+            @Application context: Context,
+            interactionHandler: WidgetInteractionHandler,
+            @Main looper: Looper,
+        ): CommunalAppWidgetHost {
+            return CommunalAppWidgetHost(context, APP_WIDGET_HOST_ID, interactionHandler, looper)
         }
 
         @SysUISingleton
         @Provides
         fun provideCommunalWidgetHost(
             appWidgetManager: Optional<AppWidgetManager>,
-            appWidgetHost: AppWidgetHost,
+            appWidgetHost: CommunalAppWidgetHost,
             @CommunalLog logBuffer: LogBuffer,
         ): CommunalWidgetHost {
             return CommunalWidgetHost(appWidgetManager, appWidgetHost, logBuffer)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 24d4c6c..80fee64 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -17,9 +17,9 @@
 package com.android.systemui.communal.domain.interactor
 
 import android.app.smartspace.SmartspaceTarget
-import android.appwidget.AppWidgetHost
 import android.content.ComponentName
 import com.android.systemui.communal.data.repository.CommunalMediaRepository
+import com.android.systemui.communal.data.repository.CommunalPrefsRepository
 import com.android.systemui.communal.data.repository.CommunalRepository
 import com.android.systemui.communal.data.repository.CommunalWidgetRepository
 import com.android.systemui.communal.domain.model.CommunalContentModel
@@ -29,35 +29,63 @@
 import com.android.systemui.communal.shared.model.CommunalContentSize.THIRD
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
+import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.smartspace.data.repository.SmartspaceRepository
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.kotlin.BooleanFlowOperators.and
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
+import com.android.systemui.util.kotlin.BooleanFlowOperators.or
 import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 
 /** Encapsulates business-logic related to communal mode. */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class CommunalInteractor
 @Inject
 constructor(
     private val communalRepository: CommunalRepository,
     private val widgetRepository: CommunalWidgetRepository,
+    private val communalPrefsRepository: CommunalPrefsRepository,
     mediaRepository: CommunalMediaRepository,
     smartspaceRepository: SmartspaceRepository,
+    userRepository: UserRepository,
     keyguardInteractor: KeyguardInteractor,
-    private val appWidgetHost: AppWidgetHost,
+    private val appWidgetHost: CommunalAppWidgetHost,
     private val editWidgetsActivityStarter: EditWidgetsActivityStarter
 ) {
+    private val _editModeOpen = MutableStateFlow(false)
+
+    /** Whether edit mode is currently open. */
+    val editModeOpen: StateFlow<Boolean> = _editModeOpen.asStateFlow()
 
     /** Whether communal features are enabled. */
     val isCommunalEnabled: Boolean
         get() = communalRepository.isCommunalEnabled
 
+    /** Whether communal features are enabled and available. */
+    val isCommunalAvailable: Flow<Boolean> =
+        and(
+                communalRepository.communalEnabledState,
+                userRepository.selectedUserInfo.map { it.isMain },
+                not(keyguardInteractor.isEncryptedOrLockdown),
+                or(keyguardInteractor.isKeyguardVisible, keyguardInteractor.isDreaming)
+            )
+            .distinctUntilChanged()
+
     /**
      * Target scene as requested by the underlying [SceneTransitionLayout] or through
      * [onSceneChanged].
@@ -77,6 +105,29 @@
         communalRepository.setTransitionState(transitionState)
     }
 
+    /** Returns a flow that tracks the progress of transitions to the given scene from 0-1. */
+    fun transitionProgressToScene(targetScene: CommunalSceneKey) =
+        transitionState
+            .flatMapLatest { state ->
+                when (state) {
+                    is ObservableCommunalTransitionState.Idle ->
+                        flowOf(CommunalTransitionProgress.Idle(state.scene))
+                    is ObservableCommunalTransitionState.Transition ->
+                        if (state.toScene == targetScene) {
+                            state.progress.map {
+                                CommunalTransitionProgress.Transition(
+                                    // Clamp the progress values between 0 and 1 as actual progress
+                                    // values can be higher than 0 or lower than 1 due to a fling.
+                                    progress = it.coerceIn(0.0f, 1.0f)
+                                )
+                            }
+                        } else {
+                            flowOf(CommunalTransitionProgress.OtherTransition)
+                        }
+                }
+            }
+            .distinctUntilChanged()
+
     /**
      * Flow that emits a boolean if the communal UI is showing, ie. the [desiredScene] is the
      * [CommunalSceneKey.Communal].
@@ -84,32 +135,40 @@
     val isCommunalShowing: Flow<Boolean> =
         communalRepository.desiredScene.map { it == CommunalSceneKey.Communal }
 
-    val isKeyguardVisible: Flow<Boolean> = keyguardInteractor.isKeyguardVisible
+    /**
+     * Flow that emits a boolean if the communal UI is fully visible and not in transition.
+     *
+     * This will not be true while transitioning to the hub and will turn false immediately when a
+     * swipe to exit the hub starts.
+     */
+    val isIdleOnCommunal: Flow<Boolean> =
+        communalRepository.transitionState.map {
+            it is ObservableCommunalTransitionState.Idle && it.scene == CommunalSceneKey.Communal
+        }
 
     /** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */
     fun onSceneChanged(newScene: CommunalSceneKey) {
         communalRepository.setDesiredScene(newScene)
     }
 
+    fun setEditModeOpen(isOpen: Boolean) {
+        _editModeOpen.value = isOpen
+    }
+
     /** Show the widget editor Activity. */
-    fun showWidgetEditor() {
-        editWidgetsActivityStarter.startActivity()
+    fun showWidgetEditor(preselectedKey: String? = null) {
+        editWidgetsActivityStarter.startActivity(preselectedKey)
     }
 
     /** Dismiss the CTA tile from the hub in view mode. */
-    fun dismissCtaTile() = communalRepository.setCtaTileInViewModeVisibility(isVisible = false)
+    suspend fun dismissCtaTile() = communalPrefsRepository.setCtaDismissedForCurrentUser()
 
-    /**
-     * Add a widget at the specified position.
-     *
-     * @param configureWidget The callback to trigger if widget configuration is needed. Should
-     *   return whether configuration was successful.
-     */
+    /** Add a widget at the specified position. */
     fun addWidget(
         componentName: ComponentName,
         priority: Int,
-        configureWidget: suspend (id: Int) -> Boolean
-    ) = widgetRepository.addWidget(componentName, priority, configureWidget)
+        configurator: WidgetConfigurator?,
+    ) = widgetRepository.addWidget(componentName, priority, configurator)
 
     /** Delete a widget by id. */
     fun deleteWidget(id: Int) = widgetRepository.deleteWidget(id)
@@ -149,8 +208,8 @@
 
     /** CTA tile to be displayed in the glanceable hub (view mode). */
     val ctaTileContent: Flow<List<CommunalContentModel.CtaTileInViewMode>> =
-        communalRepository.isCtaTileInViewModeVisible.map { visible ->
-            if (visible) listOf(CommunalContentModel.CtaTileInViewMode()) else emptyList()
+        communalPrefsRepository.isCtaDismissed.map { isDismissed ->
+            if (isDismissed) emptyList() else listOf(CommunalContentModel.CtaTileInViewMode())
         }
 
     /** A list of tutorial content to be displayed in the communal hub in tutorial mode. */
@@ -207,6 +266,12 @@
 
     companion object {
         /**
+         * The user activity timeout which should be used when the communal hub is opened. A value
+         * of -1 means that the user's chosen screen timeout will be used instead.
+         */
+        const val AWAKE_INTERVAL_MS = -1
+
+        /**
          * Calculates the content size dynamically based on the total number of contents of that
          * type.
          *
@@ -232,3 +297,17 @@
         }
     }
 }
+
+/** Simplified transition progress data class for tracking a single transition between scenes. */
+sealed class CommunalTransitionProgress {
+    /** No transition/animation is currently running. */
+    data class Idle(val scene: CommunalSceneKey) : CommunalTransitionProgress()
+
+    /** There is a transition animating to the expected scene. */
+    data class Transition(
+        val progress: Float,
+    ) : CommunalTransitionProgress()
+
+    /** There is a transition animating to a scene other than the expected scene. */
+    data object OtherTransition : CommunalTransitionProgress()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
index 5ca89f2..309c84e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
@@ -27,12 +27,15 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 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.filterNotNull
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
 /** Encapsulates business-logic related to communal tutorial state. */
@@ -45,17 +48,24 @@
     private val communalTutorialRepository: CommunalTutorialRepository,
     keyguardInteractor: KeyguardInteractor,
     private val communalRepository: CommunalRepository,
+    communalInteractor: CommunalInteractor,
 ) {
     /** An observable for whether the tutorial is available. */
-    val isTutorialAvailable: Flow<Boolean> =
+    val isTutorialAvailable: StateFlow<Boolean> =
         combine(
+                communalInteractor.isCommunalAvailable,
                 keyguardInteractor.isKeyguardVisible,
                 communalTutorialRepository.tutorialSettingState,
-            ) { isKeyguardVisible, tutorialSettingState ->
-                isKeyguardVisible &&
+            ) { isCommunalAvailable, isKeyguardVisible, tutorialSettingState ->
+                isCommunalAvailable &&
+                    isKeyguardVisible &&
                     tutorialSettingState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
             }
-            .distinctUntilChanged()
+            .stateIn(
+                scope = scope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
 
     /**
      * A flow of the new tutorial state after transitioning. The new state will be calculated based
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 46f957f..ae019a1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -16,10 +16,11 @@
 
 package com.android.systemui.communal.domain.model
 
-import android.appwidget.AppWidgetHost
 import android.appwidget.AppWidgetProviderInfo
+import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
 import android.widget.RemoteViews
 import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import java.util.UUID
 
 /** Encapsulates data for a communal content. */
@@ -41,14 +42,20 @@
         val createdTimestampMillis: Long
     }
 
-    class Widget(
+    data class Widget(
         val appWidgetId: Int,
         val providerInfo: AppWidgetProviderInfo,
-        val appWidgetHost: AppWidgetHost,
+        val appWidgetHost: CommunalAppWidgetHost,
     ) : CommunalContentModel {
         override val key = KEY.widget(appWidgetId)
         // Widget size is always half.
         override val size = CommunalContentSize.HALF
+
+        /** Whether this widget can be reconfigured after it has already been added. */
+        val reconfigurable: Boolean
+            get() =
+                (providerInfo.widgetFeatures and WIDGET_FEATURE_RECONFIGURABLE != 0) &&
+                    providerInfo.configure != null
     }
 
     /** A placeholder item representing a new widget being added */
@@ -121,4 +128,6 @@
             }
         }
     }
+
+    fun isWidget() = this is Widget
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt
new file mode 100644
index 0000000..889023e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.log
+
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.CoreStartable
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.shared.log.CommunalUiEvent
+import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.kotlin.pairwise
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.drop
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+
+/** A [CoreStartable] responsible for logging metrics for the communal hub. */
+@SysUISingleton
+class CommunalLoggerStartable
+@Inject
+constructor(
+    @Background private val backgroundScope: CoroutineScope,
+    private val communalInteractor: CommunalInteractor,
+    private val uiEventLogger: UiEventLogger,
+) : CoreStartable {
+
+    override fun start() {
+        communalInteractor.transitionState
+            .map { state ->
+                when {
+                    state.isOnCommunal() -> CommunalUiEvent.COMMUNAL_HUB_SHOWN
+                    state.isNotOnCommunal() -> CommunalUiEvent.COMMUNAL_HUB_GONE
+                    else -> null
+                }
+            }
+            .filterNotNull()
+            .distinctUntilChanged()
+            // Drop the default value.
+            .drop(1)
+            .onEach { uiEvent -> uiEventLogger.log(uiEvent) }
+            .launchIn(backgroundScope)
+
+        communalInteractor.transitionState
+            .pairwise()
+            .map { (old, new) ->
+                when {
+                    new.isOnCommunal() && old.isSwipingToCommunal() ->
+                        CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_FINISH
+                    new.isOnCommunal() && old.isSwipingFromCommunal() ->
+                        CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_CANCEL
+                    new.isNotOnCommunal() && old.isSwipingFromCommunal() ->
+                        CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_FINISH
+                    new.isNotOnCommunal() && old.isSwipingToCommunal() ->
+                        CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_CANCEL
+                    new.isSwipingToCommunal() && old.isNotOnCommunal() ->
+                        CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_START
+                    new.isSwipingFromCommunal() && old.isOnCommunal() ->
+                        CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_START
+                    else -> null
+                }
+            }
+            .filterNotNull()
+            .distinctUntilChanged()
+            .onEach { uiEvent -> uiEventLogger.log(uiEvent) }
+            .launchIn(backgroundScope)
+    }
+}
+
+/** Whether currently in communal scene. */
+private fun ObservableCommunalTransitionState.isOnCommunal(): Boolean {
+    return this is ObservableCommunalTransitionState.Idle && scene == CommunalSceneKey.Communal
+}
+
+/** Whether currently in a scene other than communal. */
+private fun ObservableCommunalTransitionState.isNotOnCommunal(): Boolean {
+    return this is ObservableCommunalTransitionState.Idle && scene != CommunalSceneKey.Communal
+}
+
+/** Whether currently transitioning from another scene to communal. */
+private fun ObservableCommunalTransitionState.isSwipingToCommunal(): Boolean {
+    return this is ObservableCommunalTransitionState.Transition &&
+        toScene == CommunalSceneKey.Communal &&
+        isInitiatedByUserInput
+}
+
+/** Whether currently transitioning from communal to another scene. */
+private fun ObservableCommunalTransitionState.isSwipingFromCommunal(): Boolean {
+    return this is ObservableCommunalTransitionState.Transition &&
+        fromScene == CommunalSceneKey.Communal &&
+        isInitiatedByUserInput
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
index 41f9cb4..965c1e8 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
@@ -16,14 +16,16 @@
 
 package com.android.systemui.communal.shared
 
-import android.appwidget.AppWidgetHost
 import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProviderInfo
 import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL
 import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
 import android.content.ComponentName
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.util.kotlin.getOrNull
 import java.util.Optional
 import javax.inject.Inject
 
@@ -35,11 +37,22 @@
 @Inject
 constructor(
     private val appWidgetManager: Optional<AppWidgetManager>,
-    private val appWidgetHost: AppWidgetHost,
+    private val appWidgetHost: CommunalAppWidgetHost,
     @CommunalLog logBuffer: LogBuffer,
 ) {
     companion object {
         private const val TAG = "CommunalWidgetHost"
+
+        /** Returns whether a particular widget requires configuration when it is first added. */
+        fun requiresConfiguration(widgetInfo: AppWidgetProviderInfo): Boolean {
+            val featureFlags: Int = widgetInfo.widgetFeatures
+            // A widget's configuration is optional only if it's configuration is marked as optional
+            // AND it can be reconfigured later.
+            val configurationOptional =
+                (featureFlags and WIDGET_FEATURE_CONFIGURATION_OPTIONAL != 0 &&
+                    featureFlags and WIDGET_FEATURE_RECONFIGURABLE != 0)
+            return widgetInfo.configure != null && !configurationOptional
+        }
     }
     private val logger = Logger(logBuffer, TAG)
 
@@ -66,22 +79,7 @@
         return false
     }
 
-    /**
-     * Returns whether a particular widget requires configuration when it is first added.
-     *
-     * Must be called after the widget id has been bound.
-     */
-    fun requiresConfiguration(widgetId: Int): Boolean {
-        if (appWidgetManager.isPresent) {
-            val widgetInfo = appWidgetManager.get().getAppWidgetInfo(widgetId)
-            val featureFlags: Int = widgetInfo.widgetFeatures
-            // A widget's configuration is optional only if it's configuration is marked as optional
-            // AND it can be reconfigured later.
-            val configurationOptional =
-                (featureFlags and WIDGET_FEATURE_CONFIGURATION_OPTIONAL != 0 &&
-                    featureFlags and WIDGET_FEATURE_RECONFIGURABLE != 0)
-            return widgetInfo.configure != null && !configurationOptional
-        }
-        return false
+    fun getAppWidgetInfo(widgetId: Int): AppWidgetProviderInfo? {
+        return appWidgetManager.getOrNull()?.getAppWidgetInfo(widgetId)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
index e167f3e..b64c195 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
@@ -22,8 +22,6 @@
 /** UI events for the Communal Hub. */
 enum class CommunalUiEvent(private val id: Int) : UiEventEnum {
     @UiEvent(doc = "Communal Hub is fully shown") COMMUNAL_HUB_SHOWN(1566),
-    @UiEvent(doc = "Communal Hub starts entering") COMMUNAL_HUB_ENTERING(1575),
-    @UiEvent(doc = "Communal Hub starts exiting") COMMUNAL_HUB_EXITING(1576),
     @UiEvent(doc = "Communal Hub is fully gone") COMMUNAL_HUB_GONE(1577),
     @UiEvent(doc = "Communal Hub times out") COMMUNAL_HUB_TIMEOUT(1578),
     @UiEvent(doc = "The visible content in the Communal Hub is fully loaded and rendered")
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
index 75f9d80..c5dac77 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
@@ -39,7 +39,7 @@
 import javax.inject.Inject
 import javax.inject.Named
 
-/** Controller for managing the smartspace view on the dream */
+/** Controller for managing the smartspace view on the glanceable hub */
 @SysUISingleton
 class CommunalSmartspaceController
 @Inject
@@ -125,7 +125,7 @@
             smartspaceManager.createSmartspaceSession(
                 SmartspaceConfig.Builder(context, UI_SURFACE_GLANCEABLE_HUB).build()
             )
-        Log.d(TAG, "Starting smartspace session for dream")
+        Log.d(TAG, "Starting smartspace session for communal")
         newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
         this.session = newSession
 
@@ -153,7 +153,7 @@
 
         plugin?.registerSmartspaceEventNotifier(null)
         plugin?.onTargetsAvailable(emptyList())
-        Log.d(TAG, "Ending smartspace session for dream")
+        Log.d(TAG, "Ending smartspace session for communal")
     }
 
     fun addListener(listener: SmartspaceTargetListener) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt
index 4dfc371..0120b5c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt
@@ -18,7 +18,6 @@
 package com.android.systemui.communal.ui.binder
 
 import android.widget.TextView
-import androidx.core.view.isGone
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
@@ -32,16 +31,14 @@
     fun bind(
         view: TextView,
         viewModel: CommunalTutorialIndicatorViewModel,
+        isPreviewMode: Boolean = false,
     ): DisposableHandle {
         val disposableHandle =
             view.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.STARTED) {
                     launch {
-                        viewModel.showIndicator.collect { isVisible ->
-                            updateView(
-                                view = view,
-                                isIndicatorVisible = isVisible,
-                            )
+                        viewModel.showIndicator(isPreviewMode).collect { showIndicator ->
+                            view.isVisible = showIndicator
                         }
                     }
 
@@ -51,18 +48,4 @@
 
         return disposableHandle
     }
-
-    private fun updateView(
-        isIndicatorVisible: Boolean,
-        view: TextView,
-    ) {
-        if (!isIndicatorVisible) {
-            view.isGone = true
-            return
-        }
-
-        if (!view.isVisible) {
-            view.isVisible = true
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt
index 027cc96..2d9dd50 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt
@@ -120,6 +120,7 @@
                 ConstraintSet.PARENT_ID,
                 ConstraintSet.BOTTOM
             )
+            setVisibility(tutorialIndicatorId, View.GONE)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 97e530a..a87ebd7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -17,31 +17,33 @@
 package com.android.systemui.communal.ui.viewmodel
 
 import android.content.ComponentName
-import android.os.PowerManager
-import android.os.SystemClock
-import android.view.MotionEvent
-import android.widget.RemoteViews
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
+import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.media.controls.ui.MediaHost
-import com.android.systemui.shade.ShadeViewController
-import javax.inject.Provider
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOf
 
 /** The base view model for the communal hub. */
 abstract class BaseCommunalViewModel(
     private val communalInteractor: CommunalInteractor,
-    private val shadeViewController: Provider<ShadeViewController>,
-    private val powerManager: PowerManager,
     val mediaHost: MediaHost,
 ) {
-    val isKeyguardVisible: Flow<Boolean> = communalInteractor.isKeyguardVisible
-
     val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene
 
+    /** Whether widgets are currently being re-ordered. */
+    open val reorderingWidgets: StateFlow<Boolean> = MutableStateFlow(false)
+
+    private val _selectedKey: MutableStateFlow<String?> = MutableStateFlow(null)
+
+    /** The key of the currently selected item, or null if no item selected. */
+    val selectedKey: StateFlow<String?>
+        get() = _selectedKey
+
     fun onSceneChanged(scene: CommunalSceneKey) {
         communalInteractor.onSceneChanged(scene)
     }
@@ -58,36 +60,12 @@
     /**
      * Called when a widget is added via drag and drop from the widget picker into the communal hub.
      */
-    open fun onAddWidget(componentName: ComponentName, priority: Int) {
-        communalInteractor.addWidget(componentName, priority, ::configureWidget)
-    }
-
-    /**
-     * Called when a widget needs to be configured, with the id of the widget. The return value
-     * should represent whether configuring the widget was successful.
-     */
-    protected open suspend fun configureWidget(widgetId: Int): Boolean {
-        return true
-    }
-
-    // TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't block
-    //  touches anymore.
-    /** Called when a touch is received outside the edge swipe area when hub mode is closed. */
-    fun onOuterTouch(motionEvent: MotionEvent) {
-        // Forward the touch to the shade so that basic gestures like swipe up/down for
-        // shade/bouncer work.
-        shadeViewController.get().handleExternalTouch(motionEvent)
-    }
-
-    // TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't block
-    //  touches anymore.
-    /** Called to refresh the screen timeout when a user touch is received. */
-    fun onUserActivity() {
-        powerManager.userActivity(
-            SystemClock.uptimeMillis(),
-            PowerManager.USER_ACTIVITY_EVENT_TOUCH,
-            0
-        )
+    open fun onAddWidget(
+        componentName: ComponentName,
+        priority: Int,
+        configurator: WidgetConfigurator? = null
+    ) {
+        communalInteractor.addWidget(componentName, priority, configurator)
     }
 
     /** A list of all the communal content to be displayed in the communal hub. */
@@ -96,6 +74,12 @@
     /** Whether in edit mode for the communal hub. */
     open val isEditMode = false
 
+    /** Whether the popup message triggered by dismissing the CTA tile is showing. */
+    open val isPopupOnDismissCtaShowing: Flow<Boolean> = flowOf(false)
+
+    /** Hide the popup message triggered by dismissing the CTA tile. */
+    open fun onHidePopupAfterDismissCta() {}
+
     /** Called as the UI requests deleting a widget. */
     open fun onDeleteWidget(id: Int) {}
 
@@ -108,12 +92,23 @@
      */
     open fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) {}
 
-    /** Called as the UI requests opening the widget editor. */
-    open fun onOpenWidgetEditor() {}
+    /** Called as the UI requests opening the widget editor with an optional preselected widget. */
+    open fun onOpenWidgetEditor(preselectedKey: String? = null) {}
 
     /** Called as the UI requests to dismiss the CTA tile. */
     open fun onDismissCtaTile() {}
 
-    /** Gets the interaction handler used to handle taps on a remote view */
-    abstract fun getInteractionHandler(): RemoteViews.InteractionHandler
+    /** Called as the user starts dragging a widget to reorder. */
+    open fun onReorderWidgetStart() {}
+
+    /** Called as the user finishes dragging a widget to reorder. */
+    open fun onReorderWidgetEnd() {}
+
+    /** Called as the user cancels dragging a widget to reorder. */
+    open fun onReorderWidgetCancel() {}
+
+    /** Set the key of the currently selected item */
+    fun setSelectedKey(key: String?) {
+        _selectedKey.value = key
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index a03e6c1..ebcfb8b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -16,28 +16,18 @@
 
 package com.android.systemui.communal.ui.viewmodel
 
-import android.app.Activity
-import android.app.Activity.RESULT_CANCELED
-import android.app.Activity.RESULT_OK
-import android.app.ActivityOptions
-import android.appwidget.AppWidgetHost
-import android.content.ActivityNotFoundException
-import android.content.ComponentName
-import android.os.PowerManager
-import android.widget.RemoteViews
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.log.CommunalUiEvent
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.media.dagger.MediaModule
-import com.android.systemui.shade.ShadeViewController
-import com.android.systemui.util.nullableAtomicReference
 import javax.inject.Inject
 import javax.inject.Named
-import javax.inject.Provider
-import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
 
 /** The view model for communal hub in edit mode. */
@@ -46,27 +36,9 @@
 @Inject
 constructor(
     private val communalInteractor: CommunalInteractor,
-    private val appWidgetHost: AppWidgetHost,
-    shadeViewController: Provider<ShadeViewController>,
-    powerManager: PowerManager,
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
-) : BaseCommunalViewModel(communalInteractor, shadeViewController, powerManager, mediaHost) {
-
-    private companion object {
-        private const val KEY_SPLASH_SCREEN_STYLE = "android.activity.splashScreenStyle"
-        private const val SPLASH_SCREEN_STYLE_EMPTY = 0
-    }
-
-    private val _widgetsToConfigure = MutableSharedFlow<Int>()
-
-    /**
-     * Flow emitting ids of widgets which need to be configured. The consumer of this flow should
-     * trigger [startConfigurationActivity] to initiate configuration.
-     */
-    val widgetsToConfigure: Flow<Int> = _widgetsToConfigure
-
-    private var pendingConfiguration: CompletableDeferred<Int>? by nullableAtomicReference()
-
+    private val uiEventLogger: UiEventLogger,
+) : BaseCommunalViewModel(communalInteractor, mediaHost) {
     override val isEditMode = true
 
     // Only widgets are editable. The CTA tile comes last in the list and remains visible.
@@ -75,64 +47,33 @@
             widgets + listOf(CommunalContentModel.CtaTileInEditMode())
         }
 
+    private val _reorderingWidgets = MutableStateFlow(false)
+
+    override val reorderingWidgets: StateFlow<Boolean>
+        get() = _reorderingWidgets
+
     override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
 
     override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
         communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
 
-    override fun getInteractionHandler(): RemoteViews.InteractionHandler {
-        // Ignore all interactions in edit mode.
-        return RemoteViews.InteractionHandler { _, _, _ -> false }
+    override fun onReorderWidgetStart() {
+        // Clear selection status
+        setSelectedKey(null)
+        _reorderingWidgets.value = true
+        uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START)
     }
 
-    override fun onAddWidget(componentName: ComponentName, priority: Int) {
-        if (pendingConfiguration != null) {
-            throw IllegalStateException(
-                "Cannot add $componentName widget while widget configuration is pending"
-            )
-        }
-        super.onAddWidget(componentName, priority)
+    override fun onReorderWidgetEnd() {
+        _reorderingWidgets.value = false
+        uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_FINISH)
     }
 
-    fun startConfigurationActivity(activity: Activity, widgetId: Int, requestCode: Int) {
-        val options =
-            ActivityOptions.makeBasic().apply {
-                setPendingIntentBackgroundActivityStartMode(
-                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
-                )
-            }
-        val bundle = options.toBundle()
-        bundle.putInt(KEY_SPLASH_SCREEN_STYLE, SPLASH_SCREEN_STYLE_EMPTY)
-        try {
-            appWidgetHost.startAppWidgetConfigureActivityForResult(
-                activity,
-                widgetId,
-                0,
-                // Use the widget id as the request code.
-                requestCode,
-                bundle
-            )
-        } catch (e: ActivityNotFoundException) {
-            setConfigurationResult(RESULT_CANCELED)
-        }
+    override fun onReorderWidgetCancel() {
+        _reorderingWidgets.value = false
+        uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL)
     }
 
-    override suspend fun configureWidget(widgetId: Int): Boolean {
-        if (pendingConfiguration != null) {
-            throw IllegalStateException(
-                "Attempting to configure $widgetId while another configuration is already active"
-            )
-        }
-        pendingConfiguration = CompletableDeferred()
-        _widgetsToConfigure.emit(widgetId)
-        val resultCode = pendingConfiguration?.await() ?: RESULT_CANCELED
-        pendingConfiguration = null
-        return resultCode == RESULT_OK
-    }
-
-    /** Sets the result of widget configuration. */
-    fun setConfigurationResult(resultCode: Int) {
-        pendingConfiguration?.complete(resultCode)
-            ?: throw IllegalStateException("No widget pending configuration")
-    }
+    /** Sets whether edit mode is currently open */
+    fun setEditModeOpen(isOpen: Boolean) = communalInteractor.setEditModeOpen(isOpen)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt
index 274e61a..63a4972 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt
@@ -20,17 +20,30 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
 
 /** View model for communal tutorial indicator on keyguard */
 class CommunalTutorialIndicatorViewModel
 @Inject
 constructor(
-    communalTutorialInteractor: CommunalTutorialInteractor,
+    private val communalTutorialInteractor: CommunalTutorialInteractor,
     bottomAreaInteractor: KeyguardBottomAreaInteractor,
 ) {
-    /** An observable for whether the tutorial indicator view should be visible. */
-    val showIndicator: Flow<Boolean> = communalTutorialInteractor.isTutorialAvailable
+    /**
+     * An observable for whether the tutorial indicator view should be visible.
+     *
+     * @param isPreviewMode Whether for preview keyguard mode in wallpaper settings.
+     */
+    fun showIndicator(isPreviewMode: Boolean): StateFlow<Boolean> {
+        return if (isPreviewMode) {
+            MutableStateFlow(false).asStateFlow()
+        } else {
+            communalTutorialInteractor.isTutorialAvailable
+        }
+    }
 
     /** An observable for the alpha level for the tutorial indicator. */
     val alpha: Flow<Float> = bottomAreaInteractor.alpha.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 066e897..d7a3705 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -16,37 +16,39 @@
 
 package com.android.systemui.communal.ui.viewmodel
 
-import android.os.PowerManager
-import android.widget.RemoteViews
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
-import com.android.systemui.communal.widgets.WidgetInteractionHandler
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.media.controls.ui.MediaHierarchyManager
 import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.MediaHostState
 import com.android.systemui.media.dagger.MediaModule
-import com.android.systemui.shade.ShadeViewController
 import javax.inject.Inject
 import javax.inject.Named
-import javax.inject.Provider
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.launch
 
 /** The default view model used for showing the communal hub. */
 @SysUISingleton
 class CommunalViewModel
 @Inject
 constructor(
+    @Application private val scope: CoroutineScope,
     private val communalInteractor: CommunalInteractor,
-    private val interactionHandler: WidgetInteractionHandler,
     tutorialInteractor: CommunalTutorialInteractor,
-    shadeViewController: Provider<ShadeViewController>,
-    powerManager: PowerManager,
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
-) : BaseCommunalViewModel(communalInteractor, shadeViewController, powerManager, mediaHost) {
+) : BaseCommunalViewModel(communalInteractor, mediaHost) {
     @OptIn(ExperimentalCoroutinesApi::class)
     override val communalContent: Flow<List<CommunalContentModel>> =
         tutorialInteractor.isTutorialAvailable.flatMapLatest { isTutorialMode ->
@@ -63,9 +65,58 @@
             }
         }
 
-    override fun onOpenWidgetEditor() = communalInteractor.showWidgetEditor()
+    private val _isPopupOnDismissCtaShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    override val isPopupOnDismissCtaShowing: Flow<Boolean> =
+        _isPopupOnDismissCtaShowing.asStateFlow()
 
-    override fun onDismissCtaTile() = communalInteractor.dismissCtaTile()
+    init {
+        // Initialize our media host for the UMO. This only needs to happen once and must be done
+        // before the MediaHierarchyManager attempts to move the UMO to the hub.
+        with(mediaHost) {
+            expansion = MediaHostState.EXPANDED
+            expandedMatchesParentHeight = true
+            showsOnlyActiveMedia = false
+            falsingProtectionNeeded = false
+            init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
+        }
+    }
 
-    override fun getInteractionHandler(): RemoteViews.InteractionHandler = interactionHandler
+    override fun onOpenWidgetEditor(preselectedKey: String?) =
+        communalInteractor.showWidgetEditor(preselectedKey)
+
+    override fun onDismissCtaTile() {
+        scope.launch {
+            communalInteractor.dismissCtaTile()
+            setPopupOnDismissCtaVisibility(true)
+            schedulePopupHiding()
+        }
+    }
+
+    override fun onHidePopupAfterDismissCta() {
+        cancelDelayedPopupHiding()
+        setPopupOnDismissCtaVisibility(false)
+    }
+
+    private fun setPopupOnDismissCtaVisibility(isVisible: Boolean) {
+        _isPopupOnDismissCtaShowing.value = isVisible
+    }
+
+    private var delayedHidePopupJob: Job? = null
+    private fun schedulePopupHiding() {
+        cancelDelayedPopupHiding()
+        delayedHidePopupJob =
+            scope.launch {
+                delay(POPUP_AUTO_HIDE_TIMEOUT_MS)
+                onHidePopupAfterDismissCta()
+            }
+    }
+
+    private fun cancelDelayedPopupHiding() {
+        delayedHidePopupJob?.cancel()
+        delayedHidePopupJob = null
+    }
+
+    companion object {
+        const val POPUP_AUTO_HIDE_TIMEOUT_MS = 12000L
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
new file mode 100644
index 0000000..61db026
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.appwidget.AppWidgetHost
+import android.appwidget.AppWidgetHostView
+import android.appwidget.AppWidgetProviderInfo
+import android.content.Context
+import android.os.Looper
+import android.widget.RemoteViews
+
+/** Communal app widget host that creates a [CommunalAppWidgetHostView]. */
+class CommunalAppWidgetHost(
+    context: Context,
+    hostId: Int,
+    interactionHandler: RemoteViews.InteractionHandler,
+    looper: Looper
+) : AppWidgetHost(context, hostId, interactionHandler, looper) {
+    override fun onCreateView(
+        context: Context,
+        appWidgetId: Int,
+        appWidget: AppWidgetProviderInfo?
+    ): AppWidgetHostView {
+        return CommunalAppWidgetHostView(context)
+    }
+
+    /**
+     * Creates and returns a [CommunalAppWidgetHostView]. This method does the same thing as
+     * `createView`. The only difference is that the returned value will be casted to
+     * [CommunalAppWidgetHostView].
+     */
+    fun createViewForCommunal(
+        context: Context?,
+        appWidgetId: Int,
+        appWidget: AppWidgetProviderInfo?
+    ): CommunalAppWidgetHostView {
+        // `createView` internally calls `onCreateView` to create the view. We cannot override
+        // `createView`, but we are sure that the hostView is `CommunalAppWidgetHostView`
+        return createView(context, appWidgetId, appWidget) as CommunalAppWidgetHostView
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
new file mode 100644
index 0000000..586df32
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.kotlin.BooleanFlowOperators.or
+import com.android.systemui.util.kotlin.pairwise
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+class CommunalAppWidgetHostStartable
+@Inject
+constructor(
+    private val appWidgetHost: CommunalAppWidgetHost,
+    private val communalInteractor: CommunalInteractor,
+    @Background private val bgScope: CoroutineScope,
+    @Main private val uiDispatcher: CoroutineDispatcher
+) : CoreStartable {
+    override fun start() {
+        or(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen)
+            // Only trigger updates on state changes, ignoring the initial false value.
+            .pairwise(false)
+            .filter { (previous, new) -> previous != new }
+            .onEach { (_, shouldListen) -> updateAppWidgetHostActive(shouldListen) }
+            .launchIn(bgScope)
+    }
+
+    private suspend fun updateAppWidgetHostActive(active: Boolean) =
+        // Always ensure this is called on the main/ui thread.
+        withContext(uiDispatcher) {
+            if (active) {
+                appWidgetHost.startListening()
+            } else {
+                appWidgetHost.stopListening()
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
new file mode 100644
index 0000000..840c3a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.appwidget.AppWidgetHostView
+import android.content.Context
+import android.graphics.Outline
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewOutlineProvider
+import com.android.systemui.animation.LaunchableView
+import com.android.systemui.animation.LaunchableViewDelegate
+
+/** AppWidgetHostView that displays in communal hub with support for rounded corners. */
+class CommunalAppWidgetHostView(context: Context) : AppWidgetHostView(context), LaunchableView {
+    private val launchableViewDelegate =
+        LaunchableViewDelegate(
+            this,
+            superSetVisibility = { super.setVisibility(it) },
+        )
+
+    // Mutable corner radius.
+    var enforcedCornerRadius: Float
+
+    // Mutable `Rect`. The size will be mutated when the widget is reapplied.
+    var enforcedRectangle: Rect
+
+    init {
+        enforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context)
+        enforcedRectangle = Rect()
+    }
+
+    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+        super.onLayout(changed, l, t, r, b)
+
+        enforceRoundedCorners()
+    }
+
+    private val cornerRadiusEnforcementOutline: ViewOutlineProvider =
+        object : ViewOutlineProvider() {
+            override fun getOutline(view: View?, outline: Outline) {
+                if (enforcedRectangle.isEmpty || enforcedCornerRadius <= 0) {
+                    outline.setEmpty()
+                } else {
+                    outline.setRoundRect(enforcedRectangle, enforcedCornerRadius)
+                }
+            }
+        }
+
+    private fun enforceRoundedCorners() {
+        if (enforcedCornerRadius <= 0) {
+            resetRoundedCorners()
+            return
+        }
+        val background: View? = RoundedCornerEnforcement.findBackground(this)
+        if (background == null || RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) {
+            resetRoundedCorners()
+            return
+        }
+        RoundedCornerEnforcement.computeRoundedRectangle(this, background, enforcedRectangle)
+        outlineProvider = cornerRadiusEnforcementOutline
+        clipToOutline = true
+        invalidateOutline()
+    }
+
+    private fun resetRoundedCorners() {
+        outlineProvider = ViewOutlineProvider.BACKGROUND
+        clipToOutline = false
+    }
+
+    override fun setShouldBlockVisibilityChanges(block: Boolean) =
+        launchableViewDelegate.setShouldBlockVisibilityChanges(block)
+
+    override fun setVisibility(visibility: Int) = launchableViewDelegate.setVisibility(visibility)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index bfc6f2b..ad1327e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -27,13 +27,11 @@
 import androidx.activity.ComponentActivity
 import androidx.activity.result.ActivityResultLauncher
 import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.communal.shared.log.CommunalUiEvent
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
 import com.android.systemui.compose.ComposeFacade.setCommunalEditWidgetActivityContent
 import javax.inject.Inject
-import kotlinx.coroutines.launch
 
 /** An Activity for editing the widgets that appear in hub mode. */
 class EditWidgetsActivity
@@ -41,19 +39,25 @@
 constructor(
     private val communalViewModel: CommunalEditModeViewModel,
     private var windowManagerService: IWindowManager? = null,
+    private val uiEventLogger: UiEventLogger,
+    private val widgetConfiguratorFactory: WidgetConfigurationController.Factory
 ) : ComponentActivity() {
     companion object {
         private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
         private const val EXTRA_FILTER_STRATEGY = "filter_strategy"
         private const val FILTER_STRATEGY_GLANCEABLE_HUB = 1
-        private const val REQUEST_CODE_CONFIGURE_WIDGET = 1
         private const val TAG = "EditWidgetsActivity"
+        const val EXTRA_PRESELECTED_KEY = "preselected_key"
     }
 
+    private val widgetConfigurator by lazy { widgetConfiguratorFactory.create(this) }
+
     private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> =
         registerForActivityResult(StartActivityForResult()) { result ->
             when (result.resultCode) {
                 RESULT_OK -> {
+                    uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_WIDGET_PICKER_SHOWN)
+
                     result.data?.let { intent ->
                         val isPendingWidgetDrag =
                             intent.getBooleanExtra(EXTRA_IS_PENDING_WIDGET_DRAG, false)
@@ -66,7 +70,7 @@
                                     Intent.EXTRA_COMPONENT_NAME,
                                     ComponentName::class.java
                                 )
-                                ?.let { communalViewModel.onAddWidget(it, 0) }
+                                ?.let { communalViewModel.onAddWidget(it, 0, widgetConfigurator) }
                                 ?: run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
                         }
                     }
@@ -83,26 +87,19 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
+        communalViewModel.setEditModeOpen(true)
+
         val windowInsetsController = window.decorView.windowInsetsController
         windowInsetsController?.hide(WindowInsets.Type.systemBars())
         window.setDecorFitsSystemWindows(false)
 
-        lifecycleScope.launch {
-            repeatOnLifecycle(Lifecycle.State.STARTED) {
-                // Start the configuration activity when new widgets are added.
-                communalViewModel.widgetsToConfigure.collect { widgetId ->
-                    communalViewModel.startConfigurationActivity(
-                        activity = this@EditWidgetsActivity,
-                        widgetId = widgetId,
-                        requestCode = REQUEST_CODE_CONFIGURE_WIDGET
-                    )
-                }
-            }
-        }
+        val preselectedKey = intent.getStringExtra(EXTRA_PRESELECTED_KEY)
+        communalViewModel.setSelectedKey(preselectedKey)
 
         setCommunalEditWidgetActivityContent(
             activity = this,
             viewModel = communalViewModel,
+            widgetConfigurator = widgetConfigurator,
             onOpenWidgetPicker = {
                 val intent =
                     Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_HOME) }
@@ -140,8 +137,23 @@
 
     override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
         super.onActivityResult(requestCode, resultCode, data)
-        if (requestCode == REQUEST_CODE_CONFIGURE_WIDGET) {
-            communalViewModel.setConfigurationResult(resultCode)
+        if (requestCode == WidgetConfigurationController.REQUEST_CODE) {
+            widgetConfigurator.setConfigurationResult(resultCode)
         }
     }
+
+    override fun onStart() {
+        super.onStart()
+        uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_SHOWN)
+    }
+
+    override fun onStop() {
+        super.onStop()
+        uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_GONE)
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        communalViewModel.setEditModeOpen(false)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
index 55acad0..d1843af 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
@@ -18,12 +18,13 @@
 
 import android.content.Context
 import android.content.Intent
+import com.android.systemui.communal.widgets.EditWidgetsActivity.Companion.EXTRA_PRESELECTED_KEY
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.plugins.ActivityStarter
 import javax.inject.Inject
 
 interface EditWidgetsActivityStarter {
-    fun startActivity()
+    fun startActivity(preselectedKey: String? = null)
 }
 
 class EditWidgetsActivityStarterImpl
@@ -33,10 +34,11 @@
     private val activityStarter: ActivityStarter,
 ) : EditWidgetsActivityStarter {
 
-    override fun startActivity() {
+    override fun startActivity(preselectedKey: String?) {
         activityStarter.startActivityDismissingKeyguard(
             Intent(applicationContext, EditWidgetsActivity::class.java)
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK),
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+                .apply { preselectedKey?.let { putExtra(EXTRA_PRESELECTED_KEY, preselectedKey) } },
             /* onlyProvisioned = */ true,
             /* dismissShade = */ true,
         )
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt
new file mode 100644
index 0000000..abda44b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.annotation.IdRes
+import android.annotation.Nullable
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.os.BuildCompat.isAtLeastS
+import com.android.systemui.res.R
+import kotlin.math.min
+
+/**
+ * Utilities to compute the enforced use of rounded corners on App Widgets. This is a fork of the
+ * Launcher3 source code to enforce the same visual treatment on communal hub.
+ */
+internal object RoundedCornerEnforcement {
+    /**
+     * Find the background view for a widget.
+     *
+     * @param appWidget the view containing the App Widget (typically the instance of
+     *   [CommunalAppWidgetHostView]).
+     */
+    fun findBackground(appWidget: View): View? {
+        val backgrounds = findViewsWithId(appWidget, R.id.background)
+        if (backgrounds.size == 1) {
+            return backgrounds[0]
+        }
+        // Really, the argument should contain the widget, so it cannot be the background.
+        if (appWidget is ViewGroup) {
+            val vg = appWidget
+            if (vg.childCount > 0) {
+                return findUndefinedBackground(vg.getChildAt(0))
+            }
+        }
+        return appWidget
+    }
+
+    /** Check whether the app widget has opted out of the enforcement. */
+    fun hasAppWidgetOptedOut(appWidget: View?, background: View): Boolean {
+        return background.id == R.id.background && background.clipToOutline
+    }
+
+    /**
+     * Computes the rounded rectangle needed for this app widget.
+     *
+     * @param appWidget View onto which the rounded rectangle will be applied.
+     * @param background Background view. This must be either `appWidget` or a descendant of
+     *   `appWidget`.
+     * @param outRect Rectangle set to the rounded rectangle coordinates, in the reference frame of
+     *   `appWidget`.
+     */
+    fun computeRoundedRectangle(appWidget: View, background: View, outRect: Rect) {
+        var background = background
+        outRect.left = 0
+        outRect.right = background.width
+        outRect.top = 0
+        outRect.bottom = background.height
+        while (background !== appWidget) {
+            outRect.offset(background.left, background.top)
+            background = background.parent as View
+        }
+    }
+
+    /** Get the radius of the rounded rectangle defined in the host's resource. */
+    private fun getOwnedEnforcedRadius(context: Context): Float {
+        val res: Resources = context.resources
+        return res.getDimension(R.dimen.communal_enforced_rounded_corner_max_radius)
+    }
+
+    /**
+     * Computes the radius of the rounded rectangle that should be applied to a widget expanded in
+     * the given context.
+     */
+    fun computeEnforcedRadius(context: Context): Float {
+        if (!isAtLeastS()) {
+            return 0f
+        }
+        val res: Resources = context.resources
+        val systemRadius: Float =
+            res.getDimension(android.R.dimen.system_app_widget_background_radius)
+        val defaultRadius = getOwnedEnforcedRadius(context)
+        return min(defaultRadius, systemRadius)
+    }
+
+    private fun findViewsWithId(view: View, @IdRes viewId: Int): List<View> {
+        val output: MutableList<View> = ArrayList()
+        accumulateViewsWithId(view, viewId, output)
+        return output
+    }
+
+    // Traverse views. If the predicate returns true, continue on the children, otherwise, don't.
+    private fun accumulateViewsWithId(view: View, @IdRes viewId: Int, output: MutableList<View>) {
+        if (view.id == viewId) {
+            output.add(view)
+            return
+        }
+        if (view is ViewGroup) {
+            val vg = view
+            for (i in 0 until vg.childCount) {
+                accumulateViewsWithId(vg.getChildAt(i), viewId, output)
+            }
+        }
+    }
+
+    private fun isViewVisible(view: View): Boolean {
+        return if (view.visibility != View.VISIBLE) {
+            false
+        } else !view.willNotDraw() || view.foreground != null || view.background != null
+    }
+
+    @Nullable
+    private fun findUndefinedBackground(current: View): View? {
+        if (current.visibility != View.VISIBLE) {
+            return null
+        }
+        if (isViewVisible(current)) {
+            return current
+        }
+        var lastVisibleView: View? = null
+        // Find the first view that is either not a ViewGroup, or a ViewGroup which will draw
+        // something, or a ViewGroup that contains more than one view.
+        if (current is ViewGroup) {
+            val vg = current
+            for (i in 0 until vg.childCount) {
+                val visibleView = findUndefinedBackground(vg.getChildAt(i))
+                if (visibleView != null) {
+                    if (lastVisibleView != null) {
+                        return current // At least two visible children
+                    }
+                    lastVisibleView = visibleView
+                }
+            }
+        }
+        return lastVisibleView
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt
new file mode 100644
index 0000000..3e68479
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.app.Activity
+import android.app.ActivityOptions
+import android.content.ActivityNotFoundException
+import android.window.SplashScreen
+import androidx.activity.ComponentActivity
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.nullableAtomicReference
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+/**
+ * Handles starting widget configuration activities and receiving the response to determine if
+ * configuration was successful.
+ */
+class WidgetConfigurationController
+@AssistedInject
+constructor(
+    @Assisted private val activity: ComponentActivity,
+    private val appWidgetHost: CommunalAppWidgetHost,
+    @Background private val bgDispatcher: CoroutineDispatcher
+) : WidgetConfigurator {
+    @AssistedFactory
+    fun interface Factory {
+        fun create(activity: ComponentActivity): WidgetConfigurationController
+    }
+
+    private var result: CompletableDeferred<Boolean>? by nullableAtomicReference()
+
+    override suspend fun configureWidget(appWidgetId: Int): Boolean =
+        withContext(bgDispatcher) {
+            if (result != null) {
+                throw IllegalStateException("There is already a pending configuration")
+            }
+            result = CompletableDeferred()
+            val options =
+                ActivityOptions.makeBasic().apply {
+                    pendingIntentBackgroundActivityStartMode =
+                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+                    splashScreenStyle = SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR
+                }
+
+            try {
+                appWidgetHost.startAppWidgetConfigureActivityForResult(
+                    activity,
+                    appWidgetId,
+                    0,
+                    REQUEST_CODE,
+                    options.toBundle()
+                )
+            } catch (e: ActivityNotFoundException) {
+                setConfigurationResult(Activity.RESULT_CANCELED)
+            }
+            val value = result?.await() ?: false
+            result = null
+            return@withContext value
+        }
+
+    fun setConfigurationResult(resultCode: Int) {
+        result?.complete(resultCode == Activity.RESULT_OK)
+    }
+
+    companion object {
+        const val REQUEST_CODE = 100
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurator.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurator.kt
new file mode 100644
index 0000000..916faa1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurator.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+/** Configurator which can be used to request a certain widget be reconfigured. */
+fun interface WidgetConfigurator {
+    /** Launch configuration for a widget, and return the result */
+    suspend fun configureWidget(appWidgetId: Int): Boolean
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
index c8db70b..afa7fa9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
@@ -19,6 +19,8 @@
 import android.app.PendingIntent
 import android.view.View
 import android.widget.RemoteViews
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.common.ui.view.getNearestParent
 import com.android.systemui.plugins.ActivityStarter
 import javax.inject.Inject
 
@@ -33,17 +35,19 @@
         response: RemoteViews.RemoteResponse
     ): Boolean =
         when {
-            pendingIntent.isActivity -> startActivity(pendingIntent)
+            pendingIntent.isActivity -> startActivity(view, pendingIntent)
             else ->
                 RemoteViews.startPendingIntent(view, pendingIntent, response.getLaunchOptions(view))
         }
 
-    private fun startActivity(pendingIntent: PendingIntent): Boolean {
+    private fun startActivity(view: View, pendingIntent: PendingIntent): Boolean {
+        val hostView = view.getNearestParent<CommunalAppWidgetHostView>()
+        val animationController = hostView?.let(ActivityLaunchAnimator.Controller::fromView)
+
         activityStarter.startPendingIntentMaybeDismissingKeyguard(
-            /* intent = */ pendingIntent,
+            pendingIntent,
             /* intentSentUiThreadCallback = */ null,
-            // TODO(b/318758390): Properly animate activities started from widgets.
-            /* animationController = */ null
+            animationController
         )
         return true
     }
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 3a92739..947cb02 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -17,17 +17,24 @@
 
 package com.android.systemui.compose
 
+import android.app.Dialog
 import android.content.Context
 import android.view.View
 import android.view.WindowInsets
 import androidx.activity.ComponentActivity
 import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
+import com.android.systemui.communal.widgets.WidgetConfigurator
+import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.scene.shared.model.Scene
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.StateFlow
 
@@ -62,10 +69,17 @@
     fun setCommunalEditWidgetActivityContent(
         activity: ComponentActivity,
         viewModel: BaseCommunalViewModel,
+        widgetConfigurator: WidgetConfigurator,
         onOpenWidgetPicker: () -> Unit,
         onEditDone: () -> Unit,
     )
 
+    fun setVolumePanelActivityContent(
+        activity: ComponentActivity,
+        viewModel: VolumePanelViewModel,
+        onDismissAnimationFinished: () -> Unit,
+    )
+
     /** Create a [View] to represent [viewModel] on screen. */
     fun createFooterActionsView(
         context: Context,
@@ -82,12 +96,25 @@
         sceneByKey: Map<SceneKey, Scene>,
     ): View
 
+    /** Creates sticky key dialog presenting provided [viewModel] */
+    fun createStickyKeysDialog(
+        dialogFactory: SystemUIDialogFactory,
+        viewModel: StickyKeysIndicatorViewModel
+    ): Dialog
+
     /** Create a [View] to represent [viewModel] on screen. */
     fun createCommunalView(
         context: Context,
         viewModel: BaseCommunalViewModel,
     ): View
 
+    /** Create a [View] to represent the [BouncerViewModel]. */
+    fun createBouncer(
+        context: Context,
+        viewModel: BouncerViewModel,
+        dialogFactory: BouncerDialogFactory,
+    ): View
+
     /** Creates a container that hosts the communal UI and handles gesture transitions. */
     fun createCommunalContainer(context: Context, viewModel: BaseCommunalViewModel): View
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepository.kt
index 5bb6eec..074b9ff3 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepository.kt
@@ -17,23 +17,36 @@
 package com.android.systemui.controls.panels
 
 import android.content.ComponentName
+import android.os.UserHandle
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.controls.ui.SelectedItem
 import com.android.systemui.flags.Flags
+import kotlinx.coroutines.flow.Flow
 
 /** Stores user-selected preferred component. */
 interface SelectedComponentRepository {
 
-    /**
-     * Returns currently set preferred component, or null when nothing is set. Consider using
-     * [ControlsUiController.getPreferredSelectedItem] to get domain specific data
-     */
-    fun getSelectedComponent(): SelectedComponent?
+    /** Returns current set preferred component for the specified user. */
+    fun selectedComponentFlow(userHandle: UserHandle): Flow<SelectedComponent?>
 
-    /** Sets preferred component. Use [getSelectedComponent] to get current one */
+    /**
+     * Returns the current set preferred component for the specified user, or null when nothing is
+     * set. If no user is specified, the current user's preference is used. This method by default
+     * operates in the context of the current user unless another user is explicitly specified.
+     * Consider using [ControlsUiController.getPreferredSelectedItem] to get domain specific data.
+     */
+    fun getSelectedComponent(userHandle: UserHandle = UserHandle.CURRENT): SelectedComponent?
+
+    /**
+     * Sets the preferred component for the current user. Use [getSelectedComponent] to retrieve the
+     * currently set preferred component. This method applies to the current user's settings.
+     */
     fun setSelectedComponent(selectedComponent: SelectedComponent)
 
-    /** Clears current preferred component. [getSelectedComponent] will return null afterwards */
+    /**
+     * Clears the current user's preferred component. After this operation, [getSelectedComponent]
+     * will return null for the current user.
+     */
     fun removeSelectedComponent()
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
index c9edd4a..0baa81a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
@@ -19,13 +19,24 @@
 import android.content.ComponentName
 import android.content.Context
 import android.content.SharedPreferences
+import android.os.UserHandle
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.launch
 
+@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class SelectedComponentRepositoryImpl
 @Inject
@@ -33,6 +44,8 @@
     private val userFileManager: UserFileManager,
     private val userTracker: UserTracker,
     private val featureFlags: FeatureFlags,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    @Application private val applicationScope: CoroutineScope
 ) : SelectedComponentRepository {
 
     private companion object {
@@ -42,16 +55,42 @@
         const val SHOULD_ADD_DEFAULT_PANEL = "should_add_default_panel"
     }
 
-    private val sharedPreferences: SharedPreferences
-        get() =
-            userFileManager.getSharedPreferences(
-                fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
-                mode = Context.MODE_PRIVATE,
-                userId = userTracker.userId
-            )
+    private fun getSharedPreferencesForUser(userId: Int): SharedPreferences {
+        return userFileManager.getSharedPreferences(
+            fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+            mode = Context.MODE_PRIVATE,
+            userId = userId
+        )
+    }
 
-    override fun getSelectedComponent(): SelectedComponentRepository.SelectedComponent? {
-        with(sharedPreferences) {
+    override fun selectedComponentFlow(
+        userHandle: UserHandle
+    ): Flow<SelectedComponentRepository.SelectedComponent?> {
+        return conflatedCallbackFlow {
+                val sharedPreferencesByUserId = getSharedPreferencesForUser(userHandle.identifier)
+                val listener =
+                    SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
+                        applicationScope.launch(bgDispatcher) {
+                            if (key == PREF_COMPONENT) {
+                                trySend(getSelectedComponent(userHandle))
+                            }
+                        }
+                    }
+                sharedPreferencesByUserId.registerOnSharedPreferenceChangeListener(listener)
+                send(getSelectedComponent(userHandle))
+                awaitClose {
+                    sharedPreferencesByUserId.unregisterOnSharedPreferenceChangeListener(listener)
+                }
+            }
+            .flowOn(bgDispatcher)
+    }
+
+    override fun getSelectedComponent(
+        userHandle: UserHandle
+    ): SelectedComponentRepository.SelectedComponent? {
+        val userId =
+            if (userHandle == UserHandle.CURRENT) userTracker.userId else userHandle.identifier
+        with(getSharedPreferencesForUser(userId)) {
             val componentString = getString(PREF_COMPONENT, null) ?: return null
             return SelectedComponentRepository.SelectedComponent(
                 name = getString(PREF_STRUCTURE_OR_APP_NAME, "")!!,
@@ -64,7 +103,7 @@
     override fun setSelectedComponent(
         selectedComponent: SelectedComponentRepository.SelectedComponent
     ) {
-        sharedPreferences
+        getSharedPreferencesForUser(userTracker.userId)
             .edit()
             .putString(PREF_COMPONENT, selectedComponent.componentName?.flattenToString())
             .putString(PREF_STRUCTURE_OR_APP_NAME, selectedComponent.name)
@@ -73,7 +112,7 @@
     }
 
     override fun removeSelectedComponent() {
-        sharedPreferences
+        getSharedPreferencesForUser(userTracker.userId)
             .edit()
             .remove(PREF_COMPONENT)
             .remove(PREF_STRUCTURE_OR_APP_NAME)
@@ -82,9 +121,12 @@
     }
 
     override fun shouldAddDefaultComponent(): Boolean =
-        sharedPreferences.getBoolean(SHOULD_ADD_DEFAULT_PANEL, true)
+        getSharedPreferencesForUser(userTracker.userId).getBoolean(SHOULD_ADD_DEFAULT_PANEL, true)
 
     override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
-        sharedPreferences.edit().putBoolean(SHOULD_ADD_DEFAULT_PANEL, shouldAdd).apply()
+        getSharedPreferencesForUser(userTracker.userId)
+            .edit()
+            .putBoolean(SHOULD_ADD_DEFAULT_PANEL, shouldAdd)
+            .apply()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index b2d7052..6d9994f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -556,7 +556,7 @@
     @Provides
     @Singleton
     static SubscriptionManager provideSubscriptionManager(Context context) {
-        return context.getSystemService(SubscriptionManager.class);
+        return context.getSystemService(SubscriptionManager.class).createForAllUserProfiles();
     }
 
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
index 5c38264..3072f74 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
@@ -24,6 +24,7 @@
 import com.android.systemui.plugins.PluginsModule;
 import com.android.systemui.unfold.UnfoldTransitionModule;
 import com.android.systemui.util.concurrency.GlobalConcurrencyModule;
+import com.android.systemui.util.kotlin.GlobalCoroutinesModule;
 
 import dagger.Module;
 import dagger.Provides;
@@ -47,6 +48,7 @@
         AndroidInternalsModule.class,
         FrameworkServicesModule.class,
         GlobalConcurrencyModule.class,
+        GlobalCoroutinesModule.class,
         UnfoldTransitionModule.class,
         PluginsModule.class,
 })
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 50f861f..e9d1e94 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -63,6 +63,7 @@
 import com.android.systemui.statusbar.policy.SensorPrivacyController;
 import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
 import com.android.systemui.toast.ToastModule;
+import com.android.systemui.unfold.UnfoldTransitionModule;
 import com.android.systemui.volume.dagger.VolumeModule;
 import com.android.systemui.wallpapers.dagger.WallpaperModule;
 
@@ -107,6 +108,7 @@
         ShadeModule.class,
         StartCentralSurfacesModule.class,
         SceneContainerFrameworkModule.class,
+        UnfoldTransitionModule.Startables.class,
         ToastModule.class,
         VolumeModule.class,
         WallpaperModule.class
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 87a736d..95233f7 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -24,6 +24,8 @@
 import com.android.systemui.back.domain.interactor.BackActionInteractor
 import com.android.systemui.biometrics.BiometricNotificationService
 import com.android.systemui.clipboardoverlay.ClipboardListener
+import com.android.systemui.communal.log.CommunalLoggerStartable
+import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable
 import com.android.systemui.controls.dagger.StartControlsStartableModule
 import com.android.systemui.dagger.qualifiers.PerUser
 import com.android.systemui.dreams.AssistantAttentionMonitor
@@ -318,4 +320,16 @@
     @IntoMap
     @ClassKey(KeyguardDismissBinder::class)
     abstract fun bindKeyguardDismissBinder(impl: KeyguardDismissBinder): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(CommunalLoggerStartable::class)
+    abstract fun bindCommunalLoggerStartable(impl: CommunalLoggerStartable): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(CommunalAppWidgetHostStartable::class)
+    abstract fun bindCommunalAppWidgetHostStartable(
+        impl: CommunalAppWidgetHostStartable
+    ): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 92300ef..2587e2d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -66,6 +66,7 @@
 import com.android.systemui.log.table.TableLogBuffer;
 import com.android.systemui.mediaprojection.appselector.MediaProjectionModule;
 import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherModule;
+import com.android.systemui.model.SceneContainerPlugin;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.motiontool.MotionToolModule;
 import com.android.systemui.navigationbar.NavigationBarComponent;
@@ -133,7 +134,7 @@
 import com.android.systemui.util.EventLogModule;
 import com.android.systemui.util.concurrency.SysUIConcurrencyModule;
 import com.android.systemui.util.dagger.UtilModule;
-import com.android.systemui.util.kotlin.CoroutinesModule;
+import com.android.systemui.util.kotlin.SysUICoroutinesModule;
 import com.android.systemui.util.reference.ReferenceModule;
 import com.android.systemui.util.sensors.SensorModule;
 import com.android.systemui.util.settings.SettingsUtilModule;
@@ -182,7 +183,6 @@
         ConfigurationControllerModule.class,
         ConnectivityModule.class,
         ControlsModule.class,
-        CoroutinesModule.class,
         DemoModeModule.class,
         DeviceEntryModule.class,
         DisableFlagsModule.class,
@@ -228,6 +228,7 @@
         StatusBarWindowModule.class,
         SystemPropertiesFlagsModule.class,
         SysUIConcurrencyModule.class,
+        SysUICoroutinesModule.class,
         SysUIUnfoldModule.class,
         TelephonyRepositoryModule.class,
         TemporaryDisplayModule.class,
@@ -268,8 +269,11 @@
 
     @SysUISingleton
     @Provides
-    static SysUiState provideSysUiState(DisplayTracker displayTracker, DumpManager dumpManager) {
-        final SysUiState state = new SysUiState(displayTracker);
+    static SysUiState provideSysUiState(
+            DisplayTracker displayTracker,
+            DumpManager dumpManager,
+            SceneContainerPlugin sceneContainerPlugin) {
+        final SysUiState state = new SysUiState(displayTracker, sceneContainerPlugin);
         dumpManager.registerDumpable(state);
         return state;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index 7a70c4a..cf7d601 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -40,8 +40,7 @@
 import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
 import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.BiometricType
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
@@ -63,10 +62,6 @@
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.UserRepository
 import com.google.errorprone.annotations.CompileTimeConstant
-import java.io.PrintWriter
-import java.util.Arrays
-import java.util.stream.Collectors
-import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
@@ -88,6 +83,10 @@
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
+import java.io.PrintWriter
+import java.util.Arrays
+import java.util.stream.Collectors
+import javax.inject.Inject
 
 /**
  * API to run face authentication and detection for device entry / on keyguard (as opposed to the
@@ -165,7 +164,6 @@
     @FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val displayStateInteractor: DisplayStateInteractor,
-    private val featureFlags: FeatureFlags,
     dumpManager: DumpManager,
 ) : DeviceEntryFaceAuthRepository, Dumpable {
     private var authCancellationSignal: CancellationSignal? = null
@@ -315,7 +313,7 @@
         // or device starts going to sleep.
         merge(
                 powerInteractor.isAsleep,
-                if (featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                if (KeyguardWmStateRefactor.isEnabled) {
                     keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE)
                 } else {
                     keyguardRepository.keyguardDoneAnimationsFinished.map { true }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
index 08e8c2d..8283438 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -8,35 +8,26 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
-import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.user.data.repository.UserRepository
-import com.android.systemui.util.kotlin.sample
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.withContext
 
 /** Interface for classes that can access device-entry-related application state. */
 interface DeviceEntryRepository {
-    /** Whether the device is immediately entering the device after a biometric unlock. */
-    val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource>
-
     /**
      * Whether the device is unlocked.
      *
@@ -85,12 +76,6 @@
     keyguardStateController: KeyguardStateController,
     keyguardRepository: KeyguardRepository,
 ) : DeviceEntryRepository {
-    override val enteringDeviceFromBiometricUnlock =
-        keyguardRepository.biometricUnlockState
-            .filter { BiometricUnlockModel.dismissesKeyguard(it) }
-            .sample(
-                keyguardRepository.biometricUnlockSource.filterNotNull(),
-            )
 
     private val _isUnlocked = MutableStateFlow(false)
 
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt
new file mode 100644
index 0000000..337fe1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/** Business logic for device entry auth ripple interactions. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class AuthRippleInteractor
+@Inject
+constructor(
+    deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
+    deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+) {
+    private val showUnlockRippleFromDeviceEntryIcon: Flow<BiometricUnlockSource> =
+        deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfpsSupported ->
+            if (isUdfpsSupported) {
+                deviceEntrySourceInteractor.deviceEntryFromDeviceEntryIcon.map {
+                    BiometricUnlockSource.FINGERPRINT_SENSOR
+                }
+            } else {
+                emptyFlow()
+            }
+        }
+
+    private val showUnlockRippleFromBiometricUnlock: Flow<BiometricUnlockSource> =
+        deviceEntrySourceInteractor.deviceEntryFromBiometricSource
+    val showUnlockRipple: Flow<BiometricUnlockSource> =
+        merge(
+            showUnlockRippleFromDeviceEntryIcon,
+            showUnlockRippleFromBiometricUnlock,
+        )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
index 649a971..782bce4 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
@@ -46,7 +46,7 @@
 class DeviceEntryHapticsInteractor
 @Inject
 constructor(
-    deviceEntryInteractor: DeviceEntryInteractor,
+    deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
     deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
     deviceEntryBiometricAuthInteractor: DeviceEntryBiometricAuthInteractor,
     fingerprintPropertyRepository: FingerprintPropertyRepository,
@@ -80,7 +80,7 @@
             }
 
     val playSuccessHaptic: Flow<Unit> =
-        deviceEntryInteractor.enteringDeviceFromBiometricUnlock
+        deviceEntrySourceInteractor.deviceEntryFromBiometricSource
             .sample(
                 combine(
                     powerButtonSideFpsEnrolled,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 2680328..73389cb 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -23,7 +23,6 @@
 import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
 import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneKey
@@ -31,7 +30,6 @@
 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.collectLatest
@@ -55,16 +53,14 @@
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
-    repository: DeviceEntryRepository,
+    private val repository: DeviceEntryRepository,
     private val authenticationInteractor: AuthenticationInteractor,
     private val sceneInteractor: SceneInteractor,
     deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
     trustRepository: TrustRepository,
     flags: SceneContainerFlags,
+    deviceUnlockedInteractor: DeviceUnlockedInteractor,
 ) {
-    val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource> =
-        repository.enteringDeviceFromBiometricUnlock
-
     /**
      * Whether the device is unlocked.
      *
@@ -74,19 +70,7 @@
      * of this flow will always be `true`, even if the lockscreen is showing and still needs to be
      * dismissed by the user to proceed.
      */
-    val isUnlocked: StateFlow<Boolean> =
-        combine(
-                repository.isUnlocked,
-                authenticationInteractor.authenticationMethod,
-            ) { isUnlocked, authenticationMethod ->
-                (!authenticationMethod.isSecure || isUnlocked) &&
-                    authenticationMethod != AuthenticationMethodModel.Sim
-            }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.Eagerly,
-                initialValue = false,
-            )
+    val isUnlocked: StateFlow<Boolean> = deviceUnlockedInteractor.isDeviceUnlocked
 
     /**
      * Whether the device has been entered (i.e. the lockscreen has been dismissed, by any method).
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt
new file mode 100644
index 0000000..d4f76a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+/**
+ * Hosts application business logic related to the source of the user entering the device. Note: The
+ * source of the user entering the device isn't equivalent to the reason the device is unlocked.
+ *
+ * For example, the user successfully enters the device when they dismiss the lockscreen via a
+ * bypass biometric or, if the device is already unlocked, by triggering an affordance that
+ * dismisses the lockscreen.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class DeviceEntrySourceInteractor
+@Inject
+constructor(
+    keyguardInteractor: KeyguardInteractor,
+) {
+    val deviceEntryFromBiometricSource: Flow<BiometricUnlockSource> =
+        keyguardInteractor.biometricUnlockState
+            .filter { BiometricUnlockModel.dismissesKeyguard(it) }
+            .sample(
+                keyguardInteractor.biometricUnlockSource.filterNotNull(),
+            )
+
+    private val attemptEnterDeviceFromDeviceEntryIcon: MutableSharedFlow<Unit> = MutableSharedFlow()
+    val deviceEntryFromDeviceEntryIcon: Flow<Unit> =
+        attemptEnterDeviceFromDeviceEntryIcon
+            .sample(keyguardInteractor.isKeyguardDismissible)
+            .filter { it } // only send events if the keyguard is dismissible
+            .map {} // map to Unit
+
+    suspend fun attemptEnterDeviceFromDeviceEntryIcon() {
+        attemptEnterDeviceFromDeviceEntryIcon.emit(Unit)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
new file mode 100644
index 0000000..b0495fb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
+
+@SysUISingleton
+class DeviceUnlockedInteractor
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    authenticationInteractor: AuthenticationInteractor,
+    deviceEntryRepository: DeviceEntryRepository,
+) {
+
+    /**
+     * Whether the device is unlocked.
+     *
+     * A device that is not yet unlocked requires unlocking by completing an authentication
+     * challenge according to the current authentication method, unless in cases when the current
+     * authentication method is not "secure" (for example, None and Swipe); in such cases, the value
+     * of this flow will always be `true`, even if the lockscreen is showing and still needs to be
+     * dismissed by the user to proceed.
+     */
+    val isDeviceUnlocked: StateFlow<Boolean> =
+        combine(
+                deviceEntryRepository.isUnlocked,
+                authenticationInteractor.authenticationMethod,
+            ) { isUnlocked, authenticationMethod ->
+                (!authenticationMethod.isSecure || isUnlocked) &&
+                    authenticationMethod != AuthenticationMethodModel.Sim
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = false,
+            )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
index db0c3c6..0fd6887 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
@@ -18,10 +18,7 @@
 
 import android.content.Context;
 import android.hardware.Sensor;
-import android.os.Handler;
 
-import com.android.systemui.res.R;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.doze.DozeAuthRemover;
 import com.android.systemui.doze.DozeBrightnessHostForwarder;
@@ -40,6 +37,7 @@
 import com.android.systemui.doze.DozeTriggers;
 import com.android.systemui.doze.DozeUi;
 import com.android.systemui.doze.DozeWallpaperState;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -75,9 +73,8 @@
 
     @Provides
     @DozeScope
-    static WakeLock providesDozeWakeLock(DelayedWakeLock.Builder delayedWakeLockBuilder,
-            @Main Handler handler) {
-        return delayedWakeLockBuilder.setHandler(handler).setTag("Doze").build();
+    static WakeLock providesDozeWakeLock(DelayedWakeLock.Factory delayedWakeLockFactory) {
+        return delayedWakeLockFactory.create("Doze");
     }
 
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index 4e4b79c..7f3b5eb 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -155,12 +155,12 @@
                         return true;
                     }
 
-                    // Don't set expansion if the user doesn't have a pin/password set so that no
-                    // animations are played we're not transitioning to the bouncer.
-                    if (!mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
-                        // Return false so the gesture is not consumed, allowing the dream to wake
-                        // if it wants instead of doing nothing.
-                        return false;
+                    // If scrolling up and keyguard is not locked, dismiss the dream since there's
+                    // no bouncer to show.
+                    if (e1.getY() > e2.getY()
+                            && !mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
+                        mCentralSurfaces.get().awakenDreams();
+                        return true;
                     }
 
                     // For consistency, we adopt the expansion definition found in the
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
new file mode 100644
index 0000000..c9b56a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.touch;
+
+import static com.android.systemui.dreams.touch.dagger.ShadeModule.COMMUNAL_GESTURE_INITIATION_WIDTH;
+
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/** {@link DreamTouchHandler} responsible for handling touches to open communal hub. **/
+public class CommunalTouchHandler implements DreamTouchHandler {
+    private final int mInitiationWidth;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
+    private final Optional<CentralSurfaces> mCentralSurfaces;
+
+    @Inject
+    public CommunalTouchHandler(
+            Optional<CentralSurfaces> centralSurfaces,
+            NotificationShadeWindowController notificationShadeWindowController,
+            @Named(COMMUNAL_GESTURE_INITIATION_WIDTH) int initiationWidth) {
+        mInitiationWidth = initiationWidth;
+        mCentralSurfaces = centralSurfaces;
+        mNotificationShadeWindowController = notificationShadeWindowController;
+    }
+
+    @Override
+    public void onSessionStart(TouchSession session) {
+        mCentralSurfaces.ifPresent(surfaces -> handleSessionStart(surfaces, session));
+    }
+
+    @Override
+    public void getTouchInitiationRegion(Rect bounds, Region region) {
+        final Rect outBounds = new Rect(bounds);
+        outBounds.inset(outBounds.width() - mInitiationWidth, 0, 0, 0);
+        region.op(outBounds, Region.Op.UNION);
+    }
+
+    private void handleSessionStart(CentralSurfaces surfaces, TouchSession session) {
+        // Force the notification shade window open (otherwise the hub won't show while swiping).
+        mNotificationShadeWindowController.setForcePluginOpen(true, this);
+
+        session.registerInputListener(ev -> {
+            surfaces.handleDreamTouch((MotionEvent) ev);
+            if (ev != null && ((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
+                var unused = session.pop();
+            }
+        });
+
+        session.registerGestureListener(new GestureDetector.SimpleOnGestureListener() {
+            @Override
+            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
+                    float distanceY) {
+                return true;
+            }
+
+            @Override
+            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+                    float velocityY) {
+                return true;
+            }
+        });
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java
index 94fe4bd..0f08d37 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java
@@ -18,11 +18,13 @@
 
 import android.content.res.Resources;
 
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.touch.CommunalTouchHandler;
 import com.android.systemui.dreams.touch.DreamTouchHandler;
 import com.android.systemui.dreams.touch.ShadeTouchHandler;
+import com.android.systemui.res.R;
 
+import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 import dagger.multibindings.IntoSet;
@@ -33,7 +35,7 @@
  * Dependencies for swipe down to notification over dream.
  */
 @Module
-public class ShadeModule {
+public abstract class ShadeModule {
     /**
      * The height, defined in pixels, of the gesture initiation region at the top of the screen for
      * swiping down notifications.
@@ -41,15 +43,22 @@
     public static final String NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT =
             "notification_shade_gesture_initiation_height";
 
+    /** Width of swipe gesture edge to show communal hub. */
+    public static final String COMMUNAL_GESTURE_INITIATION_WIDTH =
+            "communal_gesture_initiation_width";
+
     /**
      * Provides {@link ShadeTouchHandler} to handle notification swipe down over dream.
      */
-    @Provides
+    @Binds
     @IntoSet
-    public static DreamTouchHandler providesNotificationShadeTouchHandler(
-            ShadeTouchHandler touchHandler) {
-        return touchHandler;
-    }
+    public abstract DreamTouchHandler providesNotificationShadeTouchHandler(
+            ShadeTouchHandler touchHandler);
+
+    /** Provides {@link CommunalTouchHandler}. */
+    @Binds
+    @IntoSet
+    public abstract DreamTouchHandler bindCommunalTouchHandler(CommunalTouchHandler touchHandler);
 
     /**
      * Provides the height of the gesture area for notification swipe down.
@@ -59,4 +68,13 @@
     public static int providesNotificationShadeGestureRegionHeight(@Main Resources resources) {
         return resources.getDimensionPixelSize(R.dimen.dream_overlay_status_bar_height);
     }
+
+    /**
+     * Provides the width of the gesture area for swiping open communal hub.
+     */
+    @Provides
+    @Named(COMMUNAL_GESTURE_INITIATION_WIDTH)
+    public static int providesCommunalGestureInitiationWidth(@Main Resources resources) {
+        return resources.getDimensionPixelSize(R.dimen.communal_gesture_initiation_width);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 699532c..c69c9ef 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -44,11 +44,11 @@
     // 100 - notification
     // TODO(b/297792660): Tracking Bug
     @JvmField val UNCLEARED_TRANSIENT_HUN_FIX =
-        unreleasedFlag("uncleared_transient_hun_fix", teamfood = true)
+        releasedFlag("uncleared_transient_hun_fix")
 
     // TODO(b/298308067): Tracking Bug
     @JvmField val SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX =
-        unreleasedFlag("swipe_uncleared_transient_view_fix", teamfood = true)
+        releasedFlag("swipe_uncleared_transient_view_fix")
 
     // TODO(b/254512751): Tracking Bug
     val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
@@ -102,12 +102,6 @@
             default = true
         )
 
-    /** Only notify group expansion listeners when a change happens. */
-    // TODO(b/292213543): Tracking Bug
-    @JvmField
-    val NOTIFICATION_GROUP_EXPANSION_CHANGE =
-            releasedFlag("notification_group_expansion_change")
-
     // TODO(b/301955929)
     @JvmField
     val NOTIF_LS_BACKGROUND_THREAD =
@@ -122,11 +116,6 @@
     val NEW_UNLOCK_SWIPE_ANIMATION = releasedFlag("new_unlock_swipe_animation")
     val CHARGING_RIPPLE = resourceBooleanFlag(R.bool.flag_charging_ripple, "charging_ripple")
 
-    // TODO(b/254512281): Tracking Bug
-    @JvmField
-    val BOUNCER_USER_SWITCHER =
-        resourceBooleanFlag(R.bool.config_enableBouncerUserSwitcher, "bouncer_user_switcher")
-
     // TODO(b/254512676): Tracking Bug
     @JvmField
     val LOCKSCREEN_CUSTOM_CLOCKS =
@@ -231,19 +220,6 @@
     @JvmField
     val WALLPAPER_PICKER_PREVIEW_ANIMATION = releasedFlag("wallpaper_picker_preview_animation")
 
-    /**
-     * TODO(b/278086361): Tracking bug
-     * Complete rewrite of the interactions between System UI and Window Manager involving keyguard
-     * state. When enabled, calls to ActivityTaskManagerService from System UI will exclusively
-     * occur from [WmLockscreenVisibilityManager] rather than the legacy KeyguardViewMediator.
-     *
-     * This flag is under development; some types of unlock may not animate properly if you enable
-     * it.
-     */
-    @JvmField
-    val KEYGUARD_WM_STATE_REFACTOR: UnreleasedFlag =
-            unreleasedFlag("keyguard_wm_state_refactor")
-
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
     @JvmField val POWER_MENU_LITE = releasedFlag("power_menu_lite")
@@ -353,12 +329,6 @@
     // TODO(b/254512673): Tracking Bug
     @JvmField val DREAM_MEDIA_TAP_TO_OPEN = unreleasedFlag("dream_media_tap_to_open")
 
-    // TODO(b/254513168): Tracking Bug
-    @JvmField val UMO_SURFACE_RIPPLE = releasedFlag("umo_surface_ripple")
-
-    // TODO(b/261734857): Tracking Bug
-    @JvmField val UMO_TURBULENCE_NOISE = releasedFlag("umo_turbulence_noise")
-
     // TODO(b/263272731): Tracking Bug
     val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = releasedFlag("media_ttt_receiver_success_ripple")
 
@@ -377,9 +347,6 @@
     // 1000 - dock
     val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag("simulate_dock_through_charging")
 
-    // TODO(b/254512758): Tracking Bug
-    @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag("rounded_box_ripple")
-
     // TODO(b/273509374): Tracking Bug
     @JvmField
     val ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS =
@@ -490,6 +457,13 @@
     // TODO(b/283300105): Tracking Bug
     @JvmField val SCENE_CONTAINER_ENABLED = false
 
+    /**
+     * Whether the compose bouncer is enabled. This ensures ProGuard can
+     * remove unused code from our APK at compile time.
+     */
+    // TODO(b/280877228): Tracking Bug
+    @JvmField val COMPOSE_BOUNCER_ENABLED = false
+
     // 1900
     @JvmField val NOTE_TASKS = releasedFlag("keycode_flag")
 
@@ -581,9 +555,6 @@
     @JvmField
     val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog")
 
-    // TODO(b/289573946): Tracking Bug
-    @JvmField val PRECOMPUTED_TEXT = releasedFlag("precomputed_text")
-
     // TODO(b/302087895): Tracking Bug
     @JvmField val CALL_LAYOUT_ASYNC_SET_DATA =
             unreleasedFlag("call_layout_async_set_data", teamfood = true)
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 3de9e68..a95ddb5 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -2437,6 +2437,7 @@
                     return true;
                 }
             });
+            mGlobalActionsLayout.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
             mGlobalActionsLayout.setRotationListener(this::onRotate);
             mGlobalActionsLayout.setAdapter(mAdapter);
             mContainer = findViewById(com.android.systemui.res.R.id.global_actions_container);
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
new file mode 100644
index 0000000..58fb6a9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.slider
+
+import android.view.MotionEvent
+import android.view.VelocityTracker
+import android.widget.SeekBar
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.time.SystemClock
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+/**
+ * A plugin added to a manager of a [android.widget.SeekBar] that adds dynamic haptic feedback.
+ *
+ * A [SeekableSliderEventProducer] is used as the producer of slider events, a
+ * [SliderHapticFeedbackProvider] is used as the listener of slider states to play haptic feedback
+ * depending on the state, and a [SeekableSliderTracker] is used as the state machine handler that
+ * tracks and manipulates the slider state.
+ */
+class SeekableSliderHapticPlugin
+@JvmOverloads
+constructor(
+    vibratorHelper: VibratorHelper,
+    systemClock: SystemClock,
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    @Application private val applicationScope: CoroutineScope,
+    sliderHapticFeedbackConfig: SliderHapticFeedbackConfig = SliderHapticFeedbackConfig(),
+    sliderTrackerConfig: SeekableSliderTrackerConfig = SeekableSliderTrackerConfig(),
+) {
+
+    private val velocityTracker = VelocityTracker.obtain()
+
+    private val sliderEventProducer = SeekableSliderEventProducer()
+
+    private val sliderHapticFeedbackProvider =
+        SliderHapticFeedbackProvider(
+            vibratorHelper,
+            velocityTracker,
+            sliderHapticFeedbackConfig,
+            systemClock,
+        )
+
+    private val sliderTracker =
+        SeekableSliderTracker(
+            sliderHapticFeedbackProvider,
+            sliderEventProducer,
+            mainDispatcher,
+            sliderTrackerConfig,
+        )
+
+    val isTracking: Boolean
+        get() = sliderTracker.isTracking
+
+    val trackerState: SliderState
+        get() = sliderTracker.currentState
+
+    /**
+     * A waiting [Job] for a timer that estimates the key-up event when a key-down event is
+     * received.
+     *
+     * This is useful for the cases where the slider is being operated by an external key, but the
+     * release of the key is not easily accessible (e.g., the volume keys)
+     */
+    private var keyUpJob: Job? = null
+
+    @VisibleForTesting
+    val isKeyUpTimerWaiting: Boolean
+        get() = keyUpJob != null && keyUpJob?.isActive == true
+
+    /**
+     * Start the plugin.
+     *
+     * This starts the tracking of slider states, events and triggering of haptic feedback.
+     */
+    fun start() {
+        if (!isTracking) {
+            sliderTracker.startTracking()
+        }
+    }
+
+    /**
+     * Stop the plugin
+     *
+     * This stops the tracking of slider states, events and triggers of haptic feedback.
+     */
+    fun stop() = sliderTracker.stopTracking()
+
+    /** React to a touch event */
+    fun onTouchEvent(event: MotionEvent?) {
+        when (event?.actionMasked) {
+            MotionEvent.ACTION_UP,
+            MotionEvent.ACTION_CANCEL -> velocityTracker.clear()
+            MotionEvent.ACTION_DOWN,
+            MotionEvent.ACTION_MOVE -> velocityTracker.addMovement(event)
+        }
+    }
+
+    /** onStartTrackingTouch event from the slider's [android.widget.SeekBar] */
+    fun onStartTrackingTouch(seekBar: SeekBar) {
+        if (isTracking) {
+            sliderEventProducer.onStartTrackingTouch(seekBar)
+        }
+    }
+
+    /** onProgressChanged event from the slider's [android.widget.SeekBar] */
+    fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
+        if (isTracking) {
+            sliderEventProducer.onProgressChanged(seekBar, progress, fromUser)
+        }
+    }
+
+    /** onStopTrackingTouch event from the slider's [android.widget.SeekBar] */
+    fun onStopTrackingTouch(seekBar: SeekBar) {
+        if (isTracking) {
+            sliderEventProducer.onStopTrackingTouch(seekBar)
+        }
+    }
+
+    /** onArrowUp event recorded */
+    fun onArrowUp() {
+        if (isTracking) {
+            sliderEventProducer.onArrowUp()
+        }
+    }
+
+    /**
+     * An external key was pressed (e.g., a volume key).
+     *
+     * This event is used to estimate the key-up event based on by running a timer as a waiting
+     * coroutine in the [keyUpTimerScope]. A key-up event in a slider corresponds to an onArrowUp
+     * event. Therefore, [onArrowUp] must be called after the timeout.
+     */
+    fun onKeyDown() {
+        if (!isTracking) return
+
+        if (isKeyUpTimerWaiting) {
+            // Cancel the ongoing wait
+            keyUpJob?.cancel()
+        }
+        keyUpJob =
+            applicationScope.launch {
+                delay(KEY_UP_TIMEOUT)
+                onArrowUp()
+            }
+    }
+
+    companion object {
+        const val KEY_UP_TIMEOUT = 100L
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java b/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java
deleted file mode 100644
index 5deea9b..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyboard;
-
-import android.content.Context;
-import android.view.WindowManager;
-
-import com.android.systemui.statusbar.phone.SystemUIDialog;
-
-public class BluetoothDialog extends SystemUIDialog {
-
-    public BluetoothDialog(Context context) {
-        super(context);
-
-        getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
-        setShowForAllUsers(true);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialogDelegate.java
new file mode 100644
index 0000000..98642d7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialogDelegate.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard;
+
+import android.view.WindowManager;
+
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+
+import javax.inject.Inject;
+
+public class BluetoothDialogDelegate implements SystemUIDialog.Delegate{
+
+    private final SystemUIDialog.Factory mSystemUIDialogFactory;
+    @Inject
+    public BluetoothDialogDelegate(SystemUIDialog.Factory systemUIDialogFactory) {
+        mSystemUIDialogFactory = systemUIDialogFactory;
+    }
+
+    @Override
+    public SystemUIDialog createDialog() {
+        SystemUIDialog dialog = mSystemUIDialogFactory.create(this);
+        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+        dialog.setShowForAllUsers(true);
+        return dialog;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt
index 496c64e..c6fb4f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt
@@ -19,6 +19,8 @@
 
 import com.android.systemui.keyboard.data.repository.KeyboardRepository
 import com.android.systemui.keyboard.data.repository.KeyboardRepositoryImpl
+import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepository
+import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepositoryImpl
 import dagger.Binds
 import dagger.Module
 
@@ -27,4 +29,9 @@
 
     @Binds
     abstract fun bindKeyboardRepository(repository: KeyboardRepositoryImpl): KeyboardRepository
+
+    @Binds
+    abstract fun bindStickyKeysRepository(
+        repository: StickyKeysRepositoryImpl
+    ): StickyKeysRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index 1cdbe6f..17e3ca6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -52,6 +52,7 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.util.settings.SecureSettings;
 
 import java.io.PrintWriter;
@@ -109,6 +110,7 @@
 
     private final Provider<LocalBluetoothManager> mBluetoothManagerProvider;
     private final SecureSettings mSecureSettings;
+    private final BluetoothDialogDelegate mBluetoothDialogDelegate;
 
     private boolean mEnabled;
     private String mKeyboardName;
@@ -121,16 +123,20 @@
     private int mInTabletMode = InputManager.SWITCH_STATE_UNKNOWN;
     private int mScanAttempt = 0;
     private ScanCallback mScanCallback;
-    private BluetoothDialog mDialog;
+    private SystemUIDialog mDialog;
 
     private int mState;
 
     @Inject
-    public KeyboardUI(Context context, Provider<LocalBluetoothManager> bluetoothManagerProvider,
-            SecureSettings secureSettings) {
+    public KeyboardUI(
+            Context context,
+            Provider<LocalBluetoothManager> bluetoothManagerProvider,
+            SecureSettings secureSettings,
+            BluetoothDialogDelegate bluetoothDialogDelegate) {
         mContext = context;
         this.mBluetoothManagerProvider = bluetoothManagerProvider;
         mSecureSettings = secureSettings;
+        mBluetoothDialogDelegate = bluetoothDialogDelegate;
     }
 
     @Override
@@ -437,7 +443,7 @@
                             new BluetoothDialogClickListener();
                     DialogInterface.OnDismissListener dismissListener =
                             new BluetoothDialogDismissListener();
-                    mDialog = new BluetoothDialog(mContext);
+                    mDialog = mBluetoothDialogDelegate.createDialog();
                     mDialog.setTitle(R.string.enable_bluetooth_title);
                     mDialog.setMessage(R.string.enable_bluetooth_message);
                     mDialog.setPositiveButton(
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
index d078688..26e83a3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
@@ -17,11 +17,14 @@
 
 package com.android.systemui.keyboard
 
+import com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyboard.backlight.ui.KeyboardBacklightDialogCoordinator
+import com.android.systemui.keyboard.stickykeys.ui.StickyKeysIndicatorCoordinator
+import dagger.Lazy
 import javax.inject.Inject
 
 /** A [CoreStartable] that launches components interested in physical keyboard interaction. */
@@ -29,12 +32,16 @@
 class PhysicalKeyboardCoreStartable
 @Inject
 constructor(
-    private val keyboardBacklightDialogCoordinator: KeyboardBacklightDialogCoordinator,
+    private val keyboardBacklightDialogCoordinator: Lazy<KeyboardBacklightDialogCoordinator>,
+    private val stickyKeysIndicatorCoordinator: Lazy<StickyKeysIndicatorCoordinator>,
     private val featureFlags: FeatureFlags,
 ) : CoreStartable {
     override fun start() {
         if (featureFlags.isEnabled(Flags.KEYBOARD_BACKLIGHT_INDICATOR)) {
-            keyboardBacklightDialogCoordinator.startListening()
+            keyboardBacklightDialogCoordinator.get().startListening()
+        }
+        if (keyboardA11yStickyKeysFlag()) {
+            stickyKeysIndicatorCoordinator.get().startListening()
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt
new file mode 100644
index 0000000..5ef5ef1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.stickykeys
+
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.KeyboardLog
+import javax.inject.Inject
+
+private const val TAG = "stickyKeys"
+
+class StickyKeysLogger @Inject constructor(@KeyboardLog private val buffer: LogBuffer) {
+    fun logNewStickyKeysReceived(stickyKeys: Map<ModifierKey, Locked>) {
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            { str1 = stickyKeys.toString() },
+            { "new sticky keys state received: $str1" }
+        )
+    }
+
+    fun logNewUiState(stickyKeys: Map<ModifierKey, Locked>) {
+        buffer.log(
+            TAG,
+            LogLevel.INFO,
+            { str1 = stickyKeys.toString() },
+            { "new sticky keys state received: $str1" }
+        )
+    }
+
+    fun logNewSettingValue(enabled: Boolean) {
+        buffer.log(
+            TAG,
+            LogLevel.INFO,
+            { bool1 = enabled },
+            { "sticky key setting changed, new state: ${if (bool1) "enabled" else "disabled"}" }
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
new file mode 100644
index 0000000..ec29bd6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.stickykeys.data.repository
+
+import android.hardware.input.InputManager
+import android.hardware.input.InputManager.StickyModifierStateListener
+import android.hardware.input.StickyModifierState
+import android.provider.Settings
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import javax.inject.Inject
+
+interface StickyKeysRepository {
+    val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>>
+    val settingEnabled: Flow<Boolean>
+}
+
+@SysUISingleton
+@OptIn(ExperimentalCoroutinesApi::class)
+class StickyKeysRepositoryImpl
+@Inject
+constructor(
+    private val inputManager: InputManager,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val secureSettings: SecureSettings,
+    userRepository: UserRepository,
+    private val stickyKeysLogger: StickyKeysLogger,
+) : StickyKeysRepository {
+
+    override val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>> =
+        conflatedCallbackFlow {
+                val listener = StickyModifierStateListener { stickyModifierState ->
+                    trySendWithFailureLogging(stickyModifierState, TAG)
+                }
+                // after registering, InputManager calls listener with the current value
+                inputManager.registerStickyModifierStateListener(Runnable::run, listener)
+                awaitClose { inputManager.unregisterStickyModifierStateListener(listener) }
+            }
+            .map { toStickyKeysMap(it) }
+            .onEach { stickyKeysLogger.logNewStickyKeysReceived(it) }
+            .flowOn(backgroundDispatcher)
+
+    override val settingEnabled: Flow<Boolean> =
+        userRepository.selectedUserInfo
+            .flatMapLatest { stickyKeySettingObserver(it.id) }
+            .flowOn(backgroundDispatcher)
+
+    private fun stickyKeySettingObserver(userId: Int): Flow<Boolean> {
+        return secureSettings
+            .observerFlow(userId, SETTING_KEY)
+            .onStart { emit(Unit) }
+            .map { isSettingEnabledForCurrentUser(userId) }
+            .distinctUntilChanged()
+            .onEach { stickyKeysLogger.logNewSettingValue(it) }
+    }
+
+    private fun isSettingEnabledForCurrentUser(userId: Int) =
+        secureSettings.getIntForUser(
+            /* name= */ SETTING_KEY,
+            /* default= */ 0,
+            /* userHandle= */ userId
+        ) != 0
+
+    private fun toStickyKeysMap(state: StickyModifierState): LinkedHashMap<ModifierKey, Locked> {
+        val keys = linkedMapOf<ModifierKey, Locked>()
+        state.apply {
+            if (isAltGrModifierOn) keys[ALT_GR] = Locked(false)
+            if (isAltGrModifierLocked) keys[ALT_GR] = Locked(true)
+            if (isAltModifierOn) keys[ALT] = Locked(false)
+            if (isAltModifierLocked) keys[ALT] = Locked(true)
+            if (isCtrlModifierOn) keys[CTRL] = Locked(false)
+            if (isCtrlModifierLocked) keys[CTRL] = Locked(true)
+            if (isMetaModifierOn) keys[META] = Locked(false)
+            if (isMetaModifierLocked) keys[META] = Locked(true)
+            if (isShiftModifierOn) keys[SHIFT] = Locked(false)
+            if (isShiftModifierLocked) keys[SHIFT] = Locked(true)
+        }
+        return keys
+    }
+
+    companion object {
+        const val TAG = "StickyKeysRepositoryImpl"
+        const val SETTING_KEY = Settings.Secure.ACCESSIBILITY_STICKY_KEYS
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
new file mode 100644
index 0000000..d5f082a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.stickykeys.shared.model
+
+@JvmInline
+value class Locked(val locked: Boolean)
+
+enum class ModifierKey(val text: String) {
+    ALT("ALT LEFT"),
+    ALT_GR("ALT RIGHT"),
+    CTRL("CTRL"),
+    META("META"),
+    SHIFT("SHIFT"),
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
new file mode 100644
index 0000000..b68551b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.stickykeys.ui
+
+import android.app.Dialog
+import android.util.Log
+import android.view.Gravity
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.Window
+import android.view.WindowManager
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
+import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@SysUISingleton
+class StickyKeysIndicatorCoordinator
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    private val dialogFactory: SystemUIDialogFactory,
+    private val viewModel: StickyKeysIndicatorViewModel,
+    private val stickyKeysLogger: StickyKeysLogger,
+) {
+
+    private var dialog: Dialog? = null
+
+    fun startListening() {
+        // this check needs to be moved to PhysicalKeyboardCoreStartable
+        if (!ComposeFacade.isComposeAvailable()) {
+            Log.e("StickyKeysIndicatorCoordinator", "Compose is required for this UI")
+            return
+        }
+        applicationScope.launch {
+            viewModel.indicatorContent.collect { stickyKeys ->
+                stickyKeysLogger.logNewUiState(stickyKeys)
+                if (stickyKeys.isEmpty()) {
+                    dialog?.dismiss()
+                    dialog = null
+                } else if (dialog == null) {
+                    dialog = ComposeFacade.createStickyKeysDialog(dialogFactory, viewModel).apply {
+                        setCanceledOnTouchOutside(false)
+                        window?.setAttributes()
+                        show()
+                    }
+                }
+            }
+        }
+    }
+
+    private fun Window.setAttributes() {
+        setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+        addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
+        clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+        setGravity(Gravity.TOP or Gravity.END)
+        attributes = WindowManager.LayoutParams().apply {
+            copyFrom(attributes)
+            width = WRAP_CONTENT
+            title = "StickyKeysIndicator"
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt
new file mode 100644
index 0000000..26eb706
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.stickykeys.ui.viewmodel
+
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyboard.data.repository.KeyboardRepository
+import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepository
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.stateIn
+import javax.inject.Inject
+
+class StickyKeysIndicatorViewModel
+@Inject
+constructor(
+    stickyKeysRepository: StickyKeysRepository,
+    keyboardRepository: KeyboardRepository,
+    @Application applicationScope: CoroutineScope,
+) {
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    val indicatorContent: Flow<Map<ModifierKey, Locked>> =
+        keyboardRepository.isAnyKeyboardConnected
+            .flatMapLatest { keyboardPresent ->
+                if (keyboardPresent) stickyKeysRepository.settingEnabled else flowOf(false)
+            }
+            .flatMapLatest { enabled ->
+                if (enabled) stickyKeysRepository.stickyKeys else flowOf(emptyMap())
+            }
+            .stateIn(applicationScope, SharingStarted.Lazily, emptyMap())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index e2ab20e..f10b87e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -77,7 +77,6 @@
 import com.android.systemui.SystemUIApplication;
 import com.android.systemui.dagger.qualifiers.Application;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
 import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder;
 import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder;
@@ -329,7 +328,7 @@
         mFlags = featureFlags;
         mPowerInteractor = powerInteractor;
 
-        if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+        if (KeyguardWmStateRefactor.isEnabled()) {
             WindowManagerLockscreenVisibilityViewBinder.bind(
                     wmLockscreenVisibilityViewModel,
                     wmLockscreenVisibilityManager,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 01ba0d2..53c81e5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -419,7 +419,7 @@
      */
     fun canPerformInWindowLauncherAnimations(): Boolean {
         // TODO(b/278086361): Refactor in-window animations.
-        return !featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR) &&
+        return !KeyguardWmStateRefactor.isEnabled &&
                 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.
@@ -866,7 +866,7 @@
         }
 
         surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget ->
-            if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            if (!KeyguardWmStateRefactor.isEnabled) {
                 val surfaceHeight: Int =
                         surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
 
@@ -1005,7 +1005,7 @@
         if (keyguardStateController.isShowing) {
             // Hide the keyguard, with no fade out since we animated it away during the unlock.
 
-            if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            if (!KeyguardWmStateRefactor.isEnabled) {
                 keyguardViewController.hide(
                         surfaceBehindRemoteAnimationStartTime,
                         0 /* fadeOutDuration */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index a34730e..794befa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -139,7 +139,6 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.flags.SystemPropertiesHelper;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -175,8 +174,6 @@
 import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 
-import dagger.Lazy;
-
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -186,6 +183,7 @@
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
+import dagger.Lazy;
 import kotlinx.coroutines.CoroutineDispatcher;
 
 /**
@@ -1051,7 +1049,7 @@
                 IRemoteAnimationFinishedCallback finishedCallback) {
             Trace.beginSection("mExitAnimationRunner.onAnimationStart#startKeyguardExitAnimation");
             startKeyguardExitAnimation(transit, apps, wallpapers, nonApps, finishedCallback);
-            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            if (KeyguardWmStateRefactor.isEnabled()) {
                 mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationStart(
                         transit, apps, wallpapers, nonApps, finishedCallback);
             }
@@ -1061,7 +1059,7 @@
         @Override // Binder interface
         public void onAnimationCancelled() {
             cancelKeyguardExitAnimation();
-            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            if (KeyguardWmStateRefactor.isEnabled()) {
                 mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationCancelled();
             }
         }
@@ -1486,6 +1484,9 @@
 
         mOrderUnlockAndWake = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_orderUnlockAndWake);
+
+        mShowKeyguardWakeLock = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "show keyguard");
+        mShowKeyguardWakeLock.setReferenceCounted(false);
     }
 
     public void userActivity() {
@@ -1493,9 +1494,6 @@
     }
 
     private void setupLocked() {
-        mShowKeyguardWakeLock = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "show keyguard");
-        mShowKeyguardWakeLock.setReferenceCounted(false);
-
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_SHUTDOWN);
         mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter);
@@ -2186,6 +2184,10 @@
      */
     public void showDismissibleKeyguard() {
         if (mFoldGracePeriodProvider.isEnabled()) {
+            if (!mUpdateMonitor.isDeviceProvisioned()) {
+                Log.d(TAG, "Device not provisioned, so ignore request to show keyguard.");
+                return;
+            }
             Bundle showKeyguardUnlocked = new Bundle();
             showKeyguardUnlocked.putBoolean(OPTION_SHOW_DISMISSIBLE, true);
             showKeyguard(showKeyguardUnlocked);
@@ -2507,8 +2509,18 @@
             String message = "";
             switch (msg.what) {
                 case SHOW:
-                    message = "SHOW";
-                    handleShow((Bundle) msg.obj);
+                    // There is a potential race condition when SysUI starts up. CentralSurfaces
+                    // must invoke #registerCentralSurfaces on this class before any messages can be
+                    // processed. If this happens, repost the message with a small delay and try
+                    // again.
+                    if (mCentralSurfaces == null) {
+                        message = "DELAYING SHOW";
+                        Message newMsg = mHandler.obtainMessage(SHOW, (Bundle) msg.obj);
+                        mHandler.sendMessageDelayed(newMsg, 100);
+                    } else {
+                        message = "SHOW";
+                        handleShow((Bundle) msg.obj);
+                    }
                     break;
                 case HIDE:
                     message = "HIDE";
@@ -2595,8 +2607,18 @@
                     Trace.endSection();
                     break;
                 case SYSTEM_READY:
-                    message = "SYSTEM_READY";
-                    handleSystemReady();
+                    // There is a potential race condition when SysUI starts up. CentralSurfaces
+                    // must invoke #registerCentralSurfaces on this class before any messages can be
+                    // processed. If this happens, repost the message with a small delay and try
+                    // again.
+                    if (mCentralSurfaces == null) {
+                        message = "DELAYING SYSTEM_READY";
+                        Message newMsg = mHandler.obtainMessage(SYSTEM_READY);
+                        mHandler.sendMessageDelayed(newMsg, 100);
+                    } else {
+                        message = "SYSTEM_READY";
+                        handleSystemReady();
+                    }
                     break;
             }
             Log.d(TAG, "KeyguardViewMediator queue processing message: " + message);
@@ -2737,7 +2759,7 @@
         mUiBgExecutor.execute(() -> {
             Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")");
 
-            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            if (KeyguardWmStateRefactor.isEnabled()) {
                 // Handled in WmLockscreenVisibilityManager if flag is enabled.
                 return;
             }
@@ -2791,7 +2813,7 @@
             setShowingLocked(true, hidingOrGoingAway /* force */);
             mHiding = false;
 
-            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            if (!KeyguardWmStateRefactor.isEnabled()) {
                 // Handled directly in StatusBarKeyguardViewManager if enabled.
                 mKeyguardViewControllerLazy.get().show(options);
             }
@@ -2868,7 +2890,7 @@
             mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(true);
 
             // Handled in WmLockscreenVisibilityManager if flag is enabled.
-            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            if (!KeyguardWmStateRefactor.isEnabled()) {
                     // Don't actually hide the Keyguard at the moment, wait for window manager 
                     // until it tells us it's safe to do so with startKeyguardExitAnimation.
 		    // Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager 
@@ -2974,7 +2996,7 @@
             } else {
                 Log.d(TAG, "Hiding keyguard while occluded. Just hide the keyguard view and exit.");
 
-                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                if (!KeyguardWmStateRefactor.isEnabled()) {
                     mKeyguardViewControllerLazy.get().hide(
                             mSystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),
                             mHideAnimation.getDuration());
@@ -3010,7 +3032,7 @@
                 // If the flag is enabled, remote animation state is handled in
                 // WmLockscreenVisibilityManager.
                 if (finishedCallback != null
-                        && !mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                        && !KeyguardWmStateRefactor.isEnabled()) {
                     // There will not execute animation, send a finish callback to ensure the remote
                     // animation won't hang there.
                     try {
@@ -3036,7 +3058,7 @@
                         new IRemoteAnimationFinishedCallback() {
                             @Override
                             public void onAnimationFinished() throws RemoteException {
-                                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                if (!KeyguardWmStateRefactor.isEnabled()) {
                                     try {
                                         finishedCallback.onAnimationFinished();
                                     } catch (RemoteException e) {
@@ -3068,7 +3090,7 @@
             // it will dismiss the panel in that case.
             } else if (!mStatusBarStateController.leaveOpenOnKeyguardHide()
                     && apps != null && apps.length > 0) {
-                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                if (!KeyguardWmStateRefactor.isEnabled()) {
                     // Handled in WmLockscreenVisibilityManager. Other logic in this class will
                     // short circuit when this is null.
                     mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
@@ -3092,7 +3114,7 @@
                         createInteractionJankMonitorConf(
                                 CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "RemoteAnimationDisabled"));
 
-                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                if (!KeyguardWmStateRefactor.isEnabled()) {
                     // Handled directly in StatusBarKeyguardViewManager if enabled.
                     mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
                 }
@@ -3111,7 +3133,7 @@
                         Slog.e(TAG, "Keyguard exit without a corresponding app to show.");
 
                         try {
-                            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                            if (!KeyguardWmStateRefactor.isEnabled()) {
                                 finishedCallback.onAnimationFinished();
                             }
                         } catch (RemoteException e) {
@@ -3143,7 +3165,7 @@
                         @Override
                         public void onAnimationEnd(Animator animation) {
                             try {
-                                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                if (!KeyguardWmStateRefactor.isEnabled()) {
                                     finishedCallback.onAnimationFinished();
                                 }
                             } catch (RemoteException e) {
@@ -3156,7 +3178,7 @@
                         @Override
                         public void onAnimationCancel(Animator animation) {
                             try {
-                                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                if (!KeyguardWmStateRefactor.isEnabled()) {
                                     finishedCallback.onAnimationFinished();
                                 }
                             } catch (RemoteException e) {
@@ -3321,7 +3343,7 @@
                 flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
             }
 
-            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            if (!KeyguardWmStateRefactor.isEnabled()) {
                 // Handled in WmLockscreenVisibilityManager.
                 mActivityTaskManagerService.keyguardGoingAway(flags);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt
new file mode 100644
index 0000000..ddccc5d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the keyguard wm state refactor flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object KeyguardWmStateRefactor {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.keyguardWmStateRefactor()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
index b373f85..fede479 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
@@ -53,14 +53,14 @@
         @SysUISingleton
         @FaceAuthTableLog
         fun provideFaceAuthTableLog(factory: TableLogBufferFactory): TableLogBuffer {
-            return factory.create("FaceAuthTableLog", 100)
+            return factory.create("FaceAuthTableLog", 400)
         }
 
         @Provides
         @SysUISingleton
         @FaceDetectTableLog
         fun provideFaceDetectTableLog(factory: TableLogBufferFactory): TableLogBuffer {
-            return factory.create("FaceDetectTableLog", 100)
+            return factory.create("FaceDetectTableLog", 400)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 704ebdd..64e2870 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -40,11 +40,13 @@
 import com.android.systemui.keyguard.shared.model.KeyguardDone
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
@@ -54,7 +56,10 @@
 import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
 
 /** Defines interface for classes that encapsulate application state for the keyguard. */
@@ -96,7 +101,7 @@
      * Whether the device is locked or unlocked right now. This is true when keyguard has been
      * dismissed or can be dismissed by a swipe
      */
-    val isKeyguardUnlocked: StateFlow<Boolean>
+    val isKeyguardDismissible: StateFlow<Boolean>
 
     /**
      * Observable for the signal that keyguard is about to go away.
@@ -127,6 +132,9 @@
      */
     val isDozing: StateFlow<Boolean>
 
+    /** Keyguard can be clipped at the top as the shade is dragged */
+    val topClippingBounds: MutableStateFlow<Int?>
+
     /**
      * Observable for whether the device is dreaming.
      *
@@ -208,6 +216,12 @@
     val clockShouldBeCentered: Flow<Boolean>
 
     /**
+     * Whether the primary authentication is required for the given user due to lockdown or
+     * encryption after reboot.
+     */
+    val isEncryptedOrLockdown: Flow<Boolean>
+
+    /**
      * Returns `true` if the keyguard is showing; `false` otherwise.
      *
      * Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in
@@ -279,6 +293,7 @@
     @Application private val scope: CoroutineScope,
     private val systemClock: SystemClock,
     facePropertyRepository: FacePropertyRepository,
+    private val userTracker: UserTracker,
 ) : KeyguardRepository {
     private val _dismissAction: MutableStateFlow<DismissAction> =
         MutableStateFlow(DismissAction.None)
@@ -314,6 +329,8 @@
     private val _clockShouldBeCentered = MutableStateFlow(true)
     override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered.asStateFlow()
 
+    override val topClippingBounds = MutableStateFlow<Int?>(null)
+
     override val isKeyguardShowing: Flow<Boolean> =
         conflatedCallbackFlow {
                 val callback =
@@ -371,7 +388,7 @@
             }
             .distinctUntilChanged()
 
-    override val isKeyguardUnlocked: StateFlow<Boolean> =
+    override val isKeyguardDismissible: StateFlow<Boolean> =
         conflatedCallbackFlow {
                 val callback =
                     object : KeyguardStateController.Callback {
@@ -379,7 +396,7 @@
                             trySendWithFailureLogging(
                                 keyguardStateController.isUnlocked,
                                 TAG,
-                                "updated isKeyguardUnlocked due to onUnlockedChanged"
+                                "updated isKeyguardDismissible due to onUnlockedChanged"
                             )
                         }
 
@@ -387,7 +404,7 @@
                             trySendWithFailureLogging(
                                 keyguardStateController.isUnlocked,
                                 TAG,
-                                "updated isKeyguardUnlocked due to onKeyguardShowingChanged"
+                                "updated isKeyguardDismissible due to onKeyguardShowingChanged"
                             )
                         }
                     }
@@ -544,6 +561,24 @@
         awaitClose { dozeTransitionListener.removeCallback(callback) }
     }
 
+    @OptIn(ExperimentalCoroutinesApi::class)
+    override val isEncryptedOrLockdown: Flow<Boolean> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : KeyguardUpdateMonitorCallback() {
+                        override fun onStrongAuthStateChanged(userId: Int) {
+                            trySendWithFailureLogging(userId, TAG, "strong auth state change")
+                        }
+                    }
+                keyguardUpdateMonitor.registerCallback(callback)
+                awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+            }
+            .filter { userId -> userId == userTracker.userId }
+            .onStart { emit(userTracker.userId) }
+            .mapLatest { userId -> keyguardUpdateMonitor.isEncryptedOrLockdown(userId) }
+            // KeyguardUpdateMonitor#registerCallback needs to be called on the main thread.
+            .flowOn(mainDispatcher)
+
     override fun isKeyguardShowing(): Boolean {
         return keyguardStateController.isShowing
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
new file mode 100644
index 0000000..afe9151
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.view.View
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+class KeyguardSmartspaceRepository @Inject constructor() {
+    private val _bcSmartspaceVisibility: MutableStateFlow<Int> = MutableStateFlow(View.GONE)
+    val bcSmartspaceVisibility: StateFlow<Int> = _bcSmartspaceVisibility.asStateFlow()
+
+    fun setBcSmartspaceVisibility(visibility: Int) {
+        _bcSmartspaceVisibility.value = visibility
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
index cb0f186..42f14f1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
@@ -140,8 +140,9 @@
     override val revealAmount: Flow<Float> = callbackFlow {
         val updateListener =
             Animator.AnimatorUpdateListener {
-                val value = (it as ValueAnimator).animatedValue
-                trySend(value as Float)
+                val value = (it as ValueAnimator).animatedValue as Float
+                trySend(value)
+
                 if (value <= 0.0f || value >= 1.0f) {
                     scrimLogger.d(TAG, "revealAmount", value)
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index ecf78d5..b1a2297 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -30,6 +30,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
@@ -82,10 +83,11 @@
      */
     val showIndicatorForDeviceEntry: Flow<Boolean> =
         combine(showIndicatorForPrimaryBouncer, showIndicatorForAlternateBouncer) {
-            showForPrimaryBouncer,
-            showForAlternateBouncer ->
-            showForPrimaryBouncer || showForAlternateBouncer
-        }
+                showForPrimaryBouncer,
+                showForAlternateBouncer ->
+                showForPrimaryBouncer || showForAlternateBouncer
+            }
+            .distinctUntilChanged()
 
     private fun shouldShowIndicatorForPrimaryBouncer(): Boolean {
         val sfpsEnabled: Boolean =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 52f2759..a97c152 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -17,20 +17,21 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.util.kotlin.Utils.Companion.toQuad
-import com.android.systemui.util.kotlin.Utils.Companion.toQuint
+import com.android.systemui.util.kotlin.Utils.Companion.sample
 import com.android.systemui.util.kotlin.sample
 import com.android.wm.shell.animation.Interpolators
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 
@@ -39,23 +40,29 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
-    override val transitionInteractor: KeyguardTransitionInteractor,
-    @Application private val scope: CoroutineScope,
+    transitionInteractor: KeyguardTransitionInteractor,
+    @Background private val scope: CoroutineScope,
+    @Background bgDispatcher: CoroutineDispatcher,
+    @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
+    private val communalInteractor: CommunalInteractor,
     private val powerInteractor: PowerInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.ALTERNATE_BOUNCER,
+        transitionInteractor = transitionInteractor,
+        mainDispatcher = mainDispatcher,
+        bgDispatcher = bgDispatcher,
     ) {
 
     override fun start() {
         listenForAlternateBouncerToGone()
-        listenForAlternateBouncerToLockscreenAodOrDozing()
+        listenForAlternateBouncerToLockscreenHubAodOrDozing()
         listenForAlternateBouncerToPrimaryBouncer()
         listenForTransitionToCamera(scope, keyguardInteractor)
     }
 
-    private fun listenForAlternateBouncerToLockscreenAodOrDozing() {
+    private fun listenForAlternateBouncerToLockscreenHubAodOrDozing() {
         scope.launch {
             keyguardInteractor.alternateBouncerShowing
                 // Add a slight delay, as alternateBouncer and primaryBouncer showing event changes
@@ -63,14 +70,11 @@
                 // happening prematurely.
                 .onEach { delay(50) }
                 .sample(
-                    combine(
-                        keyguardInteractor.primaryBouncerShowing,
-                        transitionInteractor.startedKeyguardTransitionStep,
-                        powerInteractor.isAwake,
-                        keyguardInteractor.isAodAvailable,
-                        ::toQuad
-                    ),
-                    ::toQuint
+                    keyguardInteractor.primaryBouncerShowing,
+                    startedKeyguardTransitionStep,
+                    powerInteractor.isAwake,
+                    keyguardInteractor.isAodAvailable,
+                    communalInteractor.isIdleOnCommunal
                 )
                 .collect {
                     (
@@ -78,7 +82,8 @@
                         isPrimaryBouncerShowing,
                         lastStartedTransitionStep,
                         isAwake,
-                        isAodAvailable) ->
+                        isAodAvailable,
+                        isIdleOnCommunal) ->
                     if (
                         !isAlternateBouncerShowing &&
                             !isPrimaryBouncerShowing &&
@@ -92,7 +97,11 @@
                                     KeyguardState.DOZING
                                 }
                             } else {
-                                KeyguardState.LOCKSCREEN
+                                if (isIdleOnCommunal) {
+                                    KeyguardState.GLANCEABLE_HUB
+                                } else {
+                                    KeyguardState.LOCKSCREEN
+                                }
                             }
                         startTransitionTo(to)
                     }
@@ -102,20 +111,19 @@
 
     private fun listenForAlternateBouncerToGone() {
         scope.launch {
-            keyguardInteractor.isKeyguardGoingAway
-                .sample(transitionInteractor.finishedKeyguardState, ::Pair)
-                .collect { (isKeyguardGoingAway, keyguardState) ->
-                    if (isKeyguardGoingAway && keyguardState == KeyguardState.ALTERNATE_BOUNCER) {
-                        startTransitionTo(KeyguardState.GONE)
-                    }
+            keyguardInteractor.isKeyguardGoingAway.sample(finishedKeyguardState, ::Pair).collect {
+                (isKeyguardGoingAway, keyguardState) ->
+                if (isKeyguardGoingAway && keyguardState == KeyguardState.ALTERNATE_BOUNCER) {
+                    startTransitionTo(KeyguardState.GONE)
                 }
+            }
         }
     }
 
     private fun listenForAlternateBouncerToPrimaryBouncer() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { (isPrimaryBouncerShowing, startedKeyguardState) ->
                     if (
                         isPrimaryBouncerShowing &&
@@ -139,8 +147,10 @@
     }
 
     companion object {
+        const val TAG = "FromAlternateBouncerTransitionInteractor"
         val TRANSITION_DURATION_MS = 300.milliseconds
         val TO_GONE_DURATION = 500.milliseconds
         val TO_AOD_DURATION = TRANSITION_DURATION_MS
+        val TO_PRIMARY_BOUNCER_DURATION = TRANSITION_DURATION_MS
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 8584401..8fa33ee7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -19,7 +19,8 @@
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.DozeStateModel
@@ -29,6 +30,7 @@
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
@@ -38,48 +40,68 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
-    override val transitionInteractor: KeyguardTransitionInteractor,
-    @Application private val scope: CoroutineScope,
+    transitionInteractor: KeyguardTransitionInteractor,
+    @Background private val scope: CoroutineScope,
+    @Background bgDispatcher: CoroutineDispatcher,
+    @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.AOD,
+        transitionInteractor = transitionInteractor,
+        mainDispatcher = mainDispatcher,
+        bgDispatcher = bgDispatcher,
     ) {
 
     override fun start() {
-        listenForAodToLockscreenOrOccluded()
+        listenForAodToLockscreen()
+        listenForAodToPrimaryBouncer()
         listenForAodToGone()
+        listenForAodToOccluded()
         listenForTransitionToCamera(scope, keyguardInteractor)
     }
 
-    private fun listenForAodToLockscreenOrOccluded() {
+    /**
+     * There are cases where the transition to AOD begins but never completes, such as tapping power
+     * during an incoming phone call when unlocked. In this case, GONE->AOD should be interrupted to
+     * run AOD->OCCLUDED.
+     */
+    private fun listenForAodToOccluded() {
+        scope.launch {
+            keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect {
+                (isOccluded, startedKeyguardState) ->
+                if (isOccluded && startedKeyguardState == KeyguardState.AOD) {
+                    startTransitionTo(
+                        toState = KeyguardState.OCCLUDED,
+                        modeOnCanceled = TransitionModeOnCanceled.RESET
+                    )
+                }
+            }
+        }
+    }
+
+    private fun listenForAodToLockscreen() {
         scope.launch {
             keyguardInteractor
                 .dozeTransitionTo(DozeStateModel.FINISH)
                 .sample(
                     combine(
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         keyguardInteractor.isKeyguardOccluded,
                         ::Pair
                     ),
                     ::toTriple
                 )
                 .collect { (_, lastStartedStep, occluded) ->
-                    if (lastStartedStep.to == KeyguardState.AOD) {
-                        val toState =
-                            if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
+                    if (lastStartedStep.to == KeyguardState.AOD && !occluded) {
                         val modeOnCanceled =
-                            if (
-                                toState == KeyguardState.LOCKSCREEN &&
-                                    lastStartedStep.from == KeyguardState.LOCKSCREEN
-                            ) {
+                            if (lastStartedStep.from == KeyguardState.LOCKSCREEN) {
                                 TransitionModeOnCanceled.REVERSE
                             } else {
                                 TransitionModeOnCanceled.LAST_VALUE
                             }
-
                         startTransitionTo(
-                            toState = toState,
+                            toState = KeyguardState.LOCKSCREEN,
                             modeOnCanceled = modeOnCanceled,
                         )
                     }
@@ -87,20 +109,32 @@
         }
     }
 
-    private fun listenForAodToGone() {
+    /**
+     * If there is a biometric lockout and FPS is tapped while on AOD, it should go directly to the
+     * PRIMARY_BOUNCER.
+     */
+    private fun listenForAodToPrimaryBouncer() {
         scope.launch {
-            keyguardInteractor.biometricUnlockState
-                .sample(transitionInteractor.finishedKeyguardState, ::Pair)
-                .collect { pair ->
-                    val (biometricUnlockState, keyguardState) = pair
-                    if (
-                        keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)
-                    ) {
-                        startTransitionTo(KeyguardState.GONE)
+            keyguardInteractor.primaryBouncerShowing
+                .sample(startedKeyguardTransitionStep, ::Pair)
+                .collect { (isBouncerShowing, lastStartedTransitionStep) ->
+                    if (isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.AOD) {
+                        startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
                     }
                 }
         }
     }
+
+    private fun listenForAodToGone() {
+        scope.launch {
+            keyguardInteractor.biometricUnlockState.sample(finishedKeyguardState, ::Pair).collect {
+                (biometricUnlockState, keyguardState) ->
+                if (keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)) {
+                    startTransitionTo(KeyguardState.GONE)
+                }
+            }
+        }
+    }
     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
             interpolator = Interpolators.LINEAR
@@ -113,6 +147,7 @@
     }
 
     companion object {
+        const val TAG = "FromAodTransitionInteractor"
         private val DEFAULT_DURATION = 500.milliseconds
         val TO_LOCKSCREEN_DURATION = 500.milliseconds
         val TO_GONE_DURATION = DEFAULT_DURATION
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index eca7088..e2a8b6c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -18,16 +18,19 @@
 
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.util.kotlin.Utils.Companion.toTriple
+import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
@@ -37,36 +40,49 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
-    override val transitionInteractor: KeyguardTransitionInteractor,
-    @Application private val scope: CoroutineScope,
+    transitionInteractor: KeyguardTransitionInteractor,
+    @Background private val scope: CoroutineScope,
+    @Background bgDispatcher: CoroutineDispatcher,
+    @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
     private val powerInteractor: PowerInteractor,
+    private val communalInteractor: CommunalInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.DOZING,
+        transitionInteractor = transitionInteractor,
+        mainDispatcher = mainDispatcher,
+        bgDispatcher = bgDispatcher,
     ) {
 
     override fun start() {
-        listenForDozingToLockscreenOrOccluded()
+        listenForDozingToLockscreenHubOrOccluded()
         listenForDozingToGone()
         listenForTransitionToCamera(scope, keyguardInteractor)
     }
 
-    private fun listenForDozingToLockscreenOrOccluded() {
+    private fun listenForDozingToLockscreenHubOrOccluded() {
         scope.launch {
             powerInteractor.isAwake
                 .sample(
                     combine(
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         keyguardInteractor.isKeyguardOccluded,
-                        ::Pair
+                        communalInteractor.isIdleOnCommunal,
+                        ::Triple
                     ),
-                    ::toTriple
+                    ::toQuad
                 )
-                .collect { (isAwake, lastStartedTransition, occluded) ->
+                .collect { (isAwake, lastStartedTransition, occluded, isIdleOnCommunal) ->
                     if (isAwake && lastStartedTransition.to == KeyguardState.DOZING) {
                         startTransitionTo(
-                            if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
+                            if (occluded) {
+                                KeyguardState.OCCLUDED
+                            } else if (isIdleOnCommunal) {
+                                KeyguardState.GLANCEABLE_HUB
+                            } else {
+                                KeyguardState.LOCKSCREEN
+                            }
                         )
                     }
                 }
@@ -76,7 +92,7 @@
     private fun listenForDozingToGone() {
         scope.launch {
             keyguardInteractor.biometricUnlockState
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { (biometricUnlockState, lastStartedTransition) ->
                     if (
                         lastStartedTransition.to == KeyguardState.DOZING &&
@@ -96,6 +112,7 @@
     }
 
     companion object {
+        const val TAG = "FromDozingTransitionInteractor"
         private val DEFAULT_DURATION = 500.milliseconds
         val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
index dac6ef5..a6cdaa8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
@@ -19,7 +19,8 @@
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
@@ -28,6 +29,7 @@
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.combine
@@ -39,12 +41,17 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
-    override val transitionInteractor: KeyguardTransitionInteractor,
-    @Application private val scope: CoroutineScope,
+    transitionInteractor: KeyguardTransitionInteractor,
+    @Background private val scope: CoroutineScope,
+    @Background bgDispatcher: CoroutineDispatcher,
+    @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+        transitionInteractor = transitionInteractor,
+        mainDispatcher = mainDispatcher,
+        bgDispatcher = bgDispatcher,
     ) {
 
     override fun start() {
@@ -64,7 +71,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.dozeTransitionModel,
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         ::Pair
                     ),
                     ::toTriple
@@ -88,7 +95,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.isKeyguardOccluded,
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         ::Pair,
                     ),
                     ::toTriple
@@ -108,7 +115,7 @@
     private fun listenForDreamingLockscreenHostedToPrimaryBouncer() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { (isBouncerShowing, lastStartedTransitionStep) ->
                     if (
                         isBouncerShowing &&
@@ -123,7 +130,7 @@
     private fun listenForDreamingLockscreenHostedToGone() {
         scope.launch {
             keyguardInteractor.biometricUnlockState
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { (biometricUnlockState, lastStartedTransitionStep) ->
                     if (
                         lastStartedTransitionStep.to == KeyguardState.DREAMING_LOCKSCREEN_HOSTED &&
@@ -137,11 +144,7 @@
 
     private fun listenForDreamingLockscreenHostedToDozing() {
         scope.launch {
-            combine(
-                    keyguardInteractor.dozeTransitionModel,
-                    transitionInteractor.startedKeyguardTransitionStep,
-                    ::Pair
-                )
+            combine(keyguardInteractor.dozeTransitionModel, startedKeyguardTransitionStep, ::Pair)
                 .collect { (dozeTransitionModel, lastStartedTransitionStep) ->
                     if (
                         dozeTransitionModel.to == DozeStateModel.DOZE &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 7fdcf2f..13ffd63 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -19,7 +19,8 @@
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
@@ -28,6 +29,7 @@
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
@@ -37,12 +39,17 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
-    override val transitionInteractor: KeyguardTransitionInteractor,
-    @Application private val scope: CoroutineScope,
+    transitionInteractor: KeyguardTransitionInteractor,
+    @Background private val scope: CoroutineScope,
+    @Background bgDispatcher: CoroutineDispatcher,
+    @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.DREAMING,
+        transitionInteractor = transitionInteractor,
+        mainDispatcher = mainDispatcher,
+        bgDispatcher = bgDispatcher,
     ) {
 
     override fun start() {
@@ -66,7 +73,7 @@
     private fun listenForDreamingToOccluded() {
         scope.launch {
             combine(keyguardInteractor.isKeyguardOccluded, keyguardInteractor.isDreaming, ::Pair)
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::toTriple)
+                .sample(startedKeyguardTransitionStep, ::toTriple)
                 .collect { (isOccluded, isDreaming, lastStartedTransition) ->
                     if (
                         isOccluded &&
@@ -82,7 +89,7 @@
     private fun listenForDreamingToGone() {
         scope.launch {
             keyguardInteractor.biometricUnlockState
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { (biometricUnlockState, lastStartedTransitionStep) ->
                     if (
                         lastStartedTransitionStep.to == KeyguardState.DREAMING &&
@@ -96,11 +103,7 @@
 
     private fun listenForDreamingToAodOrDozing() {
         scope.launch {
-            combine(
-                    keyguardInteractor.dozeTransitionModel,
-                    transitionInteractor.finishedKeyguardState,
-                    ::Pair
-                )
+            combine(keyguardInteractor.dozeTransitionModel, finishedKeyguardState, ::Pair)
                 .collect { (dozeTransitionModel, keyguardState) ->
                     if (keyguardState == KeyguardState.DREAMING) {
                         if (dozeTransitionModel.to == DozeStateModel.DOZE) {
@@ -123,6 +126,7 @@
     }
 
     companion object {
+        const val TAG = "FromDreamingTransitionInteractor"
         private val DEFAULT_DURATION = 500.milliseconds
         val TO_LOCKSCREEN_DURATION = 1167.milliseconds
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 70c2e6d..9d38be9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -18,24 +18,52 @@
 
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launch
 import com.android.systemui.Flags
+import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 
 @SysUISingleton
 class FromGlanceableHubTransitionInteractor
 @Inject
 constructor(
+    @Background private val scope: CoroutineScope,
+    @Main mainDispatcher: CoroutineDispatcher,
+    @Background bgDispatcher: CoroutineDispatcher,
+    private val glanceableHubTransitions: GlanceableHubTransitions,
+    private val keyguardInteractor: KeyguardInteractor,
     override val transitionRepository: KeyguardTransitionRepository,
-    override val transitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(fromState = KeyguardState.GLANCEABLE_HUB) {
+    transitionInteractor: KeyguardTransitionInteractor,
+    private val powerInteractor: PowerInteractor,
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.GLANCEABLE_HUB,
+        transitionInteractor = transitionInteractor,
+        mainDispatcher = mainDispatcher,
+        bgDispatcher = bgDispatcher,
+    ) {
     override fun start() {
         if (!Flags.communalHub()) {
             return
         }
+        listenForHubToLockscreen()
+        listenForHubToDozing()
+        listenForHubToPrimaryBouncer()
+        listenForHubToAlternateBouncer()
+        listenForHubToOccluded()
+        listenForHubToGone()
     }
 
     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
@@ -45,8 +73,90 @@
         }
     }
 
+    /**
+     * Listens for the glanceable hub transition to lock screen and directly drives the keyguard
+     * transition.
+     */
+    private fun listenForHubToLockscreen() {
+        glanceableHubTransitions.listenForLockscreenAndHubTransition(
+            transitionName = "listenForHubToLockscreen",
+            transitionOwnerName = TAG,
+            toScene = CommunalSceneKey.Blank,
+        )
+    }
+
+    private fun listenForHubToPrimaryBouncer() {
+        scope.launch("$TAG#listenForHubToPrimaryBouncer") {
+            keyguardInteractor.primaryBouncerShowing
+                .sample(startedKeyguardTransitionStep, ::Pair)
+                .collect { pair ->
+                    val (isBouncerShowing, lastStartedTransitionStep) = pair
+                    if (
+                        isBouncerShowing &&
+                            lastStartedTransitionStep.to == KeyguardState.GLANCEABLE_HUB
+                    ) {
+                        startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
+                    }
+                }
+        }
+    }
+
+    private fun listenForHubToAlternateBouncer() {
+        scope.launch("$TAG#listenForHubToAlternateBouncer") {
+            keyguardInteractor.alternateBouncerShowing
+                .sample(startedKeyguardTransitionStep, ::Pair)
+                .collect { pair ->
+                    val (isAlternateBouncerShowing, lastStartedTransitionStep) = pair
+                    if (
+                        isAlternateBouncerShowing &&
+                            lastStartedTransitionStep.to == KeyguardState.GLANCEABLE_HUB
+                    ) {
+                        startTransitionTo(KeyguardState.ALTERNATE_BOUNCER)
+                    }
+                }
+        }
+    }
+
+    private fun listenForHubToDozing() {
+        scope.launch {
+            powerInteractor.isAsleep.sample(startedKeyguardTransitionStep, ::Pair).collect {
+                (isAsleep, lastStartedStep) ->
+                if (lastStartedStep.to == fromState && isAsleep) {
+                    startTransitionTo(
+                        toState = KeyguardState.DOZING,
+                        modeOnCanceled = TransitionModeOnCanceled.LAST_VALUE,
+                    )
+                }
+            }
+        }
+    }
+
+    private fun listenForHubToOccluded() {
+        scope.launch {
+            keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect {
+                (isOccluded, keyguardState) ->
+                if (isOccluded && keyguardState == fromState) {
+                    startTransitionTo(KeyguardState.OCCLUDED)
+                }
+            }
+        }
+    }
+
+    private fun listenForHubToGone() {
+        scope.launch {
+            keyguardInteractor.isKeyguardGoingAway
+                .sample(startedKeyguardTransitionStep, ::Pair)
+                .collect { (isKeyguardGoingAway, lastStartedStep) ->
+                    if (isKeyguardGoingAway && lastStartedStep.to == fromState) {
+                        startTransitionTo(KeyguardState.GONE)
+                    }
+                }
+        }
+    }
+
     companion object {
         const val TAG = "FromGlanceableHubTransitionInteractor"
-        val DEFAULT_DURATION = 500.milliseconds
+        val DEFAULT_DURATION = 400.milliseconds
+        val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 62a0b0e..7477624 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -18,16 +18,20 @@
 
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.Utils.Companion.sample
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
@@ -37,30 +41,45 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
-    override val transitionInteractor: KeyguardTransitionInteractor,
-    @Application private val scope: CoroutineScope,
+    transitionInteractor: KeyguardTransitionInteractor,
+    @Background private val scope: CoroutineScope,
+    @Background bgDispatcher: CoroutineDispatcher,
+    @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
     private val powerInteractor: PowerInteractor,
+    private val communalInteractor: CommunalInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.GONE,
+        transitionInteractor = transitionInteractor,
+        mainDispatcher = mainDispatcher,
+        bgDispatcher = bgDispatcher,
     ) {
 
     override fun start() {
         listenForGoneToAodOrDozing()
         listenForGoneToDreaming()
-        listenForGoneToLockscreen()
+        listenForGoneToLockscreenOrHub()
         listenForGoneToDreamingLockscreenHosted()
     }
 
     // Primarily for when the user chooses to lock down the device
-    private fun listenForGoneToLockscreen() {
+    private fun listenForGoneToLockscreenOrHub() {
         scope.launch {
             keyguardInteractor.isKeyguardShowing
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
-                .collect { (isKeyguardShowing, lastStartedStep) ->
+                .sample(
+                    startedKeyguardTransitionStep,
+                    communalInteractor.isIdleOnCommunal,
+                )
+                .collect { (isKeyguardShowing, lastStartedStep, isIdleOnCommunal) ->
                     if (isKeyguardShowing && lastStartedStep.to == KeyguardState.GONE) {
-                        startTransitionTo(KeyguardState.LOCKSCREEN)
+                        val to =
+                            if (isIdleOnCommunal) {
+                                KeyguardState.GLANCEABLE_HUB
+                            } else {
+                                KeyguardState.LOCKSCREEN
+                            }
+                        startTransitionTo(to)
                     }
                 }
         }
@@ -69,7 +88,7 @@
     private fun listenForGoneToDreamingLockscreenHosted() {
         scope.launch {
             keyguardInteractor.isActiveDreamLockscreenHosted
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { (isActiveDreamLockscreenHosted, lastStartedStep) ->
                     if (isActiveDreamLockscreenHosted && lastStartedStep.to == KeyguardState.GONE) {
                         startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
@@ -83,7 +102,7 @@
             keyguardInteractor.isAbleToDream
                 .sample(
                     combine(
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         keyguardInteractor.isActiveDreamLockscreenHosted,
                         ::Pair
                     ),
@@ -106,7 +125,7 @@
             powerInteractor.isAsleep
                 .sample(
                     combine(
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Pair
                     ),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index cecc653..3965648 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -18,11 +18,12 @@
 
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
-import com.android.app.tracing.coroutines.launch
+import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
@@ -39,28 +40,37 @@
 import java.util.UUID
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 @SysUISingleton
 class FromLockscreenTransitionInteractor
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
-    override val transitionInteractor: KeyguardTransitionInteractor,
-    @Application private val scope: CoroutineScope,
+    transitionInteractor: KeyguardTransitionInteractor,
+    @Background private val scope: CoroutineScope,
+    @Background bgDispatcher: CoroutineDispatcher,
+    @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
     private val flags: FeatureFlags,
     private val shadeRepository: ShadeRepository,
     private val powerInteractor: PowerInteractor,
+    private val glanceableHubTransitions: GlanceableHubTransitions,
     inWindowLauncherUnlockAnimationInteractor: Lazy<InWindowLauncherUnlockAnimationInteractor>,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.LOCKSCREEN,
+        transitionInteractor = transitionInteractor,
+        mainDispatcher = mainDispatcher,
+        bgDispatcher = bgDispatcher,
     ) {
 
     override fun start() {
@@ -73,6 +83,7 @@
         listenForLockscreenToPrimaryBouncerDragging()
         listenForLockscreenToAlternateBouncer()
         listenForLockscreenTransitionToCamera()
+        listenForLockscreenToGlanceableHub()
     }
 
     /**
@@ -147,12 +158,12 @@
 
     private fun listenForLockscreenToDreaming() {
         val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
-        scope.launch("$TAG#listenForLockscreenToDreaming") {
+        scope.launch {
             keyguardInteractor.isAbleToDream
                 .sample(
                     combine(
-                        transitionInteractor.startedKeyguardTransitionStep,
-                        transitionInteractor.finishedKeyguardState,
+                        startedKeyguardTransitionStep,
+                        finishedKeyguardState,
                         keyguardInteractor.isActiveDreamLockscreenHosted,
                         ::Triple
                     ),
@@ -180,9 +191,9 @@
     }
 
     private fun listenForLockscreenToPrimaryBouncer() {
-        scope.launch("$TAG#listenForLockscreenToPrimaryBouncer") {
+        scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (isBouncerShowing, lastStartedTransitionStep) = pair
                     if (
@@ -195,9 +206,9 @@
     }
 
     private fun listenForLockscreenToAlternateBouncer() {
-        scope.launch("$TAG#listenForLockscreenToAlternateBouncer") {
+        scope.launch {
             keyguardInteractor.alternateBouncerShowing
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (isAlternateBouncerShowing, lastStartedTransitionStep) = pair
                     if (
@@ -213,76 +224,78 @@
     /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
     private fun listenForLockscreenToPrimaryBouncerDragging() {
         var transitionId: UUID? = null
-        scope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") {
+        scope.launch {
             shadeRepository.legacyShadeExpansion
                 .sample(
                     combine(
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         keyguardInteractor.statusBarState,
-                        keyguardInteractor.isKeyguardUnlocked,
+                        keyguardInteractor.isKeyguardDismissible,
                         ::Triple
                     ),
                     ::toQuad
                 )
                 .collect { (shadeExpansion, keyguardState, statusBarState, isKeyguardUnlocked) ->
-                    val id = transitionId
-                    if (id != null) {
-                        if (keyguardState.to == KeyguardState.PRIMARY_BOUNCER) {
-                            // An existing `id` means a transition is started, and calls to
-                            // `updateTransition` will control it until FINISHED or CANCELED
-                            var nextState =
-                                if (shadeExpansion == 0f) {
-                                    TransitionState.FINISHED
-                                } else if (shadeExpansion == 1f) {
-                                    TransitionState.CANCELED
-                                } else {
-                                    TransitionState.RUNNING
+                    withContext(mainDispatcher) {
+                        val id = transitionId
+                        if (id != null) {
+                            if (keyguardState.to == KeyguardState.PRIMARY_BOUNCER) {
+                                // An existing `id` means a transition is started, and calls to
+                                // `updateTransition` will control it until FINISHED or CANCELED
+                                var nextState =
+                                    if (shadeExpansion == 0f) {
+                                        TransitionState.FINISHED
+                                    } else if (shadeExpansion == 1f) {
+                                        TransitionState.CANCELED
+                                    } else {
+                                        TransitionState.RUNNING
+                                    }
+                                transitionRepository.updateTransition(
+                                    id,
+                                    1f - shadeExpansion,
+                                    nextState,
+                                )
+
+                                if (
+                                    nextState == TransitionState.CANCELED ||
+                                        nextState == TransitionState.FINISHED
+                                ) {
+                                    transitionId = null
                                 }
-                            transitionRepository.updateTransition(
-                                id,
-                                1f - shadeExpansion,
-                                nextState,
-                            )
 
-                            if (
-                                nextState == TransitionState.CANCELED ||
-                                    nextState == TransitionState.FINISHED
-                            ) {
-                                transitionId = null
-                            }
-
-                            // If canceled, just put the state back
-                            // TODO(b/278086361): This logic should happen in
-                            //  FromPrimaryBouncerInteractor.
-                            if (nextState == TransitionState.CANCELED) {
-                                transitionRepository.startTransition(
-                                    TransitionInfo(
-                                        ownerName = name,
-                                        from = KeyguardState.PRIMARY_BOUNCER,
-                                        to = KeyguardState.LOCKSCREEN,
-                                        animator =
-                                            getDefaultAnimatorForTransitionsToState(
-                                                    KeyguardState.LOCKSCREEN
-                                                )
-                                                .apply { duration = 0 }
+                                // If canceled, just put the state back
+                                // TODO(b/278086361): This logic should happen in
+                                //  FromPrimaryBouncerInteractor.
+                                if (nextState == TransitionState.CANCELED) {
+                                    transitionRepository.startTransition(
+                                        TransitionInfo(
+                                            ownerName = name,
+                                            from = KeyguardState.PRIMARY_BOUNCER,
+                                            to = KeyguardState.LOCKSCREEN,
+                                            animator =
+                                                getDefaultAnimatorForTransitionsToState(
+                                                        KeyguardState.LOCKSCREEN
+                                                    )
+                                                    .apply { duration = 0 }
+                                        )
                                     )
-                                )
+                                }
                             }
-                        }
-                    } else {
-                        // TODO (b/251849525): Remove statusbarstate check when that state is
-                        // integrated into KeyguardTransitionRepository
-                        if (
-                            keyguardState.to == KeyguardState.LOCKSCREEN &&
-                                shadeRepository.legacyShadeTracking.value &&
-                                !isKeyguardUnlocked &&
-                                statusBarState == KEYGUARD
-                        ) {
-                            transitionId =
-                                startTransitionTo(
-                                    toState = KeyguardState.PRIMARY_BOUNCER,
-                                    animator = null, // transition will be manually controlled
-                                )
+                        } else {
+                            // TODO (b/251849525): Remove statusbarstate check when that state is
+                            // integrated into KeyguardTransitionRepository
+                            if (
+                                keyguardState.to == KeyguardState.LOCKSCREEN &&
+                                    shadeRepository.legacyShadeTracking.value &&
+                                    !isKeyguardUnlocked &&
+                                    statusBarState == KEYGUARD
+                            ) {
+                                transitionId =
+                                    startTransitionTo(
+                                        toState = KeyguardState.PRIMARY_BOUNCER,
+                                        animator = null, // transition will be manually controlled
+                                    )
+                            }
                         }
                     }
                 }
@@ -290,17 +303,17 @@
     }
 
     fun dismissKeyguard() {
-        startTransitionTo(KeyguardState.GONE)
+        scope.launch { startTransitionTo(KeyguardState.GONE) }
     }
 
     private fun listenForLockscreenToGone() {
-        if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+        if (KeyguardWmStateRefactor.isEnabled) {
             return
         }
 
-        scope.launch("$TAG#listenForLockscreenToGone") {
+        scope.launch {
             keyguardInteractor.isKeyguardGoingAway
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (isKeyguardGoingAway, lastStartedStep) = pair
                     if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
@@ -311,13 +324,13 @@
     }
 
     private fun listenForLockscreenToGoneDragging() {
-        if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+        if (KeyguardWmStateRefactor.isEnabled) {
             return
         }
 
-        scope.launch("$TAG#listenForLockscreenToGoneDragging") {
+        scope.launch {
             keyguardInteractor.isKeyguardGoingAway
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (isKeyguardGoingAway, lastStartedStep) = pair
                     if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
@@ -328,23 +341,22 @@
     }
 
     private fun listenForLockscreenToOccluded() {
-        scope.launch("$TAG#listenForLockscreenToOccluded") {
-            keyguardInteractor.isKeyguardOccluded
-                .sample(transitionInteractor.startedKeyguardState, ::Pair)
-                .collect { (isOccluded, keyguardState) ->
-                    if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) {
-                        startTransitionTo(KeyguardState.OCCLUDED)
-                    }
+        scope.launch {
+            keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect {
+                (isOccluded, keyguardState) ->
+                if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) {
+                    startTransitionTo(KeyguardState.OCCLUDED)
                 }
+            }
         }
     }
 
     private fun listenForLockscreenToAodOrDozing() {
-        scope.launch("$TAG#listenForLockscreenToAodOrDozing") {
+        scope.launch {
             powerInteractor.isAsleep
                 .sample(
                     combine(
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Pair
                     ),
@@ -372,6 +384,22 @@
         }
     }
 
+    /**
+     * Listens for transition from glanceable hub back to lock screen and directly drives the
+     * keyguard transition.
+     */
+    private fun listenForLockscreenToGlanceableHub() {
+        if (!com.android.systemui.Flags.communalHub()) {
+            return
+        }
+
+        glanceableHubTransitions.listenForLockscreenAndHubTransition(
+            transitionName = "listenForLockscreenToGlanceableHub",
+            transitionOwnerName = TAG,
+            toScene = CommunalSceneKey.Communal
+        )
+    }
+
     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
             interpolator = Interpolators.LINEAR
@@ -397,5 +425,6 @@
         val TO_AOD_DURATION = 500.milliseconds
         val TO_PRIMARY_BOUNCER_DURATION = DEFAULT_DURATION
         val TO_GONE_DURATION = DEFAULT_DURATION
+        val TO_GLANCEABLE_HUB_DURATION = DEFAULT_DURATION
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 6a8555c..efb604d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -18,15 +18,19 @@
 
 import android.animation.ValueAnimator
 import com.android.app.animation.Interpolators
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.Utils.Companion.sample
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
@@ -36,17 +40,23 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
-    override val transitionInteractor: KeyguardTransitionInteractor,
-    @Application private val scope: CoroutineScope,
+    transitionInteractor: KeyguardTransitionInteractor,
+    @Background private val scope: CoroutineScope,
+    @Background bgDispatcher: CoroutineDispatcher,
+    @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
     private val powerInteractor: PowerInteractor,
+    private val communalInteractor: CommunalInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.OCCLUDED,
+        transitionInteractor = transitionInteractor,
+        mainDispatcher = mainDispatcher,
+        bgDispatcher = bgDispatcher,
     ) {
 
     override fun start() {
-        listenForOccludedToLockscreen()
+        listenForOccludedToLockscreenOrHub()
         listenForOccludedToDreaming()
         listenForOccludedToAodOrDozing()
         listenForOccludedToGone()
@@ -57,7 +67,7 @@
     private fun listenForOccludedToPrimaryBouncer() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { (isBouncerShowing, lastStartedTransitionStep) ->
                     if (
                         isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.OCCLUDED
@@ -70,28 +80,24 @@
 
     private fun listenForOccludedToDreaming() {
         scope.launch {
-            keyguardInteractor.isAbleToDream
-                .sample(transitionInteractor.finishedKeyguardState, ::Pair)
-                .collect { (isAbleToDream, keyguardState) ->
-                    if (isAbleToDream && keyguardState == KeyguardState.OCCLUDED) {
-                        startTransitionTo(KeyguardState.DREAMING)
-                    }
+            keyguardInteractor.isAbleToDream.sample(finishedKeyguardState, ::Pair).collect {
+                (isAbleToDream, keyguardState) ->
+                if (isAbleToDream && keyguardState == KeyguardState.OCCLUDED) {
+                    startTransitionTo(KeyguardState.DREAMING)
                 }
+            }
         }
     }
 
-    private fun listenForOccludedToLockscreen() {
+    private fun listenForOccludedToLockscreenOrHub() {
         scope.launch {
             keyguardInteractor.isKeyguardOccluded
                 .sample(
-                    combine(
-                        keyguardInteractor.isKeyguardShowing,
-                        transitionInteractor.startedKeyguardTransitionStep,
-                        ::Pair
-                    ),
-                    ::toTriple
+                    keyguardInteractor.isKeyguardShowing,
+                    startedKeyguardTransitionStep,
+                    communalInteractor.isIdleOnCommunal,
                 )
-                .collect { (isOccluded, isShowing, lastStartedKeyguardState) ->
+                .collect { (isOccluded, isShowing, lastStartedKeyguardState, isIdleOnCommunal) ->
                     // Occlusion signals come from the framework, and should interrupt any
                     // existing transition
                     if (
@@ -99,7 +105,13 @@
                             isShowing &&
                             lastStartedKeyguardState.to == KeyguardState.OCCLUDED
                     ) {
-                        startTransitionTo(KeyguardState.LOCKSCREEN)
+                        val to =
+                            if (isIdleOnCommunal) {
+                                KeyguardState.GLANCEABLE_HUB
+                            } else {
+                                KeyguardState.LOCKSCREEN
+                            }
+                        startTransitionTo(to)
                     }
                 }
         }
@@ -111,7 +123,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.isKeyguardShowing,
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         ::Pair
                     ),
                     ::toTriple
@@ -135,7 +147,7 @@
             powerInteractor.isAsleep
                 .sample(
                     combine(
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Pair
                     ),
@@ -154,7 +166,7 @@
     private fun listenForOccludedToAlternateBouncer() {
         scope.launch {
             keyguardInteractor.alternateBouncerShowing
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { (isAlternateBouncerShowing, lastStartedTransitionStep) ->
                     if (
                         isAlternateBouncerShowing &&
@@ -183,6 +195,7 @@
     }
 
     companion object {
+        const val TAG = "FromOccludedTransitionInteractor"
         private val DEFAULT_DURATION = 500.milliseconds
         val TO_LOCKSCREEN_DURATION = 933.milliseconds
         val TO_AOD_DURATION = DEFAULT_DURATION
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 5f246e1..acbd9fb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -18,23 +18,26 @@
 
 import android.animation.ValueAnimator
 import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.kotlin.Utils.Companion.sample
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
-import com.android.systemui.util.kotlin.Utils.Companion.toQuint
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import com.android.wm.shell.animation.Interpolators
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -47,9 +50,12 @@
 @Inject
 constructor(
     override val transitionRepository: KeyguardTransitionRepository,
-    override val transitionInteractor: KeyguardTransitionInteractor,
-    @Application private val scope: CoroutineScope,
+    transitionInteractor: KeyguardTransitionInteractor,
+    @Background private val scope: CoroutineScope,
+    @Background bgDispatcher: CoroutineDispatcher,
+    @Main mainDispatcher: CoroutineDispatcher,
     private val keyguardInteractor: KeyguardInteractor,
+    private val communalInteractor: CommunalInteractor,
     private val flags: FeatureFlags,
     private val keyguardSecurityModel: KeyguardSecurityModel,
     private val selectedUserInteractor: SelectedUserInteractor,
@@ -57,12 +63,15 @@
 ) :
     TransitionInteractor(
         fromState = KeyguardState.PRIMARY_BOUNCER,
+        transitionInteractor = transitionInteractor,
+        mainDispatcher = mainDispatcher,
+        bgDispatcher = bgDispatcher,
     ) {
 
     override fun start() {
         listenForPrimaryBouncerToGone()
         listenForPrimaryBouncerToAodOrDozing()
-        listenForPrimaryBouncerToLockscreenOrOccluded()
+        listenForPrimaryBouncerToLockscreenHubOrOccluded()
         listenForPrimaryBouncerToDreamingLockscreenHosted()
         listenForTransitionToCamera(scope, keyguardInteractor)
     }
@@ -115,21 +124,18 @@
             .distinctUntilChanged()
 
     fun dismissPrimaryBouncer() {
-        startTransitionTo(KeyguardState.GONE)
+        scope.launch { startTransitionTo(KeyguardState.GONE) }
     }
 
-    private fun listenForPrimaryBouncerToLockscreenOrOccluded() {
+    private fun listenForPrimaryBouncerToLockscreenHubOrOccluded() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
                 .sample(
-                    combine(
-                        powerInteractor.isAwake,
-                        transitionInteractor.startedKeyguardTransitionStep,
-                        keyguardInteractor.isKeyguardOccluded,
-                        keyguardInteractor.isActiveDreamLockscreenHosted,
-                        ::toQuad
-                    ),
-                    ::toQuint
+                    powerInteractor.isAwake,
+                    startedKeyguardTransitionStep,
+                    keyguardInteractor.isKeyguardOccluded,
+                    keyguardInteractor.isActiveDreamLockscreenHosted,
+                    communalInteractor.isIdleOnCommunal
                 )
                 .collect {
                     (
@@ -137,16 +143,23 @@
                         isAwake,
                         lastStartedTransitionStep,
                         occluded,
-                        isActiveDreamLockscreenHosted) ->
+                        isActiveDreamLockscreenHosted,
+                        isIdleOnCommunal) ->
                     if (
                         !isBouncerShowing &&
                             lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER &&
                             isAwake &&
                             !isActiveDreamLockscreenHosted
                     ) {
-                        startTransitionTo(
-                            if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
-                        )
+                        val toState =
+                            if (occluded) {
+                                KeyguardState.OCCLUDED
+                            } else if (isIdleOnCommunal) {
+                                KeyguardState.GLANCEABLE_HUB
+                            } else {
+                                KeyguardState.LOCKSCREEN
+                            }
+                        startTransitionTo(toState)
                     }
                 }
         }
@@ -158,7 +171,7 @@
                 .sample(
                     combine(
                         powerInteractor.isAsleep,
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Triple
                     ),
@@ -185,7 +198,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.isActiveDreamLockscreenHosted,
-                        transitionInteractor.startedKeyguardTransitionStep,
+                        startedKeyguardTransitionStep,
                         ::Pair
                     ),
                     ::toTriple
@@ -204,7 +217,7 @@
     }
 
     private fun listenForPrimaryBouncerToGone() {
-        if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+        if (KeyguardWmStateRefactor.isEnabled) {
             // This is handled in KeyguardSecurityContainerController and
             // StatusBarKeyguardViewManager, which calls the transition interactor to kick off a
             // transition vs. listening to legacy state flags.
@@ -213,7 +226,7 @@
 
         scope.launch {
             keyguardInteractor.isKeyguardGoingAway
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(startedKeyguardTransitionStep, ::Pair)
                 .collect { (isKeyguardGoingAway, lastStartedTransitionStep) ->
                     if (
                         isKeyguardGoingAway &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
new file mode 100644
index 0000000..ca66153
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launch
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalTransitionProgress
+import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.util.kotlin.sample
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.flowOn
+
+class GlanceableHubTransitions
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    private val transitionInteractor: KeyguardTransitionInteractor,
+    private val transitionRepository: KeyguardTransitionRepository,
+    private val communalInteractor: CommunalInteractor,
+) {
+    /**
+     * Listens for the glanceable hub transition to the specified scene and directly drives the
+     * keyguard transition between the lockscreen and the hub.
+     *
+     * The glanceable hub transition progress is used as the source of truth as it cannot be driven
+     * externally. The progress is used for both transitions caused by user touch input or by
+     * programmatic changes.
+     */
+    fun listenForLockscreenAndHubTransition(
+        transitionName: String,
+        transitionOwnerName: String,
+        toScene: CommunalSceneKey
+    ) {
+        val fromState: KeyguardState
+        val toState: KeyguardState
+        if (toScene == CommunalSceneKey.Blank) {
+            fromState = KeyguardState.GLANCEABLE_HUB
+            toState = KeyguardState.LOCKSCREEN
+        } else {
+            fromState = KeyguardState.LOCKSCREEN
+            toState = KeyguardState.GLANCEABLE_HUB
+        }
+        var transitionId: UUID? = null
+        scope.launch("$transitionOwnerName#$transitionName") {
+            communalInteractor
+                .transitionProgressToScene(toScene)
+                .sample(
+                    transitionInteractor.startedKeyguardTransitionStep.flowOn(bgDispatcher),
+                    ::Pair
+                )
+                .collect { pair ->
+                    val (transitionProgress, lastStartedStep) = pair
+
+                    val id = transitionId
+                    if (id == null) {
+                        // No transition started.
+                        if (
+                            transitionProgress is CommunalTransitionProgress.Transition &&
+                                lastStartedStep.to == fromState
+                        ) {
+                            transitionId =
+                                transitionRepository.startTransition(
+                                    TransitionInfo(
+                                        ownerName = transitionOwnerName,
+                                        from = fromState,
+                                        to = toState,
+                                        animator = null, // transition will be manually controlled
+                                    )
+                                )
+                        }
+                    } else {
+                        if (lastStartedStep.to != toState) {
+                            return@collect
+                        }
+                        // An existing `id` means a transition is started, and calls to
+                        // `updateTransition` will control it until FINISHED or CANCELED
+                        val nextState: TransitionState
+                        val progressFraction: Float
+                        when (transitionProgress) {
+                            is CommunalTransitionProgress.Idle -> {
+                                if (transitionProgress.scene == toScene) {
+                                    nextState = TransitionState.FINISHED
+                                    progressFraction = 1f
+                                } else {
+                                    nextState = TransitionState.CANCELED
+                                    progressFraction = 0f
+                                }
+                            }
+                            is CommunalTransitionProgress.Transition -> {
+                                nextState = TransitionState.RUNNING
+                                progressFraction = transitionProgress.progress
+                            }
+                            is CommunalTransitionProgress.OtherTransition -> {
+                                // Shouldn't happen but if another transition starts during the
+                                // current one, mark the current one as canceled.
+                                nextState = TransitionState.CANCELED
+                                progressFraction = 0f
+                            }
+                        }
+                        transitionRepository.updateTransition(
+                            id,
+                            progressFraction,
+                            nextState,
+                        )
+
+                        if (
+                            nextState == TransitionState.CANCELED ||
+                                nextState == TransitionState.FINISHED
+                        ) {
+                            transitionId = null
+                        }
+
+                        // If canceled, just put the state back.
+                        if (nextState == TransitionState.CANCELED) {
+                            transitionRepository.startTransition(
+                                TransitionInfo(
+                                    ownerName = transitionOwnerName,
+                                    from = toState,
+                                    to = fromState,
+                                    animator =
+                                        ValueAnimator().apply {
+                                            interpolator = Interpolators.LINEAR
+                                            duration = 0
+                                        }
+                                )
+                            )
+                        }
+                    }
+                }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 6eb3b64..22d11d0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
@@ -98,7 +99,7 @@
     val dozeAmount: Flow<Float> = repository.linearDozeAmount
 
     /** Whether the system is in doze mode. */
-    val isDozing: Flow<Boolean> = repository.isDozing
+    val isDozing: StateFlow<Boolean> = repository.isDozing
 
     /** Receive an event for doze time tick */
     val dozeTimeTick: Flow<Long> = repository.dozeTimeTick
@@ -162,8 +163,8 @@
     /** Whether the keyguard is showing or not. */
     val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
 
-    /** Whether the keyguard is unlocked or not. */
-    val isKeyguardUnlocked: Flow<Boolean> = repository.isKeyguardUnlocked
+    /** Whether the keyguard is dismissible or not. */
+    val isKeyguardDismissible: Flow<Boolean> = repository.isKeyguardDismissible
 
     /** Whether the keyguard is occluded (covered by an activity). */
     val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
@@ -171,6 +172,14 @@
     /** Whether the keyguard is going away. */
     val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
 
+    /** Keyguard can be clipped at the top as the shade is dragged */
+    val topClippingBounds: Flow<Int?> =
+        combine(configurationInteractor.onAnyConfigurationChange, repository.topClippingBounds) {
+            _,
+            topClippingBounds ->
+            topClippingBounds
+        }
+
     /** Last point that [KeyguardRootView] view was tapped */
     val lastRootViewTapPosition: Flow<Point?> = repository.lastRootViewTapPosition.asStateFlow()
 
@@ -186,6 +195,9 @@
     /** Observable for the [StatusBarState] */
     val statusBarState: Flow<StatusBarState> = repository.statusBarState
 
+    /** Source of the most recent biometric unlock, such as fingerprint or face. */
+    val biometricUnlockSource: Flow<BiometricUnlockSource?> = repository.biometricUnlockSource
+
     /**
      * Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
      * side, under display) is used to unlock the device.
@@ -264,6 +276,12 @@
         }
     }
 
+    /**
+     * Whether the primary authentication is required for the given user due to lockdown or
+     * encryption after reboot.
+     */
+    val isEncryptedOrLockdown: Flow<Boolean> = repository.isEncryptedOrLockdown
+
     fun dozeTransitionTo(vararg states: DozeStateModel): Flow<DozeTransitionModel> {
         return dozeTransitionModel.filter { states.contains(it.to) }
     }
@@ -322,6 +340,10 @@
         repository.keyguardDoneAnimationsFinished()
     }
 
+    fun setTopClippingBounds(top: Int?) {
+        repository.topClippingBounds.value = top
+    }
+
     companion object {
         private const val TAG = "KeyguardInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 3888345..a1f9425 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -56,6 +56,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
@@ -102,10 +103,10 @@
             quickAffordanceAlwaysVisible(position),
             keyguardInteractor.isDozing,
             keyguardInteractor.isKeyguardShowing,
-            shadeInteractor.anyExpansion,
+            shadeInteractor.anyExpansion.map { it < 1.0f }.distinctUntilChanged(),
             biometricSettingsRepository.isCurrentUserInLockdown,
-        ) { affordance, isDozing, isKeyguardShowing, qsExpansion, isUserInLockdown ->
-            if (!isDozing && isKeyguardShowing && (qsExpansion < 1.0f) && !isUserInLockdown) {
+        ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown ->
+            if (!isDozing && isKeyguardShowing && isQuickSettingsVisible && !isUserInLockdown) {
                 affordance
             } else {
                 KeyguardQuickAffordanceModel.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractor.kt
new file mode 100644
index 0000000..67b5745
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractor.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.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardSmartspaceRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+@SysUISingleton
+class KeyguardSmartspaceInteractor
+@Inject
+constructor(private val keyguardSmartspaceRepository: KeyguardSmartspaceRepository) {
+    var bcSmartspaceVisibility: StateFlow<Int> = keyguardSmartspaceRepository.bcSmartspaceVisibility
+
+    fun setBcSmartspaceVisibility(visibility: Int) {
+        keyguardSmartspaceRepository.setBcSmartspaceVisibility(visibility)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index f7d1543..b43ab5e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
@@ -36,17 +37,19 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.shareIn
 
 /** Encapsulates business-logic related to the keyguard transitions. */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class KeyguardTransitionInteractor
 @Inject
@@ -135,10 +138,18 @@
     val lockscreenToDreamingLockscreenHostedTransition: Flow<TransitionStep> =
         repository.transition(LOCKSCREEN, DREAMING_LOCKSCREEN_HOSTED)
 
+    /** LOCKSCREEN->GLANCEABLE_HUB transition information. */
+    val lockscreenToGlanceableHubTransition: Flow<TransitionStep> =
+        repository.transition(LOCKSCREEN, GLANCEABLE_HUB)
+
     /** LOCKSCREEN->OCCLUDED transition information. */
     val lockscreenToOccludedTransition: Flow<TransitionStep> =
         repository.transition(LOCKSCREEN, OCCLUDED)
 
+    /** GLANCEABLE_HUB->LOCKSCREEN transition information. */
+    val glanceableHubToLockscreenTransition: Flow<TransitionStep> =
+        repository.transition(GLANCEABLE_HUB, LOCKSCREEN)
+
     /** OCCLUDED->LOCKSCREEN transition information. */
     val occludedToLockscreenTransition: Flow<TransitionStep> =
         repository.transition(OCCLUDED, LOCKSCREEN)
@@ -154,6 +165,8 @@
     val dozingToLockscreenTransition: Flow<TransitionStep> =
         repository.transition(DOZING, LOCKSCREEN)
 
+    val transitions = repository.transitions
+
     /** Receive all [TransitionStep] matching a filter of [from]->[to] */
     fun transition(from: KeyguardState, to: KeyguardState): Flow<TransitionStep> {
         return repository.transition(from, to)
@@ -181,29 +194,121 @@
     val finishedKeyguardTransitionStep: Flow<TransitionStep> =
         repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
 
-    /** The destination state of the last started transition. */
+    /** The destination state of the last [TransitionState.STARTED] transition. */
     val startedKeyguardState: SharedFlow<KeyguardState> =
         startedKeyguardTransitionStep
             .map { step -> step.to }
             .shareIn(scope, SharingStarted.Eagerly, replay = 1)
 
-    /** The last completed [KeyguardState] transition */
+    /**
+     * The last [KeyguardState] to which we [TransitionState.FINISHED] a transition.
+     *
+     * WARNING: This will NOT emit a value if a transition is CANCELED, and will also not emit a
+     * value when a subsequent transition is STARTED. It will *only* emit once we have finally
+     * FINISHED in a state. This can have unintuitive implications.
+     *
+     * For example, if we're transitioning from GONE -> DOZING, and that transition is CANCELED in
+     * favor of a DOZING -> LOCKSCREEN transition, the FINISHED state is still GONE, and will remain
+     * GONE throughout the DOZING -> LOCKSCREEN transition until the DOZING -> LOCKSCREEN transition
+     * finishes (at which point we'll be FINISHED in LOCKSCREEN).
+     *
+     * Since there's no real limit to how many consecutive transitions can be canceled, it's even
+     * possible for the FINISHED state to be the same as the STARTED state while still
+     * transitioning.
+     *
+     * For example:
+     * 1. We're finished in GONE.
+     * 2. The user presses the power button, starting a GONE -> DOZING transition. We're still
+     *    FINISHED in GONE.
+     * 3. The user changes their mind, pressing the power button to wake up; this starts a DOZING ->
+     *    LOCKSCREEN transition. We're still FINISHED in GONE.
+     * 4. The user quickly swipes away the lockscreen prior to DOZING -> LOCKSCREEN finishing; this
+     *    starts a LOCKSCREEN -> GONE transition. We're still FINISHED in GONE, but we've also
+     *    STARTED a transition *to* GONE.
+     * 5. We'll emit KeyguardState.GONE again once the transition finishes.
+     *
+     * If you just need to know when we eventually settle into a state, this flow is likely
+     * sufficient. However, if you're having issues with state *during* transitions started after
+     * one or more canceled transitions, you probably need to use [currentKeyguardState].
+     */
     val finishedKeyguardState: SharedFlow<KeyguardState> =
         finishedKeyguardTransitionStep
             .map { step -> step.to }
             .shareIn(scope, SharingStarted.Eagerly, replay = 1)
 
     /**
-     * Whether we're currently in a transition to a new [KeyguardState] and haven't yet completed
-     * it.
+     * The [KeyguardState] we're currently in.
+     *
+     * If we're not in transition, this is simply the [finishedKeyguardState]. If we're in
+     * transition, this is the state we're transitioning *from*.
+     *
+     * Absent CANCELED transitions, [currentKeyguardState] and [finishedKeyguardState] are always
+     * identical - if a transition FINISHES in a given state, the subsequent state we START a
+     * transition *from* would always be that same previously FINISHED state.
+     *
+     * However, if a transition is CANCELED, the next transition will START from a state we never
+     * FINISHED in. For example, if we transition from GONE -> DOZING, but CANCEL that transition in
+     * favor of DOZING -> LOCKSCREEN, we've STARTED a transition *from* DOZING despite never
+     * FINISHING in DOZING. Thus, the current state will be DOZING but the FINISHED state will still
+     * be GONE.
+     *
+     * In this example, if there was DOZING-related state that needs to be set up in order to
+     * properly render a DOZING -> LOCKSCREEN transition, it would never be set up if we were
+     * listening for [finishedKeyguardState] to emit DOZING. However, [currentKeyguardState] would
+     * emit DOZING immediately upon STARTING DOZING -> LOCKSCREEN, allowing us to set up the state.
+     *
+     * Whether you want to use [currentKeyguardState] or [finishedKeyguardState] depends on your
+     * specific use case and how you want to handle cancellations. In general, if you're dealing
+     * with state/UI present across multiple [KeyguardState]s, you probably want
+     * [currentKeyguardState]. If you're dealing with state/UI encapsulated within a single state,
+     * you likely want [finishedKeyguardState].
+     *
+     * As an example, let's say you want to animate in a message on the lockscreen UI after waking
+     * up, and that TextView is not involved in animations between states. You'd want to collect
+     * [finishedKeyguardState], so you'll only animate it in once we're settled on the lockscreen.
+     * If you use [currentKeyguardState] in this case, a DOZING -> LOCKSCREEN transition that is
+     * interrupted by a LOCKSCREEN -> GONE transition would cause the message to become visible
+     * immediately upon LOCKSCREEN -> GONE STARTING, as the current state would become LOCKSCREEN in
+     * that case. That's likely not what you want.
+     *
+     * On the other hand, let's say you're animating the smartspace from alpha 0f to 1f during
+     * DOZING -> LOCKSCREEN, but the transition is interrupted by LOCKSCREEN -> GONE. LS -> GONE
+     * needs the smartspace to be alpha=1f so that it can play the shared-element unlock animation.
+     * In this case, we'd want to collect [currentKeyguardState] and ensure the smartspace is
+     * visible when the current state is LOCKSCREEN. If you use [finishedKeyguardState] in this
+     * case, the smartspace will never be set to alpha = 1f and you'll have a half-faded smartspace
+     * during the LS -> GONE transition.
+     *
+     * If you need special-case handling for cancellations (such as conditional handling depending
+     * on which [KeyguardState] was canceled) you can collect [canceledKeyguardTransitionStep]
+     * directly.
+     *
+     * As a helpful footnote, here's the values of [finishedKeyguardState] and
+     * [currentKeyguardState] during a sequence with two cancellations:
+     * 1. We're FINISHED in GONE. currentKeyguardState=GONE; finishedKeyguardState=GONE.
+     * 2. We START a transition from GONE -> DOZING. currentKeyguardState=GONE;
+     *    finishedKeyguardState=GONE.
+     * 3. We CANCEL this transition and START a transition from DOZING -> LOCKSCREEN.
+     *    currentKeyguardState=DOZING; finishedKeyguardState=GONE.
+     * 4. We subsequently also CANCEL DOZING -> LOCKSCREEN and START LOCKSCREEN -> GONE.
+     *    currentKeyguardState=LOCKSCREEN finishedKeyguardState=GONE.
+     * 5. LOCKSCREEN -> GONE is allowed to FINISH. currentKeyguardState=GONE;
+     *    finishedKeyguardState=GONE.
      */
-    val isInTransitionToAnyState =
-        combine(
-            startedKeyguardTransitionStep,
-            finishedKeyguardState,
-        ) { startedStep, finishedState ->
-            startedStep.to != finishedState
-        }
+    val currentKeyguardState: SharedFlow<KeyguardState> =
+        repository.transitions
+            .mapLatest {
+                if (it.transitionState == TransitionState.FINISHED) {
+                    it.to
+                } else {
+                    it.from
+                }
+            }
+            .distinctUntilChanged()
+            .shareIn(scope, SharingStarted.Eagerly, replay = 1)
+
+    /** Whether we've currently STARTED a transition and haven't yet FINISHED it. */
+    val isInTransitionToAnyState = isInTransitionWhere({ true }, { true })
 
     /**
      * The amount of transition into or out of the given [KeyguardState].
@@ -293,13 +398,12 @@
         fromStatePredicate: (KeyguardState) -> Boolean,
         toStatePredicate: (KeyguardState) -> Boolean,
     ): Flow<Boolean> {
-        return combine(
-                startedKeyguardTransitionStep,
-                finishedKeyguardState,
-            ) { startedStep, finishedState ->
-                fromStatePredicate(startedStep.from) &&
-                    toStatePredicate(startedStep.to) &&
-                    finishedState != startedStep.to
+        return repository.transitions
+            .filter { it.transitionState != TransitionState.CANCELED }
+            .mapLatest {
+                it.transitionState != TransitionState.FINISHED &&
+                    fromStatePredicate(it.from) &&
+                    toStatePredicate(it.to)
             }
             .distinctUntilChanged()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index fbf6936..19d00cf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -22,12 +22,15 @@
 import com.android.systemui.keyguard.data.repository.LightRevealScrimRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
 import com.android.systemui.statusbar.LightRevealEffect
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
 
 @ExperimentalCoroutinesApi
@@ -39,6 +42,7 @@
     private val lightRevealScrimRepository: LightRevealScrimRepository,
     @Application private val scope: CoroutineScope,
     private val scrimLogger: ScrimLogger,
+    powerInteractor: PowerInteractor,
 ) {
 
     init {
@@ -73,7 +77,16 @@
             lightRevealScrimRepository.revealEffect
         )
 
-    val revealAmount = lightRevealScrimRepository.revealAmount
+    val revealAmount =
+        lightRevealScrimRepository.revealAmount.filter {
+            // When the screen is off we do not want to keep producing frames as this is causing
+            // (invisible) jank. However, we need to still pass through 1f and 0f to ensure that the
+            // correct end states are respected even if the screen turned off (or was still off)
+            // when the animation finished
+            powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_OFF ||
+                it == 1f ||
+                it == 0f
+        }
 
     companion object {
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index d5ac283..5c2df45 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -24,8 +24,11 @@
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
 import com.android.systemui.util.kotlin.sample
 import java.util.UUID
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 /**
  * Each TransitionInteractor is responsible for determining under which conditions to notify
@@ -40,14 +43,25 @@
  */
 sealed class TransitionInteractor(
     val fromState: KeyguardState,
+    val transitionInteractor: KeyguardTransitionInteractor,
+    val mainDispatcher: CoroutineDispatcher,
+    val bgDispatcher: CoroutineDispatcher,
 ) {
     val name = this::class.simpleName ?: "UnknownTransitionInteractor"
-
     abstract val transitionRepository: KeyguardTransitionRepository
-    abstract val transitionInteractor: KeyguardTransitionInteractor
     abstract fun start()
 
-    fun startTransitionTo(
+    /* Use background dispatcher for all [KeyguardTransitionInteractor] flows. Necessary because
+     * the [sample] utility internally runs a collect on the Unconfined dispatcher, resulting
+     * in continuations on the main thread. We don't want that for classes that inherit from this.
+     */
+    val startedKeyguardTransitionStep =
+        transitionInteractor.startedKeyguardTransitionStep.flowOn(bgDispatcher)
+    // The following are MutableSharedFlows, and do not require flowOn
+    val startedKeyguardState = transitionInteractor.startedKeyguardState
+    val finishedKeyguardState = transitionInteractor.finishedKeyguardState
+
+    suspend fun startTransitionTo(
         toState: KeyguardState,
         animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
         modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE
@@ -67,16 +81,17 @@
             )
             return null
         }
-
-        return transitionRepository.startTransition(
-            TransitionInfo(
-                name,
-                fromState,
-                toState,
-                animator,
-                modeOnCanceled,
+        return withContext(mainDispatcher) {
+            transitionRepository.startTransition(
+                TransitionInfo(
+                    name,
+                    fromState,
+                    toState,
+                    animator,
+                    modeOnCanceled,
+                )
             )
-        )
+        }
     }
 
     /** This signal may come in before the occlusion signal, and can provide a custom transition */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index cf1d247..4abda74 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -17,9 +17,13 @@
 
 import android.view.animation.Interpolator
 import com.android.app.animation.Interpolators.LINEAR
+import com.android.app.tracing.coroutines.launch
 import com.android.keyguard.logging.KeyguardTransitionAnimationLogger
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
 import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
@@ -31,10 +35,12 @@
 import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.BufferOverflow
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
 
 /**
  * Assists in creating sub-flows for a KeyguardTransition. Call [setup] once for a transition, and
@@ -45,21 +51,49 @@
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
+    private val transitionInteractor: KeyguardTransitionInteractor,
     private val logger: KeyguardTransitionAnimationLogger,
 ) {
+    private val transitionMap = mutableMapOf<Edge, MutableSharedFlow<TransitionStep>>()
 
-    /**
-     * Invoke once per transition between FROM->TO states to get access to
-     * [SharedFlowBuilder#sharedFlow].
-     */
+    init {
+        scope.launch("KeyguardTransitionAnimationFlow") {
+            transitionInteractor.transitions.collect {
+                // FROM->TO
+                transitionMap[Edge(it.from, it.to)]?.emit(it)
+                // FROM->(ANY)
+                transitionMap[Edge(it.from, null)]?.emit(it)
+                // (ANY)->TO
+                transitionMap[Edge(null, it.to)]?.emit(it)
+            }
+        }
+    }
+
+    private fun getOrCreateFlow(edge: Edge): MutableSharedFlow<TransitionStep> {
+        return transitionMap.getOrPut(edge) {
+            MutableSharedFlow<TransitionStep>(
+                extraBufferCapacity = 10,
+                onBufferOverflow = BufferOverflow.DROP_OLDEST
+            )
+        }
+    }
+
+    /** Invoke once per transition between FROM->TO states to get access to a shared flow. */
     fun setup(
         duration: Duration,
-        stepFlow: Flow<TransitionStep>,
-    ) = SharedFlowBuilder(duration, stepFlow)
+        from: KeyguardState?,
+        to: KeyguardState?,
+    ): FlowBuilder {
+        if (from == null && to == null) {
+            throw IllegalArgumentException("from and to are both null")
+        }
 
-    inner class SharedFlowBuilder(
+        return FlowBuilder(duration, Edge(from, to))
+    }
+
+    inner class FlowBuilder(
         private val transitionDuration: Duration,
-        private val stepFlow: Flow<TransitionStep>,
+        private val edge: Edge,
     ) {
         /**
          * Transitions will occur over a [transitionDuration] with [TransitionStep]s being emitted
@@ -115,20 +149,21 @@
                 }?.let { onStep(interpolator.getInterpolation(it)) }
             }
 
-            return stepFlow
+            return getOrCreateFlow(edge)
                 .map { step ->
-                    val value =
-                        when (step.transitionState) {
-                            STARTED -> stepToValue(step)
-                            RUNNING -> stepToValue(step)
-                            CANCELED -> onCancel?.invoke()
-                            FINISHED -> onFinish?.invoke()
-                        }
-                    logger.logTransitionStep(name, step, value)
-                    value
+                    StateToValue(
+                            step.transitionState,
+                            when (step.transitionState) {
+                                STARTED -> stepToValue(step)
+                                RUNNING -> stepToValue(step)
+                                CANCELED -> onCancel?.invoke()
+                                FINISHED -> onFinish?.invoke()
+                            }
+                        )
+                        .also { logger.logTransitionStep(name, step, it.value) }
                 }
-                .filterNotNull()
                 .distinctUntilChanged()
+                .mapNotNull { stateToValue -> stateToValue.value }
         }
 
         /**
@@ -138,4 +173,14 @@
             return sharedFlow(duration = 1.milliseconds, onStep = { value }, onFinish = { value })
         }
     }
+
+    data class Edge(
+        val from: KeyguardState?,
+        val to: KeyguardState?,
+    )
+
+    data class StateToValue(
+        val transitionState: TransitionState,
+        val value: Float?,
+    )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index a02e8ac..703bb87 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
 
@@ -48,6 +49,7 @@
     @SuppressLint("ClickableViewAccessibility")
     @JvmStatic
     fun bind(
+        applicationScope: CoroutineScope,
         view: DeviceEntryIconView,
         viewModel: DeviceEntryIconViewModel,
         fgViewModel: DeviceEntryForegroundViewModel,
@@ -69,7 +71,7 @@
                         view,
                         HapticFeedbackConstants.CONFIRM,
                     )
-                    viewModel.onLongPress()
+                    applicationScope.launch { viewModel.onLongPress() }
                 }
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index 3dd3e07..8d6493f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -24,6 +24,7 @@
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.BaseBlueprintTransition
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -53,7 +54,8 @@
                                 }
 
                             // Apply transition.
-                            if (prevBluePrint != null && prevBluePrint != blueprint) {
+                            if (!keyguardBottomAreaRefactor() && prevBluePrint != null &&
+                                prevBluePrint != blueprint) {
                                 TransitionManager.beginDelayedTransition(
                                     constraintLayout,
                                     BaseBlueprintTransition()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index 05fe0b2..3c3ebdf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -16,14 +16,24 @@
 
 package com.android.systemui.keyguard.ui.binder
 
+import android.animation.Animator
+import android.animation.ValueAnimator
+import android.transition.Transition
 import android.transition.TransitionManager
+import android.transition.TransitionSet
+import android.transition.TransitionValues
+import android.view.ViewGroup
 import androidx.annotation.VisibleForTesting
 import androidx.constraintlayout.helper.widget.Layer
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators
+import com.android.keyguard.KeyguardClockSwitch.LARGE
+import com.android.keyguard.KeyguardClockSwitch.SMALL
 import com.android.systemui.Flags.migrateClocksToBlueprint
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
@@ -32,6 +42,8 @@
 import com.android.systemui.res.R
 import kotlinx.coroutines.launch
 
+private const val KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION_MS = 1000L
+
 object KeyguardClockViewBinder {
     @JvmStatic
     fun bind(
@@ -39,6 +51,7 @@
         keyguardRootView: ConstraintLayout,
         viewModel: KeyguardClockViewModel,
         keyguardClockInteractor: KeyguardClockInteractor,
+        blueprintInteractor: KeyguardBlueprintInteractor,
     ) {
         keyguardRootView.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.CREATED) {
@@ -52,29 +65,62 @@
                     viewModel.currentClock.collect { currentClock ->
                         cleanupClockViews(viewModel.clock, keyguardRootView, viewModel.burnInLayer)
                         viewModel.clock = currentClock
-                        addClockViews(currentClock, keyguardRootView, viewModel.burnInLayer)
-                        viewModel.burnInLayer?.updatePostLayout(keyguardRootView)
-                        applyConstraints(clockSection, keyguardRootView, true)
+                        addClockViews(currentClock, keyguardRootView)
+                        updateBurnInLayer(keyguardRootView, viewModel)
+                        blueprintInteractor.refreshBlueprint()
                     }
                 }
-                // TODO: Weather clock dozing animation
-                // will trigger both shouldBeCentered and clockSize change
-                // we should avoid this
                 launch {
                     if (!migrateClocksToBlueprint()) return@launch
                     viewModel.clockSize.collect {
-                        applyConstraints(clockSection, keyguardRootView, true)
+                        updateBurnInLayer(keyguardRootView, viewModel)
+                        blueprintInteractor.refreshBlueprint()
                     }
                 }
                 launch {
                     if (!migrateClocksToBlueprint()) return@launch
                     viewModel.clockShouldBeCentered.collect {
+                        viewModel.clock?.let {
+                            if (it.largeClock.config.hasCustomPositionUpdatedAnimation) {
+                                playClockCenteringAnimation(clockSection, keyguardRootView, it)
+                            } else {
+                                blueprintInteractor.refreshBlueprint()
+                            }
+                        }
+                    }
+                }
+                launch {
+                    if (!migrateClocksToBlueprint()) return@launch
+                    viewModel.isAodIconsVisible.collect {
                         applyConstraints(clockSection, keyguardRootView, true)
                     }
                 }
             }
         }
     }
+    @VisibleForTesting
+    fun updateBurnInLayer(
+        keyguardRootView: ConstraintLayout,
+        viewModel: KeyguardClockViewModel,
+    ) {
+        val burnInLayer = viewModel.burnInLayer
+        val clockController = viewModel.currentClock.value
+        clockController?.let { clock ->
+            when (viewModel.clockSize.value) {
+                LARGE -> {
+                    clock.smallClock.layout.views.forEach { burnInLayer?.removeView(it) }
+                    if (clock.config.useAlternateSmartspaceAODTransition) {
+                        clock.largeClock.layout.views.forEach { burnInLayer?.addView(it) }
+                    }
+                }
+                SMALL -> {
+                    clock.smallClock.layout.views.forEach { burnInLayer?.addView(it) }
+                    clock.largeClock.layout.views.forEach { burnInLayer?.removeView(it) }
+                }
+            }
+        }
+        viewModel.burnInLayer?.updatePostLayout(keyguardRootView)
+    }
 
     private fun cleanupClockViews(
         clockController: ClockController?,
@@ -101,7 +147,6 @@
     fun addClockViews(
         clockController: ClockController?,
         rootView: ConstraintLayout,
-        burnInLayer: Layer?
     ) {
         clockController?.let { clock ->
             clock.smallClock.layout.views[0].id = R.id.lockscreen_clock_view
@@ -110,27 +155,92 @@
             }
             // small clock should either be a single view or container with id
             // `lockscreen_clock_view`
-            clock.smallClock.layout.views.forEach {
-                rootView.addView(it)
-                burnInLayer?.addView(it)
-            }
+            clock.smallClock.layout.views.forEach { rootView.addView(it) }
             clock.largeClock.layout.views.forEach { rootView.addView(it) }
-            if (clock.config.useAlternateSmartspaceAODTransition) {
-                clock.largeClock.layout.views.forEach { burnInLayer?.addView(it) }
-            }
         }
     }
-
     fun applyConstraints(
         clockSection: ClockSection,
         rootView: ConstraintLayout,
         animated: Boolean,
+        set: TransitionSet? = null,
     ) {
         val constraintSet = ConstraintSet().apply { clone(rootView) }
         clockSection.applyConstraints(constraintSet)
         if (animated) {
-            TransitionManager.beginDelayedTransition(rootView)
+            set?.let { TransitionManager.beginDelayedTransition(rootView, it) }
+                ?: run { TransitionManager.beginDelayedTransition(rootView) }
         }
         constraintSet.applyTo(rootView)
     }
+
+    private fun playClockCenteringAnimation(
+        clockSection: ClockSection,
+        keyguardRootView: ConstraintLayout,
+        clock: ClockController,
+    ) {
+        // Find the clock, so we can exclude it from this transition.
+        val clockView = clock.largeClock.view
+        val set = TransitionSet()
+        val adapter = SplitShadeTransitionAdapter(clock)
+        adapter.setInterpolator(Interpolators.LINEAR)
+        adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION_MS)
+        adapter.addTarget(clockView)
+        set.addTransition(adapter)
+        applyConstraints(clockSection, keyguardRootView, true, set)
+    }
+
+    internal class SplitShadeTransitionAdapter
+    @VisibleForTesting
+    constructor(private val clock: ClockController) : Transition() {
+        private fun captureValues(transitionValues: TransitionValues) {
+            transitionValues.values[PROP_BOUNDS_LEFT] = transitionValues.view.left
+            val locationInWindowTmp = IntArray(2)
+            transitionValues.view.getLocationInWindow(locationInWindowTmp)
+            transitionValues.values[PROP_X_IN_WINDOW] = locationInWindowTmp[0]
+        }
+
+        override fun captureEndValues(transitionValues: TransitionValues) {
+            captureValues(transitionValues)
+        }
+
+        override fun captureStartValues(transitionValues: TransitionValues) {
+            captureValues(transitionValues)
+        }
+
+        override fun createAnimator(
+            sceneRoot: ViewGroup,
+            startValues: TransitionValues?,
+            endValues: TransitionValues?
+        ): Animator? {
+            if (startValues == null || endValues == null) {
+                return null
+            }
+            val anim = ValueAnimator.ofFloat(0f, 1f)
+            val fromLeft = startValues.values[PROP_BOUNDS_LEFT] as Int
+            val fromWindowX = startValues.values[PROP_X_IN_WINDOW] as Int
+            val toWindowX = endValues.values[PROP_X_IN_WINDOW] as Int
+            // Using windowX, to determine direction, instead of left, as in RTL the difference of
+            // toLeft - fromLeft is always positive, even when moving left.
+            val direction = if (toWindowX - fromWindowX > 0) 1 else -1
+            anim.addUpdateListener { animation: ValueAnimator ->
+                clock.largeClock.animations.onPositionUpdated(
+                    fromLeft,
+                    direction,
+                    animation.animatedFraction
+                )
+            }
+            return anim
+        }
+
+        override fun getTransitionProperties(): Array<String> {
+            return TRANSITION_PROPERTIES
+        }
+
+        companion object {
+            private const val PROP_BOUNDS_LEFT = "splitShadeTransitionAdapter:boundsLeft"
+            private const val PROP_X_IN_WINDOW = "splitShadeTransitionAdapter:xInWindow"
+            private val TRANSITION_PROPERTIES = arrayOf(PROP_BOUNDS_LEFT, PROP_X_IN_WINDOW)
+        }
+    }
 }
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 2aebd99..48092c6 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
@@ -21,6 +21,7 @@
 import android.annotation.DrawableRes
 import android.annotation.SuppressLint
 import android.graphics.Point
+import android.graphics.Rect
 import android.view.HapticFeedbackConstants
 import android.view.View
 import android.view.View.OnLayoutChangeListener
@@ -158,6 +159,23 @@
                         }
 
                         launch {
+                            val clipBounds = Rect()
+                            viewModel.topClippingBounds.collect { clipTop ->
+                                if (clipTop == null) {
+                                    view.setClipBounds(null)
+                                } else {
+                                    clipBounds.apply {
+                                        top = clipTop
+                                        left = view.getLeft()
+                                        right = view.getRight()
+                                        bottom = view.getBottom()
+                                    }
+                                    view.setClipBounds(clipBounds)
+                                }
+                            }
+                        }
+
+                        launch {
                             viewModel.lockscreenStateAlpha.collect { alpha ->
                                 childViews[statusViewId]?.alpha = alpha
                             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
index 92270ad..10392e3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
@@ -16,14 +16,13 @@
 
 package com.android.systemui.keyguard.ui.binder
 
-import android.transition.TransitionManager
 import android.view.View
 import androidx.constraintlayout.helper.widget.Layer
 import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.constraintlayout.widget.ConstraintSet
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
+import com.android.systemui.Flags.migrateClocksToBlueprint
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -34,32 +33,67 @@
 object KeyguardSmartspaceViewBinder {
     @JvmStatic
     fun bind(
-        smartspaceSection: SmartspaceSection,
         keyguardRootView: ConstraintLayout,
         clockViewModel: KeyguardClockViewModel,
         smartspaceViewModel: KeyguardSmartspaceViewModel,
+        blueprintInteractor: KeyguardBlueprintInteractor,
     ) {
         keyguardRootView.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
                 launch {
+                    if (!migrateClocksToBlueprint()) return@launch
                     clockViewModel.hasCustomWeatherDataDisplay.collect { hasCustomWeatherDataDisplay
                         ->
-                        if (hasCustomWeatherDataDisplay) {
-                            removeDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel)
-                        } else {
-                            addDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel)
-                        }
-                        clockViewModel.burnInLayer?.updatePostLayout(keyguardRootView)
-                        val constraintSet = ConstraintSet().apply { clone(keyguardRootView) }
-                        smartspaceSection.applyConstraints(constraintSet)
-                        TransitionManager.beginDelayedTransition(keyguardRootView)
-                        constraintSet.applyTo(keyguardRootView)
+                        updateDateWeatherToBurnInLayer(
+                            keyguardRootView,
+                            clockViewModel,
+                            smartspaceViewModel
+                        )
+                        blueprintInteractor.refreshBlueprint()
+                    }
+                }
+
+                launch {
+                    smartspaceViewModel.bcSmartspaceVisibility.collect {
+                        updateBCSmartspaceInBurnInLayer(keyguardRootView, clockViewModel)
+                        blueprintInteractor.refreshBlueprint()
                     }
                 }
             }
         }
     }
 
+    private fun updateBCSmartspaceInBurnInLayer(
+        keyguardRootView: ConstraintLayout,
+        clockViewModel: KeyguardClockViewModel,
+    ) {
+        // Visibility is controlled by updateTargetVisibility in CardPagerAdapter
+        val burnInLayer = keyguardRootView.requireViewById<Layer>(R.id.burn_in_layer)
+        burnInLayer.apply {
+            val smartspaceView =
+                keyguardRootView.requireViewById<View>(sharedR.id.bc_smartspace_view)
+            if (smartspaceView.visibility == View.VISIBLE) {
+                addView(smartspaceView)
+            } else {
+                removeView(smartspaceView)
+            }
+        }
+        clockViewModel.burnInLayer?.updatePostLayout(keyguardRootView)
+    }
+
+    private fun updateDateWeatherToBurnInLayer(
+        keyguardRootView: ConstraintLayout,
+        clockViewModel: KeyguardClockViewModel,
+        smartspaceViewModel: KeyguardSmartspaceViewModel
+    ) {
+        if (clockViewModel.hasCustomWeatherDataDisplay.value) {
+            removeDateWeatherFromBurnInLayer(keyguardRootView, smartspaceViewModel)
+        } else {
+            addDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel)
+        }
+        clockViewModel.burnInLayer?.updatePostLayout(keyguardRootView)
+    }
+
     private fun addDateWeatherToBurnInLayer(
         constraintLayout: ConstraintLayout,
         smartspaceViewModel: KeyguardSmartspaceViewModel
@@ -74,13 +108,13 @@
                     constraintLayout.requireViewById<View>(sharedR.id.date_smartspace_view)
                 val weatherView =
                     constraintLayout.requireViewById<View>(sharedR.id.weather_smartspace_view)
-                addView(weatherView)
                 addView(dateView)
+                addView(weatherView)
             }
         }
     }
 
-    private fun removeDateWeatherToBurnInLayer(
+    private fun removeDateWeatherFromBurnInLayer(
         constraintLayout: ConstraintLayout,
         smartspaceViewModel: KeyguardSmartspaceViewModel
     ) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt
deleted file mode 100644
index 2feaa2e..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.ui.binder
-
-import android.os.Trace
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.launch
-
-/**
- * Binds the existing blueprint to the constraint layout that previews keyguard.
- *
- * This view binder should only inflate and add relevant views and apply the constraints. Actual
- * data binding should be done in {@link KeyguardPreviewRenderer}
- */
-class PreviewKeyguardBlueprintViewBinder {
-    companion object {
-
-        /**
-         * Binds the existing blueprint to the constraint layout that previews keyguard.
-         *
-         * @param constraintLayout The root view to bind to
-         * @param viewModel The instance of the view model that contains flows we collect on.
-         * @param finishedAddViewCallback Called when we have finished inflating the views.
-         */
-        fun bind(
-            constraintLayout: ConstraintLayout,
-            viewModel: KeyguardBlueprintViewModel,
-            finishedAddViewCallback: () -> Unit
-        ): DisposableHandle {
-            return constraintLayout.repeatWhenAttached {
-                repeatOnLifecycle(Lifecycle.State.CREATED) {
-                    launch {
-                        viewModel.blueprint.collect { blueprint ->
-                            val prevBluePrint = viewModel.currentBluePrint
-                            Trace.beginSection("PreviewKeyguardBlueprint#applyBlueprint")
-
-                            ConstraintSet().apply {
-                                clone(constraintLayout)
-                                val emptyLayout = ConstraintSet.Layout()
-                                knownIds.forEach { getConstraint(it).layout.copyFrom(emptyLayout) }
-                                blueprint.applyConstraints(this)
-                                // Add and remove views of sections that are not contained by the
-                                // other.
-                                blueprint.replaceViews(
-                                    prevBluePrint,
-                                    constraintLayout,
-                                    bindData = false
-                                )
-                                applyTo(constraintLayout)
-                            }
-
-                            viewModel.currentBluePrint = blueprint
-                            finishedAddViewCallback.invoke()
-                            Trace.endSection()
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
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 eb3afb7..a0c0095 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
@@ -39,7 +39,9 @@
 import android.view.ViewGroup
 import android.view.WindowManager
 import android.widget.FrameLayout
+import android.widget.TextView
 import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
 import androidx.core.view.isInvisible
 import com.android.keyguard.ClockEventController
 import com.android.keyguard.KeyguardClockSwitch
@@ -48,18 +50,17 @@
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.communal.ui.binder.CommunalTutorialIndicatorViewBinder
+import com.android.systemui.communal.ui.viewmodel.CommunalTutorialIndicatorViewModel
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
-import com.android.systemui.keyguard.ui.binder.PreviewKeyguardBlueprintViewBinder
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel
@@ -121,18 +122,18 @@
     private val broadcastDispatcher: BroadcastDispatcher,
     private val lockscreenSmartspaceController: LockscreenSmartspaceController,
     private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
-    private val featureFlags: FeatureFlagsClassic,
     private val falsingManager: FalsingManager,
     private val vibratorHelper: VibratorHelper,
     private val indicationController: KeyguardIndicationController,
     private val keyguardRootViewModel: KeyguardRootViewModel,
     @Assisted bundle: Bundle,
-    private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel,
     private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
     private val chipbarCoordinator: ChipbarCoordinator,
     private val screenOffAnimationController: ScreenOffAnimationController,
     private val shadeInteractor: ShadeInteractor,
     private val secureSettings: SecureSettings,
+    private val communalTutorialViewModel: CommunalTutorialIndicatorViewModel,
+    private val defaultShortcutsSection: DefaultShortcutsSection,
 ) {
     val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
     private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
@@ -389,30 +390,32 @@
 
         setUpUdfps(previewContext, rootView)
 
-        disposables.add(
-            PreviewKeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) {
-                if (keyguardBottomAreaRefactor()) {
-                    setupShortcuts(keyguardRootView)
-                }
+        if (keyguardBottomAreaRefactor()) {
+            setupShortcuts(keyguardRootView)
+        }
 
-                if (!shouldHideClock) {
-                    setUpClock(previewContext, rootView)
-                    KeyguardPreviewClockViewBinder.bind(
-                        largeClockHostView,
-                        smallClockHostView,
-                        clockViewModel,
-                    )
-                }
+        if (!shouldHideClock) {
+            setUpClock(previewContext, rootView)
+            KeyguardPreviewClockViewBinder.bind(
+                largeClockHostView,
+                smallClockHostView,
+                clockViewModel,
+            )
+        }
 
-                setUpSmartspace(previewContext, rootView)
-                smartSpaceView?.let {
-                    KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel)
-                }
-            }
-        )
+        setUpSmartspace(previewContext, rootView)
+        smartSpaceView?.let { KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel) }
+        setupCommunalTutorialIndicator(keyguardRootView)
     }
 
     private fun setupShortcuts(keyguardRootView: ConstraintLayout) {
+        // Add shortcuts
+        val cs = ConstraintSet()
+        cs.clone(keyguardRootView)
+        defaultShortcutsSection.addViews(keyguardRootView)
+        defaultShortcutsSection.applyConstraints(cs)
+        cs.applyTo(keyguardRootView)
+
         keyguardRootView.findViewById<LaunchableImageView?>(R.id.start_button)?.let { imageView ->
             shortcutsBindings.add(
                 KeyguardQuickAffordanceViewBinder.bind(
@@ -470,53 +473,40 @@
     }
 
     private fun setUpClock(previewContext: Context, parentView: ViewGroup) {
-        largeClockHostView =
-            if (KeyguardShadeMigrationNssl.isEnabled) {
-                parentView.requireViewById<FrameLayout>(R.id.lockscreen_clock_view_large)
-            } else {
-                val hostView = FrameLayout(previewContext)
-                hostView.layoutParams =
-                    FrameLayout.LayoutParams(
-                        FrameLayout.LayoutParams.MATCH_PARENT,
-                        FrameLayout.LayoutParams.MATCH_PARENT,
-                    )
-                parentView.addView(hostView)
-                hostView
-            }
+        val resources = parentView.resources
+        largeClockHostView = FrameLayout(previewContext)
+        largeClockHostView.layoutParams =
+            FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.MATCH_PARENT,
+                FrameLayout.LayoutParams.MATCH_PARENT,
+            )
+        parentView.addView(largeClockHostView)
         largeClockHostView.isInvisible = true
 
-        smallClockHostView =
-            if (KeyguardShadeMigrationNssl.isEnabled) {
-                parentView.requireViewById<FrameLayout>(R.id.lockscreen_clock_view)
-            } else {
-                val resources = parentView.resources
-                val hostView = FrameLayout(previewContext)
-                val layoutParams =
-                    FrameLayout.LayoutParams(
-                        FrameLayout.LayoutParams.WRAP_CONTENT,
-                        resources.getDimensionPixelSize(
-                            com.android.systemui.customization.R.dimen.small_clock_height
-                        )
-                    )
-                layoutParams.topMargin =
-                    KeyguardPreviewSmartspaceViewModel.getStatusBarHeight(resources) +
-                        resources.getDimensionPixelSize(
-                            com.android.systemui.customization.R.dimen.small_clock_padding_top
-                        )
-                hostView.layoutParams = layoutParams
-
-                hostView.setPaddingRelative(
-                    resources.getDimensionPixelSize(
-                        com.android.systemui.customization.R.dimen.clock_padding_start
-                    ),
-                    0,
-                    0,
-                    0
+        smallClockHostView = FrameLayout(previewContext)
+        val layoutParams =
+            FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.WRAP_CONTENT,
+                resources.getDimensionPixelSize(
+                    com.android.systemui.customization.R.dimen.small_clock_height
                 )
-                hostView.clipChildren = false
-                parentView.addView(hostView)
-                hostView
-            }
+            )
+        layoutParams.topMargin =
+            KeyguardPreviewSmartspaceViewModel.getStatusBarHeight(resources) +
+                resources.getDimensionPixelSize(
+                    com.android.systemui.customization.R.dimen.small_clock_padding_top
+                )
+        smallClockHostView.layoutParams = layoutParams
+        smallClockHostView.setPaddingRelative(
+            resources.getDimensionPixelSize(
+                com.android.systemui.customization.R.dimen.clock_padding_start
+            ),
+            0,
+            0,
+            0
+        )
+        smallClockHostView.clipChildren = false
+        parentView.addView(smallClockHostView)
         smallClockHostView.isInvisible = true
 
         // TODO (b/283465254): Move the listeners to KeyguardClockRepository
@@ -601,6 +591,17 @@
         }
     }
 
+    private fun setupCommunalTutorialIndicator(keyguardRootView: ConstraintLayout) {
+        keyguardRootView.findViewById<TextView>(R.id.communal_tutorial_indicator)?.let {
+            indicatorView ->
+            CommunalTutorialIndicatorViewBinder.bind(
+                indicatorView,
+                communalTutorialViewModel,
+                isPreviewMode = true,
+            )
+        }
+    }
+
     private suspend fun fetchThemeStyleFromSetting(): Style {
         val overlayPackageJson =
             withContext(backgroundDispatcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
index 920fc04..fab60e8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
@@ -17,6 +17,7 @@
 
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToAodTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToPrimaryBouncerTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
@@ -53,6 +54,12 @@
 
     @Binds
     @IntoSet
+    abstract fun alternateBouncerToPrimaryBouncer(
+        impl: AlternateBouncerToPrimaryBouncerTransitionViewModel
+    ): DeviceEntryIconTransition
+
+    @Binds
+    @IntoSet
     abstract fun aodToLockscreen(
         impl: AodToLockscreenTransitionViewModel
     ): DeviceEntryIconTransition
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 9b40433..d118d4d 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
@@ -17,12 +17,14 @@
 
 package com.android.systemui.keyguard.ui.view.layout.blueprints
 
+import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 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.ClockSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
@@ -31,7 +33,7 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection
 import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule
-import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
+import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
 import com.android.systemui.util.kotlin.getOrNull
 import java.util.Optional
 import javax.inject.Inject
@@ -44,18 +46,20 @@
 class ShortcutsBesideUdfpsKeyguardBlueprint
 @Inject
 constructor(
+    alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection,
     defaultIndicationAreaSection: DefaultIndicationAreaSection,
     defaultDeviceEntrySection: DefaultDeviceEntrySection,
     @Named(KeyguardSectionsModule.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION)
     defaultAmbientIndicationAreaSection: Optional<KeyguardSection>,
     defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
-    alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection,
     defaultStatusViewSection: DefaultStatusViewSection,
     defaultStatusBarSection: DefaultStatusBarSection,
-    splitShadeGuidelines: SplitShadeGuidelines,
     defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection,
     aodNotificationIconsSection: AodNotificationIconsSection,
     aodBurnInSection: AodBurnInSection,
+    communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
+    clockSection: ClockSection,
+    smartspaceSection: SmartspaceSection,
     udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection,
 ) : KeyguardBlueprint {
     override val id: String = SHORTCUTS_BESIDE_UDFPS
@@ -63,15 +67,17 @@
     override val sections =
         listOfNotNull(
             defaultIndicationAreaSection,
+            alignShortcutsToUdfpsSection,
             defaultAmbientIndicationAreaSection.getOrNull(),
             defaultSettingsPopupMenuSection,
-            alignShortcutsToUdfpsSection,
             defaultStatusViewSection,
             defaultStatusBarSection,
             defaultNotificationStackScrollLayoutSection,
-            splitShadeGuidelines,
             aodNotificationIconsSection,
+            smartspaceSection,
             aodBurnInSection,
+            communalTutorialIndicatorSection,
+            clockSection,
             defaultDeviceEntrySection,
             udfpsAccessibilityOverlaySection, // Add LAST: Intentionally has z-order above others
         )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
index 24d0602..8472a9f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 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.ClockSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
@@ -30,11 +31,10 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
 import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule
-import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeClockSection
+import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeMediaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeNotificationStackScrollLayoutSection
-import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeSmartspaceSection
 import com.android.systemui.util.kotlin.getOrNull
 import java.util.Optional
 import javax.inject.Inject
@@ -62,8 +62,8 @@
     aodNotificationIconsSection: AodNotificationIconsSection,
     aodBurnInSection: AodBurnInSection,
     communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
-    smartspaceSection: SplitShadeSmartspaceSection,
-    clockSection: SplitShadeClockSection,
+    clockSection: ClockSection,
+    smartspaceSection: SmartspaceSection,
     mediaSection: SplitShadeMediaSection,
 ) : KeyguardBlueprint {
     override val id: String = ID
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
index d0626d5..fd530b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
@@ -25,6 +25,8 @@
 import android.view.View
 import android.view.ViewGroup
 import androidx.constraintlayout.helper.widget.Layer
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
+import com.android.systemui.res.R
 
 class BaseBlueprintTransition : TransitionSet() {
     init {
@@ -33,7 +35,16 @@
             .addTransition(ChangeBounds())
             .addTransition(AlphaInVisibility())
         excludeTarget(Layer::class.java, /* exclude= */ true)
+        excludeClockAndSmartspaceViews()
     }
+
+    private fun excludeClockAndSmartspaceViews() {
+        excludeTarget(R.id.lockscreen_clock_view, true)
+        excludeTarget(R.id.lockscreen_clock_view_large, true)
+        excludeTarget(SmartspaceView::class.java, true)
+        // TODO(b/319468190): need to exclude views from large weather clock
+    }
+
     class AlphaOutVisibility : Visibility() {
         override fun onDisappear(
             sceneRoot: ViewGroup?,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInLayer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInLayer.kt
new file mode 100644
index 0000000..67a20e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInLayer.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import android.view.View
+import androidx.constraintlayout.helper.widget.Layer
+
+class AodBurnInLayer(context: Context) : Layer(context) {
+    // For setScale in Layer class, it stores it in mScaleX/Y and directly apply scale to
+    // referenceViews instead of keeping the value in fields of View class
+    // when we try to clone ConstraintSet, it will call getScaleX from View class and return 1.0
+    // and when we clone and apply, it will reset everything in the layer
+    // which cause the flicker from AOD to LS
+    private var _scaleX = 1F
+    private var _scaleY = 1F
+    // As described for _scaleX and _scaleY, we have similar issue with translation
+    private var _translationX = 1F
+    private var _translationY = 1F
+    // avoid adding views with same ids
+    override fun addView(view: View?) {
+        view?.let { if (it.id !in referencedIds) super.addView(view) }
+    }
+    override fun setScaleX(scaleX: Float) {
+        _scaleX = scaleX
+        super.setScaleX(scaleX)
+    }
+
+    override fun getScaleX(): Float {
+        return _scaleX
+    }
+
+    override fun setScaleY(scaleY: Float) {
+        _scaleY = scaleY
+        super.setScaleY(scaleY)
+    }
+
+    override fun getScaleY(): Float {
+        return _scaleY
+    }
+
+    override fun setTranslationX(dx: Float) {
+        _translationX = dx
+        super.setTranslationX(dx)
+    }
+
+    override fun getTranslationX(): Float {
+        return _translationX
+    }
+
+    override fun setTranslationY(dy: Float) {
+        _translationY = dy
+        super.setTranslationY(dy)
+    }
+
+    override fun getTranslationY(): Float {
+        return _translationY
+    }
+}
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
index 1ccc6cc..3d36eb0 100644
--- 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
@@ -19,16 +19,13 @@
 
 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.migrateClocksToBlueprint
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
-import com.android.systemui.shared.R as sharedR
 import javax.inject.Inject
 
 /** Adds a layer to group elements for translation for burn-in preventation */
@@ -37,10 +34,8 @@
 constructor(
     private val context: Context,
     private val clockViewModel: KeyguardClockViewModel,
-    private val smartspaceViewModel: KeyguardSmartspaceViewModel,
 ) : KeyguardSection() {
-    lateinit var burnInLayer: Layer
-
+    private lateinit var burnInLayer: AodBurnInLayer
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (!KeyguardShadeMigrationNssl.isEnabled) {
             return
@@ -48,7 +43,7 @@
 
         val nic = constraintLayout.requireViewById<View>(R.id.aod_notification_icon_container)
         burnInLayer =
-            Layer(context).apply {
+            AodBurnInLayer(context).apply {
                 id = R.id.burn_in_layer
                 addView(nic)
                 if (!migrateClocksToBlueprint()) {
@@ -57,11 +52,6 @@
                     addView(statusView)
                 }
             }
-        if (migrateClocksToBlueprint()) {
-            // weather and date parts won't be added here, cause their visibility doesn't align
-            // with others in burnInLayer
-            addSmartspaceViews(constraintLayout)
-        }
         constraintLayout.addView(burnInLayer)
     }
 
@@ -83,14 +73,4 @@
     override fun removeViews(constraintLayout: ConstraintLayout) {
         constraintLayout.removeView(R.id.burn_in_layer)
     }
-
-    private fun addSmartspaceViews(constraintLayout: ConstraintLayout) {
-        burnInLayer.apply {
-            if (smartspaceViewModel.isSmartspaceEnabled) {
-                val smartspaceView =
-                    constraintLayout.requireViewById<View>(sharedR.id.bc_smartspace_view)
-                addView(smartspaceView)
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index f560b5f..ad589df 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -30,9 +30,7 @@
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
-import com.android.systemui.shared.R as sharedR
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
@@ -53,7 +51,6 @@
     private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
     private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
     private val notificationIconAreaController: NotificationIconAreaController,
-    private val smartspaceViewModel: KeyguardSmartspaceViewModel,
     private val systemBarUtilsState: SystemBarUtilsState,
 ) : KeyguardSection() {
 
@@ -118,13 +115,25 @@
             }
         constraintSet.apply {
             if (migrateClocksToBlueprint()) {
-                connect(nicId, TOP, sharedR.id.bc_smartspace_view, BOTTOM, bottomMargin)
+                connect(nicId, TOP, R.id.smart_space_barrier_bottom, BOTTOM, bottomMargin)
                 setGoneMargin(nicId, BOTTOM, bottomMargin)
             } else {
                 connect(nicId, TOP, R.id.keyguard_status_view, topAlignment, bottomMargin)
             }
-            connect(nicId, START, PARENT_ID, START)
-            connect(nicId, END, PARENT_ID, END)
+            connect(
+                nicId,
+                START,
+                PARENT_ID,
+                START,
+                context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+            )
+            connect(
+                nicId,
+                END,
+                PARENT_ID,
+                END,
+                context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+            )
             constrainHeight(
                 nicId,
                 context.resources.getDimensionPixelSize(R.dimen.notification_shelf_height)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index b5f32c8..fe4f07d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -19,24 +19,32 @@
 
 import android.content.Context
 import android.view.View
+import androidx.constraintlayout.widget.Barrier
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
 import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.INVISIBLE
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.VISIBLE
 import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
 import com.android.systemui.Flags
+import com.android.systemui.customization.R as customizationR
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.binder.KeyguardClockViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockFaceLayout
 import com.android.systemui.res.R
+import com.android.systemui.shared.R as sharedR
 import com.android.systemui.statusbar.policy.SplitShadeStateController
 import com.android.systemui.util.Utils
+import dagger.Lazy
 import javax.inject.Inject
 
 internal fun ConstraintSet.setVisibility(
@@ -56,6 +64,8 @@
     protected val keyguardClockViewModel: KeyguardClockViewModel,
     private val context: Context,
     private val splitShadeStateController: SplitShadeStateController,
+    val smartspaceViewModel: KeyguardSmartspaceViewModel,
+    val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
 ) : KeyguardSection() {
     override fun addViews(constraintLayout: ConstraintLayout) {}
 
@@ -68,6 +78,7 @@
             constraintLayout,
             keyguardClockViewModel,
             clockInteractor,
+            blueprintInteractor.get()
         )
     }
 
@@ -88,12 +99,16 @@
     ): ConstraintSet {
         // Add constraint between rootView and clockContainer
         applyDefaultConstraints(constraintSet)
+        getNonTargetClockFace(clock).applyConstraints(constraintSet)
         getTargetClockFace(clock).applyConstraints(constraintSet)
 
         // Add constraint between elements in clock and clock container
         return constraintSet.apply {
-            setAlpha(getTargetClockFace(clock).views, 1F)
-            setAlpha(getNonTargetClockFace(clock).views, 0F)
+            setVisibility(getTargetClockFace(clock).views, VISIBLE)
+            setVisibility(getNonTargetClockFace(clock).views, INVISIBLE)
+            if (!keyguardClockViewModel.useLargeClock) {
+                connect(sharedR.id.bc_smartspace_view, TOP, sharedR.id.date_smartspace_view, BOTTOM)
+            }
         }
     }
 
@@ -106,24 +121,54 @@
 
     private fun getLargeClockFace(clock: ClockController): ClockFaceLayout = clock.largeClock.layout
     private fun getSmallClockFace(clock: ClockController): ClockFaceLayout = clock.smallClock.layout
+
+    fun constrainWeatherClockDateIconsBarrier(constraints: ConstraintSet) {
+        constraints.apply {
+            if (keyguardClockViewModel.isAodIconsVisible.value) {
+                createBarrier(
+                    R.id.weather_clock_date_and_icons_barrier_bottom,
+                    Barrier.BOTTOM,
+                    0,
+                    *intArrayOf(sharedR.id.bc_smartspace_view, R.id.aod_notification_icon_container)
+                )
+            } else {
+                if (smartspaceViewModel.bcSmartspaceVisibility.value == VISIBLE) {
+                    createBarrier(
+                        R.id.weather_clock_date_and_icons_barrier_bottom,
+                        Barrier.BOTTOM,
+                        0,
+                        (sharedR.id.bc_smartspace_view)
+                    )
+                } else {
+                    createBarrier(
+                        R.id.weather_clock_date_and_icons_barrier_bottom,
+                        Barrier.BOTTOM,
+                        getDimen(ENHANCED_SMARTSPACE_HEIGHT),
+                        (R.id.lockscreen_clock_view)
+                    )
+                }
+            }
+        }
+    }
     open fun applyDefaultConstraints(constraints: ConstraintSet) {
+        val guideline =
+            if (keyguardClockViewModel.clockShouldBeCentered.value) PARENT_ID
+            else R.id.split_shade_guideline
         constraints.apply {
             connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START)
-            connect(R.id.lockscreen_clock_view_large, END, PARENT_ID, END)
+            connect(R.id.lockscreen_clock_view_large, END, guideline, END)
             connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP)
             var largeClockTopMargin =
                 context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
                     context.resources.getDimensionPixelSize(
-                        com.android.systemui.customization.R.dimen.small_clock_padding_top
+                        customizationR.dimen.small_clock_padding_top
                     ) +
                     context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
             largeClockTopMargin += getDimen(DATE_WEATHER_VIEW_HEIGHT)
             largeClockTopMargin += getDimen(ENHANCED_SMARTSPACE_HEIGHT)
             if (!keyguardClockViewModel.useLargeClock) {
                 largeClockTopMargin -=
-                    context.resources.getDimensionPixelSize(
-                        com.android.systemui.customization.R.dimen.small_clock_height
-                    )
+                    context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
             }
             connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
             constrainHeight(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
@@ -131,18 +176,15 @@
             constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT)
             constrainHeight(
                 R.id.lockscreen_clock_view,
-                context.resources.getDimensionPixelSize(
-                    com.android.systemui.customization.R.dimen.small_clock_height
-                )
+                context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
             )
             connect(
                 R.id.lockscreen_clock_view,
                 START,
                 PARENT_ID,
                 START,
-                context.resources.getDimensionPixelSize(
-                    com.android.systemui.customization.R.dimen.clock_padding_start
-                )
+                context.resources.getDimensionPixelSize(customizationR.dimen.clock_padding_start) +
+                    context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
             )
             var smallClockTopMargin =
                 if (splitShadeStateController.shouldUseSplitNotificationShade(context.resources)) {
@@ -153,12 +195,12 @@
                 }
             if (keyguardClockViewModel.useLargeClock) {
                 smallClockTopMargin -=
-                    context.resources.getDimensionPixelSize(
-                        com.android.systemui.customization.R.dimen.small_clock_height
-                    )
+                    context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
             }
             connect(R.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin)
         }
+
+        constrainWeatherClockDateIconsBarrier(constraints)
     }
 
     private fun getDimen(name: String): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index 0bf9ad0..3fc9b42 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -31,6 +31,7 @@
 import com.android.keyguard.LockIconViewController
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
@@ -46,6 +47,7 @@
 import com.android.systemui.statusbar.VibratorHelper
 import dagger.Lazy
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 /** Includes the device entry icon. */
@@ -53,6 +55,7 @@
 class DefaultDeviceEntrySection
 @Inject
 constructor(
+    @Application private val applicationScope: CoroutineScope,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val authController: AuthController,
     private val windowManager: WindowManager,
@@ -91,6 +94,7 @@
         if (DeviceEntryUdfpsRefactor.isEnabled) {
             constraintLayout.findViewById<DeviceEntryIconView?>(deviceEntryIconViewId)?.let {
                 DeviceEntryIconViewBinder.bind(
+                    applicationScope,
                     it,
                     deviceEntryIconViewModel.get(),
                     deviceEntryForegroundViewModel.get(),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index b0eee0a..d75a72f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -27,11 +27,9 @@
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.shade.NotificationPanelView
-import com.android.systemui.shared.R as sharedR
 import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
@@ -54,7 +52,6 @@
     ambientState: AmbientState,
     controller: NotificationStackScrollLayoutController,
     notificationStackSizeCalculator: NotificationStackSizeCalculator,
-    private val smartspaceViewModel: KeyguardSmartspaceViewModel,
     @Main mainDispatcher: CoroutineDispatcher,
 ) :
     NotificationStackScrollLayoutSection(
@@ -76,16 +73,14 @@
         constraintSet.apply {
             val bottomMargin =
                 context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin)
-
             if (migrateClocksToBlueprint()) {
                 connect(
                     R.id.nssl_placeholder,
                     TOP,
-                    sharedR.id.bc_smartspace_view,
+                    R.id.smart_space_barrier_bottom,
                     BOTTOM,
                     bottomMargin
                 )
-                setGoneMargin(R.id.nssl_placeholder, TOP, bottomMargin)
             } else {
                 connect(R.id.nssl_placeholder, TOP, R.id.keyguard_status_view, BOTTOM, bottomMargin)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index 2a68f26..0c0eb8a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -24,6 +24,7 @@
 import androidx.constraintlayout.widget.ConstraintSet.LEFT
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.RIGHT
+import androidx.constraintlayout.widget.ConstraintSet.VISIBILITY_MODE_IGNORE
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
@@ -96,6 +97,11 @@
             constrainHeight(R.id.end_button, height)
             connect(R.id.end_button, RIGHT, PARENT_ID, RIGHT, horizontalOffsetMargin)
             connect(R.id.end_button, BOTTOM, PARENT_ID, BOTTOM, verticalOffsetMargin)
+
+            // The constraint set visibility for start and end button are default visible, set to
+            // ignore so the view's own initial visibility (invisible) is used
+            setVisibilityMode(R.id.start_button, VISIBILITY_MODE_IGNORE)
+            setVisibilityMode(R.id.end_button, VISIBILITY_MODE_IGNORE)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index eacd466..2f99719 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -18,37 +18,42 @@
 
 import android.content.Context
 import android.view.View
+import android.view.View.GONE
+import android.view.ViewTreeObserver.OnGlobalLayoutListener
+import androidx.constraintlayout.widget.Barrier
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
-import androidx.constraintlayout.widget.ConstraintSet.END
-import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import androidx.constraintlayout.widget.ConstraintSet.START
-import androidx.constraintlayout.widget.ConstraintSet.TOP
-import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.binder.KeyguardSmartspaceViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
-import com.android.systemui.res.R
+import com.android.systemui.res.R as R
+import com.android.systemui.shared.R as sharedR
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
+import dagger.Lazy
 import javax.inject.Inject
 
 open class SmartspaceSection
 @Inject
 constructor(
+    val context: Context,
     val keyguardClockViewModel: KeyguardClockViewModel,
     val keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
-    private val context: Context,
+    val keyguardSmartspaceInteractor: KeyguardSmartspaceInteractor,
     val smartspaceController: LockscreenSmartspaceController,
     val keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
+    val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
 ) : KeyguardSection() {
     private var smartspaceView: View? = null
     private var weatherView: View? = null
     private var dateView: View? = null
 
+    private var smartspaceVisibilityListener: OnGlobalLayoutListener? = null
+
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (!migrateClocksToBlueprint()) {
             return
@@ -64,6 +69,20 @@
             }
         }
         keyguardUnlockAnimationController.lockscreenSmartspace = smartspaceView
+        smartspaceVisibilityListener =
+            object : OnGlobalLayoutListener {
+                var pastVisibility = GONE
+                override fun onGlobalLayout() {
+                    smartspaceView?.let {
+                        val newVisibility = it.visibility
+                        if (pastVisibility != newVisibility) {
+                            keyguardSmartspaceInteractor.setBcSmartspaceVisibility(newVisibility)
+                            pastVisibility = newVisibility
+                        }
+                    }
+                }
+            }
+        smartspaceView?.viewTreeObserver?.addOnGlobalLayoutListener(smartspaceVisibilityListener)
     }
 
     override fun bindData(constraintLayout: ConstraintLayout) {
@@ -71,10 +90,10 @@
             return
         }
         KeyguardSmartspaceViewBinder.bind(
-            this,
             constraintLayout,
             keyguardClockViewModel,
             keyguardSmartspaceViewModel,
+            blueprintInteractor.get(),
         )
     }
 
@@ -82,65 +101,96 @@
         if (!migrateClocksToBlueprint()) {
             return
         }
-        // Generally, weather should be next to dateView
-        // smartspace should be below date & weather views
+        val horizontalPaddingStart =
+            context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start) +
+                context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+        val horizontalPaddingEnd =
+            context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end) +
+                context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
         constraintSet.apply {
             // migrate addDateWeatherView, addWeatherView from KeyguardClockSwitchController
-            dateView?.let { dateView ->
-                constrainHeight(dateView.id, WRAP_CONTENT)
-                constrainWidth(dateView.id, WRAP_CONTENT)
-                connect(
-                    dateView.id,
-                    START,
-                    PARENT_ID,
-                    START,
-                    context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start)
-                )
-            }
-            weatherView?.let {
-                constrainWidth(it.id, WRAP_CONTENT)
-                dateView?.let { dateView ->
-                    connect(it.id, TOP, dateView.id, TOP)
-                    connect(it.id, BOTTOM, dateView.id, BOTTOM)
-                    connect(it.id, START, dateView.id, END, 4)
-                }
-            }
+            constrainHeight(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
+            constrainWidth(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
+            connect(
+                sharedR.id.date_smartspace_view,
+                ConstraintSet.START,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.START,
+                horizontalPaddingStart
+            )
+            constrainWidth(sharedR.id.weather_smartspace_view, ConstraintSet.WRAP_CONTENT)
+            connect(
+                sharedR.id.weather_smartspace_view,
+                ConstraintSet.TOP,
+                sharedR.id.date_smartspace_view,
+                ConstraintSet.TOP
+            )
+            connect(
+                sharedR.id.weather_smartspace_view,
+                ConstraintSet.BOTTOM,
+                sharedR.id.date_smartspace_view,
+                ConstraintSet.BOTTOM
+            )
+            connect(
+                sharedR.id.weather_smartspace_view,
+                ConstraintSet.START,
+                sharedR.id.date_smartspace_view,
+                ConstraintSet.END,
+                4
+            )
+
             // migrate addSmartspaceView from KeyguardClockSwitchController
-            smartspaceView?.let {
-                constrainHeight(it.id, WRAP_CONTENT)
+            constrainHeight(sharedR.id.bc_smartspace_view, ConstraintSet.WRAP_CONTENT)
+            connect(
+                sharedR.id.bc_smartspace_view,
+                ConstraintSet.START,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.START,
+                horizontalPaddingStart
+            )
+            connect(
+                sharedR.id.bc_smartspace_view,
+                ConstraintSet.END,
+                if (keyguardClockViewModel.clockShouldBeCentered.value) ConstraintSet.PARENT_ID
+                else R.id.split_shade_guideline,
+                ConstraintSet.END,
+                horizontalPaddingEnd
+            )
+
+            if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
+                clear(sharedR.id.date_smartspace_view, ConstraintSet.TOP)
                 connect(
-                    it.id,
-                    START,
-                    PARENT_ID,
-                    START,
-                    context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start)
+                    sharedR.id.date_smartspace_view,
+                    ConstraintSet.BOTTOM,
+                    sharedR.id.bc_smartspace_view,
+                    ConstraintSet.TOP
+                )
+            } else {
+                clear(sharedR.id.date_smartspace_view, ConstraintSet.BOTTOM)
+                connect(
+                    sharedR.id.date_smartspace_view,
+                    ConstraintSet.TOP,
+                    R.id.lockscreen_clock_view,
+                    ConstraintSet.BOTTOM
                 )
                 connect(
-                    it.id,
-                    END,
-                    PARENT_ID,
-                    END,
-                    context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end)
+                    sharedR.id.bc_smartspace_view,
+                    ConstraintSet.TOP,
+                    sharedR.id.date_smartspace_view,
+                    ConstraintSet.BOTTOM
                 )
             }
 
-            if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
-                dateView?.let { dateView ->
-                    smartspaceView?.let { smartspaceView ->
-                        connect(dateView.id, BOTTOM, smartspaceView.id, TOP)
-                    }
-                }
-            } else {
-                dateView?.let { dateView ->
-                    clear(dateView.id, BOTTOM)
-                    connect(dateView.id, TOP, R.id.lockscreen_clock_view, BOTTOM)
-                    constrainHeight(dateView.id, WRAP_CONTENT)
-                    smartspaceView?.let { smartspaceView ->
-                        clear(smartspaceView.id, TOP)
-                        connect(smartspaceView.id, TOP, dateView.id, BOTTOM)
-                    }
-                }
-            }
+            createBarrier(
+                R.id.smart_space_barrier_bottom,
+                Barrier.BOTTOM,
+                0,
+                *intArrayOf(
+                    sharedR.id.bc_smartspace_view,
+                    sharedR.id.date_smartspace_view,
+                    sharedR.id.weather_smartspace_view,
+                )
+            )
         }
         updateVisibility(constraintSet)
     }
@@ -156,30 +206,28 @@
                 }
             }
         }
+        smartspaceView?.viewTreeObserver?.removeOnGlobalLayoutListener(smartspaceVisibilityListener)
+        smartspaceVisibilityListener = null
     }
 
     private fun updateVisibility(constraintSet: ConstraintSet) {
         constraintSet.apply {
-            weatherView?.let {
-                setVisibility(
-                    it.id,
-                    when (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
-                        true -> ConstraintSet.GONE
-                        false ->
-                            when (keyguardSmartspaceViewModel.isWeatherEnabled) {
-                                true -> ConstraintSet.VISIBLE
-                                false -> ConstraintSet.GONE
-                            }
-                    }
-                )
-            }
-            dateView?.let {
-                setVisibility(
-                    it.id,
-                    if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) ConstraintSet.GONE
-                    else ConstraintSet.VISIBLE
-                )
-            }
+            setVisibility(
+                sharedR.id.weather_smartspace_view,
+                when (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
+                    true -> ConstraintSet.GONE
+                    false ->
+                        when (keyguardSmartspaceViewModel.isWeatherEnabled) {
+                            true -> ConstraintSet.VISIBLE
+                            false -> ConstraintSet.GONE
+                        }
+                }
+            )
+            setVisibility(
+                sharedR.id.date_smartspace_view,
+                if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) ConstraintSet.GONE
+                else ConstraintSet.VISIBLE
+            )
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt
deleted file mode 100644
index 19ba1aa..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.view.layout.sections
-
-import android.content.Context
-import androidx.constraintlayout.widget.ConstraintSet
-import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.policy.SplitShadeStateController
-import javax.inject.Inject
-
-class SplitShadeClockSection
-@Inject
-constructor(
-    clockInteractor: KeyguardClockInteractor,
-    keyguardClockViewModel: KeyguardClockViewModel,
-    context: Context,
-    splitShadeStateController: SplitShadeStateController,
-) : ClockSection(clockInteractor, keyguardClockViewModel, context, splitShadeStateController) {
-    override fun applyDefaultConstraints(constraints: ConstraintSet) {
-        super.applyDefaultConstraints(constraints)
-        val largeClockEndGuideline =
-            if (keyguardClockViewModel.clockShouldBeCentered.value) ConstraintSet.PARENT_ID
-            else R.id.split_shade_guideline
-        constraints.apply {
-            connect(
-                R.id.lockscreen_clock_view_large,
-                ConstraintSet.END,
-                largeClockEndGuideline,
-                ConstraintSet.END
-            )
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
index f20ab06..b12a8a8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
@@ -20,7 +20,6 @@
 import android.view.View
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.widget.FrameLayout
-import androidx.constraintlayout.widget.Barrier
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
@@ -31,11 +30,9 @@
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.media.controls.ui.KeyguardMediaController
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
-import com.android.systemui.shared.R as sharedR
 import javax.inject.Inject
 
 /** Aligns media on left side for split shade, below smartspace, date, and weather. */
@@ -44,11 +41,9 @@
 constructor(
     private val context: Context,
     private val notificationPanelView: NotificationPanelView,
-    private val keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
     private val keyguardMediaController: KeyguardMediaController
 ) : KeyguardSection() {
     private val mediaContainerId = R.id.status_view_media_container
-    private val smartSpaceBarrier = R.id.smart_space_barrier_bottom
 
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (!migrateClocksToBlueprint()) {
@@ -85,18 +80,7 @@
         constraintSet.apply {
             constrainWidth(mediaContainerId, MATCH_CONSTRAINT)
             constrainHeight(mediaContainerId, WRAP_CONTENT)
-
-            createBarrier(
-                smartSpaceBarrier,
-                Barrier.BOTTOM,
-                0,
-                *intArrayOf(
-                    sharedR.id.bc_smartspace_view,
-                    sharedR.id.date_smartspace_view,
-                    sharedR.id.weather_smartspace_view,
-                )
-            )
-            connect(mediaContainerId, TOP, smartSpaceBarrier, BOTTOM)
+            connect(mediaContainerId, TOP, R.id.smart_space_barrier_bottom, BOTTOM)
             connect(mediaContainerId, START, PARENT_ID, START)
             connect(mediaContainerId, END, R.id.split_shade_guideline, END)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
index 0f8e673..756a4cc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
@@ -23,11 +23,13 @@
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.systemui.Flags.centralizedStatusBarDimensRefactor
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.shade.LargeScreenHeaderHelper
 import com.android.systemui.shade.NotificationPanelView
 import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -35,6 +37,7 @@
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 
@@ -53,6 +56,7 @@
     notificationStackSizeCalculator: NotificationStackSizeCalculator,
     private val smartspaceViewModel: KeyguardSmartspaceViewModel,
     @Main mainDispatcher: CoroutineDispatcher,
+    private val largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
 ) :
     NotificationStackScrollLayoutSection(
         context,
@@ -72,7 +76,13 @@
         }
         constraintSet.apply {
             val splitShadeTopMargin =
-                context.resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height)
+                if (centralizedStatusBarDimensRefactor()) {
+                    largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
+                } else {
+                    context.resources.getDimensionPixelSize(
+                        R.dimen.large_screen_shade_header_height
+                    )
+                }
             connect(R.id.nssl_placeholder, TOP, PARENT_ID, TOP, splitShadeTopMargin)
 
             connect(R.id.nssl_placeholder, START, PARENT_ID, START)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeSmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeSmartspaceSection.kt
deleted file mode 100644
index 8728ada..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeSmartspaceSection.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.view.layout.sections
-
-import android.content.Context
-import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
-import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
-import javax.inject.Inject
-
-/*
- * We need this class for the splitShadeBlueprint so `addViews` and `removeViews` will be called
- * when switching to and from splitShade.
- */
-class SplitShadeSmartspaceSection
-@Inject
-constructor(
-    keyguardClockViewModel: KeyguardClockViewModel,
-    keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
-    context: Context,
-    smartspaceController: LockscreenSmartspaceController,
-    keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
-) :
-    SmartspaceSection(
-        keyguardClockViewModel,
-        keyguardSmartspaceViewModel,
-        context,
-        smartspaceController,
-        keyguardUnlockAnimationController,
-    )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
index a8e3be7..b4b48a8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
@@ -19,7 +19,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
@@ -38,14 +37,14 @@
 class AlternateBouncerToAodTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
     private val transitionAnimation =
         animationFlow.setup(
             duration = FromAlternateBouncerTransitionInteractor.TO_AOD_DURATION,
-            stepFlow = interactor.transition(KeyguardState.ALTERNATE_BOUNCER, KeyguardState.AOD),
+            from = KeyguardState.ALTERNATE_BOUNCER,
+            to = KeyguardState.AOD,
         )
 
     val deviceEntryBackgroundViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
index 5d6b0ce..3737e6f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
@@ -18,7 +18,6 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TO_GONE_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.shared.model.ScrimAlpha
@@ -37,14 +36,14 @@
 class AlternateBouncerToGoneTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     bouncerToGoneFlows: BouncerToGoneFlows,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_GONE_DURATION,
-            stepFlow = interactor.transition(ALTERNATE_BOUNCER, KeyguardState.GONE),
+            from = ALTERNATE_BOUNCER,
+            to = KeyguardState.GONE,
         )
 
     /** Scrim alpha values */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
new file mode 100644
index 0000000..7592881
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down ALTERNATE BOUNCER->PRIMARY BOUNCER transition into discrete steps for corresponding
+ * views to consume.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class AlternateBouncerToPrimaryBouncerTransitionViewModel
+@Inject
+constructor(
+    animationFlow: KeyguardTransitionAnimationFlow,
+) : DeviceEntryIconTransition {
+    private val transitionAnimation =
+        animationFlow.setup(
+            duration = FromAlternateBouncerTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
+            from = KeyguardState.ALTERNATE_BOUNCER,
+            to = KeyguardState.PRIMARY_BOUNCER,
+        )
+
+    override val deviceEntryParentViewAlpha: Flow<Float> =
+        transitionAnimation.immediatelyTransitionTo(0f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
index fa4de04..ce45112 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
@@ -17,14 +17,12 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import android.content.Context
-import android.hardware.biometrics.SensorLocationInternal
 import com.android.settingslib.Utils
-import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
-import com.android.systemui.res.R
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -44,21 +42,24 @@
     configurationInteractor: ConfigurationInteractor,
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
     deviceEntryBackgroundViewModel: DeviceEntryBackgroundViewModel,
-    fingerprintPropertyRepository: FingerprintPropertyRepository,
+    fingerprintPropertyInteractor: FingerprintPropertyInteractor,
     udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) {
     private val isSupported: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
+
+    /**
+     * UDFPS icon location in pixels for the current display and screen resolution, in natural
+     * orientation.
+     */
     val iconLocation: Flow<IconLocation> =
         isSupported.flatMapLatest { supportsUI ->
             if (supportsUI) {
-                fingerprintPropertyRepository.sensorLocations.map { sensorLocations ->
-                    val sensorLocation =
-                        sensorLocations.getOrDefault("", SensorLocationInternal.DEFAULT)
+                fingerprintPropertyInteractor.sensorLocation.map { sensorLocation ->
                     IconLocation(
-                        left = sensorLocation.sensorLocationX - sensorLocation.sensorRadius,
-                        top = sensorLocation.sensorLocationY - sensorLocation.sensorRadius,
-                        right = sensorLocation.sensorLocationX + sensorLocation.sensorRadius,
-                        bottom = sensorLocation.sensorLocationY + sensorLocation.sensorRadius,
+                        left = (sensorLocation.centerX - sensorLocation.radius).toInt(),
+                        top = (sensorLocation.centerY - sensorLocation.radius).toInt(),
+                        right = (sensorLocation.centerX + sensorLocation.radius).toInt(),
+                        bottom = (sensorLocation.centerY + sensorLocation.radius).toInt(),
                     )
                 }
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
index 8e729f7..2526f0a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
@@ -19,7 +19,6 @@
 
 import android.graphics.Color
 import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TRANSITION_DURATION_MS
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
@@ -37,7 +36,6 @@
 @Inject
 constructor(
     private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
-    transitionInteractor: KeyguardTransitionInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) {
     // When we're fully transitioned to the AlternateBouncer, the alpha of the scrim should be:
@@ -46,7 +44,8 @@
         animationFlow
             .setup(
                 duration = TRANSITION_DURATION_MS,
-                stepFlow = transitionInteractor.anyStateToAlternateBouncerTransition,
+                from = null,
+                to = ALTERNATE_BOUNCER,
             )
             .sharedFlow(
                 duration = TRANSITION_DURATION_MS,
@@ -60,7 +59,8 @@
         animationFlow
             .setup(
                 TRANSITION_DURATION_MS,
-                transitionInteractor.transitionStepsFromState(ALTERNATE_BOUNCER),
+                from = ALTERNATE_BOUNCER,
+                to = null,
             )
             .sharedFlow(
                 duration = TRANSITION_DURATION_MS,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
index 2b14521..b92a9a0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
@@ -18,7 +18,6 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
@@ -31,14 +30,14 @@
 class AodToGoneTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
 
     private val transitionAnimation =
         animationFlow.setup(
             duration = FromAodTransitionInteractor.TO_GONE_DURATION,
-            stepFlow = interactor.transition(KeyguardState.AOD, KeyguardState.GONE),
+            from = KeyguardState.AOD,
+            to = KeyguardState.GONE,
         )
 
     override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
index 5e552e1..266fd02 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
@@ -37,7 +37,6 @@
 class AodToLockscreenTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
@@ -45,7 +44,8 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_LOCKSCREEN_DURATION,
-            stepFlow = interactor.aodToLockscreenTransition,
+            from = KeyguardState.AOD,
+            to = KeyguardState.LOCKSCREEN,
         )
 
     /** Ensure alpha is set to be visible */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
index d283af3..105a7ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
@@ -18,7 +18,6 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
@@ -29,13 +28,13 @@
 class AodToOccludedTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
     private val transitionAnimation =
         animationFlow.setup(
             duration = FromAodTransitionInteractor.TO_OCCLUDED_DURATION,
-            stepFlow = interactor.transition(KeyguardState.AOD, KeyguardState.OCCLUDED),
+            from = KeyguardState.AOD,
+            to = KeyguardState.OCCLUDED,
         )
 
     override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
index 41dc157..924fc5d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.ScrimAlpha
@@ -41,7 +40,6 @@
 class BouncerToGoneFlows
 @Inject
 constructor(
-    private val interactor: KeyguardTransitionInteractor,
     private val statusBarStateController: SysuiStatusBarStateController,
     private val primaryBouncerInteractor: PrimaryBouncerInteractor,
     private val keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>,
@@ -76,7 +74,8 @@
         val transitionAnimation =
             animationFlow.setup(
                 duration = duration,
-                stepFlow = interactor.transition(fromState, GONE)
+                from = fromState,
+                to = GONE,
             )
 
         return shadeInteractor.shadeExpansion.flatMapLatest { shadeExpansion ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index fe0b365..310ec95 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -20,7 +20,7 @@
 import android.content.Context
 import com.android.settingslib.Utils
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -41,7 +41,7 @@
 @Inject
 constructor(
     val context: Context,
-    configurationRepository: ConfigurationRepository, // TODO (b/309655554): create & use interactor
+    configurationInteractor: ConfigurationInteractor,
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
     transitionInteractor: KeyguardTransitionInteractor,
     deviceEntryIconViewModel: DeviceEntryIconViewModel,
@@ -62,7 +62,7 @@
 
     private val color: Flow<Int> =
         deviceEntryIconViewModel.useBackgroundProtection.flatMapLatest { useBgProtection ->
-            configurationRepository.onAnyConfigurationChange
+            configurationInteractor.onAnyConfigurationChange
                 .map { getColor(useBgProtection) }
                 .onStart { emit(getColor(useBgProtection)) }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index eacaa40..a3d5453 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -20,6 +20,7 @@
 import android.animation.IntEvaluator
 import com.android.keyguard.KeyguardViewController
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntrySourceInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -56,6 +57,7 @@
     private val sceneContainerFlags: SceneContainerFlags,
     private val keyguardViewController: Lazy<KeyguardViewController>,
     private val deviceEntryInteractor: DeviceEntryInteractor,
+    private val deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
 ) {
     private val intEvaluator = IntEvaluator()
     private val floatEvaluator = FloatEvaluator()
@@ -208,14 +210,13 @@
             }
         }
 
-    fun onLongPress() {
-        // TODO (b/309804148): play auth ripple via an interactor
-
+    suspend fun onLongPress() {
         if (sceneContainerFlags.isEnabled()) {
             deviceEntryInteractor.attemptDeviceEntry()
         } else {
             keyguardViewController.get().showPrimaryBouncer(/* scrim */ true)
         }
+        deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()
     }
 
     private fun DeviceEntryIconView.IconType.toAccessibilityHintType():
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt
index 0b34326..e4610c1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
@@ -34,13 +34,13 @@
 class DozingToLockscreenTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
     private val transitionAnimation =
         animationFlow.setup(
             duration = FromDozingTransitionInteractor.TO_LOCKSCREEN_DURATION,
-            stepFlow = interactor.dozingToLockscreenTransition,
+            from = KeyguardState.DOZING,
+            to = KeyguardState.LOCKSCREEN,
         )
 
     val shortcutsAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt
index 8bcf3f8..67568e1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromDreamingLockscreenHostedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -28,14 +28,14 @@
 class DreamingHostedToLockscreenTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) {
 
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_LOCKSCREEN_DURATION,
-            stepFlow = interactor.dreamingLockscreenHostedToLockscreenTransition,
+            from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+            to = KeyguardState.LOCKSCREEN,
         )
 
     val shortcutsAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index 5f620af..ead2d48 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
@@ -52,7 +53,8 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_LOCKSCREEN_DURATION,
-            stepFlow = keyguardTransitionInteractor.dreamingToLockscreenTransition,
+            from = KeyguardState.DREAMING,
+            to = KeyguardState.LOCKSCREEN,
         )
 
     val transitionEnded =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
new file mode 100644
index 0000000..6aa2eca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down GLANCEABLE_HUB->LOCKSCREEN transition into discrete steps for corresponding views to
+ * consume.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class GlanceableHubToLockscreenTransitionViewModel
+@Inject
+constructor(
+    animationFlow: KeyguardTransitionAnimationFlow,
+) {
+    private val transitionAnimation =
+        animationFlow.setup(
+            duration = FromGlanceableHubTransitionInteractor.TO_LOCKSCREEN_DURATION,
+            from = KeyguardState.GLANCEABLE_HUB,
+            to = KeyguardState.LOCKSCREEN,
+        )
+
+    // TODO(b/315205222): implement full animation spec instead of just a simple fade.
+    val keyguardAlpha: Flow<Float> =
+        transitionAnimation.sharedFlow(
+            duration = FromGlanceableHubTransitionInteractor.TO_LOCKSCREEN_DURATION,
+            onStep = { it },
+            onFinish = { 1f },
+            onCancel = { 0f },
+            name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardAlpha",
+        )
+
+    // TODO(b/315205216): implement full animation spec instead of just a simple fade.
+    val notificationAlpha: Flow<Float> =
+        transitionAnimation.sharedFlow(
+            duration = FromGlanceableHubTransitionInteractor.TO_LOCKSCREEN_DURATION,
+            onStep = { it },
+            onFinish = { 1f },
+            onCancel = { 0f },
+            name = "GLANCEABLE_HUB->LOCKSCREEN: notificationAlpha",
+        )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
index 3f27eb0..ba04fd3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_AOD_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
@@ -36,7 +36,6 @@
 class GoneToAodTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
@@ -44,7 +43,8 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_AOD_DURATION,
-            stepFlow = interactor.goneToAodTransition,
+            from = KeyguardState.GONE,
+            to = KeyguardState.AOD,
         )
 
     /** y-translation from the top of the screen for AOD */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt
index bba790a..b527463 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -32,14 +32,14 @@
 class GoneToDreamingLockscreenHostedTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) {
 
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_DREAMING_DURATION,
-            stepFlow = interactor.goneToDreamingLockscreenHostedTransition,
+            from = KeyguardState.GONE,
+            to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
         )
 
     /** Lockscreen views alpha - hide immediately */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
index 6762ba6..102242a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
@@ -19,7 +19,7 @@
 import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -30,14 +30,14 @@
 class GoneToDreamingTransitionViewModel
 @Inject
 constructor(
-    private val interactor: KeyguardTransitionInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) {
 
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_DREAMING_DURATION,
-            stepFlow = interactor.goneToDreamingTransition,
+            from = KeyguardState.GONE,
+            to = KeyguardState.DREAMING,
         )
 
     /** Lockscreen views y-translation */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt
index adae8ab..793abb4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -28,14 +28,14 @@
 class GoneToLockscreenTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) {
 
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_LOCKSCREEN_DURATION,
-            stepFlow = interactor.goneToLockscreenTransition
+            from = KeyguardState.GONE,
+            to = KeyguardState.LOCKSCREEN
         )
 
     val shortcutsAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 5bb2782..6763e0a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.keyguard.shared.model.SettingsClockSize
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
 import com.android.systemui.statusbar.policy.SplitShadeStateController
 import com.android.systemui.util.Utils
 import javax.inject.Inject
@@ -44,6 +45,7 @@
     private val keyguardClockInteractor: KeyguardClockInteractor,
     @Application private val applicationScope: CoroutineScope,
     private val splitShadeStateController: SplitShadeStateController,
+    notifsKeyguardInteractor: NotificationsKeyguardInteractor,
 ) {
     var burnInLayer: Layer? = null
     val useLargeClock: Boolean
@@ -91,6 +93,13 @@
             initialValue = false
         )
 
+    val isAodIconsVisible: StateFlow<Boolean> =
+        notifsKeyguardInteractor.areNotificationsFullyHidden.stateIn(
+            scope = applicationScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = false
+        )
+
     // Needs to use a non application context to get display cutout.
     fun getSmallClockTopMargin(context: Context) =
         if (splitShadeStateController.shouldUseSplitNotificationShade(context.resources)) {
@@ -99,34 +108,4 @@
             context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
                 Utils.getStatusBarHeaderHeightKeyguard(context)
         }
-
-    fun getLargeClockTopMargin(context: Context): Int {
-        var largeClockTopMargin =
-            context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
-                context.resources.getDimensionPixelSize(
-                    com.android.systemui.customization.R.dimen.small_clock_padding_top
-                ) +
-                context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
-        largeClockTopMargin += getDimen(context, DATE_WEATHER_VIEW_HEIGHT)
-        largeClockTopMargin += getDimen(context, ENHANCED_SMARTSPACE_HEIGHT)
-        if (!useLargeClock) {
-            largeClockTopMargin -=
-                context.resources.getDimensionPixelSize(
-                    com.android.systemui.customization.R.dimen.small_clock_height
-                )
-        }
-
-        return largeClockTopMargin
-    }
-
-    private fun getDimen(context: Context, name: String): Int {
-        val res = context.packageManager.getResourcesForApplication(context.packageName)
-        val id = res.getIdentifier(name, "dimen", context.packageName)
-        return res.getDimensionPixelSize(id)
-    }
-
-    companion object {
-        private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
-        private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
-    }
 }
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 5059e6b..709e184 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
@@ -21,6 +21,7 @@
 import android.view.View.VISIBLE
 import com.android.systemui.Flags.newAodTransition
 import com.android.systemui.common.shared.model.NotificationContainerBounds
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -46,6 +47,7 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
@@ -55,9 +57,12 @@
     private val deviceEntryInteractor: DeviceEntryInteractor,
     private val dozeParameters: DozeParameters,
     private val keyguardInteractor: KeyguardInteractor,
+    communalInteractor: CommunalInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
     aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+    lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
+    glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
     screenOffAnimationController: ScreenOffAnimationController,
     private val aodBurnInViewModel: AodBurnInViewModel,
     aodAlphaViewModel: AodAlphaViewModel,
@@ -77,8 +82,32 @@
     val notificationBounds: StateFlow<NotificationContainerBounds> =
         keyguardInteractor.notificationContainerBounds
 
+    /**
+     * The keyguard root view can be clipped as the shade is pulled down, typically only for
+     * non-split shade cases.
+     */
+    val topClippingBounds: Flow<Int?> = keyguardInteractor.topClippingBounds
+
     /** An observable for the alpha level for the entire keyguard root view. */
-    val alpha: Flow<Float> = aodAlphaViewModel.alpha
+    val alpha: Flow<Float> =
+        combine(
+                communalInteractor.isIdleOnCommunal,
+                merge(
+                    aodAlphaViewModel.alpha,
+                    lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha,
+                    glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
+                )
+            ) { isIdleOnCommunal, alpha ->
+                if (isIdleOnCommunal) {
+                    // Keyguard should not show while the communal hub is fully visible. This check
+                    // is added since at the moment, closing the notification shade will cause the
+                    // keyguard alpha to be set back to 1.
+                    0f
+                } else {
+                    alpha
+                }
+            }
+            .distinctUntilChanged()
 
     /** Specific alpha value for elements visible during [KeyguardState.LOCKSCREEN] */
     val lockscreenStateAlpha: Flow<Float> = aodToLockscreenTransitionViewModel.lockscreenAlpha
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
index a1dd720..e8c1ab5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -33,6 +34,7 @@
     @Application applicationScope: CoroutineScope,
     smartspaceController: LockscreenSmartspaceController,
     keyguardClockViewModel: KeyguardClockViewModel,
+    smartspaceInteractor: KeyguardSmartspaceInteractor,
 ) {
     /** Whether the smartspace section is available in the build. */
     val isSmartspaceEnabled: Boolean = smartspaceController.isEnabled()
@@ -78,4 +80,7 @@
     ): Boolean {
         return !clockIncludesCustomWeatherDisplay && isWeatherEnabled
     }
+
+    /* trigger clock and smartspace constraints change when smartspace appears */
+    var bcSmartspaceVisibility: StateFlow<Int> = smartspaceInteractor.bcSmartspaceVisibility
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 36bbe4e..d792889 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -16,9 +16,13 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.content.res.Resources
+import com.android.keyguard.KeyguardClockSwitch
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.res.R
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
@@ -31,12 +35,28 @@
 class LockscreenContentViewModel
 @Inject
 constructor(
+    clockInteractor: KeyguardClockInteractor,
     private val interactor: KeyguardBlueprintInteractor,
     private val authController: AuthController,
     val longPress: KeyguardLongPressViewModel,
 ) {
+    private val clockSize = clockInteractor.clockSize
+
     val isUdfpsVisible: Boolean
         get() = authController.isUdfpsSupported
+    val isLargeClockVisible: Boolean
+        get() = clockSize.value == KeyguardClockSwitch.LARGE
+    val areNotificationsVisible: Boolean
+        get() = !isLargeClockVisible
+
+    fun getSmartSpacePaddingTop(resources: Resources): Int {
+        return if (isLargeClockVisible) {
+            resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
+                resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)
+        } else {
+            0
+        }
+    }
 
     fun blueprintId(scope: CoroutineScope): StateFlow<String> {
         return interactor.blueprint
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
index 65614f4..7bf51a7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
@@ -36,7 +36,6 @@
 class LockscreenToAodTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
     shadeDependentFlows: ShadeDependentFlows,
     animationFlow: KeyguardTransitionAnimationFlow,
@@ -45,7 +44,8 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = FromLockscreenTransitionInteractor.TO_AOD_DURATION,
-            stepFlow = interactor.lockscreenToAodTransition,
+            from = KeyguardState.LOCKSCREEN,
+            to = KeyguardState.AOD,
         )
 
     val deviceEntryBackgroundViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
index accb20c..4c0cd2f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DOZING_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -28,14 +28,14 @@
 class LockscreenToDozingTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) {
 
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_DOZING_DURATION,
-            stepFlow = interactor.lockscreenToDozingTransition
+            from = KeyguardState.LOCKSCREEN,
+            to = KeyguardState.DOZING,
         )
 
     val shortcutsAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt
index c649b12..19b9cf47 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_HOSTED_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -28,14 +28,14 @@
 class LockscreenToDreamingHostedTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) {
 
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_DREAMING_HOSTED_DURATION,
-            stepFlow = interactor.lockscreenToDreamingLockscreenHostedTransition
+            from = KeyguardState.LOCKSCREEN,
+            to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
         )
 
     val shortcutsAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
index 7f75b54..13522a6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
@@ -19,7 +19,7 @@
 import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
@@ -34,14 +34,14 @@
 class LockscreenToDreamingTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     shadeDependentFlows: ShadeDependentFlows,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_DREAMING_DURATION,
-            stepFlow = interactor.lockscreenToDreamingTransition,
+            from = KeyguardState.LOCKSCREEN,
+            to = KeyguardState.DREAMING,
         )
 
     /** Lockscreen views y-translation */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
new file mode 100644
index 0000000..3afa49e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down LOCKSCREEN->GLANCEABLE_HUB transition into discrete steps for corresponding views to
+ * consume.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class LockscreenToGlanceableHubTransitionViewModel
+@Inject
+constructor(
+    animationFlow: KeyguardTransitionAnimationFlow,
+) {
+    private val transitionAnimation =
+        animationFlow.setup(
+            duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
+            from = KeyguardState.LOCKSCREEN,
+            to = KeyguardState.GLANCEABLE_HUB,
+        )
+
+    // TODO(b/315205222): implement full animation spec instead of just a simple fade.
+    val keyguardAlpha: Flow<Float> =
+        transitionAnimation.sharedFlow(
+            duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
+            onStep = { 1f - it },
+            onFinish = { 0f },
+            onCancel = { 1f },
+            name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardAlpha",
+        )
+
+    // TODO(b/315205216): implement full animation spec instead of just a simple fade.
+    val notificationAlpha: Flow<Float> =
+        transitionAnimation.sharedFlow(
+            duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
+            onStep = { 1f - it },
+            onFinish = { 0f },
+            onCancel = { 1f },
+            name = "LOCKSCREEN->GLANCEABLE_HUB: notificationAlpha",
+        )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
index 9e19713..a26ef07 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
@@ -18,7 +18,6 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
@@ -35,14 +34,14 @@
 class LockscreenToGoneTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
 
     private val transitionAnimation =
         animationFlow.setup(
             duration = FromLockscreenTransitionInteractor.TO_GONE_DURATION,
-            stepFlow = interactor.transition(KeyguardState.LOCKSCREEN, KeyguardState.GONE),
+            from = KeyguardState.LOCKSCREEN,
+            to = KeyguardState.GONE,
         )
 
     val shortcutsAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
index 9db0b77..dd6652e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import com.android.systemui.res.R
@@ -37,7 +37,6 @@
 class LockscreenToOccludedTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     shadeDependentFlows: ShadeDependentFlows,
     configurationInteractor: ConfigurationInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
@@ -46,7 +45,8 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_OCCLUDED_DURATION,
-            stepFlow = interactor.lockscreenToOccludedTransition,
+            from = KeyguardState.LOCKSCREEN,
+            to = KeyguardState.OCCLUDED,
         )
 
     /** Lockscreen views alpha */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
index 52e3257..ce47f3c6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
@@ -18,7 +18,6 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
@@ -26,7 +25,6 @@
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
 
 /**
  * Breaks down LOCKSCREEN->PRIMARY BOUNCER transition into discrete steps for corresponding views to
@@ -37,21 +35,21 @@
 class LockscreenToPrimaryBouncerTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     shadeDependentFlows: ShadeDependentFlows,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
     private val transitionAnimation =
         animationFlow.setup(
             duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
-            stepFlow =
-                interactor.transition(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER),
+            from = KeyguardState.LOCKSCREEN,
+            to = KeyguardState.PRIMARY_BOUNCER,
         )
 
     val shortcutsAlpha: Flow<Float> =
-        interactor.transition(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER).map {
-            1 - it.value
-        }
+        transitionAnimation.sharedFlow(
+            duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
+            onStep = { 1f - it }
+        )
 
     override val deviceEntryParentViewAlpha: Flow<Float> =
         shadeDependentFlows.transitionFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt
index ed5e83c..07c1141 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt
@@ -19,7 +19,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
@@ -35,14 +34,14 @@
 class OccludedToAodTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
     private val transitionAnimation =
         animationFlow.setup(
             duration = FromOccludedTransitionInteractor.TO_AOD_DURATION,
-            stepFlow = interactor.transition(KeyguardState.OCCLUDED, KeyguardState.AOD),
+            from = KeyguardState.OCCLUDED,
+            to = KeyguardState.AOD,
         )
 
     val deviceEntryBackgroundViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
index 4c24f83..90195bd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import com.android.systemui.res.R
@@ -41,7 +41,6 @@
 class OccludedToLockscreenTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
     configurationInteractor: ConfigurationInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
@@ -50,7 +49,8 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_LOCKSCREEN_DURATION,
-            stepFlow = interactor.occludedToLockscreenTransition,
+            from = KeyguardState.OCCLUDED,
+            to = KeyguardState.LOCKSCREEN,
         )
 
     /** Lockscreen views y-translation */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
index 93482ea..74094be 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -27,14 +27,14 @@
 class OffToLockscreenTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) {
 
     private val transitionAnimation =
         animationFlow.setup(
             duration = 250.milliseconds,
-            stepFlow = interactor.offToLockscreenTransition
+            from = KeyguardState.OFF,
+            to = KeyguardState.LOCKSCREEN,
         )
 
     val shortcutsAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
index b0e2aa2..cd8e2f1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
@@ -19,7 +19,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
@@ -39,14 +38,14 @@
 class PrimaryBouncerToAodTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
     private val transitionAnimation =
         animationFlow.setup(
             duration = FromPrimaryBouncerTransitionInteractor.TO_AOD_DURATION,
-            stepFlow = interactor.transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.AOD),
+            from = KeyguardState.PRIMARY_BOUNCER,
+            to = KeyguardState.AOD,
         )
 
     val deviceEntryBackgroundViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 9dbe97f..4f28b46 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -22,7 +22,6 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.shared.model.ScrimAlpha
@@ -44,7 +43,6 @@
 class PrimaryBouncerToGoneTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     private val statusBarStateController: SysuiStatusBarStateController,
     private val primaryBouncerInteractor: PrimaryBouncerInteractor,
     keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>,
@@ -55,7 +53,8 @@
     private val transitionAnimation =
         animationFlow.setup(
             duration = TO_GONE_DURATION,
-            stepFlow = interactor.transition(PRIMARY_BOUNCER, GONE)
+            from = PRIMARY_BOUNCER,
+            to = GONE,
         )
 
     private var leaveShadeOpen: Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
index b2eed60..284a134 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
@@ -19,7 +19,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
@@ -28,7 +27,6 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.map
 
 /**
  * Breaks down PRIMARY BOUNCER->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -39,15 +37,14 @@
 class PrimaryBouncerToLockscreenTransitionViewModel
 @Inject
 constructor(
-    interactor: KeyguardTransitionInteractor,
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
     private val transitionAnimation =
         animationFlow.setup(
             duration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION,
-            stepFlow =
-                interactor.transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.LOCKSCREEN),
+            from = KeyguardState.PRIMARY_BOUNCER,
+            to = KeyguardState.LOCKSCREEN,
         )
 
     val deviceEntryBackgroundViewAlpha: Flow<Float> =
@@ -60,9 +57,10 @@
         }
 
     val shortcutsAlpha: Flow<Float> =
-        interactor.transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.LOCKSCREEN).map {
-            it.value
-        }
+        transitionAnimation.sharedFlow(
+            duration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION,
+            onStep = { it }
+        )
 
     override val deviceEntryParentViewAlpha: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(1f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
index 693e3b7..ca9c857 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -20,7 +20,7 @@
 import android.content.Context
 import android.graphics.Point
 import androidx.annotation.VisibleForTesting
-import androidx.core.animation.doOnEnd
+import androidx.core.animation.addListener
 import com.android.systemui.Flags
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
@@ -30,15 +30,18 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.DozeServiceHost
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -46,13 +49,14 @@
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 
+@ExperimentalCoroutinesApi
 @SysUISingleton
 class SideFpsProgressBarViewModel
 @Inject
@@ -63,9 +67,11 @@
     // todo (b/317432075) Injecting DozeServiceHost directly instead of using it through
     //  DozeInteractor as DozeServiceHost already depends on DozeInteractor.
     private val dozeServiceHost: DozeServiceHost,
+    private val keyguardInteractor: KeyguardInteractor,
     displayStateInteractor: DisplayStateInteractor,
     @Main private val mainDispatcher: CoroutineDispatcher,
     @Application private val applicationScope: CoroutineScope,
+    private val powerInteractor: PowerInteractor,
 ) {
     private val _progress = MutableStateFlow(0.0f)
     private val _visible = MutableStateFlow(false)
@@ -176,48 +182,54 @@
                     return@collectLatest
                 }
                 animatorJob =
-                    combine(
-                            sfpsSensorInteractor.authenticationDuration,
-                            fpAuthRepository.authenticationStatus,
-                            ::Pair
-                        )
-                        .onEach { (authDuration, authStatus) ->
-                            when (authStatus) {
-                                is AcquiredFingerprintAuthenticationStatus -> {
-                                    if (authStatus.fingerprintCaptureStarted) {
-                                        _visible.value = true
-                                        dozeServiceHost.fireSideFpsAcquisitionStarted()
-                                        _animator?.cancel()
-                                        _animator =
-                                            ValueAnimator.ofFloat(0.0f, 1.0f)
-                                                .setDuration(authDuration)
-                                                .apply {
-                                                    addUpdateListener {
-                                                        _progress.value = it.animatedValue as Float
-                                                    }
-                                                    addListener(
-                                                        doOnEnd {
-                                                            if (_progress.value == 0.0f) {
-                                                                _visible.value = false
-                                                            }
+                    sfpsSensorInteractor.authenticationDuration
+                        .flatMapLatest { authDuration ->
+                            _animator?.cancel()
+                            fpAuthRepository.authenticationStatus.map { authStatus ->
+                                when (authStatus) {
+                                    is AcquiredFingerprintAuthenticationStatus -> {
+                                        if (authStatus.fingerprintCaptureStarted) {
+                                            if (keyguardInteractor.isDozing.value) {
+                                                dozeServiceHost.fireSideFpsAcquisitionStarted()
+                                            } else {
+                                                powerInteractor
+                                                    .wakeUpForSideFingerprintAcquisition()
+                                            }
+                                            _animator?.cancel()
+                                            _animator =
+                                                ValueAnimator.ofFloat(0.0f, 1.0f)
+                                                    .setDuration(authDuration)
+                                                    .apply {
+                                                        addUpdateListener {
+                                                            _progress.value =
+                                                                it.animatedValue as Float
                                                         }
-                                                    )
-                                                }
-                                        _animator?.start()
-                                    } else if (authStatus.fingerprintCaptureCompleted) {
-                                        onFingerprintCaptureCompleted()
-                                    } else {
-                                        // Abandoned FP Auth attempt
-                                        _animator?.reverse()
+                                                        addListener(
+                                                            onEnd = {
+                                                                if (_progress.value == 0.0f) {
+                                                                    _visible.value = false
+                                                                }
+                                                            },
+                                                            onStart = { _visible.value = true },
+                                                            onCancel = { _visible.value = false }
+                                                        )
+                                                    }
+                                            _animator?.start()
+                                        } else if (authStatus.fingerprintCaptureCompleted) {
+                                            onFingerprintCaptureCompleted()
+                                        } else {
+                                            // Abandoned FP Auth attempt
+                                            _animator?.reverse()
+                                        }
                                     }
+                                    is ErrorFingerprintAuthenticationStatus ->
+                                        onFingerprintCaptureCompleted()
+                                    is FailFingerprintAuthenticationStatus ->
+                                        onFingerprintCaptureCompleted()
+                                    is SuccessFingerprintAuthenticationStatus ->
+                                        onFingerprintCaptureCompleted()
+                                    else -> Unit
                                 }
-                                is ErrorFingerprintAuthenticationStatus ->
-                                    onFingerprintCaptureCompleted()
-                                is FailFingerprintAuthenticationStatus ->
-                                    onFingerprintCaptureCompleted()
-                                is SuccessFingerprintAuthenticationStatus ->
-                                    onFingerprintCaptureCompleted()
-                                else -> Unit
                             }
                         }
                         .flowOn(mainDispatcher)
diff --git a/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt
index 171656a..ce64ac1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt
@@ -117,4 +117,13 @@
             { "SideFpsSensor auth duration changed: $long1" }
         )
     }
+
+    fun restToUnlockSettingEnabledChanged(enabled: Boolean) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { bool1 = enabled },
+            { "restToUnlockSettingEnabled: $bool1" }
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyboardLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyboardLog.kt
new file mode 100644
index 0000000..5910701
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyboardLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for keyboard-related functionality. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class KeyboardLog
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 24cb8ff..3e00940 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -603,6 +603,14 @@
         return factory.create("BluetoothTileDialogLog", 50);
     }
 
+    /** Provides a {@link LogBuffer} for the keyboard functionalities. */
+    @Provides
+    @SysUISingleton
+    @KeyboardLog
+    public static LogBuffer provideKeyboardLogBuffer(LogBufferFactory factory) {
+        return factory.create("KeyboardLog", 50);
+    }
+
     /** Provides a {@link LogBuffer} for {@link PackageChangeRepository} */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
index 23ee00d..a3029b2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
@@ -146,7 +146,7 @@
                 null,
                 UserHandle.ALL
             )
-            userTracker.addCallback(userTrackerCallback, mainExecutor)
+            userTracker.addCallback(userTrackerCallback, backgroundExecutor)
             loadSavedComponents()
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 2551da8..5720cc7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -87,8 +87,6 @@
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.media.controls.models.GutsViewHolder;
 import com.android.systemui.media.controls.models.player.MediaAction;
 import com.android.systemui.media.controls.models.player.MediaButton;
@@ -247,7 +245,6 @@
     private String mCurrentBroadcastApp;
     private MultiRippleController mMultiRippleController;
     private TurbulenceNoiseController mTurbulenceNoiseController;
-    private final FeatureFlags mFeatureFlags;
     private final GlobalSettings mGlobalSettings;
 
     private TurbulenceNoiseAnimationConfig mTurbulenceNoiseAnimationConfig;
@@ -281,7 +278,6 @@
             ActivityIntentHelper activityIntentHelper,
             NotificationLockscreenUserManager lockscreenUserManager,
             BroadcastDialogController broadcastDialogController,
-            FeatureFlags featureFlags,
             GlobalSettings globalSettings,
             MediaFlags mediaFlags
     ) {
@@ -312,8 +308,6 @@
             return Unit.INSTANCE;
         });
 
-        mFeatureFlags = featureFlags;
-
         mGlobalSettings = globalSettings;
         updateAnimatorDurationScale();
     }
@@ -1187,9 +1181,7 @@
 
                         action.run();
 
-                        if (mFeatureFlags.isEnabled(Flags.UMO_SURFACE_RIPPLE)) {
-                            mMultiRippleController.play(createTouchRippleAnimation(button));
-                        }
+                        mMultiRippleController.play(createTouchRippleAnimation(button));
 
                         if (icon instanceof Animatable) {
                             ((Animatable) icon).start();
@@ -1228,8 +1220,7 @@
     }
 
     private boolean shouldPlayTurbulenceNoise() {
-        return mFeatureFlags.isEnabled(Flags.UMO_TURBULENCE_NOISE) && mButtonClicked && !mWasPlaying
-                && isPlaying();
+        return mButtonClicked && !mWasPlaying && isPlaying();
     }
 
     private TurbulenceNoiseAnimationConfig createTurbulenceNoiseAnimation() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index 0385aeb..523414c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -1153,7 +1153,7 @@
                 qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
                 onLockscreen && isSplitShadeExpanding() -> LOCATION_QS
                 onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
-                // TODO(b/308813166): revisit logic once interactions between the hub and
+                // TODO(b/311234666): revisit logic once interactions between the hub and
                 //  shade/keyguard state are finalized
                 isCommunalShowing && communalInteractor.isCommunalEnabled -> LOCATION_COMMUNAL_HUB
                 onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
index 631a0b8..437218f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
@@ -228,6 +228,14 @@
                 }
             }
 
+        override var expandedMatchesParentHeight: Boolean = false
+            set(value) {
+                if (value != field) {
+                    field = value
+                    changedListener?.invoke()
+                }
+            }
+
         override var squishFraction: Float = 1.0f
             set(value) {
                 if (!value.equals(field)) {
@@ -282,6 +290,7 @@
         override fun copy(): MediaHostState {
             val mediaHostState = MediaHostStateHolder()
             mediaHostState.expansion = expansion
+            mediaHostState.expandedMatchesParentHeight = expandedMatchesParentHeight
             mediaHostState.squishFraction = squishFraction
             mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia
             mediaHostState.measurementInput = measurementInput?.copy()
@@ -360,6 +369,12 @@
      */
     var expansion: Float
 
+    /**
+     * If true, the [EXPANDED] layout should stretch to match the height of its parent container,
+     * rather than having a fixed height.
+     */
+    var expandedMatchesParentHeight: Boolean
+
     /** Fraction of the height animation. */
     var squishFraction: Float
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index be93936..962764c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -20,6 +20,7 @@
 import android.content.res.Configuration
 import androidx.annotation.VisibleForTesting
 import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
 import com.android.app.tracing.traceSection
 import com.android.systemui.media.controls.models.GutsViewHolder
 import com.android.systemui.media.controls.models.player.MediaViewHolder
@@ -152,18 +153,11 @@
                         lastOrientation = newOrientation
                         // Update the height of media controls for the expanded layout. it is needed
                         // for large screen devices.
-                        val backgroundIds =
-                            if (type == TYPE.PLAYER) {
-                                MediaViewHolder.backgroundIds
-                            } else {
-                                setOf(RecommendationViewHolder.backgroundId)
-                            }
-                        backgroundIds.forEach { id ->
-                            expandedLayout.getConstraint(id).layout.mHeight =
-                                context.resources.getDimensionPixelSize(
-                                    R.dimen.qs_media_session_height_expanded
-                                )
-                        }
+                        setBackgroundHeights(
+                            context.resources.getDimensionPixelSize(
+                                R.dimen.qs_media_session_height_expanded
+                            )
+                        )
                     }
                     if (this@MediaViewController::configurationChangeListener.isInitialized) {
                         configurationChangeListener.invoke()
@@ -276,6 +270,17 @@
     private fun constraintSetForExpansion(expansion: Float): ConstraintSet =
         if (expansion > 0) expandedLayout else collapsedLayout
 
+    /** Set the height of UMO background constraints. */
+    private fun setBackgroundHeights(height: Int) {
+        val backgroundIds =
+            if (type == TYPE.PLAYER) {
+                MediaViewHolder.backgroundIds
+            } else {
+                setOf(RecommendationViewHolder.backgroundId)
+            }
+        backgroundIds.forEach { id -> expandedLayout.getConstraint(id).layout.mHeight = height }
+    }
+
     /**
      * Set the views to be showing/hidden based on the [isGutsVisible] for a given
      * [TransitionViewState].
@@ -454,6 +459,18 @@
         }
         // Let's create a new measurement
         if (state.expansion == 0.0f || state.expansion == 1.0f) {
+            if (state.expansion == 1.0f) {
+                val height =
+                    if (state.expandedMatchesParentHeight) {
+                        MATCH_CONSTRAINT
+                    } else {
+                        context.resources.getDimensionPixelSize(
+                            R.dimen.qs_media_session_height_expanded
+                        )
+                    }
+                setBackgroundHeights(height)
+            }
+
             result =
                 transitionLayout!!.calculateViewState(
                     state.measurementInput!!,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionCaptureTarget.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionCaptureTarget.kt
index 11d0be5..a618490 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionCaptureTarget.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionCaptureTarget.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.mediaprojection
 
-import android.os.IBinder
+import android.app.ActivityOptions.LaunchCookie
 import android.os.Parcel
 import android.os.Parcelable
 
@@ -24,12 +24,12 @@
  * Class that represents an area that should be captured. Currently it has only a launch cookie that
  * represents a task but we potentially could add more identifiers e.g. for a pair of tasks.
  */
-data class MediaProjectionCaptureTarget(val launchCookie: IBinder?) : Parcelable {
+data class MediaProjectionCaptureTarget(val launchCookie: LaunchCookie?) : Parcelable {
 
-    constructor(parcel: Parcel) : this(parcel.readStrongBinder())
+    constructor(parcel: Parcel) : this(LaunchCookie.readFromParcel(parcel))
 
     override fun writeToParcel(dest: Parcel, flags: Int) {
-        dest.writeStrongBinder(launchCookie)
+        LaunchCookie.writeToParcel(launchCookie, dest)
     }
 
     override fun describeContents(): Int = 0
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
index 50e9e751..4685c5a 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.mediaprojection.appselector
 
 import android.app.ActivityOptions
+import android.app.ActivityOptions.LaunchCookie
 import android.content.Intent
 import android.content.res.Configuration
 import android.content.res.Resources
@@ -24,9 +25,7 @@
 import android.media.projection.MediaProjectionManager.EXTRA_MEDIA_PROJECTION
 import android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL
 import android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_TASK
-import android.os.Binder
 import android.os.Bundle
-import android.os.IBinder
 import android.os.ResultReceiver
 import android.os.UserHandle
 import android.util.Log
@@ -163,9 +162,9 @@
 
         val intent = createIntent(targetInfo)
 
-        val launchToken: IBinder = Binder("media_projection_launch_token")
+        val launchCookie = LaunchCookie("media_projection_launch_token")
         val activityOptions = ActivityOptions.makeBasic()
-        activityOptions.launchCookie = launchToken
+        activityOptions.setLaunchCookie(launchCookie)
 
         val userHandle = mMultiProfilePagerAdapter.activeListAdapter.userHandle
 
@@ -175,7 +174,7 @@
         // is created and ready to be captured.
         val activityStarted =
             activityLauncher.startActivityAsUser(intent, userHandle, activityOptions.toBundle()) {
-                returnSelectedApp(launchToken)
+                returnSelectedApp(launchCookie)
             }
 
         // Rely on the ActivityManager to pop up a dialog regarding app suspension
@@ -233,7 +232,7 @@
         }
     }
 
-    override fun returnSelectedApp(launchCookie: IBinder) {
+    override fun returnSelectedApp(launchCookie: LaunchCookie) {
         taskSelected = true
         if (intent.hasExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER)) {
             // The client requested to return the result in the result receiver instead of
@@ -255,7 +254,7 @@
             val mediaProjectionBinder = intent.getIBinderExtra(EXTRA_MEDIA_PROJECTION)
             val projection = IMediaProjection.Stub.asInterface(mediaProjectionBinder)
 
-            projection.launchCookie = launchCookie
+            projection.setLaunchCookie(launchCookie)
 
             val intent = Intent()
             intent.putExtra(EXTRA_MEDIA_PROJECTION, projection.asBinder())
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt
index 93c3bce..f204b3e 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt
@@ -1,6 +1,6 @@
 package com.android.systemui.mediaprojection.appselector
 
-import android.os.IBinder
+import android.app.ActivityOptions.LaunchCookie
 
 /**
  * Interface that allows to continue the media projection flow and return the selected app
@@ -11,5 +11,5 @@
      * Return selected app to the original caller of the media projection app picker.
      * @param launchCookie launch cookie of the launched activity of the target app
      */
-    fun returnSelectedApp(launchCookie: IBinder)
+    fun returnSelectedApp(launchCookie: LaunchCookie)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index ba837db..a811065 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.mediaprojection.appselector.view
 
 import android.app.ActivityOptions
+import android.app.ActivityOptions.LaunchCookie
 import android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
 import android.app.IActivityTaskManager
 import android.graphics.Rect
-import android.os.Binder
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
@@ -121,7 +121,7 @@
     }
 
     override fun onRecentAppClicked(task: RecentTask, view: View) {
-        val launchCookie = Binder()
+        val launchCookie = LaunchCookie()
         val activityOptions =
             ActivityOptions.makeScaleUpAnimation(
                 view,
@@ -132,7 +132,7 @@
             )
         activityOptions.pendingIntentBackgroundActivityStartMode =
             MODE_BACKGROUND_ACTIVITY_START_ALLOWED
-        activityOptions.launchCookie = launchCookie
+        activityOptions.setLaunchCookie(launchCookie)
         activityOptions.launchDisplayId = task.displayId
 
         activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle())
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 039372d..8b034b2 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -28,6 +28,7 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.ActivityOptions.LaunchCookie;
 import android.app.AlertDialog;
 import android.app.StatusBarManager;
 import android.content.Context;
@@ -146,6 +147,13 @@
                 final IMediaProjection projection =
                         MediaProjectionServiceHelper.createOrReuseProjection(mUid, mPackageName,
                                 mReviewGrantedConsentRequired);
+
+                LaunchCookie launchCookie = launchingIntent.getParcelableExtra(
+                        MediaProjectionManager.EXTRA_LAUNCH_COOKIE, LaunchCookie.class);
+                if (launchCookie != null) {
+                    projection.setLaunchCookie(launchCookie);
+                }
+
                 // Automatically grant consent if a system-privileged component is recording.
                 final Intent intent = new Intent();
                 intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
diff --git a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
new file mode 100644
index 0000000..6eb6226
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.model
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
+import dagger.Lazy
+import javax.inject.Inject
+
+/**
+ * A plugin for [SysUiState] that provides overrides for certain state flags that must be pulled
+ * from the scene framework when that framework is enabled.
+ */
+@SysUISingleton
+class SceneContainerPlugin
+@Inject
+constructor(
+    private val interactor: Lazy<SceneInteractor>,
+) {
+    /**
+     * Returns an override value for the given [flag] or `null` if the scene framework isn't enabled
+     * or if the flag value doesn't need to be overridden.
+     */
+    fun flagValueOverride(flag: Int): Boolean? {
+        if (!SceneContainerFlag.isEnabled) {
+            return null
+        }
+
+        val transitionState = interactor.get().transitionState.value
+        val idleTransitionStateOrNull = transitionState as? ObservableTransitionState.Idle
+        val currentSceneOrNull = idleTransitionStateOrNull?.scene
+        return currentSceneOrNull?.let { sceneKey -> EvaluatorByFlag[flag]?.invoke(sceneKey) }
+    }
+
+    companion object {
+
+        /**
+         * Value evaluator function by state flag ID.
+         *
+         * The value evaluator function can be invoked, passing in the current [SceneKey] to know
+         * the override value of the flag ID.
+         *
+         * If the map doesn't contain an entry for a certain flag ID, it means that it doesn't need
+         * to be overridden by the scene framework.
+         */
+        val EvaluatorByFlag =
+            mapOf<Int, (SceneKey) -> Boolean>(
+                SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to { it != SceneKey.Gone },
+                SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to { it == SceneKey.Shade },
+                SYSUI_STATE_QUICK_SETTINGS_EXPANDED to { it == SceneKey.QuickSettings },
+                SYSUI_STATE_BOUNCER_SHOWING to { it == SceneKey.Bouncer },
+                SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to { it == SceneKey.Lockscreen },
+            )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
index 3cdcb2c..2dd2327 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
@@ -41,13 +41,15 @@
     public static final boolean DEBUG = false;
 
     private final DisplayTracker mDisplayTracker;
+    private final SceneContainerPlugin mSceneContainerPlugin;
     private @QuickStepContract.SystemUiStateFlags int mFlags;
     private final List<SysUiStateCallback> mCallbacks = new ArrayList<>();
     private int mFlagsToSet = 0;
     private int mFlagsToClear = 0;
 
-    public SysUiState(DisplayTracker displayTracker) {
+    public SysUiState(DisplayTracker displayTracker, SceneContainerPlugin sceneContainerPlugin) {
         mDisplayTracker = displayTracker;
+        mSceneContainerPlugin = sceneContainerPlugin;
     }
 
     /**
@@ -71,6 +73,16 @@
 
     /** Methods to this call can be chained together before calling {@link #commitUpdate(int)}. */
     public SysUiState setFlag(int flag, boolean enabled) {
+        final Boolean overrideOrNull = mSceneContainerPlugin.flagValueOverride(flag);
+        if (overrideOrNull != null && enabled != overrideOrNull) {
+            if (DEBUG) {
+                Log.d(TAG, "setFlag for flag " + flag + " and value " + enabled + " overridden to "
+                        + overrideOrNull + " by scene container plugin");
+            }
+
+            enabled = overrideOrNull;
+        }
+
         if (enabled) {
             mFlagsToSet |= flag;
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index d7e062f..7d13397 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -41,6 +41,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -55,12 +56,14 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.systemui.Dumpable;
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
 import com.android.systemui.accessibility.SystemActions;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
 import com.android.systemui.recents.OverviewProxyService;
@@ -79,6 +82,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -101,6 +105,7 @@
     private static final String TAG = NavBarHelper.class.getSimpleName();
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final Executor mMainExecutor;
     private final AccessibilityManager mAccessibilityManager;
     private final Lazy<AssistManager> mAssistManagerLazy;
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
@@ -185,7 +190,12 @@
             DisplayTracker displayTracker,
             NotificationShadeWindowController notificationShadeWindowController,
             DumpManager dumpManager,
-            CommandQueue commandQueue) {
+            CommandQueue commandQueue,
+            @Main Executor mainExecutor) {
+        // b/319489709: This component shouldn't be running for a non-primary user
+        if (!Process.myUserHandle().equals(UserHandle.SYSTEM)) {
+            Log.wtf(TAG, "Unexpected initialization for non-primary user", new Throwable());
+        }
         mContext = context;
         mNotificationShadeWindowController = notificationShadeWindowController;
         mCommandQueue = commandQueue;
@@ -201,6 +211,7 @@
         mWm = wm;
         mDefaultDisplayId = displayTracker.getDefaultDisplayId();
         mEdgeBackGestureHandler = edgeBackGestureHandlerFactory.create(context);
+        mMainExecutor = mainExecutor;
 
         mNavBarMode = navigationModeController.addListener(this);
         mCommandQueue.addCallback(this);
@@ -370,7 +381,7 @@
             // permission
             final List<String> a11yButtonTargets =
                     mAccessibilityManager.getAccessibilityShortcutTargets(
-                            AccessibilityManager.ACCESSIBILITY_BUTTON);
+                            ShortcutConstants.UserShortcutType.SOFTWARE);
             final int requestingServices = a11yButtonTargets.size();
 
             clickable = requestingServices >= 1;
@@ -418,7 +429,11 @@
     @Override
     public void onConnectionChanged(boolean isConnected) {
         if (isConnected) {
-            updateAssistantAvailability();
+            // We add the OPS callback during construction, so if the service is already connected
+            // then we will try to get the AssistManager dependency which itself has an indirect
+            // dependency on NavBarHelper leading to a cycle. For now, we can defer updating the
+            // assistant availability.
+            mMainExecutor.execute(this::updateAssistantAvailability);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 0a72a2f..068e5fd 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -504,24 +504,6 @@
                 }
             };
 
-    private final ViewRootImpl.SurfaceChangedCallback mSurfaceChangedCallback =
-            new SurfaceChangedCallback() {
-            @Override
-            public void surfaceCreated(Transaction t) {
-                notifyNavigationBarSurface();
-            }
-
-            @Override
-            public void surfaceDestroyed() {
-                notifyNavigationBarSurface();
-            }
-
-            @Override
-            public void surfaceReplaced(Transaction t) {
-                notifyNavigationBarSurface();
-            }
-    };
-
     private boolean mScreenPinningActive = false;
     private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
         @Override
@@ -787,8 +769,6 @@
 
         mView.getViewTreeObserver().addOnComputeInternalInsetsListener(
                 mOnComputeInternalInsetsListener);
-        mView.getViewRootImpl().addSurfaceChangedCallback(mSurfaceChangedCallback);
-        notifyNavigationBarSurface();
 
         mPipOptional.ifPresent(mView::addPipExclusionBoundsChangeListener);
         mBackAnimation.ifPresent(mView::registerBackAnimation);
@@ -860,13 +840,8 @@
         mHandler.removeCallbacks(mEnableLayoutTransitions);
         mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
         mPipOptional.ifPresent(mView::removePipExclusionBoundsChangeListener);
-        ViewRootImpl viewRoot = mView.getViewRootImpl();
-        if (viewRoot != null) {
-            viewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback);
-        }
         mFrame = null;
         mOrientationHandle = null;
-        notifyNavigationBarSurface();
     }
 
     // TODO: Remove this when we update nav bar recreation
@@ -1026,17 +1001,6 @@
         }
     }
 
-    private void notifyNavigationBarSurface() {
-        ViewRootImpl viewRoot = mView.getViewRootImpl();
-        SurfaceControl surface = mView.getParent() != null 
-                && viewRoot != null
-                && viewRoot.getSurfaceControl() != null
-                && viewRoot.getSurfaceControl().isValid()
-                        ? viewRoot.getSurfaceControl()
-                        : null;
-        mOverviewProxyService.onNavigationBarSurfaceChanged(surface);
-    }
-
     private int deltaRotation(int oldRotation, int newRotation) {
         int delta = newRotation - oldRotation;
         if (delta < 0) delta += 4;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index 0320dec..092f1ed 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -302,9 +302,6 @@
             final NavigationBarView navBarView = getNavigationBarView(displayId);
             if (navBarView != null) {
                 navBarView.showPinningEnterExitToast(entering);
-            } else if (displayId == mDisplayTracker.getDefaultDisplayId()
-                    && mTaskbarDelegate.isInitialized()) {
-                mTaskbarDelegate.showPinningEnterExitToast(entering);
             }
         }
 
@@ -314,9 +311,6 @@
             final NavigationBarView navBarView = getNavigationBarView(displayId);
             if (navBarView != null) {
                 navBarView.showPinningEscapeToast();
-            } else if (displayId == mDisplayTracker.getDefaultDisplayId()
-                    && mTaskbarDelegate.isInitialized()) {
-                mTaskbarDelegate.showPinningEscapeToast();
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 62c7343..0167287 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -504,6 +504,11 @@
     }
 
     @Override
+    public void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
+        mOverviewProxyService.onNavigationBarLumaSamplingEnabled(displayId, enable);
+    }
+
+    @Override
     public void showPinningEnterExitToast(boolean entering) {
         updateSysuiFlags();
         if (mScreenPinningNotify == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index fa03dc2..b3d848c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -34,6 +34,8 @@
 import androidx.core.os.postDelayed
 import androidx.core.view.isVisible
 import androidx.dynamicanimation.animation.DynamicAnimation
+import com.android.internal.jank.Cuj.CUJ_BACK_PANEL_ARROW
+import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.util.LatencyTracker
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.NavigationEdgeBackPlugin
@@ -86,6 +88,7 @@
     private val vibratorHelper: VibratorHelper,
     private val configurationController: ConfigurationController,
     private val latencyTracker: LatencyTracker,
+    private val interactionJankMonitor: InteractionJankMonitor,
 ) : ViewController<BackPanel>(BackPanel(context, latencyTracker)), NavigationEdgeBackPlugin {
 
     /**
@@ -103,6 +106,7 @@
         private val vibratorHelper: VibratorHelper,
         private val configurationController: ConfigurationController,
         private val latencyTracker: LatencyTracker,
+        private val interactionJankMonitor: InteractionJankMonitor,
     ) {
         /** Construct a [BackPanelController]. */
         fun create(context: Context): BackPanelController {
@@ -115,6 +119,7 @@
                     vibratorHelper,
                     configurationController,
                     latencyTracker,
+                    interactionJankMonitor
                 )
             backPanelController.init()
             return backPanelController
@@ -183,7 +188,7 @@
         /* Arrow is animating in */
         ENTRY,
 
-        /* could be entry, neutral, or stretched, releasing will commit back */
+        /* releasing will commit back */
         ACTIVE,
 
         /* releasing will cancel back */
@@ -813,7 +818,7 @@
                     scale =
                         when (currentState) {
                             GestureState.ACTIVE,
-                            GestureState.FLUNG, -> params.activeIndicator.scale
+                            GestureState.FLUNG -> params.activeIndicator.scale
                             GestureState.COMMITTED -> params.committedIndicator.scale
                             else -> params.preThresholdIndicator.scale
                         },
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index e660b97..91c86df 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -84,6 +84,7 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.shared.system.InputMonitorCompat;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -261,7 +262,7 @@
     private boolean mIsTrackpadThreeFingerSwipe;
     private boolean mIsButtonForcedVisible;
 
-    private InputMonitor mInputMonitor;
+    private InputMonitorCompat mInputMonitor;
     private InputChannelCompat.InputEventReceiver mInputEventReceiver;
 
     private NavigationEdgeBackPlugin mEdgeBackPlugin;
@@ -624,7 +625,7 @@
             }
 
             if (!mIsEnabled) {
-                mGestureNavigationSettingsObserver.unregister();
+                mBackgroundExecutor.execute(mGestureNavigationSettingsObserver::unregister);
                 if (DEBUG_MISSING_GESTURE) {
                     Log.d(DEBUG_MISSING_GESTURE_TAG, "Unregister display listener");
                 }
@@ -642,7 +643,7 @@
                 }
 
             } else {
-                mGestureNavigationSettingsObserver.register();
+                mBackgroundExecutor.execute(mGestureNavigationSettingsObserver::register);
                 updateDisplaySize();
                 if (DEBUG_MISSING_GESTURE) {
                     Log.d(DEBUG_MISSING_GESTURE_TAG, "Register display listener");
@@ -665,10 +666,8 @@
                 }
 
                 // Register input event receiver
-                mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput(
-                        "edge-swipe", mDisplayId);
-                mInputEventReceiver = new InputChannelCompat.InputEventReceiver(
-                        mInputMonitor.getInputChannel(), Looper.getMainLooper(),
+                mInputMonitor = new InputMonitorCompat("edge-swipe", mDisplayId);
+                mInputEventReceiver = mInputMonitor.getInputReceiver(Looper.getMainLooper(),
                         Choreographer.getInstance(), this::onInputEvent);
 
                 // Add a nav bar panel window
@@ -758,7 +757,8 @@
     }
 
     private void updateMLModelState() {
-        boolean newState = mIsGestureHandlingEnabled && DeviceConfig.getBoolean(
+        boolean newState = mIsGestureHandlingEnabled && mContext.getResources().getBoolean(
+                R.bool.config_useBackGestureML) && DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL, false);
 
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 958ace35..21de185 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -26,6 +26,8 @@
 import android.database.ContentObserver;
 import android.os.BatteryManager;
 import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.IThermalEventListener;
 import android.os.IThermalService;
 import android.os.PowerManager;
@@ -95,6 +97,7 @@
     private Future mLastShowWarningTask;
     private boolean mEnableSkinTemperatureWarning;
     private boolean mEnableUsbTemperatureAlarm;
+    private final HandlerThread mHandlerThread;
 
     private int mLowBatteryAlertCloseLevel;
     private final int[] mLowBatteryReminderLevels = new int[2];
@@ -167,6 +170,8 @@
         mPowerManager = powerManager;
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mUserTracker = userTracker;
+        mHandlerThread = new HandlerThread("PowerUI");
+        mHandlerThread.start();
     }
 
     public void start() {
@@ -185,7 +190,8 @@
                 false, obs, UserHandle.USER_ALL);
         updateBatteryWarningLevels();
         mReceiver.init();
-        mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+        mUserTracker.addCallback(mUserChangedCallback,
+                    new HandlerExecutor(mHandlerThread.getThreadHandler()));
         mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
 
         // Check to see if we need to let the user know that the phone previously shut down due
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index d9e3e55..3f8834a 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -119,6 +119,11 @@
         }
     }
 
+    /** Wakes up the device for the Side FPS acquisition event. */
+    fun wakeUpForSideFingerprintAcquisition() {
+        repository.wakeUp("SFPS_FP_ACQUISITION_STARTED", PowerManager.WAKE_REASON_BIOMETRIC)
+    }
+
     /**
      * Called from [KeyguardService] to inform us that the device has started waking up. This is the
      * canonical source of wakefulness information for System UI. This method should not be called
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index 8e1b00d..7a4be3f 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -23,11 +23,11 @@
 import android.view.ViewGroup
 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.LinearLayout
 import com.android.settingslib.Utils
 import com.android.systemui.res.R
-import com.android.systemui.animation.view.LaunchableFrameLayout
 import com.android.systemui.statusbar.events.BackgroundAnimatableView
 
 class OngoingPrivacyChip @JvmOverloads constructor(
@@ -35,7 +35,7 @@
     attrs: AttributeSet? = null,
     defStyleAttrs: Int = 0,
     defStyleRes: Int = 0
-) : LaunchableFrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
+) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
 
     private var configuration: Configuration
     private var iconMargin = 0
@@ -43,6 +43,8 @@
     private var iconColor = 0
 
     private val iconsContainer: LinearLayout
+    val launchableContentView
+        get() = iconsContainer
 
     var privacyList = emptyList<PrivacyItem>()
         set(value) {
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
index 76ef8a2..f121630 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
@@ -26,9 +26,9 @@
 import android.os.UserHandle
 import android.permission.PermissionGroupUsage
 import android.permission.PermissionManager
-import android.view.View
 import androidx.annotation.MainThread
 import androidx.annotation.WorkerThread
+import androidx.core.view.isVisible
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.appops.AppOpsController
@@ -214,7 +214,7 @@
      * @param context A context to use to create the dialog.
      * @see filterAndSelect
      */
-    fun showDialog(context: Context, view: View? = null) {
+    fun showDialog(context: Context, privacyChip: OngoingPrivacyChip? = null) {
         dismissDialog()
         backgroundExecutor.execute {
             val usage = permGroupUsage()
@@ -277,8 +277,8 @@
                         )
                     d.setShowForAllUsers(true)
                     d.addOnDismissListener(onDialogDismissed)
-                    if (view != null) {
-                        val controller = getPrivacyDialogController(view)
+                    if (privacyChip != null) {
+                        val controller = getPrivacyDialogController(privacyChip)
                         if (controller == null) {
                             d.show()
                         } else {
@@ -296,10 +296,13 @@
         }
     }
 
-    private fun getPrivacyDialogController(source: View): DialogLaunchAnimator.Controller? {
-        val delegate = DialogLaunchAnimator.Controller.fromView(source) ?: return null
+    private fun getPrivacyDialogController(
+        source: OngoingPrivacyChip
+    ): DialogLaunchAnimator.Controller? {
+        val delegate =
+            DialogLaunchAnimator.Controller.fromView(source.launchableContentView) ?: return null
         return object : DialogLaunchAnimator.Controller by delegate {
-            override fun shouldAnimateExit() = false
+            override fun shouldAnimateExit() = source.isVisible
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/LeftRightArrowPressedListener.kt b/packages/SystemUI/src/com/android/systemui/qs/LeftRightArrowPressedListener.kt
new file mode 100644
index 0000000..ca790e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/LeftRightArrowPressedListener.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.view.KeyEvent
+import android.view.View
+import androidx.core.util.Consumer
+
+/**
+ * Listens for left and right arrow keys pressed while focus is on the view.
+ *
+ * Key press is treated as correct when its full lifecycle happened on the view: first
+ * [KeyEvent.ACTION_DOWN] was performed, view didn't lose focus in the meantime and then
+ * [KeyEvent.ACTION_UP] was performed with the same [KeyEvent.getKeyCode]
+ */
+class LeftRightArrowPressedListener private constructor() :
+    View.OnKeyListener, View.OnFocusChangeListener {
+
+    private var lastKeyCode: Int? = 0
+    private var listener: Consumer<Int>? = null
+
+    fun setArrowKeyPressedListener(arrowPressedListener: Consumer<Int>) {
+        listener = arrowPressedListener
+    }
+
+    override fun onKey(view: View, keyCode: Int, keyEvent: KeyEvent): Boolean {
+        if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
+            // only scroll on ACTION_UP as we don't handle longpressing for now. Still we need
+            // to intercept even ACTION_DOWN otherwise keyboard focus will be moved before we
+            // have a chance to intercept ACTION_UP.
+            if (keyEvent.action == KeyEvent.ACTION_UP && keyCode == lastKeyCode) {
+                listener?.accept(keyCode)
+                lastKeyCode = null
+            } else if (keyEvent.repeatCount == 0) {
+                // we only read key events that are NOT coming from long pressing because that also
+                // causes reading ACTION_DOWN event (with repeated count > 0) when moving focus with
+                // arrow from another sibling view
+                lastKeyCode = keyCode
+            }
+            return true
+        }
+        return false
+    }
+
+    override fun onFocusChange(view: View, hasFocus: Boolean) {
+        // resetting lastKeyCode so we get fresh cleared state on focus
+        if (hasFocus) {
+            lastKeyCode = null
+        }
+    }
+
+    companion object {
+        @JvmStatic
+        fun createAndRegisterListenerForView(view: View): LeftRightArrowPressedListener {
+            val listener = LeftRightArrowPressedListener()
+            view.setOnKeyListener(listener)
+            view.onFocusChangeListener = listener
+            return listener
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
index 4770d52..6fb5174 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
@@ -1,7 +1,11 @@
 package com.android.systemui.qs;
 
+import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.LEFT;
+import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.RIGHT;
+
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Animatable2;
@@ -9,10 +13,12 @@
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageView;
 
+import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 
 import com.android.settingslib.Utils;
@@ -36,13 +42,14 @@
 
     private final ArrayList<Integer> mQueuedPositions = new ArrayList<>();
 
-    private final int mPageIndicatorWidth;
-    private final int mPageIndicatorHeight;
-    private final int mPageDotWidth;
+    private int mPageIndicatorWidth;
+    private int mPageIndicatorHeight;
+    private int mPageDotWidth;
     private @NonNull ColorStateList mTint;
 
     private int mPosition = -1;
     private boolean mAnimating;
+    private PageScrollActionListener mPageScrollActionListener;
 
     private final Animatable2.AnimationCallback mAnimationCallback =
             new Animatable2.AnimationCallback() {
@@ -77,6 +84,43 @@
         mPageIndicatorWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_width);
         mPageIndicatorHeight = res.getDimensionPixelSize(R.dimen.qs_page_indicator_height);
         mPageDotWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_dot_width);
+        LeftRightArrowPressedListener arrowListener =
+                LeftRightArrowPressedListener.createAndRegisterListenerForView(this);
+        arrowListener.setArrowKeyPressedListener(keyCode -> {
+            if (mPageScrollActionListener != null) {
+                int swipeDirection = keyCode == KeyEvent.KEYCODE_DPAD_LEFT ? LEFT : RIGHT;
+                mPageScrollActionListener.onScrollActionTriggered(swipeDirection);
+            }
+        });
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        updateResources();
+    }
+
+    private void updateResources() {
+        Resources res = getResources();
+        boolean changed = false;
+        int pageIndicatorWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_width);
+        if (pageIndicatorWidth != mPageIndicatorWidth) {
+            mPageIndicatorWidth = pageIndicatorWidth;
+            changed = true;
+        }
+        int pageIndicatorHeight = res.getDimensionPixelSize(R.dimen.qs_page_indicator_height);
+        if (pageIndicatorHeight != mPageIndicatorHeight) {
+            mPageIndicatorHeight = pageIndicatorHeight;
+            changed = true;
+        }
+        int pageIndicatorDotWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_dot_width);
+        if (pageIndicatorDotWidth != mPageDotWidth) {
+            mPageDotWidth = pageIndicatorDotWidth;
+            changed = true;
+        }
+        if (changed) {
+            invalidate();
+        }
     }
 
     public void setNumPages(int numPages) {
@@ -280,4 +324,19 @@
             getChildAt(i).layout(left, 0, mPageIndicatorWidth + left, mPageIndicatorHeight);
         }
     }
+
+    void setPageScrollActionListener(PageScrollActionListener listener) {
+        mPageScrollActionListener = listener;
+    }
+
+    interface PageScrollActionListener {
+
+        @IntDef({LEFT, RIGHT})
+        @interface Direction { }
+
+        int LEFT = 0;
+        int RIGHT = 1;
+
+        void onScrollActionTriggered(@Direction int swipeDirection);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 052c0da..43f3a22 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -1,6 +1,8 @@
 package com.android.systemui.qs;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE;
+import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.LEFT;
+import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.RIGHT;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -12,7 +14,6 @@
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.util.AttributeSet;
-import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -30,6 +31,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.PageIndicator.PageScrollActionListener.Direction;
 import com.android.systemui.qs.QSPanel.QSTileLayout;
 import com.android.systemui.qs.QSPanelControllerBase.TileRecord;
 import com.android.systemui.qs.logging.QSLogger;
@@ -310,26 +312,18 @@
         mPageIndicator = indicator;
         mPageIndicator.setNumPages(mPages.size());
         mPageIndicator.setLocation(mPageIndicatorPosition);
-        mPageIndicator.setOnKeyListener((view, keyCode, keyEvent) -> {
-            if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
-                // only scroll on ACTION_UP as we don't handle longpressing for now. Still we need
-                // to intercept even ACTION_DOWN otherwise keyboard focus will be moved before we
-                // have a chance to intercept ACTION_UP.
-                if (keyEvent.getAction() == KeyEvent.ACTION_UP && mScroller.isFinished()) {
-                    scrollByX(getDeltaXForKeyboardScrolling(keyCode),
-                            SINGLE_PAGE_SCROLL_DURATION_MILLIS);
-                }
-                return true;
+        mPageIndicator.setPageScrollActionListener(swipeDirection -> {
+            if (mScroller.isFinished()) {
+                scrollByX(getDeltaXForPageScrolling(swipeDirection),
+                        SINGLE_PAGE_SCROLL_DURATION_MILLIS);
             }
-            return false;
         });
     }
 
-    private int getDeltaXForKeyboardScrolling(int keyCode) {
-        if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && getCurrentItem() != 0) {
+    private int getDeltaXForPageScrolling(@Direction int swipeDirection) {
+        if (swipeDirection == LEFT && getCurrentItem() != 0) {
             return -getWidth();
-        } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
-                && getCurrentItem() != mPages.size() - 1) {
+        } else if (swipeDirection == RIGHT && getCurrentItem() != mPages.size() - 1) {
             return getWidth();
         }
         return 0;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 0644237..a2dfc01 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -18,6 +18,8 @@
 
 import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
 
+import static com.android.systemui.Flags.centralizedStatusBarDimensRefactor;
+
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Path;
@@ -25,11 +27,15 @@
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
+import androidx.annotation.Nullable;
+
 import com.android.systemui.Dumpable;
 import com.android.systemui.qs.customize.QSCustomizer;
 import com.android.systemui.res.R;
+import com.android.systemui.shade.LargeScreenHeaderHelper;
 import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.util.LargeScreenUtils;
 
@@ -50,6 +56,7 @@
     private QuickStatusBarHeader mHeader;
     private float mQsExpansion;
     private QSCustomizer mQSCustomizer;
+    private QSPanel mQSPanel;
     private NonInterceptingScrollView mQSPanelContainer;
 
     private int mHorizontalMargins;
@@ -69,6 +76,7 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mQSPanelContainer = findViewById(R.id.expanded_qs_scroll_view);
+        mQSPanel = findViewById(R.id.quick_settings_panel);
         mHeader = findViewById(R.id.header);
         mQSCustomizer = findViewById(R.id.qs_customize);
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
@@ -76,6 +84,13 @@
 
     void setSceneContainerEnabled(boolean enabled) {
         mSceneContainerEnabled = enabled;
+        if (enabled) {
+            mQSPanelContainer.removeAllViews();
+            removeView(mQSPanelContainer);
+            LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT);
+            addView(mQSPanel, 0, lp);
+        }
     }
 
     @Override
@@ -94,20 +109,26 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         // QSPanel will show as many rows as it can (up to TileLayout.MAX_ROWS) such that the
         // bottom and footer are inside the screen.
-        MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanelContainer.getLayoutParams();
-
         int availableHeight = View.MeasureSpec.getSize(heightMeasureSpec);
-        int maxQs = availableHeight - layoutParams.topMargin - layoutParams.bottomMargin
-                - getPaddingBottom();
-        int padding = mPaddingLeft + mPaddingRight + layoutParams.leftMargin
-                + layoutParams.rightMargin;
-        final int qsPanelWidthSpec = getChildMeasureSpec(widthMeasureSpec, padding,
-                layoutParams.width);
-        mQSPanelContainer.measure(qsPanelWidthSpec,
-                MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST));
-        int width = mQSPanelContainer.getMeasuredWidth() + padding;
-        super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
-                MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY));
+
+        if (!mSceneContainerEnabled) {
+            MarginLayoutParams layoutParams =
+                    (MarginLayoutParams) mQSPanelContainer.getLayoutParams();
+            int maxQs = availableHeight - layoutParams.topMargin - layoutParams.bottomMargin
+                    - getPaddingBottom();
+            int padding = mPaddingLeft + mPaddingRight + layoutParams.leftMargin
+                    + layoutParams.rightMargin;
+            final int qsPanelWidthSpec = getChildMeasureSpec(widthMeasureSpec, padding,
+                    layoutParams.width);
+            mQSPanelContainer.measure(qsPanelWidthSpec,
+                    MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST));
+            int width = mQSPanelContainer.getMeasuredWidth() + padding;
+            super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY));
+        } else {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+
         // QSCustomizer will always be the height of the screen, but do this after
         // other measuring to avoid changing the height of the QS.
         mQSCustomizer.measure(widthMeasureSpec,
@@ -127,12 +148,15 @@
     @Override
     protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
             int parentHeightMeasureSpec, int heightUsed) {
-        // Do not measure QSPanel again when doing super.onMeasure.
-        // This prevents the pages in PagedTileLayout to be remeasured with a different (incorrect)
-        // size to the one used for determining the number of rows and then the number of pages.
-        if (child != mQSPanelContainer) {
-            super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
-                    parentHeightMeasureSpec, heightUsed);
+        if (!mSceneContainerEnabled) {
+            // Do not measure QSPanel again when doing super.onMeasure.
+            // This prevents the pages in PagedTileLayout to be remeasured with a different
+            // (incorrect) size to the one used for determining the number of rows and then the
+            // number of pages.
+            if (child != mQSPanelContainer) {
+                super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
+                        parentHeightMeasureSpec, heightUsed);
+            }
         }
     }
 
@@ -148,6 +172,7 @@
         updateClippingPath();
     }
 
+    @Nullable
     public NonInterceptingScrollView getQSPanelContainer() {
         return mQSPanelContainer;
     }
@@ -162,14 +187,26 @@
             QuickStatusBarHeaderController quickStatusBarHeaderController) {
         int topPadding = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
         if (!LargeScreenUtils.shouldUseLargeScreenShadeHeader(mContext.getResources())) {
-            topPadding = mContext.getResources()
-                    .getDimensionPixelSize(R.dimen.large_screen_shade_header_height);
+            topPadding =
+                    centralizedStatusBarDimensRefactor()
+                            ? LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext)
+                            : mContext.getResources()
+                                    .getDimensionPixelSize(
+                                            R.dimen.large_screen_shade_header_height);
         }
-        mQSPanelContainer.setPaddingRelative(
-                mQSPanelContainer.getPaddingStart(),
-                mSceneContainerEnabled ? 0 : topPadding,
-                mQSPanelContainer.getPaddingEnd(),
-                mQSPanelContainer.getPaddingBottom());
+        if (mQSPanelContainer != null) {
+            mQSPanelContainer.setPaddingRelative(
+                    mQSPanelContainer.getPaddingStart(),
+                    mSceneContainerEnabled ? 0 : topPadding,
+                    mQSPanelContainer.getPaddingEnd(),
+                    mQSPanelContainer.getPaddingBottom());
+        } else {
+            mQSPanel.setPaddingRelative(
+                    mQSPanel.getPaddingStart(),
+                    mSceneContainerEnabled ? 0 : topPadding,
+                    mQSPanel.getPaddingEnd(),
+                    mQSPanel.getPaddingBottom());
+        }
 
         int horizontalMargins = getResources().getDimensionPixelSize(R.dimen.qs_horizontal_margin);
         int horizontalPadding = getResources().getDimensionPixelSize(
@@ -213,7 +250,9 @@
 
     public void setExpansion(float expansion) {
         mQsExpansion = expansion;
-        mQSPanelContainer.setScrollingEnabled(expansion > 0f);
+        if (mQSPanelContainer != null) {
+            mQSPanelContainer.setScrollingEnabled(expansion > 0f);
+        }
         updateExpansion();
     }
 
@@ -232,7 +271,7 @@
                 lp.rightMargin = mHorizontalMargins;
                 lp.leftMargin = mHorizontalMargins;
             }
-            if (view == mQSPanelContainer) {
+            if (view == mQSPanelContainer || view == mQSPanel) {
                 // QS panel lays out some of its content full width
                 qsPanelController.setContentMargins(mContentHorizontalPadding,
                         mContentHorizontalPadding);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index 7b001c7..ffbc560 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -81,6 +81,9 @@
     public void onInit() {
         mQuickStatusBarHeaderController.init();
         mView.setSceneContainerEnabled(mSceneContainerEnabled);
+        if (mSceneContainerEnabled && mQsPanelController != null) {
+            mQSPanelContainer.setOnTouchListener(null);
+        }
     }
 
     public void setListening(boolean listening) {
@@ -91,13 +94,17 @@
     protected void onViewAttached() {
         mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
         mConfigurationController.addCallback(mConfigurationListener);
-        mQSPanelContainer.setOnTouchListener(mContainerTouchHandler);
+        if (!mSceneContainerEnabled && mQSPanelContainer != null) {
+            mQSPanelContainer.setOnTouchListener(mContainerTouchHandler);
+        }
     }
 
     @Override
     protected void onViewDetached() {
         mConfigurationController.removeCallback(mConfigurationListener);
-        mQSPanelContainer.setOnTouchListener(null);
+        if (mQSPanelContainer != null) {
+            mQSPanelContainer.setOnTouchListener(null);
+        }
     }
 
     public QSContainerImpl getView() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
index c908e6e..5a872d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
@@ -35,6 +35,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.settingslib.development.DevelopmentSettingsEnabler;
+import com.android.systemui.FontSizeUtils;
 import com.android.systemui.res.R;
 
 /**
@@ -109,11 +110,31 @@
 
     private void updateResources() {
         updateFooterAnimator();
+        updateEditButtonResources();
+        updateBuildTextResources();
         MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
+        lp.height = getResources().getDimensionPixelSize(R.dimen.qs_footer_height);
+        int sideMargin = getResources().getDimensionPixelSize(R.dimen.qs_footer_margin);
+        lp.leftMargin = sideMargin;
+        lp.rightMargin = sideMargin;
         lp.bottomMargin = getResources().getDimensionPixelSize(R.dimen.qs_footers_margin_bottom);
         setLayoutParams(lp);
     }
 
+    private void updateEditButtonResources() {
+        int size = getResources().getDimensionPixelSize(R.dimen.qs_footer_action_button_size);
+        int padding = getResources().getDimensionPixelSize(R.dimen.qs_footer_icon_padding);
+        MarginLayoutParams lp = (MarginLayoutParams) mEditButton.getLayoutParams();
+        lp.height = size;
+        lp.width = size;
+        mEditButton.setLayoutParams(lp);
+        mEditButton.setPadding(padding, padding, padding, padding);
+    }
+
+    private void updateBuildTextResources() {
+        FontSizeUtils.updateFontSizeFromStyle(mBuildText, R.style.TextAppearance_QS_Status_Build);
+    }
+
     private void updateFooterAnimator() {
         mFooterAnimator = createFooterAnimator();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index 7f91fd2..290821e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -61,6 +61,7 @@
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarState;
@@ -171,8 +172,11 @@
     private CommandQueue mCommandQueue;
 
     private View mRootView;
+    @Nullable
     private View mFooterActionsView;
 
+    private final SceneContainerFlags mSceneContainerFlags;
+
     @Inject
     public QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
             SysuiStatusBarStateController statusBarStateController, CommandQueue commandQueue,
@@ -185,7 +189,8 @@
             FooterActionsViewModel.Factory footerActionsViewModelFactory,
             FooterActionsViewBinder footerActionsViewBinder,
             LargeScreenShadeInterpolator largeScreenShadeInterpolator,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            SceneContainerFlags sceneContainerFlags) {
         mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
         mQsMediaHost = qsMediaHost;
         mQqsMediaHost = qqsMediaHost;
@@ -201,6 +206,7 @@
         mFooterActionsViewModelFactory = footerActionsViewModelFactory;
         mFooterActionsViewBinder = footerActionsViewBinder;
         mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner();
+        mSceneContainerFlags = sceneContainerFlags;
     }
 
     /**
@@ -216,10 +222,17 @@
         mQSPanelController.init();
         mQuickQSPanelController.init();
 
-        mQSFooterActionsViewModel = mFooterActionsViewModelFactory
-                .create(mListeningAndVisibilityLifecycleOwner);
-        bindFooterActionsView(mRootView);
-        mFooterActionsController.init();
+        if (!mSceneContainerFlags.isEnabled()) {
+            mQSFooterActionsViewModel = mFooterActionsViewModelFactory
+                    .create(mListeningAndVisibilityLifecycleOwner);
+            bindFooterActionsView(mRootView);
+            mFooterActionsController.init();
+        } else {
+            View footerView = mRootView.findViewById(R.id.qs_footer_actions);
+            if (footerView != null) {
+                ((ViewGroup) footerView.getParent()).removeView(footerView);
+            }
+        }
 
         mQSPanelScrollView = mRootView.findViewById(R.id.expanded_qs_scroll_view);
         mQSPanelScrollView.addOnLayoutChangeListener(
@@ -234,6 +247,7 @@
                         mScrollListener.onQsPanelScrollChanged(scrollY);
                     }
                 });
+        mQSPanelScrollView.setScrollingEnabled(!mSceneContainerFlags.isEnabled());
         mHeader = mRootView.findViewById(R.id.header);
         mFooter = qsComponent.getQSFooter();
 
@@ -481,7 +495,9 @@
         boolean footerVisible = qsPanelVisible && (mQsExpanded || !keyguardShowing
                 || mHeaderAnimating || mShowCollapsedOnKeyguard);
         mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
-        mFooterActionsView.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
+        if (mFooterActionsView != null) {
+            mFooterActionsView.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
+        }
         mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
                 || (mQsExpanded && !mStackScrollerOverscrolling));
         mQSPanelController.setVisibility(qsPanelVisible ? View.VISIBLE : View.INVISIBLE);
@@ -622,8 +638,13 @@
 
     @Override
     public int getHeightDiff() {
-        return mQSPanelScrollView.getBottom() - mHeader.getBottom()
-                + mHeader.getPaddingBottom();
+        if (mSceneContainerFlags.isEnabled()) {
+            return mQSPanelController.getViewBottom() - mHeader.getBottom()
+                    + mHeader.getPaddingBottom();
+        } else {
+            return mQSPanelScrollView.getBottom() - mHeader.getBottom()
+                    + mHeader.getPaddingBottom();
+        }
     }
 
     @Override
@@ -678,25 +699,29 @@
         mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
         float footerActionsExpansion =
                 onKeyguardAndExpanded ? 1 : mInSplitShade ? alphaProgress : expansion;
-        mQSFooterActionsViewModel.onQuickSettingsExpansionChanged(footerActionsExpansion,
-                mInSplitShade);
+        if (mQSFooterActionsViewModel != null) {
+            mQSFooterActionsViewModel.onQuickSettingsExpansionChanged(footerActionsExpansion,
+                    mInSplitShade);
+        }
         mQSPanelController.setRevealExpansion(expansion);
         mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
         mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
 
-        float qsScrollViewTranslation =
-                onKeyguard && !mShowCollapsedOnKeyguard ? panelTranslationY : 0;
-        mQSPanelScrollView.setTranslationY(qsScrollViewTranslation);
+        if (!mSceneContainerFlags.isEnabled()) {
+            float qsScrollViewTranslation =
+                    onKeyguard && !mShowCollapsedOnKeyguard ? panelTranslationY : 0;
+            mQSPanelScrollView.setTranslationY(qsScrollViewTranslation);
 
-        if (fullyCollapsed) {
-            mQSPanelScrollView.setScrollY(0);
-        }
+            if (fullyCollapsed) {
+                mQSPanelScrollView.setScrollY(0);
+            }
 
-        if (!fullyExpanded) {
-            // Set bounds on the QS panel so it doesn't run over the header when animating.
-            mQsBounds.top = (int) -mQSPanelScrollView.getTranslationY();
-            mQsBounds.right = mQSPanelScrollView.getWidth();
-            mQsBounds.bottom = mQSPanelScrollView.getHeight();
+            if (!fullyExpanded) {
+                // Set bounds on the QS panel so it doesn't run over the header when animating.
+                mQsBounds.top = (int) -mQSPanelScrollView.getTranslationY();
+                mQsBounds.right = mQSPanelScrollView.getWidth();
+                mQsBounds.bottom = mQSPanelScrollView.getHeight();
+            }
         }
         updateQsBounds();
 
@@ -786,15 +811,17 @@
             mQsBounds.set(-sideMargin, 0, mQSPanelScrollView.getWidth() + sideMargin,
                     mQSPanelScrollView.getHeight());
         }
-        mQSPanelScrollView.setClipBounds(mQsBounds);
+        if (!mSceneContainerFlags.isEnabled()) {
+            mQSPanelScrollView.setClipBounds(mQsBounds);
 
-        mQSPanelScrollView.getLocationOnScreen(mLocationTemp);
-        int left = mLocationTemp[0];
-        int top = mLocationTemp[1];
-        mQsMediaHost.getCurrentClipping().set(left, top,
-                left + getView().getMeasuredWidth(),
-                top + mQSPanelScrollView.getMeasuredHeight()
-                        - mQSPanelController.getPaddingBottom());
+            mQSPanelScrollView.getLocationOnScreen(mLocationTemp);
+            int left = mLocationTemp[0];
+            int top = mLocationTemp[1];
+            mQsMediaHost.getCurrentClipping().set(left, top,
+                    left + getView().getMeasuredWidth(),
+                    top + mQSPanelScrollView.getMeasuredHeight()
+                            - mQSPanelController.getPaddingBottom());
+        }
     }
 
     private void updateMediaPositions() {
@@ -867,9 +894,15 @@
         // The customize state changed, so our height changed.
         mContainer.updateExpansion();
         boolean customizing = isCustomizing();
-        mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
+        if (mSceneContainerFlags.isEnabled()) {
+            mQSPanelController.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
+        } else {
+            mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
+        }
         mFooter.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
-        mFooterActionsView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
+        if (mFooterActionsView != null) {
+            mFooterActionsView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
+        }
         mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
         // Let the panel know the position changed and it needs to update where notifications
         // and whatnot are.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 51b94dd..7a7ee59 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -387,7 +387,7 @@
         setPaddingRelative(getPaddingStart(),
                 mSceneContainerEnabled ? 0 : paddingTop,
                 getPaddingEnd(),
-                paddingBottom);
+                mSceneContainerEnabled ? 0 : paddingBottom);
     }
 
     void addOnConfigurationChangedListener(OnConfigurationChangedListener listener) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index ef58a60..c3f5086 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -278,5 +278,9 @@
     public int getPaddingBottom() {
         return mView.getPaddingBottom();
     }
+
+    int getViewBottom() {
+        return mView.getBottom();
+    }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index a103566..07705f3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -58,6 +58,7 @@
     private final RecyclerView mRecyclerView;
     private boolean mCustomizing;
     private QSContainerController mQsContainerController;
+    private final Toolbar mToolbar;
     private QS mQs;
     private int mX;
     private int mY;
@@ -69,15 +70,15 @@
 
         LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content, this);
         mClipper = new QSDetailClipper(findViewById(R.id.customize_container));
-        Toolbar toolbar = findViewById(com.android.internal.R.id.action_bar);
+        mToolbar = findViewById(com.android.internal.R.id.action_bar);
         TypedValue value = new TypedValue();
         mContext.getTheme().resolveAttribute(android.R.attr.homeAsUpIndicator, value, true);
-        toolbar.setNavigationIcon(
+        mToolbar.setNavigationIcon(
                 getResources().getDrawable(value.resourceId, mContext.getTheme()));
 
-        toolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset)
+        mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset)
                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
-        toolbar.setTitle(R.string.qs_edit);
+        mToolbar.setTitle(R.string.qs_edit);
         mRecyclerView = findViewById(android.R.id.list);
         mTransparentView = findViewById(R.id.customizer_transparent_view);
         DefaultItemAnimator animator = new DefaultItemAnimator();
@@ -184,6 +185,14 @@
         return isShown;
     }
 
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mToolbar.setTitleTextAppearance(mContext,
+                android.R.style.TextAppearance_DeviceDefault_Widget_ActionBar_Title);
+        updateToolbarMenuFontSize();
+    }
+
     void setCustomizing(boolean customizing) {
         mCustomizing = customizing;
         if (mQs != null) {
@@ -269,4 +278,11 @@
         lp.height = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
         mTransparentView.setLayoutParams(lp);
     }
+
+    private void updateToolbarMenuFontSize() {
+        // Clearing and re-adding the toolbar action force updates the font size
+        mToolbar.getMenu().clear();
+        mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset)
+                .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapterDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapterDelegate.java
index 92f17f9..e098929 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapterDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapterDelegate.java
@@ -119,6 +119,8 @@
                     info.removeAction(listOfActions.get(i));
                 }
             }
+            // We really don't want it to be clickable in this case.
+            info.setClickable(false);
             return;
         }
 
@@ -126,6 +128,7 @@
                 new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
                         AccessibilityNodeInfo.ACTION_CLICK, clickActionString);
         info.addAction(action);
+        info.setClickable(true);
     }
 
     private void maybeAddActionMoveToPosition(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index aff4a67..2077d73 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -88,9 +88,9 @@
     /** Called when the expansion of the Quick Settings changed. */
     fun onQuickSettingsExpansionChanged(expansion: Float, isInSplitShade: Boolean) {
         if (isInSplitShade) {
-            // In split shade, we want to fade in the background only at the very end (see
-            // b/240563302).
-            val delay = 0.99f
+            // In split shade, we want to fade in the background when the QS background starts to
+            // show.
+            val delay = 0.15f
             _alpha.value = expansion
             _backgroundAlpha.value = max(0f, expansion - delay) / (1f - delay)
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
index adea26e..e1ec338 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
@@ -18,6 +18,8 @@
 
 import android.content.res.Resources
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.domain.autoaddable.A11yShortcutAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.A11yShortcutAutoAddableList
 import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSetting
 import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSettingList
 import com.android.systemui.qs.pipeline.domain.autoaddable.CastAutoAddable
@@ -51,6 +53,16 @@
                 )
                 .toSet()
         }
+
+        @Provides
+        @ElementsIntoSet
+        fun providesA11yShortcutAutoAddable(
+            a11yShortcutAutoAddableFactory: A11yShortcutAutoAddable.Factory
+        ): Set<AutoAddable> {
+            return A11yShortcutAutoAddableList.getA11yShortcutAutoAddables(
+                a11yShortcutAutoAddableFactory
+            )
+        }
     }
 
     @Binds @IntoSet fun bindCastAutoAddable(impl: CastAutoAddable): AutoAddable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
index 6e7e099..dc39c97 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
@@ -18,28 +18,28 @@
 
 import android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
 import android.annotation.WorkerThread
-import android.content.BroadcastReceiver
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
-import android.content.IntentFilter
 import android.content.pm.PackageManager
 import android.content.pm.PackageManager.ResolveInfoFlags
 import android.os.UserHandle
 import android.service.quicksettings.TileService
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import androidx.annotation.GuardedBy
+import com.android.systemui.common.data.repository.PackageChangeRepository
+import com.android.systemui.common.data.shared.model.PackageChangeModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.util.kotlin.isComponentActuallyEnabled
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
 
 interface InstalledTilesComponentRepository {
 
@@ -51,47 +51,39 @@
 @Inject
 constructor(
     @Application private val applicationContext: Context,
-    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    @Background private val backgroundScope: CoroutineScope,
+    private val packageChangeRepository: PackageChangeRepository
 ) : InstalledTilesComponentRepository {
 
-    override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> {
-        /*
-         * In order to query [PackageManager] for different users, this implementation will call
-         * [Context.createContextAsUser] and retrieve the [PackageManager] from that context.
-         */
-        val packageManager =
-            if (applicationContext.userId == userId) {
-                applicationContext.packageManager
-            } else {
-                applicationContext
-                    .createContextAsUser(
-                        UserHandle.of(userId),
-                        /* flags */ 0,
-                    )
-                    .packageManager
-            }
-        return conflatedCallbackFlow {
-                val receiver =
-                    object : BroadcastReceiver() {
-                        override fun onReceive(context: Context?, intent: Intent?) {
-                            trySend(Unit)
-                        }
-                    }
-                applicationContext.registerReceiverAsUser(
-                    receiver,
-                    UserHandle.of(userId),
-                    INTENT_FILTER,
-                    /* broadcastPermission = */ null,
-                    /* scheduler = */ null
-                )
+    @GuardedBy("userMap") private val userMap = mutableMapOf<Int, Flow<Set<ComponentName>>>()
 
-                awaitClose { applicationContext.unregisterReceiver(receiver) }
+    override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> =
+        synchronized(userMap) {
+            userMap.getOrPut(userId) {
+                /*
+                 * In order to query [PackageManager] for different users, this implementation will
+                 * call [Context.createContextAsUser] and retrieve the [PackageManager] from that
+                 * context.
+                 */
+                val packageManager =
+                    if (applicationContext.userId == userId) {
+                        applicationContext.packageManager
+                    } else {
+                        applicationContext
+                            .createContextAsUser(
+                                UserHandle.of(userId),
+                                /* flags */ 0,
+                            )
+                            .packageManager
+                    }
+                packageChangeRepository
+                    .packageChanged(UserHandle.of(userId))
+                    .onStart { emit(PackageChangeModel.Empty) }
+                    .map { reloadComponents(userId, packageManager) }
+                    .distinctUntilChanged()
+                    .shareIn(backgroundScope, SharingStarted.WhileSubscribed(), replay = 1)
             }
-            .onStart { emit(Unit) }
-            .map { reloadComponents(userId, packageManager) }
-            .distinctUntilChanged()
-            .flowOn(backgroundDispatcher)
-    }
+        }
 
     @WorkerThread
     private fun reloadComponents(userId: Int, packageManager: PackageManager): Set<ComponentName> {
@@ -104,14 +96,6 @@
     }
 
     companion object {
-        private val INTENT_FILTER =
-            IntentFilter().apply {
-                addAction(Intent.ACTION_PACKAGE_ADDED)
-                addAction(Intent.ACTION_PACKAGE_CHANGED)
-                addAction(Intent.ACTION_PACKAGE_REMOVED)
-                addAction(Intent.ACTION_PACKAGE_REPLACED)
-                addDataScheme("package")
-            }
         private val INTENT = Intent(TileService.ACTION_QS_TILE)
         private val FLAGS =
             ResolveInfoFlags.of(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddable.kt
new file mode 100644
index 0000000..2cebbe3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddable.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.provider.Settings
+import com.android.systemui.accessibility.data.repository.AccessibilityQsShortcutsRepository
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.Objects
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+/**
+ * [A11yShortcutAutoAddable] will auto add/remove qs tile of the accessibility framework feature
+ * based on the user's choices in the Settings app.
+ *
+ * The a11y feature component is added to [Settings.Secure.ACCESSIBILITY_QS_TARGETS] when the user
+ * selects to use qs tile as a shortcut for the a11 feature in the Settings app. The accessibility
+ * feature component is removed from [Settings.Secure.ACCESSIBILITY_QS_TARGETS] when the user
+ * doesn't want to use qs tile as a shortcut for the a11y feature in the Settings app.
+ *
+ * [A11yShortcutAutoAddable] tracks a [Settings.Secure.ACCESSIBILITY_QS_TARGETS] and when its value
+ * changes, it will emit a [AutoAddSignal.Add] for the [spec] if the [componentName] is a substring
+ * of the value; it will emit a [AutoAddSignal.Remove] for the [spec] if the [componentName] is not
+ * a substring of the value.
+ */
+class A11yShortcutAutoAddable
+@AssistedInject
+constructor(
+    private val a11yQsShortcutsRepository: AccessibilityQsShortcutsRepository,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    @Assisted private val spec: TileSpec,
+    @Assisted private val componentName: ComponentName
+) : AutoAddable {
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return a11yQsShortcutsRepository
+            .a11yQsShortcutTargets(userId)
+            .map { it.contains(componentName.flattenToString()) }
+            .filterNotNull()
+            .distinctUntilChanged()
+            .map { if (it) AutoAddSignal.Add(spec) else AutoAddSignal.Remove(spec) }
+            .flowOn(bgDispatcher)
+    }
+
+    override val autoAddTracking = AutoAddTracking.Always
+
+    override val description =
+        "A11yShortcutAutoAddableSetting: $spec:$componentName ($autoAddTracking)"
+
+    override fun equals(other: Any?): Boolean {
+        return other is A11yShortcutAutoAddable &&
+            spec == other.spec &&
+            componentName == other.componentName
+    }
+
+    override fun hashCode(): Int {
+        return Objects.hash(spec, componentName)
+    }
+
+    override fun toString(): String {
+        return description
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(spec: TileSpec, componentName: ComponentName): A11yShortcutAutoAddable
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt
new file mode 100644
index 0000000..08e3920
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.view.accessibility.Flags
+import com.android.internal.accessibility.AccessibilityShortcutController
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.ColorCorrectionTile
+import com.android.systemui.qs.tiles.ColorInversionTile
+import com.android.systemui.qs.tiles.OneHandedModeTile
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+
+object A11yShortcutAutoAddableList {
+
+    /**
+     * Generate a collection of [A11yShortcutAutoAddable] for the framework tiles related to
+     * accessibility features with shortcut options
+     */
+    fun getA11yShortcutAutoAddables(factory: A11yShortcutAutoAddable.Factory): Set<AutoAddable> {
+        return if (Flags.a11yQsShortcut()) {
+            setOf(
+                factory.create(
+                    TileSpec.create(ColorCorrectionTile.TILE_SPEC),
+                    AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME
+                ),
+                factory.create(
+                    TileSpec.create(ColorInversionTile.TILE_SPEC),
+                    AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME
+                ),
+                factory.create(
+                    TileSpec.create(OneHandedModeTile.TILE_SPEC),
+                    AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME
+                ),
+                factory.create(
+                    TileSpec.create(ReduceBrightColorsTile.TILE_SPEC),
+                    AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME
+                ),
+            )
+        } else {
+            emptySet()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index d04e4f5..53f287b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -33,11 +33,13 @@
 import android.widget.ImageView;
 import android.widget.ImageView.ScaleType;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.settingslib.Utils;
-import com.android.systemui.res.R;
 import com.android.systemui.plugins.qs.QSIconView;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTile.State;
+import com.android.systemui.res.R;
 
 import java.util.Objects;
 
@@ -52,7 +54,10 @@
     private boolean mDisabledByPolicy = false;
     private int mTint;
     @Nullable
-    private QSTile.Icon mLastIcon;
+    @VisibleForTesting
+    QSTile.Icon mLastIcon;
+
+    private boolean mIconChangeScheduled;
 
     private ValueAnimator mColorAnimator = new ValueAnimator();
 
@@ -112,6 +117,7 @@
     }
 
     protected void updateIcon(ImageView iv, State state, boolean allowAnimations) {
+        mIconChangeScheduled = false;
         final QSTile.Icon icon = state.iconSupplier != null ? state.iconSupplier.get() : state.icon;
         if (!Objects.equals(icon, iv.getTag(R.id.qs_icon_tag))) {
             boolean shouldAnimate = allowAnimations && shouldAnimate(iv);
@@ -167,7 +173,12 @@
             mState = state.state;
             mDisabledByPolicy = state.disabledByPolicy;
             if (mTint != 0 && allowAnimations && shouldAnimate(iv)) {
-                animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations));
+                mIconChangeScheduled = true;
+                animateGrayScale(mTint, color, iv, () -> {
+                    if (mIconChangeScheduled) {
+                        updateIcon(iv, state, allowAnimations);
+                    }
+                });
             } else {
                 setTint(iv, color);
                 updateIcon(iv, state, allowAnimations);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 4565200..c5eeb2f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -249,6 +249,10 @@
             height = iconSize
             marginEnd = endMargin
         }
+
+        background = createTileBackground()
+        setColor(backgroundColor)
+        setOverlayColor(backgroundOverlayColor)
     }
 
     private fun createAndAddLabels() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index 3a247c5..3b8fb26 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -20,6 +20,7 @@
 import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT;
 
 import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
+import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE;
 import static com.android.systemui.wallet.util.WalletCardUtilsKt.getPaymentCards;
 
 import android.content.Intent;
@@ -42,7 +43,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -54,6 +54,7 @@
 import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.wallet.controller.QuickAccessWalletController;
@@ -118,7 +119,8 @@
     protected void handleSetListening(boolean listening) {
         super.handleSetListening(listening);
         if (listening) {
-            mController.setupWalletChangeObservers(mCardRetriever, DEFAULT_PAYMENT_APP_CHANGE);
+            mController.setupWalletChangeObservers(mCardRetriever, DEFAULT_PAYMENT_APP_CHANGE,
+                    DEFAULT_WALLET_APP_CHANGE);
             if (!mController.getWalletClient().isWalletServiceAvailable()
                     || !mController.getWalletClient().isWalletFeatureAvailable()) {
                 Log.i(TAG, "QAW service is unavailable, recreating the wallet client.");
@@ -201,7 +203,8 @@
     @Override
     protected void handleDestroy() {
         super.handleDestroy();
-        mController.unregisterWalletChangeObservers(DEFAULT_PAYMENT_APP_CHANGE);
+        mController.unregisterWalletChangeObservers(DEFAULT_PAYMENT_APP_CHANGE,
+                DEFAULT_WALLET_APP_CHANGE);
     }
 
     private class WalletCardRetriever implements
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index 216d716..88863cb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.qs.tiles
 
 import android.app.AlertDialog
+import android.app.BroadcastOptions
+import android.app.PendingIntent
 import android.content.Intent
 import android.os.Handler
 import android.os.Looper
@@ -42,6 +44,8 @@
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.recordissue.RecordIssueDialogDelegate
 import com.android.systemui.res.R
+import com.android.systemui.screenrecord.RecordingService
+import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import javax.inject.Inject
@@ -61,6 +65,7 @@
     private val keyguardDismissUtil: KeyguardDismissUtil,
     private val keyguardStateController: KeyguardStateController,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
+    private val userContextProvider: UserContextProvider,
     private val delegateFactory: RecordIssueDialogDelegate.Factory,
 ) :
     QSTileImpl<QSTile.BooleanState>(
@@ -91,12 +96,22 @@
     public override fun handleClick(view: View?) {
         if (isRecording) {
             isRecording = false
+            stopScreenRecord()
         } else {
             mUiHandler.post { showPrompt(view) }
         }
         refreshState()
     }
 
+    private fun stopScreenRecord() =
+        PendingIntent.getService(
+                userContextProvider.userContext,
+                RecordingService.REQUEST_CODE,
+                RecordingService.getStopIntent(userContextProvider.userContext),
+                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+            )
+            .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
+
     private fun showPrompt(view: View?) {
         val dialog: AlertDialog =
             delegateFactory
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
index fc06090..fe10eaa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
@@ -18,6 +18,8 @@
 
 import android.app.PendingIntent
 import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.UserHandle
 import android.view.View
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.animation.ActivityLaunchAnimator
@@ -32,13 +34,23 @@
 interface QSTileIntentUserInputHandler {
 
     fun handle(view: View?, intent: Intent)
-    fun handle(view: View?, pendingIntent: PendingIntent)
+
+    /** @param requestLaunchingDefaultActivity used in case !pendingIndent.isActivity */
+    fun handle(
+        view: View?,
+        pendingIntent: PendingIntent,
+        requestLaunchingDefaultActivity: Boolean = false
+    )
 }
 
 @SysUISingleton
 class QSTileIntentUserInputHandlerImpl
 @Inject
-constructor(private val activityStarter: ActivityStarter) : QSTileIntentUserInputHandler {
+constructor(
+    private val activityStarter: ActivityStarter,
+    private val packageManager: PackageManager,
+    private val userHandle: UserHandle,
+) : QSTileIntentUserInputHandler {
 
     override fun handle(view: View?, intent: Intent) {
         val animationController: ActivityLaunchAnimator.Controller? =
@@ -52,21 +64,41 @@
     }
 
     // TODO(b/249804373): make sure to allow showing activities over the lockscreen. See b/292112939
-    override fun handle(view: View?, pendingIntent: PendingIntent) {
-        if (!pendingIntent.isActivity) {
-            return
-        }
-        val animationController: ActivityLaunchAnimator.Controller? =
-            view?.let {
-                ActivityLaunchAnimator.Controller.fromView(
-                    it,
-                    InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
+    override fun handle(
+        view: View?,
+        pendingIntent: PendingIntent,
+        requestLaunchingDefaultActivity: Boolean
+    ) {
+        if (pendingIntent.isActivity) {
+            val animationController: ActivityLaunchAnimator.Controller? =
+                view?.let {
+                    ActivityLaunchAnimator.Controller.fromView(
+                        it,
+                        InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
+                    )
+                }
+            activityStarter.postStartActivityDismissingKeyguard(pendingIntent, animationController)
+        } else if (requestLaunchingDefaultActivity) {
+            val intent =
+                Intent(Intent.ACTION_MAIN)
+                    .addCategory(Intent.CATEGORY_LAUNCHER)
+                    .setPackage(pendingIntent.creatorPackage)
+                    .addFlags(
+                        Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+                    )
+            val intents =
+                packageManager.queryIntentActivitiesAsUser(
+                    intent,
+                    PackageManager.ResolveInfoFlags.of(0L),
+                    userHandle.identifier
                 )
-            }
-        activityStarter.startPendingIntentMaybeDismissingKeyguard(
-            pendingIntent,
-            null,
-            animationController
-        )
+            intents
+                .firstOrNull { it.activityInfo.exported }
+                ?.let { resolved ->
+                    intent.setPackage(null)
+                    intent.setComponent(resolved.activityInfo.componentName)
+                    handle(view, intent)
+                }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 592cb3b..211b4594 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -192,6 +192,7 @@
     private DialogLaunchAnimator mDialogLaunchAnimator;
     private boolean mHasWifiEntries;
     private WifiStateWorker mWifiStateWorker;
+    private boolean mHasActiveSubId;
 
     @VisibleForTesting
     static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f;
@@ -299,6 +300,7 @@
                 mExecutor);
         // Listen the subscription changes
         mOnSubscriptionsChangedListener = new InternetOnSubscriptionChangedListener();
+        refreshHasActiveSubId();
         mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
                 mOnSubscriptionsChangedListener);
         mDefaultDataSubId = getDefaultDataSubscriptionId();
@@ -901,18 +903,22 @@
      * @return whether there is the carrier item in the slice.
      */
     boolean hasActiveSubId() {
-        if (mSubscriptionManager == null) {
-            if (DEBUG) {
-                Log.d(TAG, "SubscriptionManager is null, can not check carrier.");
-            }
+        if (isAirplaneModeEnabled() || mTelephonyManager == null) {
             return false;
         }
 
-        if (isAirplaneModeEnabled() || mTelephonyManager == null
-                || mSubscriptionManager.getActiveSubscriptionIdList().length <= 0) {
-            return false;
+        return mHasActiveSubId;
+    }
+
+    private void refreshHasActiveSubId() {
+        if (mSubscriptionManager == null) {
+            mHasActiveSubId = false;
+            Log.e(TAG, "SubscriptionManager is null, set mHasActiveSubId = false");
+            return;
         }
-        return true;
+
+        mHasActiveSubId = mSubscriptionManager.getActiveSubscriptionIdList().length > 0;
+        Log.i(TAG, "mHasActiveSubId:" + mHasActiveSubId);
     }
 
     /**
@@ -1204,6 +1210,7 @@
 
         @Override
         public void onSubscriptionsChanged() {
+            refreshHasActiveSubId();
             updateListener();
         }
     }
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 1805eb1..1a06c38 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
@@ -22,12 +22,14 @@
 import android.view.View
 import android.view.View.AccessibilityDelegate
 import android.view.View.GONE
+import android.view.View.INVISIBLE
 import android.view.View.VISIBLE
 import android.view.ViewGroup
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.view.accessibility.AccessibilityNodeInfo
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
 import android.widget.ImageView
+import android.widget.ProgressBar
 import android.widget.Switch
 import android.widget.TextView
 import androidx.recyclerview.widget.AsyncListDiffer
@@ -92,6 +94,8 @@
     private lateinit var pairNewDeviceButton: View
     private lateinit var deviceListView: RecyclerView
     private lateinit var scrollViewContent: View
+    private lateinit var progressBarAnimation: ProgressBar
+    private lateinit var progressBarBackground: View
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -122,6 +126,8 @@
             scrollViewContent = this
             layoutParams.height = cachedContentHeight
         }
+        progressBarAnimation = requireViewById(R.id.bluetooth_tile_dialog_progress_animation)
+        progressBarBackground = requireViewById(R.id.bluetooth_tile_dialog_progress_background)
     }
 
     override fun start() {
@@ -135,6 +141,17 @@
         super.dismiss()
     }
 
+    internal suspend fun animateProgressBar(animate: Boolean) {
+        withContext(mainDispatcher) {
+            if (animate) {
+                showProgressBar()
+            } else {
+                delay(PROGRESS_BAR_ANIMATION_DURATION_MS)
+                hideProgressBar()
+            }
+        }
+    }
+
     internal suspend fun onDeviceItemUpdated(
         deviceItem: List<DeviceItem>,
         showSeeAll: Boolean,
@@ -190,6 +207,28 @@
         }
     }
 
+    private fun showProgressBar() {
+        if (
+            ::progressBarAnimation.isInitialized &&
+                ::progressBarBackground.isInitialized &&
+                progressBarAnimation.visibility != VISIBLE
+        ) {
+            progressBarAnimation.visibility = VISIBLE
+            progressBarBackground.visibility = INVISIBLE
+        }
+    }
+
+    private fun hideProgressBar() {
+        if (
+            ::progressBarAnimation.isInitialized &&
+                ::progressBarBackground.isInitialized &&
+                progressBarAnimation.visibility != INVISIBLE
+        ) {
+            progressBarAnimation.visibility = INVISIBLE
+            progressBarBackground.visibility = VISIBLE
+        }
+    }
+
     internal inner class Adapter(private val onClickCallback: BluetoothTileDialogCallback) :
         RecyclerView.Adapter<Adapter.DeviceItemViewHolder>() {
 
@@ -300,6 +339,7 @@
         const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS"
         const val DISABLED_ALPHA = 0.3f
         const val ENABLED_ALPHA = 1f
+        const val PROGRESS_BAR_ANIMATION_DURATION_MS = 1500L
 
         private fun Boolean.toInt(): Int {
             return if (this) 1 else 0
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 6d08f59..194e7bc 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
@@ -105,6 +105,41 @@
                     deviceItemInteractor.updateDeviceItems(context, DeviceFetchTrigger.FIRST_LOAD)
                 }
 
+                // deviceItemUpdate is emitted when device item list is done fetching, update UI and
+                // stop the progress bar.
+                deviceItemInteractor.deviceItemUpdate
+                    .onEach {
+                        updateDialogUiJob?.cancel()
+                        updateDialogUiJob = launch {
+                            dialog.apply {
+                                onDeviceItemUpdated(
+                                    it.take(MAX_DEVICE_ITEM_ENTRY),
+                                    showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
+                                    showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled
+                                )
+                                animateProgressBar(false)
+                            }
+                        }
+                    }
+                    .launchIn(this)
+
+                // deviceItemUpdateRequest is emitted when a bluetooth callback is called, re-fetch
+                // the device item list and animiate the progress bar.
+                deviceItemInteractor.deviceItemUpdateRequest
+                    .onEach {
+                        dialog.animateProgressBar(true)
+                        updateDeviceItemJob?.cancel()
+                        updateDeviceItemJob = launch {
+                            deviceItemInteractor.updateDeviceItems(
+                                context,
+                                DeviceFetchTrigger.BLUETOOTH_CALLBACK_RECEIVED
+                            )
+                        }
+                    }
+                    .launchIn(this)
+
+                // bluetoothStateUpdate is emitted when bluetooth on/off state is changed, re-fetch
+                // the device item list.
                 bluetoothStateInteractor.bluetoothStateUpdate
                     .filterNotNull()
                     .onEach {
@@ -119,39 +154,21 @@
                     }
                     .launchIn(this)
 
-                deviceItemInteractor.deviceItemUpdateRequest
-                    .onEach {
-                        updateDeviceItemJob?.cancel()
-                        updateDeviceItemJob = launch {
-                            deviceItemInteractor.updateDeviceItems(
-                                context,
-                                DeviceFetchTrigger.BLUETOOTH_CALLBACK_RECEIVED
-                            )
-                        }
-                    }
-                    .launchIn(this)
-
-                deviceItemInteractor.deviceItemUpdate
-                    .onEach {
-                        updateDialogUiJob?.cancel()
-                        updateDialogUiJob = launch {
-                            dialog.onDeviceItemUpdated(
-                                it.take(MAX_DEVICE_ITEM_ENTRY),
-                                showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
-                                showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled
-                            )
-                        }
-                    }
-                    .launchIn(this)
-
+                // bluetoothStateToggle is emitted when user toggles the bluetooth state switch,
+                // send the new value to the bluetoothStateInteractor and animate the progress bar.
                 dialog.bluetoothStateToggle
-                    .onEach { bluetoothStateInteractor.isBluetoothEnabled = it }
+                    .onEach {
+                        dialog.animateProgressBar(true)
+                        bluetoothStateInteractor.isBluetoothEnabled = it
+                    }
                     .launchIn(this)
 
+                // deviceItemClick is emitted when user clicked on a device item.
                 dialog.deviceItemClick
                     .onEach { deviceItemInteractor.updateDeviceItemOnClick(it) }
                     .launchIn(this)
 
+                // contentHeight is emitted when the dialog is dismissed.
                 dialog.contentHeight
                     .onEach {
                         withContext(backgroundDispatcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt
index afca57c..0ad520b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt
@@ -18,9 +18,7 @@
 
 import android.content.Intent
 import android.provider.AlarmClock
-import com.android.internal.jank.InteractionJankMonitor
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
 import com.android.systemui.qs.tiles.base.interactor.QSTileInput
 import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel
@@ -31,34 +29,20 @@
 class AlarmTileUserActionInteractor
 @Inject
 constructor(
-    private val activityStarter: ActivityStarter,
+    private val inputHandler: QSTileIntentUserInputHandler,
 ) : QSTileUserActionInteractor<AlarmTileModel> {
     override suspend fun handleInput(input: QSTileInput<AlarmTileModel>): Unit =
         with(input) {
             when (action) {
                 is QSTileUserAction.Click -> {
-                    val animationController =
-                        action.view?.let {
-                            ActivityLaunchAnimator.Controller.fromView(
-                                it,
-                                InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
-                            )
-                        }
                     if (
                         data is AlarmTileModel.NextAlarmSet &&
                             data.alarmClockInfo.showIntent != null
                     ) {
                         val pendingIndent = data.alarmClockInfo.showIntent
-                        activityStarter.postStartActivityDismissingKeyguard(
-                            pendingIndent,
-                            animationController
-                        )
+                        inputHandler.handle(action.view, pendingIndent, true)
                     } else {
-                        activityStarter.postStartActivityDismissingKeyguard(
-                            Intent(AlarmClock.ACTION_SHOW_ALARMS),
-                            0,
-                            animationController
-                        )
+                        inputHandler.handle(action.view, Intent(AlarmClock.ACTION_SHOW_ALARMS))
                     }
                 }
                 is QSTileUserAction.LongClick -> {}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index ce840ee..0d43396 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -17,10 +17,13 @@
 package com.android.systemui.qs.ui.adapter
 
 import android.content.Context
+import android.content.pm.ActivityInfo
 import android.os.Bundle
 import android.view.View
 import androidx.annotation.VisibleForTesting
 import androidx.asynclayoutinflater.view.AsyncLayoutInflater
+import com.android.settingslib.applications.InterestingConfigChanges
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
@@ -58,7 +61,7 @@
 
     /**
      * Inflate an instance of [QSImpl] for this context. Once inflated, it will be available in
-     * [qsView]
+     * [qsView]. Re-inflations due to configuration changes will use the last used [context].
      */
     suspend fun inflate(context: Context)
 
@@ -90,6 +93,7 @@
     private val qsImplProvider: Provider<QSImpl>,
     @Main private val mainDispatcher: CoroutineDispatcher,
     @Application applicationScope: CoroutineScope,
+    private val configurationInteractor: ConfigurationInteractor,
     private val asyncLayoutInflaterFactory: (Context) -> AsyncLayoutInflater,
 ) : QSContainerController, QSSceneAdapter {
 
@@ -99,7 +103,15 @@
         qsImplProvider: Provider<QSImpl>,
         @Main dispatcher: CoroutineDispatcher,
         @Application scope: CoroutineScope,
-    ) : this(qsSceneComponentFactory, qsImplProvider, dispatcher, scope, ::AsyncLayoutInflater)
+        configurationInteractor: ConfigurationInteractor,
+    ) : this(
+        qsSceneComponentFactory,
+        qsImplProvider,
+        dispatcher,
+        scope,
+        configurationInteractor,
+        ::AsyncLayoutInflater,
+    )
 
     private val state = MutableStateFlow<QSSceneAdapter.State>(QSSceneAdapter.State.CLOSED)
     private val _isCustomizing: MutableStateFlow<Boolean> = MutableStateFlow(false)
@@ -109,14 +121,36 @@
     val qsImpl = _qsImpl.asStateFlow()
     override val qsView: Flow<View> = _qsImpl.map { it?.view }.filterNotNull()
 
+    // Same config changes as in FragmentHostManager
+    private val interestingChanges =
+        InterestingConfigChanges(
+            ActivityInfo.CONFIG_FONT_SCALE or
+                ActivityInfo.CONFIG_LOCALE or
+                ActivityInfo.CONFIG_ASSETS_PATHS
+        )
+
     init {
         applicationScope.launch {
-            state.sample(_isCustomizing, ::Pair).collect { (state, customizing) ->
-                _qsImpl.value?.apply {
-                    if (state != QSSceneAdapter.State.QS && customizing) {
-                        this@apply.closeCustomizerImmediately()
+            launch {
+                state.sample(_isCustomizing, ::Pair).collect { (state, customizing) ->
+                    _qsImpl.value?.apply {
+                        if (state != QSSceneAdapter.State.QS && customizing) {
+                            this@apply.closeCustomizerImmediately()
+                        }
+                        applyState(state)
                     }
-                    applyState(state)
+                }
+            }
+            launch {
+                configurationInteractor.configurationValues.collect { config ->
+                    if (interestingChanges.applyNewConfig(config)) {
+                        // Assumption: The context is always the same and with the same theme.
+                        // If colors change they will be reflected as attributes in the theme.
+                        qsImpl.value?.view?.let { inflate(it.context) }
+                    } else {
+                        qsImpl.value?.onConfigurationChanged(config)
+                        qsImpl.value?.view?.dispatchConfigurationChanged(config)
+                    }
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index e5e1e84..8a900ece 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -16,7 +16,10 @@
 
 package com.android.systemui.qs.ui.viewmodel
 
+import androidx.lifecycle.LifecycleOwner
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
@@ -24,6 +27,7 @@
 import com.android.systemui.scene.shared.model.UserAction
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+import java.util.concurrent.atomic.AtomicBoolean
 import javax.inject.Inject
 import kotlinx.coroutines.flow.map
 
@@ -35,6 +39,8 @@
     val shadeHeaderViewModel: ShadeHeaderViewModel,
     val qsSceneAdapter: QSSceneAdapter,
     val notifications: NotificationsPlaceholderViewModel,
+    private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
+    private val footerActionsController: FooterActionsController,
 ) {
     val destinationScenes =
         qsSceneAdapter.isCustomizing.map { customizing ->
@@ -47,4 +53,13 @@
                 )
             }
         }
+
+    private val footerActionsControllerInitialized = AtomicBoolean(false)
+
+    fun getFooterActionsViewModel(lifecycleOwner: LifecycleOwner): FooterActionsViewModel {
+        if (footerActionsControllerInitialized.compareAndSet(false, true)) {
+            footerActionsController.init()
+        }
+        return footerActionsViewModelFactory.create(lifecycleOwner)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 45917e8..4e89fbf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.recents;
 
+import static android.app.Flags.keyguardPrivateNotifications;
 import static android.content.Intent.ACTION_PACKAGE_ADDED;
 import static android.content.Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
@@ -81,12 +82,13 @@
 import com.android.internal.util.ScreenshotHelper;
 import com.android.internal.util.ScreenshotRequest;
 import com.android.systemui.Dumpable;
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.KeyguardWmStateRefactor;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager;
 import com.android.systemui.model.SysUiState;
@@ -167,14 +169,16 @@
     private final Optional<UnfoldTransitionProgressForwarder> mUnfoldTransitionProgressForwarder;
     private final UiEventLogger mUiEventLogger;
     private final DisplayTracker mDisplayTracker;
-
     private Region mActiveNavBarRegion;
-    private SurfaceControl mNavigationBarSurface;
+
+    private final BroadcastDispatcher mBroadcastDispatcher;
 
     private IOverviewProxy mOverviewProxy;
     private int mConnectionBackoffAttempts;
     private boolean mBound;
     private boolean mIsEnabled;
+
+    private boolean mIsNonPrimaryUser;
     private int mCurrentBoundedUserId = -1;
     private boolean mInputFocusTransferStarted;
     private float mInputFocusTransferStartY;
@@ -420,6 +424,21 @@
         retryConnectionWithBackoff();
     };
 
+    private final BroadcastReceiver mUserEventReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Objects.equals(intent.getAction(), Intent.ACTION_USER_UNLOCKED)) {
+                if (keyguardPrivateNotifications()) {
+                    // Start the overview connection to the launcher service
+                    // Connect if user hasn't connected yet
+                    if (getProxy() == null) {
+                        startConnectionToCurrentUser();
+                    }
+                }
+            }
+        }
+    };
+
     private final BroadcastReceiver mLauncherStateChangedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -488,7 +507,6 @@
                 Log.e(TAG_OPS, "Failed to call onInitialize()", e);
             }
             dispatchNavButtonBounds();
-            dispatchNavigationBarSurface();
 
             // Force-update the systemui state flags
             updateSystemUiStateFlags();
@@ -588,11 +606,13 @@
             FeatureFlags featureFlags,
             SceneContainerFlags sceneContainerFlags,
             DumpManager dumpManager,
-            Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder
+            Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder,
+            BroadcastDispatcher broadcastDispatcher
     ) {
         // b/241601880: This component shouldn't be running for a non-primary user
-        if (!Process.myUserHandle().equals(UserHandle.SYSTEM)) {
-            Log.e(TAG_OPS, "Unexpected initialization for non-primary user", new Throwable());
+        mIsNonPrimaryUser = !Process.myUserHandle().equals(UserHandle.SYSTEM);
+        if (mIsNonPrimaryUser) {
+            Log.wtf(TAG_OPS, "Unexpected initialization for non-primary user", new Throwable());
         }
 
         mContext = context;
@@ -617,8 +637,9 @@
         mUiEventLogger = uiEventLogger;
         mDisplayTracker = displayTracker;
         mUnfoldTransitionProgressForwarder = unfoldTransitionProgressForwarder;
+        mBroadcastDispatcher = broadcastDispatcher;
 
-        if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+        if (!KeyguardWmStateRefactor.isEnabled()) {
             mSysuiUnlockAnimationController = sysuiUnlockAnimationController;
         } else {
             mSysuiUnlockAnimationController = inWindowLauncherUnlockAnimationManager;
@@ -637,6 +658,12 @@
         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
         mContext.registerReceiver(mLauncherStateChangedReceiver, filter);
 
+        if (keyguardPrivateNotifications()) {
+            mBroadcastDispatcher.registerReceiver(mUserEventReceiver,
+                    new IntentFilter(Intent.ACTION_USER_UNLOCKED),
+                    null /* executor */, UserHandle.ALL);
+        }
+
         // Listen for status bar state changes
         statusBarWinController.registerCallback(mStatusBarWindowCallback);
         mScreenshotHelper = new ScreenshotHelper(context);
@@ -679,28 +706,6 @@
                 .commitUpdate(mContext.getDisplayId());
     }
 
-    /**
-     * Called when the navigation bar surface is created or changed
-     */
-    public void onNavigationBarSurfaceChanged(SurfaceControl navbarSurface) {
-        mNavigationBarSurface = navbarSurface;
-        dispatchNavigationBarSurface();
-    }
-
-    private void dispatchNavigationBarSurface() {
-        try {
-            if (mOverviewProxy != null) {
-                // Catch all for cases where the surface is no longer valid
-                if (mNavigationBarSurface != null && !mNavigationBarSurface.isValid()) {
-                    mNavigationBarSurface = null;
-                }
-                mOverviewProxy.onNavigationBarSurface(mNavigationBarSurface);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG_OPS, "Failed to notify back action", e);
-        }
-    }
-
     private void updateEnabledAndBinding() {
         updateEnabledState();
         startConnectionToCurrentUser();
@@ -796,6 +801,13 @@
     }
 
     private void internalConnectToCurrentUser(String reason) {
+        if (mIsNonPrimaryUser) {
+            // This should not happen, but if any per-user SysUI component has a dependency on OPS,
+            // then this could get triggered
+            Log.w(TAG_OPS, "Skipping connection to overview service due to non-primary user "
+                    + "caller");
+            return;
+        }
         disconnectFromLauncherService(reason);
 
         // If user has not setup yet or already connected, do not try to connect
@@ -1037,6 +1049,19 @@
         }
     }
 
+    public void onNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
+        try {
+            if (mOverviewProxy != null) {
+                mOverviewProxy.onNavigationBarLumaSamplingEnabled(displayId, enable);
+            } else {
+                Log.e(TAG_OPS, "Failed to get overview proxy to enable/disable nav bar luma"
+                        + "sampling");
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG_OPS, "Failed to call onNavigationBarLumaSamplingEnabled()", e);
+        }
+    }
+
     private void updateEnabledState() {
         final int currentUser = mUserTracker.getUserId();
         mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent,
@@ -1062,7 +1087,6 @@
         pw.print("  mInputFocusTransferStartY="); pw.println(mInputFocusTransferStartY);
         pw.print("  mInputFocusTransferStartMillis="); pw.println(mInputFocusTransferStartMillis);
         pw.print("  mActiveNavBarRegion="); pw.println(mActiveNavBarRegion);
-        pw.print("  mNavigationBarSurface="); pw.println(mNavigationBarSurface);
         pw.print("  mNavBarMode="); pw.println(mNavBarMode);
         mSysUiState.dump(pw, args);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 0abde4d..b9e9fe7 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.data.repository.SceneContainerRepository
 import com.android.systemui.scene.shared.logger.SceneLogger
@@ -50,6 +51,7 @@
     private val repository: SceneContainerRepository,
     private val powerInteractor: PowerInteractor,
     private val logger: SceneLogger,
+    private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
 ) {
 
     /**
@@ -169,28 +171,6 @@
         return repository.setVisible(isVisible)
     }
 
-    /** True if there is a transition happening from and to the specified scenes. */
-    fun transitioning(from: SceneKey, to: SceneKey): StateFlow<Boolean> {
-        fun transitioning(
-            state: ObservableTransitionState,
-            from: SceneKey,
-            to: SceneKey,
-        ): Boolean {
-            return (state as? ObservableTransitionState.Transition)?.let {
-                it.fromScene == from && it.toScene == to
-            }
-                ?: false
-        }
-
-        return transitionState
-            .map { state -> transitioning(state, from, to) }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = transitioning(transitionState.value, from, to),
-            )
-    }
-
     /**
      * Binds the given flow so the system remembers it.
      *
@@ -222,6 +202,11 @@
         loggingReason: String,
         log: (from: SceneKey, to: SceneKey, loggingReason: String) -> Unit,
     ) {
+        check(scene.key != SceneKey.Gone || deviceUnlockedInteractor.isDeviceUnlocked.value) {
+            "Cannot change to the Gone scene while the device is locked. Logging reason for scene" +
+                " change was: $loggingReason"
+        }
+
         val currentSceneKey = desiredScene.value.key
         if (currentSceneKey == scene.key) {
             return
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
index a6cccf1..1c37908 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
@@ -23,14 +23,22 @@
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.init.NotificationsController
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import javax.inject.Inject
+import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
@@ -44,6 +52,9 @@
     private val keyguardRepository: KeyguardRepository,
     private val headsUpManager: HeadsUpManager,
     private val powerInteractor: PowerInteractor,
+    private val activeNotificationsInteractor: ActiveNotificationsInteractor,
+    sceneContainerFlags: SceneContainerFlags,
+    sceneInteractorProvider: Provider<SceneInteractor>,
 ) : CoreStartable {
 
     private var notificationPresenter: NotificationPresenter? = null
@@ -55,11 +66,28 @@
     /**
      * True if lockscreen (including AOD) or the shade is visible and false otherwise. Notably,
      * false if the bouncer is visible.
-     *
-     * TODO(b/297080059): Use [SceneInteractor] as the source of truth if the scene flag is on.
      */
     val isLockscreenOrShadeVisible: StateFlow<Boolean> =
-        windowRootViewVisibilityRepository.isLockscreenOrShadeVisible
+        if (!sceneContainerFlags.isEnabled()) {
+            windowRootViewVisibilityRepository.isLockscreenOrShadeVisible
+        } else {
+            sceneInteractorProvider
+                .get()
+                .transitionState
+                .map { state ->
+                    when (state) {
+                        is ObservableTransitionState.Idle ->
+                            state.scene == SceneKey.Shade || state.scene == SceneKey.Lockscreen
+                        is ObservableTransitionState.Transition ->
+                            state.toScene == SceneKey.Shade ||
+                                state.toScene == SceneKey.Lockscreen ||
+                                state.fromScene == SceneKey.Shade ||
+                                state.fromScene == SceneKey.Lockscreen
+                    }
+                }
+                .distinctUntilChanged()
+                .stateIn(scope, SharingStarted.Eagerly, false)
+        }
 
     /**
      * True if lockscreen (including AOD) or the shade is visible **and** the user is currently
@@ -117,6 +145,14 @@
         return if (headsUpManager.hasPinnedHeadsUp() && isNotifPresenterFullyCollapsed) {
             1
         } else {
+            getActiveNotificationsCount()
+        }
+    }
+
+    private fun getActiveNotificationsCount(): Int {
+        return if (NotificationsLiveDataStoreRefactor.isEnabled) {
+            activeNotificationsInteractor.allNotificationsCountValue
+        } else {
             notificationsController?.getActiveNotificationsCount() ?: 0
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index c96651c..246ccb1 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.dagger.qualifiers.DisplayId
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.model.SceneContainerPlugin
 import com.android.systemui.model.SysUiState
 import com.android.systemui.model.updateFlags
 import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -39,11 +40,6 @@
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
-import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING
-import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
-import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE
-import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED
-import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
 import com.android.systemui.util.asIndenting
@@ -291,12 +287,10 @@
                 .collect { sceneKey ->
                     sysUiState.updateFlags(
                         displayId,
-                        SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to (sceneKey != SceneKey.Gone),
-                        SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to (sceneKey == SceneKey.Shade),
-                        SYSUI_STATE_QUICK_SETTINGS_EXPANDED to (sceneKey == SceneKey.QuickSettings),
-                        SYSUI_STATE_BOUNCER_SHOWING to (sceneKey == SceneKey.Bouncer),
-                        SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to
-                            (sceneKey == SceneKey.Lockscreen),
+                        *SceneContainerPlugin.EvaluatorByFlag.map { (flag, evaluator) ->
+                                flag to evaluator.invoke(sceneKey)
+                            }
+                            .toTypedArray(),
                     )
                 }
         }
@@ -313,7 +307,7 @@
         }
 
         applicationScope.launch {
-            keyguardInteractor.isDozing.distinctUntilChanged().collect { isDozing ->
+            keyguardInteractor.isDozing.collect { isDozing ->
                 falsingCollector.setShowingAod(isDozing)
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index 8c3e4a5..a755805 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -37,6 +37,7 @@
     /** The flag description -- not an aconfig flag name */
     const val DESCRIPTION = "SceneContainerFlag"
 
+    @JvmStatic
     inline val isEnabled
         get() =
             SCENE_CONTAINER_ENABLED && // mainStaticFlag
@@ -44,6 +45,7 @@
                 keyguardBottomAreaRefactor() &&
                 KeyguardShadeMigrationNssl.isEnabled &&
                 MediaInSceneContainerFlag.isEnabled &&
+                // NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer
                 ComposeFacade.isComposeAvailable()
 
     /**
@@ -63,6 +65,7 @@
             FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor()),
             KeyguardShadeMigrationNssl.token,
             MediaInSceneContainerFlag.token,
+            // NOTE: Changes should also be made in isEnabled and @EnableSceneContainer
         )
 
     /** The full set of requirements for SceneContainer */
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index 4a839b8..93cfc5d 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -85,12 +85,13 @@
 
                     view.addView(
                         ComposeFacade.createSceneContainerView(
-                            scope = this,
-                            context = view.context,
-                            viewModel = viewModel,
-                            windowInsets = windowInsets,
-                            sceneByKey = sortedSceneByKey,
-                        )
+                                scope = this,
+                                context = view.context,
+                                viewModel = viewModel,
+                                windowInsets = windowInsets,
+                                sceneByKey = sortedSceneByKey,
+                            )
+                            .also { it.id = R.id.scene_container_root_composable }
                     )
 
                     val legacyView = view.requireViewById<View>(R.id.legacy_window_root)
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index bd43307..7aa0dad 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -32,6 +32,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -143,6 +144,7 @@
         channel.enableVibration(true);
         mNotificationManager.createNotificationChannel(channel);
 
+        int currentUid = Process.myUid();
         int currentUserId = mUserContextTracker.getUserContext().getUserId();
         UserHandle currentUser = new UserHandle(currentUserId);
         switch (action) {
@@ -166,7 +168,7 @@
                 mRecorder = new ScreenMediaRecorder(
                         mUserContextTracker.getUserContext(),
                         mMainHandler,
-                        currentUserId,
+                        currentUid,
                         mAudioSource,
                         captureTarget,
                         this
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index 3aab3bf..a170d0da 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -83,7 +83,7 @@
     private Surface mInputSurface;
     private VirtualDisplay mVirtualDisplay;
     private MediaRecorder mMediaRecorder;
-    private int mUser;
+    private int mUid;
     private ScreenRecordingMuxer mMuxer;
     private ScreenInternalAudioRecorder mAudio;
     private ScreenRecordingAudioSource mAudioSource;
@@ -94,12 +94,12 @@
     ScreenMediaRecorderListener mListener;
 
     public ScreenMediaRecorder(Context context, Handler handler,
-            int user, ScreenRecordingAudioSource audioSource,
+            int uid, ScreenRecordingAudioSource audioSource,
             MediaProjectionCaptureTarget captureRegion,
             ScreenMediaRecorderListener listener) {
         mContext = context;
         mHandler = handler;
-        mUser = user;
+        mUid = uid;
         mCaptureRegion = captureRegion;
         mListener = listener;
         mAudioSource = audioSource;
@@ -111,7 +111,7 @@
         IMediaProjectionManager mediaService =
                 IMediaProjectionManager.Stub.asInterface(b);
         IMediaProjection proj = null;
-        proj = mediaService.createProjection(mUser, mContext.getPackageName(),
+        proj = mediaService.createProjection(mUid, mContext.getPackageName(),
                     MediaProjectionManager.TYPE_SCREEN_CAPTURE, false);
         IMediaProjection projection = IMediaProjection.Stub.asInterface(proj.asBinder());
         if (mCaptureRegion != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt
index 68cc483..2ef27a8 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt
@@ -23,6 +23,7 @@
 import androidx.annotation.GuardedBy
 import androidx.annotation.VisibleForTesting
 import androidx.annotation.WorkerThread
+import com.android.app.tracing.traceSection
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.util.Assert
 import java.lang.ref.WeakReference
@@ -46,18 +47,30 @@
     val displayChangedListener: DisplayManager.DisplayListener =
         object : DisplayManager.DisplayListener {
             override fun onDisplayAdded(displayId: Int) {
-                val list = synchronized(displayCallbacks) { displayCallbacks.toList() }
-                onDisplayAdded(displayId, list)
+                traceSection(
+                    "DisplayTrackerImpl.displayChangedDisplayListener#onDisplayAdded",
+                ) {
+                    val list = synchronized(displayCallbacks) { displayCallbacks.toList() }
+                    onDisplayAdded(displayId, list)
+                }
             }
 
             override fun onDisplayRemoved(displayId: Int) {
-                val list = synchronized(displayCallbacks) { displayCallbacks.toList() }
-                onDisplayRemoved(displayId, list)
+                traceSection(
+                    "DisplayTrackerImpl.displayChangedDisplayListener#onDisplayRemoved",
+                ) {
+                    val list = synchronized(displayCallbacks) { displayCallbacks.toList() }
+                    onDisplayRemoved(displayId, list)
+                }
             }
 
             override fun onDisplayChanged(displayId: Int) {
-                val list = synchronized(displayCallbacks) { displayCallbacks.toList() }
-                onDisplayChanged(displayId, list)
+                traceSection(
+                    "DisplayTrackerImpl.displayChangedDisplayListener#onDisplayChanged",
+                ) {
+                    val list = synchronized(displayCallbacks) { displayCallbacks.toList() }
+                    onDisplayChanged(displayId, list)
+                }
             }
         }
 
@@ -69,8 +82,12 @@
             override fun onDisplayRemoved(displayId: Int) {}
 
             override fun onDisplayChanged(displayId: Int) {
-                val list = synchronized(brightnessCallbacks) { brightnessCallbacks.toList() }
-                onDisplayChanged(displayId, list)
+                traceSection(
+                    "DisplayTrackerImpl.displayBrightnessChangedDisplayListener#onDisplayChanged",
+                ) {
+                    val list = synchronized(brightnessCallbacks) { brightnessCallbacks.toList() }
+                    onDisplayChanged(displayId, list)
+                }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt
new file mode 100644
index 0000000..b09bfe2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.settings
+
+import android.annotation.UserIdInt
+import android.content.Context
+import android.content.SharedPreferences
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Extension functions for [UserFileManager]. */
+object UserFileManagerExt {
+
+    /** Returns a flow of [Unit] that is invoked each time the shared preference is updated. */
+    fun UserFileManager.observeSharedPreferences(
+        fileName: String,
+        @Context.PreferencesMode mode: Int,
+        @UserIdInt userId: Int
+    ): Flow<Unit> = conflatedCallbackFlow {
+        val sharedPrefs = getSharedPreferences(fileName, mode, userId)
+
+        val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> trySend(Unit) }
+
+        sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
+        awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt
new file mode 100644
index 0000000..f71a401d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.shade.TouchLogger.Companion.logTouchesTo
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import dagger.Lazy
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** A base class for non-empty implementations of ShadeController. */
+@OptIn(ExperimentalCoroutinesApi::class)
+abstract class BaseShadeControllerImpl(
+    private val touchLog: LogBuffer,
+    protected val commandQueue: CommandQueue,
+    protected val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+    protected val notificationShadeWindowController: NotificationShadeWindowController,
+    protected val assistManagerLazy: Lazy<AssistManager>
+) : ShadeController {
+    protected lateinit var notifPresenter: NotificationPresenter
+    /** Runnables to run after completing a collapse of the shade. */
+    private val postCollapseActions = ArrayList<Runnable>()
+
+    override fun start() {
+        logTouchesTo(touchLog)
+    }
+
+    final override fun animateExpandShade() {
+        if (isShadeEnabled) {
+            expandToNotifications()
+        }
+    }
+
+    /** Expand the shade with notifications visible. */
+    protected abstract fun expandToNotifications()
+
+    final override fun animateExpandQs() {
+        if (isShadeEnabled) {
+            expandToQs()
+        }
+    }
+
+    /** Expand the shade showing only quick settings. */
+    protected abstract fun expandToQs()
+
+    final override fun addPostCollapseAction(action: Runnable) {
+        postCollapseActions.add(action)
+    }
+
+    protected fun runPostCollapseActions() {
+        val clonedList: ArrayList<Runnable> = ArrayList(postCollapseActions)
+        postCollapseActions.clear()
+        for (r in clonedList) {
+            r.run()
+        }
+        statusBarKeyguardViewManager.readyForKeyguardDone()
+    }
+
+    final override fun onLaunchAnimationEnd(launchIsFullScreen: Boolean) {
+        if (!this.notifPresenter.isCollapsing()) {
+            onClosingFinished()
+        }
+        if (launchIsFullScreen) {
+            instantCollapseShade()
+        }
+    }
+    final override fun onLaunchAnimationCancelled(isLaunchForActivity: Boolean) {
+        if (
+            notifPresenter.isPresenterFullyCollapsed() &&
+                !notifPresenter.isCollapsing() &&
+                isLaunchForActivity
+        ) {
+            onClosingFinished()
+        } else {
+            collapseShade(true /* animate */)
+        }
+    }
+
+    protected fun onClosingFinished() {
+        runPostCollapseActions()
+        if (!this.notifPresenter.isPresenterFullyCollapsed()) {
+            // if we set it not to be focusable when collapsing, we have to undo it when we aborted
+            // the closing
+            notificationShadeWindowController.setNotificationShadeFocusable(true)
+        }
+    }
+
+    override fun setNotificationPresenter(presenter: NotificationPresenter) {
+        notifPresenter = presenter
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 3be60b7..1c7cc00 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -17,9 +17,12 @@
 package com.android.systemui.shade
 
 import android.content.Context
+import android.os.PowerManager
+import android.os.SystemClock
 import android.view.GestureDetector
 import android.view.MotionEvent
 import android.view.View
+import android.view.ViewGroup
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
@@ -31,6 +34,7 @@
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.util.kotlin.collectFlow
 import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
 
 /**
  * Controller that's responsible for the glanceable hub container view and its touch handling.
@@ -44,15 +48,31 @@
     private val communalViewModel: CommunalViewModel,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val shadeInteractor: ShadeInteractor,
+    private val powerManager: PowerManager,
 ) {
     /** The container view for the hub. This will not be initialized until [initView] is called. */
-    private lateinit var communalContainerView: View
+    private var communalContainerView: View? = null
 
     /**
      * The width of the area in which a right edge swipe can open the hub, in pixels. Read from
      * resources when [initView] is called.
      */
-    private var edgeSwipeRegionWidth: Int = 0
+    // TODO(b/320786721): support RTL layouts
+    private var rightEdgeSwipeRegionWidth: Int = 0
+
+    /**
+     * The height of the area in which a top edge swipe while the hub is open will not intercept
+     * touches, in pixels. This allows the top edge swipe to instead open the notification shade.
+     * Read from resources when [initView] is called.
+     */
+    private var topEdgeSwipeRegionWidth: Int = 0
+
+    /**
+     * The height of the area in which a bottom edge swipe while the hub is open will not intercept
+     * touches, in pixels. This allows the bottom edge swipe to instead open the bouncer. Read from
+     * resources when [initView] is called.
+     */
+    private var bottomEdgeSwipeRegionWidth: Int = 0
 
     /**
      * True if we are currently tracking a gesture for opening the hub that started in the edge
@@ -60,6 +80,9 @@
      */
     private var isTrackingOpenGesture = false
 
+    /** True if we are currently tracking a touch on the hub while it's open. */
+    private var isTrackingHubTouch = false
+
     /**
      * True if the hub UI is fully open, meaning it should receive touch input.
      *
@@ -87,6 +110,9 @@
         return communalInteractor.isCommunalEnabled && isComposeAvailable()
     }
 
+    /** Returns a {@link StateFlow} that tracks whether communal hub is available. */
+    fun communalAvailable(): Flow<Boolean> = communalInteractor.isCommunalAvailable
+
     /**
      * Creates the container view containing the glanceable hub UI.
      *
@@ -104,32 +130,44 @@
         if (!isEnabled()) {
             throw RuntimeException("Glanceable hub is not enabled")
         }
-        if (::communalContainerView.isInitialized) {
+        if (communalContainerView != null) {
             throw RuntimeException("Communal view has already been initialized")
         }
 
         communalContainerView = containerView
 
-        edgeSwipeRegionWidth =
-            communalContainerView.resources.getDimensionPixelSize(R.dimen.communal_grid_gutter_size)
+        rightEdgeSwipeRegionWidth =
+            containerView.resources.getDimensionPixelSize(
+                R.dimen.communal_right_edge_swipe_region_width
+            )
+        topEdgeSwipeRegionWidth =
+            containerView.resources.getDimensionPixelSize(
+                R.dimen.communal_top_edge_swipe_region_height
+            )
+        bottomEdgeSwipeRegionWidth =
+            containerView.resources.getDimensionPixelSize(
+                R.dimen.communal_bottom_edge_swipe_region_height
+            )
 
         collectFlow(
-            communalContainerView,
+            containerView,
             keyguardTransitionInteractor.isFinishedInStateWhere(KeyguardState::isBouncerState),
             { anyBouncerShowing = it }
         )
-        collectFlow(
-            communalContainerView,
-            communalInteractor.isCommunalShowing,
-            { hubShowing = it }
-        )
-        collectFlow(
-            communalContainerView,
-            shadeInteractor.isAnyFullyExpanded,
-            { shadeShowing = it }
-        )
+        collectFlow(containerView, communalInteractor.isCommunalShowing, { hubShowing = it })
+        collectFlow(containerView, shadeInteractor.isAnyFullyExpanded, { shadeShowing = it })
 
-        return communalContainerView
+        communalContainerView = containerView
+
+        return containerView
+    }
+
+    /** Removes the container view from its parent. */
+    fun disposeView() {
+        communalContainerView?.let {
+            (it.parent as ViewGroup).removeView(it)
+            communalContainerView = null
+        }
     }
 
     /**
@@ -142,10 +180,10 @@
      * to be fully in control of its own touch handling.
      */
     fun onTouchEvent(ev: MotionEvent): Boolean {
-        if (!::communalContainerView.isInitialized) {
-            return false
-        }
+        return communalContainerView?.let { handleTouchEventOnCommunalView(it, ev) } ?: false
+    }
 
+    private fun handleTouchEventOnCommunalView(view: View, ev: MotionEvent): Boolean {
         val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN
         val isUp = ev.actionMasked == MotionEvent.ACTION_UP
         val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL
@@ -154,28 +192,48 @@
         //  fully showing state
         val hubOccluded = anyBouncerShowing || shadeShowing
 
-        // If the hub is fully visible, send all touch events to it.
-        val communalVisible = hubShowing && !hubOccluded
-        if (communalVisible) {
-            communalContainerView.dispatchTouchEvent(ev)
+        // If the hub is fully visible, send all touch events to it, other than top and bottom edge
+        // swipes.
+        if (hubShowing && isDown) {
+            val y = ev.rawY
+            val topSwipe: Boolean = y <= topEdgeSwipeRegionWidth
+            val bottomSwipe = y >= view.height - bottomEdgeSwipeRegionWidth
+
+            if (topSwipe || bottomSwipe) {
+                // Don't intercept touches at the top/bottom edge so that swipes can open the
+                // notification shade and bouncer.
+                return false
+            }
+
+            if (!hubOccluded) {
+                isTrackingHubTouch = true
+                dispatchTouchEvent(view, ev)
+                // Return true regardless of dispatch result as some touches at the start of a
+                // gesture may return false from dispatchTouchEvent.
+                return true
+            }
+        } else if (isTrackingHubTouch) {
+            if (isUp || isCancel) {
+                isTrackingHubTouch = false
+            }
+            dispatchTouchEvent(view, ev)
             // Return true regardless of dispatch result as some touches at the start of a gesture
             // may return false from dispatchTouchEvent.
             return true
         }
 
-        if (edgeSwipeRegionWidth == 0) {
-            // If the edge region width has not been read yet or whatever reason, don't bother
+        if (rightEdgeSwipeRegionWidth == 0) {
+            // If the edge region width has not been read yet for whatever reason, don't bother
             // intercepting touches to open the hub.
             return false
         }
 
         if (!isTrackingOpenGesture && isDown) {
             val x = ev.rawX
-            val inOpeningSwipeRegion: Boolean =
-                x >= communalContainerView.width - edgeSwipeRegionWidth
+            val inOpeningSwipeRegion: Boolean = x >= view.width - rightEdgeSwipeRegionWidth
             if (inOpeningSwipeRegion && !hubOccluded) {
                 isTrackingOpenGesture = true
-                communalContainerView.dispatchTouchEvent(ev)
+                dispatchTouchEvent(view, ev)
                 // Return true regardless of dispatch result as some touches at the start of a
                 // gesture may return false from dispatchTouchEvent.
                 return true
@@ -184,7 +242,7 @@
             if (isUp || isCancel) {
                 isTrackingOpenGesture = false
             }
-            communalContainerView.dispatchTouchEvent(ev)
+            dispatchTouchEvent(view, ev)
             // Return true regardless of dispatch result as some touches at the start of a gesture
             // may return false from dispatchTouchEvent.
             return true
@@ -192,4 +250,17 @@
 
         return false
     }
+
+    /**
+     * Dispatches the touch event to the communal container and sends a user activity event to reset
+     * the screen timeout.
+     */
+    private fun dispatchTouchEvent(view: View, ev: MotionEvent) {
+        view.dispatchTouchEvent(ev)
+        powerManager.userActivity(
+            SystemClock.uptimeMillis(),
+            PowerManager.USER_ACTIVITY_EVENT_TOUCH,
+            0
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenHeaderHelper.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenHeaderHelper.kt
new file mode 100644
index 0000000..c74f038
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenHeaderHelper.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.shade
+
+import android.content.Context
+import com.android.internal.policy.SystemBarUtils
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlin.math.max
+
+class LargeScreenHeaderHelper @Inject constructor(private val context: Context) {
+
+    fun getLargeScreenHeaderHeight(): Int = getLargeScreenHeaderHeight(context)
+
+    companion object {
+        @JvmStatic
+        fun getLargeScreenHeaderHeight(context: Context): Int {
+            val defaultHeight =
+                context.resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height)
+            val statusBarHeight = SystemBarUtils.getStatusBarHeight(context)
+            // Height has to be at least as tall as the status bar, as the status bar height takes
+            // into account display cutouts.
+            return max(defaultHeight, statusBarHeight)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 6f4a1e7..09e4e75 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -27,6 +27,7 @@
 import static com.android.systemui.Flags.keyguardBottomAreaRefactor;
 import static com.android.systemui.Flags.migrateClocksToBlueprint;
 import static com.android.systemui.Flags.predictiveBackAnimateShade;
+import static com.android.systemui.Flags.smartspaceRelocateToBottom;
 import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
 import static com.android.systemui.classifier.Classifier.GENERIC;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
@@ -65,7 +66,6 @@
 import android.os.Handler;
 import android.os.Trace;
 import android.os.UserManager;
-import android.os.VibrationEffect;
 import android.provider.Settings;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
@@ -255,12 +255,8 @@
     private static final boolean DEBUG_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
     private static final boolean SPEW_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
     private static final boolean DEBUG_DRAWABLE = false;
-    private static final VibrationEffect ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT =
-            VibrationEffect.get(VibrationEffect.EFFECT_STRENGTH_MEDIUM, false);
     /** The parallax amount of the quick settings translation when dragging down the panel. */
     public static final float QS_PARALLAX_AMOUNT = 0.175f;
-    /** The delay to reset the hint text when the hint animation is finished running. */
-    private static final int HINT_RESET_DELAY_MS = 1200;
     private static final long ANIMATION_DELAY_ICON_FADE_IN =
             ActivityLaunchAnimator.TIMINGS.getTotalDuration()
                     - CollapsedStatusBarFragment.FADE_IN_DURATION
@@ -620,7 +616,7 @@
     private int mDreamingToLockscreenTransitionTranslationY;
     private int mLockscreenToDreamingTransitionTranslationY;
     private int mGoneToDreamingTransitionTranslationY;
-    private SplitShadeStateController mSplitShadeStateController;
+    private final SplitShadeStateController mSplitShadeStateController;
     private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */,
             mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */);
     private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable =
@@ -635,9 +631,6 @@
         }
     };
 
-    private final Consumer<Boolean> mMultiShadeExpansionConsumer =
-            (Boolean expanded) -> mIsAnyMultiShadeExpanded = expanded;
-
     private final Consumer<TransitionStep> mDreamingToLockscreenTransition =
             (TransitionStep step) -> {
                 mIsOcclusionTransitionRunning =
@@ -1214,7 +1207,7 @@
                 .setMaxLengthSeconds(0.4f).build();
         mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mView.getContext());
         mStatusBarHeaderHeightKeyguard = Utils.getStatusBarHeaderHeightKeyguard(mView.getContext());
-        mClockPositionAlgorithm.loadDimens(mResources);
+        mClockPositionAlgorithm.loadDimens(mView.getContext(), mResources);
         mIndicationBottomPadding = mResources.getDimensionPixelSize(
                 R.dimen.keyguard_indication_bottom_padding);
         int statusbarHeight = SystemBarUtils.getStatusBarHeight(mView.getContext());
@@ -1428,7 +1421,12 @@
             int index = mView.indexOfChild(mKeyguardBottomArea);
             mView.removeView(mKeyguardBottomArea);
             KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
-            setKeyguardBottomArea(mKeyguardBottomAreaViewControllerProvider.get().getView());
+            KeyguardBottomAreaViewController keyguardBottomAreaViewController =
+                    mKeyguardBottomAreaViewControllerProvider.get();
+            if (smartspaceRelocateToBottom()) {
+                keyguardBottomAreaViewController.init();
+            }
+            setKeyguardBottomArea(keyguardBottomAreaViewController.getView());
             mKeyguardBottomArea.initFrom(oldBottomArea);
             mView.addView(mKeyguardBottomArea, index);
 
@@ -1751,14 +1749,9 @@
         } else {
             layout = mNotificationContainerParent;
         }
-
-        if (migrateClocksToBlueprint()) {
-            mKeyguardInteractor.setClockShouldBeCentered(mSplitShadeEnabled && shouldBeCentered);
-        } else {
-            mKeyguardStatusViewController.updateAlignment(
-                    layout, mSplitShadeEnabled, shouldBeCentered, animate);
-            mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered));
-        }
+        mKeyguardStatusViewController.updateAlignment(
+                layout, mSplitShadeEnabled, shouldBeCentered, animate);
+        mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered));
     }
 
     private boolean shouldKeyguardStatusViewBeCentered() {
@@ -2882,7 +2875,9 @@
     private void onTrackingStarted() {
         endClosing();
         mShadeRepository.setLegacyShadeTracking(true);
-        mTrackingStartedListener.onTrackingStarted();
+        if (mTrackingStartedListener != null) {
+            mTrackingStartedListener.onTrackingStarted();
+        }
         notifyExpandingStarted();
         updateExpansionAndVisibility();
         mScrimController.onTrackingStarted();
@@ -3573,11 +3568,6 @@
     }
 
     @Override
-    public NotificationStackScrollLayoutController getNotificationStackScrollLayoutController() {
-        return mNotificationStackScrollLayoutController;
-    }
-
-    @Override
     public void disableHeader(int state1, int state2, boolean animated) {
         mShadeHeaderController.disable(state1, state2, animated);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 5fbb60d..0053474 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -50,6 +50,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -118,6 +119,7 @@
     private final Lazy<SelectedUserInteractor> mUserInteractor;
     private final Lazy<ShadeInteractor> mShadeInteractorLazy;
     private final SceneContainerFlags mSceneContainerFlags;
+    private final Lazy<CommunalInteractor> mCommunalInteractor;
     private ViewGroup mWindowRootView;
     private LayoutParams mLp;
     private boolean mHasTopUi;
@@ -165,7 +167,8 @@
             ShadeWindowLogger logger,
             Lazy<SelectedUserInteractor> userInteractor,
             UserTracker userTracker,
-            SceneContainerFlags sceneContainerFlags) {
+            SceneContainerFlags sceneContainerFlags,
+            Lazy<CommunalInteractor> communalInteractor) {
         mContext = context;
         mWindowRootViewComponentFactory = windowRootViewComponentFactory;
         mWindowManager = windowManager;
@@ -184,6 +187,7 @@
         mAuthController = authController;
         mUserInteractor = userInteractor;
         mSceneContainerFlags = sceneContainerFlags;
+        mCommunalInteractor = communalInteractor;
         mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed();
         mLockScreenDisplayTimeout = context.getResources()
                 .getInteger(R.integer.config_lockScreenDisplayTimeout);
@@ -325,6 +329,11 @@
                 mShadeInteractorLazy.get().isQsExpanded(),
                 this::onQsExpansionChanged
         );
+        collectFlow(
+                mWindowRootView,
+                mCommunalInteractor.get().isCommunalShowing(),
+                this::onCommunalShowingChanged
+        );
     }
 
     @Override
@@ -373,7 +382,9 @@
             boolean onKeyguard = state.statusBarState == StatusBarState.KEYGUARD
                     && !state.keyguardFadingAway && !state.keyguardGoingAway;
             if (onKeyguard
-                    && mAuthController.isUdfpsEnrolled(mUserInteractor.get().getSelectedUserId())) {
+                    && mAuthController.isOpticalUdfpsEnrolled(
+                            mUserInteractor.get().getSelectedUserId())
+            ) {
                 // Requests the max refresh rate (ie: for smooth display). Note: By setting
                 // the preferred refresh rates below, the refresh rate will not override the max
                 // refresh rate in settings (ie: if smooth display is OFF).
@@ -499,14 +510,21 @@
     }
 
     private void applyUserActivityTimeout(NotificationShadeWindowState state) {
-        if (state.isKeyguardShowingAndNotOccluded()
+        final Boolean communalShowing = state.isCommunalShowingAndNotOccluded();
+        final Boolean keyguardShowing = state.isKeyguardShowingAndNotOccluded();
+        long timeout = -1;
+        if ((communalShowing || keyguardShowing)
                 && state.statusBarState == StatusBarState.KEYGUARD
                 && !state.qsExpanded) {
-            mLpChanged.userActivityTimeout = state.bouncerShowing
-                    ? KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS : mLockScreenDisplayTimeout;
-        } else {
-            mLpChanged.userActivityTimeout = -1;
+            if (state.bouncerShowing) {
+                timeout = KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS;
+            } else if (communalShowing) {
+                timeout = CommunalInteractor.AWAKE_INTERVAL_MS;
+            } else if (keyguardShowing) {
+                timeout = mLockScreenDisplayTimeout;
+            }
         }
+        mLpChanged.userActivityTimeout = timeout;
     }
 
     private void applyInputFeatures(NotificationShadeWindowState state) {
@@ -605,7 +623,8 @@
                 state.forcePluginOpen,
                 state.dozing,
                 state.scrimsVisibility,
-                state.backgroundBlurRadius
+                state.backgroundBlurRadius,
+                state.communalShowing
         );
     }
 
@@ -729,6 +748,12 @@
         apply(mCurrentState);
     }
 
+    @VisibleForTesting
+    void onCommunalShowingChanged(Boolean showing) {
+        mCurrentState.communalShowing = showing;
+        apply(mCurrentState);
+    }
+
     @Override
     public void setForceUserActivity(boolean forceUserActivity) {
         mCurrentState.forceUserActivity = forceUserActivity;
@@ -892,6 +917,8 @@
         pw.println(TAG + ":");
         pw.println("  mKeyguardMaxRefreshRate=" + mKeyguardMaxRefreshRate);
         pw.println("  mKeyguardPreferredRefreshRate=" + mKeyguardPreferredRefreshRate);
+        pw.println("  preferredMinDisplayRefreshRate=" + mLpChanged.preferredMinDisplayRefreshRate);
+        pw.println("  preferredMaxDisplayRefreshRate=" + mLpChanged.preferredMaxDisplayRefreshRate);
         pw.println("  mDeferWindowLayoutParams=" + mDeferWindowLayoutParams);
         pw.println(mCurrentState);
         if (mWindowRootView != null && mWindowRootView.getViewRootImpl() != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index 0b20170..f9c9d83 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -58,12 +58,17 @@
     @JvmField var dreaming: Boolean = false,
     @JvmField var scrimsVisibility: Int = 0,
     @JvmField var backgroundBlurRadius: Int = 0,
+    @JvmField var communalShowing: Boolean = false,
 ) {
 
     fun isKeyguardShowingAndNotOccluded(): Boolean {
         return keyguardShowing && !keyguardOccluded
     }
 
+    fun isCommunalShowingAndNotOccluded(): Boolean {
+        return communalShowing && !keyguardOccluded
+    }
+
     /** List of [String] to be used as a [Row] with [DumpsysTableLogger]. */
     val asStringList: List<String> by lazy {
         listOf(
@@ -93,7 +98,8 @@
             forcePluginOpen.toString(),
             dozing.toString(),
             scrimsVisibility.toString(),
-            backgroundBlurRadius.toString()
+            backgroundBlurRadius.toString(),
+            communalShowing.toString(),
         )
     }
 
@@ -134,6 +140,7 @@
             dozing: Boolean,
             scrimsVisibility: Int,
             backgroundBlurRadius: Int,
+            communalShowing: Boolean,
         ) {
             buffer.advance().apply {
                 this.keyguardShowing = keyguardShowing
@@ -165,6 +172,7 @@
                 this.dozing = dozing
                 this.scrimsVisibility = scrimsVisibility
                 this.backgroundBlurRadius = backgroundBlurRadius
+                this.communalShowing = communalShowing
             }
         }
 
@@ -209,7 +217,8 @@
                 "forcePluginOpen",
                 "dozing",
                 "scrimsVisibility",
-                "backgroundBlurRadius"
+                "backgroundBlurRadius",
+                "communalShowing"
             )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index cde2a62..19a5840 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -31,16 +31,12 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.AuthKeyguardMessageArea;
-import com.android.keyguard.KeyguardMessageAreaController;
 import com.android.keyguard.LockIconViewController;
-import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.Dumpable;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
-import com.android.systemui.bouncer.ui.binder.KeyguardBouncerViewBinder;
-import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel;
+import com.android.systemui.bouncer.ui.binder.BouncerViewBinder;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
@@ -56,8 +52,6 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.keyguard.ui.binder.AlternateBouncerViewBinder;
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies;
-import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
-import com.android.systemui.log.BouncerLogger;
 import com.android.systemui.res.R;
 import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
 import com.android.systemui.statusbar.DragDownHelper;
@@ -77,7 +71,6 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.time.SystemClock;
 
@@ -183,24 +176,18 @@
             DumpManager dumpManager,
             PulsingGestureListener pulsingGestureListener,
             LockscreenHostedDreamGestureListener lockscreenHostedDreamGestureListener,
-            KeyguardBouncerViewModel keyguardBouncerViewModel,
-            KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory,
-            KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
-            PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
             GlanceableHubContainerController glanceableHubContainerController,
             NotificationLaunchAnimationInteractor notificationLaunchAnimationInteractor,
             FeatureFlagsClassic featureFlagsClassic,
             SystemClock clock,
-            BouncerMessageInteractor bouncerMessageInteractor,
-            BouncerLogger bouncerLogger,
             SysUIKeyEventHandler sysUIKeyEventHandler,
             QuickSettingsController quickSettingsController,
             PrimaryBouncerInteractor primaryBouncerInteractor,
             AlternateBouncerInteractor alternateBouncerInteractor,
-            SelectedUserInteractor selectedUserInteractor,
             Lazy<JavaAdapter> javaAdapter,
-            Lazy<AlternateBouncerDependencies> alternateBouncerDependencies) {
+            Lazy<AlternateBouncerDependencies> alternateBouncerDependencies,
+            BouncerViewBinder bouncerViewBinder) {
         mLockscreenShadeTransitionController = transitionController;
         mFalsingCollector = falsingCollector;
         mStatusBarStateController = statusBarStateController;
@@ -234,15 +221,7 @@
         // This view is not part of the newly inflated expanded status bar.
         mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
         mDisableSubpixelTextTransitionListener = new DisableSubpixelTextTransitionListener(mView);
-        KeyguardBouncerViewBinder.bind(
-                mView.findViewById(R.id.keyguard_bouncer_container),
-                keyguardBouncerViewModel,
-                primaryBouncerToGoneTransitionViewModel,
-                keyguardBouncerComponentFactory,
-                messageAreaControllerFactory,
-                bouncerMessageInteractor,
-                bouncerLogger,
-                selectedUserInteractor);
+        bouncerViewBinder.bind(mView.findViewById(R.id.keyguard_bouncer_container));
 
         if (DeviceEntryUdfpsRefactor.isEnabled()) {
             AlternateBouncerViewBinder.bind(
@@ -293,6 +272,14 @@
         return result;
     }
 
+    /**
+     * Handle a touch event while dreaming by forwarding the event to the content view.
+     * @param event The event to forward.
+     */
+    public void handleDreamTouch(MotionEvent event) {
+        mView.dispatchTouchEvent(event);
+    }
+
     /** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */
     public void setupExpandedStatusBar() {
         mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
@@ -618,16 +605,21 @@
      * The layout lives in {@link R.id.communal_ui_stub}.
      */
     public void setupCommunalHubLayout() {
-        if (!mGlanceableHubContainerController.isEnabled()) {
-            return;
-        }
-
-        // Replace the placeholder view with the communal UI.
-        View communalPlaceholder = mView.findViewById(R.id.communal_ui_stub);
-        int index = mView.indexOfChild(communalPlaceholder);
-        mView.removeView(communalPlaceholder);
-
-        mView.addView(mGlanceableHubContainerController.initView(mView.getContext()), index);
+        collectFlow(
+                mView,
+                mGlanceableHubContainerController.communalAvailable(),
+                isEnabled -> {
+                    if (isEnabled) {
+                        View communalPlaceholder = mView.findViewById(R.id.communal_ui_stub);
+                        int index = mView.indexOfChild(communalPlaceholder);
+                        mView.addView(
+                                mGlanceableHubContainerController.initView(mView.getContext()),
+                                index);
+                    } else {
+                        mGlanceableHubContainerController.disposeView();
+                    }
+                }
+        );
     }
 
     private boolean didNotificationPanelInterceptEvent(MotionEvent ev) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 9c8a286..84cad1d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -27,6 +27,7 @@
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import androidx.lifecycle.lifecycleScope
+import com.android.systemui.Flags.centralizedStatusBarDimensRefactor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
@@ -47,10 +48,11 @@
 import com.android.systemui.util.LargeScreenUtils
 import com.android.systemui.util.ViewController
 import com.android.systemui.util.concurrency.DelayableExecutor
-import kotlinx.coroutines.launch
+import dagger.Lazy
 import java.util.function.Consumer
 import javax.inject.Inject
 import kotlin.reflect.KMutableProperty0
+import kotlinx.coroutines.launch
 
 @VisibleForTesting
 internal const val INSET_DEBOUNCE_MILLIS = 500L
@@ -67,7 +69,8 @@
         private val featureFlags: FeatureFlags,
         private val
             notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
-        private val splitShadeStateController: SplitShadeStateController
+        private val splitShadeStateController: SplitShadeStateController,
+        private val largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
 ) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
 
     private var splitShadeEnabled = false
@@ -186,7 +189,11 @@
     }
 
     private fun calculateLargeShadeHeaderHeight(): Int {
-        return resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height)
+        return if (centralizedStatusBarDimensRefactor()) {
+            largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
+        } else {
+            resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height)
+        }
     }
 
     private fun calculateShadeHeaderHeight(): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 1dff99d..f3e9c75 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -20,6 +20,7 @@
 import static android.view.WindowInsets.Type.ime;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
+import static com.android.systemui.Flags.centralizedStatusBarDimensRefactor;
 import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
 import static com.android.systemui.shade.NotificationPanelViewController.COUNTER_PANEL_OPEN_QS;
 import static com.android.systemui.shade.NotificationPanelViewController.FLING_COLLAPSE;
@@ -126,6 +127,7 @@
     private final Lazy<NotificationPanelViewController> mPanelViewControllerLazy;
 
     private final NotificationPanelView mPanelView;
+    private final Lazy<LargeScreenHeaderHelper> mLargeScreenHeaderHelperLazy;
     private final KeyguardStatusBarView mKeyguardStatusBar;
     private final FrameLayout mQsFrame;
 
@@ -344,10 +346,12 @@
             ActiveNotificationsInteractor activeNotificationsInteractor,
             JavaAdapter javaAdapter,
             CastController castController,
-            SplitShadeStateController splitShadeStateController
+            SplitShadeStateController splitShadeStateController,
+            Lazy<LargeScreenHeaderHelper> largeScreenHeaderHelperLazy
     ) {
         mPanelViewControllerLazy = panelViewControllerLazy;
         mPanelView = panelView;
+        mLargeScreenHeaderHelperLazy = largeScreenHeaderHelperLazy;
         mQsFrame = mPanelView.findViewById(R.id.qs_frame);
         mKeyguardStatusBar = mPanelView.findViewById(R.id.keyguard_header);
         mResources = mPanelView.getResources();
@@ -449,7 +453,10 @@
         mUseLargeScreenShadeHeader =
                 LargeScreenUtils.shouldUseLargeScreenShadeHeader(mPanelView.getResources());
         mLargeScreenShadeHeaderHeight =
-                mResources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height);
+                centralizedStatusBarDimensRefactor()
+                        ? mLargeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
+                        : mResources.getDimensionPixelSize(
+                                R.dimen.large_screen_shade_header_height);
         int topMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight :
                 mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top);
         mShadeHeaderController.setLargeScreenActive(mUseLargeScreenShadeHeader);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index 2c4b0b9..ec4b23a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -33,10 +33,20 @@
  * {@link com.android.systemui.keyguard.KeyguardViewMediator} and others.
  */
 public interface ShadeController extends CoreStartable {
-    /** True if the shade UI is enabled on this particular Android variant and false otherwise. */
+    /**
+     * True if the shade UI is enabled on this particular Android variant and false otherwise.
+     *
+     * @deprecated use ShadeInteractor instead
+     */
+    @Deprecated
     boolean isShadeEnabled();
 
-    /** Make our window larger and the shade expanded */
+    /**
+     * Make our window larger and the shade expanded
+     *
+     * @deprecated will no longer be needed when keyguard is a sibling view to the shade
+     */
+    @Deprecated
     void instantExpandShade();
 
     /** Collapse the shade instantly with no animation. */
@@ -74,13 +84,28 @@
     /** Expand the shade with quick settings expanded with an animation. */
     void animateExpandQs();
 
-    /** Posts a request to collapse the shade. */
+    /**
+     * Posts a request to collapse the shade.
+     *
+     * @deprecated use #animateCollapseShade
+     */
+    @Deprecated
     void postAnimateCollapseShade();
 
-    /** Posts a request to force collapse the shade. */
+    /**
+     * Posts a request to force collapse the shade.
+     *
+     * @deprecated use #animateForceCollapseShade
+     */
+    @Deprecated
     void postAnimateForceCollapseShade();
 
-    /** Posts a request to expand the shade to quick settings. */
+    /**
+     * Posts a request to expand the shade to quick settings.
+     *
+     * @deprecated use #animateExpandQs
+     */
+    @Deprecated
     void postAnimateExpandQs();
 
     /** Cancels any ongoing expansion touch handling and collapses the shade. */
@@ -90,26 +115,29 @@
      * If the shade is not fully expanded, collapse it animated.
      *
      * @return Seems to always return false
+     * @deprecated use {@link #collapseShade()} instead
      */
+    @Deprecated
     boolean closeShadeIfOpen();
 
     /**
-     * Returns whether the shade state is the keyguard or not.
-     */
-    boolean isKeyguard();
-
-    /**
      * Returns whether the shade is currently open.
      * Even though in the current implementation shade is in expanded state on keyguard, this
      * method makes distinction between shade being truly open and plain keyguard state:
      * - if QS and notifications are visible on the screen, return true
      * - for any other state, including keyguard, return false
+     *
+     * @deprecated will be replaced by ShadeInteractor once scene container launches
      */
+    @Deprecated
     boolean isShadeFullyOpen();
 
     /**
      * Returns whether shade or QS are currently opening or collapsing.
+     *
+     * @deprecated will be replaced by ShadeInteractor once scene container launches
      */
+    @Deprecated
     boolean isExpandingOrCollapsing();
 
     /**
@@ -127,37 +155,67 @@
      */
     void addPostCollapseAction(Runnable action);
 
-    /** Run all of the runnables added by {@link #addPostCollapseAction}. */
-    void runPostCollapseRunnables();
-
     /**
      * Close the shade if it was open
      *
      * @return true if the shade was open, else false
      */
-    boolean collapseShade();
+    void collapseShade();
 
     /**
      * If animate is true, does the same as {@link #collapseShade()}. Otherwise, instantly collapse
      * the shade. Post collapse runnables will be executed
      *
      * @param animate true to animate the collapse, false for instantaneous collapse
+     * @deprecated call either #animateCollapseShade or #instantCollapseShade
      */
+    @Deprecated
     void collapseShade(boolean animate);
 
-    /** Calls #collapseShade if already on the main thread. If not, posts a call to it. */
+    /**
+     * Calls #collapseShade if already on the main thread. If not, posts a call to it.
+     * @deprecated call #collapseShade
+     */
+    @Deprecated
     void collapseOnMainThread();
 
-    /** Makes shade expanded but not visible. */
+    /**
+     *  If necessary, instantly collapses the shade for an activity start, otherwise runs the
+     *  post-collapse runnables. Instant collapse is ok here, because the purpose is to have the
+     *  shade collapsed when the user returns to SysUI from the launched activity.
+     */
+    void collapseShadeForActivityStart();
+
+    /**
+     * Makes shade expanded but not visible.
+     *
+     * @deprecated no longer needed once keyguard is a sibling view to the shade
+     */
+    @Deprecated
     void makeExpandedInvisible();
 
-    /** Makes shade expanded and visible. */
+    /**
+     * Makes shade expanded and visible.
+     *
+     * @deprecated no longer needed once keyguard is a sibling view to the shade
+     */
+    @Deprecated
     void makeExpandedVisible(boolean force);
 
-    /** Returns whether the shade is expanded and visible. */
+    /**
+     * Returns whether the shade is expanded and visible.
+     *
+     * @deprecated no longer needed once keyguard is a sibling view to the shade
+     */
+    @Deprecated
     boolean isExpandedVisible();
 
-    /** Handle status bar touch event. */
+    /**
+     * Handle status bar touch event.
+     *
+     * @deprecated only called by CentralSurfaces, which is being deleted
+     */
+    @Deprecated
     void onStatusBarTouch(MotionEvent event);
 
     /** Called when a launch animation was cancelled. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
index 82959ee..08a0c93 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
@@ -42,9 +42,6 @@
     override fun closeShadeIfOpen(): Boolean {
         return false
     }
-    override fun isKeyguard(): Boolean {
-        return false
-    }
     override fun isShadeFullyOpen(): Boolean {
         return false
     }
@@ -53,12 +50,10 @@
     }
     override fun postOnShadeExpanded(action: Runnable?) {}
     override fun addPostCollapseAction(action: Runnable?) {}
-    override fun runPostCollapseRunnables() {}
-    override fun collapseShade(): Boolean {
-        return false
-    }
+    override fun collapseShade() {}
     override fun collapseShade(animate: Boolean) {}
     override fun collapseOnMainThread() {}
+    override fun collapseShadeForActivityStart() {}
     override fun makeExpandedInvisible() {}
     override fun makeExpandedVisible(force: Boolean) {}
     override fun isExpandedVisible(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index fdc7eec..e8d9c35 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -33,7 +33,6 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -44,14 +43,13 @@
 
 import dagger.Lazy;
 
-import java.util.ArrayList;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
 /** An implementation of {@link ShadeController}. */
 @SysUISingleton
-public final class ShadeControllerImpl implements ShadeController {
+public final class ShadeControllerImpl extends BaseShadeControllerImpl {
 
     private static final String TAG = "ShadeControllerImpl";
     private static final boolean SPEW = false;
@@ -60,7 +58,6 @@
 
     private final CommandQueue mCommandQueue;
     private final Executor mMainExecutor;
-    private final LogBuffer mTouchLog;
     private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
     private final KeyguardStateController mKeyguardStateController;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
@@ -73,12 +70,9 @@
     private final Lazy<AssistManager> mAssistManagerLazy;
     private final Lazy<NotificationGutsManager> mGutsManager;
 
-    private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
-
     private boolean mExpandedVisible;
     private boolean mLockscreenOrShadeVisible;
 
-    private NotificationPresenter mPresenter;
     private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
     private ShadeVisibilityListener mShadeVisibilityListener;
 
@@ -99,9 +93,13 @@
             Lazy<AssistManager> assistManagerLazy,
             Lazy<NotificationGutsManager> gutsManager
     ) {
+        super(touchLog,
+                commandQueue,
+                statusBarKeyguardViewManager,
+                notificationShadeWindowController,
+                assistManagerLazy);
         mCommandQueue = commandQueue;
         mMainExecutor = mainExecutor;
-        mTouchLog = touchLog;
         mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
         mShadeViewControllerLazy = shadeViewControllerLazy;
         mStatusBarStateController = statusBarStateController;
@@ -117,7 +115,7 @@
 
     @Override
     public boolean isShadeEnabled() {
-        return true;
+        return mCommandQueue.panelsEnabled() && mDeviceProvisionedController.isCurrentUserSetup();
     }
 
     @Override
@@ -125,20 +123,16 @@
         // Make our window larger and the panel expanded.
         makeExpandedVisible(true /* force */);
         getShadeViewController().expand(false /* animate */);
-        mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
+        getCommandQueue().recomputeDisableFlags(mDisplayId, false /* animate */);
     }
 
     @Override
     public void animateCollapseShade(int flags, boolean force, boolean delayed,
             float speedUpFactor) {
         if (!force && mStatusBarStateController.getState() != StatusBarState.SHADE) {
-            runPostCollapseRunnables();
+            runPostCollapseActions();
             return;
         }
-        if (SPEW) {
-            Log.d(TAG,
-                    "animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags);
-        }
         if (getNotificationShadeWindowView() != null
                 && getShadeViewController().canBeCollapsed()
                 && (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) {
@@ -151,28 +145,19 @@
     }
 
     @Override
-    public void animateExpandShade() {
-        if (!mCommandQueue.panelsEnabled()) {
-            return;
-        }
+    protected void expandToNotifications() {
         getShadeViewController().expandToNotifications();
     }
 
     @Override
-    public void animateExpandQs() {
-        if (!mCommandQueue.panelsEnabled()) {
-            return;
-        }
-        // Settings are not available in setup
-        if (!mDeviceProvisionedController.isCurrentUserSetup()) return;
-
+    protected void expandToQs() {
         getShadeViewController().expandToQs();
     }
 
     @Override
     public boolean closeShadeIfOpen() {
         if (!getShadeViewController().isFullyCollapsed()) {
-            mCommandQueue.animateCollapsePanels(
+            getCommandQueue().animateCollapsePanels(
                     CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
             notifyVisibilityChanged(false);
             mAssistManagerLazy.get().hideAssist();
@@ -181,11 +166,6 @@
     }
 
     @Override
-    public boolean isKeyguard() {
-        return mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
-    }
-
-    @Override
     public boolean isShadeFullyOpen() {
         return getShadeViewController().isShadeFullyExpanded();
     }
@@ -224,46 +204,34 @@
     }
 
     @Override
-    public void addPostCollapseAction(Runnable action) {
-        mPostCollapseRunnables.add(action);
+    public void collapseShade() {
+        collapseShadeInternal();
     }
 
-    @Override
-    public void runPostCollapseRunnables() {
-        ArrayList<Runnable> clonedList = new ArrayList<>(mPostCollapseRunnables);
-        mPostCollapseRunnables.clear();
-        int size = clonedList.size();
-        for (int i = 0; i < size; i++) {
-            clonedList.get(i).run();
-        }
-        mStatusBarKeyguardViewManager.readyForKeyguardDone();
-    }
-
-    @Override
-    public boolean collapseShade() {
+    private boolean collapseShadeInternal() {
         if (!getShadeViewController().isFullyCollapsed()) {
             // close the shade if it was open
             animateCollapseShadeForcedDelayed();
             notifyVisibilityChanged(false);
-
             return true;
         } else {
             return false;
         }
     }
 
+
     @Override
     public void collapseShade(boolean animate) {
         if (animate) {
-            boolean willCollapse = collapseShade();
+            boolean willCollapse = collapseShadeInternal();
             if (!willCollapse) {
-                runPostCollapseRunnables();
+                runPostCollapseActions();
             }
-        } else if (!mPresenter.isPresenterFullyCollapsed()) {
+        } else if (!getNotifPresenter().isPresenterFullyCollapsed()) {
             instantCollapseShade();
             notifyVisibilityChanged(false);
         } else {
-            runPostCollapseRunnables();
+            runPostCollapseActions();
         }
     }
 
@@ -296,46 +264,16 @@
         }
     }
 
-    private void onClosingFinished() {
-        runPostCollapseRunnables();
-        if (!mPresenter.isPresenterFullyCollapsed()) {
-            // if we set it not to be focusable when collapsing, we have to undo it when we aborted
-            // the closing
-            mNotificationShadeWindowController.setNotificationShadeFocusable(true);
-        }
-    }
-
-    @Override
-    public void onLaunchAnimationCancelled(boolean isLaunchForActivity) {
-        if (mPresenter.isPresenterFullyCollapsed()
-                && !mPresenter.isCollapsing()
-                && isLaunchForActivity) {
-            onClosingFinished();
-        } else {
-            collapseShade(true /* animate */);
-        }
-    }
-
-    @Override
-    public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
-        if (!mPresenter.isCollapsing()) {
-            onClosingFinished();
-        }
-        if (launchIsFullScreen) {
-            instantCollapseShade();
-        }
-    }
-
     @Override
     public void instantCollapseShade() {
         getShadeViewController().instantCollapse();
-        runPostCollapseRunnables();
+        runPostCollapseActions();
     }
 
     @Override
     public void makeExpandedVisible(boolean force) {
         if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
-        if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
+        if (!force && (mExpandedVisible || !getCommandQueue().panelsEnabled())) {
             return;
         }
 
@@ -346,7 +284,7 @@
         mNotificationShadeWindowController.setPanelVisible(true);
 
         notifyVisibilityChanged(true);
-        mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */);
+        getCommandQueue().recomputeDisableFlags(mDisplayId, !force /* animate */);
         notifyExpandedVisibleChanged(true);
     }
 
@@ -377,9 +315,9 @@
                 -1 /* y */,
                 true /* resetMenu */);
 
-        runPostCollapseRunnables();
+        runPostCollapseActions();
         notifyExpandedVisibleChanged(false);
-        mCommandQueue.recomputeDisableFlags(
+        getCommandQueue().recomputeDisableFlags(
                 mDisplayId,
                 getShadeViewController().shouldHideStatusBarIconsWhenExpanded());
 
@@ -421,11 +359,6 @@
     }
 
     @Override
-    public void setNotificationPresenter(NotificationPresenter presenter) {
-        mPresenter = presenter;
-    }
-
-    @Override
     public void setNotificationShadeWindowViewController(
             NotificationShadeWindowViewController controller) {
         mNotificationShadeWindowViewController = controller;
@@ -441,8 +374,8 @@
 
     @Override
     public void start() {
-        TouchLogger.logTouchesTo(mTouchLog);
-        getShadeViewController().setTrackingStartedListener(this::runPostCollapseRunnables);
+        super.start();
+        getShadeViewController().setTrackingStartedListener(this::runPostCollapseActions);
         getShadeViewController().setOpenCloseListener(
                 new OpenCloseListener() {
                     @Override
@@ -456,4 +389,16 @@
                     }
                 });
     }
+
+    @Override
+    public void collapseShadeForActivityStart() {
+        if (isExpandedVisible() && !mStatusBarKeyguardViewManager.isBouncerShowing()) {
+            animateCollapseShadeForcedDelayed();
+        } else {
+            // Do it after DismissAction has been processed to conserve the
+            // needed ordering.
+            mMainExecutor.execute(this::runPostCollapseActions);
+        }
+    }
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
new file mode 100644
index 0000000..10b9db0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.view.MotionEvent
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.dagger.ShadeTouchLog
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.shade.ShadeController.ShadeVisibilityListener
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import dagger.Lazy
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+
+/**
+ * Implementation of ShadeController backed by scenes instead of NPVC.
+ *
+ * TODO(b/300258424) rename to ShadeControllerImpl and inline/delete all the deprecated methods
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class ShadeControllerSceneImpl
+@Inject
+constructor(
+    @Background private val scope: CoroutineScope,
+    private val shadeInteractor: ShadeInteractor,
+    private val sceneInteractor: SceneInteractor,
+    private val deviceEntryInteractor: DeviceEntryInteractor,
+    private val notificationStackScrollLayout: NotificationStackScrollLayout,
+    @ShadeTouchLog private val touchLog: LogBuffer,
+    commandQueue: CommandQueue,
+    statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+    notificationShadeWindowController: NotificationShadeWindowController,
+    assistManagerLazy: Lazy<AssistManager>,
+) :
+    BaseShadeControllerImpl(
+        touchLog,
+        commandQueue,
+        statusBarKeyguardViewManager,
+        notificationShadeWindowController,
+        assistManagerLazy,
+    ) {
+
+    init {
+        scope.launch {
+            shadeInteractor.isAnyExpanded.collect {
+                if (!it) {
+                    runPostCollapseActions()
+                }
+            }
+        }
+    }
+
+    override fun isShadeEnabled() = shadeInteractor.isShadeEnabled.value
+
+    override fun isShadeFullyOpen(): Boolean = shadeInteractor.isAnyFullyExpanded.value
+
+    override fun isExpandingOrCollapsing(): Boolean =
+        shadeInteractor.anyExpansion.value > 0f && shadeInteractor.anyExpansion.value < 1f
+
+    override fun instantExpandShade() {
+        // Do nothing
+    }
+
+    override fun instantCollapseShade() {
+        // TODO(b/315921512) add support for instant transition
+        sceneInteractor.changeScene(
+            SceneModel(getCollapseDestinationScene(), "instant"),
+            "hide shade"
+        )
+    }
+
+    override fun animateCollapseShade(
+        flags: Int,
+        force: Boolean,
+        delayed: Boolean,
+        speedUpFactor: Float
+    ) {
+        if (!force && !shadeInteractor.isAnyExpanded.value) {
+            runPostCollapseActions()
+            return
+        }
+        if (
+            shadeInteractor.isAnyExpanded.value &&
+                flags and CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL == 0
+        ) {
+            // release focus immediately to kick off focus change transition
+            notificationShadeWindowController.setNotificationShadeFocusable(false)
+            notificationStackScrollLayout.cancelExpandHelper()
+            sceneInteractor.changeScene(
+                SceneModel(SceneKey.Shade, null),
+                "ShadeController.animateExpandShade"
+            )
+            if (delayed) {
+                scope.launch {
+                    delay(125)
+                    animateCollapseShadeInternal()
+                }
+            } else {
+                animateCollapseShadeInternal()
+            }
+        }
+    }
+
+    private fun animateCollapseShadeInternal() {
+        sceneInteractor.changeScene(
+            SceneModel(getCollapseDestinationScene(), "ShadeController.animateCollapseShade"),
+            "ShadeController.animateCollapseShade"
+        )
+    }
+
+    private fun getCollapseDestinationScene(): SceneKey {
+        return if (deviceEntryInteractor.isDeviceEntered.value) {
+            SceneKey.Gone
+        } else {
+            SceneKey.Lockscreen
+        }
+    }
+
+    override fun cancelExpansionAndCollapseShade() {
+        // TODO do we need to actually cancel the touch session?
+        animateCollapseShade()
+    }
+
+    override fun closeShadeIfOpen(): Boolean {
+        if (shadeInteractor.isAnyExpanded.value) {
+            commandQueue.animateCollapsePanels(
+                CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+                true /* force */
+            )
+            assistManagerLazy.get().hideAssist()
+        }
+        return false
+    }
+
+    override fun collapseShade() {
+        animateCollapseShadeForcedDelayed()
+    }
+
+    override fun collapseShade(animate: Boolean) {
+        if (animate) {
+            animateCollapseShade()
+        } else {
+            instantCollapseShade()
+        }
+    }
+
+    override fun collapseOnMainThread() {
+        // TODO if this works with delegation alone, we can deprecate and delete
+        collapseShade()
+    }
+
+    override fun expandToNotifications() {
+        sceneInteractor.changeScene(
+            SceneModel(SceneKey.Shade, null),
+            "ShadeController.animateExpandShade"
+        )
+    }
+
+    override fun expandToQs() {
+        sceneInteractor.changeScene(
+            SceneModel(SceneKey.QuickSettings, null),
+            "ShadeController.animateExpandQs"
+        )
+    }
+
+    override fun setVisibilityListener(listener: ShadeVisibilityListener) {
+        scope.launch { sceneInteractor.isVisible.collect { listener.expandedVisibleChanged(it) } }
+    }
+
+    @ExperimentalCoroutinesApi
+    override fun collapseShadeForActivityStart() {
+        if (shadeInteractor.isAnyExpanded.value) {
+            animateCollapseShadeForcedDelayed()
+        } else {
+            runPostCollapseActions()
+        }
+    }
+
+    override fun postAnimateCollapseShade() {
+        animateCollapseShade()
+    }
+
+    override fun postAnimateForceCollapseShade() {
+        animateCollapseShadeForced()
+    }
+
+    override fun postAnimateExpandQs() {
+        expandToQs()
+    }
+
+    override fun postOnShadeExpanded(action: Runnable) {
+        // TODO verify that clicking "reply" in a work profile notification launches the app
+        // TODO verify that there's not a way to replace and deprecate this method
+        scope.launch {
+            shadeInteractor.isAnyFullyExpanded.first { it }
+            action.run()
+        }
+    }
+
+    override fun makeExpandedInvisible() {
+        // Do nothing
+    }
+
+    override fun makeExpandedVisible(force: Boolean) {
+        // Do nothing
+    }
+
+    override fun isExpandedVisible(): Boolean {
+        return sceneInteractor.desiredScene.value.key != SceneKey.Gone
+    }
+
+    override fun onStatusBarTouch(event: MotionEvent) {
+        // The only call to this doesn't happen with KeyguardShadeMigrationNssl enabled
+        throw UnsupportedOperationException()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 2460a33..a66bacd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -38,6 +38,7 @@
 import com.android.app.animation.Interpolators
 import com.android.settingslib.Utils
 import com.android.systemui.Dumpable
+import com.android.systemui.Flags.centralizedStatusBarDimensRefactor
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.battery.BatteryMeterViewController
@@ -432,6 +433,9 @@
             changes += combinedShadeHeadersConstraintManager.emptyCutoutConstraints()
         }
 
+        if (centralizedStatusBarDimensRefactor()) {
+            view.setPadding(view.paddingLeft, sbInsets.top, view.paddingRight, view.paddingBottom)
+        }
         view.updateAllConstraints(changes)
         updateBatteryMode()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index c057147..fc2c3ee 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -53,6 +53,20 @@
 
         @Provides
         @SysUISingleton
+        fun provideShadeController(
+            sceneContainerFlags: SceneContainerFlags,
+            sceneContainerOn: Provider<ShadeControllerSceneImpl>,
+            sceneContainerOff: Provider<ShadeControllerImpl>
+        ): ShadeController {
+            return if (sceneContainerFlags.isEnabled()) {
+                sceneContainerOn.get()
+            } else {
+                sceneContainerOff.get()
+            }
+        }
+
+        @Provides
+        @SysUISingleton
         fun provideShadeAnimationInteractor(
             sceneContainerFlags: SceneContainerFlags,
             sceneContainerOn: Provider<ShadeAnimationInteractorSceneContainerImpl>,
@@ -79,8 +93,4 @@
     abstract fun bindsShadeViewController(
         notificationPanelViewController: NotificationPanelViewController
     ): ShadeViewController
-
-    @Binds
-    @SysUISingleton
-    abstract fun bindsShadeController(shadeControllerImpl: ShadeControllerImpl): ShadeController
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index e54286f..4f970b3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -17,7 +17,6 @@
 
 import android.view.ViewPropertyAnimator
 import com.android.systemui.statusbar.GestureRecorder
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.policy.HeadsUpManager
 
@@ -44,9 +43,6 @@
     /** Animates the view from its current alpha to zero then runs the runnable. */
     fun fadeOut(startDelayMs: Long, durationMs: Long, endAction: Runnable): ViewPropertyAnimator
 
-    /** Returns the NSSL controller. */
-    val notificationStackScrollLayoutController: NotificationStackScrollLayoutController
-
     /** Set whether the bouncer is showing. */
     fun setBouncerShowing(bouncerShowing: Boolean)
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 31a4de4..43ede2a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -29,7 +29,7 @@
     val isShadeEnabled: StateFlow<Boolean>
 
     /** Whether either the shade or QS is fully expanded. */
-    val isAnyFullyExpanded: Flow<Boolean>
+    val isAnyFullyExpanded: StateFlow<Boolean>
 
     /** Whether the Shade is fully expanded. */
     val isShadeFullyExpanded: Flow<Boolean>
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index 6defbcf..55dd674 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -34,7 +34,7 @@
     override val isQsBypassingShade: Flow<Boolean> = inactiveFlowBoolean
     override val isQsFullscreen: Flow<Boolean> = inactiveFlowBoolean
     override val anyExpansion: StateFlow<Float> = inactiveFlowFloat
-    override val isAnyFullyExpanded: Flow<Boolean> = inactiveFlowBoolean
+    override val isAnyFullyExpanded: StateFlow<Boolean> = inactiveFlowBoolean
     override val isShadeFullyExpanded: Flow<Boolean> = inactiveFlowBoolean
     override val isAnyExpanded: StateFlow<Boolean> = inactiveFlowBoolean
     override val isUserInteractingWithShade: Flow<Boolean> = inactiveFlowBoolean
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index 7a340d2..a71cf95 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -25,8 +25,8 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
 import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
 import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
 import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -55,12 +55,20 @@
     private val baseShadeInteractor: BaseShadeInteractor,
 ) : ShadeInteractor, BaseShadeInteractor by baseShadeInteractor {
     override val isShadeEnabled: StateFlow<Boolean> =
-        disableFlagsRepository.disableFlags
-            .map { it.isShadeEnabled() }
+        combine(
+                deviceProvisioningRepository.isFactoryResetProtectionActive,
+                disableFlagsRepository.disableFlags,
+            ) { isFrpActive, isDisabledByFlags ->
+                isDisabledByFlags.isShadeEnabled() && !isFrpActive
+            }
+            .distinctUntilChanged()
             .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
 
-    override val isAnyFullyExpanded: Flow<Boolean> =
-        anyExpansion.map { it >= 1f }.distinctUntilChanged()
+    override val isAnyFullyExpanded: StateFlow<Boolean> =
+        anyExpansion
+            .map { it >= 1f }
+            .distinctUntilChanged()
+            .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
 
     override val isShadeFullyExpanded: Flow<Boolean> =
         baseShadeInteractor.shadeExpansion.map { it >= 1f }.distinctUntilChanged()
@@ -94,7 +102,7 @@
             disableFlagsRepository.disableFlags,
             isShadeEnabled,
             keyguardRepository.isDozing,
-            userSetupRepository.isUserSetupFlow,
+            userSetupRepository.isUserSetUp,
             deviceProvisioningRepository.isDeviceProvisioned,
         ) { disableFlags, isShadeEnabled, isDozing, isUserSetup, isDeviceProvisioned ->
             isDeviceProvisioned &&
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
index 3fd070c..08f2c40 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -109,7 +109,7 @@
      * Returns a flow that uses scene transition progress to and from a scene that is pulled down
      * from the top of the screen to a 0-1 expansion amount float.
      */
-    internal fun sceneBasedExpansion(sceneInteractor: SceneInteractor, sceneKey: SceneKey) =
+    fun sceneBasedExpansion(sceneInteractor: SceneInteractor, sceneKey: SceneKey) =
         sceneInteractor.transitionState
             .flatMapLatest { state ->
                 when (state) {
@@ -135,7 +135,7 @@
      * Returns a flow that uses scene transition data to determine whether the user is interacting
      * with a scene that is pulled down from the top of the screen.
      */
-    internal fun sceneBasedInteracting(sceneInteractor: SceneInteractor, sceneKey: SceneKey) =
+    fun sceneBasedInteracting(sceneInteractor: SceneInteractor, sceneKey: SceneKey) =
         sceneInteractor.transitionState
             .map { state ->
                 when (state) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index 51276c6..314637e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -22,12 +22,11 @@
 import android.icu.text.DateFormat
 import android.icu.text.DisplayContext
 import android.os.UserHandle
-import com.android.systemui.res.R
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
 import java.util.Date
@@ -38,7 +37,6 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
@@ -57,16 +55,6 @@
     val mobileIconsViewModel: MobileIconsViewModel,
     broadcastDispatcher: BroadcastDispatcher,
 ) {
-    /** True if we are transitioning between Shade and QuickSettings scenes, in either direction. */
-    val isTransitioning =
-        combine(
-                sceneInteractor.transitioning(from = SceneKey.Shade, to = SceneKey.QuickSettings),
-                sceneInteractor.transitioning(from = SceneKey.QuickSettings, to = SceneKey.Shade)
-            ) { shadeToQuickSettings, quickSettingsToShade ->
-                shadeToQuickSettings || quickSettingsToShade
-            }
-            .stateIn(applicationScope, SharingStarted.WhileSubscribed(), false)
-
     /** True if there is exactly one mobile connection. */
     val isSingleCarrier: StateFlow<Boolean> = mobileIconsInteractor.isSingleCarrier
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index d0da945..9af2d58 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -20,15 +20,10 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.media.controls.pipeline.MediaDataManager
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
-import com.android.systemui.media.controls.ui.MediaHost
-import com.android.systemui.media.controls.ui.MediaHostState
-import com.android.systemui.media.dagger.MediaModule
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import javax.inject.Inject
-import javax.inject.Named
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -46,7 +41,6 @@
     val shadeHeaderViewModel: ShadeHeaderViewModel,
     val notifications: NotificationsPlaceholderViewModel,
     val mediaDataManager: MediaDataManager,
-    @Named(MediaModule.QUICK_QS_PANEL) private val mediaHost: MediaHost,
 ) {
     /** The key of the scene we should switch to when swiping up. */
     val upDestinationSceneKey: StateFlow<SceneKey> =
@@ -83,12 +77,6 @@
         }
     }
 
-    init {
-        mediaHost.expansion = MediaHostState.EXPANDED
-        mediaHost.showsOnlyActiveMedia = true
-        mediaHost.init(MediaHierarchyManager.LOCATION_QQS)
-    }
-
     fun isMediaVisible(): Boolean {
         // TODO(b/296122467): handle updates to carousel visibility while scene is still visible
         return mediaDataManager.hasActiveMediaOrRecommendation()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
deleted file mode 100644
index cfbd015..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ /dev/null
@@ -1,409 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-import android.view.accessibility.AccessibilityEvent;
-
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
-import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.time.SystemClock;
-
-import java.util.stream.Stream;
-
-/**
- * A manager which contains notification alerting functionality, providing methods to add and
- * remove notifications that appear on screen for a period of time and dismiss themselves at the
- * appropriate time.  These include heads up notifications and ambient pulses.
- */
-public abstract class AlertingNotificationManager {
-    private static final String TAG = "AlertNotifManager";
-    protected final SystemClock mSystemClock;
-    protected final ArrayMap<String, AlertEntry> mAlertEntries = new ArrayMap<>();
-    protected final HeadsUpManagerLogger mLogger;
-
-    protected int mMinimumDisplayTime;
-    protected int mStickyForSomeTimeAutoDismissTime;
-    protected int mAutoDismissTime;
-    private DelayableExecutor mExecutor;
-
-    public AlertingNotificationManager(HeadsUpManagerLogger logger,
-            SystemClock systemClock, @Main DelayableExecutor executor) {
-        mLogger = logger;
-        mExecutor = executor;
-        mSystemClock = systemClock;
-    }
-
-    /**
-     * Called when posting a new notification that should alert the user and appear on screen.
-     * Adds the notification to be managed.
-     * @param entry entry to show
-     */
-    public void showNotification(@NonNull NotificationEntry entry) {
-        mLogger.logShowNotification(entry);
-        addAlertEntry(entry);
-        updateNotification(entry.getKey(), true /* alert */);
-        entry.setInterruption();
-    }
-
-    /**
-     * Try to remove the notification.  May not succeed if the notification has not been shown long
-     * enough and needs to be kept around.
-     * @param key the key of the notification to remove
-     * @param releaseImmediately force a remove regardless of earliest removal time
-     * @return true if notification is removed, false otherwise
-     */
-    public boolean removeNotification(@NonNull String key, boolean releaseImmediately) {
-        mLogger.logRemoveNotification(key, releaseImmediately);
-        AlertEntry alertEntry = mAlertEntries.get(key);
-        if (alertEntry == null) {
-            return true;
-        }
-        if (releaseImmediately || canRemoveImmediately(key)) {
-            removeAlertEntry(key);
-        } else {
-            alertEntry.removeAsSoonAsPossible();
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Called when the notification state has been updated.
-     * @param key the key of the entry that was updated
-     * @param alert whether the notification should alert again and force reevaluation of
-     *              removal time
-     */
-    public void updateNotification(@NonNull String key, boolean alert) {
-        AlertEntry alertEntry = mAlertEntries.get(key);
-        mLogger.logUpdateNotification(key, alert, alertEntry != null);
-        if (alertEntry == null) {
-            // the entry was released before this update (i.e by a listener) This can happen
-            // with the groupmanager
-            return;
-        }
-
-        alertEntry.mEntry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-        if (alert) {
-            alertEntry.updateEntry(true /* updatePostTime */, "updateNotification");
-        }
-    }
-
-    /**
-     * Clears all managed notifications.
-     */
-    public void releaseAllImmediately() {
-        mLogger.logReleaseAllImmediately();
-        // A copy is necessary here as we are changing the underlying map.  This would cause
-        // undefined behavior if we iterated over the key set directly.
-        ArraySet<String> keysToRemove = new ArraySet<>(mAlertEntries.keySet());
-        for (String key : keysToRemove) {
-            removeAlertEntry(key);
-        }
-    }
-
-    /**
-     * Returns the entry if it is managed by this manager.
-     * @param key key of notification
-     * @return the entry
-     */
-    @Nullable
-    public NotificationEntry getEntry(@NonNull String key) {
-        AlertEntry entry = mAlertEntries.get(key);
-        return entry != null ? entry.mEntry : null;
-    }
-
-    /**
-     * Returns the stream of all current notifications managed by this manager.
-     * @return all entries
-     */
-    @NonNull
-    public Stream<NotificationEntry> getAllEntries() {
-        return mAlertEntries.values().stream().map(headsUpEntry -> headsUpEntry.mEntry);
-    }
-
-    /**
-     * Whether or not there are any active alerting notifications.
-     * @return true if there is an alert, false otherwise
-     */
-    public boolean hasNotifications() {
-        return !mAlertEntries.isEmpty();
-    }
-
-    /**
-     * Whether or not the given notification is alerting and managed by this manager.
-     * @return true if the notification is alerting
-     */
-    public boolean isAlerting(@NonNull String key) {
-        return mAlertEntries.containsKey(key);
-    }
-
-    /**
-     * Gets the flag corresponding to the notification content view this alert manager will show.
-     *
-     * @return flag corresponding to the content view
-     */
-    public abstract @InflationFlag int getContentFlag();
-
-    /**
-     * Add a new entry and begin managing it.
-     * @param entry the entry to add
-     */
-    protected final void addAlertEntry(@NonNull NotificationEntry entry) {
-        AlertEntry alertEntry = createAlertEntry();
-        alertEntry.setEntry(entry);
-        mAlertEntries.put(entry.getKey(), alertEntry);
-        onAlertEntryAdded(alertEntry);
-        entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-        entry.setIsAlerting(true);
-    }
-
-    /**
-     * Manager-specific logic that should occur when an entry is added.
-     * @param alertEntry alert entry added
-     */
-    protected abstract void onAlertEntryAdded(@NonNull AlertEntry alertEntry);
-
-    /**
-     * Remove a notification and reset the alert entry.
-     * @param key key of notification to remove
-     */
-    protected final void removeAlertEntry(@NonNull String key) {
-        AlertEntry alertEntry = mAlertEntries.get(key);
-        if (alertEntry == null) {
-            return;
-        }
-        NotificationEntry entry = alertEntry.mEntry;
-
-        // If the notification is animating, we will remove it at the end of the animation.
-        if (entry != null && entry.isExpandAnimationRunning()) {
-            return;
-        }
-        entry.demoteStickyHun();
-        mAlertEntries.remove(key);
-        onAlertEntryRemoved(alertEntry);
-        entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-        alertEntry.reset();
-    }
-
-    /**
-     * Manager-specific logic that should occur when an alert entry is removed.
-     * @param alertEntry alert entry removed
-     */
-    protected abstract void onAlertEntryRemoved(@NonNull AlertEntry alertEntry);
-
-    /**
-     * Returns a new alert entry instance.
-     * @return a new AlertEntry
-     */
-    protected AlertEntry createAlertEntry() {
-        return new AlertEntry();
-    }
-
-    /**
-     * Whether or not the alert can be removed currently.  If it hasn't been on screen long enough
-     * it should not be removed unless forced
-     * @param key the key to check if removable
-     * @return true if the alert entry can be removed
-     */
-    public boolean canRemoveImmediately(String key) {
-        AlertEntry alertEntry = mAlertEntries.get(key);
-        return alertEntry == null || alertEntry.wasShownLongEnough()
-                || alertEntry.mEntry.isRowDismissed();
-    }
-
-    /**
-     * @param key
-     * @return true if the entry is (pinned and expanded) or (has an active remote input)
-     */
-    public boolean isSticky(String key) {
-        AlertEntry alerting = mAlertEntries.get(key);
-        if (alerting != null) {
-            return alerting.isSticky();
-        }
-        return false;
-    }
-
-    /**
-     * @param key
-     * @return When a HUN entry should be removed in milliseconds from now
-     */
-    public long getEarliestRemovalTime(String key) {
-        AlertEntry alerting = mAlertEntries.get(key);
-        if (alerting != null) {
-            return Math.max(0, alerting.mEarliestRemovalTime - mSystemClock.elapsedRealtime());
-        }
-        return 0;
-    }
-
-    protected class AlertEntry implements Comparable<AlertEntry> {
-        @Nullable public NotificationEntry mEntry;
-        public long mPostTime;
-        public long mEarliestRemovalTime;
-
-        @Nullable protected Runnable mRemoveAlertRunnable;
-        @Nullable private Runnable mCancelRemoveAlertRunnable;
-
-        public void setEntry(@NonNull final NotificationEntry entry) {
-            setEntry(entry, () -> removeAlertEntry(entry.getKey()));
-        }
-
-        public void setEntry(@NonNull final NotificationEntry entry,
-                @Nullable Runnable removeAlertRunnable) {
-            mEntry = entry;
-            mRemoveAlertRunnable = removeAlertRunnable;
-
-            mPostTime = calculatePostTime();
-            updateEntry(true /* updatePostTime */, "setEntry");
-        }
-
-        /**
-         * Updates an entry's removal time.
-         * @param updatePostTime whether or not to refresh the post time
-         */
-        public void updateEntry(boolean updatePostTime, @Nullable String reason) {
-            mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
-
-            final long now = mSystemClock.elapsedRealtime();
-            mEarliestRemovalTime = now + mMinimumDisplayTime;
-
-            if (updatePostTime) {
-                mPostTime = Math.max(mPostTime, now);
-            }
-
-            if (isSticky()) {
-                removeAutoRemovalCallbacks("updateEntry (sticky)");
-                return;
-            }
-
-            final long finishTime = calculateFinishTime();
-            final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
-            scheduleAutoRemovalCallback(timeLeft, "updateEntry (not sticky)");
-        }
-
-        /**
-         * Whether or not the notification is "sticky" i.e. should stay on screen regardless
-         * of the timer (forever) and should be removed externally.
-         * @return true if the notification is sticky
-         */
-        public boolean isSticky() {
-            // This implementation is overridden by HeadsUpManager HeadsUpEntry #isSticky
-            return false;
-        }
-
-        public boolean isStickyForSomeTime() {
-            // This implementation is overridden by HeadsUpManager HeadsUpEntry #isStickyForSomeTime
-            return false;
-        }
-
-        /**
-         * Whether the notification has befen on screen long enough and can be removed.
-         * @return true if the notification has been on screen long enough
-         */
-        public boolean wasShownLongEnough() {
-            return mEarliestRemovalTime < mSystemClock.elapsedRealtime();
-        }
-
-        @Override
-        public int compareTo(@NonNull AlertEntry alertEntry) {
-            return (mPostTime < alertEntry.mPostTime)
-                    ? 1 : ((mPostTime == alertEntry.mPostTime)
-                            ? mEntry.getKey().compareTo(alertEntry.mEntry.getKey()) : -1);
-        }
-
-        public void reset() {
-            removeAutoRemovalCallbacks("reset()");
-            mEntry = null;
-            mRemoveAlertRunnable = null;
-        }
-
-        /**
-         * Clear any pending removal runnables.
-         */
-        public void removeAutoRemovalCallbacks(@Nullable String reason) {
-            final boolean removed = removeAutoRemovalCallbackInternal();
-
-            if (removed) {
-                mLogger.logAutoRemoveCanceled(mEntry, reason);
-            }
-        }
-
-        private void scheduleAutoRemovalCallback(long delayMillis, @NonNull String reason) {
-            if (mRemoveAlertRunnable == null) {
-                Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
-                return;
-            }
-
-            final boolean removed = removeAutoRemovalCallbackInternal();
-
-            if (removed) {
-                mLogger.logAutoRemoveRescheduled(mEntry, delayMillis, reason);
-            } else {
-                mLogger.logAutoRemoveScheduled(mEntry, delayMillis, reason);
-            }
-
-
-            mCancelRemoveAlertRunnable = mExecutor.executeDelayed(mRemoveAlertRunnable,
-                    delayMillis);
-        }
-
-        private boolean removeAutoRemovalCallbackInternal() {
-            final boolean scheduled = (mCancelRemoveAlertRunnable != null);
-
-            if (scheduled) {
-                mCancelRemoveAlertRunnable.run();
-                mCancelRemoveAlertRunnable = null;
-            }
-
-            return scheduled;
-        }
-
-        /**
-         * Remove the alert at the earliest allowed removal time.
-         */
-        public void removeAsSoonAsPossible() {
-            if (mRemoveAlertRunnable != null) {
-                final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
-                scheduleAutoRemovalCallback(timeLeft, "removeAsSoonAsPossible");
-            }
-        }
-
-        /**
-         * Calculate what the post time of a notification is at some current time.
-         * @return the post time
-         */
-        protected long calculatePostTime() {
-            return mSystemClock.elapsedRealtime();
-        }
-
-        /**
-         * @return When the notification should auto-dismiss itself, based on
-         * {@link SystemClock#elapsedRealtime()}
-         */
-        protected long calculateFinishTime() {
-            // Overridden by HeadsUpManager HeadsUpEntry #calculateFinishTime
-            return 0;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index 909cff37..e598242 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -542,7 +542,19 @@
                 new ShortcutKeyGroupMultiMappingInfo(
                         context.getString(R.string.group_system_access_google_assistant),
                         Arrays.asList(
-                                Pair.create(KeyEvent.KEYCODE_A, KeyEvent.META_META_ON)))
+                                Pair.create(KeyEvent.KEYCODE_A, KeyEvent.META_META_ON))),
+                /*  Lock screen: Meta + L */
+                new ShortcutKeyGroupMultiMappingInfo(
+                        context.getString(R.string.group_system_lock_screen),
+                        Arrays.asList(
+                                Pair.create(KeyEvent.KEYCODE_L, KeyEvent.META_META_ON))),
+                /* Pull up Notes app for quick memo: Meta + Ctrl + N */
+                new ShortcutKeyGroupMultiMappingInfo(
+                        context.getString(R.string.group_system_quick_memo),
+                        Arrays.asList(
+                                Pair.create(
+                                        KeyEvent.KEYCODE_N,
+                                        KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON)))
         );
         for (ShortcutKeyGroupMultiMappingInfo info : infoList) {
             systemGroup.addItem(info.getShortcutMultiMappingInfo());
@@ -584,11 +596,17 @@
                         new ArrayList<>());
 
         // System multitasking shortcuts:
+        //    Enter Split screen with current app to RHS: Meta + Ctrl + Right arrow
+        //    Enter Split screen with current app to LHS: Meta + Ctrl + Left arrow
         //    Switch from Split screen to full screen: Meta + Ctrl + Up arrow
         String[] shortcutLabels = {
+                context.getString(R.string.system_multitasking_rhs),
+                context.getString(R.string.system_multitasking_lhs),
                 context.getString(R.string.system_multitasking_full_screen),
         };
         int[] keyCodes = {
+                KeyEvent.KEYCODE_DPAD_RIGHT,
+                KeyEvent.KEYCODE_DPAD_LEFT,
                 KeyEvent.KEYCODE_DPAD_UP,
         };
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 39b7930..6e85074 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -253,6 +253,8 @@
     initialHeight: Int? = null
 ) : View(context, attrs) {
 
+    private val logString = this::class.simpleName!! + "@" + hashCode()
+
     /** Listener that is called if the scrim's opaqueness changes */
     var isScrimOpaqueChangedListener: Consumer<Boolean>? = null
 
@@ -267,13 +269,13 @@
             if (field != value) {
                 field = value
                 if (value <= 0.0f || value >= 1.0f) {
-                    scrimLogger?.d(TAG, "revealAmount", "$value on ${logString()}")
+                    scrimLogger?.d(TAG, "revealAmount", "$value on $logString")
                 }
                 revealEffect.setRevealAmountOnScrim(value, this)
                 updateScrimOpaque()
                 Trace.traceCounter(
                     Trace.TRACE_TAG_APP,
-                    "light_reveal_amount",
+                    "light_reveal_amount $logString",
                     (field * 100).toInt()
                 )
                 invalidate()
@@ -290,7 +292,7 @@
                 field = value
 
                 revealEffect.setRevealAmountOnScrim(revealAmount, this)
-                scrimLogger?.d(TAG, "revealEffect", "$value on ${logString()}")
+                scrimLogger?.d(TAG, "revealEffect", "$value on $logString")
                 invalidate()
             }
         }
@@ -350,7 +352,7 @@
             if (field != value) {
                 field = value
                 isScrimOpaqueChangedListener?.accept(field)
-                scrimLogger?.d(TAG, "isScrimOpaque", "$value on ${logString()}")
+                scrimLogger?.d(TAG, "isScrimOpaque", "$value on $logString")
             }
         }
 
@@ -368,13 +370,13 @@
 
     override fun setAlpha(alpha: Float) {
         super.setAlpha(alpha)
-        scrimLogger?.d(TAG, "alpha", "$alpha on ${logString()}")
+        scrimLogger?.d(TAG, "alpha", "$alpha on $logString")
         updateScrimOpaque()
     }
 
     override fun setVisibility(visibility: Int) {
         super.setVisibility(visibility)
-        scrimLogger?.d(TAG, "visibility", "$visibility on ${logString()}")
+        scrimLogger?.d(TAG, "visibility", "$visibility on $logString")
         updateScrimOpaque()
     }
 
@@ -467,8 +469,4 @@
                 PorterDuff.Mode.MULTIPLY
             )
     }
-
-    private fun logString(): String {
-        return this::class.simpleName!! + "@" + hashCode()
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 2438298..7f8be1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.media.controls.ui.MediaHierarchyManager
 import com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll
 import com.android.systemui.plugins.ActivityStarter
@@ -888,6 +889,9 @@
                     isDraggingDown = false
                     isTrackpadReverseScroll = false
                     shadeRepository.setLegacyLockscreenShadeTracking(false)
+                    if (KeyguardShadeMigrationNssl.isEnabled) {
+                        return true
+                    }
                 } else {
                     stopDragging()
                     return false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 9b8dd0b..2a4753d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -15,17 +15,17 @@
  */
 package com.android.systemui.statusbar;
 
+import static android.app.Flags.keyguardPrivateNotifications;
 import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
 import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+import static android.os.Flags.allowPrivateProfile;
 import static android.os.UserHandle.USER_ALL;
 import static android.os.UserHandle.USER_NULL;
 import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
 import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
-import static android.app.Flags.keyguardPrivateNotifications;
-import static android.os.Flags.allowPrivateProfile;
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
@@ -42,9 +42,8 @@
 import android.content.IntentSender;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
+import android.database.ExecutorContentObserver;
 import android.net.Uri;
-import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -79,17 +78,17 @@
 import com.android.systemui.util.ListenerSet;
 import com.android.systemui.util.settings.SecureSettings;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
-import java.util.concurrent.Executor;
 import java.util.Objects;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
-
 /**
  * Handles keeping track of the current user, profiles, and various things related to hiding
  * contents, redacting notifications, and the lockscreen.
@@ -228,17 +227,19 @@
                 updateCurrentProfilesCache();
                 if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
                     final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
-                    mBackgroundHandler.post(() -> {
+                    mBackgroundExecutor.execute(() -> {
                         initValuesForUser(userId);
                     });
                 }
             } else if (profileAvailabilityActions(action)) {
                 updateCurrentProfilesCache();
             } else if (Objects.equals(action, Intent.ACTION_USER_UNLOCKED)) {
-                // Start the overview connection to the launcher service
-                // Connect if user hasn't connected yet
-                if (mOverviewProxyServiceLazy.get().getProxy() == null) {
-                    mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
+                if (!keyguardPrivateNotifications()) {
+                    // Start the overview connection to the launcher service
+                    // Connect if user hasn't connected yet
+                    if (mOverviewProxyServiceLazy.get().getProxy() == null) {
+                        mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
+                    }
                 }
             } else if (Objects.equals(action, NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION)) {
                 final IntentSender intentSender = intent.getParcelableExtra(
@@ -289,8 +290,7 @@
             };
 
     protected final Context mContext;
-    private final Handler mMainHandler;
-    private final Handler mBackgroundHandler;
+    private final Executor mMainExecutor;
     private final Executor mBackgroundExecutor;
     /** The current user and its profiles (possibly including a communal profile). */
     protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();
@@ -313,8 +313,7 @@
             Lazy<OverviewProxyService> overviewProxyServiceLazy,
             KeyguardManager keyguardManager,
             StatusBarStateController statusBarStateController,
-            @Main Handler mainHandler,
-            @Background Handler backgroundHandler,
+            @Main Executor mainExecutor,
             @Background Executor backgroundExecutor,
             DeviceProvisionedController deviceProvisionedController,
             KeyguardStateController keyguardStateController,
@@ -323,8 +322,7 @@
             LockPatternUtils lockPatternUtils,
             FeatureFlagsClassic featureFlags) {
         mContext = context;
-        mMainHandler = mainHandler;
-        mBackgroundHandler = backgroundHandler;
+        mMainExecutor = mainExecutor;
         mBackgroundExecutor = backgroundExecutor;
         mDevicePolicyManager = devicePolicyManager;
         mUserManager = userManager;
@@ -362,10 +360,10 @@
     }
 
     private void init() {
-        mLockscreenSettingsObserver = new ContentObserver(
+        mLockscreenSettingsObserver = new ExecutorContentObserver(
                 mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)
-                        ? mBackgroundHandler
-                        : mMainHandler) {
+                        ? mBackgroundExecutor
+                        : mMainExecutor) {
 
             @Override
             public void onChange(boolean selfChange, Collection<Uri> uris, int flags) {
@@ -412,7 +410,7 @@
             }
         };
 
-        mSettingsObserver = new ContentObserver(mMainHandler) {
+        mSettingsObserver = new ExecutorContentObserver(mMainExecutor) {
             @Override
             public void onChange(boolean selfChange) {
                 updateLockscreenNotificationSetting();
@@ -468,14 +466,14 @@
         mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null,
                 Context.RECEIVER_EXPORTED_UNAUDITED);
 
-        mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));
+        mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
 
         mCurrentUserId = mUserTracker.getUserId(); // in case we reg'd receiver too late
         updateCurrentProfilesCache();
 
         if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
             // Set  up
-            mBackgroundHandler.post(() -> {
+            mBackgroundExecutor.execute(() -> {
                 @SuppressLint("MissingPermission") List<UserInfo> users = mUserManager.getUsers();
                 for (int i = users.size() - 1; i >= 0; i--) {
                     initValuesForUser(users.get(i).id);
@@ -796,7 +794,7 @@
                 }
             }
         }
-        mMainHandler.post(() -> {
+        mMainExecutor.execute(() -> {
             for (UserChangedListener listener : mListeners) {
                 listener.onCurrentProfilesChanged(mCurrentProfiles);
             }
@@ -895,7 +893,7 @@
 
     private void notifyNotificationStateChanged() {
         if (!Looper.getMainLooper().isCurrentThread()) {
-            mMainHandler.post(() -> {
+            mMainExecutor.execute(() -> {
                 for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
                     listener.onNotificationStateChanged();
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 7a2e82f..bb6ee24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -645,6 +645,10 @@
                 + "slot='" + mSlot + "' alpha=" + getAlpha() + " icon=" + mIcon
                 + " visibleState=" + getVisibleStateString(getVisibleState())
                 + " iconColor=#" + Integer.toHexString(mIconColor)
+                + " staticDrawableColor=#" + Integer.toHexString(mDrawableColor)
+                + " decorColor=#" + Integer.toHexString(mDecorColor)
+                + " animationStartColor=#" + Integer.toHexString(mAnimationStartColor)
+                + " currentSetColor=#" + Integer.toHexString(mCurrentSetColor)
                 + " notification=" + mNotification + ')';
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index fc84973..28d4457 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -35,7 +35,6 @@
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
@@ -61,7 +60,6 @@
 import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.settingslib.net.DataUsageController;
 import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -73,6 +71,7 @@
 import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.log.dagger.StatusBarNetworkControllerLog;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -85,6 +84,8 @@
 
 import dalvik.annotation.optimization.NeverCompile;
 
+import kotlin.Unit;
+
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -99,8 +100,6 @@
 
 import javax.inject.Inject;
 
-import kotlin.Unit;
-
 /** Platform implementation of the network controller. **/
 @SysUISingleton
 public class NetworkControllerImpl extends BroadcastReceiver
@@ -350,7 +349,7 @@
         // AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
         updateAirplaneMode(true /* force callback */);
         mUserTracker = userTracker;
-        mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));
+        mUserTracker.addCallback(mUserChangedCallback, mBgExecutor);
 
         deviceProvisionedController.addCallback(new DeviceProvisionedListener() {
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
index 29d53fc..9f878b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.statusbar.data
 
 import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryModule
+import com.android.systemui.statusbar.data.repository.RemoteInputRepositoryModule
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryModule
 import com.android.systemui.statusbar.phone.data.StatusBarPhoneDataLayerModule
 import dagger.Module
@@ -24,6 +25,7 @@
     includes =
         [
             KeyguardStatusBarRepositoryModule::class,
+            RemoteInputRepositoryModule::class,
             StatusBarModeRepositoryModule::class,
             StatusBarPhoneDataLayerModule::class
         ]
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
new file mode 100644
index 0000000..c0302bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.RemoteInputController
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Repository used for tracking the state of notification remote input (e.g. when the user presses
+ * "reply" on a notification and the keyboard opens).
+ */
+interface RemoteInputRepository {
+    /** Whether remote input is currently active for any notification. */
+    val isRemoteInputActive: Flow<Boolean>
+}
+
+@SysUISingleton
+class RemoteInputRepositoryImpl
+@Inject
+constructor(
+    private val notificationRemoteInputManager: NotificationRemoteInputManager,
+) : RemoteInputRepository {
+    override val isRemoteInputActive: Flow<Boolean> = conflatedCallbackFlow {
+        trySend(false) // initial value is false
+        val callback =
+            object : RemoteInputController.Callback {
+                override fun onRemoteInputActive(active: Boolean) {
+                    trySend(active)
+                }
+            }
+        notificationRemoteInputManager.addControllerCallback(callback)
+        awaitClose { notificationRemoteInputManager.removeControllerCallback(callback) }
+    }
+}
+
+@Module
+interface RemoteInputRepositoryModule {
+    @Binds fun bindImpl(impl: RemoteInputRepositoryImpl): RemoteInputRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
new file mode 100644
index 0000000..68f727b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.data.repository.RemoteInputRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Interactor used for business logic pertaining to the notification remote input (e.g. when the
+ * user presses "reply" on a notification and the keyboard opens).
+ */
+@SysUISingleton
+class RemoteInputInteractor @Inject constructor(remoteInputRepository: RemoteInputRepository) {
+    /** Is remote input currently active for a notification? */
+    val isRemoteInputActive: Flow<Boolean> = remoteInputRepository.isRemoteInputActive
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 6e3b15d..c643238 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -52,8 +52,8 @@
             entry: NotificationEntry,
             recoveredBuilder: Notification.Builder,
             logger: NotificationContentInflaterLogger
-    ) {
-        val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return
+    ): Notification.MessagingStyle? {
+        val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return null
         messagingStyle.conversationType =
                 if (entry.ranking.channel.isImportantConversation)
                     Notification.MessagingStyle.CONVERSATION_TYPE_IMPORTANT
@@ -68,6 +68,7 @@
         }
         messagingStyle.unreadMessageCount =
                 conversationNotificationManager.getUnreadCount(entry, recoveredBuilder)
+        return messagingStyle
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
index 96279e2..5983fc1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -157,9 +157,9 @@
             val summaryEntry = notificationEntry.parent?.summary
 
             return when {
-                headsUpManager.isAlerting(notificationKey) -> notification
+                headsUpManager.isHeadsUpEntry(notificationKey) -> notification
                 summaryEntry == null -> null
-                headsUpManager.isAlerting(summaryEntry.key) -> summaryEntry.row
+                headsUpManager.isHeadsUpEntry(summaryEntry.key) -> summaryEntry.row
                 else -> null
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index cfe9fbe..cdacb10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -171,7 +171,7 @@
     private int mBucket = BUCKET_ALERTING;
     @Nullable private Long mPendingAnimationDuration;
     private boolean mIsMarkedForUserTriggeredMovement;
-    private boolean mIsAlerting;
+    private boolean mIsHeadsUpEntry;
 
     public boolean mRemoteEditImeAnimatingAway;
     public boolean mRemoteEditImeVisible;
@@ -965,12 +965,12 @@
         mIsMarkedForUserTriggeredMovement = marked;
     }
 
-    public void setIsAlerting(boolean isAlerting) {
-        mIsAlerting = isAlerting;
+    public void setIsHeadsUpEntry(boolean isHeadsUpEntry) {
+        mIsHeadsUpEntry = isHeadsUpEntry;
     }
 
-    public boolean isAlerting() {
-        return mIsAlerting;
+    public boolean isHeadsUpEntry() {
+        return mIsHeadsUpEntry;
     }
 
     /** Set whether this notification is currently used to animate a launch. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 314566e..54b6ad7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -221,7 +221,7 @@
                         wasUpdated = false,
                         shouldHeadsUpEver = false,
                         shouldHeadsUpAgain = false,
-                        isAlerting = mHeadsUpManager.isAlerting(logicalSummary.key),
+                        isAlerting = mHeadsUpManager.isHeadsUpEntry(logicalSummary.key),
                         isBinding = isEntryBinding(logicalSummary),
                 )
                 // If we transfer the alert and the summary isn't even attached, that means we
@@ -268,7 +268,7 @@
                         wasUpdated = false,
                         shouldHeadsUpEver = true,
                         shouldHeadsUpAgain = true,
-                        isAlerting = mHeadsUpManager.isAlerting(childToReceiveParentAlert.key),
+                        isAlerting = mHeadsUpManager.isHeadsUpEntry(childToReceiveParentAlert.key),
                         isBinding = isEntryBinding(childToReceiveParentAlert),
                 )
                 handlePostedEntry(
@@ -425,7 +425,7 @@
             val shouldHeadsUpEver =
                 mVisualInterruptionDecisionProvider.makeAndLogHeadsUpDecision(entry).shouldInterrupt
             val shouldHeadsUpAgain = shouldHunAgain(entry)
-            val isAlerting = mHeadsUpManager.isAlerting(entry.key)
+            val isAlerting = mHeadsUpManager.isHeadsUpEntry(entry.key)
             val isBinding = isEntryBinding(entry)
             val posted = mPostedEntries.compute(entry.key) { _, value ->
                 value?.also { update ->
@@ -469,7 +469,7 @@
             mEntriesUpdateTimes.remove(entry.key)
             cancelHeadsUpBind(entry)
             val entryKey = entry.key
-            if (mHeadsUpManager.isAlerting(entryKey)) {
+            if (mHeadsUpManager.isHeadsUpEntry(entryKey)) {
                 // TODO: This should probably know the RemoteInputCoordinator's conditions,
                 //  or otherwise reference that coordinator's state, rather than replicate its logic
                 val removeImmediatelyForRemoteInput = (mRemoteInputManager.isSpinning(entryKey) &&
@@ -719,7 +719,7 @@
      * Whether the notification is already alerting or binding so that it can imminently alert
      */
     private fun isAttemptingToShowHun(entry: ListEntry) =
-        mHeadsUpManager.isAlerting(entry.key) || isEntryBinding(entry)
+        mHeadsUpManager.isHeadsUpEntry(entry.key) || isEntryBinding(entry)
 
     /**
      * Whether the notification is already alerting/binding per [isAttemptingToShowHun] OR if it
@@ -790,7 +790,7 @@
 
 /** Mutates the HeadsUp state of notifications. */
 private interface HunMutator {
-    fun updateNotification(key: String, alert: Boolean)
+    fun updateNotification(key: String, shouldHeadsUpAgain: Boolean)
     fun removeNotification(key: String, releaseImmediately: Boolean)
 }
 
@@ -801,8 +801,8 @@
 private class HunMutatorImpl(private val headsUpManager: HeadsUpManager) : HunMutator {
     private val deferred = mutableListOf<Pair<String, Boolean>>()
 
-    override fun updateNotification(key: String, alert: Boolean) {
-        headsUpManager.updateNotification(key, alert)
+    override fun updateNotification(key: String, shouldHeadsUpAgain: Boolean) {
+        headsUpManager.updateNotification(key, shouldHeadsUpAgain)
     }
 
     override fun removeNotification(key: String, releaseImmediately: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 226a957..bd659d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import javax.inject.Inject
 
 /**
@@ -61,6 +62,7 @@
         sensitiveContentCoordinator: SensitiveContentCoordinator,
         dismissibilityCoordinator: DismissibilityCoordinator,
         dreamCoordinator: DreamCoordinator,
+        statsLoggerCoordinator: NotificationStatsLoggerCoordinator,
 ) : NotifCoordinators {
 
     private val mCoreCoordinators: MutableList<CoreCoordinator> = ArrayList()
@@ -104,6 +106,10 @@
             mCoordinators.add(dreamCoordinator)
         }
 
+        if (NotificationsLiveDataStoreRefactor.isEnabled) {
+            mCoordinators.add(statsLoggerCoordinator)
+        }
+
         // Manually add Ordered Sections
         mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
         mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinator.kt
new file mode 100644
index 0000000..6e81d93
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinator.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger
+import java.util.Optional
+import javax.inject.Inject
+
+@CoordinatorScope
+class NotificationStatsLoggerCoordinator
+@Inject
+constructor(private val loggerOptional: Optional<NotificationStatsLogger>) : Coordinator {
+
+    private val collectionListener =
+        object : NotifCollectionListener {
+            override fun onEntryUpdated(entry: NotificationEntry) {
+                super.onEntryUpdated(entry)
+                loggerOptional.ifPresent { it.onNotificationUpdated(entry.key) }
+            }
+
+            override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+                super.onEntryRemoved(entry, reason)
+                loggerOptional.ifPresent { it.onNotificationRemoved(entry.key) }
+            }
+        }
+    override fun attach(pipeline: NotifPipeline) {
+        if (NotificationsLiveDataStoreRefactor.isUnexpectedlyInLegacyMode()) {
+            return
+        }
+        pipeline.addCollectionListener(collectionListener)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 73decfc..639e23a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -362,8 +362,12 @@
     }
 
     NotifInflater.Params getInflaterParams(NotifUiAdjustment adjustment, String reason) {
-        return new NotifInflater.Params(adjustment.isMinimized(), reason,
-                adjustment.isSnoozeEnabled());
+        return new NotifInflater.Params(
+                adjustment.isMinimized(),
+                reason,
+                adjustment.isSnoozeEnabled(),
+                adjustment.isChildInGroup()
+        );
     }
 
     private void abortInflation(NotificationEntry entry, String reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index 380cdad..ae4ba27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -18,6 +18,7 @@
 
 import android.os.UserHandle
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags.screenshareNotificationHiding
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.StatusBarState
@@ -30,6 +31,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import dagger.Binds
 import dagger.Module
@@ -55,6 +57,8 @@
     private val statusBarStateController: StatusBarStateController,
     private val keyguardStateController: KeyguardStateController,
     private val selectedUserInteractor: SelectedUserInteractor,
+    private val sensitiveNotificationProtectionController:
+        SensitiveNotificationProtectionController,
 ) : Invalidator("SensitiveContentInvalidator"),
         SensitiveContentCoordinator,
         DynamicPrivacyController.Listener,
@@ -82,10 +86,13 @@
             return
         }
 
+        val isSensitiveContentProtectionActive = screenshareNotificationHiding() &&
+            sensitiveNotificationProtectionController.isSensitiveStateActive
         val currentUserId = lockscreenUserManager.currentUserId
         val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId)
-        val deviceSensitive = devicePublic &&
-                !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId)
+        val deviceSensitive = (devicePublic &&
+                !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId)) ||
+                isSensitiveContentProtectionActive
         val dynamicallyUnlocked = dynamicPrivacyController.isDynamicallyUnlocked
         for (entry in extractAllRepresentativeEntries(entries).filter { it.rowExists() }) {
             val notifUserId = entry.sbn.user.identifier
@@ -105,9 +112,13 @@
                     else -> lockscreenUserManager.needsSeparateWorkChallenge(notifUserId)
                 }
             }
+
+            val shouldProtectNotification = screenshareNotificationHiding() &&
+                sensitiveNotificationProtectionController.shouldProtectNotification(entry)
+
             val needsRedaction = lockscreenUserManager.needsRedaction(entry)
             val isSensitive = userPublic && needsRedaction
-            entry.setSensitive(isSensitive, deviceSensitive)
+            entry.setSensitive(isSensitive || shouldProtectNotification, deviceSensitive)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index fa2748c..6e2beb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -61,7 +61,8 @@
             controller.setNotifStats(notifStats)
             if (NotificationIconContainerRefactor.isEnabled || FooterViewRefactor.isEnabled) {
                 renderListInteractor.setRenderedList(entries)
-            } else {
+            }
+            if (!NotificationIconContainerRefactor.isEnabled) {
                 notificationIconAreaController.updateNotificationIcons(entries)
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index a0129ff..8189fe0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -134,7 +134,7 @@
     private final NotifStabilityManager mNotifStabilityManager =
             new NotifStabilityManager("VisualStabilityCoordinator") {
                 private boolean canMoveForHeadsUp(NotificationEntry entry) {
-                    return entry != null && mHeadsUpManager.isAlerting(entry.getKey())
+                    return entry != null && mHeadsUpManager.isHeadsUpEntry(entry.getKey())
                             && !mVisibilityLocationProvider.isInVisibleLocation(entry);
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
index 4483599..c0b187b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
@@ -20,9 +20,9 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifViewController
 
 /**
- * Used by the [PreparationCoordinator].  When notifications are added or updated, the
- * NotifInflater is asked to (re)inflated and prepare their views.  This inflation occurs off the
- * main thread. When the inflation is finished, NotifInflater will trigger its InflationCallback.
+ * Used by the [PreparationCoordinator]. When notifications are added or updated, the NotifInflater
+ * is asked to (re)inflated and prepare their views. This inflation occurs off the main thread. When
+ * the inflation is finished, NotifInflater will trigger its InflationCallback.
  */
 interface NotifInflater {
     /**
@@ -33,7 +33,7 @@
     fun rebindViews(entry: NotificationEntry, params: Params, callback: InflationCallback)
 
     /**
-     * Called to inflate the views of an entry.  Views are not considered inflated until all of its
+     * Called to inflate the views of an entry. Views are not considered inflated until all of its
      * views are bound. Once all views are inflated, the InflationCallback is triggered.
      *
      * @param callback callback called after inflation finishes
@@ -41,25 +41,24 @@
     fun inflateViews(entry: NotificationEntry, params: Params, callback: InflationCallback)
 
     /**
-     * Request to stop the inflation of an entry.  For example, called when a notification is
-     * removed and no longer needs to be inflated.  Returns whether anything may have been aborted.
+     * Request to stop the inflation of an entry. For example, called when a notification is removed
+     * and no longer needs to be inflated. Returns whether anything may have been aborted.
      */
     fun abortInflation(entry: NotificationEntry): Boolean
 
-    /**
-     * Called to let the system remove the content views from the notification row.
-     */
+    /** Called to let the system remove the content views from the notification row. */
     fun releaseViews(entry: NotificationEntry)
 
-    /**
-     * Callback once all the views are inflated and bound for a given NotificationEntry.
-     */
+    /** Callback once all the views are inflated and bound for a given NotificationEntry. */
     interface InflationCallback {
         fun onInflationFinished(entry: NotificationEntry, controller: NotifViewController)
     }
 
-    /**
-     * A class holding parameters used when inflating the notification row
-     */
-    class Params(val isLowPriority: Boolean, val reason: String, val showSnooze: Boolean)
+    /** A class holding parameters used when inflating the notification row */
+    class Params(
+        val isLowPriority: Boolean,
+        val reason: String,
+        val showSnooze: Boolean,
+        val isChildInGroup: Boolean = false,
+    )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
index ee0b008..e1d2cdc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
@@ -20,6 +20,7 @@
 import android.app.RemoteInput
 import android.graphics.drawable.Icon
 import android.text.TextUtils
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
 
 /**
  * An immutable object which contains minimal state extracted from an entry that represents state
@@ -34,6 +35,7 @@
     val isSnoozeEnabled: Boolean,
     val isMinimized: Boolean,
     val needsRedaction: Boolean,
+    val isChildInGroup: Boolean,
 ) {
     companion object {
         @JvmStatic
@@ -48,6 +50,11 @@
             oldAdjustment.needsRedaction != newAdjustment.needsRedaction -> true
             areDifferent(oldAdjustment.smartActions, newAdjustment.smartActions) -> true
             newAdjustment.smartReplies != oldAdjustment.smartReplies -> true
+            // TODO(b/217799515): Here we decide whether to re-inflate the row on every group-status
+            //  change if we want to keep the single-line view, the following line should be:
+            //  !oldAdjustment.isChildInGroup && newAdjustment.isChildInGroup -> true
+            AsyncHybridViewInflation.isEnabled &&
+                    oldAdjustment.isChildInGroup != newAdjustment.isChildInGroup -> true
             else -> false
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
index 0585456..6f44c13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
 import com.android.systemui.util.ListenerSet
 import com.android.systemui.util.settings.SecureSettings
 import javax.inject.Inject
@@ -43,7 +44,8 @@
     private val secureSettings: SecureSettings,
     private val lockscreenUserManager: NotificationLockscreenUserManager,
     private val sectionStyleProvider: SectionStyleProvider,
-    private val userTracker: UserTracker
+    private val userTracker: UserTracker,
+    private val groupMembershipManager: GroupMembershipManager,
 ) {
     private val dirtyListeners = ListenerSet<Runnable>()
     private var isSnoozeSettingsEnabled = false
@@ -121,5 +123,6 @@
         isSnoozeEnabled = isSnoozeSettingsEnabled && !entry.isCanceled,
         isMinimized = isEntryMinimized(entry),
         needsRedaction = lockscreenUserManager.needsRedaction(entry),
+        isChildInGroup = groupMembershipManager.isChildInGroup(entry),
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 80ef14b..cd816ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -20,6 +20,7 @@
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE;
 
 import static java.util.Objects.requireNonNull;
 
@@ -49,6 +50,7 @@
 import com.android.systemui.statusbar.notification.row.RowContentBindStage;
 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
 import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 
 import javax.inject.Inject;
@@ -127,6 +129,8 @@
             @NonNull NotifInflater.Params params,
             NotificationRowContentBinder.InflationCallback callback)
             throws InflationException {
+        //TODO(b/217799515): Remove the entry parameter from getViewParentForNotification(), this
+        // function returns the NotificationStackScrollLayout regardless of the entry.
         ViewGroup parent = mListContainer.getViewParentForNotification(entry);
 
         if (entry.rowExists()) {
@@ -174,6 +178,9 @@
         params.markContentViewsFreeable(FLAG_CONTENT_VIEW_CONTRACTED);
         params.markContentViewsFreeable(FLAG_CONTENT_VIEW_EXPANDED);
         params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);
+        if (AsyncHybridViewInflation.isEnabled()) {
+            params.markContentViewsFreeable(FLAG_CONTENT_VIEW_SINGLE_LINE);
+        }
         mRowContentBindStage.requestRebind(entry, null);
     }
 
@@ -254,6 +261,16 @@
             params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);
         }
 
+        if (AsyncHybridViewInflation.isEnabled()) {
+            if (inflaterParams.isChildInGroup()) {
+                params.requireContentViews(FLAG_CONTENT_VIEW_SINGLE_LINE);
+            } else {
+                // TODO(b/217799515): here we decide whether to free the single-line view
+                //  when the group status changes
+                params.markContentViewsFreeable(FLAG_CONTENT_VIEW_SINGLE_LINE);
+            }
+        }
+
         params.rebindAllContentViews();
         mLogger.logRequestingRebind(entry, inflaterParams);
         mRowContentBindStage.requestRebind(entry, en -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
index e71d80c..5743ab0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
@@ -68,7 +68,7 @@
     @NonNull
     private DismissedByUserStats getDismissedByUserStats(NotificationEntry entry) {
         int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
-        if (mHeadsUpManager.isAlerting(entry.getKey())) {
+        if (mHeadsUpManager.isHeadsUpEntry(entry.getKey())) {
             dismissalSurface = NotificationStats.DISMISSAL_PEEK;
         } else if (mStatusBarStateController.isDozing()) {
             dismissalSurface = NotificationStats.DISMISSAL_AOD;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt
index ec10aaf..88e94e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt
@@ -22,12 +22,17 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.logging.NotificationLogger
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import javax.inject.Inject
 
 /** pipeline-agnostic implementation for getting [NotificationVisibility]. */
 @SysUISingleton
-class NotificationVisibilityProviderImpl @Inject constructor(
+class NotificationVisibilityProviderImpl
+@Inject
+constructor(
+    private val activeNotificationsInteractor: ActiveNotificationsInteractor,
     private val notifDataStore: NotifLiveDataStore,
     private val notifCollection: CommonNotifCollection
 ) : NotificationVisibilityProvider {
@@ -47,5 +52,10 @@
     override fun getLocation(key: String): NotificationVisibility.NotificationLocation =
         NotificationLogger.getNotificationLocation(notifCollection.getEntry(key))
 
-    private fun getCount() = notifDataStore.activeNotifCount.value
+    private fun getCount() =
+        if (NotificationsLiveDataStoreRefactor.isEnabled) {
+            activeNotificationsInteractor.allNotificationsCountValue
+        } else {
+            notifDataStore.activeNotifCount.value
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
index 2d5afd5..3cdb2cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
@@ -21,8 +21,6 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -53,14 +51,11 @@
      */
     private final Set<NotificationEntry> mExpandedGroups = new HashSet<>();
 
-    private final FeatureFlags mFeatureFlags;
-
     @Inject
     public GroupExpansionManagerImpl(DumpManager dumpManager,
-            GroupMembershipManager groupMembershipManager, FeatureFlags featureFlags) {
+            GroupMembershipManager groupMembershipManager) {
         mDumpManager = dumpManager;
         mGroupMembershipManager = groupMembershipManager;
-        mFeatureFlags = featureFlags;
     }
 
     /**
@@ -86,10 +81,8 @@
     };
 
     public void attach(NotifPipeline pipeline) {
-        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
-            mDumpManager.registerDumpable(this);
-            pipeline.addOnBeforeRenderListListener(mNotifTracker);
-        }
+        mDumpManager.registerDumpable(this);
+        pipeline.addOnBeforeRenderListListener(mNotifTracker);
     }
 
     @Override
@@ -105,8 +98,7 @@
     @Override
     public void setGroupExpanded(NotificationEntry entry, boolean expanded) {
         NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry);
-        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)
-                && entry.getParent() == null) {
+        if (entry.getParent() == null) {
             if (expanded) {
                 throw new IllegalArgumentException("Cannot expand group that is not attached");
             } else {
@@ -124,7 +116,7 @@
         }
 
         // Only notify listeners if something changed.
-        if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE) || changed) {
+        if (changed) {
             sendOnGroupExpandedChange(entry, expanded);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
index cb79353..da12479 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
@@ -22,8 +22,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.flags.FeatureFlagsClassic;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -38,25 +36,17 @@
  */
 @SysUISingleton
 public class GroupMembershipManagerImpl implements GroupMembershipManager {
-    FeatureFlagsClassic mFeatureFlags;
-
     @Inject
-    public GroupMembershipManagerImpl(FeatureFlagsClassic featureFlags) {
-        mFeatureFlags = featureFlags;
-    }
+    public GroupMembershipManagerImpl() {}
 
     @Override
     public boolean isGroupSummary(@NonNull NotificationEntry entry) {
-        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
-            if (entry.getParent() == null) {
-                // The entry is not attached, so it doesn't count.
-                return false;
-            }
-            // If entry is a summary, its parent is a GroupEntry with summary = entry.
-            return entry.getParent().getSummary() == entry;
-        } else {
-            return getGroupSummary(entry) == entry;
+        if (entry.getParent() == null) {
+            // The entry is not attached, so it doesn't count.
+            return false;
         }
+        // If entry is a summary, its parent is a GroupEntry with summary = entry.
+        return entry.getParent().getSummary() == entry;
     }
 
     @Nullable
@@ -70,12 +60,8 @@
 
     @Override
     public boolean isChildInGroup(@NonNull NotificationEntry entry) {
-        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
-            // An entry is a child if it's not a summary or top level entry, but it is attached.
-            return !isGroupSummary(entry) && !isTopLevelEntry(entry) && entry.getParent() != null;
-        } else {
-            return !isTopLevelEntry(entry);
-        }
+        // An entry is a child if it's not a summary or top level entry, but it is attached.
+        return !isGroupSummary(entry) && !isTopLevelEntry(entry) && entry.getParent() != null;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 61e6f65..8021d8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -127,6 +127,9 @@
         }
     }
 
+    /**
+     * Attach the Child Nodes to the parentNode using the structure from specMap
+     */
     private fun attachChildren(
         parentNode: ShadeNode,
         specMap: Map<NodeController, NodeSpec>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStatsLoggerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStatsLoggerModule.kt
new file mode 100644
index 0000000..cbd9887
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStatsLoggerModule.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.dagger
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.NoOpCoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.UiBackground
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.statusbar.NotificationListener
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider
+import com.android.systemui.statusbar.notification.logging.NotificationLogger
+import com.android.systemui.statusbar.notification.logging.NotificationLogger.ExpansionStateLogger
+import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationRowStatsLogger
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLoggerImpl
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationLoggerViewModel
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.kotlin.getOrNull
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import java.util.Optional
+import java.util.concurrent.Executor
+import javax.inject.Provider
+
+@Module
+interface NotificationStatsLoggerModule {
+
+    /** Binds an implementation to the [NotificationStatsLogger]. */
+    @Binds fun bindsStatsLoggerImpl(impl: NotificationStatsLoggerImpl): NotificationStatsLogger
+
+    companion object {
+
+        /** Provides a [NotificationStatsLogger] if the refactor flag is on. */
+        @Provides
+        fun provideStatsLogger(
+            provider: Provider<NotificationStatsLogger>
+        ): Optional<NotificationStatsLogger> {
+            return if (NotificationsLiveDataStoreRefactor.isEnabled) {
+                Optional.of(provider.get())
+            } else {
+                Optional.empty()
+            }
+        }
+
+        /** Provides a [NotificationLoggerViewModel] if the refactor flag is on. */
+        @Provides
+        fun provideViewModel(
+            provider: Provider<NotificationLoggerViewModel>
+        ): Optional<NotificationLoggerViewModel> {
+            return if (NotificationsLiveDataStoreRefactor.isEnabled) {
+                Optional.of(provider.get())
+            } else {
+                Optional.empty()
+            }
+        }
+
+        /** Provides the legacy [NotificationLogger] if the refactor flag is off. */
+        @Provides
+        @SysUISingleton
+        fun provideLegacyLoggerOptional(
+            notificationListener: NotificationListener?,
+            @UiBackground uiBgExecutor: Executor?,
+            notifLiveDataStore: NotifLiveDataStore?,
+            visibilityProvider: NotificationVisibilityProvider?,
+            notifPipeline: NotifPipeline?,
+            statusBarStateController: StatusBarStateController?,
+            windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor?,
+            javaAdapter: JavaAdapter?,
+            expansionStateLogger: ExpansionStateLogger?,
+            notificationPanelLogger: NotificationPanelLogger?
+        ): Optional<NotificationLogger> {
+            return if (NotificationsLiveDataStoreRefactor.isEnabled) {
+                Optional.empty()
+            } else {
+                Optional.of(
+                    NotificationLogger(
+                        notificationListener,
+                        uiBgExecutor,
+                        notifLiveDataStore,
+                        visibilityProvider,
+                        notifPipeline,
+                        statusBarStateController,
+                        windowRootViewVisibilityInteractor,
+                        javaAdapter,
+                        expansionStateLogger,
+                        notificationPanelLogger
+                    )
+                )
+            }
+        }
+
+        /**
+         * Provides a the legacy [NotificationLogger] or the new [NotificationStatsLogger] to the
+         * notification row.
+         *
+         * TODO(b/308623704) remove the [NotificationRowStatsLogger] interface, and provide a
+         *   [NotificationStatsLogger] to the row directly.
+         */
+        @Provides
+        fun provideRowStatsLogger(
+            newProvider: Provider<NotificationStatsLogger>,
+            legacyLoggerOptional: Optional<NotificationLogger>,
+        ): NotificationRowStatsLogger {
+            return legacyLoggerOptional.getOrNull() ?: newProvider.get()
+        }
+
+        /**
+         * Binds the legacy [NotificationLogger] as a [CoreStartable] if the feature flag is off, or
+         * binds a no-op [CoreStartable] otherwise.
+         *
+         * The old [NotificationLogger] is a [CoreStartable], because it's managing its own data
+         * updates, but the new [NotificationStatsLogger] is not. Currently Dagger doesn't support
+         * optionally binding entries with @[IntoMap], therefore we provide a no-op [CoreStartable]
+         * here if the feature flag is on, but this can be removed once the flag is released.
+         */
+        @Provides
+        @IntoMap
+        @ClassKey(NotificationLogger::class)
+        fun provideCoreStartable(
+            legacyLoggerOptional: Optional<NotificationLogger>
+        ): CoreStartable {
+            return legacyLoggerOptional.getOrNull() ?: NoOpCoreStartable()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 3a72205..6bba72b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -17,14 +17,12 @@
 package com.android.systemui.statusbar.notification.dagger;
 
 import android.content.Context;
+import android.service.notification.NotificationListenerService;
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.UiBackground;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
@@ -67,7 +65,6 @@
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProviderImpl;
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerImpl;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -80,7 +77,8 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.util.kotlin.JavaAdapter;
+
+import javax.inject.Provider;
 
 import dagger.Binds;
 import dagger.Module;
@@ -88,10 +86,6 @@
 import dagger.multibindings.ClassKey;
 import dagger.multibindings.IntoMap;
 
-import java.util.concurrent.Executor;
-
-import javax.inject.Provider;
-
 /**
  * Dagger Module for classes found within the com.android.systemui.statusbar.notification package.
  */
@@ -104,6 +98,7 @@
         NotificationSectionHeadersModule.class,
         ActivatableNotificationViewModelModule.class,
         NotificationMemoryModule.class,
+        NotificationStatsLoggerModule.class,
 })
 public interface NotificationsModule {
     @Binds
@@ -128,39 +123,6 @@
     VisibilityLocationProvider bindVisibilityLocationProvider(
             VisibilityLocationProviderDelegator visibilityLocationProviderDelegator);
 
-    /** Provides an instance of {@link NotificationLogger} */
-    @SysUISingleton
-    @Provides
-    static NotificationLogger provideNotificationLogger(
-            NotificationListener notificationListener,
-            @UiBackground Executor uiBgExecutor,
-            NotifLiveDataStore notifLiveDataStore,
-            NotificationVisibilityProvider visibilityProvider,
-            NotifPipeline notifPipeline,
-            StatusBarStateController statusBarStateController,
-            WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
-            JavaAdapter javaAdapter,
-            NotificationLogger.ExpansionStateLogger expansionStateLogger,
-            NotificationPanelLogger notificationPanelLogger) {
-        return new NotificationLogger(
-                notificationListener,
-                uiBgExecutor,
-                notifLiveDataStore,
-                visibilityProvider,
-                notifPipeline,
-                statusBarStateController,
-                windowRootViewVisibilityInteractor,
-                javaAdapter,
-                expansionStateLogger,
-                notificationPanelLogger);
-    }
-
-    /** Binds {@link NotificationLogger} as a {@link CoreStartable}. */
-    @Binds
-    @IntoMap
-    @ClassKey(NotificationLogger.class)
-    CoreStartable bindsNotificationLogger(NotificationLogger notificationLogger);
-
     /** Provides an instance of {@link NotificationPanelLogger} */
     @SysUISingleton
     @Provides
@@ -272,6 +234,10 @@
     NotifLiveDataStore bindNotifLiveDataStore(NotifLiveDataStoreImpl notifLiveDataStoreImpl);
 
     /** */
+    @Binds
+    NotificationListenerService bindNotificationListener(NotificationListener notificationListener);
+
+    /** */
     @Provides
     @SysUISingleton
     static VisualInterruptionDecisionProvider provideVisualInterruptionDecisionProvider(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
index 5ed82cc..5c844bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
@@ -55,6 +55,12 @@
      * invoking [get].
      */
     val renderList: List<Key> = emptyList(),
+
+    /**
+     * Map of notification key to rank, where rank is the 0-based index of the notification on the
+     * system server, meaning that in the unfiltered flattened list of notification entries.
+     */
+    val rankingsMap: Map<String, Int> = emptyMap()
 ) {
     operator fun get(key: Key): ActiveNotificationEntryModel? {
         return when (key) {
@@ -74,8 +80,9 @@
         private val groups = mutableMapOf<String, ActiveNotificationGroupModel>()
         private val individuals = mutableMapOf<String, ActiveNotificationModel>()
         private val renderList = mutableListOf<Key>()
+        private var rankingsMap: Map<String, Int> = emptyMap()
 
-        fun build() = ActiveNotificationsStore(groups, individuals, renderList)
+        fun build() = ActiveNotificationsStore(groups, individuals, renderList, rankingsMap)
 
         fun addEntry(entry: ActiveNotificationEntryModel) {
             when (entry) {
@@ -95,5 +102,9 @@
             individuals[group.summary.key] = group.summary
             group.children.forEach { individuals[it.key] = it }
         }
+
+        fun setRankingsMap(map: Map<String, Int>) {
+            rankingsMap = map.toMap()
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index e90ddf9..b22e9fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -33,7 +33,10 @@
     private val repository: ActiveNotificationListRepository,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) {
-    /** Notifications actively presented to the user in the notification stack, in order. */
+    /**
+     * Top level list of Notifications actively presented to the user in the notification stack, in
+     * order.
+     */
     val topLevelRepresentativeNotifications: Flow<List<ActiveNotificationModel>> =
         repository.activeNotifications
             .map { store ->
@@ -51,6 +54,23 @@
             }
             .flowOn(backgroundDispatcher)
 
+    /**
+     * Flattened list of Notifications actively presented to the user in the notification stack, in
+     * order.
+     */
+    val allRepresentativeNotifications: Flow<Map<String, ActiveNotificationModel>> =
+        repository.activeNotifications.map { store -> store.individuals }
+
+    /** Size of the flattened list of Notifications actively presented in the stack. */
+    val allNotificationsCount: Flow<Int> =
+        repository.activeNotifications.map { store -> store.individuals.size }
+
+    /**
+     * The same as [allNotificationsCount], but without flows, for easy access in synchronous code.
+     */
+    val allNotificationsCountValue: Int
+        get() = repository.activeNotifications.value.individuals.size
+
     /** Are any notifications being actively presented in the notification stack? */
     val areAnyNotificationsPresent: Flow<Boolean> =
         repository.activeNotifications
@@ -65,6 +85,16 @@
     val areAnyNotificationsPresentValue: Boolean
         get() = repository.activeNotifications.value.renderList.isNotEmpty()
 
+    /**
+     * Map of notification key to rank, where rank is the 0-based index of the notification in the
+     * system server, meaning that in the unfiltered flattened list of notification entries. Used
+     * for logging purposes.
+     *
+     * @see [com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger].
+     */
+    val activeNotificationRanks: Flow<Map<String, Int>> =
+        repository.activeNotifications.map { store -> store.rankingsMap }
+
     /** Are there are any notifications that can be cleared by the "Clear all" button? */
     val hasClearableNotifications: Flow<Boolean> =
         repository.notifStats
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index 6f4ed9d..ab54bda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -16,6 +16,8 @@
 package com.android.systemui.statusbar.notification.domain.interactor
 
 import android.graphics.drawable.Icon
+import android.util.ArrayMap
+import com.android.app.tracing.traceSection
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -43,9 +45,12 @@
      * Sets the current list of rendered notification entries as displayed in the notification list.
      */
     fun setRenderedList(entries: List<ListEntry>) {
-        repository.activeNotifications.update { existingModels ->
-            buildActiveNotificationsStore(existingModels, sectionStyleProvider) {
-                entries.forEach(::addListEntry)
+        traceSection("RenderNotificationListInteractor.setRenderedList") {
+            repository.activeNotifications.update { existingModels ->
+                buildActiveNotificationsStore(existingModels, sectionStyleProvider) {
+                    entries.forEach(::addListEntry)
+                    setRankingsMap(entries)
+                }
             }
         }
     }
@@ -94,6 +99,27 @@
         }
     }
 
+    fun setRankingsMap(entries: List<ListEntry>) {
+        builder.setRankingsMap(flatMapToRankingsMap(entries))
+    }
+
+    fun flatMapToRankingsMap(entries: List<ListEntry>): Map<String, Int> {
+        val result = ArrayMap<String, Int>()
+        for (entry in entries) {
+            if (entry is NotificationEntry) {
+                entry.representativeEntry?.let { representativeEntry ->
+                    result[representativeEntry.key] = representativeEntry.ranking.rank
+                }
+            } else if (entry is GroupEntry) {
+                entry.summary?.let { summary -> result[summary.key] = summary.ranking.rank }
+                for (child in entry.children) {
+                    result[child.key] = child.ranking.rank
+                }
+            }
+        }
+        return result
+    }
+
     private fun NotificationEntry.toModel(): ActiveNotificationModel =
         existingModels.createOrReuse(
             key = key,
@@ -107,6 +133,11 @@
             aodIcon = icons.aodIcon?.sourceIcon,
             shelfIcon = icons.shelfIcon?.sourceIcon,
             statusBarIcon = icons.statusBarIcon?.sourceIcon,
+            uid = sbn.uid,
+            packageName = sbn.packageName,
+            instanceId = sbn.instanceId?.id,
+            isGroupSummary = sbn.notification.isGroupSummary,
+            bucket = bucket,
         )
 }
 
@@ -121,7 +152,12 @@
     isPulsing: Boolean,
     aodIcon: Icon?,
     shelfIcon: Icon?,
-    statusBarIcon: Icon?
+    statusBarIcon: Icon?,
+    uid: Int,
+    packageName: String,
+    instanceId: Int?,
+    isGroupSummary: Boolean,
+    bucket: Int,
 ): ActiveNotificationModel {
     return individuals[key]?.takeIf {
         it.isCurrent(
@@ -135,7 +171,12 @@
             isPulsing = isPulsing,
             aodIcon = aodIcon,
             shelfIcon = shelfIcon,
-            statusBarIcon = statusBarIcon
+            statusBarIcon = statusBarIcon,
+            uid = uid,
+            instanceId = instanceId,
+            isGroupSummary = isGroupSummary,
+            packageName = packageName,
+            bucket = bucket,
         )
     }
         ?: ActiveNotificationModel(
@@ -150,6 +191,11 @@
             aodIcon = aodIcon,
             shelfIcon = shelfIcon,
             statusBarIcon = statusBarIcon,
+            uid = uid,
+            instanceId = instanceId,
+            isGroupSummary = isGroupSummary,
+            packageName = packageName,
+            bucket = bucket,
         )
 }
 
@@ -164,7 +210,12 @@
     isPulsing: Boolean,
     aodIcon: Icon?,
     shelfIcon: Icon?,
-    statusBarIcon: Icon?
+    statusBarIcon: Icon?,
+    uid: Int,
+    packageName: String,
+    instanceId: Int?,
+    isGroupSummary: Boolean,
+    bucket: Int,
 ): Boolean {
     return when {
         key != this.key -> false
@@ -178,6 +229,11 @@
         aodIcon != this.aodIcon -> false
         shelfIcon != this.shelfIcon -> false
         statusBarIcon != this.statusBarIcon -> false
+        uid != this.uid -> false
+        instanceId != this.instanceId -> false
+        isGroupSummary != this.isGroupSummary -> false
+        packageName != this.packageName -> false
+        bucket != this.bucket -> false
         else -> true
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index ae77288..f375ebc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -142,7 +142,7 @@
 
     /** Binds to [NotificationIconContainer.setAnimationsEnabled] */
     private suspend fun Flow<Boolean>.bindAnimationsEnabled(view: NotificationIconContainer) {
-        collect(view::setAnimationsEnabled)
+        collectTracingEach("NIC#bindAnimationsEnabled", view::setAnimationsEnabled)
     }
 
     private suspend fun NotificationIconContainerStatusBarViewModel.bindIsolatedIcon(
@@ -151,12 +151,12 @@
     ) {
         coroutineScope {
             launch {
-                isolatedIconLocation.collect { location ->
+                isolatedIconLocation.collectTracingEach("NIC#isolatedIconLocation") { location ->
                     view.setIsolatedIconLocation(location, true)
                 }
             }
             launch {
-                isolatedIcon.collect { iconInfo ->
+                isolatedIcon.collectTracingEach("NIC#showIconIsolated") { iconInfo ->
                     val iconView = iconInfo.value?.let { viewStore.iconView(it.notifKey) }
                     if (iconInfo.isAnimating) {
                         view.showIconIsolatedAnimated(iconView, iconInfo::stopAnimating)
@@ -187,7 +187,7 @@
             configuration.getDimensionPixelSize(RInternal.dimen.status_bar_icon_size_sp)
         val iconHorizontalPaddingFlow: Flow<Int> =
             configuration.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin)
-        val layoutParams: Flow<FrameLayout.LayoutParams> =
+        val layoutParams: StateFlow<FrameLayout.LayoutParams> =
             combine(iconSizeFlow, iconHorizontalPaddingFlow, systemBarUtilsState.statusBarHeight) {
                     iconSize,
                     iconHPadding,
@@ -206,7 +206,7 @@
 
     private suspend fun Flow<NotificationIconsViewData>.bindIcons(
         view: NotificationIconContainer,
-        layoutParams: Flow<FrameLayout.LayoutParams>,
+        layoutParams: StateFlow<FrameLayout.LayoutParams>,
         notifyBindingFailures: (Collection<String>) -> Unit,
         viewStore: IconViewStore,
         bindIcon: suspend (iconKey: String, view: StatusBarIconView) -> Unit,
@@ -214,7 +214,7 @@
         val failedBindings = mutableSetOf<String>()
         val boundViewsByNotifKey = ArrayMap<String, Pair<StatusBarIconView, Job>>()
         var prevIcons = NotificationIconsViewData()
-        collectTracingEach("NotifIconContainer#bindIcons") { iconsData: NotificationIconsViewData ->
+        collectTracingEach("NIC#bindIcons") { iconsData: NotificationIconsViewData ->
             val iconsDiff = NotificationIconsViewData.computeDifference(iconsData, prevIcons)
             prevIcons = iconsData
 
@@ -259,13 +259,19 @@
                             // added again.
                             removeTransientView(sbiv)
                         }
-                        view.addView(sbiv)
+                        view.addView(sbiv, layoutParams.value)
                         boundViewsByNotifKey.remove(notifKey)?.second?.cancel()
                         boundViewsByNotifKey[notifKey] =
                             Pair(
                                 sbiv,
                                 launch {
-                                    launch { layoutParams.collect { sbiv.layoutParams = it } }
+                                    launch {
+                                        layoutParams.collectTracingEach("SBIV#bindLayoutParams") {
+                                            if (it != sbiv.layoutParams) {
+                                                sbiv.layoutParams = it
+                                            }
+                                        }
+                                    }
                                     bindIcon(notifKey, sbiv)
                                 },
                             )
@@ -369,6 +375,7 @@
         )
     }
 
-private suspend fun <T> Flow<T>.collectTracingEach(tag: String, collector: (T) -> Unit) {
-    collect { traceSection(tag) { collector(it) } }
-}
+private suspend inline fun <T> Flow<T>.collectTracingEach(
+    tag: String,
+    crossinline collector: (T) -> Unit,
+) = collect { traceSection(tag) { collector(it) } }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
index 0331654..bfeaced 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
@@ -18,6 +18,7 @@
 
 import android.graphics.Rect
 import android.view.View
+import com.android.app.tracing.traceSection
 import com.android.internal.util.ContrastColorUtil
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
@@ -33,18 +34,18 @@
     //  view-model (which, at the time of this writing, does not yet exist).
 
     suspend fun bindColor(view: StatusBarIconView, color: Flow<Int>) {
-        color.collect { color ->
+        color.collectTracingEach("SBIV#bindColor") { color ->
             view.staticDrawableColor = color
             view.setDecorColor(color)
         }
     }
 
     suspend fun bindTintAlpha(view: StatusBarIconView, tintAlpha: Flow<Float>) {
-        tintAlpha.collect { amt -> view.setTintAlpha(amt) }
+        tintAlpha.collectTracingEach("SBIV#bindTintAlpha") { amt -> view.setTintAlpha(amt) }
     }
 
     suspend fun bindAnimationsEnabled(view: StatusBarIconView, allowAnimation: Flow<Boolean>) {
-        allowAnimation.collect(view::setAllowAnimation)
+        allowAnimation.collectTracingEach("SBIV#bindAnimationsEnabled", view::setAllowAnimation)
     }
 
     suspend fun bindIconColors(
@@ -52,7 +53,7 @@
         iconColors: Flow<NotificationIconColors>,
         contrastColorUtil: ContrastColorUtil,
     ) {
-        iconColors.collect { colors ->
+        iconColors.collectTracingEach("SBIV#bindIconColors") { colors ->
             val isPreL = java.lang.Boolean.TRUE == view.getTag(R.id.icon_is_pre_L)
             val isColorized = !isPreL || NotificationUtils.isGrayscale(view, contrastColorUtil)
             view.staticDrawableColor =
@@ -73,3 +74,8 @@
             /* bottom = */ top + height,
         )
     }
+
+private suspend inline fun <T> Flow<T>.collectTracingEach(
+    tag: String,
+    crossinline collector: (T) -> Unit,
+) = collect { traceSection(tag) { collector(it) } }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 7d1cca8..1677418 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -18,7 +18,6 @@
 
 import android.service.notification.StatusBarNotification
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
 import com.android.systemui.statusbar.NotificationListener
@@ -39,6 +38,7 @@
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.notification.logging.NotificationLogger
 import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.wm.shell.bubbles.Bubbles
 import dagger.Lazy
@@ -53,7 +53,9 @@
  * any initialization work that notifications require.
  */
 @SysUISingleton
-class NotificationsControllerImpl @Inject constructor(
+class NotificationsControllerImpl
+@Inject
+constructor(
     private val notificationListener: NotificationListener,
     private val commonNotifCollection: Lazy<CommonNotifCollection>,
     private val notifPipeline: Lazy<NotifPipeline>,
@@ -61,7 +63,7 @@
     private val targetSdkResolver: TargetSdkResolver,
     private val notifPipelineInitializer: Lazy<NotifPipelineInitializer>,
     private val notifBindPipelineInitializer: NotifBindPipelineInitializer,
-    private val notificationLogger: NotificationLogger,
+    private val notificationLoggerOptional: Optional<NotificationLogger>,
     private val notificationRowBinder: NotificationRowBinderImpl,
     private val notificationsMediaManager: NotificationMediaManager,
     private val headsUpViewBinder: HeadsUpViewBinder,
@@ -69,7 +71,6 @@
     private val animatedImageNotificationManager: AnimatedImageNotificationManager,
     private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
     private val bubblesOptional: Optional<Bubbles>,
-    private val featureFlags: FeatureFlags
 ) : NotificationsController {
 
     override fun initialize(
@@ -80,28 +81,35 @@
     ) {
         notificationListener.registerAsSystemService()
 
-        notifPipeline.get().addCollectionListener(object : NotifCollectionListener {
-            override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
-                listContainer.cleanUpViewStateForEntry(entry)
-            }
-        })
+        notifPipeline
+            .get()
+            .addCollectionListener(
+                object : NotifCollectionListener {
+                    override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+                        listContainer.cleanUpViewStateForEntry(entry)
+                    }
+                }
+            )
 
         notificationRowBinder.setNotificationClicker(
-            clickerBuilder.build(bubblesOptional, notificationActivityStarter))
+            clickerBuilder.build(bubblesOptional, notificationActivityStarter)
+        )
         notificationRowBinder.setUpWithPresenter(presenter, listContainer)
         headsUpViewBinder.setPresenter(presenter)
         notifBindPipelineInitializer.initialize()
         animatedImageNotificationManager.bind()
 
-        notifPipelineInitializer.get().initialize(
-                notificationListener,
-                notificationRowBinder,
-                listContainer,
-                stackController)
+        notifPipelineInitializer
+            .get()
+            .initialize(notificationListener, notificationRowBinder, listContainer, stackController)
 
         targetSdkResolver.initialize(notifPipeline.get())
         notificationsMediaManager.setUpWithPresenter(presenter)
-        notificationLogger.setUpWithContainer(listContainer)
+        if (!NotificationsLiveDataStoreRefactor.isEnabled) {
+            notificationLoggerOptional.ifPresent { logger ->
+                logger.setUpWithContainer(listContainer)
+            }
+        }
         peopleSpaceWidgetManager.attach(notificationListener)
     }
 
@@ -120,11 +128,14 @@
             notificationListener.snoozeNotification(sbn.key, snoozeOption.snoozeCriterion.id)
         } else {
             notificationListener.snoozeNotification(
-                    sbn.key,
-                    snoozeOption.minutesToSnoozeFor * 60 * 1000.toLong())
+                sbn.key,
+                snoozeOption.minutesToSnoozeFor * 60 * 1000.toLong()
+            )
         }
     }
 
-    override fun getActiveNotificationsCount(): Int =
-        notifLiveDataStore.activeNotifCount.value
+    override fun getActiveNotificationsCount(): Int {
+        NotificationsLiveDataStoreRefactor.assertInLegacyMode()
+        return notifLiveDataStore.activeNotifCount.value
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index aca8b64..342828c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -6,6 +6,7 @@
 import android.net.Uri
 import android.os.Handler
 import android.os.HandlerExecutor
+import android.os.HandlerThread
 import android.os.UserHandle
 import android.provider.Settings
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -87,6 +88,7 @@
             secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS)
     private val onStateChangedListeners = ListenerSet<Consumer<String>>()
     private var hideSilentNotificationsOnLockscreen: Boolean = false
+    private val handlerThread: HandlerThread = HandlerThread("KeyguardNotificationVis")
 
     private val userTrackerCallback = object : UserTracker.Callback {
         override fun onUserChanged(newUser: Int, userContext: Context) {
@@ -154,7 +156,9 @@
                 notifyStateChanged("onStatusBarUpcomingStateChanged")
             }
         })
-        userTracker.addCallback(userTrackerCallback, HandlerExecutor(handler))
+        handlerThread.start()
+        userTracker.addCallback(userTrackerCallback,
+                   HandlerExecutor(handlerThread.getThreadHandler()))
     }
 
     override fun addOnStateChangedListener(listener: Consumer<String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 8d2a63e..c6832bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -33,6 +33,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
+import com.android.internal.statusbar.NotificationVisibility.NotificationLocation;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -46,8 +47,10 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationRowStatsLogger;
 import com.android.systemui.util.Compile;
 import com.android.systemui.util.kotlin.JavaAdapter;
 
@@ -64,7 +67,8 @@
  * Handles notification logging, in particular, logging which notifications are visible and which
  * are not.
  */
-public class NotificationLogger implements StateListener, CoreStartable {
+public class NotificationLogger implements StateListener, CoreStartable,
+        NotificationRowStatsLogger {
     static final String TAG = "NotificationLogger";
     private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
 
@@ -166,31 +170,31 @@
     /**
      * Returns the location of the notification referenced by the given {@link NotificationEntry}.
      */
-    public static NotificationVisibility.NotificationLocation getNotificationLocation(
+    public static NotificationLocation getNotificationLocation(
             NotificationEntry entry) {
         if (entry == null || entry.getRow() == null || entry.getRow().getViewState() == null) {
-            return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN;
+            return NotificationLocation.LOCATION_UNKNOWN;
         }
         return convertNotificationLocation(entry.getRow().getViewState().location);
     }
 
-    private static NotificationVisibility.NotificationLocation convertNotificationLocation(
+    private static NotificationLocation convertNotificationLocation(
             int location) {
         switch (location) {
             case ExpandableViewState.LOCATION_FIRST_HUN:
-                return NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP;
+                return NotificationLocation.LOCATION_FIRST_HEADS_UP;
             case ExpandableViewState.LOCATION_HIDDEN_TOP:
-                return NotificationVisibility.NotificationLocation.LOCATION_HIDDEN_TOP;
+                return NotificationLocation.LOCATION_HIDDEN_TOP;
             case ExpandableViewState.LOCATION_MAIN_AREA:
-                return NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA;
+                return NotificationLocation.LOCATION_MAIN_AREA;
             case ExpandableViewState.LOCATION_BOTTOM_STACK_PEEKING:
-                return NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_PEEKING;
+                return NotificationLocation.LOCATION_BOTTOM_STACK_PEEKING;
             case ExpandableViewState.LOCATION_BOTTOM_STACK_HIDDEN:
-                return NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_HIDDEN;
+                return NotificationLocation.LOCATION_BOTTOM_STACK_HIDDEN;
             case ExpandableViewState.LOCATION_GONE:
-                return NotificationVisibility.NotificationLocation.LOCATION_GONE;
+                return NotificationLocation.LOCATION_GONE;
             default:
-                return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN;
+                return NotificationLocation.LOCATION_UNKNOWN;
         }
     }
 
@@ -207,6 +211,9 @@
             JavaAdapter javaAdapter,
             ExpansionStateLogger expansionStateLogger,
             NotificationPanelLogger notificationPanelLogger) {
+        // Not expected to be constructed if the feature flag is on
+        NotificationsLiveDataStoreRefactor.assertInLegacyMode();
+
         mNotificationListener = notificationListener;
         mUiBgExecutor = uiBgExecutor;
         mNotifLiveDataStore = notifLiveDataStore;
@@ -240,6 +247,9 @@
 
     public void setUpWithContainer(NotificationListContainer listContainer) {
         mListContainer = listContainer;
+        if (mLogging) {
+            mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged);
+        }
     }
 
     @Override
@@ -287,7 +297,9 @@
                 lockscreen = mLockscreen != null && mLockscreen;
             }
             mNotificationPanelLogger.logPanelShown(lockscreen, getVisibleNotifications());
-            mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged);
+            if (mListContainer != null) {
+                mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged);
+            }
             // Sometimes, the transition from lockscreenOrShadeVisible=false ->
             // lockscreenOrShadeVisible=true doesn't cause the scroller to emit child location
             // events. Hence generate one ourselves to guarantee that we're reporting visible
@@ -382,9 +394,11 @@
     /**
      * Called when the notification is expanded / collapsed.
      */
-    public void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) {
-        NotificationVisibility.NotificationLocation location = mVisibilityProvider.getLocation(key);
-        mExpansionStateLogger.onExpansionChanged(key, isUserAction, isExpanded, location);
+    @Override
+    public void onNotificationExpansionChanged(@NonNull String key, boolean isExpanded,
+            int location, boolean isUserAction) {
+        NotificationLocation notifLocation = mVisibilityProvider.getLocation(key);
+        mExpansionStateLogger.onExpansionChanged(key, isUserAction, isExpanded, notifLocation);
     }
 
     @VisibleForTesting
@@ -440,7 +454,7 @@
 
         @VisibleForTesting
         void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded,
-                NotificationVisibility.NotificationLocation location) {
+                NotificationLocation location) {
             State state = getState(key);
             state.mIsUserAction = isUserAction;
             state.mIsExpanded = isExpanded;
@@ -528,7 +542,7 @@
             @Nullable
             Boolean mIsVisible;
             @Nullable
-            NotificationVisibility.NotificationLocation mLocation;
+            NotificationLocation mLocation;
 
             private State() {}
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
index 5ca13c9..6c63d1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
@@ -40,6 +40,11 @@
 
     /**
      * Log a NOTIFICATION_PANEL_REPORTED statsd event.
+     */
+    void logPanelShown(boolean isLockscreen, Notifications.NotificationList proto);
+
+    /**
+     * Log a NOTIFICATION_PANEL_REPORTED statsd event.
      * @param visibleNotifications as provided by NotificationEntryManager.getVisibleNotifications()
      */
     void logPanelShown(boolean isLockscreen,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java
index 9a63228..d7f7b76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerImpl.java
@@ -21,6 +21,7 @@
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.nano.Notifications;
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
 
 import com.google.protobuf.nano.MessageNano;
 
@@ -31,15 +32,25 @@
  * Normal implementation of NotificationPanelLogger.
  */
 public class NotificationPanelLoggerImpl implements NotificationPanelLogger {
+
+    @Override
+    public void logPanelShown(boolean isLockscreen, Notifications.NotificationList proto) {
+        SysUiStatsLog.write(SysUiStatsLog.NOTIFICATION_PANEL_REPORTED,
+                /* event_id = */ NotificationPanelEvent.fromLockscreen(isLockscreen).getId(),
+                /* num_notifications = */ proto.notifications.length,
+                /* notifications = */ MessageNano.toByteArray(proto));
+    }
+
     @Override
     public void logPanelShown(boolean isLockscreen,
             List<NotificationEntry> visibleNotifications) {
+        NotificationsLiveDataStoreRefactor.assertInLegacyMode();
         final Notifications.NotificationList proto = NotificationPanelLogger.toNotificationProto(
                 visibleNotifications);
         SysUiStatsLog.write(SysUiStatsLog.NOTIFICATION_PANEL_REPORTED,
-                /* int event_id */ NotificationPanelEvent.fromLockscreen(isLockscreen).getId(),
-                /* int num_notifications*/ proto.notifications.length,
-                /* byte[] notifications*/ MessageNano.toByteArray(proto));
+                /* event_id = */ NotificationPanelEvent.fromLockscreen(isLockscreen).getId(),
+                /* num_notifications = */ proto.notifications.length,
+                /* notifications = */ MessageNano.toByteArray(proto));
     }
 
     @Override
@@ -47,8 +58,8 @@
         final Notifications.NotificationList proto = NotificationPanelLogger.toNotificationProto(
                 Collections.singletonList(draggedNotification));
         SysUiStatsLog.write(SysUiStatsLog.NOTIFICATION_PANEL_REPORTED,
-                /* int event_id */ NOTIFICATION_DRAG.getId(),
-                /* int num_notifications*/ proto.notifications.length,
-                /* byte[] notifications*/ MessageNano.toByteArray(proto));
+                /* event_id = */ NOTIFICATION_DRAG.getId(),
+                /* num_notifications = */ proto.notifications.length,
+                /* notifications = */ MessageNano.toByteArray(proto));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
index d626c18..8ae324f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
@@ -44,6 +44,7 @@
     /**
      * Execute the stage asynchronously.
      *
+     * @param entry the NotificationEntry to bind
      * @param row notification top-level view to bind views to
      * @param callback callback after stage finishes
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 31ca106..5eeb066 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -118,6 +118,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BooleanSupplier;
@@ -273,12 +274,15 @@
     private final RefactorFlag mInlineReplyAnimation =
             RefactorFlag.forView(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION);
 
-    private static final boolean mSimulateSlowMeasure = Compile.IS_DEBUG && RefactorFlag.forView(
-            Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE).isEnabled();
+    private static boolean shouldSimulateSlowMeasure() {
+        return Compile.IS_DEBUG && RefactorFlag.forView(
+                Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE).isEnabled();
+    }
+
     private static final String SLOW_MEASURE_SIMULATE_DELAY_PROPERTY =
             "persist.notifications.extra_measure_delay_ms";
-    private static final int SLOW_MEASURE_SIMULATE_DELAY_MS = mSimulateSlowMeasure ?
-            SystemProperties.getInt(SLOW_MEASURE_SIMULATE_DELAY_PROPERTY, 150) : 0;
+    private static final int SLOW_MEASURE_SIMULATE_DELAY_MS =
+            SystemProperties.getInt(SLOW_MEASURE_SIMULATE_DELAY_PROPERTY, 150);
 
     // Listener will be called when receiving a long click event.
     // Use #setLongPressPosition to optionally assign positional data with the long press.
@@ -985,6 +989,25 @@
     }
 
     /**
+     * Recursively collects the [{@link ExpandableViewState#location}]s populating the provided
+     * map.
+     * The visibility of each child is determined by the {@link View#getVisibility()}.
+     * Locations are added to the provided map including locations from child views, that are
+     * visible.
+     */
+    public void collectVisibleLocations(Map<String, Integer> locationsMap) {
+        if (getVisibility() == View.VISIBLE) {
+            locationsMap.put(getEntry().getKey(), getViewState().location);
+            if (mChildrenContainer != null) {
+                List<ExpandableNotificationRow> children = mChildrenContainer.getAttachedChildren();
+                for (int i = 0; i < children.size(); i++) {
+                    children.get(i).collectVisibleLocations(locationsMap);
+                }
+            }
+        }
+    }
+
+    /**
      * Updates states of all children.
      */
     public void updateChildrenStates() {
@@ -1612,7 +1635,8 @@
         /**
          * Called when the notification is expanded / collapsed.
          */
-        void logNotificationExpansion(String key, boolean userAction, boolean expanded);
+        void logNotificationExpansion(String key, int location, boolean userAction,
+                boolean expanded);
 
         /**
          * Called when a notification which was previously kept in its parent for the
@@ -1886,7 +1910,7 @@
         }
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
-        if (Compile.IS_DEBUG && mSimulateSlowMeasure) {
+        if (shouldSimulateSlowMeasure()) {
             simulateExtraMeasureDelay();
         }
         Trace.endSection();
@@ -3309,7 +3333,8 @@
         if (nowExpanded != wasExpanded) {
             updateShelfIconColor();
             if (mLogger != null) {
-                mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded);
+                mLogger.logNotificationExpansion(mLoggingKey, getViewState().location, userAction,
+                        nowExpanded);
             }
             if (mIsSummaryWithChildren) {
                 mChildrenContainer.onExpansionChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index af55f44..0afdefa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -48,13 +48,13 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.collection.render.NodeController;
 import com.android.systemui.statusbar.notification.collection.render.NotifViewController;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.dagger.AppName;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationKey;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationRowStatsLogger;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.SmartReplyConstants;
@@ -90,7 +90,7 @@
     private final GroupMembershipManager mGroupMembershipManager;
     private final GroupExpansionManager mGroupExpansionManager;
     private final RowContentBindStage mRowContentBindStage;
-    private final NotificationLogger mNotificationLogger;
+    private final NotificationRowStatsLogger mStatsLogger;
     private final NotificationRowLogger mLogBufferLogger;
     private final HeadsUpManager mHeadsUpManager;
     private final ExpandableNotificationRow.OnExpandClickListener mOnExpandClickListener;
@@ -130,9 +130,10 @@
     private final ExpandableNotificationRow.ExpandableNotificationRowLogger mLoggerCallback =
             new ExpandableNotificationRow.ExpandableNotificationRowLogger() {
                 @Override
-                public void logNotificationExpansion(String key, boolean userAction,
+                public void logNotificationExpansion(String key, int location, boolean userAction,
                         boolean expanded) {
-                    mNotificationLogger.onExpansionChanged(key, userAction, expanded);
+                    mStatsLogger.onNotificationExpansionChanged(key, expanded, location,
+                            userAction);
                 }
 
                 @Override
@@ -212,7 +213,7 @@
             GroupMembershipManager groupMembershipManager,
             GroupExpansionManager groupExpansionManager,
             RowContentBindStage rowContentBindStage,
-            NotificationLogger notificationLogger,
+            NotificationRowStatsLogger statsLogger,
             HeadsUpManager headsUpManager,
             ExpandableNotificationRow.OnExpandClickListener onExpandClickListener,
             StatusBarStateController statusBarStateController,
@@ -239,7 +240,7 @@
         mGroupMembershipManager = groupMembershipManager;
         mGroupExpansionManager = groupExpansionManager;
         mRowContentBindStage = rowContentBindStage;
-        mNotificationLogger = notificationLogger;
+        mStatsLogger = statsLogger;
         mHeadsUpManager = headsUpManager;
         mOnExpandClickListener = onExpandClickListener;
         mStatusBarStateController = statusBarStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
index 43d99a0..6bc2b2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
@@ -16,19 +16,27 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.graphics.drawable.Icon;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.ViewStub;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ConversationLayout;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.NotificationFadeAware;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ConversationAvatar;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.FacePile;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleIcon;
 
 /**
  * A hybrid view which may contain information about one ore more conversations.
@@ -37,6 +45,7 @@
 
     private ImageView mConversationIconView;
     private TextView mConversationSenderName;
+    private ViewStub mConversationFacePileStub;
     private View mConversationFacePile;
     private int mSingleAvatarSize;
     private int mFacePileSize;
@@ -65,7 +74,16 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mConversationIconView = requireViewById(com.android.internal.R.id.conversation_icon);
-        mConversationFacePile = requireViewById(com.android.internal.R.id.conversation_face_pile);
+        if (AsyncHybridViewInflation.isEnabled()) {
+            mConversationFacePileStub =
+                    requireViewById(com.android.internal.R.id.conversation_face_pile);
+        } else {
+            // TODO(b/217799515): This usage is vague because mConversationFacePile represents both
+            //  View and ViewStub at different stages of View inflation, should be removed when
+            //  AsyncHybridViewInflation flag is removed
+            mConversationFacePile =
+                    requireViewById(com.android.internal.R.id.conversation_face_pile);
+        }
         mConversationSenderName = requireViewById(R.id.conversation_notification_sender);
         applyTextColor(mConversationSenderName, mSecondaryTextColor);
         mFacePileSize = getResources()
@@ -85,7 +103,8 @@
 
     @Override
     public void bind(@Nullable CharSequence title, @Nullable CharSequence text,
-            @Nullable View contentView) {
+                     @Nullable View contentView) {
+        AsyncHybridViewInflation.assertInLegacyMode();
         if (!(contentView instanceof ConversationLayout)) {
             super.bind(title, text, contentView);
             return;
@@ -137,6 +156,77 @@
         super.bind(conversationTitle, conversationText, conversationLayout);
     }
 
+    /**
+     * Set the avatar using ConversationAvatar from SingleLineViewModel
+     *
+     * @param conversationAvatar the icon needed for a single-line conversation view, it should be
+     *                           either an instance of SingleIcon or FacePile
+     */
+    public void setAvatar(@NonNull ConversationAvatar conversationAvatar) {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return;
+        if (conversationAvatar instanceof SingleIcon) {
+            SingleIcon avatar = (SingleIcon) conversationAvatar;
+            if (mConversationFacePile != null) mConversationFacePile.setVisibility(GONE);
+            mConversationIconView.setVisibility(VISIBLE);
+            mConversationIconView.setImageDrawable(avatar.getIconDrawable());
+            setSize(mConversationIconView, mSingleAvatarSize);
+            return;
+        }
+
+        // If conversationAvatar is not a SingleIcon, it should be a FacePile.
+        // Bind the face pile with it.
+        FacePile facePileModel = (FacePile) conversationAvatar;
+        mConversationIconView.setVisibility(GONE);
+        // Inflate mConversationFacePile from ViewStub
+        if (mConversationFacePile == null) {
+            mConversationFacePile = mConversationFacePileStub.inflate();
+        }
+        mConversationFacePile.setVisibility(VISIBLE);
+
+        ImageView facePileBottomBg = mConversationFacePile.requireViewById(
+                com.android.internal.R.id.conversation_face_pile_bottom_background);
+        ImageView facePileBottom = mConversationFacePile.requireViewById(
+                com.android.internal.R.id.conversation_face_pile_bottom);
+        ImageView facePileTop = mConversationFacePile.requireViewById(
+                com.android.internal.R.id.conversation_face_pile_top);
+
+        int bottomBackgroundColor = facePileModel.getBottomBackgroundColor();
+        facePileBottomBg.setImageTintList(ColorStateList.valueOf(bottomBackgroundColor));
+
+        facePileBottom.setImageDrawable(facePileModel.getBottomIconDrawable());
+        facePileTop.setImageDrawable(facePileModel.getTopIconDrawable());
+
+        setSize(mConversationFacePile, mFacePileSize);
+        setSize(facePileBottom, mFacePileAvatarSize);
+        setSize(facePileTop, mFacePileAvatarSize);
+        setSize(facePileBottomBg, mFacePileAvatarSize + 2 * mFacePileProtectionWidth);
+
+        mTransformationHelper.addViewTransformingToSimilar(facePileTop);
+        mTransformationHelper.addViewTransformingToSimilar(facePileBottom);
+        mTransformationHelper.addViewTransformingToSimilar(facePileBottomBg);
+
+    }
+
+    /**
+     * bind the text views
+     */
+    public void setText(
+            CharSequence titleText,
+            CharSequence contentText,
+            CharSequence conversationSenderName
+    ) {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return;
+        if (conversationSenderName == null) {
+            mConversationSenderName.setVisibility(GONE);
+        } else {
+            mConversationSenderName.setVisibility(VISIBLE);
+            mConversationSenderName.setText(conversationSenderName);
+        }
+        // TODO (b/217799515): super.bind() doesn't use contentView, remove the contentView
+        //  argument when the flag is removed
+        super.bind(/* title = */ titleText, /* text = */ contentText, /* contentView = */ null);
+    }
+
     private static void setSize(View view, int size) {
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) view.getLayoutParams();
         lp.width = size;
@@ -153,4 +243,9 @@
         super.setNotificationFaded(faded);
         NotificationFadeAware.setLayerTypeForFaded(mConversationFacePile, faded);
     }
+
+    @VisibleForTesting
+    TextView getConversationSenderNameView() {
+        return mConversationSenderName;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
index ddd9bdd..09c0349 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
@@ -32,6 +32,7 @@
 
 import com.android.internal.widget.ConversationLayout;
 import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
 
 /**
  * A class managing hybrid groups that include {@link HybridNotificationView} and the notification
@@ -41,6 +42,8 @@
 
     private final Context mContext;
 
+    private static final String TAG = "HybridGroupManager";
+
     private float mOverflowNumberSize;
     private int mOverflowNumberPadding;
 
@@ -93,21 +96,34 @@
     public HybridNotificationView bindFromNotification(HybridNotificationView reusableView,
             View contentView, StatusBarNotification notification,
             ViewGroup parent) {
+        AsyncHybridViewInflation.assertInLegacyMode();
         boolean isNewView = false;
         if (reusableView == null) {
             Trace.beginSection("HybridGroupManager#bindFromNotification");
             reusableView = inflateHybridView(contentView, parent);
             isNewView = true;
         }
-        CharSequence titleText = resolveTitle(notification.getNotification());
-        CharSequence contentText = resolveText(notification.getNotification());
-        reusableView.bind(titleText, contentText, contentView);
+
+        updateReusableView(reusableView, notification, contentView);
         if (isNewView) {
             Trace.endSection();
         }
         return reusableView;
     }
 
+    /**
+     * Update the HybridNotificationView (single-line view)'s appearance
+     */
+    public void updateReusableView(HybridNotificationView reusableView,
+            StatusBarNotification notification, View contentView) {
+        AsyncHybridViewInflation.assertInLegacyMode();
+        final CharSequence titleText = resolveTitle(notification.getNotification());
+        final CharSequence contentText = resolveText(notification.getNotification());
+        if (reusableView != null) {
+            reusableView.bind(titleText, contentText, contentView);
+        }
+    }
+
     @Nullable
     public static CharSequence resolveText(Notification notification) {
         CharSequence contentText = notification.extras.getCharSequence(Notification.EXTRA_TEXT);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
index d10b556..8bc8e8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
@@ -22,11 +22,9 @@
 import android.view.LayoutInflater
 import android.view.View
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
-import com.android.systemui.statusbar.notification.row.NotificationRowModule.NOTIF_REMOTEVIEWS_FACTORIES
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import javax.inject.Named
 
 /**
  * Implementation of [NotifLayoutInflaterFactory]. This class uses a set of
@@ -37,8 +35,7 @@
 constructor(
     @Assisted private val row: ExpandableNotificationRow,
     @Assisted @InflationFlag val layoutType: Int,
-    @Named(NOTIF_REMOTEVIEWS_FACTORIES)
-    private val remoteViewsFactories: Set<@JvmSuppressWildcards NotifRemoteViewsFactory>
+    private val notifRemoteViewsFactoryContainer: NotifRemoteViewsFactoryContainer
 ) : LayoutInflater.Factory2 {
 
     override fun onCreateView(
@@ -49,7 +46,7 @@
     ): View? {
         var handledFactory: NotifRemoteViewsFactory? = null
         var result: View? = null
-        for (layoutFactory in remoteViewsFactories) {
+        for (layoutFactory in notifRemoteViewsFactoryContainer.factories) {
             layoutFactory.instantiate(row, layoutType, parent, name, context, attrs)?.run {
                 check(handledFactory == null) {
                     "$layoutFactory tries to produce name:$name with type:$layoutType. " +
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
new file mode 100644
index 0000000..99177c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+
+interface NotifRemoteViewsFactoryContainer {
+    val factories: Set<NotifRemoteViewsFactory>
+}
+
+class NotifRemoteViewsFactoryContainerImpl
+@Inject
+constructor(
+    featureFlags: FeatureFlags,
+    precomputedTextViewFactory: PrecomputedTextViewFactory,
+    bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory,
+    callLayoutSetDataAsyncFactory: CallLayoutSetDataAsyncFactory,
+) : NotifRemoteViewsFactoryContainer {
+    override val factories: Set<NotifRemoteViewsFactory> = buildSet {
+        add(precomputedTextViewFactory)
+        if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) {
+            add(bigPictureLayoutInflaterFactory)
+        }
+        if (featureFlags.isEnabled(Flags.CALL_LAYOUT_ASYNC_SET_DATA)) {
+            add(callLayoutSetDataAsyncFactory)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index f186e66..913d5f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -20,6 +20,7 @@
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
+import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_SINGLELINE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -41,15 +42,19 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ImageMessageConsumer;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.media.controls.util.MediaFeatureFlag;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineConversationViewBinder;
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
@@ -135,7 +140,7 @@
         AsyncInflationTask task = new AsyncInflationTask(
                 mBgExecutor,
                 mInflateSynchronously,
-                contentToBind,
+                /* reInflateFlags = */ contentToBind,
                 mRemoteViewCache,
                 entry,
                 mConversationProcessor,
@@ -145,7 +150,7 @@
                 bindParams.usesIncreasedHeadsUpHeight,
                 callback,
                 mRemoteInputManager.getRemoteViewsOnClickHandler(),
-                mIsMediaInQS,
+                /* isMediaFlagEnabled = */ mIsMediaInQS,
                 mSmartReplyStateInflater,
                 mNotifLayoutInflaterFactoryProvider,
                 mLogger);
@@ -178,6 +183,29 @@
 
         result = inflateSmartReplyViews(result, reInflateFlags, entry, row.getContext(),
                 packageContext, row.getExistingSmartReplyState(), smartRepliesInflater, mLogger);
+        if (AsyncHybridViewInflation.isEnabled()) {
+            boolean isConversation = entry.getRanking().isConversation();
+            Notification.MessagingStyle messagingStyle = null;
+            if (isConversation) {
+                messagingStyle = mConversationProcessor
+                        .processNotification(entry, builder, mLogger);
+            }
+            result.mInflatedSingleLineViewModel = SingleLineViewInflater
+                    .inflateSingleLineViewModel(
+                            entry.getSbn().getNotification(),
+                            messagingStyle,
+                            builder,
+                            row.getContext()
+                    );
+            result.mInflatedSingleLineViewHolder =
+                    SingleLineViewInflater.inflateSingleLineViewHolder(
+                            isConversation,
+                            reInflateFlags,
+                            entry,
+                            row.getContext(),
+                            mLogger
+                    );
+        }
 
         apply(
                 mBgExecutor,
@@ -255,6 +283,15 @@
                     mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC);
                 });
                 break;
+            case FLAG_CONTENT_VIEW_SINGLE_LINE: {
+                if (AsyncHybridViewInflation.isEnabled()) {
+                    row.getPrivateLayout().performWhenContentInactive(
+                            VISIBLE_TYPE_SINGLELINE,
+                            () -> row.getPrivateLayout().setSingleLineView(null)
+                    );
+                }
+                break;
+            }
             default:
                 break;
         }
@@ -282,6 +319,10 @@
         if ((contentViews & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
             row.getPublicLayout().removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED);
         }
+        if (AsyncHybridViewInflation.isEnabled()
+                && (contentViews & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) {
+            row.getPrivateLayout().removeContentInactiveRunnable(VISIBLE_TYPE_SINGLELINE);
+        }
     }
 
     private static InflationProgress inflateSmartReplyViews(
@@ -772,6 +813,25 @@
                 }
                 setRepliesAndActions = true;
             }
+
+            if (AsyncHybridViewInflation.isEnabled()
+                    && (reInflateFlags & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) {
+                HybridNotificationView viewHolder = result.mInflatedSingleLineViewHolder;
+                SingleLineViewModel viewModel = result.mInflatedSingleLineViewModel;
+                if (viewHolder != null && viewModel != null) {
+                    if (viewModel.isConversation()) {
+                        SingleLineConversationViewBinder.bind(
+                                result.mInflatedSingleLineViewModel,
+                                result.mInflatedSingleLineViewHolder
+                        );
+                    } else {
+                        SingleLineViewBinder.bind(result.mInflatedSingleLineViewModel,
+                                result.mInflatedSingleLineViewHolder);
+                    }
+                    privateLayout.setSingleLineView(result.mInflatedSingleLineViewHolder);
+                }
+            }
+
             if (setRepliesAndActions) {
                 privateLayout.setInflatedSmartReplyState(result.inflatedSmartReplyState);
             }
@@ -941,19 +1001,23 @@
                     // For all of our templates, we want it to be RTL
                     packageContext = new RtlEnabledContext(packageContext);
                 }
-                if (mEntry.getRanking().isConversation()) {
-                    mConversationProcessor.processNotification(mEntry, recoveredBuilder, mLogger);
+                boolean isConversation = mEntry.getRanking().isConversation();
+                Notification.MessagingStyle messagingStyle = null;
+                if (isConversation) {
+                    messagingStyle = mConversationProcessor.processNotification(
+                            mEntry, recoveredBuilder, mLogger);
                 }
                 InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
                         recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
                         mUsesIncreasedHeadsUpHeight, packageContext, mRow,
                         mNotifLayoutInflaterFactoryProvider, mLogger);
+
                 mLogger.logAsyncTaskProgress(mEntry,
                         "getting existing smart reply state (on wrong thread!)");
                 InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState();
                 mLogger.logAsyncTaskProgress(mEntry, "inflating smart reply views");
                 InflationProgress result = inflateSmartReplyViews(
-                        inflationProgress,
+                        /* result = */ inflationProgress,
                         mReInflateFlags,
                         mEntry,
                         mContext,
@@ -962,6 +1026,27 @@
                         mSmartRepliesInflater,
                         mLogger);
 
+                if (AsyncHybridViewInflation.isEnabled()) {
+                    // Inflate the single-line content view's ViewModel and ViewHolder from the
+                    // background thread, the ViewHolder needs to be bind with ViewModel later from
+                    // the main thread.
+                    result.mInflatedSingleLineViewModel = SingleLineViewInflater
+                            .inflateSingleLineViewModel(
+                                    mEntry.getSbn().getNotification(),
+                                    messagingStyle,
+                                    recoveredBuilder,
+                                    mContext
+                            );
+                    result.mInflatedSingleLineViewHolder =
+                            SingleLineViewInflater.inflateSingleLineViewHolder(
+                                    isConversation,
+                                    mReInflateFlags,
+                                    mEntry,
+                                    mContext,
+                                    mLogger
+                            );
+                }
+
                 mLogger.logAsyncTaskProgress(mEntry,
                         "getting row image resolver (on wrong thread!)");
                 final NotificationInlineImageResolver imageResolver = mRow.getImageResolver();
@@ -1078,6 +1163,11 @@
         private InflatedSmartReplyState inflatedSmartReplyState;
         private InflatedSmartReplyViewHolder expandedInflatedSmartReplies;
         private InflatedSmartReplyViewHolder headsUpInflatedSmartReplies;
+
+        // ViewModel for SingleLineView, holds the UI State
+        SingleLineViewModel mInflatedSingleLineViewModel;
+        // Inflated SingleLineViewHolder, SingleLineView that lacks the UI State
+        HybridNotificationView mInflatedSingleLineViewHolder;
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt
index 4f5455d..ee9462c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
 import javax.inject.Inject
 
@@ -99,6 +100,26 @@
         )
     }
 
+    fun logInflateSingleLine(
+        entry: NotificationEntry,
+        @InflationFlag inflationFlags: Int,
+        isConversation: Boolean
+    ) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = entry.logKey
+                int1 = inflationFlags
+                bool1 = isConversation
+            },
+            {
+                "inflateSingleLineView, inflationFlags: ${flagToString(int1)} for $str1, " +
+                    "isConversation: $bool1"
+            }
+        )
+    }
+
     companion object {
         fun flagToString(@InflationFlag flag: Int): String {
             if (flag == 0) {
@@ -121,6 +142,9 @@
             if (flag and FLAG_CONTENT_VIEW_PUBLIC != 0) {
                 l.add("PUBLIC")
             }
+            if (flag and FLAG_CONTENT_VIEW_SINGLE_LINE != 0) {
+                l.add("SINGLE_LINE")
+            }
             return l.joinToString("|")
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 6528cef3..402ea51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -37,6 +37,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -45,8 +46,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.res.R;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.TransformableView;
@@ -56,6 +57,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationCustomViewWrapper;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
 import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
@@ -85,7 +87,7 @@
     public static final int VISIBLE_TYPE_CONTRACTED = 0;
     public static final int VISIBLE_TYPE_EXPANDED = 1;
     public static final int VISIBLE_TYPE_HEADSUP = 2;
-    private static final int VISIBLE_TYPE_SINGLELINE = 3;
+    public static final int VISIBLE_TYPE_SINGLELINE = 3;
     /**
      * Used when there is no content on the view such as when we're a public layout but don't
      * need to show.
@@ -97,6 +99,7 @@
     private final Rect mClipBounds = new Rect();
 
     private int mMinContractedHeight;
+    private int mMinSingleLineHeight;
     private View mContractedChild;
     private View mExpandedChild;
     private View mHeadsUpChild;
@@ -233,6 +236,11 @@
     public void reinflate() {
         mMinContractedHeight = getResources().getDimensionPixelSize(
                 R.dimen.min_notification_layout_height);
+        if (AsyncHybridViewInflation.isEnabled()) {
+            //TODO: set the height with a more reasonable min single-line height
+            mMinSingleLineHeight = getResources().getDimensionPixelSize(
+                    R.dimen.conversation_single_line_face_pile_size);
+        }
     }
 
     public void setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight) {
@@ -539,6 +547,28 @@
         updateShownWrapper(mVisibleType);
     }
 
+    /**
+     * Sets the single-line view. Child may be null to remove the view.
+     * @param child single-line content view to set
+     */
+    public void setSingleLineView(@Nullable HybridNotificationView child) {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return;
+        if (mSingleLineView != null) {
+            mOnContentViewInactiveListeners.remove(mSingleLineView);
+            mSingleLineView.animate().cancel();
+            removeView(mSingleLineView);
+        }
+        if (child == null) {
+            mSingleLineView = null;
+            if (mTransformationStartVisibleType == VISIBLE_TYPE_SINGLELINE) {
+                mTransformationStartVisibleType = VISIBLE_TYPE_NONE;
+            }
+            return;
+        }
+        addView(child);
+        mSingleLineView = child;
+    }
+
     @Override
     public void onViewAdded(View child) {
         super.onViewAdded(child);
@@ -808,7 +838,17 @@
             return mContractedChild != null
                     ? getViewHeight(VISIBLE_TYPE_CONTRACTED) : mMinContractedHeight;
         } else {
-            return mSingleLineView.getHeight();
+            if (AsyncHybridViewInflation.isEnabled()) {
+                if (mSingleLineView != null) {
+                    return getViewHeight(VISIBLE_TYPE_SINGLELINE);
+                } else {
+                    Log.wtf(TAG, "getMinHeight: mSingleLineView == null");
+                    return mMinSingleLineHeight;
+                }
+            } else {
+                AsyncHybridViewInflation.assertInLegacyMode();
+                return mSingleLineView.getHeight();
+            }
         }
     }
 
@@ -898,6 +938,7 @@
         // forceUpdateVisibilities cancels outstanding animations without updating the
         // mAnimationStartVisibleType. Do so here instead.
         mAnimationStartVisibleType = VISIBLE_TYPE_NONE;
+        notifySubtreeForAccessibilityContentChange();
     }
 
     private void fireExpandedVisibleListenerIfVisible() {
@@ -980,6 +1021,7 @@
         // updateViewVisibilities cancels outstanding animations without updating the
         // mAnimationStartVisibleType. Do so here instead.
         mAnimationStartVisibleType = VISIBLE_TYPE_NONE;
+        notifySubtreeForAccessibilityContentChange();
     }
 
     private void updateViewVisibility(int visibleType, int type, View view,
@@ -1029,6 +1071,7 @@
                     hiddenView.setVisible(false);
                 }
                 mAnimationStartVisibleType = VISIBLE_TYPE_NONE;
+                notifySubtreeForAccessibilityContentChange();
             }
         });
         fireExpandedVisibleListenerIfVisible();
@@ -1049,6 +1092,22 @@
         }
     }
 
+    @Override
+    public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) {
+        if (isAnimatingVisibleType()) {
+            // Don't send A11y events while animating to reduce Jank.
+            return;
+        }
+        super.notifySubtreeAccessibilityStateChanged(child, source, changeType);
+    }
+
+    private void notifySubtreeForAccessibilityContentChange() {
+        if (mParent != null) {
+            mParent.notifySubtreeAccessibilityStateChanged(this, this,
+                    AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+        }
+    }
+
     /**
      * @param visibleType one of the static enum types in this view
      * @return the corresponding transformable view according to the given visible type
@@ -1244,19 +1303,30 @@
     }
 
     private void updateSingleLineView() {
-        if (mIsChildInGroup) {
+        try {
             Trace.beginSection("NotifContentView#updateSingleLineView");
-            boolean isNewView = mSingleLineView == null;
-            mSingleLineView = mHybridGroupManager.bindFromNotification(
-                    mSingleLineView, mContractedChild, mNotificationEntry.getSbn(), this);
-            if (isNewView) {
-                updateViewVisibility(mVisibleType, VISIBLE_TYPE_SINGLELINE,
-                        mSingleLineView, mSingleLineView);
+            if (AsyncHybridViewInflation.isEnabled()) {
+                return;
             }
+            AsyncHybridViewInflation.assertInLegacyMode();
+            if (mIsChildInGroup) {
+                boolean isNewView = mSingleLineView == null;
+                mSingleLineView = mHybridGroupManager.bindFromNotification(
+                        /* reusableView = */ mSingleLineView,
+                        /* contentView = */ mContractedChild,
+                        /* notification = */ mNotificationEntry.getSbn(),
+                        /* parent = */ this
+                );
+                if (isNewView && mSingleLineView != null) {
+                    updateViewVisibility(mVisibleType, VISIBLE_TYPE_SINGLELINE,
+                            mSingleLineView, mSingleLineView);
+                }
+            } else if (mSingleLineView != null) {
+                removeView(mSingleLineView);
+                mSingleLineView = null;
+            }
+        } finally {
             Trace.endSection();
-        } else if (mSingleLineView != null) {
-            removeView(mSingleLineView);
-            mSingleLineView = null;
         }
     }
 
@@ -2277,6 +2347,11 @@
         mHeadsUpWrapper = headsUpWrapper;
     }
 
+    @VisibleForTesting
+    protected void setAnimationStartVisibleType(int animationStartVisibleType) {
+        mAnimationStartVisibleType = animationStartVisibleType;
+    }
+
     @Override
     protected void dispatchDraw(Canvas canvas) {
         try {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
index d7b7aa2..736140c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
@@ -80,6 +80,7 @@
                     FLAG_CONTENT_VIEW_EXPANDED,
                     FLAG_CONTENT_VIEW_HEADS_UP,
                     FLAG_CONTENT_VIEW_PUBLIC,
+                    FLAG_CONTENT_VIEW_SINGLE_LINE,
                     FLAG_CONTENT_VIEW_ALL})
     @interface InflationFlag {}
     /**
@@ -102,7 +103,12 @@
      */
     int FLAG_CONTENT_VIEW_PUBLIC = 1 << 3;
 
-    int FLAG_CONTENT_VIEW_ALL = (1 << 4) - 1;
+    /**
+     * The single line notification view. Show when the notification is shown as a child in group.
+     */
+    int FLAG_CONTENT_VIEW_SINGLE_LINE = 1 << 4;
+
+    int FLAG_CONTENT_VIEW_ALL = (1 << 5) - 1;
 
     /**
      * Parameters for content view binding
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
index 3a59978..200a08a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -17,26 +17,15 @@
 package com.android.systemui.statusbar.notification.row;
 
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 
 import dagger.Binds;
 import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.ElementsIntoSet;
-
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.inject.Named;
 
 /**
  * Dagger Module containing notification row and view inflation implementations.
  */
 @Module
 public abstract class NotificationRowModule {
-    public static final String NOTIF_REMOTEVIEWS_FACTORIES =
-            "notif_remoteviews_factories";
 
     /**
      * Provides notification row content binder instance.
@@ -54,26 +43,11 @@
     public abstract NotifRemoteViewCache provideNotifRemoteViewCache(
             NotifRemoteViewCacheImpl cacheImpl);
 
-    /** Provides view factories to be inflated in notification content. */
-    @Provides
-    @ElementsIntoSet
-    @Named(NOTIF_REMOTEVIEWS_FACTORIES)
-    static Set<NotifRemoteViewsFactory> provideNotifRemoteViewsFactories(
-            FeatureFlags featureFlags,
-            PrecomputedTextViewFactory precomputedTextViewFactory,
-            BigPictureLayoutInflaterFactory bigPictureLayoutInflaterFactory,
-            CallLayoutSetDataAsyncFactory callLayoutSetDataAsyncFactory
-    ) {
-        final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>();
-        if (featureFlags.isEnabled(Flags.PRECOMPUTED_TEXT)) {
-            replacementFactories.add(precomputedTextViewFactory);
-        }
-        if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) {
-            replacementFactories.add(bigPictureLayoutInflaterFactory);
-        }
-        if (featureFlags.isEnabled(Flags.CALL_LAYOUT_ASYNC_SET_DATA)) {
-            replacementFactories.add(callLayoutSetDataAsyncFactory);
-        }
-        return replacementFactories;
-    }
+    /**
+     * Provides notification remote view factory container
+     */
+    @Binds
+    @SysUISingleton
+    public abstract NotifRemoteViewsFactoryContainer provideNotifRemoteViewsFactoryContainer(
+            NotifRemoteViewsFactoryContainerImpl containerImpl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
index 3443da1..99a6f6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
@@ -45,13 +45,15 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
+
 import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.res.R;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
+import com.android.systemui.res.R;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -77,6 +79,9 @@
     private static final LogMaker UNDO_LOG =
             new LogMaker(MetricsEvent.NOTIFICATION_UNDO_SNOOZE)
                     .setType(MetricsEvent.TYPE_ACTION);
+
+    private static final String PARAGRAPH_SEPARATOR = "\u2029";
+
     private NotificationGuts mGutsContainer;
     private NotificationSwipeActionHelper mSnoozeListener;
     private StatusBarNotification mSbn;
@@ -111,8 +116,7 @@
     }
 
     @VisibleForTesting
-    SnoozeOption getDefaultOption()
-    {
+    SnoozeOption getDefaultOption() {
         return mDefaultOption;
     }
 
@@ -130,6 +134,8 @@
         mSelectedOptionText = (TextView) findViewById(R.id.snooze_option_default);
         mUndoButton = (TextView) findViewById(R.id.undo);
         mUndoButton.setOnClickListener(this);
+        mUndoButton.setContentDescription(
+                getContext().getString(R.string.snooze_undo_content_description));
         mExpandButton = (ImageView) findViewById(R.id.expand_button);
         mDivider = findViewById(R.id.divider);
         mDivider.setAlpha(0f);
@@ -163,6 +169,46 @@
                 info.addAction(action);
             }
         }
+
+        mSnoozeView.setAccessibilityDelegate(new AccessibilityDelegate() {
+            @Override
+            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+                super.onInitializeAccessibilityNodeInfo(host, info);
+                // Replace "Double tap to activate" prompt with "Double tap to expand/collapse"
+                AccessibilityAction customClick = new AccessibilityAction(
+                        AccessibilityNodeInfo.ACTION_CLICK, getExpandActionString());
+                info.addAction(customClick);
+            }
+        });
+    }
+
+    /**
+     * Update the content description of the snooze view based on the snooze option and whether the
+     * snooze options are expanded or not.
+     * For example, this will be something like "Collapsed\u2029Snooze for 1 hour". The paragraph
+     * separator is added to introduce a break in speech, to match what TalkBack does by default
+     * when you e.g. press on a notification.
+     */
+    private void updateContentDescription() {
+        mSnoozeView.setContentDescription(
+                getExpandStateString() + PARAGRAPH_SEPARATOR + mSelectedOptionText.getText());
+    }
+
+    /** Returns "collapse" if the snooze options are expanded, or "expand" otherwise. */
+    @NonNull
+    private String getExpandActionString() {
+        return mContext.getString(mExpanded
+                ? com.android.internal.R.string.expand_button_content_description_expanded
+                : com.android.internal.R.string.expand_button_content_description_collapsed);
+    }
+
+
+    /** Returns "expanded" if the snooze options are expanded, or "collapsed" otherwise. */
+    @NonNull
+    private String getExpandStateString() {
+        return mContext.getString(
+                (mExpanded ? com.android.internal.R.string.content_description_expanded
+                        : com.android.internal.R.string.content_description_collapsed));
     }
 
     @Override
@@ -179,6 +225,8 @@
             if (so.getAccessibilityAction() != null
                     && so.getAccessibilityAction().getId() == action) {
                 setSelected(so, true);
+                mSnoozeView.sendAccessibilityEvent(
+                        AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
                 return true;
             }
         }
@@ -290,11 +338,10 @@
         int drawableId = show ? com.android.internal.R.drawable.ic_collapse_notification
                 : com.android.internal.R.drawable.ic_expand_notification;
         mExpandButton.setImageResource(drawableId);
-        mExpandButton.setContentDescription(mContext.getString(show
-                ? com.android.internal.R.string.expand_button_content_description_expanded
-                : com.android.internal.R.string.expand_button_content_description_collapsed));
+        mExpandButton.setContentDescription(getExpandActionString());
         if (mExpanded != show) {
             mExpanded = show;
+            updateContentDescription();
             animateSnoozeOptions(show);
             if (mGutsContainer != null) {
                 mGutsContainer.onHeightChanged();
@@ -335,8 +382,11 @@
     }
 
     private void setSelected(SnoozeOption option, boolean userAction) {
-        mSelectedOption = option;
-        mSelectedOptionText.setText(option.getConfirmation());
+        if (option != mSelectedOption) {
+            mSelectedOption = option;
+            mSelectedOptionText.setText(option.getConfirmation());
+            updateContentDescription();
+        }
         showSnoozeOptions(false);
         hideSelectedOption();
         if (userAction) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
index a52f638..1494c27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
@@ -102,9 +102,9 @@
      * @see InflationFlag
      */
     public void markContentViewsFreeable(@InflationFlag int contentViews) {
-        @InflationFlag int existingContentViews = contentViews &= mContentViews;
+        @InflationFlag int existingFreeableContentViews = contentViews &= mContentViews;
         mContentViews &= ~contentViews;
-        mDirtyContentViews |= existingContentViews;
+        mDirtyContentViews |= existingFreeableContentViews;
     }
 
     public @InflationFlag int getContentViews() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
index b70da00..f4f8374 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
@@ -63,7 +63,10 @@
         @InflationFlag int inflationFlags = params.getContentViews();
         @InflationFlag int invalidatedFlags = params.getDirtyContentViews();
 
+        // Rebind the content views which are needed now, and the corresponding old views are
+        // invalidated
         @InflationFlag int contentToBind = invalidatedFlags & inflationFlags;
+        // Unbind the content views that are not needed
         @InflationFlag int contentToUnbind = inflationFlags ^ FLAG_CONTENT_VIEW_ALL;
 
         // Bind/unbind with parameters
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
new file mode 100644
index 0000000..d6118a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.app.Notification
+import android.app.Notification.MessagingStyle
+import android.app.Person
+import android.content.Context
+import android.graphics.drawable.Icon
+import android.util.Log
+import android.view.LayoutInflater
+import com.android.app.tracing.traceSection
+import com.android.internal.R
+import com.android.internal.widget.MessagingMessage
+import com.android.internal.widget.PeopleHelper
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ConversationAvatar
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ConversationData
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.FacePile
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleIcon
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel
+
+/** The inflater of SingleLineViewModel and SingleLineViewHolder */
+internal object SingleLineViewInflater {
+    const val TAG = "SingleLineViewInflater"
+
+    /**
+     * Inflate an instance of SingleLineViewModel.
+     *
+     * @param notification the notification to show
+     * @param messagingStyle the MessagingStyle information is only provided for conversation
+     *   notification, not for legacy messaging notifications
+     * @param builder the recovered Notification Builder
+     * @param systemUiContext the context of Android System UI
+     * @return the inflated SingleLineViewModel
+     */
+    @JvmStatic
+    fun inflateSingleLineViewModel(
+        notification: Notification,
+        messagingStyle: MessagingStyle?,
+        builder: Notification.Builder,
+        systemUiContext: Context,
+    ): SingleLineViewModel {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+            return SingleLineViewModel(null, null, null)
+        }
+        peopleHelper.init(systemUiContext)
+        var titleText = HybridGroupManager.resolveTitle(notification)
+        var contentText = HybridGroupManager.resolveText(notification)
+
+        if (messagingStyle == null) {
+            return SingleLineViewModel(
+                titleText = titleText,
+                contentText = contentText,
+                conversationData = null,
+            )
+        }
+
+        val isGroupConversation = messagingStyle.isGroupConversation
+
+        val conversationTextData = messagingStyle.loadConversationTextData(systemUiContext)
+        if (conversationTextData?.conversationTitle?.isNotEmpty() == true) {
+            titleText = conversationTextData.conversationTitle
+        }
+        if (conversationTextData?.conversationText?.isNotEmpty() == true) {
+            contentText = conversationTextData.conversationText
+        }
+
+        val conversationAvatar =
+            messagingStyle.loadConversationAvatar(
+                notification = notification,
+                isGroupConversation = isGroupConversation,
+                builder = builder,
+                systemUiContext = systemUiContext
+            )
+
+        val conversationData =
+            ConversationData(
+                // We don't show the sender's name for one-to-one conversation
+                conversationSenderName =
+                    if (isGroupConversation) conversationTextData?.senderName else null,
+                avatar = conversationAvatar
+            )
+
+        return SingleLineViewModel(
+            titleText = titleText,
+            contentText = contentText,
+            conversationData = conversationData,
+        )
+    }
+
+    /** load conversation text data from the MessagingStyle of conversation notifications */
+    private fun MessagingStyle.loadConversationTextData(
+        systemUiContext: Context
+    ): ConversationTextData? {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+            return null
+        }
+        var conversationText: CharSequence?
+
+        if (messages.isEmpty()) {
+            return null
+        }
+
+        // load the conversation text
+        val lastMessage = messages[messages.lastIndex]
+        conversationText = lastMessage.text
+        if (conversationText == null && lastMessage.isImageMessage()) {
+            conversationText = findBackUpConversationText(lastMessage, systemUiContext)
+        }
+
+        // load the sender's name to display
+        val name = lastMessage.senderPerson?.name
+        val senderName =
+            systemUiContext.resources.getString(
+                R.string.conversation_single_line_name_display,
+                name
+            )
+
+        // We need to find back-up values for those texts if they are needed and empty
+        return ConversationTextData(
+            conversationTitle = conversationTitle
+                    ?: findBackUpConversationTitle(senderName, systemUiContext),
+            conversationText = conversationText,
+            senderName = senderName,
+        )
+    }
+
+    private fun MessagingStyle.Message.isImageMessage(): Boolean = MessagingMessage.hasImage(this)
+
+    /** find a back-up conversation title when the conversation title is null. */
+    private fun MessagingStyle.findBackUpConversationTitle(
+        senderName: CharSequence?,
+        systemUiContext: Context,
+    ): CharSequence {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+            return ""
+        }
+        return if (isGroupConversation) {
+            systemUiContext.resources.getString(R.string.conversation_title_fallback_group_chat)
+        } else {
+            // Is one-to-one, let's try to use the last sender's name
+            // The last back-up is the value of resource: conversation_title_fallback_one_to_one
+            senderName
+                ?: systemUiContext.resources.getString(
+                    R.string.conversation_title_fallback_one_to_one
+                )
+        }
+    }
+
+    /**
+     * find a back-up conversation text when the conversation has null text and is image message.
+     */
+    private fun findBackUpConversationText(
+        message: MessagingStyle.Message,
+        context: Context,
+    ): CharSequence? {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+            return null
+        }
+        // If the message is not an image message, just return empty, the back-up text for showing
+        // will be SingleLineViewModel.contentText
+        if (!message.isImageMessage()) return null
+        // If is image message, return a placeholder
+        return context.resources.getString(R.string.conversation_single_line_image_placeholder)
+    }
+
+    /**
+     * The text data that we load from a conversation notification to show in the single-line views.
+     *
+     * Group conversation single-line view should be formatted as:
+     * [conversationTitle, senderName, conversationText]
+     *
+     * One-to-one single-line view should be formatted as:
+     * [conversationTitle (which is equal to the senderName), conversationText]
+     *
+     * @property conversationTitle the title of the conversation, not necessarily the title of the
+     *   notification row. conversationTitle is non-null, though may be empty, in which case we need
+     *   to show the notification title instead.
+     * @property conversationText the text content of the conversation, single-line will use the
+     *   notification's text when conversationText is null
+     * @property senderName the sender's name to be shown in the row when needed. senderName can be
+     *   null
+     */
+    data class ConversationTextData(
+        val conversationTitle: CharSequence,
+        val conversationText: CharSequence?,
+        val senderName: CharSequence?,
+    )
+
+    private fun groupMessages(
+        messages: List<MessagingStyle.Message>,
+        historicMessages: List<MessagingStyle.Message>,
+    ): List<MutableList<MessagingStyle.Message>> {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+            return listOf()
+        }
+        if (messages.isEmpty() && historicMessages.isEmpty()) return listOf()
+        var currentGroup: MutableList<MessagingStyle.Message>? = null
+        var currentSenderKey: CharSequence? = null
+        val groups = mutableListOf<MutableList<MessagingStyle.Message>>()
+        for (i in 0 until (historicMessages.size + messages.size)) {
+            val message = if (i < historicMessages.size) historicMessages[i] else messages[i]
+
+            val sender = message.senderPerson
+            val senderKey = sender?.getKeyOrName()
+            val isNewGroup = (currentGroup == null) || senderKey != currentSenderKey
+            if (isNewGroup) {
+                currentGroup = mutableListOf()
+                groups.add(currentGroup)
+                currentSenderKey = senderKey
+            }
+            currentGroup?.add(message)
+        }
+        return groups
+    }
+
+    private fun MessagingStyle.loadConversationAvatar(
+        builder: Notification.Builder,
+        notification: Notification,
+        isGroupConversation: Boolean,
+        systemUiContext: Context,
+    ): ConversationAvatar {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+            return SingleIcon(null)
+        }
+        val userKey = user.getKeyOrName()
+        var conversationIcon: Icon? = null
+        var conversationText: CharSequence? = conversationTitle
+
+        val groups = groupMessages(messages, historicMessages)
+        val uniqueNames = peopleHelper.mapUniqueNamesToPrefixWithGroupList(groups)
+
+        if (!isGroupConversation) {
+            // Conversation is one-to-one, load the single icon
+            // Let's resolve the icon / text from the last sender
+            if (shortcutIcon != null) {
+                conversationIcon = shortcutIcon
+            }
+
+            for (i in messages.lastIndex downTo 0) {
+                val message = messages[i]
+                val sender = message.senderPerson
+                val senderKey = sender?.getKeyOrName()
+                if ((sender != null && senderKey != userKey) || i == 0) {
+                    if (conversationText.isNullOrEmpty()) {
+                        // We use the senderName as header text if no conversation title is provided
+                        // (This usually happens for most 1:1 conversations)
+                        conversationText = sender?.name ?: ""
+                    }
+                    if (conversationIcon == null) {
+                        var avatarIcon = sender?.icon
+                        if (avatarIcon == null) {
+                            avatarIcon = builder.getDefaultAvatar(name = conversationText)
+                        }
+                        conversationIcon = avatarIcon
+                    }
+                    break
+                }
+            }
+        }
+
+        if (conversationIcon == null) {
+            conversationIcon = notification.getLargeIcon()
+        }
+
+        // If is one-to-one or the conversation has an icon, return a single icon
+        if (!isGroupConversation || conversationIcon != null) {
+            return SingleIcon(conversationIcon?.loadDrawable(systemUiContext))
+        }
+
+        // Otherwise, let's find the two last conversations to build a face pile:
+        var secondLastIcon: Icon? = null
+        var lastIcon: Icon? = null
+        var lastKey: CharSequence? = null
+
+        for (i in groups.lastIndex downTo 0) {
+            val message = groups[i][0]
+            val sender = message.senderPerson ?: user
+            val senderKey = sender.getKeyOrName()
+            val notUser = senderKey != userKey
+            val notIncluded = senderKey != lastKey
+
+            if ((notUser && notIncluded) || (i == 0 && lastKey == null)) {
+                if (lastIcon == null) {
+                    lastIcon =
+                        sender.icon
+                            ?: builder.getDefaultAvatar(
+                                name = sender.name,
+                                uniqueNames = uniqueNames
+                            )
+                    lastKey = senderKey
+                } else {
+                    secondLastIcon =
+                        sender.icon
+                            ?: builder.getDefaultAvatar(
+                                name = sender.name,
+                                uniqueNames = uniqueNames
+                            )
+                    break
+                }
+            }
+        }
+
+        if (lastIcon == null) {
+            lastIcon = builder.getDefaultAvatar(name = "")
+        }
+
+        if (secondLastIcon == null) {
+            secondLastIcon = builder.getDefaultAvatar(name = "")
+        }
+
+        return FacePile(
+            topIconDrawable = secondLastIcon.loadDrawable(systemUiContext),
+            bottomIconDrawable = lastIcon.loadDrawable(systemUiContext),
+            bottomBackgroundColor = builder.getBackgroundColor(/* isHeader = */ false),
+        )
+    }
+
+    @JvmStatic
+    fun inflateSingleLineViewHolder(
+        isConversation: Boolean,
+        reinflateFlags: Int,
+        entry: NotificationEntry,
+        context: Context,
+        logger: NotificationContentInflaterLogger,
+    ): HybridNotificationView? {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return null
+        if (reinflateFlags and FLAG_CONTENT_VIEW_SINGLE_LINE == 0) {
+            return null
+        }
+
+        logger.logInflateSingleLine(entry, reinflateFlags, isConversation)
+        logger.logAsyncTaskProgress(entry, "inflating single-line content view")
+
+        var view: HybridNotificationView? = null
+
+        traceSection("NotificationContentInflater#inflateSingleLineView") {
+            val inflater = LayoutInflater.from(context)
+            val layoutRes: Int =
+                if (isConversation)
+                    com.android.systemui.res.R.layout.hybrid_conversation_notification
+                else com.android.systemui.res.R.layout.hybrid_notification
+            view = inflater.inflate(layoutRes, /* root = */ null) as HybridNotificationView
+            if (view == null) {
+                Log.wtf(TAG, "Single-line view inflation result is null for entry: ${entry.logKey}")
+            }
+        }
+        return view
+    }
+
+    private fun Notification.Builder.getDefaultAvatar(
+        name: CharSequence?,
+        uniqueNames: PeopleHelper.NameToPrefixMap? = null
+    ): Icon {
+        val layoutColor = getSmallIconColor(/* isHeader = */ false)
+        if (!name.isNullOrEmpty()) {
+            val symbol = uniqueNames?.getPrefix(name) ?: ""
+            return peopleHelper.createAvatarSymbol(
+                /* name = */ name,
+                /* symbol = */ symbol,
+                /* layoutColor = */ layoutColor
+            )
+        }
+        // If name is null, create default avatar with background color
+        // TODO(b/319829062): Investigate caching default icon for color
+        return peopleHelper.createAvatarSymbol(/* name = */ "", /* symbol = */ "", layoutColor)
+    }
+
+    private fun Person.getKeyOrName(): CharSequence? = if (key == null) name else key
+
+    private val peopleHelper = PeopleHelper()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncGroupHeaderViewInflation.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncGroupHeaderViewInflation.kt
new file mode 100644
index 0000000..44fc77f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncGroupHeaderViewInflation.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the Async Group Header Inflation flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object AsyncGroupHeaderViewInflation {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_NOTIFICATION_ASYNC_GROUP_HEADER_INFLATION
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the async inflation of group header views enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.notificationAsyncGroupHeaderInflation()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineConversationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineConversationViewBinder.kt
new file mode 100644
index 0000000..69284bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineConversationViewBinder.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.statusbar.notification.row.ui.viewbinder
+
+import com.android.systemui.statusbar.notification.row.HybridConversationNotificationView
+import com.android.systemui.statusbar.notification.row.HybridNotificationView
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel
+
+object SingleLineConversationViewBinder {
+    @JvmStatic
+    fun bind(viewModel: SingleLineViewModel, view: HybridNotificationView?) {
+        if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return
+        if (view !is HybridConversationNotificationView || !viewModel.isConversation()) {
+            SingleLineViewBinder.bind(viewModel, view)
+            return
+        }
+
+        viewModel.conversationData?.avatar?.let { view.setAvatar(it) }
+        view.setText(
+            viewModel.titleText,
+            viewModel.contentText,
+            viewModel.conversationData?.conversationSenderName
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
new file mode 100644
index 0000000..22e10c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewbinder
+
+import com.android.systemui.statusbar.notification.row.HybridNotificationView
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel
+
+object SingleLineViewBinder {
+    @JvmStatic
+    fun bind(viewModel: SingleLineViewModel?, view: HybridNotificationView?) {
+        // bind the title and content text views
+        view?.apply {
+            bind(
+                /* title = */ viewModel?.titleText,
+                /* text = */ viewModel?.contentText,
+                /* contentView = */ null
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt
new file mode 100644
index 0000000..d583fa5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import android.annotation.ColorInt
+import android.graphics.drawable.Drawable
+
+/**
+ * ViewModel for SingleLine Notification View.
+ *
+ * @property titleText the text of notification view title
+ * @property contentText the text of view content
+ * @property conversationData the data that is needed specifically for conversation single-line
+ *   views. Null conversationData shows that the notification is not conversation. Legacy
+ *   MessagingStyle Notifications doesn't have this member.
+ */
+data class SingleLineViewModel(
+    var titleText: CharSequence?,
+    var contentText: CharSequence?,
+    var conversationData: ConversationData?,
+) {
+    fun isConversation(): Boolean {
+        return conversationData != null
+    }
+}
+
+/**
+ * @property conversationSenderName the name of sender to show in the single-line view. Only group
+ *   conversation single-line views show the sender name.
+ * @property avatar the avatar to show for the conversation
+ */
+data class ConversationData(
+    val conversationSenderName: CharSequence?,
+    val avatar: ConversationAvatar,
+)
+
+/**
+ * An avatar to show for a single-line conversation notification, it can be either a single icon or
+ * a face pile.
+ */
+sealed class ConversationAvatar
+
+data class SingleIcon(val iconDrawable: Drawable?) : ConversationAvatar()
+
+/**
+ * A kind of avatar to show for a group conversation notification view. It consists of two avatars
+ * of the last two senders.
+ */
+data class FacePile(
+    val topIconDrawable: Drawable?,
+    val bottomIconDrawable: Drawable?,
+    @ColorInt val bottomBackgroundColor: Int
+) : ConversationAvatar()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
index eb1c1ba..5527efc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.statusbar.notification.shared
 
 import android.graphics.drawable.Icon
+import com.android.systemui.statusbar.notification.stack.PriorityBucket
 
 /**
  * Model for a top-level "entry" in the notification list, either an
@@ -55,6 +56,16 @@
     val shelfIcon: Icon?,
     /** Icon to display in the status bar. */
     val statusBarIcon: Icon?,
+    /** The notifying app's [packageName]'s uid. */
+    val uid: Int,
+    /** The notifying app's packageName. */
+    val packageName: String,
+    /** A small per-notification ID, used for statsd logging. */
+    val instanceId: Int?,
+    /** If this notification is the group summary for a group of notifications. */
+    val isGroupSummary: Boolean,
+    /** Indicates in which section the notification is displayed in. @see [PriorityBucket]. */
+    @PriorityBucket val bucket: Int,
 ) : ActiveNotificationEntryModel()
 
 /** Model for a group of notifications. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAvalancheSuppression.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAvalancheSuppression.kt
new file mode 100644
index 0000000..a21dd9b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAvalancheSuppression.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notification avalanche suppression flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationAvalancheSuppression {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_NOTIFICATION_AVALANCHE_SUPPRESSION
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.notificationAvalancheSuppression()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+            RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index d9c5108..20fae88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -26,9 +26,9 @@
 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.dump.DumpManager;
+import com.android.systemui.res.R;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.StatusBarState;
@@ -578,7 +578,7 @@
     }
 
     public boolean isPulsing(NotificationEntry entry) {
-        return mPulsing && entry.isAlerting();
+        return mPulsing && entry.isHeadsUpEntry();
     }
 
     public void setPulsingRow(ExpandableNotificationRow row) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTracker.kt
new file mode 100644
index 0000000..a1fb983
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTracker.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack
+
+import com.android.internal.util.LatencyTracker
+import com.android.internal.util.LatencyTracker.ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE
+import com.android.internal.util.LatencyTracker.ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import javax.inject.Inject
+
+/**
+ * Tracks latencies related to temporary hiding notifications while measuring
+ * them, which is an optimization to show some content as early as possible
+ * and perform notifications measurement later.
+ * See [HideNotificationsInteractor].
+ */
+class DisplaySwitchNotificationsHiderTracker @Inject constructor(
+    private val notificationsInteractor: ShadeInteractor,
+    private val latencyTracker: LatencyTracker
+) {
+
+    suspend fun trackNotificationHideTime(shouldHideNotifications: Flow<Boolean>) {
+        shouldHideNotifications
+            .collect { shouldHide ->
+                if (shouldHide) {
+                    latencyTracker.onActionStart(ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE)
+                } else {
+                    latencyTracker.onActionEnd(ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE)
+                }
+            }
+    }
+
+    suspend fun trackNotificationHideTimeWhenVisible(shouldHideNotifications: Flow<Boolean>) {
+        combine(shouldHideNotifications, notificationsInteractor.isAnyExpanded)
+            { hidden, shadeExpanded -> hidden && shadeExpanded }
+            .distinctUntilChanged()
+            .collect { hiddenButShouldBeVisible ->
+                if (hiddenButShouldBeVisible) {
+                    latencyTracker.onActionStart(
+                            ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN)
+                } else {
+                    latencyTracker.onActionEnd(
+                            ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN)
+                }
+            }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 45b9c26..abf6c27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -1295,8 +1295,8 @@
             if (singleLineView != null) {
                 minExpandHeight += singleLineView.getHeight();
             } else {
-                Log.e(TAG, "getMinHeight: child " + child + " single line view is null",
-                        new Exception());
+                Log.e(TAG, "getMinHeight: child " + child.getEntry().getKey()
+                                + " single line view is null", new Exception());
             }
             visibleChildren++;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
index 6bb9573..5c9a0b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
@@ -23,7 +23,6 @@
 
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
-import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index ea414d2..dd04531 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -94,6 +94,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.NotificationShelf;
@@ -113,6 +114,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
 import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -129,9 +131,12 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.Callable;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
@@ -183,6 +188,7 @@
     private int mOverflingDistance;
     private float mMaxOverScroll;
     private boolean mIsBeingDragged;
+    private boolean mSendingTouchesToSceneFramework;
     private int mLastMotionY;
     private int mDownX;
     private int mActivePointerId = INVALID_POINTER;
@@ -245,6 +251,7 @@
      */
     private float mOverScrolledBottomPixels;
     private NotificationLogger.OnChildLocationsChangedListener mListener;
+    private OnNotificationLocationsChangedListener mLocationsChangedListener;
     private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
     private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
     private Runnable mOnHeightChangedRunnable;
@@ -390,6 +397,14 @@
         }
     };
 
+    private final Callable<Map<String, Integer>> collectVisibleLocationsCallable =
+            new Callable<>() {
+                @Override
+                public Map<String, Integer> call() {
+                    return collectVisibleNotificationLocations();
+                }
+            };
+
     private boolean mPulsing;
     private boolean mScrollable;
     private View mForcedScroll;
@@ -1242,8 +1257,21 @@
         }
     }
 
+    /**
+     * @param listener to be notified after the location of Notification children might have
+     *                 changed.
+     */
+    public void setNotificationLocationsChangedListener(
+            @Nullable OnNotificationLocationsChangedListener listener) {
+        if (NotificationsLiveDataStoreRefactor.isUnexpectedlyInLegacyMode()) {
+            return;
+        }
+        mLocationsChangedListener = listener;
+    }
+
     public void setChildLocationsChangedListener(
             NotificationLogger.OnChildLocationsChangedListener listener) {
+        NotificationsLiveDataStoreRefactor.assertInLegacyMode();
         mListener = listener;
     }
 
@@ -1483,7 +1511,12 @@
      */
     public void setExpandedHeight(float height) {
         final boolean skipHeightUpdate = shouldSkipHeightUpdate();
-        updateStackPosition();
+
+        // when scene framework is enabled, updateStackPosition is already called by
+        // updateTopPadding every time the stack moves, so skip it here to avoid flickering.
+        if (!SceneContainerFlag.isEnabled()) {
+            updateStackPosition();
+        }
 
         if (!skipHeightUpdate) {
             mExpandedHeight = height;
@@ -2424,6 +2457,7 @@
                         /* notificationStackScrollLayout= */ this, mMaxDisplayedNotifications,
                         shelfIntrinsicHeight);
         mIntrinsicContentHeight = height;
+        mController.setIntrinsicContentHeight(mIntrinsicContentHeight);
 
         // The topPadding can be bigger than the regular padding when qs is expanded, in that
         // state the maxPanelHeight and the contentHeight should be bigger
@@ -3532,8 +3566,11 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
-        if (mTouchHandler != null && mTouchHandler.onTouchEvent(ev)) {
-            return true;
+        if (mTouchHandler != null) {
+            boolean touchHandled = mTouchHandler.onTouchEvent(ev);
+            if (SceneContainerFlag.isEnabled() || touchHandled) {
+                return touchHandled;
+            }
         }
 
         return super.onTouchEvent(ev);
@@ -3541,6 +3578,27 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (SceneContainerFlag.isEnabled() && mIsBeingDragged) {
+            if (!mSendingTouchesToSceneFramework) {
+                // if this is the first touch being sent to the scene framework,
+                // convert it into a synthetic DOWN event.
+                mSendingTouchesToSceneFramework = true;
+                MotionEvent downEvent = MotionEvent.obtain(ev);
+                downEvent.setAction(MotionEvent.ACTION_DOWN);
+                mController.sendTouchToSceneFramework(downEvent);
+                downEvent.recycle();
+            } else {
+                mController.sendTouchToSceneFramework(ev);
+            }
+
+            if (
+                    ev.getActionMasked() == MotionEvent.ACTION_UP
+                    || ev.getActionMasked() == MotionEvent.ACTION_CANCEL
+            ) {
+                setIsBeingDragged(false);
+            }
+            return false;
+        }
         return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
     }
 
@@ -3607,6 +3665,18 @@
             return true;
         }
 
+        // If the scene framework is enabled, ignore all non-move gestures if we are currently
+        // dragging - they should be dispatched to the scene framework. Move gestures should be let
+        // through to determine if we are still dragging or not.
+        if (
+                SceneContainerFlag.isEnabled()
+                && mIsBeingDragged
+                && action != MotionEvent.ACTION_MOVE
+        ) {
+            setIsBeingDragged(false);
+            return false;
+        }
+
         switch (action) {
             case MotionEvent.ACTION_DOWN: {
                 if (getChildCount() == 0 || !isInContentBounds(ev)) {
@@ -3650,6 +3720,11 @@
                     }
                 }
                 if (mIsBeingDragged) {
+                    // Defer actual scrolling to the scene framework if enabled
+                    if (SceneContainerFlag.isEnabled()) {
+                        setIsBeingDragged(false);
+                        return false;
+                    }
                     // Scroll to follow the motion event
                     mLastMotionY = y;
                     float scrollAmount;
@@ -3744,9 +3819,8 @@
     }
 
     protected boolean isInsideQsHeader(MotionEvent ev) {
-        if (mQsHeader == null) {
-            Log.wtf(TAG, "qsHeader is null while NSSL is handling a touch");
-            return false;
+        if (SceneContainerFlag.isEnabled()) {
+            return ev.getY() < mController.getPlaceholderTop();
         }
 
         mQsHeader.getBoundsOnScreen(mQsHeaderBound);
@@ -4000,9 +4074,16 @@
             requestDisallowInterceptTouchEvent(true);
             cancelLongPress();
             resetExposedMenuView(true /* animate */, true /* force */);
+        } else {
+            mSendingTouchesToSceneFramework = false;
         }
     }
 
+    @VisibleForTesting
+    boolean getIsBeingDragged() {
+        return mIsBeingDragged;
+    }
+
     public void requestDisallowLongPress() {
         cancelLongPress();
     }
@@ -4398,15 +4479,40 @@
             child.applyViewState();
         }
 
-        if (mListener != null) {
-            mListener.onChildLocationsChanged();
+        if (NotificationsLiveDataStoreRefactor.isEnabled()) {
+            if (mLocationsChangedListener != null) {
+                mLocationsChangedListener.onChildLocationsChanged(collectVisibleLocationsCallable);
+            }
+        } else {
+            if (mListener != null) {
+                mListener.onChildLocationsChanged();
+            }
         }
+
         runAnimationFinishedRunnables();
         setAnimationRunning(false);
         updateBackground();
         updateViewShadows();
     }
 
+    /**
+     * Retrieves a map of visible [{@link ExpandableViewState#location}]s of the actively displayed
+     * Notification children associated by their Notification keys.
+     * Locations are collected recursively including locations from the child views of Notification
+     * Groups, that are visible.
+     */
+    private Map<String, Integer> collectVisibleNotificationLocations() {
+        Map<String, Integer> visibilities = new HashMap<>();
+        int numChildren = getChildCount();
+        for (int i = 0; i < numChildren; i++) {
+            ExpandableView child = getChildAtIndex(i);
+            if (child instanceof ExpandableNotificationRow row) {
+                row.collectVisibleLocations(visibilities);
+            }
+        }
+        return visibilities;
+    }
+
     private void updateViewShadows() {
         // we need to work around an issue where the shadow would not cast between siblings when
         // their z difference is between 0 and 0.1
@@ -4811,30 +4917,12 @@
     public void removeContainerView(View v) {
         Assert.isMainThread();
         removeView(v);
-        if (!FooterViewRefactor.isEnabled()) {
-            // A notification was removed, and we're not currently showing the empty shade view.
-            if (v instanceof ExpandableNotificationRow && !mController.isShowingEmptyShadeView()) {
-                mController.updateShowEmptyShadeView();
-                updateFooter();
-                mController.updateImportantForAccessibility();
-            }
-        }
-
         updateSpeedBumpIndex();
     }
 
     public void addContainerView(View v) {
         Assert.isMainThread();
         addView(v);
-        if (!FooterViewRefactor.isEnabled()) {
-            // A notification was added, and we're currently showing the empty shade view.
-            if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) {
-                mController.updateShowEmptyShadeView();
-                updateFooter();
-                mController.updateImportantForAccessibility();
-            }
-        }
-
         updateSpeedBumpIndex();
     }
 
@@ -4842,14 +4930,6 @@
         Assert.isMainThread();
         ensureRemovedFromTransientContainer(v);
         addView(v, index);
-        // A notification was added, and we're currently showing the empty shade view.
-        if (!FooterViewRefactor.isEnabled() && v instanceof ExpandableNotificationRow
-                && mController.isShowingEmptyShadeView()) {
-            mController.updateShowEmptyShadeView();
-            updateFooter();
-            mController.updateImportantForAccessibility();
-        }
-
         updateSpeedBumpIndex();
     }
 
@@ -5901,7 +5981,11 @@
         }
     }
 
-    protected void setLogger(StackStateLogger logger) {
+    /**
+     * Sets a {@link StackStateLogger} which is notified as the {@link StackStateAnimator} updates
+     * the views.
+     */
+    protected void setStackStateLogger(StackStateLogger logger) {
         mStateAnimator.setLogger(logger);
     }
 
@@ -5938,6 +6022,18 @@
         void flingTopOverscroll(float velocity, boolean open);
     }
 
+    /**
+     * A listener that is notified when some ExpandableNotificationRow locations might have changed.
+     */
+    public interface OnNotificationLocationsChangedListener {
+        /**
+         * Called when the location of ExpandableNotificationRows might have changed.
+         *
+         * @param locations mapping of Notification keys to locations.
+         */
+        void onChildLocationsChanged(Callable<Map<String, Integer>> locations);
+    }
+
     private void updateSpeedBumpIndex() {
         mSpeedBumpIndexDirty = true;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index abc04b8..49fde39 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -22,6 +22,7 @@
 import static com.android.app.animation.Interpolators.STANDARD;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
+import static com.android.systemui.Flags.screenshareNotificationHiding;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener;
@@ -69,6 +70,7 @@
 import com.android.systemui.flags.FeatureFlagsClassic;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
@@ -79,6 +81,9 @@
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.ui.view.WindowRootView;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.CommandQueue;
@@ -119,6 +124,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationGuts;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.NotificationSnooze;
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor;
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
@@ -130,6 +136,7 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
@@ -144,6 +151,7 @@
 
 import javax.inject.Inject;
 import javax.inject.Named;
+import javax.inject.Provider;
 
 /**
  * Controller for {@link NotificationStackScrollLayout}.
@@ -180,6 +188,8 @@
     private final NotificationRemoteInputManager mRemoteInputManager;
     private final VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
     private final ShadeController mShadeController;
+    private final Provider<WindowRootView> mWindowRootView;
+    private final NotificationStackAppearanceInteractor mStackAppearanceInteractor;
     private final KeyguardMediaController mKeyguardMediaController;
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final KeyguardBypassController mKeyguardBypassController;
@@ -210,6 +220,8 @@
     private final SecureSettings mSecureSettings;
     private final NotificationDismissibilityProvider mDismissibilityProvider;
     private final ActivityStarter mActivityStarter;
+    private final SensitiveNotificationProtectionController
+            mSensitiveNotificationProtectionController;
 
     private View mLongPressedView;
 
@@ -287,6 +299,15 @@
                 }
             };
 
+    private final Runnable mSensitiveStateChangedListener = new Runnable() {
+        @Override
+        public void run() {
+            // Animate false to protect against screen recording capturing content
+            // during the animation
+            updateSensitivenessWithAnimation(false);
+        }
+    };
+
     private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> {
         if (mView.isExpanded()) {
             // The bottom might change because we're using the final actual height of the view
@@ -334,6 +355,12 @@
     private float mMaxAlphaForExpansion = 1.0f;
     private float mMaxAlphaForUnhide = 1.0f;
 
+    /**
+     * Maximum alpha when to and from or sitting idle on the glanceable hub. Will be 1.0f when the
+     * hub is not visible or transitioning.
+     */
+    private float mMaxAlphaForGlanceableHub = 1.0f;
+
     private final NotificationListViewBinder mViewBinder;
 
     private void updateResources() {
@@ -391,7 +418,20 @@
     }
 
     private void updateSensitivenessWithAnimation(boolean animate) {
-        mView.updateSensitiveness(animate, mLockscreenUserManager.isAnyProfilePublicMode());
+        Trace.beginSection("NSSLC.updateSensitivenessWithAnimation");
+        if (screenshareNotificationHiding()) {
+            boolean isAnyProfilePublic = mLockscreenUserManager.isAnyProfilePublicMode();
+            boolean isSensitiveContentProtectionActive =
+                    mSensitiveNotificationProtectionController.isSensitiveStateActive();
+            boolean isSensitive = isAnyProfilePublic || isSensitiveContentProtectionActive;
+
+            // Only animate if in a non-sensitive state (not screen sharing)
+            boolean shouldAnimate = animate && !isSensitiveContentProtectionActive;
+            mView.updateSensitiveness(shouldAnimate, isSensitive);
+        } else {
+            mView.updateSensitiveness(animate, mLockscreenUserManager.isAnyProfilePublicMode());
+        }
+        Trace.endSection();
     }
 
     /**
@@ -688,6 +728,9 @@
             SeenNotificationsInteractor seenNotificationsInteractor,
             NotificationListViewBinder viewBinder,
             ShadeController shadeController,
+            SceneContainerFlags sceneContainerFlags,
+            Provider<WindowRootView> windowRootView,
+            NotificationStackAppearanceInteractor stackAppearanceInteractor,
             InteractionJankMonitor jankMonitor,
             StackStateLogger stackLogger,
             NotificationStackScrollLogger logger,
@@ -697,7 +740,8 @@
             SecureSettings secureSettings,
             NotificationDismissibilityProvider dismissibilityProvider,
             ActivityStarter activityStarter,
-            SplitShadeStateController splitShadeStateController) {
+            SplitShadeStateController splitShadeStateController,
+            SensitiveNotificationProtectionController sensitiveNotificationProtectionController) {
         mView = view;
         mKeyguardTransitionRepo = keyguardTransitionRepo;
         mViewBinder = viewBinder;
@@ -738,11 +782,14 @@
         mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator;
         mSeenNotificationsInteractor = seenNotificationsInteractor;
         mShadeController = shadeController;
+        mWindowRootView = windowRootView;
+        mStackAppearanceInteractor = stackAppearanceInteractor;
         mFeatureFlags = featureFlags;
         mNotificationTargetsHelper = notificationTargetsHelper;
         mSecureSettings = secureSettings;
         mDismissibilityProvider = dismissibilityProvider;
         mActivityStarter = activityStarter;
+        mSensitiveNotificationProtectionController = sensitiveNotificationProtectionController;
         mView.passSplitShadeStateController(splitShadeStateController);
         mDumpManager.registerDumpable(this);
         updateResources();
@@ -750,7 +797,7 @@
     }
 
     private void setUpView() {
-        mView.setLogger(mStackStateLogger);
+        mView.setStackStateLogger(mStackStateLogger);
         mView.setController(this);
         mView.setLogger(mLogger);
         mView.setTouchHandler(new TouchHandler());
@@ -847,6 +894,11 @@
         mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
         mDeviceProvisionedListener.onDeviceProvisionedChanged();
 
+        if (screenshareNotificationHiding()) {
+            mSensitiveNotificationProtectionController
+                    .registerSensitiveStateListener(mSensitiveStateChangedListener);
+        }
+
         if (mView.isAttachedToWindow()) {
             mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
         }
@@ -1075,6 +1127,28 @@
         return mView.getIntrinsicContentHeight();
     }
 
+    /**
+     * Dispatch a touch to the scene container framework.
+     * TODO(b/316965302): Replace findViewById to avoid DFS
+     */
+    public void sendTouchToSceneFramework(MotionEvent ev) {
+        View sceneContainer = mWindowRootView.get()
+                .findViewById(R.id.scene_container_root_composable);
+        if (sceneContainer != null) {
+            sceneContainer.dispatchTouchEvent(ev);
+        }
+    }
+
+    /** Get the y-coordinate of the top bound of the stack. */
+    public float getPlaceholderTop() {
+        return mStackAppearanceInteractor.getStackBounds().getValue().getTop();
+    }
+
+    /** Set the intrinsic height of the stack content without additional padding. */
+    public void setIntrinsicContentHeight(float intrinsicContentHeight) {
+        mStackAppearanceInteractor.setIntrinsicContentHeight(intrinsicContentHeight);
+    }
+
     public void setIntrinsicPadding(int intrinsicPadding) {
         mView.setIntrinsicPadding(intrinsicPadding);
     }
@@ -1217,9 +1291,19 @@
         updateAlpha();
     }
 
+    /**
+     * Sets the max alpha value for notifications when idle on the glanceable hub or when
+     * transitioning to/from the glanceable hub.
+     */
+    public void setMaxAlphaForGlanceableHub(float alpha) {
+        mMaxAlphaForGlanceableHub = alpha;
+        updateAlpha();
+    }
+
     private void updateAlpha() {
         if (mView != null) {
-            mView.setAlpha(Math.min(mMaxAlphaForExpansion, mMaxAlphaForUnhide));
+            mView.setAlpha(Math.min(mMaxAlphaForExpansion,
+                    Math.min(mMaxAlphaForUnhide, mMaxAlphaForGlanceableHub)));
         }
     }
 
@@ -1711,6 +1795,7 @@
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("mMaxAlphaForExpansion=" + mMaxAlphaForExpansion);
         pw.println("mMaxAlphaForUnhide=" + mMaxAlphaForUnhide);
+        pw.println("mMaxAlphaForGlanceableHub=" + mMaxAlphaForGlanceableHub);
     }
 
     /**
@@ -1957,18 +2042,34 @@
                     mView.dispatchDownEventToScroller(ev);
                 }
             }
-            boolean scrollerWantsIt = false;
-            if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping()
-                    && !expandingNotification && !mView.getDisallowScrollingInThisMotion()) {
-                scrollerWantsIt = mView.onScrollTouch(ev);
-            }
             boolean horizontalSwipeWantsIt = false;
-            if (mLongPressedView == null && !mView.isBeingDragged()
-                    && !expandingNotification
-                    && !mView.getExpandedInThisMotion()
-                    && !onlyScrollingInThisMotion
-                    && !mView.getDisallowDismissInThisMotion()) {
-                horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
+            boolean scrollerWantsIt = false;
+            if (KeyguardShadeMigrationNssl.isEnabled()) {
+                // Reverse the order relative to the else statement. onScrollTouch will reset on an
+                // UP event, causing horizontalSwipeWantsIt to be set to true on vertical swipes.
+                if (mLongPressedView == null && !mView.isBeingDragged()
+                        && !expandingNotification
+                        && !mView.getExpandedInThisMotion()
+                        && !onlyScrollingInThisMotion
+                        && !mView.getDisallowDismissInThisMotion()) {
+                    horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
+                }
+                if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping()
+                        && !expandingNotification && !mView.getDisallowScrollingInThisMotion()) {
+                    scrollerWantsIt = mView.onScrollTouch(ev);
+                }
+            } else {
+                if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping()
+                        && !expandingNotification && !mView.getDisallowScrollingInThisMotion()) {
+                    scrollerWantsIt = mView.onScrollTouch(ev);
+                }
+                if (mLongPressedView == null && !mView.isBeingDragged()
+                        && !expandingNotification
+                        && !mView.getExpandedInThisMotion()
+                        && !onlyScrollingInThisMotion
+                        && !mView.getDisallowDismissInThisMotion()) {
+                    horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
+                }
             }
 
             // Check if we need to clear any snooze leavebehinds
@@ -2036,6 +2137,7 @@
 
             if (!FooterViewRefactor.isEnabled()) {
                 updateShowEmptyShadeView();
+                updateImportantForAccessibility();
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
index e78a694..aac3c28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
@@ -30,4 +30,18 @@
 
     /** The corner radius of the notification stack, in dp. */
     val cornerRadiusDp = MutableStateFlow(32f)
+
+    /**
+     * The height in px of the contents of notification stack. Depending on the number of
+     * notifications, this can exceed the space available on screen to show notifications, at which
+     * point the notification stack should become scrollable.
+     */
+    val intrinsicContentHeight = MutableStateFlow(0f)
+
+    /**
+     * The y-coordinate in px of top of the contents of the notification stack. This value can be
+     * negative, if the stack is scrolled such that its top extends beyond the top edge of the
+     * screen.
+     */
+    val contentTop = MutableStateFlow(0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index 61a4dfc..1dfde09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -34,12 +34,32 @@
     /** The bounds of the notification stack in the current scene. */
     val stackBounds: StateFlow<NotificationContainerBounds> = repository.stackBounds.asStateFlow()
 
+    /** The corner radius of the notification stack, in dp. */
+    val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow()
+
+    /**
+     * The height in px of the contents of notification stack. Depending on the number of
+     * notifications, this can exceed the space available on screen to show notifications, at which
+     * point the notification stack should become scrollable.
+     */
+    val intrinsicContentHeight = repository.intrinsicContentHeight.asStateFlow()
+
+    /** The y-coordinate in px of top of the contents of the notification stack. */
+    val contentTop = repository.contentTop.asStateFlow()
+
     /** Sets the position of the notification stack in the current scene. */
     fun setStackBounds(bounds: NotificationContainerBounds) {
         check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
         repository.stackBounds.value = bounds
     }
 
-    /** The corner radius of the notification stack, in dp. */
-    val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow()
+    /** Sets the height of the contents of the notification stack. */
+    fun setIntrinsicContentHeight(height: Float) {
+        repository.intrinsicContentHeight.value = height
+    }
+
+    /** Sets the y-coord in px of the top of the contents of the notification stack. */
+    fun setContentTop(startY: Float) {
+        repository.contentTop.value = startY
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
index 625fdc1..4b8fb1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
@@ -18,12 +18,15 @@
 package com.android.systemui.statusbar.notification.stack.domain.interactor
 
 import android.content.Context
+import com.android.systemui.Flags.centralizedStatusBarDimensRefactor
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.res.R
+import com.android.systemui.shade.LargeScreenHeaderHelper
 import com.android.systemui.statusbar.policy.SplitShadeStateController
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
@@ -45,6 +48,7 @@
     private val splitShadeStateController: SplitShadeStateController,
     keyguardInteractor: KeyguardInteractor,
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+    largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
 ) {
 
     private val _topPosition = MutableStateFlow(0f)
@@ -72,7 +76,11 @@
                             getDimensionPixelSize(R.dimen.notification_panel_margin_bottom),
                         marginTop = getDimensionPixelSize(R.dimen.notification_panel_margin_top),
                         marginTopLargeScreen =
-                            getDimensionPixelSize(R.dimen.large_screen_shade_header_height),
+                            if (centralizedStatusBarDimensRefactor()) {
+                                largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
+                            } else {
+                                getDimensionPixelSize(R.dimen.large_screen_shade_header_height)
+                            },
                         keyguardSplitShadeTopMargin =
                             getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin),
                     )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationRowStatsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationRowStatsLogger.kt
new file mode 100644
index 0000000..2305c7e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationRowStatsLogger.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.statusbar.notification.stack.ui.view
+
+interface NotificationRowStatsLogger {
+    fun onNotificationExpansionChanged(
+        key: String,
+        isExpanded: Boolean,
+        location: Int,
+        isUserAction: Boolean
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLogger.kt
new file mode 100644
index 0000000..365c02f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLogger.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.view
+
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import java.util.concurrent.Callable
+
+/**
+ * Logs UI events of Notifications, in particular, logging which Notifications are visible and which
+ * are not.
+ */
+interface NotificationStatsLogger : NotificationRowStatsLogger {
+    fun onLockscreenOrShadeInteractive(
+        isOnLockScreen: Boolean,
+        activeNotifications: List<ActiveNotificationModel>,
+    )
+    fun onLockscreenOrShadeNotInteractive(activeNotifications: List<ActiveNotificationModel>)
+    fun onNotificationLocationsChanged(
+        locationsProvider: Callable<Map<String, Int>>,
+        notificationRanks: Map<String, Int>,
+    )
+    override fun onNotificationExpansionChanged(
+        key: String,
+        isExpanded: Boolean,
+        location: Int,
+        isUserAction: Boolean
+    )
+    fun onNotificationRemoved(key: String)
+    fun onNotificationUpdated(key: String)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt
new file mode 100644
index 0000000..4897b42
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.view
+
+import android.service.notification.NotificationListenerService
+import androidx.annotation.VisibleForTesting
+import com.android.internal.statusbar.IStatusBarService
+import com.android.internal.statusbar.NotificationVisibility
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger
+import com.android.systemui.statusbar.notification.logging.nano.Notifications
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import com.android.systemui.statusbar.notification.stack.ExpandableViewState
+import java.util.concurrent.Callable
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+@VisibleForTesting const val UNKNOWN_RANK = -1
+
+@SysUISingleton
+class NotificationStatsLoggerImpl
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    private val notificationListenerService: NotificationListenerService,
+    private val notificationPanelLogger: NotificationPanelLogger,
+    private val statusBarService: IStatusBarService,
+) : NotificationStatsLogger {
+    private val lastLoggedVisibilities = mutableMapOf<String, VisibilityState>()
+    private var logVisibilitiesJob: Job? = null
+
+    private val expansionStates: MutableMap<String, ExpansionState> =
+        ConcurrentHashMap<String, ExpansionState>()
+    @VisibleForTesting
+    val lastReportedExpansionValues: MutableMap<String, Boolean> =
+        ConcurrentHashMap<String, Boolean>()
+
+    override fun onNotificationLocationsChanged(
+        locationsProvider: Callable<Map<String, Int>>,
+        notificationRanks: Map<String, Int>,
+    ) {
+        if (logVisibilitiesJob?.isActive == true) {
+            return
+        }
+
+        logVisibilitiesJob =
+            startLogVisibilitiesJob(
+                newVisibilities =
+                    combine(
+                        visibilities = locationsProvider.call(),
+                        rankingsMap = notificationRanks
+                    ),
+                activeNotifCount = notificationRanks.size,
+            )
+    }
+
+    override fun onNotificationExpansionChanged(
+        key: String,
+        isExpanded: Boolean,
+        location: Int,
+        isUserAction: Boolean,
+    ) {
+        val expansionState =
+            ExpansionState(
+                key = key,
+                isExpanded = isExpanded,
+                isUserAction = isUserAction,
+                location = location,
+            )
+        expansionStates[key] = expansionState
+        maybeLogNotificationExpansionChange(expansionState)
+    }
+
+    private fun maybeLogNotificationExpansionChange(expansionState: ExpansionState) {
+        if (expansionState.visible.not()) {
+            // Only log visible expansion changes
+            return
+        }
+
+        val loggedExpansionValue: Boolean? = lastReportedExpansionValues[expansionState.key]
+        if (loggedExpansionValue == null && !expansionState.isExpanded) {
+            // Consider the Notification initially collapsed, so only expanded is logged in the
+            // first time.
+            return
+        }
+
+        if (loggedExpansionValue != null && loggedExpansionValue == expansionState.isExpanded) {
+            // We have already logged this state, don't log it again
+            return
+        }
+
+        logNotificationExpansionChange(expansionState)
+        lastReportedExpansionValues[expansionState.key] = expansionState.isExpanded
+    }
+
+    private fun logNotificationExpansionChange(expansionState: ExpansionState) =
+        applicationScope.launch {
+            withContext(bgDispatcher) {
+                statusBarService.onNotificationExpansionChanged(
+                    /* key = */ expansionState.key,
+                    /* userAction = */ expansionState.isUserAction,
+                    /* expanded = */ expansionState.isExpanded,
+                    /* notificationLocation = */ expansionState.location
+                        .toNotificationLocation()
+                        .ordinal
+                )
+            }
+        }
+
+    override fun onLockscreenOrShadeInteractive(
+        isOnLockScreen: Boolean,
+        activeNotifications: List<ActiveNotificationModel>,
+    ) {
+        applicationScope.launch {
+            withContext(bgDispatcher) {
+                notificationPanelLogger.logPanelShown(
+                    isOnLockScreen,
+                    activeNotifications.toNotificationProto()
+                )
+            }
+        }
+    }
+
+    override fun onLockscreenOrShadeNotInteractive(
+        activeNotifications: List<ActiveNotificationModel>
+    ) {
+        logVisibilitiesJob =
+            startLogVisibilitiesJob(
+                newVisibilities = emptyMap(),
+                activeNotifCount = activeNotifications.size
+            )
+    }
+
+    override fun onNotificationRemoved(key: String) {
+        // No need to track expansion states for Notifications that are removed.
+        expansionStates.remove(key)
+        lastReportedExpansionValues.remove(key)
+    }
+
+    override fun onNotificationUpdated(key: String) {
+        // When the Notification is updated, we should consider it as not yet logged.
+        lastReportedExpansionValues.remove(key)
+    }
+
+    private fun combine(
+        visibilities: Map<String, Int>,
+        rankingsMap: Map<String, Int>
+    ): Map<String, VisibilityState> =
+        visibilities.mapValues { entry ->
+            VisibilityState(entry.key, entry.value, rankingsMap[entry.key] ?: UNKNOWN_RANK)
+        }
+
+    private fun startLogVisibilitiesJob(
+        newVisibilities: Map<String, VisibilityState>,
+        activeNotifCount: Int,
+    ) =
+        applicationScope.launch {
+            val newlyVisible = newVisibilities - lastLoggedVisibilities.keys
+            val noLongerVisible = lastLoggedVisibilities - newVisibilities.keys
+
+            maybeLogVisibilityChanges(newlyVisible, noLongerVisible, activeNotifCount)
+            updateExpansionStates(newlyVisible, noLongerVisible)
+
+            lastLoggedVisibilities.clear()
+            lastLoggedVisibilities.putAll(newVisibilities)
+        }
+
+    private suspend fun maybeLogVisibilityChanges(
+        newlyVisible: Map<String, VisibilityState>,
+        noLongerVisible: Map<String, VisibilityState>,
+        activeNotifCount: Int,
+    ) {
+        if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) {
+            return
+        }
+
+        val newlyVisibleAr =
+            newlyVisible.mapToNotificationVisibilitiesAr(visible = true, count = activeNotifCount)
+
+        val noLongerVisibleAr =
+            noLongerVisible.mapToNotificationVisibilitiesAr(
+                visible = false,
+                count = activeNotifCount
+            )
+
+        withContext(bgDispatcher) {
+            statusBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr)
+            if (newlyVisible.isNotEmpty()) {
+                notificationListenerService.setNotificationsShown(newlyVisible.keys.toTypedArray())
+            }
+        }
+    }
+
+    private fun updateExpansionStates(
+        newlyVisible: Map<String, VisibilityState>,
+        noLongerVisible: Map<String, VisibilityState>
+    ) {
+        expansionStates.forEach { (key, expansionState) ->
+            if (newlyVisible.contains(key)) {
+                val newState =
+                    expansionState.copy(
+                        visible = true,
+                        location = newlyVisible.getValue(key).location,
+                    )
+                expansionStates[key] = newState
+                maybeLogNotificationExpansionChange(newState)
+            }
+
+            if (noLongerVisible.contains(key)) {
+                expansionStates[key] =
+                    expansionState.copy(
+                        visible = false,
+                        location = noLongerVisible.getValue(key).location,
+                    )
+            }
+        }
+    }
+
+    private data class VisibilityState(
+        val key: String,
+        val location: Int,
+        val rank: Int,
+    )
+
+    private data class ExpansionState(
+        val key: String,
+        val isUserAction: Boolean,
+        val isExpanded: Boolean,
+        val visible: Boolean,
+        val location: Int,
+    ) {
+        constructor(
+            key: String,
+            isExpanded: Boolean,
+            location: Int,
+            isUserAction: Boolean,
+        ) : this(
+            key = key,
+            isExpanded = isExpanded,
+            isUserAction = isUserAction,
+            visible = isVisibleLocation(location),
+            location = location,
+        )
+    }
+
+    private fun Map<String, VisibilityState>.mapToNotificationVisibilitiesAr(
+        visible: Boolean,
+        count: Int,
+    ): Array<NotificationVisibility> =
+        this.map { (key, state) ->
+                NotificationVisibility.obtain(
+                    /* key = */ key,
+                    /* rank = */ state.rank,
+                    /* count = */ count,
+                    /* visible = */ visible,
+                    /* location = */ state.location.toNotificationLocation()
+                )
+            }
+            .toTypedArray()
+}
+
+private fun Int.toNotificationLocation(): NotificationVisibility.NotificationLocation {
+    return when (this) {
+        ExpandableViewState.LOCATION_FIRST_HUN ->
+            NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP
+        ExpandableViewState.LOCATION_HIDDEN_TOP ->
+            NotificationVisibility.NotificationLocation.LOCATION_HIDDEN_TOP
+        ExpandableViewState.LOCATION_MAIN_AREA ->
+            NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA
+        ExpandableViewState.LOCATION_BOTTOM_STACK_PEEKING ->
+            NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_PEEKING
+        ExpandableViewState.LOCATION_BOTTOM_STACK_HIDDEN ->
+            NotificationVisibility.NotificationLocation.LOCATION_BOTTOM_STACK_HIDDEN
+        ExpandableViewState.LOCATION_GONE ->
+            NotificationVisibility.NotificationLocation.LOCATION_GONE
+        else -> NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN
+    }
+}
+
+private fun List<ActiveNotificationModel>.toNotificationProto(): Notifications.NotificationList {
+    val notificationList = Notifications.NotificationList()
+    val protoArray: Array<Notifications.Notification> =
+        map { notification ->
+                Notifications.Notification().apply {
+                    uid = notification.uid
+                    packageName = notification.packageName
+                    notification.instanceId?.let { instanceId = it }
+                    // TODO(b/308623704) check if we can set groupInstanceId as well
+                    isGroupSummary = notification.isGroupSummary
+                    section = NotificationPanelLogger.toNotificationSection(notification.bucket)
+                }
+            }
+            .toTypedArray()
+
+    if (protoArray.isNotEmpty()) {
+        notificationList.notifications = protoArray
+    }
+
+    return notificationList
+}
+
+private fun isVisibleLocation(location: Int): Boolean =
+    location and ExpandableViewState.VISIBLE_LOCATIONS != 0
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt
index 910b40f..c2bc9ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt
@@ -16,22 +16,36 @@
 package com.android.systemui.statusbar.notification.stack.ui.viewbinder
 
 import androidx.core.view.doOnDetach
+import com.android.systemui.statusbar.notification.stack.DisplaySwitchNotificationsHiderTracker
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.launch
 
 /**
  * Binds a [NotificationStackScrollLayoutController] to its [view model][NotificationListViewModel].
  */
 object HideNotificationsBinder {
-    suspend fun bindHideList(
+    fun CoroutineScope.bindHideList(
         viewController: NotificationStackScrollLayoutController,
-        viewModel: NotificationListViewModel
+        viewModel: NotificationListViewModel,
+        hiderTracker: DisplaySwitchNotificationsHiderTracker
     ) {
         viewController.view.doOnDetach { viewController.bindHideState(shouldHide = false) }
 
-        viewModel.hideListViewModel.shouldHideListForPerformance.collect { shouldHide ->
-            viewController.bindHideState(shouldHide)
+        val hideListFlow = viewModel.hideListViewModel.shouldHideListForPerformance
+            .shareIn(this, started = Lazily)
+
+        launch {
+            hideListFlow.collect { shouldHide ->
+                viewController.bindHideState(shouldHide)
+            }
         }
+
+        launch { hiderTracker.trackNotificationHideTime(hideListFlow) }
+        launch { hiderTracker.trackNotificationHideTimeWhenVisible(hideListFlow) }
     }
 
     private fun NotificationStackScrollLayoutController.bindHideState(shouldHide: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 1b36660..44a7e7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -33,13 +33,17 @@
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
 import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder
+import com.android.systemui.statusbar.notification.stack.DisplaySwitchNotificationsHiderTracker
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.HideNotificationsBinder.bindHideList
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import com.android.systemui.util.kotlin.getOrNull
+import java.util.Optional
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.combine
@@ -49,13 +53,15 @@
 class NotificationListViewBinder
 @Inject
 constructor(
-    private val viewModel: NotificationListViewModel,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val hiderTracker: DisplaySwitchNotificationsHiderTracker,
     private val configuration: ConfigurationState,
     private val falsingManager: FalsingManager,
     private val iconAreaController: NotificationIconAreaController,
     private val metricsLogger: MetricsLogger,
     private val nicBinder: NotificationIconContainerShelfViewBinder,
+    private val loggerOptional: Optional<NotificationStatsLogger>,
+    private val viewModel: NotificationListViewModel,
 ) {
 
     fun bindWhileAttached(
@@ -70,15 +76,20 @@
         view.repeatWhenAttached {
             lifecycleScope.launch {
                 launch { bindShelf(shelf) }
-                launch { bindHideList(viewController, viewModel) }
+                bindHideList(viewController, viewModel, hiderTracker)
 
                 if (FooterViewRefactor.isEnabled) {
                     launch { bindFooter(view) }
                     launch { bindEmptyShade(view) }
-                    viewModel.isImportantForAccessibility.collect { isImportantForAccessibility ->
-                        view.setImportantForAccessibilityYesNo(isImportantForAccessibility)
+                    launch {
+                        viewModel.isImportantForAccessibility.collect { isImportantForAccessibility
+                            ->
+                            view.setImportantForAccessibilityYesNo(isImportantForAccessibility)
+                        }
                     }
                 }
+
+                launch { bindLogger(view) }
             }
         }
     }
@@ -136,4 +147,18 @@
                 )
             }
     }
+
+    private suspend fun bindLogger(view: NotificationStackScrollLayout) {
+        if (NotificationsLiveDataStoreRefactor.isEnabled) {
+            viewModel.logger.getOrNull()?.let { viewModel ->
+                loggerOptional.getOrNull()?.let { logger ->
+                    NotificationStatsLoggerBinder.bindLogger(
+                        view,
+                        logger,
+                        viewModel,
+                    )
+                }
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
index a9b542d..ed15f55 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
+import kotlin.math.pow
 import kotlin.math.roundToInt
 import kotlinx.coroutines.launch
 
@@ -43,24 +44,28 @@
             repeatOnLifecycle(Lifecycle.State.CREATED) {
                 launch {
                     viewModel.stackBounds.collect { bounds ->
-                        controller.updateTopPadding(
-                            bounds.top,
-                            controller.isAddOrRemoveAnimationPending
-                        )
                         controller.setRoundedClippingBounds(
-                            it.left,
-                            it.top,
-                            it.right,
-                            it.bottom,
+                            bounds.left.roundToInt(),
+                            bounds.top.roundToInt(),
+                            bounds.right.roundToInt(),
+                            bounds.bottom.roundToInt(),
                             viewModel.cornerRadiusDp.value.dpToPx(context),
                             viewModel.cornerRadiusDp.value.dpToPx(context),
                         )
                     }
                 }
+
+                launch {
+                    viewModel.contentTop.collect {
+                        controller.updateTopPadding(it, controller.isAddOrRemoveAnimationPending)
+                    }
+                }
+
                 launch {
                     viewModel.expandFraction.collect { expandFraction ->
                         ambientState.expansionFraction = expandFraction
                         controller.expandedHeight = expandFraction * controller.view.height
+                        controller.setMaxAlphaForExpansion(expandFraction.pow(0.75f))
                     }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStatsLoggerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStatsLoggerBinder.kt
new file mode 100644
index 0000000..a87c85f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStatsLoggerBinder.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationLoggerViewModel
+import com.android.systemui.util.kotlin.Utils
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.kotlin.throttle
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+
+/**
+ * Binds a [NotificationStatsLogger] to its [NotificationLoggerViewModel], and wires in
+ * [NotificationStackScrollLayout.OnNotificationLocationsChangedListener] updates to it.
+ */
+object NotificationStatsLoggerBinder {
+
+    /** minimum delay in ms between Notification location updates */
+    private const val NOTIFICATION_UPDATE_PERIOD_MS = 500L
+
+    suspend fun bindLogger(
+        view: NotificationStackScrollLayout,
+        logger: NotificationStatsLogger,
+        viewModel: NotificationLoggerViewModel,
+    ) {
+        // Updates the logger about whether the Notification panel, and the individual Notifications
+        // are visible to the user.
+        viewModel.isLockscreenOrShadeInteractive
+            .sample(
+                combine(viewModel.isOnLockScreen, viewModel.activeNotifications, ::Pair),
+                Utils.Companion::toTriple
+            )
+            .collectLatest { (isPanelInteractive, isOnLockScreen, notifications) ->
+                if (isPanelInteractive) {
+                    logger.onLockscreenOrShadeInteractive(
+                        isOnLockScreen = isOnLockScreen,
+                        activeNotifications = notifications,
+                    )
+                    view.onNotificationLocationsUpdated
+                        // Delay the updates with [NOTIFICATION_UPDATE_PERIOD_MS]. If the original
+                        // flow emits more than once during this period, only the latest value is
+                        // emitted, meaning that we won't log the intermediate Notification states.
+                        .throttle(NOTIFICATION_UPDATE_PERIOD_MS)
+                        .sample(viewModel.activeNotificationRanks, ::Pair)
+                        .collect { (locationsProvider, ranks) ->
+                            logger.onNotificationLocationsChanged(locationsProvider, ranks)
+                        }
+                } else {
+                    logger.onLockscreenOrShadeNotInteractive(
+                        activeNotifications = notifications,
+                    )
+                }
+            }
+    }
+}
+
+private val NotificationStackScrollLayout.onNotificationLocationsUpdated
+    get() =
+        ConflatedCallbackFlow.conflatedCallbackFlow {
+            val callback =
+                NotificationStackScrollLayout.OnNotificationLocationsChangedListener { callable ->
+                    trySend(callable)
+                }
+            setNotificationLocationsChangedListener(callback)
+            awaitClose { setNotificationLocationsChangedListener(null) }
+        }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 12927b8..fe5bdd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -18,10 +18,12 @@
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
-import android.animation.ValueAnimator
+import android.view.View
+import android.view.WindowInsets
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -31,6 +33,9 @@
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.launch
 
 /** Binds the shared notification container to its view-model. */
@@ -66,6 +71,8 @@
                 }
             }
 
+        val burnInParams = MutableStateFlow(BurnInParameters())
+
         /*
          * For animation sensitive coroutines, immediately run just like applicationScope does
          * instead of doing a post() to the main thread. This extra delay can cause visible jitter.
@@ -123,19 +130,39 @@
                         }
                     }
 
-                    launch { viewModel.translationY.collect { controller.setTranslationY(it) } }
+                    launch {
+                        burnInParams
+                            .flatMapLatest { params -> viewModel.translationY(params) }
+                            .collect { y -> controller.setTranslationY(y) }
+                    }
 
-                    launch { viewModel.alpha.collect { controller.setMaxAlphaForExpansion(it) } }
+                    launch {
+                        viewModel.expansionAlpha.collect { controller.setMaxAlphaForExpansion(it) }
+                    }
+                    launch {
+                        viewModel.glanceableHubAlpha.collect {
+                            controller.setMaxAlphaForGlanceableHub(it)
+                        }
+                    }
                 }
             }
 
         controller.setOnHeightChangedRunnable(Runnable { viewModel.notificationStackChanged() })
 
+        view.setOnApplyWindowInsetsListener { v: View, insets: WindowInsets ->
+            val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
+            burnInParams.update { current ->
+                current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
+            }
+            insets
+        }
+
         return object : DisposableHandle {
             override fun dispose() {
                 disposableHandle.dispose()
                 disposableHandleMainImmediate.dispose()
                 controller.setOnHeightChangedRunnable(null)
+                view.setOnApplyWindowInsetsListener(null)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 569ae24..86c0a678 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -40,6 +40,7 @@
     val shelf: NotificationShelfViewModel,
     val hideListViewModel: HideListViewModel,
     val footer: Optional<FooterViewModel>,
+    val logger: Optional<NotificationLoggerViewModel>,
     activeNotificationsInteractor: ActiveNotificationsInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     seenNotificationsInteractor: SeenNotificationsInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModel.kt
new file mode 100644
index 0000000..0901a7f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModel.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class NotificationLoggerViewModel
+@Inject
+constructor(
+    activeNotificationsInteractor: ActiveNotificationsInteractor,
+    keyguardInteractor: KeyguardInteractor,
+    windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor,
+) {
+    val activeNotifications: Flow<List<ActiveNotificationModel>> =
+        activeNotificationsInteractor.allRepresentativeNotifications.map { it.values.toList() }
+
+    val activeNotificationRanks: Flow<Map<String, Int>> =
+        activeNotificationsInteractor.activeNotificationRanks
+
+    val isLockscreenOrShadeInteractive: Flow<Boolean> =
+        windowRootViewVisibilityInteractor.isLockscreenOrShadeVisibleAndInteractive
+
+    val isOnLockScreen: Flow<Boolean> = keyguardInteractor.isKeyguardShowing
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
index 834d3ff..74db583 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
@@ -41,4 +41,7 @@
 
     /** The corner radius of the notification stack, in dp. */
     val cornerRadiusDp: StateFlow<Float> = stackAppearanceInteractor.cornerRadiusDp
+
+    /** The y-coordinate in px of top of the contents of the notification stack. */
+    val contentTop: StateFlow<Float> = stackAppearanceInteractor.contentTop
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 9f22118..385f061 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -21,9 +21,11 @@
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
 import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
 import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 
 /**
@@ -35,6 +37,7 @@
 @Inject
 constructor(
     private val interactor: NotificationStackAppearanceInteractor,
+    shadeInteractor: ShadeInteractor,
     flags: SceneContainerFlags,
     featureFlags: FeatureFlagsClassic,
 ) {
@@ -66,4 +69,22 @@
 
     /** The corner radius of the placeholder, in dp. */
     val cornerRadiusDp: StateFlow<Float> = interactor.cornerRadiusDp
+
+    /**
+     * The height in px of the contents of notification stack. Depending on the number of
+     * notifications, this can exceed the space available on screen to show notifications, at which
+     * point the notification stack should become scrollable.
+     */
+    val intrinsicContentHeight = interactor.intrinsicContentHeight
+
+    /**
+     * The amount [0-1] that the shade has been opened. At 0, the shade is closed; at 1, the shade
+     * is open.
+     */
+    val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion
+
+    /** Sets the y-coord in px of the top of the contents of the notification stack. */
+    fun onContentTopChanged(padding: Float) {
+        interactor.setContentTop(padding)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 5ee38be..4617ce4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -20,6 +20,7 @@
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
 import com.android.systemui.common.shared.model.NotificationContainerBounds
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -27,6 +28,10 @@
 import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -61,8 +66,12 @@
     private val keyguardInteractor: KeyguardInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val shadeInteractor: ShadeInteractor,
-    occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+    communalInteractor: CommunalInteractor,
+    private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
     lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
+    glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
+    lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
+    private val aodBurnInViewModel: AodBurnInViewModel,
 ) {
     private val statesForConstrainedNotifications =
         setOf(
@@ -87,6 +96,20 @@
             .distinctUntilChanged()
             .onStart { emit(false) }
 
+    private val lockscreenToGlanceableHubRunning =
+        keyguardTransitionInteractor
+            .transition(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+            .map { it.transitionState == STARTED || it.transitionState == RUNNING }
+            .distinctUntilChanged()
+            .onStart { emit(false) }
+
+    private val glanceableHubToLockscreenRunning =
+        keyguardTransitionInteractor
+            .transition(KeyguardState.GLANCEABLE_HUB, KeyguardState.LOCKSCREEN)
+            .map { it.transitionState == STARTED || it.transitionState == RUNNING }
+            .distinctUntilChanged()
+            .onStart { emit(false) }
+
     val shadeCollapseFadeInComplete = MutableStateFlow(false)
 
     val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
@@ -144,6 +167,24 @@
                 initialValue = false,
             )
 
+    /** Are we purely on the glanceable hub without the shade/qs? */
+    internal val isOnGlanceableHubWithoutShade: Flow<Boolean> =
+        combine(
+                communalInteractor.isIdleOnCommunal,
+                // Shade with notifications
+                shadeInteractor.shadeExpansion.map { it > 0f },
+                // Shade without notifications, quick settings only (pull down from very top on
+                // lockscreen)
+                shadeInteractor.qsExpansion.map { it > 0f },
+            ) { isIdleOnCommunal, isShadeVisible, qsExpansion ->
+                isIdleOnCommunal && !(isShadeVisible || qsExpansion)
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
     /** Fade in only for use after the shade collapses */
     val shadeCollpaseFadeIn: Flow<Boolean> =
         flow {
@@ -201,7 +242,7 @@
                 initialValue = NotificationContainerBounds(),
             )
 
-    val alpha: Flow<Float> =
+    val expansionAlpha: Flow<Float> =
         // Due to issues with the legacy shade, some shade expansion events are sent incorrectly,
         // such as when the shade resets. This can happen while the LOCKSCREEN<->OCCLUDED transition
         // is running. Therefore use a series of flatmaps to prevent unwanted interruptions while
@@ -235,23 +276,62 @@
         }
 
     /**
+     * Returns a flow of the expected alpha while running a LOCKSCREEN<->GLANCEABLE_HUB transition
+     * or idle on the glanceable hub.
+     *
+     * Must return 1.0f when not controlling the alpha since notifications does a min of all the
+     * alpha sources.
+     */
+    val glanceableHubAlpha: Flow<Float> =
+        isOnGlanceableHubWithoutShade.flatMapLatest { isOnGlanceableHubWithoutShade ->
+            combineTransform(
+                lockscreenToGlanceableHubRunning,
+                glanceableHubToLockscreenRunning,
+                merge(
+                        lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
+                        glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
+                    )
+                    .onStart {
+                        // Transition flows don't emit a value on start, kick things off so the
+                        // combine starts.
+                        emit(1f)
+                    }
+            ) { lockscreenToGlanceableHubRunning, glanceableHubToLockscreenRunning, alpha ->
+                if (isOnGlanceableHubWithoutShade) {
+                    // Notifications should not be visible on the glanceable hub.
+                    // TODO(b/321075734): implement a way to actually set the notifications to gone
+                    //  while on the hub instead of just adjusting alpha
+                    emit(0f)
+                } else if (lockscreenToGlanceableHubRunning || glanceableHubToLockscreenRunning) {
+                    emit(alpha)
+                } else {
+                    // Not on the hub and no transitions running, return full visibility so we don't
+                    // block the notifications from showing.
+                    emit(1f)
+                }
+            }
+        }
+
+    /**
      * Under certain scenarios, such as swiping up on the lockscreen, the container will need to be
      * translated as the keyguard fades out.
      */
-    val translationY: Flow<Float> =
-        combine(
-            isOnLockscreen,
+    fun translationY(params: BurnInParameters): Flow<Float> {
+        return combine(
+            aodBurnInViewModel.translationY(params).onStart { emit(0f) },
+            isOnLockscreenWithoutShade,
             merge(
                 keyguardInteractor.keyguardTranslationY,
                 occludedToLockscreenTransitionViewModel.lockscreenTranslationY,
             )
-        ) { isOnLockscreen, translationY ->
-            if (isOnLockscreen) {
-                translationY
+        ) { burnInY, isOnLockscreenWithoutShade, translationY ->
+            if (isOnLockscreenWithoutShade) {
+                burnInY + translationY
             } else {
                 0f
             }
         }
+    }
 
     /**
      * When on keyguard, there is limited space to display notifications so calculate how many could
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 63194c3..8a56da3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -797,18 +797,7 @@
                             }
                         }
                         if (dismissShade) {
-                            if (
-                                shadeControllerLazy.get().isExpandedVisible &&
-                                    !statusBarKeyguardViewManagerLazy.get().isBouncerShowing
-                            ) {
-                                shadeControllerLazy.get().animateCollapseShadeForcedDelayed()
-                            } else {
-                                // Do it after DismissAction has been processed to conserve the
-                                // needed ordering.
-                                postOnUiThread {
-                                    shadeControllerLazy.get().runPostCollapseRunnables()
-                                }
-                            }
+                            shadeControllerLazy.get().collapseShadeForActivityStart()
                         }
                         return deferred
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 90cba40..4019436 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -25,6 +25,7 @@
 import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.view.MotionEvent;
 import android.view.RemoteAnimationAdapter;
 import android.view.View;
 import android.window.RemoteTransition;
@@ -277,6 +278,13 @@
 
     void awakenDreams();
 
+    /**
+     * Handle a touch event while dreaming when the touch was initiated within a prescribed
+     * swipeable area. This method is provided for cases where swiping in certain areas of a dream
+     * should be handled by CentralSurfaces instead (e.g. swiping communal hub open).
+     */
+    void handleDreamTouch(MotionEvent event);
+
     boolean isBouncerShowing();
 
     boolean isBouncerShowingScrimmed();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
index 7dc4b96..60dfaa7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone
 
 import android.content.Intent
+import android.view.MotionEvent
 import androidx.lifecycle.LifecycleRegistry
 import com.android.keyguard.AuthKeyguardMessageArea
 import com.android.systemui.animation.ActivityLaunchAnimator
@@ -78,6 +79,7 @@
     override fun updateScrimController() {}
     override fun shouldIgnoreTouch() = false
     override fun isDeviceInteractive() = false
+    override fun handleDreamTouch(event: MotionEvent?) {}
     override fun awakenDreams() {}
     override fun isBouncerShowing() = false
     override fun isBouncerShowingScrimmed() = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 6e3aabf..64fcef5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -28,10 +28,10 @@
 
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 import static com.android.systemui.Flags.lightRevealMigration;
+import static com.android.systemui.Flags.predictiveBackSysui;
 import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
-import static com.android.systemui.Flags.predictiveBackSysui;
 
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
@@ -80,6 +80,7 @@
 import android.view.Display;
 import android.view.IRemoteAnimationRunner;
 import android.view.IWindowManager;
+import android.view.MotionEvent;
 import android.view.ThreadedRenderer;
 import android.view.View;
 import android.view.WindowInsets;
@@ -94,7 +95,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
-import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
@@ -125,6 +125,7 @@
 import com.android.systemui.charging.WirelessChargingAnimation;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -201,12 +202,10 @@
 import com.android.systemui.statusbar.core.StatusBarInitializer;
 import com.android.systemui.statusbar.data.model.StatusBarMode;
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
-import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
@@ -224,7 +223,6 @@
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
@@ -248,6 +246,7 @@
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -277,19 +276,9 @@
     private static final String BANNER_ACTION_SETUP =
             "com.android.systemui.statusbar.banner_action_setup";
 
-    private static final int MSG_OPEN_SETTINGS_PANEL = 1002;
     private static final int MSG_LAUNCH_TRANSITION_TIMEOUT = 1003;
     // 1020-1040 reserved for BaseStatusBar
 
-    /**
-     * TODO(b/249277686) delete this
-     * The delay to reset the hint text when the hint animation is finished running.
-     */
-    private static final int HINT_RESET_DELAY_MS = 1200;
-
-    /** If true, the lockscreen will show a distinct wallpaper */
-    public static final boolean ENABLE_LOCKSCREEN_WALLPAPER = true;
-
     private static final UiEventLogger sUiEventLogger = new UiEventLoggerImpl();
 
     private final Context mContext;
@@ -299,7 +288,7 @@
     private CentralSurfacesCommandQueueCallbacks mCommandQueueCallbacks;
     private float mTransitionToFullShadeProgress = 0f;
     private final NotificationListContainer mNotifListContainer;
-    private boolean mIsShortcutListSearchEnabled;
+    private final boolean mIsShortcutListSearchEnabled;
 
     private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
             new KeyguardStateController.Callback() {
@@ -458,7 +447,6 @@
     private final NotificationGutsManager mGutsManager;
     private final ShadeExpansionStateManager mShadeExpansionStateManager;
     private final KeyguardViewMediator mKeyguardViewMediator;
-    private final VisualInterruptionDecisionProvider mVisualInterruptionDecisionProvider;
     private final BrightnessSliderController.Factory mBrightnessSliderFactory;
     private final FeatureFlags mFeatureFlags;
     private final FragmentService mFragmentService;
@@ -554,7 +542,6 @@
     private int mLastLoggedStateFingerprint;
     private boolean mIsLaunchingActivityOverLockscreen;
 
-    private final UserSwitcherController mUserSwitcherController;
     private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
     protected final BatteryController mBatteryController;
     private UiModeManager mUiModeManager;
@@ -566,6 +553,25 @@
     private final WakefulnessLifecycle mWakefulnessLifecycle;
     protected final PowerInteractor mPowerInteractor;
 
+    private final CommunalInteractor mCommunalInteractor;
+
+    /**
+     * True if the device is showing the glanceable hub. See
+     * {@link CommunalInteractor#isIdleOnCommunal()} for more details.
+     */
+    private boolean mIsIdleOnCommunal = false;
+    private final Consumer<Boolean> mIdleOnCommunalConsumer = (Boolean idleOnCommunal) -> {
+        if (idleOnCommunal == mIsIdleOnCommunal) {
+            // Ignore initial value coming through the flow.
+            return;
+        }
+
+        mIsIdleOnCommunal = idleOnCommunal;
+        // Trigger an update for the scrim state when we enter or exit glanceable hub, so that we
+        // can transition to/from ScrimState.GLANCEABLE_HUB if needed.
+        updateScrimController();
+    };
+
     private boolean mNoAnimationOnNextBarModeChange;
     private final SysuiStatusBarStateController mStatusBarStateController;
 
@@ -585,8 +591,6 @@
     private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener =
             (extractor, which) -> updateTheme();
 
-    private final InteractionJankMonitor mJankMonitor;
-
     private final SceneContainerFlags mSceneContainerFlags;
 
     /**
@@ -614,12 +618,10 @@
             KeyguardBypassController keyguardBypassController,
             KeyguardStateController keyguardStateController,
             HeadsUpManager headsUpManager,
-            DynamicPrivacyController dynamicPrivacyController,
             FalsingManager falsingManager,
             FalsingCollector falsingCollector,
             BroadcastDispatcher broadcastDispatcher,
             NotificationGutsManager notificationGutsManager,
-            VisualInterruptionDecisionProvider visualInterruptionDecisionProvider,
             ShadeExpansionStateManager shadeExpansionStateManager,
             KeyguardViewMediator keyguardViewMediator,
             DisplayMetrics displayMetrics,
@@ -632,12 +634,12 @@
             NotificationLockscreenUserManager lockScreenUserManager,
             NotificationRemoteInputManager remoteInputManager,
             QuickSettingsController quickSettingsController,
-            UserSwitcherController userSwitcherController,
             BatteryController batteryController,
             SysuiColorExtractor colorExtractor,
             ScreenLifecycle screenLifecycle,
             WakefulnessLifecycle wakefulnessLifecycle,
             PowerInteractor powerInteractor,
+            CommunalInteractor communalInteractor,
             SysuiStatusBarStateController statusBarStateController,
             Optional<Bubbles> bubblesOptional,
             Lazy<NoteTaskController> noteTaskControllerLazy,
@@ -692,7 +694,6 @@
             WallpaperManager wallpaperManager,
             Optional<StartingSurface> startingSurfaceOptional,
             ActivityLaunchAnimator activityLaunchAnimator,
-            InteractionJankMonitor jankMonitor,
             DeviceStateManager deviceStateManager,
             WiredChargingRippleController wiredChargingRippleController,
             IDreamManager dreamManager,
@@ -726,7 +727,6 @@
         mFalsingManager = falsingManager;
         mBroadcastDispatcher = broadcastDispatcher;
         mGutsManager = notificationGutsManager;
-        mVisualInterruptionDecisionProvider = visualInterruptionDecisionProvider;
         mShadeExpansionStateManager = shadeExpansionStateManager;
         mKeyguardViewMediator = keyguardViewMediator;
         mDisplayMetrics = displayMetrics;
@@ -739,12 +739,12 @@
         mLockscreenUserManager = lockScreenUserManager;
         mRemoteInputManager = remoteInputManager;
         mQsController = quickSettingsController;
-        mUserSwitcherController = userSwitcherController;
         mBatteryController = batteryController;
         mColorExtractor = colorExtractor;
         mScreenLifecycle = screenLifecycle;
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mPowerInteractor = powerInteractor;
+        mCommunalInteractor = communalInteractor;
         mStatusBarStateController = statusBarStateController;
         mBubblesOptional = bubblesOptional;
         mNoteTaskControllerLazy = noteTaskControllerLazy;
@@ -794,7 +794,6 @@
         mMainExecutor = delayableExecutor;
         mMessageRouter = messageRouter;
         mWallpaperManager = wallpaperManager;
-        mJankMonitor = jankMonitor;
         mCameraLauncherLazy = cameraLauncherLazy;
         mAlternateBouncerInteractor = alternateBouncerInteractor;
         mUserTracker = userTracker;
@@ -1075,6 +1074,10 @@
         //TODO(b/264502026) move the rest of the listeners here.
         mDeviceStateManager.registerCallback(mMainExecutor,
                 new FoldStateListener(mContext, this::onFoldedStateChanged));
+
+        mJavaAdapter.alwaysCollectFlow(
+                mCommunalInteractor.isIdleOnCommunal(),
+                mIdleOnCommunalConsumer);
     }
 
     /**
@@ -2586,8 +2589,7 @@
                 // So if AOD is off or unsupported we need to trigger these updates at screen on
                 // when the keyguard is occluded.
                 mLockscreenUserManager.updatePublicMode();
-                mShadeSurface.getNotificationStackScrollLayoutController()
-                        .updateSensitivenessForOccludedWakeup();
+                mStackScrollerController.updateSensitivenessForOccludedWakeup();
             }
             if (mLaunchCameraWhenFinishedWaking) {
                 mCameraLauncherLazy.get().launchCamera(mLastCameraLaunchSource,
@@ -2820,6 +2822,8 @@
             // This will cancel the keyguardFadingAway animation if it is running. We need to do
             // this as otherwise it can remain pending and leave keyguard in a weird state.
             mUnlockScrimCallback.onCancelled();
+        } else if (mIsIdleOnCommunal) {
+            mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
         } else if (mKeyguardStateController.isShowing()
                 && !mKeyguardStateController.isOccluded()
                 && !unlocking) {
@@ -2903,6 +2907,11 @@
     };
 
     @Override
+    public void handleDreamTouch(MotionEvent event) {
+        getNotificationShadeWindowViewController().handleDreamTouch(event);
+    }
+
+    @Override
     public void awakenDreams() {
         mUiBgExecutor.execute(() -> {
             try {
@@ -3082,7 +3091,7 @@
         }
     };
 
-    private StatusBarStateController.StateListener mStateListener =
+    private final StatusBarStateController.StateListener mStateListener =
             new StatusBarStateController.StateListener() {
                 @Override
                 public void onStatePreChange(int oldState, int newState) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index e66d9e8..b07ba3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -222,9 +222,9 @@
             mReleaseOnExpandFinish = false;
         } else {
             for (NotificationEntry entry : mEntriesToRemoveAfterExpand) {
-                if (isAlerting(entry.getKey())) {
+                if (isHeadsUpEntry(entry.getKey())) {
                     // Maybe the heads-up was removed already
-                    removeAlertEntry(entry.getKey());
+                    removeEntry(entry.getKey());
                 }
             }
         }
@@ -357,9 +357,9 @@
     private final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> {
         mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
         for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
-            if (isAlerting(entry.getKey())) {
+            if (isHeadsUpEntry(entry.getKey())) {
                 // Maybe the heads-up was removed already
-                removeAlertEntry(entry.getKey());
+                removeEntry(entry.getKey());
             }
         }
         mEntriesToRemoveWhenReorderingAllowed.clear();
@@ -370,14 +370,14 @@
     //  HeadsUpManager utility (protected) methods overrides:
 
     @Override
-    protected HeadsUpEntry createAlertEntry() {
+    protected HeadsUpEntry createHeadsUpEntry() {
         return mEntryPool.acquire();
     }
 
     @Override
-    protected void onAlertEntryRemoved(AlertEntry alertEntry) {
-        super.onAlertEntryRemoved(alertEntry);
-        mEntryPool.release((HeadsUpEntryPhone) alertEntry);
+    protected void onEntryRemoved(HeadsUpEntry headsUpEntry) {
+        super.onEntryRemoved(headsUpEntry);
+        mEntryPool.release((HeadsUpEntryPhone) headsUpEntry);
     }
 
     @Override
@@ -403,7 +403,7 @@
 
     @Nullable
     private HeadsUpEntryPhone getHeadsUpEntryPhone(@NonNull String key) {
-        return (HeadsUpEntryPhone) mAlertEntries.get(key);
+        return (HeadsUpEntryPhone) mHeadsUpEntryMap.get(key);
     }
 
     @Nullable
@@ -455,7 +455,7 @@
                 } else if (mTrackingHeadsUp) {
                     mEntriesToRemoveAfterExpand.add(entry);
                 } else {
-                    removeAlertEntry(entry.getKey());
+                    removeEntry(entry.getKey());
                 }
             };
 
@@ -529,13 +529,13 @@
             mStatusBarState = newState;
             if (wasKeyguard && !isKeyguard && mBypassController.getBypassEnabled()) {
                 ArrayList<String> keysToRemove = new ArrayList<>();
-                for (AlertEntry entry : mAlertEntries.values()) {
+                for (HeadsUpEntry entry : mHeadsUpEntryMap.values()) {
                     if (entry.mEntry != null && entry.mEntry.isBubble() && !entry.isSticky()) {
                         keysToRemove.add(entry.mEntry.getKey());
                     }
                 }
                 for (String key : keysToRemove) {
-                    removeAlertEntry(key);
+                    removeEntry(key);
                 }
             }
         }
@@ -545,7 +545,7 @@
             if (!isDozing) {
                 // Let's make sure all huns we got while dozing time out within the normal timeout
                 // duration. Otherwise they could get stuck for a very long time
-                for (AlertEntry entry : mAlertEntries.values()) {
+                for (HeadsUpEntry entry : mHeadsUpEntryMap.values()) {
                     entry.updateEntry(true /* updatePostTime */, "onDozingChanged(false)");
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt
index 0bad47e..8a45ec1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt
@@ -18,12 +18,23 @@
 
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
+import com.android.systemui.Flags.smartspaceRelocateToBottom
+import android.view.View
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
 import com.android.systemui.util.ViewController
 import javax.inject.Inject
 
 class KeyguardBottomAreaViewController
-    @Inject constructor(view: KeyguardBottomAreaView, featureFlags: FeatureFlagsClassic) :
-    ViewController<KeyguardBottomAreaView> (view) {
+    @Inject constructor(
+            view: KeyguardBottomAreaView,
+            private val smartspaceController: LockscreenSmartspaceController,
+            featureFlags: FeatureFlagsClassic
+) : ViewController<KeyguardBottomAreaView> (view) {
+
+    private var smartspaceView: View? = null
 
     init {
         view.setIsLockscreenLandscapeEnabled(
@@ -31,6 +42,14 @@
     }
 
     override fun onViewAttached() {
+        if (!smartspaceRelocateToBottom() || !smartspaceController.isEnabled()) {
+            return
+        }
+
+        val ambientIndicationArea = mView.findViewById<View>(R.id.ambient_indication_container)
+        ambientIndicationArea?.visibility = View.GONE
+
+        addSmartspaceView()
     }
 
     override fun onViewDetached() {
@@ -40,4 +59,24 @@
         // TODO: remove this method.
         return mView
     }
+
+    private fun addSmartspaceView() {
+        if (!smartspaceRelocateToBottom()) {
+            return
+        }
+
+        val smartspaceContainer = mView.findViewById<View>(R.id.smartspace_container)
+        smartspaceContainer!!.visibility = View.VISIBLE
+
+        smartspaceView = smartspaceController.buildAndConnectView(smartspaceContainer as ViewGroup)
+        val lp = LinearLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+        (smartspaceContainer as ViewGroup).addView(smartspaceView, 0, lp)
+        val startPadding = context.resources.getDimensionPixelSize(
+                R.dimen.below_clock_padding_start)
+        val endPadding = context.resources.getDimensionPixelSize(
+                R.dimen.below_clock_padding_end)
+        smartspaceView?.setPaddingRelative(startPadding, 0, endPadding, 0)
+//        mKeyguardUnlockAnimationController.lockscreenSmartspace = smartspaceView
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 0a03af7..ca3e3c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -16,10 +16,12 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.Flags.centralizedStatusBarDimensRefactor;
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInScale;
 import static com.android.systemui.statusbar.notification.NotificationUtils.interpolate;
 
+import android.content.Context;
 import android.content.res.Resources;
 import android.util.MathUtils;
 
@@ -30,6 +32,7 @@
 import com.android.systemui.log.core.Logger;
 import com.android.systemui.log.dagger.KeyguardClockLog;
 import com.android.systemui.res.R;
+import com.android.systemui.shade.LargeScreenHeaderHelper;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView;
 
@@ -160,14 +163,14 @@
         mLogger = new Logger(logBuffer, TAG);
     }
 
-    /**
-     * Refreshes the dimension values.
-     */
-    public void loadDimens(Resources res) {
-        mStatusViewBottomMargin = res.getDimensionPixelSize(
-                R.dimen.keyguard_status_view_bottom_margin);
+    /** Refreshes the dimension values. */
+    public void loadDimens(Context context, Resources res) {
+        mStatusViewBottomMargin =
+                res.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin);
         mSplitShadeTopNotificationsMargin =
-                res.getDimensionPixelSize(R.dimen.large_screen_shade_header_height);
+                centralizedStatusBarDimensRefactor()
+                        ? LargeScreenHeaderHelper.getLargeScreenHeaderHeight(context)
+                        : res.getDimensionPixelSize(R.dimen.large_screen_shade_header_height);
         mSplitShadeTargetTopMargin =
                 res.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin);
 
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 7cc0888..7691459 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.Flags.centralizedStatusBarDimensRefactor;
 import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
 import static com.android.systemui.util.Utils.getStatusBarHeaderHeightKeyguard;
 
@@ -130,6 +131,9 @@
         mUserSwitcherContainer = findViewById(R.id.user_switcher_container);
         mIsPrivacyDotEnabled = mContext.getResources().getBoolean(R.bool.config_enablePrivacyDot);
         loadDimens();
+        if (!centralizedStatusBarDimensRefactor()) {
+            setGravity(Gravity.CENTER_VERTICAL);
+        }
     }
 
     /**
@@ -307,7 +311,8 @@
         final int minRight = (!isLayoutRtl() && mIsPrivacyDotEnabled)
                 ? Math.max(mMinDotWidth, mPadding.right) : mPadding.right;
 
-        setPadding(minLeft, waterfallTop, minRight, 0);
+        int top = centralizedStatusBarDimensRefactor() ? waterfallTop + mPadding.top : waterfallTop;
+        setPadding(minLeft, top, minRight, 0);
     }
 
     private boolean updateLayoutParamsNoCutout() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index f34a44a..be5c6b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -918,8 +918,15 @@
                     }
                 }
                 icon.setVisibleState(visibleState, animationsAllowed);
-                icon.setIconColor(mOverrideIconColor ? mThemedTextColorPrimary : iconColor,
-                        needsCannedAnimation && animationsAllowed);
+                if (NotificationIconContainerRefactor.isEnabled()) {
+                    if (mOverrideIconColor) {
+                        icon.setIconColor(mThemedTextColorPrimary,
+                                /* animate= */ needsCannedAnimation && animationsAllowed);
+                    }
+                } else {
+                    icon.setIconColor(mOverrideIconColor ? mThemedTextColorPrimary : iconColor,
+                            needsCannedAnimation && animationsAllowed);
+                }
                 if (animate) {
                     animateTo(icon, animationProperties);
                 } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index ae04eaf..6f78604 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -17,7 +17,9 @@
 package com.android.systemui.statusbar.phone;
 
 import static com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER;
+import static com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB;
 import static com.android.systemui.keyguard.shared.model.KeyguardState.GONE;
+import static com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN;
 import static com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
@@ -60,7 +62,9 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.keyguard.shared.model.ScrimAlpha;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -217,6 +221,7 @@
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private final KeyguardInteractor mKeyguardInteractor;
 
     private GradientColors mColors;
     private boolean mNeedsDrawableColorUpdate;
@@ -290,6 +295,30 @@
                 mScrimBehind.setViewAlpha(mBehindAlpha);
             };
 
+    /**
+     * Consumer that fades the behind scrim in and out during the transition between the lock screen
+     * and the glanceable hub.
+     *
+     * While the lock screen is showing, the behind scrim is used to slightly darken the lock screen
+     * wallpaper underneath. Since the glanceable hub is under all of the scrims, we want to fade
+     * out the scrim so that the glanceable hub isn't darkened when it opens.
+     *
+     * {@link #applyState()} handles the scrim alphas once on the glanceable hub, this is only
+     * responsible for setting the behind alpha during the transition.
+     */
+    private final Consumer<TransitionStep> mGlanceableHubConsumer = (TransitionStep step) -> {
+        final float baseAlpha = ScrimState.KEYGUARD.getBehindAlpha();
+        final float transitionProgress = step.getValue();
+        if (step.getTo() == KeyguardState.LOCKSCREEN) {
+            // Transitioning back to lock screen, fade in behind scrim again.
+            mBehindAlpha = baseAlpha * transitionProgress;
+        } else if (step.getTo() == GLANCEABLE_HUB) {
+            // Transitioning to glanceable hub, fade out behind scrim.
+            mBehindAlpha = baseAlpha * (1 - transitionProgress);
+        }
+        mScrimBehind.setViewAlpha(mBehindAlpha);
+    };
+
     Consumer<TransitionStep> mBouncerToGoneTransition;
 
     @Inject
@@ -298,7 +327,7 @@
             DozeParameters dozeParameters,
             AlarmManager alarmManager,
             KeyguardStateController keyguardStateController,
-            DelayedWakeLock.Builder delayedWakeLockBuilder,
+            DelayedWakeLock.Factory delayedWakeLockFactory,
             Handler handler,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             DockManager dockManager,
@@ -311,6 +340,7 @@
             PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
             AlternateBouncerToGoneTransitionViewModel alternateBouncerToGoneTransitionViewModel,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
+            KeyguardInteractor keyguardInteractor,
             WallpaperRepository wallpaperRepository,
             @Main CoroutineDispatcher mainDispatcher,
             LargeScreenShadeInterpolator largeScreenShadeInterpolator) {
@@ -328,7 +358,7 @@
         mScreenOffAnimationController = screenOffAnimationController;
         mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout,
                 "hide_aod_wallpaper", mHandler);
-        mWakeLock = delayedWakeLockBuilder.setHandler(mHandler).setTag("Scrims").build();
+        mWakeLock = delayedWakeLockFactory.create("Scrims");
         // Scrim alpha is initially set to the value on the resource but might be changed
         // to make sure that text on top of it is legible.
         mDozeParameters = dozeParameters;
@@ -357,6 +387,7 @@
         mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
         mAlternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+        mKeyguardInteractor = keyguardInteractor;
         mWallpaperRepository = wallpaperRepository;
         mMainDispatcher = mainDispatcher;
     }
@@ -440,6 +471,14 @@
                 mBouncerToGoneTransition, mMainDispatcher);
         collectFlow(behindScrim, mAlternateBouncerToGoneTransitionViewModel.getScrimAlpha(),
                 mScrimAlphaConsumer, mMainDispatcher);
+
+        // LOCKSCREEN<->GLANCEABLE_HUB
+        collectFlow(behindScrim,
+                mKeyguardTransitionInteractor.transition(LOCKSCREEN, GLANCEABLE_HUB),
+                mGlanceableHubConsumer, mMainDispatcher);
+        collectFlow(behindScrim,
+                mKeyguardTransitionInteractor.transition(GLANCEABLE_HUB, LOCKSCREEN),
+                mGlanceableHubConsumer, mMainDispatcher);
     }
 
     // TODO(b/270984686) recompute scrim height accurately, based on shade contents.
@@ -762,6 +801,13 @@
         } else {
             mNotificationsScrim.setDrawableBounds(left, top, right, bottom);
         }
+
+        // Only clip if the notif scrim is visible
+        if (mNotificationsAlpha > 0f) {
+            mKeyguardInteractor.setTopClippingBounds((int) top);
+        } else {
+            mKeyguardInteractor.setTopClippingBounds(null);
+        }
     }
 
     /**
@@ -804,9 +850,9 @@
             return;
         }
         mBouncerHiddenFraction = bouncerHiddenAmount;
-        if (mState == ScrimState.DREAMING) {
-            // Only the dreaming state requires this for the scrim calculation, so we should
-            // only trigger an update if dreaming.
+        if (mState == ScrimState.DREAMING || mState == ScrimState.GLANCEABLE_HUB) {
+            // The dreaming and glanceable hub states requires this for the scrim calculation, so we
+            // should only trigger an update in those states.
             applyAndDispatchState();
         }
     }
@@ -928,7 +974,7 @@
         } else if (mState == ScrimState.AUTH_SCRIMMED_SHADE) {
             mNotificationsAlpha = (float) Math.pow(getInterpolatedFraction(), 0.8f);
         } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED
-                || mState == ScrimState.PULSING) {
+                || mState == ScrimState.PULSING || mState == ScrimState.GLANCEABLE_HUB) {
             Pair<Integer, Float> result = calculateBackStateForState(mState);
             int behindTint = result.first;
             float behindAlpha = result.second;
@@ -939,6 +985,11 @@
                         mTransitionToFullShadeProgress);
                 behindTint = ColorUtils.blendARGB(behindTint, shadeResult.first,
                         mTransitionToFullShadeProgress);
+            } else if (mState == ScrimState.GLANCEABLE_HUB && mTransitionToFullShadeProgress == 0.0f
+                    && mBouncerHiddenFraction == KeyguardBouncerConstants.EXPANSION_HIDDEN) {
+                // Behind scrim should not be visible when idle on the glanceable hub and neither
+                // bouncer nor shade are showing.
+                behindAlpha = 0f;
             }
             mInFrontAlpha = mState.getFrontAlpha();
             if (mClipsQsScrim) {
@@ -954,6 +1005,13 @@
                 } else if (mState == ScrimState.SHADE_LOCKED) {
                     // going from KEYGUARD to SHADE_LOCKED state
                     mNotificationsAlpha = getInterpolatedFraction();
+                } else if (mState == ScrimState.GLANCEABLE_HUB
+                        && mTransitionToFullShadeProgress == 0.0f) {
+                    // Notification scrim should not be visible on the glanceable hub unless the
+                    // shade is showing or transitioning in. Otherwise the notification scrim will
+                    // be visible as the bouncer transitions in or after the notification shade
+                    // closes.
+                    mNotificationsAlpha = 0;
                 } else {
                     mNotificationsAlpha = Math.max(1.0f - getInterpolatedFraction(), mQsExpansion);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index e3b65ab..f2a649b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -20,6 +20,7 @@
 import android.os.Trace;
 
 import com.android.systemui.dock.DockManager;
+import com.android.systemui.res.R;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
@@ -39,8 +40,8 @@
     OFF {
         @Override
         public void prepare(ScrimState previousState) {
-            mFrontTint = Color.BLACK;
-            mBehindTint = Color.BLACK;
+            mFrontTint = mBackgroundColor;
+            mBehindTint = mBackgroundColor;
 
             mFrontAlpha = 1f;
             mBehindAlpha = 1f;
@@ -74,15 +75,15 @@
             } else {
                 mAnimationDuration = ScrimController.ANIMATION_DURATION;
             }
-            mFrontTint = Color.BLACK;
-            mBehindTint = Color.BLACK;
-            mNotifTint = mClipQsScrim ? Color.BLACK : Color.TRANSPARENT;
+            mFrontTint = mBackgroundColor;
+            mBehindTint = mBackgroundColor;
+            mNotifTint = mClipQsScrim ? mBackgroundColor : Color.TRANSPARENT;
 
             mFrontAlpha = 0;
             mBehindAlpha = mClipQsScrim ? 1 : mScrimBehindAlphaKeyguard;
             mNotifAlpha = mClipQsScrim ? mScrimBehindAlphaKeyguard : 0;
             if (mClipQsScrim) {
-                updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
+                updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor);
             }
         }
     },
@@ -93,10 +94,10 @@
             // notif scrim alpha values are determined by ScrimController#applyState
             // based on the shade expansion
 
-            mFrontTint = Color.BLACK;
+            mFrontTint = mBackgroundColor;
             mFrontAlpha = .66f;
 
-            mBehindTint = Color.BLACK;
+            mBehindTint = mBackgroundColor;
             mBehindAlpha = 1f;
         }
     },
@@ -110,7 +111,7 @@
             mBehindTint = previousState.mBehindTint;
             mBehindAlpha = previousState.mBehindAlpha;
 
-            mFrontTint = Color.BLACK;
+            mFrontTint = mBackgroundColor;
             mFrontAlpha = .66f;
         }
     },
@@ -122,7 +123,7 @@
         @Override
         public void prepare(ScrimState previousState) {
             mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
-            mBehindTint = mClipQsScrim ? Color.BLACK : mSurfaceColor;
+            mBehindTint = mClipQsScrim ? mBackgroundColor : mSurfaceColor;
             mNotifAlpha = mClipQsScrim ? mDefaultScrimAlpha : 0;
             mNotifTint = Color.TRANSPARENT;
             mFrontAlpha = 0f;
@@ -154,10 +155,10 @@
             mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
             mNotifAlpha = 1f;
             mFrontAlpha = 0f;
-            mBehindTint = mClipQsScrim ? Color.TRANSPARENT : Color.BLACK;
+            mBehindTint = mClipQsScrim ? Color.TRANSPARENT : mBackgroundColor;
 
             if (mClipQsScrim) {
-                updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
+                updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor);
             }
         }
     },
@@ -184,11 +185,11 @@
             final boolean isDocked = mDockManager.isDocked();
             mBlankScreen = mDisplayRequiresBlanking;
 
-            mFrontTint = Color.BLACK;
+            mFrontTint = mBackgroundColor;
             mFrontAlpha = (alwaysOnEnabled || isDocked || quickPickupEnabled)
                     ? mAodFrontScrimAlpha : 1f;
 
-            mBehindTint = Color.BLACK;
+            mBehindTint = mBackgroundColor;
             mBehindAlpha = ScrimController.TRANSPARENT;
 
             mAnimationDuration = ScrimController.ANIMATION_DURATION_LONG;
@@ -222,8 +223,8 @@
         @Override
         public void prepare(ScrimState previousState) {
             mFrontAlpha = mAodFrontScrimAlpha;
-            mBehindTint = Color.BLACK;
-            mFrontTint = Color.BLACK;
+            mBehindTint = mBackgroundColor;
+            mFrontTint = mBackgroundColor;
             mBlankScreen = mDisplayRequiresBlanking;
             mAnimationDuration = mWakeLockScreenSensorActive
                     ? ScrimController.ANIMATION_DURATION_LONG : ScrimController.ANIMATION_DURATION;
@@ -231,7 +232,7 @@
         @Override
         public float getMaxLightRevealScrimAlpha() {
             return mWakeLockScreenSensorActive ? ScrimController.WAKE_SENSOR_SCRIM_ALPHA
-                : AOD.getMaxLightRevealScrimAlpha();
+                    : AOD.getMaxLightRevealScrimAlpha();
         }
     },
 
@@ -245,7 +246,6 @@
             mBehindAlpha = mClipQsScrim ? 1 : 0;
             mNotifAlpha = 0;
             mFrontAlpha = 0;
-
             mAnimationDuration = mKeyguardFadingAway
                     ? mKeyguardFadingAwayDuration
                     : CentralSurfaces.FADE_KEYGUARD_DURATION;
@@ -259,22 +259,22 @@
                     && !fromAod;
 
             mFrontTint = Color.TRANSPARENT;
-            mBehindTint = Color.BLACK;
+            mBehindTint = mBackgroundColor;
             mBlankScreen = false;
 
             if (mDisplayRequiresBlanking && previousState == ScrimState.AOD) {
                 // Set all scrims black, before they fade transparent.
-                updateScrimColor(mScrimInFront, 1f /* alpha */, Color.BLACK /* tint */);
-                updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK /* tint */);
+                updateScrimColor(mScrimInFront, 1f /* alpha */, mBackgroundColor /* tint */);
+                updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor /* tint */);
 
                 // Scrims should still be black at the end of the transition.
-                mFrontTint = Color.BLACK;
-                mBehindTint = Color.BLACK;
+                mFrontTint = mBackgroundColor;
+                mBehindTint = mBackgroundColor;
                 mBlankScreen = true;
             }
 
             if (mClipQsScrim) {
-                updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
+                updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor);
             }
         }
     },
@@ -283,8 +283,8 @@
         @Override
         public void prepare(ScrimState previousState) {
             mFrontTint = Color.TRANSPARENT;
-            mBehindTint = Color.BLACK;
-            mNotifTint = mClipQsScrim ? Color.BLACK : Color.TRANSPARENT;
+            mBehindTint = mBackgroundColor;
+            mNotifTint = mClipQsScrim ? mBackgroundColor : Color.TRANSPARENT;
 
             mFrontAlpha = 0;
             mBehindAlpha = mClipQsScrim ? 1 : 0;
@@ -293,9 +293,24 @@
             mBlankScreen = false;
 
             if (mClipQsScrim) {
-                updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
+                updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor);
             }
         }
+    },
+
+    /**
+     * Device is locked or on dream and user has swiped from the right edge to enter the glanceable
+     * hub UI. From this state, the user can swipe from the left edge to go back to the lock screen
+     * or dream, as well as swipe down for the notifications and up for the bouncer.
+     */
+    GLANCEABLE_HUB {
+        @Override
+        public void prepare(ScrimState previousState) {
+            // No scrims should be visible by default in this state.
+            mBehindAlpha = 0;
+            mNotifAlpha = 0;
+            mFrontAlpha = 0;
+        }
     };
 
     boolean mBlankScreen = false;
@@ -327,9 +342,11 @@
     boolean mKeyguardFadingAway;
     long mKeyguardFadingAwayDuration;
     boolean mClipQsScrim;
+    int mBackgroundColor;
 
     public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters,
             DockManager dockManager) {
+        mBackgroundColor = scrimBehind.getContext().getColor(R.color.shade_scrim_background_dark);
         mScrimInFront = scrimInFront;
         mScrimBehind = scrimBehind;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 88347ab..4c83ca2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -69,6 +69,7 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.KeyguardWmStateRefactor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
@@ -474,7 +475,7 @@
             mIsDocked = mDockManager.isDocked();
         }
 
-        if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+        if (KeyguardWmStateRefactor.isEnabled()) {
             // Show the keyguard views whenever we've told WM that the lockscreen is visible.
             mShadeViewController.postToView(() ->
                     collectFlow(
@@ -1428,7 +1429,7 @@
             executeAfterKeyguardGoneAction();
         }
 
-        if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+        if (KeyguardWmStateRefactor.isEnabled()) {
             mKeyguardTransitionInteractor.startDismissKeyguardTransition();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 9da6111..4ee061d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -562,7 +562,7 @@
 
     private void removeHunAfterClick(ExpandableNotificationRow row) {
         String key = row.getEntry().getSbn().getKey();
-        if (mHeadsUpManager != null && mHeadsUpManager.isAlerting(key)) {
+        if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUpEntry(key)) {
             // Release the HUN notification to the shade.
             if (mPresenter.isPresenterFullyCollapsed()) {
                 HeadsUpUtil.setNeedsHeadsUpDisappearAnimationAfterClick(row, true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index 30a445f..703b3c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -118,15 +118,25 @@
 
     private void updateVpn() {
         boolean vpnVisible = mSecurityController.isVpnEnabled();
-        int vpnIconId = currentVpnIconId(mSecurityController.isVpnBranded());
+        int vpnIconId = currentVpnIconId(
+                mSecurityController.isVpnBranded(),
+                mSecurityController.isVpnValidated());
 
         mIconController.setIcon(mSlotVpn, vpnIconId,
                 mContext.getResources().getString(R.string.accessibility_vpn_on));
         mIconController.setIconVisibility(mSlotVpn, vpnVisible);
     }
 
-    private int currentVpnIconId(boolean isBranded) {
-        return isBranded ? R.drawable.stat_sys_branded_vpn : R.drawable.stat_sys_vpn_ic;
+    private int currentVpnIconId(boolean isBranded, boolean isValidated) {
+        if (isBranded) {
+            return isValidated
+                    ? R.drawable.stat_sys_branded_vpn
+                    : R.drawable.stat_sys_no_internet_branded_vpn;
+        } else {
+            return isValidated
+                    ? R.drawable.stat_sys_vpn_ic
+                    : R.drawable.stat_sys_no_internet_vpn_ic;
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/OemSatelliteInputLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/OemSatelliteInputLog.kt
new file mode 100644
index 0000000..252945f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/OemSatelliteInputLog.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.dagger
+
+import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
+import javax.inject.Qualifier
+
+/** Detailed [DeviceBasedSatelliteRepository] logs */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class OemSatelliteInputLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 89a2fb7..2b90e64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -34,8 +34,6 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigCoreStartable
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileRepositorySwitcher
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepositoryImpl
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
@@ -62,6 +60,8 @@
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryViaTrackerLib
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
+import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.policy.data.repository.UserSetupRepositoryImpl
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
@@ -265,6 +265,13 @@
             return factory.create("VerboseMobileViewLog", 100)
         }
 
+        @Provides
+        @SysUISingleton
+        @OemSatelliteInputLog
+        fun provideOemSatelliteInputLog(factory: LogBufferFactory): LogBuffer {
+            return factory.create("DeviceBasedSatelliteInputLog", 32)
+        }
+
         const val FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON =
             "FirstMobileSubShowingNetworkTypeIcon"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt
index e3c3139..8400fb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.pipeline.icons.shared.model.BindableIcon
+import com.android.systemui.statusbar.pipeline.satellite.ui.DeviceBasedSatelliteBindableIcon
 import javax.inject.Inject
 
 /**
@@ -38,11 +39,12 @@
 class BindableIconsRegistryImpl
 @Inject
 constructor(
-/** Bindables go here */
+    /** Bindables go here */
+    oemSatellite: DeviceBasedSatelliteBindableIcon
 ) : BindableIconsRegistry {
     /**
      * Adding the injected bindables to this list will get them registered with
      * StatusBarIconController
      */
-    override val bindableIcons: List<BindableIcon> = listOf()
+    override val bindableIcons: List<BindableIcon> = listOf(oemSatellite)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index bc38b53..a608be3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -72,6 +72,9 @@
      */
     val isInService: StateFlow<Boolean>
 
+    /** Reflects [android.telephony.ServiceState.isUsingNonTerrestrialNetwork] */
+    val isNonTerrestrial: StateFlow<Boolean>
+
     /** True if [android.telephony.SignalStrength] told us that this connection is using GSM */
     val isGsm: StateFlow<Boolean>
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
deleted file mode 100644
index 91886bb..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
-
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.withContext
-
-/**
- * Repository to observe the state of [DeviceProvisionedController.isUserSetup]. This information
- * can change some policy related to display
- */
-interface UserSetupRepository {
-    /** Observable tracking [DeviceProvisionedController.isUserSetup] */
-    val isUserSetupFlow: StateFlow<Boolean>
-}
-
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
-@SysUISingleton
-class UserSetupRepositoryImpl
-@Inject
-constructor(
-    private val deviceProvisionedController: DeviceProvisionedController,
-    @Background private val bgDispatcher: CoroutineDispatcher,
-    @Application scope: CoroutineScope,
-) : UserSetupRepository {
-    /** State flow that tracks [DeviceProvisionedController.isUserSetup] */
-    override val isUserSetupFlow: StateFlow<Boolean> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : DeviceProvisionedController.DeviceProvisionedListener {
-                        override fun onUserSetupChanged() {
-                            trySend(Unit)
-                        }
-                    }
-
-                deviceProvisionedController.addCallback(callback)
-
-                awaitClose { deviceProvisionedController.removeCallback(callback) }
-            }
-            .onStart { emit(Unit) }
-            .mapLatest { fetchUserSetupState() }
-            .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
-
-    private suspend fun fetchUserSetupState(): Boolean =
-        withContext(bgDispatcher) { deviceProvisionedController.isCurrentUserSetup }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
index b2a7733..6de7a00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_EMERGENCY
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_IS_GSM
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_IS_IN_SERVICE
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_IS_NTN
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_OPERATOR
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_PRIMARY_LEVEL
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_ROAMING
@@ -109,6 +110,17 @@
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), _isInService.value)
 
+    private val _isNonTerrestrial = MutableStateFlow(false)
+    override val isNonTerrestrial =
+        _isNonTerrestrial
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = COL_IS_NTN,
+                _isNonTerrestrial.value
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), _isNonTerrestrial.value)
+
     private val _isGsm = MutableStateFlow(false)
     override val isGsm =
         _isGsm
@@ -227,6 +239,7 @@
             (event.activity ?: TelephonyManager.DATA_ACTIVITY_NONE).toMobileDataActivityModel()
         _carrierNetworkChangeActive.value = event.carrierNetworkChange
         _resolvedNetworkType.value = resolvedNetworkType
+        _isNonTerrestrial.value = event.ntn
 
         isAllowedDuringAirplaneMode.value = false
         hasPrioritizedNetworkCapabilities.value = event.slice
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
index 4cd877e..11a61a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
@@ -77,6 +77,7 @@
         val roaming = getString("roam") == "show"
         val name = getString("networkname") ?: "demo mode"
         val slice = getString("slice").toBoolean()
+        val ntn = getString("ntn").toBoolean()
 
         return Mobile(
             level = level,
@@ -89,6 +90,7 @@
             roaming = roaming,
             name = name,
             slice = slice,
+            ntn = ntn,
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
index 0aa95f8..4836abe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
@@ -37,6 +37,7 @@
         val roaming: Boolean,
         val name: String,
         val slice: Boolean = false,
+        val ntn: Boolean = false,
     ) : FakeNetworkEventModel
 
     data class MobileDisabled(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index e5a5695..f8858c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -168,6 +168,7 @@
     override val isEmergencyOnly = MutableStateFlow(false).asStateFlow()
     override val operatorAlphaShort = MutableStateFlow(null).asStateFlow()
     override val isInService = MutableStateFlow(true).asStateFlow()
+    override val isNonTerrestrial = MutableStateFlow(false).asStateFlow()
     override val isGsm = MutableStateFlow(false).asStateFlow()
     override val carrierNetworkChangeActive = MutableStateFlow(false).asStateFlow()
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index 48bf7ac..a124196 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -175,6 +175,21 @@
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isInService.value)
 
+    override val isNonTerrestrial =
+        activeRepo
+            .flatMapLatest { it.isNonTerrestrial }
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = COL_IS_NTN,
+                activeRepo.value.isNonTerrestrial.value
+            )
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                activeRepo.value.isNonTerrestrial.value
+            )
+
     override val isGsm =
         activeRepo
             .flatMapLatest { it.isGsm }
@@ -366,6 +381,7 @@
         const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChangeActive"
         const val COL_CDMA_LEVEL = "cdmaLevel"
         const val COL_EMERGENCY = "emergencyOnly"
+        const val COL_IS_NTN = "isNtn"
         const val COL_IS_GSM = "isGsm"
         const val COL_IS_IN_SERVICE = "isInService"
         const val COL_OPERATOR = "operatorName"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index f44401b..77fd6be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -227,6 +227,12 @@
             .map { Utils.isInService(it.serviceState) }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
+    override val isNonTerrestrial =
+        callbackEvents
+            .mapNotNull { it.onServiceStateChanged }
+            .map { it.serviceState.isUsingNonTerrestrialNetwork }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
     override val isGsm =
         callbackEvents
             .mapNotNull { it.onSignalStrengthChanged }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index fe49c07..6b30326 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
 
 import android.content.Context
+import com.android.internal.telephony.flags.Flags
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.settingslib.graph.SignalDrawable
 import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
@@ -32,14 +33,18 @@
 import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.DefaultIcon
 import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.OverriddenIcon
 import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
@@ -79,6 +84,9 @@
     /** Whether or not to show the slice attribution */
     val showSliceAttribution: StateFlow<Boolean>
 
+    /** True if this connection is satellite-based */
+    val isNonTerrestrial: StateFlow<Boolean>
+
     /**
      * Provider name for this network connection. The name can be one of 3 values:
      * 1. The default network name, if one is configured
@@ -244,6 +252,13 @@
     override val showSliceAttribution: StateFlow<Boolean> =
         connectionRepository.hasPrioritizedNetworkCapabilities
 
+    override val isNonTerrestrial: StateFlow<Boolean> =
+        if (Flags.carrierEnabledSatelliteFlag()) {
+            connectionRepository.isNonTerrestrial
+        } else {
+            MutableStateFlow(false).asStateFlow()
+        }
+
     override val isRoaming: StateFlow<Boolean> =
         combine(
                 connectionRepository.carrierNetworkChangeActive,
@@ -313,26 +328,45 @@
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
 
-    override val signalLevelIcon: StateFlow<SignalIconModel> = run {
-        val initial =
-            SignalIconModel(
-                level = shownLevel.value,
-                numberOfLevels = numberOfLevels.value,
-                showExclamationMark = showExclamationMark.value,
-                carrierNetworkChange = carrierNetworkChangeActive.value,
-            )
+    private val cellularIcon: Flow<SignalIconModel.Cellular> =
         combine(
+            shownLevel,
+            numberOfLevels,
+            showExclamationMark,
+            carrierNetworkChangeActive,
+        ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange ->
+            SignalIconModel.Cellular(
                 shownLevel,
                 numberOfLevels,
                 showExclamationMark,
-                carrierNetworkChangeActive,
-            ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange ->
-                SignalIconModel(
-                    shownLevel,
-                    numberOfLevels,
-                    showExclamationMark,
-                    carrierNetworkChange,
-                )
+                carrierNetworkChange,
+            )
+        }
+
+    private val satelliteIcon: Flow<SignalIconModel.Satellite> =
+        shownLevel.map {
+            SignalIconModel.Satellite(
+                level = it,
+                icon = SatelliteIconModel.fromSignalStrength(it)
+                        ?: SatelliteIconModel.fromSignalStrength(0)!!
+            )
+        }
+
+    override val signalLevelIcon: StateFlow<SignalIconModel> = run {
+        val initial =
+            SignalIconModel.Cellular(
+                shownLevel.value,
+                numberOfLevels.value,
+                showExclamationMark.value,
+                carrierNetworkChangeActive.value,
+            )
+        isNonTerrestrial
+            .flatMapLatest { ntn ->
+                if (ntn) {
+                    satelliteIcon
+                } else {
+                    cellularIcon
+                }
             }
             .distinctUntilChanged()
             .logDiffsForTable(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 39135c7..d555c47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -32,9 +32,9 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
+import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
 import com.android.systemui.util.CarrierConfigTracker
 import java.lang.ref.WeakReference
 import javax.inject.Inject
@@ -105,7 +105,7 @@
     val isDefaultConnectionFailed: StateFlow<Boolean>
 
     /** True once the user has been set up */
-    val isUserSetup: StateFlow<Boolean>
+    val isUserSetUp: StateFlow<Boolean>
 
     /** True if we're configured to force-hide the mobile icons and false otherwise. */
     val isForceHidden: Flow<Boolean>
@@ -362,7 +362,7 @@
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
-    override val isUserSetup: StateFlow<Boolean> = userSetupRepo.isUserSetupFlow
+    override val isUserSetUp: StateFlow<Boolean> = userSetupRepo.isUserSetUp
 
     override val isForceHidden: Flow<Boolean> =
         connectivityRepository.forceHiddenSlots
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt
index e58f081..d6b8fd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt
@@ -17,51 +17,94 @@
 package com.android.systemui.statusbar.pipeline.mobile.domain.model
 
 import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.log.table.Diffable
 import com.android.systemui.log.table.TableRowLogger
 
-/** A model that will be consumed by [SignalDrawable] to show the mobile triangle icon. */
-data class SignalIconModel(
-    val level: Int,
-    val numberOfLevels: Int,
-    val showExclamationMark: Boolean,
-    val carrierNetworkChange: Boolean,
-) : Diffable<SignalIconModel> {
-    // TODO(b/267767715): Can we implement [logDiffs] and [logFull] generically for data classes?
+sealed interface SignalIconModel : Diffable<SignalIconModel> {
+    val level: Int
+
     override fun logDiffs(prevVal: SignalIconModel, row: TableRowLogger) {
-        if (prevVal.level != level) {
+        logPartial(prevVal, row)
+    }
+
+    override fun logFull(row: TableRowLogger) = logFully(row)
+
+    fun logFully(row: TableRowLogger)
+
+    fun logPartial(prevVal: SignalIconModel, row: TableRowLogger)
+
+    /** A model that will be consumed by [SignalDrawable] to show the mobile triangle icon. */
+    data class Cellular(
+        override val level: Int,
+        val numberOfLevels: Int,
+        val showExclamationMark: Boolean,
+        val carrierNetworkChange: Boolean,
+    ) : SignalIconModel {
+        override fun logPartial(prevVal: SignalIconModel, row: TableRowLogger) {
+            if (prevVal !is Cellular) {
+                logFull(row)
+            } else {
+                if (prevVal.level != level) {
+                    row.logChange(COL_LEVEL, level)
+                }
+                if (prevVal.numberOfLevels != numberOfLevels) {
+                    row.logChange(COL_NUM_LEVELS, numberOfLevels)
+                }
+                if (prevVal.showExclamationMark != showExclamationMark) {
+                    row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
+                }
+                if (prevVal.carrierNetworkChange != carrierNetworkChange) {
+                    row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChange)
+                }
+            }
+        }
+
+        override fun logFully(row: TableRowLogger) {
+            row.logChange(COL_TYPE, "c")
             row.logChange(COL_LEVEL, level)
-        }
-        if (prevVal.numberOfLevels != numberOfLevels) {
             row.logChange(COL_NUM_LEVELS, numberOfLevels)
-        }
-        if (prevVal.showExclamationMark != showExclamationMark) {
             row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
-        }
-        if (prevVal.carrierNetworkChange != carrierNetworkChange) {
             row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChange)
         }
+
+        /** Convert this model to an [Int] consumable by [SignalDrawable]. */
+        fun toSignalDrawableState(): Int =
+            if (carrierNetworkChange) {
+                SignalDrawable.getCarrierChangeState(numberOfLevels)
+            } else {
+                SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
+            }
     }
 
-    override fun logFull(row: TableRowLogger) {
-        row.logChange(COL_LEVEL, level)
-        row.logChange(COL_NUM_LEVELS, numberOfLevels)
-        row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
-        row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChange)
-    }
-
-    /** Convert this model to an [Int] consumable by [SignalDrawable]. */
-    fun toSignalDrawableState(): Int =
-        if (carrierNetworkChange) {
-            SignalDrawable.getCarrierChangeState(numberOfLevels)
-        } else {
-            SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
+    /**
+     * For non-terrestrial networks, we can use a resource-backed icon instead of the
+     * [SignalDrawable]-backed version above
+     */
+    data class Satellite(
+        override val level: Int,
+        val icon: Icon.Resource,
+    ) : SignalIconModel {
+        override fun logPartial(prevVal: SignalIconModel, row: TableRowLogger) {
+            if (prevVal !is Satellite) {
+                logFull(row)
+            } else {
+                if (prevVal.level != level) row.logChange(COL_LEVEL, level)
+            }
         }
 
+        override fun logFully(row: TableRowLogger) {
+            row.logChange("numLevels", "HELLO")
+            row.logChange(COL_TYPE, "s")
+            row.logChange(COL_LEVEL, level)
+        }
+    }
+
     companion object {
         private const val COL_LEVEL = "level"
         private const val COL_NUM_LEVELS = "numLevels"
         private const val COL_SHOW_EXCLAMATION = "showExclamation"
         private const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChange"
+        private const val COL_TYPE = "type"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
index a1a5370..43cb38f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
@@ -59,7 +59,7 @@
                 str1 = parentView.getIdForLogging()
                 int1 = subId
                 int2 = icon.level
-                bool1 = icon.showExclamationMark
+                bool1 = if (icon is SignalIconModel.Cellular) icon.showExclamationMark else false
             },
             {
                 "Binder[subId=$int1, viewId=$str1] received new signal icon: " +
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index 5475528..a0c5618 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
@@ -70,7 +71,7 @@
         val networkTypeView = view.requireViewById<ImageView>(R.id.mobile_type)
         val networkTypeContainer = view.requireViewById<FrameLayout>(R.id.mobile_type_container)
         val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
-        val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
+        val mobileDrawable = SignalDrawable(view.context)
         val roamingView = view.requireViewById<ImageView>(R.id.mobile_roaming)
         val roamingSpace = view.requireViewById<Space>(R.id.mobile_roaming_space)
         val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot)
@@ -138,7 +139,12 @@
                                 viewModel.subscriptionId,
                                 icon,
                             )
-                            mobileDrawable.level = icon.toSignalDrawableState()
+                            if (icon is SignalIconModel.Cellular) {
+                                iconView.setImageDrawable(mobileDrawable)
+                                mobileDrawable.level = icon.toSignalDrawableState()
+                            } else if (icon is SignalIconModel.Satellite) {
+                                IconViewBinder.bind(icon.icon, iconView)
+                            }
                         }
                     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 60c662d..eda5c44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -33,12 +33,15 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.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.mapLatest
 import kotlinx.coroutines.flow.stateIn
 
 /** Common interface for all of the location-based mobile icon view models. */
@@ -71,7 +74,6 @@
  * model gets the exact same information, as well as allows us to log that unified state only once
  * per icon.
  */
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
 class MobileIconViewModel(
     override val subscriptionId: Int,
@@ -81,6 +83,100 @@
     flags: FeatureFlagsClassic,
     scope: CoroutineScope,
 ) : MobileIconViewModelCommon {
+    private val cellProvider by lazy {
+        CellularIconViewModel(
+            subscriptionId,
+            iconInteractor,
+            airplaneModeInteractor,
+            constants,
+            flags,
+            scope,
+        )
+    }
+
+    private val satelliteProvider by lazy {
+        CarrierBasedSatelliteViewModelImpl(
+            subscriptionId,
+            iconInteractor,
+        )
+    }
+
+    /**
+     * Similar to repository switching, this allows us to split up the logic of satellite/cellular
+     * states, since they are different by nature
+     */
+    private val vmProvider: Flow<MobileIconViewModelCommon> =
+        iconInteractor.isNonTerrestrial
+            .mapLatest { nonTerrestrial ->
+                if (nonTerrestrial) {
+                    satelliteProvider
+                } else {
+                    cellProvider
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), cellProvider)
+
+    override val isVisible: StateFlow<Boolean> =
+        vmProvider
+            .flatMapLatest { it.isVisible }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+    override val icon: Flow<SignalIconModel> = vmProvider.flatMapLatest { it.icon }
+
+    override val contentDescription: Flow<ContentDescription> =
+        vmProvider.flatMapLatest { it.contentDescription }
+
+    override val roaming: Flow<Boolean> = vmProvider.flatMapLatest { it.roaming }
+
+    override val networkTypeIcon: Flow<Icon.Resource?> =
+        vmProvider.flatMapLatest { it.networkTypeIcon }
+
+    override val networkTypeBackground: StateFlow<Icon.Resource?> =
+        vmProvider
+            .flatMapLatest { it.networkTypeBackground }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+    override val activityInVisible: Flow<Boolean> =
+        vmProvider.flatMapLatest { it.activityInVisible }
+
+    override val activityOutVisible: Flow<Boolean> =
+        vmProvider.flatMapLatest { it.activityOutVisible }
+
+    override val activityContainerVisible: Flow<Boolean> =
+        vmProvider.flatMapLatest { it.activityContainerVisible }
+}
+
+/** Representation of this network when it is non-terrestrial (e.g., satellite) */
+private class CarrierBasedSatelliteViewModelImpl(
+    override val subscriptionId: Int,
+    interactor: MobileIconInteractor,
+) : MobileIconViewModelCommon {
+    override val isVisible: StateFlow<Boolean> = MutableStateFlow(true)
+    override val icon: Flow<SignalIconModel> = interactor.signalLevelIcon
+
+    override val contentDescription: Flow<ContentDescription> =
+        MutableStateFlow(ContentDescription.Loaded(""))
+
+    /** These fields are not used for satellite icons currently */
+    override val roaming: Flow<Boolean> = flowOf(false)
+    override val networkTypeIcon: Flow<Icon.Resource?> = flowOf(null)
+    override val networkTypeBackground: StateFlow<Icon.Resource?> = MutableStateFlow(null)
+    override val activityInVisible: Flow<Boolean> = flowOf(false)
+    override val activityOutVisible: Flow<Boolean> = flowOf(false)
+    override val activityContainerVisible: Flow<Boolean> = flowOf(false)
+}
+
+/** Terrestrial (cellular) icon. */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+private class CellularIconViewModel(
+    override val subscriptionId: Int,
+    iconInteractor: MobileIconInteractor,
+    airplaneModeInteractor: AirplaneModeInteractor,
+    constants: ConnectivityConstants,
+    flags: FeatureFlagsClassic,
+    scope: CoroutineScope,
+) : MobileIconViewModelCommon {
     override val isVisible: StateFlow<Boolean> =
         if (!constants.hasDataCapabilities) {
                 flowOf(false)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index 8fc8b2f..f73d089 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -19,11 +19,18 @@
 import android.os.OutcomeReceiver
 import android.telephony.satellite.NtnSignalStrengthCallback
 import android.telephony.satellite.SatelliteManager
-import android.telephony.satellite.SatelliteStateCallback
+import android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS
+import android.telephony.satellite.SatelliteModemStateCallback
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.MessageInitializer
+import com.android.systemui.log.core.MessagePrinter
+import com.android.systemui.statusbar.pipeline.dagger.OemSatelliteInputLog
 import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
 import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Companion.whenSupported
 import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.NotSupported
@@ -62,8 +69,10 @@
 /**
  * "Supported" here means supported by the device. The value of this should be stable during the
  * process lifetime.
+ *
+ * @VisibleForTesting
  */
-private sealed interface SatelliteSupport {
+sealed interface SatelliteSupport {
     /** Not yet fetched */
     data object Unknown : SatelliteSupport
 
@@ -123,6 +132,7 @@
     satelliteManagerOpt: Optional<SatelliteManager>,
     @Background private val bgDispatcher: CoroutineDispatcher,
     @Application private val scope: CoroutineScope,
+    @OemSatelliteInputLog private val logBuffer: LogBuffer,
     private val systemClock: SystemClock,
 ) : DeviceBasedSatelliteRepository {
 
@@ -132,7 +142,8 @@
 
     // Some calls into satellite manager will throw exceptions if it is not supported.
     // This is never expected to change after boot, but may need to be retried in some cases
-    private val satelliteSupport: MutableStateFlow<SatelliteSupport> = MutableStateFlow(Unknown)
+    @get:VisibleForTesting
+    val satelliteSupport: MutableStateFlow<SatelliteSupport> = MutableStateFlow(Unknown)
 
     init {
         satelliteManager = satelliteManagerOpt.getOrNull()
@@ -142,9 +153,21 @@
         if (satelliteManager != null) {
             // First, check that satellite is supported on this device
             scope.launch {
-                ensureMinUptime(systemClock, MIN_UPTIME)
+                val waitTime = ensureMinUptime(systemClock, MIN_UPTIME)
+                if (waitTime > 0) {
+                    logBuffer.i({ long1 = waitTime }) {
+                        "Waiting $long1 ms before checking for satellite support"
+                    }
+                    delay(waitTime)
+                }
+
                 satelliteSupport.value = satelliteManager.checkSatelliteSupported()
 
+                logBuffer.i(
+                    { str1 = satelliteSupport.value.toString() },
+                    { "Checked for system support. support=$str1" },
+                )
+
                 // We only need to check location availability if this mode is supported
                 if (satelliteSupport.value is Supported) {
                     isSatelliteAllowedForCurrentLocation.subscriptionCount
@@ -159,6 +182,9 @@
                                  * connection might cause more frequent checks.
                                  */
                                 while (true) {
+                                    logBuffer.i {
+                                        "requestIsCommunicationAllowedForCurrentLocation"
+                                    }
                                     checkIsSatelliteAllowed()
                                     delay(POLLING_INTERVAL_MS)
                                 }
@@ -167,6 +193,8 @@
                 }
             }
         } else {
+            logBuffer.i { "Satellite manager is null" }
+
             satelliteSupport.value = NotSupported
         }
     }
@@ -180,13 +208,21 @@
     // By using the SupportedSatelliteManager here, we expect registration never to fail
     private fun connectionStateFlow(sm: SupportedSatelliteManager): Flow<SatelliteConnectionState> =
         conflatedCallbackFlow {
-                val cb = SatelliteStateCallback { state ->
+                val cb = SatelliteModemStateCallback { state ->
+                    logBuffer.i({ int1 = state }) { "onSatelliteModemStateChanged: state=$int1" }
                     trySend(SatelliteConnectionState.fromModemState(state))
                 }
 
-                sm.registerForSatelliteModemStateChanged(bgDispatcher.asExecutor(), cb)
+                var registered = false
 
-                awaitClose { sm.unregisterForSatelliteModemStateChanged(cb) }
+                try {
+                    val res = sm.registerForModemStateChanged(bgDispatcher.asExecutor(), cb)
+                    registered = res == SATELLITE_RESULT_SUCCESS
+                } catch (e: Exception) {
+                    logBuffer.e("error registering for modem state", e)
+                }
+
+                awaitClose { if (registered) sm.unregisterForModemStateChanged(cb) }
             }
             .flowOn(bgDispatcher)
 
@@ -197,27 +233,40 @@
     private fun signalStrengthFlow(sm: SupportedSatelliteManager) =
         conflatedCallbackFlow {
                 val cb = NtnSignalStrengthCallback { signalStrength ->
+                    logBuffer.i({ int1 = signalStrength.level }) {
+                        "onNtnSignalStrengthChanged: level=$int1"
+                    }
                     trySend(signalStrength.level)
                 }
 
-                sm.registerForNtnSignalStrengthChanged(bgDispatcher.asExecutor(), cb)
+                var registered = false
+                try {
+                    sm.registerForNtnSignalStrengthChanged(bgDispatcher.asExecutor(), cb)
+                    registered = true
+                } catch (e: Exception) {
+                    logBuffer.e("error registering for signal strength", e)
+                }
 
-                awaitClose { sm.unregisterForNtnSignalStrengthChanged(cb) }
+                awaitClose { if (registered) sm.unregisterForNtnSignalStrengthChanged(cb) }
             }
             .flowOn(bgDispatcher)
 
     /** Fire off a request to check for satellite availability. Always runs on the bg context */
     private suspend fun checkIsSatelliteAllowed() =
         withContext(bgDispatcher) {
-            satelliteManager?.requestIsSatelliteCommunicationAllowedForCurrentLocation(
+            satelliteManager?.requestIsCommunicationAllowedForCurrentLocation(
                 bgDispatcher.asExecutor(),
                 object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> {
                     override fun onError(e: SatelliteManager.SatelliteException) {
-                        android.util.Log.e(TAG, "Found exception when checking for satellite: ", e)
+                        logBuffer.e(
+                            "Found exception when checking availability",
+                            e,
+                        )
                         isSatelliteAllowedForCurrentLocation.value = false
                     }
 
                     override fun onResult(allowed: Boolean) {
+                        logBuffer.i { "isSatelliteAllowedForCurrentLocation: $allowed" }
                         isSatelliteAllowedForCurrentLocation.value = allowed
                     }
                 }
@@ -239,30 +288,54 @@
                     }
 
                     override fun onError(error: SatelliteManager.SatelliteException) {
+                        logBuffer.e(
+                            "Exception when checking for satellite support. " +
+                                "Assuming it is not supported for this device.",
+                            error,
+                        )
+
                         // Assume that an error means it's not supported
                         continuation.resume(NotSupported)
                     }
                 }
 
-            requestIsSatelliteSupported(bgDispatcher.asExecutor(), cb)
+            try {
+                requestIsSupported(bgDispatcher.asExecutor(), cb)
+            } catch (error: Exception) {
+                logBuffer.e(
+                    "Exception when checking for satellite support. " +
+                        "Assuming it is not supported for this device.",
+                    error,
+                )
+                continuation.resume(NotSupported)
+            }
         }
 
     companion object {
         // TTL for satellite polling is one hour
         const val POLLING_INTERVAL_MS: Long = 1000 * 60 * 60
 
-        // Let the system boot up and stabilize before we check for system support
-        const val MIN_UPTIME: Long = 1000 * 60
+        // Let the system boot up (5s) and stabilize before we check for system support
+        const val MIN_UPTIME: Long = 1000 * 5
 
         private const val TAG = "DeviceBasedSatelliteRepo"
 
-        /** If our process hasn't been up for at least MIN_UPTIME, delay until we reach that time */
-        private suspend fun ensureMinUptime(clock: SystemClock, uptime: Long) {
-            val timeTilMinUptime =
-                uptime - (clock.uptimeMillis() - android.os.Process.getStartUptimeMillis())
-            if (timeTilMinUptime > 0) {
-                delay(timeTilMinUptime)
-            }
-        }
+        /** Calculates how long we have to wait to reach MIN_UPTIME */
+        private fun ensureMinUptime(clock: SystemClock, uptime: Long): Long =
+            uptime - (clock.uptimeMillis() - android.os.Process.getStartUptimeMillis())
+
+        /** A couple of convenience logging methods rather than a whole class */
+        private fun LogBuffer.i(
+            initializer: MessageInitializer = {},
+            printer: MessagePrinter,
+        ) = this.log(TAG, LogLevel.INFO, initializer, printer)
+
+        private fun LogBuffer.e(message: String, exception: Throwable? = null) =
+            this.log(
+                tag = TAG,
+                level = LogLevel.ERROR,
+                message = message,
+                exception = exception,
+            )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
index 8779577..6e1114c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
@@ -79,7 +79,7 @@
             } else {
                 flowOf(false)
             }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+            .stateIn(scope, SharingStarted.WhileSubscribed(), true)
 }
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/DeviceBasedSatelliteBindableIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/DeviceBasedSatelliteBindableIcon.kt
new file mode 100644
index 0000000..f5d0f6b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/DeviceBasedSatelliteBindableIcon.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.satellite.ui
+
+import android.content.Context
+import com.android.internal.telephony.flags.Flags.oemEnabledSatelliteFlag
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.icons.shared.model.BindableIcon
+import com.android.systemui.statusbar.pipeline.icons.shared.model.ModernStatusBarViewCreator
+import com.android.systemui.statusbar.pipeline.satellite.ui.binder.DeviceBasedSatelliteIconBinder
+import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.DeviceBasedSatelliteViewModel
+import com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarIconView
+import javax.inject.Inject
+
+@SysUISingleton
+class DeviceBasedSatelliteBindableIcon
+@Inject
+constructor(
+    context: Context,
+    viewModel: DeviceBasedSatelliteViewModel,
+) : BindableIcon {
+    override val slot: String =
+        context.getString(com.android.internal.R.string.status_bar_oem_satellite)
+
+    override val initializer = ModernStatusBarViewCreator { context ->
+        SingleBindableStatusBarIconView.createView(context).also { view ->
+            view.initView(slot) { DeviceBasedSatelliteIconBinder.bind(view, viewModel) }
+        }
+    }
+
+    override val shouldBindIcon: Boolean = oemEnabledSatelliteFlag()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/binder/DeviceBasedSatelliteIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/binder/DeviceBasedSatelliteIconBinder.kt
new file mode 100644
index 0000000..59ac5f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/binder/DeviceBasedSatelliteIconBinder.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.satellite.ui.binder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.DeviceBasedSatelliteViewModel
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
+import com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarIconView
+import kotlinx.coroutines.launch
+
+object DeviceBasedSatelliteIconBinder {
+    fun bind(
+        view: SingleBindableStatusBarIconView,
+        viewModel: DeviceBasedSatelliteViewModel,
+    ): ModernStatusBarViewBinding {
+        return SingleBindableStatusBarIconView.withDefaultBinding(
+            view = view,
+            shouldBeVisible = { viewModel.icon.value != null }
+        ) {
+            lifecycleScope.launch {
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    viewModel.icon.collect { newIcon ->
+                        if (newIcon == null) {
+                            view.iconView.setImageDrawable(null)
+                        } else {
+                            IconViewBinder.bind(newIcon, view.iconView)
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
new file mode 100644
index 0000000..63566ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.satellite.ui.model
+
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
+
+/**
+ * Define the [Icon] that relates to a given satellite connection state + level. Note that for now
+ * We don't need any data class box, so we can just use a simple mapping function.
+ */
+object SatelliteIconModel {
+    fun fromConnectionState(
+        connectionState: SatelliteConnectionState,
+        signalStrength: Int,
+    ): Icon.Resource? =
+        when (connectionState) {
+            // TODO(b/316635648): check if this should be null
+            SatelliteConnectionState.Unknown,
+            SatelliteConnectionState.Off,
+            SatelliteConnectionState.On ->
+                Icon.Resource(
+                    res = R.drawable.ic_satellite_not_connected,
+                    contentDescription = null,
+                )
+            SatelliteConnectionState.Connected -> fromSignalStrength(signalStrength)
+        }
+
+    /**
+     * Satellite icon appropriate for when we are connected. Use [fromConnectionState] for a more
+     * generally correct representation.
+     */
+    fun fromSignalStrength(
+        signalStrength: Int,
+    ): Icon.Resource? =
+        // TODO(b/316634365): these need content descriptions
+        when (signalStrength) {
+            // No signal
+            0 -> Icon.Resource(res = R.drawable.ic_satellite_connected_0, contentDescription = null)
+
+            // Poor -> Moderate
+            1,
+            2 -> Icon.Resource(res = R.drawable.ic_satellite_connected_1, contentDescription = null)
+
+            // Good -> Great
+            3,
+            4 -> Icon.Resource(res = R.drawable.ic_satellite_connected_2, contentDescription = null)
+            else -> null
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
new file mode 100644
index 0000000..0051161
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel
+
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor
+import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * View-Model for the device-based satellite icon. This icon will only show in the status bar if
+ * satellite is available AND all other service states are considered OOS.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+class DeviceBasedSatelliteViewModel
+@Inject
+constructor(
+    interactor: DeviceBasedSatelliteInteractor,
+    @Application scope: CoroutineScope,
+) {
+    private val shouldShowIcon: StateFlow<Boolean> =
+        interactor.areAllConnectionsOutOfService
+            .flatMapLatest { allOos ->
+                if (!allOos) {
+                    flowOf(false)
+                } else {
+                    interactor.isSatelliteAllowed
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+    val icon: StateFlow<Icon?> =
+        combine(
+                shouldShowIcon,
+                interactor.connectionState,
+                interactor.signalStrength,
+            ) { shouldShow, state, signalStrength ->
+                if (shouldShow) {
+                    SatelliteIconModel.fromConnectionState(state, signalStrength)
+                } else {
+                    null
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
index 3b87bed..25a2c9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
@@ -103,7 +103,7 @@
      *
      * Creates a dot view, and uses [bindingCreator] to get and set the binding.
      */
-    fun initView(slot: String, bindingCreator: () -> ModernStatusBarViewBinding) {
+    open fun initView(slot: String, bindingCreator: () -> ModernStatusBarViewBinding) {
         // The dot view requires [slot] to be set, and the [binding] may require an instantiated dot
         // view. So, this is the required order.
         this.slot = slot
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconView.kt
new file mode 100644
index 0000000..c663c37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconView.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.ui.view
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.ImageView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewVisibilityHelper
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
+
+/** Simple single-icon view that is bound to bindable_status_bar_icon.xml */
+class SingleBindableStatusBarIconView(
+    context: Context,
+    attrs: AttributeSet?,
+) : ModernStatusBarView(context, attrs) {
+
+    internal lateinit var iconView: ImageView
+    internal lateinit var dotView: StatusBarIconView
+
+    override fun toString(): String {
+        return "SingleBindableStatusBarIcon(" +
+            "slot='$slot', " +
+            "isCollecting=${binding.isCollecting()}, " +
+            "visibleState=${StatusBarIconView.getVisibleStateString(visibleState)}); " +
+            "viewString=${super.toString()}"
+    }
+
+    override fun initView(slot: String, bindingCreator: () -> ModernStatusBarViewBinding) {
+        super.initView(slot, bindingCreator)
+
+        iconView = requireViewById(R.id.icon_view)
+        dotView = requireViewById(R.id.status_bar_dot)
+    }
+
+    companion object {
+        fun createView(
+            context: Context,
+        ): SingleBindableStatusBarIconView {
+            return LayoutInflater.from(context).inflate(R.layout.bindable_status_bar_icon, null)
+                as SingleBindableStatusBarIconView
+        }
+
+        /**
+         * Using a given binding [block], create the necessary scaffolding to handle the general
+         * case of a single status bar icon. This includes eliding into a dot view when there is not
+         * enough space, and handling tint.
+         *
+         * [block] should be a simple [launch] call that handles updating the single icon view with
+         * its new view. Currently there is no simple way to e.g., extend to handle multiple tints
+         * for dual-layered icons, and any more complex logic should probably find a way to return
+         * its own version of [ModernStatusBarViewBinding].
+         */
+        fun withDefaultBinding(
+            view: SingleBindableStatusBarIconView,
+            shouldBeVisible: () -> Boolean,
+            block: suspend LifecycleOwner.(View) -> Unit
+        ): SingleBindableStatusBarIconViewBinding {
+            @StatusBarIconView.VisibleState
+            val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN)
+
+            val iconTint: MutableStateFlow<Int> = MutableStateFlow(Color.WHITE)
+            val decorTint: MutableStateFlow<Int> = MutableStateFlow(Color.WHITE)
+
+            var isCollecting: Boolean = false
+
+            view.repeatWhenAttached {
+                // Child binding
+                block(view)
+
+                lifecycleScope.launch {
+                    repeatOnLifecycle(Lifecycle.State.STARTED) {
+                        // isVisible controls the visibility state of the outer group, and thus it
+                        // needs
+                        // to run in the CREATED lifecycle so it can continue to watch while
+                        // invisible
+                        // See (b/291031862) for details
+                        launch {
+                            visibilityState.collect { visibilityState ->
+                                // for b/296864006, we can not hide all the child views if
+                                // visibilityState is STATE_HIDDEN. Because hiding all child views
+                                // would cause the
+                                // getWidth() of this view return 0, and that would cause the
+                                // translation
+                                // calculation fails in StatusIconContainer. Therefore, like class
+                                // MobileIconBinder, instead of set the child views visibility to
+                                // View.GONE,
+                                // we set their visibility to View.INVISIBLE to make them invisible
+                                // but
+                                // keep the width.
+                                ModernStatusBarViewVisibilityHelper.setVisibilityState(
+                                    visibilityState,
+                                    view.iconView,
+                                    view.dotView,
+                                )
+                            }
+                        }
+
+                        launch {
+                            iconTint.collect { tint ->
+                                val tintList = ColorStateList.valueOf(tint)
+                                view.iconView.imageTintList = tintList
+                                view.dotView.setDecorColor(tint)
+                            }
+                        }
+
+                        launch {
+                            decorTint.collect { decorTint -> view.dotView.setDecorColor(decorTint) }
+                        }
+
+                        try {
+                            awaitCancellation()
+                        } finally {
+                            isCollecting = false
+                        }
+                    }
+                }
+            }
+
+            return object : SingleBindableStatusBarIconViewBinding {
+                override val decorTint: Int
+                    get() = decorTint.value
+
+                override val iconTint: Int
+                    get() = iconTint.value
+
+                override fun getShouldIconBeVisible(): Boolean {
+                    return shouldBeVisible()
+                }
+
+                override fun onVisibilityStateChanged(state: Int) {
+                    visibilityState.value = state
+                }
+
+                override fun onIconTintChanged(newTint: Int, contrastTint: Int) {
+                    iconTint.value = newTint
+                }
+
+                override fun onDecorTintChanged(newTint: Int) {
+                    decorTint.value = newTint
+                }
+
+                override fun isCollecting(): Boolean {
+                    return isCollecting
+                }
+            }
+        }
+    }
+}
+
+@VisibleForTesting
+interface SingleBindableStatusBarIconViewBinding : ModernStatusBarViewBinding {
+    val iconTint: Int
+    val decorTint: Int
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
index ae58398..352413e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.text.Html
 import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -28,6 +29,7 @@
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
 import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileModel
 import com.android.systemui.statusbar.pipeline.shared.ui.model.SignalIcon
@@ -116,14 +118,31 @@
                     it.signalLevelIcon,
                     mobileDataContentName,
                 ) { networkNameModel, signalIcon, dataContentDescription ->
-                    val secondary =
-                        mobileDataContentConcat(networkNameModel.name, dataContentDescription)
-                    InternetTileModel.Active(
-                        secondaryTitle = secondary,
-                        icon = SignalIcon(signalIcon.toSignalDrawableState()),
-                        stateDescription = ContentDescription.Loaded(secondary.toString()),
-                        contentDescription = ContentDescription.Loaded(internetLabel),
-                    )
+                    when (signalIcon) {
+                        is SignalIconModel.Cellular -> {
+                            val secondary =
+                                mobileDataContentConcat(
+                                    networkNameModel.name,
+                                    dataContentDescription
+                                )
+                            InternetTileModel.Active(
+                                secondaryTitle = secondary,
+                                icon = SignalIcon(signalIcon.toSignalDrawableState()),
+                                stateDescription = ContentDescription.Loaded(secondary.toString()),
+                                contentDescription = ContentDescription.Loaded(internetLabel),
+                            )
+                        }
+                        is SignalIconModel.Satellite -> {
+                            val secondary =
+                                signalIcon.icon.contentDescription.loadContentDescription(context)
+                            InternetTileModel.Active(
+                                secondaryTitle = secondary,
+                                iconId = signalIcon.icon.res,
+                                stateDescription = ContentDescription.Loaded(secondary),
+                                contentDescription = ContentDescription.Loaded(internetLabel),
+                            )
+                        }
+                    }
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index e971128..1414150 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -26,6 +26,9 @@
 import android.database.ContentObserver;
 import android.os.Handler;
 import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.logging.MetricsLogger;
@@ -34,7 +37,6 @@
 import com.android.systemui.EventLogTags;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.res.R;
-import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.util.ListenerSet;
@@ -43,14 +45,14 @@
 import com.android.systemui.util.time.SystemClock;
 
 import java.io.PrintWriter;
+import java.util.stream.Stream;
 
 /**
  * A manager which handles heads up notifications which is a special mode where
  * they simply peek from the top of the screen.
  */
-public abstract class BaseHeadsUpManager extends AlertingNotificationManager implements
-        HeadsUpManager {
-    private static final String TAG = "HeadsUpManager";
+public abstract class BaseHeadsUpManager implements HeadsUpManager {
+    private static final String TAG = "BaseHeadsUpManager";
     private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
 
     protected final ListenerSet<OnHeadsUpChangedListener> mListeners = new ListenerSet<>();
@@ -67,6 +69,14 @@
 
     private final UiEventLogger mUiEventLogger;
 
+    protected final SystemClock mSystemClock;
+    protected final ArrayMap<String, HeadsUpEntry> mHeadsUpEntryMap = new ArrayMap<>();
+    protected final HeadsUpManagerLogger mLogger;
+    protected int mMinimumDisplayTime;
+    protected int mStickyForSomeTimeAutoDismissTime;
+    protected int mAutoDismissTime;
+    protected DelayableExecutor mExecutor;
+
     /**
      * Enum entry for notification peek logged from this class.
      */
@@ -91,7 +101,9 @@
             @Main DelayableExecutor executor,
             AccessibilityManagerWrapper accessibilityManagerWrapper,
             UiEventLogger uiEventLogger) {
-        super(logger, systemClock, executor);
+        mLogger = logger;
+        mExecutor = executor;
+        mSystemClock = systemClock;
         mContext = context;
         mAccessibilityMgr = accessibilityManagerWrapper;
         mUiEventLogger = uiEventLogger;
@@ -138,20 +150,136 @@
         mListeners.remove(listener);
     }
 
-    /** Updates the notification with the given key. */
-    public void updateNotification(@NonNull String key, boolean alert) {
-        super.updateNotification(key, alert);
-        HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
-        if (alert && headsUpEntry != null) {
-            setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUpEntry.mEntry));
+    /**
+     * Called when posting a new notification that should appear on screen.
+     * Adds the notification to be managed.
+     * @param entry entry to show
+     */
+    @Override
+    public void showNotification(@NonNull NotificationEntry entry) {
+        mLogger.logShowNotification(entry);
+        addEntry(entry);
+        updateNotification(entry.getKey(), true /* shouldHeadsUpAgain */);
+        entry.setInterruption();
+    }
+
+    /**
+     * Try to remove the notification.  May not succeed if the notification has not been shown long
+     * enough and needs to be kept around.
+     * @param key the key of the notification to remove
+     * @param releaseImmediately force a remove regardless of earliest removal time
+     * @return true if notification is removed, false otherwise
+     */
+    @Override
+    public boolean removeNotification(@NonNull String key, boolean releaseImmediately) {
+        mLogger.logRemoveNotification(key, releaseImmediately);
+        HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+        if (headsUpEntry == null) {
+            return true;
         }
+        if (releaseImmediately || canRemoveImmediately(key)) {
+            removeEntry(key);
+        } else {
+            headsUpEntry.removeAsSoonAsPossible();
+            return false;
+        }
+        return true;
+    }
+
+
+    /**
+     * Called when the notification state has been updated.
+     * @param key the key of the entry that was updated
+     * @param shouldHeadsUpAgain whether the notification should show again and force reevaluation
+     *                           of removal time
+     */
+    public void updateNotification(@NonNull String key, boolean shouldHeadsUpAgain) {
+        HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+        mLogger.logUpdateNotification(key, shouldHeadsUpAgain, headsUpEntry != null);
+        if (headsUpEntry == null) {
+            // the entry was released before this update (i.e by a listener) This can happen
+            // with the groupmanager
+            return;
+        }
+
+        headsUpEntry.mEntry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+
+        if (shouldHeadsUpAgain) {
+            headsUpEntry.updateEntry(true /* updatePostTime */, "updateNotification");
+            if (headsUpEntry != null) {
+                setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUpEntry.mEntry));
+            }
+        }
+    }
+
+    /**
+     * Clears all managed notifications.
+     */
+    public void releaseAllImmediately() {
+        mLogger.logReleaseAllImmediately();
+        // A copy is necessary here as we are changing the underlying map.  This would cause
+        // undefined behavior if we iterated over the key set directly.
+        ArraySet<String> keysToRemove = new ArraySet<>(mHeadsUpEntryMap.keySet());
+        for (String key : keysToRemove) {
+            removeEntry(key);
+        }
+    }
+
+    /**
+     * Returns the entry if it is managed by this manager.
+     * @param key key of notification
+     * @return the entry
+     */
+    @Nullable
+    public NotificationEntry getEntry(@NonNull String key) {
+        HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+        return headsUpEntry != null ? headsUpEntry.mEntry : null;
+    }
+
+    /**
+     * Returns the stream of all current notifications managed by this manager.
+     * @return all entries
+     */
+    @NonNull
+    @Override
+    public Stream<NotificationEntry> getAllEntries() {
+        return mHeadsUpEntryMap.values().stream().map(headsUpEntry -> headsUpEntry.mEntry);
+    }
+
+    /**
+     * Whether or not there are any active notifications.
+     * @return true if there is an entry, false otherwise
+     */
+    @Override
+    public boolean hasNotifications() {
+        return !mHeadsUpEntryMap.isEmpty();
+    }
+
+    /**
+     * @return true if the notification is managed by this manager
+     */
+    public boolean isHeadsUpEntry(@NonNull String key) {
+        return mHeadsUpEntryMap.containsKey(key);
+    }
+
+    /**
+     * @param key
+     * @return When a HUN entry should be removed in milliseconds from now
+     */
+    @Override
+    public long getEarliestRemovalTime(String key) {
+        HeadsUpEntry entry = mHeadsUpEntryMap.get(key);
+        if (entry != null) {
+            return Math.max(0, entry.mEarliestRemovalTime - mSystemClock.elapsedRealtime());
+        }
+        return 0;
     }
 
     protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationEntry entry) {
         final HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
         if (headsUpEntry == null) {
             // This should not happen since shouldHeadsUpBecomePinned is always called after adding
-            // the NotificationEntry into AlertingNotificationManager's mAlertEntries map.
+            // the NotificationEntry into mHeadsUpEntryMap.
             return hasFullScreenIntent(entry);
         }
         return hasFullScreenIntent(entry) && !headsUpEntry.mWasUnpinned;
@@ -190,24 +318,65 @@
         return FLAG_CONTENT_VIEW_HEADS_UP;
     }
 
-    @Override
-    protected void onAlertEntryAdded(AlertEntry alertEntry) {
-        NotificationEntry entry = alertEntry.mEntry;
+    /**
+     * Add a new entry and begin managing it.
+     * @param entry the entry to add
+     */
+    protected final void addEntry(@NonNull NotificationEntry entry) {
+        HeadsUpEntry headsUpEntry = createHeadsUpEntry();
+        headsUpEntry.setEntry(entry);
+        mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
+        onEntryAdded(headsUpEntry);
+        entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+        entry.setIsHeadsUpEntry(true);
+    }
+
+    /**
+     * Manager-specific logic that should occur when an entry is added.
+     * @param headsUpEntry entry added
+     */
+    protected void onEntryAdded(HeadsUpEntry headsUpEntry) {
+        NotificationEntry entry = headsUpEntry.mEntry;
         entry.setHeadsUp(true);
 
         final boolean shouldPin = shouldHeadsUpBecomePinned(entry);
-        setEntryPinned((HeadsUpEntry) alertEntry, shouldPin);
+        setEntryPinned(headsUpEntry, shouldPin);
         EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 1 /* visible */);
         for (OnHeadsUpChangedListener listener : mListeners) {
             listener.onHeadsUpStateChanged(entry, true);
         }
     }
 
-    @Override
-    protected void onAlertEntryRemoved(AlertEntry alertEntry) {
-        NotificationEntry entry = alertEntry.mEntry;
+    /**
+     * Remove a notification and reset the entry.
+     * @param key key of notification to remove
+     */
+    protected final void removeEntry(@NonNull String key) {
+        HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+        if (headsUpEntry == null) {
+            return;
+        }
+        NotificationEntry entry = headsUpEntry.mEntry;
+
+        // If the notification is animating, we will remove it at the end of the animation.
+        if (entry != null && entry.isExpandAnimationRunning()) {
+            return;
+        }
+        entry.demoteStickyHun();
+        mHeadsUpEntryMap.remove(key);
+        onEntryRemoved(headsUpEntry);
+        entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+        headsUpEntry.reset();
+    }
+
+    /**
+     * Manager-specific logic that should occur when an entry is removed.
+     * @param headsUpEntry entry removed
+     */
+    protected void onEntryRemoved(HeadsUpEntry headsUpEntry) {
+        NotificationEntry entry = headsUpEntry.mEntry;
         entry.setHeadsUp(false);
-        setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */);
+        setEntryPinned(headsUpEntry, false /* isPinned */);
         EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */);
         mLogger.logNotificationActuallyRemoved(entry);
         for (OnHeadsUpChangedListener listener : mListeners) {
@@ -251,8 +420,8 @@
      * Snoozes all current Heads Up Notifications.
      */
     public void snooze() {
-        for (String key : mAlertEntries.keySet()) {
-            AlertEntry entry = getHeadsUpEntry(key);
+        for (String key : mHeadsUpEntryMap.keySet()) {
+            HeadsUpEntry entry = getHeadsUpEntry(key);
             String packageName = entry.mEntry.getSbn().getPackageName();
             String snoozeKey = snoozeKey(packageName, mUser);
             mLogger.logPackageSnoozed(snoozeKey);
@@ -267,7 +436,7 @@
 
     @Nullable
     protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
-        return (HeadsUpEntry) mAlertEntries.get(key);
+        return (HeadsUpEntry) mHeadsUpEntryMap.get(key);
     }
 
     /**
@@ -281,11 +450,11 @@
 
     @Nullable
     protected HeadsUpEntry getTopHeadsUpEntry() {
-        if (mAlertEntries.isEmpty()) {
+        if (mHeadsUpEntryMap.isEmpty()) {
             return null;
         }
         HeadsUpEntry topEntry = null;
-        for (AlertEntry entry: mAlertEntries.values()) {
+        for (HeadsUpEntry entry: mHeadsUpEntryMap.values()) {
             if (topEntry == null || entry.compareTo(topEntry) < 0) {
                 topEntry = (HeadsUpEntry) entry;
             }
@@ -316,7 +485,7 @@
         pw.print("  mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
         pw.print("  now="); pw.println(mSystemClock.elapsedRealtime());
         pw.print("  mUser="); pw.println(mUser);
-        for (AlertEntry entry: mAlertEntries.values()) {
+        for (HeadsUpEntry entry: mHeadsUpEntryMap.values()) {
             pw.print("  HeadsUpEntry="); pw.println(entry.mEntry);
         }
         int n = mSnoozedPackages.size();
@@ -335,8 +504,8 @@
     }
 
     private boolean hasPinnedNotificationInternal() {
-        for (String key : mAlertEntries.keySet()) {
-            AlertEntry entry = getHeadsUpEntry(key);
+        for (String key : mHeadsUpEntryMap.keySet()) {
+            HeadsUpEntry entry = getHeadsUpEntry(key);
             if (entry.mEntry.isRowPinned()) {
                 return true;
             }
@@ -349,7 +518,7 @@
      * @param userUnPinned The unpinned action is trigger by user real operation.
      */
     public void unpinAll(boolean userUnPinned) {
-        for (String key : mAlertEntries.keySet()) {
+        for (String key : mHeadsUpEntryMap.keySet()) {
             HeadsUpEntry entry = getHeadsUpEntry(key);
             setEntryPinned(entry, false /* isPinned */);
             // maybe it got un sticky
@@ -384,8 +553,8 @@
         if (a == null || b == null) {
             return Boolean.compare(a == null, b == null);
         }
-        AlertEntry aEntry = getHeadsUpEntry(a.getKey());
-        AlertEntry bEntry = getHeadsUpEntry(b.getKey());
+        HeadsUpEntry aEntry = getHeadsUpEntry(a.getKey());
+        HeadsUpEntry bEntry = getHeadsUpEntry(b.getKey());
         if (aEntry == null || bEntry == null) {
             return Boolean.compare(aEntry == null, bEntry == null);
         }
@@ -419,18 +588,37 @@
         }
     }
 
+    /**
+     * Whether or not the entry can be removed currently.  If it hasn't been on screen long enough
+     * it should not be removed unless forced
+     * @param key the key to check if removable
+     * @return true if the entry can be removed
+     */
     @Override
     public boolean canRemoveImmediately(@NonNull String key) {
         HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
         if (headsUpEntry != null && headsUpEntry.mUserActionMayIndirectlyRemove) {
             return true;
         }
-        return super.canRemoveImmediately(key);
+        return headsUpEntry == null || headsUpEntry.wasShownLongEnough()
+                || headsUpEntry.mEntry.isRowDismissed();
+    }
+
+    /**
+     * @param key
+     * @return true if the entry is (pinned and expanded) or (has an active remote input)
+     */
+    @Override
+    public boolean isSticky(String key) {
+        HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+        if (headsUpEntry != null) {
+            return headsUpEntry.isSticky();
+        }
+        return false;
     }
 
     @NonNull
-    @Override
-    protected HeadsUpEntry createAlertEntry() {
+    protected HeadsUpEntry createHeadsUpEntry() {
         return new HeadsUpEntry();
     }
 
@@ -451,28 +639,82 @@
      * This represents a notification and how long it is in a heads up mode. It also manages its
      * lifecycle automatically when created.
      */
-    protected class HeadsUpEntry extends AlertEntry {
+    protected class HeadsUpEntry implements Comparable<HeadsUpEntry> {
         public boolean mRemoteInputActive;
         public boolean mUserActionMayIndirectlyRemove;
 
         protected boolean mExpanded;
         protected boolean mWasUnpinned;
 
-        @Override
+        @Nullable public NotificationEntry mEntry;
+        public long mPostTime;
+        public long mEarliestRemovalTime;
+
+        @Nullable protected Runnable mRemoveRunnable;
+
+        @Nullable private Runnable mCancelRemoveRunnable;
+
+        public void setEntry(@NonNull final NotificationEntry entry) {
+            setEntry(entry, () -> removeEntry(entry.getKey()));
+        }
+
+        public void setEntry(@NonNull final NotificationEntry entry,
+                @Nullable Runnable removeRunnable) {
+            mEntry = entry;
+            mRemoveRunnable = removeRunnable;
+
+            mPostTime = calculatePostTime();
+            updateEntry(true /* updatePostTime */, "setEntry");
+        }
+
+        /**
+         * Updates an entry's removal time.
+         * @param updatePostTime whether or not to refresh the post time
+         */
+        public void updateEntry(boolean updatePostTime, @Nullable String reason) {
+            mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
+
+            final long now = mSystemClock.elapsedRealtime();
+            mEarliestRemovalTime = now + mMinimumDisplayTime;
+
+            if (updatePostTime) {
+                mPostTime = Math.max(mPostTime, now);
+            }
+
+            if (isSticky()) {
+                removeAutoRemovalCallbacks("updateEntry (sticky)");
+                return;
+            }
+
+            final long finishTime = calculateFinishTime();
+            final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
+            scheduleAutoRemovalCallback(timeLeft, "updateEntry (not sticky)");
+        }
+
+        /**
+         * Whether or not the notification is "sticky" i.e. should stay on screen regardless
+         * of the timer (forever) and should be removed externally.
+         * @return true if the notification is sticky
+         */
         public boolean isSticky() {
             return (mEntry.isRowPinned() && mExpanded)
                     || mRemoteInputActive
                     || hasFullScreenIntent(mEntry);
         }
 
-        @Override
         public boolean isStickyForSomeTime() {
             return mEntry.isStickyAndNotDemoted();
         }
 
-        @Override
-        public int compareTo(@NonNull AlertEntry alertEntry) {
-            HeadsUpEntry headsUpEntry = (HeadsUpEntry) alertEntry;
+        /**
+         * Whether the notification has been on screen long enough and can be removed.
+         * @return true if the notification has been on screen long enough
+         */
+        public boolean wasShownLongEnough() {
+            return mEarliestRemovalTime < mSystemClock.elapsedRealtime();
+        }
+
+        public int compareTo(@NonNull HeadsUpEntry headsUpEntry) {
             boolean isPinned = mEntry.isRowPinned();
             boolean otherPinned = headsUpEntry.mEntry.isRowPinned();
             if (isPinned && !otherPinned) {
@@ -503,31 +745,90 @@
                 return 1;
             }
 
-            return super.compareTo(headsUpEntry);
+            if (mPostTime > headsUpEntry.mPostTime) {
+                return -1;
+            } else if (mPostTime == headsUpEntry.mPostTime) {
+                return mEntry.getKey().compareTo(headsUpEntry.mEntry.getKey());
+            } else {
+                return 1;
+            }
         }
 
         public void setExpanded(boolean expanded) {
             this.mExpanded = expanded;
         }
 
-        @Override
         public void reset() {
-            super.reset();
+            removeAutoRemovalCallbacks("reset()");
+            mEntry = null;
+            mRemoveRunnable = null;
             mExpanded = false;
             mRemoteInputActive = false;
         }
 
-        @Override
+        /**
+         * Clear any pending removal runnables.
+         */
+        public void removeAutoRemovalCallbacks(@Nullable String reason) {
+            final boolean removed = removeAutoRemovalCallbackInternal();
+
+            if (removed) {
+                mLogger.logAutoRemoveCanceled(mEntry, reason);
+            }
+        }
+
+        public void scheduleAutoRemovalCallback(long delayMillis, @NonNull String reason) {
+            if (mRemoveRunnable == null) {
+                Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
+                return;
+            }
+
+            final boolean removed = removeAutoRemovalCallbackInternal();
+
+            if (removed) {
+                mLogger.logAutoRemoveRescheduled(mEntry, delayMillis, reason);
+            } else {
+                mLogger.logAutoRemoveScheduled(mEntry, delayMillis, reason);
+            }
+
+            mCancelRemoveRunnable = mExecutor.executeDelayed(mRemoveRunnable,
+                    delayMillis);
+        }
+
+        public boolean removeAutoRemovalCallbackInternal() {
+            final boolean scheduled = (mCancelRemoveRunnable != null);
+
+            if (scheduled) {
+                mCancelRemoveRunnable.run();
+                mCancelRemoveRunnable = null;
+            }
+
+            return scheduled;
+        }
+
+        /**
+         * Remove the entry at the earliest allowed removal time.
+         */
+        public void removeAsSoonAsPossible() {
+            if (mRemoveRunnable != null) {
+                final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
+                scheduleAutoRemovalCallback(timeLeft, "removeAsSoonAsPossible");
+            }
+        }
+
+        /**
+         * Calculate what the post time of a notification is at some current time.
+         * @return the post time
+         */
         protected long calculatePostTime() {
             // The actual post time will be just after the heads-up really slided in
-            return super.calculatePostTime() + mTouchAcceptanceDelay;
+            return mSystemClock.elapsedRealtime() + mTouchAcceptanceDelay;
         }
 
         /**
          * @return When the notification should auto-dismiss itself, based on
          * {@link SystemClock#elapsedRealtime()}
          */
-        @Override
         protected long calculateFinishTime() {
             final long duration = getRecommendedHeadsUpTimeoutMs(
                     isStickyForSomeTime() ? mStickyForSomeTimeAutoDismissTime : mAutoDismissTime);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 20d1fff..a2d8d15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -28,6 +28,8 @@
 import android.icu.text.DateTimePatternGenerator;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.Parcelable;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -48,11 +50,11 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
 import com.android.systemui.FontSizeUtils;
-import com.android.systemui.res.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.demomode.DemoModeCommandReceiver;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -106,6 +108,7 @@
     private final int mAmPmStyle;
     private boolean mShowSeconds;
     private Handler mSecondsHandler;
+    private HandlerThread mHandlerThread;
 
     // Fields to cache the width so the clock remains at an approximately constant width
     private int mCharsAtCurrentWidth = -1;
@@ -146,6 +149,8 @@
         }
         mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
         mUserTracker = Dependency.get(UserTracker.class);
+        mHandlerThread = new HandlerThread("Clock");
+        mHandlerThread.start();
 
         setIncludeFontPadding(false);
     }
@@ -205,7 +210,8 @@
             Dependency.get(TunerService.class).addTunable(this, CLOCK_SECONDS,
                     StatusBarIconController.ICON_HIDE_LIST);
             mCommandQueue.addCallback(this);
-            mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+            mUserTracker.addCallback(mUserChangedCallback,
+                       new HandlerExecutor(mHandlerThread.getThreadHandler()));
             mCurrentUserId = mUserTracker.getUserId();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
index d9527fe..a7352be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
@@ -71,7 +71,7 @@
     fun hasPinnedHeadsUp(): Boolean
 
     /** Returns whether or not the given notification is alerting and managed by this manager. */
-    fun isAlerting(key: String): Boolean
+    fun isHeadsUpEntry(key: String): Boolean
 
     fun isHeadsUpGoingAway(): Boolean
 
@@ -182,7 +182,7 @@
      */
     fun unpinAll(userUnPinned: Boolean)
 
-    fun updateNotification(key: String, alert: Boolean)
+    fun updateNotification(key: String, shouldHeadsUpAgain: Boolean)
 }
 
 /** Sets the animation state of the HeadsUpManager. */
@@ -213,7 +213,7 @@
     override fun getTouchableRegion(): Region? = null
     override fun getTopEntry() = null
     override fun hasPinnedHeadsUp() = false
-    override fun isAlerting(key: String) = false
+    override fun isHeadsUpEntry(key: String) = false
     override fun isHeadsUpGoingAway() = false
     override fun isSnoozed(packageName: String) = false
     override fun isSticky(key: String?) = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
index b7d8ee3..a7440d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -21,6 +21,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.UserHandle;
 
 import androidx.annotation.NonNull;
@@ -51,6 +53,7 @@
     private final UserTracker mUserTracker;
     private AlarmManager mAlarmManager;
     private AlarmManager.AlarmClockInfo mNextAlarm;
+    private HandlerThread mHandlerThread;
 
     private final UserTracker.Callback mUserChangedCallback =
             new UserTracker.Callback() {
@@ -75,7 +78,10 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
         broadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL);
-        mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
+        mHandlerThread = new HandlerThread("NextAlarmControllerImpl");
+        mHandlerThread.start();
+        mUserTracker.addCallback(mUserChangedCallback,
+                    new HandlerExecutor(mHandlerThread.getThreadHandler()));
         updateNextAlarm();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
index 3be14bc..10bf068 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
@@ -48,6 +48,8 @@
     boolean isNetworkLoggingEnabled();
     boolean isVpnEnabled();
     boolean isVpnRestricted();
+    /** Whether the VPN network is validated. */
+    boolean isVpnValidated();
     /** Whether the VPN app should use branded VPN iconography.  */
     boolean isVpnBranded();
     String getPrimaryVpnName();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 5d69f36..6a6efbc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -15,6 +15,9 @@
  */
 package com.android.systemui.statusbar.policy;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+
 import android.annotation.Nullable;
 import android.app.admin.DeviceAdminInfo;
 import android.app.admin.DevicePolicyManager;
@@ -32,7 +35,9 @@
 import android.graphics.drawable.Drawable;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
+import android.net.LinkProperties;
 import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.VpnManager;
 import android.os.Handler;
@@ -76,7 +81,10 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final NetworkRequest REQUEST =
-            new NetworkRequest.Builder().clearCapabilities().build();
+            new NetworkRequest.Builder()
+                    .clearCapabilities()
+                    .addTransportType(TRANSPORT_VPN)
+                    .build();
     private static final int NO_NETWORK = -1;
 
     private static final String VPN_BRANDED_META_DATA = "com.android.systemui.IS_BRANDED";
@@ -99,6 +107,8 @@
     private SparseArray<VpnConfig> mCurrentVpns = new SparseArray<>();
     private int mCurrentUserId;
     private int mVpnUserId;
+    @GuardedBy("mNetworkProperties")
+    private final SparseArray<NetworkProperties> mNetworkProperties = new SparseArray<>();
 
     // Key: userId, Value: whether the user has CACerts installed
     // Needs to be cached here since the query has to be asynchronous
@@ -147,7 +157,7 @@
         // TODO: re-register network callback on user change.
         mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
         onUserSwitched(mUserTracker.getUserId());
-        mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
+        mUserTracker.addCallback(mUserChangedCallback, mBgExecutor);
     }
 
     public void dump(PrintWriter pw, String[] args) {
@@ -162,6 +172,21 @@
             pw.print(mCurrentVpns.valueAt(i).user);
         }
         pw.println("}");
+        pw.print("  mNetworkProperties={");
+        synchronized (mNetworkProperties) {
+            for (int i = 0; i < mNetworkProperties.size(); ++i) {
+                if (i > 0) {
+                    pw.print(", ");
+                }
+                pw.print(mNetworkProperties.keyAt(i));
+                pw.print("={");
+                pw.print(mNetworkProperties.valueAt(i).interfaceName);
+                pw.print(", ");
+                pw.print(mNetworkProperties.valueAt(i).validated);
+                pw.print("}");
+            }
+        }
+        pw.println("}");
     }
 
     @Override
@@ -304,6 +329,26 @@
     }
 
     @Override
+    public boolean isVpnValidated() {
+        // Prioritize reporting the network status of the parent user.
+        final VpnConfig primaryVpnConfig = mCurrentVpns.get(mVpnUserId);
+        if (primaryVpnConfig != null) {
+            return getVpnValidationStatus(primaryVpnConfig);
+        }
+        // Identify any Unvalidated status in each active VPN network within other profiles.
+        for (int profileId : mUserManager.getEnabledProfileIds(mVpnUserId)) {
+            final VpnConfig vpnConfig = mCurrentVpns.get(profileId);
+            if (vpnConfig == null) {
+                continue;
+            }
+            if (!getVpnValidationStatus(vpnConfig)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
     public boolean hasCACertInCurrentUser() {
         Boolean hasCACerts = mHasCACerts.get(mCurrentUserId);
         return hasCACerts != null && hasCACerts.booleanValue();
@@ -493,11 +538,74 @@
         @Override
         public void onLost(Network network) {
             if (DEBUG) Log.d(TAG, "onLost " + network.getNetId());
+            synchronized (mNetworkProperties) {
+                mNetworkProperties.delete(network.getNetId());
+            }
             updateState();
             fireCallbacks();
         };
+
+
+        @Override
+        public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+            if (DEBUG) Log.d(TAG, "onCapabilitiesChanged " + network.getNetId());
+            final NetworkProperties properties;
+            synchronized (mNetworkProperties) {
+                properties = mNetworkProperties.get(network.getNetId());
+            }
+            // When a new network appears, the system first notifies the application about
+            // its capabilities through onCapabilitiesChanged. This initial notification
+            // will be skipped because the interface information is included in the
+            // subsequent onLinkPropertiesChanged call. After validating the network, the
+            // system might send another onCapabilitiesChanged notification if the network
+            // becomes validated.
+            if (properties == null) {
+                return;
+            }
+            final boolean validated = nc.hasCapability(NET_CAPABILITY_VALIDATED);
+            if (properties.validated != validated) {
+                properties.validated = validated;
+                fireCallbacks();
+            }
+        }
+
+        @Override
+        public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
+            if (DEBUG) Log.d(TAG, "onLinkPropertiesChanged " + network.getNetId());
+            final String interfaceName = linkProperties.getInterfaceName();
+            if (interfaceName == null) {
+                Log.w(TAG, "onLinkPropertiesChanged event with null interface");
+                return;
+            }
+            synchronized (mNetworkProperties) {
+                final NetworkProperties properties = mNetworkProperties.get(network.getNetId());
+                if (properties == null) {
+                    mNetworkProperties.put(
+                            network.getNetId(),
+                            new NetworkProperties(interfaceName, false));
+                } else {
+                    properties.interfaceName = interfaceName;
+                }
+            }
+        }
     };
 
+    /**
+     *  Retrieve the validation status of the VPN network associated with the given VpnConfig.
+     */
+    private boolean getVpnValidationStatus(@NonNull VpnConfig vpnConfig) {
+        synchronized (mNetworkProperties) {
+            // Find the network has the same interface as the VpnConfig
+            for (int i = 0; i < mNetworkProperties.size(); ++i) {
+                if (mNetworkProperties.valueAt(i).interfaceName.equals(vpnConfig.interfaze)) {
+                    return mNetworkProperties.valueAt(i).validated;
+                }
+            }
+        }
+        // If no matching network is found, consider it validated.
+        return true;
+    }
+
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override public void onReceive(Context context, Intent intent) {
             if (KeyChain.ACTION_TRUST_STORE_CHANGED.equals(intent.getAction())) {
@@ -508,4 +616,17 @@
             }
         }
     };
+
+    /**
+     *  A data class to hold specific Network properties received through the NetworkCallback.
+     */
+    private static class NetworkProperties {
+        public String interfaceName;
+        public boolean validated;
+
+        NetworkProperties(@NonNull String interfaceName, boolean validated) {
+            this.interfaceName = interfaceName;
+            this.validated = validated;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java
new file mode 100644
index 0000000..970cc75
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+/**
+ * A controller which provides the current sensitive notification protections status as well as
+ * to assist in feature usage and exemptions
+ */
+public interface SensitiveNotificationProtectionController {
+    /**
+     * Register a runnable that triggers on changes to protection state
+     *
+     * <p> onSensitiveStateChanged not invoked on registration
+     */
+    void registerSensitiveStateListener(Runnable onSensitiveStateChanged);
+
+    /** Unregister a previously registered onSensitiveStateChanged runnable */
+    void unregisterSensitiveStateListener(Runnable onSensitiveStateChanged);
+
+    /** Return {@code true} if device in state in which notifications should be protected */
+    boolean isSensitiveStateActive();
+
+    /** Return {@code true} when notification should be protected */
+    boolean shouldProtectNotification(NotificationEntry entry);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
new file mode 100644
index 0000000..3c4ca44
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static com.android.systemui.Flags.screenshareNotificationHiding;
+
+import android.media.projection.MediaProjectionInfo;
+import android.media.projection.MediaProjectionManager;
+import android.os.Handler;
+import android.os.Trace;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.ListenerSet;
+
+import javax.inject.Inject;
+
+/** Implementation of SensitiveNotificationProtectionController. **/
+@SysUISingleton
+public class SensitiveNotificationProtectionControllerImpl
+        implements SensitiveNotificationProtectionController {
+    private final MediaProjectionManager mMediaProjectionManager;
+    private final ListenerSet<Runnable> mListeners = new ListenerSet<>();
+    private volatile MediaProjectionInfo mProjection;
+
+    @VisibleForTesting
+    final MediaProjectionManager.Callback mMediaProjectionCallback =
+            new MediaProjectionManager.Callback() {
+                @Override
+                public void onStart(MediaProjectionInfo info) {
+                    Trace.beginSection(
+                            "SNPC.onProjectionStart");
+                    mProjection = info;
+                    mListeners.forEach(Runnable::run);
+                    Trace.endSection();
+                }
+
+                @Override
+                public void onStop(MediaProjectionInfo info) {
+                    Trace.beginSection(
+                            "SNPC.onProjectionStop");
+                    mProjection = null;
+                    mListeners.forEach(Runnable::run);
+                    Trace.endSection();
+                }
+            };
+
+    @Inject
+    public SensitiveNotificationProtectionControllerImpl(
+            MediaProjectionManager mediaProjectionManager,
+            @Main Handler mainHandler) {
+        mMediaProjectionManager = mediaProjectionManager;
+
+        if (screenshareNotificationHiding()) {
+            mMediaProjectionManager.addCallback(mMediaProjectionCallback, mainHandler);
+        }
+    }
+
+    @Override
+    public void registerSensitiveStateListener(Runnable onSensitiveStateChanged) {
+        mListeners.addIfAbsent(onSensitiveStateChanged);
+    }
+
+    @Override
+    public void unregisterSensitiveStateListener(Runnable onSensitiveStateChanged) {
+        mListeners.remove(onSensitiveStateChanged);
+    }
+
+    @Override
+    public boolean isSensitiveStateActive() {
+        // TODO(b/316955558): Add disabled by developer option
+        // TODO(b/316955306): Add feature exemption for sysui and bug handlers
+        // TODO(b/316955346): Add feature exemption for single app screen sharing
+        return mProjection != null;
+    }
+
+    @Override
+    public boolean shouldProtectNotification(NotificationEntry entry) {
+        if (!isSensitiveStateActive()) {
+            return false;
+        }
+
+        // Exempt foreground service notifications from protection in effort to keep screen share
+        // stop actions easily accessible
+        // TODO(b/316955208): Exempt FGS notifications only for app that started projection
+        return !entry.getSbn().getNotification().isFgsOrUij();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 2ed9d15..0bc0e88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -36,9 +36,9 @@
 
 import com.android.internal.util.UserIcons;
 import com.android.settingslib.drawable.UserIconDrawable;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 
 import java.util.ArrayList;
@@ -66,11 +66,11 @@
     /**
      */
     @Inject
-    public UserInfoControllerImpl(Context context, @Main Executor mainExecutor,
+    public UserInfoControllerImpl(Context context, @Background Executor bgExecutor,
             UserTracker userTracker) {
         mContext = context;
         mUserTracker = userTracker;
-        mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
+        mUserTracker.addCallback(mUserChangedCallback, bgExecutor);
 
         IntentFilter profileFilter = new IntentFilter();
         profileFilter.addAction(ContactsContract.Intents.ACTION_PROFILE_CHANGED);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index df210b0..f0b4930 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -29,6 +29,7 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -81,6 +82,7 @@
     private volatile int mZenMode;
     private long mZenUpdateTime;
     private NotificationManager.Policy mConsolidatedNotificationPolicy;
+    private HandlerThread mHandlerThread;
 
     private final UserTracker.Callback mUserChangedCallback =
             new UserTracker.Callback() {
@@ -133,6 +135,8 @@
                 }
             }
         };
+        mHandlerThread = new HandlerThread("ZenModeControllerImpl");
+        mHandlerThread.start();
         mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
         globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver);
         updateZenMode(getModeSettingValueFromProvider());
@@ -143,7 +147,8 @@
         mSetupObserver = new SetupObserver(handler);
         mSetupObserver.register();
         mUserManager = context.getSystemService(UserManager.class);
-        mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(handler));
+        mUserTracker.addCallback(mUserChangedCallback,
+                new HandlerExecutor(mHandlerThread.getThreadHandler()));
         // This registers the alarm broadcast receiver for the current user
         mUserChangedCallback.onUserChanged(getCurrentUser(), context);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index 3304b98..15200bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -60,6 +60,8 @@
 import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
 import com.android.systemui.statusbar.policy.SecurityController;
 import com.android.systemui.statusbar.policy.SecurityControllerImpl;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionControllerImpl;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
 import com.android.systemui.statusbar.policy.SplitShadeStateControllerImpl;
 import com.android.systemui.statusbar.policy.UserInfoController;
@@ -146,6 +148,11 @@
 
     /** */
     @Binds
+    SensitiveNotificationProtectionController provideSensitiveNotificationProtectionController(
+            SensitiveNotificationProtectionControllerImpl controllerImpl);
+
+    /** */
+    @Binds
     UserInfoController provideUserInfoContrller(UserInfoControllerImpl controllerImpl);
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepository.kt
new file mode 100644
index 0000000..2a0812b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepository.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy.data.repository
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Repository to observe whether the user has completed the setup steps. This information can change
+ * some policy related to display.
+ */
+interface UserSetupRepository {
+    /** Whether the user has completed the setup steps. */
+    val isUserSetUp: StateFlow<Boolean>
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class UserSetupRepositoryImpl
+@Inject
+constructor(
+    private val deviceProvisionedController: DeviceProvisionedController,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    @Application scope: CoroutineScope,
+) : UserSetupRepository {
+    override val isUserSetUp: StateFlow<Boolean> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : DeviceProvisionedController.DeviceProvisionedListener {
+                        override fun onUserSetupChanged() {
+                            trySend(Unit)
+                        }
+                    }
+
+                deviceProvisionedController.addCallback(callback)
+
+                awaitClose { deviceProvisionedController.removeCallback(callback) }
+            }
+            .onStart { emit(Unit) }
+            .mapLatest { fetchUserSetupState() }
+            .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+
+    private suspend fun fetchUserSetupState(): Boolean =
+        withContext(bgDispatcher) { deviceProvisionedController.isCurrentUserSetup }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractor.kt
new file mode 100644
index 0000000..ca36e39
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractor.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.statusbar.policy.domain.interactor
+
+import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+class UserSetupInteractor @Inject constructor(repository: UserSetupRepository) {
+    /** Whether the user has completed the setup steps. */
+    val isUserSetUp: Flow<Boolean> = repository.isUserSetUp
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt
index ce811e2..10137a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt
@@ -17,10 +17,16 @@
 package com.android.systemui.statusbar.ui
 
 import com.android.internal.policy.SystemBarUtils
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.onConfigChanged
 import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 
@@ -31,6 +37,8 @@
 class SystemBarUtilsState
 @Inject
 constructor(
+    @Background bgContext: CoroutineContext,
+    @Main mainContext: CoroutineContext,
     configurationController: ConfigurationController,
     proxy: SystemBarUtilsProxy,
 ) {
@@ -38,5 +46,10 @@
     val statusBarHeight: Flow<Int> =
         configurationController.onConfigChanged
             .onStart<Any> { emit(Unit) }
+            .flowOn(mainContext)
+            .conflate()
             .map { proxy.getStatusBarHeight() }
+            .distinctUntilChanged()
+            .flowOn(bgContext)
+            .conflate()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 2b9ad50..77518db 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -480,7 +480,7 @@
             return;
         }
 
-        mUserTracker.addCallback(mUserTrackerCallback, mMainExecutor);
+        mUserTracker.addCallback(mUserTrackerCallback, mBgExecutor);
 
         mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
 
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/OtherPrefs.java b/packages/SystemUI/src/com/android/systemui/tuner/OtherPrefs.java
deleted file mode 100644
index 8d85999..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/OtherPrefs.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.tuner;
-
-import android.os.Bundle;
-
-import androidx.preference.PreferenceFragment;
-
-import com.android.systemui.res.R;
-import com.android.tools.r8.keepanno.annotations.KeepTarget;
-import com.android.tools.r8.keepanno.annotations.UsesReflection;
-
-public class OtherPrefs extends PreferenceFragment {
-    // aapt doesn't generate keep rules for android:fragment references in <Preference> tags, so
-    // explicitly declare references per usage in `R.xml.other_settings`. See b/120445169.
-    @UsesReflection(@KeepTarget(classConstant = PowerNotificationControlsFragment.class))
-    @Override
-    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
-        addPreferencesFromResource(R.xml.other_settings);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PowerNotificationControlsFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PowerNotificationControlsFragment.java
deleted file mode 100644
index ce1a2e9..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/PowerNotificationControlsFragment.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.tuner;
-
-import android.annotation.Nullable;
-import android.app.Fragment;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Switch;
-import android.widget.TextView;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.res.R;
-
-public class PowerNotificationControlsFragment extends Fragment {
-
-    private static final String KEY_SHOW_PNC = "show_importance_slider";
-
-    @Override
-    public void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        return inflater.inflate(R.layout.power_notification_controls_settings, container, false);
-    }
-
-    @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        final View switchBar = view.findViewById(R.id.switch_bar);
-        final Switch switchWidget = (Switch) switchBar.findViewById(android.R.id.switch_widget);
-        final TextView switchText = (TextView) switchBar.findViewById(R.id.switch_text);
-        switchWidget.setChecked(isEnabled());
-        switchText.setText(isEnabled()
-                ? getString(R.string.switch_bar_on)
-                : getString(R.string.switch_bar_off));
-
-        switchWidget.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                boolean newState = !isEnabled();
-                MetricsLogger.action(getContext(),
-                        MetricsEvent.ACTION_TUNER_POWER_NOTIFICATION_CONTROLS, newState);
-                Settings.Secure.putInt(getContext().getContentResolver(),
-                        KEY_SHOW_PNC, newState ? 1 : 0);
-                switchWidget.setChecked(newState);
-                switchText.setText(newState
-                        ? getString(R.string.switch_bar_on)
-                        : getString(R.string.switch_bar_off));
-            }
-        });
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        MetricsLogger.visibility(
-                getContext(), MetricsEvent.TUNER_POWER_NOTIFICATION_CONTROLS, true);
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        MetricsLogger.visibility(
-                getContext(), MetricsEvent.TUNER_POWER_NOTIFICATION_CONTROLS, false);
-    }
-
-    private boolean isEnabled() {
-        int setting = Settings.Secure.getInt(getContext().getContentResolver(), KEY_SHOW_PNC, 0);
-        return setting == 1;
-    }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 550a65c..f5b4d17 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -26,6 +26,7 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -38,11 +39,11 @@
 
 import com.android.internal.util.ArrayUtils;
 import com.android.systemui.DejankUtils;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -98,6 +99,7 @@
     private UserTracker.Callback mCurrentUserTracker;
     private UserTracker mUserTracker;
     private final ComponentName mTunerComponent;
+    private HandlerThread mHandlerThread;
 
     /**
      */
@@ -117,7 +119,8 @@
         mDemoModeController = demoModeController;
         mUserTracker = userTracker;
         mTunerComponent = new ComponentName(mContext, TunerActivity.class);
-
+        mHandlerThread = new HandlerThread("TunerServiceImpl");
+        mHandlerThread.start();
         for (UserInfo user : UserManager.get(mContext).getUsers()) {
             mCurrentUser = user.getUserHandle().getIdentifier();
             if (getValue(TUNER_VERSION, 0) != CURRENT_TUNER_VERSION) {
@@ -135,7 +138,7 @@
             }
         };
         mUserTracker.addCallback(mCurrentUserTracker,
-                new HandlerExecutor(mainHandler));
+                new HandlerExecutor(mHandlerThread.getThreadHandler()));
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
index 10fc83c..0016d95 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityManager
 import com.android.systemui.util.kotlin.getOrNull
 import dagger.BindsInstance
-import dagger.Lazy
 import dagger.Module
 import dagger.Provides
 import dagger.Subcomponent
@@ -57,7 +56,6 @@
         rotationProvider: Optional<NaturalRotationUnfoldProgressProvider>,
         @Named(UNFOLD_STATUS_BAR) scopedProvider: Optional<ScopedUnfoldTransitionProgressProvider>,
         @UnfoldBg bgProvider: Optional<UnfoldTransitionProgressProvider>,
-        unfoldLatencyTracker: Lazy<UnfoldLatencyTracker>,
         factory: SysUIUnfoldComponent.Factory
     ): Optional<SysUIUnfoldComponent> {
         val p1 = provider.getOrNull()
@@ -67,7 +65,7 @@
         return if (p1 == null || p2 == null || p3 == null || p4 == null) {
             Optional.empty()
         } else {
-            Optional.of(factory.create(p1, p2, p3, p4, unfoldLatencyTracker.get()))
+            Optional.of(factory.create(p1, p2, p3, p4))
         }
     }
 }
@@ -82,8 +80,7 @@
             @BindsInstance p1: UnfoldTransitionProgressProvider,
             @BindsInstance p2: NaturalRotationUnfoldProgressProvider,
             @BindsInstance p3: ScopedUnfoldTransitionProgressProvider,
-            @BindsInstance @UnfoldBg p4: UnfoldTransitionProgressProvider,
-            @BindsInstance p5: UnfoldLatencyTracker,
+            @BindsInstance @UnfoldBg p4: UnfoldTransitionProgressProvider
         ): SysUIUnfoldComponent
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
index 8c66c2f..33fa9b8 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
@@ -22,7 +22,6 @@
 import android.os.Trace
 import android.util.Log
 import com.android.internal.util.LatencyTracker
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.UiBackground
 import com.android.systemui.keyguard.ScreenLifecycle
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
@@ -42,7 +41,7 @@
  * For now, the focus is on the time the inner display is visible, but in the future, it is easily
  * possible to monitor the time to go from the inner screen to the outer.
  */
-@SysUISingleton
+@SysUIUnfoldScope
 class UnfoldLatencyTracker
 @Inject
 constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index 8bef53c..9bd0e32 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -177,17 +177,20 @@
 
     @Module
     interface Bindings {
-        @Binds
-        @IntoMap
-        @ClassKey(UnfoldTraceLogger::class)
-        fun bindUnfoldTraceLogger(impl: UnfoldTraceLogger): CoreStartable
-
         @Binds fun bindRepository(impl: UnfoldTransitionRepositoryImpl): UnfoldTransitionRepository
 
         @Binds fun bindInteractor(impl: UnfoldTransitionInteractorImpl): UnfoldTransitionInteractor
 
         @Binds fun bindFoldStateRepository(impl: FoldStateRepositoryImpl): FoldStateRepository
     }
+
+    @Module
+    interface Startables {
+        @Binds
+        @IntoMap
+        @ClassKey(UnfoldTraceLogger::class)
+        fun bindUnfoldTraceLogger(impl: UnfoldTraceLogger): CoreStartable
+    }
 }
 
 const val UNFOLD_STATUS_BAR = "unfold_status_bar"
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index cf76c0d..74e1339 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -190,7 +190,7 @@
                         }
                     }
 
-                tracker.addCallback(callback, mainDispatcher.asExecutor())
+                tracker.addCallback(callback, backgroundDispatcher.asExecutor())
                 send(currentSelectionStatus)
 
                 awaitClose { tracker.removeCallback(callback) }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt
new file mode 100644
index 0000000..693a835
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+
+object BooleanFlowOperators {
+    /**
+     * Logical AND operator for boolean flows. Will collect all flows and [combine] them to
+     * determine the result.
+     *
+     * Usage:
+     * ```
+     * val result = and(flow1, flow2)
+     * ```
+     */
+    fun and(vararg flows: Flow<Boolean>): Flow<Boolean> =
+        combine(flows.asIterable()) { values -> values.all { it } }
+
+    /**
+     * Logical NOT operator for a boolean flow.
+     *
+     * Usage:
+     * ```
+     * val negatedFlow = not(flow)
+     * ```
+     */
+    fun not(flow: Flow<Boolean>) = flow.map { !it }
+
+    /**
+     * Logical OR operator for a boolean flow. Will collect all flows and [combine] them to
+     * determine the result.
+     */
+    fun or(vararg flows: Flow<Boolean>): Flow<Boolean> =
+        combine(flows.asIterable()) { values -> values.any { it } }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
deleted file mode 100644
index cf6b0d9..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-package com.android.systemui.util.kotlin
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dagger.qualifiers.Tracing
-import com.android.app.tracing.coroutines.createCoroutineTracingContext
-import dagger.Module
-import dagger.Provides
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.plus
-import kotlin.coroutines.CoroutineContext
-
-/** Providers for various coroutines-related constructs. */
-@Module
-class CoroutinesModule {
-    @Provides
-    @SysUISingleton
-    @Application
-    fun applicationScope(
-            @Main dispatcherContext: CoroutineContext,
-    ): CoroutineScope = CoroutineScope(dispatcherContext)
-
-    @Provides
-    @SysUISingleton
-    @Background
-    fun bgApplicationScope(
-            @Application applicationScope: CoroutineScope,
-            @Background coroutineContext: CoroutineContext,
-    ): CoroutineScope = applicationScope.plus(coroutineContext)
-
-    @Provides
-    @SysUISingleton
-    @Main
-    @Deprecated(
-        "Use @Main CoroutineContext instead",
-        ReplaceWith("mainCoroutineContext()", "kotlin.coroutines.CoroutineContext")
-    )
-    fun mainDispatcher(): CoroutineDispatcher = Dispatchers.Main.immediate
-
-    @Provides
-    @SysUISingleton
-    @Main
-    fun mainCoroutineContext(@Tracing tracingCoroutineContext: CoroutineContext): CoroutineContext {
-        return Dispatchers.Main.immediate + tracingCoroutineContext
-    }
-
-    /**
-     * Provide a [CoroutineDispatcher] backed by a thread pool containing at most X threads, where
-     * X is the number of CPU cores available.
-     *
-     * Because there are multiple threads at play, there is no serialization order guarantee. You
-     * should use a [kotlinx.coroutines.channels.Channel] for serialization if necessary.
-     *
-     * @see Dispatchers.Default
-     */
-    @Provides
-    @SysUISingleton
-    @Background
-    @Deprecated(
-        "Use @Background CoroutineContext instead",
-        ReplaceWith("bgCoroutineContext()", "kotlin.coroutines.CoroutineContext")
-    )
-    fun bgDispatcher(): CoroutineDispatcher = Dispatchers.IO
-
-
-    @Provides
-    @Background
-    @SysUISingleton
-    fun bgCoroutineContext(@Tracing tracingCoroutineContext: CoroutineContext): CoroutineContext {
-        return Dispatchers.IO + tracingCoroutineContext
-    }
-
-    @OptIn(ExperimentalCoroutinesApi::class)
-    @Provides
-    @Tracing
-    @SysUISingleton
-    fun tracingCoroutineContext(): CoroutineContext {
-        return createCoroutineTracingContext()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
index c587f2e..5150389 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.util.kotlin
 
 import dagger.Lazy
+import java.util.Optional
 import kotlin.reflect.KProperty
 
 /**
@@ -30,3 +31,16 @@
  * ```
  */
 operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = get()
+
+/**
+ * Extension operator that allows developers to use [java.util.Optional] as a nullable property
+ * delegate:
+ * ```kotlin
+ *    class MyClass @Inject constructor(
+ *      optionalDependency: Optional<Foo>,
+ *    ) {
+ *      val dependency: Foo? by optionalDependency
+ *    }
+ * ```
+ */
+operator fun <T> Optional<T>.getValue(thisRef: Any?, property: KProperty<*>): T? = getOrNull()
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt
new file mode 100644
index 0000000..8ecf250
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.qualifiers.Tracing
+import dagger.Module
+import dagger.Provides
+import javax.inject.Singleton
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Providers for various application-wide coroutines-related constructs. */
+@Module
+class GlobalCoroutinesModule {
+    @Provides
+    @Singleton
+    @Application
+    fun applicationScope(
+        @Main dispatcherContext: CoroutineContext,
+    ): CoroutineScope = CoroutineScope(dispatcherContext)
+
+    @Provides
+    @Singleton
+    @Main
+    @Deprecated(
+        "Use @Main CoroutineContext instead",
+        ReplaceWith("mainCoroutineContext()", "kotlin.coroutines.CoroutineContext")
+    )
+    fun mainDispatcher(): CoroutineDispatcher = Dispatchers.Main.immediate
+
+    @Provides
+    @Singleton
+    @Main
+    fun mainCoroutineContext(@Tracing tracingCoroutineContext: CoroutineContext): CoroutineContext {
+        return Dispatchers.Main.immediate + tracingCoroutineContext
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Provides
+    @Tracing
+    @Singleton
+    fun tracingCoroutineContext(): CoroutineContext {
+        return createCoroutineTracingContext()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
new file mode 100644
index 0000000..a13d85b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Tracing
+import dagger.Module
+import dagger.Provides
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.plus
+
+/** Providers for various SystemIU specific coroutines-related constructs. */
+@Module
+class SysUICoroutinesModule {
+    @Provides
+    @SysUISingleton
+    @Background
+    fun bgApplicationScope(
+        @Application applicationScope: CoroutineScope,
+        @Background coroutineContext: CoroutineContext,
+    ): CoroutineScope = applicationScope.plus(coroutineContext)
+
+    /**
+     * Provide a [CoroutineDispatcher] backed by a thread pool containing at most X threads, where X
+     * is the number of CPU cores available.
+     *
+     * Because there are multiple threads at play, there is no serialization order guarantee. You
+     * should use a [kotlinx.coroutines.channels.Channel] for serialization if necessary.
+     *
+     * @see Dispatchers.Default
+     */
+    @Provides
+    @SysUISingleton
+    @Background
+    @Deprecated(
+        "Use @Background CoroutineContext instead",
+        ReplaceWith("bgCoroutineContext()", "kotlin.coroutines.CoroutineContext")
+    )
+    fun bgDispatcher(): CoroutineDispatcher = Dispatchers.IO
+
+    @Provides
+    @Background
+    @SysUISingleton
+    fun bgCoroutineContext(@Tracing tracingCoroutineContext: CoroutineContext): CoroutineContext {
+        return Dispatchers.IO + tracingCoroutineContext
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
index 2336a8e..6993c96 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
@@ -28,9 +28,13 @@
         fun <A, B, C, D> toQuad(a: A, bcd: Triple<B, C, D>) =
             Quad(a, bcd.first, bcd.second, bcd.third)
 
+        fun <A, B, C, D, E> toQuint(a: A, b: B, c: C, d: D, e: E) = Quint(a, b, c, d, e)
         fun <A, B, C, D, E> toQuint(a: A, bcde: Quad<B, C, D, E>) =
             Quint(a, bcde.first, bcde.second, bcde.third, bcde.fourth)
 
+        fun <A, B, C, D, E, F> toSextuple(a: A, bcdef: Quint<B, C, D, E, F>) =
+            Sextuple(a, bcdef.first, bcdef.second, bcdef.third, bcdef.fourth, bcdef.fifth)
+
         /**
          * Samples the provided flows, emitting a tuple of the original flow's value as well as each
          * of the combined flows' values.
@@ -69,6 +73,22 @@
         ): Flow<Quint<A, B, C, D, E>> {
             return this.sample(combine(b, c, d, e, ::Quad), ::toQuint)
         }
+
+        /**
+         * Samples the provided flows, emitting a tuple of the original flow's value as well as each
+         * of the combined flows' values.
+         *
+         * Flow<A>.sample(Flow<B>, Flow<C>, Flow<D>, Flow<E>, Flow<F>) -> (A, B, C, D, E, F)
+         */
+        fun <A, B, C, D, E, F> Flow<A>.sample(
+            b: Flow<B>,
+            c: Flow<C>,
+            d: Flow<D>,
+            e: Flow<E>,
+            f: Flow<F>,
+        ): Flow<Sextuple<A, B, C, D, E, F>> {
+            return this.sample(combine(b, c, d, e, f, ::Quint), ::toSextuple)
+        }
     }
 }
 
@@ -81,3 +101,12 @@
     val fourth: D,
     val fifth: E
 )
+
+data class Sextuple<A, B, C, D, E, F>(
+    val first: A,
+    val second: B,
+    val third: C,
+    val fourth: D,
+    val fifth: E,
+    val sixth: F,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
index 972895d..039109e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
@@ -19,7 +19,11 @@
 import android.content.Context;
 import android.os.Handler;
 
-import javax.inject.Inject;
+import com.android.systemui.dagger.qualifiers.Background;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
 
 /**
  * A wake lock that has a built in delay when releasing to give the framebuffer time to update.
@@ -32,9 +36,11 @@
     private final Handler mHandler;
     private final WakeLock mInner;
 
-    public DelayedWakeLock(Handler h, WakeLock inner) {
-        mHandler = h;
-        mInner = inner;
+    @AssistedInject
+    public DelayedWakeLock(@Background Handler handler, Context context, WakeLockLogger logger,
+            @Assisted String tag) {
+        mInner = WakeLock.createPartial(context, logger, tag);
+        mHandler = handler;
     }
 
     @Override
@@ -58,46 +64,11 @@
     }
 
     /**
-     * An injectable builder for {@see DelayedWakeLock} that has the context already filled in.
+     * Factory to create the instance of DelayedWakeLock class.
      */
-    public static class Builder {
-        private final Context mContext;
-        private final WakeLockLogger mLogger;
-        private String mTag;
-        private Handler mHandler;
-
-        /**
-         * Constructor for DelayedWakeLock.Builder
-         */
-        @Inject
-        public Builder(Context context, WakeLockLogger logger) {
-            mContext = context;
-            mLogger = logger;
-        }
-
-        /**
-         * Set the tag for the WakeLock.
-         */
-        public Builder setTag(String tag) {
-            mTag = tag;
-
-            return this;
-        }
-
-        /**
-         * Set the handler for the DelayedWakeLock.
-         */
-        public Builder setHandler(Handler handler) {
-            mHandler = handler;
-
-            return this;
-        }
-
-        /**
-         * Build the DelayedWakeLock.
-         */
-        public DelayedWakeLock build() {
-            return new DelayedWakeLock(mHandler, WakeLock.createPartial(mContext, mLogger, mTag));
-        }
+    @AssistedFactory
+    public interface Factory {
+        /** creates the instance of DelayedWakeLock class. */
+        DelayedWakeLock create(String tag);
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 9ee3d22..aee441a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -535,6 +535,7 @@
         }
         if (changed && fromKey) {
             Events.writeEvent(Events.EVENT_KEY, stream, lastAudibleStreamVolume);
+            mCallbacks.onVolumeChangedFromKey();
         }
         return changed;
     }
@@ -1030,6 +1031,18 @@
         }
 
         @Override
+        public void onVolumeChangedFromKey() {
+            for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+                entry.getValue().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        entry.getKey().onVolumeChangedFromKey();
+                    }
+                });
+            }
+        }
+
+        @Override
         public void onAccessibilityModeChanged(Boolean showA11yStream) {
             boolean show = showA11yStream != null && showA11yStream;
             for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 404621d..ce6d740 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -34,6 +34,7 @@
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL;
 import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder;
+import static com.android.systemui.Flags.hapticVolumeSlider;
 import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED;
 import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
 
@@ -49,7 +50,6 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
-import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
@@ -76,7 +76,6 @@
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.text.InputFilter;
-import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
@@ -117,14 +116,18 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.Prefs;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin;
+import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
-import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.VolumeDialog;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.plugins.VolumeDialogController.State;
 import com.android.systemui.plugins.VolumeDialogController.StreamState;
 import com.android.systemui.res.R;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DevicePostureController;
@@ -132,6 +135,8 @@
 import com.android.systemui.util.AlphaTintDrawableWrapper;
 import com.android.systemui.util.RoundedCornerProgressDrawable;
 import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
+import com.android.systemui.volume.ui.navigation.VolumeNavigator;
 
 import dagger.Lazy;
 
@@ -140,6 +145,9 @@
 import java.util.List;
 import java.util.function.Consumer;
 
+import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.CoroutineScope;
+
 /**
  * Visual presentation of the volume dialog.
  *
@@ -262,9 +270,9 @@
     private final Accessibility mAccessibility = new Accessibility();
     private final ConfigurationController mConfigurationController;
     private final MediaOutputDialogFactory mMediaOutputDialogFactory;
-    private final VolumePanelFactory mVolumePanelFactory;
     private final CsdWarningDialog.Factory mCsdWarningDialogFactory;
-    private final ActivityStarter mActivityStarter;
+    private final VolumePanelNavigationInteractor mVolumePanelNavigationInteractor;
+    private final VolumeNavigator mVolumeNavigator;
     private boolean mShowing;
     private boolean mShowA11yStream;
     private int mActiveStream;
@@ -303,6 +311,10 @@
     private int mOrientation;
     private final Lazy<SecureSettings> mSecureSettings;
     private int mDialogTimeoutMillis;
+    private final CoroutineDispatcher mMainDispatcher;
+    private final CoroutineScope mApplicationScope;
+    private final VibratorHelper mVibratorHelper;
+    private final com.android.systemui.util.time.SystemClock mSystemClock;
 
     public VolumeDialogImpl(
             Context context,
@@ -311,19 +323,26 @@
             DeviceProvisionedController deviceProvisionedController,
             ConfigurationController configurationController,
             MediaOutputDialogFactory mediaOutputDialogFactory,
-            VolumePanelFactory volumePanelFactory,
-            ActivityStarter activityStarter,
             InteractionJankMonitor interactionJankMonitor,
+            VolumePanelNavigationInteractor volumePanelNavigationInteractor,
+            VolumeNavigator volumeNavigator,
             boolean shouldListenForJank,
             CsdWarningDialog.Factory csdWarningDialogFactory,
             DevicePostureController devicePostureController,
             Looper looper,
             DumpManager dumpManager,
-            Lazy<SecureSettings> secureSettings) {
+            Lazy<SecureSettings> secureSettings,
+            VibratorHelper vibratorHelper,
+            @Main CoroutineDispatcher mainDispatcher,
+            @Application CoroutineScope applicationScope,
+            com.android.systemui.util.time.SystemClock systemClock) {
         mContext =
                 new ContextThemeWrapper(context, R.style.volume_dialog_theme);
         mHandler = new H(looper);
-
+        mMainDispatcher = mainDispatcher;
+        mApplicationScope = applicationScope;
+        mVibratorHelper = vibratorHelper;
+        mSystemClock = systemClock;
         mShouldListenForJank = shouldListenForJank;
         mController = volumeDialogController;
         mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
@@ -332,9 +351,7 @@
         mDeviceProvisionedController = deviceProvisionedController;
         mConfigurationController = configurationController;
         mMediaOutputDialogFactory = mediaOutputDialogFactory;
-        mVolumePanelFactory = volumePanelFactory;
         mCsdWarningDialogFactory = csdWarningDialogFactory;
-        mActivityStarter = activityStarter;
         mShowActiveStreamOnly = showActiveStreamOnly();
         mHasSeenODICaptionsTooltip =
                 Prefs.getBoolean(context, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false);
@@ -349,6 +366,8 @@
         mUseBackgroundBlur =
             mContext.getResources().getBoolean(R.bool.config_volumeDialogUseBackgroundBlur);
         mInteractionJankMonitor = interactionJankMonitor;
+        mVolumePanelNavigationInteractor = volumePanelNavigationInteractor;
+        mVolumeNavigator = volumeNavigator;
         mSecureSettings = secureSettings;
         mDialogTimeoutMillis = DIALOG_TIMEOUT_MILLIS;
 
@@ -839,6 +858,7 @@
             row.header.setFilters(new InputFilter[] {new InputFilter.LengthFilter(13)});
         }
         row.slider = row.view.findViewById(R.id.volume_row_slider);
+        row.createPlugin(mVibratorHelper, mSystemClock, mMainDispatcher, mApplicationScope);
         row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
         row.number = row.view.findViewById(R.id.volume_number);
 
@@ -1187,13 +1207,8 @@
                 Events.writeEvent(Events.EVENT_SETTINGS_CLICK);
                 dismissH(DISMISS_REASON_SETTINGS_CLICKED);
                 mMediaOutputDialogFactory.dismiss();
-                if (FeatureFlagUtils.isEnabled(mContext,
-                        FeatureFlagUtils.SETTINGS_VOLUME_PANEL_IN_SYSTEMUI)) {
-                    mVolumePanelFactory.create(true /* aboveStatusBar */, null);
-                } else {
-                    mActivityStarter.startActivity(new Intent(Settings.Panel.ACTION_VOLUME),
-                            true /* dismissShade */);
-                }
+                mVolumeNavigator.openVolumePanel(
+                        mVolumePanelNavigationInteractor.getVolumePanelRoute());
             });
         }
     }
@@ -1480,6 +1495,12 @@
         mController.getCaptionsComponentState(false);
         checkODICaptionsTooltip(false);
         updateBackgroundForDrawerClosedAmount();
+        for (int i = 0; i < mRows.size(); i++) {
+            VolumeRow row = mRows.get(i);
+            if (row.slider.getVisibility() == VISIBLE) {
+                row.addHaptics();
+            }
+        }
         Trace.endSection();
     }
 
@@ -1532,7 +1553,9 @@
 
     protected void dismissH(int reason) {
         Trace.beginSection("VolumeDialogImpl#dismissH");
-
+        for (int i = 0; i < mRows.size(); i++) {
+            mRows.get(i).removeHaptics();
+        }
         Log.i(TAG, "mDialog.dismiss() reason: " + Events.DISMISS_REASONS[reason]
                 + " from: " + Debug.getCaller());
 
@@ -2358,6 +2381,14 @@
         public void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkForSwitchState) {
             updateCaptionsEnabledH(isEnabled, checkForSwitchState);
         }
+
+        @Override
+        public void onVolumeChangedFromKey() {
+            VolumeRow activeRow = getActiveRow();
+            if (activeRow.mHapticPlugin != null) {
+                activeRow.mHapticPlugin.onKeyDown();
+            }
+        }
     };
 
     @VisibleForTesting void onPostureChanged(int posture) {
@@ -2459,6 +2490,15 @@
         @Override
         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
             if (mRow.ss == null) return;
+            if (getActiveRow().equals(mRow)
+                    && mRow.slider.getVisibility() == VISIBLE
+                    && mRow.mHapticPlugin != null) {
+                mRow.mHapticPlugin.onProgressChanged(seekBar, progress, fromUser);
+                if (!fromUser) {
+                    // Consider a change from program as the volume key being continuously pressed
+                    mRow.mHapticPlugin.onKeyDown();
+                }
+            }
             if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream)
                     + " onProgressChanged " + progress + " fromUser=" + fromUser);
             if (!fromUser) return;
@@ -2485,6 +2525,9 @@
         @Override
         public void onStartTrackingTouch(SeekBar seekBar) {
             if (D.BUG) Log.d(TAG, "onStartTrackingTouch"+ " " + mRow.stream);
+            if (mRow.mHapticPlugin != null) {
+                mRow.mHapticPlugin.onStartTrackingTouch(seekBar);
+            }
             mController.setActiveStream(mRow.stream);
             mRow.tracking = true;
         }
@@ -2492,6 +2535,9 @@
         @Override
         public void onStopTrackingTouch(SeekBar seekBar) {
             if (D.BUG) Log.d(TAG, "onStopTrackingTouch"+ " " + mRow.stream);
+            if (mRow.mHapticPlugin != null) {
+                mRow.mHapticPlugin.onStopTrackingTouch(seekBar);
+            }
             mRow.tracking = false;
             mRow.userAttempt = SystemClock.uptimeMillis();
             final int userLevel = getImpliedLevel(seekBar, seekBar.getProgress());
@@ -2524,6 +2570,22 @@
     }
 
     private static class VolumeRow {
+        private static final SliderHapticFeedbackConfig sSliderHapticFeedbackConfig =
+                new SliderHapticFeedbackConfig(
+                /* velocityInterpolatorFactor= */ 1f,
+                /* progressInterpolatorFactor= */ 1f,
+                /* progressBasedDragMinScale= */ 0f,
+                /* progressBasedDragMaxScale= */ 0.2f,
+                /* additionalVelocityMaxBump= */ 0.15f,
+                /* deltaMillisForDragInterval= */ 0f,
+                /* deltaProgressForDragThreshold= */ 0.015f,
+                /* numberOfLowTicks= */ 5,
+                /* maxVelocityToScale= */ 300f,
+                /* velocityAxis= */ MotionEvent.AXIS_Y,
+                /* upperBookendScale= */ 1f,
+                /* lowerBookendScale= */ 0.05f,
+                /* exponent= */ 1f / 0.89f);
+
         private View view;
         private TextView header;
         private ImageButton icon;
@@ -2544,6 +2606,7 @@
         private ObjectAnimator anim;  // slider progress animation for non-touch-related updates
         private int animTargetProgress;
         private int lastAudibleLevel = 1;
+        private SeekableSliderHapticPlugin mHapticPlugin;
 
         void setIcon(int iconRes, Resources.Theme theme) {
             if (icon != null) {
@@ -2554,6 +2617,50 @@
                 sliderProgressIcon.setDrawable(view.getResources().getDrawable(iconRes, theme));
             }
         }
+
+        void createPlugin(
+                VibratorHelper vibratorHelper,
+                com.android.systemui.util.time.SystemClock systemClock,
+                CoroutineDispatcher mainDispatcher,
+                CoroutineScope applicationScope) {
+            if (!hapticVolumeSlider() || mHapticPlugin != null) return;
+
+            mHapticPlugin = new SeekableSliderHapticPlugin(
+                    vibratorHelper,
+                    systemClock,
+                    mainDispatcher,
+                    applicationScope,
+                    sSliderHapticFeedbackConfig);
+        }
+
+
+        @SuppressLint("ClickableViewAccessibility")
+        void addTouchListener() {
+            slider.setOnTouchListener(new View.OnTouchListener() {
+                @Override
+                public boolean onTouch(View view, MotionEvent motionEvent) {
+                    if (mHapticPlugin != null) {
+                        mHapticPlugin.onTouchEvent(motionEvent);
+                    }
+                    return false;
+                }
+            });
+        }
+
+        void addHaptics() {
+            if (mHapticPlugin != null) {
+                addTouchListener();
+                mHapticPlugin.start();
+            }
+        }
+
+        @SuppressLint("ClickableViewAccessibility")
+        void removeHaptics() {
+            slider.setOnTouchListener(null);
+            if (mHapticPlugin != null) {
+                mHapticPlugin.stop();
+            }
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
new file mode 100644
index 0000000..8d5e55a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dagger
+
+import android.media.AudioManager
+import com.android.settingslib.volume.data.repository.AudioRepository
+import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
+import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import dagger.Module
+import dagger.Provides
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+
+/** Dagger module for audio code in the volume package */
+@Module
+interface AudioModule {
+
+    companion object {
+
+        @Provides
+        fun provideAudioRepository(
+            audioManager: AudioManager,
+            @Background coroutineContext: CoroutineContext,
+            @Application coroutineScope: CoroutineScope,
+        ): AudioRepository = AudioRepositoryImpl(audioManager, coroutineContext, coroutineScope)
+
+        @Provides
+        fun provideAudioModeInteractor(repository: AudioRepository): AudioModeInteractor =
+            AudioModeInteractor(repository)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 497c4cb..c842e5f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -16,30 +16,36 @@
 
 package com.android.systemui.volume.dagger;
 
+import android.app.Activity;
 import android.content.Context;
 import android.media.AudioManager;
 import android.os.Looper;
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.systemui.CoreStartable;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
-import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.VolumeDialog;
 import com.android.systemui.plugins.VolumeDialogController;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.time.SystemClock;
 import com.android.systemui.volume.CsdWarningDialog;
 import com.android.systemui.volume.VolumeComponent;
 import com.android.systemui.volume.VolumeDialogComponent;
 import com.android.systemui.volume.VolumeDialogImpl;
-import com.android.systemui.volume.VolumePanelFactory;
 import com.android.systemui.volume.VolumeUI;
+import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
 import com.android.systemui.volume.panel.dagger.VolumePanelComponent;
 import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory;
+import com.android.systemui.volume.panel.ui.activity.VolumePanelActivity;
+import com.android.systemui.volume.ui.navigation.VolumeNavigator;
 
 import dagger.Binds;
 import dagger.Lazy;
@@ -49,8 +55,14 @@
 import dagger.multibindings.IntoMap;
 import dagger.multibindings.IntoSet;
 
+import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.CoroutineScope;
+
 /** Dagger Module for code in the volume package. */
 @Module(
+        includes = {
+                AudioModule.class,
+        },
         subcomponents = {
                 VolumePanelComponent.class
         }
@@ -71,6 +83,12 @@
     @Binds
     VolumeComponent provideVolumeComponent(VolumeDialogComponent volumeDialogComponent);
 
+    /** Inject into VolumePanelActivity. */
+    @Binds
+    @IntoMap
+    @ClassKey(VolumePanelActivity.class)
+    Activity bindVolumePanelActivity(VolumePanelActivity activity);
+
     /**  */
     @Binds
     VolumePanelComponentFactory bindVolumePanelComponentFactory(VolumePanelComponent.Factory impl);
@@ -84,13 +102,17 @@
             DeviceProvisionedController deviceProvisionedController,
             ConfigurationController configurationController,
             MediaOutputDialogFactory mediaOutputDialogFactory,
-            VolumePanelFactory volumePanelFactory,
-            ActivityStarter activityStarter,
             InteractionJankMonitor interactionJankMonitor,
+            VolumePanelNavigationInteractor volumePanelNavigationInteractor,
+            VolumeNavigator volumeNavigator,
             CsdWarningDialog.Factory csdFactory,
             DevicePostureController devicePostureController,
             DumpManager dumpManager,
-            Lazy<SecureSettings> secureSettings) {
+            Lazy<SecureSettings> secureSettings,
+            VibratorHelper vibratorHelper,
+            @Main CoroutineDispatcher mainDispatcher,
+            @Application CoroutineScope applicationScope,
+            SystemClock systemClock) {
         VolumeDialogImpl impl = new VolumeDialogImpl(
                 context,
                 volumeDialogController,
@@ -98,15 +120,19 @@
                 deviceProvisionedController,
                 configurationController,
                 mediaOutputDialogFactory,
-                volumePanelFactory,
-                activityStarter,
                 interactionJankMonitor,
+                volumePanelNavigationInteractor,
+                volumeNavigator,
                 true, /* should listen for jank */
                 csdFactory,
                 devicePostureController,
                 Looper.getMainLooper(),
                 dumpManager,
-                secureSettings);
+                secureSettings,
+                vibratorHelper,
+                mainDispatcher,
+                applicationScope,
+                systemClock);
         impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
         impl.setAutomute(true);
         impl.setSilentMode(false);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/VolumePanelNavigationInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/VolumePanelNavigationInteractor.kt
new file mode 100644
index 0000000..d64bb03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/VolumePanelNavigationInteractor.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.domain.interactor
+
+import android.content.Context
+import android.util.FeatureFlagUtils
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.volume.domain.model.VolumePanelRoute
+import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag
+import javax.inject.Inject
+
+/** Provides navigation routes for Volume space. */
+class VolumePanelNavigationInteractor
+@Inject
+constructor(
+    @Application private val context: Context,
+    private val volumePanelFlag: VolumePanelFlag,
+) {
+
+    fun getVolumePanelRoute(): VolumePanelRoute {
+        return when {
+            volumePanelFlag.canUseNewVolumePanel() -> VolumePanelRoute.COMPOSE_VOLUME_PANEL
+            FeatureFlagUtils.isEnabled(
+                context,
+                FeatureFlagUtils.SETTINGS_VOLUME_PANEL_IN_SYSTEMUI
+            ) -> VolumePanelRoute.SYSTEM_UI_VOLUME_PANEL
+            else -> VolumePanelRoute.SETTINGS_VOLUME_PANEL
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/model/VolumePanelRoute.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/model/VolumePanelRoute.kt
new file mode 100644
index 0000000..c85af15
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/model/VolumePanelRoute.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.domain.model
+
+enum class VolumePanelRoute {
+    COMPOSE_VOLUME_PANEL,
+    SETTINGS_VOLUME_PANEL,
+    SYSTEM_UI_VOLUME_PANEL,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
deleted file mode 100644
index 22a74d2..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/VolumePanelComponentKey.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume.panel
-
-/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
-typealias VolumePanelComponentKey = String
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
new file mode 100644
index 0000000..8ff2837
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.bottombar.ui.viewmodel
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+import javax.inject.Inject
+
+@VolumePanelScope
+class BottomBarViewModel
+@Inject
+constructor(
+    private val activityStarter: ActivityStarter,
+    private val volumePanelViewModel: VolumePanelViewModel,
+) {
+
+    fun onDoneClicked() {
+        volumePanelViewModel.dismissPanel()
+    }
+
+    fun onSettingsClicked() {
+        volumePanelViewModel.dismissPanel()
+        activityStarter.startActivity(
+            Intent(Settings.ACTION_SOUND_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
+            true,
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
new file mode 100644
index 0000000..1a4174a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.shared.model
+
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+
+object VolumePanelComponents {
+
+    const val BOTTOM_BAR: VolumePanelComponentKey = "bottom_bar"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt
index 3660ac1..d1d5390 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt
@@ -16,8 +16,9 @@
 
 package com.android.systemui.volume.panel.dagger
 
-import com.android.systemui.volume.panel.VolumePanelComponentKey
 import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
 import dagger.Module
 import dagger.multibindings.Multibinds
 
@@ -28,4 +29,6 @@
 interface DefaultMultibindsModule {
 
     @Multibinds fun criteriaMap(): Map<VolumePanelComponentKey, ComponentAvailabilityCriteria>
+
+    @Multibinds fun components(): Map<VolumePanelComponentKey, VolumePanelUiComponent>
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
index 0a057eb..0f19e9f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
@@ -16,12 +16,14 @@
 
 package com.android.systemui.volume.panel.dagger
 
+import com.android.systemui.volume.panel.component.bottombar.BottomBarModule
 import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import com.android.systemui.volume.panel.domain.DomainModule
 import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
 import com.android.systemui.volume.panel.ui.UiModule
-import com.android.systemui.volume.panel.ui.viewmodel.ComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
 import dagger.BindsInstance
 import dagger.Subcomponent
@@ -41,6 +43,7 @@
             DomainModule::class,
             UiModule::class,
             // Components modules
+            BottomBarModule::class,
         ]
 )
 interface VolumePanelComponent {
@@ -49,6 +52,8 @@
 
     fun componentsInteractor(): ComponentsInteractor
 
+    fun componentsFactory(): ComponentsFactory
+
     fun componentsLayoutManager(): ComponentsLayoutManager
 
     @Subcomponent.Factory
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
index 7817630..f785eb7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
@@ -16,10 +16,11 @@
 
 package com.android.systemui.volume.panel.domain
 
-import com.android.systemui.volume.panel.VolumePanelComponentKey
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
 import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractorImpl
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
@@ -47,6 +48,10 @@
          */
         @Provides
         @VolumePanelScope
-        fun provideEnabledComponents(): Collection<VolumePanelComponentKey> = setOf()
+        fun provideEnabledComponents(): Collection<VolumePanelComponentKey> {
+            return setOf(
+                VolumePanelComponents.BOTTOM_BAR,
+            )
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt
index e5b52ea..5301b00 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt
@@ -16,10 +16,10 @@
 
 package com.android.systemui.volume.panel.domain.interactor
 
-import com.android.systemui.volume.panel.VolumePanelComponentKey
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
 import com.android.systemui.volume.panel.domain.model.ComponentModel
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
 import javax.inject.Inject
 import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/model/ComponentModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/model/ComponentModel.kt
index 9765713..11a9916 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/model/ComponentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/model/ComponentModel.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.volume.panel.domain.model
 
-import com.android.systemui.volume.panel.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
 
 /**
  * Represents a current state of the Volume Panel component.
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt
new file mode 100644
index 0000000..d90a9c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.shared.flag
+
+import com.android.systemui.Flags
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.flags.RefactorFlagUtils
+import javax.inject.Inject
+
+/** Provides a flag to check for the new Compose based Volume Panel availability. */
+class VolumePanelFlag @Inject constructor() {
+
+    /**
+     * Returns true when the new Volume Panel is available and false the otherwise. The new panel
+     * can only be available when [ComposeFacade.isComposeAvailable] is true.
+     */
+    fun canUseNewVolumePanel(): Boolean {
+        return ComposeFacade.isComposeAvailable() && Flags.newVolumePanel()
+    }
+
+    fun assertNewVolumePanel() {
+        require(ComposeFacade.isComposeAvailable())
+        RefactorFlagUtils.assertInNewMode(Flags.newVolumePanel(), Flags.FLAG_NEW_VOLUME_PANEL)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/VolumePanelComponentKey.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/VolumePanelComponentKey.kt
new file mode 100644
index 0000000..4644ee7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/VolumePanelComponentKey.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.shared.model
+
+/** Uniquely identifies the [com.android.systemui.volume.panel.ui.VolumePanelComponent]. */
+typealias VolumePanelComponentKey = String
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/VolumePanelUiComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/VolumePanelUiComponent.kt
new file mode 100644
index 0000000..24de41f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/VolumePanelUiComponent.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.shared.model
+
+/**
+ * An element of a Volume Panel. This can be a button bar, group of sliders or something else. The
+ * only real implementation is Compose-based and located in `compose/features/`.
+ *
+ * Steps for adding an implementation in SystemUI:
+ * 1) Implement `ComposeVolumePanelUiComponent` in `compose/features/`
+ * 2) Add a module binding `ComposeVolumePanelUiComponent` into a map in compose/facade/enabled
+ * 3) Add an interface with the same name as the 2-step module in compose/facade/disabled to stub it
+ *    when the Compose is disabled
+ * 4) Add the module to the VolumePanelComponent
+ */
+interface VolumePanelUiComponent
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
index bfa7ef2..1346c54 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
@@ -16,8 +16,8 @@
 
 package com.android.systemui.volume.panel.ui
 
-import com.android.systemui.volume.panel.ui.viewmodel.ComponentsLayoutManager
-import com.android.systemui.volume.panel.ui.viewmodel.DefaultComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager
 import dagger.Binds
 import dagger.Module
 
@@ -25,5 +25,6 @@
 @Module
 interface UiModule {
 
-    @Binds fun bindSorter(impl: DefaultComponentsLayoutManager): ComponentsLayoutManager
+    @Binds
+    fun bindComponentsLayoutManager(impl: DefaultComponentsLayoutManager): ComponentsLayoutManager
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt
new file mode 100644
index 0000000..1b2265b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.activity
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.viewModels
+import androidx.core.view.WindowCompat
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+import javax.inject.Inject
+import javax.inject.Provider
+
+class VolumePanelActivity
+@Inject
+constructor(
+    private val volumePanelViewModelFactory: Provider<VolumePanelViewModel.Factory>,
+    private val volumePanelFlag: VolumePanelFlag,
+) : ComponentActivity() {
+
+    private val viewModel: VolumePanelViewModel by
+        viewModels(factoryProducer = { volumePanelViewModelFactory.get() })
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        enableEdgeToEdge()
+        super.onCreate(savedInstanceState)
+
+        volumePanelFlag.assertNewVolumePanel()
+
+        WindowCompat.setDecorFitsSystemWindows(window, false)
+        ComposeFacade.setVolumePanelActivityContent(this, viewModel) { finish() }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactory.kt
new file mode 100644
index 0000000..db1c121
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactory.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.composable
+
+import com.android.systemui.volume.panel.dagger.VolumePanelComponent
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+import javax.inject.Inject
+import javax.inject.Provider
+
+/** Provides [VolumePanelComponent] implementation for each [VolumePanelComponentKey]. */
+@VolumePanelScope
+class ComponentsFactory
+@Inject
+constructor(
+    private val componentByKey:
+        Map<
+            VolumePanelComponentKey,
+            @JvmSuppressWildcards
+            Provider<@JvmSuppressWildcards VolumePanelUiComponent>
+        >
+) {
+
+    fun createComponent(key: VolumePanelComponentKey): VolumePanelUiComponent {
+        require(componentByKey.containsKey(key)) { "Component for key=$key is not bound." }
+        return componentByKey.getValue(key).get()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt
new file mode 100644
index 0000000..25a95d8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.layout
+
+import com.android.systemui.volume.panel.ui.viewmodel.ComponentState
+
+/** Represents components grouping into the layout. */
+data class ComponentsLayout(
+    val contentComponents: List<ComponentState>,
+    val bottomBarComponent: ComponentState,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayoutManager.kt
new file mode 100644
index 0000000..71ca95c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayoutManager.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.layout
+
+import com.android.systemui.volume.panel.ui.viewmodel.ComponentState
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
+
+/**
+ * Lays out components to [ComponentsLayout], that UI uses to render the Volume Panel.
+ *
+ * Vertical layout shows the list from top to bottom:
+ * ```
+ * -----
+ * | 1 |
+ * | 2 |
+ * | 3 |
+ * | 4 |
+ * -----
+ * ```
+ *
+ * Horizontal layout shows the list in a grid from, filling the columns first:
+ * ```
+ * ----------
+ * | 1 || 3 |
+ * | 2 || 4 |
+ * ----------
+ * ```
+ */
+interface ComponentsLayoutManager {
+
+    fun layout(
+        volumePanelState: VolumePanelState,
+        components: Collection<ComponentState>,
+    ): ComponentsLayout
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
new file mode 100644
index 0000000..ff485c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.layout
+
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.viewmodel.ComponentState
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
+import javax.inject.Inject
+
+@VolumePanelScope
+class DefaultComponentsLayoutManager @Inject constructor() : ComponentsLayoutManager {
+
+    override fun layout(
+        volumePanelState: VolumePanelState,
+        components: Collection<ComponentState>
+    ): ComponentsLayout {
+        val bottomBarKey = VolumePanelComponents.BOTTOM_BAR
+        return ComponentsLayout(
+            components.filter { it.key != bottomBarKey }.sortedBy { it.key },
+            components.find { it.key == bottomBarKey }
+                ?: error(
+                    "VolumePanelComponents.BOTTOM_BAR must be present in the default " +
+                        "components layout."
+                )
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentState.kt
deleted file mode 100644
index 0a226e2..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentState.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume.panel.ui.model
-
-import com.android.systemui.volume.panel.VolumePanelComponentKey
-
-/**
- * State of the [VolumePanelComponent].
- *
- * @property key uniquely identifies this component
- * @property component is an inflated component obtained be the View Model
- * @property isVisible determines component visibility in the UI
- */
-data class ComponentState(
-    val key: VolumePanelComponentKey,
-    val isVisible: Boolean,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentsLayout.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentsLayout.kt
deleted file mode 100644
index 5690ac3..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/ComponentsLayout.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume.panel.ui.model
-
-/** Represents components grouping into the layout. */
-data class ComponentsLayout(
-    val contentComponents: List<ComponentState>,
-    val bottomBarComponent: ComponentState,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/VolumePanelState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/VolumePanelState.kt
deleted file mode 100644
index 399342f..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/model/VolumePanelState.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume.panel.ui.model
-
-import android.content.res.Configuration
-import android.content.res.Configuration.Orientation
-
-/**
- * State of the Volume Panel itself.
- *
- * @property orientation is current Volume Panel orientation.
- */
-data class VolumePanelState(
-    @Orientation val orientation: Int,
-    val isVisible: Boolean,
-) {
-    init {
-        require(
-            orientation == Configuration.ORIENTATION_PORTRAIT ||
-                orientation == Configuration.ORIENTATION_LANDSCAPE ||
-                orientation == Configuration.ORIENTATION_UNDEFINED ||
-                orientation == Configuration.ORIENTATION_SQUARE
-        ) {
-            "Unknown orientation: $orientation"
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentState.kt
new file mode 100644
index 0000000..5f4dbfb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentState.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.viewmodel
+
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+
+/**
+ * State of the [VolumePanelComponent]. It has everything the UI layer needs to layout a particular
+ * component.
+ *
+ * @property key uniquely identifies this component
+ * @property component is an inflated component obtained be the View Model
+ * @property isVisible determines component visibility in the UI
+ */
+data class ComponentState(
+    val key: VolumePanelComponentKey,
+    val component: VolumePanelUiComponent,
+    val isVisible: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentsLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentsLayoutManager.kt
deleted file mode 100644
index f45401a..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentsLayoutManager.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume.panel.ui.viewmodel
-
-import com.android.systemui.volume.panel.ui.model.ComponentState
-import com.android.systemui.volume.panel.ui.model.ComponentsLayout
-import com.android.systemui.volume.panel.ui.model.VolumePanelState
-
-/**
- * Lays out components to [ComponentsLayout], that UI uses to render the Volume Panel.
- *
- * Vertical layout shows the list from top to bottom:
- * ```
- * -----
- * | 1 |
- * | 2 |
- * | 3 |
- * | 4 |
- * -----
- * ```
- *
- * Horizontal layout shows the list in a grid from, filling the columns first:
- * ```
- * ----------
- * | 1 || 3 |
- * | 2 || 4 |
- * ----------
- * ```
- */
-interface ComponentsLayoutManager {
-
-    fun layout(
-        volumePanelState: VolumePanelState,
-        components: Collection<ComponentState>,
-    ): ComponentsLayout
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManager.kt
deleted file mode 100644
index cedfaf3..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManager.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume.panel.ui.viewmodel
-
-import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
-import com.android.systemui.volume.panel.ui.model.ComponentState
-import com.android.systemui.volume.panel.ui.model.ComponentsLayout
-import com.android.systemui.volume.panel.ui.model.VolumePanelState
-import javax.inject.Inject
-
-/**
- * Default [ComponentsLayoutManager]. It places [VolumePanelComponents.BOTTOM_BAR] to
- * [ComponentsLayout.bottomBarComponent] and everything else to
- * [ComponentsLayout.contentComponents].
- */
-@VolumePanelScope
-class DefaultComponentsLayoutManager @Inject constructor() : ComponentsLayoutManager {
-
-    override fun layout(
-        volumePanelState: VolumePanelState,
-        components: Collection<ComponentState>
-    ): ComponentsLayout = TODO("Unimplemented yet")
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelState.kt
new file mode 100644
index 0000000..f67db96
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelState.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.viewmodel
+
+import android.content.res.Configuration
+import android.content.res.Configuration.Orientation
+
+/**
+ * State of the Volume Panel itself.
+ *
+ * @property orientation is current Volume Panel orientation.
+ */
+data class VolumePanelState(
+    @Orientation val orientation: Int,
+    val isVisible: Boolean,
+) {
+    init {
+        require(
+            orientation == Configuration.ORIENTATION_PORTRAIT ||
+                orientation == Configuration.ORIENTATION_LANDSCAPE ||
+                orientation == Configuration.ORIENTATION_UNDEFINED ||
+                orientation == Configuration.ORIENTATION_SQUARE
+        ) {
+            "Unknown orientation: $orientation"
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
index dda361a..d87a79e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
@@ -26,9 +26,9 @@
 import com.android.systemui.volume.panel.dagger.VolumePanelComponent
 import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
 import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
-import com.android.systemui.volume.panel.ui.model.ComponentState
-import com.android.systemui.volume.panel.ui.model.ComponentsLayout
-import com.android.systemui.volume.panel.ui.model.VolumePanelState
+import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.cancel
@@ -38,9 +38,10 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.update
 
 class VolumePanelViewModel(
     resources: Resources,
@@ -56,6 +57,9 @@
     private val componentsInteractor: ComponentsInteractor
         get() = volumePanelComponent.componentsInteractor()
 
+    private val componentsFactory: ComponentsFactory
+        get() = volumePanelComponent.componentsFactory()
+
     private val componentsLayoutManager: ComponentsLayoutManager
         get() = volumePanelComponent.componentsLayoutManager()
 
@@ -63,20 +67,22 @@
 
     val volumePanelState: StateFlow<VolumePanelState> =
         combine(
-                configurationController.onConfigChanged.distinctUntilChanged(),
+                configurationController.onConfigChanged
+                    .onStart { emit(resources.configuration) }
+                    .distinctUntilChanged(),
                 mutablePanelVisibility,
             ) { configuration, isVisible ->
                 VolumePanelState(orientation = configuration.orientation, isVisible = isVisible)
             }
             .stateIn(
-                volumePanelComponent.coroutineScope(),
+                scope,
                 SharingStarted.Eagerly,
                 VolumePanelState(
                     orientation = resources.configuration.orientation,
                     isVisible = mutablePanelVisibility.value,
                 ),
             )
-    val mComponentsLayout: Flow<ComponentsLayout> =
+    val componentsLayout: Flow<ComponentsLayout> =
         combine(
                 componentsInteractor.components,
                 volumePanelState,
@@ -85,19 +91,20 @@
                     components.map { model ->
                         ComponentState(
                             model.key,
+                            componentsFactory.createComponent(model.key),
                             model.isAvailable,
                         )
                     }
                 componentsLayoutManager.layout(scope, componentStates)
             }
             .shareIn(
-                volumePanelComponent.coroutineScope(),
+                scope,
                 SharingStarted.Eagerly,
                 replay = 1,
             )
 
     fun dismissPanel() {
-        scope.launch { mutablePanelVisibility.emit(false) }
+        mutablePanelVisibility.update { false }
     }
 
     override fun onCleared() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt b/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt
new file mode 100644
index 0000000..790638c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.ui.navigation
+
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.volume.VolumePanelFactory
+import com.android.systemui.volume.domain.model.VolumePanelRoute
+import com.android.systemui.volume.panel.ui.activity.VolumePanelActivity
+import javax.inject.Inject
+
+class VolumeNavigator
+@Inject
+constructor(
+    @Application private val context: Context,
+    private val volumePanelFactory: VolumePanelFactory,
+    private val activityStarter: ActivityStarter,
+) {
+
+    fun openVolumePanel(route: VolumePanelRoute) {
+        when (route) {
+            VolumePanelRoute.COMPOSE_VOLUME_PANEL ->
+                activityStarter.startActivityDismissingKeyguard(
+                    /* intent = */ Intent(context, VolumePanelActivity::class.java),
+                    /* onlyProvisioned = */ false,
+                    /* dismissShade= */ true,
+                    /* disallowEnterPictureInPictureWhileLaunching = */ true,
+                    /* callback= */ null,
+                    /* flags= */ 0,
+                    /* animationController= */ null,
+                    /* userHandle= */ null,
+                )
+            VolumePanelRoute.SETTINGS_VOLUME_PANEL ->
+                activityStarter.startActivity(
+                    /* intent= */ Intent(Settings.Panel.ACTION_VOLUME),
+                    /* dismissShade= */ true
+                )
+            VolumePanelRoute.SYSTEM_UI_VOLUME_PANEL ->
+                volumePanelFactory.create(aboveStatusBar = true, view = null)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
index e031be2..e0228d9 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
@@ -17,10 +17,13 @@
 package com.android.systemui.wallet.controller;
 
 import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
+import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE;
 import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE;
 
 import android.annotation.WorkerThread;
 import android.app.PendingIntent;
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
 import android.content.Context;
 import android.content.Intent;
 import android.database.ContentObserver;
@@ -31,12 +34,12 @@
 import android.service.quickaccesswallet.QuickAccessWalletClientImpl;
 import android.util.Log;
 
-import com.android.systemui.res.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.res.R;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.time.SystemClock;
 import com.android.systemui.wallet.ui.WalletActivity;
@@ -58,6 +61,7 @@
      */
     public enum WalletChangeEvent {
         DEFAULT_PAYMENT_APP_CHANGE,
+        DEFAULT_WALLET_APP_CHANGE,
         WALLET_PREFERENCE_CHANGE,
     }
 
@@ -71,9 +75,12 @@
 
     private QuickAccessWalletClient mQuickAccessWalletClient;
     private ContentObserver mWalletPreferenceObserver;
+    private RoleManager mRoleManager;
+    private OnRoleHoldersChangedListener mDefaultWalletAppObserver;
     private ContentObserver mDefaultPaymentAppObserver;
     private int mWalletPreferenceChangeEvents = 0;
     private int mDefaultPaymentAppChangeEvents = 0;
+    private int mDefaultWalletAppChangeEvents = 0;
     private boolean mWalletEnabled = false;
     private long mQawClientCreatedTimeMillis;
 
@@ -89,6 +96,7 @@
         mExecutor = executor;
         mBgExecutor = bgExecutor;
         mSecureSettings = secureSettings;
+        mRoleManager = mContext.getSystemService(RoleManager.class);
         mQuickAccessWalletClient = quickAccessWalletClient;
         mClock = clock;
         mQawClientCreatedTimeMillis = mClock.elapsedRealtime();
@@ -122,6 +130,8 @@
                 setupWalletPreferenceObserver();
             } else if (event == DEFAULT_PAYMENT_APP_CHANGE) {
                 setupDefaultPaymentAppObserver(cardsRetriever);
+            } else if (event == DEFAULT_WALLET_APP_CHANGE) {
+                setupDefaultWalletAppObserver(cardsRetriever);
             }
         }
     }
@@ -141,6 +151,12 @@
                 if (mDefaultPaymentAppChangeEvents == 0) {
                     mSecureSettings.unregisterContentObserver(mDefaultPaymentAppObserver);
                 }
+            } else if (event == DEFAULT_WALLET_APP_CHANGE && mDefaultWalletAppObserver != null) {
+                mDefaultWalletAppChangeEvents--;
+                if (mDefaultWalletAppChangeEvents == 0) {
+                    mRoleManager.removeOnRoleHoldersChangedListenerAsUser(mDefaultWalletAppObserver,
+                            UserHandle.ALL);
+                }
             }
         }
     }
@@ -300,6 +316,25 @@
         mDefaultPaymentAppChangeEvents++;
     }
 
+    private void setupDefaultWalletAppObserver(
+            QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever) {
+        if (mDefaultWalletAppObserver == null) {
+            mDefaultWalletAppObserver = (roleName, user) -> {
+                if (!roleName.equals(RoleManager.ROLE_WALLET)) {
+                    return;
+                }
+                mExecutor.execute(() -> {
+                    reCreateWalletClient();
+                    updateWalletPreference();
+                    queryWalletCards(cardsRetriever);
+                });
+            };
+            mRoleManager.addOnRoleHoldersChangedListenerAsUser(mExecutor,
+                    mDefaultWalletAppObserver, UserHandle.ALL);
+        }
+        mDefaultWalletAppChangeEvents++;
+    }
+
     private void setupWalletPreferenceObserver() {
         if (mWalletPreferenceObserver == null) {
             mWalletPreferenceObserver = new ContentObserver(null /* handler */) {
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
index 75df1bd..eb4ff17 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
@@ -84,7 +84,9 @@
                         walletController.setupWalletChangeObservers(
                             callback,
                             QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
-                            QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+                            QuickAccessWalletController.WalletChangeEvent
+                                .DEFAULT_PAYMENT_APP_CHANGE,
+                            QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE
                         )
                         walletController.updateWalletPreference()
                         walletController.queryWalletCards(callback, MAX_CARDS)
@@ -94,7 +96,9 @@
                                 QuickAccessWalletController.WalletChangeEvent
                                     .WALLET_PREFERENCE_CHANGE,
                                 QuickAccessWalletController.WalletChangeEvent
-                                    .DEFAULT_PAYMENT_APP_CHANGE
+                                    .DEFAULT_PAYMENT_APP_CHANGE,
+                                QuickAccessWalletController.WalletChangeEvent
+                                    .DEFAULT_WALLET_APP_CHANGE
                             )
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 20fef92..9bfc4ce 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -210,7 +210,13 @@
             if (DEBUG) {
                 Log.i(TAG, "onSurfaceDestroyed");
             }
-            mSurfaceHolder = null;
+            mLongExecutor.execute(this::onSurfaceDestroyedSynchronized);
+        }
+
+        private void onSurfaceDestroyedSynchronized() {
+            synchronized (mLock) {
+                mSurfaceHolder = null;
+            }
         }
 
         @Override
@@ -241,7 +247,7 @@
 
         private void drawFrameInternal() {
             if (mSurfaceHolder == null) {
-                Log.e(TAG, "attempt to draw a frame without a valid surface");
+                Log.i(TAG, "attempt to draw a frame without a valid surface");
                 return;
             }
 
@@ -470,10 +476,15 @@
 
         @Override
         public void onDisplayChanged(int displayId) {
-            // changes the display in the color extractor
-            // the new display dimensions will be used in the next color computation
-            if (displayId == getDisplayContext().getDisplayId()) {
-                getDisplaySizeAndUpdateColorExtractor();
+            Trace.beginSection("ImageWallpaper.CanvasEngine#onDisplayChanged");
+            try {
+                // changes the display in the color extractor
+                // the new display dimensions will be used in the next color computation
+                if (displayId == getDisplayContext().getDisplayId()) {
+                    getDisplaySizeAndUpdateColorExtractor();
+                }
+            } finally {
+                Trace.endSection();
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
index b8f9583..1ba269e 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
@@ -24,7 +24,7 @@
 import android.os.UserHandle
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.user.data.model.SelectedUserModel
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.UserRepository
@@ -54,7 +54,7 @@
 class WallpaperRepositoryImpl
 @Inject
 constructor(
-    @Application scope: CoroutineScope,
+    @Background scope: CoroutineScope,
     broadcastDispatcher: BroadcastDispatcher,
     userRepository: UserRepository,
     private val wallpaperManager: WallpaperManager,
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 1e801ae..7c6ad23 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -32,6 +32,8 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.inputmethodservice.InputMethodService;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.util.Log;
 import android.view.Display;
@@ -125,6 +127,7 @@
     private final DisplayTracker mDisplayTracker;
     private final NoteTaskInitializer mNoteTaskInitializer;
     private final Executor mSysUiMainExecutor;
+    private HandlerThread mHandlerThread;
 
     // Listeners and callbacks. Note that we prefer member variable over anonymous class here to
     // avoid the situation that some implementations, like KeyguardUpdateMonitor, use WeakReference
@@ -206,6 +209,8 @@
         mDisplayTracker = displayTracker;
         mNoteTaskInitializer = noteTaskInitializer;
         mSysUiMainExecutor = sysUiMainExecutor;
+        mHandlerThread = new HandlerThread("WMShell");
+        mHandlerThread.start();
     }
 
     @Override
@@ -219,7 +224,8 @@
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
 
         // Subscribe to user changes
-        mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+        mUserTracker.addCallback(mUserChangedCallback,
+                     new HandlerExecutor(mHandlerThread.getThreadHandler()));
 
         mCommandQueue.addCallback(this);
         mPipOptional.ifPresent(this::initPip);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
new file mode 100644
index 0000000..c2efc05
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard
+
+import android.testing.TestableLooper
+import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.util.LatencyTracker
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockscreenCredential
+import com.android.keyguard.KeyguardPinViewController.PinBouncerUiEvent
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.any
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class KeyguardPinViewControllerTest : SysuiTestCase() {
+
+    private lateinit var objectKeyguardPINView: KeyguardPINView
+
+    @Mock private lateinit var mockKeyguardPinView: KeyguardPINView
+
+    @Mock private lateinit var keyguardMessageArea: BouncerKeyguardMessageArea
+
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+    @Mock private lateinit var securityMode: SecurityMode
+
+    @Mock private lateinit var lockPatternUtils: LockPatternUtils
+
+    @Mock private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
+
+    @Mock
+    private lateinit var keyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
+
+    @Mock
+    private lateinit var keyguardMessageAreaController:
+        KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+
+    @Mock private lateinit var mLatencyTracker: LatencyTracker
+
+    @Mock private lateinit var liftToActivateListener: LiftToActivateListener
+
+    @Mock private val mEmergencyButtonController: EmergencyButtonController? = null
+    private val falsingCollector: FalsingCollector = FalsingCollectorFake()
+    @Mock lateinit var postureController: DevicePostureController
+    @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
+
+    @Mock lateinit var featureFlags: FeatureFlags
+    @Mock lateinit var passwordTextView: PasswordTextView
+    @Mock lateinit var deleteButton: NumPadButton
+    @Mock lateinit var enterButton: View
+    @Mock lateinit var uiEventLogger: UiEventLogger
+
+    @Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback>
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        Mockito.`when`(mockKeyguardPinView.requireViewById<View>(R.id.bouncer_message_area))
+            .thenReturn(keyguardMessageArea)
+        Mockito.`when`(
+                keyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea::class.java))
+            )
+            .thenReturn(keyguardMessageAreaController)
+        `when`(mockKeyguardPinView.passwordTextViewId).thenReturn(R.id.pinEntry)
+        `when`(mockKeyguardPinView.findViewById<PasswordTextView>(R.id.pinEntry))
+            .thenReturn(passwordTextView)
+        `when`(mockKeyguardPinView.resources).thenReturn(context.resources)
+        `when`(mockKeyguardPinView.findViewById<NumPadButton>(R.id.delete_button))
+            .thenReturn(deleteButton)
+        `when`(mockKeyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton)
+        // For posture tests:
+        `when`(mockKeyguardPinView.buttons).thenReturn(arrayOf())
+        `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
+        `when`(featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)).thenReturn(false)
+
+        objectKeyguardPINView =
+            View.inflate(mContext, R.layout.keyguard_pin_view, null)
+                .requireViewById(R.id.keyguard_pin_view) as KeyguardPINView
+    }
+
+    private fun constructPinViewController(
+        mKeyguardPinView: KeyguardPINView
+    ): KeyguardPinViewController {
+        return KeyguardPinViewController(
+            mKeyguardPinView,
+            keyguardUpdateMonitor,
+            securityMode,
+            lockPatternUtils,
+            mKeyguardSecurityCallback,
+            keyguardMessageAreaControllerFactory,
+            mLatencyTracker,
+            liftToActivateListener,
+            mEmergencyButtonController,
+            falsingCollector,
+            postureController,
+            featureFlags,
+            mSelectedUserInteractor,
+            uiEventLogger,
+            FakeKeyboardRepository()
+        )
+    }
+
+    @Test
+    fun onViewAttached_deviceHalfFolded_propagatedToPatternView() {
+        val pinViewController = constructPinViewController(objectKeyguardPINView)
+        overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
+        whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
+
+        pinViewController.onViewAttached()
+
+        assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
+    }
+
+    @Test
+    fun onDevicePostureChanged_deviceOpened_propagatedToPatternView() {
+        val pinViewController = constructPinViewController(objectKeyguardPINView)
+        overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
+
+        whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
+        pinViewController.onViewAttached()
+
+        // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED
+        assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
+
+        // Simulate posture change to state DEVICE_POSTURE_OPENED with callback
+        verify(postureController).addCallback(postureCallbackCaptor.capture())
+        val postureCallback: DevicePostureController.Callback = postureCallbackCaptor.value
+        postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+        // Verify view is now in posture state DEVICE_POSTURE_OPENED
+        assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
+
+        // Simulate posture change to same state with callback
+        postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+        // Verify view is still in posture state DEVICE_POSTURE_OPENED
+        assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
+    }
+
+    private fun getPinTopGuideline(): Float {
+        val cs = ConstraintSet()
+        val container =
+            objectKeyguardPINView.requireViewById(R.id.pin_container) as ConstraintLayout
+        cs.clone(container)
+        return cs.getConstraint(R.id.pin_pad_top_guideline).layout.guidePercent
+    }
+
+    private fun getHalfOpenedBouncerHeightRatio(): Float {
+        return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio)
+    }
+
+    @Test
+    fun testOnViewAttached() {
+        val pinViewController = constructPinViewController(mockKeyguardPinView)
+
+        pinViewController.onViewAttached()
+
+        verify(keyguardMessageAreaController)
+            .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
+    }
+
+    @Test
+    fun testOnViewAttached_withExistingMessage() {
+        val pinViewController = constructPinViewController(mockKeyguardPinView)
+        Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+
+        pinViewController.onViewAttached()
+
+        verify(keyguardMessageAreaController, Mockito.never()).setMessage(anyString(), anyBoolean())
+    }
+
+    @Test
+    fun testOnViewAttached_withAutoPinConfirmationFailedPasswordAttemptsLessThan5() {
+        val pinViewController = constructPinViewController(mockKeyguardPinView)
+        `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
+        `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
+        `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
+        `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3)
+        `when`(passwordTextView.text).thenReturn("")
+
+        pinViewController.onViewAttached()
+
+        verify(deleteButton).visibility = View.INVISIBLE
+        verify(enterButton).visibility = View.INVISIBLE
+        verify(passwordTextView).setUsePinShapes(true)
+        verify(passwordTextView).setIsPinHinting(true)
+    }
+
+    @Test
+    fun testOnViewAttached_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5() {
+        val pinViewController = constructPinViewController(mockKeyguardPinView)
+        `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
+        `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
+        `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
+        `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6)
+        `when`(passwordTextView.text).thenReturn("")
+
+        pinViewController.onViewAttached()
+
+        verify(deleteButton).visibility = View.VISIBLE
+        verify(enterButton).visibility = View.VISIBLE
+        verify(passwordTextView).setUsePinShapes(true)
+        verify(passwordTextView).setIsPinHinting(false)
+    }
+
+    @Test
+    fun handleLockout_readsNumberOfErrorAttempts() {
+        val pinViewController = constructPinViewController(mockKeyguardPinView)
+
+        pinViewController.handleAttemptLockout(0)
+
+        verify(lockPatternUtils).getCurrentFailedPasswordAttempts(anyInt())
+    }
+
+    @Test
+    fun onUserInput_autoConfirmation_attemptsUnlock() {
+        val pinViewController = constructPinViewController(mockKeyguardPinView)
+        whenever(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
+        whenever(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
+        whenever(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
+        whenever(passwordTextView.text).thenReturn("000000")
+        whenever(enterButton.visibility).thenReturn(View.INVISIBLE)
+        whenever(mockKeyguardPinView.enteredCredential)
+            .thenReturn(LockscreenCredential.createPin("000000"))
+
+        pinViewController.onUserInput()
+
+        verify(uiEventLogger).log(PinBouncerUiEvent.ATTEMPT_UNLOCK_WITH_AUTO_CONFIRM_FEATURE)
+        verify(keyguardUpdateMonitor).setCredentialAttempted()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
new file mode 100644
index 0000000..0959f1b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard
+
+import android.telephony.TelephonyManager
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.util.LatencyTracker
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
+import com.android.systemui.res.R
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class KeyguardSimPinViewControllerTest : SysuiTestCase() {
+    private lateinit var simPinView: KeyguardSimPinView
+    private lateinit var underTest: KeyguardSimPinViewController
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var securityMode: KeyguardSecurityModel.SecurityMode
+    @Mock private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock private lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
+    @Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
+    @Mock private lateinit var latencyTracker: LatencyTracker
+    @Mock private lateinit var liftToActivateListener: LiftToActivateListener
+    @Mock private lateinit var telephonyManager: TelephonyManager
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var emergencyButtonController: EmergencyButtonController
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
+    @Mock
+    private lateinit var keyguardMessageAreaController:
+        KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+    private val updateMonitorCallbackArgumentCaptor =
+        ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        `when`(messageAreaControllerFactory.create(Mockito.any(KeyguardMessageArea::class.java)))
+            .thenReturn(keyguardMessageAreaController)
+        `when`(telephonyManager.createForSubscriptionId(anyInt())).thenReturn(telephonyManager)
+        `when`(telephonyManager.supplyIccLockPin(anyString())).thenReturn(mock())
+        simPinView =
+            LayoutInflater.from(context).inflate(R.layout.keyguard_sim_pin_view, null)
+                as KeyguardSimPinView
+        val fakeFeatureFlags = FakeFeatureFlags()
+
+        mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
+        underTest =
+            KeyguardSimPinViewController(
+                simPinView,
+                keyguardUpdateMonitor,
+                securityMode,
+                lockPatternUtils,
+                keyguardSecurityCallback,
+                messageAreaControllerFactory,
+                latencyTracker,
+                liftToActivateListener,
+                telephonyManager,
+                falsingCollector,
+                emergencyButtonController,
+                fakeFeatureFlags,
+                mSelectedUserInteractor,
+                FakeKeyboardRepository()
+            )
+        underTest.init()
+        underTest.onViewAttached()
+        underTest.onResume(0)
+        verify(keyguardUpdateMonitor)
+            .registerCallback(updateMonitorCallbackArgumentCaptor.capture())
+        reset(keyguardMessageAreaController)
+        reset(keyguardUpdateMonitor)
+    }
+
+    @Test
+    fun onViewAttached() {
+        underTest.onViewAttached()
+        verify(keyguardMessageAreaController)
+            .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
+        verify(keyguardUpdateMonitor)
+            .registerCallback(any(KeyguardUpdateMonitorCallback::class.java))
+    }
+
+    @Test
+    fun onViewDetached() {
+        underTest.onViewDetached()
+        verify(keyguardUpdateMonitor).removeCallback(any(KeyguardUpdateMonitorCallback::class.java))
+    }
+
+    @Test
+    fun onResume() {
+        underTest.onResume(KeyguardSecurityView.VIEW_REVEALED)
+    }
+
+    @Test
+    fun onPause() {
+        underTest.onPause()
+    }
+
+    @Test
+    fun startAppearAnimation() {
+        underTest.startAppearAnimation()
+    }
+
+    @Test
+    fun startDisappearAnimation() {
+        underTest.startDisappearAnimation {}
+    }
+
+    @Test
+    fun resetState() {
+        underTest.resetState()
+        verify(keyguardMessageAreaController).setMessage("")
+    }
+
+    @Test
+    fun onSimStateChangedFromPinToPuk_showsCurrentSecurityScreen() {
+        updateMonitorCallbackArgumentCaptor.value.onSimStateChanged(
+            /* subId= */ 0,
+            /* slotId= */ 0,
+            TelephonyManager.SIM_STATE_PIN_REQUIRED
+        )
+        verify(keyguardSecurityCallback, never()).showCurrentSecurityScreen()
+
+        updateMonitorCallbackArgumentCaptor.value.onSimStateChanged(
+            /* subId= */ 0,
+            /* slotId= */ 0,
+            TelephonyManager.SIM_STATE_PUK_REQUIRED
+        )
+
+        verify(keyguardSecurityCallback).showCurrentSecurityScreen()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
new file mode 100644
index 0000000..1281e44
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard
+
+import android.telephony.PinResult
+import android.telephony.TelephonyManager
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.util.LatencyTracker
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
+import com.android.systemui.res.R
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.mockito.any
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class KeyguardSimPukViewControllerTest : SysuiTestCase() {
+    private lateinit var simPukView: KeyguardSimPukView
+    private lateinit var underTest: KeyguardSimPukViewController
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var securityMode: KeyguardSecurityModel.SecurityMode
+    @Mock private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock private lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
+    @Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
+    @Mock private lateinit var latencyTracker: LatencyTracker
+    @Mock private lateinit var liftToActivateListener: LiftToActivateListener
+    @Mock private lateinit var telephonyManager: TelephonyManager
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var emergencyButtonController: EmergencyButtonController
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
+    @Mock
+    private lateinit var keyguardMessageAreaController:
+        KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        Mockito.`when`(
+                messageAreaControllerFactory.create(Mockito.any(KeyguardMessageArea::class.java))
+            )
+            .thenReturn(keyguardMessageAreaController)
+        Mockito.`when`(telephonyManager.createForSubscriptionId(Mockito.anyInt()))
+            .thenReturn(telephonyManager)
+        Mockito.`when`(telephonyManager.supplyIccLockPuk(anyString(), anyString()))
+            .thenReturn(Mockito.mock(PinResult::class.java))
+        simPukView =
+            LayoutInflater.from(context).inflate(R.layout.keyguard_sim_puk_view, null)
+                as KeyguardSimPukView
+        val fakeFeatureFlags = FakeFeatureFlags()
+        mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
+        underTest =
+            KeyguardSimPukViewController(
+                simPukView,
+                keyguardUpdateMonitor,
+                securityMode,
+                lockPatternUtils,
+                keyguardSecurityCallback,
+                messageAreaControllerFactory,
+                latencyTracker,
+                liftToActivateListener,
+                telephonyManager,
+                falsingCollector,
+                emergencyButtonController,
+                fakeFeatureFlags,
+                mSelectedUserInteractor,
+                FakeKeyboardRepository()
+            )
+        underTest.init()
+    }
+
+    @Test
+    fun onViewAttached() {
+        underTest.onViewAttached()
+        Mockito.verify(keyguardUpdateMonitor)
+            .registerCallback(any(KeyguardUpdateMonitorCallback::class.java))
+        Mockito.verify(keyguardMessageAreaController)
+            .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
+    }
+
+    @Test
+    fun onViewDetached() {
+        underTest.onViewDetached()
+        Mockito.verify(keyguardUpdateMonitor)
+            .removeCallback(any(KeyguardUpdateMonitorCallback::class.java))
+    }
+
+    @Test
+    fun onResume() {
+        underTest.onResume(KeyguardSecurityView.VIEW_REVEALED)
+    }
+
+    @Test
+    fun onPause() {
+        underTest.onPause()
+    }
+
+    @Test
+    fun startAppearAnimation() {
+        underTest.startAppearAnimation()
+    }
+
+    @Test
+    fun startDisappearAnimation() {
+        underTest.startDisappearAnimation {}
+    }
+
+    @Test
+    fun resetState() {
+        underTest.resetState()
+        Mockito.verify(keyguardMessageAreaController)
+            .setMessage(context.resources.getString(R.string.kg_puk_enter_puk_hint))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index d8eb05a..be06cc5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -121,7 +121,6 @@
 import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated;
 import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
 import com.android.settingslib.fuelgauge.BatteryStatus;
-import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
@@ -930,7 +929,8 @@
     @Test
     public void trustAgentHasTrust() {
         // WHEN user has trust
-        givenSelectedUserCanSkipBouncerFromTrustedState();
+        mKeyguardUpdateMonitor.onTrustChanged(true, true,
+                mSelectedUserInteractor.getSelectedUserId(), 0, null);
 
         // THEN user is considered as "having trust" and bouncer can be skipped
         Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
@@ -954,7 +954,8 @@
     @Test
     public void trustAgentHasTrust_fingerprintLockout() {
         // GIVEN user has trust
-        givenSelectedUserCanSkipBouncerFromTrustedState();
+        mKeyguardUpdateMonitor.onTrustChanged(true, true,
+                mSelectedUserInteractor.getSelectedUserId(), 0, null);
         Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
                 mSelectedUserInteractor.getSelectedUserId()));
 
@@ -1720,6 +1721,24 @@
     }
 
     @Test
+    public void assistantVisible_sendEventToFaceAuthInteractor() {
+        // WHEN the assistant is visible
+        mKeyguardUpdateMonitor.setAssistantVisible(true);
+
+        // THEN send event to face auth interactor
+        verify(mFaceAuthInteractor).onAssistantTriggeredOnLockScreen();
+    }
+
+    @Test
+    public void assistantNotVisible_doesNotSendEventToFaceAuthInteractor() {
+        // WHEN the assistant is visible
+        mKeyguardUpdateMonitor.setAssistantVisible(false);
+
+        // THEN never send event to face auth interactor
+        verify(mFaceAuthInteractor, never()).onAssistantTriggeredOnLockScreen();
+    }
+
+    @Test
     public void fingerprintFailure_requestActiveUnlock_dismissKeyguard() {
         // GIVEN shouldTriggerActiveUnlock
         bouncerFullyVisible();
@@ -1997,43 +2016,6 @@
     }
 
     @Test
-    public void runFpDetectFlagDisabled_sideFps_keyguardDismissible_fingerprintAuthenticateRuns() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD);
-
-        // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
-        // will trigger updateBiometricListeningState();
-        clearInvocations(mFingerprintManager);
-        mKeyguardUpdateMonitor.resetBiometricListeningState();
-
-        // GIVEN the user can skip the bouncer
-        givenSelectedUserCanSkipBouncerFromTrustedState();
-        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
-        mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
-        mTestableLooper.processAllMessages();
-
-        // WHEN verify authenticate runs
-        verifyFingerprintAuthenticateCall();
-    }
-
-    @Test
-    public void sideFps_keyguardDismissible_fingerprintDetectRuns() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD);
-        // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
-        // will trigger updateBiometricListeningState();
-        clearInvocations(mFingerprintManager);
-        mKeyguardUpdateMonitor.resetBiometricListeningState();
-
-        // GIVEN the user can skip the bouncer
-        givenSelectedUserCanSkipBouncerFromTrustedState();
-        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
-        mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
-        mTestableLooper.processAllMessages();
-
-        // WHEN verify detect runs
-        verifyFingerprintDetectCall();
-    }
-
-    @Test
     public void testFingerprintSensorProperties() throws RemoteException {
         mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(
                 new ArrayList<>());
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index 3d7d701..da6bfe8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -48,10 +48,10 @@
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.SceneTestUtils;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -76,7 +76,7 @@
 
     protected MockitoSession mStaticMockSession;
 
-    protected final SceneTestUtils mSceneTestUtils = new SceneTestUtils(this);
+    protected final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
     protected @Mock DeviceEntryInteractor mDeviceEntryInteractor;
     protected @Mock LockIconView mLockIconView;
     protected @Mock AnimatedStateListDrawable mIconDrawable;
@@ -175,7 +175,7 @@
                 mPrimaryBouncerInteractor,
                 mContext,
                 () -> mDeviceEntryInteractor,
-                mSceneTestUtils.getSceneContainerFlags()
+                mKosmos.getFakeSceneContainerFlags()
         );
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
index 93a5393..b0887ef 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
@@ -373,7 +373,7 @@
     @Test
     public void longPress_showBouncer_sceneContainerNotEnabled() {
         init(/* useMigrationFlag= */ false);
-        mSceneTestUtils.getSceneContainerFlags().setEnabled(false);
+        mKosmos.getFakeSceneContainerFlags().setEnabled(false);
         when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
 
         // WHEN longPress
@@ -387,7 +387,7 @@
     @Test
     public void longPress_showBouncer() {
         init(/* useMigrationFlag= */ false);
-        mSceneTestUtils.getSceneContainerFlags().setEnabled(true);
+        mKosmos.getFakeSceneContainerFlags().setEnabled(true);
         when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
 
         // WHEN longPress
@@ -401,7 +401,7 @@
     @Test
     public void longPress_falsingTriggered_doesNotShowBouncer() {
         init(/* useMigrationFlag= */ false);
-        mSceneTestUtils.getSceneContainerFlags().setEnabled(true);
+        mKosmos.getFakeSceneContainerFlags().setEnabled(true);
         when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(true);
 
         // WHEN longPress
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 837a130..d86d123 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -43,7 +43,6 @@
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
 import android.view.animation.AccelerateInterpolator;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
@@ -68,7 +67,6 @@
 
 @LargeTest
 @RunWith(AndroidTestingRunner.class)
-@FlakyTest(bugId = 308501761)
 public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
 
     @Rule
@@ -642,11 +640,10 @@
     @Test
     public void deleteWindowMagnification_enabling_expectedValuesAndInvokeCallback()
             throws RemoteException {
-
         enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
                 mAnimationCallback);
 
-        Mockito.reset(mSpyController);
+        resetMockObjects();
         getInstrumentation().runOnMainSync(() -> {
             mWindowMagnificationAnimationController.deleteWindowMagnification(
                     mAnimationCallback2);
@@ -660,6 +657,11 @@
             mValueAnimator.end();
         });
 
+        // wait for animation returns
+        waitForIdleSync();
+
+        // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)} will only
+        // be triggered once in {@link ValueAnimator#end()}
         verify(mSpyController).enableWindowMagnificationInternal(
                 mScaleCaptor.capture(),
                 mCenterXCaptor.capture(), mCenterYCaptor.capture(),
@@ -719,7 +721,11 @@
         deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
                 mAnimationCallback);
 
-        deleteWindowMagnificationAndWaitAnimating(0, null);
+        // Verifying that WindowMagnificationController#deleteWindowMagnification is never called
+        // in previous steps
+        verify(mSpyController, never()).deleteWindowMagnification();
+
+        deleteWindowMagnificationWithoutAnimation();
 
         verify(mSpyController).deleteWindowMagnification();
         verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
@@ -812,6 +818,8 @@
             mWindowMagnificationAnimationController.enableWindowMagnification(
                     targetScale, targetCenterX, targetCenterY, null);
         });
+        // wait for animation returns
+        waitForIdleSync();
     }
 
     private void enableWindowMagnificationAndWaitAnimating(long duration,
@@ -831,12 +839,16 @@
                     targetScale, targetCenterX, targetCenterY, callback);
             advanceTimeBy(duration);
         });
+        // wait for animation returns
+        waitForIdleSync();
     }
 
     private void deleteWindowMagnificationWithoutAnimation() {
         getInstrumentation().runOnMainSync(() -> {
             mWindowMagnificationAnimationController.deleteWindowMagnification(null);
         });
+        // wait for animation returns
+        waitForIdleSync();
     }
 
     private void deleteWindowMagnificationAndWaitAnimating(long duration,
@@ -845,6 +857,8 @@
             mWindowMagnificationAnimationController.deleteWindowMagnification(callback);
             advanceTimeBy(duration);
         });
+        // wait for animation returns
+        waitForIdleSync();
     }
 
     private void verifyStartValue(ArgumentCaptor<Float> captor, float startValue) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 044881e..04aef82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -92,6 +92,7 @@
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.AnimatorTestRule;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.res.R;
 import com.android.systemui.settings.FakeDisplayTracker;
@@ -159,6 +160,7 @@
     private View mSpyView;
     private View.OnTouchListener mTouchListener;
     private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
+    private KosmosJavaAdapter mKosmos;
 
     /**
      *  return whether window magnification is supported for current test context.
@@ -170,6 +172,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        mKosmos = new KosmosJavaAdapter(this);
         mContext = Mockito.spy(getContext());
         mHandler = new FakeHandler(TestableLooper.get(this).getLooper());
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -185,7 +188,7 @@
             return null;
         }).when(mSfVsyncFrameProvider).postFrameCallback(
                 any(FrameCallback.class));
-        mSysUiState = new SysUiState(mDisplayTracker);
+        mSysUiState = new SysUiState(mDisplayTracker, mKosmos.getSceneContainerPlugin());
         mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class));
         when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).then(
                 returnsSecondArg());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
index 9bcab57..9087816 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
@@ -16,10 +16,12 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.NonNull;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.WindowManager;
@@ -27,10 +29,12 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.utils.TestUtils;
 import com.android.systemui.util.settings.SecureSettings;
-import com.android.wm.shell.bubbles.DismissViewUtils;
 import com.android.wm.shell.common.bubbles.DismissView;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -46,6 +50,7 @@
 @TestableLooper.RunWithLooper
 public class DragToInteractAnimationControllerTest extends SysuiTestCase {
     private DragToInteractAnimationController mDragToInteractAnimationController;
+    private DragToInteractView mInteractView;
     private DismissView mDismissView;
 
     @Rule
@@ -57,29 +62,72 @@
     @Before
     public void setUp() throws Exception {
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
+        final SecureSettings mockSecureSettings = TestUtils.mockSecureSettings();
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
-                mock(SecureSettings.class));
+                mockSecureSettings);
         final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
                 stubWindowManager);
-        final MenuView stubMenuView = new MenuView(mContext, stubMenuViewModel,
-                stubMenuViewAppearance);
+        final MenuView stubMenuView = spy(new MenuView(mContext, stubMenuViewModel,
+                stubMenuViewAppearance, mockSecureSettings));
+        mInteractView = spy(new DragToInteractView(mContext));
         mDismissView = spy(new DismissView(mContext));
-        DismissViewUtils.setup(mDismissView);
-        mDragToInteractAnimationController = new DragToInteractAnimationController(
-                mDismissView, stubMenuView);
+
+        if (Flags.floatingMenuDragToEdit()) {
+            mDragToInteractAnimationController = new DragToInteractAnimationController(
+                    mInteractView, stubMenuView);
+        } else {
+            mDragToInteractAnimationController = new DragToInteractAnimationController(
+                    mDismissView, stubMenuView);
+        }
+
+        mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
+            @Override
+            public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+
+            }
+
+            @Override
+            public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+                    float velX, float velY, boolean wasFlungOut) {
+
+            }
+
+            @Override
+            public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+
+            }
+        });
     }
 
     @Test
-    public void showDismissView_success() {
-        mDragToInteractAnimationController.showDismissView(true);
+    @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+    public void showDismissView_success_old() {
+        mDragToInteractAnimationController.showInteractView(true);
 
         verify(mDismissView).show();
     }
 
     @Test
-    public void hideDismissView_success() {
-        mDragToInteractAnimationController.showDismissView(false);
+    @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+    public void hideDismissView_success_old() {
+        mDragToInteractAnimationController.showInteractView(false);
 
         verify(mDismissView).hide();
     }
+
+    @Test
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+    public void showDismissView_success() {
+        mDragToInteractAnimationController.showInteractView(true);
+
+        verify(mInteractView).show();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+    public void hideDismissView_success() {
+        mDragToInteractAnimationController.showInteractView(false);
+
+        verify(mInteractView).hide();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index 215f93d..e0df1e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.accessibility.floatingmenu;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -42,6 +43,7 @@
 import com.android.systemui.Flags;
 import com.android.systemui.Prefs;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.utils.TestUtils;
 import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.After;
@@ -79,10 +81,12 @@
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
         final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
                 stubWindowManager);
+        final SecureSettings secureSettings = TestUtils.mockSecureSettings();
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
-                mock(SecureSettings.class));
+                secureSettings);
 
-        mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance));
+        mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
+                secureSettings));
         mViewPropertyAnimator = spy(mMenuView.animate());
         doReturn(mViewPropertyAnimator).when(mMenuView).animate();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 9c8de30..c2ed7d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -22,10 +22,13 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
 import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.WindowManager;
@@ -37,7 +40,9 @@
 import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.utils.TestUtils;
 import com.android.systemui.res.R;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -49,6 +54,8 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.concurrent.atomic.AtomicBoolean;
+
 /** Tests for {@link MenuItemAccessibilityDelegate}. */
 @SmallTest
 @TestableLooper.RunWithLooper
@@ -59,17 +66,16 @@
 
     @Mock
     private AccessibilityManager mAccessibilityManager;
-    @Mock
-    private SecureSettings mSecureSettings;
-    @Mock
-    private DragToInteractAnimationController.DismissCallback mStubDismissCallback;
-
+    private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings();
     private RecyclerView mStubListView;
     private MenuView mMenuView;
+    private MenuViewLayer mMenuViewLayer;
     private MenuItemAccessibilityDelegate mMenuItemAccessibilityDelegate;
     private MenuAnimationController mMenuAnimationController;
     private final Rect mDraggableBounds = new Rect(100, 200, 300, 400);
 
+    private final AtomicBoolean mEditReceived = new AtomicBoolean(false);
+
     @Before
     public void setUp() {
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
@@ -80,20 +86,28 @@
 
         final int halfScreenHeight =
                 stubWindowManager.getCurrentWindowMetrics().getBounds().height() / 2;
-        mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance));
+        mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
+                mSecureSettings));
         mMenuView.setTranslationY(halfScreenHeight);
 
+        mMenuViewLayer = spy(new MenuViewLayer(
+                mContext, stubWindowManager, mAccessibilityManager,
+                stubMenuViewModel, stubMenuViewAppearance, mMenuView,
+                mock(IAccessibilityFloatingMenu.class), mSecureSettings));
+
         doReturn(mDraggableBounds).when(mMenuView).getMenuDraggableBounds();
         mStubListView = new RecyclerView(mContext);
         mMenuAnimationController = spy(new MenuAnimationController(mMenuView,
                 stubMenuViewAppearance));
         mMenuItemAccessibilityDelegate =
                 new MenuItemAccessibilityDelegate(new RecyclerViewAccessibilityDelegate(
-                        mStubListView), mMenuAnimationController);
+                        mStubListView), mMenuAnimationController, mMenuViewLayer);
+        mEditReceived.set(false);
     }
 
     @Test
-    public void getAccessibilityActionList_matchSize() {
+    @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+    public void getAccessibilityActionList_matchSize_withoutEdit() {
         final AccessibilityNodeInfoCompat info =
                 new AccessibilityNodeInfoCompat(new AccessibilityNodeInfo());
 
@@ -103,6 +117,17 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+    public void getAccessibilityActionList_matchSize() {
+        final AccessibilityNodeInfoCompat info =
+                new AccessibilityNodeInfoCompat(new AccessibilityNodeInfo());
+
+        mMenuItemAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mStubListView, info);
+
+        assertThat(info.getActionList().size()).isEqualTo(7);
+    }
+
+    @Test
     public void performMoveTopLeftAction_matchPosition() {
         final boolean moveTopLeftAction =
                 mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
@@ -169,13 +194,22 @@
 
     @Test
     public void performRemoveMenuAction_success() {
-        mMenuAnimationController.setDismissCallback(mStubDismissCallback);
         final boolean removeMenuAction =
                 mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
                         R.id.action_remove_menu, null);
 
         assertThat(removeMenuAction).isTrue();
-        verify(mMenuAnimationController).removeMenu();
+        verify(mMenuViewLayer).dispatchAccessibilityAction(R.id.action_remove_menu);
+    }
+
+    @Test
+    public void performEditAction_success() {
+        final boolean editAction =
+                mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
+                        R.id.action_edit, null);
+
+        assertThat(editAction).isTrue();
+        verify(mMenuViewLayer).dispatchAccessibilityAction(R.id.action_edit);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index e1522f5..9e8c6b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
+import static android.R.id.empty;
 import static android.view.View.OVER_SCROLL_NEVER;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -27,6 +28,8 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.MotionEvent;
@@ -38,10 +41,11 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.accessibility.MotionEventHelper;
+import com.android.systemui.accessibility.utils.TestUtils;
 import com.android.systemui.util.settings.SecureSettings;
-import com.android.wm.shell.bubbles.DismissViewUtils;
 import com.android.wm.shell.common.bubbles.DismissView;
 
 import org.junit.After;
@@ -71,6 +75,7 @@
     private DragToInteractAnimationController mDragToInteractAnimationController;
     private RecyclerView mStubListView;
     private DismissView mDismissView;
+    private DragToInteractView mInteractView;
 
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
@@ -81,19 +86,28 @@
     @Before
     public void setUp() throws Exception {
         final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+        final SecureSettings secureSettings = TestUtils.mockSecureSettings();
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
-                mock(SecureSettings.class));
+                secureSettings);
         final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
                 windowManager);
-        mStubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance);
+        mStubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
+                secureSettings);
         mStubMenuView.setTranslationX(0);
         mStubMenuView.setTranslationY(0);
         mMenuAnimationController = spy(new MenuAnimationController(
                 mStubMenuView, stubMenuViewAppearance));
+        mInteractView = spy(new DragToInteractView(mContext));
         mDismissView = spy(new DismissView(mContext));
-        DismissViewUtils.setup(mDismissView);
-        mDragToInteractAnimationController =
-                spy(new DragToInteractAnimationController(mDismissView, mStubMenuView));
+
+        if (Flags.floatingMenuDragToEdit()) {
+            mDragToInteractAnimationController = spy(new DragToInteractAnimationController(
+                    mInteractView, mStubMenuView));
+        } else {
+            mDragToInteractAnimationController = spy(new DragToInteractAnimationController(
+                    mDismissView, mStubMenuView));
+        }
+
         mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
                 mDragToInteractAnimationController);
         final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets);
@@ -115,7 +129,7 @@
 
     @Test
     public void onActionMoveEvent_notConsumedEvent_shouldMoveToPosition() {
-        doReturn(false).when(mDragToInteractAnimationController).maybeConsumeMoveMotionEvent(
+        doReturn(empty).when(mDragToInteractAnimationController).maybeConsumeMoveMotionEvent(
                 any(MotionEvent.class));
         final int offset = 100;
         final MotionEvent stubDownEvent =
@@ -136,6 +150,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
     public void onActionMoveEvent_shouldShowDismissView() {
         final int offset = 100;
         final MotionEvent stubDownEvent =
@@ -154,6 +169,25 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+    public void onActionMoveEvent_shouldShowInteractView() {
+        final int offset = 100;
+        final MotionEvent stubDownEvent =
+                mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1,
+                        MotionEvent.ACTION_DOWN, mStubMenuView.getTranslationX(),
+                        mStubMenuView.getTranslationY());
+        final MotionEvent stubMoveEvent =
+                mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 3,
+                        MotionEvent.ACTION_MOVE, mStubMenuView.getTranslationX() + offset,
+                        mStubMenuView.getTranslationY() + offset);
+
+        mTouchHandler.onInterceptTouchEvent(mStubListView, stubDownEvent);
+        mTouchHandler.onInterceptTouchEvent(mStubListView, stubMoveEvent);
+
+        verify(mInteractView).show();
+    }
+
+    @Test
     public void dragAndDrop_shouldFlingMenuThenSpringToEdge() {
         final int offset = 100;
         final MotionEvent stubDownEvent =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuNotificationFactoryTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuNotificationFactoryTest.java
new file mode 100644
index 0000000..9dd337e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuNotificationFactoryTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.floatingmenu;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Notification;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class MenuNotificationFactoryTest extends SysuiTestCase {
+    private MenuNotificationFactory mMenuNotificationFactory;
+
+    @Before
+    public void setUp() {
+        mMenuNotificationFactory = new MenuNotificationFactory(mContext);
+    }
+
+    @Test
+    public void createHiddenNotification_hasUndoAndDeleteAction() {
+        Notification notification = mMenuNotificationFactory.createHiddenNotification();
+
+        assertThat(notification.contentIntent.getIntent().getAction()).isEqualTo(
+                MenuNotificationFactory.ACTION_UNDO);
+        assertThat(notification.deleteIntent.getIntent().getAction()).isEqualTo(
+                MenuNotificationFactory.ACTION_DELETE);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index be6f3ff..ca3eb3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -21,15 +21,31 @@
 import static android.view.WindowInsets.Type.displayCutout;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.systemBars;
+
+import static com.android.systemui.accessibility.floatingmenu.MenuNotificationFactory.ACTION_DELETE;
+import static com.android.systemui.accessibility.floatingmenu.MenuNotificationFactory.ACTION_UNDO;
 import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex;
+
 import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
@@ -53,16 +69,25 @@
 import androidx.dynamicanimation.animation.SpringAnimation;
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.accessibility.utils.TestUtils;
+import com.android.systemui.res.R;
 import com.android.systemui.util.settings.SecureSettings;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
+import org.mockito.Spy;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
@@ -98,20 +123,25 @@
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
 
+    @Spy
+    private SysuiTestableContext mSpyContext = getContext();
     @Mock
     private IAccessibilityFloatingMenu mFloatingMenu;
-
-    @Mock
-    private SecureSettings mSecureSettings;
-
     @Mock
     private WindowManager mStubWindowManager;
-
     @Mock
     private AccessibilityManager mStubAccessibilityManager;
+    private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings();
+
+    private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
+
+    private final ArgumentMatcher<IntentFilter> mNotificationMatcher =
+            (arg) -> arg.hasAction(ACTION_UNDO) && arg.hasAction(ACTION_DELETE);
 
     @Before
     public void setUp() throws Exception {
+        mSpyContext.addMockSystemService(Context.NOTIFICATION_SERVICE, mMockNotificationManager);
+
         final Rect mDisplayBounds = new Rect();
         mDisplayBounds.set(/* left= */ 0, /* top= */ 0, DISPLAY_WINDOW_WIDTH,
                 DISPLAY_WINDOW_HEIGHT);
@@ -119,31 +149,39 @@
                 new WindowMetrics(mDisplayBounds, fakeDisplayInsets(), /* density = */ 0.0f));
         doReturn(mWindowMetrics).when(mStubWindowManager).getCurrentWindowMetrics();
 
-        mMenuViewLayer = new MenuViewLayer(mContext, mStubWindowManager, mStubAccessibilityManager,
-                mFloatingMenu, mSecureSettings);
+        MenuViewModel menuViewModel = new MenuViewModel(
+                mSpyContext, mStubAccessibilityManager, mSecureSettings);
+        MenuViewAppearance menuViewAppearance = new MenuViewAppearance(
+                mSpyContext, mStubWindowManager);
+        mMenuView = spy(
+                new MenuView(mSpyContext, menuViewModel, menuViewAppearance, mSecureSettings));
+
+        mMenuViewLayer = spy(new MenuViewLayer(mSpyContext, mStubWindowManager,
+                mStubAccessibilityManager, menuViewModel, menuViewAppearance, mMenuView,
+                mFloatingMenu, mSecureSettings));
         mMenuView = (MenuView) mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
         mMenuAnimationController = mMenuView.getMenuAnimationController();
 
         mLastAccessibilityButtonTargets =
-                Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                Settings.Secure.getStringForUser(mSpyContext.getContentResolver(),
                         Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
         mLastEnabledAccessibilityServices =
-                Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                Settings.Secure.getStringForUser(mSpyContext.getContentResolver(),
                         Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, UserHandle.USER_CURRENT);
 
         mMenuViewLayer.onAttachedToWindow();
-        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+        Settings.Secure.putStringForUser(mSpyContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", UserHandle.USER_CURRENT);
-        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+        Settings.Secure.putStringForUser(mSpyContext.getContentResolver(),
                 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "", UserHandle.USER_CURRENT);
     }
 
     @After
     public void tearDown() throws Exception {
-        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+        Settings.Secure.putStringForUser(mSpyContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, mLastAccessibilityButtonTargets,
                 UserHandle.USER_CURRENT);
-        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+        Settings.Secure.putStringForUser(mSpyContext.getContentResolver(),
                 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, mLastEnabledAccessibilityServices,
                 UserHandle.USER_CURRENT);
 
@@ -155,16 +193,16 @@
     @Test
     public void onAttachedToWindow_menuIsVisible() {
         mMenuViewLayer.onAttachedToWindow();
-        final View menuView = mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
 
+        final View menuView = mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
         assertThat(menuView.getVisibility()).isEqualTo(VISIBLE);
     }
 
     @Test
-    public void onAttachedToWindow_menuIsGone() {
+    public void onDetachedFromWindow_menuIsGone() {
         mMenuViewLayer.onDetachedFromWindow();
-        final View menuView = mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
 
+        final View menuView = mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
         assertThat(menuView.getVisibility()).isEqualTo(GONE);
     }
 
@@ -188,7 +226,7 @@
         setupEnabledAccessibilityServiceList();
 
         mMenuViewLayer.mDismissMenuAction.run();
-        final String value = Settings.Secure.getString(mContext.getContentResolver(),
+        final String value = Settings.Secure.getString(mSpyContext.getContentResolver(),
                 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
 
         assertThat(value).isEqualTo("");
@@ -200,16 +238,37 @@
         final List<String> stubShortcutTargets = new ArrayList<>();
         stubShortcutTargets.add(TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
         when(mStubAccessibilityManager.getAccessibilityShortcutTargets(
-                AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY)).thenReturn(stubShortcutTargets);
+                ShortcutConstants.UserShortcutType.HARDWARE)).thenReturn(stubShortcutTargets);
 
         mMenuViewLayer.mDismissMenuAction.run();
-        final String value = Settings.Secure.getString(mContext.getContentResolver(),
+        final String value = Settings.Secure.getString(mSpyContext.getContentResolver(),
                 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
 
         assertThat(value).isEqualTo(TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+    public void onEditAction_gotoEditScreen_isCalled() {
+        mMenuViewLayer.dispatchAccessibilityAction(R.id.action_edit);
+        verify(mMenuView).gotoEditScreen();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
+    public void onDismissAction_hideMenuAndShowNotification() {
+        mMenuViewLayer.dispatchAccessibilityAction(R.id.action_remove_menu);
+        verify(mMenuViewLayer).hideMenuAndShowNotification();
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
+    public void onDismissAction_hideMenuAndShowMessage() {
+        mMenuViewLayer.dispatchAccessibilityAction(R.id.action_remove_menu);
+        verify(mMenuViewLayer).hideMenuAndShowMessage();
+    }
+
+    @Test
     public void showingImeInsetsChange_notOverlapOnIme_menuKeepOriginalPosition() {
         final float menuTop = STATUS_BAR_HEIGHT + 100;
         mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop));
@@ -278,9 +337,54 @@
         assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
         assertThat(mMenuView.getTranslationY()).isEqualTo(beforePosition.y);
     }
+    @Test
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
+    public void onReleasedInTarget_hideMenuAndShowNotificationWithExpectedActions() {
+        dragMenuThenReleasedInTarget(R.id.action_remove_menu);
+
+        verify(mMockNotificationManager).notify(
+                eq(SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN),
+                any(Notification.class));
+        verify(mSpyContext).registerReceiver(
+                any(BroadcastReceiver.class), argThat(mNotificationMatcher), anyInt());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
+    public void receiveActionUndo_dismissNotificationAndMenuVisible() {
+        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(
+                BroadcastReceiver.class);
+        dragMenuThenReleasedInTarget(R.id.action_remove_menu);
+
+        verify(mSpyContext).registerReceiver(broadcastReceiverCaptor.capture(),
+                argThat(mNotificationMatcher), anyInt());
+        broadcastReceiverCaptor.getValue().onReceive(mSpyContext, new Intent(ACTION_UNDO));
+
+        verify(mSpyContext).unregisterReceiver(broadcastReceiverCaptor.getValue());
+        verify(mMockNotificationManager).cancel(
+                SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN);
+        assertThat(mMenuView.getVisibility()).isEqualTo(VISIBLE);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
+    public void receiveActionDelete_dismissNotificationAndHideMenu() {
+        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(
+                BroadcastReceiver.class);
+        dragMenuThenReleasedInTarget(R.id.action_remove_menu);
+
+        verify(mSpyContext).registerReceiver(broadcastReceiverCaptor.capture(),
+                argThat(mNotificationMatcher), anyInt());
+        broadcastReceiverCaptor.getValue().onReceive(mSpyContext, new Intent(ACTION_DELETE));
+
+        verify(mSpyContext).unregisterReceiver(broadcastReceiverCaptor.getValue());
+        verify(mMockNotificationManager).cancel(
+                SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN);
+        verify(mFloatingMenu).hide();
+    }
 
     private void setupEnabledAccessibilityServiceList() {
-        Settings.Secure.putString(mContext.getContentResolver(),
+        Settings.Secure.putString(mSpyContext.getContentResolver(),
                 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                 TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
 
@@ -344,6 +448,14 @@
                     springAnimation.skipToEnd();
                     springAnimation.doAnimationFrame(500);
                 });
+    }
 
+    private void dragMenuThenReleasedInTarget(int id) {
+        MagnetizedObject.MagnetListener magnetListener =
+                mMenuViewLayer.getDragToInteractAnimationController().getMagnetListener(id);
+        View view = mock(View.class);
+        when(view.getId()).thenReturn(id);
+        magnetListener.onReleasedInTarget(
+                new MagnetizedObject.MagneticTarget(view, 200));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index 8da6cf9..7c97f53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -17,15 +17,19 @@
 package com.android.systemui.accessibility.floatingmenu;
 
 import static android.app.UiModeManager.MODE_NIGHT_YES;
+
 import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.mock;
+
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
 import android.app.UiModeManager;
+import android.content.Intent;
 import android.graphics.Rect;
 import android.graphics.drawable.GradientDrawable;
 import android.platform.test.annotations.EnableFlags;
+import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.WindowManager;
@@ -36,6 +40,8 @@
 import com.android.systemui.Flags;
 import com.android.systemui.Prefs;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.accessibility.utils.TestUtils;
 import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.After;
@@ -65,17 +71,23 @@
     @Mock
     private AccessibilityManager mAccessibilityManager;
 
+    private SysuiTestableContext mSpyContext;
+
     @Before
     public void setUp() throws Exception {
         mUiModeManager = mContext.getSystemService(UiModeManager.class);
         mNightMode = mUiModeManager.getNightMode();
         mUiModeManager.setNightMode(MODE_NIGHT_YES);
+
+        mSpyContext = spy(mContext);
+        final SecureSettings secureSettings = TestUtils.mockSecureSettings();
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
-                mock(SecureSettings.class));
+                secureSettings);
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
-        mStubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager);
-        mMenuView = spy(new MenuView(mContext, stubMenuViewModel, mStubMenuViewAppearance));
-        mLastPosition = Prefs.getString(mContext,
+        mStubMenuViewAppearance = new MenuViewAppearance(mSpyContext, stubWindowManager);
+        mMenuView = spy(new MenuView(mSpyContext, stubMenuViewModel, mStubMenuViewAppearance,
+                secureSettings));
+        mLastPosition = Prefs.getString(mSpyContext,
                 Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null);
     }
 
@@ -154,6 +166,25 @@
         assertThat(radiiAnimator.isStarted()).isTrue();
     }
 
+    @Test
+    public void getIntentForEditScreen_validate() {
+        Intent intent = mMenuView.getIntentForEditScreen();
+        String[] targets = intent.getBundleExtra(
+                ":settings:show_fragment_args").getStringArray("targets");
+
+        assertThat(intent.getAction()).isEqualTo(Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
+        assertThat(targets).asList().containsExactlyElementsIn(TestUtils.TEST_BUTTON_TARGETS);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+    public void gotoEditScreen_sendsIntent() {
+        // Notably, this shouldn't crash the settings app,
+        // because the button target args are configured.
+        mMenuView.gotoEditScreen();
+        verify(mSpyContext).startActivity(any());
+    }
+
     private InstantInsetLayerDrawable getMenuViewInsetLayer() {
         return (InstantInsetLayerDrawable) mMenuView.getBackground();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java
index 10c8caa..8399fa8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java
@@ -16,11 +16,27 @@
 
 package com.android.systemui.accessibility.utils;
 
-import android.os.SystemClock;
+import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
 
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.systemui.util.settings.SecureSettings;
+
+import java.util.Set;
+import java.util.StringJoiner;
 import java.util.function.BooleanSupplier;
 
 public class TestUtils {
+    private static final ComponentName TEST_COMPONENT_A = new ComponentName("pkg", "A");
+    private static final ComponentName TEST_COMPONENT_B = new ComponentName("pkg", "B");
+    public static final String[] TEST_BUTTON_TARGETS = {
+            TEST_COMPONENT_A.flattenToString(), TEST_COMPONENT_B.flattenToString()};
     public static long DEFAULT_CONDITION_DURATION = 5_000;
 
     /**
@@ -55,4 +71,28 @@
             SystemClock.sleep(sleepMs);
         }
     }
+
+    /**
+     * Returns a mock secure settings configured to return information needed for tests.
+     * Currently, this only includes button targets.
+     */
+    public static SecureSettings mockSecureSettings() {
+        SecureSettings secureSettings = mock(SecureSettings.class);
+
+        final String targets = getShortcutTargets(
+                Set.of(TEST_COMPONENT_A, TEST_COMPONENT_B));
+        when(secureSettings.getStringForUser(
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+                UserHandle.USER_CURRENT)).thenReturn(targets);
+
+        return secureSettings;
+    }
+
+    private static String getShortcutTargets(Set<ComponentName> components) {
+        final StringJoiner stringJoiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR));
+        for (ComponentName target : components) {
+            stringJoiner.add(target.flattenToString());
+        }
+        return stringJoiner.toString();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 7c626a1..0ba9abe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -32,18 +32,24 @@
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.shade.QuickSettingsController
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -56,7 +62,7 @@
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.runCurrent
 import org.junit.Before
 import org.junit.Rule
@@ -73,7 +79,8 @@
 @RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalCoroutinesApi::class)
 class BackActionInteractorTest : SysuiTestCase() {
-    private val testScope = TestScope()
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
     private val executor = FakeExecutor(FakeSystemClock())
 
     @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
@@ -89,6 +96,9 @@
     @Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher
     @Mock private lateinit var iStatusBarService: IStatusBarService
     @Mock private lateinit var headsUpManager: HeadsUpManager
+    private val activeNotificationsRepository = ActiveNotificationListRepository()
+    private val activeNotificationsInteractor =
+        ActiveNotificationsInteractor(activeNotificationsRepository, StandardTestDispatcher())
 
     private val keyguardRepository = FakeKeyguardRepository()
     private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
@@ -98,6 +108,9 @@
             keyguardRepository,
             headsUpManager,
             powerInteractor,
+            activeNotificationsInteractor,
+            kosmos.sceneContainerFlags,
+            kosmos::sceneInteractor,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 0ee0939..43f7c60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.biometrics
 
 import android.app.admin.DevicePolicyManager
+import android.content.pm.PackageManager
 import android.hardware.biometrics.BiometricAuthenticator
 import android.hardware.biometrics.BiometricConstants
 import android.hardware.biometrics.BiometricManager
@@ -79,6 +80,8 @@
 import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
 
+private const val OP_PACKAGE_NAME = "biometric.testapp"
+
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper(setAsMainLooper = true)
 @SmallTest
@@ -109,6 +112,8 @@
     lateinit var authController: AuthController
     @Mock
     lateinit var selectedUserInteractor: SelectedUserInteractor
+    @Mock
+    private lateinit var packageManager: PackageManager
 
     private val testScope = TestScope(StandardTestDispatcher())
     private val fakeExecutor = FakeExecutor(FakeSystemClock())
@@ -134,6 +139,7 @@
     private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
 
     private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
+    private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
 
     private var authContainer: TestAuthContainerView? = null
 
@@ -156,6 +162,9 @@
                         selectedUserInteractor,
                         testScope.backgroundScope,
                 )
+        // Set up default logo icon
+        whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon)
+        context.setMockPackageManager(packageManager)
     }
 
     @After
@@ -533,6 +542,7 @@
             mPromptInfo = PromptInfo().apply {
                 this.authenticators = authenticators
             }
+            mOpPackageName = OP_PACKAGE_NAME
         },
         testScope.backgroundScope,
         fingerprintProps,
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 a47e288..7c03d78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -27,12 +27,13 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.keyguard.logging.KeyguardLogger
+import com.android.systemui.Flags
 import com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
-import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.deviceentry.domain.interactor.AuthRippleInteractor
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -42,7 +43,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.leak.RotationUtils
 import com.android.systemui.util.mockito.any
-import javax.inject.Provider
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import org.junit.After
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -61,8 +62,10 @@
 import org.mockito.MockitoAnnotations
 import org.mockito.MockitoSession
 import org.mockito.quality.Strictness
+import javax.inject.Provider
 
 
+@ExperimentalCoroutinesApi
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class AuthRippleControllerTest : SysuiTestCase() {
@@ -74,6 +77,7 @@
     @Mock private lateinit var configurationController: ConfigurationController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var authController: AuthController
+    @Mock private lateinit var authRippleInteractor: AuthRippleInteractor
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock
     private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
@@ -88,8 +92,6 @@
     @Mock
     private lateinit var statusBarStateController: StatusBarStateController
     @Mock
-    private lateinit var featureFlags: FeatureFlags
-    @Mock
     private lateinit var lightRevealScrim: LightRevealScrim
     @Mock
     private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal
@@ -103,6 +105,7 @@
 
     @Before
     fun setUp() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
         MockitoAnnotations.initMocks(this)
         staticMockSession = mockitoSession()
                 .mockStatic(RotationUtils::class.java)
@@ -128,6 +131,7 @@
             KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)),
             biometricUnlockController,
             lightRevealScrim,
+            authRippleInteractor,
             facePropertyRepository,
             rippleView,
         )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
index 647dae6..13306be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
@@ -20,6 +20,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -44,6 +45,7 @@
     @Mock lateinit var shadeInteractor: ShadeInteractor
     @Mock lateinit var systemUIDialogManager: SystemUIDialogManager
     @Mock lateinit var dumpManager: DumpManager
+    @Mock lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
 
     private lateinit var udfpsBpViewController: UdfpsBpViewController
 
@@ -55,7 +57,8 @@
                 statusBarStateController,
                 shadeInteractor,
                 systemUIDialogManager,
-                dumpManager
+                dumpManager,
+                udfpsOverlayInteractor,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
index ec7ce63..b39e09d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
@@ -43,6 +43,7 @@
 
 private const val USER_ID = 9
 private const val CHALLENGE = 90L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -102,7 +103,8 @@
                     PromptInfo().apply { isConfirmationRequested = case },
                     USER_ID,
                     CHALLENGE,
-                    PromptKind.Biometric()
+                    PromptKind.Biometric(),
+                    OP_PACKAGE_NAME
                 )
 
                 assertThat(isConfirmationRequired).isEqualTo(case)
@@ -120,7 +122,8 @@
                     PromptInfo().apply { isConfirmationRequested = case },
                     USER_ID,
                     CHALLENGE,
-                    PromptKind.Biometric()
+                    PromptKind.Biometric(),
+                    OP_PACKAGE_NAME
                 )
 
                 assertThat(isConfirmationRequired).isTrue()
@@ -133,17 +136,19 @@
             val kind = PromptKind.Pin
             val promptInfo = PromptInfo()
 
-            repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind)
+            repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind, OP_PACKAGE_NAME)
 
             assertThat(repository.kind.value).isEqualTo(kind)
             assertThat(repository.userId.value).isEqualTo(USER_ID)
             assertThat(repository.challenge.value).isEqualTo(CHALLENGE)
             assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo)
+            assertThat(repository.opPackageName.value).isEqualTo(OP_PACKAGE_NAME)
 
             repository.unsetPrompt()
 
             assertThat(repository.promptInfo.value).isNull()
             assertThat(repository.userId.value).isNull()
             assertThat(repository.challenge.value).isNull()
+            assertThat(repository.opPackageName.value).isNull()
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
index 8f8004f..b1e471a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
@@ -5,6 +5,7 @@
 import android.hardware.biometrics.IBiometricContextListener.FoldState
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.AuthController
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
@@ -17,6 +18,7 @@
 import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
 import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
 import com.android.systemui.unfold.updates.FoldStateProvider
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -42,7 +44,10 @@
     private val testScope = TestScope()
 
     @Mock private lateinit var foldProvider: FoldStateProvider
+    @Mock private lateinit var authController: AuthController
+    @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
 
+    private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
     private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
 
     private lateinit var interactor: LogContextInteractorImpl
@@ -50,6 +55,13 @@
     @Before
     fun setup() {
         keyguardTransitionRepository = FakeKeyguardTransitionRepository()
+        udfpsOverlayInteractor =
+            UdfpsOverlayInteractor(
+                context,
+                authController,
+                selectedUserInteractor,
+                testScope.backgroundScope,
+            )
         interactor =
             LogContextInteractorImpl(
                 testScope.backgroundScope,
@@ -59,6 +71,7 @@
                         scope = testScope.backgroundScope,
                     )
                     .keyguardTransitionInteractor,
+                udfpsOverlayInteractor,
             )
     }
 
@@ -162,6 +175,18 @@
         }
 
     @Test
+    fun isHardwareIgnoringTouchesChanges() =
+        testScope.runTest {
+            val isHardwareIgnoringTouches by collectLastValue(interactor.isHardwareIgnoringTouches)
+
+            udfpsOverlayInteractor.setHandleTouches(true)
+            assertThat(isHardwareIgnoringTouches).isFalse()
+
+            udfpsOverlayInteractor.setHandleTouches(false)
+            assertThat(isHardwareIgnoringTouches).isTrue()
+        }
+
+    @Test
     fun foldStateChanges() =
         testScope.runTest {
             val foldState = collectLastValue(interactor.foldState)
@@ -195,6 +220,7 @@
 
             var folded: Int? = null
             var displayState: Int? = null
+            var ignoreTouches: Boolean? = null
             val job =
                 interactor.addBiometricContextListener(
                     object : IBiometricContextListener.Stub() {
@@ -205,12 +231,17 @@
                         override fun onDisplayStateChanged(newDisplayState: Int) {
                             displayState = newDisplayState
                         }
+
+                        override fun onHardwareIgnoreTouchesChanged(newIgnoreTouches: Boolean) {
+                            ignoreTouches = newIgnoreTouches
+                        }
                     }
                 )
             runCurrent()
 
             assertThat(folded).isEqualTo(FoldState.FULLY_CLOSED)
             assertThat(displayState).isEqualTo(AuthenticateOptions.DISPLAY_STATE_AOD)
+            assertThat(ignoreTouches).isFalse()
 
             foldListener.onFoldUpdate(FOLD_UPDATE_START_OPENING)
             foldListener.onFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN)
@@ -220,6 +251,11 @@
             assertThat(folded).isEqualTo(FoldState.HALF_OPENED)
             assertThat(displayState).isEqualTo(AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN)
 
+            udfpsOverlayInteractor.setHandleTouches(false)
+            runCurrent()
+
+            assertThat(ignoreTouches).isTrue()
+
             job.cancel()
 
             // stale updates should be ignored
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
index dcefea2..8a46c0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
@@ -30,6 +30,7 @@
 
 private const val USER_ID = 22
 private const val OPERATION_ID = 100L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -114,7 +115,8 @@
                 },
                 kind = kind,
                 userId = USER_ID,
-                challenge = OPERATION_ID
+                challenge = OPERATION_ID,
+                opPackageName = OP_PACKAGE_NAME
             )
 
             assertThat(prompt?.title).isEqualTo(title)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
index f15b738..52b4275 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
@@ -51,6 +51,7 @@
 
 private const val USER_ID = 8
 private const val CHALLENGE = 999L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -113,13 +114,20 @@
 
         assertThat(currentPrompt).isNull()
 
-        interactor.useBiometricsForAuthentication(info, USER_ID, CHALLENGE, modalities)
+        interactor.useBiometricsForAuthentication(
+            info,
+            USER_ID,
+            CHALLENGE,
+            modalities,
+            OP_PACKAGE_NAME
+        )
 
         assertThat(currentPrompt).isNotNull()
         assertThat(currentPrompt?.title).isEqualTo(TITLE)
         assertThat(currentPrompt?.description).isEqualTo(DESCRIPTION)
         assertThat(currentPrompt?.subtitle).isEqualTo(SUBTITLE)
         assertThat(currentPrompt?.negativeButtonText).isEqualTo(NEGATIVE_TEXT)
+        assertThat(currentPrompt?.opPackageName).isEqualTo(OP_PACKAGE_NAME)
 
         if (allowCredentialFallback) {
             assertThat(credentialKind).isSameInstanceAs(PromptKind.Password)
@@ -167,7 +175,7 @@
 
         assertThat(currentPrompt).isNull()
 
-        interactor.useCredentialsForAuthentication(info, kind, USER_ID, CHALLENGE)
+        interactor.useCredentialsForAuthentication(info, kind, USER_ID, CHALLENGE, OP_PACKAGE_NAME)
 
         // not using biometrics, should be null with no fallback option
         assertThat(currentPrompt).isNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
index 6a68672..c0e108e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
@@ -68,7 +68,7 @@
     @Test
     fun testShouldInterceptTouch() =
         testScope.runTest {
-            createUdpfsOverlayInteractor()
+            createUdfpsOverlayInteractor()
 
             // When fingerprint enrolled and touch is within bounds
             verify(authController).addCallback(authControllerCallback.capture())
@@ -92,7 +92,7 @@
     @Test
     fun testUdfpsOverlayParamsChange() =
         testScope.runTest {
-            createUdpfsOverlayInteractor()
+            createUdfpsOverlayInteractor()
             val udfpsOverlayParams = collectLastValue(underTest.udfpsOverlayParams)
             runCurrent()
 
@@ -105,7 +105,7 @@
             assertThat(udfpsOverlayParams()).isEqualTo(firstParams)
         }
 
-    private fun createUdpfsOverlayInteractor() {
+    private fun createUdfpsOverlayInteractor() {
         underTest =
             UdfpsOverlayInteractor(
                 context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
index 9e3c576..a46167a42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
@@ -1,11 +1,15 @@
 package com.android.systemui.biometrics.domain.model
 
+import android.graphics.Bitmap
+import android.hardware.biometrics.PromptContentItemBulletedText
+import android.hardware.biometrics.PromptVerticalListContentView
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
 import com.android.systemui.biometrics.promptInfo
 import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricUserInfo
+import com.android.systemui.res.R
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -13,6 +17,7 @@
 
 private const val USER_ID = 2
 private const val OPERATION_ID = 8L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
 
 @SmallTest
 @RunWith(JUnit4::class)
@@ -20,22 +25,38 @@
 
     @Test
     fun biometricRequestFromPromptInfo() {
+        val logoRes = R.drawable.ic_cake
         val title = "what"
         val subtitle = "a"
         val description = "request"
+        val contentView =
+            PromptVerticalListContentView.Builder()
+                .setDescription("content description")
+                .addListItem(PromptContentItemBulletedText("content item 1"))
+                .addListItem(PromptContentItemBulletedText("content item 2"), 1)
+                .build()
 
         val fpPros = fingerprintSensorPropertiesInternal().first()
         val request =
             BiometricPromptRequest.Biometric(
-                promptInfo(title = title, subtitle = subtitle, description = description),
+                promptInfo(
+                    logoRes = logoRes,
+                    title = title,
+                    subtitle = subtitle,
+                    description = description,
+                    contentView = contentView
+                ),
                 BiometricUserInfo(USER_ID),
                 BiometricOperationInfo(OPERATION_ID),
                 BiometricModalities(fingerprintProperties = fpPros),
+                OP_PACKAGE_NAME,
             )
 
+        assertThat(request.logoRes).isEqualTo(logoRes)
         assertThat(request.title).isEqualTo(title)
         assertThat(request.subtitle).isEqualTo(subtitle)
         assertThat(request.description).isEqualTo(description)
+        assertThat(request.contentView).isEqualTo(contentView)
         assertThat(request.userInfo).isEqualTo(BiometricUserInfo(USER_ID))
         assertThat(request.operationInfo).isEqualTo(BiometricOperationInfo(OPERATION_ID))
         assertThat(request.modalities)
@@ -43,6 +64,23 @@
     }
 
     @Test
+    fun biometricRequestLogoBitmapFromPromptInfo() {
+        val logoBitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888)
+        val fpPros = fingerprintSensorPropertiesInternal().first()
+        val request =
+            BiometricPromptRequest.Biometric(
+                promptInfo(
+                    logoBitmap = logoBitmap,
+                ),
+                BiometricUserInfo(USER_ID),
+                BiometricOperationInfo(OPERATION_ID),
+                BiometricModalities(fingerprintProperties = fpPros),
+                OP_PACKAGE_NAME,
+            )
+        assertThat(request.logoBitmap).isEqualTo(logoBitmap)
+    }
+
+    @Test
     fun credentialRequestFromPromptInfo() {
         val title = "what"
         val subtitle = "a"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index 54dbd04..3603c3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -63,14 +63,20 @@
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
 import com.android.systemui.log.SideFpsLogger
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.res.R
 import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+import com.android.systemui.statusbar.phone.dozeServiceHost
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -107,6 +113,8 @@
 @RunWith(JUnit4::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class SideFpsOverlayViewBinderTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
     @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
     @Mock private lateinit var activityTaskManager: ActivityTaskManager
     @Mock private lateinit var displayManager: DisplayManager
@@ -237,7 +245,8 @@
                 windowManager,
                 displayStateInteractor,
                 Optional.of(fingerprintInteractiveToAuthProvider),
-                mock(),
+                kosmos.biometricSettingsRepository,
+                kosmos.keyguardTransitionInteractor,
                 SideFpsLogger(logcatLogBuffer("SfpsLogger"))
             )
 
@@ -246,10 +255,12 @@
                 mContext,
                 mock(),
                 sfpsSensorInteractor,
-                mock(),
+                kosmos.dozeServiceHost,
+                kosmos.keyguardInteractor,
                 displayStateInteractor,
                 UnconfinedTestDispatcher(),
                 testScope.backgroundScope,
+                kosmos.powerInteractor,
             )
 
         viewModel =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 6170e0c..6a9c881 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -16,9 +16,16 @@
 
 package com.android.systemui.biometrics.ui.viewmodel
 
+import android.content.pm.PackageManager
 import android.content.res.Configuration
+import android.graphics.Bitmap
 import android.graphics.Point
+import android.graphics.drawable.BitmapDrawable
+import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
+import android.hardware.biometrics.PromptContentItemBulletedText
+import android.hardware.biometrics.PromptContentView
 import android.hardware.biometrics.PromptInfo
+import android.hardware.biometrics.PromptVerticalListContentView
 import android.hardware.face.FaceSensorPropertiesInternal
 import android.hardware.fingerprint.FingerprintSensorProperties
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
@@ -60,7 +67,6 @@
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -69,12 +75,12 @@
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 import org.mockito.Mock
-import org.mockito.Mockito.times
 import org.mockito.junit.MockitoJUnit
 
 private const val USER_ID = 4
 private const val CHALLENGE = 2L
 private const val DELAY = 1000L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -87,9 +93,14 @@
     @Mock private lateinit var authController: AuthController
     @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
     @Mock private lateinit var udfpsUtils: UdfpsUtils
+    @Mock private lateinit var packageManager: PackageManager
 
     private val fakeExecutor = FakeExecutor(FakeSystemClock())
     private val testScope = TestScope()
+    private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
+    private val logoResFromApp = R.drawable.ic_cake
+    private val logoFromApp = context.getDrawable(logoResFromApp)
+    private val logoBitmapFromApp = Bitmap.createBitmap(400, 400, Bitmap.Config.RGB_565)
 
     private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
     private lateinit var promptRepository: FakePromptRepository
@@ -101,6 +112,7 @@
     private lateinit var selector: PromptSelectorInteractor
     private lateinit var viewModel: PromptViewModel
     private lateinit var iconViewModel: PromptIconViewModel
+    private lateinit var promptContentView: PromptContentView
 
     @Before
     fun setup() {
@@ -136,6 +148,11 @@
         selector =
             PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
         selector.resetPrompt()
+        promptContentView =
+            PromptVerticalListContentView.Builder()
+                .addListItem(PromptContentItemBulletedText("content item 1"))
+                .addListItem(PromptContentItemBulletedText("content item 2"), 1)
+                .build()
 
         viewModel =
             PromptViewModel(
@@ -146,6 +163,12 @@
                 udfpsUtils
             )
         iconViewModel = viewModel.iconViewModel
+
+        // Set up default logo icon and app customized icon
+        whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon)
+        context.setMockPackageManager(packageManager)
+        val resources = context.getOrCreateTestableResources()
+        resources.addOverride(logoResFromApp, logoFromApp)
     }
 
     @Test
@@ -1200,6 +1223,51 @@
         }
     }
 
+    @Test
+    fun descriptionOverriddenByContentView() =
+        runGenericTest(contentView = promptContentView, description = "test description") {
+            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+            val contentView by collectLastValue(viewModel.contentView)
+            val description by collectLastValue(viewModel.description)
+
+            assertThat(description).isEqualTo("")
+            assertThat(contentView).isEqualTo(promptContentView)
+        }
+
+    @Test
+    fun descriptionWithoutContentView() =
+        runGenericTest(description = "test description") {
+            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+            val contentView by collectLastValue(viewModel.contentView)
+            val description by collectLastValue(viewModel.description)
+
+            assertThat(description).isEqualTo("test description")
+            assertThat(contentView).isNull()
+        }
+
+    @Test
+    fun defaultLogoIfNoLogoSet() = runGenericTest {
+        mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+        val logo by collectLastValue(viewModel.logo)
+        assertThat(logo).isEqualTo(defaultLogoIcon)
+    }
+
+    @Test
+    fun logoResSetByApp() =
+        runGenericTest(logoRes = logoResFromApp) {
+            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+            val logo by collectLastValue(viewModel.logo)
+            assertThat(logo).isEqualTo(logoFromApp)
+        }
+
+    @Test
+    fun logoBitmapSetByApp() =
+        runGenericTest(logoBitmap = logoBitmapFromApp) {
+            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+            val logo by collectLastValue(viewModel.logo)
+            assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
+        }
+
     /** Asserts that the selected buttons are visible now. */
     private suspend fun TestScope.assertButtonsVisible(
         tryAgain: Boolean = false,
@@ -1219,6 +1287,10 @@
     private fun runGenericTest(
         doNotStart: Boolean = false,
         allowCredentialFallback: Boolean = false,
+        description: String? = null,
+        contentView: PromptContentView? = null,
+        logoRes: Int = -1,
+        logoBitmap: Bitmap? = null,
         block: suspend TestScope.() -> Unit
     ) {
         selector.initializePrompt(
@@ -1226,6 +1298,10 @@
             allowCredentialFallback = allowCredentialFallback,
             fingerprint = testCase.fingerprint,
             face = testCase.face,
+            descriptionFromApp = description,
+            contentViewFromApp = contentView,
+            logoResFromApp = logoRes,
+            logoBitmapFromApp = logoBitmap,
         )
 
         // put the view model in the initial authenticating state, unless explicitly skipped
@@ -1401,20 +1477,30 @@
     face: FaceSensorPropertiesInternal? = null,
     requireConfirmation: Boolean = false,
     allowCredentialFallback: Boolean = false,
+    descriptionFromApp: String? = null,
+    contentViewFromApp: PromptContentView? = null,
+    logoResFromApp: Int = -1,
+    logoBitmapFromApp: Bitmap? = null,
 ) {
     val info =
         PromptInfo().apply {
+            logoRes = logoResFromApp
+            logoBitmap = logoBitmapFromApp
             title = "t"
             subtitle = "s"
+            description = descriptionFromApp
+            contentView = contentViewFromApp
             authenticators = listOf(face, fingerprint).extractAuthenticatorTypes()
             isDeviceCredentialAllowed = allowCredentialFallback
             isConfirmationRequested = requireConfirmation
         }
+
     useBiometricsForAuthentication(
         info,
         USER_ID,
         CHALLENGE,
         BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
+        OP_PACKAGE_NAME,
     )
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 1fa60fc..3c43031 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -56,19 +56,27 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
 import com.android.systemui.display.data.repository.FakeDisplayRepository
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
+import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.log.SideFpsLogger
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.res.R
 import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+import com.android.systemui.statusbar.phone.dozeServiceHost
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -98,6 +106,7 @@
 @SmallTest
 @RunWith(JUnit4::class)
 class SideFpsOverlayViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
     @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
 
     @Mock private lateinit var activityTaskManager: ActivityTaskManager
@@ -239,19 +248,22 @@
                 windowManager,
                 displayStateInteractor,
                 Optional.of(fingerprintInteractiveToAuthProvider),
-                mock(),
+                kosmos.biometricSettingsRepository,
+                kosmos.keyguardTransitionInteractor,
                 SideFpsLogger(logcatLogBuffer("SfpsLogger"))
             )
 
         sideFpsProgressBarViewModel =
             SideFpsProgressBarViewModel(
                 mContext,
-                mock(),
+                kosmos.deviceEntryFingerprintAuthInteractor,
                 sfpsSensorInteractor,
-                mock(),
+                kosmos.dozeServiceHost,
+                kosmos.keyguardInteractor,
                 displayStateInteractor,
-                StandardTestDispatcher(),
+                kosmos.testDispatcher,
                 testScope.backgroundScope,
+                kosmos.powerInteractor,
             )
 
         underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
index 44c57f3..134c40d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -3,8 +3,9 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.SystemClock
@@ -23,8 +24,8 @@
     @Mock private lateinit var systemClock: SystemClock
     @Mock private lateinit var bouncerLogger: TableLogBuffer
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
     lateinit var underTest: KeyguardBouncerRepository
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
deleted file mode 100644
index ee46f76..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ /dev/null
@@ -1,512 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.bouncer.domain.interactor
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import android.testing.TestableResources
-import android.view.View
-import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.DejankUtils
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
-import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
-import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
-import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
-import com.android.systemui.bouncer.ui.BouncerView
-import com.android.systemui.bouncer.ui.BouncerViewDelegate
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
-import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.res.R
-import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.utils.os.FakeHandler
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Answers
-import org.mockito.ArgumentCaptor
-import org.mockito.Mock
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidTestingRunner::class)
-class PrimaryBouncerInteractorTest : SysuiTestCase() {
-    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
-    private lateinit var repository: KeyguardBouncerRepository
-    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView
-    @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
-    @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
-    @Mock private lateinit var mPrimaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
-    @Mock private lateinit var falsingCollector: FalsingCollector
-    @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
-    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
-    @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
-    private lateinit var mainHandler: FakeHandler
-    private lateinit var underTest: PrimaryBouncerInteractor
-    private lateinit var resources: TestableResources
-    private lateinit var trustRepository: FakeTrustRepository
-    private lateinit var testScope: TestScope
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        whenever(keyguardSecurityModel.getSecurityMode(anyInt()))
-            .thenReturn(KeyguardSecurityModel.SecurityMode.PIN)
-
-        DejankUtils.setImmediate(true)
-        testScope = TestScope()
-        mainHandler = FakeHandler(android.os.Looper.getMainLooper())
-        trustRepository = FakeTrustRepository()
-        underTest =
-            PrimaryBouncerInteractor(
-                repository,
-                bouncerView,
-                mainHandler,
-                keyguardStateController,
-                keyguardSecurityModel,
-                mPrimaryBouncerCallbackInteractor,
-                falsingCollector,
-                dismissCallbackRegistry,
-                context,
-                keyguardUpdateMonitor,
-                trustRepository,
-                testScope.backgroundScope,
-                mSelectedUserInteractor,
-                faceAuthInteractor,
-            )
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        whenever(repository.primaryBouncerShow.value).thenReturn(false)
-        whenever(bouncerView.delegate).thenReturn(bouncerViewDelegate)
-        resources = context.orCreateTestableResources
-    }
-
-    @Test
-    fun show_nullDelegate() {
-        testScope.run {
-            whenever(bouncerView.delegate).thenReturn(null)
-            mainHandler.setMode(FakeHandler.Mode.QUEUEING)
-
-            // WHEN bouncer show is requested
-            underTest.show(true)
-
-            // WHEN all queued messages are dispatched
-            mainHandler.dispatchQueuedMessages()
-
-            // THEN primary bouncer state doesn't update to show since delegate was null
-            verify(repository, never()).setPrimaryShow(true)
-            verify(repository, never()).setPrimaryShowingSoon(false)
-            verify(mPrimaryBouncerCallbackInteractor, never()).dispatchStartingToShow()
-            verify(mPrimaryBouncerCallbackInteractor, never())
-                .dispatchVisibilityChanged(View.VISIBLE)
-        }
-    }
-
-    @Test
-    fun testShow_isScrimmed() {
-        underTest.show(true)
-        verify(repository).setKeyguardAuthenticatedBiometrics(null)
-        verify(repository).setPrimaryStartingToHide(false)
-        verify(repository).setPrimaryScrimmed(true)
-        verify(repository).setPanelExpansion(EXPANSION_VISIBLE)
-        verify(repository).setPrimaryShowingSoon(true)
-        verify(keyguardStateController).notifyPrimaryBouncerShowing(true)
-        verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToShow()
-        verify(repository).setPrimaryShow(true)
-        verify(repository).setPrimaryShowingSoon(false)
-        verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.VISIBLE)
-    }
-
-    @Test
-    fun testShow_isNotScrimmed() {
-        verify(repository, never()).setPanelExpansion(EXPANSION_VISIBLE)
-    }
-
-    @Test
-    fun testShow_keyguardIsDone() {
-        whenever(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true)
-        verify(keyguardStateController, never()).notifyPrimaryBouncerShowing(true)
-        verify(mPrimaryBouncerCallbackInteractor, never()).dispatchStartingToShow()
-    }
-
-    @Test
-    fun testShow_isResumed() {
-        whenever(repository.primaryBouncerShow.value).thenReturn(true)
-        whenever(keyguardSecurityModel.getSecurityMode(anyInt()))
-            .thenReturn(KeyguardSecurityModel.SecurityMode.SimPuk)
-
-        underTest.show(true)
-        verify(repository).setPrimaryShow(false)
-        verify(repository).setPrimaryShow(true)
-    }
-
-    @Test
-    fun testHide() {
-        underTest.hide()
-        verify(falsingCollector).onBouncerHidden()
-        verify(keyguardStateController).notifyPrimaryBouncerShowing(false)
-        verify(repository).setPrimaryShowingSoon(false)
-        verify(repository).setPrimaryShow(false)
-        verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.INVISIBLE)
-        verify(repository).setPrimaryStartDisappearAnimation(null)
-        verify(repository).setPanelExpansion(EXPANSION_HIDDEN)
-    }
-
-    @Test
-    fun testExpansion() {
-        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        underTest.setPanelExpansion(0.6f)
-        verify(repository).setPanelExpansion(0.6f)
-        verify(mPrimaryBouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
-    }
-
-    @Test
-    fun testExpansion_fullyShown() {
-        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        underTest.setPanelExpansion(EXPANSION_VISIBLE)
-        verify(falsingCollector).onBouncerShown()
-        verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown()
-    }
-
-    @Test
-    fun testExpansion_fullyHidden() {
-        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        underTest.setPanelExpansion(EXPANSION_HIDDEN)
-        verify(repository).setPrimaryShow(false)
-        verify(falsingCollector).onBouncerHidden()
-        verify(mPrimaryBouncerCallbackInteractor).dispatchReset()
-        verify(mPrimaryBouncerCallbackInteractor).dispatchFullyHidden()
-    }
-
-    @Test
-    fun testExpansion_startingToHide() {
-        whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
-        underTest.setPanelExpansion(0.1f)
-        verify(repository).setPrimaryStartingToHide(true)
-        verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToHide()
-    }
-
-    @Test
-    fun testShowMessage() {
-        val argCaptor = ArgumentCaptor.forClass(BouncerShowMessageModel::class.java)
-        underTest.showMessage("abc", null)
-        verify(repository).setShowMessage(argCaptor.capture())
-        assertThat(argCaptor.value.message).isEqualTo("abc")
-    }
-
-    @Test
-    fun testDismissAction() {
-        val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
-        val cancelAction = mock(Runnable::class.java)
-        underTest.setDismissAction(onDismissAction, cancelAction)
-        verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
-    }
-
-    @Test
-    fun testUpdateResources() {
-        underTest.updateResources()
-        verify(repository).setResourceUpdateRequests(true)
-    }
-
-    @Test
-    fun testNotifyKeyguardAuthenticated() {
-        underTest.notifyKeyguardAuthenticatedBiometrics(true)
-        verify(repository).setKeyguardAuthenticatedBiometrics(true)
-    }
-
-    @Test
-    fun testNotifyShowedMessage() {
-        underTest.onMessageShown()
-        verify(repository).setShowMessage(null)
-    }
-
-    @Test
-    fun testSetKeyguardPosition() {
-        underTest.setKeyguardPosition(0f)
-        verify(repository).setKeyguardPosition(0f)
-    }
-
-    @Test
-    fun testNotifyKeyguardAuthenticatedHandled() {
-        underTest.notifyKeyguardAuthenticatedHandled()
-        verify(repository).setKeyguardAuthenticatedBiometrics(null)
-    }
-
-    @Test
-    fun testNotifyUpdatedResources() {
-        underTest.notifyUpdatedResources()
-        verify(repository).setResourceUpdateRequests(false)
-    }
-
-    @Test
-    fun testSetBackButtonEnabled() {
-        underTest.setBackButtonEnabled(true)
-        verify(repository).setIsBackButtonEnabled(true)
-    }
-
-    @Test
-    fun testStartDisappearAnimation_willRunDismissFromKeyguard() {
-        whenever(bouncerViewDelegate.willRunDismissFromKeyguard()).thenReturn(true)
-
-        val runnable = mock(Runnable::class.java)
-        underTest.startDisappearAnimation(runnable)
-        // End runnable should run immediately
-        verify(runnable).run()
-        // ... while the disappear animation should never be run
-        verify(repository, never()).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
-    }
-
-    @Test
-    fun testStartDisappearAnimation_willNotRunDismissFromKeyguard_() {
-        whenever(bouncerViewDelegate.willRunDismissFromKeyguard()).thenReturn(false)
-
-        val runnable = mock(Runnable::class.java)
-        underTest.startDisappearAnimation(runnable)
-        verify(repository).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
-    }
-
-    @Test
-    fun testIsFullShowing() {
-        whenever(repository.primaryBouncerShow.value).thenReturn(true)
-        whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        assertThat(underTest.isFullyShowing()).isTrue()
-        whenever(repository.primaryBouncerShow.value).thenReturn(false)
-        assertThat(underTest.isFullyShowing()).isFalse()
-    }
-
-    @Test
-    fun testIsScrimmed() {
-        whenever(repository.primaryBouncerScrimmed.value).thenReturn(true)
-        assertThat(underTest.isScrimmed()).isTrue()
-        whenever(repository.primaryBouncerScrimmed.value).thenReturn(false)
-        assertThat(underTest.isScrimmed()).isFalse()
-    }
-
-    @Test
-    fun testIsInTransit() {
-        whenever(repository.primaryBouncerShowingSoon.value).thenReturn(true)
-        assertThat(underTest.isInTransit()).isTrue()
-        whenever(repository.primaryBouncerShowingSoon.value).thenReturn(false)
-        assertThat(underTest.isInTransit()).isFalse()
-        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        assertThat(underTest.isInTransit()).isTrue()
-    }
-
-    @Test
-    fun testIsAnimatingAway() {
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
-        assertThat(underTest.isAnimatingAway()).isTrue()
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        assertThat(underTest.isAnimatingAway()).isFalse()
-    }
-
-    @Test
-    fun testWillDismissWithAction() {
-        whenever(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
-        assertThat(underTest.willDismissWithAction()).isTrue()
-        whenever(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
-        assertThat(underTest.willDismissWithAction()).isFalse()
-    }
-
-    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
-    @Test
-    fun testSideFpsVisibility() {
-        updateSideFpsVisibilityParameters(
-            isVisible = true,
-            sfpsEnabled = true,
-            fpsDetectionRunning = true,
-            isUnlockingWithFpAllowed = true,
-            isAnimatingAway = false
-        )
-        underTest.updateSideFpsVisibility()
-        verify(repository).setSideFpsShowing(true)
-    }
-
-    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
-    @Test
-    fun testSideFpsVisibility_notVisible() {
-        updateSideFpsVisibilityParameters(
-            isVisible = false,
-            sfpsEnabled = true,
-            fpsDetectionRunning = true,
-            isUnlockingWithFpAllowed = true,
-            isAnimatingAway = false
-        )
-        underTest.updateSideFpsVisibility()
-        verify(repository).setSideFpsShowing(false)
-    }
-
-    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
-    @Test
-    fun testSideFpsVisibility_sfpsNotEnabled() {
-        updateSideFpsVisibilityParameters(
-            isVisible = true,
-            sfpsEnabled = false,
-            fpsDetectionRunning = true,
-            isUnlockingWithFpAllowed = true,
-            isAnimatingAway = false
-        )
-        underTest.updateSideFpsVisibility()
-        verify(repository).setSideFpsShowing(false)
-    }
-
-    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
-    @Test
-    fun testSideFpsVisibility_fpsDetectionNotRunning() {
-        updateSideFpsVisibilityParameters(
-            isVisible = true,
-            sfpsEnabled = true,
-            fpsDetectionRunning = false,
-            isUnlockingWithFpAllowed = true,
-            isAnimatingAway = false
-        )
-        underTest.updateSideFpsVisibility()
-        verify(repository).setSideFpsShowing(false)
-    }
-
-    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
-    @Test
-    fun testSideFpsVisibility_UnlockingWithFpNotAllowed() {
-        updateSideFpsVisibilityParameters(
-            isVisible = true,
-            sfpsEnabled = true,
-            fpsDetectionRunning = true,
-            isUnlockingWithFpAllowed = false,
-            isAnimatingAway = false
-        )
-        underTest.updateSideFpsVisibility()
-        verify(repository).setSideFpsShowing(false)
-    }
-
-    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
-    @Test
-    fun testSideFpsVisibility_AnimatingAway() {
-        updateSideFpsVisibilityParameters(
-            isVisible = true,
-            sfpsEnabled = true,
-            fpsDetectionRunning = true,
-            isUnlockingWithFpAllowed = true,
-            isAnimatingAway = true
-        )
-        underTest.updateSideFpsVisibility()
-        verify(repository).setSideFpsShowing(false)
-    }
-
-    @Test
-    fun delayBouncerWhenFaceAuthPossible() {
-        mainHandler.setMode(FakeHandler.Mode.QUEUEING)
-
-        // GIVEN bouncer should be delayed due to face auth
-        whenever(faceAuthInteractor.canFaceAuthRun()).thenReturn(true)
-
-        // WHEN bouncer show is requested
-        underTest.show(true)
-
-        // THEN primary show & primary showing soon aren't updated immediately
-        verify(repository, never()).setPrimaryShow(true)
-        verify(repository, never()).setPrimaryShowingSoon(false)
-
-        // WHEN all queued messages are dispatched
-        mainHandler.dispatchQueuedMessages()
-
-        // THEN primary show & primary showing soon are updated
-        verify(repository).setPrimaryShow(true)
-        verify(repository).setPrimaryShowingSoon(false)
-    }
-
-    @Test
-    fun noDelayBouncer_faceAuthNotAllowed() {
-        mainHandler.setMode(FakeHandler.Mode.QUEUEING)
-
-        // GIVEN bouncer should not be delayed because device isn't in the right posture for
-        // face auth
-        whenever(faceAuthInteractor.canFaceAuthRun()).thenReturn(false)
-
-        // WHEN bouncer show is requested
-        underTest.show(true)
-
-        // THEN primary show & primary showing soon are updated immediately
-        verify(repository).setPrimaryShow(true)
-        verify(repository).setPrimaryShowingSoon(false)
-    }
-
-    @Test
-    fun delayBouncerWhenActiveUnlockPossible() {
-        testScope.run {
-            mainHandler.setMode(FakeHandler.Mode.QUEUEING)
-
-            // GIVEN bouncer should be delayed due to active unlock
-            trustRepository.setCurrentUserActiveUnlockAvailable(true)
-            whenever(keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState())
-                .thenReturn(true)
-            runCurrent()
-
-            // WHEN bouncer show is requested
-            underTest.show(true)
-
-            // THEN primary show & primary showing soon were scheduled to update
-            verify(repository, never()).setPrimaryShow(true)
-            verify(repository, never()).setPrimaryShowingSoon(false)
-
-            // WHEN all queued messages are dispatched
-            mainHandler.dispatchQueuedMessages()
-
-            // THEN primary show & primary showing soon are updated
-            verify(repository).setPrimaryShow(true)
-            verify(repository).setPrimaryShowingSoon(false)
-        }
-    }
-
-    private fun updateSideFpsVisibilityParameters(
-        isVisible: Boolean,
-        sfpsEnabled: Boolean,
-        fpsDetectionRunning: Boolean,
-        isUnlockingWithFpAllowed: Boolean,
-        isAnimatingAway: Boolean
-    ) {
-        mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
-        whenever(repository.primaryBouncerShow.value).thenReturn(isVisible)
-        resources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer, sfpsEnabled)
-        whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning)
-            .thenReturn(fpsDetectionRunning)
-        whenever(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
-            .thenReturn(isUnlockingWithFpAllowed)
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value)
-            .thenReturn(if (isAnimatingAway) Runnable {} else null)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
deleted file mode 100644
index 30a5497..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.communal.data.repository
-
-import android.content.pm.UserInfo
-import android.provider.Settings
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.FakeLogBuffer
-import com.android.systemui.settings.FakeUserTracker
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.settings.FakeSettings
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalTutorialRepositoryImplTest : SysuiTestCase() {
-    private lateinit var secureSettings: FakeSettings
-    private lateinit var userRepository: FakeUserRepository
-    private lateinit var userTracker: FakeUserTracker
-    private lateinit var logBuffer: LogBuffer
-
-    private val testDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        logBuffer = FakeLogBuffer.Factory.create()
-        secureSettings = FakeSettings()
-        userRepository = FakeUserRepository()
-        val listOfUserInfo = listOf(MAIN_USER_INFO)
-        userRepository.setUserInfos(listOfUserInfo)
-
-        userTracker = FakeUserTracker()
-        userTracker.set(
-            userInfos = listOfUserInfo,
-            selectedUserIndex = 0,
-        )
-    }
-
-    @Test
-    fun tutorialSettingState_defaultToNotStarted() =
-        testScope.runTest {
-            val repository = initCommunalTutorialRepository()
-            val tutorialSettingState = collectLastValue(repository.tutorialSettingState)()
-            assertThat(tutorialSettingState)
-                .isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED)
-        }
-
-    @Test
-    fun tutorialSettingState_whenTutorialSettingsUpdatedToStarted() =
-        testScope.runTest {
-            val repository = initCommunalTutorialRepository()
-            setTutorialStateSetting(Settings.Secure.HUB_MODE_TUTORIAL_STARTED)
-            val tutorialSettingState = collectLastValue(repository.tutorialSettingState)()
-            assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_STARTED)
-        }
-
-    @Test
-    fun tutorialSettingState_whenTutorialSettingsUpdatedToCompleted() =
-        testScope.runTest {
-            val repository = initCommunalTutorialRepository()
-            setTutorialStateSetting(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
-            val tutorialSettingState = collectLastValue(repository.tutorialSettingState)()
-            assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
-        }
-
-    private fun initCommunalTutorialRepository(): CommunalTutorialRepositoryImpl {
-        return CommunalTutorialRepositoryImpl(
-            testScope.backgroundScope,
-            testDispatcher,
-            userRepository,
-            secureSettings,
-            userTracker,
-            logBuffer
-        )
-    }
-
-    private fun setTutorialStateSetting(
-        @Settings.Secure.HubModeTutorialState state: Int,
-        user: UserInfo = MAIN_USER_INFO
-    ) {
-        secureSettings.putIntForUser(Settings.Secure.HUB_MODE_TUTORIAL_STATE, state, user.id)
-    }
-
-    companion object {
-        private val MAIN_USER_INFO =
-            UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
index a7677cc..002862e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
@@ -16,27 +16,55 @@
 
 package com.android.systemui.controls.panels
 
+import android.os.UserHandle
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
 class FakeSelectedComponentRepository : SelectedComponentRepository {
-
-    private var selectedComponent: SelectedComponentRepository.SelectedComponent? = null
     private var shouldAddDefaultPanel: Boolean = true
+    private val _selectedComponentFlows =
+        mutableMapOf<UserHandle, MutableStateFlow<SelectedComponentRepository.SelectedComponent?>>()
+    private var currentUserHandle: UserHandle = UserHandle.of(0)
 
-    override fun getSelectedComponent(): SelectedComponentRepository.SelectedComponent? =
-        selectedComponent
+    override fun selectedComponentFlow(
+        userHandle: UserHandle
+    ): Flow<SelectedComponentRepository.SelectedComponent?> {
+        // Return an existing flow for the user or create a new one
+        return _selectedComponentFlows.getOrPut(getUserHandle(userHandle)) {
+            MutableStateFlow(null)
+        }
+    }
+
+    override fun getSelectedComponent(
+        userHandle: UserHandle
+    ): SelectedComponentRepository.SelectedComponent? {
+        return _selectedComponentFlows[getUserHandle(userHandle)]?.value
+    }
 
     override fun setSelectedComponent(
         selectedComponent: SelectedComponentRepository.SelectedComponent
     ) {
-        this.selectedComponent = selectedComponent
+        val flow = _selectedComponentFlows.getOrPut(currentUserHandle) { MutableStateFlow(null) }
+        flow.value = selectedComponent
     }
 
     override fun removeSelectedComponent() {
-        selectedComponent = null
+        _selectedComponentFlows[currentUserHandle]?.value = null
     }
-
     override fun shouldAddDefaultComponent(): Boolean = shouldAddDefaultPanel
 
     override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
         shouldAddDefaultPanel = shouldAdd
     }
+
+    fun setCurrentUserHandle(userHandle: UserHandle) {
+        currentUserHandle = userHandle
+    }
+    private fun getUserHandle(userHandle: UserHandle): UserHandle {
+        return if (userHandle == UserHandle.CURRENT) {
+            currentUserHandle
+        } else {
+            userHandle
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
index 6230ea7..b463adf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
@@ -18,28 +18,43 @@
 
 import android.content.ComponentName
 import android.content.SharedPreferences
+import android.os.UserHandle
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.testKosmos
 import com.android.systemui.util.FakeSharedPreferences
-import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import java.io.File
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 
+@ExperimentalCoroutinesApi
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class SelectedComponentRepositoryTest : SysuiTestCase() {
 
     private companion object {
+        const val PREF_COMPONENT = "controls_component"
+        const val PREF_STRUCTURE_OR_APP_NAME = "controls_structure"
+        const val PREF_IS_PANEL = "controls_is_panel"
+        val PRIMARY_USER: UserHandle = UserHandle.of(0)
+        val SECONDARY_USER: UserHandle = UserHandle.of(12)
         val COMPONENT_A =
             SelectedComponentRepository.SelectedComponent(
                 name = "a",
@@ -53,24 +68,40 @@
                 isPanel = false,
             )
     }
+    private lateinit var primaryUserSharedPref: FakeSharedPreferences
+    private lateinit var secondaryUserSharedPref: FakeSharedPreferences
 
     @Mock private lateinit var userTracker: UserTracker
-    @Mock private lateinit var userFileManager: UserFileManager
+    private lateinit var userFileManager: UserFileManager
 
     private val featureFlags = FakeFeatureFlags()
-    private val sharedPreferences: SharedPreferences = FakeSharedPreferences()
-
     // under test
     private lateinit var repository: SelectedComponentRepository
 
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        whenever(userFileManager.getSharedPreferences(any(), any(), any()))
-            .thenReturn(sharedPreferences)
+    private val kosmos = testKosmos()
 
-        repository = SelectedComponentRepositoryImpl(userFileManager, userTracker, featureFlags)
-    }
+    @Before
+    fun setUp() =
+        with(kosmos) {
+            primaryUserSharedPref = FakeSharedPreferences()
+            secondaryUserSharedPref = FakeSharedPreferences()
+            MockitoAnnotations.initMocks(this@SelectedComponentRepositoryTest)
+            userFileManager =
+                FakeUserFileManager(
+                    mapOf(
+                        PRIMARY_USER.identifier to primaryUserSharedPref,
+                        SECONDARY_USER.identifier to secondaryUserSharedPref
+                    )
+                )
+            repository =
+                SelectedComponentRepositoryImpl(
+                    userFileManager,
+                    userTracker,
+                    featureFlags,
+                    bgDispatcher = testDispatcher,
+                    applicationScope = applicationCoroutineScope
+                )
+        }
 
     @Test
     fun testUnsetIsNull() {
@@ -115,18 +146,10 @@
 
     @Test
     fun testGetPreferredStructure_differentUserId() {
-        sharedPreferences.savePanel(COMPONENT_A)
-        whenever(
-                userFileManager.getSharedPreferences(
-                    DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
-                    0,
-                    1,
-                )
-            )
-            .thenReturn(FakeSharedPreferences().also { it.savePanel(COMPONENT_B) })
-
+        primaryUserSharedPref.savePanel(COMPONENT_A)
+        secondaryUserSharedPref.savePanel(COMPONENT_B)
         val previousPreferredStructure = repository.getSelectedComponent()
-        whenever(userTracker.userId).thenReturn(1)
+        whenever(userTracker.userId).thenReturn(SECONDARY_USER.identifier)
         val currentPreferredStructure = repository.getSelectedComponent()
 
         assertThat(previousPreferredStructure).isEqualTo(COMPONENT_A)
@@ -134,11 +157,90 @@
         assertThat(currentPreferredStructure).isEqualTo(COMPONENT_B)
     }
 
+    @Test
+    fun testEmitValueFromGetSelectedComponent() =
+        with(kosmos) {
+            testScope.runTest {
+                primaryUserSharedPref.savePanel(COMPONENT_A)
+                val emittedValue by collectLastValue(repository.selectedComponentFlow(PRIMARY_USER))
+                assertThat(emittedValue).isEqualTo(COMPONENT_A)
+            }
+        }
+
+    @Test
+    fun testEmitNullWhenRemoveSelectedComponentIsCalled() =
+        with(kosmos) {
+            testScope.runTest {
+                primaryUserSharedPref.savePanel(COMPONENT_A)
+                primaryUserSharedPref.removePanel()
+                val emittedValue by collectLastValue(repository.selectedComponentFlow(PRIMARY_USER))
+                assertThat(emittedValue).isEqualTo(null)
+            }
+        }
+
+    @Test
+    fun testChangeEmitValueChangeWhenANewComponentIsSelected() =
+        with(kosmos) {
+            testScope.runTest {
+                primaryUserSharedPref.savePanel(COMPONENT_A)
+                val emittedValue by collectLastValue(repository.selectedComponentFlow(PRIMARY_USER))
+                advanceUntilIdle()
+                assertThat(emittedValue).isEqualTo(COMPONENT_A)
+                primaryUserSharedPref.savePanel(COMPONENT_B)
+                advanceUntilIdle()
+                assertThat(emittedValue).isEqualTo(COMPONENT_B)
+            }
+        }
+
+    @Test
+    fun testDifferentUsersWithDifferentComponentSelected() =
+        with(kosmos) {
+            testScope.runTest {
+                primaryUserSharedPref.savePanel(COMPONENT_A)
+                secondaryUserSharedPref.savePanel(COMPONENT_B)
+                val primaryUserValue by
+                    collectLastValue(repository.selectedComponentFlow(PRIMARY_USER))
+                val secondaryUserValue by
+                    collectLastValue(repository.selectedComponentFlow(SECONDARY_USER))
+                assertThat(primaryUserValue).isEqualTo(COMPONENT_A)
+                assertThat(secondaryUserValue).isEqualTo(COMPONENT_B)
+            }
+        }
+
     private fun SharedPreferences.savePanel(panel: SelectedComponentRepository.SelectedComponent) {
         edit()
-            .putString("controls_component", panel.componentName?.flattenToString())
-            .putString("controls_structure", panel.name)
-            .putBoolean("controls_is_panel", panel.isPanel)
+            .putString(PREF_COMPONENT, panel.componentName?.flattenToString())
+            .putString(PREF_STRUCTURE_OR_APP_NAME, panel.name)
+            .putBoolean(PREF_IS_PANEL, panel.isPanel)
             .commit()
     }
+
+    private fun SharedPreferences.removePanel() {
+        edit()
+            .remove(PREF_COMPONENT)
+            .remove(PREF_STRUCTURE_OR_APP_NAME)
+            .remove(PREF_IS_PANEL)
+            .commit()
+    }
+
+    private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :
+        UserFileManager {
+        override fun getFile(fileName: String, userId: Int): File {
+            throw UnsupportedOperationException()
+        }
+
+        override fun getSharedPreferences(
+            fileName: String,
+            mode: Int,
+            userId: Int
+        ): SharedPreferences {
+            if (fileName != DeviceControlsControllerImpl.PREFS_CONTROLS_FILE) {
+                throw IllegalArgumentException(
+                    "Preference files must be " +
+                        "$DeviceControlsControllerImpl.PREFS_CONTROLS_FILE"
+                )
+            }
+            return sharedPrefs.getValue(userId)
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
deleted file mode 100644
index d5c3641..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.deviceentry.data.repository
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
-import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor
-import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
-import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository
-import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
-import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.power.data.repository.powerRepository
-import com.android.systemui.power.shared.model.WakeSleepReason
-import com.android.systemui.power.shared.model.WakefulnessState
-import com.android.systemui.testKosmos
-import com.android.systemui.util.time.fakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-    private val underTest = kosmos.deviceEntryHapticsInteractor
-
-    @Test
-    fun nonPowerButtonFPS_vibrateSuccess() =
-        testScope.runTest {
-            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
-            setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
-            runCurrent()
-            enterDeviceFromBiometricUnlock()
-            assertThat(playSuccessHaptic).isNotNull()
-        }
-
-    @Test
-    fun powerButtonFPS_vibrateSuccess() =
-        testScope.runTest {
-            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
-            setPowerButtonFingerprintProperty()
-            setFingerprintEnrolled()
-            kosmos.fakeKeyEventRepository.setPowerButtonDown(false)
-
-            // It's been 10 seconds since the last power button wakeup
-            setAwakeFromPowerButton()
-            runCurrent()
-            kosmos.fakeSystemClock.setUptimeMillis(kosmos.fakeSystemClock.uptimeMillis() + 10000)
-
-            enterDeviceFromBiometricUnlock()
-            assertThat(playSuccessHaptic).isNotNull()
-        }
-
-    @Test
-    fun powerButtonFPS_powerDown_doNotVibrateSuccess() =
-        testScope.runTest {
-            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
-            setPowerButtonFingerprintProperty()
-            setFingerprintEnrolled()
-            kosmos.fakeKeyEventRepository.setPowerButtonDown(true) // power button is currently DOWN
-
-            // It's been 10 seconds since the last power button wakeup
-            setAwakeFromPowerButton()
-            runCurrent()
-            kosmos.fakeSystemClock.setUptimeMillis(kosmos.fakeSystemClock.uptimeMillis() + 10000)
-
-            enterDeviceFromBiometricUnlock()
-            assertThat(playSuccessHaptic).isNull()
-        }
-
-    @Test
-    fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() =
-        testScope.runTest {
-            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
-            setPowerButtonFingerprintProperty()
-            setFingerprintEnrolled()
-            kosmos.fakeKeyEventRepository.setPowerButtonDown(false)
-
-            // It's only been 50ms since the last power button wakeup
-            setAwakeFromPowerButton()
-            runCurrent()
-            kosmos.fakeSystemClock.setUptimeMillis(kosmos.fakeSystemClock.uptimeMillis() + 50)
-
-            enterDeviceFromBiometricUnlock()
-            assertThat(playSuccessHaptic).isNull()
-        }
-
-    @Test
-    fun nonPowerButtonFPS_vibrateError() =
-        testScope.runTest {
-            val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
-            setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
-            runCurrent()
-            fingerprintFailure()
-            assertThat(playErrorHaptic).isNotNull()
-        }
-
-    @Test
-    fun nonPowerButtonFPS_coExFaceFailure_doNotVibrateError() =
-        testScope.runTest {
-            val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
-            setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
-            coExEnrolledAndEnabled()
-            runCurrent()
-            faceFailure()
-            assertThat(playErrorHaptic).isNull()
-        }
-
-    @Test
-    fun powerButtonFPS_vibrateError() =
-        testScope.runTest {
-            val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
-            setPowerButtonFingerprintProperty()
-            setFingerprintEnrolled()
-            runCurrent()
-            fingerprintFailure()
-            assertThat(playErrorHaptic).isNotNull()
-        }
-
-    @Test
-    fun powerButtonFPS_powerDown_doNotVibrateError() =
-        testScope.runTest {
-            val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
-            setPowerButtonFingerprintProperty()
-            setFingerprintEnrolled()
-            kosmos.fakeKeyEventRepository.setPowerButtonDown(true)
-            runCurrent()
-            fingerprintFailure()
-            assertThat(playErrorHaptic).isNull()
-        }
-
-    private suspend fun enterDeviceFromBiometricUnlock() {
-        kosmos.fakeDeviceEntryRepository.enteringDeviceFromBiometricUnlock(
-            BiometricUnlockSource.FINGERPRINT_SENSOR
-        )
-    }
-
-    private fun fingerprintFailure() {
-        kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
-            FailFingerprintAuthenticationStatus
-        )
-    }
-
-    private fun faceFailure() {
-        kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
-            FailedFaceAuthenticationStatus()
-        )
-    }
-
-    private fun setFingerprintSensorType(fingerprintSensorType: FingerprintSensorType) {
-        kosmos.fingerprintPropertyRepository.setProperties(
-            sensorId = 0,
-            strength = SensorStrength.STRONG,
-            sensorType = fingerprintSensorType,
-            sensorLocations = mapOf(),
-        )
-    }
-
-    private fun setPowerButtonFingerprintProperty() {
-        setFingerprintSensorType(FingerprintSensorType.POWER_BUTTON)
-    }
-
-    private fun setFingerprintEnrolled() {
-        kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
-    }
-
-    private fun setAwakeFromPowerButton() {
-        kosmos.powerRepository.updateWakefulness(
-            WakefulnessState.AWAKE,
-            WakeSleepReason.POWER_BUTTON,
-            WakeSleepReason.POWER_BUTTON,
-            powerButtonLaunchGestureTriggered = false,
-        )
-    }
-
-    private fun coExEnrolledAndEnabled() {
-        setFingerprintEnrolled()
-        kosmos.biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
new file mode 100644
index 0000000..bdf0e06
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.powerRepository
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val underTest = kosmos.deviceEntryHapticsInteractor
+
+    @Test
+    fun nonPowerButtonFPS_vibrateSuccess() =
+        testScope.runTest {
+            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+            setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+            runCurrent()
+            enterDeviceFromBiometricUnlock()
+            assertThat(playSuccessHaptic).isNotNull()
+        }
+
+    @Test
+    fun powerButtonFPS_vibrateSuccess() =
+        testScope.runTest {
+            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+            setPowerButtonFingerprintProperty()
+            setFingerprintEnrolled()
+            kosmos.fakeKeyEventRepository.setPowerButtonDown(false)
+
+            // It's been 10 seconds since the last power button wakeup
+            setAwakeFromPowerButton()
+            advanceTimeBy(10000)
+            runCurrent()
+
+            enterDeviceFromBiometricUnlock()
+            assertThat(playSuccessHaptic).isNotNull()
+        }
+
+    @Test
+    fun powerButtonFPS_powerDown_doNotVibrateSuccess() =
+        testScope.runTest {
+            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+            setPowerButtonFingerprintProperty()
+            setFingerprintEnrolled()
+            kosmos.fakeKeyEventRepository.setPowerButtonDown(true) // power button is currently DOWN
+
+            // It's been 10 seconds since the last power button wakeup
+            setAwakeFromPowerButton()
+            advanceTimeBy(10000)
+            runCurrent()
+
+            enterDeviceFromBiometricUnlock()
+            assertThat(playSuccessHaptic).isNull()
+        }
+
+    @Test
+    fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() =
+        testScope.runTest {
+            val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+            setPowerButtonFingerprintProperty()
+            setFingerprintEnrolled()
+            kosmos.fakeKeyEventRepository.setPowerButtonDown(false)
+
+            // It's only been 50ms since the last power button wakeup
+            setAwakeFromPowerButton()
+            advanceTimeBy(50)
+            runCurrent()
+
+            enterDeviceFromBiometricUnlock()
+            assertThat(playSuccessHaptic).isNull()
+        }
+
+    @Test
+    fun nonPowerButtonFPS_vibrateError() =
+        testScope.runTest {
+            val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+            setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+            runCurrent()
+            fingerprintFailure()
+            assertThat(playErrorHaptic).isNotNull()
+        }
+
+    @Test
+    fun nonPowerButtonFPS_coExFaceFailure_doNotVibrateError() =
+        testScope.runTest {
+            val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+            setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+            coExEnrolledAndEnabled()
+            runCurrent()
+            faceFailure()
+            assertThat(playErrorHaptic).isNull()
+        }
+
+    @Test
+    fun powerButtonFPS_vibrateError() =
+        testScope.runTest {
+            val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+            setPowerButtonFingerprintProperty()
+            setFingerprintEnrolled()
+            runCurrent()
+            fingerprintFailure()
+            assertThat(playErrorHaptic).isNotNull()
+        }
+
+    @Test
+    fun powerButtonFPS_powerDown_doNotVibrateError() =
+        testScope.runTest {
+            val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+            setPowerButtonFingerprintProperty()
+            setFingerprintEnrolled()
+            kosmos.fakeKeyEventRepository.setPowerButtonDown(true)
+            runCurrent()
+            fingerprintFailure()
+            assertThat(playErrorHaptic).isNull()
+        }
+
+    private suspend fun enterDeviceFromBiometricUnlock() {
+        kosmos.fakeKeyguardRepository.setBiometricUnlockSource(
+            BiometricUnlockSource.FINGERPRINT_SENSOR
+        )
+        kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+    }
+
+    private fun fingerprintFailure() {
+        kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+            FailFingerprintAuthenticationStatus
+        )
+    }
+
+    private fun faceFailure() {
+        kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+            FailedFaceAuthenticationStatus()
+        )
+    }
+
+    private fun setFingerprintSensorType(fingerprintSensorType: FingerprintSensorType) {
+        kosmos.fingerprintPropertyRepository.setProperties(
+            sensorId = 0,
+            strength = SensorStrength.STRONG,
+            sensorType = fingerprintSensorType,
+            sensorLocations = mapOf(),
+        )
+    }
+
+    private fun setPowerButtonFingerprintProperty() {
+        setFingerprintSensorType(FingerprintSensorType.POWER_BUTTON)
+    }
+
+    private fun setFingerprintEnrolled() {
+        kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+    }
+
+    private fun setAwakeFromPowerButton() {
+        kosmos.powerRepository.updateWakefulness(
+            WakefulnessState.AWAKE,
+            WakeSleepReason.POWER_BUTTON,
+            WakeSleepReason.POWER_BUTTON,
+            powerButtonLaunchGestureTriggered = false,
+        )
+    }
+
+    private fun coExEnrolledAndEnabled() {
+        setFingerprintEnrolled()
+        kosmos.biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt
new file mode 100644
index 0000000..ed80a86
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt
@@ -0,0 +1,98 @@
+package com.android.systemui.keyboard.stickykeys.data.repository
+
+import android.content.pm.UserInfo
+import android.hardware.input.InputManager
+import android.provider.Settings.Secure.ACCESSIBILITY_STICKY_KEYS
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class StickyKeysRepositoryImplTest : SysuiTestCase() {
+
+    private val dispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+    private val secureSettings = FakeSettings()
+    private val userRepository = Kosmos().fakeUserRepository
+    private lateinit var stickyKeysRepository: StickyKeysRepositoryImpl
+
+    @Before
+    fun setup() {
+        stickyKeysRepository = StickyKeysRepositoryImpl(
+            mock<InputManager>(),
+            dispatcher,
+            secureSettings,
+            userRepository,
+            mock<StickyKeysLogger>()
+        )
+        userRepository.setUserInfos(USER_INFOS)
+        setStickyKeySettingForUser(enabled = true, userInfo = SETTING_ENABLED_USER)
+        setStickyKeySettingForUser(enabled = false, userInfo = SETTING_DISABLED_USER)
+    }
+
+    @Test
+    fun settingEnabledEmitsValueForCurrentUser() {
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+
+            val enabled by collectLastValue(stickyKeysRepository.settingEnabled)
+
+            assertThat(enabled).isTrue()
+        }
+    }
+
+    @Test
+    fun settingEnabledEmitsNewValueWhenSettingChanges() {
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+            val enabled by collectValues(stickyKeysRepository.settingEnabled)
+            runCurrent()
+
+            setStickyKeySettingForUser(enabled = false, userInfo = SETTING_ENABLED_USER)
+
+            assertThat(enabled).containsExactly(true, false).inOrder()
+        }
+    }
+
+    @Test
+    fun settingEnabledEmitsValueForNewUserWhenUserChanges() {
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+            val enabled by collectLastValue(stickyKeysRepository.settingEnabled)
+            runCurrent()
+
+            userRepository.setSelectedUserInfo(SETTING_DISABLED_USER)
+
+            assertThat(enabled).isFalse()
+        }
+    }
+
+    private fun setStickyKeySettingForUser(enabled: Boolean, userInfo: UserInfo) {
+        val newValue = if (enabled) "1" else "0"
+        secureSettings.putStringForUser(ACCESSIBILITY_STICKY_KEYS, newValue, userInfo.id)
+    }
+
+    private companion object {
+        val SETTING_ENABLED_USER = UserInfo(/* id= */ 0, "user1", /* flags= */ 0)
+        val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
+        val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt
new file mode 100644
index 0000000..df73cc8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.stickykeys.ui
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.keyboard.data.repository.FakeStickyKeysRepository
+import com.android.systemui.keyboard.data.repository.keyboardRepository
+import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
+import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.phone.ComponentSystemUIDialog
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class StickyKeysIndicatorCoordinatorTest : SysuiTestCase() {
+
+    private lateinit var coordinator: StickyKeysIndicatorCoordinator
+    private val testScope = TestScope(StandardTestDispatcher())
+    private val stickyKeysRepository = FakeStickyKeysRepository()
+    private val dialog = mock<ComponentSystemUIDialog>()
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(ComposeFacade.isComposeAvailable())
+        val dialogFactory = mock<SystemUIDialogFactory> {
+            whenever(applicationContext).thenReturn(context)
+            whenever(create(any(), anyInt(), anyBoolean())).thenReturn(dialog)
+        }
+        val keyboardRepository = Kosmos().keyboardRepository
+        val viewModel = StickyKeysIndicatorViewModel(
+                stickyKeysRepository,
+                keyboardRepository,
+                testScope.backgroundScope)
+        coordinator = StickyKeysIndicatorCoordinator(
+                testScope.backgroundScope,
+                dialogFactory,
+                viewModel,
+                mock<StickyKeysLogger>())
+        coordinator.startListening()
+        keyboardRepository.setIsAnyKeyboardConnected(true)
+    }
+
+    @Test
+    fun dialogIsShownWhenStickyKeysAreEmitted() {
+        testScope.run {
+            verifyZeroInteractions(dialog)
+
+            stickyKeysRepository.setStickyKeys(linkedMapOf(SHIFT to Locked(true)))
+            runCurrent()
+
+            verify(dialog).show()
+        }
+    }
+
+    @Test
+    fun dialogDisappearsWhenStickyKeysAreEmpty() {
+        testScope.run {
+            verifyZeroInteractions(dialog)
+
+            stickyKeysRepository.setStickyKeys(linkedMapOf(SHIFT to Locked(true)))
+            runCurrent()
+            stickyKeysRepository.setStickyKeys(linkedMapOf())
+            runCurrent()
+
+            verify(dialog).dismiss()
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
new file mode 100644
index 0000000..6eebb6d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.stickykeys.ui.viewmodel
+
+import android.hardware.input.InputManager
+import android.hardware.input.StickyModifierState
+import android.provider.Settings.Secure.ACCESSIBILITY_STICKY_KEYS
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
+import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
+import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepositoryImpl
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
+
+    private val dispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+    private lateinit var viewModel: StickyKeysIndicatorViewModel
+    private val inputManager = mock<InputManager>()
+    private val keyboardRepository = FakeKeyboardRepository()
+    private val secureSettings = FakeSettings()
+    private val userRepository = Kosmos().fakeUserRepository
+    private val captor =
+        ArgumentCaptor.forClass(InputManager.StickyModifierStateListener::class.java)
+
+    @Before
+    fun setup() {
+        val stickyKeysRepository = StickyKeysRepositoryImpl(
+            inputManager,
+            dispatcher,
+            secureSettings,
+            userRepository,
+            mock<StickyKeysLogger>()
+        )
+        setStickyKeySetting(enabled = false)
+        viewModel =
+            StickyKeysIndicatorViewModel(
+                stickyKeysRepository = stickyKeysRepository,
+                keyboardRepository = keyboardRepository,
+                applicationScope = testScope.backgroundScope,
+            )
+    }
+
+    @Test
+    fun doesntListenToStickyKeysOnlyWhenKeyboardIsConnected() {
+        testScope.runTest {
+            collectLastValue(viewModel.indicatorContent)
+
+            keyboardRepository.setIsAnyKeyboardConnected(true)
+            runCurrent()
+
+            verifyZeroInteractions(inputManager)
+        }
+    }
+
+    @Test
+    fun startsListeningToStickyKeysOnlyWhenKeyboardIsConnectedAndSettingIsOn() {
+        testScope.runTest {
+            collectLastValue(viewModel.indicatorContent)
+            keyboardRepository.setIsAnyKeyboardConnected(true)
+
+            setStickyKeySetting(enabled = true)
+            runCurrent()
+
+            verify(inputManager)
+                .registerStickyModifierStateListener(
+                    any(),
+                    any(InputManager.StickyModifierStateListener::class.java)
+                )
+        }
+    }
+
+    private fun setStickyKeySetting(enabled: Boolean) {
+        val newValue = if (enabled) "1" else "0"
+        val defaultUser = userRepository.getSelectedUserInfo().id
+        secureSettings.putStringForUser(ACCESSIBILITY_STICKY_KEYS, newValue, defaultUser)
+    }
+
+    @Test
+    fun stopsListeningToStickyKeysWhenStickyKeySettingsIsTurnedOff() {
+        testScope.runTest {
+            collectLastValue(viewModel.indicatorContent)
+            setStickyKeysActive()
+            runCurrent()
+
+            setStickyKeySetting(enabled = false)
+            runCurrent()
+
+            verify(inputManager).unregisterStickyModifierStateListener(any())
+        }
+    }
+
+    @Test
+    fun stopsListeningToStickyKeysWhenKeyboardDisconnects() {
+        testScope.runTest {
+            collectLastValue(viewModel.indicatorContent)
+            setStickyKeysActive()
+            runCurrent()
+
+            keyboardRepository.setIsAnyKeyboardConnected(false)
+            runCurrent()
+
+            verify(inputManager).unregisterStickyModifierStateListener(any())
+        }
+    }
+
+    @Test
+    fun emitsStickyKeysListWhenStickyKeyIsPressed() {
+        testScope.runTest {
+            val stickyKeys by collectLastValue(viewModel.indicatorContent)
+            setStickyKeysActive()
+
+            setStickyKeys(mapOf(ALT to false))
+
+            assertThat(stickyKeys).isEqualTo(mapOf(ALT to Locked(false)))
+        }
+    }
+
+    @Test
+    fun emitsEmptyListWhenNoStickyKeysAreActive() {
+        testScope.runTest {
+            val stickyKeys by collectLastValue(viewModel.indicatorContent)
+            setStickyKeysActive()
+
+            setStickyKeys(emptyMap())
+
+            assertThat(stickyKeys).isEqualTo(emptyMap<ModifierKey, Locked>())
+        }
+    }
+
+    @Test
+    fun passesAllStickyKeysToDialog() {
+        testScope.runTest {
+            val stickyKeys by collectLastValue(viewModel.indicatorContent)
+            setStickyKeysActive()
+
+            setStickyKeys(mapOf(
+                ALT to false,
+                META to false,
+                SHIFT to false))
+
+            assertThat(stickyKeys).isEqualTo(mapOf(
+                ALT to Locked(false),
+                META to Locked(false),
+                SHIFT to Locked(false),
+            ))
+        }
+    }
+
+    @Test
+    fun showsOnlyLockedStateIfKeyIsStickyAndLocked() {
+        testScope.runTest {
+            val stickyKeys by collectLastValue(viewModel.indicatorContent)
+            setStickyKeysActive()
+
+            setStickyKeys(mapOf(
+                ALT to false,
+                ALT to true))
+
+            assertThat(stickyKeys).isEqualTo(mapOf(ALT to Locked(true)))
+        }
+    }
+
+    @Test
+    fun doesNotChangeOrderOfKeysIfTheyBecomeLocked() {
+        testScope.runTest {
+            val stickyKeys by collectLastValue(viewModel.indicatorContent)
+            setStickyKeysActive()
+
+            setStickyKeys(mapOf(
+                META to false,
+                SHIFT to false, // shift is sticky but not locked
+                CTRL to false))
+            val previousShiftIndex = stickyKeys?.toList()?.indexOf(SHIFT to Locked(false))
+
+            setStickyKeys(mapOf(
+                SHIFT to false,
+                SHIFT to true, // shift is now locked
+                META to false,
+                CTRL to false))
+            assertThat(stickyKeys?.toList()?.indexOf(SHIFT to Locked(true)))
+                .isEqualTo(previousShiftIndex)
+        }
+    }
+
+    private fun setStickyKeysActive() {
+        keyboardRepository.setIsAnyKeyboardConnected(true)
+        setStickyKeySetting(enabled = true)
+    }
+
+    private fun TestScope.setStickyKeys(keys: Map<ModifierKey, Boolean>) {
+        runCurrent()
+        verify(inputManager).registerStickyModifierStateListener(any(), captor.capture())
+        captor.value.onStickyModifierStateChanged(TestStickyModifierState(keys))
+        runCurrent()
+    }
+
+    private class TestStickyModifierState(private val keys: Map<ModifierKey, Boolean>) :
+        StickyModifierState() {
+
+        private fun isOn(key: ModifierKey) = keys.any { it.key == key && !it.value }
+        private fun isLocked(key: ModifierKey) = keys.any { it.key == key && it.value }
+
+        override fun isAltGrModifierLocked() = isLocked(ALT_GR)
+        override fun isAltGrModifierOn() = isOn(ALT_GR)
+        override fun isAltModifierLocked() = isLocked(ALT)
+        override fun isAltModifierOn() = isOn(ALT)
+        override fun isCtrlModifierLocked() = isLocked(CTRL)
+        override fun isCtrlModifierOn() = isOn(CTRL)
+        override fun isMetaModifierLocked() = isLocked(META)
+        override fun isMetaModifierOn() = isOn(META)
+        override fun isShiftModifierLocked() = isLocked(SHIFT)
+        override fun isShiftModifierOn() = isOn(SHIFT)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index b57cf53..14cae0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -25,6 +25,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER;
+import static com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR;
 import static com.android.systemui.keyguard.KeyguardViewMediator.DELAYED_KEYGUARD_ACTION;
 import static com.android.systemui.keyguard.KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT;
 import static com.android.systemui.keyguard.KeyguardViewMediator.REBOOT_MAINLINE_UPDATE;
@@ -97,6 +98,7 @@
 import com.android.systemui.flags.SystemPropertiesHelper;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.scene.FakeWindowRootViewComponent;
@@ -150,6 +152,7 @@
 @TestableLooper.RunWithLooper
 @SmallTest
 public class KeyguardViewMediatorTest extends SysuiTestCase {
+    private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
     private KeyguardViewMediator mViewMediator;
 
     private final TestScope mTestScope = TestScopeProvider.getTestScope();
@@ -265,10 +268,11 @@
                 mShadeWindowLogger,
                 () -> mSelectedUserInteractor,
                 mUserTracker,
-                mSceneContainerFlags);
+                mSceneContainerFlags,
+                mKosmos::getCommunalInteractor);
         mFeatureFlags = new FakeFeatureFlags();
-        mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
         mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER);
+        mSetFlagsRule.disableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR);
 
         DejankUtils.setImmediate(true);
 
@@ -306,6 +310,28 @@
 
     @Test
     @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    public void testRaceCondition_doNotRegisterCentralSurfacesImmediately() {
+        create(false);
+
+        // GIVEN central surfaces is not registered with KeyguardViewMediator, but a call to enable
+        // keyguard comes in
+        mViewMediator.onSystemReady();
+        mViewMediator.setKeyguardEnabled(true);
+        TestableLooper.get(this).processAllMessages();
+
+        // If this step has been reached, then system ui has not crashed. Now register
+        // CentralSurfaces
+        assertFalse(mViewMediator.isShowingAndNotOccluded());
+        register();
+        TestableLooper.get(this).moveTimeForward(100);
+        TestableLooper.get(this).processAllMessages();
+
+        // THEN keyguard is shown
+        assertTrue(mViewMediator.isShowingAndNotOccluded());
+    }
+
+    @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
     public void onLockdown_showKeyguard_evenIfKeyguardIsNotEnabledExternally() {
         // GIVEN keyguard is not enabled and isn't showing
         mViewMediator.onSystemReady();
@@ -331,6 +357,7 @@
                 mock(FoldGracePeriodProvider.class);
         mViewMediator.mFoldGracePeriodProvider = mockedFoldGracePeriodProvider;
         when(mockedFoldGracePeriodProvider.isEnabled()).thenReturn(true);
+        when(mUpdateMonitor.isDeviceProvisioned()).thenReturn(true);
 
         // GIVEN keyguard is not enabled and isn't showing
         mViewMediator.onSystemReady();
@@ -349,12 +376,40 @@
 
     @Test
     @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    public void doNotShowKeyguard_deviceNotProvisioned() {
+        // GIVEN feature is enabled
+        final FoldGracePeriodProvider mockedFoldGracePeriodProvider =
+                mock(FoldGracePeriodProvider.class);
+        mViewMediator.mFoldGracePeriodProvider = mockedFoldGracePeriodProvider;
+        when(mockedFoldGracePeriodProvider.isEnabled()).thenReturn(true);
+
+        // GIVEN keyguard is not enabled and isn't showing
+        mViewMediator.onSystemReady();
+        mViewMediator.setKeyguardEnabled(false);
+        TestableLooper.get(this).processAllMessages();
+        captureKeyguardUpdateMonitorCallback();
+        assertFalse(mViewMediator.isShowingAndNotOccluded());
+
+        // WHEN device is NOT provisioned
+        when(mUpdateMonitor.isDeviceProvisioned()).thenReturn(false);
+
+        // WHEN showKeyguard is requested
+        mViewMediator.showDismissibleKeyguard();
+
+        // THEN keyguard is NOT shown
+        TestableLooper.get(this).processAllMessages();
+        assertFalse(mViewMediator.isShowingAndNotOccluded());
+    }
+
+    @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
     public void showKeyguardAfterKeyguardNotEnabled_featureNotEnabled() {
         // GIVEN feature is NOT enabled
         final FoldGracePeriodProvider mockedFoldGracePeriodProvider =
                 mock(FoldGracePeriodProvider.class);
         mViewMediator.mFoldGracePeriodProvider = mockedFoldGracePeriodProvider;
         when(mockedFoldGracePeriodProvider.isEnabled()).thenReturn(false);
+        when(mUpdateMonitor.isDeviceProvisioned()).thenReturn(true);
 
         // GIVEN keyguard is not enabled and isn't showing
         mViewMediator.onSystemReady();
@@ -1136,6 +1191,11 @@
     }
 
     private void createAndStartViewMediator(boolean orderUnlockAndWake) {
+        create(orderUnlockAndWake);
+        register();
+    }
+
+    private void create(boolean orderUnlockAndWake) {
         mContext.getOrCreateTestableResources().addOverride(
                 com.android.internal.R.bool.config_orderUnlockAndWake, orderUnlockAndWake);
 
@@ -1186,7 +1246,9 @@
                 mSelectedUserInteractor,
                 mKeyguardInteractor);
         mViewMediator.start();
+    }
 
+    private void register() {
         mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
index c4df27c..cb8c40c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
@@ -69,6 +70,7 @@
 
     private val bouncerRepository = FakeKeyguardBouncerRepository()
     private val biometricSettingsRepository = FakeBiometricSettingsRepository()
+    private val deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
 
     private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
     private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@@ -112,7 +114,7 @@
             DeviceEntrySideFpsOverlayInteractor(
                 testScope.backgroundScope,
                 mContext,
-                FakeDeviceEntryFingerprintAuthRepository(),
+                deviceEntryFingerprintAuthRepository,
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
                 keyguardUpdateMonitor
@@ -216,6 +218,30 @@
             assertThat(showIndicatorForDeviceEntry).isEqualTo(false)
         }
 
+    @Test
+    fun ignoresDuplicateRequestsToShowIndicatorForDeviceEntry() =
+        testScope.runTest {
+            val showIndicatorForDeviceEntry by collectValues(underTest.showIndicatorForDeviceEntry)
+            runCurrent()
+
+            // Request to show indicator for primary bouncer showing
+            updatePrimaryBouncer(
+                isShowing = true,
+                isAnimatingAway = false,
+                fpsDetectionRunning = true,
+                isUnlockingWithFpAllowed = true
+            )
+
+            // Another request to show indicator for deviceEntryFingerprintAuthRepository update
+            deviceEntryFingerprintAuthRepository.setShouldUpdateIndicatorVisibility(true)
+
+            // Request to show indicator for alternate bouncer showing
+            bouncerRepository.setAlternateVisible(true)
+
+            // Ensure only one show request is sent
+            assertThat(showIndicatorForDeviceEntry).containsExactly(false, true)
+        }
+
     private fun updatePrimaryBouncer(
         isShowing: Boolean,
         isAnimatingAway: Boolean,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
index 8e81185..6092b6b35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -63,7 +63,10 @@
                 transitionRepository = super.transitionRepository,
                 transitionInteractor = super.transitionInteractor,
                 scope = super.testScope.backgroundScope,
+                bgDispatcher = super.testDispatcher,
+                mainDispatcher = super.testDispatcher,
                 keyguardInteractor = super.keyguardInteractor,
+                communalInteractor = super.communalInteractor,
                 flags = FakeFeatureFlags(),
                 keyguardSecurityModel = mock(),
                 powerInteractor = PowerInteractorFactory.create().powerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
index 339fd22..a03aed0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
@@ -17,14 +17,18 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import dagger.Lazy
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 
 open class KeyguardTransitionInteractorTestCase : SysuiTestCase() {
+    private val kosmos = testKosmos()
     val testDispatcher = StandardTestDispatcher()
     var testScope = TestScope(testDispatcher)
 
@@ -32,6 +36,7 @@
     lateinit var transitionRepository: FakeKeyguardTransitionRepository
 
     lateinit var keyguardInteractor: KeyguardInteractor
+    lateinit var communalInteractor: CommunalInteractor
     lateinit var transitionInteractor: KeyguardTransitionInteractor
 
     /**
@@ -51,6 +56,8 @@
         keyguardInteractor =
             KeyguardInteractorFactory.create(repository = keyguardRepository).keyguardInteractor
 
+        communalInteractor = kosmos.communalInteractor
+
         transitionInteractor =
             KeyguardTransitionInteractorFactory.create(
                     repository = transitionRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index b8a8bdf..e93ad0be3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -20,16 +20,23 @@
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
-import com.android.keyguard.TestScopeProvider
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
@@ -38,11 +45,15 @@
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
@@ -51,6 +62,8 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runCurrent
@@ -76,7 +89,8 @@
 @SmallTest
 @RunWith(JUnit4::class)
 class KeyguardTransitionScenariosTest : SysuiTestCase() {
-    private lateinit var testScope: TestScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
     private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
@@ -102,25 +116,29 @@
         FromPrimaryBouncerTransitionInteractor
     private lateinit var fromDreamingLockscreenHostedTransitionInteractor:
         FromDreamingLockscreenHostedTransitionInteractor
+    private lateinit var fromGlanceableHubTransitionInteractor:
+        FromGlanceableHubTransitionInteractor
 
     private lateinit var powerInteractor: PowerInteractor
     private lateinit var keyguardInteractor: KeyguardInteractor
+    private lateinit var communalInteractor: CommunalInteractor
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        testScope = TestScopeProvider.getTestScope()
 
-        keyguardRepository = FakeKeyguardRepository()
-        bouncerRepository = FakeKeyguardBouncerRepository()
+        keyguardRepository = kosmos.fakeKeyguardRepository
+        bouncerRepository = kosmos.fakeKeyguardBouncerRepository
         commandQueue = FakeCommandQueue()
-        shadeRepository = FakeShadeRepository()
-        transitionRepository = spy(FakeKeyguardTransitionRepository())
+        shadeRepository = kosmos.fakeShadeRepository
+        transitionRepository = spy(kosmos.fakeKeyguardTransitionRepository)
         powerInteractor = PowerInteractorFactory.create().powerInteractor
+        communalInteractor = kosmos.communalInteractor
 
         whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
 
-        featureFlags = FakeFeatureFlags().apply { set(Flags.KEYGUARD_WM_STATE_REFACTOR, false) }
+        mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
+        featureFlags = FakeFeatureFlags()
 
         keyguardInteractor = createKeyguardInteractor()
 
@@ -136,15 +154,26 @@
                 )
                 .keyguardTransitionInteractor
 
+        val glanceableHubTransitions =
+            GlanceableHubTransitions(
+                scope = testScope,
+                bgDispatcher = kosmos.testDispatcher,
+                transitionInteractor = transitionInteractor,
+                transitionRepository = transitionRepository,
+                communalInteractor = communalInteractor
+            )
         fromLockscreenTransitionInteractor =
             FromLockscreenTransitionInteractor(
                     scope = testScope,
+                    bgDispatcher = kosmos.testDispatcher,
+                    mainDispatcher = kosmos.testDispatcher,
                     keyguardInteractor = keyguardInteractor,
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
                     flags = featureFlags,
                     shadeRepository = shadeRepository,
                     powerInteractor = powerInteractor,
+                    glanceableHubTransitions = glanceableHubTransitions,
                     inWindowLauncherUnlockAnimationInteractor = {
                         InWindowLauncherUnlockAnimationInteractor(
                             InWindowLauncherUnlockAnimationRepository(),
@@ -160,12 +189,15 @@
         fromPrimaryBouncerTransitionInteractor =
             FromPrimaryBouncerTransitionInteractor(
                     scope = testScope,
+                    bgDispatcher = kosmos.testDispatcher,
+                    mainDispatcher = kosmos.testDispatcher,
                     keyguardInteractor = keyguardInteractor,
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
                     flags = featureFlags,
                     keyguardSecurityModel = keyguardSecurityModel,
                     powerInteractor = powerInteractor,
+                    communalInteractor = communalInteractor,
                     selectedUserInteractor = mSelectedUserInteractor,
                 )
                 .apply { start() }
@@ -173,6 +205,8 @@
         fromDreamingTransitionInteractor =
             FromDreamingTransitionInteractor(
                     scope = testScope,
+                    bgDispatcher = kosmos.testDispatcher,
+                    mainDispatcher = kosmos.testDispatcher,
                     keyguardInteractor = keyguardInteractor,
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
@@ -182,6 +216,8 @@
         fromDreamingLockscreenHostedTransitionInteractor =
             FromDreamingLockscreenHostedTransitionInteractor(
                     scope = testScope,
+                    bgDispatcher = kosmos.testDispatcher,
+                    mainDispatcher = kosmos.testDispatcher,
                     keyguardInteractor = keyguardInteractor,
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
@@ -191,6 +227,8 @@
         fromAodTransitionInteractor =
             FromAodTransitionInteractor(
                     scope = testScope,
+                    bgDispatcher = kosmos.testDispatcher,
+                    mainDispatcher = kosmos.testDispatcher,
                     keyguardInteractor = keyguardInteractor,
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
@@ -200,26 +238,61 @@
         fromGoneTransitionInteractor =
             FromGoneTransitionInteractor(
                     scope = testScope,
+                    bgDispatcher = kosmos.testDispatcher,
+                    mainDispatcher = kosmos.testDispatcher,
                     keyguardInteractor = keyguardInteractor,
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
                     powerInteractor = powerInteractor,
+                    communalInteractor = communalInteractor,
                 )
                 .apply { start() }
 
         fromDozingTransitionInteractor =
             FromDozingTransitionInteractor(
                     scope = testScope,
+                    bgDispatcher = kosmos.testDispatcher,
+                    mainDispatcher = kosmos.testDispatcher,
                     keyguardInteractor = keyguardInteractor,
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
                     powerInteractor = powerInteractor,
+                    communalInteractor = communalInteractor,
                 )
                 .apply { start() }
 
         fromOccludedTransitionInteractor =
             FromOccludedTransitionInteractor(
                     scope = testScope,
+                    bgDispatcher = kosmos.testDispatcher,
+                    mainDispatcher = kosmos.testDispatcher,
+                    keyguardInteractor = keyguardInteractor,
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                    powerInteractor = powerInteractor,
+                    communalInteractor = communalInteractor,
+                )
+                .apply { start() }
+
+        fromAlternateBouncerTransitionInteractor =
+            FromAlternateBouncerTransitionInteractor(
+                    scope = testScope,
+                    bgDispatcher = kosmos.testDispatcher,
+                    mainDispatcher = kosmos.testDispatcher,
+                    keyguardInteractor = keyguardInteractor,
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                    communalInteractor = communalInteractor,
+                    powerInteractor = powerInteractor,
+                )
+                .apply { start() }
+
+        fromGlanceableHubTransitionInteractor =
+            FromGlanceableHubTransitionInteractor(
+                    scope = testScope,
+                    bgDispatcher = kosmos.testDispatcher,
+                    mainDispatcher = kosmos.testDispatcher,
+                    glanceableHubTransitions = glanceableHubTransitions,
                     keyguardInteractor = keyguardInteractor,
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
@@ -227,15 +300,9 @@
                 )
                 .apply { start() }
 
-        fromAlternateBouncerTransitionInteractor =
-            FromAlternateBouncerTransitionInteractor(
-                    scope = testScope,
-                    keyguardInteractor = keyguardInteractor,
-                    transitionRepository = transitionRepository,
-                    transitionInteractor = transitionInteractor,
-                    powerInteractor = powerInteractor,
-                )
-                .apply { start() }
+        mSetFlagsRule.disableFlags(
+            FLAG_KEYGUARD_WM_STATE_REFACTOR,
+        )
     }
 
     @Test
@@ -675,6 +742,38 @@
         }
 
     @Test
+    fun dozingToGlanceableHub() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to DOZING
+            runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.DOZING)
+            runCurrent()
+
+            // GIVEN the device is idle on the glanceable hub
+            val idleTransitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(
+                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+                )
+            communalInteractor.setTransitionState(idleTransitionState)
+            runCurrent()
+
+            // WHEN the device begins to wake
+            powerInteractor.setAwakeForTest()
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+            // THEN a transition to DOZING should occur
+            assertThat(info.ownerName).isEqualTo(FromDozingTransitionInteractor::class.simpleName)
+            assertThat(info.from).isEqualTo(KeyguardState.DOZING)
+            assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun goneToDozing() =
         testScope.runTest {
             // GIVEN a device with AOD not available
@@ -813,6 +912,37 @@
         }
 
     @Test
+    fun goneToGlanceableHub() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to GONE
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
+
+            // GIVEN the device is idle on the glanceable hub
+            val idleTransitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(
+                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+                )
+            communalInteractor.setTransitionState(idleTransitionState)
+            runCurrent()
+
+            // WHEN the keyguard starts to show
+            keyguardRepository.setKeyguardShowing(true)
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+            // THEN a transition to DOZING should occur
+            assertThat(info.ownerName).isEqualTo(FromGoneTransitionInteractor::class.simpleName)
+            assertThat(info.from).isEqualTo(KeyguardState.GONE)
+            assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun alternateBouncerToPrimaryBouncer() =
         testScope.runTest {
             // GIVEN a prior transition has run to ALTERNATE_BOUNCER
@@ -934,6 +1064,45 @@
         }
 
     @Test
+    fun alternateBouncerToGlanceableHub() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to ALTERNATE_BOUNCER
+            bouncerRepository.setAlternateVisible(true)
+            runTransitionAndSetWakefulness(
+                KeyguardState.LOCKSCREEN,
+                KeyguardState.ALTERNATE_BOUNCER
+            )
+
+            // GIVEN the primary bouncer isn't showing and device not sleeping
+            bouncerRepository.setPrimaryShow(false)
+
+            // GIVEN the device is idle on the glanceable hub
+            val idleTransitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(
+                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+                )
+            communalInteractor.setTransitionState(idleTransitionState)
+            runCurrent()
+
+            // WHEN the alternateBouncer stops showing
+            bouncerRepository.setAlternateVisible(false)
+            advanceUntilIdle()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+            // THEN a transition to LOCKSCREEN should occur
+            assertThat(info.ownerName)
+                .isEqualTo(FromAlternateBouncerTransitionInteractor::class.simpleName)
+            assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
+            assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun primaryBouncerToAod() =
         testScope.runTest {
             // GIVEN a prior transition has run to PRIMARY_BOUNCER
@@ -996,7 +1165,7 @@
             bouncerRepository.setPrimaryShow(true)
             runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER)
 
-            // WHEN the alternateBouncer stops showing
+            // WHEN the primaryBouncer stops showing
             bouncerRepository.setPrimaryShow(false)
             runCurrent()
 
@@ -1014,6 +1183,39 @@
         }
 
     @Test
+    fun primaryBouncerToGlanceableHub() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to PRIMARY_BOUNCER
+            bouncerRepository.setPrimaryShow(true)
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER)
+
+            // GIVEN the device is idle on the glanceable hub
+            val idleTransitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(
+                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+                )
+            communalInteractor.setTransitionState(idleTransitionState)
+            runCurrent()
+
+            // WHEN the primaryBouncer stops showing
+            bouncerRepository.setPrimaryShow(false)
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+            // THEN a transition to LOCKSCREEN should occur
+            assertThat(info.ownerName)
+                .isEqualTo(FromPrimaryBouncerTransitionInteractor::class.simpleName)
+            assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
+            assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun primaryBouncerToDreamingLockscreenHosted() =
         testScope.runTest {
             // GIVEN device dreaming with the lockscreen hosted dream and not dozing
@@ -1104,6 +1306,43 @@
         }
 
     @Test
+    fun occludedToGlanceableHub() =
+        testScope.runTest {
+            // GIVEN a device on lockscreen
+            keyguardRepository.setKeyguardShowing(true)
+            runCurrent()
+
+            // GIVEN the device is idle on the glanceable hub
+            val idleTransitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(
+                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+                )
+            communalInteractor.setTransitionState(idleTransitionState)
+            runCurrent()
+
+            // GIVEN a prior transition has run to OCCLUDED
+            runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.OCCLUDED)
+            keyguardRepository.setKeyguardOccluded(true)
+            runCurrent()
+
+            // WHEN occlusion ends
+            keyguardRepository.setKeyguardOccluded(false)
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+            // THEN a transition to GLANCEABLE_HUB should occur
+            assertThat(info.ownerName).isEqualTo(FromOccludedTransitionInteractor::class.simpleName)
+            assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
+            assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun occludedToAlternateBouncer() =
         testScope.runTest {
             // GIVEN a prior transition has run to OCCLUDED
@@ -1292,14 +1531,8 @@
             runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
             runCurrent()
 
-            // WHEN the keyguard is occluded and aod ends
+            // WHEN the keyguard is occluded
             keyguardRepository.setKeyguardOccluded(true)
-            keyguardRepository.setDozeTransitionModel(
-                DozeTransitionModel(
-                    from = DozeStateModel.DOZE_AOD,
-                    to = DozeStateModel.FINISH,
-                )
-            )
             runCurrent()
 
             val info =
@@ -1316,6 +1549,30 @@
         }
 
     @Test
+    fun aodToPrimaryBouncer() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to AOD
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
+            runCurrent()
+
+            // WHEN the primary bouncer is set to show
+            bouncerRepository.setPrimaryShow(true)
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+            // THEN a transition to OCCLUDED should occur
+            assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.AOD)
+            assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun lockscreenToOccluded_fromCameraGesture() =
         testScope.runTest {
             // GIVEN a prior transition has run to LOCKSCREEN
@@ -1391,6 +1648,253 @@
             coroutineContext.cancelChildren()
         }
 
+    @Test
+    fun lockscreenToGlanceableHub() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to LOCKSCREEN
+            runTransitionAndSetWakefulness(KeyguardState.AOD, KeyguardState.LOCKSCREEN)
+            runCurrent()
+
+            // WHEN a glanceable hub transition starts
+            val currentScene = CommunalSceneKey.Blank
+            val targetScene = CommunalSceneKey.Communal
+
+            val progress = MutableStateFlow(0f)
+            val transitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(
+                    ObservableCommunalTransitionState.Transition(
+                        fromScene = currentScene,
+                        toScene = targetScene,
+                        progress = progress,
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            communalInteractor.setTransitionState(transitionState)
+            progress.value = .1f
+            runCurrent()
+
+            // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+            assertThat(info.ownerName)
+                .isEqualTo(FromLockscreenTransitionInteractor::class.simpleName)
+            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
+            assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+            assertThat(info.animator).isNull() // transition should be manually animated
+
+            // WHEN the user stops dragging and the glanceable hub opening is cancelled
+            clearInvocations(transitionRepository)
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+            val idleTransitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(
+                    ObservableCommunalTransitionState.Idle(currentScene)
+                )
+            communalInteractor.setTransitionState(idleTransitionState)
+            runCurrent()
+
+            // THEN a transition from GLANCEABLE_HUB => LOCKSCREEN should occur
+            val info2 =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+            assertThat(info2.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+            assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN)
+            assertThat(info.animator).isNull() // transition should be manually animated
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
+    fun glanceableHubToLockscreen() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to GLANCEABLE_HUB
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+            runCurrent()
+
+            // WHEN a transition away from glanceable hub starts
+            val currentScene = CommunalSceneKey.Communal
+            val targetScene = CommunalSceneKey.Blank
+
+            val progress = MutableStateFlow(0f)
+            val transitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(
+                    ObservableCommunalTransitionState.Transition(
+                        fromScene = currentScene,
+                        toScene = targetScene,
+                        progress = progress,
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            communalInteractor.setTransitionState(transitionState)
+            progress.value = .1f
+            runCurrent()
+
+            // THEN a transition from GLANCEABLE_HUB => LOCKSCREEN should occur
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+            assertThat(info.ownerName)
+                .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
+            assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+            assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
+            assertThat(info.animator).isNull() // transition should be manually animated
+
+            // WHEN the user stops dragging and the glanceable hub closing is cancelled
+            clearInvocations(transitionRepository)
+            runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.LOCKSCREEN)
+            val idleTransitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(
+                    ObservableCommunalTransitionState.Idle(currentScene)
+                )
+            communalInteractor.setTransitionState(idleTransitionState)
+            runCurrent()
+
+            // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
+            val info2 =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+            assertThat(info2.from).isEqualTo(KeyguardState.LOCKSCREEN)
+            assertThat(info2.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+            assertThat(info.animator).isNull() // transition should be manually animated
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
+    fun glanceableHubToDozing() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to GLANCEABLE_HUB
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+
+            // WHEN the device begins to sleep
+            powerInteractor.setAsleepForTest()
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+            // THEN a transition to DOZING should occur
+            assertThat(info.ownerName)
+                .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
+            assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+            assertThat(info.to).isEqualTo(KeyguardState.DOZING)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
+    fun glanceableHubToPrimaryBouncer() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to ALTERNATE_BOUNCER
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+
+            // WHEN the primary bouncer shows
+            bouncerRepository.setPrimaryShow(true)
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+            // THEN a transition to PRIMARY_BOUNCER should occur
+            assertThat(info.ownerName)
+                .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
+            assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+            assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
+    fun glanceableHubToAlternateBouncer() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to ALTERNATE_BOUNCER
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+
+            // WHEN the primary bouncer shows
+            bouncerRepository.setAlternateVisible(true)
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+            // THEN a transition to PRIMARY_BOUNCER should occur
+            assertThat(info.ownerName)
+                .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
+            assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+            assertThat(info.to).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
+    fun glanceableHubToOccluded() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to GLANCEABLE_HUB
+            runTransitionAndSetWakefulness(KeyguardState.GONE, KeyguardState.GLANCEABLE_HUB)
+            runCurrent()
+
+            // GIVEN the device is idle on the glanceable hub
+            val idleTransitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(
+                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+                )
+            communalInteractor.setTransitionState(idleTransitionState)
+            runCurrent()
+
+            // WHEN the keyguard is occluded
+            keyguardRepository.setKeyguardOccluded(true)
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+            // THEN a transition to OCCLUDED should occur
+            assertThat(info.ownerName)
+                .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
+            assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
+    fun glanceableHubToGone() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to GLANCEABLE_HUB
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+
+            // WHEN keyguard goes away
+            keyguardRepository.setKeyguardGoingAway(true)
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture())
+                }
+            // THEN a transition to DOZING should occur
+            assertThat(info.ownerName)
+                .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
+            assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
+            assertThat(info.to).isEqualTo(KeyguardState.GONE)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
     private fun createKeyguardInteractor(): KeyguardInteractor {
         return KeyguardInteractorFactory.create(
                 featureFlags = featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
index edd781d..2d9d5ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
@@ -20,14 +20,16 @@
 import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.milliseconds
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -37,23 +39,21 @@
 @SmallTest
 @RunWith(JUnit4::class)
 class KeyguardTransitionAnimationFlowTest : SysuiTestCase() {
-    private lateinit var underTest: KeyguardTransitionAnimationFlow.SharedFlowBuilder
-    private lateinit var repository: FakeKeyguardTransitionRepository
-    private lateinit var testScope: TestScope
+    val kosmos = testKosmos()
+    val testScope = kosmos.testScope
+    val animationFlow = kosmos.keyguardTransitionAnimationFlow
+    val repository = kosmos.fakeKeyguardTransitionRepository
+
+    private lateinit var underTest: KeyguardTransitionAnimationFlow.FlowBuilder
 
     @Before
     fun setUp() {
-        testScope = TestScope()
-        repository = FakeKeyguardTransitionRepository()
         underTest =
-            KeyguardTransitionAnimationFlow(
-                    testScope.backgroundScope,
-                    mock(),
-                )
-                .setup(
-                    duration = 1000.milliseconds,
-                    stepFlow = repository.transitions,
-                )
+            animationFlow.setup(
+                duration = 1000.milliseconds,
+                from = KeyguardState.GONE,
+                to = KeyguardState.DREAMING,
+            )
     }
 
     @Test(expected = IllegalArgumentException::class)
@@ -83,6 +83,8 @@
                     onFinish = { 10f },
                 )
             var animationValues = collectLastValue(flow)
+            runCurrent()
+
             repository.sendTransitionStep(step(1f, TransitionState.FINISHED), validateStep = false)
             assertThat(animationValues()).isEqualTo(10f)
         }
@@ -97,6 +99,8 @@
                     onCancel = { 100f },
                 )
             var animationValues = collectLastValue(flow)
+            runCurrent()
+
             repository.sendTransitionStep(step(0.5f, TransitionState.CANCELED))
             assertThat(animationValues()).isEqualTo(100f)
         }
@@ -111,6 +115,8 @@
                     onStep = { it },
                 )
             var animationValues = collectLastValue(flow)
+            runCurrent()
+
             repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             assertThat(animationValues()).isEqualTo(0f)
 
@@ -137,6 +143,8 @@
                     onStep = { it },
                 )
             var animationValues = collectLastValue(flow)
+            runCurrent()
+
             repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0f))
             repository.sendTransitionStep(step(0.5f, TransitionState.RUNNING))
@@ -157,17 +165,56 @@
                     duration = 1000.milliseconds,
                     onStep = { it * 2 },
                 )
-            var animationValues = collectLastValue(flow)
+            val animationValues by collectLastValue(flow)
+            runCurrent()
+
             repository.sendTransitionStep(step(0f, TransitionState.STARTED))
-            assertFloat(animationValues(), 0f)
+            assertFloat(animationValues, 0f)
             repository.sendTransitionStep(step(0.3f, TransitionState.RUNNING))
-            assertFloat(animationValues(), 0.6f)
+            assertFloat(animationValues, 0.6f)
             repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
-            assertFloat(animationValues(), 1.2f)
+            assertFloat(animationValues, 1.2f)
             repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
-            assertFloat(animationValues(), 1.6f)
+            assertFloat(animationValues, 1.6f)
             repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
-            assertFloat(animationValues(), 2f)
+            assertFloat(animationValues, 2f)
+        }
+
+    @Test
+    fun sameFloatValueWithTheSameTransitionStateDoesNotEmitTwice() =
+        testScope.runTest {
+            val flow =
+                underTest.sharedFlow(
+                    duration = 1000.milliseconds,
+                    onStep = { it },
+                )
+            val values by collectValues(flow)
+            runCurrent()
+
+            repository.sendTransitionStep(step(0.3f, TransitionState.RUNNING))
+            repository.sendTransitionStep(step(0.3f, TransitionState.RUNNING))
+
+            assertThat(values.size).isEqualTo(1)
+            assertThat(values[0]).isEqualTo(0.3f)
+        }
+
+    @Test
+    fun sameFloatValueWithADifferentTransitionStateDoesEmitTwice() =
+        testScope.runTest {
+            val flow =
+                underTest.sharedFlow(
+                    duration = 1000.milliseconds,
+                    onStep = { it },
+                )
+            val values by collectValues(flow)
+            runCurrent()
+
+            repository.sendTransitionStep(step(0.3f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0.3f, TransitionState.RUNNING))
+
+            assertThat(values.size).isEqualTo(2)
+            assertThat(values[0]).isEqualTo(0.3f)
+            assertThat(values[0]).isEqualTo(0.3f)
         }
 
     private fun assertFloat(actual: Float?, expected: Float) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
index a4d217f..5dd37ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
@@ -21,13 +21,17 @@
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardClockSwitch.LARGE
+import com.android.keyguard.KeyguardClockSwitch.SMALL
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.plugins.clocks.ClockConfig
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockFaceController
 import com.android.systemui.plugins.clocks.ClockFaceLayout
 import com.android.systemui.util.mockito.whenever
 import kotlin.test.Test
+import kotlinx.coroutines.flow.MutableStateFlow
 import org.junit.Before
 import org.junit.runner.RunWith
 import org.mockito.Mock
@@ -48,32 +52,60 @@
     @Mock private lateinit var smallClockView: View
     @Mock private lateinit var smallClockFaceLayout: ClockFaceLayout
     @Mock private lateinit var largeClockFaceLayout: ClockFaceLayout
+    @Mock private lateinit var clockViewModel: KeyguardClockViewModel
+    private val clockSize = MutableStateFlow(LARGE)
+    private val currentClock: MutableStateFlow<ClockController?> = MutableStateFlow(null)
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-    }
-
-    @Test
-    fun addClockViews_nonWeatherClock() {
-        setupNonWeatherClock()
-        KeyguardClockViewBinder.addClockViews(clock, rootView, burnInLayer)
-        verify(rootView).addView(smallClockView)
-        verify(rootView).addView(largeClockView)
-        verify(burnInLayer).addView(smallClockView)
-        verify(burnInLayer, never()).addView(largeClockView)
+        whenever(clockViewModel.clockSize).thenReturn(clockSize)
+        whenever(clockViewModel.currentClock).thenReturn(currentClock)
+        whenever(clockViewModel.burnInLayer).thenReturn(burnInLayer)
     }
 
     @Test
     fun addClockViews_WeatherClock() {
         setupWeatherClock()
-        KeyguardClockViewBinder.addClockViews(clock, rootView, burnInLayer)
+        KeyguardClockViewBinder.addClockViews(clock, rootView)
         verify(rootView).addView(smallClockView)
         verify(rootView).addView(largeClockView)
-        verify(burnInLayer).addView(smallClockView)
+    }
+
+    @Test
+    fun addClockViews_nonWeatherClock() {
+        setupNonWeatherClock()
+        KeyguardClockViewBinder.addClockViews(clock, rootView)
+        verify(rootView).addView(smallClockView)
+        verify(rootView).addView(largeClockView)
+    }
+    @Test
+    fun addClockViewsToBurnInLayer_LargeWeatherClock() {
+        setupWeatherClock()
+        clockSize.value = LARGE
+        KeyguardClockViewBinder.updateBurnInLayer(rootView, clockViewModel)
+        verify(burnInLayer).removeView(smallClockView)
         verify(burnInLayer).addView(largeClockView)
     }
 
+    @Test
+    fun addClockViewsToBurnInLayer_LargeNonWeatherClock() {
+        setupNonWeatherClock()
+        clockSize.value = LARGE
+        KeyguardClockViewBinder.updateBurnInLayer(rootView, clockViewModel)
+        verify(burnInLayer).removeView(smallClockView)
+        verify(burnInLayer, never()).addView(largeClockView)
+    }
+
+    @Test
+    fun addClockViewsToBurnInLayer_SmallClock() {
+        setupNonWeatherClock()
+        clockSize.value = SMALL
+        KeyguardClockViewBinder.updateBurnInLayer(rootView, clockViewModel)
+        verify(burnInLayer).addView(smallClockView)
+        verify(burnInLayer).removeView(largeClockView)
+    }
+
     private fun setupWeatherClock() {
         setupClock()
         val clockConfig =
@@ -99,5 +131,7 @@
         whenever(clock.smallClock).thenReturn(smallClock)
         whenever(largeClock.layout).thenReturn(largeClockFaceLayout)
         whenever(smallClock.layout).thenReturn(smallClockFaceLayout)
+        whenever(clockViewModel.clock).thenReturn(clock)
+        currentClock.value = clock
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index 070a0cc..acb6ff0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -19,11 +19,15 @@
 
 import android.content.pm.PackageManager
 import android.content.res.Resources
+import android.view.View.GONE
+import android.view.View.VISIBLE
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.SplitShadeStateController
 import com.android.systemui.util.Utils
@@ -31,6 +35,8 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
+import kotlinx.coroutines.flow.MutableStateFlow
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -46,6 +52,11 @@
     @Mock private lateinit var keyguardClockInteractor: KeyguardClockInteractor
     @Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel
     @Mock private lateinit var splitShadeStateController: SplitShadeStateController
+    @Mock private lateinit var smartspaceViewModel: KeyguardSmartspaceViewModel
+    @Mock private lateinit var blueprintInteractor: Lazy<KeyguardBlueprintInteractor>
+    private val bcSmartspaceVisibility: MutableStateFlow<Int> = MutableStateFlow(VISIBLE)
+    private val clockShouldBeCentered: MutableStateFlow<Boolean> = MutableStateFlow(true)
+    private val isAodIconsVisible: MutableStateFlow<Boolean> = MutableStateFlow(true)
 
     private lateinit var underTest: ClockSection
 
@@ -104,12 +115,18 @@
         whenever(packageManager.getResourcesForApplication(anyString())).thenReturn(remoteResources)
         mContext.setMockPackageManager(packageManager)
 
+        whenever(keyguardClockViewModel.clockShouldBeCentered).thenReturn(clockShouldBeCentered)
+        whenever(keyguardClockViewModel.isAodIconsVisible).thenReturn(isAodIconsVisible)
+        whenever(smartspaceViewModel.bcSmartspaceVisibility).thenReturn(bcSmartspaceVisibility)
+
         underTest =
             ClockSection(
                 keyguardClockInteractor,
                 keyguardClockViewModel,
                 mContext,
                 splitShadeStateController,
+                smartspaceViewModel,
+                blueprintInteractor
             )
     }
 
@@ -168,6 +185,40 @@
         assetSmallClockTop(cs, expectedSmallClockTopMargin)
     }
 
+    @Test
+    fun testSmartspaceVisible_weatherClockDateAndIconsBarrierBottomBelowBCSmartspace() {
+        isAodIconsVisible.value = false
+        bcSmartspaceVisibility.value = VISIBLE
+        val cs = ConstraintSet()
+        underTest.applyDefaultConstraints(cs)
+        val referencedIds = cs.getReferencedIds(R.id.weather_clock_date_and_icons_barrier_bottom)
+        referencedIds.contentEquals(intArrayOf(com.android.systemui.shared.R.id.bc_smartspace_view))
+    }
+
+    @Test
+    fun testSmartspaceGone_weatherClockDateAndIconsBarrierBottomBelowSmartspaceDateWeather() {
+        isAodIconsVisible.value = false
+        bcSmartspaceVisibility.value = GONE
+        val cs = ConstraintSet()
+        underTest.applyDefaultConstraints(cs)
+        val referencedIds = cs.getReferencedIds(R.id.weather_clock_date_and_icons_barrier_bottom)
+        referencedIds.contentEquals(intArrayOf(R.id.lockscreen_clock_view))
+    }
+
+    @Test
+    fun testHasAodIcons_weatherClockDateAndIconsBarrierBottomBelowSmartspaceDateWeather() {
+        isAodIconsVisible.value = true
+        val cs = ConstraintSet()
+        underTest.applyDefaultConstraints(cs)
+        val referencedIds = cs.getReferencedIds(R.id.weather_clock_date_and_icons_barrier_bottom)
+        referencedIds.contentEquals(
+            intArrayOf(
+                com.android.systemui.shared.R.id.bc_smartspace_view,
+                R.id.aod_notification_icon_container
+            )
+        )
+    }
+
     private fun setLargeClock(useLargeClock: Boolean) {
         whenever(keyguardClockViewModel.useLargeClock).thenReturn(useLargeClock)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index c864704..699284e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.statusbar.VibratorHelper
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -71,6 +72,7 @@
             FakeFeatureFlagsClassic().apply { set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) }
         underTest =
             DefaultDeviceEntrySection(
+                TestScope().backgroundScope,
                 keyguardUpdateMonitor,
                 authController,
                 windowManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
index 28da957..deb3a83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
@@ -26,6 +26,8 @@
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
@@ -34,7 +36,8 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.StateFlow
+import dagger.Lazy
+import kotlinx.coroutines.flow.MutableStateFlow
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -50,7 +53,8 @@
     @Mock private lateinit var keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel
     @Mock private lateinit var lockscreenSmartspaceController: LockscreenSmartspaceController
     @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
-    @Mock private lateinit var hasCustomWeatherDataDisplay: StateFlow<Boolean>
+    @Mock private lateinit var keyguardSmartspaceInteractor: KeyguardSmartspaceInteractor
+    @Mock private lateinit var blueprintInteractor: Lazy<KeyguardBlueprintInteractor>
 
     private val smartspaceView = View(mContext).also { it.id = sharedR.id.bc_smartspace_view }
     private val weatherView = View(mContext).also { it.id = sharedR.id.weather_smartspace_view }
@@ -58,17 +62,22 @@
     private lateinit var constraintLayout: ConstraintLayout
     private lateinit var constraintSet: ConstraintSet
 
+    private val clockShouldBeCentered = MutableStateFlow(false)
+    private val hasCustomWeatherDataDisplay = MutableStateFlow(false)
+
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
         mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
         underTest =
             SmartspaceSection(
+                mContext,
                 keyguardClockViewModel,
                 keyguardSmartspaceViewModel,
-                mContext,
+                keyguardSmartspaceInteractor,
                 lockscreenSmartspaceController,
                 keyguardUnlockAnimationController,
+                blueprintInteractor
             )
         constraintLayout = ConstraintLayout(mContext)
         whenever(lockscreenSmartspaceController.buildAndConnectView(any()))
@@ -78,6 +87,7 @@
         whenever(lockscreenSmartspaceController.buildAndConnectDateView(any())).thenReturn(dateView)
         whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay)
             .thenReturn(hasCustomWeatherDataDisplay)
+        whenever(keyguardClockViewModel.clockShouldBeCentered).thenReturn(clockShouldBeCentered)
         constraintSet = ConstraintSet()
     }
 
@@ -115,7 +125,7 @@
     fun testConstraintsWhenNotHasCustomWeatherDataDisplay() {
         whenever(keyguardSmartspaceViewModel.isSmartspaceEnabled).thenReturn(true)
         whenever(keyguardSmartspaceViewModel.isDateWeatherDecoupled).thenReturn(true)
-        whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay.value).thenReturn(false)
+        hasCustomWeatherDataDisplay.value = false
         underTest.addViews(constraintLayout)
         underTest.applyConstraints(constraintSet)
         assertWeatherSmartspaceConstrains(constraintSet)
@@ -129,7 +139,7 @@
 
     @Test
     fun testConstraintsWhenHasCustomWeatherDataDisplay() {
-        whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay.value).thenReturn(true)
+        hasCustomWeatherDataDisplay.value = true
         underTest.addViews(constraintLayout)
         underTest.applyConstraints(constraintSet)
         assertWeatherSmartspaceConstrains(constraintSet)
@@ -140,7 +150,7 @@
 
     @Test
     fun testNormalDateWeatherVisibility() {
-        whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay.value).thenReturn(false)
+        hasCustomWeatherDataDisplay.value = false
         whenever(keyguardSmartspaceViewModel.isWeatherEnabled).thenReturn(true)
         underTest.addViews(constraintLayout)
         underTest.applyConstraints(constraintSet)
@@ -153,7 +163,7 @@
     }
     @Test
     fun testCustomDateWeatherVisibility() {
-        whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay.value).thenReturn(true)
+        hasCustomWeatherDataDisplay.value = true
         underTest.addViews(constraintLayout)
         underTest.applyConstraints(constraintSet)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
index d959872..87391cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
@@ -31,6 +31,7 @@
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -50,6 +51,7 @@
     fun transitionToAlternateBouncer_scrimAlphaUpdate() =
         testScope.runTest {
             val scrimAlphas by collectValues(underTest.scrimAlpha)
+            runCurrent()
 
             transitionRepository.sendTransitionSteps(
                 listOf(
@@ -69,17 +71,17 @@
     fun transitionFromAlternateBouncer_scrimAlphaUpdate() =
         testScope.runTest {
             val scrimAlphas by collectValues(underTest.scrimAlpha)
+            runCurrent()
 
             transitionRepository.sendTransitionSteps(
                 listOf(
-                    stepToAlternateBouncer(0f, TransitionState.STARTED),
-                    stepToAlternateBouncer(.4f),
-                    stepToAlternateBouncer(.6f),
-                    stepToAlternateBouncer(1f),
+                    stepFromAlternateBouncer(0f, TransitionState.STARTED),
+                    stepFromAlternateBouncer(.4f),
+                    stepFromAlternateBouncer(.6f),
+                    stepFromAlternateBouncer(1f),
                 ),
                 testScope,
             )
-
             assertThat(scrimAlphas.size).isEqualTo(4)
             scrimAlphas.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
index af8d8a8..795e68d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -63,6 +64,7 @@
             fingerprintPropertyRepository.supportsUdfps()
             val deviceEntryBackgroundViewAlpha by
                 collectLastValue(underTest.deviceEntryBackgroundViewAlpha)
+            runCurrent()
 
             // fade in
             repository.sendTransitionStep(step(0f, TransitionState.STARTED))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
index daafe12..75994da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
@@ -39,6 +39,7 @@
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -78,6 +79,8 @@
     fun scrimAlpha_runDimissFromKeyguard_shadeExpanded() =
         testScope.runTest {
             val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
+            runCurrent()
+
             shadeRepository.setLockscreenShadeExpansion(1f)
             whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
 
@@ -101,6 +104,8 @@
     fun scrimAlpha_runDimissFromKeyguard_shadeNotExpanded() =
         testScope.runTest {
             val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
+            runCurrent()
+
             shadeRepository.setLockscreenShadeExpansion(0f)
 
             whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
@@ -123,6 +128,7 @@
     fun scrimBehindAlpha_leaveShadeOpen() =
         testScope.runTest {
             val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
+            runCurrent()
 
             sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(true)
 
@@ -146,6 +152,8 @@
     fun scrimBehindAlpha_doNotLeaveShadeOpen() =
         testScope.runTest {
             val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
+            runCurrent()
+
             keyguardTransitionRepository.sendTransitionSteps(
                 listOf(
                     step(0f, TransitionState.STARTED),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt
index dd542d4..471029b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt
@@ -20,18 +20,15 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -39,29 +36,10 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class DozingToLockscreenTransitionViewModelTest : SysuiTestCase() {
-    private lateinit var testScope: TestScope
-    private lateinit var underTest: DozingToLockscreenTransitionViewModel
-    private lateinit var repository: FakeKeyguardTransitionRepository
-
-    @Before
-    fun setUp() {
-        testScope = TestScope()
-        repository = FakeKeyguardTransitionRepository()
-        underTest =
-            DozingToLockscreenTransitionViewModel(
-                interactor =
-                    KeyguardTransitionInteractorFactory.create(
-                            scope = testScope.backgroundScope,
-                            repository = repository,
-                        )
-                        .keyguardTransitionInteractor,
-                animationFlow =
-                    KeyguardTransitionAnimationFlow(
-                        scope = testScope.backgroundScope,
-                        logger = mock()
-                    ),
-            )
-    }
+    val kosmos = testKosmos()
+    val testScope = kosmos.testScope
+    val repository = kosmos.fakeKeyguardTransitionRepository
+    val underTest = kosmos.dozingToLockscreenTransitionViewModel
 
     @Test
     fun deviceEntryParentViewShows() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
index a105008..1c9c942 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
@@ -31,6 +31,7 @@
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -52,6 +53,7 @@
             val pixels = -100f
             val enterFromTopTranslationY by
                 collectLastValue(underTest.enterFromTopTranslationY(pixels.toInt()))
+            runCurrent()
 
             // The animation should only start > .4f way through
             repository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -72,6 +74,7 @@
     fun enterFromTopAnimationAlpha() =
         testScope.runTest {
             val enterFromTopAnimationAlpha by collectLastValue(underTest.enterFromTopAnimationAlpha)
+            runCurrent()
 
             // The animation should only start > .4f way through
             repository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -92,6 +95,7 @@
         testScope.runTest {
             val deviceEntryBackgroundViewAlpha by
                 collectLastValue(underTest.deviceEntryBackgroundViewAlpha)
+            runCurrent()
 
             // immediately 0f
             repository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -113,6 +117,7 @@
             fingerprintPropertyRepository.supportsUdfps()
             biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
             val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+            runCurrent()
 
             // animation doesn't start until the end
             repository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -137,6 +142,7 @@
             fingerprintPropertyRepository.supportsRearFps()
             biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
             val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+            runCurrent()
 
             // animation doesn't start until the end
             repository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -161,6 +167,7 @@
             fingerprintPropertyRepository.supportsUdfps()
             biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
             val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+            runCurrent()
 
             // animation doesn't start until the end
             repository.sendTransitionStep(step(0f, TransitionState.STARTED))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 1b4573d..22a2e93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -34,12 +34,14 @@
 import com.android.systemui.plugins.clocks.ClockFaceConfig
 import com.android.systemui.plugins.clocks.ClockFaceController
 import com.android.systemui.shared.clocks.ClockRegistry
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
 import com.android.systemui.statusbar.policy.SplitShadeStateController
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
 import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestCoroutineScheduler
 import kotlinx.coroutines.test.TestScope
@@ -68,6 +70,8 @@
     @Mock private lateinit var clockFaceConfig: ClockFaceConfig
     @Mock private lateinit var eventController: ClockEventController
     @Mock private lateinit var splitShadeStateController: SplitShadeStateController
+    @Mock private lateinit var notifsKeyguardInteractor: NotificationsKeyguardInteractor
+    @Mock private lateinit var areNotificationsFullyHidden: Flow<Boolean>
 
     @Before
     fun setup() {
@@ -90,12 +94,15 @@
                 scope.backgroundScope
             )
         keyguardClockInteractor = KeyguardClockInteractor(keyguardClockRepository)
+        whenever(notifsKeyguardInteractor.areNotificationsFullyHidden)
+            .thenReturn(areNotificationsFullyHidden)
         underTest =
             KeyguardClockViewModel(
                 keyguardInteractor,
                 keyguardClockInteractor,
                 scope.backgroundScope,
                 splitShadeStateController,
+                notifsKeyguardInteractor
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt
index 5e62317..1912987 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -51,6 +52,7 @@
         testScope.runTest {
             val deviceEntryBackgroundViewAlpha by
                 collectLastValue(underTest.deviceEntryBackgroundViewAlpha)
+            runCurrent()
 
             // immediately 0f
             keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -72,6 +74,7 @@
             fingerprintPropertyRepository.supportsUdfps()
             biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
             val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+            runCurrent()
 
             // immediately 1f
             keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -96,6 +99,7 @@
             fingerprintPropertyRepository.supportsRearFps()
             biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
             val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+            runCurrent()
 
             // no updates
             keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -120,6 +124,7 @@
             fingerprintPropertyRepository.supportsUdfps()
             biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
             val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+            runCurrent()
 
             // no updates
             keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt
index 9729022..c55c27c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -54,6 +55,7 @@
             fingerprintPropertyRepository.supportsUdfps()
             val deviceEntryBackgroundViewAlpha by
                 collectLastValue(underTest.deviceEntryBackgroundViewAlpha)
+            runCurrent()
 
             // immediately 0f
             keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -75,6 +77,7 @@
             fingerprintPropertyRepository.supportsUdfps()
             biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
             val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+            runCurrent()
 
             keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
 
@@ -92,6 +95,7 @@
             fingerprintPropertyRepository.supportsRearFps()
             biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
             val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+            runCurrent()
 
             // animation doesn't start until the end
             keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -116,6 +120,7 @@
             fingerprintPropertyRepository.supportsUdfps()
             biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
             val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+            runCurrent()
 
             keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
             assertThat(deviceEntryParentViewAlpha).isNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
index 2c6436e..0796af0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -71,6 +72,7 @@
         testScope.runTest {
             fingerprintPropertyRepository.supportsUdfps()
             val bgViewAlpha by collectLastValue(underTest.deviceEntryBackgroundViewAlpha)
+            runCurrent()
 
             // immediately 1f
             keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index 2f35380..5996502 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -64,8 +64,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bluetooth.BroadcastDialogController
 import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.media.controls.MediaTestUtils
 import com.android.systemui.media.controls.models.GutsViewHolder
 import com.android.systemui.media.controls.models.player.MediaAction
@@ -227,11 +225,6 @@
     @Mock private lateinit var recProgressBar2: SeekBar
     @Mock private lateinit var recProgressBar3: SeekBar
     private var shouldShowBroadcastButton: Boolean = false
-    private val fakeFeatureFlag =
-        FakeFeatureFlags().apply {
-            this.set(Flags.UMO_SURFACE_RIPPLE, false)
-            this.set(Flags.UMO_TURBULENCE_NOISE, false)
-        }
     @Mock private lateinit var globalSettings: GlobalSettings
     @Mock private lateinit var mediaFlags: MediaFlags
 
@@ -275,7 +268,6 @@
                     activityIntentHelper,
                     lockscreenUserManager,
                     broadcastDialogController,
-                    fakeFeatureFlag,
                     globalSettings,
                     mediaFlags,
                 ) {
@@ -2397,8 +2389,7 @@
     }
 
     @Test
-    fun onButtonClick_touchRippleFlagEnabled_playsTouchRipple() {
-        fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true)
+    fun onButtonClick_playsTouchRipple() {
         val semanticActions =
             MediaButton(
                 playOrPause =
@@ -2419,31 +2410,7 @@
     }
 
     @Test
-    fun onButtonClick_touchRippleFlagDisabled_doesNotPlayTouchRipple() {
-        fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, false)
-        val semanticActions =
-            MediaButton(
-                playOrPause =
-                    MediaAction(
-                        icon = null,
-                        action = {},
-                        contentDescription = "play",
-                        background = null
-                    )
-            )
-        val data = mediaData.copy(semanticActions = semanticActions)
-        player.attachPlayer(viewHolder)
-        player.bindPlayer(data, KEY)
-
-        viewHolder.actionPlayPause.callOnClick()
-
-        assertThat(viewHolder.multiRippleView.ripples.size).isEqualTo(0)
-    }
-
-    @Test
     fun playTurbulenceNoise_finishesAfterDuration() {
-        fakeFeatureFlag.set(Flags.UMO_TURBULENCE_NOISE, true)
-
         val semanticActions =
             MediaButton(
                 playOrPause =
@@ -2474,8 +2441,6 @@
 
     @Test
     fun playTurbulenceNoise_whenPlaybackStateIsNotPlaying_doesNotPlayTurbulence() {
-        fakeFeatureFlag.set(Flags.UMO_TURBULENCE_NOISE, true)
-
         val semanticActions =
             MediaButton(
                 custom0 =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index 73885f4..ba7927d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -25,8 +25,7 @@
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardViewController
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
 import com.android.systemui.dreams.DreamOverlayStateController
@@ -43,6 +42,7 @@
 import com.android.systemui.statusbar.policy.FakeConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.animation.UniqueObjectHostView
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
@@ -79,6 +79,8 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class MediaHierarchyManagerTest : SysuiTestCase() {
 
+    private val kosmos = testKosmos()
+
     @Mock private lateinit var lockHost: MediaHost
     @Mock private lateinit var qsHost: MediaHost
     @Mock private lateinit var qqsHost: MediaHost
@@ -104,18 +106,16 @@
     private lateinit var dreamOverlayCallback:
         ArgumentCaptor<(DreamOverlayStateController.Callback)>
     @JvmField @Rule val mockito = MockitoJUnit.rule()
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
     private lateinit var mediaHierarchyManager: MediaHierarchyManager
     private lateinit var isQsBypassingShade: MutableStateFlow<Boolean>
     private lateinit var mediaFrame: ViewGroup
     private val configurationController = FakeConfigurationController()
-    private val communalRepository = FakeCommunalRepository(isCommunalEnabled = true)
-    private val communalInteractor =
-        CommunalInteractorFactory.create(communalRepository = communalRepository).communalInteractor
+    private val communalInteractor = kosmos.communalInteractor
     private val settings = FakeSettings()
     private lateinit var testableLooper: TestableLooper
     private lateinit var fakeHandler: FakeHandler
-    private val testDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
 
     @Before
     fun setup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index 0a464e6..b701d7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -21,6 +21,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
+import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.media.controls.models.player.MediaViewHolder
@@ -171,6 +172,38 @@
     }
 
     @Test
+    fun testObtainViewState_expandedMatchesParentHeight() {
+        mediaViewController.attach(player, MediaViewController.TYPE.PLAYER)
+        player.measureState =
+            TransitionViewState().apply {
+                this.height = 100
+                this.measureHeight = 100
+            }
+        mediaHostStateHolder.expandedMatchesParentHeight = true
+        mediaHostStateHolder.expansion = 1f
+        mediaHostStateHolder.measurementInput =
+            MeasurementInput(
+                View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY),
+            )
+
+        // Assign the height of each expanded layout
+        MediaViewHolder.backgroundIds.forEach { id ->
+            mediaViewController.expandedLayout.getConstraint(id).layout.mHeight = 100
+        }
+
+        mediaViewController.obtainViewState(mediaHostStateHolder)
+
+        // Verify height of each expanded layout is updated to match constraint
+        MediaViewHolder.backgroundIds.forEach { id ->
+            assertTrue(
+                mediaViewController.expandedLayout.getConstraint(id).layout.mHeight ==
+                    ConstraintSet.MATCH_CONSTRAINT
+            )
+        }
+    }
+
+    @Test
     fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forMediaPlayer() {
         whenever(mockViewState.copy()).thenReturn(mockCopiedState)
         whenever(mockCopiedState.widgetStates)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 72847a6..c6cfabc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -414,6 +414,18 @@
 
     @Test
     public void onDeviceListUpdate_verifyDeviceListCallback() {
+        // This test relies on mMediaOutputController.start being called while the selected device
+        // list has exactly one item, and that item's id is:
+        // - Different from both ids in mMediaDevices.
+        // - Different from the id of the route published by the device under test (usually the
+        //   built-in speakers).
+        // So mock the selected device to respect these two preconditions.
+        MediaDevice mockSelectedMediaDevice = Mockito.mock(MediaDevice.class);
+        when(mockSelectedMediaDevice.getId()).thenReturn(TEST_DEVICE_3_ID);
+        doReturn(List.of(mockSelectedMediaDevice))
+                .when(mLocalMediaManager)
+                .getSelectedMediaDevice();
+
         mMediaOutputController.start(mCb);
         reset(mCb);
 
@@ -434,6 +446,18 @@
 
     @Test
     public void advanced_onDeviceListUpdateWithConnectedDeviceRemote_verifyItemSize() {
+        // This test relies on mMediaOutputController.start being called while the selected device
+        // list has exactly one item, and that item's id is:
+        // - Different from both ids in mMediaDevices.
+        // - Different from the id of the route published by the device under test (usually the
+        //   built-in speakers).
+        // So mock the selected device to respect these two preconditions.
+        MediaDevice mockSelectedMediaDevice = Mockito.mock(MediaDevice.class);
+        when(mockSelectedMediaDevice.getId()).thenReturn(TEST_DEVICE_3_ID);
+        doReturn(List.of(mockSelectedMediaDevice))
+                .when(mLocalMediaManager)
+                .getSelectedMediaDevice();
+
         when(mMediaDevice1.getFeatures()).thenReturn(
                 ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK));
         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
index 45f0a8c..44c411f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
@@ -66,6 +66,11 @@
 
         private const val DEFAULT_PACKAGE_NAME = "com.media.projection.test"
         private val DEFAULT_USER_HANDLE = UserHandle.getUserHandleForUid(UserHandle.myUserId())
-        private val DEFAULT_INFO = MediaProjectionInfo(DEFAULT_PACKAGE_NAME, DEFAULT_USER_HANDLE)
+        private val DEFAULT_INFO =
+            MediaProjectionInfo(
+                DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_HANDLE,
+                /* launchCookie = */ null
+            )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt
index f5a70f0..8e05410 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt
@@ -20,6 +20,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.settings.FakeDisplayTracker
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -29,7 +30,13 @@
 @RunWith(JUnit4::class)
 class SysUiStateExtTest : SysuiTestCase() {
 
-    private val underTest = SysUiState(FakeDisplayTracker(context))
+    private val kosmos = testKosmos()
+
+    private val underTest =
+        SysUiState(
+            FakeDisplayTracker(context),
+            kosmos.sceneContainerPlugin,
+        )
 
     @Test
     fun updateFlags() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java
index 1a93adc..f03f4f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java
@@ -26,6 +26,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.settings.FakeDisplayTracker;
 
 import org.junit.Before;
@@ -42,13 +43,15 @@
     private static final int FLAG_4 = 1 << 3;
     private static final int DISPLAY_ID = DEFAULT_DISPLAY;
 
+    private KosmosJavaAdapter mKosmos;
     private SysUiState.SysUiStateCallback mCallback;
     private SysUiState mFlagsContainer;
 
     @Before
     public void setup() {
         FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext);
-        mFlagsContainer = new SysUiState(displayTracker);
+        mKosmos = new KosmosJavaAdapter(this);
+        mFlagsContainer = new SysUiState(displayTracker, mKosmos.getSceneContainerPlugin());
         mCallback = mock(SysUiState.SysUiStateCallback.class);
         mFlagsContainer.addCallback(mCallback);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index 8d306cce..28d35ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -41,6 +41,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
@@ -56,6 +57,8 @@
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
+import dagger.Lazy;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -65,8 +68,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
-
-import dagger.Lazy;
+import java.util.concurrent.Executor;
 
 /**
  * Tests for {@link NavBarHelper}.
@@ -123,6 +125,8 @@
             SYSUI_STATE_A11Y_BUTTON_CLICKABLE | SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
     private NavBarHelper mNavBarHelper;
 
+    private final Executor mSynchronousExecutor = runnable -> runnable.run();
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -140,7 +144,8 @@
                 mSystemActions, mOverviewProxyService, mAssistManagerLazy,
                 () -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardStateController.class),
                 mNavigationModeController, mEdgeBackGestureHandlerFactory, mWm, mUserTracker,
-                mDisplayTracker, mNotificationShadeWindowController, mDumpManager, mCommandQueue);
+                mDisplayTracker, mNotificationShadeWindowController, mDumpManager, mCommandQueue,
+                mSynchronousExecutor);
 
     }
 
@@ -266,7 +271,8 @@
     @Test
     public void initNavBarHelper_buttonModeNavBar_a11yButtonClickableState() {
         when(mAccessibilityManager.getAccessibilityShortcutTargets(
-                AccessibilityManager.ACCESSIBILITY_BUTTON)).thenReturn(createFakeShortcutTargets());
+                ShortcutConstants.UserShortcutType.SOFTWARE)).thenReturn(
+                createFakeShortcutTargets());
 
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
 
@@ -291,7 +297,8 @@
                 ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
 
         when(mAccessibilityManager.getAccessibilityShortcutTargets(
-                AccessibilityManager.ACCESSIBILITY_BUTTON)).thenReturn(createFakeShortcutTargets());
+                ShortcutConstants.UserShortcutType.SOFTWARE)).thenReturn(
+                createFakeShortcutTargets());
         mAccessibilityServicesStateChangeListener.onAccessibilityServicesStateChanged(
                 mAccessibilityManager);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index ddceed6..db5bd9b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -228,6 +228,8 @@
     @Rule
     public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
 
+    private final Executor mSynchronousExecutor = runnable -> runnable.run();
+
     @Before
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -269,7 +271,7 @@
                     mEdgeBackGestureHandlerFactory, mock(IWindowManager.class),
                     mock(UserTracker.class), mock(DisplayTracker.class),
                     mNotificationShadeWindowController, mock(DumpManager.class),
-                    mock(CommandQueue.class)));
+                    mock(CommandQueue.class), mSynchronousExecutor));
             mNavigationBar = createNavBar(mContext);
             mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
         });
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
index f93d52b..aa54565 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -28,6 +28,7 @@
 import android.view.ViewConfiguration
 import android.view.WindowManager
 import androidx.test.filters.SmallTest
+import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.util.LatencyTracker
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.NavigationEdgeBackPlugin
@@ -40,8 +41,10 @@
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
+import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -59,12 +62,16 @@
     @Mock private lateinit var windowManager: WindowManager
     @Mock private lateinit var configurationController: ConfigurationController
     @Mock private lateinit var latencyTracker: LatencyTracker
+    @Mock private lateinit var interactionJankMonitor: InteractionJankMonitor
     @Mock private lateinit var layoutParams: WindowManager.LayoutParams
     @Mock private lateinit var backCallback: NavigationEdgeBackPlugin.BackCallback
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
+        `when`(interactionJankMonitor.begin(any(), anyInt())).thenReturn(true)
+        `when`(interactionJankMonitor.end(anyInt())).thenReturn(true)
+        `when`(interactionJankMonitor.cancel(anyInt())).thenReturn(true)
         mBackPanelController =
             BackPanelController(
                 context,
@@ -74,6 +81,7 @@
                 vibratorHelper,
                 configurationController,
                 latencyTracker,
+                interactionJankMonitor,
             )
         mBackPanelController.setLayoutParams(layoutParams)
         mBackPanelController.setBackCallback(backCallback)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
index e4432f3..0636831 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
@@ -30,13 +30,11 @@
 import android.permission.PermissionGroupUsage
 import android.permission.PermissionManager
 import android.testing.AndroidTestingRunner
-import android.view.View
 import android.widget.LinearLayout
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
-import com.android.systemui.animation.LaunchableView
 import com.android.systemui.appops.AppOpsController
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.privacy.logging.PrivacyLogger
@@ -206,10 +204,7 @@
     @Test
     fun testShowDialogShowsDialogWithView() {
         val parent = LinearLayout(context)
-        val view =
-            object : View(context), LaunchableView {
-                override fun setShouldBlockVisibilityChanges(block: Boolean) {}
-            }
+        val view = OngoingPrivacyChip(context)
         parent.addView(view)
         val usage = createMockPermGroupUsage()
         `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
index fa02e8c..f98b68f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
@@ -173,7 +173,7 @@
         captor.value.onClick(privacyChip)
         verify(privacyDialogController).showDialog(any(Context::class.java))
         verify(privacyDialogControllerV2, never())
-            .showDialog(any(Context::class.java), any(View::class.java))
+            .showDialog(any(Context::class.java), any(OngoingPrivacyChip::class.java))
     }
 
     @Test
@@ -186,7 +186,7 @@
         captor.value.onClick(privacyChip)
         verify(privacyDialogController).showDialog(any(Context::class.java))
         verify(privacyDialogControllerV2, never())
-                .showDialog(any(Context::class.java), any(View::class.java))
+                .showDialog(any(Context::class.java), any(OngoingPrivacyChip::class.java))
     }
 
     @Test
@@ -207,7 +207,7 @@
         captor.value.onClick(privacyChip)
         verify(privacyDialogController, never()).showDialog(any(Context::class.java))
         verify(privacyDialogControllerV2, never())
-            .showDialog(any(Context::class.java), any(View::class.java))
+            .showDialog(any(Context::class.java), any(OngoingPrivacyChip::class.java))
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt
new file mode 100644
index 0000000..40eccad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.testing.AndroidTestingRunner
+import android.view.KeyEvent
+import android.view.KeyEvent.KEYCODE_DPAD_LEFT
+import android.view.View
+import androidx.core.util.Consumer
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LeftRightArrowPressedListenerTest : SysuiTestCase() {
+
+    private lateinit var underTest: LeftRightArrowPressedListener
+    private val callback =
+        object : Consumer<Int> {
+            var lastValue: Int? = null
+
+            override fun accept(keyCode: Int) {
+                lastValue = keyCode
+            }
+        }
+
+    private val view = View(context)
+
+    @Before
+    fun setUp() {
+        underTest = LeftRightArrowPressedListener.createAndRegisterListenerForView(view)
+        underTest.setArrowKeyPressedListener(callback)
+    }
+
+    @Test
+    fun shouldTriggerCallback_whenArrowUpReceived_afterArrowDownReceived() {
+        underTest.sendKey(KeyEvent.ACTION_DOWN, KEYCODE_DPAD_LEFT)
+
+        underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT)
+
+        assertThat(callback.lastValue).isEqualTo(KEYCODE_DPAD_LEFT)
+    }
+
+    @Test
+    fun shouldNotTriggerCallback_whenKeyUpReceived_ifKeyDownNotReceived() {
+        underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT)
+
+        assertThat(callback.lastValue).isNull()
+    }
+
+    @Test
+    fun shouldNotTriggerCallback_whenKeyUpReceived_ifKeyDownWasRepeated() {
+        underTest.sendKeyWithRepeat(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT, repeat = 2)
+        underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT)
+
+        assertThat(callback.lastValue).isNull()
+    }
+
+    @Test
+    fun shouldNotTriggerCallback_whenKeyUpReceived_ifKeyDownReceivedBeforeLosingFocus() {
+        underTest.sendKey(KeyEvent.ACTION_DOWN, KEYCODE_DPAD_LEFT)
+        underTest.onFocusChange(view, hasFocus = false)
+        underTest.onFocusChange(view, hasFocus = true)
+
+        underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT)
+
+        assertThat(callback.lastValue).isNull()
+    }
+
+    private fun LeftRightArrowPressedListener.sendKey(action: Int, keyCode: Int) {
+        onKey(view, keyCode, KeyEvent(action, keyCode))
+    }
+
+    private fun LeftRightArrowPressedListener.sendKeyWithRepeat(
+        action: Int,
+        keyCode: Int,
+        repeat: Int
+    ) {
+        val keyEvent =
+            KeyEvent(
+                /* downTime= */ 0L,
+                /* eventTime= */ 0L,
+                /* action= */ action,
+                /* code= */ KEYCODE_DPAD_LEFT,
+                /* repeat= */ repeat
+            )
+        onKey(view, keyCode, keyEvent)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
index db9e548..8ef3f57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
@@ -2,11 +2,13 @@
 
 import android.content.Context
 import android.testing.AndroidTestingRunner
-import android.view.KeyEvent
 import android.view.View
 import android.widget.Scroller
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.PageIndicator.PageScrollActionListener
+import com.android.systemui.qs.PageIndicator.PageScrollActionListener.LEFT
+import com.android.systemui.qs.PageIndicator.PageScrollActionListener.RIGHT
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -22,7 +24,7 @@
 class PagedTileLayoutTest : SysuiTestCase() {
 
     @Mock private lateinit var pageIndicator: PageIndicator
-    @Captor private lateinit var captor: ArgumentCaptor<View.OnKeyListener>
+    @Captor private lateinit var captor: ArgumentCaptor<PageScrollActionListener>
 
     private lateinit var pageTileLayout: TestPagedTileLayout
     private lateinit var scroller: Scroller
@@ -32,7 +34,7 @@
         MockitoAnnotations.initMocks(this)
         pageTileLayout = TestPagedTileLayout(mContext)
         pageTileLayout.setPageIndicator(pageIndicator)
-        verify(pageIndicator).setOnKeyListener(captor.capture())
+        verify(pageIndicator).setPageScrollActionListener(captor.capture())
         setViewWidth(pageTileLayout, width = PAGE_WIDTH)
         scroller = pageTileLayout.mScroller
     }
@@ -43,28 +45,27 @@
     }
 
     @Test
-    fun scrollsRight_afterRightArrowPressed_whenFocusOnPagerIndicator() {
+    fun scrollsRight_afterRightScrollActionTriggered() {
         pageTileLayout.currentPageIndex = 0
 
-        sendUpEvent(KeyEvent.KEYCODE_DPAD_RIGHT)
+        sendScrollActionEvent(RIGHT)
 
         assertThat(scroller.isFinished).isFalse() // aka we're scrolling
         assertThat(scroller.finalX).isEqualTo(scroller.currX + PAGE_WIDTH)
     }
 
     @Test
-    fun scrollsLeft_afterLeftArrowPressed_whenFocusOnPagerIndicator() {
+    fun scrollsLeft_afterLeftScrollActionTriggered() {
         pageTileLayout.currentPageIndex = 1 // we won't scroll left if we're on the first page
 
-        sendUpEvent(KeyEvent.KEYCODE_DPAD_LEFT)
+        sendScrollActionEvent(LEFT)
 
         assertThat(scroller.isFinished).isFalse() // aka we're scrolling
         assertThat(scroller.finalX).isEqualTo(scroller.currX - PAGE_WIDTH)
     }
 
-    private fun sendUpEvent(keyCode: Int) {
-        val event = KeyEvent(KeyEvent.ACTION_UP, keyCode)
-        captor.value.onKey(pageIndicator, keyCode, event)
+    private fun sendScrollActionEvent(@PageScrollActionListener.Direction direction: Int) {
+        captor.value.onScrollActionTriggered(direction)
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
index c8c134a..563a3fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
@@ -35,6 +35,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
@@ -47,6 +48,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.FrameLayout;
 
 import androidx.lifecycle.Lifecycle;
 import androidx.test.filters.SmallTest;
@@ -63,6 +65,7 @@
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.CommandQueue;
@@ -111,7 +114,8 @@
     @Mock private FooterActionsViewBinder mFooterActionsViewBinder;
     @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
     @Mock private FeatureFlagsClassic mFeatureFlags;
-    private View mQsView;
+    @Mock private SceneContainerFlags mSceneContainerFlags;
+    private ViewGroup mQsView;
 
     private final CommandQueue mCommandQueue =
             new CommandQueue(mContext, new FakeDisplayTracker(mContext));
@@ -121,6 +125,9 @@
 
     @Before
     public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mSceneContainerFlags.isEnabled()).thenReturn(false);
+
         mUnderTest = instantiate();
 
         mUnderTest.onComponentCreated(mQsComponent, null);
@@ -487,9 +494,24 @@
         verify(mQSAnimator).setOnKeyguard(true);
     }
 
-    private QSImpl instantiate() {
-        MockitoAnnotations.initMocks(this);
+    @Test
+    public void testSceneContainerFlagsEnabled_FooterActionsRemoved_controllerNotStarted() {
+        when(mSceneContainerFlags.isEnabled()).thenReturn(true);
+        clearInvocations(
+                mFooterActionsViewBinder, mFooterActionsViewModel, mFooterActionsViewModelFactory);
+        QSImpl other = instantiate();
 
+        other.onComponentCreated(mQsComponent, null);
+
+        assertThat((View) other.getView().findViewById(R.id.qs_footer_actions)).isNull();
+        verifyZeroInteractions(
+                mFooterActionsViewModel,
+                mFooterActionsViewBinder,
+                mFooterActionsViewModelFactory
+        );
+    }
+
+    private QSImpl instantiate() {
         setupQsComponent();
         setUpViews();
         setUpInflater();
@@ -514,7 +536,8 @@
                 mFooterActionsViewModelFactory,
                 mFooterActionsViewBinder,
                 mLargeScreenShadeInterpolator,
-                mFeatureFlags);
+                mFeatureFlags,
+                mSceneContainerFlags);
     }
 
     private void setUpOther() {
@@ -533,14 +556,23 @@
     }
 
     private void setUpViews() {
-        mQsView = spy(new View(mContext));
+        mQsView = spy(new FrameLayout(mContext));
         when(mQsComponent.getRootView()).thenReturn(mQsView);
-        when(mQsView.findViewById(R.id.expanded_qs_scroll_view))
+
+        when(mQSPanelScrollView.findViewById(R.id.expanded_qs_scroll_view))
                 .thenReturn(mQSPanelScrollView);
-        when(mQsView.findViewById(R.id.header)).thenReturn(mHeader);
-        when(mQsView.findViewById(android.R.id.edit)).thenReturn(new View(mContext));
-        when(mQsView.findViewById(R.id.qs_footer_actions)).thenAnswer(
-                invocation -> new FooterActionsViewBinder().create(mContext));
+        mQsView.addView(mQSPanelScrollView);
+
+        when(mHeader.findViewById(R.id.header)).thenReturn(mHeader);
+        mQsView.addView(mHeader);
+
+        View customizer = new View(mContext);
+        customizer.setId(android.R.id.edit);
+        mQsView.addView(customizer);
+
+        View footerActionsView = new FooterActionsViewBinder().create(mContext);
+        footerActionsView.setId(R.id.qs_footer_actions);
+        mQsView.addView(footerActionsView);
     }
 
     private void setUpInflater() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java
index 6cad985..6e2f5db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java
@@ -31,8 +31,8 @@
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -136,6 +136,7 @@
         AccessibilityNodeInfoCompat.AccessibilityActionCompat action =
                 getActionForId(mInfo, AccessibilityNodeInfo.ACTION_CLICK);
         assertThat(action.getLabel().toString()).contains(expectedString);
+        assertThat(mInfo.isClickable()).isTrue();
     }
 
     @Test
@@ -152,10 +153,11 @@
         AccessibilityNodeInfoCompat.AccessibilityActionCompat action =
                 getActionForId(mInfo, AccessibilityNodeInfo.ACTION_CLICK);
         assertThat(action.getLabel().toString()).contains(expectedString);
+        assertThat(mInfo.isClickable()).isTrue();
     }
 
     @Test
-    public void testNoClickAction() {
+    public void testNoClickActionAndNotClickable() {
         mView.setTag(mHolder);
         when(mHolder.canTakeAccessibleAction()).thenReturn(true);
         when(mHolder.canAdd()).thenReturn(false);
@@ -167,6 +169,7 @@
         AccessibilityNodeInfoCompat.AccessibilityActionCompat action =
                 getActionForId(mInfo, AccessibilityNodeInfo.ACTION_CLICK);
         assertThat(action).isNull();
+        assertThat(mInfo.isClickable()).isFalse();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index c711806..1cb3bf6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -393,23 +393,23 @@
     }
 
     @Test
-    fun backgroundAlpha_inSplitShade_followsExpansion_with_0_99_delay() {
+    fun backgroundAlpha_inSplitShade_followsExpansion_with_0_15_delay() {
         val underTest = utils.footerActionsViewModel()
         val floatTolerance = 0.01f
 
         underTest.onQuickSettingsExpansionChanged(0f, isInSplitShade = true)
         assertThat(underTest.backgroundAlpha.value).isEqualTo(0f)
 
-        underTest.onQuickSettingsExpansionChanged(0.5f, isInSplitShade = true)
+        underTest.onQuickSettingsExpansionChanged(0.1f, isInSplitShade = true)
         assertThat(underTest.backgroundAlpha.value).isEqualTo(0f)
 
-        underTest.onQuickSettingsExpansionChanged(0.9f, isInSplitShade = true)
+        underTest.onQuickSettingsExpansionChanged(0.14f, isInSplitShade = true)
         assertThat(underTest.backgroundAlpha.value).isEqualTo(0f)
 
-        underTest.onQuickSettingsExpansionChanged(0.991f, isInSplitShade = true)
+        underTest.onQuickSettingsExpansionChanged(0.235f, isInSplitShade = true)
         assertThat(underTest.backgroundAlpha.value).isWithin(floatTolerance).of(0.1f)
 
-        underTest.onQuickSettingsExpansionChanged(0.995f, isInSplitShade = true)
+        underTest.onQuickSettingsExpansionChanged(0.575f, isInSplitShade = true)
         assertThat(underTest.backgroundAlpha.value).isWithin(floatTolerance).of(0.5f)
 
         underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
new file mode 100644
index 0000000..e8aa8f0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tileimpl
+
+import android.animation.AnimatorTestRule
+import android.content.Context
+import android.service.quicksettings.Tile
+import android.testing.AndroidTestingRunner
+import android.testing.UiThreadTest
+import android.view.ContextThemeWrapper
+import android.view.View
+import android.widget.ImageView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.connectivity.WifiIcons
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.Rule
+import org.junit.runner.RunWith
+
+/** Test for regression b/311121830 */
+@RunWith(AndroidTestingRunner::class)
+@UiThreadTest
+@SmallTest
+class QSIconViewImplTest_311121830 : SysuiTestCase() {
+
+    @get:Rule val animatorRule = AnimatorTestRule()
+
+    @Test
+    fun alwaysLastIcon() {
+        // Need to inflate with the correct theme so the colors can be retrieved and the animations
+        // are run
+        val iconView =
+            AnimateQSIconViewImpl(
+                ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
+            )
+
+        val initialState =
+            QSTile.State().apply {
+                state = Tile.STATE_INACTIVE
+                icon = QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_no_internet_available)
+            }
+        val firstState =
+            QSTile.State().apply {
+                state = Tile.STATE_ACTIVE
+                icon = QSTileImpl.ResourceIcon.get(WifiIcons.WIFI_NO_INTERNET_ICONS[4])
+            }
+        val secondState =
+            QSTile.State().apply {
+                state = Tile.STATE_ACTIVE
+                icon = QSTileImpl.ResourceIcon.get(WifiIcons.WIFI_FULL_ICONS[4])
+            }
+
+        // Start with the initial state
+        iconView.setIcon(initialState, /* allowAnimations= */ false)
+
+        // Set the first state to animate, and advance time to half the time of the animation
+        iconView.setIcon(firstState, /* allowAnimations= */ true)
+        animatorRule.advanceTimeBy(QSIconViewImpl.QS_ANIM_LENGTH / 2)
+
+        // Set the second state to animate (it shouldn't, because `State.state` is the same) and
+        // advance time to 2 animations length
+        iconView.setIcon(secondState, /* allowAnimations= */ true)
+        animatorRule.advanceTimeBy(QSIconViewImpl.QS_ANIM_LENGTH * 2)
+
+        assertThat(iconView.mLastIcon).isEqualTo(secondState.icon)
+    }
+
+    private class AnimateQSIconViewImpl(context: Context) : QSIconViewImpl(context) {
+        override fun createIcon(): View {
+            return object : ImageView(context) {
+                override fun isShown(): Boolean {
+                    return true
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index c7479fd5..1ed8c3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.recordissue.RecordIssueDialogDelegate
 import com.android.systemui.res.R
+import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -65,6 +66,7 @@
     @Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var dialogLauncherAnimator: DialogLaunchAnimator
+    @Mock private lateinit var userContextProvider: UserContextProvider
     @Mock private lateinit var delegateFactory: RecordIssueDialogDelegate.Factory
     @Mock private lateinit var dialogDelegate: RecordIssueDialogDelegate
     @Mock private lateinit var dialog: SystemUIDialog
@@ -94,6 +96,7 @@
                 keyguardDismissUtil,
                 keyguardStateController,
                 dialogLauncherAnimator,
+                userContextProvider,
                 delegateFactory,
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index b24b877..c0ef50f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -1069,6 +1069,22 @@
         assertThat(mInternetDialogController.mCallback).isNull();
     }
 
+    @Test
+    public void hasActiveSubId_activeSubIdListIsEmpty_returnFalse() {
+        when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{});
+        mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
+
+        assertThat(mInternetDialogController.hasActiveSubId()).isFalse();
+    }
+
+    @Test
+    public void hasActiveSubId_activeSubIdListNotEmpty_returnTrue() {
+        when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
+        mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
+
+        assertThat(mInternetDialogController.hasActiveSubId()).isTrue();
+    }
+
     private String getResourcesString(String name) {
         return mContext.getResources().getString(getResourcesId(name));
     }
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 33066d2..9563ceb 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
@@ -162,13 +162,15 @@
 
     @Test
     fun testStartSettingsActivity_activityLaunched_dialogDismissed() {
-        `when`(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
-        bluetoothTileDialogViewModel.showDialog(context, null)
+        testScope.runTest {
+            `when`(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
+            bluetoothTileDialogViewModel.showDialog(context, null)
 
-        val clickedView = View(context)
-        bluetoothTileDialogViewModel.onPairNewDeviceClicked(clickedView)
+            val clickedView = View(context)
+            bluetoothTileDialogViewModel.onPairNewDeviceClicked(clickedView)
 
-        verify(uiEventLogger).log(BluetoothTileDialogUiEvent.PAIR_NEW_DEVICE_CLICKED)
-        verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt(), nullable())
+            verify(uiEventLogger).log(BluetoothTileDialogUiEvent.PAIR_NEW_DEVICE_CLICKED)
+            verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt(), nullable())
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index 70a48f5..10d6ebf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -17,16 +17,21 @@
 package com.android.systemui.recents
 
 import android.content.ComponentName
+import android.content.Context
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
 import android.os.PowerManager
+import android.os.Process;
+import android.os.UserHandle
 import android.testing.AndroidTestingRunner
 import android.testing.TestableContext
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
 import com.android.internal.app.AssistUtils
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FakeFeatureFlags
@@ -35,6 +40,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager
 import com.android.systemui.model.SysUiState
+import com.android.systemui.model.sceneContainerPlugin
 import com.android.systemui.navigationbar.NavigationBarController
 import com.android.systemui.navigationbar.NavigationModeController
 import com.android.systemui.recents.OverviewProxyService.ACTION_QUICKSTEP
@@ -50,6 +56,7 @@
 import com.android.systemui.shared.system.QuickStepContract.WAKEFULNESS_WAKING
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.testKosmos
 import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -66,10 +73,14 @@
 import org.mockito.Mock
 import org.mockito.Mockito.any
 import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.atLeast
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.intThat
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -79,11 +90,12 @@
 
     @Main private val executor: Executor = MoreExecutors.directExecutor()
 
+    private val kosmos = testKosmos()
     private lateinit var subject: OverviewProxyService
-    private val dumpManager = DumpManager()
+    @Mock private val dumpManager = DumpManager()
     private val displayTracker = FakeDisplayTracker(mContext)
     private val fakeSystemClock = FakeSystemClock()
-    private val sysUiState = SysUiState(displayTracker)
+    private val sysUiState = SysUiState(displayTracker, kosmos.sceneContainerPlugin)
     private val featureFlags = FakeFeatureFlags()
     private val wakefulnessLifecycle =
         WakefulnessLifecycle(mContext, null, fakeSystemClock, dumpManager)
@@ -109,6 +121,8 @@
     @Mock
     private lateinit var unfoldTransitionProgressForwarder:
         Optional<UnfoldTransitionProgressForwarder>
+    @Mock
+    private lateinit var broadcastDispatcher: BroadcastDispatcher
 
     @Before
     fun setUp() {
@@ -131,32 +145,11 @@
         whenever(packageManager.resolveServiceAsUser(any(), anyInt(), anyInt()))
             .thenReturn(mock(ResolveInfo::class.java))
 
-        featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
-        subject =
-            OverviewProxyService(
-                context,
-                executor,
-                commandQueue,
-                shellInterface,
-                { navBarController },
-                { shadeViewController },
-                screenPinningRequest,
-                navModeController,
-                statusBarWinController,
-                sysUiState,
-                mock(),
-                userTracker,
-                wakefulnessLifecycle,
-                uiEventLogger,
-                displayTracker,
-                sysuiUnlockAnimationController,
-                inWindowLauncherUnlockAnimationManager,
-                assistUtils,
-                featureFlags,
-                FakeSceneContainerFlags(),
-                dumpManager,
-                unfoldTransitionProgressForwarder
-            )
+        mSetFlagsRule.disableFlags(
+            com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+        )
+
+        subject = createOverviewProxyService(context)
     }
 
     @After
@@ -209,4 +202,66 @@
                 intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP }
             )
     }
+
+    @Test
+    fun connectToOverviewService_primaryUser_expectBindService() {
+        val mockitoSession = ExtendedMockito.mockitoSession()
+                .spyStatic(Process::class.java)
+                .startMocking()
+        try {
+            `when`(Process.myUserHandle()).thenReturn(UserHandle.SYSTEM)
+            val spyContext = spy(context)
+            val ops = createOverviewProxyService(spyContext)
+            ops.startConnectionToCurrentUser()
+            verify(spyContext, atLeast(1)).bindServiceAsUser(any(), any(),
+                anyInt(), any())
+        } finally {
+            mockitoSession.finishMocking()
+        }
+    }
+
+    @Test
+    fun connectToOverviewService_nonPrimaryUser_expectNoBindService() {
+        val mockitoSession = ExtendedMockito.mockitoSession()
+                .spyStatic(Process::class.java)
+                .startMocking()
+        try {
+            `when`(Process.myUserHandle()).thenReturn(UserHandle.of(12345))
+            val spyContext = spy(context)
+            val ops = createOverviewProxyService(spyContext)
+            ops.startConnectionToCurrentUser()
+            verify(spyContext, times(0)).bindServiceAsUser(any(), any(),
+                anyInt(), any())
+        } finally {
+            mockitoSession.finishMocking()
+        }
+    }
+
+    private fun createOverviewProxyService(ctx: Context) : OverviewProxyService {
+        return OverviewProxyService(
+            ctx,
+            executor,
+            commandQueue,
+            shellInterface,
+            { navBarController },
+            { shadeViewController },
+            screenPinningRequest,
+            navModeController,
+            statusBarWinController,
+            sysUiState,
+            mock(),
+            userTracker,
+            wakefulnessLifecycle,
+            uiEventLogger,
+            displayTracker,
+            sysuiUnlockAnimationController,
+            inWindowLauncherUnlockAnimationManager,
+            assistUtils,
+            featureFlags,
+            FakeSceneContainerFlags(),
+            dumpManager,
+            unfoldTransitionProgressForwarder,
+            broadcastDispatcher
+        )
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
index 9941661..543f6c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
@@ -16,17 +16,13 @@
 
 package com.android.systemui.scene.shared.flag
 
+import android.platform.test.annotations.DisableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.compose.ComposeFacade
-import com.android.systemui.flags.Flags
-import com.android.systemui.flags.setFlagValue
-import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
-import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
+import com.android.systemui.flags.EnableSceneContainer
 import com.google.common.truth.Truth
-import org.junit.Assume
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -34,45 +30,17 @@
 @RunWith(AndroidJUnit4::class)
 internal class SceneContainerFlagsTest : SysuiTestCase() {
 
-    @Before
-    fun setUp() {
-        // TODO(b/283300105): remove this reflection setting once the hard-coded
-        //  Flags.SCENE_CONTAINER_ENABLED is no longer needed.
-        val field = Flags::class.java.getField("SCENE_CONTAINER_ENABLED")
-        field.isAccessible = true
-        field.set(null, true) // note: this does not work with multivalent tests
-    }
-
-    private fun setAconfigFlagsEnabled(enabled: Boolean) {
-        listOf(
-                com.android.systemui.Flags.FLAG_SCENE_CONTAINER,
-                com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
-                KeyguardShadeMigrationNssl.FLAG_NAME,
-                MediaInSceneContainerFlag.FLAG_NAME,
-            )
-            .forEach { flagName -> mSetFlagsRule.setFlagValue(flagName, enabled) }
-    }
-
     @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER)
     fun isNotEnabled_withoutAconfigFlags() {
-        setAconfigFlagsEnabled(false)
         Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(false)
         Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(false)
     }
 
     @Test
-    fun isEnabled_withAconfigFlags_withCompose() {
-        Assume.assumeTrue(ComposeFacade.isComposeAvailable())
-        setAconfigFlagsEnabled(true)
+    @EnableSceneContainer
+    fun isEnabled_withAconfigFlags() {
         Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(true)
         Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(true)
     }
-
-    @Test
-    fun isNotEnabled_withAconfigFlags_withoutCompose() {
-        Assume.assumeFalse(ComposeFacade.isComposeAvailable())
-        setAconfigFlagsEnabled(true)
-        Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(false)
-        Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(false)
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index a2aed98..9ce77e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -28,10 +28,10 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.app.ActivityOptions.LaunchCookie;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.Intent;
-import android.os.Binder;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -146,7 +146,7 @@
 
     @Test
     public void testLogStartPartialRecording() {
-        MediaProjectionCaptureTarget target = new MediaProjectionCaptureTarget(new Binder());
+        MediaProjectionCaptureTarget target = new MediaProjectionCaptureTarget(new LaunchCookie());
         Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0, false, target);
         mRecordingService.onStartCommand(startIntent, 0, 0);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 5569ca9..a6e240b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -16,22 +16,26 @@
 
 package com.android.systemui.shade
 
+import android.os.PowerManager
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.ViewUtils
 import android.view.MotionEvent
 import android.view.View
+import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -43,16 +47,22 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 class GlanceableHubContainerControllerTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
     @Mock private lateinit var communalViewModel: CommunalViewModel
     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock private lateinit var shadeInteractor: ShadeInteractor
+    @Mock private lateinit var powerManager: PowerManager
 
+    private lateinit var parentView: FrameLayout
     private lateinit var containerView: View
     private lateinit var testableLooper: TestableLooper
 
@@ -67,16 +77,16 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        val withDeps = CommunalInteractorFactory.create()
-        communalInteractor = withDeps.communalInteractor
-        communalRepository = withDeps.communalRepository
+        communalInteractor = kosmos.communalInteractor
+        communalRepository = kosmos.fakeCommunalRepository
 
         underTest =
             GlanceableHubContainerController(
                 communalInteractor,
                 communalViewModel,
                 keyguardTransitionInteractor,
-                shadeInteractor
+                shadeInteractor,
+                powerManager
             )
         testableLooper = TestableLooper.get(this)
 
@@ -86,18 +96,23 @@
             .thenReturn(bouncerShowingFlow)
         whenever(shadeInteractor.isAnyFullyExpanded).thenReturn(shadeShowingFlow)
 
-        overrideResource(R.dimen.communal_grid_gutter_size, SWIPE_REGION_WIDTH)
+        overrideResource(R.dimen.communal_right_edge_swipe_region_width, RIGHT_SWIPE_REGION_WIDTH)
+        overrideResource(R.dimen.communal_top_edge_swipe_region_height, TOP_SWIPE_REGION_WIDTH)
+        overrideResource(
+            R.dimen.communal_bottom_edge_swipe_region_height,
+            BOTTOM_SWIPE_REGION_WIDTH
+        )
     }
 
     @Test
-    fun isEnabled_interactorEnabled_returnsTrue() {
+    fun isEnabled_interactorEnabled_interceptsTouches() {
         communalRepository.setIsCommunalEnabled(true)
 
         assertThat(underTest.isEnabled()).isTrue()
     }
 
     @Test
-    fun isEnabled_interactorDisabled_returnsFalse() {
+    fun isEnabled_interactorDisabled_doesNotIntercept() {
         communalRepository.setIsCommunalEnabled(false)
 
         assertThat(underTest.isEnabled()).isFalse()
@@ -120,18 +135,18 @@
     }
 
     @Test
-    fun onTouchEvent_touchInsideGestureRegion_returnsTrue() {
+    fun onTouchEvent_touchInsideGestureRegion_interceptsTouches() {
         // Communal is open.
         communalRepository.setDesiredScene(CommunalSceneKey.Communal)
 
         initAndAttachContainerView()
 
         // Touch events are intercepted.
-        assertThat(underTest.onTouchEvent(DOWN_IN_SWIPE_REGION_EVENT)).isTrue()
+        assertThat(underTest.onTouchEvent(DOWN_IN_RIGHT_SWIPE_REGION_EVENT)).isTrue()
     }
 
     @Test
-    fun onTouchEvent_subsequentTouchesAfterGestureStart_returnsTrue() {
+    fun onTouchEvent_subsequentTouchesAfterGestureStart_interceptsTouches() {
         // Communal is open.
         communalRepository.setDesiredScene(CommunalSceneKey.Communal)
 
@@ -139,14 +154,14 @@
 
         // Initial touch down is intercepted, and so are touches outside of the region, until an up
         // event is received.
-        assertThat(underTest.onTouchEvent(DOWN_IN_SWIPE_REGION_EVENT)).isTrue()
+        assertThat(underTest.onTouchEvent(DOWN_IN_RIGHT_SWIPE_REGION_EVENT)).isTrue()
         assertThat(underTest.onTouchEvent(MOVE_EVENT)).isTrue()
         assertThat(underTest.onTouchEvent(UP_EVENT)).isTrue()
         assertThat(underTest.onTouchEvent(MOVE_EVENT)).isFalse()
     }
 
     @Test
-    fun onTouchEvent_communalOpen_returnsTrue() {
+    fun onTouchEvent_communalOpen_interceptsTouches() {
         // Communal is open.
         communalRepository.setDesiredScene(CommunalSceneKey.Communal)
 
@@ -155,10 +170,34 @@
 
         // Touch events are intercepted.
         assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+        // User activity sent to PowerManager.
+        verify(powerManager).userActivity(any(), any(), any())
     }
 
     @Test
-    fun onTouchEvent_communalAndBouncerShowing_returnsFalse() {
+    fun onTouchEvent_topSwipeWhenHubOpen_returnsFalse() {
+        // Communal is open.
+        communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+
+        initAndAttachContainerView()
+
+        // Touch event in the top swipe reqgion is not intercepted.
+        assertThat(underTest.onTouchEvent(DOWN_IN_TOP_SWIPE_REGION_EVENT)).isFalse()
+    }
+
+    @Test
+    fun onTouchEvent_bottomSwipeWhenHubOpen_returnsFalse() {
+        // Communal is open.
+        communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+
+        initAndAttachContainerView()
+
+        // Touch event in the bottom swipe reqgion is not intercepted.
+        assertThat(underTest.onTouchEvent(DOWN_IN_BOTTOM_SWIPE_REGION_EVENT)).isFalse()
+    }
+
+    @Test
+    fun onTouchEvent_communalAndBouncerShowing_doesNotIntercept() {
         // Communal is open.
         communalRepository.setDesiredScene(CommunalSceneKey.Communal)
 
@@ -170,10 +209,12 @@
 
         // Touch events are not intercepted.
         assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+        // User activity is not sent to PowerManager.
+        verify(powerManager, times(0)).userActivity(any(), any(), any())
     }
 
     @Test
-    fun onTouchEvent_communalAndShadeShowing_returnsFalse() {
+    fun onTouchEvent_communalAndShadeShowing_doesNotIntercept() {
         // Communal is open.
         communalRepository.setDesiredScene(CommunalSceneKey.Communal)
 
@@ -186,26 +227,70 @@
         assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
     }
 
+    @Test
+    fun onTouchEvent_containerViewDisposed_doesNotIntercept() {
+        // Communal is open.
+        communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+
+        initAndAttachContainerView()
+        testableLooper.processAllMessages()
+
+        // Touch events are intercepted.
+        assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+
+        // Container view disposed.
+        underTest.disposeView()
+
+        // Touch events are not intercepted.
+        assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+    }
+
     private fun initAndAttachContainerView() {
         containerView = View(context)
+
+        parentView = FrameLayout(context)
+        parentView.addView(containerView)
+
         // Make view clickable so that dispatchTouchEvent returns true.
         containerView.isClickable = true
 
         underTest.initView(containerView)
         // Attach the view so that flows start collecting.
-        ViewUtils.attachView(containerView)
+        ViewUtils.attachView(parentView)
         // Give the view a size so that determining if a touch starts at the right edge works.
+        parentView.layout(0, 0, CONTAINER_WIDTH, CONTAINER_HEIGHT)
         containerView.layout(0, 0, CONTAINER_WIDTH, CONTAINER_HEIGHT)
     }
 
     companion object {
         private const val CONTAINER_WIDTH = 100
         private const val CONTAINER_HEIGHT = 100
-        private const val SWIPE_REGION_WIDTH = 20
+        private const val RIGHT_SWIPE_REGION_WIDTH = 20
+        private const val TOP_SWIPE_REGION_WIDTH = 20
+        private const val BOTTOM_SWIPE_REGION_WIDTH = 20
 
-        private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
-        private val DOWN_IN_SWIPE_REGION_EVENT =
+        private val DOWN_EVENT =
+            MotionEvent.obtain(
+                0L,
+                0L,
+                MotionEvent.ACTION_DOWN,
+                CONTAINER_WIDTH.toFloat(),
+                CONTAINER_HEIGHT.toFloat(),
+                0
+            )
+        private val DOWN_IN_RIGHT_SWIPE_REGION_EVENT =
             MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, CONTAINER_WIDTH.toFloat(), 0f, 0)
+        private val DOWN_IN_TOP_SWIPE_REGION_EVENT =
+            MotionEvent.obtain(
+                0L,
+                0L,
+                MotionEvent.ACTION_DOWN,
+                0f,
+                TOP_SWIPE_REGION_WIDTH.toFloat(),
+                0
+            )
+        private val DOWN_IN_BOTTOM_SWIPE_REGION_EVENT =
+            MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, CONTAINER_HEIGHT.toFloat(), 0)
         private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
         private val UP_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index a206581..b3e386e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -113,6 +113,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
@@ -125,7 +126,6 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.qs.QSFragmentLegacy;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.SceneTestUtils;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.shade.data.repository.FakeShadeRepository;
 import com.android.systemui.shade.data.repository.ShadeAnimationRepository;
@@ -181,7 +181,6 @@
 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
 import com.android.systemui.statusbar.phone.TapAgainViewController;
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -191,6 +190,7 @@
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
@@ -346,6 +346,7 @@
     @Mock protected ActiveNotificationsInteractor mActiveNotificationsInteractor;
     @Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm;
     @Mock private NaturalScrollingSettingObserver mNaturalScrollingSettingObserver;
+    @Mock private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
 
     protected final int mMaxUdfpsBurnInOffsetY = 5;
     protected FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
@@ -354,8 +355,8 @@
     protected FakeKeyguardRepository mFakeKeyguardRepository;
     protected KeyguardInteractor mKeyguardInteractor;
     protected ShadeAnimationInteractor mShadeAnimationInteractor;
-    protected SceneTestUtils mUtils = new SceneTestUtils(this);
-    protected TestScope mTestScope = mUtils.getTestScope();
+    protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+    protected TestScope mTestScope = mKosmos.getTestScope();
     protected ShadeInteractor mShadeInteractor;
     protected PowerInteractor mPowerInteractor;
     protected NotificationPanelViewController.TouchHandler mTouchHandler;
@@ -426,7 +427,8 @@
                                 mContext,
                                 new ResourcesSplitShadeStateController(),
                                 mKeyguardInteractor,
-                                deviceEntryUdfpsInteractor
+                                deviceEntryUdfpsInteractor,
+                                () -> mLargeScreenHeaderHelper
                         ),
                         mShadeRepository
                 )
@@ -812,7 +814,8 @@
                 mActiveNotificationsInteractor,
                 mJavaAdapter,
                 mCastController,
-                new ResourcesSplitShadeStateController()
+                new ResourcesSplitShadeStateController(),
+                () -> mLargeScreenHeaderHelper
         );
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 9d8b214..461db8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -23,8 +23,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -38,6 +36,8 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
 import android.app.IActivityManager;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
@@ -56,6 +56,7 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlagsClassic;
@@ -67,14 +68,15 @@
 import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository;
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions;
 import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.FakeWindowRootViewComponent;
-import com.android.systemui.scene.SceneTestUtils;
 import com.android.systemui.scene.data.repository.SceneContainerRepository;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
@@ -94,11 +96,11 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
 
@@ -128,7 +130,6 @@
     @Spy private final NotificationShadeWindowView mNotificationShadeWindowView = spy(
             new NotificationShadeWindowView(mContext, null));
     @Mock private IActivityManager mActivityManager;
-    @Mock private SysuiStatusBarStateController mStatusBarStateController;
     @Mock private ConfigurationController mConfigurationController;
     @Mock private KeyguardViewMediator mKeyguardViewMediator;
     @Mock private KeyguardBypassController mKeyguardBypassController;
@@ -137,25 +138,27 @@
     @Mock private DumpManager mDumpManager;
     @Mock private KeyguardSecurityModel mKeyguardSecurityModel;
     @Mock private KeyguardStateController mKeyguardStateController;
-    @Mock private ScreenOffAnimationController mScreenOffAnimationController;
     @Mock private AuthController mAuthController;
     @Mock private ShadeWindowLogger mShadeWindowLogger;
     @Mock private SelectedUserInteractor mSelectedUserInteractor;
     @Mock private UserTracker mUserTracker;
     @Mock private SceneContainerFlags mSceneContainerFlags;
+    @Mock private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
     @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
     @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
 
     private final Executor mMainExecutor = MoreExecutors.directExecutor();
     private final Executor mBackgroundExecutor = MoreExecutors.directExecutor();
-    private final SceneTestUtils mUtils = new SceneTestUtils(this);
-    private final TestScope mTestScope = mUtils.getTestScope();
+    private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+    private final TestScope mTestScope = mKosmos.getTestScope();
     private ShadeInteractor mShadeInteractor;
 
     private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
     private float mPreferredRefreshRate = -1;
     private FromLockscreenTransitionInteractor mFromLockscreenTransitionInteractor;
     private FromPrimaryBouncerTransitionInteractor mFromPrimaryBouncerTransitionInteractor;
+    private ScreenOffAnimationController mScreenOffAnimationController;
+    private SysuiStatusBarStateController mStatusBarStateController;
 
     @Before
     public void setUp() {
@@ -174,19 +177,18 @@
         FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
         FakeShadeRepository shadeRepository = new FakeShadeRepository();
 
-        PowerInteractor powerInteractor = mUtils.powerInteractor(
-                mUtils.getPowerRepository(),
-                mUtils.falsingCollector(),
-                mScreenOffAnimationController,
-                mStatusBarStateController);
+        mScreenOffAnimationController = mKosmos.getScreenOffAnimationController();
+        mStatusBarStateController = spy(mKosmos.getStatusBarStateController());
+        PowerInteractor powerInteractor = mKosmos.getPowerInteractor();
 
         SceneInteractor sceneInteractor = new SceneInteractor(
                 mTestScope.getBackgroundScope(),
                 new SceneContainerRepository(
                         mTestScope.getBackgroundScope(),
-                        mUtils.fakeSceneContainerConfig()),
+                        mKosmos.getFakeSceneContainerConfig()),
                 powerInteractor,
-                mock(SceneLogger.class));
+                mock(SceneLogger.class),
+                mKosmos.getDeviceUnlockedInteractor());
 
         FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
         FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags();
@@ -199,6 +201,7 @@
                 new ConfigurationInteractor(configurationRepository),
                 shadeRepository,
                 () -> sceneInteractor);
+        CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
 
         FakeKeyguardTransitionRepository keyguardTransitionRepository =
                 new FakeKeyguardTransitionRepository();
@@ -215,10 +218,19 @@
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 mTestScope.getBackgroundScope(),
+                mKosmos.getTestDispatcher(),
+                mKosmos.getTestDispatcher(),
                 keyguardInteractor,
                 featureFlags,
                 shadeRepository,
                 powerInteractor,
+                new GlanceableHubTransitions(
+                        mTestScope,
+                        mKosmos.getTestDispatcher(),
+                        keyguardTransitionInteractor,
+                        keyguardTransitionRepository,
+                        communalInteractor
+                ),
                 () ->
                         new InWindowLauncherUnlockAnimationInteractor(
                                 new InWindowLauncherUnlockAnimationRepository(),
@@ -233,7 +245,10 @@
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 mTestScope.getBackgroundScope(),
+                mKosmos.getTestDispatcher(),
+                mKosmos.getTestDispatcher(),
                 keyguardInteractor,
+                communalInteractor,
                 featureFlags,
                 mKeyguardSecurityModel,
                 mSelectedUserInteractor,
@@ -261,7 +276,8 @@
                                 mContext,
                                 new ResourcesSplitShadeStateController(),
                                 keyguardInteractor,
-                                deviceEntryUdfpsInteractor),
+                                deviceEntryUdfpsInteractor,
+                                () -> mLargeScreenHeaderHelper),
                         shadeRepository
                 )
         );
@@ -287,7 +303,8 @@
                 mShadeWindowLogger,
                 () -> mSelectedUserInteractor,
                 mUserTracker,
-                mSceneContainerFlags) {
+                mSceneContainerFlags,
+                () -> communalInteractor) {
                     @Override
                     protected boolean isDebuggable() {
                         return false;
@@ -435,6 +452,24 @@
     }
 
     @Test
+    public void setCommunalShowing_userTimeout() {
+        setKeyguardShowing();
+        clearInvocations(mWindowManager);
+
+        mNotificationShadeWindowController.onCommunalShowingChanged(true);
+        verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+        assertThat(mLayoutParameters.getValue().userActivityTimeout)
+                .isEqualTo(CommunalInteractor.AWAKE_INTERVAL_MS);
+        clearInvocations(mWindowManager);
+
+        // Bouncer showing over communal overrides communal value
+        mNotificationShadeWindowController.setBouncerShowing(true);
+        verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+        assertThat(mLayoutParameters.getValue().userActivityTimeout)
+                .isEqualTo(KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS);
+    }
+
+    @Test
     public void setKeyguardShowing_notFocusable_byDefault() {
         mNotificationShadeWindowController.setKeyguardShowing(false);
 
@@ -519,8 +554,8 @@
 
     @Test
     public void udfpsEnrolled_minAndMaxRefreshRateSetToPreferredRefreshRate() {
-        // GIVEN udfps is enrolled
-        when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
+        // GIVEN optical udfps is enrolled
+        when(mAuthController.isOpticalUdfpsEnrolled(anyInt())).thenReturn(true);
 
         // WHEN keyguard is showing
         setKeyguardShowing();
@@ -534,9 +569,9 @@
     }
 
     @Test
-    public void udfpsNotEnrolled_refreshRateUnset() {
+    public void opticalUdfpsNotEnrolled_refreshRateUnset() {
         // GIVEN udfps is NOT enrolled
-        when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
+        when(mAuthController.isOpticalUdfpsEnrolled(anyInt())).thenReturn(false);
 
         // WHEN keyguard is showing
         setKeyguardShowing();
@@ -551,8 +586,8 @@
 
     @Test
     public void keyguardNotShowing_refreshRateUnset() {
-        // GIVEN UDFPS is enrolled
-        when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
+        // GIVEN optical UDFPS is enrolled
+        when(mAuthController.isOpticalUdfpsEnrolled(anyInt())).thenReturn(true);
 
         // WHEN keyguard is NOT showing
         mNotificationShadeWindowController.setKeyguardShowing(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index ee7c6c8..22b05be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.shade
 
 import android.content.Context
-import android.os.Handler
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.KeyEvent
@@ -25,50 +24,29 @@
 import android.view.View
 import android.view.ViewGroup
 import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardMessageAreaController
 import com.android.keyguard.KeyguardSecurityContainerController
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.LockIconViewController
 import com.android.keyguard.dagger.KeyguardBouncerComponent
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
-import com.android.systemui.bouncer.data.repository.BouncerMessageRepositoryImpl
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
-import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.bouncer.ui.BouncerView
-import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
-import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.bouncer.ui.binder.BouncerViewBinder
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.compose.ComposeFacade.isComposeAvailable
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
 import com.android.systemui.flags.Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION
 import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON
 import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_FEATURES
-import com.android.systemui.flags.SystemPropertiesHelper
 import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
-import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
-import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
-import com.android.systemui.log.BouncerLogger
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
 import com.android.systemui.statusbar.DragDownHelper
@@ -86,21 +64,22 @@
 import com.android.systemui.statusbar.phone.DozeServiceHost
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
-import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
-import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.kotlin.JavaAdapter
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import java.util.Optional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
@@ -110,9 +89,8 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import java.util.Optional
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -133,7 +111,6 @@
     @Mock private lateinit var shadeLogger: ShadeLogger
     @Mock private lateinit var dumpManager: DumpManager
     @Mock private lateinit var ambientState: AmbientState
-    @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
     @Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
     @Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController
@@ -156,8 +133,6 @@
     @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock lateinit var dragDownHelper: DragDownHelper
     @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
-    @Mock
-    lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel
     @Mock lateinit var sysUIKeyEventHandler: SysUIKeyEventHandler
     @Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
     @Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@@ -224,55 +199,18 @@
                 dumpManager,
                 pulsingGestureListener,
                 mLockscreenHostedDreamGestureListener,
-                keyguardBouncerViewModel,
-                keyguardBouncerComponentFactory,
-                mock(KeyguardMessageAreaController.Factory::class.java),
                 keyguardTransitionInteractor,
-                primaryBouncerToGoneTransitionViewModel,
                 mGlanceableHubContainerController,
                 notificationLaunchAnimationInteractor,
                 featureFlagsClassic,
                 fakeClock,
-                BouncerMessageInteractor(
-                    repository = BouncerMessageRepositoryImpl(),
-                    userRepository = FakeUserRepository(),
-                    countDownTimerUtil = mock(CountDownTimerUtil::class.java),
-                    updateMonitor = mock(KeyguardUpdateMonitor::class.java),
-                    biometricSettingsRepository = FakeBiometricSettingsRepository(),
-                    applicationScope = testScope.backgroundScope,
-                    trustRepository = FakeTrustRepository(),
-                    systemPropertiesHelper = mock(SystemPropertiesHelper::class.java),
-                    primaryBouncerInteractor =
-                        PrimaryBouncerInteractor(
-                            FakeKeyguardBouncerRepository(),
-                            mock(BouncerView::class.java),
-                            mock(Handler::class.java),
-                            mock(KeyguardStateController::class.java),
-                            mock(KeyguardSecurityModel::class.java),
-                            mock(PrimaryBouncerCallbackInteractor::class.java),
-                            mock(FalsingCollector::class.java),
-                            mock(DismissCallbackRegistry::class.java),
-                            context,
-                            mock(KeyguardUpdateMonitor::class.java),
-                            FakeTrustRepository(),
-                            testScope.backgroundScope,
-                            mSelectedUserInteractor,
-                            mock(DeviceEntryFaceAuthInteractor::class.java)
-                        ),
-                    facePropertyRepository = FakeFacePropertyRepository(),
-                    deviceEntryFingerprintAuthRepository =
-                        FakeDeviceEntryFingerprintAuthRepository(),
-                    faceAuthRepository = FakeDeviceEntryFaceAuthRepository(),
-                    securityModel = mock(KeyguardSecurityModel::class.java),
-                ),
-                BouncerLogger(logcatLogBuffer("BouncerLog")),
                 sysUIKeyEventHandler,
                 quickSettingsController,
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
-                mSelectedUserInteractor,
-                { mock (JavaAdapter::class.java )},
+                { mock(JavaAdapter::class.java) },
                 { mock(AlternateBouncerDependencies::class.java) },
+                mock(BouncerViewBinder::class.java)
             )
         underTest.setupExpandedStatusBar()
         underTest.setDragDownHelper(dragDownHelper)
@@ -544,6 +482,7 @@
         }
 
     @Test
+    @Ignore("b/321332798")
     fun setsUpCommunalHubLayout_whenFlagEnabled() {
         if (!isComposeAvailable()) {
             return
@@ -575,6 +514,8 @@
         }
 
         whenever(mGlanceableHubContainerController.isEnabled()).thenReturn(false)
+        whenever(mGlanceableHubContainerController.communalAvailable())
+            .thenReturn(MutableStateFlow(false))
 
         val mockCommunalPlaceholder = mock(View::class.java)
         val fakeViewIndex = 20
@@ -584,8 +525,7 @@
 
         underTest.setupCommunalHubLayout()
 
-        // No adding or removing of views occurs.
-        verify(view, times(0)).removeView(mockCommunalPlaceholder)
+        // No adding of views occurs.
         verify(view, times(0)).addView(any(), eq(fakeViewIndex))
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 33d60ea..0c4bf81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -15,51 +15,28 @@
  */
 package com.android.systemui.shade
 
-import android.os.Handler
 import android.os.SystemClock
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.MotionEvent
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardMessageAreaController
 import com.android.keyguard.KeyguardSecurityContainerController
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.LockIconViewController
 import com.android.keyguard.dagger.KeyguardBouncerComponent
 import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
-import com.android.systemui.bouncer.data.repository.BouncerMessageRepositoryImpl
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
-import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.bouncer.ui.BouncerView
-import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
-import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.flags.SystemPropertiesHelper
 import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
-import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
-import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
-import com.android.systemui.log.BouncerLogger
-import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
 import com.android.systemui.statusbar.DragDownHelper
@@ -77,11 +54,8 @@
 import com.android.systemui.statusbar.phone.DozeScrimController
 import com.android.systemui.statusbar.phone.DozeServiceHost
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
-import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.kotlin.JavaAdapter
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
@@ -137,7 +111,6 @@
     @Mock private lateinit var pulsingGestureListener: PulsingGestureListener
     @Mock
     private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
-    @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
     @Mock private lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
     @Mock private lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
     @Mock
@@ -150,10 +123,6 @@
     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
     @Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
-    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
-    @Mock
-    private lateinit var primaryBouncerToGoneTransitionViewModel:
-        PrimaryBouncerToGoneTransitionViewModel
     @Captor
     private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
 
@@ -217,55 +186,18 @@
                 dumpManager,
                 pulsingGestureListener,
                 mLockscreenHostedDreamGestureListener,
-                keyguardBouncerViewModel,
-                keyguardBouncerComponentFactory,
-                Mockito.mock(KeyguardMessageAreaController.Factory::class.java),
                 keyguardTransitionInteractor,
-                primaryBouncerToGoneTransitionViewModel,
                 mGlanceableHubContainerController,
                 NotificationLaunchAnimationInteractor(NotificationLaunchAnimationRepository()),
                 featureFlags,
                 FakeSystemClock(),
-                BouncerMessageInteractor(
-                    repository = BouncerMessageRepositoryImpl(),
-                    userRepository = FakeUserRepository(),
-                    countDownTimerUtil = Mockito.mock(CountDownTimerUtil::class.java),
-                    updateMonitor = Mockito.mock(KeyguardUpdateMonitor::class.java),
-                    biometricSettingsRepository = FakeBiometricSettingsRepository(),
-                    applicationScope = testScope.backgroundScope,
-                    trustRepository = FakeTrustRepository(),
-                    systemPropertiesHelper = Mockito.mock(SystemPropertiesHelper::class.java),
-                    primaryBouncerInteractor =
-                        PrimaryBouncerInteractor(
-                            FakeKeyguardBouncerRepository(),
-                            Mockito.mock(BouncerView::class.java),
-                            Mockito.mock(Handler::class.java),
-                            Mockito.mock(KeyguardStateController::class.java),
-                            Mockito.mock(KeyguardSecurityModel::class.java),
-                            Mockito.mock(PrimaryBouncerCallbackInteractor::class.java),
-                            Mockito.mock(FalsingCollector::class.java),
-                            Mockito.mock(DismissCallbackRegistry::class.java),
-                            context,
-                            Mockito.mock(KeyguardUpdateMonitor::class.java),
-                            FakeTrustRepository(),
-                            testScope.backgroundScope,
-                            mSelectedUserInteractor,
-                            mock(),
-                        ),
-                    facePropertyRepository = FakeFacePropertyRepository(),
-                    deviceEntryFingerprintAuthRepository =
-                        FakeDeviceEntryFingerprintAuthRepository(),
-                    faceAuthRepository = FakeDeviceEntryFaceAuthRepository(),
-                    securityModel = Mockito.mock(KeyguardSecurityModel::class.java),
-                ),
-                BouncerLogger(logcatLogBuffer("BouncerLog")),
                 Mockito.mock(SysUIKeyEventHandler::class.java),
                 quickSettingsController,
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
-                mSelectedUserInteractor,
                 { Mockito.mock(JavaAdapter::class.java) },
                 { Mockito.mock(AlternateBouncerDependencies::class.java) },
+                mock()
             )
 
         controller.setupExpandedStatusBar()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 88a47eb..697b05a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -26,6 +26,7 @@
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
@@ -42,6 +43,7 @@
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -52,7 +54,6 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
-import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.RETURNS_DEEP_STUBS
 import org.mockito.Mockito.any
@@ -74,15 +75,16 @@
 @SmallTest
 class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
 
-    @Mock lateinit var view: NotificationsQuickSettingsContainer
-    @Mock lateinit var navigationModeController: NavigationModeController
-    @Mock lateinit var overviewProxyService: OverviewProxyService
-    @Mock lateinit var shadeHeaderController: ShadeHeaderController
-    @Mock lateinit var shadeInteractor: ShadeInteractor
-    @Mock lateinit var fragmentService: FragmentService
-    @Mock lateinit var fragmentHostManager: FragmentHostManager
-    @Mock
-    lateinit var notificationStackScrollLayoutController: NotificationStackScrollLayoutController
+    private val view = mock<NotificationsQuickSettingsContainer>()
+    private val navigationModeController = mock<NavigationModeController>()
+    private val overviewProxyService = mock<OverviewProxyService>()
+    private val shadeHeaderController = mock<ShadeHeaderController>()
+    private val shadeInteractor = mock<ShadeInteractor>()
+    private val fragmentService = mock<FragmentService>()
+    private val fragmentHostManager = mock<FragmentHostManager>()
+    private val notificationStackScrollLayoutController =
+        mock<NotificationStackScrollLayoutController>()
+    private val largeScreenHeaderHelper = mock<LargeScreenHeaderHelper>()
 
     @Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
     @Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
@@ -123,7 +125,8 @@
                 delayableExecutor,
                 featureFlags,
                 notificationStackScrollLayoutController,
-                ResourcesSplitShadeStateController()
+                ResourcesSplitShadeStateController(),
+                largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
             )
 
         overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
@@ -165,10 +168,15 @@
     }
 
     @Test
-    fun testLargeScreen_updateResources_splitShadeHeightIsSet() {
+    fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSetBasedOnResource() {
+        val headerResourceHeight = 20
+        val headerHelperHeight = 30
+        mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+            .thenReturn(headerHelperHeight)
         overrideResource(R.bool.config_use_large_screen_shade_header, true)
         overrideResource(R.dimen.qs_header_height, 10)
-        overrideResource(R.dimen.large_screen_shade_header_height, 20)
+        overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
 
         // ensure the estimated height (would be 3 here) wouldn't impact this test case
         overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
@@ -178,7 +186,31 @@
 
         val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
         verify(view).applyConstraints(capture(captor))
-        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(20)
+        assertThat(captor.value.getHeight(R.id.split_shade_status_bar))
+            .isEqualTo(headerResourceHeight)
+    }
+
+    @Test
+    fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSetBasedOnHelper() {
+        val headerResourceHeight = 20
+        val headerHelperHeight = 30
+        mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+            .thenReturn(headerHelperHeight)
+        overrideResource(R.bool.config_use_large_screen_shade_header, true)
+        overrideResource(R.dimen.qs_header_height, 10)
+        overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
+
+        // ensure the estimated height (would be 3 here) wouldn't impact this test case
+        overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
+        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 1)
+
+        underTest.updateResources()
+
+        val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+        verify(view).applyConstraints(capture(captor))
+        assertThat(captor.value.getHeight(R.id.split_shade_status_bar))
+            .isEqualTo(headerHelperHeight)
     }
 
     @Test
@@ -414,10 +446,14 @@
     }
 
     @Test
-    fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeight() {
+    fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderHeightResource() {
+        mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
         setLargeScreen()
-        val largeScreenHeaderHeight = 100
-        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
+        val largeScreenHeaderResourceHeight = 100
+        val largeScreenHeaderHelperHeight = 200
+        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+            .thenReturn(largeScreenHeaderHelperHeight)
+        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight)
 
         // ensure the estimated height (would be 30 here) wouldn't impact this test case
         overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
@@ -426,9 +462,31 @@
         underTest.updateResources()
 
         assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
-            .isEqualTo(largeScreenHeaderHeight)
+            .isEqualTo(largeScreenHeaderResourceHeight)
         assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
-            .isEqualTo(largeScreenHeaderHeight)
+            .isEqualTo(largeScreenHeaderResourceHeight)
+    }
+
+    @Test
+    fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHeightHelper() {
+        mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+        setLargeScreen()
+        val largeScreenHeaderResourceHeight = 100
+        val largeScreenHeaderHelperHeight = 200
+        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+            .thenReturn(largeScreenHeaderHelperHeight)
+        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight)
+
+        // ensure the estimated height (would be 30 here) wouldn't impact this test case
+        overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
+        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 10)
+
+        underTest.updateResources()
+
+        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
+            .isEqualTo(largeScreenHeaderHelperHeight)
+        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
+            .isEqualTo(largeScreenHeaderHelperHeight)
     }
 
     @Test
@@ -480,7 +538,8 @@
                 delayableExecutor,
                 featureFlags,
                 notificationStackScrollLayoutController,
-                ResourcesSplitShadeStateController()
+                ResourcesSplitShadeStateController(),
+                largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
             )
         controller.updateConstraints()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index 1f37ca0..e66251a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -26,6 +26,7 @@
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
@@ -43,6 +44,7 @@
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -53,7 +55,6 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
-import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.RETURNS_DEEP_STUBS
 import org.mockito.Mockito.any
@@ -71,15 +72,16 @@
 @SmallTest
 class NotificationsQSContainerControllerTest : SysuiTestCase() {
 
-    @Mock lateinit var view: NotificationsQuickSettingsContainer
-    @Mock lateinit var navigationModeController: NavigationModeController
-    @Mock lateinit var overviewProxyService: OverviewProxyService
-    @Mock lateinit var shadeHeaderController: ShadeHeaderController
-    @Mock lateinit var shadeInteractor: ShadeInteractor
-    @Mock lateinit var fragmentService: FragmentService
-    @Mock lateinit var fragmentHostManager: FragmentHostManager
-    @Mock
-    lateinit var notificationStackScrollLayoutController: NotificationStackScrollLayoutController
+    private val view = mock<NotificationsQuickSettingsContainer>()
+    private val navigationModeController = mock<NavigationModeController>()
+    private val overviewProxyService = mock<OverviewProxyService>()
+    private val shadeHeaderController = mock<ShadeHeaderController>()
+    private val shadeInteractor = mock<ShadeInteractor>()
+    private val fragmentService = mock<FragmentService>()
+    private val fragmentHostManager = mock<FragmentHostManager>()
+    private val notificationStackScrollLayoutController =
+        mock<NotificationStackScrollLayoutController>()
+    private val largeScreenHeaderHelper = mock<LargeScreenHeaderHelper>()
 
     @Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
     @Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
@@ -122,7 +124,8 @@
                 delayableExecutor,
                 featureFlags,
                 notificationStackScrollLayoutController,
-                ResourcesSplitShadeStateController()
+                ResourcesSplitShadeStateController(),
+                largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
             )
 
         overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
@@ -164,10 +167,14 @@
     }
 
     @Test
-    fun testLargeScreen_updateResources_splitShadeHeightIsSet() {
+    fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSet_basedOnResource() {
+        mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+        val helperHeight = 30
+        val resourceHeight = 20
+        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight)
         overrideResource(R.bool.config_use_large_screen_shade_header, true)
         overrideResource(R.dimen.qs_header_height, 10)
-        overrideResource(R.dimen.large_screen_shade_header_height, 20)
+        overrideResource(R.dimen.large_screen_shade_header_height, resourceHeight)
 
         // ensure the estimated height (would be 3 here) wouldn't impact this test case
         overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
@@ -177,7 +184,28 @@
 
         val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
         verify(view).applyConstraints(capture(captor))
-        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(20)
+        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(resourceHeight)
+    }
+
+    @Test
+    fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSet_basedOnHelper() {
+        mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+        val helperHeight = 30
+        val resourceHeight = 20
+        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight)
+        overrideResource(R.bool.config_use_large_screen_shade_header, true)
+        overrideResource(R.dimen.qs_header_height, 10)
+        overrideResource(R.dimen.large_screen_shade_header_height, resourceHeight)
+
+        // ensure the estimated height (would be 3 here) wouldn't impact this test case
+        overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
+        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 1)
+
+        underTest.updateResources()
+
+        val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+        verify(view).applyConstraints(capture(captor))
+        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(helperHeight)
     }
 
     @Test
@@ -402,10 +430,14 @@
     }
 
     @Test
-    fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeight() {
+    fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderResourceHeight() {
+        mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
         setLargeScreen()
-        val largeScreenHeaderHeight = 100
-        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
+        val largeScreenHeaderHelperHeight = 200
+        val largeScreenHeaderResourceHeight = 100
+        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+            .thenReturn(largeScreenHeaderHelperHeight)
+        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight)
 
         // ensure the estimated height (would be 30 here) wouldn't impact this test case
         overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
@@ -414,7 +446,27 @@
         underTest.updateResources()
 
         assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
-            .isEqualTo(largeScreenHeaderHeight)
+            .isEqualTo(largeScreenHeaderResourceHeight)
+    }
+
+    @Test
+    fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHelperHeight() {
+        mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+        setLargeScreen()
+        val largeScreenHeaderHelperHeight = 200
+        val largeScreenHeaderResourceHeight = 100
+        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+            .thenReturn(largeScreenHeaderHelperHeight)
+        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight)
+
+        // ensure the estimated height (would be 30 here) wouldn't impact this test case
+        overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
+        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 10)
+
+        underTest.updateResources()
+
+        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
+            .isEqualTo(largeScreenHeaderHelperHeight)
     }
 
     @Test
@@ -463,7 +515,8 @@
                 delayableExecutor,
                 featureFlags,
                 notificationStackScrollLayoutController,
-                ResourcesSplitShadeStateController()
+                ResourcesSplitShadeStateController(),
+                largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
             )
         controller.updateConstraints()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 727a6c3..3e0a647 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -42,6 +42,7 @@
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
 import com.android.systemui.dump.DumpManager;
@@ -55,9 +56,11 @@
 import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository;
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions;
 import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.plugins.FalsingManager;
@@ -65,7 +68,6 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.qs.QSFragmentLegacy;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.SceneTestUtils;
 import com.android.systemui.scene.data.repository.SceneContainerRepository;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
@@ -82,7 +84,6 @@
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.QsFrameTranslateController;
-import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
@@ -96,15 +97,14 @@
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
 import com.android.systemui.util.kotlin.JavaAdapter;
@@ -130,8 +130,8 @@
 
     protected QuickSettingsController mQsController;
 
-    protected SceneTestUtils mUtils = new SceneTestUtils(this);
-    protected TestScope mTestScope = mUtils.getTestScope();
+    protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+    protected TestScope mTestScope = mKosmos.getTestScope();
 
     @Mock protected Resources mResources;
     @Mock protected KeyguardBottomAreaView mQsFrame;
@@ -169,19 +169,20 @@
     @Mock protected LockscreenGestureLogger mLockscreenGestureLogger;
     @Mock protected MetricsLogger mMetricsLogger;
     @Mock protected FeatureFlags mFeatureFlags;
-    @Mock protected InteractionJankMonitor mInteractionJankMonitor;
     @Mock protected ShadeLogger mShadeLogger;
     @Mock protected DumpManager mDumpManager;
     @Mock protected UiEventLogger mUiEventLogger;
     @Mock protected CastController mCastController;
     @Mock protected UserSwitcherInteractor mUserSwitcherInteractor;
     @Mock protected SelectedUserInteractor mSelectedUserInteractor;
+    @Mock protected LargeScreenHeaderHelper mLargeScreenHeaderHelper;
 
     protected FakeDisableFlagsRepository mDisableFlagsRepository =
             new FakeDisableFlagsRepository();
     protected FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
     protected FakeShadeRepository mShadeRepository = new FakeShadeRepository();
 
+    protected InteractionJankMonitor mInteractionJankMonitor;
     protected SysuiStatusBarStateController mStatusBarStateController;
     protected ShadeInteractor mShadeInteractor;
 
@@ -201,8 +202,8 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
         when(mPanelViewControllerLazy.get()).thenReturn(mNotificationPanelViewController);
-        mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger,
-                mInteractionJankMonitor, mock(JavaAdapter.class), () -> mShadeInteractor);
+        mStatusBarStateController = mKosmos.getStatusBarStateController();
+        mInteractionJankMonitor = mKosmos.getInteractionJankMonitor();
 
         FakeDeviceProvisioningRepository deviceProvisioningRepository =
                 new FakeDeviceProvisioningRepository();
@@ -210,19 +211,16 @@
         FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
         FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
 
-        PowerInteractor powerInteractor = mUtils.powerInteractor(
-                mUtils.getPowerRepository(),
-                mUtils.falsingCollector(),
-                mock(ScreenOffAnimationController.class),
-                mStatusBarStateController);
+        PowerInteractor powerInteractor = mKosmos.getPowerInteractor();
 
         SceneInteractor sceneInteractor = new SceneInteractor(
                 mTestScope.getBackgroundScope(),
                 new SceneContainerRepository(
                         mTestScope.getBackgroundScope(),
-                        mUtils.fakeSceneContainerConfig()),
+                        mKosmos.getFakeSceneContainerConfig()),
                 powerInteractor,
-                mock(SceneLogger.class));
+                mock(SceneLogger.class),
+                mKosmos.getDeviceUnlockedInteractor());
 
         FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags();
         KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
@@ -234,6 +232,7 @@
                 new ConfigurationInteractor(configurationRepository),
                 mShadeRepository,
                 () -> sceneInteractor);
+        CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
 
         FakeKeyguardTransitionRepository keyguardTransitionRepository =
                 new FakeKeyguardTransitionRepository();
@@ -250,10 +249,19 @@
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 mTestScope.getBackgroundScope(),
+                mKosmos.getTestDispatcher(),
+                mKosmos.getTestDispatcher(),
                 keyguardInteractor,
                 featureFlags,
                 mShadeRepository,
                 powerInteractor,
+                new GlanceableHubTransitions(
+                        mTestScope,
+                        mKosmos.getTestDispatcher(),
+                        keyguardTransitionInteractor,
+                        keyguardTransitionRepository,
+                        communalInteractor
+                ),
                 () ->
                         new InWindowLauncherUnlockAnimationInteractor(
                                 new InWindowLauncherUnlockAnimationRepository(),
@@ -268,7 +276,10 @@
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 mTestScope.getBackgroundScope(),
+                mKosmos.getTestDispatcher(),
+                mKosmos.getTestDispatcher(),
                 keyguardInteractor,
+                communalInteractor,
                 featureFlags,
                 mock(KeyguardSecurityModel.class),
                 mSelectedUserInteractor,
@@ -299,7 +310,8 @@
                                 mContext,
                                 splitShadeStateController,
                                 keyguardInteractor,
-                                deviceEntryUdfpsInteractor),
+                                deviceEntryUdfpsInteractor,
+                                () -> mLargeScreenHeaderHelper),
                         mShadeRepository
                 )
         );
@@ -384,7 +396,8 @@
                 mActiveNotificationsInteractor,
                 new JavaAdapter(mTestScope.getBackgroundScope()),
                 mCastController,
-                splitShadeStateController
+                splitShadeStateController,
+                () -> mLargeScreenHeaderHelper
         );
         mQsController.init();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 215f8b1..cc79ca4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -22,17 +22,22 @@
 import android.view.WindowManager
 import androidx.test.filters.SmallTest
 import com.android.internal.statusbar.IStatusBarService
-import com.android.keyguard.TestScopeProvider
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.assist.AssistManager
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -45,6 +50,7 @@
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import dagger.Lazy
+import kotlinx.coroutines.test.StandardTestDispatcher
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -59,6 +65,10 @@
 @SmallTest
 class ShadeControllerImplTest : SysuiTestCase() {
     private val executor = FakeExecutor(FakeSystemClock())
+    private val testDispatcher = StandardTestDispatcher()
+    private val activeNotificationsRepository = ActiveNotificationListRepository()
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
 
     @Mock private lateinit var commandQueue: CommandQueue
     @Mock private lateinit var keyguardStateController: KeyguardStateController
@@ -79,11 +89,14 @@
 
     private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
         WindowRootViewVisibilityInteractor(
-            TestScopeProvider.getTestScope(),
+            testScope,
             WindowRootViewVisibilityRepository(iStatusBarService, executor),
             FakeKeyguardRepository(),
             headsUpManager,
             PowerInteractorFactory.create().powerInteractor,
+            ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher),
+            kosmos.sceneContainerFlags,
+            kosmos::sceneInteractor,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt
deleted file mode 100644
index 6bbe900c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shade.domain.interactor
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.flags.Flags
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.user.domain.UserDomainLayerModule
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth
-import dagger.BindsInstance
-import dagger.Component
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
-import org.junit.Test
-
-@SmallTest
-class ShadeAnimationInteractorSceneContainerImplTest : SysuiTestCase() {
-
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                UserDomainLayerModule::class,
-            ]
-    )
-    interface TestComponent : SysUITestComponent<ShadeAnimationInteractorSceneContainerImpl> {
-        val sceneInteractor: SceneInteractor
-
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-                featureFlags: FakeFeatureFlagsClassicModule,
-                mocks: TestMocksModule,
-            ): TestComponent
-        }
-    }
-
-    private val dozeParameters: DozeParameters = mock()
-
-    private val testComponent: TestComponent =
-        DaggerShadeAnimationInteractorSceneContainerImplTest_TestComponent.factory()
-            .create(
-                test = this,
-                featureFlags =
-                    FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) },
-                mocks =
-                    TestMocksModule(
-                        dozeParameters = dozeParameters,
-                    ),
-            )
-
-    @Test
-    fun isAnyCloseAnimationRunning_qsToShade() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.isAnyCloseAnimationRunning)
-
-            // WHEN transitioning from QS to Shade
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = SceneKey.QuickSettings,
-                        toScene = SceneKey.Shade,
-                        progress = MutableStateFlow(.1f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            sceneInteractor.setTransitionState(transitionState)
-            runCurrent()
-
-            // THEN qs is animating closed
-            Truth.assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun isAnyCloseAnimationRunning_qsToGone_userInputNotOngoing() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.isAnyCloseAnimationRunning)
-
-            // WHEN transitioning from QS to Gone with no ongoing user input
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = SceneKey.QuickSettings,
-                        toScene = SceneKey.Gone,
-                        progress = MutableStateFlow(.1f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            sceneInteractor.setTransitionState(transitionState)
-            runCurrent()
-
-            // THEN qs is animating closed
-            Truth.assertThat(actual).isTrue()
-        }
-
-    @Test
-    fun isAnyCloseAnimationRunning_qsToGone_userInputOngoing() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.isAnyCloseAnimationRunning)
-
-            // WHEN transitioning from QS to Gone with user input ongoing
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = SceneKey.QuickSettings,
-                        toScene = SceneKey.Gone,
-                        progress = MutableStateFlow(.1f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(true),
-                    )
-                )
-            sceneInteractor.setTransitionState(transitionState)
-            runCurrent()
-
-            // THEN qs is not animating closed
-            Truth.assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun updateIsLaunchingActivity() =
-        testComponent.runTest {
-            Truth.assertThat(underTest.isLaunchingActivity.value).isEqualTo(false)
-
-            underTest.setIsLaunchingActivity(true)
-            Truth.assertThat(underTest.isLaunchingActivity.value).isEqualTo(true)
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
deleted file mode 100644
index 65e0fa1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
+++ /dev/null
@@ -1,793 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.shade.domain.interactor
-
-import android.app.StatusBarManager.DISABLE2_NONE
-import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
-import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS
-import android.content.pm.UserInfo
-import android.os.UserManager
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.DozeStateModel
-import com.android.systemui.keyguard.shared.model.DozeTransitionModel
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.shared.model.WakeSleepReason
-import com.android.systemui.power.shared.model.WakefulnessState
-import com.android.systemui.res.R
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
-import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
-import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
-import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.domain.UserDomainLayerModule
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-
-@SmallTest
-class ShadeInteractorImplTest : SysuiTestCase() {
-
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                UserDomainLayerModule::class,
-            ]
-    )
-    interface TestComponent : SysUITestComponent<ShadeInteractorImpl> {
-
-        val configurationRepository: FakeConfigurationRepository
-        val deviceProvisioningRepository: FakeDeviceProvisioningRepository
-        val disableFlagsRepository: FakeDisableFlagsRepository
-        val keyguardRepository: FakeKeyguardRepository
-        val keyguardTransitionRepository: FakeKeyguardTransitionRepository
-        val powerRepository: FakePowerRepository
-        val sceneInteractor: SceneInteractor
-        val shadeRepository: FakeShadeRepository
-        val userRepository: FakeUserRepository
-        val userSetupRepository: FakeUserSetupRepository
-
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-                featureFlags: FakeFeatureFlagsClassicModule,
-                mocks: TestMocksModule,
-            ): TestComponent
-        }
-    }
-
-    private val dozeParameters: DozeParameters = mock()
-
-    private val testComponent: TestComponent =
-        DaggerShadeInteractorImplTest_TestComponent.factory()
-            .create(
-                test = this,
-                featureFlags =
-                    FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) },
-                mocks =
-                    TestMocksModule(
-                        dozeParameters = dozeParameters,
-                    ),
-            )
-
-    @Before
-    fun setUp() {
-        runBlocking {
-            val userInfos =
-                listOf(
-                    UserInfo(
-                        /* id= */ 0,
-                        /* name= */ "zero",
-                        /* iconPath= */ "",
-                        /* flags= */ UserInfo.FLAG_PRIMARY or
-                            UserInfo.FLAG_ADMIN or
-                            UserInfo.FLAG_FULL,
-                        UserManager.USER_TYPE_FULL_SYSTEM,
-                    ),
-                )
-            testComponent.apply {
-                userRepository.setUserInfos(userInfos)
-                userRepository.setSelectedUserInfo(userInfos[0])
-            }
-        }
-    }
-
-    @Test
-    fun isShadeEnabled_matchesDisableFlagsRepo() =
-        testComponent.runTest {
-            val actual by collectLastValue(underTest.isShadeEnabled)
-
-            disableFlagsRepository.disableFlags.value =
-                DisableFlagsModel(disable2 = DISABLE2_NOTIFICATION_SHADE)
-            assertThat(actual).isFalse()
-
-            disableFlagsRepository.disableFlags.value = DisableFlagsModel(disable2 = DISABLE2_NONE)
-
-            assertThat(actual).isTrue()
-        }
-
-    @Test
-    fun isExpandToQsEnabled_deviceNotProvisioned_false() =
-        testComponent.runTest {
-            deviceProvisioningRepository.setDeviceProvisioned(false)
-
-            val actual by collectLastValue(underTest.isExpandToQsEnabled)
-
-            assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun isExpandToQsEnabled_userNotSetupAndSimpleUserSwitcher_false() =
-        testComponent.runTest {
-            deviceProvisioningRepository.setDeviceProvisioned(true)
-
-            userSetupRepository.setUserSetup(false)
-            userRepository.setSettings(UserSwitcherSettingsModel(isSimpleUserSwitcher = true))
-
-            val actual by collectLastValue(underTest.isExpandToQsEnabled)
-
-            assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun isExpandToQsEnabled_shadeNotEnabled_false() =
-        testComponent.runTest {
-            deviceProvisioningRepository.setDeviceProvisioned(true)
-            userSetupRepository.setUserSetup(true)
-
-            disableFlagsRepository.disableFlags.value =
-                DisableFlagsModel(
-                    disable2 = DISABLE2_NOTIFICATION_SHADE,
-                )
-
-            val actual by collectLastValue(underTest.isExpandToQsEnabled)
-
-            assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun isExpandToQsEnabled_quickSettingsNotEnabled_false() =
-        testComponent.runTest {
-            deviceProvisioningRepository.setDeviceProvisioned(true)
-            userSetupRepository.setUserSetup(true)
-
-            disableFlagsRepository.disableFlags.value =
-                DisableFlagsModel(
-                    disable2 = DISABLE2_QUICK_SETTINGS,
-                )
-            val actual by collectLastValue(underTest.isExpandToQsEnabled)
-
-            assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun isExpandToQsEnabled_dozing_false() =
-        testComponent.runTest {
-            deviceProvisioningRepository.setDeviceProvisioned(true)
-            userSetupRepository.setUserSetup(true)
-            disableFlagsRepository.disableFlags.value =
-                DisableFlagsModel(
-                    disable2 = DISABLE2_NONE,
-                )
-
-            keyguardRepository.setIsDozing(true)
-
-            val actual by collectLastValue(underTest.isExpandToQsEnabled)
-
-            assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun isExpandToQsEnabled_userSetup_true() =
-        testComponent.runTest {
-            deviceProvisioningRepository.setDeviceProvisioned(true)
-            keyguardRepository.setIsDozing(false)
-            disableFlagsRepository.disableFlags.value =
-                DisableFlagsModel(
-                    disable2 = DISABLE2_NONE,
-                )
-
-            userSetupRepository.setUserSetup(true)
-
-            val actual by collectLastValue(underTest.isExpandToQsEnabled)
-
-            assertThat(actual).isTrue()
-        }
-
-    @Test
-    fun isExpandToQsEnabled_notSimpleUserSwitcher_true() =
-        testComponent.runTest {
-            deviceProvisioningRepository.setDeviceProvisioned(true)
-            keyguardRepository.setIsDozing(false)
-            disableFlagsRepository.disableFlags.value =
-                DisableFlagsModel(
-                    disable2 = DISABLE2_NONE,
-                )
-
-            userRepository.setSettings(UserSwitcherSettingsModel(isSimpleUserSwitcher = false))
-
-            val actual by collectLastValue(underTest.isExpandToQsEnabled)
-
-            assertThat(actual).isTrue()
-        }
-
-    @Test
-    fun isExpandToQsEnabled_respondsToDozingUpdates() =
-        testComponent.runTest {
-            deviceProvisioningRepository.setDeviceProvisioned(true)
-            keyguardRepository.setIsDozing(false)
-            disableFlagsRepository.disableFlags.value =
-                DisableFlagsModel(
-                    disable2 = DISABLE2_NONE,
-                )
-            userSetupRepository.setUserSetup(true)
-
-            val actual by collectLastValue(underTest.isExpandToQsEnabled)
-
-            assertThat(actual).isTrue()
-
-            // WHEN dozing starts
-            keyguardRepository.setIsDozing(true)
-
-            // THEN expand is disabled
-            assertThat(actual).isFalse()
-
-            // WHEN dozing stops
-            keyguardRepository.setIsDozing(false)
-
-            // THEN expand is enabled
-            assertThat(actual).isTrue()
-        }
-
-    @Test
-    fun isExpandToQsEnabled_respondsToDisableUpdates() =
-        testComponent.runTest {
-            deviceProvisioningRepository.setDeviceProvisioned(true)
-            keyguardRepository.setIsDozing(false)
-            disableFlagsRepository.disableFlags.value =
-                DisableFlagsModel(
-                    disable2 = DISABLE2_NONE,
-                )
-            userSetupRepository.setUserSetup(true)
-
-            val actual by collectLastValue(underTest.isExpandToQsEnabled)
-
-            assertThat(actual).isTrue()
-
-            // WHEN QS is disabled
-            disableFlagsRepository.disableFlags.value =
-                DisableFlagsModel(
-                    disable2 = DISABLE2_QUICK_SETTINGS,
-                )
-            // THEN expand is disabled
-            assertThat(actual).isFalse()
-
-            // WHEN QS is enabled
-            disableFlagsRepository.disableFlags.value =
-                DisableFlagsModel(
-                    disable2 = DISABLE2_NONE,
-                )
-            // THEN expand is enabled
-            assertThat(actual).isTrue()
-        }
-
-    @Test
-    fun isExpandToQsEnabled_respondsToUserUpdates() =
-        testComponent.runTest {
-            deviceProvisioningRepository.setDeviceProvisioned(true)
-            keyguardRepository.setIsDozing(false)
-            disableFlagsRepository.disableFlags.value =
-                DisableFlagsModel(
-                    disable2 = DISABLE2_NONE,
-                )
-            userSetupRepository.setUserSetup(true)
-
-            val actual by collectLastValue(underTest.isExpandToQsEnabled)
-
-            assertThat(actual).isTrue()
-
-            // WHEN the user is no longer setup
-            userSetupRepository.setUserSetup(false)
-            userRepository.setSettings(UserSwitcherSettingsModel(isSimpleUserSwitcher = true))
-
-            // THEN expand is disabled
-            assertThat(actual).isFalse()
-
-            // WHEN the user is setup again
-            userSetupRepository.setUserSetup(true)
-
-            // THEN expand is enabled
-            assertThat(actual).isTrue()
-        }
-
-    @Test
-    fun fullShadeExpansionWhenShadeLocked() =
-        testComponent.runTest {
-            val actual by collectLastValue(underTest.shadeExpansion)
-
-            keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
-            shadeRepository.setLockscreenShadeExpansion(0.5f)
-
-            assertThat(actual).isEqualTo(1f)
-        }
-
-    @Test
-    fun fullShadeExpansionWhenStatusBarStateIsNotShadeLocked() =
-        testComponent.runTest {
-            val actual by collectLastValue(underTest.shadeExpansion)
-
-            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-
-            shadeRepository.setLockscreenShadeExpansion(0.5f)
-            assertThat(actual).isEqualTo(0.5f)
-
-            shadeRepository.setLockscreenShadeExpansion(0.8f)
-            assertThat(actual).isEqualTo(0.8f)
-        }
-
-    @Test
-    fun shadeExpansionWhenInSplitShadeAndQsExpanded() =
-        testComponent.runTest {
-            val actual by collectLastValue(underTest.shadeExpansion)
-
-            // WHEN split shade is enabled and QS is expanded
-            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            overrideResource(R.bool.config_use_split_notification_shade, true)
-            configurationRepository.onAnyConfigurationChange()
-            shadeRepository.setQsExpansion(.5f)
-            shadeRepository.setLegacyShadeExpansion(.7f)
-            runCurrent()
-
-            // THEN legacy shade expansion is passed through
-            assertThat(actual).isEqualTo(.7f)
-        }
-
-    @Test
-    fun shadeExpansionWhenNotInSplitShadeAndQsExpanded() =
-        testComponent.runTest {
-            val actual by collectLastValue(underTest.shadeExpansion)
-
-            // WHEN split shade is not enabled and QS is expanded
-            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            overrideResource(R.bool.config_use_split_notification_shade, false)
-            shadeRepository.setQsExpansion(.5f)
-            shadeRepository.setLegacyShadeExpansion(1f)
-            runCurrent()
-
-            // THEN shade expansion is zero
-            assertThat(actual).isEqualTo(0f)
-        }
-
-    @Test
-    fun shadeExpansionWhenNotInSplitShadeAndQsCollapsed() =
-        testComponent.runTest {
-            val actual by collectLastValue(underTest.shadeExpansion)
-
-            // WHEN split shade is not enabled and QS is expanded
-            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            shadeRepository.setQsExpansion(0f)
-            shadeRepository.setLegacyShadeExpansion(.6f)
-
-            // THEN shade expansion is zero
-            assertThat(actual).isEqualTo(.6f)
-        }
-
-    @Test
-    fun anyExpansion_shadeGreater() =
-        testComponent.runTest() {
-            // WHEN shade is more expanded than QS
-            shadeRepository.setLegacyShadeExpansion(.5f)
-            shadeRepository.setQsExpansion(0f)
-            runCurrent()
-
-            // THEN anyExpansion is .5f
-            assertThat(underTest.anyExpansion.value).isEqualTo(.5f)
-        }
-
-    @Test
-    fun anyExpansion_qsGreater() =
-        testComponent.runTest() {
-            // WHEN qs is more expanded than shade
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setQsExpansion(.5f)
-            runCurrent()
-
-            // THEN anyExpansion is .5f
-            assertThat(underTest.anyExpansion.value).isEqualTo(.5f)
-        }
-
-    @Test
-    fun userInteractingWithShade_shadeDraggedUpAndDown() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.isUserInteractingWithShade)
-            // GIVEN shade collapsed and not tracking input
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setLegacyShadeTracking(false)
-            runCurrent()
-
-            // THEN user is not interacting
-            assertThat(actual).isFalse()
-
-            // WHEN shade tracking starts
-            shadeRepository.setLegacyShadeTracking(true)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade dragged down halfway
-            shadeRepository.setLegacyShadeExpansion(.5f)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade fully expanded but tracking is not stopped
-            shadeRepository.setLegacyShadeExpansion(1f)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade fully collapsed but tracking is not stopped
-            shadeRepository.setLegacyShadeExpansion(0f)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade dragged halfway and tracking is stopped
-            shadeRepository.setLegacyShadeExpansion(.6f)
-            shadeRepository.setLegacyShadeTracking(false)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade completes expansion stopped
-            shadeRepository.setLegacyShadeExpansion(1f)
-            runCurrent()
-
-            // THEN user is not interacting
-            assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun userInteractingWithShade_shadeExpanded() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.isUserInteractingWithShade)
-            // GIVEN shade collapsed and not tracking input
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setLegacyShadeTracking(false)
-            runCurrent()
-
-            // THEN user is not interacting
-            assertThat(actual).isFalse()
-
-            // WHEN shade tracking starts
-            shadeRepository.setLegacyShadeTracking(true)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade dragged down halfway
-            shadeRepository.setLegacyShadeExpansion(.5f)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade fully expanded and tracking is stopped
-            shadeRepository.setLegacyShadeExpansion(1f)
-            shadeRepository.setLegacyShadeTracking(false)
-            runCurrent()
-
-            // THEN user is not interacting
-            assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun userInteractingWithShade_shadePartiallyExpanded() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.isUserInteractingWithShade)
-            // GIVEN shade collapsed and not tracking input
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setLegacyShadeTracking(false)
-            runCurrent()
-
-            // THEN user is not interacting
-            assertThat(actual).isFalse()
-
-            // WHEN shade tracking starts
-            shadeRepository.setLegacyShadeTracking(true)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade partially expanded
-            shadeRepository.setLegacyShadeExpansion(.4f)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN tracking is stopped
-            shadeRepository.setLegacyShadeTracking(false)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade goes back to collapsed
-            shadeRepository.setLegacyShadeExpansion(0f)
-            runCurrent()
-
-            // THEN user is not interacting
-            assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun userInteractingWithShade_shadeCollapsed() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.isUserInteractingWithShade)
-            // GIVEN shade expanded and not tracking input
-            shadeRepository.setLegacyShadeExpansion(1f)
-            shadeRepository.setLegacyShadeTracking(false)
-            runCurrent()
-
-            // THEN user is not interacting
-            assertThat(actual).isFalse()
-
-            // WHEN shade tracking starts
-            shadeRepository.setLegacyShadeTracking(true)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade dragged up halfway
-            shadeRepository.setLegacyShadeExpansion(.5f)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade fully collapsed and tracking is stopped
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setLegacyShadeTracking(false)
-            runCurrent()
-
-            // THEN user is not interacting
-            assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun userInteractingWithQs_qsDraggedUpAndDown() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.isUserInteractingWithQs)
-            // GIVEN qs collapsed and not tracking input
-            shadeRepository.setQsExpansion(0f)
-            shadeRepository.setLegacyQsTracking(false)
-            runCurrent()
-
-            // THEN user is not interacting
-            assertThat(actual).isFalse()
-
-            // WHEN qs tracking starts
-            shadeRepository.setLegacyQsTracking(true)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN qs dragged down halfway
-            shadeRepository.setQsExpansion(.5f)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN qs fully expanded but tracking is not stopped
-            shadeRepository.setQsExpansion(1f)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN qs fully collapsed but tracking is not stopped
-            shadeRepository.setQsExpansion(0f)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN qs dragged halfway and tracking is stopped
-            shadeRepository.setQsExpansion(.6f)
-            shadeRepository.setLegacyQsTracking(false)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN qs completes expansion stopped
-            shadeRepository.setQsExpansion(1f)
-            runCurrent()
-
-            // THEN user is not interacting
-            assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun isShadeTouchable_isFalse_whenFrpIsActive() =
-        testComponent.runTest {
-            deviceProvisioningRepository.setFactoryResetProtectionActive(true)
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
-                )
-            )
-            val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
-            runCurrent()
-            assertThat(isShadeTouchable).isFalse()
-        }
-
-    @Test
-    fun isShadeTouchable_isFalse_whenDeviceAsleepAndNotPulsing() =
-        testComponent.runTest {
-            powerRepository.updateWakefulness(
-                rawState = WakefulnessState.ASLEEP,
-                lastWakeReason = WakeSleepReason.POWER_BUTTON,
-                lastSleepReason = WakeSleepReason.OTHER,
-            )
-            // goingToSleep == false
-            // TODO: remove?
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.GONE,
-                    to = KeyguardState.LOCKSCREEN,
-                    transitionState = TransitionState.STARTED,
-                )
-            )
-            keyguardRepository.setDozeTransitionModel(
-                DozeTransitionModel(
-                    to = DozeStateModel.DOZE_AOD,
-                )
-            )
-            val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
-            runCurrent()
-            assertThat(isShadeTouchable).isFalse()
-        }
-
-    @Test
-    fun isShadeTouchable_isTrue_whenDeviceAsleepAndPulsing() =
-        testComponent.runTest {
-            powerRepository.updateWakefulness(
-                rawState = WakefulnessState.ASLEEP,
-                lastWakeReason = WakeSleepReason.POWER_BUTTON,
-                lastSleepReason = WakeSleepReason.OTHER,
-            )
-            // goingToSleep == false
-            // TODO: remove?
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.GONE,
-                    to = KeyguardState.LOCKSCREEN,
-                    transitionState = TransitionState.STARTED,
-                )
-            )
-            keyguardRepository.setDozeTransitionModel(
-                DozeTransitionModel(
-                    to = DozeStateModel.DOZE_PULSING,
-                )
-            )
-            val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
-            runCurrent()
-            assertThat(isShadeTouchable).isTrue()
-        }
-
-    @Test
-    fun isShadeTouchable_isFalse_whenStartingToSleepAndNotControlScreenOff() =
-        testComponent.runTest {
-            powerRepository.updateWakefulness(
-                rawState = WakefulnessState.STARTING_TO_SLEEP,
-                lastWakeReason = WakeSleepReason.POWER_BUTTON,
-                lastSleepReason = WakeSleepReason.OTHER,
-            )
-            // goingToSleep == true
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.GONE,
-                    to = KeyguardState.AOD,
-                    transitionState = TransitionState.STARTED,
-                )
-            )
-            whenever(dozeParameters.shouldControlScreenOff()).thenReturn(false)
-            val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
-            runCurrent()
-            assertThat(isShadeTouchable).isFalse()
-        }
-
-    @Test
-    fun isShadeTouchable_isTrue_whenStartingToSleepAndControlScreenOff() =
-        testComponent.runTest {
-            powerRepository.updateWakefulness(
-                rawState = WakefulnessState.STARTING_TO_SLEEP,
-                lastWakeReason = WakeSleepReason.POWER_BUTTON,
-                lastSleepReason = WakeSleepReason.OTHER,
-            )
-            // goingToSleep == true
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.GONE,
-                    to = KeyguardState.AOD,
-                    transitionState = TransitionState.STARTED,
-                )
-            )
-            whenever(dozeParameters.shouldControlScreenOff()).thenReturn(true)
-            val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
-            runCurrent()
-            assertThat(isShadeTouchable).isTrue()
-        }
-
-    @Test
-    fun isShadeTouchable_isTrue_whenNotAsleep() =
-        testComponent.runTest {
-            powerRepository.updateWakefulness(
-                rawState = WakefulnessState.AWAKE,
-                lastWakeReason = WakeSleepReason.POWER_BUTTON,
-                lastSleepReason = WakeSleepReason.OTHER,
-            )
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
-                )
-            )
-            val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
-            runCurrent()
-            assertThat(isShadeTouchable).isTrue()
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt
deleted file mode 100644
index 6e6e438..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt
+++ /dev/null
@@ -1,412 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.shade.domain.interactor
-
-import android.content.pm.UserInfo
-import android.os.UserManager
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.res.R
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.domain.UserDomainLayerModule
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-
-@SmallTest
-class ShadeInteractorLegacyImplTest : SysuiTestCase() {
-
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                UserDomainLayerModule::class,
-            ]
-    )
-    interface TestComponent : SysUITestComponent<ShadeInteractorLegacyImpl> {
-
-        val configurationRepository: FakeConfigurationRepository
-        val keyguardRepository: FakeKeyguardRepository
-        val keyguardTransitionRepository: FakeKeyguardTransitionRepository
-        val powerRepository: FakePowerRepository
-        val sceneInteractor: SceneInteractor
-        val shadeRepository: FakeShadeRepository
-        val userRepository: FakeUserRepository
-
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-                featureFlags: FakeFeatureFlagsClassicModule,
-                mocks: TestMocksModule,
-            ): TestComponent
-        }
-    }
-
-    private val dozeParameters: DozeParameters = mock()
-
-    private val testComponent: TestComponent =
-        DaggerShadeInteractorLegacyImplTest_TestComponent.factory()
-            .create(
-                test = this,
-                featureFlags =
-                    FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) },
-                mocks =
-                    TestMocksModule(
-                        dozeParameters = dozeParameters,
-                    ),
-            )
-
-    @Before
-    fun setUp() {
-        runBlocking {
-            val userInfos =
-                listOf(
-                    UserInfo(
-                        /* id= */ 0,
-                        /* name= */ "zero",
-                        /* iconPath= */ "",
-                        /* flags= */ UserInfo.FLAG_PRIMARY or
-                            UserInfo.FLAG_ADMIN or
-                            UserInfo.FLAG_FULL,
-                        UserManager.USER_TYPE_FULL_SYSTEM,
-                    ),
-                )
-            testComponent.apply {
-                userRepository.setUserInfos(userInfos)
-                userRepository.setSelectedUserInfo(userInfos[0])
-            }
-        }
-    }
-
-    @Test
-    fun fullShadeExpansionWhenShadeLocked() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.shadeExpansion)
-
-            keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
-            shadeRepository.setLockscreenShadeExpansion(0.5f)
-
-            assertThat(actual).isEqualTo(1f)
-        }
-
-    @Test
-    fun fullShadeExpansionWhenStatusBarStateIsNotShadeLocked() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.shadeExpansion)
-
-            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-
-            shadeRepository.setLockscreenShadeExpansion(0.5f)
-            assertThat(actual).isEqualTo(0.5f)
-
-            shadeRepository.setLockscreenShadeExpansion(0.8f)
-            assertThat(actual).isEqualTo(0.8f)
-        }
-
-    @Test
-    fun shadeExpansionWhenInSplitShadeAndQsExpanded() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.shadeExpansion)
-
-            // WHEN split shade is enabled and QS is expanded
-            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            overrideResource(R.bool.config_use_split_notification_shade, true)
-            configurationRepository.onAnyConfigurationChange()
-            shadeRepository.setQsExpansion(.5f)
-            shadeRepository.setLegacyShadeExpansion(.7f)
-            runCurrent()
-
-            // THEN legacy shade expansion is passed through
-            assertThat(actual).isEqualTo(.7f)
-        }
-
-    @Test
-    fun shadeExpansionWhenNotInSplitShadeAndQsExpanded() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.shadeExpansion)
-
-            // WHEN split shade is not enabled and QS is expanded
-            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            overrideResource(R.bool.config_use_split_notification_shade, false)
-            shadeRepository.setQsExpansion(.5f)
-            shadeRepository.setLegacyShadeExpansion(1f)
-            runCurrent()
-
-            // THEN shade expansion is zero
-            assertThat(actual).isEqualTo(0f)
-        }
-
-    @Test
-    fun shadeExpansionWhenNotInSplitShadeAndQsCollapsed() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.shadeExpansion)
-
-            // WHEN split shade is not enabled and QS is expanded
-            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            shadeRepository.setQsExpansion(0f)
-            shadeRepository.setLegacyShadeExpansion(.6f)
-
-            // THEN shade expansion is zero
-            assertThat(actual).isEqualTo(.6f)
-        }
-
-    @Test
-    fun userInteractingWithShade_shadeDraggedUpAndDown() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.isUserInteractingWithShade)
-            // GIVEN shade collapsed and not tracking input
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setLegacyShadeTracking(false)
-            runCurrent()
-
-            // THEN user is not interacting
-            assertThat(actual).isFalse()
-
-            // WHEN shade tracking starts
-            shadeRepository.setLegacyShadeTracking(true)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade dragged down halfway
-            shadeRepository.setLegacyShadeExpansion(.5f)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade fully expanded but tracking is not stopped
-            shadeRepository.setLegacyShadeExpansion(1f)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade fully collapsed but tracking is not stopped
-            shadeRepository.setLegacyShadeExpansion(0f)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade dragged halfway and tracking is stopped
-            shadeRepository.setLegacyShadeExpansion(.6f)
-            shadeRepository.setLegacyShadeTracking(false)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade completes expansion stopped
-            shadeRepository.setLegacyShadeExpansion(1f)
-            runCurrent()
-
-            // THEN user is not interacting
-            assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun userInteractingWithShade_shadeExpanded() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.isUserInteractingWithShade)
-            // GIVEN shade collapsed and not tracking input
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setLegacyShadeTracking(false)
-            runCurrent()
-
-            // THEN user is not interacting
-            assertThat(actual).isFalse()
-
-            // WHEN shade tracking starts
-            shadeRepository.setLegacyShadeTracking(true)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade dragged down halfway
-            shadeRepository.setLegacyShadeExpansion(.5f)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade fully expanded and tracking is stopped
-            shadeRepository.setLegacyShadeExpansion(1f)
-            shadeRepository.setLegacyShadeTracking(false)
-            runCurrent()
-
-            // THEN user is not interacting
-            assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun userInteractingWithShade_shadePartiallyExpanded() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.isUserInteractingWithShade)
-            // GIVEN shade collapsed and not tracking input
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setLegacyShadeTracking(false)
-            runCurrent()
-
-            // THEN user is not interacting
-            assertThat(actual).isFalse()
-
-            // WHEN shade tracking starts
-            shadeRepository.setLegacyShadeTracking(true)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade partially expanded
-            shadeRepository.setLegacyShadeExpansion(.4f)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN tracking is stopped
-            shadeRepository.setLegacyShadeTracking(false)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade goes back to collapsed
-            shadeRepository.setLegacyShadeExpansion(0f)
-            runCurrent()
-
-            // THEN user is not interacting
-            assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun userInteractingWithShade_shadeCollapsed() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.isUserInteractingWithShade)
-            // GIVEN shade expanded and not tracking input
-            shadeRepository.setLegacyShadeExpansion(1f)
-            shadeRepository.setLegacyShadeTracking(false)
-            runCurrent()
-
-            // THEN user is not interacting
-            assertThat(actual).isFalse()
-
-            // WHEN shade tracking starts
-            shadeRepository.setLegacyShadeTracking(true)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade dragged up halfway
-            shadeRepository.setLegacyShadeExpansion(.5f)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN shade fully collapsed and tracking is stopped
-            shadeRepository.setLegacyShadeExpansion(0f)
-            shadeRepository.setLegacyShadeTracking(false)
-            runCurrent()
-
-            // THEN user is not interacting
-            assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun userInteractingWithQs_qsDraggedUpAndDown() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.isUserInteractingWithQs)
-            // GIVEN qs collapsed and not tracking input
-            shadeRepository.setQsExpansion(0f)
-            shadeRepository.setLegacyQsTracking(false)
-            runCurrent()
-
-            // THEN user is not interacting
-            assertThat(actual).isFalse()
-
-            // WHEN qs tracking starts
-            shadeRepository.setLegacyQsTracking(true)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN qs dragged down halfway
-            shadeRepository.setQsExpansion(.5f)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN qs fully expanded but tracking is not stopped
-            shadeRepository.setQsExpansion(1f)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN qs fully collapsed but tracking is not stopped
-            shadeRepository.setQsExpansion(0f)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN qs dragged halfway and tracking is stopped
-            shadeRepository.setQsExpansion(.6f)
-            shadeRepository.setLegacyQsTracking(false)
-            runCurrent()
-
-            // THEN user is interacting
-            assertThat(actual).isTrue()
-
-            // WHEN qs completes expansion stopped
-            shadeRepository.setQsExpansion(1f)
-            runCurrent()
-
-            // THEN user is not interacting
-            assertThat(actual).isFalse()
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
deleted file mode 100644
index 310b86f..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
+++ /dev/null
@@ -1,627 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shade.domain.interactor
-
-import android.content.pm.UserInfo
-import android.os.UserManager
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.res.R
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.domain.UserDomainLayerModule
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth
-import dagger.BindsInstance
-import dagger.Component
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Ignore
-import org.junit.Test
-
-@SmallTest
-class ShadeInteractorSceneContainerImplTest : SysuiTestCase() {
-
-    @SysUISingleton
-    @Component(
-        modules =
-            [
-                SysUITestModule::class,
-                UserDomainLayerModule::class,
-            ]
-    )
-    interface TestComponent : SysUITestComponent<ShadeInteractorSceneContainerImpl> {
-
-        val configurationRepository: FakeConfigurationRepository
-        val keyguardRepository: FakeKeyguardRepository
-        val keyguardTransitionRepository: FakeKeyguardTransitionRepository
-        val powerRepository: FakePowerRepository
-        val sceneInteractor: SceneInteractor
-        val shadeRepository: FakeShadeRepository
-        val userRepository: FakeUserRepository
-
-        @Component.Factory
-        interface Factory {
-            fun create(
-                @BindsInstance test: SysuiTestCase,
-                featureFlags: FakeFeatureFlagsClassicModule,
-                mocks: TestMocksModule,
-            ): TestComponent
-        }
-    }
-
-    private val dozeParameters: DozeParameters = mock()
-
-    private val testComponent: TestComponent =
-        DaggerShadeInteractorSceneContainerImplTest_TestComponent.factory()
-            .create(
-                test = this,
-                featureFlags =
-                    FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) },
-                mocks =
-                    TestMocksModule(
-                        dozeParameters = dozeParameters,
-                    ),
-            )
-
-    @Before
-    fun setUp() {
-        runBlocking {
-            val userInfos =
-                listOf(
-                    UserInfo(
-                        /* id= */ 0,
-                        /* name= */ "zero",
-                        /* iconPath= */ "",
-                        /* flags= */ UserInfo.FLAG_PRIMARY or
-                            UserInfo.FLAG_ADMIN or
-                            UserInfo.FLAG_FULL,
-                        UserManager.USER_TYPE_FULL_SYSTEM,
-                    ),
-                )
-            testComponent.apply {
-                userRepository.setUserInfos(userInfos)
-                userRepository.setSelectedUserInfo(userInfos[0])
-            }
-        }
-    }
-
-    @Ignore("b/309825977")
-    @Test
-    fun qsExpansionWhenInSplitShadeAndQsExpanded() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.qsExpansion)
-
-            // WHEN split shade is enabled and QS is expanded
-            overrideResource(R.bool.config_use_split_notification_shade, true)
-            configurationRepository.onAnyConfigurationChange()
-            runCurrent()
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = SceneKey.QuickSettings,
-                        toScene = SceneKey.Shade,
-                        progress = MutableStateFlow(.3f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            sceneInteractor.setTransitionState(transitionState)
-            runCurrent()
-            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
-
-            // THEN legacy shade expansion is passed through
-            Truth.assertThat(actual).isEqualTo(.3f)
-        }
-
-    @Ignore("b/309825977")
-    @Test
-    fun qsExpansionWhenNotInSplitShadeAndQsExpanded() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.qsExpansion)
-
-            // WHEN split shade is not enabled and QS is expanded
-            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            overrideResource(R.bool.config_use_split_notification_shade, false)
-            configurationRepository.onAnyConfigurationChange()
-            runCurrent()
-            val progress = MutableStateFlow(.3f)
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = SceneKey.QuickSettings,
-                        toScene = SceneKey.Shade,
-                        progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            sceneInteractor.setTransitionState(transitionState)
-            runCurrent()
-
-            // THEN shade expansion is zero
-            Truth.assertThat(actual).isEqualTo(.7f)
-        }
-
-    @Test
-    fun qsFullscreen_falseWhenTransitioning() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.isQsFullscreen)
-
-            // WHEN scene transition active
-            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = SceneKey.QuickSettings,
-                        toScene = SceneKey.Shade,
-                        progress = MutableStateFlow(.3f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            sceneInteractor.setTransitionState(transitionState)
-            runCurrent()
-
-            // THEN QS is not fullscreen
-            Truth.assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun qsFullscreen_falseWhenIdleNotQS() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.isQsFullscreen)
-
-            // WHEN Idle but not on QuickSettings scene
-            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(SceneKey.Shade)
-                )
-            sceneInteractor.setTransitionState(transitionState)
-            runCurrent()
-
-            // THEN QS is not fullscreen
-            Truth.assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun qsFullscreen_trueWhenIdleQS() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.isQsFullscreen)
-
-            // WHEN Idle on QuickSettings scene
-            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(SceneKey.QuickSettings)
-                )
-            sceneInteractor.setTransitionState(transitionState)
-            runCurrent()
-
-            // THEN QS is fullscreen
-            Truth.assertThat(actual).isTrue()
-        }
-
-    @Test
-    fun lockscreenShadeExpansion_idle_onScene() =
-        testComponent.runTest() {
-            // GIVEN an expansion flow based on transitions to and from a scene
-            val key = SceneKey.Shade
-            val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
-            val expansionAmount by collectLastValue(expansion)
-
-            // WHEN transition state is idle on the scene
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
-            sceneInteractor.setTransitionState(transitionState)
-
-            // THEN expansion is 1
-            Truth.assertThat(expansionAmount).isEqualTo(1f)
-        }
-
-    @Test
-    fun lockscreenShadeExpansion_idle_onDifferentScene() =
-        testComponent.runTest() {
-            // GIVEN an expansion flow based on transitions to and from a scene
-            val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.Shade)
-            val expansionAmount by collectLastValue(expansion)
-
-            // WHEN transition state is idle on a different scene
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Idle(SceneKey.Lockscreen)
-                )
-            sceneInteractor.setTransitionState(transitionState)
-
-            // THEN expansion is 0
-            Truth.assertThat(expansionAmount).isEqualTo(0f)
-        }
-
-    @Test
-    fun lockscreenShadeExpansion_transitioning_toScene() =
-        testComponent.runTest() {
-            // GIVEN an expansion flow based on transitions to and from a scene
-            val key = SceneKey.QuickSettings
-            val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
-            val expansionAmount by collectLastValue(expansion)
-
-            // WHEN transition state is starting to move to the scene
-            val progress = MutableStateFlow(0f)
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Lockscreen,
-                        toScene = key,
-                        progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            sceneInteractor.setTransitionState(transitionState)
-
-            // THEN expansion is 0
-            Truth.assertThat(expansionAmount).isEqualTo(0f)
-
-            // WHEN transition state is partially to the scene
-            progress.value = .4f
-
-            // THEN expansion matches the progress
-            Truth.assertThat(expansionAmount).isEqualTo(.4f)
-
-            // WHEN transition completes
-            progress.value = 1f
-
-            // THEN expansion is 1
-            Truth.assertThat(expansionAmount).isEqualTo(1f)
-        }
-
-    @Test
-    fun lockscreenShadeExpansion_transitioning_fromScene() =
-        testComponent.runTest() {
-            // GIVEN an expansion flow based on transitions to and from a scene
-            val key = SceneKey.QuickSettings
-            val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
-            val expansionAmount by collectLastValue(expansion)
-
-            // WHEN transition state is starting to move to the scene
-            val progress = MutableStateFlow(0f)
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = key,
-                        toScene = SceneKey.Lockscreen,
-                        progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            sceneInteractor.setTransitionState(transitionState)
-
-            // THEN expansion is 1
-            Truth.assertThat(expansionAmount).isEqualTo(1f)
-
-            // WHEN transition state is partially to the scene
-            progress.value = .4f
-
-            // THEN expansion reflects the progress
-            Truth.assertThat(expansionAmount).isEqualTo(.6f)
-
-            // WHEN transition completes
-            progress.value = 1f
-
-            // THEN expansion is 0
-            Truth.assertThat(expansionAmount).isEqualTo(0f)
-        }
-
-    fun isQsBypassingShade_goneToQs() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.isQsBypassingShade)
-
-            // WHEN transitioning from QS directly to Gone
-            configurationRepository.onAnyConfigurationChange()
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Gone,
-                        toScene = SceneKey.QuickSettings,
-                        progress = MutableStateFlow(.1f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            sceneInteractor.setTransitionState(transitionState)
-            runCurrent()
-
-            // THEN qs is bypassing shade
-            Truth.assertThat(actual).isTrue()
-        }
-
-    fun isQsBypassingShade_shadeToQs() =
-        testComponent.runTest() {
-            val actual by collectLastValue(underTest.isQsBypassingShade)
-
-            // WHEN transitioning from QS to Shade
-            configurationRepository.onAnyConfigurationChange()
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Shade,
-                        toScene = SceneKey.QuickSettings,
-                        progress = MutableStateFlow(.1f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            sceneInteractor.setTransitionState(transitionState)
-            runCurrent()
-
-            // THEN qs is not bypassing shade
-            Truth.assertThat(actual).isFalse()
-        }
-
-    @Test
-    fun lockscreenShadeExpansion_transitioning_toAndFromDifferentScenes() =
-        testComponent.runTest() {
-            // GIVEN an expansion flow based on transitions to and from a scene
-            val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.QuickSettings)
-            val expansionAmount by collectLastValue(expansion)
-
-            // WHEN transition state is starting to between different scenes
-            val progress = MutableStateFlow(0f)
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Lockscreen,
-                        toScene = SceneKey.Shade,
-                        progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            sceneInteractor.setTransitionState(transitionState)
-
-            // THEN expansion is 0
-            Truth.assertThat(expansionAmount).isEqualTo(0f)
-
-            // WHEN transition state is partially complete
-            progress.value = .4f
-
-            // THEN expansion is still 0
-            Truth.assertThat(expansionAmount).isEqualTo(0f)
-
-            // WHEN transition completes
-            progress.value = 1f
-
-            // THEN expansion is still 0
-            Truth.assertThat(expansionAmount).isEqualTo(0f)
-        }
-
-    @Test
-    fun userInteracting_idle() =
-        testComponent.runTest() {
-            // GIVEN an interacting flow based on transitions to and from a scene
-            val key = SceneKey.Shade
-            val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
-            val interacting by collectLastValue(interactingFlow)
-
-            // WHEN transition state is idle
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
-            sceneInteractor.setTransitionState(transitionState)
-
-            // THEN interacting is false
-            Truth.assertThat(interacting).isFalse()
-        }
-
-    @Test
-    fun userInteracting_transitioning_toScene_programmatic() =
-        testComponent.runTest() {
-            // GIVEN an interacting flow based on transitions to and from a scene
-            val key = SceneKey.QuickSettings
-            val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
-            val interacting by collectLastValue(interactingFlow)
-
-            // WHEN transition state is starting to move to the scene
-            val progress = MutableStateFlow(0f)
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Lockscreen,
-                        toScene = key,
-                        progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            sceneInteractor.setTransitionState(transitionState)
-
-            // THEN interacting is false
-            Truth.assertThat(interacting).isFalse()
-
-            // WHEN transition state is partially to the scene
-            progress.value = .4f
-
-            // THEN interacting is false
-            Truth.assertThat(interacting).isFalse()
-
-            // WHEN transition completes
-            progress.value = 1f
-
-            // THEN interacting is false
-            Truth.assertThat(interacting).isFalse()
-        }
-
-    @Test
-    fun userInteracting_transitioning_toScene_userInputDriven() =
-        testComponent.runTest() {
-            // GIVEN an interacting flow based on transitions to and from a scene
-            val key = SceneKey.QuickSettings
-            val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
-            val interacting by collectLastValue(interactingFlow)
-
-            // WHEN transition state is starting to move to the scene
-            val progress = MutableStateFlow(0f)
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Lockscreen,
-                        toScene = key,
-                        progress = progress,
-                        isInitiatedByUserInput = true,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            sceneInteractor.setTransitionState(transitionState)
-
-            // THEN interacting is true
-            Truth.assertThat(interacting).isTrue()
-
-            // WHEN transition state is partially to the scene
-            progress.value = .4f
-
-            // THEN interacting is true
-            Truth.assertThat(interacting).isTrue()
-
-            // WHEN transition completes
-            progress.value = 1f
-
-            // THEN interacting is true
-            Truth.assertThat(interacting).isTrue()
-        }
-
-    @Test
-    fun userInteracting_transitioning_fromScene_programmatic() =
-        testComponent.runTest() {
-            // GIVEN an interacting flow based on transitions to and from a scene
-            val key = SceneKey.QuickSettings
-            val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
-            val interacting by collectLastValue(interactingFlow)
-
-            // WHEN transition state is starting to move to the scene
-            val progress = MutableStateFlow(0f)
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = key,
-                        toScene = SceneKey.Lockscreen,
-                        progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            sceneInteractor.setTransitionState(transitionState)
-
-            // THEN interacting is false
-            Truth.assertThat(interacting).isFalse()
-
-            // WHEN transition state is partially to the scene
-            progress.value = .4f
-
-            // THEN interacting is false
-            Truth.assertThat(interacting).isFalse()
-
-            // WHEN transition completes
-            progress.value = 1f
-
-            // THEN interacting is false
-            Truth.assertThat(interacting).isFalse()
-        }
-
-    @Test
-    fun userInteracting_transitioning_fromScene_userInputDriven() =
-        testComponent.runTest() {
-            // GIVEN an interacting flow based on transitions to and from a scene
-            val key = SceneKey.QuickSettings
-            val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
-            val interacting by collectLastValue(interactingFlow)
-
-            // WHEN transition state is starting to move to the scene
-            val progress = MutableStateFlow(0f)
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = key,
-                        toScene = SceneKey.Lockscreen,
-                        progress = progress,
-                        isInitiatedByUserInput = true,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            sceneInteractor.setTransitionState(transitionState)
-
-            // THEN interacting is true
-            Truth.assertThat(interacting).isTrue()
-
-            // WHEN transition state is partially to the scene
-            progress.value = .4f
-
-            // THEN interacting is true
-            Truth.assertThat(interacting).isTrue()
-
-            // WHEN transition completes
-            progress.value = 1f
-
-            // THEN interacting is true
-            Truth.assertThat(interacting).isTrue()
-        }
-
-    @Test
-    fun userInteracting_transitioning_toAndFromDifferentScenes() =
-        testComponent.runTest() {
-            // GIVEN an interacting flow based on transitions to and from a scene
-            val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, SceneKey.Shade)
-            val interacting by collectLastValue(interactingFlow)
-
-            // WHEN transition state is starting to between different scenes
-            val progress = MutableStateFlow(0f)
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = SceneKey.Lockscreen,
-                        toScene = SceneKey.QuickSettings,
-                        progress = MutableStateFlow(0f),
-                        isInitiatedByUserInput = true,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            sceneInteractor.setTransitionState(transitionState)
-
-            // THEN interacting is false
-            Truth.assertThat(interacting).isFalse()
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index ee27c5c..64fd80d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.plugins.PluginManager
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
+import java.util.function.BiConsumer
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.fail
 import kotlinx.coroutines.CoroutineDispatcher
@@ -100,10 +101,7 @@
         override fun toString() = "Manager[$tag]"
         override fun getPackage(): String = mComponentName.getPackageName()
         override fun getComponentName(): ComponentName = mComponentName
-
-        private var isDebug: Boolean = false
-        override fun getIsDebug(): Boolean = isDebug
-        override fun setIsDebug(value: Boolean) { isDebug = value }
+        override fun setLogFunc(func: BiConsumer<String, String>) { }
 
         override fun loadPlugin() {
             if (!mIsLoaded) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index bc50c25..3defee9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -112,7 +112,7 @@
         mPluginInstance = mPluginInstanceFactory.create(
                 mContext, mAppInfo, TEST_PLUGIN_COMPONENT_NAME,
                 TestPlugin.class, mPluginListener);
-        mPluginInstance.setIsDebug(true);
+        mPluginInstance.setLogFunc((tag, msg) -> Log.d((String) tag, (String) msg));
         mPluginContext = new WeakReference<>(mPluginInstance.getPluginContext());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
deleted file mode 100644
index e1d9282..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package com.android.systemui.statusbar;
-
-import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.spy;
-
-import android.app.ActivityManager;
-import android.app.Notification;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.res.R;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.FakeGlobalSettings;
-import com.android.systemui.util.time.FakeSystemClock;
-import com.android.systemui.util.time.SystemClock;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class AlertingNotificationManagerTest extends SysuiTestCase {
-    @Rule
-    public MockitoRule rule = MockitoJUnit.rule();
-
-    private static final String TEST_PACKAGE_NAME = "test";
-    private static final int TEST_UID = 0;
-
-    protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
-    protected static final int TEST_AUTO_DISMISS_TIME = 600;
-    protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800;
-    // Number of notifications to use in tests requiring multiple notifications
-    private static final int TEST_NUM_NOTIFICATIONS = 4;
-
-    protected final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings();
-    protected final FakeSystemClock mSystemClock = new FakeSystemClock();
-    protected final FakeExecutor mExecutor = new FakeExecutor(mSystemClock);
-
-    @Mock protected ExpandableNotificationRow mRow;
-
-    static {
-        assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
-        assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
-    }
-
-    private static class TestableAlertingNotificationManager extends AlertingNotificationManager {
-        private AlertEntry mLastCreatedEntry;
-
-        private TestableAlertingNotificationManager(SystemClock systemClock,
-                DelayableExecutor executor) {
-            super(new HeadsUpManagerLogger(logcatLogBuffer()), systemClock, executor);
-            mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
-            mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
-            mStickyForSomeTimeAutoDismissTime = TEST_STICKY_AUTO_DISMISS_TIME;
-        }
-
-        @Override
-        protected void onAlertEntryAdded(AlertEntry alertEntry) {}
-
-        @Override
-        protected void onAlertEntryRemoved(AlertEntry alertEntry) {}
-
-        @Override
-        protected AlertEntry createAlertEntry() {
-            mLastCreatedEntry = spy(super.createAlertEntry());
-            return mLastCreatedEntry;
-        }
-
-        @Override
-        public int getContentFlag() {
-            return FLAG_CONTENT_VIEW_CONTRACTED;
-        }
-    }
-
-    protected AlertingNotificationManager createAlertingNotificationManager() {
-        return new TestableAlertingNotificationManager(mSystemClock, mExecutor);
-    }
-
-    protected StatusBarNotification createSbn(int id, Notification n) {
-        return new StatusBarNotification(
-                TEST_PACKAGE_NAME /* pkg */,
-                TEST_PACKAGE_NAME,
-                id,
-                null /* tag */,
-                TEST_UID,
-                0 /* initialPid */,
-                n,
-                new UserHandle(ActivityManager.getCurrentUser()),
-                null /* overrideGroupKey */,
-                0 /* postTime */);
-    }
-
-    protected StatusBarNotification createSbn(int id, Notification.Builder n) {
-        return createSbn(id, n.build());
-    }
-
-    protected StatusBarNotification createSbn(int id) {
-        final Notification.Builder b = new Notification.Builder(mContext, "")
-                .setSmallIcon(R.drawable.ic_person)
-                .setContentTitle("Title")
-                .setContentText("Text");
-        return createSbn(id, b);
-    }
-
-    protected NotificationEntry createEntry(int id, Notification n) {
-        return new NotificationEntryBuilder().setSbn(createSbn(id, n)).build();
-    }
-
-    protected NotificationEntry createEntry(int id) {
-        return new NotificationEntryBuilder().setSbn(createSbn(id)).build();
-    }
-
-
-    @Test
-    public void testShowNotification_addsEntry() {
-        final AlertingNotificationManager alm = createAlertingNotificationManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
-
-        alm.showNotification(entry);
-
-        assertTrue(alm.isAlerting(entry.getKey()));
-        assertTrue(alm.hasNotifications());
-        assertEquals(entry, alm.getEntry(entry.getKey()));
-    }
-
-    @Test
-    public void testShowNotification_autoDismisses() {
-        final AlertingNotificationManager alm = createAlertingNotificationManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
-
-        alm.showNotification(entry);
-        mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME * 3 / 2);
-
-        assertFalse(alm.isAlerting(entry.getKey()));
-    }
-
-    @Test
-    public void testRemoveNotification_removeDeferred() {
-        final AlertingNotificationManager alm = createAlertingNotificationManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
-
-        alm.showNotification(entry);
-
-        final boolean removedImmediately = alm.removeNotification(
-                entry.getKey(), /* releaseImmediately = */ false);
-        assertFalse(removedImmediately);
-        assertTrue(alm.isAlerting(entry.getKey()));
-    }
-
-    @Test
-    public void testRemoveNotification_forceRemove() {
-        final AlertingNotificationManager alm = createAlertingNotificationManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
-
-        alm.showNotification(entry);
-
-        final boolean removedImmediately = alm.removeNotification(
-                entry.getKey(), /* releaseImmediately = */ true);
-        assertTrue(removedImmediately);
-        assertFalse(alm.isAlerting(entry.getKey()));
-    }
-
-    @Test
-    public void testReleaseAllImmediately() {
-        final AlertingNotificationManager alm = createAlertingNotificationManager();
-        for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
-            final NotificationEntry entry = createEntry(i);
-            entry.setRow(mRow);
-            alm.showNotification(entry);
-        }
-
-        alm.releaseAllImmediately();
-
-        assertEquals(0, alm.getAllEntries().count());
-    }
-
-    @Test
-    public void testCanRemoveImmediately_notShownLongEnough() {
-        final AlertingNotificationManager alm = createAlertingNotificationManager();
-        final NotificationEntry entry = createEntry(/* id = */ 0);
-
-        alm.showNotification(entry);
-
-        // The entry has just been added so we should not remove immediately.
-        assertFalse(alm.canRemoveImmediately(entry.getKey()));
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java
deleted file mode 100644
index 01fe40f..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java
+++ /dev/null
@@ -1,625 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
-import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
-import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
-import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
-import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.app.KeyguardManager;
-import android.app.Notification;
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.UserInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.util.SparseArray;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlagsClassic;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
-import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.settings.FakeSettings;
-
-import com.google.android.collect.Lists;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.concurrent.Executor;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class NotificationLockscreenUserManagerMainThreadTest extends SysuiTestCase {
-    @Mock
-    private NotificationPresenter mPresenter;
-    @Mock
-    private UserManager mUserManager;
-    @Mock
-    private UserTracker mUserTracker;
-
-    // Dependency mocks:
-    @Mock
-    private NotificationVisibilityProvider mVisibilityProvider;
-    @Mock
-    private CommonNotifCollection mNotifCollection;
-    @Mock
-    private DevicePolicyManager mDevicePolicyManager;
-    @Mock
-    private NotificationClickNotifier mClickNotifier;
-    @Mock
-    private OverviewProxyService mOverviewProxyService;
-    @Mock
-    private KeyguardManager mKeyguardManager;
-    @Mock
-    private DeviceProvisionedController mDeviceProvisionedController;
-    @Mock
-    private StatusBarStateController mStatusBarStateController;
-    @Mock
-    private BroadcastDispatcher mBroadcastDispatcher;
-    @Mock
-    private KeyguardStateController mKeyguardStateController;
-
-    private UserInfo mCurrentUser;
-    private UserInfo mSecondaryUser;
-    private UserInfo mWorkUser;
-    private UserInfo mCommunalUser;
-    private FakeSettings mSettings;
-    private TestNotificationLockscreenUserManager mLockscreenUserManager;
-    private NotificationEntry mCurrentUserNotif;
-    private NotificationEntry mSecondaryUserNotif;
-    private NotificationEntry mWorkProfileNotif;
-    private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic();
-    private Executor mBackgroundExecutor = Runnable::run; // Direct executor
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
-
-        int currentUserId = ActivityManager.getCurrentUser();
-        when(mUserTracker.getUserId()).thenReturn(currentUserId);
-        mSettings = new FakeSettings();
-        mSettings.setUserId(ActivityManager.getCurrentUser());
-        mCurrentUser = new UserInfo(currentUserId, "", 0);
-        mSecondaryUser = new UserInfo(currentUserId + 1, "", 0);
-        mWorkUser = new UserInfo(currentUserId + 2, "" /* name */, null /* iconPath */, 0,
-                UserManager.USER_TYPE_PROFILE_MANAGED);
-        mCommunalUser = new UserInfo(currentUserId + 3, "" /* name */, null /* iconPath */, 0,
-                UserManager.USER_TYPE_PROFILE_COMMUNAL);
-
-        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true);
-        when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList(
-                mCurrentUser, mWorkUser));
-        when(mUserManager.getProfilesIncludingCommunal(currentUserId)).thenReturn(
-                Lists.newArrayList(mCurrentUser, mWorkUser, mCommunalUser));
-        when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList(
-                mSecondaryUser));
-        when(mUserManager.getProfilesIncludingCommunal(mSecondaryUser.id)).thenReturn(
-                Lists.newArrayList(mSecondaryUser, mCommunalUser));
-        mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
-                Handler.createAsync(Looper.myLooper()));
-
-        Notification notifWithPrivateVisibility = new Notification();
-        notifWithPrivateVisibility.visibility = Notification.VISIBILITY_PRIVATE;
-        mCurrentUserNotif = new NotificationEntryBuilder()
-                .setNotification(notifWithPrivateVisibility)
-                .setUser(new UserHandle(mCurrentUser.id))
-                .build();
-        mSecondaryUserNotif = new NotificationEntryBuilder()
-                .setNotification(notifWithPrivateVisibility)
-                .setUser(new UserHandle(mSecondaryUser.id))
-                .build();
-        mWorkProfileNotif = new NotificationEntryBuilder()
-                .setNotification(notifWithPrivateVisibility)
-                .setUser(new UserHandle(mWorkUser.id))
-                .build();
-
-        mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
-        mLockscreenUserManager.setUpWithPresenter(mPresenter);
-    }
-
-    private void changeSetting(String setting) {
-        final Collection<Uri> lockScreenUris = new ArrayList<>();
-        lockScreenUris.add(Settings.Secure.getUriFor(setting));
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false,
-            lockScreenUris, 0);
-    }
-
-    @Test
-    public void testGetCurrentProfiles() {
-        final SparseArray<UserInfo> expectedCurProfiles = new SparseArray<>();
-        expectedCurProfiles.put(mCurrentUser.id, mCurrentUser);
-        expectedCurProfiles.put(mWorkUser.id, mWorkUser);
-        if (android.multiuser.Flags.supportCommunalProfile()) {
-            expectedCurProfiles.put(mCommunalUser.id, mCommunalUser);
-        }
-        assertTrue(mLockscreenUserManager.getCurrentProfiles().contentEquals(expectedCurProfiles));
-
-        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-
-        final SparseArray<UserInfo> expectedSecProfiles = new SparseArray<>();
-        expectedSecProfiles.put(mSecondaryUser.id, mSecondaryUser);
-        if (android.multiuser.Flags.supportCommunalProfile()) {
-            expectedSecProfiles.put(mCommunalUser.id, mCommunalUser);
-        }
-        assertTrue(mLockscreenUserManager.getCurrentProfiles().contentEquals(expectedSecProfiles));
-    }
-
-    @Test
-    public void testLockScreenShowNotificationsFalse() {
-        mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-        assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
-    }
-
-    @Test
-    public void testLockScreenShowNotificationsTrue() {
-        mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-        assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
-    }
-
-    @Test
-    public void testLockScreenAllowPrivateNotificationsTrue() {
-        mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-        assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
-    }
-
-    @Test
-    public void testLockScreenAllowPrivateNotificationsFalse() {
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
-                mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
-    }
-
-    @Test
-    public void testLockScreenAllowsWorkPrivateNotificationsFalse() {
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
-                mWorkUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
-    }
-
-    @Test
-    public void testLockScreenAllowsWorkPrivateNotificationsTrue() {
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
-                mWorkUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-        assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
-    }
-
-    @Test
-    public void testCurrentUserPrivateNotificationsNotRedacted() {
-        // GIVEN current user doesn't allow private notifications to show
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
-                mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
-        // THEN current user's notification is redacted
-        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
-    }
-
-    @Test
-    public void testCurrentUserPrivateNotificationsRedacted() {
-        // GIVEN current user allows private notifications to show
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
-                mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
-        // THEN current user's notification isn't redacted
-        assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
-    }
-
-    @Test
-    public void testWorkPrivateNotificationsRedacted() {
-        // GIVEN work profile doesn't private notifications to show
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
-                mWorkUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
-        // THEN work profile notification is redacted
-        assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
-        assertFalse(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
-    }
-
-    @Test
-    public void testWorkPrivateNotificationsNotRedacted() {
-        // GIVEN work profile allows private notifications to show
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
-                mWorkUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
-        // THEN work profile notification isn't redacted
-        assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
-        assertTrue(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
-    }
-
-    @Test
-    public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() {
-        // GIVEN work profile allows private notifications to show but the other users don't
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
-                mWorkUser.id);
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
-                mCurrentUser.id);
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
-                mSecondaryUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
-        // THEN the work profile notification doesn't need to be redacted
-        assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
-
-        // THEN the current user and secondary user notifications do need to be redacted
-        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
-        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
-    }
-
-    @Test
-    public void testWorkProfileRedacted_otherUsersNotRedacted() {
-        // GIVEN work profile doesn't allow private notifications to show but the other users do
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
-                mWorkUser.id);
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
-                mCurrentUser.id);
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
-                mSecondaryUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
-        // THEN the work profile notification needs to be redacted
-        assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
-
-        // THEN the current user and secondary user notifications don't need to be redacted
-        assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
-        assertFalse(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
-    }
-
-    @Test
-    public void testSecondaryUserNotRedacted_currentUserRedacted() {
-        // GIVEN secondary profile allows private notifications to show but the current user
-        // doesn't allow private notifications to show
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
-                mCurrentUser.id);
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
-                mSecondaryUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
-        // THEN the secondary profile notification still needs to be redacted because the current
-        // user's setting takes precedence
-        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
-    }
-
-    @Test
-    public void testUserSwitchedCallsOnUserSwitching() {
-        mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanging(mSecondaryUser.id,
-                mContext);
-        verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id);
-    }
-
-    @Test
-    public void testIsLockscreenPublicMode() {
-        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
-        mLockscreenUserManager.setLockscreenPublicMode(true, mCurrentUser.id);
-        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
-    }
-
-    @Test
-    public void testUpdateIsPublicMode() {
-        when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
-
-        NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class);
-        mLockscreenUserManager.addNotificationStateChangedListener(listener);
-        mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
-
-        // first call explicitly sets user 0 to not public; notifies
-        mLockscreenUserManager.updatePublicMode();
-        TestableLooper.get(this).processAllMessages();
-        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
-        verify(listener).onNotificationStateChanged();
-        clearInvocations(listener);
-
-        // calling again has no changes; does not notify
-        mLockscreenUserManager.updatePublicMode();
-        TestableLooper.get(this).processAllMessages();
-        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
-        verify(listener, never()).onNotificationStateChanged();
-
-        // Calling again with keyguard now showing makes user 0 public; notifies
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
-        mLockscreenUserManager.updatePublicMode();
-        TestableLooper.get(this).processAllMessages();
-        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
-        verify(listener).onNotificationStateChanged();
-        clearInvocations(listener);
-
-        // calling again has no changes; does not notify
-        mLockscreenUserManager.updatePublicMode();
-        TestableLooper.get(this).processAllMessages();
-        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
-        verify(listener, never()).onNotificationStateChanged();
-    }
-
-    @Test
-    public void testDevicePolicyDoesNotAllowNotifications() {
-        // User allows them
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        // DevicePolicy hides notifs on lockscreen
-        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
-                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
-
-        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
-                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
-        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
-        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
-                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
-        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
-    }
-
-    @Test
-    public void testDevicePolicyDoesNotAllowNotifications_secondary() {
-        Mockito.clearInvocations(mDevicePolicyManager);
-        // User allows notifications
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        // DevicePolicy hides notifications
-        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
-                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
-
-        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
-                0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
-        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
-        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
-                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
-        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
-    }
-
-    @Test
-    public void testDevicePolicy_noPrivateNotifications() {
-        Mockito.clearInvocations(mDevicePolicyManager);
-        // User allows notifications
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        // DevicePolicy hides sensitive content
-        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
-                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
-
-        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
-                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
-        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
-        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
-                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
-        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
-    }
-
-    @Test
-    public void testDevicePolicy_noPrivateNotifications_userAll() {
-        // User allows notifications
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        // DevicePolicy hides sensitive content
-        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
-                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
-
-        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
-                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
-        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
-        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
-                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
-        assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder()
-                .setNotification(new Notification())
-                .setUser(UserHandle.ALL)
-                .build()));
-    }
-
-    @Test
-    public void testDevicePolicyPrivateNotifications_secondary() {
-        Mockito.clearInvocations(mDevicePolicyManager);
-        // User allows notifications
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        // DevicePolicy hides sensitive content
-        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
-                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
-
-        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
-                0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
-        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
-        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
-                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
-        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
-    }
-
-    @Test
-    public void testHideNotifications_primary() {
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
-    }
-
-    @Test
-    public void testHideNotifications_secondary() {
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
-    }
-
-    @Test
-    public void testHideNotifications_secondary_userSwitch() {
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-
-        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
-    }
-
-    @Test
-    public void testShowNotifications_secondary_userSwitch() {
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-
-        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
-    }
-
-    @Test
-    public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() {
-        // DevicePolicy allows notifications
-        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
-                .thenReturn(0);
-        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
-                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
-        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
-        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
-                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
-        // KeyguardManager does not allow notifications
-        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
-
-        // User allows notifications
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no
-        // callback, so it's only updated when the setting is
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
-    }
-
-    @Test
-    public void testUserAllowsNotificationsInPublic_settingsChange() {
-        // User allows notifications
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
-
-        // User disables
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
-    }
-
-    private class TestNotificationLockscreenUserManager
-            extends NotificationLockscreenUserManagerImpl {
-        public TestNotificationLockscreenUserManager(Context context) {
-            super(
-                    context,
-                    mBroadcastDispatcher,
-                    mDevicePolicyManager,
-                    mUserManager,
-                    mUserTracker,
-                    (() -> mVisibilityProvider),
-                    (() -> mNotifCollection),
-                    mClickNotifier,
-                    (() -> mOverviewProxyService),
-                    NotificationLockscreenUserManagerMainThreadTest.this.mKeyguardManager,
-                    mStatusBarStateController,
-                    Handler.createAsync(Looper.myLooper()),
-                    Handler.createAsync(Looper.myLooper()),
-                    mBackgroundExecutor,
-                    mDeviceProvisionedController,
-                    mKeyguardStateController,
-                    mSettings,
-                    mock(DumpManager.class),
-                    mock(LockPatternUtils.class),
-                    mFakeFeatureFlags);
-        }
-
-        public BroadcastReceiver getBaseBroadcastReceiverForTest() {
-            return mBaseBroadcastReceiver;
-        }
-
-        public UserTracker.Callback getUserTrackerCallbackForTest() {
-            return mUserChangedCallback;
-        }
-
-        public ContentObserver getLockscreenSettingsObserverForTest() {
-            return mLockscreenSettingsObserver;
-        }
-
-        public ContentObserver getSettingsObserverForTest() {
-            return mSettingsObserver;
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
deleted file mode 100644
index 757f16c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ /dev/null
@@ -1,979 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import static android.app.Notification.VISIBILITY_PRIVATE;
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
-import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
-import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
-import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
-import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
-import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
-import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS;
-import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
-import static android.os.UserHandle.USER_ALL;
-import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
-import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
-import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atMost;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.app.KeyguardManager;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.UserInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.FlagsParameterization;
-import android.provider.Settings;
-import android.testing.TestableLooper;
-import android.util.SparseArray;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlagsClassic;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.log.LogWtfHandlerRule;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
-import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.FakeSettings;
-import com.android.systemui.util.time.FakeSystemClock;
-import com.google.android.collect.Lists;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
-@SmallTest
-@RunWith(ParameterizedAndroidJunit4.class)
-@TestableLooper.RunWithLooper
-public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
-
-    @Parameters(name = "{0}")
-    public static List<FlagsParameterization> getParams() {
-        return FlagsParameterization.allCombinationsOf(
-                FLAG_ALLOW_PRIVATE_PROFILE,
-                FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS);
-    }
-
-    public NotificationLockscreenUserManagerTest(FlagsParameterization flags) {
-        mSetFlagsRule.setFlagsParameterization(flags);
-    }
-
-    private static final int TEST_PROFILE_USERHANDLE = 12;
-    @Mock
-    private NotificationPresenter mPresenter;
-    @Mock
-    private UserManager mUserManager;
-    @Mock
-    private UserTracker mUserTracker;
-
-    // Dependency mocks:
-    @Mock
-    private NotificationVisibilityProvider mVisibilityProvider;
-    @Mock
-    private CommonNotifCollection mNotifCollection;
-    @Mock
-    private DevicePolicyManager mDevicePolicyManager;
-    @Mock
-    private NotificationClickNotifier mClickNotifier;
-    @Mock
-    private OverviewProxyService mOverviewProxyService;
-    @Mock
-    private KeyguardManager mKeyguardManager;
-    @Mock
-    private DeviceProvisionedController mDeviceProvisionedController;
-    @Mock
-    private StatusBarStateController mStatusBarStateController;
-    @Mock
-    private BroadcastDispatcher mBroadcastDispatcher;
-    @Mock
-    private KeyguardStateController mKeyguardStateController;
-
-    private UserInfo mCurrentUser;
-    private UserInfo mSecondaryUser;
-    private UserInfo mWorkUser;
-    private UserInfo mCommunalUser;
-    private FakeSettings mSettings;
-    private TestNotificationLockscreenUserManager mLockscreenUserManager;
-    private NotificationEntry mCurrentUserNotif;
-    private NotificationEntry mSecondaryUserNotif;
-    private NotificationEntry mWorkProfileNotif;
-    private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic();
-    private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
-    private final FakeExecutor mBackgroundExecutor = new FakeExecutor(mFakeSystemClock);
-    private final Executor mMainExecutor = Runnable::run; // Direct executor
-
-    @Rule public final LogWtfHandlerRule wtfHandlerRule = new LogWtfHandlerRule();
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
-
-        int currentUserId = ActivityManager.getCurrentUser();
-        when(mUserTracker.getUserId()).thenReturn(currentUserId);
-        mSettings = new FakeSettings();
-        mSettings.setUserId(ActivityManager.getCurrentUser());
-        mCurrentUser = new UserInfo(currentUserId, "", 0);
-        mSecondaryUser = new UserInfo(currentUserId + 1, "", 0);
-        mWorkUser = new UserInfo(currentUserId + 2, "" /* name */, null /* iconPath */, 0,
-                UserManager.USER_TYPE_PROFILE_MANAGED);
-        mCommunalUser = new UserInfo(currentUserId + 3, "" /* name */, null /* iconPath */, 0,
-                UserManager.USER_TYPE_PROFILE_COMMUNAL);
-
-        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true);
-        when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList(
-                mCurrentUser, mWorkUser));
-        when(mUserManager.getProfilesIncludingCommunal(currentUserId)).thenReturn(
-                Lists.newArrayList(mCurrentUser, mWorkUser, mCommunalUser));
-        when(mUserManager.getUsers()).thenReturn(Lists.newArrayList(
-                mCurrentUser, mWorkUser, mSecondaryUser, mCommunalUser));
-        when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList(
-                mSecondaryUser));
-        when(mUserManager.getProfilesIncludingCommunal(mSecondaryUser.id)).thenReturn(
-                Lists.newArrayList(mSecondaryUser, mCommunalUser));
-        mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
-                mockExecutorHandler(mMainExecutor));
-
-        Notification notifWithPrivateVisibility = new Notification();
-        notifWithPrivateVisibility.visibility = VISIBILITY_PRIVATE;
-        mCurrentUserNotif = new NotificationEntryBuilder()
-                .setNotification(notifWithPrivateVisibility)
-                .setUser(new UserHandle(mCurrentUser.id))
-                .build();
-        NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
-        channel.setLockscreenVisibility(VISIBILITY_NO_OVERRIDE);
-        mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking())
-                .setChannel(channel)
-                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
-        when(mNotifCollection.getEntry(mCurrentUserNotif.getKey())).thenReturn(mCurrentUserNotif);
-        mSecondaryUserNotif = new NotificationEntryBuilder()
-                .setNotification(notifWithPrivateVisibility)
-                .setUser(new UserHandle(mSecondaryUser.id))
-                .build();
-        mSecondaryUserNotif.setRanking(new RankingBuilder(mSecondaryUserNotif.getRanking())
-                .setChannel(channel)
-                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
-        when(mNotifCollection.getEntry(
-                mSecondaryUserNotif.getKey())).thenReturn(mSecondaryUserNotif);
-        mWorkProfileNotif = new NotificationEntryBuilder()
-                .setNotification(notifWithPrivateVisibility)
-                .setUser(new UserHandle(mWorkUser.id))
-                .build();
-        mWorkProfileNotif.setRanking(new RankingBuilder(mWorkProfileNotif.getRanking())
-                .setChannel(channel)
-                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
-        when(mNotifCollection.getEntry(mWorkProfileNotif.getKey())).thenReturn(mWorkProfileNotif);
-
-        mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
-        mLockscreenUserManager.setUpWithPresenter(mPresenter);
-
-        mBackgroundExecutor.runAllReady();
-    }
-
-    @After
-    public void tearDown() {
-        // Validate that all tests processed all background posted code
-        assertEquals(0, mBackgroundExecutor.numPending());
-    }
-
-    private void changeSetting(String setting) {
-        final Collection<Uri> lockScreenUris = new ArrayList<>();
-        lockScreenUris.add(Settings.Secure.getUriFor(setting));
-        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false,
-            lockScreenUris, 0);
-    }
-
-    @Test
-    @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
-    public void testInit() {
-        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
-        mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
-        mLockscreenUserManager.setUpWithPresenter(mPresenter);
-
-        mBackgroundExecutor.runAllReady();
-
-        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
-        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
-    }
-
-    @Test
-    public void testGetCurrentProfiles() {
-        final SparseArray<UserInfo> expectedCurProfiles = new SparseArray<>();
-        expectedCurProfiles.put(mCurrentUser.id, mCurrentUser);
-        expectedCurProfiles.put(mWorkUser.id, mWorkUser);
-        if (android.multiuser.Flags.supportCommunalProfile()) {
-            expectedCurProfiles.put(mCommunalUser.id, mCommunalUser);
-        }
-        assertTrue(mLockscreenUserManager.getCurrentProfiles().contentEquals(expectedCurProfiles));
-
-        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-
-        final SparseArray<UserInfo> expectedSecProfiles = new SparseArray<>();
-        expectedSecProfiles.put(mSecondaryUser.id, mSecondaryUser);
-        if (android.multiuser.Flags.supportCommunalProfile()) {
-            expectedSecProfiles.put(mCommunalUser.id, mCommunalUser);
-        }
-        assertTrue(mLockscreenUserManager.getCurrentProfiles().contentEquals(expectedSecProfiles));
-    }
-
-    @Test
-    public void testLockScreenShowNotificationsFalse() {
-        mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-        assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
-    }
-
-    @Test
-    public void testLockScreenShowNotificationsTrue() {
-        mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-        assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
-    }
-
-    @Test
-    public void testLockScreenAllowPrivateNotificationsTrue() {
-        mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-        assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
-    }
-
-    @Test
-    public void testLockScreenAllowPrivateNotificationsFalse() {
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
-                mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
-    }
-
-    @Test
-    public void testLockScreenAllowsWorkPrivateNotificationsFalse() {
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
-                mWorkUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
-    }
-
-    @Test
-    public void testLockScreenAllowsWorkPrivateNotificationsTrue() {
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
-                mWorkUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-        assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
-    }
-
-    @Test
-    public void testCurrentUserPrivateNotificationsRedacted() {
-        // GIVEN current user doesn't allow private notifications to show
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
-                mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
-        // THEN current user's notification is redacted
-        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
-    }
-
-    @Test
-    public void testCurrentUserPrivateNotificationsNotRedacted() {
-        // GIVEN current user allows private notifications to show
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
-                mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
-        // THEN current user's notification isn't redacted
-        assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
-    }
-
-    @Test
-    public void testCurrentUserPrivateNotificationsRedactedChannel() {
-        // GIVEN current user allows private notifications to show
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
-                mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
-        // but doesn't allow it at the channel level
-        NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
-        channel.setLockscreenVisibility(VISIBILITY_PRIVATE);
-        mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking())
-                .setChannel(channel)
-                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
-        // THEN the notification is redacted
-        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
-    }
-
-    @Test
-    public void testCurrentUserPrivateNotificationsNullChannel() {
-        // GIVEN current user allows private notifications to show
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
-                mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
-        mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking())
-                .setChannel(null)
-                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
-        // THEN the notification is not redacted
-        assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
-    }
-
-    @Test
-    public void testWorkPrivateNotificationsRedacted() {
-        // GIVEN work profile doesn't private notifications to show
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
-                mWorkUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
-        // THEN work profile notification is redacted
-        assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
-        assertFalse(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
-    }
-
-    @Test
-    public void testWorkPrivateNotificationsNotRedacted() {
-        // GIVEN work profile allows private notifications to show
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
-                mWorkUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
-        // THEN work profile notification isn't redacted
-        assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
-        assertTrue(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
-    }
-
-    @Test
-    public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() {
-        // GIVEN work profile allows private notifications to show but the other users don't
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
-                mWorkUser.id);
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
-                mCurrentUser.id);
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
-                mSecondaryUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
-        // THEN the work profile notification doesn't need to be redacted
-        assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
-
-        // THEN the current user and secondary user notifications do need to be redacted
-        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
-        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
-    }
-
-    @Test
-    public void testWorkProfileRedacted_otherUsersNotRedacted() {
-        // GIVEN work profile doesn't allow private notifications to show but the other users do
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
-                mWorkUser.id);
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
-                mCurrentUser.id);
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
-                mSecondaryUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
-        // THEN the work profile notification needs to be redacted
-        assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
-
-        // THEN the current user and secondary user notifications don't need to be redacted
-        assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
-        assertFalse(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
-    }
-
-    @Test
-    public void testSecondaryUserNotRedacted_currentUserRedacted() {
-        // GIVEN secondary profile allows private notifications to show but the current user
-        // doesn't allow private notifications to show
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
-                mCurrentUser.id);
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
-                mSecondaryUser.id);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
-        // THEN the secondary profile notification still needs to be redacted because the current
-        // user's setting takes precedence
-        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
-    }
-
-    @Test
-    public void testUserSwitchedCallsOnUserSwitching() {
-        mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanging(mSecondaryUser.id,
-                mContext);
-        verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id);
-    }
-
-    @Test
-    public void testIsLockscreenPublicMode() {
-        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
-        mLockscreenUserManager.setLockscreenPublicMode(true, mCurrentUser.id);
-        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
-    }
-
-    @Test
-    public void testUpdateIsPublicMode() {
-        when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
-        when(mKeyguardStateController.isShowing()).thenReturn(false);
-
-        NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class);
-        mLockscreenUserManager.addNotificationStateChangedListener(listener);
-        mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
-
-        // first call explicitly sets user 0 to not public; notifies
-        mLockscreenUserManager.updatePublicMode();
-        mBackgroundExecutor.runAllReady();
-        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
-        verify(listener).onNotificationStateChanged();
-        clearInvocations(listener);
-
-        // calling again has no changes; does not notify
-        mLockscreenUserManager.updatePublicMode();
-        mBackgroundExecutor.runAllReady();
-        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
-        verify(listener, never()).onNotificationStateChanged();
-
-        // Calling again with keyguard now showing makes user 0 public; notifies
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
-        mLockscreenUserManager.updatePublicMode();
-        mBackgroundExecutor.runAllReady();
-        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
-        verify(listener).onNotificationStateChanged();
-        clearInvocations(listener);
-
-        // calling again has no changes; does not notify
-        mLockscreenUserManager.updatePublicMode();
-        mBackgroundExecutor.runAllReady();
-        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
-        verify(listener, never()).onNotificationStateChanged();
-    }
-
-    @Test
-    public void testDevicePolicyDoesNotAllowNotifications() {
-        // User allows them
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        // DevicePolicy hides notifs on lockscreen
-        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
-                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
-
-        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
-                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
-        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
-        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
-                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
-        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
-    }
-
-    @Test
-    public void testDevicePolicyDoesNotAllowNotifications_deviceOwnerSetsForUserAll() {
-        // User allows them
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        // DevicePolicy hides notifs on lockscreen
-        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
-                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
-        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
-                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
-
-        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
-                0, null, null, 0, true, false, null, USER_ALL, 0);
-        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
-        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
-                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
-        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
-        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
-    }
-
-    @Test
-    public void testDevicePolicyDoesNotAllowNotifications_userAll() {
-        // User allows them
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        // DevicePolicy hides notifications
-        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
-                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
-
-        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
-                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
-        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
-        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
-                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
-        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(USER_ALL));
-    }
-
-    @Test
-    public void testDevicePolicyDoesNotAllowNotifications_secondary() {
-        Mockito.clearInvocations(mDevicePolicyManager);
-        // User allows notifications
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        // DevicePolicy hides notifications
-        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
-                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
-
-        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
-                0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
-        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
-        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
-                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
-        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
-
-        verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
-    }
-
-    @Test
-    @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
-    public void testEarlyUserSwitch() {
-        mLockscreenUserManager =
-                new TestNotificationLockscreenUserManager(mContext);
-        mBackgroundExecutor.runAllReady();
-        mLockscreenUserManager.mUserChangedCallback.onUserChanging(
-                mCurrentUser.id, mContext);
-        // no crash!
-    }
-
-    @Test
-    @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
-    public void testKeyguardManager_noPrivateNotifications() {
-        Mockito.clearInvocations(mDevicePolicyManager);
-        // User allows notifications
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
-                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
-        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
-        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
-                new Intent(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED)
-                        .putExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, true));
-
-        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
-        // it's a global field, confirm secondary too
-        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
-        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
-        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
-                mSecondaryUser.id));
-    }
-
-    @Test
-    public void testDevicePolicy_noPrivateNotifications() {
-        Mockito.clearInvocations(mDevicePolicyManager);
-        // User allows notifications
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        // DevicePolicy hides sensitive content
-        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
-                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
-
-        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
-                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
-        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
-        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
-                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
-        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
-
-        verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
-    }
-
-    @Test
-    public void testDevicePolicy_noPrivateNotifications_userAll() {
-        NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
-        channel.setLockscreenVisibility(VISIBILITY_NO_OVERRIDE);
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setNotification(new Notification())
-                .setUser(UserHandle.ALL)
-                .build();
-        notifEntry.setRanking(new RankingBuilder(notifEntry.getRanking())
-                .setChannel(channel)
-                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
-        when(mNotifCollection.getEntry(notifEntry.getKey())).thenReturn(notifEntry);
-        // User allows notifications
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        // DevicePolicy hides sensitive content
-        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
-                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
-
-        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
-                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
-        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
-        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
-                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
-        assertTrue(mLockscreenUserManager.needsRedaction(notifEntry));
-    }
-
-    @Test
-    public void testDevicePolicyPrivateNotifications_secondary() {
-        Mockito.clearInvocations(mDevicePolicyManager);
-        // User allows notifications
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        // DevicePolicy hides sensitive content
-        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
-                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
-
-        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
-                0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
-        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
-        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
-                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
-        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
-
-        verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
-    }
-
-    @Test
-    public void testHideNotifications_primary() {
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
-    }
-
-    @Test
-    public void testHideNotifications_secondary() {
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
-    }
-
-    @Test
-    public void testHideNotifications_workProfile() {
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mWorkUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mWorkUser.id));
-    }
-
-    @Test
-    public void testHideNotifications_secondary_userSwitch() {
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-
-        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
-    }
-
-    @Test
-    public void testShowNotifications_secondary_userSwitch() {
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-
-        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
-    }
-
-    @Test
-    @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
-    public void testShouldShowLockscreenNotifications_keyguardManagerNoPrivateNotifications_show() {
-        // KeyguardManager does not allow notifications
-        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
-        // User allows notifications
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-        // DevicePolicy allows notifications
-        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
-                .thenReturn(0);
-        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
-                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
-        mLockscreenUserManager.mKeyguardReceiver.setPendingResult(pr);
-        mLockscreenUserManager.mKeyguardReceiver.onReceive(mContext,
-                new Intent(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED)
-                        .putExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, false));
-
-        assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
-        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
-    }
-
-    @Test
-    @DisableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
-    public void testShouldShowLockscreenNotifications_keyguardManagerNoPrivateNotifications() {
-        // KeyguardManager does not allow notifications
-        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
-        // User allows notifications
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-        // DevicePolicy allows notifications
-        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
-                .thenReturn(0);
-        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
-                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
-        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
-        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
-                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
-        assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
-    }
-
-    @Test
-    @DisableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
-    public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() {
-        // DevicePolicy allows notifications
-        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
-                .thenReturn(0);
-        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
-                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
-        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
-        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
-                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
-        // KeyguardManager does not allow notifications
-        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
-
-        // User allows notifications
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no
-        // callback, so it's only updated when the setting is
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
-    }
-
-    @Test
-    public void testUserAllowsNotificationsInPublic_settingsChange() {
-        // User allows notifications
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
-
-        // User disables
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
-    }
-
-    @Test
-    public void testUserAllowsNotificationsInPublic_devicePolicyChange() {
-        // User allows notifications
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
-        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
-        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
-
-        // DevicePolicy disables notifications
-        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
-                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
-        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
-                0, null, null, 0, true, false, null, mCurrentUser.id, 0);
-        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
-        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
-                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
-        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
-    }
-
-    @Test
-    public void testNewUserAdded() {
-        int newUserId = 14;
-        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, newUserId);
-        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, newUserId);
-        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, newUserId))
-                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
-
-        BroadcastReceiver broadcastReceiver =
-                mLockscreenUserManager.getBaseBroadcastReceiverForTest();
-        final Bundle extras = new Bundle();
-        final Intent intent = new Intent(Intent.ACTION_USER_ADDED);
-        intent.putExtras(extras);
-        intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
-        broadcastReceiver.onReceive(mContext, intent);
-
-        // One background task to run which will setup the new user
-        assertEquals(1, mBackgroundExecutor.runAllReady());
-
-        verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), eq(newUserId));
-
-        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(newUserId));
-        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(newUserId));
-    }
-
-    @Test
-    @EnableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
-    public void testProfileAvailabilityIntent() {
-        mLockscreenUserManager.mCurrentProfiles.clear();
-        assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
-        mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
-        simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_AVAILABLE);
-        int numProfiles = android.multiuser.Flags.supportCommunalProfile() ? 3 : 2;
-        assertEquals(numProfiles, mLockscreenUserManager.mCurrentProfiles.size());
-    }
-
-    @Test
-    @EnableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
-    public void testProfileUnAvailabilityIntent() {
-        mLockscreenUserManager.mCurrentProfiles.clear();
-        assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
-        mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
-        simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_UNAVAILABLE);
-        int numProfiles = android.multiuser.Flags.supportCommunalProfile() ? 3 : 2;
-        assertEquals(numProfiles, mLockscreenUserManager.mCurrentProfiles.size());
-    }
-
-    @Test
-    @DisableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
-    public void testManagedProfileAvailabilityIntent() {
-        mLockscreenUserManager.mCurrentProfiles.clear();
-        mLockscreenUserManager.mCurrentManagedProfiles.clear();
-        assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
-        assertEquals(0, mLockscreenUserManager.mCurrentManagedProfiles.size());
-        mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
-        simulateProfileAvailabilityActions(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
-        int numProfiles = android.multiuser.Flags.supportCommunalProfile() ? 3 : 2;
-        assertEquals(numProfiles, mLockscreenUserManager.mCurrentProfiles.size());
-        assertEquals(1, mLockscreenUserManager.mCurrentManagedProfiles.size());
-    }
-
-    @Test
-    @DisableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
-    public void testManagedProfileUnAvailabilityIntent() {
-        mLockscreenUserManager.mCurrentProfiles.clear();
-        mLockscreenUserManager.mCurrentManagedProfiles.clear();
-        assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
-        assertEquals(0, mLockscreenUserManager.mCurrentManagedProfiles.size());
-        mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
-        simulateProfileAvailabilityActions(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
-        int numProfiles = android.multiuser.Flags.supportCommunalProfile() ? 3 : 2;
-        assertEquals(numProfiles, mLockscreenUserManager.mCurrentProfiles.size());
-        assertEquals(1, mLockscreenUserManager.mCurrentManagedProfiles.size());
-    }
-
-    private void simulateProfileAvailabilityActions(String intentAction) {
-        BroadcastReceiver broadcastReceiver =
-                mLockscreenUserManager.getBaseBroadcastReceiverForTest();
-        final Intent intent = new Intent(intentAction);
-        intent.putExtra(Intent.EXTRA_USER_HANDLE, TEST_PROFILE_USERHANDLE);
-        broadcastReceiver.onReceive(mContext, intent);
-    }
-
-    private class TestNotificationLockscreenUserManager
-            extends NotificationLockscreenUserManagerImpl {
-        public TestNotificationLockscreenUserManager(Context context) {
-            super(
-                    context,
-                    mBroadcastDispatcher,
-                    mDevicePolicyManager,
-                    mUserManager,
-                    mUserTracker,
-                    (() -> mVisibilityProvider),
-                    (() -> mNotifCollection),
-                    mClickNotifier,
-                    (() -> mOverviewProxyService),
-                    NotificationLockscreenUserManagerTest.this.mKeyguardManager,
-                    mStatusBarStateController,
-                    mockExecutorHandler(mMainExecutor),
-                    mockExecutorHandler(mBackgroundExecutor),
-                    mBackgroundExecutor,
-                    mDeviceProvisionedController,
-                    mKeyguardStateController,
-                    mSettings,
-                    mock(DumpManager.class),
-                    mock(LockPatternUtils.class),
-                    mFakeFeatureFlags);
-        }
-
-        public BroadcastReceiver getBaseBroadcastReceiverForTest() {
-            return mBaseBroadcastReceiver;
-        }
-
-        public UserTracker.Callback getUserTrackerCallbackForTest() {
-            return mUserChangedCallback;
-        }
-
-        public ContentObserver getLockscreenSettingsObserverForTest() {
-            return mLockscreenSettingsObserver;
-        }
-
-        public ContentObserver getSettingsObserverForTest() {
-            return mSettingsObserver;
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index f25ce0a..8cb064d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -27,7 +27,8 @@
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -36,23 +37,28 @@
 import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions
 import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.data.repository.FakePowerRepository
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
+import com.android.systemui.shade.LargeScreenHeaderHelper
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl
 import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl
 import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
 import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import kotlinx.coroutines.flow.emptyFlow
 import org.junit.Assert.assertEquals
@@ -65,7 +71,6 @@
 import org.mockito.ArgumentMatchers.anyFloat
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
@@ -77,15 +82,17 @@
 @TestableLooper.RunWithLooper
 class StatusBarStateControllerImplTest : SysuiTestCase() {
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val testDispatcher = kosmos.testDispatcher
     private lateinit var shadeInteractor: ShadeInteractor
     private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
     private lateinit var fromPrimaryBouncerTransitionInteractor:
         FromPrimaryBouncerTransitionInteractor
-    @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
-    @Mock lateinit var mockDarkAnimator: ObjectAnimator
-    @Mock lateinit var deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor
+    private val interactionJankMonitor = mock<InteractionJankMonitor>()
+    private val mockDarkAnimator = mock<ObjectAnimator>()
+    private val deviceEntryUdfpsInteractor = mock<DeviceEntryUdfpsInteractor>()
+    private val largeScreenHeaderHelper = mock<LargeScreenHeaderHelper>()
 
     private lateinit var controller: StatusBarStateControllerImpl
     private lateinit var uiEventLogger: UiEventLoggerFake
@@ -127,7 +134,7 @@
                 FakeKeyguardBouncerRepository(),
                 ConfigurationInteractor(configurationRepository),
                 shadeRepository,
-                utils::sceneInteractor
+                { kosmos.sceneInteractor },
             )
         val keyguardTransitionInteractor =
             KeyguardTransitionInteractor(
@@ -137,15 +144,25 @@
                 { fromLockscreenTransitionInteractor },
                 { fromPrimaryBouncerTransitionInteractor }
             )
+        val communalInteractor = kosmos.communalInteractor
         fromLockscreenTransitionInteractor =
             FromLockscreenTransitionInteractor(
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 testScope.backgroundScope,
+                testDispatcher,
+                testDispatcher,
                 keyguardInteractor,
                 featureFlags,
                 shadeRepository,
                 powerInteractor,
+                GlanceableHubTransitions(
+                    testScope,
+                    testDispatcher,
+                    keyguardTransitionInteractor,
+                    keyguardTransitionRepository,
+                    communalInteractor
+                ),
                 {
                     InWindowLauncherUnlockAnimationInteractor(
                         InWindowLauncherUnlockAnimationRepository(),
@@ -161,7 +178,10 @@
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 testScope.backgroundScope,
+                testDispatcher,
+                testDispatcher,
                 keyguardInteractor,
+                communalInteractor,
                 featureFlags,
                 mock(),
                 mock(),
@@ -189,6 +209,7 @@
                         ResourcesSplitShadeStateController(),
                         keyguardInteractor,
                         deviceEntryUdfpsInteractor,
+                        largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
                     ),
                     shadeRepository,
                 )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
index 6059363..cd74410 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
@@ -73,7 +73,7 @@
     }
 
     private fun flagNotificationAsHun() {
-        `when`(headsUpManager.isAlerting(notificationKey)).thenReturn(true)
+        `when`(headsUpManager.isHeadsUpEntry(notificationKey)).thenReturn(true)
     }
 
     @Test
@@ -151,8 +151,8 @@
             .build()
         assertSame(summary, notification.entry.parent?.summary)
 
-        `when`(headsUpManager.isAlerting(notificationKey)).thenReturn(false)
-        `when`(headsUpManager.isAlerting(summary.key)).thenReturn(true)
+        `when`(headsUpManager.isHeadsUpEntry(notificationKey)).thenReturn(false)
+        `when`(headsUpManager.isHeadsUpEntry(summary.key)).thenReturn(true)
 
         assertNotSame(GROUP_ALERT_SUMMARY, summary.sbn.notification.groupAlertBehavior)
         assertNotSame(GROUP_ALERT_SUMMARY, notification.entry.sbn.notification.groupAlertBehavior)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
deleted file mode 100644
index dda7fad..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection;
-
-import androidx.annotation.Nullable;
-
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Builder to construct instances of {@link GroupEntry} for tests.
- */
-public class GroupEntryBuilder {
-    private String mKey = "test_group_key";
-    private long mCreationTime = 0;
-    @Nullable private GroupEntry mParent = GroupEntry.ROOT_ENTRY;
-    private NotifSection mNotifSection;
-    @Nullable private NotificationEntry mSummary = null;
-    private final List<NotificationEntry> mChildren = new ArrayList<>();
-
-    /** Builds a new instance of GroupEntry */
-    public GroupEntry build() {
-        GroupEntry ge = new GroupEntry(mKey, mCreationTime);
-        ge.setParent(mParent);
-        ge.getAttachState().setSection(mNotifSection);
-
-        ge.setSummary(mSummary);
-        if (mSummary != null) {
-            mSummary.setParent(ge);
-        }
-
-        for (NotificationEntry child : mChildren) {
-            ge.addChild(child);
-            child.setParent(ge);
-        }
-        return ge;
-    }
-
-    public GroupEntryBuilder setKey(String key) {
-        mKey = key;
-        return this;
-    }
-
-    public GroupEntryBuilder setCreationTime(long creationTime) {
-        mCreationTime = creationTime;
-        return this;
-    }
-
-    public GroupEntryBuilder setParent(@Nullable GroupEntry entry) {
-        mParent = entry;
-        return this;
-    }
-
-    public GroupEntryBuilder setSection(@Nullable NotifSection section) {
-        mNotifSection = section;
-        return this;
-    }
-
-    public GroupEntryBuilder setSummary(
-            NotificationEntry summary) {
-        mSummary = summary;
-        return this;
-    }
-
-    public GroupEntryBuilder setChildren(List<NotificationEntry> children) {
-        mChildren.clear();
-        mChildren.addAll(children);
-        return this;
-    }
-
-    /** Adds a child to the existing list of children */
-    public GroupEntryBuilder addChild(NotificationEntry entry) {
-        mChildren.add(entry);
-        return this;
-    }
-
-    public static List<NotificationEntry> getRawChildren(GroupEntry groupEntry) {
-        return groupEntry.getRawChildren();
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
index 65697b73..36f643a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
@@ -24,7 +24,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -53,8 +52,8 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -78,28 +77,22 @@
 
     private lateinit var coordinator: ConversationCoordinator
 
-    private val featureFlags = FakeFeatureFlagsClassic()
-
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        coordinator = ConversationCoordinator(
-            peopleNotificationIdentifier,
-            conversationIconManager,
-            HighPriorityProvider(
+        coordinator =
+            ConversationCoordinator(
                 peopleNotificationIdentifier,
-                GroupMembershipManagerImpl(featureFlags)
-            ),
-            headerController
-        )
+                conversationIconManager,
+                HighPriorityProvider(peopleNotificationIdentifier, GroupMembershipManagerImpl()),
+                headerController
+            )
         whenever(channel.isImportantConversation).thenReturn(true)
 
         coordinator.attach(pipeline)
 
         // capture arguments:
-        promoter = withArgCaptor {
-            verify(pipeline).addPromoter(capture())
-        }
+        promoter = withArgCaptor { verify(pipeline).addPromoter(capture()) }
         beforeRenderListListener = withArgCaptor {
             verify(pipeline).addOnBeforeRenderListListener(capture())
         }
@@ -111,10 +104,10 @@
         entry = NotificationEntryBuilder().setChannel(channel).build()
 
         val section = NotifSection(peopleAlertingSectioner, 0)
-        entryA = NotificationEntryBuilder().setChannel(channel)
-            .setSection(section).setTag("A").build()
-        entryB = NotificationEntryBuilder().setChannel(channel)
-            .setSection(section).setTag("B").build()
+        entryA =
+            NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("A").build()
+        entryB =
+            NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("B").build()
     }
 
     @Test
@@ -129,11 +122,12 @@
         val altChildA = NotificationEntryBuilder().setTag("A").build()
         val altChildB = NotificationEntryBuilder().setTag("B").build()
         val summary = NotificationEntryBuilder().setId(2).setChannel(channel).build()
-        val groupEntry = GroupEntryBuilder()
-            .setParent(GroupEntry.ROOT_ENTRY)
-            .setSummary(summary)
-            .setChildren(listOf(entry, altChildA, altChildB))
-            .build()
+        val groupEntry =
+            GroupEntryBuilder()
+                .setParent(GroupEntry.ROOT_ENTRY)
+                .setSummary(summary)
+                .setChildren(listOf(entry, altChildA, altChildB))
+                .build()
         assertTrue(promoter.shouldPromoteToTopLevel(entry))
         assertFalse(promoter.shouldPromoteToTopLevel(altChildA))
         assertFalse(promoter.shouldPromoteToTopLevel(altChildB))
@@ -146,41 +140,42 @@
     @Test
     fun testInAlertingPeopleSectionWhenTheImportanceIsAtLeastDefault() {
         // GIVEN
-        val alertingEntry = NotificationEntryBuilder().setChannel(channel)
-                .setImportance(IMPORTANCE_DEFAULT).build()
+        val alertingEntry =
+            NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_DEFAULT).build()
         whenever(peopleNotificationIdentifier.getPeopleNotificationType(alertingEntry))
-                .thenReturn(TYPE_PERSON)
+            .thenReturn(TYPE_PERSON)
 
         // put alerting people notifications in this section
         assertThat(peopleAlertingSectioner.isInSection(alertingEntry)).isTrue()
-       }
+    }
 
     @Test
     fun testInSilentPeopleSectionWhenTheImportanceIsLowerThanDefault() {
         // GIVEN
-        val silentEntry = NotificationEntryBuilder().setChannel(channel)
-                .setImportance(IMPORTANCE_LOW).build()
+        val silentEntry =
+            NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build()
         whenever(peopleNotificationIdentifier.getPeopleNotificationType(silentEntry))
-                .thenReturn(TYPE_PERSON)
+            .thenReturn(TYPE_PERSON)
 
         // THEN put silent people notifications in this section
         assertThat(peopleSilentSectioner.isInSection(silentEntry)).isTrue()
         // People Alerting sectioning happens before the silent one.
-        // It claims high important conversations and rest of conversations will be considered as silent.
+        // It claims high important conversations and rest of conversations will be considered as
+        // silent.
         assertThat(peopleAlertingSectioner.isInSection(silentEntry)).isFalse()
     }
 
     @Test
     fun testNotInPeopleSection() {
         // GIVEN
-        val entry = NotificationEntryBuilder().setChannel(channel)
-                .setImportance(IMPORTANCE_LOW).build()
-        val importantEntry = NotificationEntryBuilder().setChannel(channel)
-                .setImportance(IMPORTANCE_HIGH).build()
+        val entry =
+            NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build()
+        val importantEntry =
+            NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_HIGH).build()
         whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry))
-                .thenReturn(TYPE_NON_PERSON)
+            .thenReturn(TYPE_NON_PERSON)
         whenever(peopleNotificationIdentifier.getPeopleNotificationType(importantEntry))
-                .thenReturn(TYPE_NON_PERSON)
+            .thenReturn(TYPE_NON_PERSON)
 
         // THEN - only put people notification either silent or alerting
         assertThat(peopleSilentSectioner.isInSection(entry)).isFalse()
@@ -190,19 +185,23 @@
     @Test
     fun testInAlertingPeopleSectionWhenThereIsAnImportantChild() {
         // GIVEN
-        val altChildA = NotificationEntryBuilder().setTag("A")
-                .setImportance(IMPORTANCE_DEFAULT).build()
-        val altChildB = NotificationEntryBuilder().setTag("B")
-                .setImportance(IMPORTANCE_LOW).build()
-        val summary = NotificationEntryBuilder().setId(2)
-                .setImportance(IMPORTANCE_LOW).setChannel(channel).build()
-        val groupEntry = GroupEntryBuilder()
+        val altChildA =
+            NotificationEntryBuilder().setTag("A").setImportance(IMPORTANCE_DEFAULT).build()
+        val altChildB = NotificationEntryBuilder().setTag("B").setImportance(IMPORTANCE_LOW).build()
+        val summary =
+            NotificationEntryBuilder()
+                .setId(2)
+                .setImportance(IMPORTANCE_LOW)
+                .setChannel(channel)
+                .build()
+        val groupEntry =
+            GroupEntryBuilder()
                 .setParent(GroupEntry.ROOT_ENTRY)
                 .setSummary(summary)
                 .setChildren(listOf(altChildA, altChildB))
                 .build()
         whenever(peopleNotificationIdentifier.getPeopleNotificationType(summary))
-                .thenReturn(TYPE_PERSON)
+            .thenReturn(TYPE_PERSON)
         // THEN
         assertThat(peopleAlertingSectioner.isInSection(groupEntry)).isTrue()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index a8be62b..cd75e08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -148,7 +148,7 @@
             verify(remoteInputManager).addActionPressListener(capture())
         }
         given(headsUpManager.allEntries).willAnswer { huns.stream() }
-        given(headsUpManager.isAlerting(anyString())).willAnswer { invocation ->
+        given(headsUpManager.isHeadsUpEntry(anyString())).willAnswer { invocation ->
             val key = invocation.getArgument<String>(0)
             huns.any { entry -> entry.key == key }
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt
new file mode 100644
index 0000000..c29ff41
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.platform.test.annotations.EnableFlags
+import android.service.notification.NotificationListenerService.REASON_CANCEL
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+class NotificationStatsLoggerCoordinatorTest : SysuiTestCase() {
+
+    private lateinit var collectionListener: NotifCollectionListener
+
+    private val pipeline: NotifPipeline = mock()
+    private val logger: NotificationStatsLogger = mock()
+    private val underTest = NotificationStatsLoggerCoordinator(Optional.of(logger))
+
+    @Before
+    fun attachPipeline() {
+        underTest.attach(pipeline)
+        collectionListener = withArgCaptor { verify(pipeline).addCollectionListener(capture()) }
+    }
+
+    @Test
+    fun onEntryAdded_loggerCalled() {
+        collectionListener.onEntryRemoved(mockEntry("key"), REASON_CANCEL)
+
+        verify(logger).onNotificationRemoved("key")
+    }
+
+    @Test
+    fun onEntryRemoved_loggerCalled() {
+        collectionListener.onEntryUpdated(mockEntry("key"))
+
+        verify(logger).onNotificationUpdated("key")
+    }
+
+    private fun mockEntry(key: String): NotificationEntry {
+        return mock { whenever(this.key).thenReturn(key) }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index 58eec2e..4519ba6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -65,6 +65,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.collection.render.NotifViewBarn;
 import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
 import com.android.systemui.util.settings.SecureSettings;
@@ -111,6 +112,7 @@
     @Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater();
     private final SectionStyleProvider mSectionStyleProvider = new SectionStyleProvider();
     @Mock private UserTracker mUserTracker;
+    @Mock private GroupMembershipManager mGroupMembershipManager;
 
     private NotifUiAdjustmentProvider mAdjustmentProvider;
 
@@ -127,7 +129,9 @@
                 mSecureSettings,
                 mLockscreenUserManager,
                 mSectionStyleProvider,
-                mUserTracker);
+                mUserTracker,
+                mGroupMembershipManager
+                );
         mEntry = getNotificationEntryBuilder().setParent(ROOT_ENTRY).build();
         mInflationError = new Exception(TEST_MESSAGE);
         mErrorManager = new NotifInflationErrorManager();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index df547ae..350ed2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -17,9 +17,11 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
 import android.service.notification.StatusBarNotification
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -33,6 +35,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -55,28 +58,31 @@
     val statusBarStateController: StatusBarStateController = mock()
     val keyguardStateController: KeyguardStateController = mock()
     val mSelectedUserInteractor: SelectedUserInteractor = mock()
+    val sensitiveNotificationProtectionController: SensitiveNotificationProtectionController =
+        mock()
 
     val coordinator: SensitiveContentCoordinator =
-        DaggerTestSensitiveContentCoordinatorComponent
-                .factory()
-                .create(
-                        dynamicPrivacyController,
-                        lockscreenUserManager,
-                        keyguardUpdateMonitor,
-                        statusBarStateController,
-                        keyguardStateController,
-                        mSelectedUserInteractor)
-                .coordinator
+        DaggerTestSensitiveContentCoordinatorComponent.factory()
+            .create(
+                dynamicPrivacyController,
+                lockscreenUserManager,
+                keyguardUpdateMonitor,
+                statusBarStateController,
+                keyguardStateController,
+                mSelectedUserInteractor,
+                sensitiveNotificationProtectionController
+            )
+            .coordinator
 
     @Test
     fun onDynamicPrivacyChanged_invokeInvalidationListener() {
         coordinator.attach(pipeline)
-        val invalidator = withArgCaptor<Invalidator> {
-            verify(pipeline).addPreRenderInvalidator(capture())
-        }
-        val dynamicPrivacyListener = withArgCaptor<DynamicPrivacyController.Listener> {
-            verify(dynamicPrivacyController).addListener(capture())
-        }
+        val invalidator =
+            withArgCaptor<Invalidator> { verify(pipeline).addPreRenderInvalidator(capture()) }
+        val dynamicPrivacyListener =
+            withArgCaptor<DynamicPrivacyController.Listener> {
+                verify(dynamicPrivacyController).addListener(capture())
+            }
 
         val invalidationListener = mock<Pluggable.PluggableListener<Invalidator>>()
         invalidator.setInvalidationListener(invalidationListener)
@@ -89,9 +95,10 @@
     @Test
     fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction() {
         coordinator.attach(pipeline)
-        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
-            verify(pipeline).addOnBeforeRenderListListener(capture())
-        }
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
 
         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
@@ -105,11 +112,59 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction_sensitiveActive() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, false)
+        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(false, true)
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction_shouldProtectNotification() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, false)
+        whenever(
+                sensitiveNotificationProtectionController.shouldProtectNotification(
+                    entry.getRepresentativeEntry()
+                )
+            )
+            .thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(true, false)
+    }
+
+    @Test
     fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction() {
         coordinator.attach(pipeline)
-        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
-            verify(pipeline).addOnBeforeRenderListListener(capture())
-        }
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
 
         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
@@ -123,11 +178,59 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction_sensitiveActive() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, true)
+        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(false, true)
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction_shouldProtectNotification() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, true)
+        whenever(
+                sensitiveNotificationProtectionController.shouldProtectNotification(
+                    entry.getRepresentativeEntry()
+                )
+            )
+            .thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(true, false)
+    }
+
+    @Test
     fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs() {
         coordinator.attach(pipeline)
-        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
-            verify(pipeline).addOnBeforeRenderListListener(capture())
-        }
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
 
         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
@@ -141,11 +244,59 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs_sensitiveActive() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, false)
+        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(false, true)
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs_shouldProtectNotification() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, false)
+        whenever(
+                sensitiveNotificationProtectionController.shouldProtectNotification(
+                    entry.getRepresentativeEntry()
+                )
+            )
+            .thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(true, false)
+    }
+
+    @Test
     fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction() {
         coordinator.attach(pipeline)
-        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
-            verify(pipeline).addOnBeforeRenderListListener(capture())
-        }
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
 
         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
@@ -159,11 +310,61 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    @Suppress("ktlint:standard:max-line-length")
+    fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction_sensitiveActive() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, false)
+        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(false, true)
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    @Suppress("ktlint:standard:max-line-length")
+    fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction_shouldProtectNotification() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, false)
+        whenever(
+                sensitiveNotificationProtectionController.shouldProtectNotification(
+                    entry.getRepresentativeEntry()
+                )
+            )
+            .thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(true, true)
+    }
+
+    @Test
     fun onBeforeRenderList_deviceLocked_notifNeedsRedaction() {
         coordinator.attach(pipeline)
-        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
-            verify(pipeline).addOnBeforeRenderListListener(capture())
-        }
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
 
         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
@@ -177,11 +378,59 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun onBeforeRenderList_deviceLocked_notifNeedsRedaction_sensitiveActive() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, true)
+        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(true, true)
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun onBeforeRenderList_deviceLocked_notifNeedsRedaction_shouldProtectNotification() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, true)
+        whenever(
+                sensitiveNotificationProtectionController.shouldProtectNotification(
+                    entry.getRepresentativeEntry()
+                )
+            )
+            .thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(true, true)
+    }
+
+    @Test
     fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction() {
         coordinator.attach(pipeline)
-        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
-            verify(pipeline).addOnBeforeRenderListListener(capture())
-        }
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
 
         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
@@ -195,18 +444,66 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction_sensitiveActive() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+        val entry = fakeNotification(1, true)
+        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(false, true)
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    @Suppress("ktlint:standard:max-line-length")
+    fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction_shouldProtectNotification() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+        val entry = fakeNotification(1, true)
+        whenever(
+                sensitiveNotificationProtectionController.shouldProtectNotification(
+                    entry.getRepresentativeEntry()
+                )
+            )
+            .thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(true, true)
+    }
+
+    @Test
     fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge() {
         coordinator.attach(pipeline)
-        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
-            verify(pipeline).addOnBeforeRenderListListener(capture())
-        }
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
 
         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
         whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
         whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
         whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true)
-
         val entry = fakeNotification(2, true)
 
         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
@@ -215,11 +512,62 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge_sensitiveActive() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+        whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true)
+        val entry = fakeNotification(2, true)
+        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(true, true)
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    @Suppress("ktlint:standard:max-line-length")
+    fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge_shouldProtectNotification() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+        whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true)
+        val entry = fakeNotification(2, true)
+        whenever(
+                sensitiveNotificationProtectionController.shouldProtectNotification(
+                    entry.getRepresentativeEntry()
+                )
+            )
+            .thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(true, true)
+    }
+
+    @Test
     fun onBeforeRenderList_deviceDynamicallyUnlocked_deviceBiometricBypassingLockScreen() {
         coordinator.attach(pipeline)
-        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
-            verify(pipeline).addOnBeforeRenderListListener(capture())
-        }
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
 
         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
@@ -227,9 +575,11 @@
         whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
         whenever(statusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD)
         whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(any()))
-                .thenReturn(true)
-
+            .thenReturn(true)
         val entry = fakeNotification(2, true)
+        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+        whenever(sensitiveNotificationProtectionController.shouldProtectNotification(any()))
+            .thenReturn(true)
 
         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
 
@@ -237,15 +587,11 @@
     }
 
     private fun fakeNotification(notifUserId: Int, needsRedaction: Boolean): ListEntry {
-        val mockUserHandle = mock<UserHandle>().apply {
-            whenever(identifier).thenReturn(notifUserId)
-        }
-        val mockSbn: StatusBarNotification = mock<StatusBarNotification>().apply {
-            whenever(user).thenReturn(mockUserHandle)
-        }
-        val mockEntry = mock<NotificationEntry>().apply {
-            whenever(sbn).thenReturn(mockSbn)
-        }
+        val mockUserHandle =
+            mock<UserHandle>().apply { whenever(identifier).thenReturn(notifUserId) }
+        val mockSbn: StatusBarNotification =
+            mock<StatusBarNotification>().apply { whenever(user).thenReturn(mockUserHandle) }
+        val mockEntry = mock<NotificationEntry>().apply { whenever(sbn).thenReturn(mockSbn) }
         whenever(lockscreenUserManager.needsRedaction(mockEntry)).thenReturn(needsRedaction)
         whenever(mockEntry.rowExists()).thenReturn(true)
         return object : ListEntry("key", 0) {
@@ -268,6 +614,8 @@
             @BindsInstance statusBarStateController: StatusBarStateController,
             @BindsInstance keyguardStateController: KeyguardStateController,
             @BindsInstance selectedUserInteractor: SelectedUserInteractor,
+            @BindsInstance
+            sensitiveNotificationProtectionController: SensitiveNotificationProtectionController,
         ): TestSensitiveContentCoordinatorComponent
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index 9b4a100..a1daff1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -81,7 +81,7 @@
     }
 
     @Test
-    @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME, FooterViewRefactor.FLAG_NAME)
+    @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME)
     fun testUpdateNotificationIcons() {
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
         verify(notificationIconAreaController).updateNotificationIcons(eq(listOf(entry)))
@@ -89,7 +89,14 @@
 
     @Test
     @EnableFlags(NotificationIconContainerRefactor.FLAG_NAME)
-    fun testSetRenderedListOnInteractor() {
+    fun testSetRenderedListOnInteractor_iconContainerFlagOn() {
+        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+        verify(renderListInteractor).setRenderedList(eq(listOf(entry)))
+    }
+
+    @Test
+    @EnableFlags(FooterViewRefactor.FLAG_NAME)
+    fun testSetRenderedListOnInteractor_footerFlagOn() {
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
         verify(renderListInteractor).setRenderedList(eq(listOf(entry)))
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index 2e74d11..ea5a6e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -140,7 +140,7 @@
                 .setSummary(mEntry)
                 .build();
 
-        when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(false);
+        when(mHeadsUpManager.isHeadsUpEntry(mEntry.getKey())).thenReturn(false);
 
         // Whenever we invalidate, the pipeline runs again, so we invalidate the state
         doAnswer(i -> {
@@ -373,7 +373,7 @@
         setSleepy(false);
 
         // WHEN a notification is alerting and visible
-        when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+        when(mHeadsUpManager.isHeadsUpEntry(mEntry.getKey())).thenReturn(true);
         when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
                 .thenReturn(true);
 
@@ -389,7 +389,7 @@
         setSleepy(false);
 
         // WHEN a notification is alerting but not visible
-        when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+        when(mHeadsUpManager.isHeadsUpEntry(mEntry.getKey())).thenReturn(true);
         when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
                 .thenReturn(false);
 
@@ -537,7 +537,7 @@
         assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
 
         // GIVEN mEntry is a HUN
-        when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+        when(mHeadsUpManager.isHeadsUpEntry(mEntry.getKey())).thenReturn(true);
 
         // THEN group + section changes are allowed
         assertTrue(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
index f9f8d8a..73c49c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
@@ -17,6 +17,8 @@
 
 import android.database.ContentObserver
 import android.os.Handler
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
@@ -28,6 +30,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
@@ -35,6 +39,8 @@
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -55,6 +61,7 @@
     private val uri = FakeSettings().getUriFor(SHOW_NOTIFICATION_SNOOZE)
     private val dirtyListener: Runnable = mock()
     private val userTracker: UserTracker = mock()
+    private val groupMembershipManager: GroupMembershipManager = mock()
 
     private val section = NotifSection(mock(), 0)
     private val entry = NotificationEntryBuilder()
@@ -69,7 +76,8 @@
         secureSettings,
         lockscreenUserManager,
         sectionStyleProvider,
-        userTracker
+        userTracker,
+        groupMembershipManager,
     )
 
     @Before
@@ -127,4 +135,42 @@
         assertThat(withSnoozing.isSnoozeEnabled).isTrue()
         assertThat(withSnoozing).isNotEqualTo(original)
     }
+
+    @Test
+    @EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
+    fun changeIsChildInGroup_asyncHybirdFlagEnabled_needReInflation() {
+        // Given: an Entry that is not child in group
+        // AsyncHybridViewInflation flag is enabled
+        whenever(groupMembershipManager.isChildInGroup(entry)).thenReturn(false)
+        val oldAdjustment = adjustmentProvider.calculateAdjustment(entry)
+        assertThat(oldAdjustment.isChildInGroup).isFalse()
+
+        // When: the Entry becomes a group child
+        whenever(groupMembershipManager.isChildInGroup(entry)).thenReturn(true)
+        val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
+        assertThat(newAdjustment.isChildInGroup).isTrue()
+        assertThat(newAdjustment).isNotEqualTo(oldAdjustment)
+
+        // Then: need re-inflation
+        assertTrue(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment))
+    }
+
+    @Test
+    @DisableFlags(AsyncHybridViewInflation.FLAG_NAME)
+    fun changeIsChildInGroup_asyncHybirdFlagDisabled_noNeedForReInflation() {
+        // Given: an Entry that is not child in group
+        // AsyncHybridViewInflation flag is disabled
+        whenever(groupMembershipManager.isChildInGroup(entry)).thenReturn(false)
+        val oldAdjustment = adjustmentProvider.calculateAdjustment(entry)
+        assertThat(oldAdjustment.isChildInGroup).isFalse()
+
+        // When: the Entry becomes a group child
+        whenever(groupMembershipManager.isChildInGroup(entry)).thenReturn(true)
+        val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
+        assertThat(newAdjustment.isChildInGroup).isTrue()
+        assertThat(newAdjustment).isNotEqualTo(oldAdjustment)
+
+        // Then: need no re-inflation
+        assertFalse(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment))
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
deleted file mode 100644
index 0a10b2c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.render
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
-import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
-import com.android.systemui.statusbar.notification.collection.ListEntry
-import com.android.systemui.statusbar.notification.collection.NotifPipeline
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
-import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager.OnGroupExpansionChangeListener
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.withArgCaptor
-import com.google.common.truth.Truth.assertThat
-import org.junit.Assert.assertThrows
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-class GroupExpansionManagerTest : SysuiTestCase() {
-    private lateinit var gem: GroupExpansionManagerImpl
-
-    private val dumpManager: DumpManager = mock()
-    private val groupMembershipManager: GroupMembershipManager = mock()
-    private val featureFlags = FakeFeatureFlagsClassic()
-
-    private val pipeline: NotifPipeline = mock()
-    private lateinit var beforeRenderListListener: OnBeforeRenderListListener
-
-    private val summary1 = notificationEntry("foo", 1)
-    private val summary2 = notificationEntry("bar", 1)
-    private val entries =
-        listOf<ListEntry>(
-            GroupEntryBuilder()
-                .setSummary(summary1)
-                .setChildren(
-                    listOf(
-                        notificationEntry("foo", 2),
-                        notificationEntry("foo", 3),
-                        notificationEntry("foo", 4)
-                    )
-                )
-                .build(),
-            GroupEntryBuilder()
-                .setSummary(summary2)
-                .setChildren(
-                    listOf(
-                        notificationEntry("bar", 2),
-                        notificationEntry("bar", 3),
-                        notificationEntry("bar", 4)
-                    )
-                )
-                .build(),
-            notificationEntry("baz", 1)
-        )
-
-    private fun notificationEntry(pkg: String, id: Int) =
-        NotificationEntryBuilder().setPkg(pkg).setId(id).build().apply { row = mock() }
-
-    @Before
-    fun setUp() {
-        whenever(groupMembershipManager.getGroupSummary(summary1)).thenReturn(summary1)
-        whenever(groupMembershipManager.getGroupSummary(summary2)).thenReturn(summary2)
-
-        gem = GroupExpansionManagerImpl(dumpManager, groupMembershipManager, featureFlags)
-    }
-
-    @Test
-    fun testNotifyOnlyOnChange_enabled() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
-        var listenerCalledCount = 0
-        gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
-
-        gem.setGroupExpanded(summary1, false)
-        assertThat(listenerCalledCount).isEqualTo(0)
-        gem.setGroupExpanded(summary1, true)
-        assertThat(listenerCalledCount).isEqualTo(1)
-        gem.setGroupExpanded(summary2, true)
-        assertThat(listenerCalledCount).isEqualTo(2)
-        gem.setGroupExpanded(summary1, true)
-        assertThat(listenerCalledCount).isEqualTo(2)
-        gem.setGroupExpanded(summary2, false)
-        assertThat(listenerCalledCount).isEqualTo(3)
-    }
-
-    @Test
-    fun testNotifyOnlyOnChange_disabled() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
-
-        var listenerCalledCount = 0
-        gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
-
-        gem.setGroupExpanded(summary1, false)
-        assertThat(listenerCalledCount).isEqualTo(1)
-        gem.setGroupExpanded(summary1, true)
-        assertThat(listenerCalledCount).isEqualTo(2)
-        gem.setGroupExpanded(summary2, true)
-        assertThat(listenerCalledCount).isEqualTo(3)
-        gem.setGroupExpanded(summary1, true)
-        assertThat(listenerCalledCount).isEqualTo(4)
-        gem.setGroupExpanded(summary2, false)
-        assertThat(listenerCalledCount).isEqualTo(5)
-    }
-
-    @Test
-    fun testExpandUnattachedEntry() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
-        // First, expand the entry when it is attached.
-        gem.setGroupExpanded(summary1, true)
-        assertThat(gem.isGroupExpanded(summary1)).isTrue()
-
-        // Un-attach it, and un-expand it.
-        NotificationEntryBuilder.setNewParent(summary1, null)
-        gem.setGroupExpanded(summary1, false)
-
-        // Expanding again should throw.
-        assertThrows(IllegalArgumentException::class.java) { gem.setGroupExpanded(summary1, true) }
-    }
-
-    @Test
-    fun testSyncWithPipeline() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-        gem.attach(pipeline)
-        beforeRenderListListener = withArgCaptor {
-            verify(pipeline).addOnBeforeRenderListListener(capture())
-        }
-
-        val listener: OnGroupExpansionChangeListener = mock()
-        gem.registerGroupExpansionChangeListener(listener)
-
-        beforeRenderListListener.onBeforeRenderList(entries)
-        verify(listener, never()).onGroupExpansionChange(any(), any())
-
-        // Expand one of the groups.
-        gem.setGroupExpanded(summary1, true)
-        verify(listener).onGroupExpansionChange(summary1.row, true)
-
-        // Empty the pipeline list and verify that the group is no longer expanded.
-        beforeRenderListListener.onBeforeRenderList(emptyList())
-        verify(listener).onGroupExpansionChange(summary1.row, false)
-        verifyNoMoreInteractions(listener)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
deleted file mode 100644
index c1ffa64..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.render
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
-import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-
-@SmallTest
-class GroupMembershipManagerTest : SysuiTestCase() {
-    private lateinit var gmm: GroupMembershipManagerImpl
-
-    private val featureFlags = FakeFeatureFlagsClassic()
-
-    @Before
-    fun setUp() {
-        gmm = GroupMembershipManagerImpl(featureFlags)
-    }
-
-    @Test
-    fun testIsChildInGroup_topLevel() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
-        val topLevelEntry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
-        assertThat(gmm.isChildInGroup(topLevelEntry)).isFalse()
-    }
-
-    @Test
-    fun testIsChildInGroup_noParent_old() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
-        val noParentEntry = NotificationEntryBuilder().setParent(null).build()
-        assertThat(gmm.isChildInGroup(noParentEntry)).isTrue()
-    }
-
-    @Test
-    fun testIsChildInGroup_noParent_new() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-        val noParentEntry = NotificationEntryBuilder().setParent(null).build()
-        assertThat(gmm.isChildInGroup(noParentEntry)).isFalse()
-    }
-    @Test
-    fun testIsChildInGroup_summary_old() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
-
-        val groupKey = "group"
-        val summary =
-            NotificationEntryBuilder()
-                .setGroup(mContext, groupKey)
-                .setGroupSummary(mContext, true)
-                .build()
-        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
-        assertThat(gmm.isChildInGroup(summary)).isTrue()
-    }
-
-    @Test
-    fun testIsChildInGroup_summary_new() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
-        val groupKey = "group"
-        val summary =
-            NotificationEntryBuilder()
-                .setGroup(mContext, groupKey)
-                .setGroupSummary(mContext, true)
-                .build()
-        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
-        assertThat(gmm.isChildInGroup(summary)).isFalse()
-    }
-
-    @Test
-    fun testIsChildInGroup_child() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
-        val childEntry = NotificationEntryBuilder().build()
-        assertThat(gmm.isChildInGroup(childEntry)).isTrue()
-    }
-
-    @Test
-    fun testIsGroupSummary_topLevelEntry() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-        val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
-        assertThat(gmm.isGroupSummary(entry)).isFalse()
-    }
-
-    @Test
-    fun testIsGroupSummary_summary() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
-        val groupKey = "group"
-        val summary =
-            NotificationEntryBuilder()
-                .setGroup(mContext, groupKey)
-                .setGroupSummary(mContext, true)
-                .build()
-        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
-        assertThat(gmm.isGroupSummary(summary)).isTrue()
-    }
-
-    @Test
-    fun testIsGroupSummary_child() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
-        val groupKey = "group"
-        val summary =
-            NotificationEntryBuilder()
-                .setGroup(mContext, groupKey)
-                .setGroupSummary(mContext, true)
-                .build()
-        val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
-        GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
-
-        assertThat(gmm.isGroupSummary(entry)).isFalse()
-    }
-
-    @Test
-    fun testGetGroupSummary_topLevelEntry() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-        val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
-        assertThat(gmm.getGroupSummary(entry)).isNull()
-    }
-
-    @Test
-    fun testGetGroupSummary_summary() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
-        val groupKey = "group"
-        val summary =
-            NotificationEntryBuilder()
-                .setGroup(mContext, groupKey)
-                .setGroupSummary(mContext, true)
-                .build()
-        GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
-        assertThat(gmm.getGroupSummary(summary)).isEqualTo(summary)
-    }
-
-    @Test
-    fun testGetGroupSummary_child() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
-        val groupKey = "group"
-        val summary =
-            NotificationEntryBuilder()
-                .setGroup(mContext, groupKey)
-                .setGroupSummary(mContext, true)
-                .build()
-        val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
-        GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
-
-        assertThat(gmm.getGroupSummary(entry)).isEqualTo(summary)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
index 4ab3cd4..9b641f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
@@ -50,6 +50,18 @@
         DaggerActiveNotificationsInteractorTest_TestComponent.factory().create(test = this)
 
     @Test
+    fun testAllNotificationsCount() =
+        testComponent.runTest {
+            val count by collectLastValue(underTest.allNotificationsCount)
+
+            activeNotificationListRepository.setActiveNotifs(5)
+            runCurrent()
+
+            assertThat(count).isEqualTo(5)
+            assertThat(underTest.allNotificationsCountValue).isEqualTo(5)
+        }
+
+    @Test
     fun testAreAnyNotificationsPresent_isTrue() =
         testComponent.runTest {
             val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent)
@@ -74,6 +86,18 @@
         }
 
     @Test
+    fun testActiveNotificationRanks_sizeMatches() {
+        testComponent.runTest {
+            val activeNotificationRanks by collectLastValue(underTest.activeNotificationRanks)
+
+            activeNotificationListRepository.setActiveNotifs(5)
+            runCurrent()
+
+            assertThat(activeNotificationRanks!!.size).isEqualTo(5)
+        }
+    }
+
+    @Test
     fun testHasClearableNotifications_whenHasClearableAlertingNotifs() =
         testComponent.runTest {
             val hasClearable by collectLastValue(underTest.hasClearableNotifications)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
index 6374d5e..334776c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -15,10 +15,12 @@
 
 package com.android.systemui.statusbar.notification.domain.interactor
 
+import android.service.notification.StatusBarNotification
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.RankingBuilder
+import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.shared.byKey
@@ -49,22 +51,101 @@
         testScope.runTest {
             val notifs by collectLastValue(notifsInteractor.topLevelRepresentativeNotifications)
             val keys = (1..50).shuffled().map { "$it" }
-            val entries =
-                keys.map {
-                    mock<ListEntry> {
-                        val mockRep =
-                            mock<NotificationEntry> {
-                                whenever(key).thenReturn(it)
-                                whenever(sbn).thenReturn(mock())
-                                whenever(icons).thenReturn(mock())
-                            }
-                        whenever(representativeEntry).thenReturn(mockRep)
-                    }
-                }
+            val entries = keys.map { mockNotificationEntry(key = it) }
             underTest.setRenderedList(entries)
             assertThat(notifs)
                 .comparingElementsUsing(byKey)
                 .containsExactlyElementsIn(keys)
                 .inOrder()
         }
+
+    @Test
+    fun setRenderList_flatMapsRankings() =
+        testScope.runTest {
+            val ranks by collectLastValue(notifsInteractor.activeNotificationRanks)
+
+            val single = mockNotificationEntry("single", 0)
+            val group =
+                mockGroupEntry(
+                    key = "group",
+                    summary = mockNotificationEntry("summary", 1),
+                    children =
+                        listOf(
+                            mockNotificationEntry("child0", 2),
+                            mockNotificationEntry("child1", 3),
+                        ),
+                )
+
+            underTest.setRenderedList(listOf(single, group))
+
+            assertThat(ranks)
+                .containsExactlyEntriesIn(
+                    mapOf(
+                        "single" to 0,
+                        "summary" to 1,
+                        "child0" to 2,
+                        "child1" to 3,
+                    )
+                )
+        }
+
+    @Test
+    fun setRenderList_singleItems_mapsRankings() =
+        testScope.runTest {
+            val actual by collectLastValue(notifsInteractor.activeNotificationRanks)
+            val expected =
+                (0..10).shuffled().mapIndexed { index, value -> "$value" to index }.toMap()
+
+            val entries = expected.map { (key, rank) -> mockNotificationEntry(key, rank) }
+
+            underTest.setRenderedList(entries)
+
+            assertThat(actual).containsAtLeastEntriesIn(expected)
+        }
+
+    @Test
+    fun setRenderList_groupWithNoSummary_flatMapsRankings() =
+        testScope.runTest {
+            val actual by collectLastValue(notifsInteractor.activeNotificationRanks)
+            val expected =
+                (0..10).shuffled().mapIndexed { index, value -> "$value" to index }.toMap()
+
+            val group =
+                mockGroupEntry(
+                    key = "group",
+                    summary = null,
+                    children = expected.map { (key, rank) -> mockNotificationEntry(key, rank) },
+                )
+
+            underTest.setRenderedList(listOf(group))
+
+            assertThat(actual).containsAtLeastEntriesIn(expected)
+        }
+}
+
+private fun mockGroupEntry(
+    key: String,
+    summary: NotificationEntry?,
+    children: List<NotificationEntry>,
+): GroupEntry {
+    return mock<GroupEntry> {
+        whenever(this.key).thenReturn(key)
+        whenever(this.summary).thenReturn(summary)
+        whenever(this.children).thenReturn(children)
+    }
+}
+
+private fun mockNotificationEntry(key: String, rank: Int = 0): NotificationEntry {
+    val mockSbn =
+        mock<StatusBarNotification>() {
+            whenever(notification).thenReturn(mock())
+            whenever(packageName).thenReturn("com.android")
+        }
+    return mock<NotificationEntry> {
+        whenever(this.key).thenReturn(key)
+        whenever(this.icons).thenReturn(mock())
+        whenever(this.representativeEntry).thenReturn(this)
+        whenever(this.ranking).thenReturn(RankingBuilder().setRank(rank).build())
+        whenever(this.sbn).thenReturn(mockSbn)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index 168e782..b01281c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -28,6 +28,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
+
 import android.app.Notification;
 import android.os.Handler;
 import android.os.Looper;
@@ -40,9 +42,9 @@
 import com.android.internal.logging.InstanceId;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
-import com.android.keyguard.TestScopeProvider;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository;
@@ -56,6 +58,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
 import com.android.systemui.statusbar.notification.logging.nano.Notifications;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -106,10 +110,16 @@
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
     private NotificationPanelLoggerFake mNotificationPanelLoggerFake =
             new NotificationPanelLoggerFake();
-    private final TestScope mTestScope = TestScopeProvider.getTestScope();
+    private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+    private final TestScope mTestScope = mKosmos.getTestScope();
     private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
     private final PowerInteractor mPowerInteractor =
             PowerInteractorFactory.create().getPowerInteractor();
+    private final ActiveNotificationListRepository mActiveNotificationListRepository =
+            new ActiveNotificationListRepository();
+    private final ActiveNotificationsInteractor mActiveNotificationsInteractor =
+            new ActiveNotificationsInteractor(mActiveNotificationListRepository,
+                    StandardTestDispatcher(null, null));
     private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
     private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
 
@@ -123,7 +133,10 @@
                 new WindowRootViewVisibilityRepository(mBarService, mUiBgExecutor),
                 mKeyguardRepository,
                 mHeadsUpManager,
-                mPowerInteractor);
+                mPowerInteractor,
+                mActiveNotificationsInteractor,
+                mKosmos.getFakeSceneContainerFlags(),
+                () -> mKosmos.getSceneInteractor());
         mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
 
         mEntry = new NotificationEntryBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
index dae0aa2..d61fc05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
@@ -34,6 +34,11 @@
     }
 
     @Override
+    public void logPanelShown(boolean isLockscreen, Notifications.NotificationList proto) {
+        mCalls.add(new CallRecord(isLockscreen, proto));
+    }
+
+    @Override
     public void logPanelShown(boolean isLockscreen,
             List<NotificationEntry> visibleNotifications) {
         mCalls.add(new CallRecord(isLockscreen,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index 9547af1..8ac2a33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -40,12 +40,12 @@
 import com.android.systemui.statusbar.notification.collection.render.FakeNodeController
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
-import com.android.systemui.statusbar.notification.logging.NotificationLogger
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController.BUBBLES_SETTING_URI
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationRowStatsLogger
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.SmartReplyConstants
@@ -92,7 +92,7 @@
     private val groupMembershipManager: GroupMembershipManager = mock()
     private val groupExpansionManager: GroupExpansionManager = mock()
     private val rowContentBindStage: RowContentBindStage = mock()
-    private val notifLogger: NotificationLogger = mock()
+    private val notifLogger: NotificationRowStatsLogger = mock()
     private val headsUpManager: HeadsUpManager = mock()
     private val onExpandClickListener: ExpandableNotificationRow.OnExpandClickListener = mock()
     private val statusBarStateController: StatusBarStateController = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
index 3f7fc97..fd41921 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
@@ -62,7 +62,7 @@
     fun onCreateView_noMatchingViewForName_returnNull() {
         // GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts
         val layoutType = FLAG_CONTENT_VIEW_EXPANDED
-        inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
+        inflaterFactory = createNotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
 
         // WHEN we try to inflate an ImageView for the expanded layout
         val createdView = inflaterFactory.onCreateView("ImageView", context, attrs)
@@ -78,7 +78,7 @@
     fun onCreateView_noMatchingViewForLayoutType_returnNull() {
         // GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts
         val layoutType = FLAG_CONTENT_VIEW_HEADS_UP
-        inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
+        inflaterFactory = createNotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
 
         // WHEN we try to inflate a TextView for the heads-up layout
         val createdView = inflaterFactory.onCreateView("TextView", context, attrs)
@@ -94,7 +94,7 @@
     fun onCreateView_matchingViews_returnReplacementView() {
         // GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts
         val layoutType = FLAG_CONTENT_VIEW_EXPANDED
-        inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
+        inflaterFactory = createNotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
 
         // WHEN we try to inflate a TextView for the expanded layout
         val createdView = inflaterFactory.onCreateView("TextView", context, attrs)
@@ -110,7 +110,7 @@
         // GIVEN we have two factories that replaces TextViews in expanded layouts
         val layoutType = FLAG_CONTENT_VIEW_EXPANDED
         inflaterFactory =
-            NotifLayoutInflaterFactory(
+            createNotifLayoutInflaterFactory(
                 row,
                 layoutType,
                 setOf(
@@ -147,4 +147,18 @@
                     null
                 }
         }
+
+    private fun createNotifLayoutInflaterFactory(
+        row: ExpandableNotificationRow,
+        layoutType: Int,
+        notifRemoteViewsFactoryContainer: Set<NotifRemoteViewsFactory>
+    ) =
+        NotifLayoutInflaterFactory(
+            row,
+            layoutType,
+            object : NotifRemoteViewsFactoryContainer {
+                override val factories: Set<NotifRemoteViewsFactory> =
+                    notifRemoteViewsFactoryContainer
+            }
+        )
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index b0996ad..a0d1075 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -88,6 +88,8 @@
     private Notification.Builder mBuilder;
     private ExpandableNotificationRow mRow;
 
+    private NotificationTestHelper mHelper;
+
     @Mock private NotifRemoteViewCache mCache;
     @Mock private ConversationNotificationProcessor mConversationNotificationProcessor;
     @Mock private InflatedSmartReplyState mInflatedSmartReplyState;
@@ -119,11 +121,11 @@
                 .setContentTitle("Title")
                 .setContentText("Text")
                 .setStyle(new Notification.BigTextStyle().bigText("big text"));
-        NotificationTestHelper helper = new NotificationTestHelper(
+        mHelper = new NotificationTestHelper(
                 mContext,
                 mDependency,
                 TestableLooper.get(this));
-        ExpandableNotificationRow row = helper.createRow(mBuilder.build());
+        ExpandableNotificationRow row = mHelper.createRow(mBuilder.build());
         mRow = spy(row);
         when(mNotifLayoutInflaterFactoryProvider.provide(any(), any()))
                 .thenReturn(mNotifLayoutInflaterFactory);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index 5549fee..91e4666 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -37,6 +37,7 @@
 import com.android.systemui.statusbar.notification.FeedbackIcon
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import junit.framework.Assert.assertEquals
@@ -49,6 +50,8 @@
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
@@ -74,7 +77,8 @@
     @Before
     fun setup() {
         initMocks(this)
-        fakeParent = FrameLayout(mContext, /* attrs= */ null).also { it.visibility = View.GONE }
+        fakeParent =
+            spy(FrameLayout(mContext, /* attrs= */ null).also { it.visibility = View.GONE })
         row =
             spy(
                 ExpandableNotificationRow(mContext, /* attrs= */ null).apply {
@@ -558,6 +562,35 @@
         verify(view.headsUpWrapper, never()).setAnimationsRunning(false)
     }
 
+    @Test
+    fun notifySubtreeAccessibilityStateChanged_notifiesParent() {
+        // Given: a contentView is created
+        val view = createContentView()
+        clearInvocations(fakeParent)
+
+        // When: the contentView is notified for an A11y change
+        view.notifySubtreeAccessibilityStateChanged(view.contractedChild, view.contractedChild, 0)
+
+        // Then: the contentView propagates the event to its parent
+        verify(fakeParent).notifySubtreeAccessibilityStateChanged(any(), any(), anyInt())
+    }
+
+    @Test
+    fun notifySubtreeAccessibilityStateChanged_animatingContentView_dontNotifyParent() {
+        // Given: a collapsed contentView is created
+        val view = createContentView()
+        clearInvocations(fakeParent)
+
+        // And: it is animating to expanded
+        view.setAnimationStartVisibleType(NotificationContentView.VISIBLE_TYPE_EXPANDED)
+
+        // When: the contentView is notified for an A11y change
+        view.notifySubtreeAccessibilityStateChanged(view.contractedChild, view.contractedChild, 0)
+
+        // Then: the contentView DOESN'T propagates the event to its parent
+        verify(fakeParent, never()).notifySubtreeAccessibilityStateChanged(any(), any(), anyInt())
+    }
+
     private fun createMockContainingNotification(notificationEntry: NotificationEntry) =
         mock<ExpandableNotificationRow>().apply {
             whenever(this.entry).thenReturn(notificationEntry)
@@ -597,7 +630,7 @@
         }
 
     private fun createContentView(
-        isSystemExpanded: Boolean,
+        isSystemExpanded: Boolean = false,
         contractedView: View = createViewWithHeight(contractedHeight),
         expandedView: View = createViewWithHeight(expandedHeight),
         headsUpView: View = createViewWithHeight(contractedHeight),
@@ -647,5 +680,5 @@
 }
 
 private fun NotificationContentView.clearInvocations() {
-    Mockito.clearInvocations(contractedWrapper, expandedWrapper, headsUpWrapper)
+    clearInvocations(contractedWrapper, expandedWrapper, headsUpWrapper)
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 8a730cf..6549193 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -42,6 +42,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
+
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -67,9 +69,9 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.keyguard.TestScopeProvider;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -85,6 +87,8 @@
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -120,7 +124,8 @@
     private NotificationChannel mTestNotificationChannel = new NotificationChannel(
             TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
 
-    private TestScope mTestScope = TestScopeProvider.getTestScope();
+    private KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+    private TestScope mTestScope = mKosmos.getTestScope();
     private JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
     private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
     private TestableLooper mTestableLooper;
@@ -156,6 +161,12 @@
 
     @Mock private UserManager mUserManager;
 
+    private final ActiveNotificationListRepository mActiveNotificationListRepository =
+            new ActiveNotificationListRepository();
+    private final ActiveNotificationsInteractor mActiveNotificationsInteractor =
+            new ActiveNotificationsInteractor(mActiveNotificationListRepository,
+                    StandardTestDispatcher(null, null));
+
     private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
 
     @Before
@@ -171,7 +182,11 @@
                 new WindowRootViewVisibilityRepository(mBarService, mExecutor),
                 new FakeKeyguardRepository(),
                 mHeadsUpManager,
-                PowerInteractorFactory.create().getPowerInteractor());
+                PowerInteractorFactory.create().getPowerInteractor(),
+                mActiveNotificationsInteractor,
+                mKosmos.getFakeSceneContainerFlags(),
+                () -> mKosmos.getSceneInteractor()
+        );
 
         mGutsManager = new NotificationGutsManager(
                 mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
new file mode 100644
index 0000000..446b9d0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -0,0 +1,631 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.row
+
+import android.R
+import android.app.AppOpsManager
+import android.app.INotificationManager
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ShortcutManager
+import android.content.pm.launcherApps
+import android.graphics.Color
+import android.os.Binder
+import android.os.Handler
+import android.os.userManager
+import android.provider.Settings
+import android.service.notification.NotificationListenerService.Ranking
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.util.ArraySet
+import android.view.View
+import android.view.accessibility.accessibilityManager
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.metricsLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.statusbar.statusBarService
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.power.domain.interactor.PowerInteractorFactory.create
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.shade.shadeControllerSceneImpl
+import com.android.systemui.statusbar.NotificationEntryHelper
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.AssistantFeedbackController
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.notificationLockscreenUserManager
+import com.android.systemui.statusbar.policy.deviceProvisionedController
+import com.android.systemui.statusbar.policy.headsUpManager
+import com.android.systemui.testKosmos
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.wmshell.BubblesManager
+import java.util.Optional
+import junit.framework.Assert
+import kotlin.test.assertEquals
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.invocation.InvocationOnMock
+
+/** Tests for [NotificationGutsManager] with the scene container enabled. */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
+    private val testNotificationChannel =
+        NotificationChannel(
+            TEST_CHANNEL_ID,
+            TEST_CHANNEL_ID,
+            NotificationManager.IMPORTANCE_DEFAULT
+        )
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val javaAdapter = JavaAdapter(testScope.backgroundScope)
+    private val executor = FakeExecutor(FakeSystemClock())
+    private lateinit var testableLooper: TestableLooper
+    private lateinit var handler: Handler
+    private lateinit var helper: NotificationTestHelper
+    private lateinit var gutsManager: NotificationGutsManager
+    private lateinit var windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor
+
+    private val metricsLogger = kosmos.metricsLogger
+    private val deviceProvisionedController = kosmos.deviceProvisionedController
+    private val accessibilityManager = kosmos.accessibilityManager
+    private val mBarService = kosmos.statusBarService
+    private val launcherApps = kosmos.launcherApps
+    private val shadeController = kosmos.shadeControllerSceneImpl
+    private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
+    private val statusBarStateController = kosmos.statusBarStateController
+    private val headsUpManager = kosmos.headsUpManager
+    private val activityStarter = kosmos.activityStarter
+    private val userManager = kosmos.userManager
+    private val activeNotificationsInteractor = kosmos.activeNotificationsInteractor
+    private val sceneInteractor = kosmos.sceneInteractor
+
+    @Mock private lateinit var onUserInteractionCallback: OnUserInteractionCallback
+    @Mock private lateinit var presenter: NotificationPresenter
+    @Mock private lateinit var notificationActivityStarter: NotificationActivityStarter
+    @Mock private lateinit var notificationListContainer: NotificationListContainer
+    @Mock
+    private lateinit var onSettingsClickListener: NotificationGutsManager.OnSettingsClickListener
+    @Mock private lateinit var highPriorityProvider: HighPriorityProvider
+    @Mock private lateinit var notificationManager: INotificationManager
+    @Mock private lateinit var shortcutManager: ShortcutManager
+    @Mock private lateinit var channelEditorDialogController: ChannelEditorDialogController
+    @Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier
+    @Mock private lateinit var contextTracker: UserContextProvider
+    @Mock private lateinit var bubblesManager: BubblesManager
+    @Mock private lateinit var peopleSpaceWidgetManager: PeopleSpaceWidgetManager
+    @Mock private lateinit var assistantFeedbackController: AssistantFeedbackController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        val sceneContainerFlags = kosmos.fakeSceneContainerFlags
+        sceneContainerFlags.enabled = true
+        testableLooper = TestableLooper.get(this)
+        allowTestableLooperAsMainThread()
+        handler = Handler.createAsync(testableLooper.getLooper())
+        helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+        Mockito.`when`(accessibilityManager.isTouchExplorationEnabled).thenReturn(false)
+        windowRootViewVisibilityInteractor =
+            WindowRootViewVisibilityInteractor(
+                testScope.backgroundScope,
+                WindowRootViewVisibilityRepository(mBarService, executor),
+                FakeKeyguardRepository(),
+                headsUpManager,
+                create().powerInteractor,
+                activeNotificationsInteractor,
+                sceneContainerFlags,
+                { sceneInteractor },
+            )
+        gutsManager =
+            NotificationGutsManager(
+                mContext,
+                handler,
+                handler,
+                javaAdapter,
+                accessibilityManager,
+                highPriorityProvider,
+                notificationManager,
+                userManager,
+                peopleSpaceWidgetManager,
+                launcherApps,
+                shortcutManager,
+                channelEditorDialogController,
+                contextTracker,
+                assistantFeedbackController,
+                Optional.of(bubblesManager),
+                UiEventLoggerFake(),
+                onUserInteractionCallback,
+                shadeController,
+                windowRootViewVisibilityInteractor,
+                notificationLockscreenUserManager,
+                statusBarStateController,
+                mBarService,
+                deviceProvisionedController,
+                metricsLogger,
+                headsUpManager,
+                activityStarter
+            )
+        gutsManager.setUpWithPresenter(
+            presenter,
+            notificationListContainer,
+            onSettingsClickListener
+        )
+        gutsManager.setNotificationActivityStarter(notificationActivityStarter)
+        gutsManager.start()
+    }
+
+    @Test
+    fun testOpenAndCloseGuts() {
+        val guts = Mockito.spy(NotificationGuts(mContext))
+        Mockito.`when`(guts.post(ArgumentMatchers.any())).thenAnswer { invocation: InvocationOnMock
+            ->
+            handler.post((invocation.arguments[0] as Runnable))
+            null
+        }
+
+        // Test doesn't support animation since the guts view is not attached.
+        Mockito.doNothing()
+            .`when`(guts)
+            .openControls(
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyBoolean(),
+                ArgumentMatchers.any(Runnable::class.java)
+            )
+        val realRow = createTestNotificationRow()
+        val menuItem = createTestMenuItem(realRow)
+        val row = Mockito.spy(realRow)
+        Mockito.`when`(row!!.windowToken).thenReturn(Binder())
+        Mockito.`when`(row.guts).thenReturn(guts)
+        Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
+        assertEquals(View.INVISIBLE.toLong(), guts.visibility.toLong())
+        testableLooper.processAllMessages()
+        verify(guts)
+            .openControls(
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyBoolean(),
+                ArgumentMatchers.any(Runnable::class.java)
+            )
+        verify(headsUpManager).setGutsShown(realRow!!.entry, true)
+        assertEquals(View.VISIBLE.toLong(), guts.visibility.toLong())
+        gutsManager.closeAndSaveGuts(false, false, true, 0, 0, false)
+        verify(guts)
+            .closeControls(
+                ArgumentMatchers.anyBoolean(),
+                ArgumentMatchers.anyBoolean(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyBoolean()
+            )
+        verify(row, Mockito.times(1)).setGutsView(ArgumentMatchers.any())
+        testableLooper.processAllMessages()
+        verify(headsUpManager).setGutsShown(realRow.entry, false)
+    }
+
+    @Test
+    fun testLockscreenShadeVisible_visible_gutsNotClosed() {
+        // First, start out lockscreen or shade as not visible
+        setIsLockscreenOrShadeVisible(false)
+        testScope.testScheduler.runCurrent()
+        val guts = Mockito.mock(NotificationGuts::class.java)
+        gutsManager.exposedGuts = guts
+
+        // WHEN the lockscreen or shade becomes visible
+        setIsLockscreenOrShadeVisible(true)
+        testScope.testScheduler.runCurrent()
+
+        // THEN the guts are not closed
+        verify(guts, Mockito.never()).removeCallbacks(ArgumentMatchers.any())
+        verify(guts, Mockito.never())
+            .closeControls(
+                ArgumentMatchers.anyBoolean(),
+                ArgumentMatchers.anyBoolean(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyBoolean()
+            )
+    }
+
+    @Test
+    fun testLockscreenShadeVisible_notVisible_gutsClosed() {
+        // First, start out lockscreen or shade as visible
+        setIsLockscreenOrShadeVisible(true)
+        testScope.testScheduler.runCurrent()
+        val guts = Mockito.mock(NotificationGuts::class.java)
+        gutsManager.exposedGuts = guts
+
+        // WHEN the lockscreen or shade is no longer visible
+        setIsLockscreenOrShadeVisible(false)
+        testScope.testScheduler.runCurrent()
+
+        // THEN the guts are closed
+        verify(guts).removeCallbacks(ArgumentMatchers.any())
+        verify(guts)
+            .closeControls(
+                /* leavebehinds= */ ArgumentMatchers.eq(true),
+                /* controls= */ ArgumentMatchers.eq(true),
+                /* x= */ ArgumentMatchers.anyInt(),
+                /* y= */ ArgumentMatchers.anyInt(),
+                /* force= */ ArgumentMatchers.eq(true)
+            )
+    }
+
+    @Test
+    fun testLockscreenShadeVisible_notVisible_listContainerReset() {
+        // First, start out lockscreen or shade as visible
+        setIsLockscreenOrShadeVisible(true)
+        testScope.testScheduler.runCurrent()
+
+        // WHEN the lockscreen or shade is no longer visible
+        setIsLockscreenOrShadeVisible(false)
+        testScope.testScheduler.runCurrent()
+
+        // THEN the list container is reset
+        verify(notificationListContainer)
+            .resetExposedMenuView(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyBoolean())
+    }
+
+    @Test
+    fun testChangeDensityOrFontScale() {
+        val guts = Mockito.spy(NotificationGuts(mContext))
+        Mockito.`when`(guts.post(ArgumentMatchers.any())).thenAnswer { invocation: InvocationOnMock
+            ->
+            handler.post((invocation.arguments[0] as Runnable))
+            null
+        }
+
+        // Test doesn't support animation since the guts view is not attached.
+        Mockito.doNothing()
+            .`when`(guts)
+            .openControls(
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyBoolean(),
+                ArgumentMatchers.any(Runnable::class.java)
+            )
+        val realRow = createTestNotificationRow()
+        val menuItem = createTestMenuItem(realRow)
+        val row = Mockito.spy(realRow)
+        Mockito.`when`(row!!.windowToken).thenReturn(Binder())
+        Mockito.`when`(row.guts).thenReturn(guts)
+        Mockito.doNothing().`when`(row).ensureGutsInflated()
+        val realEntry = realRow!!.entry
+        val entry = Mockito.spy(realEntry)
+        Mockito.`when`(entry.row).thenReturn(row)
+        Mockito.`when`(entry.getGuts()).thenReturn(guts)
+        Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
+        testableLooper.processAllMessages()
+        verify(guts)
+            .openControls(
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyBoolean(),
+                ArgumentMatchers.any(Runnable::class.java)
+            )
+
+        // called once by mGutsManager.bindGuts() in mGutsManager.openGuts()
+        verify(row).setGutsView(ArgumentMatchers.any())
+        row.onDensityOrFontScaleChanged()
+        gutsManager.onDensityOrFontScaleChanged(entry)
+        testableLooper.processAllMessages()
+        gutsManager.closeAndSaveGuts(false, false, false, 0, 0, false)
+        verify(guts)
+            .closeControls(
+                ArgumentMatchers.anyBoolean(),
+                ArgumentMatchers.anyBoolean(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyBoolean()
+            )
+
+        // called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged()
+        verify(row, Mockito.times(2)).setGutsView(ArgumentMatchers.any())
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_camera() {
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_CAMERA)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+        val captor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(notificationActivityStarter, Mockito.times(1))
+            .startNotificationGutsIntent(
+                captor.capture(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.any()
+            )
+        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_mic() {
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_RECORD_AUDIO)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+        val captor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(notificationActivityStarter, Mockito.times(1))
+            .startNotificationGutsIntent(
+                captor.capture(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.any()
+            )
+        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_camera_mic() {
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_CAMERA)
+        ops.add(AppOpsManager.OP_RECORD_AUDIO)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+        val captor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(notificationActivityStarter, Mockito.times(1))
+            .startNotificationGutsIntent(
+                captor.capture(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.any()
+            )
+        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_overlay() {
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+        val captor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(notificationActivityStarter, Mockito.times(1))
+            .startNotificationGutsIntent(
+                captor.capture(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.any()
+            )
+        assertEquals(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION, captor.value.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_camera_mic_overlay() {
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_CAMERA)
+        ops.add(AppOpsManager.OP_RECORD_AUDIO)
+        ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+        val captor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(notificationActivityStarter, Mockito.times(1))
+            .startNotificationGutsIntent(
+                captor.capture(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.any()
+            )
+        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_camera_overlay() {
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_CAMERA)
+        ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+        val captor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(notificationActivityStarter, Mockito.times(1))
+            .startNotificationGutsIntent(
+                captor.capture(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.any()
+            )
+        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_mic_overlay() {
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_RECORD_AUDIO)
+        ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+        val captor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(notificationActivityStarter, Mockito.times(1))
+            .startNotificationGutsIntent(
+                captor.capture(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.any()
+            )
+        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action)
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun testInitializeNotificationInfoView_highPriority() {
+        val notificationInfoView = Mockito.mock(NotificationInfo::class.java)
+        val row = Mockito.spy(helper.createRow())
+        val entry = row.entry
+        NotificationEntryHelper.modifyRanking(entry)
+            .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
+            .setImportance(NotificationManager.IMPORTANCE_HIGH)
+            .build()
+        Mockito.`when`(row.getIsNonblockable()).thenReturn(false)
+        Mockito.`when`(highPriorityProvider.isHighPriority(entry)).thenReturn(true)
+        val statusBarNotification = entry.sbn
+        gutsManager.initializeNotificationInfo(row, notificationInfoView)
+        verify(notificationInfoView)
+            .bindNotification(
+                ArgumentMatchers.any(PackageManager::class.java),
+                ArgumentMatchers.any(INotificationManager::class.java),
+                ArgumentMatchers.eq(onUserInteractionCallback),
+                ArgumentMatchers.eq(channelEditorDialogController),
+                ArgumentMatchers.eq(statusBarNotification.packageName),
+                ArgumentMatchers.any(NotificationChannel::class.java),
+                ArgumentMatchers.eq(entry),
+                ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java),
+                ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java),
+                ArgumentMatchers.any(UiEventLogger::class.java),
+                ArgumentMatchers.eq(true),
+                ArgumentMatchers.eq(false),
+                ArgumentMatchers.eq(true), /* wasShownHighPriority */
+                ArgumentMatchers.eq(assistantFeedbackController),
+                ArgumentMatchers.any(MetricsLogger::class.java)
+            )
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun testInitializeNotificationInfoView_PassesAlongProvisionedState() {
+        val notificationInfoView = Mockito.mock(NotificationInfo::class.java)
+        val row = Mockito.spy(helper.createRow())
+        NotificationEntryHelper.modifyRanking(row.entry)
+            .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
+            .build()
+        Mockito.`when`(row.getIsNonblockable()).thenReturn(false)
+        val statusBarNotification = row.entry.sbn
+        val entry = row.entry
+        gutsManager.initializeNotificationInfo(row, notificationInfoView)
+        verify(notificationInfoView)
+            .bindNotification(
+                ArgumentMatchers.any(PackageManager::class.java),
+                ArgumentMatchers.any(INotificationManager::class.java),
+                ArgumentMatchers.eq(onUserInteractionCallback),
+                ArgumentMatchers.eq(channelEditorDialogController),
+                ArgumentMatchers.eq(statusBarNotification.packageName),
+                ArgumentMatchers.any(NotificationChannel::class.java),
+                ArgumentMatchers.eq(entry),
+                ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java),
+                ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java),
+                ArgumentMatchers.any(UiEventLogger::class.java),
+                ArgumentMatchers.eq(true),
+                ArgumentMatchers.eq(false),
+                ArgumentMatchers.eq(false), /* wasShownHighPriority */
+                ArgumentMatchers.eq(assistantFeedbackController),
+                ArgumentMatchers.any(MetricsLogger::class.java)
+            )
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun testInitializeNotificationInfoView_withInitialAction() {
+        val notificationInfoView = Mockito.mock(NotificationInfo::class.java)
+        val row = Mockito.spy(helper.createRow())
+        NotificationEntryHelper.modifyRanking(row.entry)
+            .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
+            .build()
+        Mockito.`when`(row.getIsNonblockable()).thenReturn(false)
+        val statusBarNotification = row.entry.sbn
+        val entry = row.entry
+        gutsManager.initializeNotificationInfo(row, notificationInfoView)
+        verify(notificationInfoView)
+            .bindNotification(
+                ArgumentMatchers.any(PackageManager::class.java),
+                ArgumentMatchers.any(INotificationManager::class.java),
+                ArgumentMatchers.eq(onUserInteractionCallback),
+                ArgumentMatchers.eq(channelEditorDialogController),
+                ArgumentMatchers.eq(statusBarNotification.packageName),
+                ArgumentMatchers.any(NotificationChannel::class.java),
+                ArgumentMatchers.eq(entry),
+                ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java),
+                ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java),
+                ArgumentMatchers.any(UiEventLogger::class.java),
+                ArgumentMatchers.eq(true),
+                ArgumentMatchers.eq(false),
+                ArgumentMatchers.eq(false), /* wasShownHighPriority */
+                ArgumentMatchers.eq(assistantFeedbackController),
+                ArgumentMatchers.any(MetricsLogger::class.java)
+            )
+    }
+
+    private fun createTestNotificationRow(): ExpandableNotificationRow? {
+        val nb =
+            Notification.Builder(mContext, testNotificationChannel.id)
+                .setContentTitle("foo")
+                .setColorized(true)
+                .setColor(Color.RED)
+                .setFlag(Notification.FLAG_CAN_COLORIZE, true)
+                .setSmallIcon(R.drawable.sym_def_app_icon)
+        return try {
+            val row = helper.createRow(nb.build())
+            NotificationEntryHelper.modifyRanking(row.entry)
+                .setChannel(testNotificationChannel)
+                .build()
+            row
+        } catch (e: Exception) {
+            org.junit.Assert.fail()
+            null
+        }
+    }
+
+    private fun setIsLockscreenOrShadeVisible(isVisible: Boolean) {
+        val key =
+            if (isVisible) {
+                SceneKey.Lockscreen
+            } else {
+                SceneKey.Bouncer
+            }
+        sceneInteractor.changeScene(SceneModel(key), "test")
+        sceneInteractor.setTransitionState(
+            MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+        )
+        testScope.runCurrent()
+    }
+
+    private fun createTestMenuItem(
+        row: ExpandableNotificationRow?
+    ): NotificationMenuRowPlugin.MenuItem {
+        val menuRow: NotificationMenuRowPlugin =
+            NotificationMenuRow(mContext, peopleNotificationIdentifier)
+        menuRow.createMenu(row, row!!.entry.sbn)
+        val menuItem = menuRow.getLongpressMenuItem(mContext)
+        Assert.assertNotNull(menuItem)
+        return menuItem
+    }
+
+    companion object {
+        private const val TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt
new file mode 100644
index 0000000..1c959af
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.row
+
+import android.app.Notification
+import android.app.Person
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
+import com.android.systemui.statusbar.notification.row.SingleLineViewInflater.inflateSingleLineViewHolder
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineConversationViewBinder
+import com.android.systemui.util.mockito.mock
+import kotlin.test.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class SingleLineConversationViewBinderTest : SysuiTestCase() {
+    private lateinit var notificationBuilder: Notification.Builder
+    private lateinit var helper: NotificationTestHelper
+
+    @Before
+    fun setUp() {
+        allowTestableLooperAsMainThread()
+        helper = NotificationTestHelper(context, mDependency, TestableLooper.get(this))
+        notificationBuilder = Notification.Builder(context, CHANNEL_ID)
+        notificationBuilder
+            .setSmallIcon(R.drawable.ic_corp_icon)
+            .setContentTitle(CONTENT_TITLE)
+            .setContentText(CONTENT_TEXT)
+    }
+
+    @Test
+    @EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
+    fun bindGroupConversationSingleLineView() {
+        // GIVEN a row with a group conversation notification
+        val user =
+            Person.Builder()
+                //                .setIcon(Icon.createWithResource(mContext,
+                // R.drawable.ic_account_circle))
+                .setName(USER_NAME)
+                .build()
+        val style =
+            Notification.MessagingStyle(user)
+                .addMessage(MESSAGE_TEXT, System.currentTimeMillis(), user)
+                .addMessage(
+                    "How about lunch?",
+                    System.currentTimeMillis(),
+                    Person.Builder().setName("user2").build()
+                )
+                .setGroupConversation(true)
+        notificationBuilder.setStyle(style).setShortcutId(SHORTCUT_ID)
+        val notification = notificationBuilder.build()
+        val row = helper.createRow(notification)
+
+        val viewHolder =
+            inflateSingleLineViewHolder(
+                isConversation = true,
+                reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE,
+                entry = row.entry,
+                context = context,
+                logger = mock()
+            )
+                as HybridConversationNotificationView
+        val viewModel =
+            SingleLineViewInflater.inflateSingleLineViewModel(
+                notification = notification,
+                messagingStyle = style,
+                builder = notificationBuilder,
+                systemUiContext = context,
+            )
+        // WHEN: binds the viewHolder
+        SingleLineConversationViewBinder.bind(
+            viewModel,
+            viewHolder,
+        )
+
+        // THEN: the single-line conversation view should be bind with view model's corresponding
+        // fields
+        assertEquals(viewModel.titleText, viewHolder.titleView.text)
+        assertEquals(viewModel.contentText, viewHolder.textView.text)
+        assertEquals(
+            viewModel.conversationData?.conversationSenderName,
+            viewHolder.conversationSenderNameView.text
+        )
+    }
+
+    private companion object {
+        const val CHANNEL_ID = "CHANNEL_ID"
+        const val CONTENT_TITLE = "CONTENT_TITLE"
+        const val CONTENT_TEXT = "CONTENT_TEXT"
+        const val USER_NAME = "USER_NAME"
+        const val MESSAGE_TEXT = "MESSAGE_TEXT"
+        const val SHORTCUT_ID = "Shortcut"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
new file mode 100644
index 0000000..f0fc349
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.row
+
+import android.app.Notification
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
+import com.android.systemui.statusbar.notification.row.SingleLineViewInflater.inflateSingleLineViewHolder
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder
+import com.android.systemui.util.mockito.mock
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class SingleLineViewBinderTest : SysuiTestCase() {
+    private lateinit var notificationBuilder: Notification.Builder
+    private lateinit var helper: NotificationTestHelper
+
+    @Before
+    fun setUp() {
+        allowTestableLooperAsMainThread()
+        helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+        notificationBuilder = Notification.Builder(mContext, CHANNEL_ID)
+        notificationBuilder
+            .setSmallIcon(R.drawable.ic_corp_icon)
+            .setContentTitle(CONTENT_TITLE)
+            .setContentText(CONTENT_TEXT)
+    }
+
+    @Test
+    @EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
+    fun bindNonConversationSingleLineView() {
+        // GIVEN: a row with bigText style notification
+        val style = Notification.BigTextStyle().bigText(CONTENT_TEXT)
+        notificationBuilder.setStyle(style)
+        val notification = notificationBuilder.build()
+        val row: ExpandableNotificationRow = helper.createRow(notification)
+
+        val viewHolder =
+            inflateSingleLineViewHolder(
+                isConversation = false,
+                reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE,
+                entry = row.entry,
+                context = context,
+                logger = mock()
+            )
+        val viewModel =
+            SingleLineViewInflater.inflateSingleLineViewModel(
+                notification = notification,
+                messagingStyle = null,
+                builder = notificationBuilder,
+                systemUiContext = context,
+            )
+
+        // WHEN: binds the viewHolder
+        SingleLineViewBinder.bind(viewModel, viewHolder)
+
+        // THEN: the single-line view should be bind with viewModel's title and content text
+        Assert.assertEquals(viewModel.titleText, viewHolder?.titleView?.text)
+        Assert.assertEquals(viewModel.contentText, viewHolder?.textView?.text)
+    }
+
+    private companion object {
+        const val CHANNEL_ID = "CHANNEL_ID"
+        const val CONTENT_TITLE = "A Cool New Feature"
+        const val CONTENT_TEXT = "Checkout out new feature!"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt
new file mode 100644
index 0000000..b67153a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.app.Notification
+import android.app.Person
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.core.graphics.drawable.toBitmap
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ConversationAvatar
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.FacePile
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleIcon
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel
+import kotlin.test.assertEquals
+import kotlin.test.assertIs
+import kotlin.test.assertIsNot
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
+class SingleLineViewInflaterTest : SysuiTestCase() {
+    private lateinit var helper: NotificationTestHelper
+    // Non-group MessagingStyles only have firstSender
+    private lateinit var firstSender: Person
+    private lateinit var lastSender: Person
+    private lateinit var firstSenderIcon: Icon
+    private lateinit var lastSenderIcon: Icon
+    private var firstSenderIconDrawable: Drawable? = null
+    private var lastSenderIconDrawable: Drawable? = null
+    private val currentUser: Person? = null
+
+    private companion object {
+        const val FIRST_SENDER_NAME = "First Sender"
+        const val LAST_SENDER_NAME = "Second Sender"
+        const val LAST_MESSAGE = "How about lunch?"
+
+        const val CONVERSATION_TITLE = "The Sender Family"
+        const val CONTENT_TITLE = "A Cool Group"
+        const val CONTENT_TEXT = "This is an amazing group chat"
+
+        const val SHORTCUT_ID = "Shortcut"
+    }
+
+    @Before
+    fun setUp() {
+        helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+        firstSenderIcon = Icon.createWithBitmap(getBitmap(context, R.drawable.ic_person))
+        firstSenderIconDrawable = firstSenderIcon.loadDrawable(context)
+        lastSenderIcon =
+            Icon.createWithBitmap(
+                getBitmap(context, com.android.internal.R.drawable.ic_account_circle)
+            )
+        lastSenderIconDrawable = lastSenderIcon.loadDrawable(context)
+        firstSender = Person.Builder().setName(FIRST_SENDER_NAME).setIcon(firstSenderIcon).build()
+        lastSender = Person.Builder().setName(LAST_SENDER_NAME).setIcon(lastSenderIcon).build()
+    }
+
+    @Test
+    fun createViewModelForNonConversationSingleLineView() {
+        // Given: a non-conversation notification
+        val notificationType = NonMessaging()
+        val notification = getNotification(NonMessaging())
+
+        // When: inflate the SingleLineViewModel
+        val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+        // Then: the inflated SingleLineViewModel should be as expected
+        // conversationData: null, because it's not a conversation notification
+        assertEquals(SingleLineViewModel(CONTENT_TITLE, CONTENT_TEXT, null), singleLineViewModel)
+    }
+
+    @Test
+    fun createViewModelForNonGroupConversationNotification() {
+        // Given: a non-group conversation notification
+        val notificationType = OneToOneConversation()
+        val notification = getNotification(notificationType)
+
+        // When: inflate the SingleLineViewModel
+        val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+        // Then: the inflated SingleLineViewModel should be as expected
+        // titleText: Notification.ConversationTitle
+        // contentText: the last message text
+        // conversationSenderName: null, because it's not a group conversation
+        // conversationData.avatar: a single icon of the last sender
+        assertEquals(CONVERSATION_TITLE, singleLineViewModel.titleText)
+        assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+        assertNull(
+            singleLineViewModel.conversationData?.conversationSenderName,
+            "Sender name should be null for one-on-one conversation"
+        )
+        assertTrue {
+            singleLineViewModel.conversationData
+                ?.avatar
+                ?.equalsTo(SingleIcon(firstSenderIcon.loadDrawable(context))) == true
+        }
+    }
+
+    @Test
+    fun createViewModelForNonGroupLegacyMessagingStyleNotification() {
+        // Given: a non-group legacy messaging style notification
+        val notificationType = LegacyMessaging()
+        val notification = getNotification(notificationType)
+
+        // When: inflate the SingleLineViewModel
+        val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+        // Then: the inflated SingleLineViewModel should be as expected
+        // titleText: CONVERSATION_TITLE: SENDER_NAME
+        // contentText: the last message text
+        // conversationData: null, because it's not a conversation notification
+        assertEquals("$CONVERSATION_TITLE: $FIRST_SENDER_NAME", singleLineViewModel.titleText)
+        assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+        assertNull(
+            singleLineViewModel.conversationData,
+            "conversationData should be null for legacy messaging conversation"
+        )
+    }
+
+    @Test
+    fun createViewModelForGroupLegacyMessagingStyleNotification() {
+        // Given: a non-group legacy messaging style notification
+        val notificationType = LegacyMessagingGroup()
+        val notification = getNotification(notificationType)
+
+        // When: inflate the SingleLineViewModel
+        val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+        // Then: the inflated SingleLineViewModel should be as expected
+        // titleText: CONVERSATION_TITLE: LAST_SENDER_NAME
+        // contentText: the last message text
+        // conversationData: null, because it's not a conversation notification
+        assertEquals("$CONVERSATION_TITLE: $LAST_SENDER_NAME", singleLineViewModel.titleText)
+        assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+        assertNull(
+            singleLineViewModel.conversationData,
+            "conversationData should be null for legacy messaging conversation"
+        )
+    }
+
+    @Test
+    fun createViewModelForNonGroupConversationNotificationWithShortcutIcon() {
+        // Given: a non-group conversation notification with a shortcut icon
+        val shortcutIcon =
+            Icon.createWithResource(context, com.android.internal.R.drawable.ic_account_circle)
+        val notificationType = OneToOneConversation(shortcutIcon = shortcutIcon)
+        val notification = getNotification(notificationType)
+
+        // When: inflate the SingleLineViewModel
+        val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+        // Then: the inflated SingleLineViewModel should be expected
+        // titleText: Notification.ConversationTitle
+        // contentText: the last message text
+        // conversationSenderName: null, because it's not a group conversation
+        // conversationData.avatar: a single icon of the shortcut icon
+        assertEquals(CONVERSATION_TITLE, singleLineViewModel.titleText)
+        assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+        assertNull(
+            singleLineViewModel.conversationData?.conversationSenderName,
+            "Sender name should be null for one-on-one conversation"
+        )
+        assertTrue {
+            singleLineViewModel.conversationData
+                ?.avatar
+                ?.equalsTo(SingleIcon(shortcutIcon.loadDrawable(context))) == true
+        }
+    }
+
+    @Test
+    fun createViewModelForGroupConversationNotificationWithLargeIcon() {
+        // Given: a group conversation notification with a large icon
+        val largeIcon =
+            Icon.createWithResource(context, com.android.internal.R.drawable.ic_account_circle)
+        val notificationType = GroupConversation(largeIcon = largeIcon)
+        val notification = getNotification(notificationType)
+
+        // When: inflate the SingleLineViewModel
+        val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+        // Then: the inflated SingleLineViewModel should be expected
+        // titleText: Notification.ConversationTitle
+        // contentText: the last message text
+        // conversationSenderName: the last non-user sender's name
+        // conversationData.avatar: a single icon
+        assertEquals(CONVERSATION_TITLE, singleLineViewModel.titleText)
+        assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+        assertEquals(
+            context.resources.getString(
+                com.android.internal.R.string.conversation_single_line_name_display,
+                LAST_SENDER_NAME
+            ),
+            singleLineViewModel.conversationData?.conversationSenderName
+        )
+        assertTrue {
+            singleLineViewModel.conversationData
+                ?.avatar
+                ?.equalsTo(SingleIcon(largeIcon.loadDrawable(context))) == true
+        }
+    }
+
+    @Test
+    fun createViewModelForGroupConversationWithNoIcon() {
+        // Given: a group conversation notification
+        val notificationType = GroupConversation()
+        val notification = getNotification(notificationType)
+
+        // When: inflate the SingleLineViewModel
+        val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+        // Then: the inflated SingleLineViewModel should be expected
+        // titleText: Notification.ConversationTitle
+        // contentText: the last message text
+        // conversationSenderName: the last non-user sender's name
+        // conversationData.avatar: a face-pile consists the last sender's icon
+        assertEquals(CONVERSATION_TITLE, singleLineViewModel.titleText)
+        assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+        assertEquals(
+            context.resources.getString(
+                com.android.internal.R.string.conversation_single_line_name_display,
+                LAST_SENDER_NAME
+            ),
+            singleLineViewModel.conversationData?.conversationSenderName
+        )
+
+        val backgroundColor =
+            Notification.Builder.recoverBuilder(context, notification)
+                .getBackgroundColor(/* isHeader = */ false)
+        assertTrue {
+            singleLineViewModel.conversationData
+                ?.avatar
+                ?.equalsTo(
+                    FacePile(
+                        firstSenderIconDrawable,
+                        lastSenderIconDrawable,
+                        backgroundColor,
+                    )
+                ) == true
+        }
+    }
+
+    sealed class NotificationType(val largeIcon: Icon? = null)
+
+    class NonMessaging(largeIcon: Icon? = null) : NotificationType(largeIcon)
+
+    class LegacyMessaging(largeIcon: Icon? = null) : NotificationType(largeIcon)
+
+    class LegacyMessagingGroup(largeIcon: Icon? = null) : NotificationType(largeIcon)
+
+    class OneToOneConversation(largeIcon: Icon? = null, val shortcutIcon: Icon? = null) :
+        NotificationType(largeIcon)
+
+    class GroupConversation(largeIcon: Icon? = null) : NotificationType(largeIcon)
+
+    private fun getNotification(type: NotificationType): Notification {
+        val notificationBuilder: Notification.Builder =
+            Notification.Builder(mContext, "channelId")
+                .setSmallIcon(R.drawable.ic_person)
+                .setContentTitle(CONTENT_TITLE)
+                .setContentText(CONTENT_TEXT)
+                .setLargeIcon(type.largeIcon)
+
+        val user = Person.Builder().setName("User").build()
+
+        val buildMessagingStyle =
+            Notification.MessagingStyle(user)
+                .setConversationTitle(CONVERSATION_TITLE)
+                .addMessage("Hi", 0, currentUser)
+
+        return when (type) {
+            is NonMessaging ->
+                notificationBuilder
+                    .setStyle(Notification.BigTextStyle().bigText("Big Text"))
+                    .build()
+            is LegacyMessaging -> {
+                buildMessagingStyle
+                    .addMessage("What's up?", 0, firstSender)
+                    .addMessage("Not much", 0, currentUser)
+                    .addMessage(LAST_MESSAGE, 0, firstSender)
+
+                val notification = notificationBuilder.setStyle(buildMessagingStyle).build()
+
+                assertNull(notification.shortcutId)
+                notification
+            }
+            is LegacyMessagingGroup -> {
+                buildMessagingStyle
+                    .addMessage("What's up?", 0, firstSender)
+                    .addMessage("Check out my new hover board!", 0, lastSender)
+                    .setGroupConversation(true)
+                    .addMessage(LAST_MESSAGE, 0, lastSender)
+
+                val notification = notificationBuilder.setStyle(buildMessagingStyle).build()
+
+                assertNull(notification.shortcutId)
+                notification
+            }
+            is OneToOneConversation -> {
+                buildMessagingStyle
+                    .addMessage("What's up?", 0, firstSender)
+                    .addMessage("Not much", 0, currentUser)
+                    .addMessage(LAST_MESSAGE, 0, firstSender)
+                    .setShortcutIcon(type.shortcutIcon)
+                notificationBuilder.setShortcutId(SHORTCUT_ID).setStyle(buildMessagingStyle).build()
+            }
+            is GroupConversation -> {
+                buildMessagingStyle
+                    .addMessage("What's up?", 0, firstSender)
+                    .addMessage("Check out my new hover board!", 0, lastSender)
+                    .setGroupConversation(true)
+                    .addMessage(LAST_MESSAGE, 0, lastSender)
+                notificationBuilder.setShortcutId(SHORTCUT_ID).setStyle(buildMessagingStyle).build()
+            }
+        }
+    }
+
+    private fun Notification.makeSingleLineViewModel(type: NotificationType): SingleLineViewModel {
+        val builder = Notification.Builder.recoverBuilder(context, this)
+
+        // Validate the recovered builder has the right type of style
+        val expectMessagingStyle =
+            when (type) {
+                is LegacyMessaging,
+                is LegacyMessagingGroup,
+                is OneToOneConversation,
+                is GroupConversation -> true
+                else -> false
+            }
+        if (expectMessagingStyle) {
+            assertIs<Notification.MessagingStyle>(
+                builder.style,
+                "Notification style should be MessagingStyle"
+            )
+        } else {
+            assertIsNot<Notification.MessagingStyle>(
+                builder.style,
+                message = "Notification style should not be MessagingStyle"
+            )
+        }
+
+        // Inflate the SingleLineViewModel
+        // Mock the behavior of NotificationContentInflater.doInBackground
+        val messagingStyle = builder.getMessagingStyle()
+        val isConversation = type is OneToOneConversation || type is GroupConversation
+        return SingleLineViewInflater.inflateSingleLineViewModel(
+            this,
+            if (isConversation) messagingStyle else null,
+            builder,
+            context
+        )
+    }
+
+    private fun Notification.Builder.getMessagingStyle(): Notification.MessagingStyle? {
+        return style as? Notification.MessagingStyle
+    }
+
+    private fun getBitmap(context: Context, resId: Int): Bitmap {
+        val largeIconDimension =
+            context.resources.getDimension(R.dimen.conversation_single_line_avatar_size)
+        val d = context.resources.getDrawable(resId)
+        val b =
+            Bitmap.createBitmap(
+                largeIconDimension.toInt(),
+                largeIconDimension.toInt(),
+                Bitmap.Config.ARGB_8888
+            )
+        val c = Canvas(b)
+        val paint = Paint()
+        c.drawCircle(
+            largeIconDimension / 2,
+            largeIconDimension / 2,
+            largeIconDimension.coerceAtMost(largeIconDimension) / 2,
+            paint
+        )
+        d.setBounds(0, 0, largeIconDimension.toInt(), largeIconDimension.toInt())
+        paint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.SRC_IN))
+        c.saveLayer(0F, 0F, largeIconDimension, largeIconDimension, paint, Canvas.ALL_SAVE_FLAG)
+        d.draw(c)
+        c.restore()
+        return b
+    }
+
+    fun ConversationAvatar.equalsTo(other: ConversationAvatar?): Boolean =
+        when {
+            this === other -> true
+            this is SingleIcon && other is SingleIcon -> equalsTo(other)
+            this is FacePile && other is FacePile -> equalsTo(other)
+            else -> false
+        }
+
+    private fun SingleIcon.equalsTo(other: SingleIcon): Boolean =
+        iconDrawable?.equalsTo(other.iconDrawable) == true
+
+    private fun FacePile.equalsTo(other: FacePile): Boolean =
+        when {
+            bottomBackgroundColor != other.bottomBackgroundColor -> false
+            topIconDrawable?.equalsTo(other.topIconDrawable) != true -> false
+            bottomIconDrawable?.equalsTo(other.bottomIconDrawable) != true -> false
+            else -> true
+        }
+
+    fun Drawable.equalsTo(other: Drawable?): Boolean =
+        when {
+            this === other -> true
+            this.pixelsEqualTo(other) -> true
+            else -> false
+        }
+
+    private fun <T : Drawable> T.pixelsEqualTo(t: T?) =
+        toBitmap().pixelsEqualTo(t?.toBitmap(), false)
+
+    private fun Bitmap.pixelsEqualTo(otherBitmap: Bitmap?, shouldRecycle: Boolean = false) =
+        otherBitmap?.let { other ->
+            if (width == other.width && height == other.height) {
+                val res = toPixels().contentEquals(other.toPixels())
+                if (shouldRecycle) {
+                    doRecycle().also { otherBitmap.doRecycle() }
+                }
+                res
+            } else false
+        }
+            ?: kotlin.run { false }
+
+    private fun Bitmap.toPixels() =
+        IntArray(width * height).apply { getPixels(this, 0, width, 0, 0, width, height) }
+
+    fun Bitmap.doRecycle() {
+        if (!isRecycled) recycle()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTrackerTest.kt
new file mode 100644
index 0000000..1dfcb38
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTrackerTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.stack
+
+import androidx.test.filters.SmallTest
+import com.android.internal.util.LatencyTracker
+import com.android.internal.util.LatencyTracker.ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE
+import com.android.internal.util.LatencyTracker.ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class DisplaySwitchNotificationsHiderTrackerTest : SysuiTestCase() {
+
+    private val testScope = TestScope()
+    private val shadeInteractor = mock<ShadeInteractor>()
+    private val latencyTracker = mock<LatencyTracker>()
+
+    private val shouldHideFlow = MutableStateFlow(false)
+    private val shadeExpandedFlow = MutableStateFlow(false)
+
+    private val tracker = DisplaySwitchNotificationsHiderTracker(shadeInteractor, latencyTracker)
+
+    @Before
+    fun setup() {
+        whenever(shadeInteractor.isAnyExpanded).thenReturn(shadeExpandedFlow)
+    }
+
+    @Test
+    fun notificationsBecomeHidden_tracksHideActionStart() = testScope.runTest {
+        startTracking()
+
+        shouldHideFlow.value = true
+        runCurrent()
+
+        verify(latencyTracker).onActionStart(HIDE_NOTIFICATIONS_ACTION)
+    }
+
+    @Test
+    fun notificationsBecomeVisibleAfterHidden_tracksHideActionEnd() = testScope.runTest {
+        startTracking()
+
+        shouldHideFlow.value = true
+        runCurrent()
+        clearInvocations(latencyTracker)
+        shouldHideFlow.value = false
+        runCurrent()
+
+        verify(latencyTracker).onActionEnd(HIDE_NOTIFICATIONS_ACTION)
+    }
+
+    @Test
+    fun notificationsBecomeHiddenWhenShadeIsClosed_doesNotTrackHideWhenVisibleActionStart() =
+            testScope.runTest {
+                shouldHideFlow.value = false
+                shadeExpandedFlow.value = false
+                startTracking()
+
+                shouldHideFlow.value = true
+                runCurrent()
+
+                verify(latencyTracker, never())
+                        .onActionStart(HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION)
+            }
+
+    @Test
+    fun notificationsBecomeHiddenWhenShadeIsOpen_tracksHideWhenVisibleActionStart() = testScope.runTest {
+        shouldHideFlow.value = false
+        shadeExpandedFlow.value = false
+        startTracking()
+
+        shouldHideFlow.value = true
+        shadeExpandedFlow.value = true
+        runCurrent()
+
+        verify(latencyTracker).onActionStart(HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION)
+    }
+
+    @Test
+    fun shadeBecomesOpenWhenNotificationsHidden_tracksHideWhenVisibleActionStart() =
+            testScope.runTest {
+            shouldHideFlow.value = true
+            shadeExpandedFlow.value = false
+            startTracking()
+
+            shadeExpandedFlow.value = true
+            runCurrent()
+
+            verify(latencyTracker).onActionStart(HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION)
+        }
+
+    @Test
+    fun notificationsBecomeVisibleWhenShadeIsOpen_tracksHideWhenVisibleActionEnd() = testScope.runTest {
+        shouldHideFlow.value = false
+        shadeExpandedFlow.value = false
+        startTracking()
+        shouldHideFlow.value = true
+        shadeExpandedFlow.value = true
+        runCurrent()
+        clearInvocations(latencyTracker)
+
+        shouldHideFlow.value = false
+        runCurrent()
+
+        verify(latencyTracker).onActionEnd(HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION)
+    }
+
+    @Test
+    fun shadeBecomesClosedWhenNotificationsHidden_tracksHideWhenVisibleActionEnd() = testScope.runTest {
+        shouldHideFlow.value = false
+        shadeExpandedFlow.value = false
+        startTracking()
+        shouldHideFlow.value = true
+        shadeExpandedFlow.value = true
+        runCurrent()
+        clearInvocations(latencyTracker)
+
+        shadeExpandedFlow.value = false
+        runCurrent()
+
+        verify(latencyTracker).onActionEnd(HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION)
+    }
+
+    private fun TestScope.startTracking() {
+        backgroundScope.launch { tracker.trackNotificationHideTime(shouldHideFlow) }
+        backgroundScope.launch { tracker.trackNotificationHideTimeWhenVisible(shouldHideFlow) }
+        runCurrent()
+        clearInvocations(latencyTracker)
+    }
+
+    private companion object {
+        const val HIDE_NOTIFICATIONS_ACTION = ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE
+        const val HIDE_NOTIFICATIONS_WHEN_VISIBLE_ACTION =
+                ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 88662b6..dbe63f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
+import static com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
 import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
@@ -32,6 +33,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
@@ -39,6 +41,7 @@
 
 import android.metrics.LogMaker;
 import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
@@ -66,6 +69,8 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.ui.view.WindowRootView;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -91,6 +96,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
 import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback;
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor;
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScrimController;
@@ -98,6 +104,7 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.settings.SecureSettings;
@@ -112,6 +119,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import javax.inject.Provider;
+
 /**
  * Tests for {@link NotificationStackScrollLayoutController}.
  */
@@ -153,6 +162,9 @@
     @Mock private NotificationRemoteInputManager mRemoteInputManager;
     @Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
     @Mock private ShadeController mShadeController;
+    @Mock private SceneContainerFlags mSceneContainerFlags;
+    @Mock private Provider<WindowRootView> mWindowRootView;
+    @Mock private NotificationStackAppearanceInteractor mNotificationStackAppearanceInteractor;
     @Mock private InteractionJankMonitor mJankMonitor;
     private final StackStateLogger mStackLogger = new StackStateLogger(logcatLogBuffer(),
             logcatLogBuffer());
@@ -164,10 +176,16 @@
     @Mock private ActivityStarter mActivityStarter;
     @Mock private KeyguardTransitionRepository mKeyguardTransitionRepo;
     @Mock private NotificationListViewBinder mViewBinder;
+    @Mock
+    private SensitiveNotificationProtectionController mSensitiveNotificationProtectionController;
+
+    @Captor
+    private ArgumentCaptor<Runnable> mSensitiveStateListenerArgumentCaptor;
 
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
 
+
     private final ActiveNotificationListRepository mActiveNotificationsRepository =
             new ActiveNotificationListRepository();
 
@@ -378,6 +396,23 @@
     }
 
     @Test
+    public void testOnUserChange_verifyNotSensitive() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+        initController(/* viewIsAttached= */ true);
+
+        ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
+                .forClass(UserChangedListener.class);
+
+        verify(mNotificationLockscreenUserManager)
+                .addUserChangedListener(userChangedCaptor.capture());
+        reset(mNotificationStackScrollLayout);
+
+        UserChangedListener changedListener = userChangedCaptor.getValue();
+        changedListener.onUserChanged(0);
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+    }
+
+    @Test
     public void testOnUserChange_verifySensitiveProfile() {
         when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
         initController(/* viewIsAttached= */ true);
@@ -395,6 +430,80 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnUserChange_verifyNotSensitive_screenshareNotificationHidingEnabled() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+        initController(/* viewIsAttached= */ true);
+
+        ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
+                .forClass(UserChangedListener.class);
+
+        verify(mNotificationLockscreenUserManager)
+                .addUserChangedListener(userChangedCaptor.capture());
+        reset(mNotificationStackScrollLayout);
+
+        UserChangedListener changedListener = userChangedCaptor.getValue();
+        changedListener.onUserChanged(0);
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnUserChange_verifySensitiveProfile_screenshareNotificationHidingEnabled() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+        initController(/* viewIsAttached= */ true);
+
+        ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
+                .forClass(UserChangedListener.class);
+
+        verify(mNotificationLockscreenUserManager)
+                .addUserChangedListener(userChangedCaptor.capture());
+        reset(mNotificationStackScrollLayout);
+
+        UserChangedListener changedListener = userChangedCaptor.getValue();
+        changedListener.onUserChanged(0);
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnUserChange_verifySensitiveActive_screenshareNotificationHidingEnabled() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+        initController(/* viewIsAttached= */ true);
+
+        ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
+                .forClass(UserChangedListener.class);
+
+        verify(mNotificationLockscreenUserManager)
+                .addUserChangedListener(userChangedCaptor.capture());
+        reset(mNotificationStackScrollLayout);
+
+        UserChangedListener changedListener = userChangedCaptor.getValue();
+        changedListener.onUserChanged(0);
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+    }
+
+    @Test
+    public void testOnStatePostChange_verifyNotSensitive() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
+
+        stateListener.onStatePostChange();
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+    }
+
+    @Test
     public void testOnStatePostChange_verifyIfProfileIsPublic() {
         when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
 
@@ -410,6 +519,194 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnStatePostChange_verifyNotSensitive_screenshareNotificationHidingEnabled() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
+
+        stateListener.onStatePostChange();
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnStatePostChange_verifyIfProfileIsPublic_screenshareNotificationHidingEnabled(
+    ) {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
+
+        stateListener.onStatePostChange();
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnStatePostChange_verifyIfSensitiveActive_screenshareNotificationHidingEnabled(
+    ) {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
+
+        stateListener.onStatePostChange();
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+    }
+
+    @Test
+    public void testOnStatePostChange_goingFullShade_verifyNotSensitive() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+        when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
+
+        stateListener.onStatePostChange();
+        verify(mNotificationStackScrollLayout).updateSensitiveness(true, false);
+    }
+
+    @Test
+    public void testOnStatePostChange_goingFullShade_verifyIfProfileIsPublic() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+        when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
+
+        stateListener.onStatePostChange();
+        verify(mNotificationStackScrollLayout).updateSensitiveness(true, true);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnStatePostChange_goingFullShade_verifyNotSensitive_screenshareHideEnabled(
+    ) {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+        when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
+
+        stateListener.onStatePostChange();
+        verify(mNotificationStackScrollLayout).updateSensitiveness(true, false);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnStatePostChange_goingFullShade_verifyProfileIsPublic_screenshareHideEnabled(
+    ) {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+        when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
+
+        stateListener.onStatePostChange();
+        verify(mNotificationStackScrollLayout).updateSensitiveness(true, true);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnStatePostChange_goingFullShade_verifySensitiveActive_screenshareHideEnabled(
+    ) {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+        when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
+
+        stateListener.onStatePostChange();
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnProjectionStateChanged_verifyNotSensitive() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive())
+                .thenReturn(false);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSensitiveNotificationProtectionController)
+                .registerSensitiveStateListener(mSensitiveStateListenerArgumentCaptor.capture());
+
+        mSensitiveStateListenerArgumentCaptor.getValue().run();
+
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnProjectionStateChanged_verifyIfProfileIsPublic() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSensitiveNotificationProtectionController)
+                .registerSensitiveStateListener(mSensitiveStateListenerArgumentCaptor.capture());
+
+        mSensitiveStateListenerArgumentCaptor.getValue().run();
+
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnProjectionStateChanged_verifyIfSensitiveActive() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSensitiveNotificationProtectionController)
+                .registerSensitiveStateListener(mSensitiveStateListenerArgumentCaptor.capture());
+
+        mSensitiveStateListenerArgumentCaptor.getValue().run();
+
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+    }
+
+    @Test
     public void testOnMenuShownLogging() {
         ExpandableNotificationRow row = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS);
         when(row.getEntry().getSbn().getLogMaker()).thenReturn(new LogMaker(
@@ -563,9 +860,6 @@
         when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(true);
         mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
 
-        // WHEN: call updateImportantForAccessibility
-        mController.updateImportantForAccessibility();
-
         // THEN: mNotificationStackScrollLayout should not be important for A11y
         verify(mNotificationStackScrollLayout)
                 .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
@@ -587,9 +881,6 @@
                         /* hasClearableSilentNotifs = */ false)
         );
 
-        // WHEN: call updateImportantForAccessibility
-        mController.updateImportantForAccessibility();
-
         // THEN: mNotificationStackScrollLayout should be important for A11y
         verify(mNotificationStackScrollLayout)
                 .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
@@ -611,9 +902,6 @@
                         /* hasClearableSilentNotifs = */ false)
         );
 
-        // WHEN: call updateImportantForAccessibility
-        mController.updateImportantForAccessibility();
-
         // THEN: mNotificationStackScrollLayout should be important for A11y
         verify(mNotificationStackScrollLayout)
                 .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
@@ -628,9 +916,6 @@
         when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(false);
         mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
 
-        // WHEN: call updateImportantForAccessibility
-        mController.updateImportantForAccessibility();
-
         // THEN: mNotificationStackScrollLayout should be important for A11y
         verify(mNotificationStackScrollLayout)
                 .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
@@ -658,6 +943,20 @@
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(eq(false), anyBoolean());
     }
 
+    @Test
+    @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void sensitiveNotificationProtectionControllerListenerNotRegistered() {
+        initController(/* viewIsAttached= */ true);
+        verifyZeroInteractions(mSensitiveNotificationProtectionController);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void sensitiveNotificationProtectionControllerListenerRegistered() {
+        initController(/* viewIsAttached= */ true);
+        verify(mSensitiveNotificationProtectionController).registerSensitiveStateListener(any());
+    }
+
     private LogMaker logMatcher(int category, int type) {
         return argThat(new LogMatcher(category, type));
     }
@@ -724,6 +1023,9 @@
                 mSeenNotificationsInteractor,
                 mViewBinder,
                 mShadeController,
+                mSceneContainerFlags,
+                mWindowRootView,
+                mNotificationStackAppearanceInteractor,
                 mJankMonitor,
                 mStackLogger,
                 mLogger,
@@ -733,7 +1035,8 @@
                 mSecureSettings,
                 mock(NotificationDismissibilityProvider.class),
                 mActivityStarter,
-                new ResourcesSplitShadeStateController());
+                new ResourcesSplitShadeStateController(),
+                mSensitiveNotificationProtectionController);
     }
 
     static class LogMatcher implements ArgumentMatcher<LogMaker> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 83ba684..4afcc8c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -20,6 +20,7 @@
 import static android.view.WindowInsets.Type.ime;
 
 import static com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION;
+import static com.android.systemui.Flags.FLAG_SCENE_CONTAINER;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.RUBBER_BAND_FACTOR_NORMAL;
@@ -37,6 +38,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
@@ -51,6 +53,7 @@
 
 import android.graphics.Insets;
 import android.graphics.Rect;
+import android.os.SystemClock;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
@@ -74,6 +77,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.EmptyShadeView;
@@ -93,11 +97,13 @@
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -626,8 +632,6 @@
 
     @Test
     public void testClearNotifications_clearAllInProgress() {
-        mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
-
         ExpandableNotificationRow row = createClearableRow();
         when(row.getEntry().hasFinishedInitialization()).thenReturn(true);
         doReturn(true).when(mStackScroller).isVisible(row);
@@ -672,8 +676,6 @@
 
     @Test
     public void testAddNotificationUpdatesSpeedBumpIndex() {
-        mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
-
         // initial state calculated == 0
         assertEquals(0, mStackScroller.getSpeedBumpIndex());
 
@@ -690,8 +692,6 @@
 
     @Test
     public void testAddAmbientNotificationNoSpeedBumpUpdate() {
-        mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
-
         // initial state calculated  == 0
         assertEquals(0, mStackScroller.getSpeedBumpIndex());
 
@@ -708,8 +708,6 @@
 
     @Test
     public void testRemoveNotificationUpdatesSpeedBump() {
-        mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
-
         // initial state calculated == 0
         assertEquals(0, mStackScroller.getSpeedBumpIndex());
 
@@ -955,6 +953,78 @@
         verify(runnable).run();
     }
 
+    @Test
+    public void testDispatchTouchEvent_sceneContainerDisabled() {
+        Assume.assumeFalse(SceneContainerFlag.isEnabled());
+
+        MotionEvent event = MotionEvent.obtain(
+                SystemClock.uptimeMillis(),
+                SystemClock.uptimeMillis(),
+                MotionEvent.ACTION_MOVE,
+                0,
+                0,
+                0
+        );
+
+        mStackScroller.dispatchTouchEvent(event);
+
+        verify(mStackScrollLayoutController, never()).sendTouchToSceneFramework(any());
+    }
+
+    @Test
+    public void testDispatchTouchEvent_sceneContainerEnabled() {
+        Assume.assumeTrue(SceneContainerFlag.isEnabled());
+        mStackScroller.setIsBeingDragged(true);
+
+        MotionEvent moveEvent = MotionEvent.obtain(
+                SystemClock.uptimeMillis(),
+                SystemClock.uptimeMillis(),
+                MotionEvent.ACTION_MOVE,
+                0,
+                0,
+                0
+        );
+        MotionEvent syntheticDownEvent = moveEvent.copy();
+        syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN);
+        mStackScroller.dispatchTouchEvent(moveEvent);
+
+        verify(mStackScrollLayoutController).sendTouchToSceneFramework(argThat(
+                new MotionEventMatcher(syntheticDownEvent)));
+
+        mStackScroller.dispatchTouchEvent(moveEvent);
+
+        verify(mStackScrollLayoutController).sendTouchToSceneFramework(moveEvent);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCENE_CONTAINER)
+    public void testDispatchTouchEvent_sceneContainerEnabled_actionUp() {
+        Assume.assumeTrue(SceneContainerFlag.isEnabled());
+        mStackScroller.setIsBeingDragged(true);
+
+        MotionEvent upEvent = MotionEvent.obtain(
+                SystemClock.uptimeMillis(),
+                SystemClock.uptimeMillis(),
+                MotionEvent.ACTION_UP,
+                0,
+                0,
+                0
+        );
+        MotionEvent syntheticDownEvent = upEvent.copy();
+        syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN);
+
+        mStackScroller.dispatchTouchEvent(upEvent);
+
+        verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat(
+                new MotionEventMatcher(syntheticDownEvent)));
+
+        mStackScroller.dispatchTouchEvent(upEvent);
+
+        verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat(
+                new MotionEventMatcher(upEvent)));
+        assertFalse(mStackScroller.getIsBeingDragged());
+    }
+
     private void setBarStateForTest(int state) {
         // Can't inject this through the listener or we end up on the actual implementation
         // rather than the mock because the spy just coppied the anonymous inner /shruggie.
@@ -1001,4 +1071,21 @@
                 /* metaState= */0
         );
     }
+
+    private static class MotionEventMatcher implements ArgumentMatcher<MotionEvent> {
+        private final MotionEvent mLeftEvent;
+
+        MotionEventMatcher(MotionEvent leftEvent) {
+            mLeftEvent = leftEvent;
+        }
+
+        @Override
+        public boolean matches(MotionEvent right) {
+            return mLeftEvent.getActionMasked() == right.getActionMasked()
+                    && mLeftEvent.getDownTime() == right.getDownTime()
+                    && mLeftEvent.getEventTime() == right.getEventTime()
+                    && mLeftEvent.getX() == right.getX()
+                    && mLeftEvent.getY() == right.getY();
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt
new file mode 100644
index 0000000..c61756c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt
@@ -0,0 +1,547 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.view
+
+import android.service.notification.notificationListenerService
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.NotificationVisibility
+import com.android.internal.statusbar.statusBarService
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
+import com.android.systemui.statusbar.notification.logging.nano.Notifications
+import com.android.systemui.statusbar.notification.logging.notificationPanelLogger
+import com.android.systemui.statusbar.notification.stack.ExpandableViewState
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Callable
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationStatsLoggerTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private val testScope = kosmos.testScope
+    private val mockNotificationListenerService = kosmos.notificationListenerService
+    private val mockPanelLogger = kosmos.notificationPanelLogger
+    private val mockStatusBarService = kosmos.statusBarService
+
+    private val underTest = kosmos.notificationStatsLogger
+
+    private val visibilityArrayCaptor = argumentCaptor<Array<NotificationVisibility>>()
+    private val stringArrayCaptor = argumentCaptor<Array<String>>()
+    private val notificationListProtoCaptor = argumentCaptor<Notifications.NotificationList>()
+
+    @Test
+    fun onNotificationListUpdated_itemsAdded_logsNewlyVisibleItems() =
+        testScope.runTest {
+            // WHEN new Notifications are added
+            // AND they're visible
+            val (ranks, locations) = fakeNotificationMaps("key0", "key1")
+            val callable = Callable { locations }
+            underTest.onNotificationLocationsChanged(callable, ranks)
+            runCurrent()
+
+            // THEN visibility changes are reported
+            verify(mockStatusBarService)
+                .onNotificationVisibilityChanged(visibilityArrayCaptor.capture(), eq(emptyArray()))
+            verify(mockNotificationListenerService)
+                .setNotificationsShown(stringArrayCaptor.capture())
+            val loggedVisibilities = visibilityArrayCaptor.value
+            val loggedKeys = stringArrayCaptor.value
+            assertThat(loggedVisibilities).hasLength(2)
+            assertThat(loggedKeys).hasLength(2)
+            assertThat(loggedVisibilities[0]).apply {
+                isKeyEqualTo("key0")
+                isRankEqualTo(0)
+                isVisible()
+                isInMainArea()
+                isCountEqualTo(2)
+            }
+            assertThat(loggedVisibilities[1]).apply {
+                isKeyEqualTo("key1")
+                isRankEqualTo(1)
+                isVisible()
+                isInMainArea()
+                isCountEqualTo(2)
+            }
+            assertThat(loggedKeys[0]).isEqualTo("key0")
+            assertThat(loggedKeys[1]).isEqualTo("key1")
+        }
+
+    @Test
+    fun onNotificationListUpdated_itemsRemoved_logsNoLongerVisibleItems() =
+        testScope.runTest {
+            // GIVEN some visible Notifications are reported
+            val (ranks, locations) = fakeNotificationMaps("key0", "key1")
+            val callable = Callable { locations }
+            underTest.onNotificationLocationsChanged(callable, ranks)
+            runCurrent()
+            clearInvocations(mockStatusBarService, mockNotificationListenerService)
+
+            // WHEN the same Notifications are removed
+            val emptyCallable = Callable { emptyMap<String, Int>() }
+            underTest.onNotificationLocationsChanged(emptyCallable, emptyMap())
+            runCurrent()
+
+            // THEN visibility changes are reported
+            verify(mockStatusBarService)
+                .onNotificationVisibilityChanged(eq(emptyArray()), visibilityArrayCaptor.capture())
+            verifyZeroInteractions(mockNotificationListenerService)
+            val noLongerVisible = visibilityArrayCaptor.value
+            assertThat(noLongerVisible).hasLength(2)
+            assertThat(noLongerVisible[0]).apply {
+                isKeyEqualTo("key0")
+                isRankEqualTo(0)
+                notVisible()
+                isInMainArea()
+                isCountEqualTo(0)
+            }
+            assertThat(noLongerVisible[1]).apply {
+                isKeyEqualTo("key1")
+                isRankEqualTo(1)
+                notVisible()
+                isInMainArea()
+                isCountEqualTo(0)
+            }
+        }
+
+    @Test
+    fun onNotificationListUpdated_itemsBecomeInvisible_logsNoLongerVisibleItems() =
+        testScope.runTest {
+            // GIVEN some visible Notifications are reported
+            val (ranks, locations) = fakeNotificationMaps("key0", "key1")
+            val callable = Callable { locations }
+            underTest.onNotificationLocationsChanged(callable, ranks)
+            runCurrent()
+            clearInvocations(mockStatusBarService, mockNotificationListenerService)
+
+            // WHEN the same Notifications are becoming invisible
+            val emptyCallable = Callable { emptyMap<String, Int>() }
+            underTest.onNotificationLocationsChanged(emptyCallable, ranks)
+            runCurrent()
+
+            // THEN visibility changes are reported
+            verify(mockStatusBarService)
+                .onNotificationVisibilityChanged(eq(emptyArray()), visibilityArrayCaptor.capture())
+            verifyZeroInteractions(mockNotificationListenerService)
+            val noLongerVisible = visibilityArrayCaptor.value
+            assertThat(noLongerVisible).hasLength(2)
+            assertThat(noLongerVisible[0]).apply {
+                isKeyEqualTo("key0")
+                isRankEqualTo(0)
+                notVisible()
+                isInMainArea()
+                isCountEqualTo(2)
+            }
+            assertThat(noLongerVisible[1]).apply {
+                isKeyEqualTo("key1")
+                isRankEqualTo(1)
+                notVisible()
+                isInMainArea()
+                isCountEqualTo(2)
+            }
+        }
+
+    @Test
+    fun onNotificationListUpdated_itemsChangedPositions_nothingLogged() =
+        testScope.runTest {
+            // GIVEN some visible Notifications are reported
+            val (ranks, locations) = fakeNotificationMaps("key0", "key1")
+            underTest.onNotificationLocationsChanged({ locations }, ranks)
+            runCurrent()
+            clearInvocations(mockStatusBarService, mockNotificationListenerService)
+
+            // WHEN the reported Notifications are changing positions
+            val (newRanks, newLocations) = fakeNotificationMaps("key1", "key0")
+            underTest.onNotificationLocationsChanged({ newLocations }, newRanks)
+            runCurrent()
+
+            // THEN no visibility changes are reported
+            verifyZeroInteractions(mockStatusBarService, mockNotificationListenerService)
+        }
+
+    @Test
+    fun onNotificationListUpdated_calledTwice_usesTheNewCallable() =
+        testScope.runTest {
+            // GIVEN some visible Notifications are reported
+            val (ranks, locations) = fakeNotificationMaps("key0", "key1", "key2")
+            val callable = spy(Callable { locations })
+            underTest.onNotificationLocationsChanged(callable, ranks)
+            runCurrent()
+            clearInvocations(callable)
+
+            // WHEN a new update comes
+            val otherCallable = spy(Callable { locations })
+            underTest.onNotificationLocationsChanged(otherCallable, ranks)
+            runCurrent()
+
+            // THEN we call the new Callable
+            verifyZeroInteractions(callable)
+            verify(otherCallable).call()
+        }
+
+    @Test
+    fun onLockscreenOrShadeNotInteractive_logsNoLongerVisibleItems() =
+        testScope.runTest {
+            // GIVEN some visible Notifications are reported
+            val (ranks, locations) = fakeNotificationMaps("key0", "key1")
+            val callable = Callable { locations }
+            underTest.onNotificationLocationsChanged(callable, ranks)
+            runCurrent()
+            clearInvocations(mockStatusBarService, mockNotificationListenerService)
+
+            // WHEN the Shade becomes non interactive
+            underTest.onLockscreenOrShadeNotInteractive(emptyList())
+            runCurrent()
+
+            // THEN visibility changes are reported
+            verify(mockStatusBarService)
+                .onNotificationVisibilityChanged(eq(emptyArray()), visibilityArrayCaptor.capture())
+            verifyZeroInteractions(mockNotificationListenerService)
+            val noLongerVisible = visibilityArrayCaptor.value
+            assertThat(noLongerVisible).hasLength(2)
+            assertThat(noLongerVisible[0]).apply {
+                isKeyEqualTo("key0")
+                isRankEqualTo(0)
+                notVisible()
+                isInMainArea()
+                isCountEqualTo(0)
+            }
+            assertThat(noLongerVisible[1]).apply {
+                isKeyEqualTo("key1")
+                isRankEqualTo(1)
+                notVisible()
+                isInMainArea()
+                isCountEqualTo(0)
+            }
+        }
+
+    @Test
+    fun onLockscreenOrShadeInteractive_logsPanelShown() =
+        testScope.runTest {
+            // WHEN the Shade becomes interactive
+            underTest.onLockscreenOrShadeInteractive(
+                isOnLockScreen = true,
+                listOf(
+                    activeNotificationModel(
+                        key = "key0",
+                        uid = 0,
+                        packageName = "com.android.first"
+                    ),
+                    activeNotificationModel(
+                        key = "key1",
+                        uid = 1,
+                        packageName = "com.android.second"
+                    ),
+                )
+            )
+            runCurrent()
+
+            // THEN the Panel shown event is reported
+            verify(mockPanelLogger).logPanelShown(eq(true), notificationListProtoCaptor.capture())
+            val loggedNotifications = notificationListProtoCaptor.value.notifications
+            assertThat(loggedNotifications.size).isEqualTo(2)
+            with(loggedNotifications[0]) {
+                assertThat(uid).isEqualTo(0)
+                assertThat(packageName).isEqualTo("com.android.first")
+            }
+            with(loggedNotifications[1]) {
+                assertThat(uid).isEqualTo(1)
+                assertThat(packageName).isEqualTo("com.android.second")
+            }
+        }
+
+    @Test
+    fun onNotificationExpansionChanged_whenExpandedInVisibleLocation_logsExpansion() =
+        testScope.runTest {
+            // WHEN a Notification is expanded
+            underTest.onNotificationExpansionChanged(
+                key = "key",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_MAIN_AREA,
+                isUserAction = true
+            )
+            runCurrent()
+
+            // THEN the Expand event is reported
+            verify(mockStatusBarService)
+                .onNotificationExpansionChanged(
+                    /* key = */ "key",
+                    /* userAction = */ true,
+                    /* expanded = */ true,
+                    NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.ordinal
+                )
+        }
+
+    @Test
+    fun onNotificationExpansionChanged_whenCalledTwiceWithTheSameUpdate_doesNotDuplicateLogs() =
+        testScope.runTest {
+            // GIVEN a Notification is expanded
+            underTest.onNotificationExpansionChanged(
+                key = "key",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_MAIN_AREA,
+                isUserAction = true
+            )
+            runCurrent()
+            clearInvocations(mockStatusBarService)
+
+            // WHEN the logger receives the same expansion update
+            underTest.onNotificationExpansionChanged(
+                key = "key",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_MAIN_AREA,
+                isUserAction = true
+            )
+            runCurrent()
+
+            // THEN the Expand event is not reported again
+            verifyZeroInteractions(mockStatusBarService)
+        }
+
+    @Test
+    fun onNotificationExpansionChanged_whenCalledForNotVisibleItem_nothingLogged() =
+        testScope.runTest {
+            // WHEN a NOT visible Notification is expanded
+            underTest.onNotificationExpansionChanged(
+                key = "key",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_BOTTOM_STACK_HIDDEN,
+                isUserAction = true
+            )
+            runCurrent()
+
+            // No events are reported
+            verifyZeroInteractions(mockStatusBarService)
+        }
+
+    @Test
+    fun onNotificationExpansionChanged_whenNotVisibleItemBecomesVisible_logsChanges() =
+        testScope.runTest {
+            // WHEN a NOT visible Notification is expanded
+            underTest.onNotificationExpansionChanged(
+                key = "key",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_GONE,
+                isUserAction = false
+            )
+            runCurrent()
+
+            // AND it becomes visible
+            val (ranks, locations) = fakeNotificationMaps("key")
+            val callable = Callable { locations }
+            underTest.onNotificationLocationsChanged(callable, ranks)
+            runCurrent()
+
+            // THEN the Expand event is reported
+            verify(mockStatusBarService)
+                .onNotificationExpansionChanged(
+                    /* key = */ "key",
+                    /* userAction = */ false,
+                    /* expanded = */ true,
+                    NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.ordinal
+                )
+        }
+
+    @Test
+    fun onNotificationExpansionChanged_whenUpdatedItemBecomesVisible_logsChanges() =
+        testScope.runTest {
+            // GIVEN a NOT visible Notification is expanded
+            underTest.onNotificationExpansionChanged(
+                key = "key",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_GONE,
+                isUserAction = false
+            )
+            runCurrent()
+            // AND we open the shade, so we log its events
+            val (ranks, locations) = fakeNotificationMaps("key")
+            val callable = Callable { locations }
+            underTest.onNotificationLocationsChanged(callable, ranks)
+            runCurrent()
+            // AND we close the shade, so it is NOT visible
+            val emptyCallable = Callable { emptyMap<String, Int>() }
+            underTest.onNotificationLocationsChanged(emptyCallable, ranks)
+            runCurrent()
+            clearInvocations(mockStatusBarService) // clear the previous expand log
+
+            // WHEN it receives an update
+            underTest.onNotificationUpdated("key")
+            // AND it becomes visible again
+            underTest.onNotificationLocationsChanged(callable, ranks)
+            runCurrent()
+
+            // THEN we log its expand event again
+            verify(mockStatusBarService)
+                .onNotificationExpansionChanged(
+                    /* key = */ "key",
+                    /* userAction = */ false,
+                    /* expanded = */ true,
+                    NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.ordinal
+                )
+        }
+
+    @Test
+    fun onNotificationExpansionChanged_whenCollapsedForTheFirstTime_nothingLogged() =
+        testScope.runTest {
+            // WHEN a Notification is collapsed, and it is the first interaction
+            underTest.onNotificationExpansionChanged(
+                key = "key",
+                isExpanded = false,
+                location = ExpandableViewState.LOCATION_MAIN_AREA,
+                isUserAction = false
+            )
+            runCurrent()
+
+            // THEN no events are reported, because we consider the Notification initially
+            // collapsed, so only expanded is logged in the first time.
+            verifyZeroInteractions(mockStatusBarService)
+        }
+
+    @Test
+    fun onNotificationExpansionChanged_receivesMultipleUpdates_logsChanges() =
+        testScope.runTest {
+            // GIVEN a Notification is expanded
+            underTest.onNotificationExpansionChanged(
+                key = "key",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_MAIN_AREA,
+                isUserAction = true
+            )
+            runCurrent()
+
+            // WHEN the Notification is collapsed
+            verify(mockStatusBarService)
+                .onNotificationExpansionChanged(
+                    /* key = */ "key",
+                    /* userAction = */ true,
+                    /* expanded = */ true,
+                    NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.ordinal
+                )
+
+            // AND the Notification is expanded again
+            underTest.onNotificationExpansionChanged(
+                key = "key",
+                isExpanded = false,
+                location = ExpandableViewState.LOCATION_MAIN_AREA,
+                isUserAction = true
+            )
+            runCurrent()
+
+            // THEN the expansion changes are logged
+            verify(mockStatusBarService)
+                .onNotificationExpansionChanged(
+                    /* key = */ "key",
+                    /* userAction = */ true,
+                    /* expanded = */ false,
+                    NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.ordinal
+                )
+        }
+
+    @Test
+    fun onNotificationUpdated_clearsTrackedExpansionChanges() =
+        testScope.runTest {
+            // GIVEN some notification updates are posted
+            underTest.onNotificationExpansionChanged(
+                key = "key1",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_MAIN_AREA,
+                isUserAction = true
+            )
+            runCurrent()
+            underTest.onNotificationExpansionChanged(
+                key = "key2",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_MAIN_AREA,
+                isUserAction = true
+            )
+            runCurrent()
+            clearInvocations(mockStatusBarService)
+
+            // WHEN a Notification is updated
+            underTest.onNotificationUpdated("key1")
+
+            // THEN the tracked expansion changes are updated
+            assertThat(underTest.lastReportedExpansionValues.keys).containsExactly("key2")
+        }
+
+    @Test
+    fun onNotificationRemoved_clearsTrackedExpansionChanges() =
+        testScope.runTest {
+            // GIVEN some notification updates are posted
+            underTest.onNotificationExpansionChanged(
+                key = "key1",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_MAIN_AREA,
+                isUserAction = true
+            )
+            runCurrent()
+            underTest.onNotificationExpansionChanged(
+                key = "key2",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_MAIN_AREA,
+                isUserAction = true
+            )
+            runCurrent()
+            clearInvocations(mockStatusBarService)
+
+            // WHEN a Notification is removed
+            underTest.onNotificationRemoved("key1")
+
+            // THEN it is removed from the tracked expansion changes
+            assertThat(underTest.lastReportedExpansionValues.keys).doesNotContain("key1")
+        }
+
+    private fun fakeNotificationMaps(
+        vararg keys: String
+    ): Pair<Map<String, Int>, Map<String, Int>> {
+        val ranks: Map<String, Int> = keys.mapIndexed { index, key -> key to index }.toMap()
+        val locations: Map<String, Int> =
+            keys.associateWith { ExpandableViewState.LOCATION_MAIN_AREA }
+
+        return Pair(ranks, locations)
+    }
+
+    private fun assertThat(visibility: NotificationVisibility) =
+        NotificationVisibilitySubject(visibility)
+}
+
+private class NotificationVisibilitySubject(private val visibility: NotificationVisibility) {
+    fun isKeyEqualTo(key: String) = assertThat(visibility.key).isEqualTo(key)
+    fun isRankEqualTo(rank: Int) = assertThat(visibility.rank).isEqualTo(rank)
+    fun isCountEqualTo(count: Int) = assertThat(visibility.count).isEqualTo(count)
+    fun isVisible() = assertThat(this.visibility.visible).isTrue()
+    fun notVisible() = assertThat(this.visibility.visible).isFalse()
+    fun isInMainArea() =
+        assertThat(this.visibility.location)
+            .isEqualTo(NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index c17a8ef..4188c5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.runCurrent
 import com.android.systemui.runTest
 import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.notification.dagger.NotificationStatsLoggerModule
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
@@ -71,6 +72,7 @@
                 FooterViewModelModule::class,
                 HeadlessSystemUserModeModule::class,
                 UnfoldTransitionModule.Bindings::class,
+                NotificationStatsLoggerModule::class,
             ]
     )
     interface TestComponent : SysUITestComponent<NotificationListViewModel> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
new file mode 100644
index 0000000..e9d88cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationLoggerViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private val testScope = kosmos.testScope
+    private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+    private val powerInteractor = kosmos.powerInteractor
+    private val windowRootViewVisibilityRepository = kosmos.windowRootViewVisibilityRepository
+
+    private val underTest = kosmos.notificationListLoggerViewModel
+
+    @Test
+    fun isLockscreenOrShadeInteractive_deviceActiveAndShadeIsInteractive_true() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(true)
+
+            val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isLockscreenOrShadeInteractive_deviceIsAsleepAndShadeIsInteractive_false() =
+        testScope.runTest {
+            powerInteractor.setAsleepForTest()
+            windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(true)
+
+            val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isLockscreenOrShadeInteractive_deviceActiveAndShadeIsNotInteractive_false() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(false)
+
+            val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun activeNotifications_hasNotifications() =
+        testScope.runTest {
+            activeNotificationListRepository.setActiveNotifs(5)
+
+            val notifs by collectLastValue(underTest.activeNotifications)
+
+            assertThat(notifs).hasSize(5)
+            requireNotNull(notifs).forEachIndexed { i, notif ->
+                assertThat(notif.key).isEqualTo("$i")
+            }
+        }
+
+    @Test
+    fun activeNotifications_isEmpty() =
+        testScope.runTest {
+            activeNotificationListRepository.setActiveNotifs(0)
+
+            val notifications by collectLastValue(underTest.activeNotifications)
+
+            assertThat(notifications).isEmpty()
+        }
+
+    @Test
+    fun activeNotificationRanks_hasNotifications() =
+        testScope.runTest {
+            val keys = (0..4).map { "$it" }
+            activeNotificationListRepository.setActiveNotifs(5)
+
+            val rankingsMap by collectLastValue(underTest.activeNotificationRanks)
+
+            assertThat(rankingsMap).hasSize(5)
+            keys.forEachIndexed { rank, key -> assertThat(rankingsMap).containsEntry(key, rank) }
+        }
+
+    @Test
+    fun activeNotificationRanks_isEmpty() =
+        testScope.runTest {
+            activeNotificationListRepository.setActiveNotifs(0)
+
+            val rankingsMap by collectLastValue(underTest.activeNotificationRanks)
+
+            assertThat(rankingsMap).isEmpty()
+        }
+
+    @Test
+    fun isOnLockScreen_true() =
+        testScope.runTest {
+            keyguardRepository.setKeyguardShowing(true)
+
+            val isOnLockScreen by collectLastValue(underTest.isOnLockScreen)
+
+            assertThat(isOnLockScreen).isTrue()
+        }
+    @Test
+    fun isOnLockScreen_false() =
+        testScope.runTest {
+            keyguardRepository.setKeyguardShowing(false)
+
+            val isOnLockScreen by collectLastValue(underTest.isOnLockScreen)
+
+            assertThat(isOnLockScreen).isFalse()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 20020f2..32c727c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -21,9 +21,13 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -32,46 +36,64 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.mockLargeScreenHeaderHelper
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
 import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class SharedNotificationContainerViewModelTest : SysuiTestCase() {
+    val aodBurnInViewModel = mock(AodBurnInViewModel::class.java)
+    lateinit var translationYFlow: MutableStateFlow<Float>
 
     val kosmos =
         testKosmos().apply {
             fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         }
+
+    init {
+        kosmos.aodBurnInViewModel = aodBurnInViewModel
+    }
     val testScope = kosmos.testScope
     val configurationRepository = kosmos.fakeConfigurationRepository
     val keyguardRepository = kosmos.fakeKeyguardRepository
     val keyguardInteractor = kosmos.keyguardInteractor
     val keyguardRootViewModel = kosmos.keyguardRootViewModel
     val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    val communalInteractor = kosmos.communalInteractor
     val shadeRepository = kosmos.shadeRepository
     val sharedNotificationContainerInteractor = kosmos.sharedNotificationContainerInteractor
+    val largeScreenHeaderHelper = kosmos.mockLargeScreenHeaderHelper
 
-    val underTest = kosmos.sharedNotificationContainerViewModel
+    lateinit var underTest: SharedNotificationContainerViewModel
 
     @Before
     fun setUp() {
         overrideResource(R.bool.config_use_split_notification_shade, false)
+        translationYFlow = MutableStateFlow(0f)
+        whenever(aodBurnInViewModel.translationY(any())).thenReturn(translationYFlow)
+        underTest = kosmos.sharedNotificationContainerViewModel
     }
 
     @Test
@@ -101,8 +123,10 @@
         }
 
     @Test
-    fun validatePaddingTopInSplitShade() =
+    fun validatePaddingTopInSplitShade_refactorFlagOff_usesLargeHeaderResource() =
         testScope.runTest {
+            mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+            whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
             overrideResource(R.bool.config_use_split_notification_shade, true)
             overrideResource(R.dimen.large_screen_shade_header_height, 10)
             overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -115,6 +139,22 @@
         }
 
     @Test
+    fun validatePaddingTopInSplitShade_refactorFlagOn_usesLargeHeaderHelper() =
+        testScope.runTest {
+            mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+            whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            overrideResource(R.dimen.large_screen_shade_header_height, 10)
+            overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
+
+            val dimens by collectLastValue(underTest.configurationBasedDimensions)
+
+            configurationRepository.onAnyConfigurationChange()
+
+            assertThat(dimens!!.paddingTop).isEqualTo(40)
+        }
+
+    @Test
     fun validatePaddingTop() =
         testScope.runTest {
             overrideResource(R.bool.config_use_split_notification_shade, false)
@@ -153,17 +193,96 @@
         }
 
     @Test
-    fun validateMarginTopWithLargeScreenHeader() =
+    fun validateMarginTopWithLargeScreenHeader_refactorFlagOff_usesResource() =
         testScope.runTest {
+            mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+            val headerResourceHeight = 50
+            val headerHelperHeight = 100
+            whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+                .thenReturn(headerHelperHeight)
             overrideResource(R.bool.config_use_large_screen_shade_header, true)
-            overrideResource(R.dimen.large_screen_shade_header_height, 50)
+            overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
             overrideResource(R.dimen.notification_panel_margin_top, 0)
 
             val dimens by collectLastValue(underTest.configurationBasedDimensions)
 
             configurationRepository.onAnyConfigurationChange()
 
-            assertThat(dimens!!.marginTop).isEqualTo(50)
+            assertThat(dimens!!.marginTop).isEqualTo(headerResourceHeight)
+        }
+
+    @Test
+    fun validateMarginTopWithLargeScreenHeader_refactorFlagOn_usesHelper() =
+        testScope.runTest {
+            mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+            val headerResourceHeight = 50
+            val headerHelperHeight = 100
+            whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+                .thenReturn(headerHelperHeight)
+            overrideResource(R.bool.config_use_large_screen_shade_header, true)
+            overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
+            overrideResource(R.dimen.notification_panel_margin_top, 0)
+
+            val dimens by collectLastValue(underTest.configurationBasedDimensions)
+
+            configurationRepository.onAnyConfigurationChange()
+
+            assertThat(dimens!!.marginTop).isEqualTo(headerHelperHeight)
+        }
+
+    @Test
+    fun glanceableHubAlpha() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.glanceableHubAlpha)
+
+            // Start on lockscreen
+            showLockscreen()
+            assertThat(alpha).isEqualTo(1f)
+
+            // Start transitioning to glanceable hub
+            val progress = 0.6f
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    value = 0f,
+                )
+            )
+            runCurrent()
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    value = progress,
+                )
+            )
+            runCurrent()
+            // Expected alpha is inverse of progress as notifications are fading away
+            assertThat(alpha).isEqualTo(1 - progress)
+
+            // Finish transition to glanceable hub
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    value = 1f,
+                )
+            )
+            val idleTransitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(
+                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+                )
+            communalInteractor.setTransitionState(idleTransitionState)
+            runCurrent()
+            assertThat(alpha).isEqualTo(0f)
+
+            // While state is GLANCEABLE_HUB, verify alpha is restored to full if glanceable hub is
+            // not fully visible.
+            shadeRepository.setLockscreenShadeExpansion(0.1f)
+            assertThat(alpha).isEqualTo(1f)
         }
 
     @Test
@@ -255,6 +374,43 @@
         }
 
     @Test
+    fun isOnGlanceableHubWithoutShade() =
+        testScope.runTest {
+            val isOnGlanceableHubWithoutShade by
+                collectLastValue(underTest.isOnGlanceableHubWithoutShade)
+
+            // Start on lockscreen
+            showLockscreen()
+            assertThat(isOnGlanceableHubWithoutShade).isFalse()
+
+            // Move to glanceable hub
+            val idleTransitionState =
+                MutableStateFlow<ObservableCommunalTransitionState>(
+                    ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+                )
+            communalInteractor.setTransitionState(idleTransitionState)
+            runCurrent()
+            assertThat(isOnGlanceableHubWithoutShade).isTrue()
+
+            // While state is GLANCEABLE_HUB, validate variations of both shade and qs expansion
+            shadeRepository.setLockscreenShadeExpansion(0.1f)
+            shadeRepository.setQsExpansion(0f)
+            assertThat(isOnGlanceableHubWithoutShade).isFalse()
+
+            shadeRepository.setLockscreenShadeExpansion(0.1f)
+            shadeRepository.setQsExpansion(0.1f)
+            assertThat(isOnGlanceableHubWithoutShade).isFalse()
+
+            shadeRepository.setLockscreenShadeExpansion(0f)
+            shadeRepository.setQsExpansion(0.1f)
+            assertThat(isOnGlanceableHubWithoutShade).isFalse()
+
+            shadeRepository.setLockscreenShadeExpansion(0f)
+            shadeRepository.setQsExpansion(0f)
+            assertThat(isOnGlanceableHubWithoutShade).isTrue()
+        }
+
+    @Test
     fun boundsOnLockscreenNotInSplitShade() =
         testScope.runTest {
             val bounds by collectLastValue(underTest.bounds)
@@ -275,11 +431,13 @@
         }
 
     @Test
-    fun boundsOnLockscreenInSplitShade() =
+    fun boundsOnLockscreenInSplitShade_refactorFlagOff_usesLargeHeaderResource() =
         testScope.runTest {
+            mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
             val bounds by collectLastValue(underTest.bounds)
 
             // When in split shade
+            whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
             overrideResource(R.bool.config_use_split_notification_shade, true)
             overrideResource(R.dimen.large_screen_shade_header_height, 10)
             overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -300,6 +458,33 @@
         }
 
     @Test
+    fun boundsOnLockscreenInSplitShade_refactorFlagOn_usesLargeHeaderHelper() =
+        testScope.runTest {
+            mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+            val bounds by collectLastValue(underTest.bounds)
+
+            // When in split shade
+            whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            overrideResource(R.dimen.large_screen_shade_header_height, 10)
+            overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
+
+            configurationRepository.onAnyConfigurationChange()
+            runCurrent()
+
+            // Start on lockscreen
+            showLockscreen()
+
+            keyguardInteractor.setNotificationContainerBounds(
+                NotificationContainerBounds(top = 1f, bottom = 2f)
+            )
+            runCurrent()
+
+            // Top should be equal to bounds (1) + padding adjustment (40)
+            assertThat(bounds).isEqualTo(NotificationContainerBounds(top = 41f, bottom = 2f))
+        }
+
+    @Test
     fun boundsOnShade() =
         testScope.runTest {
             val bounds by collectLastValue(underTest.bounds)
@@ -408,6 +593,58 @@
         }
 
     @Test
+    fun translationYUpdatesOnKeyguardForBurnIn() =
+        testScope.runTest {
+            val translationY by collectLastValue(underTest.translationY(BurnInParameters()))
+
+            showLockscreen()
+            assertThat(translationY).isEqualTo(0)
+
+            translationYFlow.value = 150f
+            assertThat(translationY).isEqualTo(150f)
+        }
+
+    @Test
+    fun translationYUpdatesOnKeyguard() =
+        testScope.runTest {
+            val translationY by collectLastValue(underTest.translationY(BurnInParameters()))
+
+            configurationRepository.setDimensionPixelSize(
+                R.dimen.keyguard_translate_distance_on_swipe_up,
+                -100
+            )
+            configurationRepository.onAnyConfigurationChange()
+
+            // legacy expansion means the user is swiping up, usually for the bouncer
+            shadeRepository.setLegacyShadeExpansion(0.5f)
+
+            showLockscreen()
+
+            // The translation values are negative
+            assertThat(translationY).isLessThan(0f)
+        }
+
+    @Test
+    fun translationYDoesNotUpdateWhenShadeIsExpanded() =
+        testScope.runTest {
+            val translationY by collectLastValue(underTest.translationY(BurnInParameters()))
+
+            configurationRepository.setDimensionPixelSize(
+                R.dimen.keyguard_translate_distance_on_swipe_up,
+                -100
+            )
+            configurationRepository.onAnyConfigurationChange()
+
+            // legacy expansion means the user is swiping up, usually for the bouncer but also for
+            // shade collapsing
+            shadeRepository.setLegacyShadeExpansion(0.5f)
+
+            showLockscreenWithShadeExpanded()
+
+            assertThat(translationY).isEqualTo(0f)
+        }
+
+    @Test
     fun updateBounds_fromKeyguardRoot() =
         testScope.runTest {
             val bounds by collectLastValue(underTest.bounds)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
deleted file mode 100644
index 00a86ff..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ /dev/null
@@ -1,331 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone
-
-import android.app.PendingIntent
-import android.content.Intent
-import android.os.RemoteException
-import android.os.UserHandle
-import android.testing.AndroidTestingRunner
-import android.view.View
-import android.widget.FrameLayout
-import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.ActivityIntentHelper
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.LaunchableView
-import com.android.systemui.assist.AssistManager
-import com.android.systemui.keyguard.KeyguardViewMediator
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.plugins.ActivityStarter.OnDismissAction
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.shade.ShadeController
-import com.android.systemui.shade.ShadeViewController
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.shade.data.repository.ShadeAnimationRepository
-import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl
-import com.android.systemui.statusbar.NotificationLockscreenUserManager
-import com.android.systemui.statusbar.NotificationShadeWindowController
-import com.android.systemui.statusbar.SysuiStatusBarStateController
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.statusbar.window.StatusBarWindowController
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.nullable
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import dagger.Lazy
-import java.util.Optional
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class ActivityStarterImplTest : SysuiTestCase() {
-    @Mock private lateinit var centralSurfaces: CentralSurfaces
-    @Mock private lateinit var assistManager: AssistManager
-    @Mock private lateinit var dozeServiceHost: DozeServiceHost
-    @Mock private lateinit var biometricUnlockController: BiometricUnlockController
-    @Mock private lateinit var keyguardViewMediator: KeyguardViewMediator
-    @Mock private lateinit var shadeController: ShadeController
-    @Mock private lateinit var shadeViewController: ShadeViewController
-    @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
-    @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
-    @Mock private lateinit var lockScreenUserManager: NotificationLockscreenUserManager
-    @Mock private lateinit var statusBarWindowController: StatusBarWindowController
-    @Mock private lateinit var notifShadeWindowController: NotificationShadeWindowController
-    @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
-    @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
-    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
-    @Mock private lateinit var userTracker: UserTracker
-    @Mock private lateinit var activityIntentHelper: ActivityIntentHelper
-    private lateinit var underTest: ActivityStarterImpl
-    private val mainExecutor = FakeExecutor(FakeSystemClock())
-    private val shadeAnimationInteractor =
-        ShadeAnimationInteractorLegacyImpl(ShadeAnimationRepository(), FakeShadeRepository())
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        underTest =
-            ActivityStarterImpl(
-                Lazy { Optional.of(centralSurfaces) },
-                Lazy { assistManager },
-                Lazy { dozeServiceHost },
-                Lazy { biometricUnlockController },
-                Lazy { keyguardViewMediator },
-                Lazy { shadeController },
-                Lazy { shadeViewController },
-                shadeAnimationInteractor,
-                Lazy { statusBarKeyguardViewManager },
-                Lazy { notifShadeWindowController },
-                activityLaunchAnimator,
-                context,
-                DISPLAY_ID,
-                lockScreenUserManager,
-                statusBarWindowController,
-                wakefulnessLifecycle,
-                keyguardStateController,
-                statusBarStateController,
-                keyguardUpdateMonitor,
-                deviceProvisionedController,
-                userTracker,
-                activityIntentHelper,
-                mainExecutor,
-            )
-        whenever(userTracker.userHandle).thenReturn(UserHandle.OWNER)
-    }
-
-    @Test
-    fun startPendingIntentDismissingKeyguard_keyguardShowing_dismissWithAction() {
-        val pendingIntent = mock(PendingIntent::class.java)
-        whenever(pendingIntent.isActivity).thenReturn(true)
-        whenever(keyguardStateController.isShowing).thenReturn(true)
-        whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
-
-        underTest.startPendingIntentDismissingKeyguard(pendingIntent)
-        mainExecutor.runAllReady()
-
-        verify(statusBarKeyguardViewManager)
-            .dismissWithAction(any(OnDismissAction::class.java), eq(null), anyBoolean(), eq(null))
-    }
-
-    @Test
-    fun startPendingIntentMaybeDismissingKeyguard_keyguardShowing_showOverLockscreen_activityLaunchAnimator() {
-        val pendingIntent = mock(PendingIntent::class.java)
-        val parent = FrameLayout(context)
-        val view =
-            object : View(context), LaunchableView {
-                override fun setShouldBlockVisibilityChanges(block: Boolean) {}
-            }
-        parent.addView(view)
-        val controller = ActivityLaunchAnimator.Controller.fromView(view)
-        whenever(pendingIntent.isActivity).thenReturn(true)
-        whenever(keyguardStateController.isShowing).thenReturn(true)
-        whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
-        whenever(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt()))
-            .thenReturn(true)
-
-        underTest.startPendingIntentMaybeDismissingKeyguard(
-            intent = pendingIntent,
-            animationController = controller,
-            intentSentUiThreadCallback = null,
-        )
-        mainExecutor.runAllReady()
-
-        verify(activityLaunchAnimator)
-            .startPendingIntentWithAnimation(
-                nullable(),
-                eq(true),
-                nullable(),
-                eq(true),
-                any(),
-            )
-    }
-
-    @Test
-    fun startPendingIntentDismissingKeyguard_associatedView_getAnimatorController() {
-        val pendingIntent = mock(PendingIntent::class.java)
-        val associatedView = mock(ExpandableNotificationRow::class.java)
-
-        underTest.startPendingIntentDismissingKeyguard(
-            intent = pendingIntent,
-            intentSentUiThreadCallback = null,
-            associatedView = associatedView,
-        )
-
-        verify(centralSurfaces).getAnimatorControllerFromNotification(associatedView)
-    }
-
-    @Test
-    fun startActivity_noUserHandleProvided_getUserHandle() {
-        val intent = mock(Intent::class.java)
-
-        underTest.startActivity(intent, false)
-
-        verify(userTracker).userHandle
-    }
-
-    @Test
-    fun postStartActivityDismissingKeyguard_pendingIntent_postsOnMain() {
-        val intent = mock(PendingIntent::class.java)
-
-        underTest.postStartActivityDismissingKeyguard(intent)
-
-        assertThat(mainExecutor.numPending()).isEqualTo(1)
-    }
-
-    @Test
-    fun postStartActivityDismissingKeyguard_intent_postsOnMain() {
-        whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
-        val intent = mock(Intent::class.java)
-
-        underTest.postStartActivityDismissingKeyguard(intent, 0)
-
-        assertThat(mainExecutor.numPending()).isEqualTo(1)
-        mainExecutor.runAllReady()
-
-        verify(deviceProvisionedController).isDeviceProvisioned
-        verify(shadeController).runPostCollapseRunnables()
-    }
-
-    @Test
-    fun postStartActivityDismissingKeyguard_intent_notDeviceProvisioned_doesNotProceed() {
-        whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(false)
-        val intent = mock(Intent::class.java)
-
-        underTest.postStartActivityDismissingKeyguard(intent, 0)
-        mainExecutor.runAllReady()
-
-        verify(deviceProvisionedController).isDeviceProvisioned
-        verify(shadeController, never()).runPostCollapseRunnables()
-    }
-
-    @Test
-    fun dismissKeyguardThenExecute_startWakeAndUnlock() {
-        whenever(wakefulnessLifecycle.wakefulness)
-            .thenReturn(WakefulnessLifecycle.WAKEFULNESS_ASLEEP)
-        whenever(keyguardStateController.canDismissLockScreen()).thenReturn(true)
-        whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
-        whenever(dozeServiceHost.isPulsing).thenReturn(true)
-
-        underTest.dismissKeyguardThenExecute({ true }, {}, false)
-
-        verify(biometricUnlockController)
-            .startWakeAndUnlock(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
-    }
-
-    @Test
-    fun dismissKeyguardThenExecute_keyguardIsShowing_dismissWithAction() {
-        val customMessage = "Enter your pin."
-        whenever(keyguardStateController.isShowing).thenReturn(true)
-
-        underTest.dismissKeyguardThenExecute({ true }, {}, false, customMessage)
-
-        verify(statusBarKeyguardViewManager)
-            .dismissWithAction(
-                any(OnDismissAction::class.java),
-                any(Runnable::class.java),
-                eq(false),
-                eq(customMessage)
-            )
-    }
-
-    @Test
-    fun dismissKeyguardThenExecute_awakeDreams() {
-        val customMessage = "Enter your pin."
-        var dismissActionExecuted = false
-        whenever(keyguardStateController.isShowing).thenReturn(false)
-        whenever(keyguardUpdateMonitor.isDreaming).thenReturn(true)
-
-        underTest.dismissKeyguardThenExecute(
-            {
-                dismissActionExecuted = true
-                true
-            },
-            {},
-            false,
-            customMessage
-        )
-
-        verify(centralSurfaces).awakenDreams()
-        assertThat(dismissActionExecuted).isTrue()
-    }
-
-    @Test
-    @Throws(RemoteException::class)
-    fun executeRunnableDismissingKeyguard_dreaming_notShowing_awakenDreams() {
-        whenever(keyguardStateController.isShowing).thenReturn(false)
-        whenever(keyguardStateController.isOccluded).thenReturn(false)
-        whenever(keyguardUpdateMonitor.isDreaming).thenReturn(true)
-
-        underTest.executeRunnableDismissingKeyguard(
-            runnable = {},
-            cancelAction = null,
-            dismissShade = false,
-            afterKeyguardGone = false,
-            deferred = false
-        )
-
-        verify(centralSurfaces, times(1)).awakenDreams()
-    }
-
-    @Test
-    @Throws(RemoteException::class)
-    fun executeRunnableDismissingKeyguard_notDreaming_notShowing_doNotAwakenDreams() {
-        whenever(keyguardStateController.isShowing).thenReturn(false)
-        whenever(keyguardStateController.isOccluded).thenReturn(false)
-        whenever(keyguardUpdateMonitor.isDreaming).thenReturn(false)
-
-        underTest.executeRunnableDismissingKeyguard(
-            runnable = {},
-            cancelAction = null,
-            dismissShade = false,
-            afterKeyguardGone = false,
-            deferred = false
-        )
-
-        verify(centralSurfaces, never()).awakenDreams()
-    }
-
-    @Test
-    fun postQSRunnableDismissingKeyguard_leaveOpenStatusBarState() {
-        underTest.postQSRunnableDismissingKeyguard {}
-
-        assertThat(mainExecutor.numPending()).isEqualTo(1)
-        mainExecutor.runAllReady()
-        verify(statusBarStateController).setLeaveOpenOnKeyguardHide(true)
-    }
-
-    private companion object {
-        private const val DISPLAY_ID = 0
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 316f2b9..849a13b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -41,6 +41,8 @@
 
 import static java.util.Collections.emptySet;
 
+import static kotlinx.coroutines.flow.FlowKt.flowOf;
+
 import android.app.ActivityManager;
 import android.app.IWallpaperManager;
 import android.app.WallpaperManager;
@@ -72,13 +74,11 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.colorextraction.ColorExtractor;
-import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.logging.testing.FakeMetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.TestScopeProvider;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.InitController;
 import com.android.systemui.SysuiTestCase;
@@ -93,6 +93,10 @@
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.communal.data.repository.CommunalRepository;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
+import com.android.systemui.communal.shared.model.CommunalSceneKey;
+import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
@@ -103,6 +107,7 @@
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.notetask.NoteTaskController;
@@ -131,7 +136,6 @@
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.LightRevealScrim;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
-import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationPresenter;
@@ -144,13 +148,10 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.core.StatusBarInitializer;
 import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
-import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
@@ -169,7 +170,6 @@
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.util.EventLog;
@@ -201,6 +201,8 @@
 
 import javax.inject.Provider;
 
+import kotlinx.coroutines.test.TestScope;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper(setAsMainLooper = true)
@@ -209,11 +211,17 @@
     private static final int FOLD_STATE_FOLDED = 0;
     private static final int FOLD_STATE_UNFOLDED = 1;
 
+    private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+
     private CentralSurfacesImpl mCentralSurfaces;
     private FakeMetricsLogger mMetricsLogger;
     private PowerManager mPowerManager;
     private VisualInterruptionDecisionProvider mVisualInterruptionDecisionProvider;
 
+
+    private final TestScope mTestScope = mKosmos.getTestScope();
+    private final CommunalInteractor mCommunalInteractor = mKosmos.getCommunalInteractor();
+    private final CommunalRepository mCommunalRepository = mKosmos.getCommunalRepository();
     @Mock private NotificationsController mNotificationsController;
     @Mock private LightBarController mLightBarController;
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -235,12 +243,10 @@
     @Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
     @Mock private BiometricUnlockController mBiometricUnlockController;
     @Mock private AuthRippleController mAuthRippleController;
-    @Mock private NotificationListener mNotificationListener;
     @Mock private KeyguardViewMediator mKeyguardViewMediator;
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
     @Mock private NotificationRemoteInputManager mRemoteInputManager;
     @Mock private StatusBarStateControllerImpl mStatusBarStateController;
-    @Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
     @Mock private BatteryController mBatteryController;
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
     @Mock private NotificationLaunchAnimatorControllerProvider mNotifLaunchAnimControllerProvider;
@@ -262,12 +268,10 @@
     @Mock private PulseExpansionHandler mPulseExpansionHandler;
     @Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
     @Mock private KeyguardBypassController mKeyguardBypassController;
-    @Mock private DynamicPrivacyController mDynamicPrivacyController;
     @Mock private AutoHideController mAutoHideController;
     @Mock private StatusBarWindowController mStatusBarWindowController;
     @Mock private Provider<CollapsedStatusBarFragment> mCollapsedStatusBarFragmentProvider;
     @Mock private StatusBarWindowStateController mStatusBarWindowStateController;
-    @Mock private UserSwitcherController mUserSwitcherController;
     @Mock private Bubbles mBubbles;
     @Mock private NoteTaskController mNoteTaskController;
     @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
@@ -295,7 +299,6 @@
     @Mock private WallpaperController mWallpaperController;
     @Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
     @Mock private LockscreenShadeTransitionController mLockscreenTransitionController;
-    @Mock private NotificationVisibilityProvider mVisibilityProvider;
     @Mock private WallpaperManager mWallpaperManager;
     @Mock private IWallpaperManager mIWallpaperManager;
     @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@@ -304,8 +307,6 @@
     @Mock private OperatorNameViewController mOperatorNameViewController;
     @Mock private OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
     @Mock private ActivityLaunchAnimator mActivityLaunchAnimator;
-    @Mock private NotifLiveDataStore mNotifLiveDataStore;
-    @Mock private InteractionJankMonitor mJankMonitor;
     @Mock private DeviceStateManager mDeviceStateManager;
     @Mock private WiredChargingRippleController mWiredChargingRippleController;
     @Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
@@ -465,30 +466,28 @@
                 mKeyguardBypassController,
                 mKeyguardStateController,
                 mHeadsUpManager,
-                mDynamicPrivacyController,
                 new FalsingManagerFake(),
                 new FalsingCollectorFake(),
                 mBroadcastDispatcher,
                 mNotificationGutsManager,
-                mVisualInterruptionDecisionProvider,
                 new ShadeExpansionStateManager(),
                 mKeyguardViewMediator,
                 new DisplayMetrics(),
                 mMetricsLogger,
                 mShadeLogger,
-                new JavaAdapter(TestScopeProvider.getTestScope()),
+                new JavaAdapter(mTestScope),
                 mUiBgExecutor,
                 mNotificationPanelViewController,
                 mNotificationMediaManager,
                 mLockscreenUserManager,
                 mRemoteInputManager,
                 mQuickSettingsController,
-                mUserSwitcherController,
                 mBatteryController,
                 mColorExtractor,
                 mScreenLifecycle,
                 mWakefulnessLifecycle,
                 mPowerInteractor,
+                mCommunalInteractor,
                 mStatusBarStateController,
                 Optional.of(mBubbles),
                 () -> mNoteTaskController,
@@ -542,7 +541,6 @@
                 mWallpaperManager,
                 Optional.of(mStartingSurface),
                 mActivityLaunchAnimator,
-                mJankMonitor,
                 mDeviceStateManager,
                 mWiredChargingRippleController,
                 mDreamManager,
@@ -838,6 +836,25 @@
     }
 
     @Test
+    public void testEnteringGlanceableHub_updatesScrim() {
+        // Transition to the glanceable hub.
+        mCommunalRepository.setTransitionState(flowOf(new ObservableCommunalTransitionState.Idle(
+                CommunalSceneKey.Communal.INSTANCE)));
+        mTestScope.getTestScheduler().runCurrent();
+
+        // ScrimState also transitions.
+        verify(mScrimController).transitionTo(ScrimState.GLANCEABLE_HUB);
+
+        // Transition away from the glanceable hub.
+        mCommunalRepository.setTransitionState(flowOf(new ObservableCommunalTransitionState.Idle(
+                CommunalSceneKey.Blank.INSTANCE)));
+        mTestScope.getTestScheduler().runCurrent();
+
+        // ScrimState goes back to UNLOCKED.
+        verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any());
+    }
+
+    @Test
     public void testShowKeyguardImplementation_setsState() {
         when(mLockscreenUserManager.getCurrentProfiles()).thenReturn(new SparseArray<>());
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 2b1f5fc..c350de2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -35,13 +35,12 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.statusbar.AlertingNotificationManager;
-import com.android.systemui.statusbar.AlertingNotificationManagerTest;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.BaseHeadsUpManagerTest;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
@@ -64,7 +63,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest {
+public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
     @Rule public MockitoRule rule = MockitoJUnit.rule();
 
     private final HeadsUpManagerLogger mHeadsUpManagerLogger = new HeadsUpManagerLogger(
@@ -137,11 +136,6 @@
         );
     }
 
-    @Override
-    protected AlertingNotificationManager createAlertingNotificationManager() {
-        return createHeadsUpManagerPhone();
-    }
-
     @Before
     public void setUp() {
         when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
@@ -179,7 +173,7 @@
                 /* releaseImmediately = */ false);
 
         assertTrue(removedImmediately);
-        assertFalse(hmp.isAlerting(entry.getKey()));
+        assertFalse(hmp.isHeadsUpEntry(entry.getKey()));
     }
 
     @Test
@@ -218,6 +212,6 @@
         hmp.extendHeadsUp();
         mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2);
 
-        assertTrue(hmp.isAlerting(entry.getKey()));
+        assertTrue(hmp.isHeadsUpEntry(entry.getKey()));
     }
 }
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 91cbc32..7362e34 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
@@ -24,9 +24,9 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 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
@@ -35,6 +35,7 @@
 import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
 import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.tuner.TunerService
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -60,8 +61,8 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class KeyguardBypassControllerTest : SysuiTestCase() {
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
     private val featureFlags = FakeFeatureFlags()
     private val shadeRepository = FakeShadeRepository()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 3556703..bbf9a6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -30,11 +30,13 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.doze.util.BurnInHelperKt;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.core.FakeLogBuffer;
 import com.android.systemui.res.R;
+import com.android.systemui.shade.LargeScreenHeaderHelper;
 
 import org.junit.After;
 import org.junit.Before;
@@ -79,12 +81,13 @@
         MockitoAnnotations.initMocks(this);
         mStaticMockSession = mockitoSession()
                 .mockStatic(BurnInHelperKt.class)
+                .mockStatic(LargeScreenHeaderHelper.class)
                 .startMocking();
 
         LogBuffer logBuffer = FakeLogBuffer.Factory.Companion.create();
         mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm(logBuffer);
         when(mResources.getDimensionPixelSize(anyInt())).thenReturn(0);
-        mClockPositionAlgorithm.loadDimens(mResources);
+        mClockPositionAlgorithm.loadDimens(mContext, mResources);
 
         mClockPosition = new KeyguardClockPositionAlgorithm.Result();
     }
@@ -292,18 +295,44 @@
     }
 
     @Test
-    public void notifPaddingMakesUpToFullMarginInSplitShade() {
+    public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOff_usesResource() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR);
+        int keyguardSplitShadeTopMargin = 100;
+        int largeScreenHeaderHeightResource = 70;
         when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin))
-                .thenReturn(100);
+                .thenReturn(keyguardSplitShadeTopMargin);
         when(mResources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height))
-                .thenReturn(70);
-        mClockPositionAlgorithm.loadDimens(mResources);
+                .thenReturn(largeScreenHeaderHeightResource);
+        mClockPositionAlgorithm.loadDimens(mContext, mResources);
         givenLockScreen();
         mIsSplitShade = true;
         // WHEN the position algorithm is run
         positionClock();
-        // THEN the notif padding makes up lacking margin (margin - header height = 30).
-        assertThat(mClockPosition.stackScrollerPadding).isEqualTo(30);
+        // THEN the notif padding makes up lacking margin (margin - header height).
+        int expectedPadding = keyguardSplitShadeTopMargin - largeScreenHeaderHeightResource;
+        assertThat(mClockPosition.stackScrollerPadding).isEqualTo(expectedPadding);
+    }
+
+    @Test
+    public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOn_usesHelper() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR);
+        int keyguardSplitShadeTopMargin = 100;
+        int largeScreenHeaderHeightHelper = 50;
+        int largeScreenHeaderHeightResource = 70;
+        when(LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext))
+                .thenReturn(largeScreenHeaderHeightHelper);
+        when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin))
+                .thenReturn(keyguardSplitShadeTopMargin);
+        when(mResources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height))
+                .thenReturn(largeScreenHeaderHeightResource);
+        mClockPositionAlgorithm.loadDimens(mContext, mResources);
+        givenLockScreen();
+        mIsSplitShade = true;
+        // WHEN the position algorithm is run
+        positionClock();
+        // THEN the notif padding makes up lacking margin (margin - header height).
+        int expectedPadding = keyguardSplitShadeTopMargin - largeScreenHeaderHeightHelper;
+        assertThat(mClockPosition.stackScrollerPadding).isEqualTo(expectedPadding);
     }
 
     @Test
@@ -589,7 +618,7 @@
     private void setSplitShadeTopMargin(int value) {
         when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin))
                 .thenReturn(value);
-        mClockPositionAlgorithm.loadDimens(mResources);
+        mClockPositionAlgorithm.loadDimens(mContext, mResources);
     }
 
     private void givenHighestBurnInOffset() {
@@ -603,7 +632,7 @@
     private void givenMaxBurnInOffset(int offset) {
         when(mResources.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y_clock))
                 .thenReturn(offset);
-        mClockPositionAlgorithm.loadDimens(mResources);
+        mClockPositionAlgorithm.loadDimens(mContext, mResources);
     }
 
     private void givenAOD() {
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 5102b4f..2d120cd 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
@@ -60,10 +60,10 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.SceneTestUtils;
 import com.android.systemui.shade.ShadeViewStateProvider;
 import com.android.systemui.shade.data.repository.FakeShadeRepository;
 import com.android.systemui.statusbar.CommandQueue;
@@ -149,7 +149,7 @@
     private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
     private final TestScope mTestScope = TestScopeProvider.getTestScope();
     private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
-    private final SceneTestUtils mSceneTestUtils = new SceneTestUtils(this);
+    private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
     private KeyguardInteractor mKeyguardInteractor;
     private KeyguardStatusBarViewModel mViewModel;
 
@@ -166,11 +166,11 @@
                 mKeyguardRepository,
                 mCommandQueue,
                 PowerInteractorFactory.create().getPowerInteractor(),
-                mSceneTestUtils.getSceneContainerFlags(),
+                mKosmos.getFakeSceneContainerFlags(),
                 new FakeKeyguardBouncerRepository(),
                 new ConfigurationInteractor(new FakeConfigurationRepository()),
                 new FakeShadeRepository(),
-                () -> mSceneTestUtils.sceneInteractor());
+                () -> mKosmos.getSceneInteractor());
         mViewModel =
                 new KeyguardStatusBarViewModel(
                         mTestScope.getBackgroundScope(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 4827c92..3bde6e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -31,6 +31,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -48,9 +49,9 @@
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.Color;
-import android.os.Handler;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.testing.ViewUtils;
 import android.util.MathUtils;
 import android.view.View;
 
@@ -59,19 +60,21 @@
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.TestScopeProvider;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.shade.transition.LinearLargeScreenShadeInterpolator;
@@ -102,7 +105,6 @@
 import java.util.HashSet;
 import java.util.Map;
 
-import kotlinx.coroutines.CoroutineDispatcher;
 import kotlinx.coroutines.test.TestScope;
 
 @RunWith(AndroidTestingRunner.class)
@@ -111,13 +113,14 @@
 public class ScrimControllerTest extends SysuiTestCase {
 
     @Rule public Expect mExpect = Expect.create();
+    private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
 
     private final FakeConfigurationController mConfigurationController =
             new FakeConfigurationController();
     private final LargeScreenShadeInterpolator
             mLinearLargeScreenShadeInterpolator = new LinearLargeScreenShadeInterpolator();
 
-    private final TestScope mTestScope = TestScopeProvider.getTestScope();
+    private final TestScope mTestScope = mKosmos.getTestScope();
     private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
 
     private ScrimController mScrimController;
@@ -134,7 +137,7 @@
     @Mock private AlarmManager mAlarmManager;
     @Mock private DozeParameters mDozeParameters;
     @Mock private LightBarController mLightBarController;
-    @Mock private DelayedWakeLock.Builder mDelayedWakeLockBuilder;
+    @Mock private DelayedWakeLock.Factory mDelayedWakeLockFactory;
     @Mock private DelayedWakeLock mWakeLock;
     @Mock private KeyguardStateController mKeyguardStateController;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -144,9 +147,12 @@
     @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
     @Mock private AlternateBouncerToGoneTransitionViewModel
             mAlternateBouncerToGoneTransitionViewModel;
-    @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    private final KeyguardTransitionInteractor mKeyguardTransitionInteractor =
+            mKosmos.getKeyguardTransitionInteractor();
+    private final FakeKeyguardTransitionRepository mKeyguardTransitionRepository =
+            mKosmos.getKeyguardTransitionRepository();
+    @Mock private KeyguardInteractor mKeyguardInteractor;
     private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
-    @Mock private CoroutineDispatcher mMainDispatcher;
     @Mock private TypedArray mMockTypedArray;
 
     // TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
@@ -260,15 +266,9 @@
         }).when(mLightBarController).setScrimState(
                 any(ScrimState.class), anyFloat(), any(GradientColors.class));
 
-        when(mDelayedWakeLockBuilder.setHandler(any(Handler.class)))
-                .thenReturn(mDelayedWakeLockBuilder);
-        when(mDelayedWakeLockBuilder.setTag(any(String.class)))
-                .thenReturn(mDelayedWakeLockBuilder);
-        when(mDelayedWakeLockBuilder.build()).thenReturn(mWakeLock);
+        when(mDelayedWakeLockFactory.create(any(String.class))).thenReturn(mWakeLock);
         when(mDockManager.isDocked()).thenReturn(false);
 
-        when(mKeyguardTransitionInteractor.transition(any(), any()))
-                .thenReturn(emptyFlow());
         when(mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha())
                 .thenReturn(emptyFlow());
         when(mAlternateBouncerToGoneTransitionViewModel.getScrimAlpha())
@@ -279,7 +279,7 @@
                 mDozeParameters,
                 mAlarmManager,
                 mKeyguardStateController,
-                mDelayedWakeLockBuilder,
+                mDelayedWakeLockFactory,
                 new FakeHandler(mLooper.getLooper()),
                 mKeyguardUpdateMonitor,
                 mDockManager,
@@ -292,14 +292,18 @@
                 mPrimaryBouncerToGoneTransitionViewModel,
                 mAlternateBouncerToGoneTransitionViewModel,
                 mKeyguardTransitionInteractor,
+                mKeyguardInteractor,
                 mWallpaperRepository,
-                mMainDispatcher,
+                mKosmos.getTestDispatcher(),
                 mLinearLargeScreenShadeInterpolator);
         mScrimController.start();
         mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
         mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
         mScrimController.setAnimatorListener(mAnimatorListener);
 
+        // Attach behind scrim so flows that are collecting on it start running.
+        ViewUtils.attachView(mScrimBehind);
+
         mScrimController.setHasBackdrop(false);
 
         mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false);
@@ -630,6 +634,164 @@
     }
 
     @Test
+    public void lockscreenToHubTransition_setsBehindScrimAlpha() {
+        // Start on lockscreen.
+        mScrimController.transitionTo(ScrimState.KEYGUARD);
+        finishAnimationsImmediately();
+
+        // Behind scrim starts at default alpha.
+        final float transitionProgress = 0f;
+        float expectedAlpha = ScrimState.KEYGUARD.getBehindAlpha();
+        mKeyguardTransitionRepository.sendTransitionStepJava(mKosmos.getTestScope(),
+                new TransitionStep(
+                        KeyguardState.LOCKSCREEN,
+                        KeyguardState.GLANCEABLE_HUB,
+                        transitionProgress,
+                        TransitionState.STARTED
+                ), true);
+        mTestScope.getTestScheduler().runCurrent();
+        assertThat(mScrimBehind.getViewAlpha()).isEqualTo(expectedAlpha);
+
+        // Scrim fades out as transition runs.
+        final float runningProgress = 0.2f;
+        expectedAlpha = (1 - runningProgress) * ScrimState.KEYGUARD.getBehindAlpha();
+        mKeyguardTransitionRepository.sendTransitionStepJava(mKosmos.getTestScope(),
+                new TransitionStep(
+                        KeyguardState.LOCKSCREEN,
+                        KeyguardState.GLANCEABLE_HUB,
+                        runningProgress,
+                        TransitionState.RUNNING
+                ), true);
+        mTestScope.getTestScheduler().runCurrent();
+        assertThat(mScrimBehind.getViewAlpha()).isEqualTo(expectedAlpha);
+
+        // Scrim invisible at end of transition.
+        final float finishedProgress = 1f;
+        expectedAlpha = 0f;
+        mKeyguardTransitionRepository.sendTransitionStepJava(mKosmos.getTestScope(),
+                new TransitionStep(
+                        KeyguardState.LOCKSCREEN,
+                        KeyguardState.GLANCEABLE_HUB,
+                        finishedProgress,
+                        TransitionState.FINISHED
+                ), true);
+        mTestScope.getTestScheduler().runCurrent();
+        assertThat(mScrimBehind.getViewAlpha()).isEqualTo(expectedAlpha);
+    }
+
+    @Test
+    public void hubToLockscreenTransition_setsViewAlpha() {
+        // Start on glanceable hub.
+        mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
+        finishAnimationsImmediately();
+
+        // Behind scrim starts at 0 alpha.
+        final float transitionProgress = 0f;
+        float expectedAlpha = 0f;
+        mKeyguardTransitionRepository.sendTransitionStepJava(mKosmos.getTestScope(),
+                new TransitionStep(
+                        KeyguardState.GLANCEABLE_HUB,
+                        KeyguardState.LOCKSCREEN,
+                        transitionProgress,
+                        TransitionState.STARTED
+                ), true);
+        mTestScope.getTestScheduler().runCurrent();
+        assertThat(mScrimBehind.getViewAlpha()).isEqualTo(expectedAlpha);
+
+        // Scrim fades in as transition runs.
+        final float runningProgress = 0.2f;
+        expectedAlpha = runningProgress * ScrimState.KEYGUARD.getBehindAlpha();
+        mKeyguardTransitionRepository.sendTransitionStepJava(mKosmos.getTestScope(),
+                new TransitionStep(
+                        KeyguardState.GLANCEABLE_HUB,
+                        KeyguardState.LOCKSCREEN,
+                        runningProgress,
+                        TransitionState.RUNNING
+                ), true);
+        mTestScope.getTestScheduler().runCurrent();
+        assertThat(mScrimBehind.getViewAlpha()).isEqualTo(expectedAlpha);
+
+        // Scrim at default visibility at end of transition.
+        final float finishedProgress = 1f;
+        expectedAlpha = finishedProgress * ScrimState.KEYGUARD.getBehindAlpha();
+        mKeyguardTransitionRepository.sendTransitionStepJava(mKosmos.getTestScope(),
+                new TransitionStep(
+                        KeyguardState.GLANCEABLE_HUB,
+                        KeyguardState.LOCKSCREEN,
+                        finishedProgress,
+                        TransitionState.FINISHED
+                ), true);
+        mTestScope.getTestScheduler().runCurrent();
+        assertThat(mScrimBehind.getViewAlpha()).isEqualTo(expectedAlpha);
+    }
+
+    @Test
+    public void transitionToHub() {
+        mScrimController.setRawPanelExpansionFraction(0f);
+        mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
+        mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
+        finishAnimationsImmediately();
+
+        // All scrims transparent on the hub.
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, TRANSPARENT,
+                mScrimBehind, TRANSPARENT));
+    }
+
+    @Test
+    public void openBouncerOnHub() {
+        mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
+
+        // Open the bouncer.
+        mScrimController.setRawPanelExpansionFraction(0f);
+        mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_VISIBLE);
+        finishAnimationsImmediately();
+
+        // Only behind widget is visible.
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, TRANSPARENT,
+                mScrimBehind, OPAQUE));
+
+        // Bouncer is closed.
+        mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
+        mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
+        finishAnimationsImmediately();
+
+        // All scrims are transparent.
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, TRANSPARENT,
+                mScrimBehind, TRANSPARENT));
+    }
+
+    @Test
+    public void openShadeOnHub() {
+        mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
+
+        // Open the shade.
+        mScrimController.transitionTo(SHADE_LOCKED);
+        mScrimController.setQsPosition(1f, 0);
+        finishAnimationsImmediately();
+
+        // Shade scrims are visible.
+        assertScrimAlpha(Map.of(
+                mNotificationsScrim, OPAQUE,
+                mScrimInFront, TRANSPARENT,
+                mScrimBehind, OPAQUE));
+
+        mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
+        finishAnimationsImmediately();
+
+        // All scrims are transparent.
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, TRANSPARENT,
+                mScrimBehind, TRANSPARENT));
+    }
+
+    @Test
     public void onThemeChange_bouncerBehindTint_isUpdatedToSurfaceColor() {
         assertEquals(BOUNCER.getBehindTint(), 0x112233);
         mSurfaceColor = 0x223344;
@@ -987,7 +1149,7 @@
                 mDozeParameters,
                 mAlarmManager,
                 mKeyguardStateController,
-                mDelayedWakeLockBuilder,
+                mDelayedWakeLockFactory,
                 new FakeHandler(mLooper.getLooper()),
                 mKeyguardUpdateMonitor,
                 mDockManager,
@@ -1000,8 +1162,9 @@
                 mPrimaryBouncerToGoneTransitionViewModel,
                 mAlternateBouncerToGoneTransitionViewModel,
                 mKeyguardTransitionInteractor,
+                mKeyguardInteractor,
                 mWallpaperRepository,
-                mMainDispatcher,
+                mKosmos.getTestDispatcher(),
                 mLinearLargeScreenShadeInterpolator);
         mScrimController.start();
         mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
@@ -1267,7 +1430,7 @@
                 ScrimState.UNINITIALIZED, ScrimState.KEYGUARD, BOUNCER,
                 ScrimState.DREAMING, ScrimState.BOUNCER_SCRIMMED, ScrimState.BRIGHTNESS_MIRROR,
                 ScrimState.UNLOCKED, SHADE_LOCKED, ScrimState.AUTH_SCRIMMED,
-                ScrimState.AUTH_SCRIMMED_SHADE));
+                ScrimState.AUTH_SCRIMMED_SHADE, ScrimState.GLANCEABLE_HUB));
 
         for (ScrimState state : ScrimState.values()) {
             if (!lowPowerModeStates.contains(state) && !regularStates.contains(state)) {
@@ -1684,6 +1847,26 @@
     }
 
     @Test
+    public void notificationBoundsTopGetsPassedToKeyguard() {
+        mScrimController.transitionTo(SHADE_LOCKED);
+        mScrimController.setQsPosition(1f, 0);
+        finishAnimationsImmediately();
+
+        mScrimController.setNotificationsBounds(0f, 100f, 0f, 0f);
+        verify(mKeyguardInteractor).setTopClippingBounds(eq(100));
+    }
+
+    @Test
+    public void notificationBoundsTopDoesNotGetPassedToKeyguardWhenNotifScrimIsNotVisible() {
+        mScrimController.setKeyguardOccluded(true);
+        mScrimController.transitionTo(ScrimState.KEYGUARD);
+        finishAnimationsImmediately();
+
+        mScrimController.setNotificationsBounds(0f, 100f, 0f, 0f);
+        verify(mKeyguardInteractor).setTopClippingBounds(eq(null));
+    }
+
+    @Test
     public void transitionToDreaming() {
         mScrimController.setRawPanelExpansionFraction(0f);
         mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 8dde935..cb45315 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -182,8 +182,10 @@
         when(mBouncerViewDelegate.getBackCallback()).thenReturn(mBouncerViewDelegateBackCallback);
         mFeatureFlags = new FakeFeatureFlags();
         mFeatureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false);
-        mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
-        mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
+        mSetFlagsRule.disableFlags(
+                com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR,
+                com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
+        );
 
         when(mNotificationShadeWindowController.getWindowRootView())
                 .thenReturn(mNotificationShadeWindowView);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt
deleted file mode 100644
index 91c233a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-class UserSetupRepositoryTest : SysuiTestCase() {
-    private lateinit var underTest: UserSetupRepository
-    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
-    private val scope = CoroutineScope(IMMEDIATE)
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        underTest =
-            UserSetupRepositoryImpl(
-                deviceProvisionedController,
-                IMMEDIATE,
-                scope,
-            )
-    }
-
-    @After
-    fun tearDown() {
-        scope.cancel()
-    }
-
-    @Test
-    fun testUserSetup_defaultFalse() =
-        runBlocking(IMMEDIATE) {
-            var latest: Boolean? = null
-
-            val job = underTest.isUserSetupFlow.onEach { latest = it }.launchIn(this)
-
-            assertThat(latest).isFalse()
-
-            job.cancel()
-        }
-
-    @Test
-    fun testUserSetup_updatesOnChange() =
-        runBlocking(IMMEDIATE) {
-            var latest: Boolean? = null
-
-            val job = underTest.isUserSetupFlow.onEach { latest = it }.launchIn(this)
-
-            whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
-            val callback = getDeviceProvisionedListener()
-            callback.onUserSetupChanged()
-
-            assertThat(latest).isTrue()
-
-            job.cancel()
-        }
-
-    private fun getDeviceProvisionedListener(): DeviceProvisionedListener {
-        val captor = argumentCaptor<DeviceProvisionedListener>()
-        verify(deviceProvisionedController).addCallback(captor.capture())
-        return captor.value!!
-    }
-
-    companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
index 1f8cc54..6785de93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
@@ -168,6 +168,7 @@
                 assertThat(conn.carrierName.value)
                     .isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}"))
                 assertThat(conn.hasPrioritizedNetworkCapabilities.value).isEqualTo(model.slice)
+                assertThat(conn.isNonTerrestrial.value).isEqualTo(model.ntn)
 
                 // TODO(b/261029387): check these once we start handling them
                 assertThat(conn.isEmergencyOnly.value).isFalse()
@@ -194,6 +195,7 @@
         val roaming: Boolean,
         val name: String,
         val slice: Boolean,
+        val ntn: Boolean,
     ) {
         override fun toString(): String {
             return "INPUT(level=$level, " +
@@ -205,7 +207,8 @@
                 "carrierNetworkChange=$carrierNetworkChange, " +
                 "roaming=$roaming, " +
                 "name=$name," +
-                "slice=$slice)"
+                "slice=$slice" +
+                "ntn=$ntn)"
         }
 
         // Convenience for iterating test data and creating new cases
@@ -220,6 +223,7 @@
             roaming: Boolean? = null,
             name: String? = null,
             slice: Boolean? = null,
+            ntn: Boolean? = null,
         ): TestCase =
             TestCase(
                 level = level ?: this.level,
@@ -232,6 +236,7 @@
                 roaming = roaming ?: this.roaming,
                 name = name ?: this.name,
                 slice = slice ?: this.slice,
+                ntn = ntn ?: this.ntn,
             )
     }
 
@@ -262,6 +267,7 @@
         private val roaming = listOf(false, true)
         private val names = listOf("name 1", "name 2")
         private val slice = listOf(false, true)
+        private val ntn = listOf(false, true)
 
         @Parameters(name = "{0}") @JvmStatic fun data() = testData()
 
@@ -300,6 +306,7 @@
                     roaming.first(),
                     names.first(),
                     slice.first(),
+                    ntn.first(),
                 )
 
             val tail =
@@ -312,7 +319,8 @@
                         carrierNetworkChange.map { baseCase.modifiedBy(carrierNetworkChange = it) },
                         roaming.map { baseCase.modifiedBy(roaming = it) },
                         names.map { baseCase.modifiedBy(name = it) },
-                        slice.map { baseCase.modifiedBy(slice = it) }
+                        slice.map { baseCase.modifiedBy(slice = it) },
+                        ntn.map { baseCase.modifiedBy(ntn = it) }
                     )
                     .flatten()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
index 03814bd..b958f35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -579,6 +579,7 @@
                 assertThat(conn.carrierName.value)
                     .isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}"))
                 assertThat(conn.hasPrioritizedNetworkCapabilities.value).isEqualTo(model.slice)
+                assertThat(conn.isNonTerrestrial.value).isEqualTo(model.ntn)
 
                 // TODO(b/261029387) check these once we start handling them
                 assertThat(conn.isEmergencyOnly.value).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 9d6f315..9855651 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -21,6 +21,7 @@
 import android.content.Intent
 import android.net.ConnectivityManager
 import android.net.ConnectivityManager.NetworkCallback
+import android.platform.test.annotations.EnableFlags
 import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN
 import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN
 import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
@@ -963,6 +964,31 @@
         }
 
     @Test
+    @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    fun isNonTerrestrial_updatesFromServiceState() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isNonTerrestrial)
+
+            // Lambda makes it a little clearer what we are testing IMO
+            val serviceStateCreator = { ntn: Boolean ->
+                mock<ServiceState>().also {
+                    whenever(it.isUsingNonTerrestrialNetwork).thenReturn(ntn)
+                }
+            }
+
+            // Starts out false
+            assertThat(latest).isFalse()
+
+            getTelephonyCallbackForType<ServiceStateListener>()
+                .onServiceStateChanged(serviceStateCreator(true))
+            assertThat(latest).isTrue()
+
+            getTelephonyCallbackForType<ServiceStateListener>()
+                .onServiceStateChanged(serviceStateCreator(false))
+            assertThat(latest).isFalse()
+        }
+
+    @Test
     fun numberOfLevels_usesCarrierConfig() =
         testScope.runTest {
             var latest: Int? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 20d5c5d..49953a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
 
+import android.platform.test.annotations.EnableFlags
 import android.telephony.CellSignalStrength
 import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
 import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
@@ -159,10 +160,13 @@
         }
 
     @Test
-    fun numberOfLevels_comesFromRepo() =
+    fun numberOfLevels_comesFromRepo_whenApplicable() =
         testScope.runTest {
             var latest: Int? = null
-            val job = underTest.signalLevelIcon.onEach { latest = it.numberOfLevels }.launchIn(this)
+            val job =
+                underTest.signalLevelIcon
+                    .onEach { latest = (it as? SignalIconModel.Cellular)?.numberOfLevels }
+                    .launchIn(this)
 
             connectionRepository.numberOfLevels.value = 5
             assertThat(latest).isEqualTo(5)
@@ -491,14 +495,19 @@
         }
 
     @Test
-    fun iconId_correctLevel_notCutout() =
+    fun cellBasedIconId_correctLevel_notCutout() =
         testScope.runTest {
+            connectionRepository.isNonTerrestrial.value = false
             connectionRepository.isInService.value = true
             connectionRepository.primaryLevel.value = 1
             connectionRepository.setDataEnabled(false)
+            connectionRepository.isNonTerrestrial.value = false
 
-            var latest: SignalIconModel? = null
-            val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+            var latest: SignalIconModel.Cellular? = null
+            val job =
+                underTest.signalLevelIcon
+                    .onEach { latest = it as? SignalIconModel.Cellular }
+                    .launchIn(this)
 
             assertThat(latest?.level).isEqualTo(1)
             assertThat(latest?.showExclamationMark).isFalse()
@@ -509,6 +518,7 @@
     @Test
     fun icon_usesLevelFromInteractor() =
         testScope.runTest {
+            connectionRepository.isNonTerrestrial.value = false
             connectionRepository.isInService.value = true
 
             var latest: SignalIconModel? = null
@@ -524,10 +534,15 @@
         }
 
     @Test
-    fun icon_usesNumberOfLevelsFromInteractor() =
+    fun cellBasedIcon_usesNumberOfLevelsFromInteractor() =
         testScope.runTest {
-            var latest: SignalIconModel? = null
-            val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+            connectionRepository.isNonTerrestrial.value = false
+
+            var latest: SignalIconModel.Cellular? = null
+            val job =
+                underTest.signalLevelIcon
+                    .onEach { latest = it as? SignalIconModel.Cellular }
+                    .launchIn(this)
 
             connectionRepository.numberOfLevels.value = 5
             assertThat(latest!!.numberOfLevels).isEqualTo(5)
@@ -539,12 +554,16 @@
         }
 
     @Test
-    fun icon_defaultDataDisabled_showExclamationTrue() =
+    fun cellBasedIcon_defaultDataDisabled_showExclamationTrue() =
         testScope.runTest {
+            connectionRepository.isNonTerrestrial.value = false
             mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false
 
-            var latest: SignalIconModel? = null
-            val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+            var latest: SignalIconModel.Cellular? = null
+            val job =
+                underTest.signalLevelIcon
+                    .onEach { latest = it as? SignalIconModel.Cellular }
+                    .launchIn(this)
 
             assertThat(latest!!.showExclamationMark).isTrue()
 
@@ -552,12 +571,16 @@
         }
 
     @Test
-    fun icon_defaultConnectionFailed_showExclamationTrue() =
+    fun cellBasedIcon_defaultConnectionFailed_showExclamationTrue() =
         testScope.runTest {
+            connectionRepository.isNonTerrestrial.value = false
             mobileIconsInteractor.isDefaultConnectionFailed.value = true
 
-            var latest: SignalIconModel? = null
-            val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+            var latest: SignalIconModel.Cellular? = null
+            val job =
+                underTest.signalLevelIcon
+                    .onEach { latest = it as? SignalIconModel.Cellular }
+                    .launchIn(this)
 
             assertThat(latest!!.showExclamationMark).isTrue()
 
@@ -565,14 +588,18 @@
         }
 
     @Test
-    fun icon_enabledAndNotFailed_showExclamationFalse() =
+    fun cellBasedIcon_enabledAndNotFailed_showExclamationFalse() =
         testScope.runTest {
+            connectionRepository.isNonTerrestrial.value = false
             connectionRepository.isInService.value = true
             mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true
             mobileIconsInteractor.isDefaultConnectionFailed.value = false
 
-            var latest: SignalIconModel? = null
-            val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+            var latest: SignalIconModel.Cellular? = null
+            val job =
+                underTest.signalLevelIcon
+                    .onEach { latest = it as? SignalIconModel.Cellular }
+                    .launchIn(this)
 
             assertThat(latest!!.showExclamationMark).isFalse()
 
@@ -580,11 +607,15 @@
         }
 
     @Test
-    fun icon_usesEmptyState_whenNotInService() =
+    fun cellBasedIcon_usesEmptyState_whenNotInService() =
         testScope.runTest {
-            var latest: SignalIconModel? = null
-            val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+            var latest: SignalIconModel.Cellular? = null
+            val job =
+                underTest.signalLevelIcon
+                    .onEach { latest = it as? SignalIconModel.Cellular }
+                    .launchIn(this)
 
+            connectionRepository.isNonTerrestrial.value = false
             connectionRepository.isInService.value = false
 
             assertThat(latest?.level).isEqualTo(0)
@@ -604,11 +635,15 @@
         }
 
     @Test
-    fun icon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() =
+    fun cellBasedIcon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() =
         testScope.runTest {
-            var latest: SignalIconModel? = null
-            val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+            var latest: SignalIconModel.Cellular? = null
+            val job =
+                underTest.signalLevelIcon
+                    .onEach { latest = it as? SignalIconModel.Cellular? }
+                    .launchIn(this)
 
+            connectionRepository.isNonTerrestrial.value = false
             connectionRepository.isInService.value = true
             connectionRepository.carrierNetworkChangeActive.value = true
             connectionRepository.primaryLevel.value = 1
@@ -626,6 +661,20 @@
             job.cancel()
         }
 
+    @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    @Test
+    fun satBasedIcon_isUsedWhenNonTerrestrial() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.signalLevelIcon)
+
+            // Start off using cellular
+            assertThat(latest).isInstanceOf(SignalIconModel.Cellular::class.java)
+
+            connectionRepository.isNonTerrestrial.value = true
+
+            assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
+        }
+
     private fun createInteractor(
         overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
     ) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 2060288..0b14be1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -31,10 +31,10 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
 import com.android.systemui.util.CarrierConfigTracker
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
index 90a8946..ebec003 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
@@ -32,7 +32,7 @@
     @Test
     fun drawableFromModel_level0_numLevels4_noExclamation_notCarrierNetworkChange() {
         val model =
-            SignalIconModel(
+            SignalIconModel.Cellular(
                 level = 0,
                 numberOfLevels = 4,
                 showExclamationMark = false,
@@ -59,7 +59,7 @@
         val expected: Int,
     ) {
         fun toSignalIconModel() =
-            SignalIconModel(
+            SignalIconModel.Cellular(
                 level = level,
                 numberOfLevels = numberOfLevels,
                 showExclamationMark = showExclamation,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index 52fc258..deb9fcf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -30,7 +30,6 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorImpl
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
@@ -39,6 +38,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
 import com.android.systemui.util.CarrierConfigTracker
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
@@ -190,7 +190,7 @@
 
         /** Convenience constructor for these tests */
         fun defaultSignal(level: Int = 1): SignalIconModel {
-            return SignalIconModel(
+            return SignalIconModel.Cellular(
                 level,
                 NUM_LEVELS,
                 showExclamationMark = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index 44fa132..83d0fe8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -42,7 +42,6 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorImpl
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
 import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
@@ -51,10 +50,12 @@
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
 import com.android.systemui.util.CarrierConfigTracker
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.filterIsInstance
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.TestScope
@@ -709,6 +710,87 @@
                 .isEqualTo(Icon.Resource(R.drawable.mobile_network_type_background, null))
         }
 
+    @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    @Test
+    fun nonTerrestrial_defaultProperties() =
+        testScope.runTest {
+            repository.isNonTerrestrial.value = true
+
+            val roaming by collectLastValue(underTest.roaming)
+            val networkTypeIcon by collectLastValue(underTest.networkTypeIcon)
+            val networkTypeBackground by collectLastValue(underTest.networkTypeBackground)
+            val activityInVisible by collectLastValue(underTest.activityInVisible)
+            val activityOutVisible by collectLastValue(underTest.activityOutVisible)
+            val activityContainerVisible by collectLastValue(underTest.activityContainerVisible)
+
+            assertThat(roaming).isFalse()
+            assertThat(networkTypeIcon).isNull()
+            assertThat(networkTypeBackground).isNull()
+            assertThat(activityInVisible).isFalse()
+            assertThat(activityOutVisible).isFalse()
+            assertThat(activityContainerVisible).isFalse()
+        }
+
+    @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    @Test
+    fun nonTerrestrial_ignoresDefaultProperties() =
+        testScope.runTest {
+            repository.isNonTerrestrial.value = true
+
+            val roaming by collectLastValue(underTest.roaming)
+            val networkTypeIcon by collectLastValue(underTest.networkTypeIcon)
+            val networkTypeBackground by collectLastValue(underTest.networkTypeBackground)
+            val activityInVisible by collectLastValue(underTest.activityInVisible)
+            val activityOutVisible by collectLastValue(underTest.activityOutVisible)
+            val activityContainerVisible by collectLastValue(underTest.activityContainerVisible)
+
+            repository.setAllRoaming(true)
+            repository.setNetworkTypeKey(connectionsRepository.LTE_KEY)
+            // sets the background on cellular
+            repository.hasPrioritizedNetworkCapabilities.value = true
+            repository.dataActivityDirection.value =
+                DataActivityModel(
+                    hasActivityIn = true,
+                    hasActivityOut = true,
+                )
+
+            assertThat(roaming).isFalse()
+            assertThat(networkTypeIcon).isNull()
+            assertThat(networkTypeBackground).isNull()
+            assertThat(activityInVisible).isFalse()
+            assertThat(activityOutVisible).isFalse()
+            assertThat(activityContainerVisible).isFalse()
+        }
+
+    @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    @Test
+    fun nonTerrestrial_usesSatelliteIcon() =
+        testScope.runTest {
+            repository.isNonTerrestrial.value = true
+            repository.setAllLevels(0)
+
+            val latest by
+                collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class))
+
+            // Level 0 -> no connection
+            assertThat(latest).isNotNull()
+            assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0)
+
+            // 1-2 -> 1 bar
+            repository.setAllLevels(1)
+            assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+            repository.setAllLevels(2)
+            assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+            // 3-4 -> 2 bars
+            repository.setAllLevels(3)
+            assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+
+            repository.setAllLevels(4)
+            assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+        }
+
     private fun createAndSetViewModel() {
         underTest =
             MobileIconViewModel(
@@ -723,24 +805,5 @@
 
     companion object {
         private const val SUB_1_ID = 1
-        private const val NUM_LEVELS = 4
-
-        /** Convenience constructor for these tests */
-        fun defaultSignal(level: Int = 1): SignalIconModel {
-            return SignalIconModel(
-                level,
-                NUM_LEVELS,
-                showExclamationMark = false,
-                carrierNetworkChange = false,
-            )
-        }
-
-        fun emptySignal(): SignalIconModel =
-            SignalIconModel(
-                level = 0,
-                numberOfLevels = NUM_LEVELS,
-                showExclamationMark = true,
-                carrierNetworkChange = false,
-            )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
index a906a89..77e48bff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -31,10 +31,11 @@
 import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE
 import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN
 import android.telephony.satellite.SatelliteManager.SatelliteException
-import android.telephony.satellite.SatelliteStateCallback
+import android.telephony.satellite.SatelliteModemStateCallback
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.log.core.FakeLogBuffer
 import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.MIN_UPTIME
 import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.POLLING_INTERVAL_MS
 import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
@@ -87,6 +88,7 @@
                     Optional.empty(),
                     dispatcher,
                     testScope.backgroundScope,
+                    FakeLogBuffer.Factory.create(),
                     systemClock,
                 )
 
@@ -106,8 +108,8 @@
             val latest by collectLastValue(underTest.connectionState)
             runCurrent()
             val callback =
-                withArgCaptor<SatelliteStateCallback> {
-                    verify(satelliteManager).registerForSatelliteModemStateChanged(any(), capture())
+                withArgCaptor<SatelliteModemStateCallback> {
+                    verify(satelliteManager).registerForModemStateChanged(any(), capture())
                 }
 
             // Mapping from modem state to SatelliteConnectionState is rote, just run all of the
@@ -182,7 +184,7 @@
                     null
                 }
                 .`when`(satelliteManager)
-                .requestIsSatelliteCommunicationAllowedForCurrentLocation(
+                .requestIsCommunicationAllowedForCurrentLocation(
                     any(),
                     any<OutcomeReceiver<Boolean, SatelliteException>>()
                 )
@@ -205,7 +207,7 @@
                     null
                 }
                 .`when`(satelliteManager)
-                .requestIsSatelliteCommunicationAllowedForCurrentLocation(
+                .requestIsCommunicationAllowedForCurrentLocation(
                     any(),
                     any<OutcomeReceiver<Boolean, SatelliteException>>()
                 )
@@ -228,7 +230,7 @@
                     null
                 }
                 .`when`(satelliteManager)
-                .requestIsSatelliteCommunicationAllowedForCurrentLocation(
+                .requestIsCommunicationAllowedForCurrentLocation(
                     any(),
                     any<OutcomeReceiver<Boolean, SatelliteException>>()
                 )
@@ -272,7 +274,7 @@
                     null
                 }
                 .`when`(satelliteManager)
-                .requestIsSatelliteCommunicationAllowedForCurrentLocation(
+                .requestIsCommunicationAllowedForCurrentLocation(
                     any(),
                     any<OutcomeReceiver<Boolean, SatelliteException>>()
                 )
@@ -305,7 +307,7 @@
                     null
                 }
                 .`when`(satelliteManager)
-                .requestIsSatelliteCommunicationAllowedForCurrentLocation(
+                .requestIsCommunicationAllowedForCurrentLocation(
                     any(),
                     any<OutcomeReceiver<Boolean, SatelliteException>>()
                 )
@@ -331,7 +333,7 @@
             val signalStrength by collectLastValue(underTest.signalStrength)
 
             // THEN the manager is not asked for the information, and default values are returned
-            verify(satelliteManager, never()).registerForSatelliteModemStateChanged(any(), any())
+            verify(satelliteManager, never()).registerForModemStateChanged(any(), any())
             verify(satelliteManager, never()).registerForNtnSignalStrengthChanged(any(), any())
         }
 
@@ -357,7 +359,7 @@
             runCurrent()
 
             // THEN we finally register with the satellite manager
-            verify(satelliteManager).registerForSatelliteModemStateChanged(any(), any())
+            verify(satelliteManager).registerForModemStateChanged(any(), any())
         }
 
     private fun setUpRepo(
@@ -371,7 +373,7 @@
                 callback.onResult(satelliteSupported)
             }
             .whenever(satelliteManager)
-            .requestIsSatelliteSupported(any(), any())
+            .requestIsSupported(any(), any())
 
         systemClock.setUptimeMillis(Process.getStartUptimeMillis() + uptime)
 
@@ -380,6 +382,7 @@
                 if (satMan != null) Optional.of(satMan) else Optional.empty(),
                 dispatcher,
                 testScope.backgroundScope,
+                FakeLogBuffer.Factory.create(),
                 systemClock,
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
index e010b86..d465b47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.pipeline.satellite.domain.interactor
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import androidx.test.filters.SmallTest
 import com.android.internal.telephony.flags.Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG
 import com.android.systemui.SysuiTestCase
@@ -49,8 +51,6 @@
 
     @Before
     fun setUp() {
-        mSetFlagsRule.enableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
-
         underTest =
             DeviceBasedSatelliteInteractor(
                 repo,
@@ -60,6 +60,7 @@
     }
 
     @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
     fun isSatelliteAllowed_falseWhenNotAllowed() =
         testScope.runTest {
             val latest by collectLastValue(underTest.isSatelliteAllowed)
@@ -72,6 +73,7 @@
         }
 
     @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
     fun isSatelliteAllowed_trueWhenAllowed() =
         testScope.runTest {
             val latest by collectLastValue(underTest.isSatelliteAllowed)
@@ -84,10 +86,10 @@
         }
 
     @Test
+    @DisableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
     fun isSatelliteAllowed_offWhenFlagIsOff() =
         testScope.runTest {
             // GIVEN feature is disabled
-            mSetFlagsRule.disableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
 
             // Remake the interactor so the flag is read
             underTest =
@@ -107,6 +109,7 @@
         }
 
     @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
     fun connectionState_matchesRepositoryValue() =
         testScope.runTest {
             val latest by collectLastValue(underTest.connectionState)
@@ -129,10 +132,10 @@
         }
 
     @Test
+    @DisableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
     fun connectionState_offWhenFeatureIsDisabled() =
         testScope.runTest {
             // GIVEN the flag is disabled
-            mSetFlagsRule.disableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
 
             // Remake the interactor so the flag is read
             underTest =
@@ -164,6 +167,7 @@
         }
 
     @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
     fun signalStrength_matchesRepo() =
         testScope.runTest {
             val latest by collectLastValue(underTest.signalStrength)
@@ -182,10 +186,10 @@
         }
 
     @Test
+    @DisableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
     fun signalStrength_zeroWhenDisabled() =
         testScope.runTest {
             // GIVEN the flag is enabled
-            mSetFlagsRule.disableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
 
             // Remake the interactor so the flag is read
             underTest =
@@ -212,6 +216,19 @@
         }
 
     @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    fun areAllConnectionsOutOfService_noConnections_yes() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+            // GIVEN, 0 connections
+
+            // THEN the value is propagated to this interactor
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
     fun areAllConnectionsOutOfService_twoConnectionsOos_yes() =
         testScope.runTest {
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
@@ -229,6 +246,7 @@
         }
 
     @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
     fun areAllConnectionsOutOfService_oneConnectionOos_yes() =
         testScope.runTest {
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
@@ -244,6 +262,7 @@
         }
 
     @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
     fun areAllConnectionsOutOfService_oneConnectionInService_no() =
         testScope.runTest {
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
@@ -259,6 +278,7 @@
         }
 
     @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
     fun areAllConnectionsOutOfService_twoConnectionsOneInService_no() =
         testScope.runTest {
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
@@ -276,6 +296,7 @@
         }
 
     @Test
+    @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
     fun areAllConnectionsOutOfService_twoConnectionsInService_no() =
         testScope.runTest {
             val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
@@ -293,10 +314,10 @@
         }
 
     @Test
+    @DisableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
     fun areAllConnectionsOutOfService_falseWhenFlagIsOff() =
         testScope.runTest {
             // GIVEN the flag is disabled
-            mSetFlagsRule.disableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
 
             // Remake the interactor so the flag is read
             underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
new file mode 100644
index 0000000..21c038a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository
+import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
+    private lateinit var underTest: DeviceBasedSatelliteViewModel
+    private lateinit var interactor: DeviceBasedSatelliteInteractor
+
+    private val repo = FakeDeviceBasedSatelliteRepository()
+    private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+
+    private val testScope = TestScope()
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        interactor =
+            DeviceBasedSatelliteInteractor(
+                repo,
+                mobileIconsInteractor,
+                testScope.backgroundScope,
+            )
+
+        underTest =
+            DeviceBasedSatelliteViewModel(
+                interactor,
+                testScope.backgroundScope,
+            )
+    }
+
+    @Test
+    fun icon_nullWhenShouldNotShow_satelliteNotAllowed() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.icon)
+
+            // GIVEN satellite is not allowed
+            repo.isSatelliteAllowedForCurrentLocation.value = false
+
+            // GIVEN all icons are OOS
+            val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+            i1.isInService.value = false
+
+            // THEN icon is null because we should not be showing it
+            assertThat(latest).isNull()
+        }
+
+    @Test
+    fun icon_nullWhenShouldNotShow_notAllOos() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.icon)
+
+            // GIVEN satellite is allowed
+            repo.isSatelliteAllowedForCurrentLocation.value = true
+
+            // GIVEN all icons are not OOS
+            val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+            i1.isInService.value = true
+
+            // THEN icon is null because we have service
+            assertThat(latest).isNull()
+        }
+
+    @Test
+    fun icon_satelliteIsOff() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.icon)
+
+            // GIVEN satellite is allowed
+            repo.isSatelliteAllowedForCurrentLocation.value = true
+
+            // GIVEN all icons are OOS
+            val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+            i1.isInService.value = false
+
+            // THEN icon is null because we have service
+            assertThat(latest).isInstanceOf(Icon::class.java)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt
new file mode 100644
index 0000000..ca9df57
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.ui.view
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.StatusBarIconView
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Being a simple subclass of [ModernStatusBarView], use the same basic test cases to verify the
+ * root behavior, and add testing for the new [SingleBindableStatusBarIconView.withDefaultBinding]
+ * method.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class SingleBindableStatusBarIconViewTest : SysuiTestCase() {
+    private lateinit var binding: SingleBindableStatusBarIconViewBinding
+
+    // Visibility is outsourced to view-models. This simulates it
+    private var isVisible = true
+    private var visibilityFn: () -> Boolean = { isVisible }
+
+    @Test
+    fun initView_hasCorrectSlot() {
+        val view = createAndInitView()
+
+        assertThat(view.slot).isEqualTo(SLOT_NAME)
+    }
+
+    @Test
+    fun getVisibleState_icon_returnsIcon() {
+        val view = createAndInitView()
+
+        view.setVisibleState(StatusBarIconView.STATE_ICON, /* animate= */ false)
+
+        assertThat(view.visibleState).isEqualTo(StatusBarIconView.STATE_ICON)
+    }
+
+    @Test
+    fun getVisibleState_dot_returnsDot() {
+        val view = createAndInitView()
+
+        view.setVisibleState(StatusBarIconView.STATE_DOT, /* animate= */ false)
+
+        assertThat(view.visibleState).isEqualTo(StatusBarIconView.STATE_DOT)
+    }
+
+    @Test
+    fun getVisibleState_hidden_returnsHidden() {
+        val view = createAndInitView()
+
+        view.setVisibleState(StatusBarIconView.STATE_HIDDEN, /* animate= */ false)
+
+        assertThat(view.visibleState).isEqualTo(StatusBarIconView.STATE_HIDDEN)
+    }
+
+    @Test
+    fun onDarkChanged_bindingReceivesIconAndDecorTint() {
+        val view = createAndInitView()
+
+        view.onDarkChangedWithContrast(arrayListOf(), 0x12345678, 0x12344321)
+
+        assertThat(binding.iconTint).isEqualTo(0x12345678)
+        assertThat(binding.decorTint).isEqualTo(0x12345678)
+    }
+
+    @Test
+    fun setStaticDrawableColor_bindingReceivesIconTint() {
+        val view = createAndInitView()
+
+        view.setStaticDrawableColor(0x12345678, 0x12344321)
+
+        assertThat(binding.iconTint).isEqualTo(0x12345678)
+    }
+
+    @Test
+    fun setDecorColor_bindingReceivesDecorColor() {
+        val view = createAndInitView()
+
+        view.setDecorColor(0x23456789)
+
+        assertThat(binding.decorTint).isEqualTo(0x23456789)
+    }
+
+    @Test
+    fun isIconVisible_usesBinding_true() {
+        val view = createAndInitView()
+
+        isVisible = true
+
+        assertThat(view.isIconVisible).isEqualTo(true)
+    }
+
+    @Test
+    fun isIconVisible_usesBinding_false() {
+        val view = createAndInitView()
+
+        isVisible = false
+
+        assertThat(view.isIconVisible).isEqualTo(false)
+    }
+
+    @Test
+    fun getDrawingRect_takesTranslationIntoAccount() {
+        val view = createAndInitView()
+
+        view.translationX = 50f
+        view.translationY = 60f
+
+        val drawingRect = Rect()
+        view.getDrawingRect(drawingRect)
+
+        assertThat(drawingRect.left).isEqualTo(view.left + 50)
+        assertThat(drawingRect.right).isEqualTo(view.right + 50)
+        assertThat(drawingRect.top).isEqualTo(view.top + 60)
+        assertThat(drawingRect.bottom).isEqualTo(view.bottom + 60)
+    }
+
+    private fun createAndInitView(): SingleBindableStatusBarIconView {
+        val view = SingleBindableStatusBarIconView.createView(context)
+        binding = SingleBindableStatusBarIconView.withDefaultBinding(view, visibilityFn) {}
+        view.initView(SLOT_NAME) { binding }
+        return view
+    }
+
+    companion object {
+        private const val SLOT_NAME = "test_slot"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
index 89842d6..f63f79f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.collectLastValue
 import com.android.systemui.collectValues
+import com.android.systemui.communal.dagger.CommunalModule
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -58,6 +59,7 @@
         modules =
             [
                 SysUITestModule::class,
+                CommunalModule::class,
             ]
     )
     interface TestComponent : SysUITestComponent<CollapsedStatusBarViewModelImpl> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
index 1bdf644..0cb3329 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
@@ -35,7 +35,6 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
@@ -48,6 +47,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
 import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
 import com.android.systemui.util.CarrierConfigTracker
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index 6a3b2c3..4c824c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -19,6 +19,7 @@
 import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
 
 import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
 import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -36,12 +37,15 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.Person;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Region;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -51,13 +55,16 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.systemui.SysuiTestCase;
 import com.android.systemui.res.R;
-import com.android.systemui.statusbar.AlertingNotificationManager;
-import com.android.systemui.statusbar.AlertingNotificationManagerTest;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.settings.FakeGlobalSettings;
 import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.util.time.SystemClock;
 
 import org.junit.Rule;
@@ -70,10 +77,12 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
+public class BaseHeadsUpManagerTest extends SysuiTestCase {
     @Rule
     public MockitoRule rule = MockitoJUnit.rule();
 
+    private static final String TEST_PACKAGE_NAME = "BaseHeadsUpManagerTest";
+
     private static final int TEST_TOUCH_ACCEPTANCE_TIME = 200;
     private static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
 
@@ -81,6 +90,20 @@
     private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
     @Mock private AccessibilityManagerWrapper mAccessibilityMgr;
 
+    private static final int TEST_UID = 0;
+
+    protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
+    protected static final int TEST_AUTO_DISMISS_TIME = 600;
+    protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800;
+    // Number of notifications to use in tests requiring multiple notifications
+    private static final int TEST_NUM_NOTIFICATIONS = 4;
+
+    protected final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings();
+    protected final FakeSystemClock mSystemClock = new FakeSystemClock();
+    protected final FakeExecutor mExecutor = new FakeExecutor(mSystemClock);
+
+    @Mock protected ExpandableNotificationRow mRow;
+
     static {
         assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
         assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
@@ -88,6 +111,9 @@
     }
 
     private final class TestableHeadsUpManager extends BaseHeadsUpManager {
+
+        private HeadsUpEntry mLastCreatedEntry;
+
         TestableHeadsUpManager(Context context,
                 HeadsUpManagerLogger logger,
                 DelayableExecutor executor,
@@ -97,10 +123,23 @@
                 UiEventLogger uiEventLogger) {
             super(context, logger, mockExecutorHandler(executor), globalSettings, systemClock,
                     executor, accessibilityManagerWrapper, uiEventLogger);
+
             mTouchAcceptanceDelay = TEST_TOUCH_ACCEPTANCE_TIME;
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
             mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
             mStickyForSomeTimeAutoDismissTime = TEST_STICKY_AUTO_DISMISS_TIME;
+
+        }
+
+        @Override
+        protected HeadsUpEntry createHeadsUpEntry() {
+            mLastCreatedEntry = spy(super.createHeadsUpEntry());
+            return mLastCreatedEntry;
+        }
+
+        @Override
+        public int getContentFlag() {
+            return FLAG_CONTENT_VIEW_CONTRACTED;
         }
 
         // The following are only implemented by HeadsUpManagerPhone. If you need them, use that.
@@ -173,16 +212,46 @@
         }
     }
 
+    protected StatusBarNotification createSbn(int id, Notification n) {
+        return new StatusBarNotification(
+                TEST_PACKAGE_NAME /* pkg */,
+                TEST_PACKAGE_NAME,
+                id,
+                null /* tag */,
+                TEST_UID,
+                0 /* initialPid */,
+                n,
+                new UserHandle(ActivityManager.getCurrentUser()),
+                null /* overrideGroupKey */,
+                0 /* postTime */);
+    }
+
+    protected StatusBarNotification createSbn(int id, Notification.Builder n) {
+        return createSbn(id, n.build());
+    }
+
+    protected StatusBarNotification createSbn(int id) {
+        final Notification.Builder b = new Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .setContentTitle("Title")
+                .setContentText("Text");
+        return createSbn(id, b);
+    }
+
+    protected NotificationEntry createEntry(int id, Notification n) {
+        return new NotificationEntryBuilder().setSbn(createSbn(id, n)).build();
+    }
+
+    protected NotificationEntry createEntry(int id) {
+        return new NotificationEntryBuilder().setSbn(createSbn(id)).build();
+    }
+
+
     private BaseHeadsUpManager createHeadsUpManager() {
         return new TestableHeadsUpManager(mContext, mLogger, mExecutor, mGlobalSettings,
                 mSystemClock, mAccessibilityMgr, mUiEventLoggerFake);
     }
 
-    @Override
-    protected AlertingNotificationManager createAlertingNotificationManager() {
-        return createHeadsUpManager();
-    }
-
     private NotificationEntry createStickyEntry(int id) {
         final Notification notif = new Notification.Builder(mContext, "")
                 .setSmallIcon(R.drawable.ic_person)
@@ -224,6 +293,79 @@
         }
     }
 
+    @Test
+    public void testShowNotification_addsEntry() {
+        final BaseHeadsUpManager alm = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        alm.showNotification(entry);
+
+        assertTrue(alm.isHeadsUpEntry(entry.getKey()));
+        assertTrue(alm.hasNotifications());
+        assertEquals(entry, alm.getEntry(entry.getKey()));
+    }
+
+    @Test
+    public void testShowNotification_autoDismisses() {
+        final BaseHeadsUpManager alm = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        alm.showNotification(entry);
+        mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME * 3 / 2);
+
+        assertFalse(alm.isHeadsUpEntry(entry.getKey()));
+    }
+
+    @Test
+    public void testRemoveNotification_removeDeferred() {
+        final BaseHeadsUpManager alm = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        alm.showNotification(entry);
+
+        final boolean removedImmediately = alm.removeNotification(
+                entry.getKey(), /* releaseImmediately = */ false);
+        assertFalse(removedImmediately);
+        assertTrue(alm.isHeadsUpEntry(entry.getKey()));
+    }
+
+    @Test
+    public void testRemoveNotification_forceRemove() {
+        final BaseHeadsUpManager alm = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        alm.showNotification(entry);
+
+        final boolean removedImmediately = alm.removeNotification(
+                entry.getKey(), /* releaseImmediately = */ true);
+        assertTrue(removedImmediately);
+        assertFalse(alm.isHeadsUpEntry(entry.getKey()));
+    }
+
+    @Test
+    public void testReleaseAllImmediately() {
+        final BaseHeadsUpManager alm = createHeadsUpManager();
+        for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
+            final NotificationEntry entry = createEntry(i);
+            entry.setRow(mRow);
+            alm.showNotification(entry);
+        }
+
+        alm.releaseAllImmediately();
+
+        assertEquals(0, alm.getAllEntries().count());
+    }
+
+    @Test
+    public void testCanRemoveImmediately_notShownLongEnough() {
+        final BaseHeadsUpManager alm = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        alm.showNotification(entry);
+
+        // The entry has just been added so we should not remove immediately.
+        assertFalse(alm.canRemoveImmediately(entry.getKey()));
+    }
 
     @Test
     public void testHunRemovedLogging() {
@@ -233,7 +375,7 @@
                 BaseHeadsUpManager.HeadsUpEntry.class);
         headsUpEntry.mEntry = notifEntry;
 
-        hum.onAlertEntryRemoved(headsUpEntry);
+        hum.onEntryRemoved(headsUpEntry);
 
         verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(notifEntry));
     }
@@ -286,7 +428,7 @@
         hum.showNotification(entry);
         mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME);
 
-        assertTrue(hum.isAlerting(entry.getKey()));
+        assertTrue(hum.isHeadsUpEntry(entry.getKey()));
     }
 
 
@@ -300,7 +442,7 @@
         mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
                 + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2);
 
-        assertFalse(hum.isAlerting(entry.getKey()));
+        assertFalse(hum.isHeadsUpEntry(entry.getKey()));
     }
 
 
@@ -314,7 +456,7 @@
         mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
                 + (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2);
 
-        assertTrue(hum.isAlerting(entry.getKey()));
+        assertTrue(hum.isHeadsUpEntry(entry.getKey()));
     }
 
 
@@ -327,7 +469,7 @@
         hum.showNotification(entry);
         mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME);
 
-        assertTrue(hum.isAlerting(entry.getKey()));
+        assertTrue(hum.isHeadsUpEntry(entry.getKey()));
     }
 
 
@@ -341,7 +483,7 @@
         mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
                 + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2);
 
-        assertTrue(hum.isAlerting(entry.getKey()));
+        assertTrue(hum.isHeadsUpEntry(entry.getKey()));
     }
 
 
@@ -355,7 +497,7 @@
         mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
                 + (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2);
 
-        assertTrue(hum.isAlerting(entry.getKey()));
+        assertTrue(hum.isHeadsUpEntry(entry.getKey()));
     }
 
 
@@ -370,11 +512,11 @@
         final boolean removedImmediately = hum.removeNotification(
                 entry.getKey(), /* releaseImmediately = */ false);
         assertFalse(removedImmediately);
-        assertTrue(hum.isAlerting(entry.getKey()));
+        assertTrue(hum.isHeadsUpEntry(entry.getKey()));
 
         mSystemClock.advanceTime((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2);
 
-        assertFalse(hum.isAlerting(entry.getKey()));
+        assertFalse(hum.isHeadsUpEntry(entry.getKey()));
     }
 
 
@@ -387,12 +529,12 @@
         hum.showNotification(entry);
         mSystemClock.advanceTime((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2);
 
-        assertTrue(hum.isAlerting(entry.getKey()));
+        assertTrue(hum.isHeadsUpEntry(entry.getKey()));
 
         final boolean removedImmediately = hum.removeNotification(
                 entry.getKey(), /* releaseImmediately = */ false);
         assertTrue(removedImmediately);
-        assertFalse(hum.isAlerting(entry.getKey()));
+        assertFalse(hum.isHeadsUpEntry(entry.getKey()));
     }
 
 
@@ -406,7 +548,7 @@
         final boolean removedImmediately = hum.removeNotification(
                 entry.getKey(), /* releaseImmediately = */ true);
         assertTrue(removedImmediately);
-        assertFalse(hum.isAlerting(entry.getKey()));
+        assertFalse(hum.isHeadsUpEntry(entry.getKey()));
     }
 
 
@@ -560,7 +702,7 @@
         // the notification and then updates it; in order to not log twice, the entry needs
         // to have a functional ExpandableNotificationRow that can keep track of whether it's
         // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
-        hum.onAlertEntryAdded(entryToPin);
+        hum.onEntryAdded(entryToPin);
 
         assertEquals(1, mUiEventLoggerFake.numLogs());
         assertEquals(BaseHeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index 1dab84e..cb6ce68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.policy;
 
 import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -214,7 +215,8 @@
     public void testNetworkRequest() {
         verify(mConnectivityManager, times(1)).registerNetworkCallback(argThat(
                 (NetworkRequest request) ->
-                        request.equals(new NetworkRequest.Builder().clearCapabilities().build())
+                        request.equals(new NetworkRequest.Builder()
+                                .clearCapabilities().addTransportType(TRANSPORT_VPN).build())
                 ), any(NetworkCallback.class));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
new file mode 100644
index 0000000..cd5d5ed
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.app.Notification
+import android.media.projection.MediaProjectionInfo
+import android.media.projection.MediaProjectionManager
+import android.os.Handler
+import android.service.notification.StatusBarNotification
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
+    @Mock private lateinit var handler: Handler
+
+    @Mock private lateinit var mediaProjectionManager: MediaProjectionManager
+
+    @Mock private lateinit var mediaProjectionInfo: MediaProjectionInfo
+
+    @Mock private lateinit var listener1: Runnable
+    @Mock private lateinit var listener2: Runnable
+    @Mock private lateinit var listener3: Runnable
+
+    @Captor
+    private lateinit var mediaProjectionCallbackCaptor:
+        ArgumentCaptor<MediaProjectionManager.Callback>
+
+    private lateinit var controller: SensitiveNotificationProtectionControllerImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        mSetFlagsRule.enableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+
+        controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler)
+
+        // Obtain useful MediaProjectionCallback
+        verify(mediaProjectionManager).addCallback(mediaProjectionCallbackCaptor.capture(), any())
+    }
+
+    @Test
+    fun init_flagEnabled_registerMediaProjectionManagerCallback() {
+        assertNotNull(mediaProjectionCallbackCaptor.value)
+    }
+
+    @Test
+    fun init_flagDisabled_noRegisterMediaProjectionManagerCallback() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+        reset(mediaProjectionManager)
+
+        controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler)
+
+        verifyZeroInteractions(mediaProjectionManager)
+    }
+
+    @Test
+    fun registerSensitiveStateListener_singleListener() {
+        controller.registerSensitiveStateListener(listener1)
+
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+        verify(listener1, times(2)).run()
+    }
+
+    @Test
+    fun registerSensitiveStateListener_multipleListeners() {
+        controller.registerSensitiveStateListener(listener1)
+        controller.registerSensitiveStateListener(listener2)
+
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+        verify(listener1, times(2)).run()
+        verify(listener2, times(2)).run()
+    }
+
+    @Test
+    fun registerSensitiveStateListener_afterProjectionActive() {
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+        controller.registerSensitiveStateListener(listener1)
+        verifyZeroInteractions(listener1)
+
+        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+        verify(listener1).run()
+    }
+
+    @Test
+    fun unregisterSensitiveStateListener_singleListener() {
+        controller.registerSensitiveStateListener(listener1)
+
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+        verify(listener1, times(2)).run()
+
+        controller.unregisterSensitiveStateListener(listener1)
+
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+        verifyNoMoreInteractions(listener1)
+    }
+
+    @Test
+    fun unregisterSensitiveStateListener_multipleListeners() {
+        controller.registerSensitiveStateListener(listener1)
+        controller.registerSensitiveStateListener(listener2)
+        controller.registerSensitiveStateListener(listener3)
+
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+        verify(listener1, times(2)).run()
+        verify(listener2, times(2)).run()
+        verify(listener3, times(2)).run()
+
+        controller.unregisterSensitiveStateListener(listener1)
+        controller.unregisterSensitiveStateListener(listener2)
+
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+        verifyNoMoreInteractions(listener1)
+        verifyNoMoreInteractions(listener2)
+        verify(listener3, times(4)).run()
+    }
+
+    @Test
+    fun isSensitiveStateActive_projectionInactive_false() {
+        assertFalse(controller.isSensitiveStateActive)
+    }
+
+    @Test
+    fun isSensitiveStateActive_projectionActive_true() {
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+        assertTrue(controller.isSensitiveStateActive)
+    }
+
+    @Test
+    fun isSensitiveStateActive_projectionInactiveAfterActive_false() {
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+        assertFalse(controller.isSensitiveStateActive)
+    }
+
+    @Test
+    fun isSensitiveStateActive_projectionActiveAfterInactive_true() {
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+        assertTrue(controller.isSensitiveStateActive)
+    }
+
+    @Test
+    fun shouldProtectNotification_projectionInactive_false() {
+        val notificationEntry = mock(NotificationEntry::class.java)
+
+        assertFalse(controller.shouldProtectNotification(notificationEntry))
+    }
+
+    @Test
+    fun shouldProtectNotification_projectionActive_fgsNotification_false() {
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+        val notificationEntry = mock(NotificationEntry::class.java)
+        val sbn = mock(StatusBarNotification::class.java)
+        val notification = mock(Notification::class.java)
+        `when`(notificationEntry.sbn).thenReturn(sbn)
+        `when`(sbn.notification).thenReturn(notification)
+        `when`(notification.isFgsOrUij).thenReturn(true)
+
+        assertFalse(controller.shouldProtectNotification(notificationEntry))
+    }
+
+    @Test
+    fun shouldProtectNotification_projectionActive_notFgsNotification_true() {
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+        val notificationEntry = mock(NotificationEntry::class.java)
+        val sbn = mock(StatusBarNotification::class.java)
+        val notification = mock(Notification::class.java)
+        `when`(notificationEntry.sbn).thenReturn(sbn)
+        `when`(sbn.notification).thenReturn(notification)
+        `when`(notification.isFgsOrUij).thenReturn(false)
+
+        assertTrue(controller.shouldProtectNotification(notificationEntry))
+    }
+}
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 9419d63..ca0e526 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
@@ -25,20 +25,22 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 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.testKosmos
 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
@@ -47,20 +49,20 @@
 @SmallTest
 @OptIn(ExperimentalCoroutinesApi::class)
 class KeyguardStatusBarViewModelTest : SysuiTestCase() {
-    private val testScope = TestScope()
-    private val sceneTestUtils = SceneTestUtils(this)
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
     private val keyguardRepository = FakeKeyguardRepository()
     private val keyguardInteractor =
         KeyguardInteractor(
             keyguardRepository,
             mock<CommandQueue>(),
             PowerInteractorFactory.create().powerInteractor,
-            sceneTestUtils.sceneContainerFlags,
+            kosmos.fakeSceneContainerFlags,
             FakeKeyguardBouncerRepository(),
             ConfigurationInteractor(FakeConfigurationRepository()),
             FakeShadeRepository(),
         ) {
-            sceneTestUtils.sceneInteractor()
+            kosmos.sceneInteractor
         }
     private val keyguardStatusBarInteractor =
         KeyguardStatusBarInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index b58a41c..457acd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -190,7 +190,7 @@
 
         mWakefulnessLifecycle.dispatchFinishedWakingUp();
         mThemeOverlayController.start();
-        verify(mUserTracker).addCallback(mUserTrackerCallback.capture(), eq(mMainExecutor));
+        verify(mUserTracker).addCallback(mUserTrackerCallback.capture(), eq(mBgExecutor));
         verify(mWallpaperManager).addOnColorsChangedListener(mColorsListener.capture(), eq(null),
                 eq(UserHandle.USER_ALL));
         verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiver.capture(), any(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
index 5b4f4d3..95c934e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
@@ -88,6 +88,30 @@
     }
 
     @Test
+    fun setReadyToHandleTransition_whileTransitionRunning_fromBgThread_propagatesCallbacks() =
+        testScope.runTest {
+            runBlockingInBg { rootProvider.onTransitionStarted() }
+
+            runBlockingInBg {
+                // This causes the transition started callback to be propagated immediately, without
+                // the need to switch thread (as we're already in the correct one). We don't need a
+                // sync barrier on the bg thread as in
+                // setReadyToHandleTransition_whileTransitionRunning_propagatesCallbacks here.
+                scopedProvider.setReadyToHandleTransition(true)
+            }
+
+            listener.assertStarted()
+
+            runBlockingInBg { rootProvider.onTransitionProgress(1f) }
+
+            listener.assertLastProgress(1f)
+
+            runBlockingInBg { rootProvider.onTransitionFinished() }
+
+            listener.assertNotStarted()
+        }
+
+    @Test
     fun setReadyToHandleTransition_beforeAnyCallback_doesNotCrash() {
         testScope.runTest { scopedProvider.setReadyToHandleTransition(true) }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index 6714c94..b6a033a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -32,6 +32,7 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.GuestResetOrExitSessionReceiver
 import com.android.systemui.GuestResumeSessionReceiver
 import com.android.systemui.SysuiTestCase
@@ -39,13 +40,18 @@
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.user.UserSwitchDialogController
 import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.telephonyInteractor
+import com.android.systemui.testKosmos
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.source.UserRecord
@@ -97,8 +103,8 @@
     @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
     private lateinit var spyContext: Context
     private lateinit var userRepository: FakeUserRepository
     private lateinit var keyguardReply: KeyguardInteractorFactory.WithDependencies
@@ -120,15 +126,17 @@
             SUPERVISED_USER_CREATION_APP_PACKAGE,
         )
 
-        utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+        kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+        mSetFlagsRule.enableFlags(AConfigFlags.FLAG_SWITCH_USER_ON_BG)
         spyContext = spy(context)
-        keyguardReply = KeyguardInteractorFactory.create(featureFlags = utils.featureFlags)
+        keyguardReply =
+            KeyguardInteractorFactory.create(featureFlags = kosmos.fakeFeatureFlagsClassic)
         keyguardRepository = keyguardReply.repository
         userRepository = FakeUserRepository()
         refreshUsersScheduler =
             RefreshUsersScheduler(
                 applicationScope = testScope.backgroundScope,
-                mainDispatcher = utils.testDispatcher,
+                mainDispatcher = kosmos.testDispatcher,
                 repository = userRepository,
             )
     }
@@ -172,6 +180,7 @@
             userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
 
             underTest.onRecordSelected(UserRecord(info = userInfos[1]), dialogShower)
+            runCurrent()
 
             verify(uiEventLogger, times(1))
                 .log(MultiUserActionsEvent.SWITCH_TO_USER_FROM_USER_SWITCHER)
@@ -191,6 +200,7 @@
             userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
 
             underTest.onRecordSelected(UserRecord(info = userInfos.last()))
+            runCurrent()
 
             verify(uiEventLogger, times(1))
                 .log(MultiUserActionsEvent.SWITCH_TO_GUEST_FROM_USER_SWITCHER)
@@ -218,6 +228,7 @@
             userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
 
             underTest.onRecordSelected(UserRecord(info = userInfos.last()))
+            runCurrent()
 
             verify(uiEventLogger, times(1))
                 .log(MultiUserActionsEvent.SWITCH_TO_RESTRICTED_USER_FROM_USER_SWITCHER)
@@ -358,7 +369,7 @@
     fun actions_deviceUnlocked_fullScreen() {
         createUserInteractor()
         testScope.runTest {
-            utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
             val userInfos = createUserInfos(count = 2, includeGuest = false)
 
             userRepository.setUserInfos(userInfos)
@@ -442,7 +453,7 @@
     fun actions_deviceLockedAddFromLockscreenSet_fullList_fullScreen() {
         createUserInteractor()
         testScope.runTest {
-            utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
             userRepository.setSelectedUserInfo(userInfos[0])
@@ -635,7 +646,7 @@
 
             val refreshUsersCallCount = userRepository.refreshUsersCallCount
 
-            utils.telephonyRepository.setCallState(1)
+            kosmos.fakeTelephonyRepository.setCallState(1)
             runCurrent()
 
             assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
@@ -787,7 +798,7 @@
     fun userRecordsFullScreen() {
         createUserInteractor()
         testScope.runTest {
-            utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
             val userInfos = createUserInfos(count = 3, includeGuest = false)
             userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
             userRepository.setUserInfos(userInfos)
@@ -896,7 +907,7 @@
     fun showUserSwitcher_fullScreenEnabled_launchesFullScreenDialog() {
         createUserInteractor()
         testScope.runTest {
-            utils.featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+            kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
 
             val expandable = mock<Expandable>()
             underTest.showUserSwitcher(expandable)
@@ -1111,19 +1122,19 @@
                 manager = manager,
                 headlessSystemUserMode = headlessSystemUserMode,
                 applicationScope = testScope.backgroundScope,
-                telephonyInteractor = utils.telephonyInteractor(),
+                telephonyInteractor = kosmos.telephonyInteractor,
                 broadcastDispatcher = fakeBroadcastDispatcher,
                 keyguardUpdateMonitor = keyguardUpdateMonitor,
-                backgroundDispatcher = utils.testDispatcher,
-                mainDispatcher = utils.testDispatcher,
+                backgroundDispatcher = kosmos.testDispatcher,
+                mainDispatcher = kosmos.testDispatcher,
                 activityManager = activityManager,
                 refreshUsersScheduler = refreshUsersScheduler,
                 guestUserInteractor =
                     GuestUserInteractor(
                         applicationContext = spyContext,
                         applicationScope = testScope.backgroundScope,
-                        mainDispatcher = utils.testDispatcher,
-                        backgroundDispatcher = utils.testDispatcher,
+                        mainDispatcher = kosmos.testDispatcher,
+                        backgroundDispatcher = kosmos.testDispatcher,
                         manager = manager,
                         repository = userRepository,
                         deviceProvisionedController = deviceProvisionedController,
@@ -1134,7 +1145,7 @@
                         resetOrExitSessionReceiver = resetOrExitSessionReceiver,
                     ),
                 uiEventLogger = uiEventLogger,
-                featureFlags = utils.featureFlags,
+                featureFlags = kosmos.fakeFeatureFlagsClassic,
                 userRestrictionChecker = mock(),
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 8c823b2..7a8dce8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -32,6 +32,7 @@
 
 import static org.junit.Assume.assumeNotNull;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -63,15 +64,16 @@
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.keyguard.TestScopeProvider;
 import com.android.systemui.Prefs;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.AnimatorTestRule;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
-import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.plugins.VolumeDialogController.State;
 import com.android.systemui.res.R;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DevicePostureController;
@@ -79,6 +81,9 @@
 import com.android.systemui.statusbar.policy.FakeConfigurationController;
 import com.android.systemui.util.settings.FakeSettings;
 import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
+import com.android.systemui.volume.ui.navigation.VolumeNavigator;
 
 import dagger.Lazy;
 
@@ -97,6 +102,8 @@
 import java.util.Arrays;
 import java.util.function.Predicate;
 
+import kotlinx.coroutines.Dispatchers;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -126,10 +133,6 @@
     @Mock
     MediaOutputDialogFactory mMediaOutputDialogFactory;
     @Mock
-    VolumePanelFactory mVolumePanelFactory;
-    @Mock
-    ActivityStarter mActivityStarter;
-    @Mock
     InteractionJankMonitor mInteractionJankMonitor;
     @Mock
     private DumpManager mDumpManager;
@@ -138,6 +141,10 @@
     DevicePostureController mPostureController;
     @Mock
     private Lazy<SecureSettings> mLazySecureSettings;
+    @Mock
+    private VolumePanelNavigationInteractor mVolumePanelNavigationInteractor;
+    @Mock
+    private VolumeNavigator mVolumeNavigator;
 
     private final CsdWarningDialog.Factory mCsdWarningDialogFactory =
             new CsdWarningDialog.Factory() {
@@ -146,6 +153,8 @@
             return mCsdWarningDialog;
         }
     };
+    @Mock
+    private VibratorHelper mVibratorHelper;
 
     private int mLongestHideShowAnimationDuration = 250;
     private FakeSettings mSecureSettings;
@@ -180,6 +189,8 @@
 
         when(mLazySecureSettings.get()).thenReturn(mSecureSettings);
 
+        when(mVibratorHelper.getPrimitiveDurations(anyInt())).thenReturn(new int[]{0});
+
         mDialog = new VolumeDialogImpl(
                 getContext(),
                 mVolumeDialogController,
@@ -187,15 +198,19 @@
                 mDeviceProvisionedController,
                 mConfigurationController,
                 mMediaOutputDialogFactory,
-                mVolumePanelFactory,
-                mActivityStarter,
                 mInteractionJankMonitor,
+                mVolumePanelNavigationInteractor,
+                mVolumeNavigator,
                 false,
                 mCsdWarningDialogFactory,
                 mPostureController,
                 mTestableLooper.getLooper(),
                 mDumpManager,
-                mLazySecureSettings);
+                mLazySecureSettings,
+                mVibratorHelper,
+                Dispatchers.getUnconfined(),
+                TestScopeProvider.getTestScope(),
+                new FakeSystemClock());
         mDialog.init(0, null);
         State state = createShellState();
         mDialog.onStateChangedH(state);
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 814ea19..7eba3f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -99,6 +99,7 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
@@ -111,14 +112,15 @@
 import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository;
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions;
 import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.scene.FakeWindowRootViewComponent;
-import com.android.systemui.scene.SceneTestUtils;
 import com.android.systemui.scene.data.repository.SceneContainerRepository;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
 import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
@@ -126,6 +128,7 @@
 import com.android.systemui.scene.shared.logger.SceneLogger;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.LargeScreenHeaderHelper;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.ShadeController;
@@ -159,7 +162,6 @@
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -168,6 +170,7 @@
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
 import com.android.systemui.util.FakeEventLog;
@@ -349,9 +352,11 @@
     private Display mDefaultDisplay;
     @Mock
     private SceneContainerFlags mSceneContainerFlags;
+    @Mock
+    private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
 
-    private final SceneTestUtils mUtils = new SceneTestUtils(this);
-    private final TestScope mTestScope = mUtils.getTestScope();
+    private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+    private final TestScope mTestScope = mKosmos.getTestScope();
     private ShadeInteractor mShadeInteractor;
     private ShellTaskOrganizer mShellTaskOrganizer;
     private TaskViewTransitions mTaskViewTransitions;
@@ -402,8 +407,8 @@
         FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
 
         PowerInteractor powerInteractor = new PowerInteractor(
-                mUtils.getPowerRepository(),
-                mUtils.falsingCollector(),
+                mKosmos.getPowerRepository(),
+                mKosmos.getFalsingCollector(),
                 mock(ScreenOffAnimationController.class),
                 mStatusBarStateController);
 
@@ -411,9 +416,10 @@
                 mTestScope.getBackgroundScope(),
                 new SceneContainerRepository(
                         mTestScope.getBackgroundScope(),
-                        mUtils.fakeSceneContainerConfig()),
+                        mKosmos.getFakeSceneContainerConfig()),
                 powerInteractor,
-                mock(SceneLogger.class));
+                mock(SceneLogger.class),
+                mKosmos.getDeviceUnlockedInteractor());
 
         FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags();
         KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
@@ -436,15 +442,25 @@
                         () -> keyguardInteractor,
                         () -> mFromLockscreenTransitionInteractor,
                         () -> mFromPrimaryBouncerTransitionInteractor);
+        CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
 
         mFromLockscreenTransitionInteractor = new FromLockscreenTransitionInteractor(
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 mTestScope.getBackgroundScope(),
+                mKosmos.getTestDispatcher(),
+                mKosmos.getTestDispatcher(),
                 keyguardInteractor,
                 featureFlags,
                 shadeRepository,
                 powerInteractor,
+                new GlanceableHubTransitions(
+                        mTestScope,
+                        mKosmos.getTestDispatcher(),
+                        keyguardTransitionInteractor,
+                        keyguardTransitionRepository,
+                        communalInteractor
+                ),
                 () ->
                         new InWindowLauncherUnlockAnimationInteractor(
                                 new InWindowLauncherUnlockAnimationRepository(),
@@ -459,7 +475,10 @@
                 keyguardTransitionRepository,
                 keyguardTransitionInteractor,
                 mTestScope.getBackgroundScope(),
+                mKosmos.getTestDispatcher(),
+                mKosmos.getTestDispatcher(),
                 keyguardInteractor,
+                communalInteractor,
                 featureFlags,
                 mock(KeyguardSecurityModel.class),
                 mSelectedUserInteractor,
@@ -490,7 +509,8 @@
                                         mContext,
                                         splitShadeStateController,
                                         keyguardInteractor,
-                                        deviceEntryUdfpsInteractor),
+                                        deviceEntryUdfpsInteractor,
+                                        () -> mLargeScreenHeaderHelper),
                                 shadeRepository
                         )
                 );
@@ -516,7 +536,8 @@
                 mShadeWindowLogger,
                 () -> mSelectedUserInteractor,
                 mUserTracker,
-                mSceneContainerFlags
+                mSceneContainerFlags,
+                mKosmos::getCommunalInteractor
         );
         mNotificationShadeWindowController.fetchWindowRootView();
         mNotificationShadeWindowController.attach();
@@ -527,7 +548,7 @@
         mZenModeConfig.suppressedVisualEffects = 0;
         when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
 
-        mSysUiState = new SysUiState(mDisplayTracker);
+        mSysUiState = new SysUiState(mDisplayTracker, mKosmos.getSceneContainerPlugin());
         mSysUiState.addCallback(sysUiFlags -> {
             mSysUiStateBubblesManageMenuExpanded =
                     (sysUiFlags
diff --git a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
index b820ca6..1afe56f 100644
--- a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
+++ b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
@@ -22,6 +22,7 @@
 import android.os.Looper;
 import android.os.SystemClock;
 import android.util.AndroidRuntimeException;
+import android.util.Singleton;
 import android.view.Choreographer;
 
 import com.android.internal.util.Preconditions;
@@ -67,7 +68,12 @@
 public final class AnimatorTestRule implements TestRule {
 
     private final Object mLock = new Object();
-    private final TestHandler mTestHandler = new TestHandler();
+    private final Singleton<TestHandler> mTestHandler = new Singleton<>() {
+        @Override
+        protected TestHandler create() {
+            return new TestHandler();
+        }
+    };
     private final long mStartTime;
     private long mTotalTimeDelta = 0;
 
@@ -95,16 +101,17 @@
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
-                AnimationHandler objAtStart = AnimationHandler.setTestHandler(mTestHandler);
+                final TestHandler testHandler = mTestHandler.get();
+                AnimationHandler objAtStart = AnimationHandler.setTestHandler(testHandler);
                 try {
                     base.evaluate();
                 } finally {
                     AnimationHandler objAtEnd = AnimationHandler.setTestHandler(objAtStart);
-                    if (mTestHandler != objAtEnd) {
+                    if (testHandler != objAtEnd) {
                         // pass or fail, inner logic not restoring the handler needs to be reported.
                         // noinspection ThrowFromFinallyBlock
                         throw new IllegalStateException("Test handler was altered: expected="
-                                + mTestHandler + " actual=" + objAtEnd);
+                                + testHandler + " actual=" + objAtEnd);
                     }
                 }
             }
@@ -125,8 +132,9 @@
     public void initNewAnimators() {
         requireLooper("AnimationTestRule#initNewAnimators()");
         long currentTime = getCurrentTime();
-        List<AnimationFrameCallback> newCallbacks = new ArrayList<>(mTestHandler.mNewCallbacks);
-        mTestHandler.mNewCallbacks.clear();
+        final TestHandler testHandler = mTestHandler.get();
+        List<AnimationFrameCallback> newCallbacks = new ArrayList<>(testHandler.mNewCallbacks);
+        testHandler.mNewCallbacks.clear();
         for (AnimationFrameCallback newCallback : newCallbacks) {
             newCallback.doAnimationFrame(currentTime);
         }
@@ -158,9 +166,10 @@
     public void advanceTimeBy(long timeDelta, @Nullable Consumer<Long> preFrameAction) {
         Preconditions.checkArgumentNonnegative(timeDelta, "timeDelta must not be negative");
         requireLooper("AnimationTestRule#advanceTimeBy(long)");
+        final TestHandler testHandler = mTestHandler.get();
         if (timeDelta == 0) {
             // If time is not being advanced, all animators will get a tick; don't double tick these
-            mTestHandler.mNewCallbacks.clear();
+            testHandler.mNewCallbacks.clear();
         } else {
             // before advancing time, start new animators with the current time
             initNewAnimators();
@@ -172,10 +181,10 @@
         if (preFrameAction != null) {
             preFrameAction.accept(timeDelta);
             // After letting other code run, clear any new callbacks to avoid double-ticking them
-            mTestHandler.mNewCallbacks.clear();
+            testHandler.mNewCallbacks.clear();
         }
         // produce a frame
-        mTestHandler.doFrame();
+        testHandler.doFrame();
     }
 
     /**
diff --git a/packages/SystemUI/tests/utils/src/android/app/KeyguardManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/app/KeyguardManagerKosmos.kt
new file mode 100644
index 0000000..e5121d5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/app/KeyguardManagerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.keyguardManager by Kosmos.Fixture { mock<KeyguardManager>() }
diff --git a/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
index f96c508..5e254bf 100644
--- a/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
@@ -16,9 +16,7 @@
 
 package android.content
 
-import com.android.systemui.SysuiTestableContext
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testCase
 
-val Kosmos.testableContext: SysuiTestableContext by Kosmos.Fixture { testCase.context }
-var Kosmos.applicationContext: Context by Kosmos.Fixture { testableContext }
+var Kosmos.applicationContext: Context by Kosmos.Fixture { testCase.context }
diff --git a/packages/SystemUI/tests/utils/src/android/os/HandlerKosmos.kt b/packages/SystemUI/tests/utils/src/android/os/HandlerKosmos.kt
new file mode 100644
index 0000000..4e2683b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/os/HandlerKosmos.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 android.os
+
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.concurrency.mockExecutorHandler
+
+val Kosmos.fakeExecutorHandler by Kosmos.Fixture { mockExecutorHandler(fakeExecutor) }
diff --git a/packages/SystemUI/tests/utils/src/android/service/dream/DreamManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/service/dream/DreamManagerKosmos.kt
new file mode 100644
index 0000000..fb51f0f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/service/dream/DreamManagerKosmos.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 android.service.dream
+
+import android.service.dreams.IDreamManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.dreamManager by Kosmos.Fixture { mock<IDreamManager>() }
diff --git a/packages/SystemUI/tests/utils/src/android/service/notification/NotificationListenerServiceKosmos.kt b/packages/SystemUI/tests/utils/src/android/service/notification/NotificationListenerServiceKosmos.kt
new file mode 100644
index 0000000..bff0d0e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/service/notification/NotificationListenerServiceKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.notification
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.notificationListenerService by Fixture { mockNotificationListenerService }
+val Kosmos.mockNotificationListenerService by Fixture { mock<NotificationListenerService>() }
diff --git a/packages/SystemUI/tests/utils/src/android/telephony/TelephonyManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/telephony/TelephonyManagerKosmos.kt
new file mode 100644
index 0000000..a4ee702
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/telephony/TelephonyManagerKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+
+val Kosmos.telephonyManager by Fixture {
+    mock<TelephonyManager> {
+        whenever(createForSubscriptionId(anyInt())).thenReturn(this)
+        whenever(supplyIccLockPin(anyString()))
+            .thenReturn(PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 3))
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
new file mode 100644
index 0000000..2a05598
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.Mockito.mock
+
+val Kosmos.windowManager by Kosmos.Fixture<WindowManager> { mock(WindowManager::class.java) }
diff --git a/packages/SystemUI/tests/utils/src/android/view/accessibility/AccessibilityManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/view/accessibility/AccessibilityManagerKosmos.kt
index a11bf6a..d095f42 100644
--- a/packages/SystemUI/tests/utils/src/android/view/accessibility/AccessibilityManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/view/accessibility/AccessibilityManagerKosmos.kt
@@ -18,6 +18,11 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
 import com.android.systemui.util.mockito.mock
 
 var Kosmos.accessibilityManager by Fixture { mock<AccessibilityManager>() }
+
+var Kosmos.accessibilityManagerWrapper by Fixture {
+    AccessibilityManagerWrapper(accessibilityManager)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/app/ActivityTaskManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/app/ActivityTaskManagerKosmos.kt
new file mode 100644
index 0000000..fa3e8f9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/app/ActivityTaskManagerKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.app
+
+import android.app.ActivityTaskManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.activityTaskManager by Fixture { mock<ActivityTaskManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/internal/logging/MetricsLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/internal/logging/MetricsLoggerKosmos.kt
index a1815c5..22dff0a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/internal/logging/MetricsLoggerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/internal/logging/MetricsLoggerKosmos.kt
@@ -16,8 +16,12 @@
 
 package com.android.internal.logging
 
+import com.android.internal.logging.testing.FakeMetricsLogger
+import com.android.internal.util.LatencyTracker
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.metricsLogger by Fixture { mock<MetricsLogger>() }
+val Kosmos.fakeMetricsLogger by Fixture { FakeMetricsLogger() }
+val Kosmos.metricsLogger by Fixture<MetricsLogger> { fakeMetricsLogger }
+val Kosmos.latencyTracker by Fixture { mock<LatencyTracker>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/internal/util/EmergencyAffordanceManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/internal/util/EmergencyAffordanceManagerKosmos.kt
new file mode 100644
index 0000000..3133437
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/internal/util/EmergencyAffordanceManagerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.emergencyAffordanceManager by Fixture { mock<EmergencyAffordanceManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt
new file mode 100644
index 0000000..d9ea5e9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.lockPatternUtils by Kosmos.Fixture { mock<LockPatternUtils>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/ActivityIntentHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/ActivityIntentHelperKosmos.kt
new file mode 100644
index 0000000..7185b7c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/ActivityIntentHelperKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.activityIntentHelper by Kosmos.Fixture { ActivityIntentHelper(applicationContext) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/CoroutineTestScopeModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/CoroutineTestScopeModule.kt
index e24ba26..c2dc673 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/CoroutineTestScopeModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/CoroutineTestScopeModule.kt
@@ -48,6 +48,9 @@
     @get:[Provides Application]
     val appScope: CoroutineScope = scope.backgroundScope
 
+    @get:[Provides Background]
+    val bgScope: CoroutineScope = scope.backgroundScope
+
     @Module
     interface Bindings {
         @Binds @Main fun bindMainContext(dispatcher: TestDispatcher): CoroutineContext
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 d23dae9..b62b3a2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -21,17 +21,25 @@
 import static org.mockito.Mockito.when;
 
 import android.app.Instrumentation;
+import android.content.Context;
+import android.content.res.Resources;
 import android.os.Build;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.os.MessageQueue;
 import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.DisabledOnRavenwood;
 import android.platform.test.flag.junit.SetFlagsRule;
+import android.platform.test.ravenwood.RavenwoodClassRule;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.test.mock.MockContext;
 import android.testing.DexmakerShareClassLoaderRule;
 import android.testing.LeakCheck;
 import android.testing.TestWithLooperRule;
 import android.testing.TestableLooper;
 import android.util.Log;
+import android.util.Singleton;
 
 import androidx.annotation.NonNull;
 import androidx.core.animation.AndroidXAnimatorIsolationRule;
@@ -39,21 +47,29 @@
 import androidx.test.uiautomator.UiDevice;
 
 import com.android.systemui.broadcast.FakeBroadcastDispatcher;
+import com.android.systemui.flags.SceneContainerRule;
 
 import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.ClassRule;
 import org.junit.Rule;
 import org.mockito.Mockito;
 
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
 import java.util.concurrent.Future;
 
 /**
  * Base class that does System UI specific setup.
  */
+// NOTE: This @DisabledOnRavenwood annotation is inherited to all subclasses (unless overridden
+// via a more-specific @EnabledOnRavenwood annotation); this means that by default all
+// subclasses will be "ignored" when executed on the Ravenwood testing environment; more
+// background on Ravenwood is available at go/ravenwood-docs
+@DisabledOnRavenwood
 public abstract class SysuiTestCase {
 
     private static final String TAG = "SysuiTestCase";
@@ -65,16 +81,36 @@
     public AndroidXAnimatorIsolationRule mAndroidXAnimatorIsolationRule =
             new AndroidXAnimatorIsolationRule();
 
+    /**
+     * Rule that respects class-level annotations such as {@code @DisabledOnRavenwood} when tests
+     * are running on Ravenwood; on all other test environments this rule is a no-op passthrough.
+     */
+    @ClassRule(order = Integer.MIN_VALUE + 1)
+    public static final RavenwoodClassRule sRavenwood = new RavenwoodClassRule();
+
+    /**
+     * Rule that defines and prepares the Ravenwood environment when tests are running on
+     * Ravenwood; on all other test environments this rule is a no-op passthrough.
+     */
+    @Rule(order = Integer.MIN_VALUE + 1)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProcessApp()
+            .setProvideMainThread(true)
+            .build();
+
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
 
+    @Rule(order = 10)
+    public final SceneContainerRule mSceneContainerRule = new SceneContainerRule();
+
     @Rule
     public SysuiTestableContext mContext = createTestableContext();
 
     @NonNull
     private SysuiTestableContext createTestableContext() {
         SysuiTestableContext context = new SysuiTestableContext(
-                InstrumentationRegistry.getContext(), getLeakCheck());
+                getTestableContextBase(), getLeakCheck());
         if (isRobolectricTest()) {
             // Manually associate a Display to context for Robolectric test. Similar to b/214297409
             return context.createDefaultDisplayContext();
@@ -83,6 +119,43 @@
         }
     }
 
+    @NonNull
+    private Context getTestableContextBase() {
+        if (isRavenwoodTest()) {
+            // TODO(b/292141694): build out Ravenwood support for Context
+            // Ravenwood doesn't yet provide a Context, but many SysUI tests assume one exists;
+            // so here we construct just enough of a Context to be useful; this will be replaced
+            // as more of the Ravenwood environment is built out
+            return new MockContext() {
+                @Override
+                public void setTheme(int resid) {
+                    // TODO(b/318393625): build out Ravenwood support for Resources
+                    // until then, ignored as no-op
+                }
+
+                @Override
+                public Resources getResources() {
+                    // TODO(b/318393625): build out Ravenwood support for Resources
+                    return Mockito.mock(Resources.class);
+                }
+
+                private Singleton<Executor> mMainExecutor = new Singleton<>() {
+                    @Override
+                    protected Executor create() {
+                        return new HandlerExecutor(new Handler(Looper.getMainLooper()));
+                    }
+                };
+
+                @Override
+                public Executor getMainExecutor() {
+                    return mMainExecutor.get();
+                }
+            };
+        } else {
+            return InstrumentationRegistry.getContext();
+        }
+    }
+
     @Rule
     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
             new DexmakerShareClassLoaderRule();
@@ -99,17 +172,22 @@
     public void SysuiSetup() throws Exception {
         mSysuiDependency = new SysuiTestDependency(mContext, shouldFailOnLeakedReceiver());
         mDependency = mSysuiDependency.install();
-        mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
-        Instrumentation inst = spy(mRealInstrumentation);
-        when(inst.getContext()).thenAnswer(invocation -> {
-            throw new RuntimeException(
-                    "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
-        });
-        when(inst.getTargetContext()).thenAnswer(invocation -> {
-            throw new RuntimeException(
-                    "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
-        });
-        InstrumentationRegistry.registerInstance(inst, InstrumentationRegistry.getArguments());
+        // TODO(b/292141694): build out Ravenwood support for Instrumentation
+        // Ravenwood doesn't yet provide Instrumentation, so we sidestep this global configuration
+        // step; any tests that rely on it are already being excluded on Ravenwood
+        if (!isRavenwoodTest()) {
+            mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
+            Instrumentation inst = spy(mRealInstrumentation);
+            when(inst.getContext()).thenAnswer(invocation -> {
+                throw new RuntimeException(
+                        "Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
+            });
+            when(inst.getTargetContext()).thenAnswer(invocation -> {
+                throw new RuntimeException(
+                        "Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
+            });
+            InstrumentationRegistry.registerInstance(inst, InstrumentationRegistry.getArguments());
+        }
     }
 
     protected boolean shouldFailOnLeakedReceiver() {
@@ -205,7 +283,11 @@
     }
 
     public static boolean isRobolectricTest() {
-        return Build.FINGERPRINT.contains("robolectric");
+        return !isRavenwoodTest() && Build.FINGERPRINT.contains("robolectric");
+    }
+
+    public static boolean isRavenwoodTest() {
+        return RavenwoodRule.isOnRavenwood();
     }
 
     private static final void validateThread(Looper l) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt
index d89d7b0..364d3b2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt
@@ -34,10 +34,16 @@
         // is missing (constructing the actual one would throw).
         // TODO(b/219008720): Remove this.
         dependency.injectMockDependency(SystemUIDialogManager::class.java)
-        dependency.injectTestDependency(
-            DialogLaunchAnimator::class.java,
-            fakeDialogLaunchAnimator()
-        )
+
+        // TODO(b/292141694): build out Ravenwood support for UI animations
+        // Ravenwood doesn't yet provide UI animations, so we sidestep this global configuration
+        // step; any tests that rely on it are already being excluded under Ravenwood
+        if (!SysuiTestCase.isRavenwoodTest()) {
+            dependency.injectTestDependency(
+                    DialogLaunchAnimator::class.java,
+                    fakeDialogLaunchAnimator()
+            )
+        }
 
         // Many tests end up creating a BroadcastDispatcher. Instead, give them a fake that will
         // record receivers registered. They are not actually leaked as they are kept just as a weak
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index b28af46..b18859d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -18,13 +18,16 @@
 import android.app.ActivityManager
 import android.app.admin.DevicePolicyManager
 import android.os.UserManager
+import android.service.notification.NotificationListenerService
 import android.util.DisplayMetrics
 import android.view.LayoutInflater
 import com.android.internal.logging.MetricsLogger
+import com.android.internal.statusbar.IStatusBarService
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardViewController
 import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.demomode.DemoModeController
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.ScreenLifecycle
@@ -48,9 +51,11 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
 import com.android.systemui.statusbar.notification.collection.NotifCollection
+import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger
 import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.LSShadeTransitionLogger
@@ -58,6 +63,7 @@
 import com.android.systemui.statusbar.phone.ScrimController
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.ZenModeController
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
@@ -79,6 +85,7 @@
     @get:Provides val deviceProvisionedController: DeviceProvisionedController = mock(),
     @get:Provides val dozeParameters: DozeParameters = mock(),
     @get:Provides val dumpManager: DumpManager = mock(),
+    @get:Provides val headsUpManager: HeadsUpManager = mock(),
     @get:Provides val guestResumeSessionReceiver: GuestResumeSessionReceiver = mock(),
     @get:Provides val keyguardBypassController: KeyguardBypassController = mock(),
     @get:Provides val keyguardSecurityModel: KeyguardSecurityModel = mock(),
@@ -88,8 +95,10 @@
     val lockscreenShadeTransitionController: LockscreenShadeTransitionController = mock(),
     @get:Provides val mediaHierarchyManager: MediaHierarchyManager = mock(),
     @get:Provides val notifCollection: NotifCollection = mock(),
+    @get:Provides val notificationListLogger: NotificationStatsLogger = mock(),
     @get:Provides val notificationListener: NotificationListener = mock(),
     @get:Provides val notificationLockscreenUserManager: NotificationLockscreenUserManager = mock(),
+    @get:Provides val notificationPanelLogger: NotificationPanelLogger = mock(),
     @get:Provides val notificationMediaManager: NotificationMediaManager = mock(),
     @get:Provides val notificationShadeDepthController: NotificationShadeDepthController = mock(),
     @get:Provides
@@ -111,6 +120,7 @@
     @get:Provides val zenModeController: ZenModeController = mock(),
     @get:Provides val systemUIDialogManager: SystemUIDialogManager = mock(),
     @get:Provides val deviceEntryIconTransitions: Set<DeviceEntryIconTransition> = emptySet(),
+    @get:Provides val communalInteractor: CommunalInteractor = mock(),
 
     // log buffers
     @get:[Provides BroadcastDispatcherLog]
@@ -127,6 +137,10 @@
     @get:Provides val displayMetrics: DisplayMetrics = mock(),
     @get:Provides val metricsLogger: MetricsLogger = mock(),
     @get:Provides val userManager: UserManager = mock(),
+
+    // system server mocks
+    @get:Provides val mockStatusBarService: IStatusBarService = mock(),
+    @get:Provides val mockNotificationListenerService: NotificationListenerService = mock(),
 ) {
     @Module
     interface Bindings {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityQsShortcutsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityQsShortcutsRepository.kt
new file mode 100644
index 0000000..e547da1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityQsShortcutsRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+
+class FakeAccessibilityQsShortcutsRepository : AccessibilityQsShortcutsRepository {
+
+    private val targetsPerUser = mutableMapOf<Int, MutableSharedFlow<Set<String>>>()
+
+    override fun a11yQsShortcutTargets(userId: Int): SharedFlow<Set<String>> {
+        return getFlow(userId).asSharedFlow()
+    }
+
+    /**
+     * Set the a11y qs shortcut targets. In real world, the A11y QS Shortcut targets are set by the
+     * Settings app not in SysUi
+     */
+    suspend fun setA11yQsShortcutTargets(userId: Int, targets: Set<String>) {
+        getFlow(userId).emit(targets)
+    }
+
+    private fun getFlow(userId: Int): MutableSharedFlow<Set<String>> =
+        targetsPerUser.getOrPut(userId) { MutableSharedFlow(replay = 1) }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
new file mode 100644
index 0000000..128f58b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.animation
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.activityLaunchAnimator by Kosmos.Fixture { ActivityLaunchAnimator() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/assist/AssistManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/assist/AssistManagerKosmos.kt
new file mode 100644
index 0000000..b7d6f3a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/assist/AssistManagerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.assist
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.assistManager by Kosmos.Fixture { mock<AssistManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt
index 7c8a7c8..2bd104d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt
@@ -19,12 +19,14 @@
 import com.android.systemui.authentication.data.repository.authenticationRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.user.domain.interactor.selectedUserInteractor
 
 val Kosmos.authenticationInteractor by
     Kosmos.Fixture {
         AuthenticationInteractor(
             applicationScope = applicationCoroutineScope,
+            backgroundDispatcher = testDispatcher,
             repository = authenticationRepository,
             selectedUserInteractor = selectedUserInteractor,
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FakeFingerprintInteractiveToAuthProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FakeFingerprintInteractiveToAuthProvider.kt
new file mode 100644
index 0000000..8fcb60c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FakeFingerprintInteractiveToAuthProvider.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.hardware.biometrics.common.AuthenticateReason
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeFingerprintInteractiveToAuthProvider : FingerprintInteractiveToAuthProvider {
+    override val enabledForCurrentUser: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+    private val userIdToExtension = mutableMapOf<Int, AuthenticateReason.Vendor>()
+    override fun getVendorExtension(userId: Int): AuthenticateReason.Vendor? =
+        userIdToExtension[userId]
+
+    fun setVendorExtension(userId: Int, extension: AuthenticateReason.Vendor) {
+        userIdToExtension[userId] = extension
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProviderKosmos.kt
new file mode 100644
index 0000000..57dc37e3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProviderKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fingerprintInteractiveToAuthProvider by
+    Kosmos.Fixture { fakeFingerprintInteractiveToAuthProvider }
+
+val Kosmos.fakeFingerprintInteractiveToAuthProvider by
+    Kosmos.Fixture { FakeFingerprintInteractiveToAuthProvider() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/UdfpsUtilsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/UdfpsUtilsKosmos.kt
new file mode 100644
index 0000000..4849fec
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/UdfpsUtilsKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.udfpsUtils by Kosmos.Fixture { mock<UdfpsUtils>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt
new file mode 100644
index 0000000..961022f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.biometrics.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.biometricStatusRepository by Fixture { FakeBiometricStatusRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
index 42ec8fed..f192de2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -26,12 +26,24 @@
     private val _isConfirmationRequired = MutableStateFlow(false)
     override val isConfirmationRequired = _isConfirmationRequired.asStateFlow()
 
+    private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null)
+    override val opPackageName = _opPackageName.asStateFlow()
+
     override fun setPrompt(
         promptInfo: PromptInfo,
         userId: Int,
         gatekeeperChallenge: Long?,
         kind: PromptKind,
-    ) = setPrompt(promptInfo, userId, gatekeeperChallenge, kind, forceConfirmation = false)
+        opPackageName: String,
+    ) =
+        setPrompt(
+            promptInfo,
+            userId,
+            gatekeeperChallenge,
+            kind,
+            forceConfirmation = false,
+            opPackageName = opPackageName
+        )
 
     fun setPrompt(
         promptInfo: PromptInfo,
@@ -39,12 +51,14 @@
         gatekeeperChallenge: Long?,
         kind: PromptKind,
         forceConfirmation: Boolean = false,
+        opPackageName: String? = null,
     ) {
         _promptInfo.value = promptInfo
         _userId.value = userId
         _challenge.value = gatekeeperChallenge
         _kind.value = kind
         _isConfirmationRequired.value = promptInfo.isConfirmationRequested || forceConfirmation
+        _opPackageName.value = opPackageName
     }
 
     override fun unsetPrompt() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt
index 8702e00..b5515c4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt
@@ -19,4 +19,6 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 
-val Kosmos.fingerprintPropertyRepository by Fixture { FakeFingerprintPropertyRepository() }
+val Kosmos.fingerprintPropertyRepository by Fixture { fakeFingerprintPropertyRepository }
+
+val Kosmos.fakeFingerprintPropertyRepository by Fixture { FakeFingerprintPropertyRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/PromptRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/PromptRepositoryKosmos.kt
new file mode 100644
index 0000000..31fa5d2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/PromptRepositoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+var Kosmos.promptRepository by Fixture { FakePromptRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorKosmos.kt
new file mode 100644
index 0000000..1493f14
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import com.android.app.activityTaskManager
+import com.android.systemui.biometrics.data.repository.biometricStatusRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.biometricStatusInteractor by Fixture {
+    BiometricStatusInteractorImpl(
+        activityTaskManager = activityTaskManager,
+        biometricStatusRepository = biometricStatusRepository
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorKosmos.kt
new file mode 100644
index 0000000..4a089d3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorKosmos.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import android.content.applicationContext
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.displayStateRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.util.mockito.mock
+import java.util.concurrent.Executor
+
+val Kosmos.displayStateInteractor by Fixture {
+    DisplayStateInteractorImpl(
+        applicationScope = applicationCoroutineScope,
+        context = applicationContext,
+        mainExecutor = mock<Executor>(),
+        displayStateRepository = displayStateRepository,
+        displayRepository = displayRepository,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorKosmos.kt
new file mode 100644
index 0000000..e262066
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import android.content.applicationContext
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.fingerprintPropertyInteractor by Fixture {
+    FingerprintPropertyInteractor(
+        context = applicationContext,
+        repository = fingerprintPropertyRepository,
+        configurationInteractor = configurationInteractor,
+        displayStateInteractor = displayStateInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt
new file mode 100644
index 0000000..7f9a71c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import com.android.internal.widget.lockPatternUtils
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.data.repository.promptRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.promptSelectorInteractor by Fixture {
+    PromptSelectorInteractorImpl(
+        fingerprintPropertyRepository = fingerprintPropertyRepository,
+        promptRepository = promptRepository,
+        lockPatternUtils = lockPatternUtils
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorKosmos.kt
new file mode 100644
index 0000000..979a49b7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import android.content.applicationContext
+import android.view.windowManager
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.fingerprintInteractiveToAuthProvider
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.SideFpsLogger
+import java.util.Optional
+import org.mockito.Mockito.mock
+
+val Kosmos.sideFpsSensorInteractor by
+    Kosmos.Fixture {
+        SideFpsSensorInteractor(
+            applicationContext,
+            fingerprintPropertyRepository,
+            windowManager,
+            displayStateInteractor,
+            Optional.of(fingerprintInteractiveToAuthProvider),
+            biometricSettingsRepository,
+            keyguardTransitionInteractor,
+            mock(SideFpsLogger::class.java),
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt
new file mode 100644
index 0000000..9cbe633
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.promptSelectorInteractor
+import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor
+import com.android.systemui.biometrics.udfpsUtils
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.promptViewModel by Fixture {
+    PromptViewModel(
+        displayStateInteractor = displayStateInteractor,
+        promptSelectorInteractor = promptSelectorInteractor,
+        context = applicationContext,
+        udfpsOverlayInteractor = udfpsOverlayInteractor,
+        udfpsUtils = udfpsUtils
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryKosmos.kt
new file mode 100644
index 0000000..c0f8638
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.data.repository
+
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.bouncerRepository by Fixture {
+    BouncerRepository(
+        flags = featureFlagsClassic,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryKosmos.kt
new file mode 100644
index 0000000..8851709
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.data.repository
+
+import android.content.res.mainResources
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testScope
+
+val Kosmos.emergencyServicesRepository by Fixture {
+    EmergencyServicesRepository(
+        applicationScope = testScope.backgroundScope,
+        resources = mainResources,
+        configurationRepository = configurationRepository,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
index ff5179a..8010261 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
@@ -2,6 +2,7 @@
 
 import com.android.systemui.biometrics.shared.SideFpsControllerRefactor
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.bouncer.shared.model.BouncerDismissActionModel
 import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
 import com.android.systemui.dagger.SysUISingleton
 import dagger.Binds
@@ -53,6 +54,7 @@
     override val alternateBouncerUIAvailable = _isAlternateBouncerUIAvailable.asStateFlow()
     private val _sideFpsShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
     override val sideFpsShowing: StateFlow<Boolean> = _sideFpsShowing.asStateFlow()
+    override var bouncerDismissActionModel: BouncerDismissActionModel? = null
 
     override fun setPrimaryScrimmed(isScrimmed: Boolean) {
         _primaryBouncerScrimmed.value = isScrimmed
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryKosmos.kt
new file mode 100644
index 0000000..7af39df
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.fakeSimBouncerRepository by Fixture { FakeSimBouncerRepository() }
+
+val Kosmos.simBouncerRepository by Fixture<SimBouncerRepository> { fakeSimBouncerRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
index 86a4509..c4fc30d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.plugins.statusbar.statusBarStateController
 import com.android.systemui.statusbar.policy.KeyguardStateControllerImpl
 import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.time.fakeSystemClock
+import com.android.systemui.util.time.systemClock
 
 var Kosmos.alternateBouncerInteractor by
     Kosmos.Fixture {
@@ -35,7 +35,7 @@
             bouncerRepository = keyguardBouncerRepository,
             fingerprintPropertyRepository = fingerprintPropertyRepository,
             biometricSettingsRepository = biometricSettingsRepository,
-            systemClock = fakeSystemClock,
+            systemClock = systemClock,
             keyguardUpdateMonitor = keyguardUpdateMonitor,
             scope = testScope.backgroundScope,
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt
new file mode 100644
index 0000000..5ced578
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.domain.interactor
+
+import android.content.Intent
+import android.content.applicationContext
+import com.android.app.activityTaskManager
+import com.android.internal.logging.metricsLogger
+import com.android.internal.util.emergencyAffordanceManager
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.bouncer.data.repository.emergencyServicesRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
+import com.android.systemui.telephony.domain.interactor.telephonyInteractor
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+import com.android.systemui.util.mockito.mock
+import com.android.telecom.telecomManager
+
+val Kosmos.bouncerActionButtonInteractor by Fixture {
+    BouncerActionButtonInteractor(
+        applicationContext = applicationContext,
+        backgroundDispatcher = testDispatcher,
+        repository = emergencyServicesRepository,
+        mobileConnectionsRepository = mobileConnectionsRepository,
+        telephonyInteractor = telephonyInteractor,
+        authenticationInteractor = authenticationInteractor,
+        selectedUserInteractor = selectedUserInteractor,
+        activityTaskManager = activityTaskManager,
+        telecomManager = telecomManager,
+        emergencyAffordanceManager = emergencyAffordanceManager,
+        emergencyDialerIntentFactory =
+            object : EmergencyDialerIntentFactory {
+                override fun invoke(): Intent = Intent()
+            },
+        metricsLogger = metricsLogger,
+        dozeLogger = mock(),
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
new file mode 100644
index 0000000..27803b2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.domain.interactor
+
+import android.content.applicationContext
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.bouncer.data.repository.bouncerRepository
+import com.android.systemui.classifier.domain.interactor.falsingInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+
+val Kosmos.bouncerInteractor by Fixture {
+    BouncerInteractor(
+        applicationScope = testScope.backgroundScope,
+        applicationContext = applicationContext,
+        repository = bouncerRepository,
+        authenticationInteractor = authenticationInteractor,
+        deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
+        falsingInteractor = falsingInteractor,
+        powerInteractor = powerInteractor,
+        simBouncerInteractor = simBouncerInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorKosmos.kt
new file mode 100644
index 0000000..8ed9f45
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorKosmos.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.domain.interactor
+
+import android.content.Context
+import android.content.applicationContext
+import android.content.res.mainResources
+import android.telephony.euicc.EuiccManager
+import android.telephony.telephonyManager
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.bouncer.data.repository.simBouncerRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
+
+val Kosmos.simBouncerInteractor by Fixture {
+    SimBouncerInteractor(
+        applicationContext = applicationContext,
+        backgroundDispatcher = testDispatcher,
+        applicationScope = testScope.backgroundScope,
+        repository = simBouncerRepository,
+        telephonyManager = telephonyManager,
+        resources = mainResources,
+        keyguardUpdateMonitor = keyguardUpdateMonitor,
+        euiccManager = applicationContext.getSystemService(Context.EUICC_SERVICE) as EuiccManager,
+        mobileConnectionsRepository = mobileConnectionsRepository,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
new file mode 100644
index 0000000..d91c597
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.systemClock
+
+val Kosmos.bouncerViewModel by Fixture {
+    BouncerViewModel(
+        applicationContext = applicationContext,
+        applicationScope = testScope.backgroundScope,
+        mainDispatcher = testDispatcher,
+        bouncerInteractor = bouncerInteractor,
+        simBouncerInteractor = simBouncerInteractor,
+        authenticationInteractor = authenticationInteractor,
+        flags = sceneContainerFlags,
+        selectedUser = userSwitcherViewModel.selectedUser,
+        users = userSwitcherViewModel.users,
+        userSwitcherMenu = userSwitcherViewModel.menu,
+        actionButton = bouncerActionButtonInteractor.actionButton,
+        clock = systemClock,
+        devicePolicyManager = mock(),
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/domain/interactor/FalsingInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/domain/interactor/FalsingInteractorKosmos.kt
new file mode 100644
index 0000000..8fee5b2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/domain/interactor/FalsingInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.classifier.domain.interactor
+
+import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.falsingInteractor by Fixture {
+    FalsingInteractor(
+        collector = falsingCollector,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryKosmos.kt
new file mode 100644
index 0000000..0768106
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeCommunalMediaRepository by Kosmos.Fixture { FakeCommunalMediaRepository() }
+
+val Kosmos.communalMediaRepository by
+    Kosmos.Fixture<CommunalMediaRepository> { fakeCommunalMediaRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.kt
new file mode 100644
index 0000000..79107cc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.communal.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.communalPrefsRepository: CommunalPrefsRepository by
+    Kosmos.Fixture { fakeCommunalPrefsRepository }
+val Kosmos.fakeCommunalPrefsRepository by Kosmos.Fixture { FakeCommunalPrefsRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt
new file mode 100644
index 0000000..482d60c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+
+val Kosmos.fakeCommunalRepository by Fixture {
+    FakeCommunalRepository(applicationScope = applicationCoroutineScope)
+}
+
+val Kosmos.communalRepository by Fixture<CommunalRepository> { fakeCommunalRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryKosmos.kt
new file mode 100644
index 0000000..f7665fb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.communalTutorialRepository by
+    Kosmos.Fixture<CommunalTutorialRepository> { fakeCommunalTutorialRepository }
+val Kosmos.fakeCommunalTutorialRepository by Kosmos.Fixture { FakeCommunalTutorialRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryKosmos.kt
new file mode 100644
index 0000000..c225e3c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+
+val Kosmos.fakeCommunalWidgetRepository by Fixture {
+    FakeCommunalWidgetRepository(
+        coroutineScope = applicationCoroutineScope,
+    )
+}
+
+val Kosmos.communalWidgetRepository by
+    Fixture<CommunalWidgetRepository> { fakeCommunalWidgetRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt
new file mode 100644
index 0000000..d3ed58b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.communal.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Fake implementation of [CommunalPrefsRepository] */
+class FakeCommunalPrefsRepository : CommunalPrefsRepository {
+    private val _isCtaDismissed = MutableStateFlow(false)
+    override val isCtaDismissed: Flow<Boolean> = _isCtaDismissed.asStateFlow()
+
+    override suspend fun setCtaDismissedForCurrentUser() {
+        _isCtaDismissed.value = true
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
index e82cae4..cccd908 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
@@ -2,24 +2,21 @@
 
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
-import com.android.systemui.dagger.qualifiers.Background
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.test.TestScope
 
 /** Fake implementation of [CommunalRepository]. */
 @OptIn(ExperimentalCoroutinesApi::class)
 class FakeCommunalRepository(
-    @Background applicationScope: CoroutineScope = TestScope(),
-    override var isCommunalEnabled: Boolean = false,
+    applicationScope: CoroutineScope,
+    override var isCommunalEnabled: Boolean = true,
     override val desiredScene: MutableStateFlow<CommunalSceneKey> =
         MutableStateFlow(CommunalSceneKey.DEFAULT),
 ) : CommunalRepository {
@@ -54,11 +51,10 @@
         _isCommunalHubShowing.value = isCommunalHubShowing
     }
 
-    private val _isCtaTileInViewModeVisible: MutableStateFlow<Boolean> = MutableStateFlow(true)
-    override val isCtaTileInViewModeVisible: Flow<Boolean> =
-        _isCtaTileInViewModeVisible.asStateFlow()
+    private val _communalEnabledState: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    override val communalEnabledState: StateFlow<Boolean> = _communalEnabledState
 
-    override fun setCtaTileInViewModeVisibility(isVisible: Boolean) {
-        _isCtaTileInViewModeVisible.value = isVisible
+    fun setCommunalEnabledState(enabled: Boolean) {
+        _communalEnabledState.value = enabled
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 397dc1a..bc7e7af 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -1,11 +1,11 @@
 package com.android.systemui.communal.data.repository
 
+import android.appwidget.AppWidgetProviderInfo
 import android.content.ComponentName
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.widgets.WidgetConfigurator
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.launch
 
@@ -14,8 +14,6 @@
     CommunalWidgetRepository {
     private val _communalWidgets = MutableStateFlow<List<CommunalWidgetContentModel>>(emptyList())
     override val communalWidgets: Flow<List<CommunalWidgetContentModel>> = _communalWidgets
-    private val _widgetAdded = MutableSharedFlow<Int>()
-    val widgetAdded: Flow<Int> = _widgetAdded
 
     private var nextWidgetId = 1
 
@@ -26,13 +24,19 @@
     override fun addWidget(
         provider: ComponentName,
         priority: Int,
-        configureWidget: suspend (id: Int) -> Boolean
+        configurator: WidgetConfigurator?
     ) {
         coroutineScope.launch {
             val id = nextWidgetId++
-            if (configureWidget.invoke(id)) {
-                _widgetAdded.emit(id)
+            val providerInfo = AppWidgetProviderInfo().apply { this.provider = provider }
+            val configured = configurator?.configureWidget(id) ?: true
+            if (configured) {
+                onConfigured(id, providerInfo, priority)
             }
         }
     }
+
+    private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, priority: Int) {
+        _communalWidgets.value += listOf(CommunalWidgetContentModel(id, providerInfo, priority))
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
deleted file mode 100644
index eb287ee..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.communal.domain.interactor
-
-import android.appwidget.AppWidgetHost
-import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
-import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
-import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
-import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
-import com.android.systemui.util.mockito.mock
-import kotlinx.coroutines.test.TestScope
-
-object CommunalInteractorFactory {
-
-    @JvmOverloads
-    @JvmStatic
-    fun create(
-        testScope: TestScope = TestScope(),
-        communalRepository: FakeCommunalRepository = FakeCommunalRepository(),
-        widgetRepository: FakeCommunalWidgetRepository =
-            FakeCommunalWidgetRepository(testScope.backgroundScope),
-        mediaRepository: FakeCommunalMediaRepository = FakeCommunalMediaRepository(),
-        smartspaceRepository: FakeSmartspaceRepository = FakeSmartspaceRepository(),
-        tutorialRepository: FakeCommunalTutorialRepository = FakeCommunalTutorialRepository(),
-        appWidgetHost: AppWidgetHost = mock(),
-        editWidgetsActivityStarter: EditWidgetsActivityStarter = mock(),
-    ): WithDependencies {
-        val withDeps =
-            CommunalTutorialInteractorFactory.create(
-                testScope = testScope,
-                communalTutorialRepository = tutorialRepository,
-                communalRepository = communalRepository,
-            )
-        return WithDependencies(
-            communalRepository,
-            widgetRepository,
-            mediaRepository,
-            smartspaceRepository,
-            tutorialRepository,
-            withDeps.keyguardRepository,
-            withDeps.keyguardInteractor,
-            withDeps.communalTutorialInteractor,
-            appWidgetHost,
-            editWidgetsActivityStarter,
-            CommunalInteractor(
-                communalRepository,
-                widgetRepository,
-                mediaRepository,
-                smartspaceRepository,
-                withDeps.keyguardInteractor,
-                appWidgetHost,
-                editWidgetsActivityStarter,
-            ),
-        )
-    }
-
-    data class WithDependencies(
-        val communalRepository: FakeCommunalRepository,
-        val widgetRepository: FakeCommunalWidgetRepository,
-        val mediaRepository: FakeCommunalMediaRepository,
-        val smartspaceRepository: FakeSmartspaceRepository,
-        val tutorialRepository: FakeCommunalTutorialRepository,
-        val keyguardRepository: FakeKeyguardRepository,
-        val keyguardInteractor: KeyguardInteractor,
-        val tutorialInteractor: CommunalTutorialInteractor,
-        val appWidgetHost: AppWidgetHost,
-        val editWidgetsActivityStarter: EditWidgetsActivityStarter,
-        val communalInteractor: CommunalInteractor,
-    )
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
new file mode 100644
index 0000000..c818e9c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.domain.interactor
+
+import com.android.systemui.communal.data.repository.communalMediaRepository
+import com.android.systemui.communal.data.repository.communalPrefsRepository
+import com.android.systemui.communal.data.repository.communalRepository
+import com.android.systemui.communal.data.repository.communalWidgetRepository
+import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.smartspace.data.repository.smartspaceRepository
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.communalInteractor by Fixture {
+    CommunalInteractor(
+        communalRepository = communalRepository,
+        widgetRepository = communalWidgetRepository,
+        mediaRepository = communalMediaRepository,
+        communalPrefsRepository = communalPrefsRepository,
+        smartspaceRepository = smartspaceRepository,
+        userRepository = userRepository,
+        appWidgetHost = mock(),
+        keyguardInteractor = keyguardInteractor,
+        editWidgetsActivityStarter = editWidgetsActivityStarter,
+    )
+}
+
+val Kosmos.editWidgetsActivityStarter by Fixture<EditWidgetsActivityStarter> { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorFactory.kt
deleted file mode 100644
index e5cadab..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorFactory.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.communal.domain.interactor
-
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
-import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import kotlinx.coroutines.test.TestScope
-
-object CommunalTutorialInteractorFactory {
-
-    @JvmOverloads
-    @JvmStatic
-    fun create(
-        testScope: TestScope,
-        communalTutorialRepository: FakeCommunalTutorialRepository =
-            FakeCommunalTutorialRepository(),
-        communalRepository: FakeCommunalRepository =
-            FakeCommunalRepository(isCommunalEnabled = true),
-        keyguardRepository: FakeKeyguardRepository = FakeKeyguardRepository(),
-        keyguardInteractor: KeyguardInteractor =
-            KeyguardInteractorFactory.create(
-                    repository = keyguardRepository,
-                )
-                .keyguardInteractor
-    ): WithDependencies {
-        return WithDependencies(
-            testScope = testScope,
-            communalRepository = communalRepository,
-            communalTutorialRepository = communalTutorialRepository,
-            keyguardRepository = keyguardRepository,
-            keyguardInteractor = keyguardInteractor,
-            communalTutorialInteractor =
-                CommunalTutorialInteractor(
-                    testScope.backgroundScope,
-                    communalTutorialRepository,
-                    keyguardInteractor,
-                    communalRepository,
-                )
-        )
-    }
-
-    data class WithDependencies(
-        val testScope: TestScope,
-        val communalRepository: FakeCommunalRepository,
-        val communalTutorialRepository: FakeCommunalTutorialRepository,
-        val keyguardRepository: FakeKeyguardRepository,
-        val keyguardInteractor: KeyguardInteractor,
-        val communalTutorialInteractor: CommunalTutorialInteractor,
-    )
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
new file mode 100644
index 0000000..adaea7c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.domain.interactor
+
+import com.android.systemui.communal.data.repository.communalRepository
+import com.android.systemui.communal.data.repository.communalTutorialRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+
+val Kosmos.communalTutorialInteractor by
+    Kosmos.Fixture {
+        CommunalTutorialInteractor(
+            scope = applicationCoroutineScope,
+            communalTutorialRepository = communalTutorialRepository,
+            keyguardInteractor = keyguardInteractor,
+            communalRepository = communalRepository,
+            communalInteractor = communalInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/widgets/FakeWidgetConfigurator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/widgets/FakeWidgetConfigurator.kt
new file mode 100644
index 0000000..662303e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/widgets/FakeWidgetConfigurator.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import kotlinx.coroutines.CompletableDeferred
+
+class FakeWidgetConfigurator(private val immediateResult: Boolean? = null) : WidgetConfigurator {
+    private val result: CompletableDeferred<Boolean> =
+        immediateResult?.let { CompletableDeferred(it) } ?: CompletableDeferred()
+
+    override suspend fun configureWidget(appWidgetId: Int): Boolean = result.await()
+
+    fun setResult(success: Boolean) {
+        result.complete(success)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/widgets/WidgetConfiguratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/widgets/WidgetConfiguratorKosmos.kt
new file mode 100644
index 0000000..7bb86af
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/widgets/WidgetConfiguratorKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import com.android.systemui.kosmos.Kosmos
+
+/** A fake configurator which always successfully configures */
+val Kosmos.widgetConfiguratorSuccess: WidgetConfigurator by
+    Kosmos.Fixture { FakeWidgetConfigurator(true) }
+
+/** A fake configurator which always fails to configures */
+val Kosmos.widgetConfiguratorFail: WidgetConfigurator by
+    Kosmos.Fixture { FakeWidgetConfigurator(false) }
+
+/** A fake configurator whose result can be set programmatically in a test */
+val Kosmos.fakeWidgetConfigurator by Kosmos.Fixture { FakeWidgetConfigurator() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
index 6436a38..77caeaa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -16,25 +16,16 @@
 package com.android.systemui.deviceentry.data.repository
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 
 /** Fake implementation of [DeviceEntryRepository] */
 @SysUISingleton
 class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository {
-    private val _enteringDeviceFromBiometricUnlock: MutableSharedFlow<BiometricUnlockSource> =
-        MutableSharedFlow()
-    override val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource> =
-        _enteringDeviceFromBiometricUnlock.asSharedFlow()
-
     private var isLockscreenEnabled = true
 
     private val _isBypassEnabled = MutableStateFlow(false)
@@ -62,10 +53,6 @@
     fun setBypassEnabled(isBypassEnabled: Boolean) {
         _isBypassEnabled.value = isBypassEnabled
     }
-
-    suspend fun enteringDeviceFromBiometricUnlock(sourceType: BiometricUnlockSource) {
-        _enteringDeviceFromBiometricUnlock.emit(sourceType)
-    }
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt
new file mode 100644
index 0000000..3070cf4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.authRippleInteractor by
+    Kosmos.Fixture {
+        AuthRippleInteractor(
+            deviceEntrySourceInteractor = deviceEntrySourceInteractor,
+            deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
index 6bf527d..878e385 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
@@ -24,20 +24,20 @@
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.power.domain.interactor.powerInteractor
-import com.android.systemui.util.time.fakeSystemClock
+import com.android.systemui.util.time.systemClock
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 val Kosmos.deviceEntryHapticsInteractor by
     Kosmos.Fixture {
         DeviceEntryHapticsInteractor(
-            deviceEntryInteractor = deviceEntryInteractor,
+            deviceEntrySourceInteractor = deviceEntrySourceInteractor,
             deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
             deviceEntryBiometricAuthInteractor = deviceEntryBiometricAuthInteractor,
             fingerprintPropertyRepository = fingerprintPropertyRepository,
             biometricSettingsRepository = biometricSettingsRepository,
             keyEventInteractor = keyEventInteractor,
             powerInteractor = powerInteractor,
-            systemClock = fakeSystemClock,
+            systemClock = systemClock,
             logger = biometricUnlockLogger,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
index b600b50..0d1a31f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
 package com.android.systemui.deviceentry.domain.interactor
 
 import com.android.systemui.authentication.domain.interactor.authenticationInteractor
@@ -28,6 +26,7 @@
 import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
+@ExperimentalCoroutinesApi
 val Kosmos.deviceEntryInteractor by
     Kosmos.Fixture {
         DeviceEntryInteractor(
@@ -38,5 +37,6 @@
             deviceEntryFaceAuthRepository = deviceEntryFaceAuthRepository,
             trustRepository = trustRepository,
             flags = sceneContainerFlags,
+            deviceUnlockedInteractor = deviceUnlockedInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt
new file mode 100644
index 0000000..0b9ec92
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.deviceEntrySourceInteractor by
+    Kosmos.Fixture {
+        DeviceEntrySourceInteractor(
+            keyguardInteractor = keyguardInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
new file mode 100644
index 0000000..df1cdc2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+
+val Kosmos.deviceUnlockedInteractor by Fixture {
+    DeviceUnlockedInteractor(
+        applicationScope = applicationCoroutineScope,
+        authenticationInteractor = authenticationInteractor,
+        deviceEntryRepository = deviceEntryRepository,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayRepositoryKosmos.kt
new file mode 100644
index 0000000..048ea3c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayRepositoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.displayRepository by Fixture { FakeDisplayRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayStateRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayStateRepositoryKosmos.kt
new file mode 100644
index 0000000..4a71a09
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayStateRepositoryKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.data.repository
+
+import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.displayStateRepository by Fixture { FakeDisplayStateRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
new file mode 100644
index 0000000..97f84c6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import android.platform.test.annotations.EnableFlags
+import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
+import com.android.systemui.Flags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL
+import com.android.systemui.Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
+
+/**
+ * This includes @[EnableFlags] to work with [SetFlagsRule] to enable all aconfig flags required by
+ * that feature. It is also picked up by [SceneContainerRule] to set non-aconfig prerequisites.
+ */
+@EnableFlags(
+    FLAG_SCENE_CONTAINER,
+    FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
+    FLAG_KEYGUARD_SHADE_MIGRATION_NSSL,
+    FLAG_MEDIA_IN_SCENE_CONTAINER,
+)
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
+annotation class EnableSceneContainer
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
index abadaf7..7b36a29 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
@@ -30,7 +30,13 @@
  * Fixture supplying a shared [FakeFeatureFlagsClassic] instance. Can be accessed in tests in order
  * to override flag values.
  */
-val Kosmos.fakeFeatureFlagsClassic by Kosmos.Fixture { FakeFeatureFlagsClassic() }
+val Kosmos.fakeFeatureFlagsClassic by
+    Kosmos.Fixture {
+        FakeFeatureFlagsClassic().apply {
+            set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+            set(Flags.NSSL_DEBUG_LINES, false)
+        }
+    }
 
 /**
  * Fixture supplying a real [FeatureFlagsClassicRelease] instance, for use by tests that want to
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt
new file mode 100644
index 0000000..3faa6eb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import android.util.Log
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import org.junit.Assert
+import org.junit.Assume
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Should always be used with [SetFlagsRule] and should be ordered after it.
+ *
+ * Used to ensure tests annotated with [EnableSceneContainer] can actually get `true` from
+ * [SceneContainerFlag.isEnabled].
+ */
+class SceneContainerRule : TestRule {
+    override fun apply(base: Statement?, description: Description?): Statement {
+        return object : Statement() {
+            @Throws(Throwable::class)
+            override fun evaluate() {
+                val initialEnabledValue = Flags.SCENE_CONTAINER_ENABLED
+                val hasAnnotation =
+                    description?.testClass?.getAnnotation(EnableSceneContainer::class.java) !=
+                        null || description?.getAnnotation(EnableSceneContainer::class.java) != null
+                if (hasAnnotation) {
+                    Assume.assumeTrue(
+                        "Compose must be available for @EnableSceneContainer test",
+                        ComposeFacade.isComposeAvailable()
+                    )
+                    Assume.assumeTrue(
+                        "Couldn't set Flags.SCENE_CONTAINER_ENABLED for @EnableSceneContainer test",
+                        trySetSceneContainerEnabled(true)
+                    )
+                    Assert.assertTrue(
+                        "SceneContainerFlag.isEnabled is false:" +
+                            "\n * Did you forget to add a new aconfig flag dependency in" +
+                            " @EnableSceneContainer?" +
+                            "\n * Did you forget to use SetFlagsRule with an earlier order?",
+                        SceneContainerFlag.isEnabled
+                    )
+                }
+                try {
+                    base?.evaluate()
+                } finally {
+                    if (hasAnnotation) {
+                        trySetSceneContainerEnabled(initialEnabledValue)
+                    }
+                }
+            }
+        }
+    }
+
+    companion object {
+        fun trySetSceneContainerEnabled(enabled: Boolean): Boolean {
+            if (Flags.SCENE_CONTAINER_ENABLED == enabled) {
+                return true
+            }
+            return try {
+                // TODO(b/283300105): remove this reflection setting once the hard-coded
+                //  Flags.SCENE_CONTAINER_ENABLED is no longer needed.
+                val field = Flags::class.java.getField("SCENE_CONTAINER_ENABLED")
+                field.isAccessible = true
+                field.set(null, enabled) // note: this does not work with multivalent tests
+                true
+            } catch (t: Throwable) {
+                Log.e("SceneContainerRule", "Unable to set SCENE_CONTAINER_ENABLED=$enabled", t)
+                false
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt
new file mode 100644
index 0000000..5c5016d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.jank
+
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.interactionJankMonitor by Fixture<InteractionJankMonitor> { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeStickyKeysRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeStickyKeysRepository.kt
new file mode 100644
index 0000000..68e1457
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeStickyKeysRepository.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.data.repository
+
+import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepository
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeStickyKeysRepository : StickyKeysRepository {
+    override val settingEnabled: Flow<Boolean> = MutableStateFlow(true)
+    private val _stickyKeys: MutableStateFlow<LinkedHashMap<ModifierKey, Locked>> =
+        MutableStateFlow(LinkedHashMap())
+
+    override val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>> = _stickyKeys
+
+    fun setStickyKeys(keys: LinkedHashMap<ModifierKey, Locked>) {
+        _stickyKeys.value = keys
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryKosmos.kt
new file mode 100644
index 0000000..46f7355
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.keyboardRepository by Kosmos.Fixture { FakeKeyboardRepository() }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt
index 45d39b0..cf8f812 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt
@@ -19,4 +19,5 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 
-val Kosmos.biometricSettingsRepository by Fixture { FakeBiometricSettingsRepository() }
+val Kosmos.fakeBiometricSettingsRepository by Fixture { FakeBiometricSettingsRepository() }
+val Kosmos.biometricSettingsRepository by Fixture { fakeBiometricSettingsRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt
index 6437ef3..0d20939 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt
@@ -19,6 +19,10 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 
-val Kosmos.deviceEntryFingerprintAuthRepository by Fixture {
+val Kosmos.fakeDeviceEntryFingerprintAuthRepository by Fixture {
     FakeDeviceEntryFingerprintAuthRepository()
 }
+
+val Kosmos.deviceEntryFingerprintAuthRepository by Fixture {
+    fakeDeviceEntryFingerprintAuthRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
index 1d44929..93e0b41 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
@@ -62,6 +62,10 @@
     fun setAuthenticationStatus(status: FingerprintAuthenticationStatus) {
         _authenticationStatus.value = status
     }
+
+    fun setShouldUpdateIndicatorVisibility(shouldUpdateIndicatorVisibility: Boolean) {
+        _shouldUpdateIndicatorVisibility.value = shouldUpdateIndicatorVisibility
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 975db3b..793e2d7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -65,7 +65,7 @@
     override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
 
     private val _isKeyguardUnlocked = MutableStateFlow(false)
-    override val isKeyguardUnlocked: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow()
+    override val isKeyguardDismissible: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow()
 
     private val _isKeyguardOccluded = MutableStateFlow(false)
     override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded
@@ -127,6 +127,11 @@
 
     override val ambientIndicationVisible: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
+    private val _isEncryptedOrLockdown = MutableStateFlow(true)
+    override val isEncryptedOrLockdown: Flow<Boolean> = _isEncryptedOrLockdown
+
+    override val topClippingBounds = MutableStateFlow<Int?>(null)
+
     override fun setQuickSettingsVisible(isVisible: Boolean) {
         _isQuickSettingsVisible.value = isVisible
     }
@@ -160,7 +165,7 @@
         _isKeyguardOccluded.value = isOccluded
     }
 
-    fun setKeyguardUnlocked(isUnlocked: Boolean) {
+    fun setKeyguardDismissible(isUnlocked: Boolean) {
         _isKeyguardUnlocked.value = isUnlocked
     }
 
@@ -247,6 +252,10 @@
     override fun setKeyguardAlpha(alpha: Float) {
         _keyguardAlpha.value = alpha
     }
+
+    fun setIsEncryptedOrLockdown(value: Boolean) {
+        _isEncryptedOrLockdown.value = value
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index a94ca29..e20a0ab 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -28,9 +28,12 @@
 import java.util.UUID
 import javax.inject.Inject
 import junit.framework.Assert.fail
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.channels.BufferOverflow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.TestCoroutineScheduler
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
@@ -147,10 +150,18 @@
                 )
             }
         }
-
         _transitions.emit(step)
     }
 
+    /** Version of [sendTransitionStep] that's usable from Java tests. */
+    fun sendTransitionStepJava(
+        coroutineScope: CoroutineScope,
+        step: TransitionStep,
+        validateStep: Boolean = true
+    ): Job {
+        return coroutineScope.launch { sendTransitionStep(step, validateStep) }
+    }
+
     suspend fun sendTransitionSteps(
         steps: List<TransitionStep>,
         testScope: TestScope,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
new file mode 100644
index 0000000..19cd950
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.keyguardBlueprintRepository by
+    Kosmos.Fixture {
+        KeyguardBlueprintRepository(
+            configurationRepository = configurationRepository,
+            blueprints = setOf(defaultBlueprint),
+        )
+    }
+
+private val defaultBlueprint =
+    object : KeyguardBlueprint {
+        override val id: String
+            get() = DEFAULT
+        override val sections: List<KeyguardSection>
+            get() = listOf()
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
index b03d0b8..3b38342 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.shade.data.repository.shadeRepository
 import dagger.Lazy
@@ -30,10 +31,13 @@
             transitionRepository = keyguardTransitionRepository,
             transitionInteractor = keyguardTransitionInteractor,
             scope = applicationCoroutineScope,
+            bgDispatcher = testDispatcher,
+            mainDispatcher = testDispatcher,
             keyguardInteractor = keyguardInteractor,
             flags = featureFlagsClassic,
             shadeRepository = shadeRepository,
             powerInteractor = powerInteractor,
+            glanceableHubTransitions = glanceableHubTransitions,
             inWindowLauncherUnlockAnimationInteractor =
                 Lazy { inWindowLauncherUnlockAnimationInteractor },
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
index ade3e1a..719686e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
@@ -17,10 +17,12 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import com.android.keyguard.keyguardSecurityModel
+import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.flags.featureFlagsClassic
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.user.domain.interactor.selectedUserInteractor
 
@@ -30,7 +32,10 @@
             transitionRepository = keyguardTransitionRepository,
             transitionInteractor = keyguardTransitionInteractor,
             scope = applicationCoroutineScope,
+            bgDispatcher = testDispatcher,
+            mainDispatcher = testDispatcher,
             keyguardInteractor = keyguardInteractor,
+            communalInteractor = communalInteractor,
             flags = featureFlagsClassic,
             keyguardSecurityModel = keyguardSecurityModel,
             selectedUserInteractor = selectedUserInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
new file mode 100644
index 0000000..55885bf
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+
+val Kosmos.glanceableHubTransitions by
+    Kosmos.Fixture {
+        GlanceableHubTransitions(
+            scope = applicationCoroutineScope,
+            bgDispatcher = testDispatcher,
+            transitionRepository = keyguardTransitionRepository,
+            transitionInteractor = keyguardTransitionInteractor,
+            communalInteractor = communalInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt
new file mode 100644
index 0000000..d9a3192
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.content.applicationContext
+import com.android.systemui.keyguard.data.repository.keyguardBlueprintRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.policy.splitShadeStateController
+
+val Kosmos.keyguardBlueprintInteractor by
+    Kosmos.Fixture {
+        KeyguardBlueprintInteractor(
+            keyguardBlueprintRepository = keyguardBlueprintRepository,
+            applicationScope = applicationCoroutineScope,
+            context = applicationContext,
+            splitShadeStateController = splitShadeStateController,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
new file mode 100644
index 0000000..638a6a3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.content.applicationContext
+import android.view.accessibility.accessibilityManagerWrapper
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.qs.uiEventLogger
+
+val Kosmos.keyguardLongPressInteractor by
+    Kosmos.Fixture {
+        KeyguardLongPressInteractor(
+            appContext = applicationContext,
+            scope = applicationCoroutineScope,
+            transitionInteractor = keyguardTransitionInteractor,
+            repository = keyguardRepository,
+            logger = uiEventLogger,
+            featureFlags = featureFlagsClassic,
+            broadcastDispatcher = broadcastDispatcher,
+            accessibilityManager = accessibilityManagerWrapper,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt
index 8d6529a..dad1887 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt
@@ -19,6 +19,7 @@
 package com.android.systemui.keyguard.ui
 
 import com.android.keyguard.logging.keyguardTransitionAnimationLogger
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -27,6 +28,7 @@
 val Kosmos.keyguardTransitionAnimationFlow by Fixture {
     KeyguardTransitionAnimationFlow(
         scope = applicationCoroutineScope,
+        transitionInteractor = keyguardTransitionInteractor,
         logger = keyguardTransitionAnimationLogger,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.kt
index d9c6e4f..3ed9392 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.kt
@@ -19,7 +19,6 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +26,6 @@
 
 val Kosmos.alternateBouncerToAodTransitionViewModel by Fixture {
     AlternateBouncerToAodTransitionViewModel(
-        interactor = keyguardTransitionInteractor,
         deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
         animationFlow = keyguardTransitionAnimationFlow,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt
index e4821b0..c909dd6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt
@@ -18,7 +18,6 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -26,7 +25,6 @@
 
 val Kosmos.alternateBouncerToGoneTransitionViewModel by Fixture {
     AlternateBouncerToGoneTransitionViewModel(
-        interactor = keyguardTransitionInteractor,
         bouncerToGoneFlows = bouncerToGoneFlows,
         animationFlow = keyguardTransitionAnimationFlow,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..2d1f836
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelKosmos.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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.alternateBouncerToPrimaryBouncerTransitionViewModel by Fixture {
+    AlternateBouncerToPrimaryBouncerTransitionViewModel(
+        animationFlow = keyguardTransitionAnimationFlow,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt
index 9f0466d..b4f1218 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt
@@ -18,7 +18,6 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -28,7 +27,6 @@
 val Kosmos.alternateBouncerViewModel by Fixture {
     AlternateBouncerViewModel(
         statusBarKeyguardViewManager = statusBarKeyguardViewManager,
-        transitionInteractor = keyguardTransitionInteractor,
         animationFlow = keyguardTransitionAnimationFlow,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
index 35cfa89..a8f45b0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-val Kosmos.aodBurnInViewModel by Fixture {
+var Kosmos.aodBurnInViewModel by Fixture {
     AodBurnInViewModel(
         burnInInteractor = burnInInteractor,
         configurationInteractor = configurationInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelKosmos.kt
index 44e5426..b6f278c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelKosmos.kt
@@ -18,7 +18,6 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -26,7 +25,6 @@
 
 val Kosmos.aodToGoneTransitionViewModel by Fixture {
     AodToGoneTransitionViewModel(
-        interactor = keyguardTransitionInteractor,
         animationFlow = keyguardTransitionAnimationFlow,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
index b5a5f03..733340c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
@@ -19,7 +19,6 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +26,6 @@
 
 val Kosmos.aodToLockscreenTransitionViewModel by Fixture {
     AodToLockscreenTransitionViewModel(
-        interactor = keyguardTransitionInteractor,
         deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
         animationFlow = keyguardTransitionAnimationFlow,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelKosmos.kt
index 27ad0f0..8d066fc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelKosmos.kt
@@ -18,7 +18,6 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -26,7 +25,6 @@
 
 val Kosmos.aodToOccludedTransitionViewModel by Fixture {
     AodToOccludedTransitionViewModel(
-        interactor = keyguardTransitionInteractor,
         animationFlow = keyguardTransitionAnimationFlow,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt
index 6ffcc9a..c71c1c3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt
@@ -20,7 +20,6 @@
 
 import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
 import com.android.systemui.flags.featureFlagsClassic
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -31,7 +30,6 @@
 
 val Kosmos.bouncerToGoneFlows by Fixture {
     BouncerToGoneFlows(
-        interactor = keyguardTransitionInteractor,
         statusBarStateController = sysuiStatusBarStateController,
         primaryBouncerInteractor = primaryBouncerInteractor,
         keyguardDismissActionInteractor = mock(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt
index 4bfe4f5..4f638d0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt
@@ -18,7 +18,7 @@
 
 import android.content.applicationContext
 import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor
-import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
@@ -29,7 +29,7 @@
 val Kosmos.deviceEntryForegroundIconViewModel by Fixture {
     DeviceEntryForegroundViewModel(
         context = applicationContext,
-        configurationRepository = configurationRepository,
+        configurationInteractor = configurationInteractor,
         deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
         transitionInteractor = keyguardTransitionInteractor,
         deviceEntryIconViewModel = deviceEntryIconViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
index 5ceefde..73fd999 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntrySourceInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.burnInInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -27,6 +28,7 @@
 import com.android.systemui.scene.shared.flag.sceneContainerFlags
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 val Kosmos.fakeDeviceEntryIconViewModelTransition by Fixture { FakeDeviceEntryIconTransition() }
 
@@ -34,6 +36,7 @@
     setOf<DeviceEntryIconTransition>(fakeDeviceEntryIconViewModelTransition)
 }
 
+@ExperimentalCoroutinesApi
 val Kosmos.deviceEntryIconViewModel by Fixture {
     DeviceEntryIconViewModel(
         transitions = deviceEntryIconViewModelTransitionsMock,
@@ -46,5 +49,6 @@
         sceneContainerFlags = sceneContainerFlags,
         keyguardViewController = { statusBarKeyguardViewManager },
         deviceEntryInteractor = deviceEntryInteractor,
+        deviceEntrySourceInteractor = deviceEntrySourceInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..400a0d8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.dozingToLockscreenTransitionViewModel by Fixture {
+    DozingToLockscreenTransitionViewModel(
+        animationFlow = keyguardTransitionAnimationFlow,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..28fce77
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.glanceableHubToLockscreenTransitionViewModel by Fixture {
+    GlanceableHubToLockscreenTransitionViewModel(
+        animationFlow = keyguardTransitionAnimationFlow,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt
index 00ece14..19e4241 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt
@@ -19,7 +19,6 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +26,6 @@
 
 var Kosmos.goneToAodTransitionViewModel by Fixture {
     GoneToAodTransitionViewModel(
-        interactor = keyguardTransitionInteractor,
         deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
         animationFlow = keyguardTransitionAnimationFlow,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelKosmos.kt
index 073b34b..b267a96 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelKosmos.kt
@@ -18,7 +18,6 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -26,7 +25,6 @@
 
 val Kosmos.goneToDreamingTransitionViewModel by Fixture {
     GoneToDreamingTransitionViewModel(
-        interactor = keyguardTransitionInteractor,
         animationFlow = keyguardTransitionAnimationFlow,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
index 5ca0439..4a85909 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
 import com.android.systemui.statusbar.policy.splitShadeStateController
 
 val Kosmos.keyguardClockViewModel by
@@ -29,5 +30,6 @@
             keyguardClockInteractor = keyguardClockInteractor,
             applicationScope = applicationCoroutineScope,
             splitShadeStateController = splitShadeStateController,
+            notifsKeyguardInteractor = notificationsKeyguardInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModelKosmos.kt
new file mode 100644
index 0000000..3c9846a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModelKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.keyguardLongPressInteractor
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.keyguardLongPressViewModel by
+    Kosmos.Fixture {
+        KeyguardLongPressViewModel(
+            interactor = keyguardLongPressInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index 933f50c..d376f12 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -18,6 +18,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
@@ -33,11 +34,14 @@
         deviceEntryInteractor = deviceEntryInteractor,
         dozeParameters = dozeParameters,
         keyguardInteractor = keyguardInteractor,
+        communalInteractor = communalInteractor,
         keyguardTransitionInteractor = keyguardTransitionInteractor,
         notificationsKeyguardInteractor = notificationsKeyguardInteractor,
         aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
         screenOffAnimationController = screenOffAnimationController,
         aodBurnInViewModel = aodBurnInViewModel,
         aodAlphaViewModel = aodAlphaViewModel,
+        lockscreenToGlanceableHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel,
+        glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
new file mode 100644
index 0000000..96de4ba
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.biometrics.authController
+import com.android.systemui.keyguard.domain.interactor.keyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.lockscreenContentViewModel by
+    Kosmos.Fixture {
+        LockscreenContentViewModel(
+            clockInteractor = keyguardClockInteractor,
+            interactor = keyguardBlueprintInteractor,
+            authController = authController,
+            longPress = keyguardLongPressViewModel,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelKosmos.kt
index 7865f71..07b4cd4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelKosmos.kt
@@ -19,7 +19,6 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +26,6 @@
 
 val Kosmos.lockscreenToAodTransitionViewModel by Fixture {
     LockscreenToAodTransitionViewModel(
-        interactor = keyguardTransitionInteractor,
         deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
         shadeDependentFlows = shadeDependentFlows,
         animationFlow = keyguardTransitionAnimationFlow,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelKosmos.kt
index b9f4b71..56d5ff6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelKosmos.kt
@@ -18,7 +18,6 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -26,7 +25,6 @@
 
 val Kosmos.lockscreenToDreamingTransitionViewModel by Fixture {
     LockscreenToDreamingTransitionViewModel(
-        interactor = keyguardTransitionInteractor,
         shadeDependentFlows = shadeDependentFlows,
         animationFlow = keyguardTransitionAnimationFlow,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..9fe4ea3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.lockscreenToGlanceableHubTransitionViewModel by Fixture {
+    LockscreenToGlanceableHubTransitionViewModel(
+        animationFlow = keyguardTransitionAnimationFlow,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt
index 475aa2d..1b2337f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt
@@ -18,7 +18,6 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -26,7 +25,6 @@
 
 val Kosmos.lockscreenToGoneTransitionViewModel by Fixture {
     LockscreenToGoneTransitionViewModel(
-        interactor = keyguardTransitionInteractor,
         animationFlow = keyguardTransitionAnimationFlow,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelKosmos.kt
index 8541a4f..9953d39 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelKosmos.kt
@@ -19,7 +19,6 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.common.ui.domain.interactor.configurationInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +26,6 @@
 
 val Kosmos.lockscreenToOccludedTransitionViewModel by Fixture {
     LockscreenToOccludedTransitionViewModel(
-        interactor = keyguardTransitionInteractor,
         shadeDependentFlows = shadeDependentFlows,
         configurationInteractor = configurationInteractor,
         animationFlow = keyguardTransitionAnimationFlow,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelKosmos.kt
index 65c47fc..f094f22 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelKosmos.kt
@@ -18,7 +18,6 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -26,7 +25,6 @@
 
 val Kosmos.lockscreenToPrimaryBouncerTransitionViewModel by Fixture {
     LockscreenToPrimaryBouncerTransitionViewModel(
-        interactor = keyguardTransitionInteractor,
         shadeDependentFlows = shadeDependentFlows,
         animationFlow = keyguardTransitionAnimationFlow,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelKosmos.kt
index ddde549..b7867b6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelKosmos.kt
@@ -19,7 +19,6 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +26,6 @@
 
 val Kosmos.occludedToAodTransitionViewModel by Fixture {
     OccludedToAodTransitionViewModel(
-        interactor = keyguardTransitionInteractor,
         deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
         animationFlow = keyguardTransitionAnimationFlow,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt
index 93ecb79..e6651a4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt
@@ -20,7 +20,6 @@
 
 import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -28,7 +27,6 @@
 
 var Kosmos.occludedToLockscreenTransitionViewModel by Fixture {
     OccludedToLockscreenTransitionViewModel(
-        interactor = keyguardTransitionInteractor,
         deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
         configurationInteractor = configurationInteractor,
         animationFlow = keyguardTransitionAnimationFlow,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelKosmos.kt
index a7f29d6..8d88730 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelKosmos.kt
@@ -19,7 +19,6 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +26,6 @@
 
 val Kosmos.primaryBouncerToAodTransitionViewModel by Fixture {
     PrimaryBouncerToAodTransitionViewModel(
-        interactor = keyguardTransitionInteractor,
         deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
         animationFlow = keyguardTransitionAnimationFlow,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt
index ace6ae3..ab28d0d6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt
@@ -20,7 +20,6 @@
 
 import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
 import com.android.systemui.flags.featureFlagsClassic
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -30,7 +29,6 @@
 
 val Kosmos.primaryBouncerToGoneTransitionViewModel by Fixture {
     PrimaryBouncerToGoneTransitionViewModel(
-        interactor = keyguardTransitionInteractor,
         statusBarStateController = sysuiStatusBarStateController,
         primaryBouncerInteractor = primaryBouncerInteractor,
         keyguardDismissActionInteractor = mock(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelKosmos.kt
index 3bbabf7..8566251 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelKosmos.kt
@@ -19,7 +19,6 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +26,6 @@
 
 val Kosmos.primaryBouncerToLockscreenTransitionViewModel by Fixture {
     PrimaryBouncerToLockscreenTransitionViewModel(
-        interactor = keyguardTransitionInteractor,
         deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
         animationFlow = keyguardTransitionAnimationFlow,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
new file mode 100644
index 0000000..083de10
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.kosmos
+
+import android.content.applicationContext
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.bouncerRepository
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
+import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.jank.interactionJankMonitor
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.model.sceneContainerPlugin
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.sceneContainerConfig
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.statusbar.phone.screenOffAnimationController
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.util.time.systemClock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Helper for using [Kosmos] from Java. */
+@Deprecated("Please convert your test to Kotlin and use [Kosmos] directly.")
+class KosmosJavaAdapter(
+    testCase: SysuiTestCase,
+) {
+
+    private val kosmos = Kosmos()
+
+    val testDispatcher by lazy { kosmos.testDispatcher }
+    val testScope by lazy { kosmos.testScope }
+    val fakeFeatureFlags by lazy { kosmos.fakeFeatureFlagsClassic }
+    val fakeSceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags }
+    val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
+    val configurationInteractor by lazy { kosmos.configurationInteractor }
+    val bouncerRepository by lazy { kosmos.bouncerRepository }
+    val communalRepository by lazy { kosmos.fakeCommunalRepository }
+    val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+    val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+    val keyguardTransitionInteractor by lazy { kosmos.keyguardTransitionInteractor }
+    val powerRepository by lazy { kosmos.fakePowerRepository }
+    val clock by lazy { kosmos.systemClock }
+    val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository }
+    val simBouncerInteractor by lazy { kosmos.simBouncerInteractor }
+    val statusBarStateController by lazy { kosmos.statusBarStateController }
+    val interactionJankMonitor by lazy { kosmos.interactionJankMonitor }
+    val screenOffAnimationController by lazy { kosmos.screenOffAnimationController }
+    val fakeSceneContainerConfig by lazy { kosmos.sceneContainerConfig }
+    val sceneInteractor by lazy { kosmos.sceneInteractor }
+    val falsingCollector by lazy { kosmos.falsingCollector }
+    val powerInteractor by lazy { kosmos.powerInteractor }
+    val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
+    val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
+    val communalInteractor by lazy { kosmos.communalInteractor }
+    val sceneContainerPlugin by lazy { kosmos.sceneContainerPlugin }
+
+    init {
+        kosmos.applicationContext = testCase.context
+        kosmos.testCase = testCase
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/model/SceneContainerPluginKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/model/SceneContainerPluginKosmos.kt
new file mode 100644
index 0000000..b1027b9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/model/SceneContainerPluginKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.model
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+
+val Kosmos.sceneContainerPlugin by Fixture { SceneContainerPlugin { sceneInteractor } }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index cac2646..73b7c50 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -16,7 +16,20 @@
 
 package com.android.systemui.plugins.statusbar
 
+import com.android.systemui.jank.interactionJankMonitor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.uiEventLogger
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.StatusBarStateControllerImpl
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.statusBarStateController by Kosmos.Fixture { mock<StatusBarStateController>() }
+var Kosmos.statusBarStateController by
+    Kosmos.Fixture {
+        StatusBarStateControllerImpl(
+            uiEventLogger,
+            interactionJankMonitor,
+            mock(),
+        ) {
+            shadeInteractor
+        }
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
index 1185f2e..0307c41 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
@@ -35,14 +35,21 @@
         mutableInputs.add(Input.Intent(view, intent))
     }
 
-    override fun handle(view: View?, pendingIntent: PendingIntent) {
-        mutableInputs.add(Input.PendingIntent(view, pendingIntent))
+    override fun handle(
+        view: View?,
+        pendingIntent: PendingIntent,
+        requestLaunchingDefaultActivity: Boolean
+    ) {
+        mutableInputs.add(Input.PendingIntent(view, pendingIntent, requestLaunchingDefaultActivity))
     }
 
     sealed interface Input {
         data class Intent(val view: View?, val intent: android.content.Intent) : Input
-        data class PendingIntent(val view: View?, val pendingIntent: android.app.PendingIntent) :
-            Input
+        data class PendingIntent(
+            val view: View?,
+            val pendingIntent: android.app.PendingIntent,
+            val requestLaunchingDefaultActivity: Boolean
+        ) : Input
     }
 }
 
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
deleted file mode 100644
index 09ab655..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ /dev/null
@@ -1,423 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene
-
-import android.app.ActivityTaskManager
-import android.app.admin.DevicePolicyManager
-import android.content.Context
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.graphics.Bitmap
-import android.graphics.drawable.BitmapDrawable
-import android.telecom.TelecomManager
-import android.telephony.PinResult
-import android.telephony.PinResult.PIN_RESULT_TYPE_SUCCESS
-import android.telephony.TelephonyManager
-import android.telephony.euicc.EuiccManager
-import com.android.internal.logging.MetricsLogger
-import com.android.internal.util.EmergencyAffordanceManager
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.data.repository.AuthenticationRepository
-import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
-import com.android.systemui.bouncer.data.repository.BouncerRepository
-import com.android.systemui.bouncer.data.repository.EmergencyServicesRepository
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.bouncer.data.repository.FakeSimBouncerRepository
-import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.EmergencyDialerIntentFactory
-import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
-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.common.ui.domain.interactor.ConfigurationInteractor
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
-import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
-import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
-import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.doze.DozeLogger
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.data.repository.PowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.scene.data.repository.SceneContainerRepository
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
-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.statusbar.notification.stack.data.repository.NotificationStackAppearanceRepository
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
-import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
-import com.android.systemui.telephony.data.repository.TelephonyRepository
-import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-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 com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.currentTime
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.anyString
-
-/**
- * Utilities for creating scene container framework related repositories, interactors, and
- * view-models for tests.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-class SceneTestUtils(
-    private val context: Context,
-) {
-    constructor(test: SysuiTestCase) : this(context = test.context)
-
-    val kosmos = Kosmos()
-    val testDispatcher = kosmos.testDispatcher
-    val testScope = kosmos.testScope
-    val featureFlags =
-        FakeFeatureFlagsClassic().apply {
-            set(Flags.FULL_SCREEN_USER_SWITCHER, false)
-            set(Flags.NSSL_DEBUG_LINES, false)
-        }
-    val sceneContainerFlags = FakeSceneContainerFlags().apply { enabled = true }
-    val deviceEntryRepository: FakeDeviceEntryRepository by lazy { FakeDeviceEntryRepository() }
-    val authenticationRepository: FakeAuthenticationRepository by lazy {
-        FakeAuthenticationRepository(
-            currentTime = { testScope.currentTime },
-        )
-    }
-    val configurationRepository: FakeConfigurationRepository by lazy {
-        FakeConfigurationRepository()
-    }
-    val configurationInteractor: ConfigurationInteractor by lazy {
-        ConfigurationInteractor(configurationRepository)
-    }
-    private val emergencyServicesRepository: EmergencyServicesRepository by lazy {
-        EmergencyServicesRepository(
-            applicationScope = applicationScope(),
-            resources = context.resources,
-            configurationRepository = configurationRepository,
-        )
-    }
-    val telephonyRepository: FakeTelephonyRepository by lazy { FakeTelephonyRepository() }
-    val bouncerRepository = BouncerRepository(featureFlags)
-    val communalRepository: FakeCommunalRepository by lazy { FakeCommunalRepository() }
-    val keyguardRepository: FakeKeyguardRepository by lazy { FakeKeyguardRepository() }
-    val powerRepository: FakePowerRepository by lazy { FakePowerRepository() }
-    val simBouncerRepository: FakeSimBouncerRepository by lazy { FakeSimBouncerRepository() }
-
-    val clock: SystemClock = mock {
-        whenever(elapsedRealtime()).thenAnswer { testScope.currentTime }
-    }
-    val telephonyManager: TelephonyManager = mock {
-        whenever(createForSubscriptionId(anyInt())).thenReturn(this)
-        whenever(supplyIccLockPin(anyString())).thenReturn(PinResult(PIN_RESULT_TYPE_SUCCESS, 3))
-    }
-    val devicePolicyManager: DevicePolicyManager = mock {}
-    val mobileConnectionsRepository: FakeMobileConnectionsRepository by lazy {
-        FakeMobileConnectionsRepository(mock(), mock())
-    }
-
-    val simBouncerInteractor =
-        SimBouncerInteractor(
-            applicationContext = context,
-            backgroundDispatcher = testDispatcher,
-            applicationScope = applicationScope(),
-            repository = simBouncerRepository,
-            telephonyManager = telephonyManager,
-            resources = context.resources,
-            keyguardUpdateMonitor = mock(),
-            euiccManager = context.getSystemService(Context.EUICC_SERVICE) as EuiccManager,
-            mobileConnectionsRepository = mobileConnectionsRepository,
-        )
-
-    val userRepository: FakeUserRepository by lazy {
-        FakeUserRepository().apply {
-            val users = listOf(UserInfo(/* id=  */ 0, "name", /* flags= */ 0))
-            setUserInfos(users)
-            runBlocking { setSelectedUserInfo(users.first()) }
-        }
-    }
-
-    private val falsingCollectorFake: FalsingCollector by lazy { FalsingCollectorFake() }
-    private var falsingInteractor: FalsingInteractor? = null
-    private var powerInteractor: PowerInteractor? = null
-
-    fun fakeSceneContainerRepository(
-        containerConfig: SceneContainerConfig = fakeSceneContainerConfig(),
-    ): SceneContainerRepository {
-        return SceneContainerRepository(applicationScope(), containerConfig)
-    }
-
-    fun fakeSceneKeys(): List<SceneKey> {
-        return kosmos.sceneKeys
-    }
-
-    fun fakeSceneContainerConfig(): SceneContainerConfig {
-        return kosmos.sceneContainerConfig
-    }
-
-    @JvmOverloads
-    fun sceneInteractor(
-        repository: SceneContainerRepository = fakeSceneContainerRepository()
-    ): SceneInteractor {
-        return SceneInteractor(
-            applicationScope = applicationScope(),
-            repository = repository,
-            powerInteractor = powerInteractor(),
-            logger = mock(),
-        )
-    }
-
-    fun deviceEntryInteractor(
-        repository: DeviceEntryRepository = deviceEntryRepository,
-        authenticationInteractor: AuthenticationInteractor,
-        sceneInteractor: SceneInteractor,
-        faceAuthRepository: DeviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository(),
-        trustRepository: TrustRepository = FakeTrustRepository(),
-    ): DeviceEntryInteractor {
-        return DeviceEntryInteractor(
-            applicationScope = applicationScope(),
-            repository = repository,
-            authenticationInteractor = authenticationInteractor,
-            sceneInteractor = sceneInteractor,
-            deviceEntryFaceAuthRepository = faceAuthRepository,
-            trustRepository = trustRepository,
-            flags = FakeSceneContainerFlags(enabled = true)
-        )
-    }
-
-    fun authenticationInteractor(
-        repository: AuthenticationRepository = authenticationRepository,
-    ): AuthenticationInteractor {
-        return AuthenticationInteractor(
-            applicationScope = applicationScope(),
-            repository = repository,
-            selectedUserInteractor = selectedUserInteractor(),
-        )
-    }
-
-    fun keyguardInteractor(
-        repository: KeyguardRepository = keyguardRepository
-    ): KeyguardInteractor {
-        return KeyguardInteractor(
-            repository = repository,
-            commandQueue = FakeCommandQueue(),
-            sceneContainerFlags = sceneContainerFlags,
-            bouncerRepository = FakeKeyguardBouncerRepository(),
-            configurationInteractor = configurationInteractor,
-            shadeRepository = FakeShadeRepository(),
-            sceneInteractorProvider = { sceneInteractor() },
-            powerInteractor = PowerInteractorFactory.create().powerInteractor,
-        )
-    }
-
-    fun communalInteractor(): CommunalInteractor {
-        return CommunalInteractorFactory.create(
-                communalRepository = communalRepository,
-            )
-            .communalInteractor
-    }
-
-    fun bouncerInteractor(
-        authenticationInteractor: AuthenticationInteractor,
-        deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor = mock(),
-    ): BouncerInteractor {
-        return BouncerInteractor(
-            applicationScope = applicationScope(),
-            applicationContext = context,
-            repository = bouncerRepository,
-            authenticationInteractor = authenticationInteractor,
-            deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
-            falsingInteractor = falsingInteractor(),
-            powerInteractor = powerInteractor(),
-            simBouncerInteractor = simBouncerInteractor,
-        )
-    }
-
-    fun keyguardRootViewModel(): KeyguardRootViewModel = mock()
-
-    fun notificationsPlaceholderViewModel(): NotificationsPlaceholderViewModel {
-        return NotificationsPlaceholderViewModel(
-            interactor =
-                NotificationStackAppearanceInteractor(
-                    repository = NotificationStackAppearanceRepository(),
-                ),
-            flags = sceneContainerFlags,
-            featureFlags = featureFlags,
-        )
-    }
-
-    fun bouncerViewModel(
-        bouncerInteractor: BouncerInteractor,
-        authenticationInteractor: AuthenticationInteractor,
-        actionButtonInteractor: BouncerActionButtonInteractor = bouncerActionButtonInteractor(),
-        users: List<UserViewModel> = createUsers(),
-    ): BouncerViewModel {
-        return BouncerViewModel(
-            applicationContext = context,
-            applicationScope = applicationScope(),
-            mainDispatcher = testDispatcher,
-            bouncerInteractor = bouncerInteractor,
-            simBouncerInteractor = simBouncerInteractor,
-            authenticationInteractor = authenticationInteractor,
-            flags = sceneContainerFlags,
-            selectedUser = flowOf(users.first { it.isSelectionMarkerVisible }),
-            users = flowOf(users),
-            userSwitcherMenu = flowOf(createMenuActions()),
-            actionButton = actionButtonInteractor.actionButton,
-            clock = clock,
-            devicePolicyManager = devicePolicyManager,
-        )
-    }
-
-    fun telephonyInteractor(
-        repository: TelephonyRepository = telephonyRepository,
-    ): TelephonyInteractor {
-        return TelephonyInteractor(repository = repository)
-    }
-
-    fun falsingInteractor(collector: FalsingCollector = falsingCollector()): FalsingInteractor {
-        return falsingInteractor ?: FalsingInteractor(collector).also { falsingInteractor = it }
-    }
-
-    fun falsingCollector(): FalsingCollector {
-        return falsingCollectorFake
-    }
-
-    fun powerInteractor(
-        repository: PowerRepository = powerRepository,
-        falsingCollector: FalsingCollector = falsingCollector(),
-        screenOffAnimationController: ScreenOffAnimationController = mock(),
-        statusBarStateController: StatusBarStateController = mock(),
-    ): PowerInteractor {
-        return powerInteractor
-            ?: PowerInteractor(
-                    repository = repository,
-                    falsingCollector = falsingCollector,
-                    screenOffAnimationController = screenOffAnimationController,
-                    statusBarStateController = statusBarStateController,
-                )
-                .also { powerInteractor = it }
-    }
-
-    private fun applicationScope(): CoroutineScope {
-        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 = {},
-                    )
-                )
-            }
-        }
-    }
-
-    fun selectedUserInteractor(): SelectedUserInteractor {
-        return SelectedUserInteractor(userRepository)
-    }
-
-    fun bouncerActionButtonInteractor(
-        mobileConnectionsRepository: MobileConnectionsRepository = mock(),
-        activityTaskManager: ActivityTaskManager = mock(),
-        telecomManager: TelecomManager? = null,
-        emergencyAffordanceManager: EmergencyAffordanceManager =
-            EmergencyAffordanceManager(context),
-        emergencyDialerIntentFactory: EmergencyDialerIntentFactory =
-            object : EmergencyDialerIntentFactory {
-                override fun invoke(): Intent = Intent()
-            },
-        metricsLogger: MetricsLogger = mock(),
-        dozeLogger: DozeLogger = mock(),
-    ): BouncerActionButtonInteractor {
-        return BouncerActionButtonInteractor(
-            applicationContext = context,
-            backgroundDispatcher = testDispatcher,
-            repository = emergencyServicesRepository,
-            mobileConnectionsRepository = mobileConnectionsRepository,
-            telephonyInteractor = telephonyInteractor(),
-            authenticationInteractor = authenticationInteractor(),
-            selectedUserInteractor = selectedUserInteractor(),
-            activityTaskManager = activityTaskManager,
-            telecomManager = telecomManager,
-            emergencyAffordanceManager = emergencyAffordanceManager,
-            emergencyDialerIntentFactory = emergencyDialerIntentFactory,
-            metricsLogger = metricsLogger,
-            dozeLogger = dozeLogger,
-        )
-    }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryKosmos.kt
index 7c4e160..e19941c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryKosmos.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.scene.shared.model.sceneContainerConfig
+import com.android.systemui.scene.sceneContainerConfig
 
 val Kosmos.sceneContainerRepository by
     Kosmos.Fixture { SceneContainerRepository(applicationCoroutineScope, sceneContainerConfig) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
index 9989876..fc02375 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.scene.domain.interactor
 
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.power.domain.interactor.powerInteractor
@@ -29,5 +30,6 @@
             repository = sceneContainerRepository,
             powerInteractor = powerInteractor,
             logger = sceneLogger,
+            deviceUnlockedInteractor = deviceUnlockedInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
index c2cdbed..979d8e7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
@@ -18,4 +18,5 @@
 
 import com.android.systemui.kosmos.Kosmos
 
-var Kosmos.sceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() }
+var Kosmos.fakeSceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() }
+val Kosmos.sceneContainerFlags by Kosmos.Fixture<SceneContainerFlags> { fakeSceneContainerFlags }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneContainerConfigModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneContainerConfigModule.kt
index b4fc948..8811b8d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneContainerConfigModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneContainerConfigModule.kt
@@ -30,6 +30,7 @@
                     SceneKey.Lockscreen,
                     SceneKey.Bouncer,
                     SceneKey.Gone,
+                    SceneKey.Communal,
                 ),
             initialSceneKey = SceneKey.Lockscreen,
         ),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt
deleted file mode 100644
index f9cdc1b..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneContainerConfigKosmos.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.shared.model
-
-import com.android.systemui.kosmos.Kosmos
-
-val Kosmos.sceneContainerConfig by
-    Kosmos.Fixture { FakeSceneContainerConfigModule().sceneContainerConfig }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/LargeScreenHeaderHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/LargeScreenHeaderHelperKosmos.kt
new file mode 100644
index 0000000..d3e7574
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/LargeScreenHeaderHelperKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.largeScreenHeaderHelper by Fixture { mockLargeScreenHeaderHelper }
+val Kosmos.mockLargeScreenHeaderHelper by Fixture { mock<LargeScreenHeaderHelper>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
new file mode 100644
index 0000000..e13fa52
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.shade
+
+import android.view.WindowManager
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.windowRootViewVisibilityInteractor
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.deviceProvisionedController
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.shadeControllerSceneImpl by
+    Kosmos.Fixture {
+        ShadeControllerSceneImpl(
+            scope = applicationCoroutineScope,
+            shadeInteractor = shadeInteractor,
+            sceneInteractor = sceneInteractor,
+            notificationStackScrollLayout = mock<NotificationStackScrollLayout>(),
+            deviceEntryInteractor = deviceEntryInteractor,
+            touchLog = mock<LogBuffer>(),
+            commandQueue = mock<CommandQueue>(),
+            statusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>(),
+            notificationShadeWindowController = mock<NotificationShadeWindowController>(),
+            assistManagerLazy = { mock<AssistManager>() },
+        )
+    }
+
+val Kosmos.shadeControllerImpl by
+    Kosmos.Fixture {
+        ShadeControllerImpl(
+            mock<CommandQueue>(),
+            fakeExecutor,
+            mock<LogBuffer>(),
+            windowRootViewVisibilityInteractor,
+            mock<KeyguardStateController>(),
+            statusBarStateController,
+            statusBarKeyguardViewManager,
+            mock<StatusBarWindowController>(),
+            deviceProvisionedController,
+            mock<NotificationShadeWindowController>(),
+            mock<WindowManager>(),
+            { mock<ShadeViewController>() },
+            { mock<AssistManager>() },
+            { mock<NotificationGutsManager>() },
+        )
+    }
+var Kosmos.shadeController: ShadeController by Kosmos.Fixture { shadeControllerImpl }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt
new file mode 100644
index 0000000..1ceab68
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.shadeViewController by Kosmos.Fixture { mock<ShadeViewController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt
new file mode 100644
index 0000000..4dcd220
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.shadeAnimationRepository by Kosmos.Fixture { ShadeAnimationRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt
new file mode 100644
index 0000000..d2dd200
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.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.shade.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.data.repository.shadeAnimationRepository
+
+var Kosmos.shadeAnimationInteractor: ShadeAnimationInteractor by
+    Kosmos.Fixture { ShadeAnimationInteractorEmptyImpl(shadeAnimationRepository) }
+var Kosmos.shadeAnimationInteractorSceneContainerImpl: ShadeAnimationInteractorSceneContainerImpl by
+    Kosmos.Fixture {
+        ShadeAnimationInteractorSceneContainerImpl(shadeAnimationRepository, sceneInteractor)
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
index 7da57f0..afd37b3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
@@ -29,8 +29,8 @@
 import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
 import com.android.systemui.statusbar.phone.dozeParameters
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.userSetupRepository
 import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
+import com.android.systemui.statusbar.policy.data.repository.userSetupRepository
 import com.android.systemui.user.domain.interactor.userSwitcherInteractor
 
 var Kosmos.baseShadeInteractor: BaseShadeInteractor by
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryKosmos.kt
new file mode 100644
index 0000000..a75d2bc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.notifications.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
+
+val Kosmos.notificationSettingsRepository by
+    Kosmos.Fixture {
+        NotificationSettingsRepository(
+            scope = testScope.backgroundScope,
+            backgroundDispatcher = testDispatcher,
+            secureSettingsRepository = secureSettingsRepository,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractorKosmos.kt
new file mode 100644
index 0000000..17b4603
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractorKosmos.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.shared.notifications.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.shared.notifications.data.repository.notificationSettingsRepository
+
+val Kosmos.notificationSettingsInteractor by
+    Kosmos.Fixture { NotificationSettingsInteractor(notificationSettingsRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryKosmos.kt
new file mode 100644
index 0000000..552b09e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryKosmos.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.shared.settings.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.secureSettingsRepository: SecureSettingsRepository by
+    Kosmos.Fixture { fakeSecureSettingsRepository }
+val Kosmos.fakeSecureSettingsRepository by Kosmos.Fixture { FakeSecureSettingsRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryKosmos.kt
new file mode 100644
index 0000000..0e4c923
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.smartspace.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.fakeSmartspaceRepository by Fixture { FakeSmartspaceRepository() }
+
+val Kosmos.smartspaceRepository by Fixture<SmartspaceRepository> { fakeSmartspaceRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionControllerKosmos.kt
index 93a7adf..8385403 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionControllerKosmos.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar
 
-import android.content.testableContext
+import android.content.applicationContext
 import com.android.systemui.dump.dumpManager
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +27,7 @@
 val Kosmos.lockscreenShadeScrimTransitionController by Fixture {
     LockscreenShadeScrimTransitionController(
         scrimController = scrimController,
-        context = testableContext,
+        context = applicationContext,
         configurationController = configurationController,
         dumpManager = dumpManager,
         splitShadeStateController = splitShadeStateController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
index 2752cc2..1c6ce79 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar
 
-import android.content.testableContext
+import android.content.applicationContext
 import com.android.systemui.classifier.falsingCollector
 import com.android.systemui.classifier.falsingManager
 import com.android.systemui.dump.dumpManager
@@ -47,7 +47,7 @@
         scrimTransitionController = lockscreenShadeScrimTransitionController,
         keyguardTransitionControllerFactory = lockscreenShadeKeyguardTransitionControllerFactory,
         depthController = notificationShadeDepthController,
-        context = testableContext,
+        context = applicationContext,
         splitShadeOverScrollerFactory = splitShadeLockScreenOverScrollerFactory,
         singleShadeOverScrollerFactory = singleShadeLockScreenOverScrollerFactory,
         activityStarter = activityStarter,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationClickNotifierKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationClickNotifierKosmos.kt
new file mode 100644
index 0000000..7b912ae
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationClickNotifierKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.notificationClickNotifier by Kosmos.Fixture { mock<NotificationClickNotifier>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationPresenterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationPresenterKosmos.kt
new file mode 100644
index 0000000..8d30049
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationPresenterKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.notificationPresenter by Kosmos.Fixture { mock<NotificationPresenter>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt
new file mode 100644
index 0000000..554bdbe
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.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
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.notificationRemoteInputManager by
+    Kosmos.Fixture { mock<NotificationRemoteInputManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationShadeWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationShadeWindowControllerKosmos.kt
new file mode 100644
index 0000000..e8ca3b8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationShadeWindowControllerKosmos.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
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.notificationShadeWindowController by
+    Kosmos.Fixture { mock<NotificationShadeWindowController>() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeKeyguardStatusBarRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeKeyguardStatusBarRepository.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeKeyguardStatusBarRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeKeyguardStatusBarRepository.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
new file mode 100644
index 0000000..c416ea1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.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 FakeRemoteInputRepository : RemoteInputRepository {
+    override val isRemoteInputActive = MutableStateFlow(false)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryKosmos.kt
new file mode 100644
index 0000000..1684efb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryKosmos.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 com.android.systemui.kosmos.Kosmos
+
+var Kosmos.remoteInputRepository: RemoteInputRepository by
+    Kosmos.Fixture { fakeRemoteInputRepository }
+val Kosmos.fakeRemoteInputRepository by Kosmos.Fixture { FakeRemoteInputRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorKosmos.kt
new file mode 100644
index 0000000..07b39dc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractorKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.data.repository.remoteInputRepository
+
+val Kosmos.remoteInputInteractor by Kosmos.Fixture { RemoteInputInteractor(remoteInputRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationActivityStarterKosmos.kt
new file mode 100644
index 0000000..c337ac2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationActivityStarterKosmos.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.notification
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.phone.statusBarNotificationActivityStarter
+
+var Kosmos.notificationActivityStarter: NotificationActivityStarter by
+    Kosmos.Fixture { statusBarNotificationActivityStarter }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerProviderKosmos.kt
new file mode 100644
index 0000000..c3db34b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerProviderKosmos.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.notification
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.notificationLaunchAnimatorControllerProvider by
+    Kosmos.Fixture { mock<NotificationLaunchAnimatorControllerProvider>() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
new file mode 100644
index 0000000..4efcada
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import androidx.annotation.Nullable;
+
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Builder to construct instances of {@link GroupEntry} for tests.
+ */
+public class GroupEntryBuilder {
+    private String mKey = "test_group_key";
+    private long mCreationTime = 0;
+    @Nullable private GroupEntry mParent = GroupEntry.ROOT_ENTRY;
+    private NotifSection mNotifSection;
+    @Nullable private NotificationEntry mSummary = null;
+    private final List<NotificationEntry> mChildren = new ArrayList<>();
+
+    /** Builds a new instance of GroupEntry */
+    public GroupEntry build() {
+        GroupEntry ge = new GroupEntry(mKey, mCreationTime);
+        ge.setParent(mParent);
+        ge.getAttachState().setSection(mNotifSection);
+
+        ge.setSummary(mSummary);
+        if (mSummary != null) {
+            mSummary.setParent(ge);
+        }
+
+        for (NotificationEntry child : mChildren) {
+            ge.addChild(child);
+            child.setParent(ge);
+        }
+        return ge;
+    }
+
+    /** Sets the group key. */
+    public GroupEntryBuilder setKey(String key) {
+        mKey = key;
+        return this;
+    }
+
+    /** Sets the creation time. */
+    public GroupEntryBuilder setCreationTime(long creationTime) {
+        mCreationTime = creationTime;
+        return this;
+    }
+
+    /** Sets the parent entry of the group. */
+    public GroupEntryBuilder setParent(@Nullable GroupEntry entry) {
+        mParent = entry;
+        return this;
+    }
+
+    /** Sets the section the group belongs to. */
+    public GroupEntryBuilder setSection(@Nullable NotifSection section) {
+        mNotifSection = section;
+        return this;
+    }
+
+    /** Sets the group summary. */
+    public GroupEntryBuilder setSummary(
+            NotificationEntry summary) {
+        mSummary = summary;
+        return this;
+    }
+
+    /** Sets the group children. */
+    public GroupEntryBuilder setChildren(List<NotificationEntry> children) {
+        mChildren.clear();
+        mChildren.addAll(children);
+        return this;
+    }
+
+    /** Adds a child to the existing list of children */
+    public GroupEntryBuilder addChild(NotificationEntry entry) {
+        mChildren.add(entry);
+        return this;
+    }
+
+    /** Get the group's internal children list. */
+    public static List<NotificationEntry> getRawChildren(GroupEntry groupEntry) {
+        return groupEntry.getRawChildren();
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifCollectionKosmos.kt
new file mode 100644
index 0000000..9b27a9f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifCollectionKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.notifCollection by Fixture { mockNotifCollection }
+val Kosmos.mockNotifCollection by Fixture { mock<NotifCollection>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreKosmos.kt
new file mode 100644
index 0000000..1f45fbb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreKosmos.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.notification.collection
+
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.notifLiveDataStore: NotifLiveDataStore by
+    Kosmos.Fixture { NotifLiveDataStoreImpl(fakeExecutor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorKosmos.kt
new file mode 100644
index 0000000..358d251
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.visualStabilityCoordinator by Kosmos.Fixture { mock<VisualStabilityCoordinator>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProviderKosmos.kt
new file mode 100644
index 0000000..a5c9561
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProviderKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.provider
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.launchFullScreenIntentProvider by Kosmos.Fixture { LaunchFullScreenIntentProvider() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProviderKosmos.kt
new file mode 100644
index 0000000..edce5d58
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProviderKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.render
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.collection.notifLiveDataStore
+import com.android.systemui.statusbar.notification.collection.notifPipeline
+import com.android.systemui.statusbar.notification.collection.provider.NotificationVisibilityProviderImpl
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+
+var Kosmos.notificationVisibilityProvider: NotificationVisibilityProvider by
+    Kosmos.Fixture {
+        NotificationVisibilityProviderImpl(
+            activeNotificationsInteractor,
+            notifLiveDataStore,
+            notifPipeline,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
index 9851b0e..9c5c486 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
@@ -18,6 +18,7 @@
 
 import android.graphics.drawable.Icon
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import com.android.systemui.statusbar.notification.stack.BUCKET_UNKNOWN
 
 /** Simple ActiveNotificationModel builder for use in tests. */
 fun activeNotificationModel(
@@ -32,6 +33,11 @@
     aodIcon: Icon? = null,
     shelfIcon: Icon? = null,
     statusBarIcon: Icon? = null,
+    uid: Int = 0,
+    instanceId: Int? = null,
+    isGroupSummary: Boolean = false,
+    packageName: String = "pkg",
+    bucket: Int = BUCKET_UNKNOWN,
 ) =
     ActiveNotificationModel(
         key = key,
@@ -45,4 +51,9 @@
         aodIcon = aodIcon,
         shelfIcon = shelfIcon,
         statusBarIcon = statusBarIcon,
+        uid = uid,
+        packageName = packageName,
+        instanceId = instanceId,
+        isGroupSummary = isGroupSummary,
+        bucket = bucket,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt
index cb1ba20..b40e1e7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt
@@ -20,11 +20,20 @@
 
 /**
  * Make the repository hold [count] active notifications for testing. The keys of the notifications
- * are "0", "1", ..., (count - 1).toString().
+ * are "0", "1", ..., (count - 1).toString(). The ranks are the same values in Int.
  */
 fun ActiveNotificationListRepository.setActiveNotifs(count: Int) {
     this.activeNotifications.value =
         ActiveNotificationsStore.Builder()
-            .apply { repeat(count) { i -> addEntry(activeNotificationModel(key = i.toString())) } }
+            .apply {
+                val rankingsMap = mutableMapOf<String, Int>()
+                repeat(count) { i ->
+                    val key = "$i"
+                    addEntry(activeNotificationModel(key = key))
+                    rankingsMap[key] = i
+                }
+
+                setRankingsMap(rankingsMap)
+            }
             .build()
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt
index 67fecb4..acc455f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt
@@ -19,8 +19,8 @@
 import com.android.systemui.common.ui.configurationState
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.notification.collection.notifCollection
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.notificationIconContainerShelfViewModel
-import com.android.systemui.statusbar.notification.stack.ui.viewbinder.notifCollection
 import com.android.systemui.statusbar.ui.systemBarUtilsState
 
 val Kosmos.notificationIconContainerShelfViewBinder by Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.kt
new file mode 100644
index 0000000..30fc2f3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.logging
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.notificationPanelLogger by Fixture { mockNotificationPanelLogger }
+val Kosmos.mockNotificationPanelLogger by Fixture { mock<NotificationPanelLogger>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallbackKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallbackKosmos.kt
new file mode 100644
index 0000000..1e3897b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallbackKosmos.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.statusbar.notification.collection.coordinator.visualStabilityCoordinator
+import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl
+import com.android.systemui.statusbar.notification.collection.notifCollection
+import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider
+import com.android.systemui.statusbar.policy.headsUpManager
+
+var Kosmos.onUserInteractionCallback: OnUserInteractionCallback by
+    Kosmos.Fixture {
+        OnUserInteractionCallbackImpl(
+            notificationVisibilityProvider,
+            notifCollection,
+            headsUpManager,
+            statusBarStateController,
+            visualStabilityCoordinator,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/AmbientStateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/AmbientStateKosmos.kt
index 83ac330..7f6f698 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/AmbientStateKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/AmbientStateKosmos.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.notification.stack
 
-import android.content.testableContext
+import android.content.applicationContext
 import com.android.systemui.dump.dumpManager
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -27,7 +27,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 val Kosmos.ambientState by Fixture {
     AmbientState(
-        /*context=*/ testableContext,
+        /*context=*/ applicationContext,
         /*dumpManager=*/ dumpManager,
         /*sectionProvider=*/ stackScrollAlgorithmSectionProvider,
         /*bypassController=*/ stackScrollAlgorithmBypassController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotficationsHiderTrackerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotficationsHiderTrackerKosmos.kt
new file mode 100644
index 0000000..b12f5af
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotficationsHiderTrackerKosmos.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.statusbar.notification.stack
+
+import com.android.internal.logging.latencyTracker
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+
+val Kosmos.displaySwitchNotificationsHiderTracker by Fixture {
+    DisplaySwitchNotificationsHiderTracker(shadeInteractor, latencyTracker)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
index 13d577b..8909d75 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.shade.largeScreenHeaderHelper
 import com.android.systemui.statusbar.policy.splitShadeStateController
 
 val Kosmos.sharedNotificationContainerInteractor by
@@ -31,5 +32,6 @@
             splitShadeStateController = splitShadeStateController,
             keyguardInteractor = keyguardInteractor,
             deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+            largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerKosmos.kt
new file mode 100644
index 0000000..de52155
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.view
+
+import android.service.notification.notificationListenerService
+import com.android.internal.statusbar.statusBarService
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.notification.logging.notificationPanelLogger
+
+val Kosmos.notificationStatsLogger by Fixture {
+    NotificationStatsLoggerImpl(
+        applicationScope = testScope,
+        bgDispatcher = testDispatcher,
+        statusBarService = statusBarService,
+        notificationListenerService = notificationListenerService,
+        notificationPanelLogger = notificationPanelLogger,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
deleted file mode 100644
index d98f496..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotifCollectionKosmos.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
-
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.collection.NotifCollection
-import com.android.systemui.util.mockito.mock
-
-var Kosmos.notifCollection by Fixture { mock<NotifCollection>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
index 04716b9..748d04d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
@@ -23,8 +23,11 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.notificationIconContainerShelfViewBinder
+import com.android.systemui.statusbar.notification.stack.ui.view.notificationStatsLogger
+import com.android.systemui.statusbar.notification.stack.displaySwitchNotificationsHiderTracker
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel
 import com.android.systemui.statusbar.phone.notificationIconAreaController
+import java.util.Optional
 
 val Kosmos.notificationListViewBinder by Fixture {
     NotificationListViewBinder(
@@ -34,6 +37,8 @@
         falsingManager = falsingManager,
         iconAreaController = notificationIconAreaController,
         metricsLogger = metricsLogger,
+        hiderTracker = displaySwitchNotificationsHiderTracker,
         nicBinder = notificationIconContainerShelfViewBinder,
+        loggerOptional = Optional.of(notificationStatsLogger),
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListLoggerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListLoggerViewModelKosmos.kt
new file mode 100644
index 0000000..08bda46
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListLoggerViewModelKosmos.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.statusbar.notification.stack.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+
+val Kosmos.notificationListLoggerViewModel by Fixture {
+    NotificationLoggerViewModel(
+        keyguardInteractor = keyguardInteractor,
+        windowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor,
+        activeNotificationsInteractor = activeNotificationsInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
index 44f3134..998e579 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.footerViewModel
 import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.notificationShelfViewModel
+import com.android.systemui.statusbar.policy.domain.interactor.userSetupInteractor
 import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
 import java.util.Optional
 
@@ -32,6 +33,7 @@
         shelf = notificationShelfViewModel,
         hideListViewModel = hideListViewModel,
         footer = Optional.of(footerViewModel),
+        logger = Optional.of(notificationListLoggerViewModel),
         activeNotificationsInteractor = activeNotificationsInteractor,
         keyguardTransitionInteractor = keyguardTransitionInteractor,
         seenNotificationsInteractor = seenNotificationsInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index 0dbade7..d7e948e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -20,11 +20,13 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
 
 val Kosmos.notificationsPlaceholderViewModel by Fixture {
     NotificationsPlaceholderViewModel(
         interactor = notificationStackAppearanceInteractor,
+        shadeInteractor = shadeInteractor,
         flags = sceneContainerFlags,
         featureFlags = featureFlagsClassic,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 5ef9a8e..7c398cd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -16,8 +16,12 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
+import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.lockscreenToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.occludedToLockscreenTransitionViewModel
 import com.android.systemui.kosmos.Kosmos
@@ -33,7 +37,11 @@
         keyguardInteractor = keyguardInteractor,
         keyguardTransitionInteractor = keyguardTransitionInteractor,
         shadeInteractor = shadeInteractor,
+        communalInteractor = communalInteractor,
         occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
         lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
+        glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
+        lockscreenToGlanceableHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel,
+        aodBurnInViewModel = aodBurnInViewModel,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
index e4313bb1..cf800d0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
@@ -19,18 +19,24 @@
 import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 import com.android.systemui.statusbar.policy.headsUpManager
 
 val Kosmos.windowRootViewVisibilityInteractor by Fixture {
     WindowRootViewVisibilityInteractor(
-        scope = testScope,
+        scope = applicationCoroutineScope,
         windowRootViewVisibilityRepository = windowRootViewVisibilityRepository,
         keyguardRepository = keyguardRepository,
         headsUpManager = headsUpManager,
         powerInteractor = powerInteractor,
+        activeNotificationsInteractor = activeNotificationsInteractor,
+        sceneInteractorProvider = { sceneInteractor },
+        sceneContainerFlags = sceneContainerFlags,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
new file mode 100644
index 0000000..370b177
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import org.mockito.Mockito.mock
+
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.dozeServiceHost: DozeServiceHost by Kosmos.Fixture { mock(DozeServiceHost::class.java) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
new file mode 100644
index 0000000..6ddc9df
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.app.keyguardManager
+import android.content.applicationContext
+import android.os.fakeExecutorHandler
+import android.service.dream.dreamManager
+import com.android.internal.logging.metricsLogger
+import com.android.internal.widget.lockPatternUtils
+import com.android.systemui.activityIntentHelper
+import com.android.systemui.animation.activityLaunchAnimator
+import com.android.systemui.assist.assistManager
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.settings.userTracker
+import com.android.systemui.shade.domain.interactor.shadeAnimationInteractor
+import com.android.systemui.shade.shadeController
+import com.android.systemui.shade.shadeViewController
+import com.android.systemui.statusbar.notification.collection.provider.launchFullScreenIntentProvider
+import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider
+import com.android.systemui.statusbar.notification.notificationLaunchAnimatorControllerProvider
+import com.android.systemui.statusbar.notification.row.onUserInteractionCallback
+import com.android.systemui.statusbar.notificationClickNotifier
+import com.android.systemui.statusbar.notificationLockscreenUserManager
+import com.android.systemui.statusbar.notificationPresenter
+import com.android.systemui.statusbar.notificationRemoteInputManager
+import com.android.systemui.statusbar.notificationShadeWindowController
+import com.android.systemui.statusbar.policy.headsUpManager
+import com.android.systemui.statusbar.policy.keyguardStateController
+import com.android.systemui.wmshell.bubblesManager
+import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.statusBarNotificationActivityStarter by
+    Kosmos.Fixture {
+        StatusBarNotificationActivityStarter(
+            applicationContext,
+            applicationContext.displayId,
+            fakeExecutorHandler,
+            fakeExecutor,
+            notificationVisibilityProvider,
+            headsUpManager,
+            activityStarter,
+            notificationClickNotifier,
+            statusBarKeyguardViewManager,
+            keyguardManager,
+            dreamManager,
+            Optional.of(bubblesManager),
+            { assistManager },
+            notificationRemoteInputManager,
+            notificationLockscreenUserManager,
+            shadeController,
+            keyguardStateController,
+            lockPatternUtils,
+            statusBarRemoteInputCallback,
+            activityIntentHelper,
+            metricsLogger,
+            statusBarNotificationActivityStarterLogger,
+            onUserInteractionCallback,
+            notificationPresenter,
+            shadeViewController,
+            notificationShadeWindowController,
+            activityLaunchAnimator,
+            shadeAnimationInteractor,
+            notificationLaunchAnimatorControllerProvider,
+            launchFullScreenIntentProvider,
+            powerInteractor,
+            userTracker,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLoggerKosmos.kt
new file mode 100644
index 0000000..31cfc97
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLoggerKosmos.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.phone
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
+
+val Kosmos.statusBarNotificationActivityStarterLogger by
+    Kosmos.Fixture { StatusBarNotificationActivityStarterLogger(logcatLogBuffer()) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackKosmos.kt
new file mode 100644
index 0000000..475d7fa
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.statusBarRemoteInputCallback by Kosmos.Fixture { mock<StatusBarRemoteInputCallback>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/FakeStatusBarPipelineMobileDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/FakeStatusBarPipelineMobileDataLayerModule.kt
index 549929c..6e2d12a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/FakeStatusBarPipelineMobileDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/FakeStatusBarPipelineMobileDataLayerModule.kt
@@ -15,7 +15,7 @@
  */
 package com.android.systemui.statusbar.pipeline.mobile.data
 
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepositoryModule
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepositoryModule
 import dagger.Module
 
 @Module(includes = [FakeUserSetupRepositoryModule::class])
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index a9c8ec7..32d572e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -35,6 +35,7 @@
     override val isRoaming = MutableStateFlow(false)
     override val operatorAlphaShort: MutableStateFlow<String?> = MutableStateFlow(null)
     override val isInService = MutableStateFlow(false)
+    override val isNonTerrestrial = MutableStateFlow(false)
     override val isGsm = MutableStateFlow(false)
     override val cdmaLevel = MutableStateFlow(0)
     override val primaryLevel = MutableStateFlow(0)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
deleted file mode 100644
index 55e81bb..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-import dagger.Binds
-import dagger.Module
-import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-
-/** Defaults to `true` */
-@SysUISingleton
-class FakeUserSetupRepository @Inject constructor() : UserSetupRepository {
-    private val _isUserSetup: MutableStateFlow<Boolean> = MutableStateFlow(true)
-    override val isUserSetupFlow = _isUserSetup
-
-    fun setUserSetup(setup: Boolean) {
-        _isUserSetup.value = setup
-    }
-}
-
-@Module
-interface FakeUserSetupRepositoryModule {
-    @Binds fun bindFake(fake: FakeUserSetupRepository): UserSetupRepository
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.kt
new file mode 100644
index 0000000..9d62ea5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.fakeMobileConnectionsRepository by Fixture {
+    FakeMobileConnectionsRepository(tableLogBuffer = mock())
+}
+
+val Kosmos.mobileConnectionsRepository by
+    Fixture<MobileConnectionsRepository> { fakeMobileConnectionsRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
deleted file mode 100644
index 7b9634a..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryKosmos.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
-
-import com.android.systemui.kosmos.Kosmos
-
-var Kosmos.userSetupRepository: UserSetupRepository by Kosmos.Fixture { fakeUserSetupRepository }
-val Kosmos.fakeUserSetupRepository by Kosmos.Fixture { FakeUserSetupRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
new file mode 100644
index 0000000..c010327
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeMobileIconInteractor(
+    override val tableLogBuffer: TableLogBuffer,
+) : MobileIconInteractor {
+    override val alwaysShowDataRatIcon = MutableStateFlow(false)
+
+    override val activity =
+        MutableStateFlow(
+            DataActivityModel(
+                hasActivityIn = false,
+                hasActivityOut = false,
+            )
+        )
+
+    override val carrierNetworkChangeActive = MutableStateFlow(false)
+
+    override val mobileIsDefault = MutableStateFlow(true)
+
+    override val isSingleCarrier = MutableStateFlow(true)
+
+    override val networkTypeIconGroup =
+        MutableStateFlow<NetworkTypeIconModel>(
+            NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
+        )
+
+    override val showSliceAttribution = MutableStateFlow(false)
+
+    override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo mode"))
+
+    override val carrierName = MutableStateFlow("demo mode")
+
+    override val isRoaming = MutableStateFlow(false)
+
+    override val isDataConnected = MutableStateFlow(true)
+
+    override val isInService = MutableStateFlow(true)
+
+    override val isNonTerrestrial = MutableStateFlow(false)
+
+    private val _isDataEnabled = MutableStateFlow(true)
+    override val isDataEnabled = _isDataEnabled
+
+    override val isForceHidden = MutableStateFlow(false)
+
+    override val isAllowedDuringAirplaneMode = MutableStateFlow(false)
+
+    override val signalLevelIcon: MutableStateFlow<SignalIconModel> =
+        MutableStateFlow(
+            SignalIconModel.Cellular(
+                level = 0,
+                numberOfLevels = 4,
+                showExclamationMark = false,
+                carrierNetworkChange = false,
+            )
+        )
+
+    fun setIsDataEnabled(enabled: Boolean) {
+        _isDataEnabled.value = enabled
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
new file mode 100644
index 0000000..de6c87c2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO
+import android.telephony.TelephonyManager.NETWORK_TYPE_GSM
+import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
+import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeMobileIconsInteractor(
+    mobileMappings: MobileMappingsProxy,
+    val tableLogBuffer: TableLogBuffer,
+) : MobileIconsInteractor {
+    val THREE_G_KEY = mobileMappings.toIconKey(THREE_G)
+    val LTE_KEY = mobileMappings.toIconKey(LTE)
+    val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G)
+    val FIVE_G_OVERRIDE_KEY = mobileMappings.toIconKeyOverride(FIVE_G_OVERRIDE)
+
+    /**
+     * To avoid a reliance on [MobileMappings], we'll build a simpler map from network type to
+     * mobile icon. See TelephonyManager.NETWORK_TYPES for a list of types and [TelephonyIcons] for
+     * the exhaustive set of icons
+     */
+    val TEST_MAPPING: Map<String, MobileIconGroup> =
+        mapOf(
+            THREE_G_KEY to TelephonyIcons.THREE_G,
+            LTE_KEY to TelephonyIcons.LTE,
+            FOUR_G_KEY to TelephonyIcons.FOUR_G,
+            FIVE_G_OVERRIDE_KEY to TelephonyIcons.NR_5G,
+        )
+
+    private val interactorCache: MutableMap<Int, FakeMobileIconInteractor> = mutableMapOf()
+
+    override val isDefaultConnectionFailed = MutableStateFlow(false)
+
+    override val filteredSubscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
+
+    private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
+    override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
+
+    override val activeDataIconInteractor = MutableStateFlow(null)
+
+    override val alwaysShowDataRatIcon = MutableStateFlow(false)
+
+    override val alwaysUseCdmaLevel = MutableStateFlow(false)
+
+    override val mobileIsDefault = MutableStateFlow(false)
+
+    override val isSingleCarrier = MutableStateFlow(true)
+
+    override val icons: MutableStateFlow<List<MobileIconInteractor>> = MutableStateFlow(emptyList())
+
+    private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
+    override val defaultMobileIconMapping = _defaultMobileIconMapping
+
+    private val _defaultMobileIconGroup = MutableStateFlow(DEFAULT_ICON)
+    override val defaultMobileIconGroup = _defaultMobileIconGroup
+
+    private val _isUserSetUp = MutableStateFlow(true)
+    override val isUserSetUp = _isUserSetUp
+
+    override val isForceHidden = MutableStateFlow(false)
+
+    /** Always returns a new fake interactor */
+    override fun getMobileConnectionInteractorForSubId(subId: Int): FakeMobileIconInteractor {
+        return FakeMobileIconInteractor(tableLogBuffer).also {
+            interactorCache[subId] = it
+            // Also update the icons
+            icons.value = interactorCache.values.toList()
+        }
+    }
+
+    /**
+     * Returns the most recently created interactor for the given subId, or null if an interactor
+     * has never been created for that sub.
+     */
+    fun getInteractorForSubId(subId: Int): FakeMobileIconInteractor? {
+        return interactorCache[subId]
+    }
+
+    companion object {
+        val DEFAULT_ICON = TelephonyIcons.G
+
+        const val DEFAULT_DATA_SUB_ID = 1
+
+        // Use [MobileMappings] to define some simple definitions
+        const val THREE_G = NETWORK_TYPE_GSM
+        const val LTE = NETWORK_TYPE_LTE
+        const val FOUR_G = NETWORK_TYPE_UMTS
+        const val FIVE_G_OVERRIDE = OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
index 18a2f94..33ed7e6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
@@ -20,3 +20,5 @@
 import com.android.systemui.util.mockito.mock
 
 val Kosmos.configurationController by Kosmos.Fixture { mock<ConfigurationController>() }
+val Kosmos.fakeConfigurationController: FakeConfigurationController by
+    Kosmos.Fixture { FakeConfigurationController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt
index 021e7df..ac90a45 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt
@@ -77,6 +77,8 @@
 
     override fun isVpnBranded(): Boolean = fakeState.isVpnBranded
 
+    override fun isVpnValidated(): Boolean = fakeState.isVpnValidated
+
     override fun getPrimaryVpnName(): String? = fakeState.primaryVpnName
 
     override fun getWorkProfileVpnName(): String? = fakeState.workProfileVpnName
@@ -110,6 +112,7 @@
         var isVpnEnabled: Boolean = false,
         var isVpnRestricted: Boolean = false,
         var isVpnBranded: Boolean = false,
+        var isVpnValidated: Boolean = false,
         var primaryVpnName: String? = null,
         var workProfileVpnName: String? = null,
         var hasCACertInCurrentUser: Boolean = false,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt
new file mode 100644
index 0000000..0e909c4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.keyguardStateController by Kosmos.Fixture { mock<KeyguardStateController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeUserSetupRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeUserSetupRepository.kt
new file mode 100644
index 0000000..76a9861
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeUserSetupRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Defaults to `true` */
+@SysUISingleton
+class FakeUserSetupRepository @Inject constructor() : UserSetupRepository {
+    private val _isUserSetup: MutableStateFlow<Boolean> = MutableStateFlow(true)
+    override val isUserSetUp = _isUserSetup
+
+    fun setUserSetUp(isSetUp: Boolean) {
+        _isUserSetup.value = isSetUp
+    }
+}
+
+@Module
+interface FakeUserSetupRepositoryModule {
+    @Binds fun bindFake(fake: FakeUserSetupRepository): UserSetupRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryKosmos.kt
new file mode 100644
index 0000000..a1c5b9a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/UserSetupRepositoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.userSetupRepository: UserSetupRepository by Kosmos.Fixture { fakeUserSetupRepository }
+val Kosmos.fakeUserSetupRepository by Kosmos.Fixture { FakeUserSetupRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorKosmos.kt
new file mode 100644
index 0000000..83f4939
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/UserSetupInteractorKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.policy.data.repository.userSetupRepository
+
+val Kosmos.userSetupInteractor by Kosmos.Fixture { UserSetupInteractor(userSetupRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt
index e208add..5476d55 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt
@@ -17,7 +17,15 @@
 package com.android.systemui.statusbar.ui
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.statusbar.policy.configurationController
 
-val Kosmos.systemBarUtilsState by
-    Kosmos.Fixture { SystemBarUtilsState(configurationController, systemBarUtilsProxy) }
+val Kosmos.systemBarUtilsState by Fixture {
+    SystemBarUtilsState(
+        testDispatcher,
+        testDispatcher,
+        configurationController,
+        systemBarUtilsProxy,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelKosmos.kt
new file mode 100644
index 0000000..0b9f897
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.user.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.user.domain.interactor.guestUserInteractor
+import com.android.systemui.user.domain.interactor.userSwitcherInteractor
+
+val Kosmos.userSwitcherViewModel by Fixture {
+    UserSwitcherViewModel(
+        userSwitcherInteractor = userSwitcherInteractor,
+        guestUserInteractor = guestUserInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutorModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutorModule.kt
index 1f48d94..11c09ee 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutorModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutorModule.kt
@@ -17,6 +17,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.qualifiers.UiBackground
 import com.android.systemui.util.time.FakeSystemClock
 import dagger.Binds
 import dagger.Module
@@ -27,8 +28,9 @@
 interface FakeExecutorModule {
     @Binds @Main @SysUISingleton fun bindMainExecutor(executor: FakeExecutor): Executor
 
+    @Binds @UiBackground @SysUISingleton fun bindUiBgExecutor(executor: FakeExecutor): Executor
+
     companion object {
-        @Provides
-        fun provideFake(clock: FakeSystemClock) = FakeExecutor(clock)
+        @Provides fun provideFake(clock: FakeSystemClock) = FakeExecutor(clock)
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt
index 914e654..f3a8b14 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt
@@ -17,5 +17,19 @@
 package com.android.systemui.util.time
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.currentTime
 
-var Kosmos.fakeSystemClock by Kosmos.Fixture { FakeSystemClock() }
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.systemClock by
+    Kosmos.Fixture<SystemClock> {
+        mock {
+            whenever(elapsedRealtime()).thenAnswer { testScope.currentTime }
+            whenever(uptimeMillis()).thenAnswer { testScope.currentTime }
+        }
+    }
+
+val Kosmos.fakeSystemClock by Kosmos.Fixture { FakeSystemClock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java
index 76199e3..791165d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java
@@ -109,6 +109,11 @@
     }
 
     @Override
+    public boolean isVpnValidated() {
+        return false;
+    }
+
+    @Override
     public String getPrimaryVpnName() {
         return null;
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
new file mode 100644
index 0000000..d341073
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel
+
+import android.content.res.mainResources
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.policy.fakeConfigurationController
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.volume.panel.dagger.factory.KosmosVolumePanelComponentFactory
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.domain.TestComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
+import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractorImpl
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+import javax.inject.Provider
+
+val Kosmos.mockVolumePanelUiComponent: VolumePanelUiComponent by Kosmos.Fixture { mock {} }
+val Kosmos.mockVolumePanelUiComponentProvider: Provider<VolumePanelUiComponent> by
+    Kosmos.Fixture { Provider { mockVolumePanelUiComponent } }
+var Kosmos.componentByKey: Map<VolumePanelComponentKey, Provider<VolumePanelUiComponent>> by
+    Kosmos.Fixture { emptyMap() }
+val Kosmos.componentsFactory: ComponentsFactory by
+    Kosmos.Fixture { ComponentsFactory(componentByKey) }
+
+var Kosmos.componentsLayoutManager: ComponentsLayoutManager by Kosmos.Fixture()
+var Kosmos.enabledComponents: Collection<VolumePanelComponentKey> by
+    Kosmos.Fixture { componentByKey.keys }
+val Kosmos.unavailableCriteria: Provider<ComponentAvailabilityCriteria> by
+    Kosmos.Fixture { Provider { TestComponentAvailabilityCriteria(false) } }
+val Kosmos.availableCriteria: Provider<ComponentAvailabilityCriteria> by
+    Kosmos.Fixture { Provider { TestComponentAvailabilityCriteria(true) } }
+var Kosmos.defaultCriteria: Provider<ComponentAvailabilityCriteria> by
+    Kosmos.Fixture { availableCriteria }
+var Kosmos.criteriaByKey: Map<VolumePanelComponentKey, Provider<ComponentAvailabilityCriteria>> by
+    Kosmos.Fixture { emptyMap() }
+var Kosmos.componentsInteractor: ComponentsInteractor by
+    Kosmos.Fixture {
+        ComponentsInteractorImpl(
+            enabledComponents,
+            defaultCriteria,
+            testScope.backgroundScope,
+            criteriaByKey,
+        )
+    }
+
+var Kosmos.volumePanelViewModel: VolumePanelViewModel by
+    Kosmos.Fixture {
+        VolumePanelViewModel(
+            mainResources,
+            KosmosVolumePanelComponentFactory(this),
+            fakeConfigurationController,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt
new file mode 100644
index 0000000..49041ed
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.dagger.factory
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.volume.panel.componentsFactory
+import com.android.systemui.volume.panel.componentsInteractor
+import com.android.systemui.volume.panel.componentsLayoutManager
+import com.android.systemui.volume.panel.dagger.VolumePanelComponent
+import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
+import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+import kotlinx.coroutines.CoroutineScope
+
+class KosmosVolumePanelComponentFactory(private val kosmos: Kosmos) : VolumePanelComponentFactory {
+
+    override fun create(viewModel: VolumePanelViewModel): VolumePanelComponent =
+        object : VolumePanelComponent {
+
+            override fun coroutineScope(): CoroutineScope = kosmos.testScope.backgroundScope
+
+            override fun componentsInteractor(): ComponentsInteractor = kosmos.componentsInteractor
+
+            override fun componentsFactory(): ComponentsFactory = kosmos.componentsFactory
+
+            override fun componentsLayoutManager(): ComponentsLayoutManager =
+                kosmos.componentsLayoutManager
+        }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/TestComponentAvailabilityCriteria.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/TestComponentAvailabilityCriteria.kt
new file mode 100644
index 0000000..5ab9274
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/TestComponentAvailabilityCriteria.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.domain
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+class TestComponentAvailabilityCriteria(val isAvailable: Boolean) : ComponentAvailabilityCriteria {
+
+    override fun isAvailable(): Flow<Boolean> = flowOf(isAvailable)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/FakeComponentsLayoutManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/FakeComponentsLayoutManager.kt
new file mode 100644
index 0000000..655d8f7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/FakeComponentsLayoutManager.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.layout
+
+import com.android.systemui.volume.panel.ui.viewmodel.ComponentState
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
+
+class FakeComponentsLayoutManager(
+    private val isBottomBar: (components: ComponentState) -> Boolean
+) : ComponentsLayoutManager {
+
+    override fun layout(
+        volumePanelState: VolumePanelState,
+        components: Collection<ComponentState>
+    ): ComponentsLayout {
+        return ComponentsLayout(
+            components
+                .filter { componentState -> !isBottomBar(componentState) }
+                .sortedBy { it.key },
+            components.find(isBottomBar)!!,
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wmshell/BubblesManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wmshell/BubblesManagerKosmos.kt
new file mode 100644
index 0000000..1d05d62
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wmshell/BubblesManagerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wmshell
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.bubblesManager by Kosmos.Fixture { mock<BubblesManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/telecom/TelecomManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/telecom/TelecomManagerKosmos.kt
new file mode 100644
index 0000000..4e0c0883
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/telecom/TelecomManagerKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.telecom
+
+import android.telecom.TelecomManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.telecomManager by Fixture<TelecomManager?> { mock() }
diff --git a/packages/SystemUI/unfold/Android.bp b/packages/SystemUI/unfold/Android.bp
index e52cefb..81fd8ce 100644
--- a/packages/SystemUI/unfold/Android.bp
+++ b/packages/SystemUI/unfold/Android.bp
@@ -39,7 +39,4 @@
     sdk_version: "current",
     min_sdk_version: "current",
     plugins: ["dagger2-compiler"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
diff --git a/packages/SystemUI/unfold/lint-baseline.xml b/packages/SystemUI/unfold/lint-baseline.xml
deleted file mode 100644
index 449ed2e..0000000
--- a/packages/SystemUI/unfold/lint-baseline.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" name="" variant="all" version="7.1.0-dev">
-</issues>
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
index 82ea362..bb91f9b 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
@@ -20,6 +20,7 @@
 import android.hardware.display.DisplayManager
 import android.os.Handler
 import android.os.RemoteException
+import android.os.Trace
 import com.android.systemui.unfold.util.CallbackController
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
@@ -86,14 +87,19 @@
     private inner class RotationDisplayListener : DisplayManager.DisplayListener {
 
         override fun onDisplayChanged(displayId: Int) {
-            val display = context.display ?: return
+            Trace.beginSection("RotationChangeProvider.RotationDisplayListener#onDisplayChanged")
+            try {
+                val display = context.display ?: return
 
-            if (displayId == display.displayId) {
-                val currentRotation = display.rotation
-                if (lastRotation == null || lastRotation != currentRotation) {
-                    listeners.forEach { it.onRotationChanged(currentRotation) }
-                    lastRotation = currentRotation
+                if (displayId == display.displayId) {
+                    val currentRotation = display.rotation
+                    if (lastRotation == null || lastRotation != currentRotation) {
+                        listeners.forEach { it.onRotationChanged(currentRotation) }
+                        lastRotation = currentRotation
+                    }
                 }
+            } finally {
+                Trace.endSection()
             }
         }
 
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
index 2bca272..24b22f8 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
@@ -17,6 +17,7 @@
 
 import android.os.Handler
 import android.os.Looper
+import androidx.annotation.GuardedBy
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import java.util.concurrent.CopyOnWriteArrayList
@@ -41,8 +42,12 @@
 
     private val listeners = CopyOnWriteArrayList<TransitionProgressListener>()
 
-    @Volatile private var isReadyToHandleTransition = false
-    @Volatile private var isTransitionRunning = false
+    private val lock = Object()
+
+    @GuardedBy("lock") private var isReadyToHandleTransition = false
+    // Accessed only from progress thread
+    private var isTransitionRunning = false
+    // Accessed only from progress thread
     private var lastTransitionProgress = PROGRESS_UNSET
 
     init {
@@ -72,23 +77,44 @@
      * the transition progress events.
      *
      * Call it with readyToHandleTransition = false when listeners can't process the events.
+     *
+     * Note that this could be called by any thread.
      */
     fun setReadyToHandleTransition(isReadyToHandleTransition: Boolean) {
-        val progressHandler = this.progressHandler
-        if (isTransitionRunning && progressHandler != null) {
-            progressHandler.post {
-                if (isReadyToHandleTransition) {
-                    listeners.forEach { it.onTransitionStarted() }
-                    if (lastTransitionProgress != PROGRESS_UNSET) {
-                        listeners.forEach { it.onTransitionProgress(lastTransitionProgress) }
-                    }
-                } else {
-                    isTransitionRunning = false
-                    listeners.forEach { it.onTransitionFinished() }
-                }
+        synchronized(lock) {
+            this.isReadyToHandleTransition = isReadyToHandleTransition
+            val progressHandlerLocal = this.progressHandler
+            if (progressHandlerLocal != null) {
+                ensureInHandler(progressHandlerLocal) { reportLastProgressIfNeeded() }
             }
         }
-        this.isReadyToHandleTransition = isReadyToHandleTransition
+    }
+
+    /** Runs directly if called from the handler thread. Posts otherwise. */
+    private fun ensureInHandler(handler: Handler, f: () -> Unit) {
+        if (handler.looper.isCurrentThread) {
+            f()
+        } else {
+            handler.post(f)
+        }
+    }
+
+    private fun reportLastProgressIfNeeded() {
+        assertInProgressThread()
+        synchronized(lock) {
+            if (!isTransitionRunning) {
+                return
+            }
+            if (isReadyToHandleTransition) {
+                listeners.forEach { it.onTransitionStarted() }
+                if (lastTransitionProgress != PROGRESS_UNSET) {
+                    listeners.forEach { it.onTransitionProgress(lastTransitionProgress) }
+                }
+            } else {
+                isTransitionRunning = false
+                listeners.forEach { it.onTransitionFinished() }
+            }
+        }
     }
 
     override fun addCallback(listener: TransitionProgressListener) {
@@ -106,34 +132,42 @@
 
     override fun onTransitionStarted() {
         assertInProgressThread()
-        isTransitionRunning = true
-        if (isReadyToHandleTransition) {
-            listeners.forEach { it.onTransitionStarted() }
+        synchronized(lock) {
+            isTransitionRunning = true
+            if (isReadyToHandleTransition) {
+                listeners.forEach { it.onTransitionStarted() }
+            }
         }
     }
 
     override fun onTransitionProgress(progress: Float) {
         assertInProgressThread()
-        if (isReadyToHandleTransition) {
-            listeners.forEach { it.onTransitionProgress(progress) }
+        synchronized(lock) {
+            if (isReadyToHandleTransition) {
+                listeners.forEach { it.onTransitionProgress(progress) }
+            }
+            lastTransitionProgress = progress
         }
-        lastTransitionProgress = progress
     }
 
     override fun onTransitionFinishing() {
         assertInProgressThread()
-        if (isReadyToHandleTransition) {
-            listeners.forEach { it.onTransitionFinishing() }
+        synchronized(lock) {
+            if (isReadyToHandleTransition) {
+                listeners.forEach { it.onTransitionFinishing() }
+            }
         }
     }
 
     override fun onTransitionFinished() {
         assertInProgressThread()
-        if (isReadyToHandleTransition) {
-            listeners.forEach { it.onTransitionFinished() }
+        synchronized(lock) {
+            if (isReadyToHandleTransition) {
+                listeners.forEach { it.onTransitionFinished() }
+            }
+            isTransitionRunning = false
+            lastTransitionProgress = PROGRESS_UNSET
         }
-        isTransitionRunning = false
-        lastTransitionProgress = PROGRESS_UNSET
     }
 
     private fun assertInProgressThread() {
@@ -151,7 +185,7 @@
         }
     }
 
-    companion object {
-        private const val PROGRESS_UNSET = -1f
+    private companion object {
+        const val PROGRESS_UNSET = -1f
     }
 }
diff --git a/packages/VpnDialogs/res/values-zh-rTW/strings.xml b/packages/VpnDialogs/res/values-zh-rTW/strings.xml
index 3f1336b..7d5e867 100644
--- a/packages/VpnDialogs/res/values-zh-rTW/strings.xml
+++ b/packages/VpnDialogs/res/values-zh-rTW/strings.xml
@@ -25,7 +25,7 @@
     <string name="data_transmitted" msgid="7988167672982199061">"已傳送:"</string>
     <string name="data_received" msgid="4062776929376067820">"已接收:"</string>
     <string name="data_value_format" msgid="2192466557826897580">"<xliff:g id="NUMBER_0">%1$s</xliff:g> 位元組 / <xliff:g id="NUMBER_1">%2$s</xliff:g> 個封包"</string>
-    <string name="always_on_disconnected_title" msgid="1906740176262776166">"無法連上永久連線的 VPN"</string>
+    <string name="always_on_disconnected_title" msgid="1906740176262776166">"無法連上永久連線 VPN"</string>
     <string name="always_on_disconnected_message" msgid="555634519845992917">"「<xliff:g id="VPN_APP_0">%1$s</xliff:g>」設定為隨時保持連線,但目前無法連線。在重新連上「<xliff:g id="VPN_APP_1">%1$s</xliff:g>」之前,你的手機將會使用公用網路。"</string>
     <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"「<xliff:g id="VPN_APP">%1$s</xliff:g>」設定為隨時保持連線,但目前無法連線。在重新連上 VPN 之前,你將無法連接網路。"</string>
     <string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string>
diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java
index 1fc74f7..67a7d12 100644
--- a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java
+++ b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java
@@ -16,6 +16,8 @@
 
 package com.android.vpndialogs;
 
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
 import android.content.DialogInterface;
 import android.net.VpnManager;
 import android.os.Bundle;
@@ -87,6 +89,7 @@
             mAlertParams.mNegativeButtonListener = this;
             mAlertParams.mView = view;
             setupAlert();
+            getWindow().addPrivateFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
 
             if (mHandler == null) {
                 mHandler = new Handler(this);
diff --git a/packages/WallpaperBackup/AndroidManifest.xml b/packages/WallpaperBackup/AndroidManifest.xml
index c548101..3ce97cd 100644
--- a/packages/WallpaperBackup/AndroidManifest.xml
+++ b/packages/WallpaperBackup/AndroidManifest.xml
@@ -17,11 +17,19 @@
  */
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.wallpaperbackup"
-    android:sharedUserId="android.uid.system" >
+    package="com.android.wallpaperbackup" >
+
+    <uses-permission android:name="android.permission.READ_WALLPAPER_INTERNAL" />
+    <uses-permission android:name="android.permission.SET_WALLPAPER_COMPONENT" />
+    <uses-permission android:name="android.permission.SET_WALLPAPER" />
+
+    <queries>
+        <intent>
+            <action android:name="android.service.wallpaper.WallpaperService" />
+        </intent>
+    </queries>
 
     <application android:allowClearUserData="false"
-                 android:process="system"
                  android:killAfterRestore="false"
                  android:allowBackup="true"
                  android:backupInForeground="true"
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 98421a9..f31eb44 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -18,11 +18,13 @@
 
 import static android.app.WallpaperManager.FLAG_LOCK;
 import static android.app.WallpaperManager.FLAG_SYSTEM;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
 
 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_INELIGIBLE;
 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_METADATA;
 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_WALLPAPER;
 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_QUOTA_EXCEEDED;
+import static com.android.window.flags.Flags.multiCrop;
 
 import android.app.AppGlobals;
 import android.app.WallpaperManager;
@@ -43,7 +45,9 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.Pair;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.Xml;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -55,6 +59,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.List;
 
 /**
  * Backs up and restores wallpaper and metadata related to it.
@@ -432,6 +437,27 @@
     private void restoreFromStage(File stage, File info, String hintTag, int which)
             throws IOException {
         if (stage.exists()) {
+            if (multiCrop()) {
+                SparseArray<Rect> cropHints = parseCropHints(info, hintTag);
+                if (cropHints != null) {
+                    Slog.i(TAG, "Got restored wallpaper; applying which=" + which
+                            + "; cropHints = " + cropHints);
+                    try (FileInputStream in = new FileInputStream(stage)) {
+                        mWallpaperManager.setStreamWithCrops(in, cropHints, true, which);
+                    }
+                    // And log the success
+                    if ((which & FLAG_SYSTEM) > 0) {
+                        mEventLogger.onSystemImageWallpaperRestored();
+                    }
+                    if ((which & FLAG_LOCK) > 0) {
+                        mEventLogger.onLockImageWallpaperRestored();
+                    }
+                } else {
+                    logRestoreError(which, ERROR_NO_METADATA);
+                }
+                return;
+            }
+
             // Parse the restored info file to find the crop hint.  Note that this currently
             // relies on a priori knowledge of the wallpaper info file schema.
             Rect cropHint = parseCropHint(info, hintTag);
@@ -501,6 +527,47 @@
         return cropHint;
     }
 
+    private SparseArray<Rect> parseCropHints(File wallpaperInfo, String sectionTag) {
+        SparseArray<Rect> cropHints = new SparseArray<>();
+        try (FileInputStream stream = new FileInputStream(wallpaperInfo)) {
+            XmlPullParser parser = Xml.resolvePullParser(stream);
+            int type;
+            do {
+                type = parser.next();
+                if (type != XmlPullParser.START_TAG) continue;
+                String tag = parser.getName();
+                if (!sectionTag.equals(tag)) continue;
+                for (Pair<Integer, String> pair: List.of(
+                        new Pair<>(WallpaperManager.PORTRAIT, "Portrait"),
+                        new Pair<>(WallpaperManager.LANDSCAPE, "Landscape"),
+                        new Pair<>(WallpaperManager.SQUARE_PORTRAIT, "SquarePortrait"),
+                        new Pair<>(WallpaperManager.SQUARE_LANDSCAPE, "SquareLandscape"))) {
+                    Rect cropHint = new Rect(
+                            getAttributeInt(parser, "cropLeft" + pair.second, 0),
+                            getAttributeInt(parser, "cropTop" + pair.second, 0),
+                            getAttributeInt(parser, "cropRight" + pair.second, 0),
+                            getAttributeInt(parser, "cropBottom" + pair.second, 0));
+                    if (!cropHint.isEmpty()) cropHints.put(pair.first, cropHint);
+                }
+                if (cropHints.size() == 0) {
+                    // migration case: the crops per screen orientation are not specified.
+                    // use the old attributes to restore the crop for one screen orientation.
+                    Rect cropHint = new Rect(
+                            getAttributeInt(parser, "cropLeft", 0),
+                            getAttributeInt(parser, "cropTop", 0),
+                            getAttributeInt(parser, "cropRight", 0),
+                            getAttributeInt(parser, "cropBottom", 0));
+                    if (!cropHint.isEmpty()) cropHints.put(ORIENTATION_UNKNOWN, cropHint);
+                }
+            } while (type != XmlPullParser.END_DOCUMENT);
+        } catch (Exception e) {
+            // Whoops; can't process the info file at all.  Report failure.
+            Slog.w(TAG, "Failed to parse restored crops: " + e.getMessage());
+            return null;
+        }
+        return cropHints;
+    }
+
     private ComponentName parseWallpaperComponent(File wallpaperInfo, String sectionTag) {
         ComponentName name = null;
         try (FileInputStream stream = new FileInputStream(wallpaperInfo)) {
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index fb521e1..053ed77 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -31,6 +31,7 @@
 import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_SYSTEM;
 import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_LOCK;
 import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_SYSTEM;
+import static com.android.window.flags.Flags.multiCrop;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -60,6 +61,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
 import android.service.wallpaper.WallpaperService;
+import android.util.SparseArray;
 import android.util.Xml;
 
 import androidx.test.InstrumentationRegistry;
@@ -711,8 +713,13 @@
 
     @Test
     public void testOnRestore_throwsException_logsErrors() throws Exception {
-        when(mWallpaperManager.setStream(any(), any(), anyBoolean(), anyInt())).thenThrow(
-                new RuntimeException());
+        if (!multiCrop()) {
+            when(mWallpaperManager.setStream(any(), any(), anyBoolean(), anyInt()))
+                    .thenThrow(new RuntimeException());
+        } else {
+            when(mWallpaperManager.setStreamWithCrops(any(), any(SparseArray.class), anyBoolean(),
+                    anyInt())).thenThrow(new RuntimeException());
+        }
         mockStagedWallpaperFile(SYSTEM_WALLPAPER_STAGE);
         mockStagedWallpaperFile(WALLPAPER_INFO_STAGE);
         mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD,
diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..be1f081
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values-sw600dp/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+    <!-- If true, attach the navigation bar to the app during app transition -->
+    <bool name="config_attachNavBarToAppDuringTransition">false</bool>
+</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..be1f081
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+    <!-- If true, attach the navigation bar to the app during app transition -->
+    <bool name="config_attachNavBarToAppDuringTransition">false</bool>
+</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..be1f081
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+    <!-- If true, attach the navigation bar to the app during app transition -->
+    <bool name="config_attachNavBarToAppDuringTransition">false</bool>
+</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..be1f081
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+    <!-- If true, attach the navigation bar to the app during app transition -->
+    <bool name="config_attachNavBarToAppDuringTransition">false</bool>
+</resources>
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 7fcef9c..155c523 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -59,6 +59,7 @@
 import android.hardware.camera2.extension.SizeList;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
+import android.hardware.camera2.utils.SurfaceUtils;
 import android.media.Image;
 import android.media.ImageReader;
 import android.os.Binder;
@@ -131,7 +132,7 @@
 
     private static final String CAMERA_EXTENSION_VERSION_NAME =
             "androidx.camera.extensions.impl.ExtensionVersionImpl";
-    private static final String LATEST_VERSION = "1.4.0";
+    private static final String LATEST_VERSION = "1.5.0";
     // No support for the init sequence
     private static final String NON_INIT_VERSION_PREFIX = "1.0";
     // Support advanced API and latency queries
@@ -1691,11 +1692,20 @@
         private final Surface mSurface;
         private final Size mSize;
         private final int mImageFormat;
+        private final int mDataspace;
+        private final long mUsage;
 
         public OutputSurfaceImplStub(OutputSurface outputSurface) {
             mSurface = outputSurface.surface;
             mSize = new Size(outputSurface.size.width, outputSurface.size.height);
             mImageFormat = outputSurface.imageFormat;
+            if (mSurface != null) {
+                mDataspace = SurfaceUtils.getSurfaceDataspace(mSurface);
+                mUsage = SurfaceUtils.getSurfaceUsage(mSurface);
+            } else {
+                mDataspace = -1;
+                mUsage = 0;
+            }
         }
 
         @Override
@@ -1712,6 +1722,16 @@
         public int getImageFormat() {
             return mImageFormat;
         }
+
+        @Override
+        public int getDataspace() {
+            return mDataspace;
+        }
+
+        @Override
+        public long getUsage() {
+            return mUsage;
+        }
     }
 
     private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub implements
@@ -2459,6 +2479,11 @@
             ret.size.height = imageReaderOutputConfig.getSize().getHeight();
             ret.imageFormat = imageReaderOutputConfig.getImageFormat();
             ret.capacity = imageReaderOutputConfig.getMaxImages();
+            if (EFV_SUPPORTED) {
+                ret.usage = imageReaderOutputConfig.getUsage();
+            } else {
+                ret.usage = 0;
+            }
         } else if (output instanceof MultiResolutionImageReaderOutputConfigImpl) {
             MultiResolutionImageReaderOutputConfigImpl multiResReaderConfig =
                     (MultiResolutionImageReaderOutputConfigImpl) output;
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index b403a7f..7f542d1 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -408,5 +408,9 @@
     // Notify the user about external display events related to screenshot.
     // Package: com.android.systemui
     NOTE_GLOBAL_SCREENSHOT_EXTERNAL_DISPLAY = 1008;
+
+    // Notify the user that accessibility floating menu is hidden.
+    // Package: com.android.systemui
+    NOTE_A11Y_FLOATING_MENU_HIDDEN = 1009;
   }
 }
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index e013a3e..1ac69f6 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -30,6 +30,9 @@
         "junit-src/**/*.java",
         "junit-impl-src/**/*.java",
     ],
+    static_libs: [
+        "androidx.test.monitor-for-device",
+    ],
     libs: [
         "framework-minus-apex.ravenwood",
         "junit",
@@ -61,3 +64,17 @@
         "core-xml-for-host",
     ],
 }
+
+java_host_for_device {
+    name: "androidx.test.monitor-for-device",
+    libs: [
+        "androidx.test.monitor-for-host",
+    ],
+}
+
+java_device_for_host {
+    name: "androidx.test.monitor-for-host",
+    libs: [
+        "androidx.test.monitor",
+    ],
+}
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index f5e4af5..16f99e9 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -6,6 +6,9 @@
 # Keep all feature flag implementations
 class :feature_flags stubclass
 
+# Keep all sysprops generated code implementations
+class :sysprops stubclass
+
 # Collections
 class android.util.ArrayMap stubclass
 class android.util.ArraySet stubclass
@@ -112,6 +115,12 @@
 class android.os.PatternMatcher stubclass
 class android.os.ParcelUuid stubclass
 
+# Logging related interfaces from modules-utils
+class com.android.internal.logging.InstanceId stubclass
+class com.android.internal.logging.InstanceIdSequence stubclass
+class com.android.internal.logging.UiEvent stubclass
+class com.android.internal.logging.UiEventLogger stubclass
+
 # XML
 class com.android.internal.util.XmlPullParserWrapper stubclass
 class com.android.internal.util.XmlSerializerWrapper stubclass
@@ -129,6 +138,9 @@
 class android.net.Uri stubclass
 class android.net.UriCodec stubclass
 
+# Telephony
+class android.telephony.PinResult stubclass
+
 # Just enough to support mocking, no further functionality
 class android.content.Context stub
     method <init> ()V stub
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index eacdc2f..a797b1d 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -16,21 +16,46 @@
 
 package android.platform.test.ravenwood;
 
+import android.app.Instrumentation;
+import android.os.Bundle;
 import android.os.HandlerThread;
 import android.os.Looper;
 
-import java.util.Objects;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.io.PrintStream;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
 
 public class RavenwoodRuleImpl {
     private static final String MAIN_THREAD_NAME = "RavenwoodMain";
 
-    public static boolean isUnderRavenwood() {
+    /**
+     * When enabled, attempt to dump all thread stacks just before we hit the
+     * overall Tradefed timeout, to aid in debugging deadlocks.
+     */
+    private static final boolean ENABLE_TIMEOUT_STACKS = false;
+    private static final int TIMEOUT_MILLIS = 9_000;
+
+    private static final ScheduledExecutorService sTimeoutExecutor =
+            Executors.newScheduledThreadPool(1);
+
+    private static ScheduledFuture<?> sPendingTimeout;
+
+    public static boolean isOnRavenwood() {
         return true;
     }
 
     public static void init(RavenwoodRule rule) {
         android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
         android.os.Binder.init$ravenwood();
+        android.os.SystemProperties.init$ravenwood(
+                rule.mSystemProperties.getValues(),
+                rule.mSystemProperties.getKeyReadablePredicate(),
+                rule.mSystemProperties.getKeyWritablePredicate());
 
         com.android.server.LocalServices.removeAllServicesForTest();
 
@@ -39,9 +64,22 @@
             main.start();
             Looper.setMainLooperForTest(main.getLooper());
         }
+
+        InstrumentationRegistry.registerInstance(new Instrumentation(), Bundle.EMPTY);
+
+        if (ENABLE_TIMEOUT_STACKS) {
+            sPendingTimeout = sTimeoutExecutor.schedule(RavenwoodRuleImpl::dumpStacks,
+                    TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        }
     }
 
     public static void reset(RavenwoodRule rule) {
+        if (ENABLE_TIMEOUT_STACKS) {
+            sPendingTimeout.cancel(false);
+        }
+
+        InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
+
         if (rule.mProvideMainThread) {
             Looper.getMainLooper().quit();
             Looper.clearMainLooperForTest();
@@ -49,7 +87,23 @@
 
         com.android.server.LocalServices.removeAllServicesForTest();
 
-        android.os.Process.reset$ravenwood();
+        android.os.SystemProperties.reset$ravenwood();
         android.os.Binder.reset$ravenwood();
+        android.os.Process.reset$ravenwood();
+    }
+
+    private static void dumpStacks() {
+        final PrintStream out = System.err;
+        out.println("-----BEGIN ALL THREAD STACKS-----");
+        final Map<Thread, StackTraceElement[]> stacks = Thread.getAllStackTraces();
+        for (Map.Entry<Thread, StackTraceElement[]> stack : stacks.entrySet()) {
+            out.println();
+            Thread t = stack.getKey();
+            out.println(t.toString() + " ID=" + t.getId());
+            for (StackTraceElement e : stack.getValue()) {
+                out.println("\tat " + e);
+            }
+        }
+        out.println("-----END ALL THREAD STACKS-----");
     }
 }
diff --git a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnRavenwood.java
new file mode 100644
index 0000000..4bf0980
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnRavenwood.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Tests marked with this annotation are disabled when running under a Ravenwood test environment.
+ *
+ * A more specific method-level annotation always takes precedence over any class-level
+ * annotation, and an {@link EnabledOnRavenwood} annotation always takes precedence over
+ * an {@link DisabledOnRavenwood} annotation.
+ *
+ * This annotation only takes effect when the containing class has a {@code
+ * RavenwoodRule} or {@code RavenwoodClassRule} configured. Ignoring is accomplished by
+ * throwing an {@code org.junit.AssumptionViolatedException} which test infrastructure treats as
+ * being ignored.
+ *
+ * This annotation has no effect on any other non-Ravenwood test environments.
+ *
+ * @hide
+ */
+@Inherited
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DisabledOnRavenwood {
+}
diff --git a/ravenwood/junit-src/android/platform/test/annotations/EnabledOnRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/EnabledOnRavenwood.java
new file mode 100644
index 0000000..9dd0a58
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/annotations/EnabledOnRavenwood.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Tests marked with this annotation are enabled when running under a Ravenwood test environment.
+ *
+ * A more specific method-level annotation always takes precedence over any class-level
+ * annotation, and an {@link EnabledOnRavenwood} annotation always takes precedence over
+ * an {@link DisabledOnRavenwood} annotation.
+ *
+ * This annotation only takes effect when the containing class has a {@code
+ * RavenwoodRule} or {@code RavenwoodClassRule} configured. Ignoring is accomplished by
+ * throwing an {@code org.junit.AssumptionViolatedException} which test infrastructure treats as
+ * being ignored.
+ *
+ * This annotation has no effect on any other non-Ravenwood test environments.
+ *
+ * @hide
+ */
+@Inherited
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnabledOnRavenwood {
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
new file mode 100644
index 0000000..8d76970
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.ravenwood;
+
+import static android.platform.test.ravenwood.RavenwoodRule.ENABLE_PROBE_IGNORED;
+import static android.platform.test.ravenwood.RavenwoodRule.IS_ON_RAVENWOOD;
+import static android.platform.test.ravenwood.RavenwoodRule.shouldEnableOnRavenwood;
+import static android.platform.test.ravenwood.RavenwoodRule.shouldStillIgnoreInProbeIgnoreMode;
+
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.annotations.EnabledOnRavenwood;
+
+import org.junit.Assume;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * {@code @ClassRule} that respects Ravenwood-specific class annotations. This rule has no effect
+ * when tests are run on non-Ravenwood test environments.
+ *
+ * By default, all tests are executed on Ravenwood, but annotations such as
+ * {@link DisabledOnRavenwood} and {@link EnabledOnRavenwood} can be used at both the method
+ * and class level to "ignore" tests that may not be ready.
+ */
+public class RavenwoodClassRule implements TestRule {
+    @Override
+    public Statement apply(Statement base, Description description) {
+        // No special treatment when running outside Ravenwood; run tests as-is
+        if (!IS_ON_RAVENWOOD) {
+            return base;
+        }
+
+        if (ENABLE_PROBE_IGNORED) {
+            Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description));
+            // Pass through to possible underlying RavenwoodRule for both environment
+            // configuration and handling method-level annotations
+            return base;
+        } else {
+            return new Statement() {
+                @Override
+                public void evaluate() throws Throwable {
+                    Assume.assumeTrue(shouldEnableOnRavenwood(description));
+                    // Pass through to possible underlying RavenwoodRule for both environment
+                    // configuration and handling method-level annotations
+                    base.evaluate();
+                }
+            };
+        }
+    }
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 513c095..1e7cbf6 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -18,6 +18,8 @@
 
 import static org.junit.Assert.fail;
 
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.annotations.EnabledOnRavenwood;
 import android.platform.test.annotations.IgnoreUnderRavenwood;
 
 import org.junit.Assume;
@@ -25,32 +27,79 @@
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Pattern;
 
 /**
- * THIS RULE IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
- * QUESTIONS ABOUT IT.
+ * {@code @Rule} that configures the Ravenwood test environment. This rule has no effect when
+ * tests are run on non-Ravenwood test environments.
  *
- * @hide
+ * This rule initializes and resets the Ravenwood environment between each test method to offer a
+ * hermetic testing environment.
+ *
+ * By default, all tests are executed on Ravenwood, but annotations such as
+ * {@link DisabledOnRavenwood} and {@link EnabledOnRavenwood} can be used at both the method
+ * and class level to "ignore" tests that may not be ready. When needed, a
+ * {@link RavenwoodClassRule} can be used in addition to a {@link RavenwoodRule} to ignore tests
+ * before a test class is fully initialized.
  */
 public class RavenwoodRule implements TestRule {
-    private static AtomicInteger sNextPid = new AtomicInteger(100);
-
-    private static final boolean IS_UNDER_RAVENWOOD = RavenwoodRuleImpl.isUnderRavenwood();
+    static final boolean IS_ON_RAVENWOOD = RavenwoodRuleImpl.isOnRavenwood();
 
     /**
-     * When probing is enabled, all tests will be unconditionally run under Ravenwood to detect
+     * When probing is enabled, all tests will be unconditionally run on Ravenwood to detect
      * cases where a test is able to pass despite being marked as {@code IgnoreUnderRavenwood}.
      *
      * This is typically helpful for internal maintainers discovering tests that had previously
      * been ignored, but now have enough Ravenwood-supported functionality to be enabled.
      */
-    private static final boolean ENABLE_PROBE_IGNORED = false; // DO NOT SUBMIT WITH TRUE
+    static final boolean ENABLE_PROBE_IGNORED = "1".equals(
+            System.getenv("RAVENWOOD_RUN_DISABLED_TESTS"));
+
+    /**
+     * When using ENABLE_PROBE_IGNORED, you may still want to skip certain tests,
+     * for example because the test would crash the JVM.
+     *
+     * This regex defines the tests that should still be disabled even if ENABLE_PROBE_IGNORED
+     * is set.
+     *
+     * Before running each test class and method, we check if this pattern can be found in
+     * the full test name (either [class full name], or [class full name] + "#" + [method name]),
+     * and if so, we skip it.
+     *
+     * For example, if you want to skip an entire test class, use:
+     * RAVENWOOD_REALLY_DISABLE='\.CustomTileDefaultsRepositoryTest$'
+     *
+     * For example, if you want to skip an entire test class, use:
+     * RAVENWOOD_REALLY_DISABLE='\.CustomTileDefaultsRepositoryTest#testSimple$'
+     *
+     * To ignore multiple classes, use (...|...), for example:
+     * RAVENWOOD_REALLY_DISABLE='\.(ClassA|ClassB)$'
+     *
+     * Because we use a regex-find, setting "." would disable all tests.
+     */
+    private static final Pattern REALLY_DISABLE_PATTERN = Pattern.compile(
+            Objects.requireNonNullElse(System.getenv("RAVENWOOD_REALLY_DISABLE"), ""));
+
+    private static final boolean ENABLE_REALLY_DISABLE_PATTERN =
+            !REALLY_DISABLE_PATTERN.pattern().isEmpty();
+
+    static {
+        if (ENABLE_PROBE_IGNORED) {
+            System.out.println("$RAVENWOOD_RUN_DISABLED_TESTS enabled: force running all tests");
+            if (ENABLE_REALLY_DISABLE_PATTERN) {
+                System.out.println("$RAVENWOOD_REALLY_DISABLE=" + REALLY_DISABLE_PATTERN.pattern());
+            }
+        }
+    }
 
     private static final int SYSTEM_UID = 1000;
     private static final int NOBODY_UID = 9999;
     private static final int FIRST_APPLICATION_UID = 10000;
 
+    private static final AtomicInteger sNextPid = new AtomicInteger(100);
+
     /**
      * Unless the test author requests differently, run as "nobody", and give each collection of
      * tests its own unique PID.
@@ -60,6 +109,8 @@
 
     boolean mProvideMainThread = false;
 
+    final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
+
     public RavenwoodRule() {
     }
 
@@ -71,7 +122,7 @@
 
         /**
          * Configure the identity of this process to be the system UID for the duration of the
-         * test. Has no effect under non-Ravenwood environments.
+         * test. Has no effect on non-Ravenwood environments.
          */
         public Builder setProcessSystem() {
             mRule.mUid = SYSTEM_UID;
@@ -80,7 +131,7 @@
 
         /**
          * Configure the identity of this process to be an app UID for the duration of the
-         * test. Has no effect under non-Ravenwood environments.
+         * test. Has no effect on non-Ravenwood environments.
          */
         public Builder setProcessApp() {
             mRule.mUid = FIRST_APPLICATION_UID;
@@ -89,41 +140,130 @@
 
         /**
          * Configure a "main" thread to be available for the duration of the test, as defined
-         * by {@code Looper.getMainLooper()}. Has no effect under non-Ravenwood environments.
+         * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments.
          */
         public Builder setProvideMainThread(boolean provideMainThread) {
             mRule.mProvideMainThread = provideMainThread;
             return this;
         }
 
+        /**
+         * Configure the given system property as immutable for the duration of the test.
+         * Read access to the key is allowed, and write access will fail. When {@code value} is
+         * {@code null}, the value is left as undefined.
+         *
+         * All properties in the {@code debug.*} namespace are automatically mutable, with no
+         * developer action required.
+         *
+         * Has no effect on non-Ravenwood environments.
+         */
+        public Builder setSystemPropertyImmutable(/* @NonNull */ String key,
+                /* @Nullable */ Object value) {
+            mRule.mSystemProperties.setValue(key, value);
+            mRule.mSystemProperties.setAccessReadOnly(key);
+            return this;
+        }
+
+        /**
+         * Configure the given system property as mutable for the duration of the test.
+         * Both read and write access to the key is allowed, and its value will be reset between
+         * each test. When {@code value} is {@code null}, the value is left as undefined.
+         *
+         * All properties in the {@code debug.*} namespace are automatically mutable, with no
+         * developer action required.
+         *
+         * Has no effect on non-Ravenwood environments.
+         */
+        public Builder setSystemPropertyMutable(/* @NonNull */ String key,
+                /* @Nullable */ Object value) {
+            mRule.mSystemProperties.setValue(key, value);
+            mRule.mSystemProperties.setAccessReadWrite(key);
+            return this;
+        }
+
         public RavenwoodRule build() {
             return mRule;
         }
     }
 
     /**
-     * Return if the current process is running under a Ravenwood test environment.
+     * @deprecated replaced by {@link #isOnRavenwood()}
      */
+    @Deprecated
     public static boolean isUnderRavenwood() {
-        return IS_UNDER_RAVENWOOD;
+        return IS_ON_RAVENWOOD;
     }
 
     /**
-     * Test if the given {@link Description} has been marked with an {@link IgnoreUnderRavenwood}
-     * annotation, either at the method or class level.
+     * Return if the current process is running on a Ravenwood test environment.
      */
-    private static boolean hasIgnoreUnderRavenwoodAnnotation(Description description) {
-        if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
+    public static boolean isOnRavenwood() {
+        return IS_ON_RAVENWOOD;
+    }
+
+    /**
+     * Determine if the given {@link Description} should be enabled when running on the
+     * Ravenwood test environment.
+     *
+     * A more specific method-level annotation always takes precedence over any class-level
+     * annotation, and an {@link EnabledOnRavenwood} annotation always takes precedence over
+     * an {@link DisabledOnRavenwood} annotation.
+     */
+    static boolean shouldEnableOnRavenwood(Description description) {
+        // First, consult any method-level annotations
+        if (description.isTest()) {
+            // Stopgap for http://g/ravenwood/EPAD-N5ntxM
+            if (description.getMethodName().endsWith("$noRavenwood")) {
+                return false;
+            }
+            if (description.getAnnotation(EnabledOnRavenwood.class) != null) {
+                return true;
+            }
+            if (description.getAnnotation(DisabledOnRavenwood.class) != null) {
+                return false;
+            }
+            if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
+                return false;
+            }
+        }
+
+        // Otherwise, consult any class-level annotations
+        if (description.getTestClass().getAnnotation(EnabledOnRavenwood.class) != null) {
             return true;
-        } else if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
-            return true;
-        } else {
+        }
+        if (description.getTestClass().getAnnotation(DisabledOnRavenwood.class) != null) {
             return false;
         }
+        if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
+            return false;
+        }
+
+        // When no annotations have been requested, assume test should be included
+        return true;
+    }
+
+    static boolean shouldStillIgnoreInProbeIgnoreMode(Description description) {
+        if (!ENABLE_REALLY_DISABLE_PATTERN) {
+            return false;
+        }
+
+        final var fullname = description.getTestClass().getName()
+                + (description.isTest() ? "#" + description.getMethodName() : "");
+
+        if (REALLY_DISABLE_PATTERN.matcher(fullname).find()) {
+            System.out.println("Still ignoring " + fullname);
+            return true;
+        }
+        return false;
     }
 
     @Override
     public Statement apply(Statement base, Description description) {
+        // No special treatment when running outside Ravenwood; run tests as-is
+        if (!IS_ON_RAVENWOOD) {
+            return base;
+        }
+
         if (ENABLE_PROBE_IGNORED) {
             return applyProbeIgnored(base, description);
         } else {
@@ -138,9 +278,7 @@
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
-                if (hasIgnoreUnderRavenwoodAnnotation(description)) {
-                    Assume.assumeFalse(IS_UNDER_RAVENWOOD);
-                }
+                Assume.assumeTrue(shouldEnableOnRavenwood(description));
 
                 RavenwoodRuleImpl.init(RavenwoodRule.this);
                 try {
@@ -154,30 +292,29 @@
 
     /**
      * Run the given {@link Statement} with probing enabled. All tests will be unconditionally
-     * run under Ravenwood to detect cases where a test is able to pass despite being marked as
+     * run on Ravenwood to detect cases where a test is able to pass despite being marked as
      * {@code IgnoreUnderRavenwood}.
      */
     private Statement applyProbeIgnored(Statement base, Description description) {
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
+                Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description));
                 RavenwoodRuleImpl.init(RavenwoodRule.this);
                 try {
                     base.evaluate();
                 } catch (Throwable t) {
-                    if (hasIgnoreUnderRavenwoodAnnotation(description)) {
-                        // This failure is expected, so eat the exception and report the
-                        // assumption failure that test authors expect
-                        Assume.assumeFalse(IS_UNDER_RAVENWOOD);
-                    }
+                    // If the test isn't included, eat the exception and report the
+                    // assumption failure that test authors expect; otherwise throw
+                    Assume.assumeTrue(shouldEnableOnRavenwood(description));
                     throw t;
                 } finally {
                     RavenwoodRuleImpl.reset(RavenwoodRule.this);
                 }
 
-                if (hasIgnoreUnderRavenwoodAnnotation(description) && IS_UNDER_RAVENWOOD) {
-                    fail("Test was annotated with IgnoreUnderRavenwood, but it actually "
-                            + "passed under Ravenwood; consider removing the annotation");
+                if (!shouldEnableOnRavenwood(description)) {
+                    fail("Test wasn't included under Ravenwood, but it actually "
+                            + "passed under Ravenwood; consider updating annotations");
                 }
             }
         };
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
new file mode 100644
index 0000000..85ad4e4
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.ravenwood;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+class RavenwoodSystemProperties {
+    private final Map<String, String> mValues = new HashMap<>();
+
+    /** Set of additional keys that should be considered readable */
+    private final Set<String> mKeyReadable = new HashSet<>();
+    private final Predicate<String> mKeyReadablePredicate = (key) -> {
+        final String root = getKeyRoot(key);
+
+        if (root.startsWith("debug.")) return true;
+
+        // This set is carefully curated to help identify situations where a test may
+        // accidentally depend on a default value of an obscure property whose owner hasn't
+        // decided how Ravenwood should behave.
+        if (root.startsWith("boot.")) return true;
+        if (root.startsWith("build.")) return true;
+        if (root.startsWith("product.")) return true;
+        if (root.startsWith("soc.")) return true;
+        if (root.startsWith("system.")) return true;
+
+        switch (key) {
+            case "gsm.version.baseband":
+            case "no.such.thing":
+            case "ro.bootloader":
+            case "ro.debuggable":
+            case "ro.hardware":
+            case "ro.hw_timeout_multiplier":
+            case "ro.odm.build.media_performance_class":
+            case "ro.treble.enabled":
+            case "ro.vndk.version":
+                return true;
+        }
+
+        return mKeyReadable.contains(key);
+    };
+
+    /** Set of additional keys that should be considered writable */
+    private final Set<String> mKeyWritable = new HashSet<>();
+    private final Predicate<String> mKeyWritablePredicate = (key) -> {
+        final String root = getKeyRoot(key);
+
+        if (root.startsWith("debug.")) return true;
+
+        return mKeyWritable.contains(key);
+    };
+
+    public RavenwoodSystemProperties() {
+        // TODO: load these values from build.prop generated files
+        setValueForPartitions("product.brand", "Android");
+        setValueForPartitions("product.device", "Ravenwood");
+        setValueForPartitions("product.manufacturer", "Android");
+        setValueForPartitions("product.model", "Ravenwood");
+        setValueForPartitions("product.name", "Ravenwood");
+
+        setValueForPartitions("product.cpu.abilist", "x86_64");
+        setValueForPartitions("product.cpu.abilist32", "");
+        setValueForPartitions("product.cpu.abilist64", "x86_64");
+
+        setValueForPartitions("build.date", "Thu Jan 01 00:00:00 GMT 2024");
+        setValueForPartitions("build.date.utc", "1704092400");
+        setValueForPartitions("build.id", "MAIN");
+        setValueForPartitions("build.tags", "dev-keys");
+        setValueForPartitions("build.type", "userdebug");
+        setValueForPartitions("build.version.all_codenames", "REL");
+        setValueForPartitions("build.version.codename", "REL");
+        setValueForPartitions("build.version.incremental", "userdebug.ravenwood.20240101");
+        setValueForPartitions("build.version.known_codenames", "REL");
+        setValueForPartitions("build.version.release", "14");
+        setValueForPartitions("build.version.release_or_codename", "VanillaIceCream");
+        setValueForPartitions("build.version.sdk", "34");
+
+        setValue("ro.board.first_api_level", "1");
+        setValue("ro.product.first_api_level", "1");
+
+        setValue("ro.soc.manufacturer", "Android");
+        setValue("ro.soc.model", "Ravenwood");
+
+        setValue("ro.debuggable", "1");
+    }
+
+    Map<String, String> getValues() {
+        return new HashMap<>(mValues);
+    }
+
+    Predicate<String> getKeyReadablePredicate() {
+        return mKeyReadablePredicate;
+    }
+
+    Predicate<String> getKeyWritablePredicate() {
+        return mKeyWritablePredicate;
+    }
+
+    private static final String[] PARTITIONS = {
+            "bootimage",
+            "odm",
+            "product",
+            "system",
+            "system_ext",
+            "vendor",
+            "vendor_dlkm",
+    };
+
+    /**
+     * Set the given property for all possible partitions where it could be defined. For
+     * example, the value of {@code ro.build.type} is typically also mirrored under
+     * {@code ro.system.build.type}, etc.
+     */
+    private void setValueForPartitions(String key, String value) {
+        setValue("ro." + key, value);
+        for (String partition : PARTITIONS) {
+            setValue("ro." + partition + "." + key, value);
+        }
+    }
+
+    public void setValue(String key, Object value) {
+        final String valueString = (value == null) ? null : String.valueOf(value);
+        if ((valueString == null) || valueString.isEmpty()) {
+            mValues.remove(key);
+        } else {
+            mValues.put(key, valueString);
+        }
+    }
+
+    public void setAccessNone(String key) {
+        mKeyReadable.remove(key);
+        mKeyWritable.remove(key);
+    }
+
+    public void setAccessReadOnly(String key) {
+        mKeyReadable.add(key);
+        mKeyWritable.remove(key);
+    }
+
+    public void setAccessReadWrite(String key) {
+        mKeyReadable.add(key);
+        mKeyWritable.add(key);
+    }
+
+    /**
+     * Return the "root" of the given property key, stripping away any modifier prefix such as
+     * {@code ro.} or {@code persist.}.
+     */
+    private static String getKeyRoot(String key) {
+        if (key.startsWith("ro.")) {
+            return key.substring(3);
+        } else if (key.startsWith("persist.")) {
+            return key.substring(8);
+        } else {
+            return key;
+        }
+    }
+}
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 0ff6a1a..d0c2e18 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -17,7 +17,7 @@
 package android.platform.test.ravenwood;
 
 public class RavenwoodRuleImpl {
-    public static boolean isUnderRavenwood() {
+    public static boolean isOnRavenwood() {
         return false;
     }
 
diff --git a/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java b/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
index 085c186..7abfecf 100644
--- a/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
+++ b/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
@@ -42,4 +42,9 @@
     public void testIgnored() {
         throw new RuntimeException("Shouldn't be executed under ravenwood");
     }
+
+    @Test
+    public void testIgnored$noRavenwood() {
+        throw new RuntimeException("Shouldn't be executed under ravenwood");
+    }
 }
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
index 36fa3dd..364a86a 100644
--- a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
+++ b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
@@ -22,7 +22,6 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.ravenwood.RavenwoodRule;
 
 import org.junit.Rule;
@@ -62,31 +61,7 @@
         assertThat(object.exitValue()).isEqualTo(42);
     }
 
-    /*
- - Intent can't be mocked because of the dependency to `org.xmlpull.v1.XmlPullParser`.
-   (The error says "Mockito can only mock non-private & non-final classes", but that's likely a
-   red-herring.)
-
-STACKTRACE:
-org.mockito.exceptions.base.MockitoException:
-Mockito cannot mock this class: class android.content.Intent.
-
-  :
-
-Underlying exception : java.lang.IllegalArgumentException: Could not create type
-    at com.android.ravenwood.mockito.RavenwoodMockitoTest.testMockAndroidClass1
-    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
-
-  :
-
-Caused by: java.lang.ClassNotFoundException: org.xmlpull.v1.XmlPullParser
-    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
-    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
-    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
-    ... 54 more
-     */
     @Test
-    @IgnoreUnderRavenwood
     public void testMockAndroidClass1() {
         Intent object = mock(Intent.class);
 
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 491ed22..b775f9a 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -1,6 +1,10 @@
 # Only classes listed here can use the Ravenwood annotations.
 
+com.android.internal.display.BrightnessSynchronizer
 com.android.internal.util.ArrayUtils
+com.android.internal.logging.MetricsLogger
+com.android.internal.logging.testing.FakeMetricsLogger
+com.android.internal.logging.testing.UiEventLoggerFake
 com.android.internal.os.BatteryStatsHistory
 com.android.internal.os.BatteryStatsHistory$TraceDelegate
 com.android.internal.os.BatteryStatsHistory$VarintParceler
@@ -9,8 +13,10 @@
 com.android.internal.os.LongArrayMultiStateCounter
 com.android.internal.os.LongArrayMultiStateCounter$LongArrayContainer
 com.android.internal.os.MonotonicClock
+com.android.internal.os.PowerProfile
 com.android.internal.os.PowerStats
 com.android.internal.os.PowerStats$Descriptor
+com.android.internal.power.ModemPowerProfile
 
 android.util.AtomicFile
 android.util.DataUnit
@@ -26,20 +32,27 @@
 android.util.MonthDisplayHelper
 android.util.RecurrenceRule
 android.util.RotationUtils
+android.util.Singleton
 android.util.Slog
 android.util.SparseDoubleArray
 android.util.SparseSetArray
 android.util.TimeUtils
 android.util.Xml
 
+android.os.AggregateBatteryConsumer
 android.os.BatteryConsumer
+android.os.BatteryStats
 android.os.BatteryStats$HistoryItem
 android.os.BatteryStats$HistoryStepDetails
 android.os.BatteryStats$HistoryTag
+android.os.BatteryStats$LongCounter
 android.os.BatteryStats$ProcessStateChange
+android.os.BatteryUsageStats
+android.os.BatteryUsageStatsQuery
 android.os.Binder
 android.os.Binder$IdentitySupplier
 android.os.Broadcaster
+android.os.Build
 android.os.BundleMerger
 android.os.ConditionVariable
 android.os.FileUtils
@@ -54,12 +67,19 @@
 android.os.PackageTagsList
 android.os.Parcel
 android.os.Parcelable
+android.os.PowerComponents
 android.os.Process
 android.os.ServiceSpecificException
 android.os.SystemClock
+android.os.SystemProperties
+android.os.TestLooperManager
 android.os.ThreadLocalWorkSource
 android.os.TimestampedValue
+android.os.Trace
+android.os.UidBatteryConsumer
+android.os.UidBatteryConsumer$Builder
 android.os.UserHandle
+android.os.UserManager
 android.os.WorkSource
 
 android.content.ClipData
@@ -73,16 +93,22 @@
 android.content.IntentFilter
 android.content.UriMatcher
 
-android.content.pm.PackageInfo
-android.content.pm.ApplicationInfo
-android.content.pm.PackageItemInfo
-android.content.pm.ComponentInfo
 android.content.pm.ActivityInfo
-android.content.pm.ServiceInfo
+android.content.pm.ApplicationInfo
+android.content.pm.ComponentInfo
+android.content.pm.PackageInfo
+android.content.pm.PackageItemInfo
+android.content.pm.PackageManager$Flags
+android.content.pm.PackageManager$PackageInfoFlags
+android.content.pm.PackageManager$ApplicationInfoFlags
+android.content.pm.PackageManager$ComponentInfoFlags
+android.content.pm.PackageManager$ResolveInfoFlags
 android.content.pm.PathPermission
 android.content.pm.ProviderInfo
 android.content.pm.ResolveInfo
+android.content.pm.ServiceInfo
 android.content.pm.Signature
+android.content.pm.UserInfo
 
 android.database.AbstractCursor
 android.database.CharArrayBuffer
@@ -116,7 +142,16 @@
 
 android.content.ContentProvider
 
+android.app.Instrumentation
+
+android.metrics.LogMaker
+
+android.view.Display$HdrCapabilities
+android.view.Display$Mode
+android.view.DisplayInfo
+
 com.android.server.LocalServices
+com.android.server.power.stats.BatteryStatsImpl
 
 com.android.internal.util.BitUtils
 com.android.internal.util.BitwiseInputStream
diff --git a/services/Android.bp b/services/Android.bp
index 0b484f4..7e8333c 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -148,9 +148,6 @@
 java_library {
     name: "Slogf",
     srcs: ["core/java/com/android/server/utils/Slogf.java"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 // merge all required services into one jar
diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp
index a354671..69cc68a 100644
--- a/services/accessibility/Android.bp
+++ b/services/accessibility/Android.bp
@@ -22,6 +22,7 @@
     lint: {
         error_checks: ["MissingPermissionAnnotation"],
         baseline_filename: "lint-baseline.xml",
+
     },
     srcs: [
         ":services.accessibility-sources",
@@ -50,9 +51,6 @@
     libs: [
         "androidx.annotation_annotation",
     ],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 aconfig_declarations {
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 993b254..44682e2 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -45,6 +45,13 @@
 }
 
 flag {
+    name: "fix_drag_pointer_when_ending_drag"
+    namespace: "accessibility"
+    description: "Send the correct pointer id when transitioning from dragging to delegating states."
+    bug: "300002193"
+}
+
+flag {
     name: "pinch_zoom_zero_min_span"
     namespace: "accessibility"
     description: "Whether to set min span of ScaleGestureDetector to zero."
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 3d8d7b7..57c0539 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -30,10 +30,7 @@
 import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
 import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 import static android.view.accessibility.AccessibilityManager.FlashNotificationReason;
-import static android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
@@ -142,6 +139,7 @@
 import com.android.internal.accessibility.AccessibilityShortcutController;
 import com.android.internal.accessibility.AccessibilityShortcutController.FrameworkFeatureInfo;
 import com.android.internal.accessibility.AccessibilityShortcutController.LaunchableFrameworkFeatureInfo;
+import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
 import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity;
 import com.android.internal.accessibility.util.AccessibilityUtils;
@@ -1721,7 +1719,7 @@
         }
         mMainHandler.sendMessage(obtainMessage(
                 AccessibilityManagerService::performAccessibilityShortcutInternal, this,
-                displayId, ACCESSIBILITY_BUTTON, targetName));
+                displayId, ShortcutConstants.UserShortcutType.SOFTWARE, targetName));
     }
 
     /**
@@ -2200,11 +2198,12 @@
     }
 
     private void showAccessibilityTargetsSelection(int displayId,
-            @ShortcutType int shortcutType) {
+            @ShortcutConstants.UserShortcutType int shortcutType) {
         final Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
-        final String chooserClassName = (shortcutType == ACCESSIBILITY_SHORTCUT_KEY)
-                ? AccessibilityShortcutChooserActivity.class.getName()
-                : AccessibilityButtonChooserActivity.class.getName();
+        final String chooserClassName =
+                (shortcutType == ShortcutConstants.UserShortcutType.HARDWARE)
+                        ? AccessibilityShortcutChooserActivity.class.getName()
+                        : AccessibilityButtonChooserActivity.class.getName();
         intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
         final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
@@ -3275,7 +3274,7 @@
         }
 
         final Set<String> currentTargets =
-                userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
+                userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.HARDWARE);
         if (targetsFromSetting.equals(currentTargets)) {
             return false;
         }
@@ -3291,7 +3290,7 @@
                 userState.mUserId, str -> str, targetsFromSetting);
 
         final Set<String> currentTargets =
-                userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
+                userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.SOFTWARE);
         if (targetsFromSetting.equals(currentTargets)) {
             return false;
         }
@@ -3346,7 +3345,7 @@
      */
     private void updateAccessibilityShortcutKeyTargetsLocked(AccessibilityUserState userState) {
         final Set<String> currentTargets =
-                userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
+                userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.HARDWARE);
         final int lastSize = currentTargets.size();
         if (lastSize == 0) {
             return;
@@ -3531,7 +3530,7 @@
         }
 
         final Set<String> currentTargets =
-                userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
+                userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.SOFTWARE);
         final int lastSize = currentTargets.size();
         if (lastSize == 0) {
             return;
@@ -3571,7 +3570,7 @@
             return;
         }
         final Set<String> buttonTargets =
-                userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
+                userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.SOFTWARE);
         int lastSize = buttonTargets.size();
         buttonTargets.removeIf(name -> {
             if (packageName != null && name != null && !name.contains(packageName)) {
@@ -3608,7 +3607,7 @@
         lastSize = buttonTargets.size();
 
         final Set<String> shortcutKeyTargets =
-                userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
+                userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.HARDWARE);
         userState.mEnabledServices.forEach(componentName -> {
             if (packageName != null && componentName != null
                     && !packageName.equals(componentName.getPackageName())) {
@@ -3665,16 +3664,18 @@
             return;
         }
         final ComponentName serviceName = service.getComponentName();
-        if (userState.removeShortcutTargetLocked(ACCESSIBILITY_SHORTCUT_KEY, serviceName)) {
+        if (userState.removeShortcutTargetLocked(
+                ShortcutConstants.UserShortcutType.HARDWARE, serviceName)) {
             final Set<String> currentTargets = userState.getShortcutTargetsLocked(
-                    ACCESSIBILITY_SHORTCUT_KEY);
+                    ShortcutConstants.UserShortcutType.HARDWARE);
             persistColonDelimitedSetToSettingLocked(
                     Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
                     userState.mUserId, currentTargets, str -> str);
         }
-        if (userState.removeShortcutTargetLocked(ACCESSIBILITY_BUTTON, serviceName)) {
+        if (userState.removeShortcutTargetLocked(
+                ShortcutConstants.UserShortcutType.SOFTWARE, serviceName)) {
             final Set<String> currentTargets = userState.getShortcutTargetsLocked(
-                    ACCESSIBILITY_BUTTON);
+                    ShortcutConstants.UserShortcutType.SOFTWARE);
             persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
                     userState.mUserId, currentTargets, str -> str);
         }
@@ -3750,7 +3751,7 @@
         }
         mMainHandler.sendMessage(obtainMessage(
                 AccessibilityManagerService::performAccessibilityShortcutInternal, this,
-                Display.DEFAULT_DISPLAY, ACCESSIBILITY_SHORTCUT_KEY, targetName));
+                Display.DEFAULT_DISPLAY, ShortcutConstants.UserShortcutType.HARDWARE, targetName));
     }
 
     /**
@@ -3763,7 +3764,7 @@
      *        specified target.
      */
     private void performAccessibilityShortcutInternal(int displayId,
-            @ShortcutType int shortcutType, @Nullable String targetName) {
+            @ShortcutConstants.UserShortcutType int shortcutType, @Nullable String targetName) {
         final List<String> shortcutTargets = getAccessibilityShortcutTargetsInternal(shortcutType);
         if (shortcutTargets.isEmpty()) {
             Slog.d(LOG_TAG, "No target to perform shortcut, shortcutType=" + shortcutType);
@@ -3814,7 +3815,7 @@
     }
 
     private boolean performAccessibilityFrameworkFeature(int displayId,
-            ComponentName assignedTarget, @ShortcutType int shortcutType) {
+            ComponentName assignedTarget, @ShortcutConstants.UserShortcutType int shortcutType) {
         final Map<ComponentName, FrameworkFeatureInfo> frameworkFeatureMap =
                 AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
         if (!frameworkFeatureMap.containsKey(assignedTarget)) {
@@ -3863,12 +3864,12 @@
     /**
      * Perform accessibility service shortcut action.
      *
-     * 1) For {@link AccessibilityManager#ACCESSIBILITY_BUTTON} type and services targeting sdk
+     * 1) For {@link ShortcutConstants.UserShortcutType.SOFTWARE} type and services targeting sdk
      *    version <= Q: callbacks to accessibility service if service is bounded and requests
      *    accessibility button.
-     * 2) For {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY} type and service targeting sdk
+     * 2) For {@link ShortcutConstants.UserShortcutType.HARDWARE} type and service targeting sdk
      *    version <= Q: turns on / off the accessibility service.
-     * 3) For {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY} type and service targeting sdk
+     * 3) For {@link ShortcutConstants.UserShortcutType.HARDWARE} type and service targeting sdk
      *    version > Q and request accessibility button: turn on the accessibility service if it's
      *    not in the enabled state.
      *    (It'll happen when a service is disabled and assigned to shortcut then upgraded.)
@@ -3879,7 +3880,7 @@
      *       button.
      */
     private boolean performAccessibilityShortcutTargetService(int displayId,
-            @ShortcutType int shortcutType, ComponentName assignedTarget) {
+            @ShortcutConstants.UserShortcutType int shortcutType, ComponentName assignedTarget) {
         synchronized (mLock) {
             final AccessibilityUserState userState = getCurrentUserStateLocked();
             final AccessibilityServiceInfo installedServiceInfo =
@@ -3897,7 +3898,8 @@
             final boolean requestA11yButton = (installedServiceInfo.flags
                     & FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
             // Turns on / off the accessibility service
-            if ((targetSdk <= Build.VERSION_CODES.Q && shortcutType == ACCESSIBILITY_SHORTCUT_KEY)
+            if ((targetSdk <= Build.VERSION_CODES.Q
+                    && shortcutType == ShortcutConstants.UserShortcutType.HARDWARE)
                     || (targetSdk > Build.VERSION_CODES.Q && !requestA11yButton)) {
                 if (serviceConnection == null) {
                     logAccessibilityShortcutActivated(mContext, assignedTarget, shortcutType,
@@ -3911,7 +3913,8 @@
                 }
                 return true;
             }
-            if (shortcutType == ACCESSIBILITY_SHORTCUT_KEY && targetSdk > Build.VERSION_CODES.Q
+            if (shortcutType == ShortcutConstants.UserShortcutType.HARDWARE
+                    && targetSdk > Build.VERSION_CODES.Q
                     && requestA11yButton) {
                 if (!userState.getEnabledServicesLocked().contains(assignedTarget)) {
                     enableAccessibilityServiceLocked(assignedTarget, mCurrentUserId);
@@ -3941,7 +3944,8 @@
     }
 
     @Override
-    public List<String> getAccessibilityShortcutTargets(@ShortcutType int shortcutType) {
+    public List<String> getAccessibilityShortcutTargets(
+            @ShortcutConstants.UserShortcutType int shortcutType) {
         if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".getAccessibilityShortcutTargets",
                     FLAGS_ACCESSIBILITY_MANAGER, "shortcutType=" + shortcutType);
@@ -3955,12 +3959,13 @@
         return getAccessibilityShortcutTargetsInternal(shortcutType);
     }
 
-    private List<String> getAccessibilityShortcutTargetsInternal(@ShortcutType int shortcutType) {
+    private List<String> getAccessibilityShortcutTargetsInternal(
+            @ShortcutConstants.UserShortcutType int shortcutType) {
         synchronized (mLock) {
             final AccessibilityUserState userState = getCurrentUserStateLocked();
             final ArrayList<String> shortcutTargets = new ArrayList<>(
                     userState.getShortcutTargetsLocked(shortcutType));
-            if (shortcutType != ACCESSIBILITY_BUTTON) {
+            if (shortcutType != ShortcutConstants.UserShortcutType.SOFTWARE) {
                 return shortcutTargets;
             }
             // Adds legacy a11y services requesting a11y button into the list.
@@ -4413,25 +4418,50 @@
     @Override
     public boolean isAccessibilityServiceWarningRequired(AccessibilityServiceInfo info) {
         mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
+        final ComponentName componentName = info.getComponentName();
 
         // Warning is not required if the service is already enabled.
         synchronized (mLock) {
             final AccessibilityUserState userState = getCurrentUserStateLocked();
-            if (userState.getEnabledServicesLocked().contains(info.getComponentName())) {
+            if (userState.getEnabledServicesLocked().contains(componentName)) {
                 return false;
             }
         }
         // Warning is not required if the service is already assigned to a shortcut.
-        for (int shortcutType : AccessibilityManager.SHORTCUT_TYPES) {
+        for (int shortcutType : ShortcutConstants.USER_SHORTCUT_TYPES) {
             if (getAccessibilityShortcutTargets(shortcutType).contains(
-                    info.getComponentName().flattenToString())) {
+                    componentName.flattenToString())) {
                 return false;
             }
         }
+        // Warning is not required if the service is preinstalled and in the
+        // trustedAccessibilityServices allowlist.
+        if (android.view.accessibility.Flags.skipAccessibilityWarningDialogForTrustedServices()
+                && isAccessibilityServicePreinstalledAndTrusted(info)) {
+            return false;
+        }
+
         // Warning is required by default.
         return true;
     }
 
+    private boolean isAccessibilityServicePreinstalledAndTrusted(AccessibilityServiceInfo info) {
+        final ComponentName componentName = info.getComponentName();
+        final boolean isPreinstalled =
+                info.getResolveInfo().serviceInfo.applicationInfo.isSystemApp();
+        if (isPreinstalled) {
+            final String[] trustedAccessibilityServices =
+                    mContext.getResources().getStringArray(
+                            R.array.config_trustedAccessibilityServices);
+            if (Arrays.stream(trustedAccessibilityServices)
+                    .map(ComponentName::unflattenFromString)
+                    .anyMatch(componentName::equals)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @Override
     public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
@@ -4578,8 +4608,8 @@
     public void onShellCommand(FileDescriptor in, FileDescriptor out,
             FileDescriptor err, String[] args, ShellCallback callback,
             ResultReceiver resultReceiver) {
-        new AccessibilityShellCommand(this, mSystemActionPerformer).exec(this, in, out, err, args,
-                callback, resultReceiver);
+        new AccessibilityShellCommand(this, mSystemActionPerformer)
+                .exec(this, in, out, err, args, callback, resultReceiver);
     }
 
     private final class InteractionBridge {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 68ee780..41165b6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -24,9 +24,6 @@
 import static android.accessibilityservice.AccessibilityService.SHOW_MODE_MASK;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
-import static android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
 
@@ -51,6 +48,7 @@
 
 import com.android.internal.R;
 import com.android.internal.accessibility.AccessibilityShortcutController;
+import com.android.internal.accessibility.common.ShortcutConstants;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -757,13 +755,21 @@
      * @param shortcutType The shortcut type.
      * @return The array set of the strings
      */
-    public ArraySet<String> getShortcutTargetsLocked(@ShortcutType int shortcutType) {
-        if (shortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+    public ArraySet<String> getShortcutTargetsLocked(
+            @ShortcutConstants.UserShortcutType int shortcutType) {
+        if (shortcutType == ShortcutConstants.UserShortcutType.HARDWARE) {
             return mAccessibilityShortcutKeyTargets;
-        } else if (shortcutType == ACCESSIBILITY_BUTTON) {
+        } else if (shortcutType == ShortcutConstants.UserShortcutType.SOFTWARE) {
             return mAccessibilityButtonTargets;
+        } else if ((shortcutType == ShortcutConstants.UserShortcutType.TRIPLETAP
+                && isMagnificationSingleFingerTripleTapEnabledLocked()) || (
+                shortcutType == ShortcutConstants.UserShortcutType.TWO_FINGERS_TRIPLE_TAP
+                        && isMagnificationTwoFingerTripleTapEnabledLocked())) {
+            ArraySet<String> targets = new ArraySet<>();
+            targets.add(MAGNIFICATION_CONTROLLER_NAME);
+            return targets;
         }
-        return null;
+        return new ArraySet<>();
     }
 
     /**
@@ -802,12 +808,22 @@
     /**
      * Removes given shortcut target in the list.
      *
-     * @param shortcutType The shortcut type.
-     * @param target The component name of the shortcut target.
+     * @param shortcutType one of {@link ShortcutConstants.UserShortcutType.HARDWARE} or
+     *                     {@link ShortcutConstants.UserShortcutType.SOFTWARE}. Other types are not
+     *                     implemented.
+     * @param target       The component name of the shortcut target.
      * @return true if the shortcut target is removed.
      */
-    public boolean removeShortcutTargetLocked(@ShortcutType int shortcutType,
+    public boolean removeShortcutTargetLocked(@ShortcutConstants.UserShortcutType int shortcutType,
             ComponentName target) {
+        if (shortcutType == ShortcutConstants.UserShortcutType.TRIPLETAP
+                || shortcutType == ShortcutConstants.UserShortcutType.TWO_FINGERS_TRIPLE_TAP) {
+            throw new UnsupportedOperationException(
+                    "removeShortcutTargetLocked only support shortcut type: "
+                            + "software and hardware for now"
+            );
+        }
+
         return getShortcutTargetsLocked(shortcutType).removeIf(name -> {
             ComponentName componentName;
             if (name == null
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index c418485..3086ce1 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -1466,8 +1466,11 @@
             int policyFlags = mState.getLastReceivedPolicyFlags();
             if (mState.isDragging()) {
                 // Send an event to the end of the drag gesture.
-                mDispatcher.sendMotionEvent(
-                        event, ACTION_UP, rawEvent, ALL_POINTER_ID_BITS, policyFlags);
+                int pointerIdBits = ALL_POINTER_ID_BITS;
+                if (Flags.fixDragPointerWhenEndingDrag()) {
+                    pointerIdBits = 1 << mDraggingPointerId;
+                }
+                mDispatcher.sendMotionEvent(event, ACTION_UP, rawEvent, pointerIdBits, policyFlags);
             }
             mState.startDelegating();
             // Deliver all pointers to the view hierarchy.
diff --git a/services/accessibility/lint-baseline.xml b/services/accessibility/lint-baseline.xml
index 6bec8cf..b808219 100644
--- a/services/accessibility/lint-baseline.xml
+++ b/services/accessibility/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="8.1.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="SimpleManualPermissionEnforcement"
@@ -23,4 +23,4 @@
             column="9"/>
     </issue>
 
-</issues>
+</issues>
\ No newline at end of file
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index cab2d74..5407af7 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -362,6 +362,7 @@
         packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
         packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
         packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        packageFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
         packageFilter.addDataScheme("package");
         mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
                 packageFilter, null, mCallbackHandler);
@@ -402,6 +403,7 @@
         boolean added = false;
         boolean changed = false;
         boolean componentsModified = false;
+        int clearedUid = -1;
 
         final String pkgList[];
         switch (action) {
@@ -416,6 +418,10 @@
             case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
                 pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
                 break;
+            case Intent.ACTION_PACKAGE_DATA_CLEARED:
+                pkgList = null;
+                clearedUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+                break;
             default: {
                 Uri uri = intent.getData();
                 if (uri == null) {
@@ -430,7 +436,7 @@
                 changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
             }
         }
-        if (pkgList == null || pkgList.length == 0) {
+        if ((pkgList == null || pkgList.length == 0) && clearedUid == -1) {
             return;
         }
 
@@ -461,6 +467,8 @@
                         }
                     }
                 }
+            } else if (clearedUid != -1) {
+                componentsModified |= clearPreviewsForUidLocked(clearedUid);
             } else {
                 // If the package is being updated, we'll receive a PACKAGE_ADDED
                 // shortly, otherwise it is removed permanently.
@@ -486,6 +494,19 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private boolean clearPreviewsForUidLocked(int clearedUid) {
+        boolean changed = false;
+        final int providerCount = mProviders.size();
+        for (int i = 0; i < providerCount; i++) {
+            Provider provider = mProviders.get(i);
+            if (provider.id.uid == clearedUid) {
+                changed |= provider.clearGeneratedPreviewsLocked();
+            }
+        }
+        return changed;
+    }
+
     /**
      * Reload all widgets' masked state for the given user and its associated profiles, including
      * due to user not being available and package suspension.
@@ -3904,6 +3925,124 @@
         }
     }
 
+    @Override
+    @Nullable
+    public RemoteViews getWidgetPreview(@NonNull String callingPackage,
+            @NonNull ComponentName providerComponent, int profileId,
+            @AppWidgetProviderInfo.CategoryFlags int widgetCategory) {
+        final int callingUserId = UserHandle.getCallingUserId();
+        if (DEBUG) {
+            Slog.i(TAG, "getWidgetPreview() " + callingUserId);
+        }
+        mSecurityPolicy.enforceCallFromPackage(callingPackage);
+        ensureWidgetCategoryCombinationIsValid(widgetCategory);
+
+        synchronized (mLock) {
+            ensureGroupStateLoadedLocked(profileId);
+            final int providerCount = mProviders.size();
+            for (int i = 0; i < providerCount; i++) {
+                Provider provider = mProviders.get(i);
+                final ComponentName componentName = provider.id.componentName;
+                if (provider.zombie || !providerComponent.equals(componentName)) {
+                    continue;
+                }
+
+                final AppWidgetProviderInfo info = provider.getInfoLocked(mContext);
+                final int providerProfileId = info.getProfile().getIdentifier();
+                if (providerProfileId != profileId) {
+                    continue;
+                }
+
+                // Allow access to this provider if it is from the calling package or the caller has
+                // BIND_APPWIDGET permission.
+                final int callingUid = Binder.getCallingUid();
+                final String providerPackageName = componentName.getPackageName();
+                final boolean providerIsInCallerProfile =
+                        mSecurityPolicy.isProviderInCallerOrInProfileAndWhitelListed(
+                                providerPackageName, providerProfileId);
+                final boolean shouldFilterAppAccess = mPackageManagerInternal.filterAppAccess(
+                        providerPackageName, callingUid, providerProfileId);
+                final boolean providerIsInCallerPackage =
+                        mSecurityPolicy.isProviderInPackageForUid(provider, callingUid,
+                                callingPackage);
+                final boolean hasBindAppWidgetPermission =
+                        mSecurityPolicy.hasCallerBindPermissionOrBindWhiteListedLocked(
+                                callingPackage);
+                if (providerIsInCallerProfile && !shouldFilterAppAccess
+                        && (providerIsInCallerPackage || hasBindAppWidgetPermission)) {
+                    return provider.getGeneratedPreviewLocked(widgetCategory);
+                }
+            }
+        }
+        throw new IllegalArgumentException(
+                providerComponent + " is not a valid AppWidget provider");
+    }
+
+    @Override
+    public void setWidgetPreview(@NonNull ComponentName providerComponent,
+            @AppWidgetProviderInfo.CategoryFlags int widgetCategories,
+            @NonNull RemoteViews preview) {
+        final int userId = UserHandle.getCallingUserId();
+        if (DEBUG) {
+            Slog.i(TAG, "setWidgetPreview() " + userId);
+        }
+
+        // Make sure callers only set previews for their own package.
+        mSecurityPolicy.enforceCallFromPackage(providerComponent.getPackageName());
+
+        ensureWidgetCategoryCombinationIsValid(widgetCategories);
+
+        synchronized (mLock) {
+            ensureGroupStateLoadedLocked(userId);
+
+            final ProviderId providerId = new ProviderId(Binder.getCallingUid(), providerComponent);
+            final Provider provider = lookupProviderLocked(providerId);
+            if (provider == null) {
+                throw new IllegalArgumentException(
+                        providerComponent + " is not a valid AppWidget provider");
+            }
+            provider.setGeneratedPreviewLocked(widgetCategories, preview);
+            scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+        }
+    }
+
+    @Override
+    public void removeWidgetPreview(@NonNull ComponentName providerComponent,
+            @AppWidgetProviderInfo.CategoryFlags int widgetCategories) {
+        final int userId = UserHandle.getCallingUserId();
+        if (DEBUG) {
+            Slog.i(TAG, "removeWidgetPreview() " + userId);
+        }
+
+        // Make sure callers only remove previews for their own package.
+        mSecurityPolicy.enforceCallFromPackage(providerComponent.getPackageName());
+
+        ensureWidgetCategoryCombinationIsValid(widgetCategories);
+        synchronized (mLock) {
+            ensureGroupStateLoadedLocked(userId);
+
+            final ProviderId providerId = new ProviderId(Binder.getCallingUid(), providerComponent);
+            final Provider provider = lookupProviderLocked(providerId);
+            if (provider == null) {
+                throw new IllegalArgumentException(
+                        providerComponent + " is not a valid AppWidget provider");
+            }
+            final boolean changed = provider.removeGeneratedPreviewLocked(widgetCategories);
+            if (changed) scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+        }
+    }
+
+    private static void ensureWidgetCategoryCombinationIsValid(int widgetCategories) {
+        int validCategories = AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN
+                | AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
+                | AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX;
+        int invalid = ~validCategories;
+        if ((widgetCategories & invalid) != 0) {
+            throw new IllegalArgumentException(widgetCategories
+                    + " is not a valid widget category combination");
+        }
+    }
+
     private final class CallbackHandler extends Handler {
         public static final int MSG_NOTIFY_UPDATE_APP_WIDGET = 1;
         public static final int MSG_NOTIFY_PROVIDER_CHANGED = 2;
@@ -4201,6 +4340,12 @@
         ArrayList<Widget> widgets = new ArrayList<>();
         PendingIntent broadcast;
         String infoTag;
+        SparseArray<RemoteViews> generatedPreviews = new SparseArray<>(3);
+        private static final int[] WIDGET_CATEGORY_FLAGS = new int[]{
+                AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN,
+                AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
+                AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX,
+        };
 
         boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
 
@@ -4234,7 +4379,7 @@
             return false;
         }
 
-        @GuardedBy("AppWidgetServiceImpl.mLock")
+        @GuardedBy("this.mLock")
         public AppWidgetProviderInfo getInfoLocked(Context context) {
             if (!mInfoParsed) {
                 // parse
@@ -4250,6 +4395,7 @@
                     }
                     if (newInfo != null) {
                         info = newInfo;
+                        updateGeneratedPreviewCategoriesLocked();
                     }
                 }
                 mInfoParsed = true;
@@ -4279,6 +4425,62 @@
             mInfoParsed = true;
         }
 
+        @GuardedBy("this.mLock")
+        @Nullable
+        public RemoteViews getGeneratedPreviewLocked(
+                @AppWidgetProviderInfo.CategoryFlags int widgetCategories) {
+            for (int i = 0; i < generatedPreviews.size(); i++) {
+                if ((widgetCategories & generatedPreviews.keyAt(i)) != 0) {
+                    return generatedPreviews.valueAt(i);
+                }
+            }
+            return null;
+        }
+
+        @GuardedBy("this.mLock")
+        public void setGeneratedPreviewLocked(
+                @AppWidgetProviderInfo.CategoryFlags int widgetCategories,
+                @NonNull RemoteViews preview) {
+            for (int flag : WIDGET_CATEGORY_FLAGS) {
+                if ((widgetCategories & flag) != 0) {
+                    generatedPreviews.put(flag, preview);
+                }
+            }
+            updateGeneratedPreviewCategoriesLocked();
+        }
+
+        @GuardedBy("this.mLock")
+        public boolean removeGeneratedPreviewLocked(int widgetCategories) {
+            boolean changed = false;
+            for (int flag : WIDGET_CATEGORY_FLAGS) {
+                if ((widgetCategories & flag) != 0) {
+                    changed |= generatedPreviews.removeReturnOld(flag) != null;
+                }
+            }
+            if (changed) {
+                updateGeneratedPreviewCategoriesLocked();
+            }
+            return changed;
+        }
+
+        @GuardedBy("this.mLock")
+        public boolean clearGeneratedPreviewsLocked() {
+            if (generatedPreviews.size() > 0) {
+                generatedPreviews.clear();
+                updateGeneratedPreviewCategoriesLocked();
+                return true;
+            }
+            return false;
+        }
+
+        @GuardedBy("this.mLock")
+        private void updateGeneratedPreviewCategoriesLocked() {
+            info.generatedPreviewCategories = 0;
+            for (int i = 0; i < generatedPreviews.size(); i++) {
+                info.generatedPreviewCategories |= generatedPreviews.keyAt(i);
+            }
+        }
+
         @Override
         public String toString() {
             return "Provider{" + id + (zombie ? " Z" : "") + '}';
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index b5130a1..ced10fb 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -34,3 +34,10 @@
   description: "Mitigation for autofill providers miscalculating view visibility"
   bug: "291795358"
 }
+
+flag {
+  name: "remote_fill_service_use_weak_reference"
+  namespace: "autofill"
+  description: "Use weak reference to address binder leak problem"
+  bug: "307972253"
+}
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index 92e00ee..727721d 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -5,4 +5,11 @@
   namespace: "autofill"
   description: "Guards Autofill Framework against Autofill-Credman integration"
   bug: "296907283"
+}
+
+flag {
+    name: "autofill_credman_integration_phase2"
+    namespace: "autofill"
+    description: "Guards against Autofill-Credman integration phase 2"
+    bug: "320730001"
 }
\ No newline at end of file
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 6d0915b..f914ed5 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -17,6 +17,7 @@
 package com.android.server.autofill;
 
 import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
+import static android.service.autofill.Flags.remoteFillServiceUseWeakReference;
 
 import static com.android.server.autofill.Helper.sVerbose;
 
@@ -30,9 +31,12 @@
 import android.os.ICancellationSignal;
 import android.os.RemoteException;
 import android.service.autofill.AutofillService;
+import android.service.autofill.ConvertCredentialRequest;
+import android.service.autofill.ConvertCredentialResponse;
 import android.service.autofill.FillRequest;
 import android.service.autofill.FillResponse;
 import android.service.autofill.IAutoFillService;
+import android.service.autofill.IConvertCredentialCallback;
 import android.service.autofill.IFillCallback;
 import android.service.autofill.ISaveCallback;
 import android.service.autofill.SaveRequest;
@@ -44,6 +48,7 @@
 import com.android.internal.infra.ServiceConnector;
 import com.android.internal.os.IResultReceiver;
 
+import java.lang.ref.WeakReference;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
@@ -65,6 +70,9 @@
     private final Object mLock = new Object();
     private CompletableFuture<FillResponse> mPendingFillRequest;
     private int mPendingFillRequestId = INVALID_REQUEST_ID;
+    private AtomicReference<IFillCallback> mFillCallback;
+    private AtomicReference<ISaveCallback> mSaveCallback;
+    private AtomicReference<IConvertCredentialCallback> mConvertCredentialCallback;
     private final ComponentName mComponentName;
 
     private final boolean mIsCredentialAutofillService;
@@ -77,13 +85,20 @@
             extends AbstractRemoteService.VultureCallback<RemoteFillService> {
         void onFillRequestSuccess(int requestId, @Nullable FillResponse response,
                 @NonNull String servicePackageName, int requestFlags);
+
         void onFillRequestFailure(int requestId, @Nullable CharSequence message);
+
         void onFillRequestTimeout(int requestId);
+
         void onSaveRequestSuccess(@NonNull String servicePackageName,
                 @Nullable IntentSender intentSender);
+
         // TODO(b/80093094): add timeout here too?
         void onSaveRequestFailure(@Nullable CharSequence message,
                 @NonNull String servicePackageName);
+
+        void onConvertCredentialRequestSuccess(@NonNull ConvertCredentialResponse
+                convertCredentialResponse);
     }
 
     RemoteFillService(Context context, ComponentName componentName, int userId,
@@ -150,6 +165,127 @@
         }
     }
 
+    static class IFillCallbackDelegate extends IFillCallback.Stub {
+        private WeakReference<IFillCallback> mCallbackWeakRef;
+
+        IFillCallbackDelegate(IFillCallback callback) {
+            mCallbackWeakRef = new WeakReference(callback);
+        }
+
+        @Override
+        public void onCancellable(ICancellationSignal cancellation) throws RemoteException {
+            IFillCallback callback = mCallbackWeakRef.get();
+            if (callback != null) {
+                callback.onCancellable(cancellation);
+            }
+        }
+
+        @Override
+        public void onSuccess(FillResponse response) throws RemoteException {
+            IFillCallback callback = mCallbackWeakRef.get();
+            if (callback != null) {
+                callback.onSuccess(response);
+            }
+        }
+
+        @Override
+        public void onFailure(int requestId, CharSequence message) throws RemoteException {
+            IFillCallback callback = mCallbackWeakRef.get();
+            if (callback != null) {
+                callback.onFailure(requestId, message);
+            }
+        }
+    }
+
+    static class ISaveCallbackDelegate extends ISaveCallback.Stub {
+
+        private WeakReference<ISaveCallback> mCallbackWeakRef;
+
+        ISaveCallbackDelegate(ISaveCallback callback) {
+            mCallbackWeakRef = new WeakReference(callback);
+        }
+
+        @Override
+        public void onSuccess(IntentSender intentSender) throws RemoteException {
+            ISaveCallback callback = mCallbackWeakRef.get();
+            if (callback != null) {
+                callback.onSuccess(intentSender);
+            }
+        }
+
+        @Override
+        public void onFailure(CharSequence message) throws RemoteException {
+            ISaveCallback callback = mCallbackWeakRef.get();
+            if (callback != null) {
+                callback.onFailure(message);
+            }
+        }
+    }
+
+    static class IConvertCredentialCallbackDelegate extends IConvertCredentialCallback.Stub {
+
+        private WeakReference<IConvertCredentialCallback> mCallbackWeakRef;
+
+        IConvertCredentialCallbackDelegate(IConvertCredentialCallback callback) {
+            mCallbackWeakRef = new WeakReference(callback);
+        }
+
+        @Override
+        public void onSuccess(ConvertCredentialResponse convertCredentialResponse)
+                throws RemoteException {
+            IConvertCredentialCallback callback = mCallbackWeakRef.get();
+            if (callback != null) {
+                callback.onSuccess(convertCredentialResponse);
+            }
+        }
+
+        @Override
+        public void onFailure(CharSequence message) throws RemoteException {
+            IConvertCredentialCallback callback = mCallbackWeakRef.get();
+            if (callback != null) {
+                callback.onFailure(message);
+            }
+        }
+    }
+
+    /**
+     * Wraps an {@link IFillCallback} object using weak reference.
+     *
+     * This prevents lingering allocation issue by breaking the chain of strong references from
+     * Binder to {@link callback}. Since {@link callback} is not held by Binder anymore, it needs
+     * to be held by {@link mFillCallback} so it's not deallocated prematurely.
+     */
+    private IFillCallback maybeWrapWithWeakReference(IFillCallback callback) {
+        if (remoteFillServiceUseWeakReference()) {
+            mFillCallback = new AtomicReference<>(callback);
+            return new IFillCallbackDelegate(callback);
+        }
+        return callback;
+    }
+
+    /**
+     * Wraps an {@link ISaveCallback} object using weak reference.
+     */
+    private ISaveCallback maybeWrapWithWeakReference(ISaveCallback callback) {
+        if (remoteFillServiceUseWeakReference()) {
+            mSaveCallback = new AtomicReference<>(callback);
+            return new ISaveCallbackDelegate(callback);
+        }
+        return callback;
+    }
+
+    /**
+     * Wraps an {@link IConvertCredentialCallback} object using weak reference
+     */
+    private IConvertCredentialCallback maybeWrapWithWeakReference(
+            IConvertCredentialCallback callback) {
+        if (remoteFillServiceUseWeakReference()) {
+            mConvertCredentialCallback = new AtomicReference<>(callback);
+            return new IConvertCredentialCallbackDelegate(callback);
+        }
+        return callback;
+    }
+
     public void onFillCredentialRequest(@NonNull FillRequest request,
             IAutoFillManagerClient autofillCallback) {
         if (sVerbose) {
@@ -164,29 +300,30 @@
             }
 
             CompletableFuture<FillResponse> fillRequest = new CompletableFuture<>();
-            remoteService.onFillCredentialRequest(request, new IFillCallback.Stub() {
-                @Override
-                public void onCancellable(ICancellationSignal cancellation) {
-                    CompletableFuture<FillResponse> future = futureRef.get();
-                    if (future != null && future.isCancelled()) {
-                        dispatchCancellationSignal(cancellation);
-                    } else {
-                        cancellationSink.set(cancellation);
-                    }
-                }
+            remoteService.onFillCredentialRequest(
+                    request, maybeWrapWithWeakReference(new IFillCallback.Stub() {
+                        @Override
+                        public void onCancellable(ICancellationSignal cancellation) {
+                            CompletableFuture<FillResponse> future = futureRef.get();
+                            if (future != null && future.isCancelled()) {
+                                dispatchCancellationSignal(cancellation);
+                            } else {
+                                cancellationSink.set(cancellation);
+                            }
+                        }
 
-                @Override
-                public void onSuccess(FillResponse response) {
-                    fillRequest.complete(response);
-                }
+                        @Override
+                        public void onSuccess(FillResponse response) {
+                            fillRequest.complete(response);
+                        }
 
-                @Override
-                public void onFailure(int requestId, CharSequence message) {
-                    String errorMessage = message == null ? "" : String.valueOf(message);
-                    fillRequest.completeExceptionally(
-                            new RuntimeException(errorMessage));
-                }
-            }, autofillCallback);
+                        @Override
+                        public void onFailure(int requestId, CharSequence message) {
+                            String errorMessage = message == null ? "" : String.valueOf(message);
+                            fillRequest.completeExceptionally(
+                                    new RuntimeException(errorMessage));
+                        }
+                    }), autofillCallback);
             return fillRequest;
         }).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
         futureRef.set(connectThenFillRequest);
@@ -235,29 +372,30 @@
             }
 
             CompletableFuture<FillResponse> fillRequest = new CompletableFuture<>();
-            remoteService.onFillRequest(request, new IFillCallback.Stub() {
-                @Override
-                public void onCancellable(ICancellationSignal cancellation) {
-                    CompletableFuture<FillResponse> future = futureRef.get();
-                    if (future != null && future.isCancelled()) {
-                        dispatchCancellationSignal(cancellation);
-                    } else {
-                        cancellationSink.set(cancellation);
-                    }
-                }
+            remoteService.onFillRequest(
+                    request, maybeWrapWithWeakReference(new IFillCallback.Stub() {
+                        @Override
+                        public void onCancellable(ICancellationSignal cancellation) {
+                            CompletableFuture<FillResponse> future = futureRef.get();
+                            if (future != null && future.isCancelled()) {
+                                dispatchCancellationSignal(cancellation);
+                            } else {
+                                cancellationSink.set(cancellation);
+                            }
+                        }
 
-                @Override
-                public void onSuccess(FillResponse response) {
-                    fillRequest.complete(response);
-                }
+                        @Override
+                        public void onSuccess(FillResponse response) {
+                            fillRequest.complete(response);
+                        }
 
-                @Override
-                public void onFailure(int requestId, CharSequence message) {
-                    String errorMessage = message == null ? "" : String.valueOf(message);
-                    fillRequest.completeExceptionally(
-                            new RuntimeException(errorMessage));
-                }
-            });
+                        @Override
+                        public void onFailure(int requestId, CharSequence message) {
+                            String errorMessage = message == null ? "" : String.valueOf(message);
+                            fillRequest.completeExceptionally(
+                                    new RuntimeException(errorMessage));
+                        }
+                    }));
             return fillRequest;
         }).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
         futureRef.set(connectThenFillRequest);
@@ -289,12 +427,58 @@
         }));
     }
 
+    public void onConvertCredentialRequest(
+            @NonNull ConvertCredentialRequest convertCredentialRequest) {
+        if (sVerbose) Slog.v(TAG, "calling onConvertCredentialRequest()");
+        CompletableFuture<ConvertCredentialResponse>
+                connectThenConvertCredentialRequest = postAsync(
+                    remoteService -> {
+                        if (sVerbose) {
+                            Slog.v(TAG, "calling onConvertCredentialRequest()");
+                        }
+                        CompletableFuture<ConvertCredentialResponse>
+                                convertCredentialCompletableFuture = new CompletableFuture<>();
+                        remoteService.onConvertCredentialRequest(convertCredentialRequest,
+                                maybeWrapWithWeakReference(
+                                        new IConvertCredentialCallback.Stub() {
+                                            @Override
+                                            public void onSuccess(ConvertCredentialResponse
+                                                    convertCredentialResponse) {
+                                                convertCredentialCompletableFuture
+                                                        .complete(convertCredentialResponse);
+                                            }
+
+                                            @Override
+                                            public void onFailure(CharSequence message) {
+                                                String errorMessage =
+                                                        message == null ? "" :
+                                                                    String.valueOf(message);
+                                                convertCredentialCompletableFuture
+                                                        .completeExceptionally(
+                                                            new RuntimeException(errorMessage));
+                                            }
+                                        })
+                        );
+                        return convertCredentialCompletableFuture;
+                    }).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+
+        connectThenConvertCredentialRequest.whenComplete(
+                (res, err) -> Handler.getMain().post(() -> {
+                    if (err == null) {
+                        mCallbacks.onConvertCredentialRequestSuccess(res);
+                    } else {
+                        // TODO: Add a callback function to log this failure
+                        Slog.e(TAG, "Error calling on convert credential request", err);
+                    }
+                }));
+    }
+
     public void onSaveRequest(@NonNull SaveRequest request) {
         postAsync(service -> {
             if (sVerbose) Slog.v(TAG, "calling onSaveRequest()");
 
             CompletableFuture<IntentSender> save = new CompletableFuture<>();
-            service.onSaveRequest(request, new ISaveCallback.Stub() {
+            service.onSaveRequest(request, maybeWrapWithWeakReference(new ISaveCallback.Stub() {
                 @Override
                 public void onSuccess(IntentSender intentSender) {
                     save.complete(intentSender);
@@ -304,7 +488,7 @@
                 public void onFailure(CharSequence message) {
                     save.completeExceptionally(new RuntimeException(String.valueOf(message)));
                 }
-            });
+            }));
             return save;
         }).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS)
                 .whenComplete((res, err) -> Handler.getMain().post(() -> {
diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
index 553ba12..0af703e 100644
--- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
+++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
@@ -16,6 +16,7 @@
 
 package com.android.server.autofill;
 
+import static com.android.server.autofill.Session.REQUEST_ID_KEY;
 import static com.android.server.autofill.Session.SESSION_ID_KEY;
 
 import android.annotation.NonNull;
@@ -24,6 +25,7 @@
 import android.content.Context;
 import android.content.IntentSender;
 import android.os.Bundle;
+import android.service.autofill.ConvertCredentialResponse;
 import android.service.autofill.FillRequest;
 import android.service.autofill.FillResponse;
 import android.util.Slog;
@@ -97,6 +99,12 @@
 
     }
 
+    @Override
+    public void  onConvertCredentialRequestSuccess(@NonNull ConvertCredentialResponse
+            convertCredentialResponse) {
+
+    }
+
     /**
      * Requests a new fill response.
      */
@@ -107,15 +115,16 @@
         mLastFlag = flag;
         if (mRemoteFillService != null && mRemoteFillService.isCredentialAutofillService()) {
             Slog.v(TAG, "About to call CredAutofill service as secondary provider");
-            addSessionIdToClientState(pendingFillRequest, pendingInlineSuggestionsRequest, id);
-            mRemoteFillService.onFillCredentialRequest(pendingFillRequest, client);
+            FillRequest request = addSessionIdAndRequestIdToClientState(pendingFillRequest,
+                    pendingInlineSuggestionsRequest, id);
+            mRemoteFillService.onFillCredentialRequest(request, client);
         } else {
             mRemoteFillService.onFillRequest(pendingFillRequest);
         }
     }
 
-    private FillRequest addSessionIdToClientState(FillRequest pendingFillRequest,
-            InlineSuggestionsRequest pendingInlineSuggestionsRequest, int id) {
+    private FillRequest addSessionIdAndRequestIdToClientState(FillRequest pendingFillRequest,
+            InlineSuggestionsRequest pendingInlineSuggestionsRequest, int sessionId) {
         if (pendingFillRequest.getClientState() == null) {
             pendingFillRequest = new FillRequest(pendingFillRequest.getId(),
                     pendingFillRequest.getFillContexts(),
@@ -125,7 +134,8 @@
                     pendingInlineSuggestionsRequest,
                     pendingFillRequest.getDelayedFillIntentSender());
         }
-        pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, id);
+        pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, sessionId);
+        pendingFillRequest.getClientState().putInt(REQUEST_ID_KEY, pendingFillRequest.getId());
         return pendingFillRequest;
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 007be05..049feee 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -24,6 +24,7 @@
 import static android.service.autofill.Dataset.PICK_REASON_PROVIDER_DETECTION_ONLY;
 import static android.service.autofill.Dataset.PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC;
 import static android.service.autofill.Dataset.PICK_REASON_UNKNOWN;
+import static android.service.autofill.FillEventHistory.Event.UI_TYPE_CREDMAN_BOTTOM_SHEET;
 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_DIALOG;
 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE;
 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_MENU;
@@ -44,6 +45,7 @@
 import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
 import static android.view.autofill.AutofillManager.COMMIT_REASON_SESSION_DESTROYED;
 import static android.view.autofill.AutofillManager.COMMIT_REASON_UNKNOWN;
+import static android.view.autofill.AutofillManager.EXTRA_AUTOFILL_REQUEST_ID;
 import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
 import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
 
@@ -130,6 +132,7 @@
 import android.service.autofill.AutofillFieldClassificationService.Scores;
 import android.service.autofill.AutofillService;
 import android.service.autofill.CompositeUserData;
+import android.service.autofill.ConvertCredentialResponse;
 import android.service.autofill.Dataset;
 import android.service.autofill.Dataset.DatasetEligibleReason;
 import android.service.autofill.Field;
@@ -234,7 +237,8 @@
             new ComponentName("com.android.credentialmanager",
                     "com.android.credentialmanager.autofill.CredentialAutofillService");
 
-    static final String SESSION_ID_KEY = "session_id";
+    static final String SESSION_ID_KEY = "autofill_session_id";
+    static final String REQUEST_ID_KEY = "autofill_request_id";
 
     final Object mLock;
 
@@ -729,7 +733,7 @@
                         mPendingFillRequest.getFlags(), id, mClient);
             } else if (mRemoteFillService != null) {
                 if (mIsPrimaryCredential) {
-                    mPendingFillRequest = addSessionIdToClientState(mPendingFillRequest,
+                    mPendingFillRequest = addSessionIdAndRequestIdToClientState(mPendingFillRequest,
                             mPendingInlineSuggestionsRequest, id);
                     mRemoteFillService.onFillCredentialRequest(mPendingFillRequest, mClient);
                 } else {
@@ -877,8 +881,8 @@
         }
     }
 
-    private FillRequest addSessionIdToClientState(FillRequest pendingFillRequest,
-            InlineSuggestionsRequest pendingInlineSuggestionsRequest, int id) {
+    private FillRequest addSessionIdAndRequestIdToClientState(FillRequest pendingFillRequest,
+            InlineSuggestionsRequest pendingInlineSuggestionsRequest, int sessionId) {
         if (pendingFillRequest.getClientState() == null) {
             pendingFillRequest = new FillRequest(pendingFillRequest.getId(),
                     pendingFillRequest.getFillContexts(),
@@ -888,7 +892,8 @@
                     pendingInlineSuggestionsRequest,
                     pendingFillRequest.getDelayedFillIntentSender());
         }
-        pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, id);
+        pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, sessionId);
+        pendingFillRequest.getClientState().putInt(REQUEST_ID_KEY, pendingFillRequest.getId());
         return pendingFillRequest;
     }
 
@@ -1733,6 +1738,7 @@
 
         processResponseLockedForPcc(response, response.getClientState(), requestFlags);
         mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis();
+        mFillResponseEventLogger.logAndEndEvent();
     }
 
 
@@ -1845,6 +1851,33 @@
             return;
         }
         synchronized (mLock) {
+            // TODO(b/319913595): refactor logging for fill response for primary and secondary
+            //  providers
+            // Start a new FillResponse logger for the success case.
+            mFillResponseEventLogger.startLogForNewResponse();
+            mFillResponseEventLogger.maybeSetRequestId(fillResponse.getRequestId());
+            mFillResponseEventLogger.maybeSetAppPackageUid(uid);
+            mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SUCCESS);
+            mFillResponseEventLogger.startResponseProcessingTime();
+            // Time passed since session was created
+            final long fillRequestReceivedRelativeTimestamp =
+                    SystemClock.elapsedRealtime() - mLatencyBaseTime;
+            mPresentationStatsEventLogger.maybeSetFillResponseReceivedTimestampMs(
+                    (int) (fillRequestReceivedRelativeTimestamp));
+            mFillResponseEventLogger.maybeSetLatencyFillResponseReceivedMillis(
+                    (int) (fillRequestReceivedRelativeTimestamp));
+            if (mDestroyed) {
+                Slog.w(TAG, "Call to Session#onSecondaryFillResponse() rejected - session: "
+                        + id + " destroyed");
+                mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED);
+                mFillResponseEventLogger.logAndEndEvent();
+                return;
+            }
+
+            List<Dataset> datasetList = fillResponse.getDatasets();
+            int datasetCount = (datasetList == null) ? 0 : datasetList.size();
+            mFillResponseEventLogger.maybeSetTotalDatasetsProvided(datasetCount);
+            mFillResponseEventLogger.maybeSetAvailableCount(datasetCount);
             if (mSecondaryResponses == null) {
                 mSecondaryResponses = new SparseArray<>(2);
             }
@@ -1857,6 +1890,8 @@
             if (currentView != null) {
                 currentView.maybeCallOnFillReady(flags);
             }
+            mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis();
+            mFillResponseEventLogger.logAndEndEvent();
         }
     }
 
@@ -2397,6 +2432,29 @@
         removeFromService();
     }
 
+    // FillServiceCallbacks
+    @Override
+    public void onConvertCredentialRequestSuccess(@NonNull ConvertCredentialResponse
+            convertCredentialResponse) {
+        Dataset dataset = convertCredentialResponse.getDataset();
+        Bundle clientState = convertCredentialResponse.getClientState();
+        if (dataset != null) {
+            int requestId = -1;
+            if (clientState != null) {
+                requestId = clientState.getInt(EXTRA_AUTOFILL_REQUEST_ID);
+            } else {
+                Slog.e(TAG, "onConvertCredentialRequestSuccess(): client state is null, this "
+                        + "would cause loss in logging.");
+            }
+            // TODO: Add autofill related logging; consider whether to log the index
+            fill(requestId, /* datasetIndex=*/ -1, dataset, UI_TYPE_CREDMAN_BOTTOM_SHEET);
+        } else {
+            // TODO: Add logging to log this error case
+            Slog.e(TAG, "onConvertCredentialRequestSuccess(): dataset inside response is "
+                    + "null");
+        }
+    }
+
     /**
      * Gets the {@link FillContext} for a request.
      *
@@ -4269,13 +4327,19 @@
                 if (value != null) {
                     viewState.setCurrentValue(value);
                 }
-
+                boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
                 if (shouldRequestSecondaryProvider(flags)) {
                     if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(
                             id, viewState, flags)) {
                         Slog.v(TAG, "Started a new fill request for secondary provider.");
                         return;
                     }
+
+                    FillResponse response = viewState.getSecondaryResponse();
+                    if (response != null) {
+                        logPresentationStatsOnViewEntered(response, isCredmanRequested);
+                    }
+
                     // If the ViewState is ready to be displayed, onReady() will be called.
                     viewState.update(value, virtualBounds, flags);
 
@@ -4361,15 +4425,9 @@
                     return;
                 }
 
-                if (viewState.getResponse() != null) {
-                    boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
-                    FillResponse response = viewState.getResponse();
-                    mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId());
-                    mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested);
-                    mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(
-                            mFieldClassificationIdSnapshot);
-                    mPresentationStatsEventLogger.maybeSetAvailableCount(
-                            response.getDatasets(), mCurrentViewId);
+                FillResponse response = viewState.getResponse();
+                if (response != null) {
+                    logPresentationStatsOnViewEntered(response, isCredmanRequested);
                 }
 
                 if (isSameViewEntered) {
@@ -4410,6 +4468,17 @@
     }
 
     @GuardedBy("mLock")
+    private void logPresentationStatsOnViewEntered(FillResponse response,
+            boolean isCredmanRequested) {
+        mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId());
+        mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested);
+        mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(
+                mFieldClassificationIdSnapshot);
+        mPresentationStatsEventLogger.maybeSetAvailableCount(
+                response.getDatasets(), mCurrentViewId);
+    }
+
+    @GuardedBy("mLock")
     private void hideAugmentedAutofillLocked(@NonNull ViewState viewState) {
         if ((viewState.getState()
                 & ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL) != 0) {
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 42ab05f..4d42f15 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -58,11 +58,13 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
+import android.view.ViewTreeObserver;
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.autofill.AutofillManager;
 import android.widget.ImageView;
 import android.widget.RemoteViews;
+import android.widget.ScrollView;
 import android.widget.TextView;
 
 import com.android.internal.R;
@@ -370,9 +372,23 @@
         params.windowAnimations = R.style.AutofillSaveAnimation;
         params.setTrustedOverlay();
 
+        ScrollView scrollView = view.findViewById(R.id.autofill_sheet_scroll_view);
+
+        View divider = view.findViewById(R.id.autofill_sheet_divider);
+
+        ViewTreeObserver observer = scrollView.getViewTreeObserver();
+        observer.addOnGlobalLayoutListener(() -> adjustDividerVisibility(scrollView, divider));
+
+        scrollView.getViewTreeObserver()
+                .addOnScrollChangedListener(() -> adjustDividerVisibility(scrollView, divider));
         show();
     }
 
+    private void adjustDividerVisibility(ScrollView scrollView, View divider) {
+        boolean canScrollDown = scrollView.canScrollVertically(1); // 1 to check scrolling down
+        divider.setVisibility(canScrollDown ? View.VISIBLE : View.INVISIBLE);
+    }
+
     private boolean applyCustomDescription(@NonNull Context context, @NonNull View saveUiView,
             @NonNull ValueFinder valueFinder, @NonNull SaveInfo info) {
         final CustomDescription customDescription = info.getCustomDescription();
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index d695d36..1416c88 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -7,4 +7,21 @@
             "restore for apps that have been launched."
     bug: "308401499"
     is_fixed_read_only: true
+}
+
+flag {
+    name: "enable_metrics_system_backup_agents"
+    namespace: "onboarding"
+    description: "Enable SystemBackupAgent to collect B&R agent metrics by passing an instance of "
+            "the logger to each BackupHelper."
+    bug: "296844513"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "enable_max_size_writes_to_pipes"
+    namespace: "onboarding"
+    description: "Enables the write buffer to pipes to be of maximum size."
+    bug: "265976737"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index 6aed9aa..cca166b 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -40,8 +40,8 @@
 
 import com.android.server.EventLogTags;
 import com.android.server.backup.BackupAgentTimeoutParameters;
-import com.android.server.backup.BackupAndRestoreFeatureFlags;
 import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.Flags;
 import com.android.server.backup.FullBackupJob;
 import com.android.server.backup.OperationStorage;
 import com.android.server.backup.OperationStorage.OpState;
@@ -390,8 +390,11 @@
 
             // Set up to send data to the transport
             final int N = mPackages.size();
-            final int chunkSizeInBytes =
-                    BackupAndRestoreFeatureFlags.getFullBackupWriteToTransportBufferSizeBytes();
+            int chunkSizeInBytes = 8 * 1024; // 8KB
+            if (Flags.enableMaxSizeWritesToPipes()) {
+                // Linux pipe capacity (buffer size) is 16 pages where each page is 4KB
+                chunkSizeInBytes = 64 * 1024; // 64KB
+            }
             final byte[] buffer = new byte[chunkSizeInBytes];
             for (int i = 0; i < N; i++) {
                 mBackupRunner = null;
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index ff72476..2c9eb51 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -29,7 +29,6 @@
 import android.app.IBackupAgent;
 import android.app.backup.BackupAgent;
 import android.app.backup.BackupAnnotations;
-import android.app.backup.BackupManager;
 import android.app.backup.FullBackup;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IFullBackupRestoreObserver;
@@ -51,6 +50,7 @@
 import com.android.server.backup.BackupAgentTimeoutParameters;
 import com.android.server.backup.BackupRestoreTask;
 import com.android.server.backup.FileMetadata;
+import com.android.server.backup.Flags;
 import com.android.server.backup.KeyValueAdbRestoreEngine;
 import com.android.server.backup.OperationStorage;
 import com.android.server.backup.OperationStorage.OpType;
@@ -157,13 +157,19 @@
         mMonitor = monitor;
         mOnlyPackage = onlyPackage;
         mAllowApks = allowApks;
-        mBuffer = new byte[32 * 1024];
         mAgentTimeoutParameters = Objects.requireNonNull(
                 backupManagerService.getAgentTimeoutParameters(),
                 "Timeout parameters cannot be null");
         mIsAdbRestore = isAdbRestore;
         mUserId = backupManagerService.getUserId();
         mBackupEligibilityRules = backupEligibilityRules;
+
+        if (Flags.enableMaxSizeWritesToPipes()) {
+            // Linux pipe capacity (buffer size) is 16 pages where each page is 4KB
+            mBuffer = new byte[64 * 1024]; // 64KB
+        } else {
+            mBuffer = new byte[32 * 1024];
+        }
     }
 
     @VisibleForTesting
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 316a16d..0559708 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -140,8 +140,8 @@
     // Widget-related data handled as part of this restore operation
     private byte[] mWidgetData;
 
-    // Number of apps restored in this pass
-    private int mCount;
+    // Number of apps attempted to restore in this pass
+    private int mRestoreAttemptedAppsCount;
 
     // When did we start?
     private long mStartRealtime;
@@ -574,7 +574,8 @@
                     Slog.v(TAG, "No more packages; finishing restore");
                 }
                 int millis = (int) (SystemClock.elapsedRealtime() - mStartRealtime);
-                EventLog.writeEvent(EventLogTags.RESTORE_SUCCESS, mCount, millis);
+                EventLog.writeEvent(
+                        EventLogTags.RESTORE_SUCCESS, mRestoreAttemptedAppsCount, millis);
                 nextState = UnifiedRestoreState.FINAL;
                 return;
             }
@@ -582,7 +583,8 @@
             if (DEBUG) {
                 Slog.i(TAG, "Next restore package: " + mRestoreDescription);
             }
-            sendOnRestorePackage(pkgName);
+            mRestoreAttemptedAppsCount++;
+            sendOnRestorePackage(mRestoreAttemptedAppsCount, pkgName);
 
             Metadata metaInfo = mPmAgent.getRestoredMetadata(pkgName);
             if (metaInfo == null) {
@@ -810,7 +812,6 @@
         // And then finally start the restore on this agent
         try {
             initiateOneRestore(mCurrentPackage, metaInfo.versionCode);
-            ++mCount;
         } catch (Exception e) {
             Slog.e(TAG, "Error when attempting restore: " + e.toString());
             Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
@@ -968,7 +969,12 @@
             throws Exception {
         Set<String> excludedKeysForPackage = getExcludedKeysForPackage(packageName);
 
-        byte[] buffer = new byte[8192]; // will grow when needed
+        int bufferSize = 8192; // 8KB
+        if (Flags.enableMaxSizeWritesToPipes()) {
+            // Linux pipe capacity (buffer size) is 16 pages where each page is 4KB
+            bufferSize = 64 * 1024; // 64KB
+        }
+        byte[] buffer = new byte[bufferSize]; // will grow when needed
         while (in.readNextHeader()) {
             final String key = in.getKey();
             final int size = in.getDataSize();
@@ -1116,7 +1122,11 @@
             ParcelFileDescriptor tReadEnd = mTransportPipes[0];
             ParcelFileDescriptor tWriteEnd = mTransportPipes[1];
 
-            int bufferSize = 32 * 1024;
+            int bufferSize = 32 * 1024; // 32KB
+            if (Flags.enableMaxSizeWritesToPipes()) {
+                // Linux pipe capacity (buffer size) is 16 pages where each page is 4KB
+                bufferSize = 64 * 1024; // 64KB
+            }
             byte[] buffer = new byte[bufferSize];
             FileOutputStream engineOut = new FileOutputStream(eWriteEnd.getFileDescriptor());
             FileInputStream transportIn = new FileInputStream(tReadEnd.getFileDescriptor());
@@ -1322,13 +1332,7 @@
         }
 
         // Tell the observer we're done
-        if (mObserver != null) {
-            try {
-                mObserver.restoreFinished(mStatus);
-            } catch (RemoteException e) {
-                Slog.d(TAG, "Restore observer died at restoreFinished");
-            }
-        }
+        sendEndRestore();
 
         // Clear any ongoing session timeout.
         backupManagerService.getBackupHandler().removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
@@ -1642,10 +1646,10 @@
         }
     }
 
-    void sendOnRestorePackage(String name) {
+    void sendOnRestorePackage(int index, String name) {
         if (mObserver != null) {
             try {
-                mObserver.onUpdate(mCount, name);
+                mObserver.onUpdate(index, name);
             } catch (RemoteException e) {
                 Slog.d(TAG, "Restore observer died in onUpdate");
                 mObserver = null;
diff --git a/services/backup/java/com/android/server/backup/utils/FullBackupUtils.java b/services/backup/java/com/android/server/backup/utils/FullBackupUtils.java
index 1c0cd87..843354e 100644
--- a/services/backup/java/com/android/server/backup/utils/FullBackupUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/FullBackupUtils.java
@@ -21,7 +21,7 @@
 import android.os.ParcelFileDescriptor;
 import android.util.Slog;
 
-import com.android.server.backup.BackupAndRestoreFeatureFlags;
+import com.android.server.backup.Flags;
 
 import java.io.DataInputStream;
 import java.io.EOFException;
@@ -46,8 +46,11 @@
         // We do not take close() responsibility for the pipe FD
         FileInputStream raw = new FileInputStream(inPipe.getFileDescriptor());
         DataInputStream in = new DataInputStream(raw);
-        final int chunkSizeInBytes =
-                BackupAndRestoreFeatureFlags.getFullBackupUtilsRouteBufferSizeBytes();
+        int chunkSizeInBytes = 32 * 1024; // 32KB
+        if (Flags.enableMaxSizeWritesToPipes()) {
+            // Linux pipe capacity (buffer size) is 16 pages where each page is 4KB
+            chunkSizeInBytes = 64 * 1024; // 64KB
+        }
         byte[] buffer = new byte[chunkSizeInBytes];
         int chunkTotal;
         while ((chunkTotal = in.readInt()) > 0) {
diff --git a/services/backup/java/com/android/server/backup/utils/RestoreUtils.java b/services/backup/java/com/android/server/backup/utils/RestoreUtils.java
index 0accb9f..5a8533a 100644
--- a/services/backup/java/com/android/server/backup/utils/RestoreUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/RestoreUtils.java
@@ -40,6 +40,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.LocalServices;
 import com.android.server.backup.FileMetadata;
+import com.android.server.backup.Flags;
 import com.android.server.backup.restore.RestoreDeleteObserver;
 import com.android.server.backup.restore.RestorePolicy;
 
@@ -93,7 +94,12 @@
                 try (Session session = installer.openSession(sessionId)) {
                     try (OutputStream apkStream = session.openWrite(info.packageName, 0,
                             info.size)) {
-                        byte[] buffer = new byte[32 * 1024];
+                        int bufferSize = 32 * 1024; // 32KB
+                        if (Flags.enableMaxSizeWritesToPipes()) {
+                            // Linux pipe capacity (buffer size) is 16 pages where each page is 4KB
+                            bufferSize = 64 * 1024; // 64KB
+                        }
+                        byte[] buffer = new byte[bufferSize];
                         long size = info.size;
                         while (size > 0) {
                             long toRead = (buffer.length < size) ? buffer.length : size;
diff --git a/services/backup/lint-baseline.xml b/services/backup/lint-baseline.xml
index 93c9390..46de2cdd 100644
--- a/services/backup/lint-baseline.xml
+++ b/services/backup/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.1.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NonUserGetterCalled"
@@ -36,4 +36,4 @@
             line="207"/>
     </issue>
 
-</issues>
+</issues>
\ No newline at end of file
diff --git a/services/companion/TEST_MAPPING b/services/companion/TEST_MAPPING
index 37c47ba..ae6d591 100644
--- a/services/companion/TEST_MAPPING
+++ b/services/companion/TEST_MAPPING
@@ -9,5 +9,10 @@
     {
       "name": "CtsCompanionDeviceManagerNoCompanionServicesTestCases"
     }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsCompanionDeviceManagerMultiProcessTestCases"
+    }
   ]
 }
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index 4b3772a..d0eb59d 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -22,6 +22,7 @@
 import static android.companion.CompanionDeviceManager.REASON_INTERNAL_ERROR;
 import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR;
 import static android.content.ComponentName.createRelative;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
 
 import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
 import static com.android.server.companion.MetricUtils.logCreateAssociation;
@@ -169,16 +170,29 @@
         enforcePermissionsForAssociation(mContext, request, packageUid);
         enforceUsesCompanionDeviceFeature(mContext, userId, packageName);
 
-        // 2. Check if association can be created without launching UI (i.e. CDM needs NEITHER
+        // 2a. Check if association can be created without launching UI (i.e. CDM needs NEITHER
         // to perform discovery NOR to collect user consent).
         if (request.isSelfManaged() && !request.isForceConfirmation()
                 && !willAddRoleHolder(request, packageName, userId)) {
-            // 2a. Create association right away.
+            // 2a.1. Create association right away.
             createAssociationAndNotifyApplication(request, packageName, userId,
                     /* macAddress */ null, callback, /* resultReceiver */ null);
             return;
         }
 
+        // 2a.2. Report an error if a 3p app tries to create a non-self-managed association and
+        //       launch UI on watch.
+        if (mContext.getPackageManager().hasSystemFeature(FEATURE_WATCH)) {
+            String errorMessage = "3p apps are not allowed to create associations on watch.";
+            Slog.e(TAG, errorMessage);
+            try {
+                callback.onFailure(errorMessage);
+            } catch (RemoteException e) {
+                // ignored
+            }
+            return;
+        }
+
         // 2b. Build a PendingIntent for launching the confirmation UI, and send it back to the app:
 
         // 2b.1. Populate the request with required info.
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
index c2d2468..586aa8a 100644
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -22,9 +22,11 @@
 import android.annotation.UserIdInt;
 import android.companion.AssociationInfo;
 import android.companion.CompanionDeviceService;
+import android.companion.DevicePresenceEvent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.Handler;
+import android.os.ParcelUuid;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -46,7 +48,8 @@
  * the services, maintaining the connection (the binding), and invoking callback methods such as
  * {@link CompanionDeviceService#onDeviceAppeared(AssociationInfo)},
  * {@link CompanionDeviceService#onDeviceDisappeared(AssociationInfo)} and
- * {@link CompanionDeviceService#onDeviceEvent(AssociationInfo, int)} in the application process.
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} in the
+ * application process.
  *
  * <p>
  * The following is the list of the APIs provided by {@link CompanionApplicationController} (to be
@@ -54,7 +57,7 @@
  * <ul>
  * <li> {@link #bindCompanionApplication(int, String, boolean)}
  * <li> {@link #unbindCompanionApplication(int, String)}
- * <li> {@link #notifyCompanionApplicationDeviceEvent(AssociationInfo, int)} (AssociationInfo, int)}
+ * <li> {@link #notifyCompanionApplicationDevicePresenceEvent(AssociationInfo, int)}
  * <li> {@link #isCompanionApplicationBound(int, String)}
  * <li> {@link #isRebindingCompanionApplicationScheduled(int, String)}
  * </ul>
@@ -72,6 +75,7 @@
 
     private final @NonNull Context mContext;
     private final @NonNull AssociationStore mAssociationStore;
+    private final @NonNull ObservableUuidStore mObservableUuidStore;
     private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor;
     private final @NonNull CompanionServicesRegister mCompanionServicesRegister;
 
@@ -82,9 +86,11 @@
     private final @NonNull AndroidPackageMap<Boolean> mScheduledForRebindingCompanionApplications;
 
     CompanionApplicationController(Context context, AssociationStore associationStore,
+            ObservableUuidStore observableUuidStore,
             CompanionDevicePresenceMonitor companionDevicePresenceMonitor) {
         mContext = context;
         mAssociationStore = associationStore;
+        mObservableUuidStore =  observableUuidStore;
         mDevicePresenceMonitor = companionDevicePresenceMonitor;
         mCompanionServicesRegister = new CompanionServicesRegister();
         mBoundCompanionApplications = new AndroidPackageMap<>();
@@ -281,25 +287,50 @@
         primaryServiceConnector.postOnDeviceDisappeared(association);
     }
 
-    void notifyCompanionApplicationDeviceEvent(AssociationInfo association, int event) {
+    void notifyCompanionApplicationDevicePresenceEvent(AssociationInfo association, int event) {
         final int userId = association.getUserId();
         final String packageName = association.getPackageName();
         final CompanionDeviceServiceConnector primaryServiceConnector =
                 getPrimaryServiceConnector(userId, packageName);
+        final DevicePresenceEvent devicePresenceEvent =
+                new DevicePresenceEvent(association.getId(), event, null);
 
         if (primaryServiceConnector == null) {
-            Slog.e(TAG, "notifyCompanionApplicationDeviceEvent(): "
+            Slog.e(TAG, "notifyCompanionApplicationDevicePresenceEvent(): "
                         + "u" + userId + "/" + packageName
                         + " event=[ " + event  + " ] is NOT bound.");
             Slog.e(TAG, "Stacktrace", new Throwable());
             return;
         }
 
-        Slog.i(TAG, "Calling onDeviceEvent() to userId=[" + userId + "] package=["
+        Slog.i(TAG, "Calling onDevicePresenceEvent() to userId=[" + userId + "] package=["
                 + packageName + "] associationId=[" + association.getId()
-                + "] state=[" + event + "]");
+                + "] event=[" + event + "]");
 
-        primaryServiceConnector.postOnDeviceEvent(association, event);
+        primaryServiceConnector.postOnDevicePresenceEvent(devicePresenceEvent);
+    }
+
+    void notifyApplicationDevicePresenceEvent(ObservableUuid uuid, int event) {
+        final int userId = uuid.getUserId();
+        final ParcelUuid parcelUuid = uuid.getUuid();
+        final String packageName = uuid.getPackageName();
+        final CompanionDeviceServiceConnector primaryServiceConnector =
+                getPrimaryServiceConnector(userId, packageName);
+        final DevicePresenceEvent devicePresenceEvent =
+                new DevicePresenceEvent(DevicePresenceEvent.NO_ASSOCIATION, event, parcelUuid);
+
+        if (primaryServiceConnector == null) {
+            Slog.e(TAG, "notifyApplicationDevicePresenceChanged(): "
+                    + "u" + userId + "/" + packageName
+                    + " event=[ " + event  + " ] is NOT bound.");
+            Slog.e(TAG, "Stacktrace", new Throwable());
+            return;
+        }
+
+        Slog.i(TAG, "Calling onDevicePresenceEvent() to userId=[" + userId + "] package=["
+                + packageName + "]" + "event= [" + event + "]");
+
+        primaryServiceConnector.postOnDevicePresenceEvent(devicePresenceEvent);
     }
 
     void dump(@NonNull PrintWriter out) {
@@ -364,6 +395,9 @@
         // Make sure to clean up the state for all the associations
         // that associate with this package.
         boolean shouldScheduleRebind = false;
+        boolean shouldScheduleRebindForUuid = false;
+        final List<ObservableUuid> uuids =
+                mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
 
         for (AssociationInfo ai :
                 mAssociationStore.getAssociationsForPackage(userId, packageName)) {
@@ -385,7 +419,14 @@
             }
         }
 
-        return stillAssociated && shouldScheduleRebind;
+        for (ObservableUuid uuid : uuids) {
+            if (mDevicePresenceMonitor.isDeviceUuidPresent(uuid.getUuid())) {
+                shouldScheduleRebindForUuid = true;
+                break;
+            }
+        }
+
+        return (stillAssociated && shouldScheduleRebind) || shouldScheduleRebindForUuid;
     }
 
     private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 056ec89..5019428 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -20,16 +20,17 @@
 import static android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES;
 import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
 import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
+import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION;
 import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE;
 import static android.Manifest.permission.USE_COMPANION_TRANSPORTS;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BLE_APPEARED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BLE_DISAPPEARED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BT_CONNECTED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BT_DISCONNECTED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_SELF_MANAGED_APPEARED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_SELF_MANAGED_DISAPPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED;
 import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Process.SYSTEM_UID;
@@ -44,6 +45,7 @@
 import static com.android.server.companion.PackageUtils.getPackageInfo;
 import static com.android.server.companion.PermissionsUtils.checkCallerCanManageCompanionDevice;
 import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
+import static com.android.server.companion.PermissionsUtils.enforceCallerCanObservingDevicePresenceByUuid;
 import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr;
 import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
 import static com.android.server.companion.PermissionsUtils.sanitizeWithCallerChecks;
@@ -74,6 +76,7 @@
 import android.companion.IOnMessageReceivedListener;
 import android.companion.IOnTransportsChangedListener;
 import android.companion.ISystemDataTransferCallback;
+import android.companion.ObservingDevicePresenceRequest;
 import android.companion.datatransfer.PermissionSyncRequest;
 import android.content.ComponentName;
 import android.content.Context;
@@ -82,6 +85,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
+import android.hardware.power.Mode;
 import android.net.MacAddress;
 import android.net.NetworkPolicyManager;
 import android.os.Binder;
@@ -90,6 +94,8 @@
 import android.os.Message;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
+import android.os.ParcelUuid;
+import android.os.PowerManagerInternal;
 import android.os.PowerWhitelistManager;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -129,6 +135,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -175,6 +182,7 @@
     private final PowerWhitelistManager mPowerWhitelistManager;
     private final UserManager mUserManager;
     final PackageManagerInternal mPackageManagerInternal;
+    private final PowerManagerInternal mPowerManagerInternal;
 
     /**
      * A structure that consists of two nested maps, and effectively maps (userId + packageName) to
@@ -217,6 +225,8 @@
 
     private CrossDeviceSyncController mCrossDeviceSyncController;
 
+    private ObservableUuidStore mObservableUuidStore;
+
     public CompanionDeviceManagerService(Context context) {
         super(context);
 
@@ -235,6 +245,8 @@
 
         mOnPackageVisibilityChangeListener =
                 new OnPackageVisibilityChangeListener(mActivityManager);
+        mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
+        mObservableUuidStore = new ObservableUuidStore();
     }
 
     @Override
@@ -242,24 +254,27 @@
         final Context context = getContext();
 
         mPersistentStore = new PersistentDataStore();
+        mAssociationRequestsProcessor = new AssociationRequestsProcessor(
+                /* cdmService */ this, mAssociationStore);
+        mBackupRestoreProcessor = new BackupRestoreProcessor(
+                /* cdmService */ this, mAssociationStore, mPersistentStore,
+                mSystemDataTransferRequestStore, mAssociationRequestsProcessor);
 
         loadAssociationsFromDisk();
+
+        mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId());
+
         mAssociationStore.registerListener(mAssociationStoreChangeListener);
 
         mDevicePresenceMonitor = new CompanionDevicePresenceMonitor(mUserManager,
-                mAssociationStore, mDevicePresenceCallback);
+                mAssociationStore, mObservableUuidStore, mDevicePresenceCallback);
 
-        mAssociationRequestsProcessor = new AssociationRequestsProcessor(
-                /* cdmService */this, mAssociationStore);
         mCompanionAppController = new CompanionApplicationController(
-                context, mAssociationStore, mDevicePresenceMonitor);
+                context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor);
         mTransportManager = new CompanionTransportManager(context, mAssociationStore);
         mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
                 mPackageManagerInternal, mAssociationStore,
                 mSystemDataTransferRequestStore, mTransportManager);
-        mBackupRestoreProcessor = new BackupRestoreProcessor(
-                /* cdmService */ this, mAssociationStore, mPersistentStore,
-                mSystemDataTransferRequestStore, mAssociationRequestsProcessor);
         // TODO(b/279663946): move context sync to a dedicated system service
         mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager);
 
@@ -347,13 +362,31 @@
         final int userId = user.getUserIdentifier();
         final Set<BluetoothDevice> blueToothDevices =
                 mDevicePresenceMonitor.getPendingConnectedDevices().get(userId);
+
+        final List<ObservableUuid> observableUuids =
+                mObservableUuidStore.getObservableUuidsForUser(userId);
+
         if (blueToothDevices != null) {
             for (BluetoothDevice bluetoothDevice : blueToothDevices) {
+                final ParcelUuid[] bluetoothDeviceUuids = bluetoothDevice.getUuids();
+
+                final List<ParcelUuid> deviceUuids = ArrayUtils.isEmpty(bluetoothDeviceUuids)
+                        ? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids);
+
                 for (AssociationInfo ai:
                         mAssociationStore.getAssociationsByAddress(bluetoothDevice.getAddress())) {
                     Slog.i(TAG, "onUserUnlocked, device id( " + ai.getId() + " ) is connected");
                     mDevicePresenceMonitor.onBluetoothCompanionDeviceConnected(ai.getId());
                 }
+
+                for (ObservableUuid observableUuid : observableUuids) {
+                    if (deviceUuids.contains(observableUuid.getUuid())) {
+                        Slog.i(TAG, "onUserUnlocked, UUID( "
+                                + observableUuid.getUuid() + " ) is connected");
+                        mDevicePresenceMonitor.onDevicePresenceEventByUuid(
+                                observableUuid, EVENT_BT_CONNECTED);
+                    }
+                }
             }
         }
     }
@@ -418,31 +451,31 @@
         }
     }
 
-    private void onDeviceEventInternal(int associationId, int event) {
-        Slog.i(TAG, "onDeviceEventInternal() id=" + associationId + " event= " + event);
+    private void onDevicePresenceEventInternal(int associationId, int event) {
+        Slog.i(TAG, "onDevicePresenceEventInternal() id=" + associationId + " event= " + event);
         final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
         final String packageName = association.getPackageName();
         final int userId = association.getUserId();
         switch (event) {
-            case DEVICE_EVENT_BLE_APPEARED:
-            case DEVICE_EVENT_BT_CONNECTED:
-            case DEVICE_EVENT_SELF_MANAGED_APPEARED:
+            case EVENT_BLE_APPEARED:
+            case EVENT_BT_CONNECTED:
+            case EVENT_SELF_MANAGED_APPEARED:
                 if (!association.shouldBindWhenPresent()) return;
 
                 bindApplicationIfNeeded(association);
 
-                mCompanionAppController.notifyCompanionApplicationDeviceEvent(
+                mCompanionAppController.notifyCompanionApplicationDevicePresenceEvent(
                         association, event);
                 break;
-            case DEVICE_EVENT_BLE_DISAPPEARED:
-            case DEVICE_EVENT_BT_DISCONNECTED:
-            case DEVICE_EVENT_SELF_MANAGED_DISAPPEARED:
+            case EVENT_BLE_DISAPPEARED:
+            case EVENT_BT_DISCONNECTED:
+            case EVENT_SELF_MANAGED_DISAPPEARED:
                 if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
                     if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound");
                     return;
                 }
                 if (association.shouldBindWhenPresent()) {
-                    mCompanionAppController.notifyCompanionApplicationDeviceEvent(
+                    mCompanionAppController.notifyCompanionApplicationDevicePresenceEvent(
                             association, event);
                 }
                 // Check if there are other devices associated to the app that are present.
@@ -455,6 +488,45 @@
         }
     }
 
+    private void onDevicePresenceEventByUuidInternal(ObservableUuid uuid, int event) {
+        Slog.i(TAG, "onDevicePresenceEventByUuidInternal() id=" + uuid.getUuid()
+                + "for package=" + uuid.getPackageName() + " event=" + event);
+        final String packageName = uuid.getPackageName();
+        final int userId = uuid.getUserId();
+
+        switch(event) {
+            case EVENT_BT_CONNECTED:
+                if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
+                    mCompanionAppController.bindCompanionApplication(
+                            userId, packageName, /*bindImportant*/ false);
+
+                } else if (DEBUG) {
+                    Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound");
+                }
+
+                mCompanionAppController.notifyApplicationDevicePresenceEvent(uuid, event);
+
+                break;
+            case EVENT_BT_DISCONNECTED:
+                if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
+                    if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound");
+                    return;
+                }
+
+                mCompanionAppController.notifyApplicationDevicePresenceEvent(uuid, event);
+                // Check if there are other devices associated to the app or the UUID to be
+                // observed are present.
+                if (shouldBindPackage(userId, packageName)) return;
+
+                mCompanionAppController.unbindCompanionApplication(userId, packageName);
+
+                break;
+            default:
+                Slog.e(TAG, "Event: " + event + "is not supported");
+                break;
+        }
+    }
+
     private void bindApplicationIfNeeded(AssociationInfo association) {
         final String packageName = association.getPackageName();
         final int userId = association.getUserId();
@@ -471,15 +543,26 @@
 
     /**
      * @return whether the package should be bound (i.e. at least one of the devices associated with
-     *         the package is currently present).
+     *         the package is currently present OR the UUID to be observed by this package is
+     *         currently present).
      */
     private boolean shouldBindPackage(@UserIdInt int userId, @NonNull String packageName) {
         final List<AssociationInfo> packageAssociations =
                 mAssociationStore.getAssociationsForPackage(userId, packageName);
+        final List<ObservableUuid> observableUuids =
+                mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
+
         for (AssociationInfo association : packageAssociations) {
             if (!association.shouldBindWhenPresent()) continue;
             if (mDevicePresenceMonitor.isDevicePresent(association.getId())) return true;
         }
+
+        for (ObservableUuid uuid : observableUuids) {
+            if (mDevicePresenceMonitor.isDeviceUuidPresent(uuid.getUuid())) {
+                return true;
+            }
+        }
+
         return false;
     }
 
@@ -563,6 +646,8 @@
         // Clear associations.
         final List<AssociationInfo> associationsForPackage =
                 mAssociationStore.getAssociationsForPackage(userId, packageName);
+        final List<ObservableUuid> uuidsTobeObserved =
+                mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
         for (AssociationInfo association : associationsForPackage) {
             mAssociationStore.removeAssociation(association.getId());
         }
@@ -570,6 +655,10 @@
         for (AssociationInfo association : associationsForPackage) {
             maybeRemoveRoleHolderForAssociation(association);
         }
+        // Clear the uuids to be observed.
+        for (ObservableUuid uuid : uuidsTobeObserved) {
+            mObservableUuidStore.removeObservableUuid(userId, uuid.getUuid(), packageName);
+        }
 
         mCompanionAppController.onPackagesChanged(userId);
     }
@@ -850,6 +939,95 @@
         }
 
         @Override
+        @EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
+        public void startObservingDevicePresence(ObservingDevicePresenceRequest request,
+                String packageName, int userId) {
+            startObservingDevicePresence_enforcePermission();
+            registerDevicePresenceListener(request, packageName, userId, /* active */ true);
+        }
+
+        @Override
+        @EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
+        public void stopObservingDevicePresence(ObservingDevicePresenceRequest request,
+                String packageName, int userId) {
+            stopObservingDevicePresence_enforcePermission();
+            registerDevicePresenceListener(request, packageName, userId, /* active */ false);
+        }
+
+        private void registerDevicePresenceListener(ObservingDevicePresenceRequest request,
+                String packageName, int userId, boolean active) {
+            enforceUsesCompanionDeviceFeature(getContext(), userId, packageName);
+            enforceCallerIsSystemOr(userId, packageName);
+
+            final int associationId = request.getAssociationId();
+            final AssociationInfo associationInfo = mAssociationStore.getAssociationById(
+                    associationId);
+            final ParcelUuid uuid = request.getUuid();
+
+            if (uuid != null) {
+                enforceCallerCanObservingDevicePresenceByUuid(getContext());
+                if (active) {
+                    startObservingDevicePresenceByUuid(uuid, packageName, userId);
+                } else {
+                    stopObservingDevicePresenceByUuid(uuid, packageName, userId);
+                }
+            } else if (associationInfo == null) {
+                throw new IllegalArgumentException("App " + packageName
+                        + " is not associated with device " + request.getAssociationId()
+                        + " for user " + userId);
+            } else {
+                processDevicePresenceListener(
+                        associationInfo, userId, packageName, active);
+            }
+        }
+
+        private void startObservingDevicePresenceByUuid(ParcelUuid uuid, String packageName,
+                int userId) {
+            final List<ObservableUuid> observableUuids =
+                    mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
+
+            for (ObservableUuid observableUuid : observableUuids) {
+                if (observableUuid.getUuid().equals(uuid)) {
+                    Slog.i(TAG, "The uuid: " + uuid + " for package:" + packageName
+                            + "has been already scheduled for observing");
+                    return;
+                }
+            }
+
+            final ObservableUuid observableUuid = new ObservableUuid(userId, uuid,
+                    packageName, System.currentTimeMillis());
+
+            mObservableUuidStore.writeObservableUuid(userId, observableUuid);
+        }
+
+        private void stopObservingDevicePresenceByUuid(ParcelUuid uuid, String packageName,
+                int userId) {
+            final List<ObservableUuid> uuidsTobeObserved =
+                    mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
+            boolean isScheduledObserving = false;
+
+            for (ObservableUuid observableUuid : uuidsTobeObserved) {
+                if (observableUuid.getUuid().equals(uuid)) {
+                    isScheduledObserving = true;
+                    break;
+                }
+            }
+
+            if (!isScheduledObserving) {
+                Slog.i(TAG, "The uuid: " + uuid.toString() + " for package:" + packageName
+                        + "has NOT been scheduled for observing yet");
+                return;
+            }
+
+            mObservableUuidStore.removeObservableUuid(userId, uuid, packageName);
+            mDevicePresenceMonitor.removeCurrentConnectedUuidDevice(uuid);
+
+            if (!shouldBindPackage(userId, packageName)) {
+                mCompanionAppController.unbindCompanionApplication(userId, packageName);
+            }
+        }
+
+        @Override
         public PendingIntent buildPermissionTransferUserConsentIntent(String packageName,
                 int userId, int associationId) {
             return mSystemDataTransferProcessor.buildPermissionTransferUserConsentIntent(
@@ -949,6 +1127,10 @@
             mAssociationStore.updateAssociation(association);
 
             mDevicePresenceMonitor.onSelfManagedDeviceConnected(associationId);
+
+            if (association.getDeviceProfile() == REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION) {
+                mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, true);
+            }
         }
 
         @Override
@@ -963,6 +1145,10 @@
             }
 
             mDevicePresenceMonitor.onSelfManagedDeviceDisconnected(associationId);
+
+            if (association.getDeviceProfile() == REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION) {
+                mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false);
+            }
         }
 
         @Override
@@ -989,6 +1175,11 @@
                         + " for user " + userId));
             }
 
+            processDevicePresenceListener(association, userId, packageName, active);
+        }
+
+        private void processDevicePresenceListener(AssociationInfo association,
+                int userId, String packageName, boolean active) {
             // If already at specified state, then no-op.
             if (active == association.isNotifyOnDeviceNearby()) {
                 if (DEBUG) Log.d(TAG, "Device presence listener is already at desired state.");
@@ -1012,9 +1203,9 @@
                 if (mDevicePresenceMonitor.isBlePresent(associationId)
                         || mDevicePresenceMonitor.isSimulatePresent(associationId)) {
                     onDeviceAppearedInternal(associationId);
-                    onDeviceEventInternal(associationId, DEVICE_EVENT_BLE_APPEARED);
+                    onDevicePresenceEventInternal(associationId, EVENT_BLE_APPEARED);
                 } else if (mDevicePresenceMonitor.isBtConnected(associationId)) {
-                    onDeviceEventInternal(associationId, DEVICE_EVENT_BT_CONNECTED);
+                    onDevicePresenceEventInternal(associationId, EVENT_BT_CONNECTED);
                 }
             }
 
@@ -1505,20 +1696,25 @@
 
     private final CompanionDevicePresenceMonitor.Callback mDevicePresenceCallback =
             new CompanionDevicePresenceMonitor.Callback() {
-        @Override
-        public void onDeviceAppeared(int associationId) {
-            onDeviceAppearedInternal(associationId);
-        }
+                @Override
+                public void onDeviceAppeared(int associationId) {
+                    onDeviceAppearedInternal(associationId);
+                }
 
-        @Override
-        public void onDeviceDisappeared(int associationId) {
-            onDeviceDisappearedInternal(associationId);
-        }
+                @Override
+                public void onDeviceDisappeared(int associationId) {
+                    onDeviceDisappearedInternal(associationId);
+                }
 
-        @Override
-        public void onDeviceEvent(int associationId, int event) {
-            onDeviceEventInternal(associationId, event);
-        }
+                @Override
+                public void onDevicePresenceEvent(int associationId, int event) {
+                    onDevicePresenceEventInternal(associationId, event);
+                }
+
+                @Override
+                public void onDevicePresenceEventByUuid(ObservableUuid uuid, int event) {
+                    onDevicePresenceEventByUuidInternal(uuid, event);
+                }
     };
 
     private final PackageMonitor mPackageMonitor = new PackageMonitor() {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
index 928842c..5abdb42 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
@@ -26,6 +26,7 @@
 import android.annotation.UserIdInt;
 import android.companion.AssociationInfo;
 import android.companion.CompanionDeviceService;
+import android.companion.DevicePresenceEvent;
 import android.companion.ICompanionDeviceService;
 import android.content.ComponentName;
 import android.content.Context;
@@ -106,12 +107,11 @@
     void postOnDeviceDisappeared(@NonNull AssociationInfo associationInfo) {
         post(companionService -> companionService.onDeviceDisappeared(associationInfo));
     }
-    void postOnDeviceEvent(@NonNull AssociationInfo associationInfo, int event) {
-        post(companionService -> companionService.onDeviceEvent(associationInfo, event));
+
+    void postOnDevicePresenceEvent(@NonNull DevicePresenceEvent event) {
+        post(companionService -> companionService.onDevicePresenceEvent(event));
     }
 
-
-
     /**
      * Post "unbind" job, which will run *after* all previously posted jobs complete.
      *
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 53c0184c..5663434 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -27,6 +27,7 @@
 import android.companion.datatransfer.PermissionSyncRequest;
 import android.net.MacAddress;
 import android.os.Binder;
+import android.os.ParcelUuid;
 import android.os.ShellCommand;
 import android.util.Base64;
 import android.util.proto.ProtoOutputStream;
@@ -80,6 +81,19 @@
                 mDevicePresenceMonitor.simulateDeviceEvent(associationId, event);
                 return 0;
             }
+
+            if ("simulate-device-uuid-event".equals(cmd) && Flags.devicePresence()) {
+                String uuid = getNextArgRequired();
+                String packageName = getNextArgRequired();
+                int userId = getNextIntArgRequired();
+                int event = getNextIntArgRequired();
+                ObservableUuid observableUuid = new ObservableUuid(
+                        userId, ParcelUuid.fromString(uuid), packageName,
+                        System.currentTimeMillis());
+                mDevicePresenceMonitor.simulateDeviceEventByUuid(observableUuid, event);
+                return 0;
+            }
+
             switch (cmd) {
                 case "list": {
                     final int userId = getNextIntArgRequired();
@@ -101,7 +115,7 @@
                     String deviceProfile = getNextArg();
                     final MacAddress macAddress = MacAddress.fromString(address);
                     mService.createNewAssociation(userId, packageName, macAddress,
-                            null, deviceProfile, false);
+                            /* displayName= */ deviceProfile, deviceProfile, false);
                 }
                 break;
 
@@ -447,6 +461,16 @@
             pw.println("    Case(3): ");
             pw.println("      Make CDM act as if the given companion device is BT disconnected ");
             pw.println("      USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+
+            pw.println("  simulate-device-uuid-event UUID PACKAGE USERID EVENT");
+            pw.println("  Simulate the companion device event changes:");
+            pw.println("    Case(2): ");
+            pw.println("      Make CDM act as if the given DEVICE is BT connected base"
+                    + "on the UUID");
+            pw.println("    Case(3): ");
+            pw.println("      Make CDM act as if the given DEVICE is BT disconnected base"
+                    + "on the UUID");
+            pw.println("      USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
         }
 
         pw.println("  remove-inactive-associations");
diff --git a/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java b/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java
index 3482863..aac628c 100644
--- a/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java
+++ b/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java
@@ -36,7 +36,8 @@
  * will be killed if association/role are revoked.
  */
 public class InactiveAssociationsRemovalService extends JobService {
-    private static final int JOB_ID = InactiveAssociationsRemovalService.class.hashCode();
+    private static final String JOB_NAMESPACE = "companion";
+    private static final int JOB_ID = 1;
     private static final long ONE_DAY_INTERVAL = DAYS.toMillis(1);
 
     @Override
@@ -61,7 +62,8 @@
 
     static void schedule(Context context) {
         Slog.i(TAG, "Scheduling the Association Removal job");
-        final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+        final JobScheduler jobScheduler =
+                context.getSystemService(JobScheduler.class).forNamespace(JOB_NAMESPACE);
         final JobInfo job = new JobInfo.Builder(JOB_ID,
                 new ComponentName(context, InactiveAssociationsRemovalService.class))
                 .setRequiresCharging(true)
@@ -71,4 +73,3 @@
         jobScheduler.schedule(job);
     }
 }
-
diff --git a/services/companion/java/com/android/server/companion/ObservableUuid.java b/services/companion/java/com/android/server/companion/ObservableUuid.java
new file mode 100644
index 0000000..6ab3188
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/ObservableUuid.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.os.ParcelUuid;
+
+public class ObservableUuid {
+    private final int mUserId;
+    private final String mPackageName;
+
+    private final ParcelUuid mUuid;
+
+    private final long mTimeApprovedMs;
+
+    public ObservableUuid(@UserIdInt int userId, @NonNull ParcelUuid uuid,
+            @NonNull String packageName, Long timeApprovedMs) {
+        mUserId = userId;
+        mUuid = uuid;
+        mPackageName = packageName;
+        mTimeApprovedMs = timeApprovedMs;
+    }
+
+    public int getUserId() {
+        return mUserId;
+    }
+
+    public ParcelUuid getUuid() {
+        return mUuid;
+    }
+
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    public long getTimeApprovedMs() {
+        return mTimeApprovedMs;
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/ObservableUuidStore.java b/services/companion/java/com/android/server/companion/ObservableUuidStore.java
new file mode 100644
index 0000000..94be22a
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/ObservableUuidStore.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion;
+
+import static com.android.internal.util.XmlUtils.readIntAttribute;
+import static com.android.internal.util.XmlUtils.readLongAttribute;
+import static com.android.internal.util.XmlUtils.readStringAttribute;
+import static com.android.internal.util.XmlUtils.writeIntAttribute;
+import static com.android.internal.util.XmlUtils.writeLongAttribute;
+import static com.android.internal.util.XmlUtils.writeStringAttribute;
+import static com.android.server.companion.DataStoreUtils.createStorageFileForUser;
+import static com.android.server.companion.DataStoreUtils.isEndOfTag;
+import static com.android.server.companion.DataStoreUtils.isStartOfTag;
+import static com.android.server.companion.DataStoreUtils.writeToFileSafely;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.os.ParcelUuid;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class ObservableUuidStore {
+    private static final String TAG = "CDM_ObservableUuidStore";
+    private static final String FILE_NAME = "observing_uuids_presence.xml";
+    private static final String XML_TAG_UUIDS = "uuids";
+    private static final String XML_TAG_UUID = "uuid";
+    private static final String XML_ATTR_UUID = "uuid";
+    private static final String XML_ATTR_TIME_APPROVED = "time_approved";
+    private static final String XML_ATTR_USER_ID = "user_id";
+    private static final String XML_ATTR_PACKAGE = "package_name";
+    private static final int READ_FROM_DISK_TIMEOUT = 5; // in seconds
+
+
+    private final ExecutorService mExecutor;
+    private final ConcurrentMap<Integer, AtomicFile> mUserIdToStorageFile =
+            new ConcurrentHashMap<>();
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final SparseArray<List<ObservableUuid>> mCachedPerUser =
+            new SparseArray<>();
+
+    public ObservableUuidStore() {
+        mExecutor = Executors.newSingleThreadExecutor();
+    }
+
+    /**
+     * Remove the observable uuid from the disk.
+     */
+    void removeObservableUuid(@UserIdInt int userId, ParcelUuid uuid, String packageName) {
+        List<ObservableUuid> cachedObservableUuids;
+
+        synchronized (mLock) {
+            // Remove requests from cache
+            cachedObservableUuids = readObservableUuidsFromCache(userId);
+            cachedObservableUuids.removeIf(
+                    uuid1 -> uuid1.getPackageName().equals(packageName)
+                            && uuid1.getUuid().equals(uuid));
+            mCachedPerUser.set(userId, cachedObservableUuids);
+        }
+        // Remove requests from store
+        mExecutor.execute(() -> writeObservableUuidToStore(userId, cachedObservableUuids));
+    }
+
+    void writeObservableUuid(@UserIdInt int userId, ObservableUuid uuid) {
+        Slog.i(TAG, "Writing uuid=" + uuid.getUuid() + " to store.");
+
+        List<ObservableUuid> cachedObservableUuids;
+        synchronized (mLock) {
+            // Write to cache
+            cachedObservableUuids = readObservableUuidsFromCache(userId);
+            cachedObservableUuids.removeIf(uuid1 -> uuid1.getUuid().equals(
+                    uuid.getUuid()) && uuid1.getPackageName().equals(uuid.getPackageName()));
+            cachedObservableUuids.add(uuid);
+            mCachedPerUser.set(userId, cachedObservableUuids);
+        }
+        // Write to store
+        mExecutor.execute(() -> writeObservableUuidToStore(userId, cachedObservableUuids));
+    }
+
+    private void writeObservableUuidToStore(@UserIdInt int userId,
+            @NonNull List<ObservableUuid> cachedObservableUuids) {
+        final AtomicFile file = getStorageFileForUser(userId);
+        Slog.i(TAG, "Writing ObservableUuid for user " + userId + " to file="
+                + file.getBaseFile().getPath());
+
+        // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
+        // accesses to the file on the file system using this AtomicFile object.
+        synchronized (file) {
+            writeToFileSafely(file, out -> {
+                final TypedXmlSerializer serializer = Xml.resolveSerializer(out);
+                serializer.setFeature(
+                        "http://xmlpull.org/v1/doc/features.html#indent-output", true);
+                serializer.startDocument(null, true);
+                writeObservableUuidToXml(serializer, cachedObservableUuids);
+                serializer.endDocument();
+            });
+        }
+    }
+
+    private void writeObservableUuidToXml(@NonNull TypedXmlSerializer serializer,
+            @Nullable Collection<ObservableUuid> uuids) throws IOException {
+        serializer.startTag(null, XML_TAG_UUIDS);
+
+        for (ObservableUuid uuid : uuids) {
+            writeUuidToXml(serializer, uuid);
+        }
+
+        serializer.endTag(null, XML_TAG_UUIDS);
+    }
+
+    private void writeUuidToXml(@NonNull TypedXmlSerializer serializer,
+            @NonNull ObservableUuid uuid) throws IOException {
+        serializer.startTag(null, XML_TAG_UUID);
+
+        writeIntAttribute(serializer, XML_ATTR_USER_ID, uuid.getUserId());
+        writeStringAttribute(serializer, XML_ATTR_UUID, uuid.getUuid().toString());
+        writeStringAttribute(serializer, XML_ATTR_PACKAGE, uuid.getPackageName());
+        writeLongAttribute(serializer, XML_ATTR_TIME_APPROVED, uuid.getTimeApprovedMs());
+
+        serializer.endTag(null, XML_TAG_UUID);
+    }
+
+    /**
+     * Read the observable UUIDs from the cache.
+     */
+    @GuardedBy("mLock")
+    private List<ObservableUuid> readObservableUuidsFromCache(@UserIdInt int userId) {
+        List<ObservableUuid> cachedObservableUuids = mCachedPerUser.get(userId);
+        if (cachedObservableUuids == null) {
+            Future<List<ObservableUuid>> future =
+                    mExecutor.submit(() -> readObservableUuidFromStore(userId));
+            try {
+                cachedObservableUuids = future.get(READ_FROM_DISK_TIMEOUT, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                Slog.e(TAG, "Thread reading ObservableUuid from disk is "
+                        + "interrupted.");
+            } catch (ExecutionException e) {
+                Slog.e(TAG, "Error occurred while reading ObservableUuid "
+                        + "from disk.");
+            } catch (TimeoutException e) {
+                Slog.e(TAG, "Reading ObservableUuid from disk timed out.");
+            }
+            mCachedPerUser.set(userId, cachedObservableUuids);
+        }
+        return cachedObservableUuids;
+    }
+
+    /**
+     * Reads previously persisted data for the given user
+     *
+     * @param userId Android UserID
+     * @return a list of ObservableUuid
+     */
+    @NonNull
+    public List<ObservableUuid> readObservableUuidFromStore(@UserIdInt int userId) {
+        final AtomicFile file = getStorageFileForUser(userId);
+        Slog.i(TAG, "Reading ObservableUuid for user " + userId + " from "
+                + "file=" + file.getBaseFile().getPath());
+
+        // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
+        // accesses to the file on the file system using this AtomicFile object.
+        synchronized (file) {
+            if (!file.getBaseFile().exists()) {
+                Slog.d(TAG, "File does not exist -> Abort");
+                return new ArrayList<>();
+            }
+            try (FileInputStream in = file.openRead()) {
+                final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+                XmlUtils.beginDocument(parser, XML_TAG_UUIDS);
+
+                return readObservableUuidFromXml(parser);
+            } catch (XmlPullParserException | IOException e) {
+                Slog.e(TAG, "Error while reading requests file", e);
+                return new ArrayList<>();
+            }
+        }
+    }
+
+    @NonNull
+    private List<ObservableUuid> readObservableUuidFromXml(
+            @NonNull TypedXmlPullParser parser) throws XmlPullParserException, IOException {
+        if (!isStartOfTag(parser, XML_TAG_UUIDS)) {
+            throw new XmlPullParserException("The XML doesn't have start tag: " + XML_TAG_UUIDS);
+        }
+
+        List<ObservableUuid> observableUuids = new ArrayList<>();
+
+        while (true) {
+            parser.nextTag();
+            if (isEndOfTag(parser, XML_TAG_UUIDS)) {
+                break;
+            }
+            if (isStartOfTag(parser, XML_TAG_UUID)) {
+                observableUuids.add(readUuidFromXml(parser));
+            }
+        }
+
+        return observableUuids;
+    }
+
+    private ObservableUuid readUuidFromXml(@NonNull TypedXmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        if (!isStartOfTag(parser, XML_TAG_UUID)) {
+            throw new XmlPullParserException("XML doesn't have start tag: " + XML_TAG_UUID);
+        }
+
+        final int userId = readIntAttribute(parser, XML_ATTR_USER_ID);
+        final ParcelUuid uuid = ParcelUuid.fromString(readStringAttribute(parser, XML_ATTR_UUID));
+        final String packageName = readStringAttribute(parser, XML_ATTR_PACKAGE);
+        final Long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED);
+
+        return new ObservableUuid(userId, uuid, packageName, timeApproved);
+    }
+
+    /**
+     * Creates and caches {@link AtomicFile} object that represents the back-up file for the given
+     * user.
+     * <p>
+     * IMPORTANT: the method will ALWAYS return the same {@link AtomicFile} object, which makes it
+     * possible to synchronize reads and writes to the file using the returned object.
+     */
+    @NonNull
+    private AtomicFile getStorageFileForUser(@UserIdInt int userId) {
+        return mUserIdToStorageFile.computeIfAbsent(userId,
+                u -> createStorageFileForUser(userId, FILE_NAME));
+    }
+
+    /**
+     * @return A list of ObservableUuids per package.
+     */
+    public List<ObservableUuid> getObservableUuidsForPackage(
+            @UserIdInt int userId, @NonNull String packageName) {
+        final List<ObservableUuid> uuidsTobeObservedPerPackage = new ArrayList<>();
+        synchronized (mLock) {
+            final List<ObservableUuid> uuids = readObservableUuidsFromCache(userId);
+
+            for (ObservableUuid uuid : uuids) {
+                if (uuid.getPackageName().equals(packageName)) {
+                    uuidsTobeObservedPerPackage.add(uuid);
+                }
+            }
+        }
+
+        return uuidsTobeObservedPerPackage;
+    }
+
+    /**
+     * @return A list of ObservableUuids per user.
+     */
+    public List<ObservableUuid> getObservableUuidsForUser(@UserIdInt int userId) {
+        synchronized (mLock) {
+            return readObservableUuidsFromCache(userId);
+        }
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java
index f4e14df..15bebba 100644
--- a/services/companion/java/com/android/server/companion/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
 import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED;
+import static android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
@@ -174,6 +175,14 @@
                 + " for u" + userId + "/" + packageName);
     }
 
+    static void enforceCallerCanObservingDevicePresenceByUuid(@NonNull Context context) {
+        if (context.checkCallingPermission(REQUEST_OBSERVE_DEVICE_UUID_PRESENCE)
+                != PERMISSION_GRANTED) {
+            throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
+                    + "permissions to request observing device presence base on the UUID");
+        }
+    }
+
     /**
      * Check if the caller is either:
      * <ul>
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 4e471f5..260b21f 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -21,6 +21,7 @@
 import static android.app.PendingIntent.FLAG_ONE_SHOT;
 import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PERMISSION_RESTORE;
 import static android.content.ComponentName.createRelative;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
 
 import static com.android.server.companion.Utils.prepareForIpc;
 
@@ -40,6 +41,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -306,6 +308,13 @@
     }
 
     private void onReceivePermissionRestore(byte[] message) {
+        // TODO: Disable Permissions Sync for non-watch devices until we figure out a better UX
+        //       model
+        if (!Build.isDebuggable() && !mContext.getPackageManager().hasSystemFeature(
+                FEATURE_WATCH)) {
+            Slog.e(LOG_TAG, "Permissions restore is only available on watch.");
+            return;
+        }
         Slog.i(LOG_TAG, "Applying permissions.");
         // Start applying permissions
         UserHandle user = mContext.getUser();
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index 6ba85bd..c514f3e 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -16,6 +16,9 @@
 
 package com.android.server.companion.presence;
 
+import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
+
 import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG;
 import static com.android.server.companion.presence.Utils.btDeviceToString;
 
@@ -27,6 +30,7 @@
 import android.net.MacAddress;
 import android.os.Handler;
 import android.os.HandlerExecutor;
+import android.os.ParcelUuid;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
@@ -34,9 +38,13 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
 import com.android.server.companion.AssociationStore;
+import com.android.server.companion.ObservableUuid;
+import com.android.server.companion.ObservableUuidStore;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -53,6 +61,8 @@
         void onBluetoothCompanionDeviceConnected(int associationId);
 
         void onBluetoothCompanionDeviceDisconnected(int associationId);
+
+        void onDevicePresenceEventByUuid(ObservableUuid uuid, int event);
     }
 
     private final UserManager mUserManager;
@@ -61,6 +71,8 @@
     /** A set of ALL connected BT device (not only companion.) */
     private final @NonNull Map<MacAddress, BluetoothDevice> mAllConnectedDevices = new HashMap<>();
 
+    private final @NonNull ObservableUuidStore mObservableUuidStore;
+
     /**
      * A structure hold the connected BT devices that are pending to be reported to the companion
      * app when the user unlocks the local device per userId.
@@ -70,8 +82,10 @@
     final SparseArray<Set<BluetoothDevice>> mPendingConnectedDevices = new SparseArray<>();
 
     BluetoothCompanionDeviceConnectionListener(UserManager userManager,
-            @NonNull AssociationStore associationStore, @NonNull Callback callback) {
+            @NonNull AssociationStore associationStore,
+            @NonNull ObservableUuidStore observableUuidStore, @NonNull Callback callback) {
         mAssociationStore = associationStore;
+        mObservableUuidStore = observableUuidStore;
         mCallback = callback;
         mUserManager = userManager;
     }
@@ -109,7 +123,6 @@
                 bluetoothDevices.add(device);
                 mPendingConnectedDevices.put(userId, bluetoothDevices);
             }
-
         } else {
             onDeviceConnectivityChanged(device, true);
         }
@@ -155,8 +168,15 @@
     }
 
     private void onDeviceConnectivityChanged(@NonNull BluetoothDevice device, boolean connected) {
+        int userId = UserHandle.myUserId();
         final List<AssociationInfo> associations =
                 mAssociationStore.getAssociationsByAddress(device.getAddress());
+        final List<ObservableUuid> observableUuids =
+                mObservableUuidStore.getObservableUuidsForUser(userId);
+        final ParcelUuid[] bluetoothDeviceUuids = device.getUuids();
+
+        final List<ParcelUuid> deviceUuids = ArrayUtils.isEmpty(bluetoothDeviceUuids)
+                ? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids);
 
         if (DEBUG) {
             Log.d(TAG, "onDevice_ConnectivityChanged() " + btDeviceToString(device)
@@ -177,6 +197,14 @@
                 mCallback.onBluetoothCompanionDeviceDisconnected(id);
             }
         }
+
+        for (ObservableUuid uuid : observableUuids) {
+            if (deviceUuids.contains(uuid.getUuid())) {
+                mCallback.onDevicePresenceEventByUuid(
+                        uuid, connected ? EVENT_BT_CONNECTED
+                                : EVENT_BT_DISCONNECTED);
+            }
+        }
     }
 
     @Override
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index e42b935..54a4692 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -16,12 +16,12 @@
 
 package com.android.server.companion.presence;
 
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BLE_APPEARED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BLE_DISAPPEARED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BT_CONNECTED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BT_DISCONNECTED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_SELF_MANAGED_APPEARED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_SELF_MANAGED_DISAPPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED;
 import static android.os.Process.ROOT_UID;
 import static android.os.Process.SHELL_UID;
 
@@ -36,12 +36,15 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.ParcelUuid;
 import android.os.UserManager;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.server.companion.AssociationStore;
+import com.android.server.companion.ObservableUuid;
+import com.android.server.companion.ObservableUuidStore;
 
 import java.io.PrintWriter;
 import java.util.HashSet;
@@ -61,7 +64,7 @@
  * <li> {@link #isDevicePresent(int)}
  * <li> {@link Callback#onDeviceAppeared(int) Callback.onDeviceAppeared(int)}
  * <li> {@link Callback#onDeviceDisappeared(int) Callback.onDeviceDisappeared(int)}
- * <li> {@link Callback#onDeviceStateChanged(int, int)}}
+ * <li> {@link Callback#onDevicePresenceEvent(int, int)}}
  * </ul>
  */
 @SuppressLint("LongLogTag")
@@ -78,11 +81,15 @@
         /** Invoked when a companion device no longer seen nearby or disconnects. */
         void onDeviceDisappeared(int associationId);
 
-        /**Invoked when device has corresponding event changes. */
-        void onDeviceEvent(int associationId, int event);
+        /** Invoked when device has corresponding event changes. */
+        void onDevicePresenceEvent(int associationId, int event);
+
+        /** Invoked when device has corresponding event changes base on the UUID */
+        void onDevicePresenceEventByUuid(ObservableUuid uuid, int event);
     }
 
     private final @NonNull AssociationStore mAssociationStore;
+    private final @NonNull ObservableUuidStore mObservableUuidStore;
     private final @NonNull Callback mCallback;
     private final @NonNull BluetoothCompanionDeviceConnectionListener mBtConnectionListener;
     private final @NonNull BleCompanionDeviceScanner mBleScanner;
@@ -94,6 +101,7 @@
     private final @NonNull Set<Integer> mConnectedBtDevices = new HashSet<>();
     private final @NonNull Set<Integer> mNearbyBleDevices = new HashSet<>();
     private final @NonNull Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
+    private final @NonNull Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>();
 
     // Tracking "simulated" presence. Used for debugging and testing only.
     private final @NonNull Set<Integer> mSimulated = new HashSet<>();
@@ -101,11 +109,14 @@
             new SimulatedDevicePresenceSchedulerHelper();
 
     public CompanionDevicePresenceMonitor(UserManager userManager,
-            @NonNull AssociationStore associationStore, @NonNull Callback callback) {
+            @NonNull AssociationStore associationStore,
+            @NonNull ObservableUuidStore observableUuidStore, @NonNull Callback callback) {
         mAssociationStore = associationStore;
+        mObservableUuidStore = observableUuidStore;
         mCallback = callback;
         mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(userManager,
-                associationStore, /* BluetoothCompanionDeviceConnectionListener.Callback */ this);
+                associationStore, mObservableUuidStore,
+                /* BluetoothCompanionDeviceConnectionListener.Callback */ this);
         mBleScanner = new BleCompanionDeviceScanner(associationStore,
                 /* BleCompanionDeviceScanner.Callback */ this);
     }
@@ -126,6 +137,20 @@
     }
 
     /**
+     * @return current connected UUID devices.
+     */
+    public Set<ParcelUuid> getCurrentConnectedUuidDevices() {
+        return mConnectedUuidDevices;
+    }
+
+    /**
+     * Remove current connected UUID device.
+     */
+    public void removeCurrentConnectedUuidDevice(ParcelUuid uuid) {
+        mConnectedUuidDevices.remove(uuid);
+    }
+
+    /**
      * @return whether the associated companion devices is present. I.e. device is nearby (for BLE);
      *         or devices is connected (for Bluetooth); or reported (by the application) to be
      *         nearby (for "self-managed" associations).
@@ -138,6 +163,13 @@
     }
 
     /**
+     * @return whether the current uuid to be observed is present.
+     */
+    public boolean isDeviceUuidPresent(ParcelUuid uuid) {
+        return mConnectedUuidDevices.contains(uuid);
+    }
+
+    /**
      * @return whether the current device is BT connected and had already reported to the app.
      */
 
@@ -169,8 +201,8 @@
      * {@link android.companion.CompanionDeviceManager#notifyDeviceAppeared(int) notifyDeviceAppeared()}
      */
     public void onSelfManagedDeviceConnected(int associationId) {
-        onDeviceEvent(mReportedSelfManagedDevices,
-                associationId, DEVICE_EVENT_SELF_MANAGED_APPEARED);
+        onDevicePresenceEvent(mReportedSelfManagedDevices,
+                associationId, EVENT_SELF_MANAGED_APPEARED);
     }
 
     /**
@@ -183,23 +215,23 @@
      * {@link android.companion.CompanionDeviceManager#notifyDeviceDisappeared(int) notifyDeviceDisappeared()}
      */
     public void onSelfManagedDeviceDisconnected(int associationId) {
-        onDeviceEvent(mReportedSelfManagedDevices,
-                associationId, DEVICE_EVENT_SELF_MANAGED_DISAPPEARED);
+        onDevicePresenceEvent(mReportedSelfManagedDevices,
+                associationId, EVENT_SELF_MANAGED_DISAPPEARED);
     }
 
     /**
      * Marks a "self-managed" device as disconnected when binderDied.
      */
     public void onSelfManagedDeviceReporterBinderDied(int associationId) {
-        onDeviceEvent(mReportedSelfManagedDevices,
-                associationId, DEVICE_EVENT_SELF_MANAGED_DISAPPEARED);
+        onDevicePresenceEvent(mReportedSelfManagedDevices,
+                associationId, EVENT_SELF_MANAGED_DISAPPEARED);
     }
 
     @Override
     public void onBluetoothCompanionDeviceConnected(int associationId) {
         Slog.i(TAG, "onBluetoothCompanionDeviceConnected: "
                 + "associationId( " + associationId + " )");
-        onDeviceEvent(mConnectedBtDevices, associationId, DEVICE_EVENT_BT_CONNECTED);
+        onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED);
         // Stop scanning for BLE devices when this device is connected
         // and there are no other devices to connect to.
         if (canStopBleScan()) {
@@ -214,22 +246,53 @@
         // Start BLE scanning when the device is disconnected.
         mBleScanner.startScan();
 
-        onDeviceEvent(mConnectedBtDevices, associationId, DEVICE_EVENT_BT_DISCONNECTED);
+        onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_DISCONNECTED);
     }
 
     @Override
+    public void onDevicePresenceEventByUuid(ObservableUuid uuid, int event) {
+        final ParcelUuid parcelUuid = uuid.getUuid();
+
+        switch(event) {
+            case EVENT_BT_CONNECTED:
+                boolean added = mConnectedUuidDevices.add(parcelUuid);
+
+                if (!added) {
+                    Slog.w(TAG, "Uuid= " + parcelUuid + "is ALREADY reported as "
+                            + "present by this event=" + event);
+                }
+
+                break;
+            case EVENT_BT_DISCONNECTED:
+                final boolean removed = mConnectedUuidDevices.remove(parcelUuid);
+
+                if (!removed) {
+                    Slog.w(TAG, "UUID= " + parcelUuid + " was NOT reported "
+                            + "as present by this event= " + event);
+
+                    return;
+                }
+
+                break;
+        }
+
+        mCallback.onDevicePresenceEventByUuid(uuid, event);
+    }
+
+
+    @Override
     public void onBleCompanionDeviceFound(int associationId) {
-        onDeviceEvent(mNearbyBleDevices, associationId, DEVICE_EVENT_BLE_APPEARED);
+        onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED);
     }
 
     @Override
     public void onBleCompanionDeviceLost(int associationId) {
-        onDeviceEvent(mNearbyBleDevices, associationId, DEVICE_EVENT_BLE_DISAPPEARED);
+        onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED);
     }
 
     /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
     @TestApi
-    public void simulateDeviceEvent(int associationId, int state) {
+    public void simulateDeviceEvent(int associationId, int event) {
         // IMPORTANT: this API should only be invoked via the
         // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to
         // make this call are SHELL and ROOT.
@@ -238,32 +301,43 @@
         // Make sure the association exists.
         enforceAssociationExists(associationId);
 
-        switch (state) {
-            case DEVICE_EVENT_BLE_APPEARED:
-                simulateDeviceAppeared(associationId, state);
+        switch (event) {
+            case EVENT_BLE_APPEARED:
+                simulateDeviceAppeared(associationId, event);
                 break;
-            case DEVICE_EVENT_BT_CONNECTED:
+            case EVENT_BT_CONNECTED:
                 onBluetoothCompanionDeviceConnected(associationId);
                 break;
-            case DEVICE_EVENT_BLE_DISAPPEARED:
-                simulateDeviceDisappeared(associationId, state);
+            case EVENT_BLE_DISAPPEARED:
+                simulateDeviceDisappeared(associationId, event);
                 break;
-            case DEVICE_EVENT_BT_DISCONNECTED:
+            case EVENT_BT_DISCONNECTED:
                 onBluetoothCompanionDeviceDisconnected(associationId);
                 break;
             default:
-                throw new IllegalArgumentException("State: " + state + "is not supported");
+                throw new IllegalArgumentException("Event: " + event + "is not supported");
         }
     }
 
+    /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
+    @TestApi
+    public void simulateDeviceEventByUuid(ObservableUuid uuid, int event) {
+        // IMPORTANT: this API should only be invoked via the
+        // 'companiondevice simulate-device-uuid-events' Shell command, so the only uid-s allowed to
+        // make this call are SHELL and ROOT.
+        // No other caller (including SYSTEM!) should be allowed.
+        enforceCallerShellOrRoot();
+        onDevicePresenceEventByUuid(uuid, event);
+    }
+
     private void simulateDeviceAppeared(int associationId, int state) {
-        onDeviceEvent(mSimulated, associationId, state);
+        onDevicePresenceEvent(mSimulated, associationId, state);
         mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
     }
 
     private void simulateDeviceDisappeared(int associationId, int state) {
         mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
-        onDeviceEvent(mSimulated, associationId, state);
+        onDevicePresenceEvent(mSimulated, associationId, state);
     }
 
     private void enforceAssociationExists(int associationId) {
@@ -273,14 +347,14 @@
         }
     }
 
-    private void onDeviceEvent(@NonNull Set<Integer> presentDevicesForSource,
+    private void onDevicePresenceEvent(@NonNull Set<Integer> presentDevicesForSource,
             int associationId, int event) {
-        Slog.i(TAG, "onDeviceEvent() id=" + associationId + ", state=" + event);
+        Slog.i(TAG, "onDevicePresenceEvent() id=" + associationId + ", event=" + event);
 
         switch (event) {
-            case DEVICE_EVENT_BLE_APPEARED:
-            case DEVICE_EVENT_BT_CONNECTED:
-            case DEVICE_EVENT_SELF_MANAGED_APPEARED:
+            case EVENT_BLE_APPEARED:
+            case EVENT_BT_CONNECTED:
+            case EVENT_SELF_MANAGED_APPEARED:
                 final boolean added = presentDevicesForSource.add(associationId);
 
                 if (!added) {
@@ -292,9 +366,9 @@
                 mCallback.onDeviceAppeared(associationId);
 
                 break;
-            case DEVICE_EVENT_BLE_DISAPPEARED:
-            case DEVICE_EVENT_BT_DISCONNECTED:
-            case DEVICE_EVENT_SELF_MANAGED_DISAPPEARED:
+            case EVENT_BLE_DISAPPEARED:
+            case EVENT_BT_DISCONNECTED:
+            case EVENT_SELF_MANAGED_DISAPPEARED:
                 final boolean removed = presentDevicesForSource.remove(associationId);
 
                 if (!removed) {
@@ -312,7 +386,7 @@
                 return;
         }
 
-        mCallback.onDeviceEvent(associationId, event);
+        mCallback.onDevicePresenceEvent(associationId, event);
     }
 
     /**
@@ -436,7 +510,7 @@
         public void handleMessage(@NonNull Message msg) {
             final int associationId = msg.what;
             if (mSimulated.contains(associationId)) {
-                onDeviceEvent(mSimulated, associationId, DEVICE_EVENT_BLE_DISAPPEARED);
+                onDevicePresenceEvent(mSimulated, associationId, EVENT_BLE_DISAPPEARED);
             }
         }
     }
diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
index 720687e..0e66fbc 100644
--- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
+++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
@@ -23,12 +23,12 @@
 import android.os.Build;
 import android.util.Slog;
 
-import com.google.security.cryptauth.lib.securegcm.BadHandleException;
-import com.google.security.cryptauth.lib.securegcm.CryptoException;
-import com.google.security.cryptauth.lib.securegcm.D2DConnectionContextV1;
-import com.google.security.cryptauth.lib.securegcm.D2DHandshakeContext;
-import com.google.security.cryptauth.lib.securegcm.D2DHandshakeContext.Role;
-import com.google.security.cryptauth.lib.securegcm.HandshakeException;
+import com.google.security.cryptauth.lib.securegcm.ukey2.BadHandleException;
+import com.google.security.cryptauth.lib.securegcm.ukey2.CryptoException;
+import com.google.security.cryptauth.lib.securegcm.ukey2.D2DConnectionContextV1;
+import com.google.security.cryptauth.lib.securegcm.ukey2.D2DHandshakeContext;
+import com.google.security.cryptauth.lib.securegcm.ukey2.D2DHandshakeContext.Role;
+import com.google.security.cryptauth.lib.securegcm.ukey2.HandshakeException;
 
 import libcore.io.IoUtils;
 import libcore.io.Streams;
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 62c6703..3e45626 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -125,7 +125,7 @@
      * Send a message to remote devices through the transports
      */
     public void sendMessage(int message, byte[] data, int[] associationIds) {
-        Slog.i(TAG, "Sending message 0x" + Integer.toHexString(message)
+        Slog.d(TAG, "Sending message 0x" + Integer.toHexString(message)
                 + " data length " + data.length);
         synchronized (mTransports) {
             for (int i = 0; i < associationIds.length; i++) {
diff --git a/services/companion/java/com/android/server/companion/transport/SecureTransport.java b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
index a0301a9..6e906eb 100644
--- a/services/companion/java/com/android/server/companion/transport/SecureTransport.java
+++ b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
@@ -34,7 +34,7 @@
 
     private volatile boolean mShouldProcessRequests = false;
 
-    private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(100);
+    private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(500);
 
     SecureTransport(int associationId, ParcelFileDescriptor fd, Context context) {
         super(associationId, fd, context);
diff --git a/services/companion/java/com/android/server/companion/transport/Transport.java b/services/companion/java/com/android/server/companion/transport/Transport.java
index 22b18ac..8a5774e 100644
--- a/services/companion/java/com/android/server/companion/transport/Transport.java
+++ b/services/companion/java/com/android/server/companion/transport/Transport.java
@@ -284,7 +284,7 @@
         if (mListeners.containsKey(message)) {
             try {
                 mListeners.get(message).onMessageReceived(getAssociationId(), data);
-                Slog.i(TAG, "Message 0x" + Integer.toHexString(message)
+                Slog.d(TAG, "Message 0x" + Integer.toHexString(message)
                         + " is received from associationId " + mAssociationId
                         + ", sending data length " + data.length + " to the listener.");
             } catch (RemoteException ignored) {
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 1f89e57b..8962bf0 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -30,6 +30,8 @@
 import android.hardware.input.VirtualMouseButtonEvent;
 import android.hardware.input.VirtualMouseRelativeEvent;
 import android.hardware.input.VirtualMouseScrollEvent;
+import android.hardware.input.VirtualStylusButtonEvent;
+import android.hardware.input.VirtualStylusMotionEvent;
 import android.hardware.input.VirtualTouchEvent;
 import android.os.Handler;
 import android.os.IBinder;
@@ -71,12 +73,14 @@
     static final String PHYS_TYPE_MOUSE = "Mouse";
     static final String PHYS_TYPE_TOUCHSCREEN = "Touchscreen";
     static final String PHYS_TYPE_NAVIGATION_TOUCHPAD = "NavigationTouchpad";
+    static final String PHYS_TYPE_STYLUS = "Stylus";
     @StringDef(prefix = { "PHYS_TYPE_" }, value = {
             PHYS_TYPE_DPAD,
             PHYS_TYPE_KEYBOARD,
             PHYS_TYPE_MOUSE,
             PHYS_TYPE_TOUCHSCREEN,
             PHYS_TYPE_NAVIGATION_TOUCHPAD,
+            PHYS_TYPE_STYLUS,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface PhysType {
@@ -159,7 +163,7 @@
         createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId,
                 deviceToken, displayId, phys,
                 () -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys));
-        mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
+        setVirtualMousePointerDisplayId(displayId);
     }
 
     void createTouchscreen(@NonNull String deviceName, int vendorId, int productId,
@@ -188,6 +192,16 @@
         }
     }
 
+    void createStylus(@NonNull String deviceName, int vendorId, int productId,
+            @NonNull IBinder deviceToken, int displayId, int height, int width)
+            throws DeviceCreationException {
+        final String phys = createPhys(PHYS_TYPE_STYLUS);
+        createDeviceInternal(InputDeviceDescriptor.TYPE_STYLUS, deviceName, vendorId,
+                productId, deviceToken, displayId, phys,
+                () -> mNativeWrapper.openUinputStylus(deviceName, vendorId, productId, phys,
+                        height, width));
+    }
+
     void unregisterInputDevice(@NonNull IBinder token) {
         synchronized (mLock) {
             final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.remove(
@@ -221,8 +235,7 @@
         // id if there's another mouse (choose the most recent). The inputDeviceDescriptor must be
         // removed from the mInputDeviceDescriptors instance variable prior to this point.
         if (inputDeviceDescriptor.isMouse()) {
-            if (mInputManagerInternal.getVirtualMousePointerDisplayId()
-                    == inputDeviceDescriptor.getDisplayId()) {
+            if (getVirtualMousePointerDisplayId() == inputDeviceDescriptor.getDisplayId()) {
                 updateActivePointerDisplayIdLocked();
             }
         }
@@ -245,8 +258,8 @@
         mInputManagerInternal.setPointerIconVisible(visible, displayId);
     }
 
-    void setPointerAcceleration(float pointerAcceleration, int displayId) {
-        mInputManagerInternal.setPointerAcceleration(pointerAcceleration, displayId);
+    void setMousePointerAccelerationEnabled(boolean enabled, int displayId) {
+        mInputManagerInternal.setMousePointerAccelerationEnabled(enabled, displayId);
     }
 
     void setDisplayEligibilityForPointerCapture(boolean isEligible, int displayId) {
@@ -257,6 +270,7 @@
         mWindowManager.setDisplayImePolicy(displayId, policy);
     }
 
+    // TODO(b/293587049): Remove after pointer icon refactor is complete.
     @GuardedBy("mLock")
     private void updateActivePointerDisplayIdLocked() {
         InputDeviceDescriptor mostRecentlyCreatedMouse = null;
@@ -271,11 +285,11 @@
             }
         }
         if (mostRecentlyCreatedMouse != null) {
-            mInputManagerInternal.setVirtualMousePointerDisplayId(
+            setVirtualMousePointerDisplayId(
                     mostRecentlyCreatedMouse.getDisplayId());
         } else {
             // All mice have been unregistered
-            mInputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
+            setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
         }
     }
 
@@ -335,10 +349,8 @@
             if (inputDeviceDescriptor == null) {
                 return false;
             }
-            if (inputDeviceDescriptor.getDisplayId()
-                    != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
-                mInputManagerInternal.setVirtualMousePointerDisplayId(
-                        inputDeviceDescriptor.getDisplayId());
+            if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) {
+                setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId());
             }
             return mNativeWrapper.writeButtonEvent(inputDeviceDescriptor.getNativePointer(),
                     event.getButtonCode(), event.getAction(), event.getEventTimeNanos());
@@ -366,10 +378,8 @@
             if (inputDeviceDescriptor == null) {
                 return false;
             }
-            if (inputDeviceDescriptor.getDisplayId()
-                    != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
-                mInputManagerInternal.setVirtualMousePointerDisplayId(
-                        inputDeviceDescriptor.getDisplayId());
+            if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) {
+                setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId());
             }
             return mNativeWrapper.writeRelativeEvent(inputDeviceDescriptor.getNativePointer(),
                     event.getRelativeX(), event.getRelativeY(), event.getEventTimeNanos());
@@ -383,10 +393,8 @@
             if (inputDeviceDescriptor == null) {
                 return false;
             }
-            if (inputDeviceDescriptor.getDisplayId()
-                    != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
-                mInputManagerInternal.setVirtualMousePointerDisplayId(
-                        inputDeviceDescriptor.getDisplayId());
+            if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) {
+                setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId());
             }
             return mNativeWrapper.writeScrollEvent(inputDeviceDescriptor.getNativePointer(),
                     event.getXAxisMovement(), event.getYAxisMovement(), event.getEventTimeNanos());
@@ -401,12 +409,37 @@
                 throw new IllegalArgumentException(
                         "Could not get cursor position for input device for given token");
             }
-            if (inputDeviceDescriptor.getDisplayId()
-                    != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
-                mInputManagerInternal.setVirtualMousePointerDisplayId(
-                        inputDeviceDescriptor.getDisplayId());
+            if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) {
+                setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId());
             }
-            return LocalServices.getService(InputManagerInternal.class).getCursorPosition();
+            return LocalServices.getService(InputManagerInternal.class).getCursorPosition(
+                    inputDeviceDescriptor.getDisplayId());
+        }
+    }
+
+    boolean sendStylusMotionEvent(@NonNull IBinder token, @NonNull VirtualStylusMotionEvent event) {
+        synchronized (mLock) {
+            final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
+                    token);
+            if (inputDeviceDescriptor == null) {
+                return false;
+            }
+            return mNativeWrapper.writeStylusMotionEvent(inputDeviceDescriptor.getNativePointer(),
+                    event.getToolType(), event.getAction(), event.getX(), event.getY(),
+                    event.getPressure(), event.getTiltX(), event.getTiltY(),
+                    event.getEventTimeNanos());
+        }
+    }
+
+    boolean sendStylusButtonEvent(@NonNull IBinder token, @NonNull VirtualStylusButtonEvent event) {
+        synchronized (mLock) {
+            final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
+                    token);
+            if (inputDeviceDescriptor == null) {
+                return false;
+            }
+            return mNativeWrapper.writeStylusButtonEvent(inputDeviceDescriptor.getNativePointer(),
+                    event.getButtonCode(), event.getAction(), event.getEventTimeNanos());
         }
     }
 
@@ -437,7 +470,7 @@
         }
     }
 
-    @VisibleForTesting
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     Map<IBinder, InputDeviceDescriptor> getInputDeviceDescriptors() {
         final Map<IBinder, InputDeviceDescriptor> inputDeviceDescriptors = new ArrayMap<>();
         synchronized (mLock) {
@@ -454,6 +487,8 @@
             String phys);
     private static native long nativeOpenUinputTouchscreen(String deviceName, int vendorId,
             int productId, String phys, int height, int width);
+    private static native long nativeOpenUinputStylus(String deviceName, int vendorId,
+            int productId, String phys, int height, int width);
     private static native void nativeCloseUinput(long ptr);
     private static native boolean nativeWriteDpadKeyEvent(long ptr, int androidKeyCode, int action,
             long eventTimeNanos);
@@ -468,6 +503,10 @@
             float relativeY, long eventTimeNanos);
     private static native boolean nativeWriteScrollEvent(long ptr, float xAxisMovement,
             float yAxisMovement, long eventTimeNanos);
+    private static native boolean nativeWriteStylusMotionEvent(long ptr, int toolType, int action,
+            int locationX, int locationY, int pressure, int tiltX, int tiltY, long eventTimeNanos);
+    private static native boolean nativeWriteStylusButtonEvent(long ptr, int buttonCode, int action,
+            long eventTimeNanos);
 
     /** Wrapper around the static native methods for tests. */
     @VisibleForTesting
@@ -491,6 +530,11 @@
                     width);
         }
 
+        public long openUinputStylus(String deviceName, int vendorId, int productId, String phys,
+                int height, int width) {
+            return nativeOpenUinputStylus(deviceName, vendorId, productId, phys, height, width);
+        }
+
         public void closeUinput(long ptr) {
             nativeCloseUinput(ptr);
         }
@@ -527,21 +571,35 @@
                 long eventTimeNanos) {
             return nativeWriteScrollEvent(ptr, xAxisMovement, yAxisMovement, eventTimeNanos);
         }
+
+        public boolean writeStylusMotionEvent(long ptr, int toolType, int action, int locationX,
+                int locationY, int pressure, int tiltX, int tiltY, long eventTimeNanos) {
+            return nativeWriteStylusMotionEvent(ptr, toolType, action, locationX, locationY,
+                    pressure, tiltX, tiltY, eventTimeNanos);
+        }
+
+        public boolean writeStylusButtonEvent(long ptr, int buttonCode, int action,
+                long eventTimeNanos) {
+            return nativeWriteStylusButtonEvent(ptr, buttonCode, action, eventTimeNanos);
+        }
     }
 
-    @VisibleForTesting static final class InputDeviceDescriptor {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    static final class InputDeviceDescriptor {
 
         static final int TYPE_KEYBOARD = 1;
         static final int TYPE_MOUSE = 2;
         static final int TYPE_TOUCHSCREEN = 3;
         static final int TYPE_DPAD = 4;
         static final int TYPE_NAVIGATION_TOUCHPAD = 5;
+        static final int TYPE_STYLUS = 6;
         @IntDef(prefix = { "TYPE_" }, value = {
                 TYPE_KEYBOARD,
                 TYPE_MOUSE,
                 TYPE_TOUCHSCREEN,
                 TYPE_DPAD,
                 TYPE_NAVIGATION_TOUCHPAD,
+                TYPE_STYLUS,
         })
         @Retention(RetentionPolicy.SOURCE)
         @interface Type {
@@ -782,4 +840,22 @@
         /** Returns true if the calling thread is a valid thread for device creation. */
         boolean isValidThread();
     }
+
+    // TODO(b/293587049): Remove after pointer icon refactor is complete.
+    private void setVirtualMousePointerDisplayId(int displayId) {
+        if (com.android.input.flags.Flags.enablePointerChoreographer()) {
+            // We no longer need to set the pointer display when pointer choreographer is enabled.
+            return;
+        }
+        mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
+    }
+
+    // TODO(b/293587049): Remove after pointer icon refactor is complete.
+    private int getVirtualMousePointerDisplayId() {
+        if (com.android.input.flags.Flags.enablePointerChoreographer()) {
+            // We no longer need to get the pointer display when pointer choreographer is enabled.
+            return Display.INVALID_DISPLAY;
+        }
+        return mInputManagerInternal.getVirtualMousePointerDisplayId();
+    }
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 58aa2c3..f13f49a 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -23,6 +23,7 @@
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.NAVIGATION_POLICY_DEFAULT_ALLOWED;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_ACTIVITY;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CLIPBOARD;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
 import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
@@ -75,6 +76,9 @@
 import android.hardware.input.VirtualMouseRelativeEvent;
 import android.hardware.input.VirtualMouseScrollEvent;
 import android.hardware.input.VirtualNavigationTouchpadConfig;
+import android.hardware.input.VirtualStylusButtonEvent;
+import android.hardware.input.VirtualStylusConfig;
+import android.hardware.input.VirtualStylusMotionEvent;
 import android.hardware.input.VirtualTouchEvent;
 import android.hardware.input.VirtualTouchscreenConfig;
 import android.os.Binder;
@@ -258,7 +262,9 @@
                 runningAppsChangedCallback,
                 params,
                 DisplayManagerGlobal.getInstance(),
-                Flags.virtualCamera() ? new VirtualCameraController() : null);
+                Flags.virtualCamera()
+                        ? new VirtualCameraController(params.getDevicePolicy(POLICY_TYPE_CAMERA))
+                        : null);
     }
 
     @VisibleForTesting
@@ -776,6 +782,26 @@
 
     @Override // Binder call
     @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void createVirtualStylus(@NonNull VirtualStylusConfig config,
+            @NonNull IBinder deviceToken) {
+        super.createVirtualStylus_enforcePermission();
+        Objects.requireNonNull(config);
+        Objects.requireNonNull(deviceToken);
+        checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId());
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mInputController.createStylus(config.getInputDeviceName(), config.getVendorId(),
+                    config.getProductId(), deviceToken, config.getAssociatedDisplayId(),
+                    config.getHeight(), config.getWidth());
+        } catch (InputController.DeviceCreationException e) {
+            throw new IllegalArgumentException(e);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void unregisterInputDevice(IBinder token) {
         super.unregisterInputDevice_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
@@ -881,6 +907,36 @@
 
     @Override // Binder call
     @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public boolean sendStylusMotionEvent(@NonNull IBinder token,
+            @NonNull VirtualStylusMotionEvent event) {
+        super.sendStylusMotionEvent_enforcePermission();
+        Objects.requireNonNull(token);
+        Objects.requireNonNull(event);
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return mInputController.sendStylusMotionEvent(token, event);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public boolean sendStylusButtonEvent(@NonNull IBinder token,
+            @NonNull VirtualStylusButtonEvent event) {
+        super.sendStylusButtonEvent_enforcePermission();
+        Objects.requireNonNull(token);
+        Objects.requireNonNull(event);
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return mInputController.sendStylusButtonEvent(token, event);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void setShowPointerIcon(boolean showPointerIcon) {
         super.setShowPointerIcon_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
@@ -1110,7 +1166,7 @@
         final long token = Binder.clearCallingIdentity();
         try {
             mInputController.setShowPointerIcon(showPointer, displayId);
-            mInputController.setPointerAcceleration(1f, displayId);
+            mInputController.setMousePointerAccelerationEnabled(false, displayId);
             mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false,
                     displayId);
             // WM throws a SecurityException if the display is untrusted.
@@ -1337,6 +1393,11 @@
         }
     }
 
+    boolean isInputDeviceOwnedByVirtualDevice(int inputDeviceId) {
+        return mInputController.getInputDeviceDescriptors().values().stream().anyMatch(
+                inputDeviceDescriptor -> inputDeviceDescriptor.getInputDeviceId() == inputDeviceId);
+    }
+
     void onEnteringPipBlocked(int uid) {
         // Do nothing. ActivityRecord#checkEnterPictureInPictureState logs that the display does not
         // support PiP.
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 0d5cdcb..2168cb2 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -56,6 +56,7 @@
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.ExceptionUtils;
 import android.util.Slog;
@@ -112,7 +113,7 @@
             Context.DEVICE_ID_DEFAULT + 1);
 
     @GuardedBy("mVirtualDeviceManagerLock")
-    private List<AssociationInfo> mActiveAssociations = new ArrayList<>();
+    private ArrayMap<String, AssociationInfo> mActiveAssociations = new ArrayMap<>();
 
     private final CompanionDeviceManager.OnAssociationsChangedListener mCdmAssociationListener =
             new CompanionDeviceManager.OnAssociationsChangedListener() {
@@ -343,34 +344,29 @@
 
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     void onCdmAssociationsChanged(List<AssociationInfo> associations) {
-        List<AssociationInfo> vdmAssociations = new ArrayList<>();
-        Set<Integer> activeAssociationIds = new HashSet<>();
+        ArrayMap<String, AssociationInfo> vdmAssociations = new ArrayMap<>();
         for (int i = 0; i < associations.size(); ++i) {
             AssociationInfo association = associations.get(i);
-            if (VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES.contains(association.getDeviceProfile())) {
-                vdmAssociations.add(association);
-                activeAssociationIds.add(association.getId());
+            if (VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES.contains(association.getDeviceProfile())
+                    && !association.isRevoked()) {
+                String persistentId =
+                        VirtualDeviceImpl.createPersistentDeviceId(association.getId());
+                vdmAssociations.put(persistentId, association);
             }
         }
         Set<VirtualDeviceImpl> virtualDevicesToRemove = new HashSet<>();
-        Set<String> removedPersistentDeviceIds = new HashSet<>();
+        Set<String> removedPersistentDeviceIds;
         synchronized (mVirtualDeviceManagerLock) {
-            for (int i = 0; i < mActiveAssociations.size(); ++i) {
-                AssociationInfo associationInfo = mActiveAssociations.get(i);
-                if (!activeAssociationIds.contains(associationInfo.getId())) {
-                    removedPersistentDeviceIds.add(
-                            VirtualDeviceImpl.createPersistentDeviceId(associationInfo.getId()));
-                }
-            }
+            removedPersistentDeviceIds = mActiveAssociations.keySet();
+            removedPersistentDeviceIds.removeAll(vdmAssociations.keySet());
+            mActiveAssociations = vdmAssociations;
 
             for (int i = 0; i < mVirtualDevices.size(); i++) {
                 VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i);
-                if (!activeAssociationIds.contains(virtualDevice.getAssociationId())) {
+                if (removedPersistentDeviceIds.contains(virtualDevice.getPersistentDeviceId())) {
                     virtualDevicesToRemove.add(virtualDevice);
                 }
             }
-
-            mActiveAssociations = vdmAssociations;
         }
 
         for (VirtualDeviceImpl virtualDevice : virtualDevicesToRemove) {
@@ -577,6 +573,21 @@
             return Context.DEVICE_ID_DEFAULT;
         }
 
+        @Override // Binder call
+        public @Nullable CharSequence getDisplayNameForPersistentDeviceId(
+                @NonNull String persistentDeviceId) {
+            final AssociationInfo associationInfo;
+            synchronized (mVirtualDeviceManagerLock) {
+                associationInfo = mActiveAssociations.get(persistentDeviceId);
+            }
+            return associationInfo == null ? null : associationInfo.getDisplayName();
+        }
+
+        @Override // Binder call
+        public @NonNull List<String> getAllPersistentDeviceIds() {
+            return new ArrayList<>(mLocalService.getAllPersistentDeviceIds());
+        }
+
         // Binder call
         @Override
         public boolean isValidVirtualDeviceId(int deviceId) {
@@ -838,10 +849,11 @@
         }
 
         @Override
-        public boolean isDisplayOwnedByAnyVirtualDevice(int displayId) {
+        public boolean isInputDeviceOwnedByVirtualDevice(int inputDeviceId) {
             ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
             for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
-                if (virtualDevicesSnapshot.get(i).isDisplayOwnedByVirtualDevice(displayId)) {
+                if (virtualDevicesSnapshot.get(i)
+                        .isInputDeviceOwnedByVirtualDevice(inputDeviceId)) {
                     return true;
                 }
             }
@@ -884,15 +896,9 @@
 
         @Override
         public @NonNull Set<String> getAllPersistentDeviceIds() {
-            Set<String> persistentIds = new ArraySet<>();
             synchronized (mVirtualDeviceManagerLock) {
-                for (int i = 0; i < mActiveAssociations.size(); ++i) {
-                    AssociationInfo associationInfo = mActiveAssociations.get(i);
-                    persistentIds.add(
-                            VirtualDeviceImpl.createPersistentDeviceId(associationInfo.getId()));
-                }
+                return Set.copyOf(mActiveAssociations.keySet());
             }
-            return persistentIds;
         }
 
         @Override
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
index 2f9b6a5..2d82b5e 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,10 +16,13 @@
 
 package com.android.server.companion.virtual.camera;
 
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+
 import static com.android.server.companion.virtual.camera.VirtualCameraConversionUtil.getServiceCameraConfiguration;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.companion.virtual.VirtualDeviceParams.DevicePolicy;
 import android.companion.virtual.camera.VirtualCameraConfig;
 import android.companion.virtualcamera.IVirtualCameraService;
 import android.companion.virtualcamera.VirtualCameraConfiguration;
@@ -51,15 +54,21 @@
 
     @GuardedBy("mServiceLock")
     @Nullable private IVirtualCameraService mVirtualCameraService;
+    @DevicePolicy
+    private final int mCameraPolicy;
 
     @GuardedBy("mCameras")
     private final Map<IBinder, CameraDescriptor> mCameras = new ArrayMap<>();
 
-    public VirtualCameraController() {}
+    public VirtualCameraController(@DevicePolicy int cameraPolicy) {
+        this(/* virtualCameraService= */ null, cameraPolicy);
+    }
 
     @VisibleForTesting
-    VirtualCameraController(IVirtualCameraService virtualCameraService) {
+    VirtualCameraController(IVirtualCameraService virtualCameraService,
+            @DevicePolicy int cameraPolicy) {
         mVirtualCameraService = virtualCameraService;
+        mCameraPolicy = cameraPolicy;
     }
 
     /**
@@ -68,6 +77,8 @@
      * @param cameraConfig The {@link VirtualCameraConfig} sent by the client.
      */
     public void registerCamera(@NonNull VirtualCameraConfig cameraConfig) {
+        checkConfigByPolicy(cameraConfig);
+
         connectVirtualCameraServiceIfNeeded();
 
         try {
@@ -173,6 +184,29 @@
         }
     }
 
+    private void checkConfigByPolicy(VirtualCameraConfig config) {
+        if (mCameraPolicy == DEVICE_POLICY_DEFAULT) {
+            throw new IllegalArgumentException(
+                    "Cannot create virtual camera with DEVICE_POLICY_DEFAULT for "
+                            + "POLICY_TYPE_CAMERA");
+        } else if (isLensFacingAlreadyPresent(config.getLensFacing())) {
+            throw new IllegalArgumentException(
+                    "Only a single virtual camera can be created with lens facing "
+                            + config.getLensFacing());
+        }
+    }
+
+    private boolean isLensFacingAlreadyPresent(int lensFacing) {
+        synchronized (mCameras) {
+            for (CameraDescriptor cameraDescriptor : mCameras.values()) {
+                if (cameraDescriptor.mConfig.getLensFacing() == lensFacing) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     private void connectVirtualCameraServiceIfNeeded() {
         synchronized (mServiceLock) {
             // Try to connect to service if not connected already.
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
index f24c4cc..c4a84b0 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
@@ -25,6 +25,7 @@
 import android.companion.virtualcamera.SupportedStreamConfiguration;
 import android.companion.virtualcamera.VirtualCameraConfiguration;
 import android.graphics.ImageFormat;
+import android.graphics.PixelFormat;
 import android.os.RemoteException;
 import android.view.Surface;
 
@@ -45,12 +46,12 @@
             getServiceCameraConfiguration(@NonNull VirtualCameraConfig cameraConfig)
                     throws RemoteException {
         VirtualCameraConfiguration serviceConfiguration = new VirtualCameraConfiguration();
-
         serviceConfiguration.supportedStreamConfigs =
                 cameraConfig.getStreamConfigs().stream()
                         .map(VirtualCameraConversionUtil::convertSupportedStreamConfiguration)
                         .toArray(SupportedStreamConfiguration[]::new);
-
+        serviceConfiguration.sensorOrientation = cameraConfig.getSensorOrientation();
+        serviceConfiguration.lensFacing = cameraConfig.getLensFacing();
         serviceConfiguration.virtualCameraCallback = convertCallback(cameraConfig.getCallback());
         return serviceConfiguration;
     }
@@ -60,12 +61,10 @@
             @NonNull IVirtualCameraCallback camera) {
         return new android.companion.virtualcamera.IVirtualCameraCallback.Stub() {
             @Override
-            public void onStreamConfigured(
-                    int streamId, Surface surface, int width, int height, int pixelFormat)
-                    throws RemoteException {
-                VirtualCameraStreamConfig streamConfig =
-                        createStreamConfig(width, height, pixelFormat);
-                camera.onStreamConfigured(streamId, surface, streamConfig);
+            public void onStreamConfigured(int streamId, Surface surface, int width, int height,
+                    int format) throws RemoteException {
+                camera.onStreamConfigured(streamId, surface, width, height,
+                        convertToJavaFormat(format));
             }
 
             @Override
@@ -81,23 +80,30 @@
     }
 
     @NonNull
-    private static VirtualCameraStreamConfig createStreamConfig(
-            int width, int height, int pixelFormat) {
-        return new VirtualCameraStreamConfig(width, height, pixelFormat);
-    }
-
-    @NonNull
     private static SupportedStreamConfiguration convertSupportedStreamConfiguration(
             VirtualCameraStreamConfig stream) {
         SupportedStreamConfiguration supportedConfig = new SupportedStreamConfiguration();
         supportedConfig.height = stream.getHeight();
         supportedConfig.width = stream.getWidth();
-        supportedConfig.pixelFormat = convertFormat(stream.getFormat());
+        supportedConfig.pixelFormat = convertToHalFormat(stream.getFormat());
+        supportedConfig.maxFps = stream.getMaximumFramesPerSecond();
         return supportedConfig;
     }
 
-    private static int convertFormat(int format) {
-        return format == ImageFormat.YUV_420_888 ? Format.YUV_420_888 : Format.UNKNOWN;
+    private static int convertToHalFormat(int javaFormat) {
+        return switch (javaFormat) {
+            case ImageFormat.YUV_420_888 -> Format.YUV_420_888;
+            case PixelFormat.RGBA_8888 -> Format.RGBA_8888;
+            default -> Format.UNKNOWN;
+        };
+    }
+
+    private static int convertToJavaFormat(int halFormat) {
+        return switch (halFormat) {
+            case Format.YUV_420_888 -> ImageFormat.YUV_420_888;
+            case Format.RGBA_8888 -> PixelFormat.RGBA_8888;
+            default -> ImageFormat.UNKNOWN;
+        };
     }
 
     private VirtualCameraConversionUtil() {
diff --git a/services/companion/lint-baseline.xml b/services/companion/lint-baseline.xml
index 03eae39..020126f 100644
--- a/services/companion/lint-baseline.xml
+++ b/services/companion/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.1.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NonUserGetterCalled"
@@ -12,4 +12,4 @@
             column="14"/>
     </issue>
 
-</issues>
+</issues>
\ No newline at end of file
diff --git a/services/core/Android.bp b/services/core/Android.bp
index a3fc3bf..8e35b74 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -30,6 +30,14 @@
     ],
 }
 
+java_library_static {
+    name: "services-config-update",
+    srcs: [
+        "java/**/ConfigUpdateInstallReceiver.java",
+        "java/**/*.logtags",
+    ],
+}
+
 genrule {
     name: "services.core.protologsrc",
     srcs: [
@@ -131,6 +139,7 @@
 
     libs: [
         "services.net",
+        "android.frameworks.location.altitude-V2-java",
         "android.hardware.common-V2-java",
         "android.hardware.light-V2.0-java",
         "android.hardware.gnss-V2-java",
@@ -152,7 +161,6 @@
     ],
 
     static_libs: [
-        "android.frameworks.location.altitude-V2-java", // AIDL
         "android.frameworks.vibrator-V1-java", // AIDL
         "android.hardware.authsecret-V1.0-java",
         "android.hardware.authsecret-V1-java",
@@ -203,7 +211,10 @@
         "com_android_wm_shell_flags_lib",
         "com.android.server.utils_aconfig-java",
         "service-jobscheduler-deviceidle.flags-aconfig-java",
+        "backup_flags_lib",
         "policy_flags_lib",
+        "net_flags_lib",
+        "stats_flags_lib",
     ],
     javac_shard_size: 50,
     javacflags: [
@@ -234,9 +245,6 @@
 java_library {
     name: "services.core",
     static_libs: ["services.core.priorityboosted"],
-    lint: {
-        baseline_filename: "lint-baseline.xml",
-    },
 }
 
 java_library_host {
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 5a44ac8..9d9e7c9 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -1387,6 +1387,8 @@
                 case BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE:
                 case BatteryManager.BATTERY_PROPERTY_FIRST_USAGE_DATE:
                 case BatteryManager.BATTERY_PROPERTY_CHARGING_POLICY:
+                case BatteryManager.BATTERY_PROPERTY_SERIAL_NUMBER:
+                case BatteryManager.BATTERY_PROPERTY_PART_STATUS:
                     mContext.enforceCallingPermission(
                             android.Manifest.permission.BATTERY_STATS, null);
                     break;
diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java
index b87184a..416b36f 100644
--- a/services/core/java/com/android/server/BinderCallsStatsService.java
+++ b/services/core/java/com/android/server/BinderCallsStatsService.java
@@ -288,22 +288,23 @@
                         CachedDeviceState.Readonly.class);
                 mBinderCallsStats.setDeviceState(deviceState);
 
-                BatteryStatsInternal batteryStatsInternal = getLocalService(
-                        BatteryStatsInternal.class);
-                mBinderCallsStats.setCallStatsObserver(new BinderInternal.CallStatsObserver() {
-                    @Override
-                    public void noteCallStats(int workSourceUid, long incrementalCallCount,
-                            Collection<BinderCallsStats.CallStat> callStats) {
-                        batteryStatsInternal.noteBinderCallStats(workSourceUid,
-                                incrementalCallCount, callStats);
-                    }
+                if (!com.android.server.power.optimization.Flags.disableSystemServicePowerAttr()) {
+                    BatteryStatsInternal batteryStatsInternal = getLocalService(
+                            BatteryStatsInternal.class);
+                    mBinderCallsStats.setCallStatsObserver(new BinderInternal.CallStatsObserver() {
+                        @Override
+                        public void noteCallStats(int workSourceUid, long incrementalCallCount,
+                                Collection<BinderCallsStats.CallStat> callStats) {
+                            batteryStatsInternal.noteBinderCallStats(workSourceUid,
+                                    incrementalCallCount, callStats);
+                        }
 
-                    @Override
-                    public void noteBinderThreadNativeIds(int[] binderThreadNativeTids) {
-                        batteryStatsInternal.noteBinderThreadNativeIds(binderThreadNativeTids);
-                    }
-                });
-
+                        @Override
+                        public void noteBinderThreadNativeIds(int[] binderThreadNativeTids) {
+                            batteryStatsInternal.noteBinderThreadNativeIds(binderThreadNativeTids);
+                        }
+                    });
+                }
                 // It needs to be called before mService.systemReady to make sure the observer is
                 // initialized before installing it.
                 mWorkSourceProvider.systemReady(getContext());
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 329aac6..9f279b1 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -48,8 +48,6 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.am.DropboxRateLimiter;
-import com.android.server.os.TombstoneProtos;
-import com.android.server.os.TombstoneProtos.Tombstone;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -62,14 +60,11 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.attribute.PosixFilePermissions;
-import java.util.AbstractMap;
 import java.util.HashMap;
 import java.util.Iterator;
-import java.util.Map;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import java.util.stream.Collectors;
 
 /**
  * Performs a number of miscellaneous, non-system-critical actions
@@ -337,12 +332,12 @@
      *
      * @param ctx Context
      * @param tombstone path to the tombstone
-     * @param tombstoneProto the parsed proto tombstone
+     * @param proto whether the tombstone is stored as proto
      * @param processName the name of the process corresponding to the tombstone
      * @param tmpFileLock the lock for reading/writing tmp files
      */
     public static void addTombstoneToDropBox(
-                Context ctx, File tombstone, Tombstone tombstoneProto, String processName,
+                Context ctx, File tombstone, boolean proto, String processName,
                 ReentrantLock tmpFileLock) {
         final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
         if (db == null) {
@@ -352,33 +347,31 @@
 
         // Check if we should rate limit and abort early if needed.
         DropboxRateLimiter.RateLimitResult rateLimitResult =
-                sDropboxRateLimiter.shouldRateLimit(TAG_TOMBSTONE_PROTO_WITH_HEADERS, processName);
+                sDropboxRateLimiter.shouldRateLimit(
+                        proto ? TAG_TOMBSTONE_PROTO_WITH_HEADERS : TAG_TOMBSTONE, processName);
         if (rateLimitResult.shouldRateLimit()) return;
 
         HashMap<String, Long> timestamps = readTimestamps();
         try {
-            // Remove the memory data from the proto.
-            Tombstone tombstoneProtoWithoutMemory = removeMemoryFromTombstone(tombstoneProto);
-
-            final byte[] tombstoneBytes = tombstoneProtoWithoutMemory.toByteArray();
-
-            // Use JNI to call the c++ proto to text converter and add the headers to the tombstone.
-            String tombstoneWithoutMemory = new StringBuilder(getBootHeadersToLogAndUpdate())
-                    .append(rateLimitResult.createHeader())
-                    .append(getTombstoneText(tombstoneBytes))
-                    .toString();
-
-            // Add the tombstone without memory data to dropbox.
-            db.addText(TAG_TOMBSTONE, tombstoneWithoutMemory);
-
-            // Add the tombstone proto to dropbox.
-            if (recordFileTimestamp(tombstone, timestamps)) {
-                tmpFileLock.lock();
-                try {
-                    addAugmentedProtoToDropbox(tombstone, tombstoneBytes, db, rateLimitResult);
-                } finally {
-                    tmpFileLock.unlock();
+            if (proto) {
+                if (recordFileTimestamp(tombstone, timestamps)) {
+                    // We need to attach the count indicating the number of dropped dropbox entries
+                    // due to rate limiting. Do this by enclosing the proto tombsstone in a
+                    // container proto that has the dropped entry count and the proto tombstone as
+                    // bytes (to avoid the complexity of reading and writing nested protos).
+                    tmpFileLock.lock();
+                    try {
+                        addAugmentedProtoToDropbox(tombstone, db, rateLimitResult);
+                    } finally {
+                        tmpFileLock.unlock();
+                    }
                 }
+            } else {
+                // Add the header indicating how many events have been dropped due to rate limiting.
+                final String headers = getBootHeadersToLogAndUpdate()
+                        + rateLimitResult.createHeader();
+                addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE,
+                                 TAG_TOMBSTONE);
             }
         } catch (IOException e) {
             Slog.e(TAG, "Can't log tombstone", e);
@@ -387,8 +380,11 @@
     }
 
     private static void addAugmentedProtoToDropbox(
-                File tombstone, byte[] tombstoneBytes, DropBoxManager db,
+                File tombstone, DropBoxManager db,
                 DropboxRateLimiter.RateLimitResult rateLimitResult) throws IOException {
+        // Read the proto tombstone file as bytes.
+        final byte[] tombstoneBytes = Files.readAllBytes(tombstone.toPath());
+
         final File tombstoneProtoWithHeaders = File.createTempFile(
                 tombstone.getName(), ".tmp", TOMBSTONE_TMP_DIR);
         Files.setPosixFilePermissions(
@@ -421,8 +417,6 @@
         }
     }
 
-    private static native String getTombstoneText(byte[] tombstoneBytes);
-
     private static void addLastkToDropBox(
             DropBoxManager db, HashMap<String, Long> timestamps,
             String headers, String footers, String filename, int maxSize,
@@ -440,31 +434,6 @@
         addFileWithFootersToDropBox(db, timestamps, headers, footers, filename, maxSize, tag);
     }
 
-    /** Removes memory information from the Tombstone proto. */
-    @VisibleForTesting
-    public static Tombstone removeMemoryFromTombstone(Tombstone tombstoneProto) {
-        Tombstone.Builder tombstoneBuilder = tombstoneProto.toBuilder()
-                .clearMemoryMappings()
-                .clearThreads()
-                .putAllThreads(tombstoneProto.getThreadsMap().entrySet()
-                        .stream()
-                        .map(BootReceiver::clearMemoryDump)
-                        .collect(Collectors.toMap(e->e.getKey(), e->e.getValue())));
-
-        if (tombstoneProto.hasSignalInfo()) {
-            tombstoneBuilder.setSignalInfo(
-                    tombstoneProto.getSignalInfo().toBuilder().clearFaultAdjacentMetadata());
-        }
-
-        return tombstoneBuilder.build();
-    }
-
-    private static AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread> clearMemoryDump(
-            Map.Entry<Integer, TombstoneProtos.Thread> e) {
-        return new AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread>(
-            e.getKey(), e.getValue().toBuilder().clearMemoryDump().build());
-    }
-
     private static void addFileToDropBox(
             DropBoxManager db, HashMap<String, Long> timestamps,
             String headers, String filename, int maxSize, String tag) throws IOException {
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index 70bd4b3..f619ca3 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -27,6 +27,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.RankingMap;
@@ -34,11 +35,11 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wm.SensitiveContentPackages.PackageInfo;
 import com.android.server.wm.WindowManagerInternal;
 
-import java.util.Collections;
 import java.util.Set;
 
 /**
@@ -49,23 +50,38 @@
     private static final String TAG = "SensitiveContentProtect";
     private static final boolean DEBUG = false;
 
-    @VisibleForTesting
-    NotificationListener mNotificationListener;
+    @VisibleForTesting NotificationListener mNotificationListener;
     private @Nullable MediaProjectionManager mProjectionManager;
     private @Nullable WindowManagerInternal mWindowManager;
 
+    final Object mSensitiveContentProtectionLock = new Object();
+
+    @GuardedBy("mSensitiveContentProtectionLock")
+    private boolean mProjectionActive = false;
+
     private final MediaProjectionManager.Callback mProjectionCallback =
             new MediaProjectionManager.Callback() {
                 @Override
                 public void onStart(MediaProjectionInfo info) {
                     if (DEBUG) Log.d(TAG, "onStart projection: " + info);
-                    onProjectionStart();
+                    Trace.beginSection(
+                            "SensitiveContentProtectionManagerService.onProjectionStart");
+                    try {
+                        onProjectionStart();
+                    } finally {
+                        Trace.endSection();
+                    }
                 }
 
                 @Override
                 public void onStop(MediaProjectionInfo info) {
                     if (DEBUG) Log.d(TAG, "onStop projection: " + info);
-                    onProjectionEnd();
+                    Trace.beginSection("SensitiveContentProtectionManagerService.onProjectionStop");
+                    try {
+                        onProjectionEnd();
+                    } finally {
+                        Trace.endSection();
+                    }
                 }
             };
 
@@ -90,8 +106,7 @@
     }
 
     @VisibleForTesting
-    void init(MediaProjectionManager projectionManager,
-            WindowManagerInternal windowManager) {
+    void init(MediaProjectionManager projectionManager, WindowManagerInternal windowManager) {
         if (DEBUG) Log.d(TAG, "init");
 
         checkNotNull(projectionManager, "Failed to get valid MediaProjectionManager");
@@ -105,7 +120,8 @@
         mProjectionManager.addCallback(mProjectionCallback, new Handler(Looper.getMainLooper()));
 
         try {
-            mNotificationListener.registerAsSystemService(getContext(),
+            mNotificationListener.registerAsSystemService(
+                    getContext(),
                     new ComponentName(getContext(), NotificationListener.class),
                     UserHandle.USER_ALL);
         } catch (RemoteException e) {
@@ -132,14 +148,23 @@
     }
 
     private void onProjectionStart() {
-        StatusBarNotification[] notifications;
-        try {
-            notifications = mNotificationListener.getActiveNotifications();
-        } catch (SecurityException e) {
-            Log.e(TAG, "SensitiveContentProtectionManagerService doesn't have access.", e);
-            notifications = new StatusBarNotification[0];
+        synchronized (mSensitiveContentProtectionLock) {
+            mProjectionActive = true;
+            updateAppsThatShouldBlockScreenCapture();
         }
+    }
 
+    private void onProjectionEnd() {
+        synchronized (mSensitiveContentProtectionLock) {
+            mProjectionActive = false;
+
+            // notify windowmanager to clear any sensitive notifications observed during projection
+            // session
+            mWindowManager.clearBlockedApps();
+        }
+    }
+
+    private void updateAppsThatShouldBlockScreenCapture() {
         RankingMap rankingMap;
         try {
             rankingMap = mNotificationListener.getCurrentRanking();
@@ -148,41 +173,115 @@
             rankingMap = null;
         }
 
-        // notify windowmanager of any currently posted sensitive content notifications
-        Set<PackageInfo> packageInfos = getSensitivePackagesFromNotifications(
-                notifications,
-                rankingMap);
-
-        mWindowManager.setShouldBlockScreenCaptureForApp(packageInfos);
+        updateAppsThatShouldBlockScreenCapture(rankingMap);
     }
 
-    private void onProjectionEnd() {
-        // notify windowmanager to clear any sensitive notifications observed during projection
-        // session
-        mWindowManager.setShouldBlockScreenCaptureForApp(Collections.emptySet());
-    }
-
-    private Set<PackageInfo> getSensitivePackagesFromNotifications(
-            StatusBarNotification[] notifications, RankingMap rankingMap) {
-        if (rankingMap == null) {
-            Log.w(TAG, "Ranking map not initialized.");
-            return Collections.emptySet();
+    private void updateAppsThatShouldBlockScreenCapture(RankingMap rankingMap) {
+        StatusBarNotification[] notifications;
+        try {
+            notifications = mNotificationListener.getActiveNotifications();
+        } catch (SecurityException e) {
+            Log.e(TAG, "SensitiveContentProtectionManagerService doesn't have access.", e);
+            notifications = new StatusBarNotification[0];
         }
 
-        Set<PackageInfo> sensitivePackages = new ArraySet<>();
+        // notify windowmanager of any currently posted sensitive content notifications
+        ArraySet<PackageInfo> packageInfos =
+                getSensitivePackagesFromNotifications(notifications, rankingMap);
+
+        mWindowManager.addBlockScreenCaptureForApps(packageInfos);
+    }
+
+    private ArraySet<PackageInfo> getSensitivePackagesFromNotifications(
+            @NonNull StatusBarNotification[] notifications, RankingMap rankingMap) {
+        ArraySet<PackageInfo> sensitivePackages = new ArraySet<>();
+        if (rankingMap == null) {
+            Log.w(TAG, "Ranking map not initialized.");
+            return sensitivePackages;
+        }
+
         for (StatusBarNotification sbn : notifications) {
-            NotificationListenerService.Ranking ranking =
-                    rankingMap.getRawRankingObject(sbn.getKey());
-            if (ranking != null && ranking.hasSensitiveContent()) {
-                PackageInfo info = new PackageInfo(sbn.getPackageName(), sbn.getUid());
+            PackageInfo info = getSensitivePackageFromNotification(sbn, rankingMap);
+            if (info != null) {
                 sensitivePackages.add(info);
             }
         }
         return sensitivePackages;
     }
 
-    // TODO(b/317251408): add trigger that updates on onNotificationPosted,
-    //  onNotificationRankingUpdate and onListenerConnected
+    private PackageInfo getSensitivePackageFromNotification(
+            StatusBarNotification sbn, RankingMap rankingMap) {
+        if (sbn == null) {
+            Log.w(TAG, "Unable to protect null notification");
+            return null;
+        }
+        if (rankingMap == null) {
+            Log.w(TAG, "Ranking map not initialized.");
+            return null;
+        }
+
+        NotificationListenerService.Ranking ranking = rankingMap.getRawRankingObject(sbn.getKey());
+        if (ranking != null && ranking.hasSensitiveContent()) {
+            return new PackageInfo(sbn.getPackageName(), sbn.getUid());
+        }
+        return null;
+    }
+
     @VisibleForTesting
-    static class NotificationListener extends NotificationListenerService {}
+    class NotificationListener extends NotificationListenerService {
+        @Override
+        public void onListenerConnected() {
+            super.onListenerConnected();
+            Trace.beginSection("SensitiveContentProtectionManagerService.onListenerConnected");
+            try {
+                // Projection started before notification listener was connected
+                synchronized (mSensitiveContentProtectionLock) {
+                    if (mProjectionActive) {
+                        updateAppsThatShouldBlockScreenCapture();
+                    }
+                }
+            } finally {
+                Trace.endSection();
+            }
+        }
+
+        @Override
+        public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+            super.onNotificationPosted(sbn, rankingMap);
+            Trace.beginSection("SensitiveContentProtectionManagerService.onNotificationPosted");
+            try {
+                synchronized (mSensitiveContentProtectionLock) {
+                    if (!mProjectionActive) {
+                        return;
+                    }
+
+                    // notify windowmanager of any currently posted sensitive content notifications
+                    PackageInfo packageInfo = getSensitivePackageFromNotification(sbn, rankingMap);
+
+                    if (packageInfo != null) {
+                        mWindowManager.addBlockScreenCaptureForApps(
+                                new ArraySet(Set.of(packageInfo)));
+                    }
+                }
+            } finally {
+                Trace.endSection();
+            }
+        }
+
+        @Override
+        public void onNotificationRankingUpdate(RankingMap rankingMap) {
+            super.onNotificationRankingUpdate(rankingMap);
+            Trace.beginSection(
+                    "SensitiveContentProtectionManagerService.onNotificationRankingUpdate");
+            try {
+                synchronized (mSensitiveContentProtectionLock) {
+                    if (mProjectionActive) {
+                        updateAppsThatShouldBlockScreenCapture(rankingMap);
+                    }
+                }
+            } finally {
+                Trace.endSection();
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 19a9239..ea1b0f5 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1076,6 +1076,7 @@
             final UserManager userManager = mContext.getSystemService(UserManager.class);
             final List<UserInfo> users = userManager.getUsers();
 
+            extendWatchdogTimeout("#onReset might be slow");
             mStorageSessionController.onReset(mVold, () -> {
                 mHandler.removeCallbacksAndMessages(null);
             });
@@ -1356,8 +1357,8 @@
 
         final int flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
         for (UserInfo user : users) {
-            prepareUserStorageInternal(fromVolumeUuid, user.id, user.serialNumber, flags);
-            prepareUserStorageInternal(toVolumeUuid, user.id, user.serialNumber, flags);
+            prepareUserStorageInternal(fromVolumeUuid, user.id, flags);
+            prepareUserStorageInternal(toVolumeUuid, user.id, flags);
         }
     }
 
@@ -3231,7 +3232,7 @@
 
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
     @Override
-    public void createUserStorageKeys(int userId, int serialNumber, boolean ephemeral) {
+    public void createUserStorageKeys(int userId, boolean ephemeral) {
 
         super.createUserStorageKeys_enforcePermission();
 
@@ -3276,8 +3277,7 @@
     /* Only for use by LockSettingsService */
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
     @Override
-    public void unlockCeStorage(@UserIdInt int userId, int serialNumber, byte[] secret)
-            throws RemoteException {
+    public void unlockCeStorage(@UserIdInt int userId, byte[] secret) throws RemoteException {
         super.unlockCeStorage_enforcePermission();
 
         if (StorageManager.isFileEncrypted()) {
@@ -3348,25 +3348,25 @@
                 continue;
             }
 
-            prepareUserStorageInternal(vol.fsUuid, user.id, user.serialNumber, flags);
+            prepareUserStorageInternal(vol.fsUuid, user.id, flags);
         }
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
     @Override
-    public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) {
+    public void prepareUserStorage(String volumeUuid, int userId, int flags) {
 
         super.prepareUserStorage_enforcePermission();
 
         try {
-            prepareUserStorageInternal(volumeUuid, userId, serialNumber, flags);
+            prepareUserStorageInternal(volumeUuid, userId, flags);
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
 
-    private void prepareUserStorageInternal(String volumeUuid, int userId, int serialNumber,
-            int flags) throws Exception {
+    private void prepareUserStorageInternal(String volumeUuid, int userId, int flags)
+            throws Exception {
         try {
             mVold.prepareUserStorage(volumeUuid, userId, flags);
             // After preparing user storage, we should check if we should mount data mirror again,
@@ -5041,9 +5041,9 @@
 
         @Override
         public IFsveritySetupAuthToken createFsveritySetupAuthToken(ParcelFileDescriptor authFd,
-                int appUid, @UserIdInt int userId) throws IOException {
+                int uid) throws IOException {
             try {
-                return mInstaller.createFsveritySetupAuthToken(authFd, appUid, userId);
+                return mInstaller.createFsveritySetupAuthToken(authFd, uid);
             } catch (Installer.InstallerException e) {
                 throw new IOException(e);
             }
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 3483c1a..a493d7a 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -95,6 +95,7 @@
     private static final int ALLOW_OVERRIDE_APP_RESTRICTIONS = 0x100;
     private static final int ALLOW_IMPLICIT_BROADCASTS = 0x200;
     private static final int ALLOW_VENDOR_APEX = 0x400;
+    private static final int ALLOW_SIGNATURE_PERMISSIONS = 0x800;
     private static final int ALLOW_ALL = ~0;
 
     // property for runtime configuration differentiation
@@ -597,7 +598,7 @@
 
         // Vendors are only allowed to customize these
         int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS
-                | ALLOW_ASSOCIATIONS | ALLOW_VENDOR_APEX;
+                | ALLOW_SIGNATURE_PERMISSIONS | ALLOW_ASSOCIATIONS | ALLOW_VENDOR_APEX;
         if (Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.O_MR1) {
             // For backward compatibility
             vendorPermissionFlag |= (ALLOW_PERMISSIONS | ALLOW_APP_CONFIGS);
@@ -649,9 +650,9 @@
         // TODO(b/157203468): ALLOW_HIDDENAPI_WHITELISTING must be removed because we prohibited
         // the use of hidden APIs from the product partition.
         int productPermissionFlag = ALLOW_FEATURES | ALLOW_LIBS | ALLOW_PERMISSIONS
-                | ALLOW_APP_CONFIGS | ALLOW_PRIVAPP_PERMISSIONS | ALLOW_HIDDENAPI_WHITELISTING
-                | ALLOW_ASSOCIATIONS | ALLOW_OVERRIDE_APP_RESTRICTIONS | ALLOW_IMPLICIT_BROADCASTS
-                | ALLOW_VENDOR_APEX;
+                | ALLOW_APP_CONFIGS | ALLOW_PRIVAPP_PERMISSIONS | ALLOW_SIGNATURE_PERMISSIONS
+                | ALLOW_HIDDENAPI_WHITELISTING | ALLOW_ASSOCIATIONS
+                | ALLOW_OVERRIDE_APP_RESTRICTIONS | ALLOW_IMPLICIT_BROADCASTS | ALLOW_VENDOR_APEX;
         if (Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.R) {
             // TODO(b/157393157): This must check product interface enforcement instead of
             // DEVICE_INITIAL_SDK_INT for the devices without product interface enforcement.
@@ -772,6 +773,8 @@
             final boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0;
             final boolean allowPrivappPermissions = (permissionFlag & ALLOW_PRIVAPP_PERMISSIONS)
                     != 0;
+            final boolean allowSignaturePermissions = (permissionFlag & ALLOW_SIGNATURE_PERMISSIONS)
+                    != 0;
             final boolean allowOemPermissions = (permissionFlag & ALLOW_OEM_PERMISSIONS) != 0;
             final boolean allowApiWhitelisting = (permissionFlag & ALLOW_HIDDENAPI_WHITELISTING)
                     != 0;
@@ -1246,6 +1249,38 @@
                             XmlUtils.skipCurrentTag(parser);
                         }
                     } break;
+                    case "signature-permissions": {
+                        if (allowSignaturePermissions) {
+                            // signature permissions from system, apex, vendor, product and
+                            // system_ext partitions are stored separately. This is to
+                            // prevent xml files in the vendor partition from granting
+                            // permissions to signature apps in the system partition and vice versa.
+                            boolean vendor = permFile.toPath().startsWith(
+                                    Environment.getVendorDirectory().toPath() + "/")
+                                    || permFile.toPath().startsWith(
+                                    Environment.getOdmDirectory().toPath() + "/");
+                            boolean product = permFile.toPath().startsWith(
+                                    Environment.getProductDirectory().toPath() + "/");
+                            boolean systemExt = permFile.toPath().startsWith(
+                                    Environment.getSystemExtDirectory().toPath() + "/");
+                            if (vendor) {
+                                readSignatureAppPermissions(parser,
+                                        mPermissionAllowlist.getVendorSignatureAppAllowlist());
+                            } else if (product) {
+                                readSignatureAppPermissions(parser,
+                                        mPermissionAllowlist.getProductSignatureAppAllowlist());
+                            } else if (systemExt) {
+                                readSignatureAppPermissions(parser,
+                                        mPermissionAllowlist.getSystemExtSignatureAppAllowlist());
+                            } else {
+                                readSignatureAppPermissions(parser,
+                                        mPermissionAllowlist.getSignatureAppAllowlist());
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                            XmlUtils.skipCurrentTag(parser);
+                        }
+                    } break;
                     case "oem-permissions": {
                         if (allowOemPermissions) {
                             readOemPermissions(parser);
@@ -1655,6 +1690,12 @@
         readPermissionAllowlist(parser, allowlist, "privapp-permissions");
     }
 
+    private void readSignatureAppPermissions(@NonNull XmlPullParser parser,
+            @NonNull ArrayMap<String, ArrayMap<String, Boolean>> allowlist)
+            throws IOException, XmlPullParserException {
+        readPermissionAllowlist(parser, allowlist, "signature-permissions");
+    }
+
     private void readInstallInUserType(XmlPullParser parser,
             Map<String, Set<String>> doInstallMap,
             Map<String, Set<String>> nonInstallMap)
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 9eb35fd..bd67cf420 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -101,6 +101,7 @@
 import com.android.internal.telephony.IPhoneStateListener;
 import com.android.internal.telephony.ITelephonyRegistry;
 import com.android.internal.telephony.TelephonyPermissions;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
@@ -417,6 +418,8 @@
             LinkCapacityEstimate.INVALID, LinkCapacityEstimate.INVALID)));
     private List<List<LinkCapacityEstimate>> mLinkCapacityEstimateLists;
 
+    private int[] mSimultaneousCellularCallingSubIds = {};
+
     private int[] mECBMReason;
     private boolean[] mECBMStarted;
     private int[] mSCBMReason;
@@ -563,7 +566,9 @@
                 || events.contains(TelephonyCallback.EVENT_VOICE_ACTIVATION_STATE_CHANGED)
                 || events.contains(TelephonyCallback.EVENT_RADIO_POWER_STATE_CHANGED)
                 || events.contains(TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED)
-                || events.contains(TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED);
+                || events.contains(TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED)
+                || events.contains(TelephonyCallback
+                        .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED);
     }
 
     private static final int MSG_USER_SWITCHED = 1;
@@ -1121,6 +1126,21 @@
             return;
         }
 
+        int phoneId = -1;
+        int subscriptionId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+        if(Flags.preventSystemServerAndPhoneDeadlock()) {
+            // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
+            // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
+            if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+                if (DBG) {
+                    log("invalid subscription id, use default id");
+                }
+            } else { //APP specify subID
+                subscriptionId = subId;
+            }
+            phoneId = getPhoneIdFromSubId(subscriptionId);
+        }
+
         synchronized (mRecords) {
             // register
             IBinder b = callback.asBinder();
@@ -1140,17 +1160,23 @@
             r.renounceFineLocationAccess = renounceFineLocationAccess;
             r.callerUid = Binder.getCallingUid();
             r.callerPid = Binder.getCallingPid();
-            // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
-            // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
-            if (!SubscriptionManager.isValidSubscriptionId(subId)) {
-                if (DBG) {
-                    log("invalid subscription id, use default id");
+
+            if(!Flags.preventSystemServerAndPhoneDeadlock()) {
+                // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
+                // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
+                if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+                    if (DBG) {
+                        log("invalid subscription id, use default id");
+                    }
+                    r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+                } else {//APP specify subID
+                    r.subId = subId;
                 }
-                r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
-            } else {//APP specify subID
-                r.subId = subId;
+                r.phoneId = getPhoneIdFromSubId(r.subId);
+            } else {
+                r.subId = subscriptionId;
+                r.phoneId = phoneId;
             }
-            r.phoneId = getPhoneIdFromSubId(r.subId);
             r.eventList = events;
 
             if (DBG) {
@@ -1426,6 +1452,15 @@
                         remove(r.binder);
                     }
                 }
+                if (events.contains(TelephonyCallback
+                        .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED)) {
+                    try {
+                        r.callback.onSimultaneousCallingStateChanged(
+                                mSimultaneousCellularCallingSubIds);
+                    } catch (RemoteException ex) {
+                        remove(r.binder);
+                    }
+                }
                 if (events.contains(
                         TelephonyCallback.EVENT_LINK_CAPACITY_ESTIMATE_CHANGED)) {
                     try {
@@ -1879,8 +1914,14 @@
     }
 
     private void notifyCarrierNetworkChangeWithPermission(int subId, boolean active) {
+        int phoneId = -1;
+        if(Flags.preventSystemServerAndPhoneDeadlock()) {
+            phoneId = getPhoneIdFromSubId(subId);
+        }
         synchronized (mRecords) {
-            int phoneId = getPhoneIdFromSubId(subId);
+            if(!Flags.preventSystemServerAndPhoneDeadlock()) {
+                phoneId = getPhoneIdFromSubId(subId);
+            }
             mCarrierNetworkChangeState[phoneId] = active;
 
             if (VDBG) {
@@ -2679,6 +2720,14 @@
         if (!checkNotifyPermission("notifyEmergencyNumberList()")) {
             return;
         }
+        if (Flags.enforceTelephonyFeatureMappingForPublicApis()) {
+            if (!mContext.getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_TELEPHONY_CALLING)) {
+                // TelephonyManager.getEmergencyNumberList() throws an exception if
+                // FEATURE_TELEPHONY_CALLING is not defined.
+                return;
+            }
+        }
 
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
@@ -3083,6 +3132,43 @@
         }
     }
 
+    /**
+     * Notify the listeners that simultaneous cellular calling subscriptions have changed
+     * @param subIds The set of subIds that support simultaneous cellular calling
+     */
+    public void notifySimultaneousCellularCallingSubscriptionsChanged(int[] subIds) {
+        if (!checkNotifyPermission("notifySimultaneousCellularCallingSubscriptionsChanged()")) {
+            return;
+        }
+
+        if (VDBG) {
+            StringBuilder b = new StringBuilder();
+            b.append("notifySimultaneousCellularCallingSubscriptionsChanged: ");
+            b.append("subIds = {");
+            for (int i : subIds) {
+                b.append(" ");
+                b.append(i);
+            }
+            b.append("}");
+            log(b.toString());
+        }
+
+        synchronized (mRecords) {
+            mSimultaneousCellularCallingSubIds = subIds;
+            for (Record r : mRecords) {
+                if (r.matchTelephonyCallbackEvent(TelephonyCallback
+                        .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED)) {
+                    try {
+                        r.callback.onSimultaneousCallingStateChanged(subIds);
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
     @Override
     public void addCarrierPrivilegesCallback(
             int phoneId,
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 66a10e4..cd8be33 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -16,8 +16,10 @@
 
 package com.android.server;
 
+import static android.app.Flags.modesApi;
 import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
 import static android.app.UiModeManager.DEFAULT_PRIORITY;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
 import static android.app.UiModeManager.MODE_NIGHT_AUTO;
 import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
 import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
@@ -50,6 +52,7 @@
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
 import android.app.UiModeManager;
+import android.app.UiModeManager.AttentionModeThemeOverlayType;
 import android.app.UiModeManager.NightModeCustomReturnType;
 import android.app.UiModeManager.NightModeCustomType;
 import android.content.BroadcastReceiver;
@@ -134,6 +137,7 @@
     private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
     private int mNightMode = UiModeManager.MODE_NIGHT_NO;
     private int mNightModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
+    private int mAttentionModeThemeOverlay = UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
     private final LocalTime DEFAULT_CUSTOM_NIGHT_START_TIME = LocalTime.of(22, 0);
     private final LocalTime DEFAULT_CUSTOM_NIGHT_END_TIME = LocalTime.of(6, 0);
     private LocalTime mCustomAutoNightModeStartMilliseconds = DEFAULT_CUSTOM_NIGHT_START_TIME;
@@ -839,6 +843,8 @@
                                 ? customModeType
                                 : MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
                         mNightMode = mode;
+                        //deactivates AttentionMode if user toggles DarkTheme
+                        mAttentionModeThemeOverlay = MODE_ATTENTION_THEME_OVERLAY_OFF;
                         resetNightModeOverrideLocked();
                         persistNightMode(user);
                         // on screen off will update configuration instead
@@ -879,6 +885,29 @@
             }
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+        @Override
+            public void setAttentionModeThemeOverlay(
+                @AttentionModeThemeOverlayType int attentionModeThemeOverlayType) {
+            setAttentionModeThemeOverlay_enforcePermission();
+
+            synchronized (mLock) {
+                if (mAttentionModeThemeOverlay != attentionModeThemeOverlayType) {
+                    mAttentionModeThemeOverlay = attentionModeThemeOverlayType;
+                    Binder.withCleanCallingIdentity(()-> updateLocked(0, 0));
+                }
+            }
+        }
+
+        @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+        @Override
+        public @AttentionModeThemeOverlayType int getAttentionModeThemeOverlay() {
+            getAttentionModeThemeOverlay_enforcePermission();
+            synchronized (mLock) {
+                return mAttentionModeThemeOverlay;
+            }
+        }
+
         @Override
         public void setApplicationNightMode(@UiModeManager.NightMode int mode) {
             switch (mode) {
@@ -1406,7 +1435,7 @@
             pw.print(Shell.nightModeToStr(mNightMode, mNightModeCustomType)); pw.print(") ");
             pw.print(" mOverrideOn/Off="); pw.print(mOverrideNightModeOn);
             pw.print("/"); pw.print(mOverrideNightModeOff);
-
+            pw.print("  mAttentionModeThemeOverlay="); pw.print(mAttentionModeThemeOverlay);
             pw.print(" mNightModeLocked="); pw.println(mNightModeLocked);
 
             pw.print("  mCarModeEnabled="); pw.print(mCarModeEnabled);
@@ -1685,7 +1714,7 @@
     }
 
     @UiModeManager.NightMode
-    private int getComputedUiModeConfiguration(@UiModeManager.NightMode int uiMode) {
+    private int getComputedUiModeConfiguration(int uiMode) {
         uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
                 : Configuration.UI_MODE_NIGHT_NO;
         uiMode &= mComputedNightMode ? ~Configuration.UI_MODE_NIGHT_NO
@@ -1980,18 +2009,26 @@
     }
 
     private void updateComputedNightModeLocked(boolean activate) {
-        mComputedNightMode = activate;
-        if (mNightMode == MODE_NIGHT_YES || mNightMode == UiModeManager.MODE_NIGHT_NO) {
-            return;
+        boolean newComputedValue = activate;
+        if (mNightMode != MODE_NIGHT_YES && mNightMode != UiModeManager.MODE_NIGHT_NO) {
+            if (mOverrideNightModeOn && !newComputedValue) {
+                newComputedValue = true;
+            } else if (mOverrideNightModeOff && newComputedValue) {
+                newComputedValue = false;
+            }
         }
-        if (mOverrideNightModeOn && !mComputedNightMode) {
-            mComputedNightMode = true;
-            return;
+
+        if (modesApi()) {
+            // Computes final night mode values based on Attention Mode.
+            mComputedNightMode = switch (mAttentionModeThemeOverlay) {
+                case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT) -> true;
+                case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_DAY) -> false;
+                default -> newComputedValue; // case OFF
+            };
+        } else {
+            mComputedNightMode = newComputedValue;
         }
-        if (mOverrideNightModeOff && mComputedNightMode) {
-            mComputedNightMode = false;
-            return;
-        }
+
         if (mNightMode != MODE_NIGHT_AUTO || (mTwilightManager != null
                 && mTwilightManager.getLastTwilightState() != null)) {
             resetNightModeOverrideLocked();
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index fd17261..c18bacb 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -172,6 +172,7 @@
     public static final String[] AIDL_INTERFACE_PREFIXES_OF_INTEREST = new String[] {
             "android.hardware.audio.core.IModule/",
             "android.hardware.audio.core.IConfig/",
+            "android.hardware.audio.effect.IFactory/",
             "android.hardware.biometrics.face.IFace/",
             "android.hardware.biometrics.fingerprint.IFingerprint/",
             "android.hardware.bluetooth.IBluetoothHci/",
diff --git a/services/core/java/com/android/server/ZramWriteback.java b/services/core/java/com/android/server/ZramWriteback.java
index 5d97def..39ca29e 100644
--- a/services/core/java/com/android/server/ZramWriteback.java
+++ b/services/core/java/com/android/server/ZramWriteback.java
@@ -177,7 +177,6 @@
         // back at later point if they remain untouched.
         js.schedule(new JobInfo.Builder(MARK_IDLE_JOB_ID, sZramWriteback)
                         .setMinimumLatency(TimeUnit.MINUTES.toMillis(markIdleDelay))
-                        .setOverrideDeadline(TimeUnit.MINUTES.toMillis(markIdleDelay))
                         .build());
 
         // Schedule a one time job to flush idle pages to disk.
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 85abf87..130a733 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -3152,6 +3152,7 @@
                                     new AccountAuthenticatorResponse(this),
                                     authTokenType,
                                     true);
+                            mCanStartAccountManagerActivity = true;
                             Bundle bundle = new Bundle();
                             bundle.putParcelable(AccountManager.KEY_INTENT, intent);
                             onResult(bundle);
@@ -4933,6 +4934,7 @@
         IAccountAuthenticator mAuthenticator = null;
 
         private final boolean mStripAuthTokenFromResult;
+        protected boolean mCanStartAccountManagerActivity = false;
         protected final UserAccounts mAccounts;
 
         public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType,
@@ -5068,9 +5070,13 @@
 
         private boolean isExportedSystemActivity(ActivityInfo activityInfo) {
             String className = activityInfo.name;
-            return "android".equals(activityInfo.packageName) &&
-                    (GrantCredentialsPermissionActivity.class.getName().equals(className)
-                    || CantAddAccountActivity.class.getName().equals(className));
+            if (!"android".equals(activityInfo.packageName)) {
+                return false;
+
+            }
+            return (mCanStartAccountManagerActivity
+                    && GrantCredentialsPermissionActivity.class.getName().equals(className))
+                    || CantAddAccountActivity.class.getName().equals(className);
         }
 
         private void close() {
diff --git a/services/core/java/com/android/server/adaptiveauth/OWNERS b/services/core/java/com/android/server/adaptiveauth/OWNERS
new file mode 100644
index 0000000..b188105
--- /dev/null
+++ b/services/core/java/com/android/server/adaptiveauth/OWNERS
@@ -0,0 +1 @@
+hainingc@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 02f4485..adc0255 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -94,6 +94,8 @@
 import static android.os.Process.SHELL_UID;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
+import static android.content.flags.Flags.enableBindPackageIsolatedProcess;
+
 
 import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICE_BG_LAUNCH;
 import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_DELEGATE;
@@ -179,7 +181,6 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.ServiceInfo.ForegroundServiceType;
-import android.os.Binder;
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
@@ -304,6 +305,57 @@
     @interface FgsStopReason {}
 
     /**
+     * The policy to be applied to the service bindings; this one means it follows the legacy
+     * behavior.
+     */
+    static final int SERVICE_BIND_OOMADJ_POLICY_LEGACY = 0;
+
+    /**
+     * The policy to be applied to the service bindings; this one means we'll skip
+     * updating the target process's oom adj score / process state for its {@link Service#onCreate}.
+     */
+    static final int SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE = 1;
+
+    /**
+     * The policy to be applied to the service bindings; this one means we'll skip
+     * updating the target process's oom adj score / process state for its {@link Service#onBind}.
+     */
+    static final int SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_BIND = 1 << 1;
+
+    /**
+     * The policy to be applied to the service bindings; this one means we'll skip
+     * updating the target process's oom adj score / process state on setting up the service
+     * connection between the client and the service host process.
+     */
+    static final int SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT = 1 << 2;
+    /**
+     * The policy to be applied to the service bindings; this one means the caller
+     * will be frozen upon calling the bindService APIs.
+     */
+    static final int SERVICE_BIND_OOMADJ_POLICY_FREEZE_CALLER = 1 << 3;
+
+    @IntDef(flag = true, prefix = { "SERVICE_BIND_OOMADJ_POLICY_" }, value = {
+            SERVICE_BIND_OOMADJ_POLICY_LEGACY,
+            SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE,
+            SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_BIND,
+            SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT,
+            SERVICE_BIND_OOMADJ_POLICY_FREEZE_CALLER,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ServiceBindingOomAdjPolicy {}
+
+    @ServiceBindingOomAdjPolicy
+    static final int DEFAULT_SERVICE_NO_BUMP_BIND_POLICY_FLAG =
+            SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE
+            | SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_BIND
+            | SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT;
+
+    @ServiceBindingOomAdjPolicy
+    static final int DEFAULT_SERVICE_CACHED_BIND_POLICY_FLAG =
+            SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE
+            | SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_BIND;
+
+    /**
      * Disables foreground service background starts from BOOT_COMPLETED broadcasts for all types
      * except:
      * <ul>
@@ -791,13 +843,15 @@
 
     static String getProcessNameForService(ServiceInfo sInfo, ComponentName name,
             String callingPackage, String instanceName, boolean isSdkSandbox,
-            boolean inSharedIsolatedProcess) {
+            boolean inSharedIsolatedProcess, boolean inPrivateSharedIsolatedProcess) {
         if (isSdkSandbox) {
             // For SDK sandbox, the process name is passed in as the instanceName
             return instanceName;
         }
-        if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) {
-            // For regular processes, just the name in sInfo
+        if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0
+                || (inPrivateSharedIsolatedProcess && !isDefaultProcessService(sInfo))) {
+            // For regular processes, or private package-shared isolated processes, just the name
+            // in sInfo
             return sInfo.processName;
         }
         // Isolated processes remain.
@@ -809,6 +863,10 @@
         }
     }
 
+    private static boolean isDefaultProcessService(ServiceInfo serviceInfo) {
+        return serviceInfo.applicationInfo.processName.equals(serviceInfo.processName);
+    }
+
     private static void traceInstant(@NonNull String message, @NonNull ServiceRecord service) {
         if (!Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
             return;
@@ -864,7 +922,7 @@
 
         ServiceLookupResult res = retrieveServiceLocked(service, instanceName, isSdkSandboxService,
                 sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, callingPackage,
-                callingPid, callingUid, userId, true, callerFg, false, false, null, false);
+                callingPid, callingUid, userId, true, callerFg, false, false, null, false, false);
         if (res == null) {
             return null;
         }
@@ -1236,7 +1294,7 @@
                             @Override
                             public void onResult(Bundle result) {
                                 synchronized (mAm) {
-                                    final long identity = Binder.clearCallingIdentity();
+                                    final long identity = mAm.mInjector.clearCallingIdentity();
                                     try {
                                         if (!mPendingServices.contains(r)) {
                                             return;
@@ -1255,7 +1313,8 @@
                                                         false /* whileRestarting */,
                                                         false /* permissionsReviewRequired */,
                                                         false /* packageFrozen */,
-                                                        true /* enqueueOomAdj */);
+                                                        true /* enqueueOomAdj */,
+                                                        SERVICE_BIND_OOMADJ_POLICY_LEGACY);
                                             } catch (RemoteException e) {
                                                 /* ignore - local call */
                                             } finally {
@@ -1267,7 +1326,7 @@
                                             unbindServiceLocked(connection);
                                         }
                                     } finally {
-                                        Binder.restoreCallingIdentity(identity);
+                                        mAm.mInjector.restoreCallingIdentity(identity);
                                     }
                                 }
                             }
@@ -1345,7 +1404,8 @@
                                     false /* whileRestarting */,
                                     false /* permissionsReviewRequired */,
                                     false /* packageFrozen */,
-                                    true /* enqueueOomAdj */);
+                                    true /* enqueueOomAdj */,
+                                    SERVICE_BIND_OOMADJ_POLICY_LEGACY);
                         } catch (TransactionTooLargeException e) {
                             /* ignore - local call */
                         } finally {
@@ -1423,7 +1483,8 @@
                 false /* whileRestarting */,
                 false /* permissionsReviewRequired */,
                 false /* packageFrozen */,
-                true /* enqueueOomAdj */);
+                true /* enqueueOomAdj */,
+                SERVICE_BIND_OOMADJ_POLICY_LEGACY);
         /* Will be a no-op if nothing pending */
         mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
         if (error != null) {
@@ -1542,22 +1603,22 @@
         if (caller != null && callerApp == null) {
             throw new SecurityException(
                     "Unable to find app for caller " + caller
-                    + " (pid=" + Binder.getCallingPid()
+                    + " (pid=" + mAm.mInjector.getCallingPid()
                     + ") when stopping service " + service);
         }
 
         // If this service is active, make sure it is stopped.
         ServiceLookupResult r = retrieveServiceLocked(service, instanceName, isSdkSandboxService,
                 sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, null,
-                Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, false, false,
-                null, false);
+                mAm.mInjector.getCallingPid(), mAm.mInjector.getCallingUid(),
+                userId, false, false, false, false, null, false, false);
         if (r != null) {
             if (r.record != null) {
-                final long origId = Binder.clearCallingIdentity();
+                final long origId = mAm.mInjector.clearCallingIdentity();
                 try {
                     stopServiceLocked(r.record, false);
                 } finally {
-                    Binder.restoreCallingIdentity(origId);
+                    mAm.mInjector.restoreCallingIdentity(origId);
                 }
                 return 1;
             }
@@ -1641,8 +1702,8 @@
 
     IBinder peekServiceLocked(Intent service, String resolvedType, String callingPackage) {
         ServiceLookupResult r = retrieveServiceLocked(service, null, resolvedType, callingPackage,
-                Binder.getCallingPid(), Binder.getCallingUid(),
-                UserHandle.getCallingUserId(), false, false, false, false, false);
+                mAm.mInjector.getCallingPid(), mAm.mInjector.getCallingUid(),
+                UserHandle.getCallingUserId(), false, false, false, false, false, false);
 
         IBinder ret = null;
         if (r != null) {
@@ -1650,8 +1711,8 @@
             if (r.record == null) {
                 throw new SecurityException(
                         "Permission Denial: Accessing service"
-                        + " from pid=" + Binder.getCallingPid()
-                        + ", uid=" + Binder.getCallingUid()
+                        + " from pid=" + mAm.mInjector.getCallingPid()
+                        + ", uid=" + mAm.mInjector.getCallingUid()
                         + " requires " + r.permission);
             }
             IntentBindRecord ib = r.record.bindings.get(r.record.intent);
@@ -1711,9 +1772,9 @@
                 }
             }
             r.callStart = false;
-            final long origId = Binder.clearCallingIdentity();
+            final long origId = mAm.mInjector.clearCallingIdentity();
             bringDownServiceIfNeededLocked(r, false, false, false, "stopServiceToken");
-            Binder.restoreCallingIdentity(origId);
+            mAm.mInjector.restoreCallingIdentity(origId);
             return true;
         }
         return false;
@@ -1726,14 +1787,14 @@
     public void setServiceForegroundLocked(ComponentName className, IBinder token,
             int id, Notification notification, int flags, int foregroundServiceType) {
         final int userId = UserHandle.getCallingUserId();
-        final long origId = Binder.clearCallingIdentity();
+        final long origId = mAm.mInjector.clearCallingIdentity();
         try {
             ServiceRecord r = findServiceLocked(className, token, userId);
             if (r != null) {
                 setServiceForegroundInnerLocked(r, id, notification, flags, foregroundServiceType);
             }
         } finally {
-            Binder.restoreCallingIdentity(origId);
+            mAm.mInjector.restoreCallingIdentity(origId);
         }
     }
 
@@ -1745,7 +1806,7 @@
      */
     public int getForegroundServiceTypeLocked(ComponentName className, IBinder token) {
         final int userId = UserHandle.getCallingUserId();
-        final long origId = Binder.clearCallingIdentity();
+        final long origId = mAm.mInjector.clearCallingIdentity();
         int ret = ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
         try {
             ServiceRecord r = findServiceLocked(className, token, userId);
@@ -1753,7 +1814,7 @@
                 ret = r.foregroundServiceType;
             }
         } finally {
-            Binder.restoreCallingIdentity(origId);
+            mAm.mInjector.restoreCallingIdentity(origId);
         }
         return ret;
     }
@@ -3475,7 +3536,7 @@
 
     boolean shouldServiceTimeOutLocked(ComponentName className, IBinder token) {
         final int userId = UserHandle.getCallingUserId();
-        final long ident = Binder.clearCallingIdentity();
+        final long ident = mAm.mInjector.clearCallingIdentity();
         try {
             ServiceRecord sr = findServiceLocked(className, token, userId);
             if (sr == null) {
@@ -3484,7 +3545,7 @@
             final long nowUptime = SystemClock.uptimeMillis();
             return sr.shouldTriggerShortFgsTimeout(nowUptime);
         } finally {
-            Binder.restoreCallingIdentity(ident);
+            mAm.mInjector.restoreCallingIdentity(ident);
         }
     }
 
@@ -3628,8 +3689,8 @@
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service
                 + " type=" + resolvedType + " conn=" + connection.asBinder()
                 + " flags=0x" + Long.toHexString(flags));
-        final int callingPid = Binder.getCallingPid();
-        final int callingUid = Binder.getCallingUid();
+        final int callingPid = mAm.mInjector.getCallingPid();
+        final int callingUid = mAm.mInjector.getCallingUid();
         final ProcessRecord callerApp = mAm.getRecordForAppLOSP(caller);
         if (callerApp == null) {
             throw new SecurityException(
@@ -3714,6 +3775,9 @@
                 || (flags & Context.BIND_EXTERNAL_SERVICE_LONG) != 0;
         final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;
         final boolean inSharedIsolatedProcess = (flags & Context.BIND_SHARED_ISOLATED_PROCESS) != 0;
+        final boolean inPrivateSharedIsolatedProcess =
+                ((flags & Context.BIND_PACKAGE_ISOLATED_PROCESS) != 0)
+                        && enableBindPackageIsolatedProcess();
         final boolean matchQuarantined =
                 (flags & Context.BIND_MATCH_QUARANTINED_COMPONENTS) != 0;
 
@@ -3725,7 +3789,7 @@
                 isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage,
                 resolvedType, callingPackage, callingPid, callingUid, userId, true, callerFg,
                 isBindExternal, allowInstant, null /* fgsDelegateOptions */,
-                inSharedIsolatedProcess, matchQuarantined);
+                inSharedIsolatedProcess, inPrivateSharedIsolatedProcess, matchQuarantined);
         if (res == null) {
             return 0;
         }
@@ -3767,7 +3831,7 @@
                 && !requestStartTargetPermissionsReviewIfNeededLocked(s, callingPackage, null,
                         callingUid, service, callerFg, userId, true, connection);
 
-        final long origId = Binder.clearCallingIdentity();
+        final long origId = mAm.mInjector.clearCallingIdentity();
 
         try {
             if (unscheduleServiceRestartLocked(s, callerApp.info.uid, false)) {
@@ -3848,12 +3912,34 @@
             }
             clist.add(c);
 
+            final boolean isolated = (s.serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
+            final ProcessRecord hostApp = isolated
+                    ? null
+                    : mAm.getProcessRecordLocked(s.processName, s.appInfo.uid);
+            final int serviceBindingOomAdjPolicy = hostApp != null
+                    ? getServiceBindingOomAdjPolicyForAddLocked(b.client, hostApp, c)
+                    : SERVICE_BIND_OOMADJ_POLICY_LEGACY;
+
+            final boolean shouldFreezeCaller = !packageFrozen && !permissionsReviewRequired
+                    && (serviceBindingOomAdjPolicy & SERVICE_BIND_OOMADJ_POLICY_FREEZE_CALLER) != 0
+                    && callerApp.isFreezable();
+
+            if (shouldFreezeCaller) {
+                // Freeze the caller immediately, so the following #onBind/#onConnected will be
+                // queued up in the app side as they're one way calls. And we'll also hold off
+                // the service timeout timer until the process is unfrozen.
+                mAm.mOomAdjuster.updateAppFreezeStateLSP(callerApp, OOM_ADJ_REASON_BIND_SERVICE,
+                        true);
+            }
+
             boolean needOomAdj = false;
             if (c.hasFlag(Context.BIND_AUTO_CREATE)) {
                 s.lastActivity = SystemClock.uptimeMillis();
-                needOomAdj = true;
+                needOomAdj = (serviceBindingOomAdjPolicy
+                        & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE) == 0;
                 if (bringUpServiceLocked(s, service.getFlags(), callerFg, false,
-                        permissionsReviewRequired, packageFrozen, true) != null) {
+                        permissionsReviewRequired, packageFrozen, true, serviceBindingOomAdjPolicy)
+                        != null) {
                     mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE);
                     return 0;
                 }
@@ -3875,8 +3961,11 @@
                         || (callerApp.mState.getCurProcState() <= PROCESS_STATE_TOP
                             && c.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)),
                         b.client);
-                needOomAdj = true;
-                mAm.enqueueOomAdjTargetLocked(s.app);
+                if ((serviceBindingOomAdjPolicy
+                        & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT) == 0) {
+                    needOomAdj = true;
+                    mAm.enqueueOomAdjTargetLocked(s.app);
+                }
             }
             if (needOomAdj) {
                 mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE);
@@ -3926,10 +4015,12 @@
                 // and the service had previously asked to be told when
                 // rebound, then do so.
                 if (b.intent.apps.size() == 1 && b.intent.doRebind) {
-                    requestServiceBindingLocked(s, b.intent, callerFg, true);
+                    requestServiceBindingLocked(s, b.intent, callerFg, true,
+                            serviceBindingOomAdjPolicy);
                 }
             } else if (!b.intent.requested) {
-                requestServiceBindingLocked(s, b.intent, callerFg, false);
+                requestServiceBindingLocked(s, b.intent, callerFg, false,
+                        serviceBindingOomAdjPolicy);
             }
 
             maybeLogBindCrossProfileService(userId, callingPackage, callerApp.info.uid);
@@ -3937,7 +4028,7 @@
             getServiceMapLocked(s.userId).ensureNotStartingBackgroundLocked(s);
 
         } finally {
-            Binder.restoreCallingIdentity(origId);
+            mAm.mInjector.restoreCallingIdentity(origId);
         }
 
         notifyBindingServiceEventLocked(callerApp, callingPackage);
@@ -3971,7 +4062,7 @@
     }
 
     void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
-        final long origId = Binder.clearCallingIdentity();
+        final long origId = mAm.mInjector.clearCallingIdentity();
         try {
             if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "PUBLISHING " + r
                     + " " + intent + ": " + service);
@@ -4014,10 +4105,11 @@
                 }
 
                 serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false,
-                        OOM_ADJ_REASON_EXECUTING_SERVICE);
+                        !Flags.serviceBindingOomAdjPolicy() || b == null || !b.mSkippedOomAdj
+                        ? OOM_ADJ_REASON_EXECUTING_SERVICE : OOM_ADJ_REASON_NONE);
             }
         } finally {
-            Binder.restoreCallingIdentity(origId);
+            mAm.mInjector.restoreCallingIdentity(origId);
         }
     }
 
@@ -4067,8 +4159,8 @@
             return false;
         }
 
-        final int callingPid = Binder.getCallingPid();
-        final long origId = Binder.clearCallingIdentity();
+        final int callingPid = mAm.mInjector.getCallingPid();
+        final long origId = mAm.mInjector.clearCallingIdentity();
         try {
             if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                 String info;
@@ -4081,9 +4173,10 @@
                 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "unbindServiceLocked: " + info);
             }
 
+            boolean needOomAdj = false;
             while (clist.size() > 0) {
                 ConnectionRecord r = clist.get(0);
-                removeConnectionLocked(r, null, null, true);
+                int serviceBindingOomAdjPolicy = removeConnectionLocked(r, null, null, true);
                 if (clist.size() > 0 && clist.get(0) == r) {
                     // In case it didn't get removed above, do it now.
                     Slog.wtf(TAG, "Connection " + r + " not removed for binder " + binder);
@@ -4101,22 +4194,28 @@
                         psr.setTreatLikeActivity(true);
                         mAm.updateLruProcessLocked(app, true, null);
                     }
-                    mAm.enqueueOomAdjTargetLocked(app);
+                    // If the bindee is more important than the binder, we may skip the OomAdjuster.
+                    if (serviceBindingOomAdjPolicy == SERVICE_BIND_OOMADJ_POLICY_LEGACY) {
+                        mAm.enqueueOomAdjTargetLocked(app);
+                        needOomAdj = true;
+                    }
                 }
             }
 
-            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UNBIND_SERVICE);
+            if (needOomAdj) {
+                mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UNBIND_SERVICE);
+            }
 
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-            Binder.restoreCallingIdentity(origId);
+            mAm.mInjector.restoreCallingIdentity(origId);
         }
 
         return true;
     }
 
     void unbindFinishedLocked(ServiceRecord r, Intent intent, boolean doRebind) {
-        final long origId = Binder.clearCallingIdentity();
+        final long origId = mAm.mInjector.clearCallingIdentity();
         try {
             if (r != null) {
                 Intent.FilterComparison filter
@@ -4127,6 +4226,7 @@
                         + (b != null ? b.apps.size() : 0));
 
                 boolean inDestroying = mDestroyingServices.contains(r);
+                boolean skipOomAdj = false;
                 if (b != null) {
                     if (b.apps.size() > 0 && !inDestroying) {
                         // Applications have already bound since the last
@@ -4141,7 +4241,8 @@
                             }
                         }
                         try {
-                            requestServiceBindingLocked(r, b, inFg, true);
+                            requestServiceBindingLocked(r, b, inFg, true,
+                                    SERVICE_BIND_OOMADJ_POLICY_LEGACY);
                         } catch (TransactionTooLargeException e) {
                             // Don't pass this back to ActivityThread, it's unrelated.
                         }
@@ -4150,13 +4251,14 @@
                         // a new client.
                         b.doRebind = true;
                     }
+                    skipOomAdj = Flags.serviceBindingOomAdjPolicy() && b.mSkippedOomAdj;
                 }
 
                 serviceDoneExecutingLocked(r, inDestroying, false, false,
-                        OOM_ADJ_REASON_UNBIND_SERVICE);
+                        skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE);
             }
         } finally {
-            Binder.restoreCallingIdentity(origId);
+            mAm.mInjector.restoreCallingIdentity(origId);
         }
     }
 
@@ -4204,14 +4306,14 @@
     }
 
     private ServiceLookupResult retrieveServiceLocked(Intent service,
-            String instanceName, String resolvedType, String callingPackage,
-            int callingPid, int callingUid, int userId,
-            boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
-            boolean allowInstant, boolean inSharedIsolatedProcess) {
+            String instanceName, String resolvedType, String callingPackage, int callingPid,
+            int callingUid, int userId, boolean createIfNeeded, boolean callingFromFg,
+            boolean isBindExternal, boolean allowInstant, boolean inSharedIsolatedProcess,
+            boolean inPrivateSharedIsolatedProcess) {
         return retrieveServiceLocked(service, instanceName, false, INVALID_UID, null, resolvedType,
                 callingPackage, callingPid, callingUid, userId, createIfNeeded, callingFromFg,
                 isBindExternal, allowInstant, null /* fgsDelegateOptions */,
-                inSharedIsolatedProcess);
+                inSharedIsolatedProcess, inPrivateSharedIsolatedProcess);
     }
 
     // TODO(b/265746493): Special case for HotwordDetectionService,
@@ -4233,21 +4335,22 @@
             String callingPackage, int callingPid, int callingUid, int userId,
             boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
             boolean allowInstant, ForegroundServiceDelegationOptions fgsDelegateOptions,
-            boolean inSharedIsolatedProcess) {
+            boolean inSharedIsolatedProcess, boolean inPrivateSharedIsolatedProcess) {
         return retrieveServiceLocked(service, instanceName, isSdkSandboxService,
                 sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, callingPackage,
                 callingPid, callingUid, userId, createIfNeeded, callingFromFg, isBindExternal,
                 allowInstant, fgsDelegateOptions, inSharedIsolatedProcess,
-                false /* matchQuarantined */);
+                inPrivateSharedIsolatedProcess, false /* matchQuarantined */);
     }
 
-    private ServiceLookupResult retrieveServiceLocked(Intent service,
-            String instanceName, boolean isSdkSandboxService, int sdkSandboxClientAppUid,
-            String sdkSandboxClientAppPackage, String resolvedType,
+    private ServiceLookupResult retrieveServiceLocked(
+            Intent service, String instanceName, boolean isSdkSandboxService,
+            int sdkSandboxClientAppUid, String sdkSandboxClientAppPackage, String resolvedType,
             String callingPackage, int callingPid, int callingUid, int userId,
             boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
             boolean allowInstant, ForegroundServiceDelegationOptions fgsDelegateOptions,
-            boolean inSharedIsolatedProcess, boolean matchQuarantined) {
+            boolean inSharedIsolatedProcess, boolean inPrivateSharedIsolatedProcess,
+            boolean matchQuarantined) {
         if (isSdkSandboxService && instanceName == null) {
             throw new IllegalArgumentException("No instanceName provided for sdk sandbox process");
         }
@@ -4344,7 +4447,8 @@
                 final ServiceRestarter res = new ServiceRestarter();
                 final String processName = getProcessNameForService(sInfo, cn, callingPackage,
                         null /* instanceName */, false /* isSdkSandbox */,
-                        false /* inSharedIsolatedProcess */);
+                        false /* inSharedIsolatedProcess */,
+                        false /*inPrivateSharedIsolatedProcess*/);
                 r = new ServiceRecord(mAm, cn /* name */, cn /* instanceName */,
                         sInfo.applicationInfo.packageName, sInfo.applicationInfo.uid, filter, sInfo,
                         callingFromFg, res, processName,
@@ -4415,6 +4519,10 @@
                             throw new SecurityException("BIND_EXTERNAL_SERVICE failed, "
                                     + className + " is not exported");
                         }
+                        if (inPrivateSharedIsolatedProcess) {
+                            throw new SecurityException("BIND_PACKAGE_ISOLATED_PROCESS cannot be "
+                                    + "applied to an external service.");
+                        }
                         if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) {
                             throw new SecurityException("BIND_EXTERNAL_SERVICE failed, "
                                     + className + " is not an isolatedProcess");
@@ -4448,29 +4556,45 @@
                     throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + name +
                             " is not an externalService");
                 }
-                if (inSharedIsolatedProcess) {
+                if (inSharedIsolatedProcess && inPrivateSharedIsolatedProcess) {
+                    throw new SecurityException("Either BIND_SHARED_ISOLATED_PROCESS or "
+                            + "BIND_PACKAGE_ISOLATED_PROCESS should be set. Not both.");
+                }
+                if (inSharedIsolatedProcess || inPrivateSharedIsolatedProcess) {
                     if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) {
                         throw new SecurityException("BIND_SHARED_ISOLATED_PROCESS failed, "
                                 + className + " is not an isolatedProcess");
                     }
+                }
+                if (inPrivateSharedIsolatedProcess && isDefaultProcessService(sInfo)) {
+                    throw new SecurityException("BIND_PACKAGE_ISOLATED_PROCESS cannot be used for "
+                            + "services running in the main app process.");
+                }
+                if (inSharedIsolatedProcess) {
+                    if (instanceName == null) {
+                        throw new IllegalArgumentException("instanceName must be provided for "
+                                + "binding a service into a shared isolated process.");
+                    }
                     if ((sInfo.flags & ServiceInfo.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) == 0) {
                         throw new SecurityException("BIND_SHARED_ISOLATED_PROCESS failed, "
                                 + className + " has not set the allowSharedIsolatedProcess "
                                 + " attribute.");
                     }
-                    if (instanceName == null) {
-                        throw new IllegalArgumentException("instanceName must be provided for "
-                                + "binding a service into a shared isolated process.");
-                    }
                 }
                 if (userId > 0) {
+                    if (mAm.isSystemUserOnly(sInfo.flags)) {
+                        Slog.w(TAG_SERVICE, service + " is only available for the SYSTEM user,"
+                                + " calling userId is: " + userId);
+                        return null;
+                    }
+
                     if (mAm.isSingleton(sInfo.processName, sInfo.applicationInfo,
                             sInfo.name, sInfo.flags)
                             && mAm.isValidSingletonCall(callingUid, sInfo.applicationInfo.uid)) {
                         userId = 0;
                         smap = getServiceMapLocked(0);
                         // Bypass INTERACT_ACROSS_USERS permission check
-                        final long token = Binder.clearCallingIdentity();
+                        final long token = mAm.mInjector.clearCallingIdentity();
                         try {
                             ResolveInfo rInfoForUserId0 =
                                     mAm.getPackageManagerInternal().resolveService(service,
@@ -4483,7 +4607,7 @@
                             }
                             sInfo = rInfoForUserId0.serviceInfo;
                         } finally {
-                            Binder.restoreCallingIdentity(token);
+                            mAm.mInjector.restoreCallingIdentity(token);
                         }
                     }
                     sInfo = new ServiceInfo(sInfo);
@@ -4497,11 +4621,13 @@
                             = new Intent.FilterComparison(service.cloneFilter());
                     final ServiceRestarter res = new ServiceRestarter();
                     String processName = getProcessNameForService(sInfo, name, callingPackage,
-                            instanceName, isSdkSandboxService, inSharedIsolatedProcess);
+                            instanceName, isSdkSandboxService, inSharedIsolatedProcess,
+                            inPrivateSharedIsolatedProcess);
                     r = new ServiceRecord(mAm, className, name, definingPackageName,
                             definingUid, filter, sInfo, callingFromFg, res,
                             processName, sdkSandboxClientAppUid,
-                            sdkSandboxClientAppPackage, inSharedIsolatedProcess);
+                            sdkSandboxClientAppPackage,
+                            (inSharedIsolatedProcess || inPrivateSharedIsolatedProcess));
                     res.setService(r);
                     smap.mServicesByInstanceName.put(name, r);
                     smap.mServicesByIntent.put(filter, r);
@@ -4610,7 +4736,8 @@
      * @return {@code true} if it performed oomAdjUpdate.
      */
     private boolean bumpServiceExecutingLocked(
-            ServiceRecord r, boolean fg, String why, @OomAdjReason int oomAdjReason) {
+            ServiceRecord r, boolean fg, String why, @OomAdjReason int oomAdjReason,
+            boolean skipTimeoutIfPossible) {
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING "
                 + why + " of " + r + " in app " + r.app);
         else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, ">>> EXECUTING "
@@ -4634,6 +4761,10 @@
             timeoutNeeded = false;
         }
 
+        // If the process is frozen or to be frozen, and we want to skip the timeout, skip it.
+        final boolean shouldSkipTimeout = skipTimeoutIfPossible && r.app != null
+                && (r.app.mOptRecord.isPendingFreeze() || r.app.mOptRecord.isFrozen());
+
         ProcessServiceRecord psr;
         if (r.executeNesting == 0) {
             r.executeFg = fg;
@@ -4649,7 +4780,11 @@
                 psr.startExecutingService(r);
                 psr.setExecServicesFg(psr.shouldExecServicesFg() || fg);
                 if (timeoutNeeded && psr.numberOfExecutingServices() == 1) {
-                    scheduleServiceTimeoutLocked(r.app);
+                    if (!shouldSkipTimeout) {
+                        scheduleServiceTimeoutLocked(r.app);
+                    } else {
+                        r.app.mServices.noteScheduleServiceTimeoutPending(true);
+                    }
                 }
             }
         } else if (r.app != null && fg) {
@@ -4657,7 +4792,11 @@
             if (!psr.shouldExecServicesFg()) {
                 psr.setExecServicesFg(true);
                 if (timeoutNeeded) {
-                    scheduleServiceTimeoutLocked(r.app);
+                    if (!shouldSkipTimeout) {
+                        scheduleServiceTimeoutLocked(r.app);
+                    } else {
+                        r.app.mServices.noteScheduleServiceTimeoutPending(true);
+                    }
                 }
             }
         }
@@ -4677,16 +4816,22 @@
     }
 
     private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i,
-            boolean execInFg, boolean rebind) throws TransactionTooLargeException {
+            boolean execInFg, boolean rebind,
+            @ServiceBindingOomAdjPolicy int serviceBindingOomAdjPolicy)
+            throws TransactionTooLargeException {
         if (r.app == null || r.app.getThread() == null) {
             // If service is not currently running, can't yet bind.
             return false;
         }
         if (DEBUG_SERVICE) Slog.d(TAG_SERVICE, "requestBind " + i + ": requested=" + i.requested
                 + " rebind=" + rebind);
+        final boolean skipOomAdj = (serviceBindingOomAdjPolicy
+                & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_BIND) != 0;
         if ((!i.requested || rebind) && i.apps.size() > 0) {
             try {
-                bumpServiceExecutingLocked(r, execInFg, "bind", OOM_ADJ_REASON_BIND_SERVICE);
+                i.mSkippedOomAdj = !bumpServiceExecutingLocked(r, execInFg, "bind",
+                        skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_BIND_SERVICE,
+                        skipOomAdj /* skipTimeoutIfPossible */);
                 if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                     Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "requestServiceBinding="
                             + i.intent.getIntent() + ". bindSeq=" + mBindServiceSeqCounter);
@@ -4703,14 +4848,14 @@
                 if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r, e);
                 final boolean inDestroying = mDestroyingServices.contains(r);
                 serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
-                        OOM_ADJ_REASON_UNBIND_SERVICE);
+                        skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE);
                 throw e;
             } catch (RemoteException e) {
                 if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r);
                 // Keep the executeNesting count accurate.
                 final boolean inDestroying = mDestroyingServices.contains(r);
                 serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
-                        OOM_ADJ_REASON_UNBIND_SERVICE);
+                        skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE);
                 return false;
             }
         }
@@ -5082,7 +5227,7 @@
         }
         try {
             bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true, false,
-                    false, true);
+                    false, true, SERVICE_BIND_OOMADJ_POLICY_LEGACY);
         } catch (TransactionTooLargeException e) {
             // Ignore, it's been logged and nothing upstack cares.
         } finally {
@@ -5182,7 +5327,7 @@
 
     private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
             boolean whileRestarting, boolean permissionsReviewRequired, boolean packageFrozen,
-            boolean enqueueOomAdj)
+            boolean enqueueOomAdj, @ServiceBindingOomAdjPolicy int serviceBindingOomAdjPolicy)
             throws TransactionTooLargeException {
         try {
             if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
@@ -5190,7 +5335,8 @@
                         "bringUpServiceLocked: " + r.shortInstanceName);
             }
             return bringUpServiceInnerLocked(r, intentFlags, execInFg, whileRestarting,
-                    permissionsReviewRequired, packageFrozen, enqueueOomAdj);
+                    permissionsReviewRequired, packageFrozen, enqueueOomAdj,
+                    serviceBindingOomAdjPolicy);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
         }
@@ -5198,7 +5344,7 @@
 
     private String bringUpServiceInnerLocked(ServiceRecord r, int intentFlags, boolean execInFg,
             boolean whileRestarting, boolean permissionsReviewRequired, boolean packageFrozen,
-            boolean enqueueOomAdj)
+            boolean enqueueOomAdj, @ServiceBindingOomAdjPolicy int serviceBindingOomAdjPolicy)
             throws TransactionTooLargeException {
         if (r.app != null && r.app.isThreadReady()) {
             sendServiceArgsLocked(r, execInFg, false);
@@ -5282,7 +5428,7 @@
                         app.addPackage(r.appInfo.packageName, r.appInfo.longVersionCode,
                                 mAm.mProcessStats);
                         realStartServiceLocked(r, app, thread, pid, uidRecord, execInFg,
-                                enqueueOomAdj);
+                                enqueueOomAdj, serviceBindingOomAdjPolicy);
                         return null;
                     } catch (TransactionTooLargeException e) {
                         throw e;
@@ -5312,7 +5458,7 @@
                                         "realStartServiceLocked: " + r.shortInstanceName);
                             }
                             realStartServiceLocked(r, app, thread, pid, uidRecord, execInFg,
-                                    enqueueOomAdj);
+                                    enqueueOomAdj, SERVICE_BIND_OOMADJ_POLICY_LEGACY);
                             return null;
                         } catch (TransactionTooLargeException e) {
                             throw e;
@@ -5371,13 +5517,13 @@
                 return msg;
             }
             mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
-                    hostingRecord, true);
+                    true);
             if (isolated) {
                 r.isolationHostProc = app;
             }
         } else {
             mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
-                    hostingRecord, false);
+                    false);
         }
 
         if (r.fgRequired) {
@@ -5417,16 +5563,61 @@
         return HostingRecord.TRIGGER_TYPE_UNKNOWN;
     }
 
-    private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg)
+    private void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg,
+            @ServiceBindingOomAdjPolicy int serviceBindingOomAdjPolicy)
             throws TransactionTooLargeException {
         for (int i=r.bindings.size()-1; i>=0; i--) {
             IntentBindRecord ibr = r.bindings.valueAt(i);
-            if (!requestServiceBindingLocked(r, ibr, execInFg, false)) {
+            if (!requestServiceBindingLocked(r, ibr, execInFg, false, serviceBindingOomAdjPolicy)) {
                 break;
             }
         }
     }
 
+    @ServiceBindingOomAdjPolicy
+    private int getServiceBindingOomAdjPolicyForAddLocked(ProcessRecord clientApp,
+            ProcessRecord hostApp, ConnectionRecord cr) {
+        @ServiceBindingOomAdjPolicy int policy = SERVICE_BIND_OOMADJ_POLICY_LEGACY;
+        if (Flags.serviceBindingOomAdjPolicy() && clientApp != null && hostApp != null) {
+            if (clientApp == hostApp) {
+                policy = DEFAULT_SERVICE_NO_BUMP_BIND_POLICY_FLAG;
+            } else if (clientApp.isCached()) {
+                policy = DEFAULT_SERVICE_NO_BUMP_BIND_POLICY_FLAG;
+                if (clientApp.isFreezable()) {
+                    policy |= SERVICE_BIND_OOMADJ_POLICY_FREEZE_CALLER;
+                }
+            }
+            if ((policy & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT) == 0) {
+                // Binding between two different processes.
+                // Check if the caller has a better process state, oom adj score,
+                // or if the caller has more capabilities.
+                if (!mAm.mOomAdjuster.evaluateServiceConnectionAdd(clientApp, hostApp, cr)) {
+                    // Running an oom adjuster won't be give the host app a better score, skip it.
+                    policy = DEFAULT_SERVICE_NO_BUMP_BIND_POLICY_FLAG;
+                }
+            }
+        }
+        return policy;
+    }
+
+    @ServiceBindingOomAdjPolicy
+    private int getServiceBindingOomAdjPolicyForRemovalLocked(ProcessRecord clientApp,
+            ProcessRecord hostApp, ConnectionRecord cr) {
+        @ServiceBindingOomAdjPolicy int policy = SERVICE_BIND_OOMADJ_POLICY_LEGACY;
+        if (Flags.serviceBindingOomAdjPolicy() && clientApp != null && hostApp != null
+                && cr != null) {
+            if (clientApp == hostApp) {
+                policy = DEFAULT_SERVICE_NO_BUMP_BIND_POLICY_FLAG;
+            } else {
+                if (!mAm.mOomAdjuster.evaluateServiceConnectionRemoval(clientApp, hostApp, cr)) {
+                    // Running an oom adjuster won't be give the host app a better score, skip it.
+                    policy = DEFAULT_SERVICE_NO_BUMP_BIND_POLICY_FLAG;
+                }
+            }
+        }
+        return policy;
+    }
+
     /**
      * Note the name of this method should not be confused with the started services concept.
      * The "start" here means bring up the instance in the client, and this method is called
@@ -5434,7 +5625,8 @@
      */
     private void realStartServiceLocked(ServiceRecord r, ProcessRecord app,
             IApplicationThread thread, int pid, UidRecord uidRecord, boolean execInFg,
-            boolean enqueueOomAdj) throws RemoteException {
+            boolean enqueueOomAdj, @ServiceBindingOomAdjPolicy int serviceBindingOomAdjPolicy)
+            throws RemoteException {
         if (thread == null) {
             throw new RemoteException();
         }
@@ -5443,17 +5635,28 @@
                     + ", ProcessRecord.uid = " + app.uid);
         r.setProcess(app, thread, pid, uidRecord);
         r.restartTime = r.lastActivity = SystemClock.uptimeMillis();
-
+        final boolean skipOomAdj = (serviceBindingOomAdjPolicy
+                & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE) != 0;
         final ProcessServiceRecord psr = app.mServices;
         final boolean newService = psr.startService(r);
         bumpServiceExecutingLocked(r, execInFg, "create",
-                OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */);
+                OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */,
+                skipOomAdj /* skipTimeoutIfPossible */);
         mAm.updateLruProcessLocked(app, false, null);
         updateServiceForegroundLocked(psr, /* oomAdj= */ false);
-        // Force an immediate oomAdjUpdate, so the client app could be in the correct process state
-        // before doing any service related transactions
-        mAm.enqueueOomAdjTargetLocked(app);
-        mAm.updateOomAdjLocked(app, OOM_ADJ_REASON_START_SERVICE);
+        // Skip the oom adj update if it's a self-binding, the Service#onCreate() will be running
+        // at its current adj score.
+        if (!skipOomAdj) {
+            // Force an immediate oomAdjUpdate, so the host app could be in the correct
+            // process state before doing any service related transactions
+            mAm.enqueueOomAdjTargetLocked(app);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
+        } else {
+            // Since we skipped the oom adj update, the Service#onCreate() might be running in
+            // the cached state, if the service process drops into the cached state after the call.
+            // But there is still a grace period before freezing it, so we should be fine
+            // in terms of not getting an ANR.
+        }
 
         boolean created = false;
         try {
@@ -5488,7 +5691,7 @@
                 // Keep the executeNesting count accurate.
                 final boolean inDestroying = mDestroyingServices.contains(r);
                 serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
-                        OOM_ADJ_REASON_STOP_SERVICE);
+                        skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_STOP_SERVICE);
 
                 // Cleanup.
                 if (newService) {
@@ -5507,7 +5710,7 @@
             psr.mAllowlistManager = true;
         }
 
-        requestServiceBindingsLocked(r, execInFg);
+        requestServiceBindingsLocked(r, execInFg, serviceBindingOomAdjPolicy);
 
         updateServiceClientActivitiesLocked(psr, null, true);
 
@@ -5575,7 +5778,8 @@
                     UserHandle.getAppId(r.appInfo.uid)
             );
             bumpServiceExecutingLocked(r, execInFg, "start",
-                    OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */);
+                    OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */,
+                    false /* skipTimeoutIfPossible */);
             if (r.fgRequired && !r.fgWaiting) {
                 if (!r.isForeground) {
                     if (DEBUG_BACKGROUND_CHECK) {
@@ -5718,7 +5922,8 @@
                 if (ibr.hasBound) {
                     try {
                         oomAdjusted |= bumpServiceExecutingLocked(r, false, "bring down unbind",
-                                OOM_ADJ_REASON_UNBIND_SERVICE);
+                                OOM_ADJ_REASON_UNBIND_SERVICE,
+                                false /* skipTimeoutIfPossible */);
                         ibr.hasBound = false;
                         ibr.requested = false;
                         r.app.getThread().scheduleUnbindService(r,
@@ -5874,7 +6079,8 @@
                 } else {
                     try {
                         oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy",
-                                oomAdjusted ? 0 : OOM_ADJ_REASON_STOP_SERVICE);
+                                oomAdjusted ? 0 : OOM_ADJ_REASON_STOP_SERVICE,
+                                false /* skipTimeoutIfPossible */);
                         mDestroyingServices.add(r);
                         r.destroying = true;
                         r.app.getThread().scheduleStopService(r);
@@ -5957,11 +6163,17 @@
         }
     }
 
-    void removeConnectionLocked(ConnectionRecord c, ProcessRecord skipApp,
+    /**
+     * @return The ServiceBindingOomAdjPolicy used in this removal.
+     */
+    @ServiceBindingOomAdjPolicy
+    int removeConnectionLocked(ConnectionRecord c, ProcessRecord skipApp,
             ActivityServiceConnectionsHolder skipAct, boolean enqueueOomAdj) {
         IBinder binder = c.conn.asBinder();
         AppBindRecord b = c.binding;
         ServiceRecord s = b.service;
+        @ServiceBindingOomAdjPolicy int serviceBindingOomAdjPolicy =
+                SERVICE_BIND_OOMADJ_POLICY_LEGACY;
         ArrayList<ConnectionRecord> clist = s.getConnections().get(binder);
         if (clist != null) {
             clist.remove(c);
@@ -6020,8 +6232,14 @@
                     + ": shouldUnbind=" + b.intent.hasBound);
             if (s.app != null && s.app.isThreadReady() && b.intent.apps.size() == 0
                     && b.intent.hasBound) {
+                serviceBindingOomAdjPolicy = getServiceBindingOomAdjPolicyForRemovalLocked(b.client,
+                        s.app, c);
+                final boolean skipOomAdj = (serviceBindingOomAdjPolicy
+                        & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT) != 0;
                 try {
-                    bumpServiceExecutingLocked(s, false, "unbind", OOM_ADJ_REASON_UNBIND_SERVICE);
+                    b.intent.mSkippedOomAdj = !bumpServiceExecutingLocked(s, false, "unbind",
+                            skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE,
+                            skipOomAdj /* skipTimeoutIfPossible */);
                     if (b.client != s.app && c.notHasFlag(Context.BIND_WAIVE_PRIORITY)
                             && s.app.mState.getSetProcState() <= PROCESS_STATE_HEAVY_WEIGHT) {
                         // If this service's process is not already in the cached list,
@@ -6061,12 +6279,14 @@
                         "removeConnection");
             }
         }
+        return serviceBindingOomAdjPolicy;
     }
 
     void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res,
-            boolean enqueueOomAdj) {
+            boolean enqueueOomAdj, Intent intent) {
         boolean inDestroying = mDestroyingServices.contains(r);
         if (r != null) {
+            boolean skipOomAdj = false;
             if (type == ActivityThread.SERVICE_DONE_EXECUTING_START) {
                 // This is a call from a service start...  take care of
                 // book-keeping.
@@ -6142,14 +6362,19 @@
                     // Fake it to keep from ANR due to orphaned entry.
                     r.executeNesting = 1;
                 }
+            } else if (type == ActivityThread.SERVICE_DONE_EXECUTING_REBIND
+                    || type == ActivityThread.SERVICE_DONE_EXECUTING_UNBIND) {
+                final Intent.FilterComparison filter = new Intent.FilterComparison(intent);
+                final IntentBindRecord b = r.bindings.get(filter);
+                skipOomAdj = Flags.serviceBindingOomAdjPolicy() && b != null && b.mSkippedOomAdj;
             }
-            final long origId = Binder.clearCallingIdentity();
+            final long origId = mAm.mInjector.clearCallingIdentity();
             serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj,
-                    OOM_ADJ_REASON_EXECUTING_SERVICE);
-            Binder.restoreCallingIdentity(origId);
+                    skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_EXECUTING_SERVICE);
+            mAm.mInjector.restoreCallingIdentity(origId);
         } else {
             Slog.w(TAG, "Done executing unknown service from pid "
-                    + Binder.getCallingPid());
+                    + mAm.mInjector.getCallingPid());
         }
     }
 
@@ -6201,10 +6426,17 @@
                     mDestroyingServices.remove(r);
                     r.bindings.clear();
                 }
-                if (enqueueOomAdj) {
-                    mAm.enqueueOomAdjTargetLocked(r.app);
+                boolean oomAdjusted = false;
+                if (oomAdjReason != OOM_ADJ_REASON_NONE) {
+                    if (enqueueOomAdj) {
+                        mAm.enqueueOomAdjTargetLocked(r.app);
+                    } else {
+                        mAm.updateOomAdjLocked(r.app, oomAdjReason);
+                    }
+                    oomAdjusted = true;
                 } else {
-                    mAm.updateOomAdjLocked(r.app, oomAdjReason);
+                    // Skip oom adj if it wasn't bumped during the bumpServiceExecutingLocked()
+                    oomAdjusted = false;
                 }
             }
             r.executeFg = false;
@@ -6261,7 +6493,7 @@
                                     "realStartServiceLocked: " + sr.shortInstanceName);
                         }
                         realStartServiceLocked(sr, proc, thread, pid, uidRecord, sr.createdFromFg,
-                                true);
+                                true, SERVICE_BIND_OOMADJ_POLICY_LEGACY);
                     } finally {
                         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                     }
@@ -6751,6 +6983,7 @@
         }
 
         psr.stopAllExecutingServices();
+        psr.noteScheduleServiceTimeoutPending(false);
     }
 
     ActivityManager.RunningServiceInfo makeRunningServiceInfoLocked(ServiceRecord r) {
@@ -6801,7 +7034,7 @@
         ArrayList<ActivityManager.RunningServiceInfo> res
                 = new ArrayList<ActivityManager.RunningServiceInfo>();
 
-        final long ident = Binder.clearCallingIdentity();
+        final long ident = mAm.mInjector.clearCallingIdentity();
         try {
             if (canInteractAcrossUsers) {
                 int[] users = mAm.mUserController.getUsers();
@@ -6843,14 +7076,14 @@
                 }
             }
         } finally {
-            Binder.restoreCallingIdentity(ident);
+            mAm.mInjector.restoreCallingIdentity(ident);
         }
 
         return res;
     }
 
     public PendingIntent getRunningServiceControlPanelLocked(ComponentName name) {
-        int userId = UserHandle.getUserId(Binder.getCallingUid());
+        int userId = UserHandle.getUserId(mAm.mInjector.getCallingUid());
         ServiceRecord r = getServiceByNameLocked(name, userId);
         if (r != null) {
             ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = r.getConnections();
@@ -7042,6 +7275,7 @@
         final long delay = proc.mServices.shouldExecServicesFg()
                 ? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT;
         mActiveServiceAnrTimer.start(proc, delay);
+        proc.mServices.noteScheduleServiceTimeoutPending(false);
     }
 
     void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
@@ -8498,7 +8732,8 @@
                 null /* sdkSandboxClientAppPackage */, null /* resolvedType */, callingPackage,
                 callingPid, callingUid, userId, true /* createIfNeeded */,
                 false /* callingFromFg */, false /* isBindExternal */, false /* allowInstant */ ,
-                options, false /* inSharedIsolatedProcess */);
+                options, false /* inSharedIsolatedProcess */,
+                false /*inPrivateSharedIsolatedProcess*/);
         if (res == null || res.record == null) {
             Slog.d(TAG,
                     "startForegroundServiceDelegateLocked retrieveServiceLocked returns null");
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 8ad60e6..72e62c3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -243,7 +243,7 @@
     /**
      * The default value to {@link #KEY_ENABLE_NEW_OOMADJ}.
      */
-    private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = Flags.oomadjusterCorrectnessRewrite();
+    private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = false;
 
     /**
      * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index fddb570..374a17a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -135,6 +135,7 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__INTERNAL_NON_EXPORTED_COMPONENT_MATCH;
 import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NEW_MUTABLE_IMPLICIT_PENDING_INTENT_RETRIEVED;
+import static com.android.sdksandbox.flags.Flags.sdkSandboxInstrumentationInfo;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALLOWLISTS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
@@ -472,6 +473,8 @@
 import com.android.server.pm.snapshot.PackageDataSnapshot;
 import com.android.server.power.stats.BatteryStatsImpl;
 import com.android.server.sdksandbox.SdkSandboxManagerLocal;
+import com.android.server.stats.pull.StatsPullAtomService;
+import com.android.server.stats.pull.StatsPullAtomServiceInternal;
 import com.android.server.uri.GrantUri;
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.uri.UriGrantsManagerInternal;
@@ -1308,6 +1311,8 @@
      */
     final BatteryStatsService mBatteryStatsService;
 
+    StatsPullAtomServiceInternal mStatsPullAtomServiceInternal;
+
     /**
      * Information about component usage
      */
@@ -1747,7 +1752,7 @@
     @GuardedBy("mProcLock")
     private long mLastBinderHeavyHitterAutoSamplerStart = 0L;
 
-    final AppProfiler mAppProfiler;
+    AppProfiler mAppProfiler;
 
     private static final int INDEX_NATIVE_PSS = 0;
     private static final int INDEX_NATIVE_SWAP_PSS = 1;
@@ -2492,7 +2497,7 @@
         mInjector = injector;
         mContext = mInjector.getContext();
         mUiContext = null;
-        mAppErrors = null;
+        mAppErrors = injector.getAppErrors();
         mPackageWatchdog = null;
         mAppOpsService = mInjector.getAppOpsService(null /* recentAccessesFile */,
             null /* storageFile */, null /* handler */);
@@ -2510,7 +2515,7 @@
                 ? new OomAdjusterModernImpl(this, mProcessList, activeUids, handlerThread)
                 : new OomAdjuster(this, mProcessList, activeUids, handlerThread);
 
-        mIntentFirewall = null;
+        mIntentFirewall = injector.getIntentFirewall();
         mProcessStats = new ProcessStatsService(this, mContext.getCacheDir());
         mCpHelper = new ContentProviderHelper(this, false);
         mServices = mInjector.getActiveServices(this);
@@ -5087,13 +5092,11 @@
         intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
                 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
                 | Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
-        final BroadcastOptions bOptions = mUserController.getTemporaryAppAllowlistBroadcastOptions(
-                reason);
 
         broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null,
                 new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
                 null, null, AppOpsManager.OP_NONE,
-                bOptions.toBundle(), true,
+                null, true,
                 false, MY_PID, SYSTEM_UID,
                 SYSTEM_UID, MY_PID, app.userId);
     }
@@ -6525,7 +6528,24 @@
     @Override
     public int checkUriPermission(Uri uri, int pid, int uid,
             final int modeFlags, int userId, IBinder callerToken) {
-        enforceNotIsolatedCaller("checkUriPermission");
+        return checkUriPermission(uri, pid, uid, modeFlags, userId,
+                /* isFullAccessForContentUri */ false, "checkUriPermission");
+    }
+
+    /**
+     * @param uri This uri must NOT contain an embedded userId.
+     * @param userId The userId in which the uri is to be resolved.
+     */
+    @Override
+    public int checkContentUriPermissionFull(Uri uri, int pid, int uid,
+            final int modeFlags, int userId) {
+        return checkUriPermission(uri, pid, uid, modeFlags, userId,
+                /* isFullAccessForContentUri */ true, "checkContentUriPermissionFull");
+    }
+
+    private int checkUriPermission(Uri uri, int pid, int uid,
+            final int modeFlags, int userId, boolean isFullAccessForContentUri, String methodName) {
+        enforceNotIsolatedCaller(methodName);
 
         // Our own process gets to do everything.
         if (pid == MY_PID) {
@@ -6536,8 +6556,10 @@
                 return PackageManager.PERMISSION_DENIED;
             }
         }
-        return mUgmInternal.checkUriPermission(new GrantUri(userId, uri, modeFlags), uid, modeFlags)
-                ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED;
+        boolean granted = mUgmInternal.checkUriPermission(new GrantUri(userId, uri, modeFlags), uid,
+                modeFlags, isFullAccessForContentUri);
+
+        return granted ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED;
     }
 
     @Override
@@ -7800,12 +7822,7 @@
     @Override
     public void registerUidObserver(IUidObserver observer, int which, int cutpoint,
             String callingPackage) {
-        if (!hasUsageStatsPermission(callingPackage)) {
-            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
-                    "registerUidObserver");
-        }
-        mUidObserverController.register(observer, which, cutpoint, callingPackage,
-                Binder.getCallingUid(), /*uids*/null);
+        registerUidObserverForUids(observer, which, cutpoint, callingPackage, null /* uids */);
     }
 
     /**
@@ -9858,7 +9875,7 @@
 
 
     @Override
-    public void setApplicationStartInfoCompleteListener(
+    public void addApplicationStartInfoCompleteListener(
             IApplicationStartInfoCompleteListener listener, int userId) {
         enforceNotIsolatedCaller("setApplicationStartInfoCompleteListener");
 
@@ -9873,7 +9890,8 @@
 
 
     @Override
-    public void clearApplicationStartInfoCompleteListener(int userId) {
+    public void removeApplicationStartInfoCompleteListener(
+            IApplicationStartInfoCompleteListener listener, int userId) {
         enforceNotIsolatedCaller("clearApplicationStartInfoCompleteListener");
 
         // For the simplification, we don't support USER_ALL nor USER_CURRENT here.
@@ -9882,7 +9900,8 @@
         }
 
         final int callingUid = Binder.getCallingUid();
-        mProcessList.getAppStartInfoTracker().clearStartInfoCompleteListener(callingUid, true);
+        mProcessList.getAppStartInfoTracker().removeStartInfoCompleteListener(listener, callingUid,
+                true);
     }
 
     @Override
@@ -13747,6 +13766,11 @@
         return result;
     }
 
+    boolean isSystemUserOnly(int flags) {
+        return android.multiuser.Flags.enableSystemUserOnlyForServicesAndProviders()
+                && (flags & ServiceInfo.FLAG_SYSTEM_USER_ONLY) != 0;
+    }
+
     /**
      * Checks to see if the caller is in the same app as the singleton
      * component, or the component is in a special app. It allows special apps
@@ -13866,13 +13890,15 @@
         }
     }
 
-    public void serviceDoneExecuting(IBinder token, int type, int startId, int res) {
+    @Override
+    public void serviceDoneExecuting(IBinder token, int type, int startId, int res, Intent intent) {
         synchronized(this) {
             if (!(token instanceof ServiceRecord)) {
                 Slog.e(TAG, "serviceDoneExecuting: Invalid service token=" + token);
                 throw new IllegalArgumentException("Invalid service token");
             }
-            mServices.serviceDoneExecutingLocked((ServiceRecord) token, type, startId, res, false);
+            mServices.serviceDoneExecutingLocked((ServiceRecord) token, type, startId, res, false,
+                    intent);
         }
     }
 
@@ -16116,10 +16142,22 @@
         }
 
         final ApplicationInfo sdkSandboxInfo;
+        final String processName;
         try {
-            sdkSandboxInfo =
-                    sandboxManagerLocal.getSdkSandboxApplicationInfoForInstrumentation(
-                            sdkSandboxClientAppInfo, isSdkInSandbox);
+            if (sdkSandboxInstrumentationInfo()) {
+                sdkSandboxInfo =
+                        sandboxManagerLocal.getSdkSandboxApplicationInfoForInstrumentation(
+                                sdkSandboxClientAppInfo, isSdkInSandbox);
+                processName = sdkSandboxInfo.processName;
+            } else {
+                final PackageManager pm = mContext.getPackageManager();
+                sdkSandboxInfo =
+                        pm.getApplicationInfoAsUser(pm.getSdkSandboxPackageName(), 0, userId);
+                processName =
+                        sandboxManagerLocal.getSdkSandboxProcessNameForInstrumentation(
+                                sdkSandboxClientAppInfo);
+                sdkSandboxInfo.uid = Process.toSdkSandboxUid(sdkSandboxClientAppInfo.uid);
+            }
         } catch (NameNotFoundException e) {
             reportStartInstrumentationFailureLocked(
                     watcher, className, "Can't find SdkSandbox package");
@@ -16128,7 +16166,7 @@
 
         ActiveInstrumentation activeInstr = new ActiveInstrumentation(this);
         activeInstr.mClass = className;
-        activeInstr.mTargetProcesses = new String[]{sdkSandboxInfo.processName};
+        activeInstr.mTargetProcesses = new String[]{processName};
         activeInstr.mTargetInfo = sdkSandboxInfo;
         activeInstr.mIsSdkInSandbox = isSdkInSandbox;
         activeInstr.mProfileFile = profileFile;
@@ -16171,7 +16209,7 @@
 
                 ProcessRecord app = addAppLocked(
                         sdkSandboxInfo,
-                        sdkSandboxInfo.processName,
+                        processName,
                         /* isolated= */ false,
                         /* isSdkSandbox= */ true,
                         sdkSandboxInfo.uid,
@@ -16535,6 +16573,21 @@
                 final @ProcessCapability int capability) {
         mBatteryStatsService.noteUidProcessState(uid, state);
         mAppOpsService.updateUidProcState(uid, state, capability);
+        if (StatsPullAtomService.ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) {
+            try {
+                if (mStatsPullAtomServiceInternal == null) {
+                    mStatsPullAtomServiceInternal = LocalServices.getService(
+                            StatsPullAtomServiceInternal.class);
+                }
+                if (mStatsPullAtomServiceInternal != null) {
+                    mStatsPullAtomServiceInternal.noteUidProcessState(uid, state);
+                } else {
+                    Slog.d(TAG, "StatsPullAtomService not ready yet");
+                }
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception during logging uid proc state change event", e);
+            }
+        }
         if (mTrackingAssociations) {
             for (int i1=0, N1=mAssociations.size(); i1<N1; i1++) {
                 ArrayMap<ComponentName, SparseArray<ArrayMap<String, Association>>> targetComponents
@@ -20144,8 +20197,7 @@
          * Returns the {@link BatteryStatsService} instance
          */
         public BatteryStatsService getBatteryStatsService() {
-            return new BatteryStatsService(mContext, SystemServiceManager.ensureSystemDir(),
-                BackgroundThread.get().getHandler());
+            return new BatteryStatsService(mContext, SystemServiceManager.ensureSystemDir());
         }
 
         /**
@@ -20199,6 +20251,36 @@
             }
             return broadcastQueues;
         }
+
+        /** @see Binder#getCallingUid */
+        public int getCallingUid() {
+            return Binder.getCallingUid();
+        }
+
+        /** @see Binder#getCallingPid */
+        public int getCallingPid() {
+            return Binder.getCallingUid();
+        }
+
+        /** @see Binder#clearCallingIdentity */
+        public long clearCallingIdentity() {
+            return Binder.clearCallingIdentity();
+        }
+
+        /** @see Binder#clearCallingIdentity */
+        public void restoreCallingIdentity(long ident) {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        /** @return the default instance of AppErrors */
+        public AppErrors getAppErrors() {
+            return null;
+        }
+
+        /** @return the default instance of intent firewall */
+        public IntentFirewall getIntentFirewall() {
+            return null;
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 57c52c2..45f657d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -3754,6 +3754,11 @@
         }
 
         @Override
+        public void onProcessStarted(int pid, int processUid, int packageUid, String packageName,
+                String processName) {
+        }
+
+        @Override
         public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
         }
 
diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java
index e0a2246..9fc0bf9 100644
--- a/services/core/java/com/android/server/am/AnrHelper.java
+++ b/services/core/java/com/android/server/am/AnrHelper.java
@@ -63,6 +63,11 @@
     private static final long CONSECUTIVE_ANR_TIME_MS = TimeUnit.MINUTES.toMillis(2);
 
     /**
+     * Time to wait before taking dumps for other processes to reduce load at boot time.
+     */
+    private static final long SELF_ONLY_AFTER_BOOT_MS = TimeUnit.MINUTES.toMillis(10);
+
+    /**
      * The keep alive time for the threads in the helper threadpool executor
     */
     private static final int DEFAULT_THREAD_KEEP_ALIVE_SECOND = 10;
@@ -231,7 +236,8 @@
                 // If there are many ANR at the same time, the latency may be larger.
                 // If the latency is too large, the stack trace might not be meaningful.
                 final long reportLatency = startTime - r.mTimestamp;
-                final boolean onlyDumpSelf = reportLatency > EXPIRED_REPORT_TIME_MS;
+                final boolean onlyDumpSelf = reportLatency > EXPIRED_REPORT_TIME_MS
+                        || startTime < SELF_ONLY_AFTER_BOOT_MS;
                 r.appNotResponding(onlyDumpSelf);
                 final long endTime = SystemClock.uptimeMillis();
                 Slog.d(TAG, "Completed ANR of " + r.mApp.processName + " in "
diff --git a/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
index b07d9a6..9c2e69b 100644
--- a/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
@@ -520,7 +520,7 @@
         /**
          * Default value to {@link #mTrackerEnabled}.
          */
-        static final boolean DEFAULT_BG_BATTERY_EXEMPTION_ENABLED = true;
+        static final boolean DEFAULT_BG_BATTERY_EXEMPTION_ENABLED = false;
 
         AppBatteryExemptionPolicy(@NonNull Injector injector,
                 @NonNull AppBatteryExemptionTracker tracker) {
diff --git a/services/core/java/com/android/server/am/AppFGSTracker.java b/services/core/java/com/android/server/am/AppFGSTracker.java
index 1f98aba..fb89b8e 100644
--- a/services/core/java/com/android/server/am/AppFGSTracker.java
+++ b/services/core/java/com/android/server/am/AppFGSTracker.java
@@ -102,6 +102,11 @@
         }
 
         @Override
+        public void onProcessStarted(int pid, int processUid, int packageUid, String packageName,
+                String processName) {
+        }
+
+        @Override
         public void onProcessDied(int pid, int uid) {
         }
     };
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 82e554e..c857235 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -81,18 +81,18 @@
     private static final int FOREACH_ACTION_REMOVE_ITEM = 1;
     private static final int FOREACH_ACTION_STOP_ITERATION = 2;
 
-    private static final int APP_START_INFO_HISTORY_LIST_SIZE = 16;
+    @VisibleForTesting static final int APP_START_INFO_HISTORY_LIST_SIZE = 16;
 
     @VisibleForTesting static final String APP_START_STORE_DIR = "procstartstore";
 
     @VisibleForTesting static final String APP_START_INFO_FILE = "procstartinfo";
 
-    private final Object mLock = new Object();
+    @VisibleForTesting final Object mLock = new Object();
 
-    private boolean mEnabled = false;
+    @VisibleForTesting boolean mEnabled = false;
 
     /** Initialized in {@link #init} and read-only after that. */
-    private ActivityManagerService mService;
+    @VisibleForTesting ActivityManagerService mService;
 
     /** Initialized in {@link #init} and read-only after that. */
     private Handler mHandler;
@@ -112,14 +112,14 @@
      *
      * <p>Initialized in {@link #init} and read-only after that. No lock is needed.
      */
-    private int mAppStartInfoHistoryListSize;
+    @VisibleForTesting int mAppStartInfoHistoryListSize;
 
     @GuardedBy("mLock")
     private final ProcessMap<AppStartInfoContainer> mData;
 
     /** UID as key. */
     @GuardedBy("mLock")
-    private final SparseArray<ApplicationStartInfoCompleteCallback> mCallbacks;
+    private final SparseArray<ArrayList<ApplicationStartInfoCompleteCallback>> mCallbacks;
 
     /**
      * Whether or not we've loaded the historical app process start info from persistent storage.
@@ -146,7 +146,8 @@
      * Key is timestamp of launch from {@link #ActivityMetricsLaunchObserver}.
      */
     @GuardedBy("mLock")
-    private ArrayMap<Long, ApplicationStartInfo> mInProgRecords = new ArrayMap<>();
+    @VisibleForTesting
+    final ArrayMap<Long, ApplicationStartInfo> mInProgRecords = new ArrayMap<>();
 
     AppStartInfoTracker() {
         mCallbacks = new SparseArray<>();
@@ -229,7 +230,7 @@
                 ApplicationStartInfo info = mInProgRecords.get(id);
                 info.setStartType((int) temperature);
                 addBaseFieldsFromProcessRecord(info, app);
-                addStartInfoLocked(info);
+                mInProgRecords.put(id, addStartInfoLocked(info));
             } else {
                 mInProgRecords.remove(id);
             }
@@ -262,6 +263,7 @@
             ApplicationStartInfo info = mInProgRecords.get(id);
             info.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN);
             info.setLaunchMode(launchMode);
+            checkCompletenessAndCallback(info);
         }
     }
 
@@ -281,7 +283,7 @@
     }
 
     public void handleProcessServiceStart(long startTimeNs, ProcessRecord app,
-                ServiceRecord serviceRecord, HostingRecord hostingRecord, boolean cold) {
+                ServiceRecord serviceRecord, boolean cold) {
         synchronized (mLock) {
             if (!mEnabled) {
                 return;
@@ -297,7 +299,9 @@
                     && serviceRecord.permission.contains("android.permission.BIND_JOB_SERVICE")
                     ? ApplicationStartInfo.START_REASON_JOB
                     : ApplicationStartInfo.START_REASON_SERVICE);
-            start.setIntent(serviceRecord.intent.getIntent());
+            if (serviceRecord.intent != null) {
+                start.setIntent(serviceRecord.intent.getIntent());
+            }
             addStartInfoLocked(start);
         }
     }
@@ -378,6 +382,7 @@
         start.setPackageUid(app.info.uid);
         start.setDefiningUid(definingUid > 0 ? definingUid : app.info.uid);
         start.setProcessName(app.processName);
+        start.setPackageName(app.info.packageName);
     }
 
     void reportApplicationOnCreateTimeNanos(ProcessRecord app, long timeNs) {
@@ -419,12 +424,12 @@
     }
 
     private void addTimestampToStart(ProcessRecord app, long timeNs, int key) {
-        addTimestampToStart(app.processName, app.uid, timeNs, key);
+        addTimestampToStart(app.info.packageName, app.uid, timeNs, key);
     }
 
-    private void addTimestampToStart(String processName, int uid, long timeNs, int key) {
+    private void addTimestampToStart(String packageName, int uid, long timeNs, int key) {
         synchronized (mLock) {
-            AppStartInfoContainer container = mData.get(processName, uid);
+            AppStartInfoContainer container = mData.get(packageName, uid);
             if (container == null) {
                 // Record was not created, discard new data.
                 return;
@@ -443,11 +448,11 @@
 
         final ApplicationStartInfo info = new ApplicationStartInfo(raw);
 
-        AppStartInfoContainer container = mData.get(raw.getProcessName(), raw.getRealUid());
+        AppStartInfoContainer container = mData.get(raw.getPackageName(), raw.getRealUid());
         if (container == null) {
             container = new AppStartInfoContainer(mAppStartInfoHistoryListSize);
             container.mUid = info.getRealUid();
-            mData.put(raw.getProcessName(), raw.getRealUid(), container);
+            mData.put(raw.getPackageName(), raw.getRealUid(), container);
         }
         container.addStartInfoLocked(info);
 
@@ -465,11 +470,18 @@
         synchronized (mLock) {
             if (startInfo.getStartupState()
                     == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN) {
-                ApplicationStartInfoCompleteCallback callback =
+                final List<ApplicationStartInfoCompleteCallback> callbacks =
                         mCallbacks.get(startInfo.getRealUid());
-                if (callback != null) {
-                    callback.onApplicationStartInfoComplete(startInfo);
+                if (callbacks == null) {
+                    return;
                 }
+                final int size = callbacks.size();
+                for (int i = 0; i < size; i++) {
+                    if (callbacks.get(i) != null) {
+                        callbacks.get(i).onApplicationStartInfoComplete(startInfo);
+                    }
+                }
+                mCallbacks.remove(startInfo.getRealUid());
             }
         }
     }
@@ -479,6 +491,9 @@
         if (!mEnabled) {
             return;
         }
+        if (maxNum == 0) {
+            maxNum = APP_START_INFO_HISTORY_LIST_SIZE;
+        }
         final long identity = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
@@ -542,7 +557,6 @@
             } catch (RemoteException e) {
                 /*ignored*/
             }
-            clearStartInfoCompleteListener(mUid, true);
         }
 
         void unlinkToDeath() {
@@ -551,7 +565,7 @@
 
         @Override
         public void binderDied() {
-            clearStartInfoCompleteListener(mUid, false);
+            removeStartInfoCompleteListener(mCallback, mUid, false);
         }
     }
 
@@ -561,22 +575,43 @@
             if (!mEnabled) {
                 return;
             }
-            mCallbacks.put(uid, new ApplicationStartInfoCompleteCallback(listener, uid));
+            ArrayList<ApplicationStartInfoCompleteCallback> callbacks = mCallbacks.get(uid);
+            if (callbacks == null) {
+                mCallbacks.set(uid,
+                        callbacks = new ArrayList<ApplicationStartInfoCompleteCallback>());
+            }
+            callbacks.add(new ApplicationStartInfoCompleteCallback(listener, uid));
         }
     }
 
-    void clearStartInfoCompleteListener(final int uid, boolean unlinkDeathRecipient) {
+    void removeStartInfoCompleteListener(
+            final IApplicationStartInfoCompleteListener listener, final int uid,
+            boolean unlinkDeathRecipient) {
         synchronized (mLock) {
             if (!mEnabled) {
                 return;
             }
-            if (unlinkDeathRecipient) {
-                ApplicationStartInfoCompleteCallback callback = mCallbacks.get(uid);
-                if (callback != null) {
-                    callback.unlinkToDeath();
+            final ArrayList<ApplicationStartInfoCompleteCallback> callbacks = mCallbacks.get(uid);
+            if (callbacks == null) {
+                return;
+            }
+            final int size  = callbacks.size();
+            int index;
+            for (index = 0; index < size; index++) {
+                final ApplicationStartInfoCompleteCallback callback = callbacks.get(index);
+                if (callback.mCallback == listener) {
+                    if (unlinkDeathRecipient) {
+                        callback.unlinkToDeath();
+                    }
+                    break;
                 }
             }
-            mCallbacks.remove(uid);
+            if (index < size) {
+                callbacks.remove(index);
+            }
+            if (callbacks.isEmpty()) {
+                mCallbacks.remove(uid);
+            }
         }
     }
 
@@ -864,6 +899,7 @@
                 mProcStartInfoFile.delete();
             }
             mData.getMap().clear();
+            mInProgRecords.clear();
         }
     }
 
@@ -933,6 +969,10 @@
 
     /** Convenience method to obtain timestamp of beginning of start.*/
     private static long getStartTimestamp(ApplicationStartInfo startInfo) {
+        if (startInfo.getStartupTimestamps() == null
+                    || !startInfo.getStartupTimestamps().containsKey(START_TIMESTAMP_LAUNCH)) {
+            return -1;
+        }
         return startInfo.getStartupTimestamps().get(START_TIMESTAMP_LAUNCH);
     }
 
@@ -970,7 +1010,6 @@
                 if (oldestIndex >= 0) {
                     mInfos.remove(oldestIndex);
                 }
-                mInfos.remove(0);
             }
             mInfos.add(info);
             Collections.sort(mInfos, (a, b) ->
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index eea9337..4f46ecd 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -151,6 +151,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Properties;
+import java.util.concurrent.CancellationException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
@@ -381,8 +382,7 @@
         }
     };
 
-    BatteryStatsService(Context context, File systemDir, Handler handler) {
-        // BatteryStatsImpl expects the ActivityManagerService handler, so pass that one through.
+    BatteryStatsService(Context context, File systemDir) {
         mContext = context;
         mUserManagerUserInfoProvider = new BatteryStatsImpl.UserInfoProvider() {
             private UserManagerInternal umi;
@@ -416,13 +416,15 @@
                         .build();
         mPowerStatsUidResolver = new PowerStatsUidResolver();
         mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
-                systemDir, handler, this, this, mUserManagerUserInfoProvider, mPowerProfile,
+                systemDir, mHandler, this, this, mUserManagerUserInfoProvider, mPowerProfile,
                 mCpuScalingPolicies, mPowerStatsUidResolver);
         mWorker = new BatteryExternalStatsWorker(context, mStats);
         mStats.setExternalStatsSyncLocked(mWorker);
         mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_radioScanningTimeout) * 1000L);
-        mStats.startTrackingSystemServerCpuTime();
+        if (!Flags.disableSystemServicePowerAttr()) {
+            mStats.startTrackingSystemServerCpuTime();
+        }
 
         mAggregatedPowerStatsConfig = createAggregatedPowerStatsConfig();
         mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, mAggregatedPowerStatsConfig);
@@ -477,7 +479,7 @@
      */
     public static BatteryStatsService create(Context context, File systemDir, Handler handler,
             BatteryStatsImpl.BatteryCallback callback) {
-        BatteryStatsService service = new BatteryStatsService(context, systemDir, handler);
+        BatteryStatsService service = new BatteryStatsService(context, systemDir);
         service.mStats.setCallback(callback);
         synchronized (service.mStats) {
             service.mStats.readLocked();
@@ -654,7 +656,7 @@
             try {
                 future.get();
                 return;
-            } catch (ExecutionException e) {
+            } catch (ExecutionException | CancellationException e) {
                 return;
             } catch (InterruptedException e) {
                 // Keep looping
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 2cac7a0..db0f03f 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -1244,9 +1244,11 @@
     }
 
     private void deliveryTimeout(@NonNull BroadcastProcessQueue queue) {
+        final int cookie = traceBegin("deliveryTimeout");
         synchronized (mService) {
             deliveryTimeoutLocked(queue);
         }
+        traceEnd(cookie);
     }
 
     private void deliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue) {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 626b70b..d92a24b 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -1419,6 +1419,11 @@
     }
 
     @GuardedBy({"mAm", "mProcLock"})
+    void freezeAppAsyncAtEarliestLSP(ProcessRecord app) {
+        freezeAppAsyncLSP(app, updateEarliestFreezableTime(app, 0));
+    }
+
+    @GuardedBy({"mAm", "mProcLock"})
     void freezeAppAsyncInternalLSP(ProcessRecord app, @UptimeMillisLong long delayMillis,
             boolean force) {
         final ProcessCachedOptimizerRecord opt = app.mOptRecord;
@@ -1714,6 +1719,14 @@
                 compactApp(frozenProc, CompactProfile.FULL, CompactSource.APP, false);
             }
         }
+        frozenProc.onProcessFrozen();
+    }
+
+    /**
+     * Callback received when an attempt to freeze a process is cancelled (failed).
+     */
+    void onProcessFrozenCancelled(ProcessRecord app) {
+        app.onProcessFrozenCancelled();
     }
 
     /**
@@ -2203,6 +2216,8 @@
                         onProcessFrozen(proc);
                         removeMessages(DEADLOCK_WATCHDOG_MSG);
                         sendEmptyMessageDelayed(DEADLOCK_WATCHDOG_MSG, FREEZE_DEADLOCK_TIMEOUT_MS);
+                    } else {
+                        onProcessFrozenCancelled(proc);
                     }
                 } break;
                 case REPORT_UNFREEZE_MSG: {
@@ -2460,7 +2475,7 @@
                                 pr = mAm.mPidsSelfLocked.get(blocked);
                             }
                             if (pr != null
-                                    && pr.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
+                                    && pr.mState.getCurAdj() < ProcessList.FREEZER_CUTOFF_ADJ) {
                                 Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks "
                                         + pr.processName + " (" + blocked + ")");
                                 // Found at least one blocked non-cached process
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 095d907..cb7898d 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -35,6 +35,7 @@
 import static com.android.internal.util.FrameworkStatsLog.PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
 import static com.android.server.am.ActivityManagerService.TAG_MU;
+import static com.android.server.am.Flags.serviceBindingOomAdjPolicy;
 
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -319,8 +320,10 @@
 
                     checkTime(startTime, "getContentProviderImpl: before updateOomAdj");
                     final int verifiedAdj = cpr.proc.mState.getVerifiedAdj();
-                    boolean success = mService.updateOomAdjLocked(cpr.proc,
-                            OOM_ADJ_REASON_GET_PROVIDER);
+                    boolean success = !serviceBindingOomAdjPolicy()
+                            || mService.mOomAdjuster.evaluateProviderConnectionAdd(r, cpr.proc)
+                            ? mService.updateOomAdjLocked(cpr.proc, OOM_ADJ_REASON_GET_PROVIDER)
+                            : true;
                     // XXX things have changed so updateOomAdjLocked doesn't actually tell us
                     // if the process has been successfully adjusted.  So to reduce races with
                     // it, we will check whether the process still exists.  Note that this doesn't
@@ -1249,9 +1252,9 @@
             ProviderInfo cpi = providers.get(i);
             boolean singleton = mService.isSingleton(cpi.processName, cpi.applicationInfo,
                     cpi.name, cpi.flags);
-            if (singleton && app.userId != UserHandle.USER_SYSTEM) {
-                // This is a singleton provider, but a user besides the
-                // default user is asking to initialize a process it runs
+            if (isSingletonOrSystemUserOnly(cpi) && app.userId != UserHandle.USER_SYSTEM) {
+                // This is a singleton or a SYSTEM user only provider, but a user besides the
+                // SYSTEM user is asking to initialize a process it runs
                 // in...  well, no, it doesn't actually run in this process,
                 // it runs in the process of the default user.  Get rid of it.
                 providers.remove(i);
@@ -1398,8 +1401,7 @@
                                     final boolean processMatch =
                                             Objects.equals(pi.processName, app.processName)
                                             || pi.multiprocess;
-                                    final boolean userMatch = !mService.isSingleton(
-                                            pi.processName, pi.applicationInfo, pi.name, pi.flags)
+                                    final boolean userMatch = !isSingletonOrSystemUserOnly(pi)
                                             || app.userId == UserHandle.USER_SYSTEM;
                                     final boolean isInstantApp = pi.applicationInfo.isInstantApp();
                                     final boolean splitInstalled = pi.splitName == null
@@ -1530,7 +1532,9 @@
             }
             mService.stopAssociationLocked(conn.client.uid, conn.client.processName, cpr.uid,
                     cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
-            if (updateOomAdj) {
+            if (updateOomAdj && (!serviceBindingOomAdjPolicy()
+                    || mService.mOomAdjuster.evaluateProviderConnectionRemoval(conn.client,
+                            cpr.proc))) {
                 mService.updateOomAdjLocked(conn.provider.proc, OOM_ADJ_REASON_REMOVE_PROVIDER);
             }
         }
@@ -1985,4 +1989,13 @@
             return isAuthRedirected;
         }
     }
+
+    /**
+     * Returns true if Provider is either singleUser or systemUserOnly provider.
+     */
+    private boolean isSingletonOrSystemUserOnly(ProviderInfo pi) {
+        return (android.multiuser.Flags.enableSystemUserOnlyForServicesAndProviders()
+                && mService.isSystemUserOnly(pi.flags))
+                || mService.isSingleton(pi.processName, pi.applicationInfo, pi.name, pi.flags);
+    }
 }
diff --git a/services/core/java/com/android/server/am/IntentBindRecord.java b/services/core/java/com/android/server/am/IntentBindRecord.java
index abc7ab1..db47e3f 100644
--- a/services/core/java/com/android/server/am/IntentBindRecord.java
+++ b/services/core/java/com/android/server/am/IntentBindRecord.java
@@ -46,9 +46,17 @@
     boolean hasBound;
     /** Set when the service's onUnbind() has asked to be told about new clients. */
     boolean doRebind;
-    
+
     String stringName;      // caching of toString
-    
+
+    /**
+     * Mark if we've skipped oom adj update before calling into the {@link Service#onBind()}
+     * or {@link Service#onUnbind()}.
+     *
+     * <p>If it's true, we'll skip the oom adj update too during the serviceDoneExecuting.
+     */
+    boolean mSkippedOomAdj;
+
     void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("service="); pw.println(service);
         dumpInService(pw, prefix);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index ef7a0e0..862542e 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -103,6 +103,7 @@
 import static com.android.server.am.ProcessList.CACHED_APP_MAX_ADJ;
 import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
 import static com.android.server.am.ProcessList.FOREGROUND_APP_ADJ;
+import static com.android.server.am.ProcessList.FREEZER_CUTOFF_ADJ;
 import static com.android.server.am.ProcessList.HEAVY_WEIGHT_APP_ADJ;
 import static com.android.server.am.ProcessList.HOME_APP_ADJ;
 import static com.android.server.am.ProcessList.INVALID_ADJ;
@@ -2309,7 +2310,7 @@
                     }
 
                     computeServiceHostOomAdjLSP(cr, app, cr.binding.client, now, topApp, doingAll,
-                            cycleReEval, computeClients, oomAdjReason, cachedAdj, true);
+                            cycleReEval, computeClients, oomAdjReason, cachedAdj, true, false);
 
                     adj = state.getCurRawAdj();
                     procState = state.getCurRawProcState();
@@ -2341,7 +2342,7 @@
                     ContentProviderConnection conn = cpr.connections.get(i);
                     ProcessRecord client = conn.client;
                     computeProviderHostOomAdjLSP(conn, app, client, now, topApp, doingAll,
-                            cycleReEval, computeClients, oomAdjReason, cachedAdj, true);
+                            cycleReEval, computeClients, oomAdjReason, cachedAdj, true, false);
 
                     adj = state.getCurRawAdj();
                     procState = state.getCurRawProcState();
@@ -2558,17 +2559,18 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    protected void computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app,
+    protected boolean computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app,
             ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
             boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj,
-            boolean couldRecurse) {
+            boolean couldRecurse, boolean dryRun) {
         if (app.isPendingFinishAttach()) {
             // We've set the attaching process state in the computeInitialOomAdjLSP. Skip it here.
-            return;
+            return false;
         }
 
         final ProcessStateRecord state = app.mState;
         ProcessStateRecord cstate = client.mState;
+        boolean updated = false;
 
         if (couldRecurse) {
             if (app.isSdkSandbox && cr.binding.attributedClient != null) {
@@ -2599,19 +2601,25 @@
         final int prevRawAdj = adj;
         final int prevProcState = procState;
         final int prevSchedGroup = schedGroup;
+        final int prevCapability = capability;
 
         final int appUid = app.info.uid;
         final int logUid = mService.mCurOomAdjUid;
 
-        state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp()
-                || cstate.isCurBoundByNonBgRestrictedApp()
-                || clientProcState <= PROCESS_STATE_BOUND_TOP
-                || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE
-                        && !cstate.isBackgroundRestricted()));
+        if (!dryRun) {
+            state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp()
+                    || cstate.isCurBoundByNonBgRestrictedApp()
+                    || clientProcState <= PROCESS_STATE_BOUND_TOP
+                    || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE
+                            && !cstate.isBackgroundRestricted()));
+        }
 
         if (client.mOptRecord.shouldNotFreeze()) {
             // Propagate the shouldNotFreeze flag down the bindings.
-            app.mOptRecord.setShouldNotFreeze(true);
+            if (app.mOptRecord.setShouldNotFreeze(true, dryRun)) {
+                // Bail out early, as we only care about the return value for a dryrun.
+                return true;
+            }
         }
 
         boolean trackedProcState = false;
@@ -2653,7 +2661,7 @@
             }
 
             if (couldRecurse && shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) {
-                return;
+                return false;
             }
 
             if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
@@ -2666,7 +2674,10 @@
             if (cr.hasFlag(Context.BIND_ALLOW_OOM_MANAGEMENT)) {
                 // Similar to BIND_WAIVE_PRIORITY, keep it unfrozen.
                 if (clientAdj < CACHED_APP_MIN_ADJ) {
-                    app.mOptRecord.setShouldNotFreeze(true);
+                    if (app.mOptRecord.setShouldNotFreeze(true, dryRun)) {
+                        // Bail out early, as we only care about the return value for a dryrun.
+                        return true;
+                    }
                 }
                 // Not doing bind OOM management, so treat
                 // this guy more like a started service.
@@ -2678,7 +2689,10 @@
                     if (adj > clientAdj) {
                         adjType = "cch-bound-ui-services";
                     }
-                    state.setCached(false);
+                    if (state.setCached(false, dryRun)) {
+                        // Bail out early, as we only care about the return value for a dryrun.
+                        return true;
+                    }
                     clientAdj = adj;
                     clientProcState = procState;
                 } else {
@@ -2721,7 +2735,9 @@
                             newAdj = PERSISTENT_SERVICE_ADJ;
                             schedGroup = SCHED_GROUP_DEFAULT;
                             procState = ActivityManager.PROCESS_STATE_PERSISTENT;
-                            cr.trackProcState(procState, mAdjSeq);
+                            if (!dryRun) {
+                                cr.trackProcState(procState, mAdjSeq);
+                            }
                             trackedProcState = true;
                         }
                     } else if (cr.hasFlag(Context.BIND_NOT_PERCEPTIBLE)
@@ -2762,11 +2778,16 @@
                         }
                     }
                     if (!cstate.isCached()) {
-                        state.setCached(false);
+                        if (state.setCached(false, dryRun)) {
+                            // Bail out early, as we only care about the return value for a dryrun.
+                            return true;
+                        }
                     }
                     if (adj >  newAdj) {
                         adj = newAdj;
-                        state.setCurRawAdj(adj);
+                        if (state.setCurRawAdj(adj, dryRun)) {
+                            // Bail out early, as we only care about the return value for a dryrun.
+                        }
                         adjType = "service";
                     }
                 }
@@ -2833,25 +2854,35 @@
 
             if (cr.hasFlag(Context.BIND_SCHEDULE_LIKE_TOP_APP) && clientIsSystem) {
                 schedGroup = SCHED_GROUP_TOP_APP;
-                state.setScheduleLikeTopApp(true);
+                if (dryRun) {
+                    if (prevSchedGroup < schedGroup) {
+                        // Bail out early, as we only care about the return value for a dryrun.
+                        return true;
+                    }
+                } else {
+                    state.setScheduleLikeTopApp(true);
+                }
             }
 
-            if (!trackedProcState) {
+            if (!trackedProcState && !dryRun) {
                 cr.trackProcState(clientProcState, mAdjSeq);
             }
 
             if (procState > clientProcState) {
                 procState = clientProcState;
-                state.setCurRawProcState(procState);
+                if (state.setCurRawProcState(procState, dryRun)) {
+                    // Bail out early, as we only care about the return value for a dryrun.
+                    return true;
+                }
                 if (adjType == null) {
                     adjType = "service";
                 }
             }
             if (procState < PROCESS_STATE_IMPORTANT_BACKGROUND
-                    && cr.hasFlag(Context.BIND_SHOWING_UI)) {
+                    && cr.hasFlag(Context.BIND_SHOWING_UI) && !dryRun) {
                 app.setPendingUiClean(true);
             }
-            if (adjType != null) {
+            if (adjType != null && !dryRun) {
                 state.setAdjType(adjType);
                 state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
                         .REASON_SERVICE_IN_USE);
@@ -2876,11 +2907,16 @@
             // bound by an unfrozen app via a WPRI binding has to remain
             // unfrozen.
             if (clientAdj < CACHED_APP_MIN_ADJ) {
-                app.mOptRecord.setShouldNotFreeze(true);
+                if (app.mOptRecord.setShouldNotFreeze(true, dryRun)) {
+                    // Bail out early, as we only care about the return value for a dryrun.
+                    return true;
+                }
             }
         }
         if (cr.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) {
-            app.mServices.setTreatLikeActivity(true);
+            if (!dryRun) {
+                app.mServices.setTreatLikeActivity(true);
+            }
             if (clientProcState <= PROCESS_STATE_CACHED_ACTIVITY
                     && procState > PROCESS_STATE_CACHED_ACTIVITY) {
                 // This is a cached process, but somebody wants us to treat it like it has
@@ -2894,7 +2930,9 @@
             if (a != null && adj > FOREGROUND_APP_ADJ
                     && a.isActivityVisible()) {
                 adj = FOREGROUND_APP_ADJ;
-                state.setCurRawAdj(adj);
+                if (state.setCurRawAdj(adj, dryRun)) {
+                    return true;
+                }
                 if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND)) {
                     if (cr.hasFlag(Context.BIND_IMPORTANT)) {
                         schedGroup = SCHED_GROUP_TOP_APP_BOUND;
@@ -2902,16 +2940,18 @@
                         schedGroup = SCHED_GROUP_DEFAULT;
                     }
                 }
-                state.setCached(false);
-                state.setAdjType("service");
-                state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
-                        .REASON_SERVICE_IN_USE);
-                state.setAdjSource(a);
-                state.setAdjSourceProcState(procState);
-                state.setAdjTarget(cr.binding.service.instanceName);
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ,
-                            "Raise to service w/activity: " + app);
+                if (!dryRun) {
+                    state.setCached(false);
+                    state.setAdjType("service");
+                    state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
+                            .REASON_SERVICE_IN_USE);
+                    state.setAdjSource(a);
+                    state.setAdjSourceProcState(procState);
+                    state.setAdjTarget(cr.binding.service.instanceName);
+                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                        reportOomAdjMessageLocked(TAG_OOM_ADJ,
+                                "Raise to service w/activity: " + app);
+                    }
                 }
             }
         }
@@ -2922,7 +2962,15 @@
         if (procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
             capability &= ~PROCESS_CAPABILITY_BFSL;
         }
+        if (!updated) {
+            updated = adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup
+                || (capability != prevCapability
+                        && (capability & prevCapability) == prevCapability);
+        }
 
+        if (dryRun) {
+            return updated;
+        }
         if (adj < prevRawAdj) {
             schedGroup = setIntermediateAdjLSP(app, adj, prevRawAdj, schedGroup);
         }
@@ -2935,15 +2983,16 @@
         state.setCurCapability(capability);
 
         state.setEmpty(false);
+        return updated;
     }
 
-    protected void computeProviderHostOomAdjLSP(ContentProviderConnection conn, ProcessRecord app,
-            ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
-            boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj,
-            boolean couldRecurse) {
+    protected boolean computeProviderHostOomAdjLSP(ContentProviderConnection conn,
+            ProcessRecord app, ProcessRecord client, long now, ProcessRecord topApp,
+            boolean doingAll, boolean cycleReEval, boolean computeClients, int oomAdjReason,
+            int cachedAdj, boolean couldRecurse, boolean dryRun) {
         if (app.isPendingFinishAttach()) {
             // We've set the attaching process state in the computeInitialOomAdjLSP. Skip it here.
-            return;
+            return false;
         }
 
         final ProcessStateRecord state = app.mState;
@@ -2951,7 +3000,7 @@
 
         if (client == app) {
             // Being our own client is not interesting.
-            return;
+            return false;
         }
         if (couldRecurse) {
             if (computeClients) {
@@ -2964,7 +3013,7 @@
 
             if (shouldSkipDueToCycle(app, cstate, state.getCurRawProcState(), state.getCurRawAdj(),
                     cycleReEval)) {
-                return;
+                return false;
             }
         }
 
@@ -2979,6 +3028,7 @@
         final int prevRawAdj = adj;
         final int prevProcState = procState;
         final int prevSchedGroup = schedGroup;
+        final int prevCapability = capability;
 
         final int appUid = app.info.uid;
         final int logUid = mService.mCurOomAdjUid;
@@ -2995,14 +3045,19 @@
         }
         if (client.mOptRecord.shouldNotFreeze()) {
             // Propagate the shouldNotFreeze flag down the bindings.
-            app.mOptRecord.setShouldNotFreeze(true);
+            if (app.mOptRecord.setShouldNotFreeze(true, dryRun)) {
+                // Bail out early, as we only care about the return value for a dryrun.
+                return true;
+            }
         }
 
-        state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp()
-                || cstate.isCurBoundByNonBgRestrictedApp()
-                || clientProcState <= PROCESS_STATE_BOUND_TOP
-                || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE
-                        && !cstate.isBackgroundRestricted()));
+        if (!dryRun) {
+            state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp()
+                    || cstate.isCurBoundByNonBgRestrictedApp()
+                    || clientProcState <= PROCESS_STATE_BOUND_TOP
+                    || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE
+                            && !cstate.isBackgroundRestricted()));
+        }
 
         String adjType = null;
         if (adj > clientAdj) {
@@ -3011,10 +3066,16 @@
                 adjType = "cch-ui-provider";
             } else {
                 adj = Math.max(clientAdj, FOREGROUND_APP_ADJ);
-                state.setCurRawAdj(adj);
+                if (state.setCurRawAdj(adj, dryRun)) {
+                    // Bail out early, as we only care about the return value for a dryrun.
+                    return true;
+                }
                 adjType = "provider";
             }
-            state.setCached(state.isCached() & cstate.isCached());
+            if (state.setCached(state.isCached() & cstate.isCached(), dryRun)) {
+                // Bail out early, as we only care about the return value for a dryrun.
+                return true;
+            }
         }
 
         if (clientProcState <= PROCESS_STATE_FOREGROUND_SERVICE) {
@@ -3028,15 +3089,20 @@
             }
         }
 
-        conn.trackProcState(clientProcState, mAdjSeq);
+        if (!dryRun) {
+            conn.trackProcState(clientProcState, mAdjSeq);
+        }
         if (procState > clientProcState) {
             procState = clientProcState;
-            state.setCurRawProcState(procState);
+            if (state.setCurRawProcState(procState, dryRun)) {
+                // Bail out early, as we only care about the return value for a dryrun.
+                return true;
+            }
         }
         if (cstate.getCurrentSchedulingGroup() > schedGroup) {
             schedGroup = SCHED_GROUP_DEFAULT;
         }
-        if (adjType != null) {
+        if (adjType != null && !dryRun) {
             state.setAdjType(adjType);
             state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
                     .REASON_PROVIDER_IN_USE);
@@ -3056,6 +3122,12 @@
             capability &= ~PROCESS_CAPABILITY_BFSL;
         }
 
+        if (dryRun && (adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup
+                || (capability != prevCapability
+                        && (capability & prevCapability) == prevCapability))) {
+            return true;
+        }
+
         if (adj < prevRawAdj) {
             schedGroup = setIntermediateAdjLSP(app, adj, prevRawAdj, schedGroup);
         }
@@ -3068,6 +3140,7 @@
         state.setCurCapability(capability);
 
         state.setEmpty(false);
+        return false;
     }
 
     protected int getDefaultCapability(ProcessRecord app, int procState) {
@@ -3342,7 +3415,7 @@
             changes |= ActivityManagerService.ProcessChangeItem.CHANGE_ACTIVITIES;
         }
 
-        updateAppFreezeStateLSP(app, oomAdjReson);
+        updateAppFreezeStateLSP(app, oomAdjReson, false);
 
         if (state.getReportedProcState() != state.getCurProcState()) {
             state.setReportedProcState(state.getCurProcState());
@@ -3727,7 +3800,8 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    private void updateAppFreezeStateLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
+    void updateAppFreezeStateLSP(ProcessRecord app, @OomAdjReason int oomAdjReason,
+            boolean immediate) {
         if (!mCachedAppOptimizer.useFreezer()) {
             return;
         }
@@ -3746,10 +3820,14 @@
 
         final ProcessStateRecord state = app.mState;
         // Use current adjustment when freezing, set adjustment when unfreezing.
-        if (state.getCurAdj() >= CACHED_APP_MIN_ADJ && !opt.isFrozen()
+        if (state.getCurAdj() >= FREEZER_CUTOFF_ADJ && !opt.isFrozen()
                 && !opt.shouldNotFreeze()) {
-            mCachedAppOptimizer.freezeAppAsyncLSP(app);
-        } else if (state.getSetAdj() < CACHED_APP_MIN_ADJ) {
+            if (!immediate) {
+                mCachedAppOptimizer.freezeAppAsyncLSP(app);
+            } else {
+                mCachedAppOptimizer.freezeAppAsyncAtEarliestLSP(app);
+            }
+        } else if (state.getSetAdj() < FREEZER_CUTOFF_ADJ) {
             mCachedAppOptimizer.unfreezeAppLSP(app,
                     CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason));
         }
@@ -3826,4 +3904,89 @@
         // The caller will set the initial value in this implementation.
         return app.mState.isCurBoundByNonBgRestrictedApp();
     }
+
+    /**
+     * Evaluate the service connection, return {@code true} if the client will change the state
+     * of the service host process by the given connection.
+     */
+    @GuardedBy("mService")
+    boolean evaluateServiceConnectionAdd(ProcessRecord client, ProcessRecord app,
+            ConnectionRecord cr) {
+        if (evaluateConnectionPrelude(client, app)) {
+            return true;
+        }
+        if (app.getSetAdj() <= client.getSetAdj()
+                && app.getSetProcState() <= client.getSetProcState()
+                && ((app.getSetCapability() & client.getSetCapability())
+                        == client.getSetCapability()
+                        || cr.notHasFlag(Context.BIND_INCLUDE_CAPABILITIES
+                                | Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS))) {
+            // The service host process has better states than the client, so no change.
+            return false;
+        }
+        // Take a dry run of the computeServiceHostOomAdjLSP, this would't be expensive
+        // since it's only evaluating one service connection.
+        return computeServiceHostOomAdjLSP(cr, app, client, SystemClock.uptimeMillis(),
+                mService.getTopApp(), false, false, false, OOM_ADJ_REASON_NONE,
+                CACHED_APP_MIN_ADJ, false, true /* dryRun */);
+    }
+
+    @GuardedBy("mService")
+    boolean evaluateServiceConnectionRemoval(ProcessRecord client, ProcessRecord app,
+            ConnectionRecord cr) {
+        if (evaluateConnectionPrelude(client, app)) {
+            return true;
+        }
+
+        if (app.getSetAdj() < client.getSetAdj()
+                && app.getSetProcState() < client.getSetProcState()) {
+            // The service host process has better states than the client.
+            if (((app.getSetCapability() & client.getSetCapability()) == PROCESS_CAPABILITY_NONE)
+                    || cr.notHasFlag(Context.BIND_INCLUDE_CAPABILITIES
+                            | Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS)) {
+                // The service host app doesn't get any capabilities from the client.
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @GuardedBy("mService")
+    boolean evaluateProviderConnectionAdd(ProcessRecord client, ProcessRecord app) {
+        if (evaluateConnectionPrelude(client, app)) {
+            return true;
+        }
+        if (app.getSetAdj() <= client.getSetAdj()
+                && app.getSetProcState() <= client.getSetProcState()) {
+            // The provider host process has better states than the client, so no change.
+            return false;
+        }
+        return computeProviderHostOomAdjLSP(null, app, client, SystemClock.uptimeMillis(),
+                mService.getTopApp(), false, false, false, OOM_ADJ_REASON_NONE, CACHED_APP_MIN_ADJ,
+                false, true /* dryRun */);
+    }
+
+    @GuardedBy("mService")
+    boolean evaluateProviderConnectionRemoval(ProcessRecord client, ProcessRecord app) {
+        if (evaluateConnectionPrelude(client, app)) {
+            return true;
+        }
+        if (app.getSetAdj() < client.getSetAdj()
+                && app.getSetProcState() < client.getSetProcState()) {
+            // The provider host process has better states than the client, so no change.
+            return false;
+        }
+        return true;
+    }
+
+    private boolean evaluateConnectionPrelude(ProcessRecord client, ProcessRecord app) {
+        if (client == null || app == null) {
+            return true;
+        }
+        if (app.isSdkSandbox || app.isolated || app.isKilledByAm() || app.isKilled()) {
+            // Let's always re-evaluate them for now.
+            return true;
+        }
+        return false;
+    }
 }
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 5a3fbe9..f85b03e 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -1002,7 +1002,7 @@
 
 
             computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
-                    oomAdjReason, cachedAdj, false);
+                    oomAdjReason, cachedAdj, false, false);
         }
 
         for (int i = psr.numberOfSdkSandboxConnections() - 1; i >= 0; i--) {
@@ -1018,7 +1018,7 @@
             }
 
             computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
-                    oomAdjReason, cachedAdj, false);
+                    oomAdjReason, cachedAdj, false, false);
         }
 
         final ProcessProviderRecord ppr = app.mProviders;
@@ -1035,7 +1035,7 @@
             }
 
             computeProviderHostOomAdjLSP(cpc, provider, app, now, topApp, fullUpdate, false, false,
-                    oomAdjReason, cachedAdj, false);
+                    oomAdjReason, cachedAdj, false, false);
         }
     }
 }
diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java
index a20623c..5df9107 100644
--- a/services/core/java/com/android/server/am/PendingIntentController.java
+++ b/services/core/java/com/android/server/am/PendingIntentController.java
@@ -30,6 +30,7 @@
 import android.app.AppGlobals;
 import android.app.PendingIntent;
 import android.app.PendingIntentStats;
+import android.app.compat.CompatChanges;
 import android.content.IIntentSender;
 import android.content.Intent;
 import android.os.Binder;
@@ -136,6 +137,11 @@
                         + "intent creator ("
                         + packageName
                         + ") because this option is meant for the pending intent sender");
+                if (CompatChanges.isChangeEnabled(PendingIntent.PENDING_INTENT_OPTIONS_CHECK,
+                        callingUid)) {
+                    throw new IllegalArgumentException("pendingIntentBackgroundActivityStartMode "
+                            + "must not be set when creating a PendingIntent");
+                }
                 opts.setPendingIntentBackgroundActivityStartMode(
                         ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
             }
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 10d5fd3..95e130e 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -406,6 +406,9 @@
             String resolvedType, IBinder allowlistToken, IIntentReceiver finishedReceiver,
             String requiredPermission, IBinder resultTo, String resultWho, int requestCode,
             int flagsMask, int flagsValues, Bundle options) {
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+
         if (intent != null) intent.setDefusable(true);
         if (options != null) options.setDefusable(true);
 
@@ -458,6 +461,12 @@
                                     + key.packageName
                                     + ") because this option is meant for the pending intent "
                                     + "creator");
+                    if (CompatChanges.isChangeEnabled(PendingIntent.PENDING_INTENT_OPTIONS_CHECK,
+                            callingUid)) {
+                        throw new IllegalArgumentException(
+                                "pendingIntentCreatorBackgroundActivityStartMode "
+                                + "must not be set when sending a PendingIntent");
+                    }
                     opts.setPendingIntentCreatorBackgroundActivityStartMode(
                             ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
                 }
@@ -494,9 +503,6 @@
         }
         // We don't hold the controller lock beyond this point as we will be calling into AM and WM.
 
-        final int callingUid = Binder.getCallingUid();
-        final int callingPid = Binder.getCallingPid();
-
         // Only system senders can declare a broadcast to be alarm-originated.  We check
         // this here rather than in the general case handling below to fail before the other
         // invocation side effects such as allowlisting.
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index f5c5ea8..a8fe734 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -274,7 +274,20 @@
 
     @GuardedBy("mProcLock")
     void setShouldNotFreeze(boolean shouldNotFreeze) {
+        setShouldNotFreeze(shouldNotFreeze, false);
+    }
+
+    /**
+     * @return {@code true} if it's a dry run and it's going to unfreeze the process
+     * if it was a real run.
+     */
+    @GuardedBy("mProcLock")
+    boolean setShouldNotFreeze(boolean shouldNotFreeze, boolean dryRun) {
+        if (dryRun) {
+            return mFrozen && !shouldNotFreeze;
+        }
         mShouldNotFreeze = shouldNotFreeze;
+        return false;
     }
 
     @GuardedBy("mProcLock")
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index b03183c..10cd6e5 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -371,6 +371,12 @@
     private static final long LMKD_RECONNECT_DELAY_MS = 1000;
 
     /**
+     * The cuttoff adj for the freezer, app processes with adj greater than this value will be
+     * eligible for the freezer.
+     */
+    static final int FREEZER_CUTOFF_ADJ = CACHED_APP_MIN_ADJ;
+
+    /**
      * Apps have no access to the private data directories of any other app, even if the other
      * app has made them world-readable.
      */
@@ -2852,6 +2858,7 @@
                         ? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);
             }
         }
+        dispatchProcessStarted(app, pid);
         checkSlow(app.getStartTime(), "startProcess: done updating pids map");
         return true;
     }
@@ -2935,7 +2942,11 @@
         return true;
     }
 
-    private static void freezeBinderAndPackageCgroup(ArrayList<Pair<ProcessRecord, Boolean>> procs,
+    private static boolean unfreezePackageCgroup(int packageUID) {
+        return freezePackageCgroup(packageUID, false);
+    }
+
+    private static void freezeBinderAndPackageCgroup(List<Pair<ProcessRecord, Boolean>> procs,
                                                      int packageUID) {
         // Freeze all binder processes under the target UID (whose cgroup is about to be frozen).
         // Since we're going to kill these, we don't need to unfreze them later.
@@ -2943,12 +2954,9 @@
         // processes (forks) should not be Binder users.
         int N = procs.size();
         for (int i = 0; i < N; i++) {
-            final int uid = procs.get(i).first.uid;
             final int pid = procs.get(i).first.getPid();
             int nRetries = 0;
-            // We only freeze the cgroup of the target package, so we do not need to freeze the
-            // Binder interfaces of dependant processes in other UIDs.
-            if (pid > 0 && uid == packageUID) {
+            if (pid > 0) {
                 try {
                     int rc;
                     do {
@@ -2962,12 +2970,19 @@
         }
 
         // We freeze the entire UID (parent) cgroup so that newly-specialized processes also freeze
-        // despite being added to a new child cgroup. The cgroups of package dependant processes are
-        // not frozen, since it's possible this would freeze processes with no dependency on the
-        // package being killed here.
+        // despite being added to a child cgroup created after this call that would otherwise be
+        // unfrozen.
         freezePackageCgroup(packageUID, true);
     }
 
+    private static List<Pair<ProcessRecord, Boolean>> getUIDSublist(
+            List<Pair<ProcessRecord, Boolean>> procs, int startIdx) {
+        final int uid = procs.get(startIdx).first.uid;
+        int endIdx = startIdx + 1;
+        while (endIdx < procs.size() && procs.get(endIdx).first.uid == uid) ++endIdx;
+        return procs.subList(startIdx, endIdx);
+    }
+
     @GuardedBy({"mService", "mProcLock"})
     boolean killPackageProcessesLSP(String packageName, int appId,
             int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
@@ -3063,25 +3078,36 @@
             }
         }
 
-        final int packageUID = UserHandle.getUid(userId, appId);
-        final boolean doFreeze = appId >= Process.FIRST_APPLICATION_UID
-                              && appId <= Process.LAST_APPLICATION_UID;
-        if (doFreeze) {
-            freezeBinderAndPackageCgroup(procs, packageUID);
+        final boolean killingUserApp = appId >= Process.FIRST_APPLICATION_UID
+                                    && appId <= Process.LAST_APPLICATION_UID;
+
+        if (killingUserApp) {
+            procs.sort((o1, o2) -> Integer.compare(o1.first.uid, o2.first.uid));
         }
 
-        int N = procs.size();
-        for (int i=0; i<N; i++) {
-            final Pair<ProcessRecord, Boolean> proc = procs.get(i);
-            removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
-                    reasonCode, subReason, reason, !doFreeze /* async */);
+        int idx = 0;
+        while (idx < procs.size()) {
+            final List<Pair<ProcessRecord, Boolean>> uidProcs = getUIDSublist(procs, idx);
+            final int packageUID = uidProcs.get(0).first.uid;
+
+            // Do not freeze for system apps or for dependencies of the targeted package, but
+            // make sure to freeze the targeted package for all users if called with USER_ALL.
+            final boolean doFreeze = killingUserApp && UserHandle.getAppId(packageUID) == appId;
+
+            if (doFreeze) freezeBinderAndPackageCgroup(uidProcs, packageUID);
+
+            for (Pair<ProcessRecord, Boolean> proc : uidProcs) {
+                removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
+                        reasonCode, subReason, reason, !doFreeze /* async */);
+            }
+            killAppZygotesLocked(packageName, appId, userId, false /* force */);
+
+            if (doFreeze) unfreezePackageCgroup(packageUID);
+
+            idx += uidProcs.size();
         }
-        killAppZygotesLocked(packageName, appId, userId, false /* force */);
         mService.updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END);
-        if (doFreeze) {
-            freezePackageCgroup(packageUID, false);
-        }
-        return N > 0;
+        return procs.size() > 0;
     }
 
     @GuardedBy("mService")
@@ -4958,6 +4984,22 @@
         }
     }
 
+    void dispatchProcessStarted(ProcessRecord app, int pid) {
+        int i = mProcessObservers.beginBroadcast();
+        while (i > 0) {
+            i--;
+            final IProcessObserver observer = mProcessObservers.getBroadcastItem(i);
+            if (observer != null) {
+                try {
+                    observer.onProcessStarted(pid, app.uid, app.info.uid,
+                            app.info.packageName, app.processName);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+        mProcessObservers.finishBroadcast();
+    }
+
     void dispatchProcessDied(int pid, int uid) {
         int i = mProcessObservers.beginBroadcast();
         while (i > 0) {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index e5c4a66..de6f034 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -720,6 +720,11 @@
         return mState.getSetProcState();
     }
 
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getSetCapability() {
+        return mState.getSetCapability();
+    }
+
     @GuardedBy({"mService", "mProcLock"})
     public void makeActive(IApplicationThread thread, ProcessStatsService tracker) {
         mProfile.onProcessActive(thread, tracker);
@@ -1401,8 +1406,12 @@
 
     void onProcessUnfrozen() {
         mProfile.onProcessUnfrozen();
+        mServices.onProcessUnfrozen();
     }
 
+    void onProcessFrozenCancelled() {
+        mServices.onProcessFrozenCancelled();
+    }
 
     /*
      *  Delete all packages from list except the package indicated in info
@@ -1644,6 +1653,13 @@
         return mWasForceStopped;
     }
 
+    boolean isFreezable() {
+        return mService.mOomAdjuster.mCachedAppOptimizer.useFreezer()
+                && !mOptRecord.isFreezeExempt()
+                && !mOptRecord.shouldNotFreeze()
+                && mState.getCurAdj() >= ProcessList.FREEZER_CUTOFF_ADJ;
+    }
+
     /**
      * Traverses all client processes and feed them to consumer.
      */
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index f5f2b10..57d233e 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -19,6 +19,8 @@
 import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BOUND_SERVICE;
 import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE;
 
+import static com.android.server.am.Flags.serviceBindingOomAdjPolicy;
+
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
@@ -144,6 +146,11 @@
      */
     private ArraySet<Integer> mBoundClientUids = new ArraySet<>();
 
+    /**
+     * The process should schedule a service timeout timer but haven't done so.
+     */
+    private boolean mScheduleServiceTimeoutPending;
+
     final ProcessRecord mApp;
 
     private final ActivityManagerService mService;
@@ -657,6 +664,41 @@
         setHasClientActivities(false);
     }
 
+    @GuardedBy("mService")
+    void noteScheduleServiceTimeoutPending(boolean pending) {
+        mScheduleServiceTimeoutPending = pending;
+    }
+
+    @GuardedBy("mService")
+    boolean isScheduleServiceTimeoutPending() {
+        return mScheduleServiceTimeoutPending;
+    }
+
+    @GuardedBy("mService")
+    void onProcessUnfrozen() {
+        scheduleServiceTimeoutIfNeededLocked();
+    }
+
+    @GuardedBy("mService")
+    void onProcessFrozenCancelled() {
+        scheduleServiceTimeoutIfNeededLocked();
+    }
+
+    @GuardedBy("mService")
+    private void scheduleServiceTimeoutIfNeededLocked() {
+        if (!serviceBindingOomAdjPolicy()) {
+            return;
+        }
+        if (mScheduleServiceTimeoutPending && mExecutingServices.size() > 0) {
+            mService.mServices.scheduleServiceTimeoutLocked(mApp);
+            // We'll need to reset the executingStart since the app was frozen.
+            final long now = SystemClock.uptimeMillis();
+            for (int i = 0, size = mExecutingServices.size(); i < size; i++) {
+                mExecutingServices.valueAt(i).executingStart = now;
+            }
+        }
+    }
+
     void dump(PrintWriter pw, String prefix, long nowUptime) {
         if (mHasForegroundServices || mApp.mState.getForcingToImportant() != null) {
             pw.print(prefix); pw.print("mHasForegroundServices="); pw.print(mHasForegroundServices);
@@ -701,5 +743,10 @@
                 pw.print(prefix); pw.print("  - "); pw.println(mConnections.valueAt(i));
             }
         }
+        if (serviceBindingOomAdjPolicy()) {
+            pw.print(prefix);
+            pw.print("scheduleServiceTimeoutPending=");
+            pw.println(mScheduleServiceTimeoutPending);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 3391ec7..8362eaf 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -479,9 +479,22 @@
 
     @GuardedBy({"mService", "mProcLock"})
     void setCurRawAdj(int curRawAdj) {
+        setCurRawAdj(curRawAdj, false);
+    }
+
+    /**
+     * @return {@code true} if it's a dry run and it's going to bump the adj score of the process
+     * if it was a real run.
+     */
+    @GuardedBy({"mService", "mProcLock"})
+    boolean setCurRawAdj(int curRawAdj, boolean dryRun) {
+        if (dryRun) {
+            return mCurRawAdj > curRawAdj;
+        }
         mCurRawAdj = curRawAdj;
         mApp.getWindowProcessController().setPerceptible(
                 curRawAdj <= ProcessList.PERCEPTIBLE_APP_ADJ);
+        return false;
     }
 
     @GuardedBy(anyOf = {"mService", "mProcLock"})
@@ -594,7 +607,20 @@
 
     @GuardedBy({"mService", "mProcLock"})
     void setCurRawProcState(int curRawProcState) {
+        setCurRawProcState(curRawProcState, false);
+    }
+
+    /**
+     * @return {@code true} if it's a dry run and it's going to bump the procstate of the process
+     * if it was a real run.
+     */
+    @GuardedBy({"mService", "mProcLock"})
+    boolean setCurRawProcState(int curRawProcState, boolean dryRun) {
+        if (dryRun) {
+            return mCurRawProcState > curRawProcState;
+        }
         mCurRawProcState = curRawProcState;
+        return false;
     }
 
     @GuardedBy(anyOf = {"mService", "mProcLock"})
@@ -900,7 +926,20 @@
 
     @GuardedBy("mService")
     void setCached(boolean cached) {
+        setCached(cached, false);
+    }
+
+    /**
+     * @return {@code true} if it's a dry run and it's going to uncache the process
+     * if it was a real run.
+     */
+    @GuardedBy("mService")
+    boolean setCached(boolean cached, boolean dryRun) {
+        if (dryRun) {
+            return mCached && !cached;
+        }
         mCached = cached;
+        return false;
     }
 
     @GuardedBy("mService")
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 08b129e..2771572 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -39,7 +39,7 @@
 import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
-import android.compat.annotation.Disabled;
+import android.compat.annotation.EnabledAfter;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -49,6 +49,7 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
+import android.os.Build.VERSION_CODES;
 import android.os.IBinder;
 import android.os.PowerExemptionManager;
 import android.os.SystemClock;
@@ -94,16 +95,14 @@
      * (See also android.app.ForegroundServiceTypePolicy)
      */
     @ChangeId
-    // @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE)
-    @Disabled
+    @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE)
     static final long USE_NEW_WIU_LOGIC_FOR_START = 311208629L;
 
     /**
      * Compat ID to enable the new FGS start logic, for capability calculation.
      */
     @ChangeId
-    // Always enabled
-    @Disabled
+    @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE)
     static final long USE_NEW_WIU_LOGIC_FOR_CAPABILITIES = 313677553L;
 
     /**
@@ -111,8 +110,7 @@
      * the background.
      */
     @ChangeId
-    // @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE)
-    @Disabled
+    @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE)
     static final long USE_NEW_BFSL_LOGIC = 311208749L;
 
     final ActivityManagerService ams;
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 9db5d0a..7aafda5 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -153,6 +153,7 @@
         "machine_learning",
         "mainline_modularization",
         "mainline_sdk",
+        "make_pixel_haptics",
         "media_audio",
         "media_drm",
         "media_reliability",
@@ -162,6 +163,7 @@
         "pdf_viewer",
         "pixel_audio_android",
         "pixel_bluetooth",
+        "pixel_connectivity_gps",
         "pixel_system_sw_video",
         "pixel_watch",
         "platform_security",
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 654aebd..31d9cc9 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -35,3 +35,10 @@
      description: "Enable the new FGS restriction logic"
      bug: "276963716"
 }
+
+flag {
+    name: "service_binding_oom_adj_policy"
+    namespace: "backstage_power"
+    description: "Optimize the service bindings by different policies like skipping oom adjuster"
+    bug: "318717054"
+}
diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
index 9f31f37..5f12ce1 100644
--- a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
+++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
@@ -73,7 +73,8 @@
     private static final Set<Integer> DEFAULT_EVENT_SET = Sets.newHashSet(
             AmbientContextEvent.EVENT_COUGH,
             AmbientContextEvent.EVENT_SNORE,
-            AmbientContextEvent.EVENT_BACK_DOUBLE_TAP);
+            AmbientContextEvent.EVENT_BACK_DOUBLE_TAP,
+            AmbientContextEvent.EVENT_HEART_RATE);
 
     /** Default value in absence of {@link DeviceConfig} override. */
     private static final boolean DEFAULT_SERVICE_ENABLED = true;
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 8b7e56e..f6df60f 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -28,6 +28,7 @@
 import static com.android.internal.R.styleable.GameModeConfig_supportsBatteryGameMode;
 import static com.android.internal.R.styleable.GameModeConfig_supportsPerformanceGameMode;
 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+import static com.android.server.wm.CompatScaleProvider.COMPAT_SCALE_MODE_GAME;
 
 import android.Manifest;
 import android.annotation.EnforcePermission;
@@ -56,6 +57,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
+import android.content.res.CompatibilityInfo.CompatScale;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
@@ -76,6 +78,7 @@
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.Properties;
@@ -97,6 +100,8 @@
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
 import com.android.server.SystemService.TargetUser;
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.CompatScaleProvider;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -927,12 +932,24 @@
         }
     }
 
-    private final class LocalService extends GameManagerInternal {
+    private final class LocalService extends GameManagerInternal implements CompatScaleProvider {
         @Override
         public float getResolutionScalingFactor(String packageName, int userId) {
             final int gameMode = getGameModeFromSettingsUnchecked(packageName, userId);
             return getResolutionScalingFactorInternal(packageName, gameMode, userId);
         }
+
+        @Nullable
+        @Override
+        public CompatScale getCompatScale(@NonNull String packageName, int uid) {
+            UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
+            int userId = userHandle.getIdentifier();
+            float scalingFactor = getResolutionScalingFactor(packageName, userId);
+            if (scalingFactor > 0) {
+                return new CompatScale(1f / scalingFactor);
+            }
+            return null;
+        }
     }
 
     /**
@@ -2080,7 +2097,13 @@
     }
 
     private void publishLocalService() {
-        LocalServices.addService(GameManagerInternal.class, new LocalService());
+        LocalService localService = new LocalService();
+
+        ActivityTaskManagerInternal atmi =
+                LocalServices.getService(ActivityTaskManagerInternal.class);
+        atmi.registerCompatScaleProvider(COMPAT_SCALE_MODE_GAME, localService);
+
+        LocalServices.addService(GameManagerInternal.class, localService);
     }
 
     private void registerStatsCallbacks() {
diff --git a/services/core/java/com/android/server/app/GameManagerSettings.java b/services/core/java/com/android/server/app/GameManagerSettings.java
index 5189017..b084cf3 100644
--- a/services/core/java/com/android/server/app/GameManagerSettings.java
+++ b/services/core/java/com/android/server/app/GameManagerSettings.java
@@ -251,6 +251,7 @@
                             + type);
                 }
             }
+            str.close();
         } catch (XmlPullParserException | java.io.IOException e) {
             Slog.wtf(TAG, "Error reading game manager settings", e);
             return false;
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index 684d6a0..cdd147a 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -177,6 +177,11 @@
         }
 
         @Override
+        public void onProcessStarted(int pid, int processUid, int packageUid, String packageName,
+                String processName) {
+        }
+
+        @Override
         public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
         }
     };
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index df8d9e1..2ed217a 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -65,6 +65,9 @@
 import static android.app.AppOpsManager.opToName;
 import static android.app.AppOpsManager.opToPublicName;
 import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
+import static android.content.Intent.ACTION_PACKAGE_ADDED;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static android.content.Intent.EXTRA_REPLACING;
 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
 import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
 
@@ -973,7 +976,29 @@
             String pkgName = intent.getData().getEncodedSchemeSpecificPart();
             int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
 
-            if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
+            if (action.equals(ACTION_PACKAGE_ADDED)
+                    && !intent.getBooleanExtra(EXTRA_REPLACING, false)) {
+                PackageInfo pi = getPackageManagerInternal().getPackageInfo(pkgName,
+                        PackageManager.GET_PERMISSIONS, Process.myUid(),
+                        UserHandle.getUserId(uid));
+                boolean isSamplingTarget = isSamplingTarget(pi);
+                synchronized (AppOpsService.this) {
+                    if (isSamplingTarget) {
+                        mRarelyUsedPackages.add(pkgName);
+                    }
+                    UidState uidState = getUidStateLocked(uid, true);
+                    if (!uidState.pkgOps.containsKey(pkgName)) {
+                        uidState.pkgOps.put(pkgName,
+                                new Ops(pkgName, uidState));
+                    }
+
+                    createSandboxUidStateIfNotExistsForAppLocked(uid);
+                }
+            } else if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) {
+                synchronized (AppOpsService.this) {
+                    packageRemovedLocked(uid, pkgName);
+                }
+            } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
                 AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName);
                 if (pkg == null) {
                     return;
@@ -1052,7 +1077,9 @@
         mHistoricalRegistry.systemReady(mContext.getContentResolver());
 
         IntentFilter packageUpdateFilter = new IntentFilter();
+        packageUpdateFilter.addAction(ACTION_PACKAGE_ADDED);
         packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        packageUpdateFilter.addAction(ACTION_PACKAGE_REMOVED);
         packageUpdateFilter.addDataScheme("package");
 
         mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL,
@@ -1079,7 +1106,7 @@
 
                     String action;
                     if (!ArrayUtils.contains(pkgsInUid, pkg)) {
-                        action = Intent.ACTION_PACKAGE_REMOVED;
+                        action = ACTION_PACKAGE_REMOVED;
                     } else {
                         action = Intent.ACTION_PACKAGE_REPLACED;
                     }
@@ -1160,44 +1187,6 @@
 
                     // onUserRemoved handled by #removeUser
                 });
-
-        getPackageManagerInternal().getPackageList(
-                new PackageManagerInternal.PackageListObserver() {
-                    @Override
-                    public void onPackageAdded(String packageName, int appId) {
-                        PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName,
-                                PackageManager.GET_PERMISSIONS, Process.myUid(),
-                                mContext.getUserId());
-                        boolean isSamplingTarget = isSamplingTarget(pi);
-                        int[] userIds = getUserManagerInternal().getUserIds();
-                        synchronized (AppOpsService.this) {
-                            if (isSamplingTarget) {
-                                mRarelyUsedPackages.add(packageName);
-                            }
-                            for (int i = 0; i < userIds.length; i++) {
-                                int uid = UserHandle.getUid(userIds[i], appId);
-                                UidState uidState = getUidStateLocked(uid, true);
-                                if (!uidState.pkgOps.containsKey(packageName)) {
-                                    uidState.pkgOps.put(packageName,
-                                            new Ops(packageName, uidState));
-                                }
-
-                                createSandboxUidStateIfNotExistsForAppLocked(uid);
-                            }
-                        }
-                    }
-
-                    @Override
-                    public void onPackageRemoved(String packageName, int appId) {
-                        int[] userIds = getUserManagerInternal().getUserIds();
-                        synchronized (AppOpsService.this) {
-                            for (int i = 0; i < userIds.length; i++) {
-                                int uid = UserHandle.getUid(userIds[i], appId);
-                                packageRemovedLocked(uid, packageName);
-                            }
-                        }
-                    }
-                });
     }
 
     /**
@@ -2893,6 +2882,10 @@
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
+        if (proxyAttributionTag != null
+                && !isAttributionTagDefined(packageName, proxyPackageName, proxyAttributionTag)) {
+            proxyAttributionTag = null;
+        }
 
         synchronized (this) {
             final Ops ops = getOpsLocked(uid, packageName, attributionTag,
@@ -3487,6 +3480,10 @@
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
+        if (proxyAttributionTag != null
+                && !isAttributionTagDefined(packageName, proxyPackageName, proxyAttributionTag)) {
+            proxyAttributionTag = null;
+        }
 
         boolean isRestricted = false;
         int startType = START_TYPE_FAILED;
@@ -4340,6 +4337,36 @@
         return false;
     }
 
+    /**
+     * Checks to see if the attribution tag is defined in either package or proxyPackage.
+     * This method is intended for ProxyAttributionTag validation and returns false
+     * if it does not exist in either one of them.
+     *
+     * @param packageName Name of the package
+     * @param proxyPackageName Name of the proxy package
+     * @param attributionTag attribution tag to be checked
+     *
+     * @return boolean specifying if attribution tag is valid or not
+     */
+    private boolean isAttributionTagDefined(@Nullable String packageName,
+                                          @Nullable String proxyPackageName,
+                                          @Nullable String attributionTag) {
+        if (packageName == null) {
+            return false;
+        } else if (attributionTag == null) {
+            return true;
+        }
+        PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+        if (proxyPackageName != null) {
+            AndroidPackage proxyPkg = pmInt.getPackage(proxyPackageName);
+            if (proxyPkg != null && isAttributionInPackage(proxyPkg, attributionTag)) {
+                return true;
+            }
+        }
+        AndroidPackage pkg = pmInt.getPackage(packageName);
+        return isAttributionInPackage(pkg, attributionTag);
+    }
+
     private void logVerifyAndGetBypassFailure(int uid, @NonNull SecurityException e,
             @NonNull String methodName) {
         if (Process.isIsolated(uid)) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index dada72e..cd295b5 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1047,11 +1047,9 @@
     private void initAudioHalBluetoothState() {
         synchronized (mBluetoothAudioStateLock) {
             mBluetoothScoOnApplied = false;
-            AudioSystem.setParameters("BT_SCO=off");
             mBluetoothA2dpSuspendedApplied = false;
-            AudioSystem.setParameters("A2dpSuspended=false");
             mBluetoothLeSuspendedApplied = false;
-            AudioSystem.setParameters("LeAudioSuspended=false");
+            reapplyAudioHalBluetoothState();
         }
     }
 
@@ -1114,6 +1112,34 @@
         }
     }
 
+    @GuardedBy("mBluetoothAudioStateLock")
+    private void reapplyAudioHalBluetoothState() {
+        if (AudioService.DEBUG_COMM_RTE) {
+            Log.v(TAG, "reapplyAudioHalBluetoothState() mBluetoothScoOnApplied: "
+                    + mBluetoothScoOnApplied + ", mBluetoothA2dpSuspendedApplied: "
+                    + mBluetoothA2dpSuspendedApplied + ", mBluetoothLeSuspendedApplied: "
+                    + mBluetoothLeSuspendedApplied);
+        }
+        // Note: the order of parameters is important.
+        if (mBluetoothScoOnApplied) {
+            AudioSystem.setParameters("A2dpSuspended=true");
+            AudioSystem.setParameters("LeAudioSuspended=true");
+            AudioSystem.setParameters("BT_SCO=on");
+        } else {
+            AudioSystem.setParameters("BT_SCO=off");
+            if (mBluetoothA2dpSuspendedApplied) {
+                AudioSystem.setParameters("A2dpSuspended=true");
+            } else {
+                AudioSystem.setParameters("A2dpSuspended=false");
+            }
+            if (mBluetoothLeSuspendedApplied) {
+                AudioSystem.setParameters("LeAudioSuspended=true");
+            } else {
+                AudioSystem.setParameters("LeAudioSuspended=false");
+            }
+        }
+    }
+
     /*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
         if (AudioService.DEBUG_COMM_RTE) {
             Log.v(TAG, "setBluetoothScoOn: " + on + " " + eventSource);
@@ -1775,6 +1801,9 @@
                             initRoutingStrategyIds();
                             updateActiveCommunicationDevice();
                             mDeviceInventory.onRestoreDevices();
+                            synchronized (mBluetoothAudioStateLock) {
+                                reapplyAudioHalBluetoothState();
+                            }
                             mBtHelper.onAudioServerDiedRestoreA2dp();
                             updateCommunicationRoute("MSG_RESTORE_DEVICES");
                         }
@@ -1812,21 +1841,21 @@
                                         "msg: MSG_L_SET_BT_ACTIVE_DEVICE "
                                             + "received with null profile proxy: "
                                             + btInfo)).printLog(TAG));
-                                return;
-                            }
-                            @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
-                                    mBtHelper.getCodecWithFallback(btInfo.mDevice,
-                                            btInfo.mProfile, btInfo.mIsLeOutput,
-                                            "MSG_L_SET_BT_ACTIVE_DEVICE");
-                            mDeviceInventory.onSetBtActiveDevice(btInfo, codec,
-                                    (btInfo.mProfile
-                                            != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput)
-                                            ? mAudioService.getBluetoothContextualVolumeStream()
-                                            : AudioSystem.STREAM_DEFAULT);
-                            if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
-                                    || btInfo.mProfile == BluetoothProfile.HEARING_AID) {
-                                onUpdateCommunicationRouteClient(isBluetoothScoRequested(),
-                                        "setBluetoothActiveDevice");
+                            } else {
+                                @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
+                                        mBtHelper.getCodecWithFallback(btInfo.mDevice,
+                                                btInfo.mProfile, btInfo.mIsLeOutput,
+                                                "MSG_L_SET_BT_ACTIVE_DEVICE");
+                                mDeviceInventory.onSetBtActiveDevice(btInfo, codec,
+                                        (btInfo.mProfile
+                                                != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput)
+                                                ? mAudioService.getBluetoothContextualVolumeStream()
+                                                : AudioSystem.STREAM_DEFAULT);
+                                if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
+                                        || btInfo.mProfile == BluetoothProfile.HEARING_AID) {
+                                    onUpdateCommunicationRouteClient(isBluetoothScoRequested(),
+                                            "setBluetoothActiveDevice");
+                                }
                             }
                         }
                     }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index e05824a..690c37a 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -764,7 +764,7 @@
     /** only public for mocking/spying, do not call outside of AudioService */
     // @GuardedBy("mDeviceBroker.mSetModeLock")
     @VisibleForTesting
-    @GuardedBy("mDeviceBroker.mDeviceStateLock")
+    //@GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
     public void onSetBtActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo btInfo,
                                     @AudioSystem.AudioFormatNativeEnumForBtCodec int codec,
                                     int streamType) {
@@ -914,28 +914,27 @@
                         di.mDeviceCodecFormat = codec;
                         mConnectedDevices.replace(key, di);
                         codecChange = true;
-                    }
-                    final int res = mAudioSystem.handleDeviceConfigChange(
-                            btInfo.mAudioSystemDevice, address, BtHelper.getName(btDevice), codec);
+                        final int res = mAudioSystem.handleDeviceConfigChange(
+                                btInfo.mAudioSystemDevice, address,
+                                BtHelper.getName(btDevice), codec);
+                        if (res != AudioSystem.AUDIO_STATUS_OK) {
+                            AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+                                    "APM handleDeviceConfigChange failed for A2DP device addr="
+                                            + address + " codec="
+                                            + AudioSystem.audioFormatToString(codec))
+                                    .printLog(TAG));
 
-                    if (res != AudioSystem.AUDIO_STATUS_OK) {
-                        AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
-                                "APM handleDeviceConfigChange failed for A2DP device addr="
-                                        + address + " codec="
-                                        + AudioSystem.audioFormatToString(codec))
-                                .printLog(TAG));
-
-                        // force A2DP device disconnection in case of error so that AudioService
-                        // state is consistent with audio policy manager state
-                        setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btInfo,
-                                BluetoothProfile.STATE_DISCONNECTED));
-                    } else {
-                        AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
-                                "APM handleDeviceConfigChange success for A2DP device addr="
-                                        + address
-                                        + " codec=" + AudioSystem.audioFormatToString(codec))
-                                .printLog(TAG));
-
+                            // force A2DP device disconnection in case of error so that AudioService
+                            // state is consistent with audio policy manager state
+                            setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btInfo,
+                                    BluetoothProfile.STATE_DISCONNECTED));
+                        } else {
+                            AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+                                    "APM handleDeviceConfigChange success for A2DP device addr="
+                                            + address
+                                            + " codec=" + AudioSystem.audioFormatToString(codec))
+                                    .printLog(TAG));
+                        }
                     }
                 }
                 if (!codecChange) {
@@ -1992,7 +1991,7 @@
             // TODO: return;
         } else {
             AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
-                    "A2DP source device addr=" + Utils.anonymizeBluetoothAddress(address)
+                    "A2DP sink device addr=" + Utils.anonymizeBluetoothAddress(address)
                             + " now available").printLog(TAG));
         }
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 91d533c..9f7c07e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -18,9 +18,6 @@
 
 import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED;
 import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT;
-import static android.media.audio.Flags.autoPublicVolumeApiHardening;
-import static android.media.audio.Flags.automaticBtDeviceType;
-import static android.media.audio.Flags.focusFreezeTestApi;
 import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET;
 import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER;
 import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
@@ -33,6 +30,9 @@
 import static android.media.AudioManager.RINGER_MODE_SILENT;
 import static android.media.AudioManager.RINGER_MODE_VIBRATE;
 import static android.media.AudioManager.STREAM_SYSTEM;
+import static android.media.audio.Flags.autoPublicVolumeApiHardening;
+import static android.media.audio.Flags.automaticBtDeviceType;
+import static android.media.audio.Flags.focusFreezeTestApi;
 import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.INVALID_UID;
@@ -116,7 +116,6 @@
 import android.media.AudioRecordingConfiguration;
 import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
-import android.media.AudioTrack;
 import android.media.BluetoothProfileConnectionInfo;
 import android.media.FadeManagerConfiguration;
 import android.media.IAudioDeviceVolumeDispatcher;
@@ -144,7 +143,7 @@
 import android.media.IStrategyPreferredDevicesDispatcher;
 import android.media.IStreamAliasingDispatcher;
 import android.media.IVolumeController;
-import android.media.LoudnessCodecConfigurator;
+import android.media.LoudnessCodecController;
 import android.media.LoudnessCodecInfo;
 import android.media.MediaCodec;
 import android.media.MediaMetrics;
@@ -8835,6 +8834,8 @@
                 synchronized (VolumeStreamState.class) {
                     oldIndex = getIndex(device);
                     index = getValidIndex(index, hasModifyAudioSettings);
+                    // for STREAM_SYSTEM_ENFORCED, do not sync aliased streams on the enforced index
+                    int aliasIndex = index;
                     if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) {
                         index = mIndexMax;
                     }
@@ -8853,7 +8854,8 @@
                         if (streamType != mStreamType &&
                                 mStreamVolumeAlias[streamType] == mStreamType &&
                                 (changed || !aliasStreamState.hasIndexForDevice(device))) {
-                            final int scaledIndex = rescaleIndex(index, mStreamType, streamType);
+                            final int scaledIndex =
+                                    rescaleIndex(aliasIndex, mStreamType, streamType);
                             aliasStreamState.setIndex(scaledIndex, device, caller,
                                     hasModifyAudioSettings);
                             if (isCurrentDevice) {
@@ -9375,6 +9377,14 @@
             if (mIsSingleVolume && (streamState.mStreamType != AudioSystem.STREAM_MUSIC)) {
                 return;
             }
+
+            // Persisting STREAM_SYSTEM_ENFORCED index is not needed as its alias (STREAM_RING)
+            // is persisted. This can also be problematic when the enforcement is active as it will
+            // override current SYSTEM_RING persisted value given they share the same settings name
+            // (due to aliasing).
+            if (streamState.mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) {
+                return;
+            }
             if (streamState.hasValidSettingsName()) {
                 mSettings.putSystemIntForUser(mContentResolver,
                         streamState.getSettingNameForDevice(device),
@@ -10726,34 +10736,35 @@
         mLoudnessCodecHelper.unregisterLoudnessCodecUpdatesDispatcher(dispatcher);
     }
 
-    /** @see LoudnessCodecConfigurator#setAudioTrack(AudioTrack) */
+    /** @see LoudnessCodecController#create(int) */
     @Override
-    public void startLoudnessCodecUpdates(int piid, List<LoudnessCodecInfo> codecInfoList) {
-        mLoudnessCodecHelper.startLoudnessCodecUpdates(piid, codecInfoList);
+    public void startLoudnessCodecUpdates(int sessionId) {
+        mLoudnessCodecHelper.startLoudnessCodecUpdates(sessionId);
     }
 
-    /** @see LoudnessCodecConfigurator#setAudioTrack(AudioTrack) */
+    /** @see LoudnessCodecController#release() */
     @Override
-    public void stopLoudnessCodecUpdates(int piid) {
-        mLoudnessCodecHelper.stopLoudnessCodecUpdates(piid);
+    public void stopLoudnessCodecUpdates(int sessionId) {
+        mLoudnessCodecHelper.stopLoudnessCodecUpdates(sessionId);
     }
 
-    /** @see LoudnessCodecConfigurator#addMediaCodec(MediaCodec) */
+    /** @see LoudnessCodecController#addMediaCodec(MediaCodec) */
     @Override
-    public void addLoudnessCodecInfo(int piid, int mediaCodecHash, LoudnessCodecInfo codecInfo) {
-        mLoudnessCodecHelper.addLoudnessCodecInfo(piid, mediaCodecHash, codecInfo);
+    public void addLoudnessCodecInfo(int sessionId, int mediaCodecHash,
+            LoudnessCodecInfo codecInfo) {
+        mLoudnessCodecHelper.addLoudnessCodecInfo(sessionId, mediaCodecHash, codecInfo);
     }
 
-    /** @see LoudnessCodecConfigurator#removeMediaCodec(MediaCodec) */
+    /** @see LoudnessCodecController#removeMediaCodec(MediaCodec) */
     @Override
-    public void removeLoudnessCodecInfo(int piid, LoudnessCodecInfo codecInfo) {
-        mLoudnessCodecHelper.removeLoudnessCodecInfo(piid, codecInfo);
+    public void removeLoudnessCodecInfo(int sessionId, LoudnessCodecInfo codecInfo) {
+        mLoudnessCodecHelper.removeLoudnessCodecInfo(sessionId, codecInfo);
     }
 
-    /** @see LoudnessCodecConfigurator#getLoudnessCodecParams(AudioTrack, MediaCodec) */
+    /** @see LoudnessCodecController#getLoudnessCodecParams(MediaCodec) */
     @Override
-    public PersistableBundle getLoudnessParams(int piid, LoudnessCodecInfo codecInfo) {
-        return mLoudnessCodecHelper.getLoudnessParams(piid, codecInfo);
+    public PersistableBundle getLoudnessParams(LoudnessCodecInfo codecInfo) {
+        return mLoudnessCodecHelper.getLoudnessParams(codecInfo);
     }
 
     //==========================================================================================
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index a818c30..f51043d 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -634,16 +634,17 @@
             return;
         }
         List<BluetoothDevice> activeDevices = adapter.getActiveDevices(profile);
-        if (activeDevices.isEmpty() || activeDevices.get(0) == null) {
-            return;
+        BluetoothProfileConnectionInfo bpci = new BluetoothProfileConnectionInfo(profile);
+        for (BluetoothDevice device : activeDevices) {
+            if (device == null) {
+                continue;
+            }
+            AudioDeviceBroker.BtDeviceChangedData data = new AudioDeviceBroker.BtDeviceChangedData(
+                    device, null, bpci, "mBluetoothProfileServiceListener");
+            AudioDeviceBroker.BtDeviceInfo info = mDeviceBroker.createBtDeviceInfo(
+                    data, device, BluetoothProfile.STATE_CONNECTED);
+            mDeviceBroker.postBluetoothActiveDevice(info, 0 /* delay */);
         }
-        AudioDeviceBroker.BtDeviceChangedData data = new AudioDeviceBroker.BtDeviceChangedData(
-                activeDevices.get(0), null, new BluetoothProfileConnectionInfo(profile),
-                "mBluetoothProfileServiceListener");
-        AudioDeviceBroker.BtDeviceInfo info =
-                mDeviceBroker.createBtDeviceInfo(data, activeDevices.get(0),
-                        BluetoothProfile.STATE_CONNECTED);
-        mDeviceBroker.postBluetoothActiveDevice(info, 0 /* delay */);
     }
 
     // @GuardedBy("mDeviceBroker.mSetModeLock")
@@ -678,8 +679,11 @@
         if (adapter != null) {
             List<BluetoothDevice> activeDevices =
                     adapter.getActiveDevices(BluetoothProfile.HEADSET);
-            if (activeDevices.size() > 0 && activeDevices.get(0) != null) {
-                onSetBtScoActiveDevice(activeDevices.get(0));
+            for (BluetoothDevice device : activeDevices) {
+                if (device == null) {
+                    continue;
+                }
+                onSetBtScoActiveDevice(device);
             }
         } else {
             Log.e(TAG, "onHeadsetProfileConnected: Null BluetoothAdapter");
diff --git a/services/core/java/com/android/server/audio/LoudnessCodecHelper.java b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java
index 9b0afc4..01f770b 100644
--- a/services/core/java/com/android/server/audio/LoudnessCodecHelper.java
+++ b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java
@@ -30,6 +30,8 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager.AudioDeviceCategory;
 import android.media.AudioPlaybackConfiguration;
@@ -44,7 +46,6 @@
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.util.Log;
-import android.util.SparseArray;
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
@@ -59,7 +60,9 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -75,9 +78,9 @@
     /**
      * Property containing a string to set for a custom built in speaker SPL range as defined by
      * CTA2075. The options that can be set are:
-     *   - "small": for max SPL with test signal < 75 dB,
-     *   - "medium": for max SPL with test signal between 70 and 90 dB,
-     *   - "large": for max SPL with test signal > 85 dB.
+     * - "small": for max SPL with test signal < 75 dB,
+     * - "medium": for max SPL with test signal between 70 and 90 dB,
+     * - "large": for max SPL with test signal > 85 dB.
      */
     private static final String SYSTEM_PROPERTY_SPEAKER_SPL_RANGE_SIZE =
             "audio.loudness.builtin-speaker-spl-range-size";
@@ -99,11 +102,13 @@
             SPL_RANGE_LARGE
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface DeviceSplRange {}
+    public @interface DeviceSplRange {
+    }
 
     private static final class LoudnessRemoteCallbackList extends
             RemoteCallbackList<ILoudnessCodecUpdatesDispatcher> {
         private final LoudnessCodecHelper mLoudnessCodecHelper;
+
         LoudnessRemoteCallbackList(LoudnessCodecHelper loudnessCodecHelper) {
             mLoudnessCodecHelper = loudnessCodecHelper;
         }
@@ -133,9 +138,15 @@
 
     private final Object mLock = new Object();
 
-    /** Contains for each started piid the set corresponding to unique registered audio codecs. */
+    /** Contains for each started track id the known started piids. */
     @GuardedBy("mLock")
-    private final SparseArray<Set<LoudnessCodecInfo>> mStartedPiids = new SparseArray<>();
+    private final HashMap<LoudnessTrackId, Set<Integer>> mStartedConfigPiids =
+            new HashMap<>();
+
+    /** Contains for each LoudnessTrackId a set of started coudec infos. */
+    @GuardedBy("mLock")
+    private final HashMap<LoudnessTrackId, Set<LoudnessCodecInfo>> mStartedConfigInfo =
+            new HashMap<>();
 
     /** Contains the current device id assignment for each piid. */
     @GuardedBy("mLock")
@@ -169,10 +180,12 @@
                 mMetadataType = metadataType;
                 return this;
             }
+
             Builder setIsDownmixing(boolean isDownmixing) {
                 mIsDownmixing = isDownmixing;
                 return this;
             }
+
             Builder setDeviceSplRange(@DeviceSplRange int deviceSplRange) {
                 mDeviceSplRange = deviceSplRange;
                 return this;
@@ -185,8 +198,8 @@
         }
 
         private LoudnessCodecInputProperties(int metadataType,
-                                             boolean isDownmixing,
-                                             @DeviceSplRange int deviceSplRange) {
+                boolean isDownmixing,
+                @DeviceSplRange int deviceSplRange) {
             mMetadataType = metadataType;
             mIsDownmixing = isDownmixing;
             mDeviceSplRange = deviceSplRange;
@@ -273,6 +286,50 @@
         }
     }
 
+    /**
+     * Contains the properties necessary to identify the tracks that are receiving annotated
+     * loudness data.
+     **/
+    @VisibleForTesting
+    static final class LoudnessTrackId {
+        private final int mSessionId;
+
+        private final int mPid;
+
+        private LoudnessTrackId(int sessionId, int pid) {
+            mSessionId = sessionId;
+            mPid = pid;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            // type check and cast
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            final LoudnessTrackId lti = (LoudnessTrackId) obj;
+            return mSessionId == lti.mSessionId && mPid == lti.mPid;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mSessionId, mPid);
+        }
+
+        @Override
+        public String toString() {
+            return "Loudness track id:"
+                    + " session ID: " + mSessionId
+                    + " pid: " + mPid;
+        }
+    }
+
     @GuardedBy("mLock")
     private final HashMap<LoudnessCodecInputProperties, PersistableBundle> mCachedProperties =
             new HashMap<>();
@@ -290,121 +347,161 @@
         mLoudnessUpdateDispatchers.unregister(dispatcher);
     }
 
-    void startLoudnessCodecUpdates(int piid, List<LoudnessCodecInfo> codecInfoList) {
+    void startLoudnessCodecUpdates(int sessionId) {
+        int pid = Binder.getCallingPid();
         if (DEBUG) {
-            Log.d(TAG, "startLoudnessCodecUpdates: piid " + piid + " codecInfos " + codecInfoList);
+            Log.d(TAG,
+                    "startLoudnessCodecUpdates: sessionId " + sessionId + " pid " + pid);
         }
 
+        final LoudnessTrackId newConfig = new LoudnessTrackId(sessionId, pid);
+        HashSet<Integer> newPiids;
         synchronized (mLock) {
-            if (mStartedPiids.contains(piid)) {
-                Log.w(TAG, "Already started loudness updates for piid " + piid);
+            if (mStartedConfigInfo.containsKey(newConfig)) {
+                Log.w(TAG, "Already started loudness updates for config: " + newConfig);
                 return;
             }
-            Set<LoudnessCodecInfo> infoSet = new HashSet<>(codecInfoList);
-            mStartedPiids.put(piid, infoSet);
 
-            int pid = Binder.getCallingPid();
-            mPiidToPidCache.put(piid, pid);
-
-            sLogger.enqueue(LoudnessEvent.getStartPiid(piid, pid));
+            mStartedConfigInfo.put(newConfig, new HashSet<>());
+            newPiids = new HashSet<>();
+            mStartedConfigPiids.put(newConfig, newPiids);
         }
 
         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
             mAudioService.getActivePlaybackConfigurations().stream().filter(
-                    conf -> conf.getPlayerInterfaceId() == piid).findFirst().ifPresent(
-                    this::updateCodecParametersForConfiguration);
-        }
-    }
-
-    void stopLoudnessCodecUpdates(int piid) {
-        if (DEBUG) {
-            Log.d(TAG, "stopLoudnessCodecUpdates: piid " + piid);
-        }
-
-        synchronized (mLock) {
-            if (!mStartedPiids.contains(piid)) {
-                Log.w(TAG, "Loudness updates are already stopped for piid " + piid);
-                return;
-            }
-            mStartedPiids.remove(piid);
-
-            sLogger.enqueue(LoudnessEvent.getStopPiid(piid, mPiidToPidCache.get(piid, -1)));
-            mPiidToDeviceIdCache.delete(piid);
-            mPiidToPidCache.delete(piid);
-        }
-    }
-
-    void addLoudnessCodecInfo(int piid, int mediaCodecHash, LoudnessCodecInfo info) {
-        if (DEBUG) {
-            Log.d(TAG, "addLoudnessCodecInfo: piid " + piid + " mcHash " + mediaCodecHash + " info "
-                    + info);
-        }
-
-        Set<LoudnessCodecInfo> infoSet;
-        synchronized (mLock) {
-            if (!mStartedPiids.contains(piid)) {
-                Log.w(TAG, "Cannot add new loudness info for stopped piid " + piid);
-                return;
-            }
-
-            infoSet = mStartedPiids.get(piid);
-            infoSet.add(info);
-        }
-
-        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
-            mAudioService.getActivePlaybackConfigurations().stream().filter(
-                    conf -> conf.getPlayerInterfaceId() == piid).findFirst().ifPresent(
-                            apc -> {
-                                final AudioDeviceInfo deviceInfo = apc.getAudioDeviceInfo();
-                                if (deviceInfo != null) {
-                                    PersistableBundle updateBundle = new PersistableBundle();
-                                    synchronized (mLock) {
-                                        updateBundle.putPersistableBundle(
-                                                Integer.toString(mediaCodecHash),
-                                                getCodecBundle_l(deviceInfo, info));
-                                    }
-                                    if (!updateBundle.isDefinitelyEmpty()) {
-                                        dispatchNewLoudnessParameters(piid, updateBundle);
-                                    }
+                    conf -> conf.getSessionId() == sessionId
+                            && conf.getClientPid() == pid).forEach(apc -> {
+                                int piid = apc.getPlayerInterfaceId();
+                                synchronized (mLock) {
+                                    newPiids.add(piid);
+                                    mPiidToPidCache.put(piid, pid);
+                                    sLogger.enqueue(LoudnessEvent.getStartPiid(piid, pid));
                                 }
                             });
         }
     }
 
-    void removeLoudnessCodecInfo(int piid, LoudnessCodecInfo codecInfo) {
+    void stopLoudnessCodecUpdates(int sessionId) {
+        int pid = Binder.getCallingPid();
         if (DEBUG) {
-            Log.d(TAG, "removeLoudnessCodecInfo: piid " + piid + " info " + codecInfo);
+            Log.d(TAG,
+                    "stopLoudnessCodecUpdates: sessionId " + sessionId + " pid " + pid);
         }
+
+        final LoudnessTrackId config = new LoudnessTrackId(sessionId, pid);
         synchronized (mLock) {
-            if (!mStartedPiids.contains(piid)) {
-                Log.w(TAG, "Cannot remove loudness info for stopped piid " + piid);
+            if (!mStartedConfigInfo.containsKey(config)) {
+                Log.w(TAG, "Loudness updates are already stopped config: " + config);
                 return;
             }
-            final Set<LoudnessCodecInfo> infoSet = mStartedPiids.get(piid);
-            infoSet.remove(codecInfo);
+
+            final Set<Integer> startedPiidSet = mStartedConfigPiids.get(config);
+            if (startedPiidSet == null) {
+                Log.e(TAG, "Loudness updates are already stopped config: " + config);
+                return;
+            }
+            for (Integer piid : startedPiidSet) {
+                sLogger.enqueue(LoudnessEvent.getStopPiid(piid, mPiidToPidCache.get(piid, -1)));
+                mPiidToDeviceIdCache.delete(piid);
+                mPiidToPidCache.delete(piid);
+            }
+            mStartedConfigPiids.remove(config);
+            mStartedConfigInfo.remove(config);
         }
     }
 
-    PersistableBundle getLoudnessParams(int piid, LoudnessCodecInfo codecInfo) {
+    void addLoudnessCodecInfo(int sessionId, int mediaCodecHash,
+            LoudnessCodecInfo info) {
+        int pid = Binder.getCallingPid();
         if (DEBUG) {
-            Log.d(TAG, "getLoudnessParams: piid " + piid + " codecInfo " + codecInfo);
+            Log.d(TAG, "addLoudnessCodecInfo: sessionId " + sessionId
+                    + " mcHash " + mediaCodecHash + " info " + info + " pid " + pid);
         }
-        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
-            final List<AudioPlaybackConfiguration> configs =
-                    mAudioService.getActivePlaybackConfigurations();
 
-            for (final AudioPlaybackConfiguration apc : configs) {
-                if (apc.getPlayerInterfaceId() == piid) {
-                    final AudioDeviceInfo info = apc.getAudioDeviceInfo();
-                    if (info == null) {
-                        Log.i(TAG, "Player with piid " + piid + " is not assigned any device");
-                        break;
-                    }
+        final LoudnessTrackId config = new LoudnessTrackId(sessionId, pid);
+        Set<LoudnessCodecInfo> infoSet;
+        Set<Integer> piids;
+        synchronized (mLock) {
+            if (!mStartedConfigInfo.containsKey(config) || !mStartedConfigPiids.containsKey(
+                    config)) {
+                Log.w(TAG, "Cannot add new loudness info for stopped config " + config);
+                return;
+            }
+
+            piids = mStartedConfigPiids.get(config);
+            infoSet = mStartedConfigInfo.get(config);
+            infoSet.add(info);
+        }
+
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            final PersistableBundle updateBundle = new PersistableBundle();
+            Optional<AudioPlaybackConfiguration> apc =
+                    mAudioService.getActivePlaybackConfigurations().stream().filter(
+                            conf -> conf.getSessionId() == sessionId
+                                    && conf.getClientPid() == pid).findFirst();
+            if (apc.isEmpty()) {
+                if (DEBUG) {
+                    Log.d(TAG,
+                            "No APCs found when adding loudness codec info. Using AudioAttributes"
+                                    + " routing for initial update");
+                }
+                updateBundle.putPersistableBundle(Integer.toString(mediaCodecHash),
+                        getLoudnessParams(info));
+            } else {
+                final AudioDeviceInfo deviceInfo = apc.get().getAudioDeviceInfo();
+                if (deviceInfo != null) {
                     synchronized (mLock) {
-                        return getCodecBundle_l(info, codecInfo);
+                        // found a piid that matches the configuration
+                        piids.add(apc.get().getPlayerInterfaceId());
+
+                        updateBundle.putPersistableBundle(
+                                Integer.toString(mediaCodecHash),
+                                getCodecBundle_l(deviceInfo.getInternalType(),
+                                        deviceInfo.getAddress(), info));
                     }
                 }
             }
+            if (!updateBundle.isDefinitelyEmpty()) {
+                dispatchNewLoudnessParameters(sessionId, updateBundle);
+            }
+        }
+    }
+
+    void removeLoudnessCodecInfo(int sessionId, LoudnessCodecInfo codecInfo) {
+        if (DEBUG) {
+            Log.d(TAG, "removeLoudnessCodecInfo: session ID" + sessionId + " info " + codecInfo);
+        }
+
+        int pid = Binder.getCallingPid();
+        final LoudnessTrackId config = new LoudnessTrackId(sessionId, pid);
+        synchronized (mLock) {
+            if (!mStartedConfigInfo.containsKey(config) || !mStartedConfigPiids.containsKey(
+                    config)) {
+                Log.w(TAG, "Cannot remove loudness info for stopped config " + config);
+                return;
+            }
+            final Set<LoudnessCodecInfo> codecInfos = mStartedConfigInfo.get(config);
+            if (!codecInfos.remove(codecInfo)) {
+                Log.w(TAG, "Could not find to remove codecInfo " + codecInfo);
+            }
+        }
+    }
+
+    PersistableBundle getLoudnessParams(LoudnessCodecInfo codecInfo) {
+        if (DEBUG) {
+            Log.d(TAG, "getLoudnessParams: codecInfo " + codecInfo);
+        }
+        final ArrayList<AudioDeviceAttributes> devicesForAttributes =
+                mAudioService.getDevicesForAttributesInt(new AudioAttributes.Builder()
+                        .setUsage(AudioAttributes.USAGE_MEDIA)
+                        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+                        .build(), /*forVolume=*/false);
+        if (!devicesForAttributes.isEmpty()) {
+            final AudioDeviceAttributes audioDeviceAttribute = devicesForAttributes.get(0);
+            synchronized (mLock) {
+                return getCodecBundle_l(audioDeviceAttribute.getInternalType(),
+                        audioDeviceAttribute.getAddress(), codecInfo);
+            }
         }
 
         // return empty Bundle
@@ -444,13 +541,21 @@
                     continue;
                 }
                 mPiidToDeviceIdCache.put(piid, deviceInfo.getId());
-                if (mStartedPiids.contains(piid)) {
+                final LoudnessTrackId config = new LoudnessTrackId(apc.getSessionId(),
+                        apc.getClientPid());
+                if (mStartedConfigInfo.containsKey(config) && mStartedConfigPiids.containsKey(
+                        config)) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Updating config: " + config + " with APC " + apc);
+                    }
                     updateApcList.add(apc);
+                    // update the started piid set
+                    mStartedConfigPiids.get(config).add(piid);
                 }
             }
         }
 
-        updateApcList.forEach(apc -> updateCodecParametersForConfiguration(apc));
+        updateApcList.forEach(this::updateCodecParametersForConfiguration);
     }
 
     /** Updates and dispatches the new loudness parameters for all its registered codecs. */
@@ -458,13 +563,18 @@
         // Registered clients
         pw.println("\nRegistered clients:\n");
         synchronized (mLock) {
-            for (int i = 0; i < mStartedPiids.size(); ++i) {
-                int piid = mStartedPiids.keyAt(i);
-                int pid = mPiidToPidCache.get(piid, -1);
-                final Set<LoudnessCodecInfo> codecInfos = mStartedPiids.get(piid);
-                pw.println(String.format("Player piid %d pid %d active codec types %s\n", piid,
-                        pid, codecInfos.stream().map(Object::toString).collect(
-                                Collectors.joining(", "))));
+            for (Map.Entry<LoudnessTrackId, Set<Integer>> entry : mStartedConfigPiids.entrySet()) {
+                for (Integer piid : entry.getValue()) {
+                    int pid = mPiidToPidCache.get(piid, -1);
+                    final Set<LoudnessCodecInfo> codecInfos = mStartedConfigInfo.get(
+                            entry.getKey());
+                    if (codecInfos != null) {
+                        pw.println(
+                                String.format("Player piid %d pid %d active codec types %s\n", piid,
+                                        pid, codecInfos.stream().map(Object::toString).collect(
+                                                Collectors.joining(", "))));
+                    }
+                }
             }
             pw.println();
         }
@@ -481,10 +591,12 @@
                     if (DEBUG) {
                         Log.d(TAG, "Removing piid  " + piid);
                     }
-                    mStartedPiids.delete(piid);
                     mPiidToDeviceIdCache.delete(piid);
                 }
             }
+
+            mStartedConfigPiids.entrySet().removeIf(entry -> entry.getKey().mPid == pid);
+            mStartedConfigInfo.entrySet().removeIf(entry -> entry.getKey().mPid == pid);
         }
     }
 
@@ -499,46 +611,55 @@
         }
 
         final PersistableBundle allBundles = new PersistableBundle();
-        final int piid = apc.getPlayerInterfaceId();
 
         synchronized (mLock) {
-            final Set<LoudnessCodecInfo> codecInfos = mStartedPiids.get(piid);
-
+            final LoudnessTrackId config = new LoudnessTrackId(apc.getSessionId(),
+                    apc.getClientPid());
+            final Set<LoudnessCodecInfo> codecInfos = mStartedConfigInfo.get(config);
             final AudioDeviceInfo deviceInfo = apc.getAudioDeviceInfo();
+
             if (codecInfos != null && deviceInfo != null) {
                 for (LoudnessCodecInfo info : codecInfos) {
-                    allBundles.putPersistableBundle(Integer.toString(info.hashCode()),
-                            getCodecBundle_l(deviceInfo, info));
+                    if (info != null) {
+                        allBundles.putPersistableBundle(Integer.toString(info.hashCode()),
+                                getCodecBundle_l(deviceInfo.getInternalType(),
+                                        deviceInfo.getAddress(), info));
+                    }
                 }
             }
         }
 
         if (!allBundles.isDefinitelyEmpty()) {
-            dispatchNewLoudnessParameters(piid, allBundles);
+            dispatchNewLoudnessParameters(apc.getSessionId(), allBundles);
         }
     }
 
-    private void dispatchNewLoudnessParameters(int piid, PersistableBundle bundle) {
+    private void dispatchNewLoudnessParameters(int sessionId,
+            PersistableBundle bundle) {
         if (DEBUG) {
-            Log.d(TAG, "dispatchNewLoudnessParameters: piid " + piid + " bundle: " + bundle);
+            Log.d(TAG,
+                    "dispatchNewLoudnessParameters: sessionId " + sessionId + " bundle: " + bundle);
         }
         final int nbDispatchers = mLoudnessUpdateDispatchers.beginBroadcast();
         for (int i = 0; i < nbDispatchers; ++i) {
             try {
                 mLoudnessUpdateDispatchers.getBroadcastItem(i)
-                        .dispatchLoudnessCodecParameterChange(piid, bundle);
+                        .dispatchLoudnessCodecParameterChange(sessionId, bundle);
             } catch (RemoteException e) {
-                Log.e(TAG, "Error dispatching for piid: " + piid + " bundle: " + bundle , e);
+                Log.e(TAG, "Error dispatching for sessionId " + sessionId + " bundle: " + bundle,
+                        e);
             }
         }
         mLoudnessUpdateDispatchers.finishBroadcast();
     }
 
     @GuardedBy("mLock")
-    private PersistableBundle getCodecBundle_l(AudioDeviceInfo deviceInfo,
-                                             LoudnessCodecInfo codecInfo) {
+    private PersistableBundle getCodecBundle_l(int internalDeviceType,
+            String address,
+            LoudnessCodecInfo codecInfo) {
         LoudnessCodecInputProperties.Builder builder = new LoudnessCodecInputProperties.Builder();
-        LoudnessCodecInputProperties prop = builder.setDeviceSplRange(getDeviceSplRange(deviceInfo))
+        LoudnessCodecInputProperties prop = builder.setDeviceSplRange(
+                        getDeviceSplRange(internalDeviceType, address))
                 .setIsDownmixing(codecInfo.isDownmixing)
                 .setMetadataType(codecInfo.metadataType)
                 .build();
@@ -552,14 +673,15 @@
     }
 
     @DeviceSplRange
-    private int getDeviceSplRange(AudioDeviceInfo deviceInfo) {
-        final int internalDeviceType = deviceInfo.getInternalType();
-        final @AudioDeviceCategory int deviceCategory;
-        if (automaticBtDeviceType()) {
-            deviceCategory = mAudioService.getBluetoothAudioDeviceCategory(deviceInfo.getAddress());
-        } else {
-            deviceCategory = mAudioService.getBluetoothAudioDeviceCategory_legacy(
-                    deviceInfo.getAddress(), AudioSystem.isBluetoothLeDevice(internalDeviceType));
+    private int getDeviceSplRange(int internalDeviceType, String address) {
+        @AudioDeviceCategory int deviceCategory;
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            if (automaticBtDeviceType()) {
+                deviceCategory = mAudioService.getBluetoothAudioDeviceCategory(address);
+            } else {
+                deviceCategory = mAudioService.getBluetoothAudioDeviceCategory_legacy(
+                        address, AudioSystem.isBluetoothLeDevice(internalDeviceType));
+            }
         }
         if (internalDeviceType == AudioSystem.DEVICE_OUT_SPEAKER) {
             final String splRange = SystemProperties.get(
@@ -595,20 +717,28 @@
 
     private static String splRangeToString(@DeviceSplRange int splRange) {
         switch (splRange) {
-            case SPL_RANGE_LARGE: return "large";
-            case SPL_RANGE_MEDIUM: return "medium";
-            case SPL_RANGE_SMALL: return "small";
-            default: return "unknown";
+            case SPL_RANGE_LARGE:
+                return "large";
+            case SPL_RANGE_MEDIUM:
+                return "medium";
+            case SPL_RANGE_SMALL:
+                return "small";
+            default:
+                return "unknown";
         }
     }
 
     @DeviceSplRange
     private static int stringToSplRange(String splRange) {
         switch (splRange) {
-            case "large": return SPL_RANGE_LARGE;
-            case "medium": return SPL_RANGE_MEDIUM;
-            case "small": return SPL_RANGE_SMALL;
-            default: return SPL_RANGE_UNKNOWN;
+            case "large":
+                return SPL_RANGE_LARGE;
+            case "medium":
+                return SPL_RANGE_MEDIUM;
+            case "small":
+                return SPL_RANGE_SMALL;
+            default:
+                return SPL_RANGE_UNKNOWN;
         }
     }
 }
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index c2bc1e4..9610034ca 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -62,6 +62,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -147,6 +148,15 @@
 
     private static final int SAFE_MEDIA_VOLUME_UNINITIALIZED = -1;
 
+    // see {@link #recordToPersistedString(SoundDoseRecord)}
+    // this is computed conservatively to accommodate the legacy persisting of SoundDoseRecords in
+    // which we materialized more decimal values.
+    // TODO: adjust value after soaking in
+    private static final int MAX_RECORDS_STRING_LENGTH = 50;
+    private static final int MAX_SETTINGS_LENGTH = 32768;
+    private static final int MAX_NUMBER_OF_CACHED_RECORDS =
+            MAX_SETTINGS_LENGTH / MAX_RECORDS_STRING_LENGTH;
+
     private final EventLogger mLogger = new EventLogger(AudioService.LOG_NB_EVENTS_SOUND_DOSE,
             "CSD updates");
 
@@ -923,7 +933,7 @@
         Log.v(TAG, "Initializing sound dose");
 
         try {
-            if (mCachedAudioDeviceCategories.size() > 0) {
+            if (!mCachedAudioDeviceCategories.isEmpty()) {
                 soundDose.initCachedAudioDeviceCategories(mCachedAudioDeviceCategories.toArray(
                         new ISoundDose.AudioDeviceCategory[0]));
                 mCachedAudioDeviceCategories.clear();
@@ -957,6 +967,7 @@
                         mGlobalTimeOffsetInSecs);
                 if (records != null) {
                     mDoseRecords.addAll(records);
+                    sanitizeDoseRecords_l();
                 }
             }
         }
@@ -1176,17 +1187,35 @@
                                 && r.duration == record.duration)) {
                     Log.w(TAG, "Could not find cached record to remove: " + record);
                 }
-            } else {
+            } else if (record.value > 0) {
                 mDoseRecords.add(record);
             }
         }
 
+        sanitizeDoseRecords_l();
+
         mAudioHandler.sendMessageAtTime(mAudioHandler.obtainMessage(MSG_PERSIST_CSD_VALUES,
                 /* arg1= */0, /* arg2= */0, /* obj= */null), /* delay= */0);
 
         mLogger.enqueue(SoundDoseEvent.getDoseUpdateEvent(currentCsd, totalDuration));
     }
 
+    @GuardedBy("mCsdStateLock")
+    private void sanitizeDoseRecords_l() {
+        if (mDoseRecords.size() > MAX_NUMBER_OF_CACHED_RECORDS) {
+            int nrToRemove = mDoseRecords.size() - MAX_NUMBER_OF_CACHED_RECORDS;
+            Log.w(TAG,
+                    "Removing " + nrToRemove + " records from the total of " + mDoseRecords.size());
+            // Remove older elements to fit into persisted settings max length
+            Iterator<SoundDoseRecord> recordIterator = mDoseRecords.iterator();
+            while (recordIterator.hasNext() && nrToRemove > 0) {
+                recordIterator.next();
+                recordIterator.remove();
+                --nrToRemove;
+            }
+        }
+    }
+
     @SuppressWarnings("GuardedBy")  // avoid limitation with intra-procedural analysis of lambdas
     private void onPersistSoundDoseRecords() {
         synchronized (mCsdStateLock) {
@@ -1213,8 +1242,8 @@
             long globalTimeOffsetInSecs) {
         return convertToGlobalTime(record.timestamp, globalTimeOffsetInSecs)
                 + PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.duration
-                + PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.value
-                + PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.averageMel;
+                + PERSIST_CSD_RECORD_FIELD_SEPARATOR + String.format("%.3f", record.value)
+                + PERSIST_CSD_RECORD_FIELD_SEPARATOR + String.format("%.3f", record.averageMel);
     }
 
     private static long convertToGlobalTime(long bootTimeInSecs, long globalTimeOffsetInSecs) {
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index de4979a..5b9469b 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -20,7 +20,8 @@
 import android.app.backup.BackupAgentHelper;
 import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.BackupDataInput;
-import android.app.backup.BackupHelper;
+import android.app.backup.BackupHelperWithLogger;
+import android.app.backup.BackupRestoreEventLogger;
 import android.app.backup.FullBackup;
 import android.app.backup.FullBackupDataOutput;
 import android.app.backup.WallpaperBackupHelper;
@@ -33,9 +34,10 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
-
 import com.google.android.collect.Sets;
 
+import com.android.server.backup.Flags;
+
 import java.io.File;
 import java.io.IOException;
 import java.util.Set;
@@ -107,10 +109,12 @@
 
     private int mUserId = UserHandle.USER_SYSTEM;
     private boolean mIsProfileUser = false;
+    private BackupRestoreEventLogger mLogger;
 
     @Override
     public void onCreate(UserHandle user, @BackupDestination int backupDestination) {
         super.onCreate(user, backupDestination);
+        mLogger = this.getBackupRestoreEventLogger();
 
         mUserId = user.getIdentifier();
         if (mUserId != UserHandle.USER_SYSTEM) {
@@ -209,9 +213,12 @@
         }
     }
 
-    private void addHelperIfEligibleForUser(String keyPrefix, BackupHelper helper) {
+    private void addHelperIfEligibleForUser(String keyPrefix, BackupHelperWithLogger helper) {
         if (isHelperEligibleForUser(keyPrefix)) {
             addHelper(keyPrefix, helper);
+            if (Flags.enableMetricsSystemBackupAgents()) {
+                helper.setLogger(mLogger);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index d5d8fd2..3f3540e 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -20,6 +20,7 @@
 // TODO(b/141025588): Create separate internal and external permissions for AuthService.
 // TODO(b/141025588): Get rid of the USE_FINGERPRINT permission.
 
+import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO;
 import static android.Manifest.permission.TEST_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -304,6 +305,9 @@
             if (promptInfo.containsPrivateApiConfigurations()) {
                 checkInternalPermission();
             }
+            if (promptInfo.containsSetLogoApiConfigurations()) {
+                checkManageBiometricPermission();
+            }
 
             final long identity = Binder.clearCallingIdentity();
             try {
@@ -435,6 +439,10 @@
             if (fingerprintService != null) {
                 fingerprintService.registerAuthenticationStateListener(listener);
             }
+            final IFaceService faceService = mInjector.getFaceService();
+            if (faceService != null) {
+                faceService.registerAuthenticationStateListener(listener);
+            }
         }
 
         @Override
@@ -445,6 +453,10 @@
             if (fingerprintService != null) {
                 fingerprintService.unregisterAuthenticationStateListener(listener);
             }
+            final IFaceService faceService = mInjector.getFaceService();
+            if (faceService != null) {
+                faceService.unregisterAuthenticationStateListener(listener);
+            }
         }
 
         @Override
@@ -984,6 +996,11 @@
                 "Must have USE_BIOMETRIC_INTERNAL permission");
     }
 
+    private void checkManageBiometricPermission() {
+        getContext().enforceCallingOrSelfPermission(SET_BIOMETRIC_DIALOG_LOGO,
+                "Must have SET_BIOMETRIC_DIALOG_LOGO permission");
+    }
+
     private void checkPermission() {
         if (getContext().checkCallingOrSelfPermission(USE_FINGERPRINT)
                 != PackageManager.PERMISSION_GRANTED) {
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
index 3dcea19..7f04628 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContext.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
@@ -77,6 +77,9 @@
     @AuthenticateOptions.DisplayState
     int getDisplayState();
 
+    /** Gets whether touches on sensor are ignored by HAL */
+    boolean isHardwareIgnoringTouches();
+
     /**
      * Subscribe to context changes.
      *
@@ -84,10 +87,30 @@
      *
      * @param context context that will be modified when changed
      * @param consumer callback when the context is modified
+     *
+     * @deprecated instead use {@link BiometricContext#subscribe(OperationContextExt, Consumer,
+     *                                                           Consumer, AuthenticateOptions)}
+     * TODO (b/294161627): Delete this API once Flags.DE_HIDL is removed.
      */
+    @Deprecated
     void subscribe(@NonNull OperationContextExt context,
             @NonNull Consumer<OperationContext> consumer);
 
+    /**
+     * Subscribe to context changes and start the HAL operation.
+     *
+     * Note that this method only notifies for properties that are visible to the HAL.
+     *
+     * @param context               context that will be modified when changed
+     * @param startHalConsumer      callback to start HAL operation after subscription is done
+     * @param updateContextConsumer callback when the context is modified
+     * @param options               authentication options for updating the context
+     */
+    void subscribe(@NonNull OperationContextExt context,
+            @NonNull Consumer<OperationContext> startHalConsumer,
+            @NonNull Consumer<OperationContext> updateContextConsumer,
+            @Nullable AuthenticateOptions options);
+
     /** Unsubscribe from context changes. */
     void unsubscribe(@NonNull OperationContextExt context);
 
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
index 95a047f..d8dfa60 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
@@ -86,8 +86,8 @@
     @Nullable private final Handler mHandler;
     private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
     private int mFoldState = IBiometricContextListener.FoldState.UNKNOWN;
-
     private int mDisplayState = AuthenticateOptions.DISPLAY_STATE_UNKNOWN;
+    private boolean mIsHardwareIgnoringTouches = false;
     @VisibleForTesting
     final BroadcastReceiver mDockStateReceiver = new BroadcastReceiver() {
         @Override
@@ -129,6 +129,14 @@
                         notifyChanged();
                     }
                 }
+
+                @Override
+                public void onHardwareIgnoreTouchesChanged(boolean shouldIgnore) {
+                    if (mIsHardwareIgnoringTouches != shouldIgnore) {
+                        mIsHardwareIgnoringTouches = shouldIgnore;
+                        notifyChanged();
+                    }
+                }
             });
             service.registerSessionListener(SESSION_TYPES, new ISessionListener.Stub() {
                 @Override
@@ -215,6 +223,11 @@
     }
 
     @Override
+    public boolean isHardwareIgnoringTouches() {
+        return mIsHardwareIgnoringTouches;
+    }
+
+    @Override
     public void subscribe(@NonNull OperationContextExt context,
             @NonNull Consumer<OperationContext> consumer) {
         mSubscribers.put(context, consumer);
@@ -225,6 +238,19 @@
     }
 
     @Override
+    public void subscribe(@NonNull OperationContextExt context,
+            @NonNull Consumer<OperationContext> startHalConsumer,
+            @NonNull Consumer<OperationContext> updateContextConsumer,
+            @Nullable AuthenticateOptions options) {
+        mSubscribers.put(updateContext(context, context.isCrypto()), updateContextConsumer);
+        if (options != null) {
+            startHalConsumer.accept(context.toAidlContext(options));
+        } else {
+            startHalConsumer.accept(context.toAidlContext());
+        }
+    }
+
+    @Override
     public void unsubscribe(@NonNull OperationContextExt context) {
         mSubscribers.remove(context);
     }
@@ -254,6 +280,7 @@
                 + "bp session: " + getBiometricPromptSessionInfo() + ", "
                 + "displayState: " + getDisplayState() + ", "
                 + "isAwake: " + isAwake() +  ", "
+                + "isHardwareIgnoring: " + isHardwareIgnoringTouches() +  ", "
                 + "isDisplayOn: " + isDisplayOn() +  ", "
                 + "dock: " + getDockedState() + ", "
                 + "rotation: " + getCurrentRotation() + ", "
diff --git a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
index b4e0dff..da4e515 100644
--- a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
+++ b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
@@ -20,12 +20,14 @@
 import android.annotation.Nullable;
 import android.content.Intent;
 import android.hardware.biometrics.AuthenticateOptions;
+import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.common.AuthenticateReason;
 import android.hardware.biometrics.common.DisplayState;
 import android.hardware.biometrics.common.FoldState;
 import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.common.OperationReason;
+import android.hardware.biometrics.common.OperationState;
 import android.hardware.biometrics.common.WakeReason;
 import android.hardware.face.FaceAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
@@ -51,13 +53,26 @@
 
     /** Create a context. */
     public OperationContextExt(boolean isBP) {
-        this(new OperationContext(), isBP);
+        this(new OperationContext(), isBP, BiometricAuthenticator.TYPE_NONE);
+    }
+
+    public OperationContextExt(boolean isBP, @BiometricAuthenticator.Modality int modality) {
+        this(new OperationContext(), isBP, modality);
     }
 
     /** Create a wrapped context. */
-    public OperationContextExt(@NonNull OperationContext context, boolean isBP) {
+    public OperationContextExt(@NonNull OperationContext context, boolean isBP,
+            @BiometricAuthenticator.Modality int modality) {
         mAidlContext = context;
         mIsBP = isBP;
+
+        if (modality == BiometricAuthenticator.TYPE_FINGERPRINT) {
+            mAidlContext.operationState = OperationState.fingerprintOperationState(
+                    new OperationState.FingerprintOperationState());
+        } else if (modality == BiometricAuthenticator.TYPE_FACE) {
+            mAidlContext.operationState = OperationState.faceOperationState(
+                    new OperationState.FaceOperationState());
+        }
     }
 
     /**
@@ -87,6 +102,24 @@
      * @return the underlying AIDL context
      */
     @NonNull
+    public OperationContext toAidlContext(@NonNull AuthenticateOptions options) {
+        if (options instanceof FaceAuthenticateOptions) {
+            return toAidlContext((FaceAuthenticateOptions) options);
+        }
+        if (options instanceof FingerprintAuthenticateOptions) {
+            return toAidlContext((FingerprintAuthenticateOptions) options);
+        }
+        throw new IllegalStateException("Authenticate options are invalid.");
+    }
+
+    /**
+     * Gets the subset of the context that can be shared with the HAL and updates
+     * it with the given options.
+     *
+     * @param options authenticate options
+     * @return the underlying AIDL context
+     */
+    @NonNull
     public OperationContext toAidlContext(@NonNull FaceAuthenticateOptions options) {
         mAidlContext.authenticateReason = AuthenticateReason
                 .faceAuthenticateReason(getAuthReason(options));
@@ -247,12 +280,23 @@
         return mOrientation;
     }
 
+    /** The current operation state  */
+    public OperationState getOperationState() {
+        return mAidlContext.operationState;
+    }
+
     /** Update this object with the latest values from the given context. */
     OperationContextExt update(@NonNull BiometricContext biometricContext, boolean isCrypto) {
         mAidlContext.isAod = biometricContext.isAod();
         mAidlContext.displayState = toAidlDisplayState(biometricContext.getDisplayState());
         mAidlContext.foldState = toAidlFoldState(biometricContext.getFoldState());
         mAidlContext.isCrypto = isCrypto;
+
+        if (mAidlContext.operationState != null && mAidlContext.operationState.getTag()
+                == OperationState.fingerprintOperationState) {
+            mAidlContext.operationState.getFingerprintOperationState().isHardwareIgnoringTouches =
+                    biometricContext.isHardwareIgnoringTouches();
+        }
         setFirstSessionId(biometricContext);
 
         mIsDisplayOn = biometricContext.isDisplayOn();
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
index 5863535..1ae4d64 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
@@ -91,6 +91,40 @@
         }
     }
 
+    /**
+     * Defines behavior in response to a successful authentication
+     * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+     *                      authentication
+     * @param userId The user Id for the requested authentication
+     */
+    public void onAuthenticationSucceeded(int requestReason, int userId) {
+        for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+            try {
+                listener.onAuthenticationSucceeded(requestReason, userId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception in notifying listener that authentication "
+                        + "succeeded", e);
+            }
+        }
+    }
+
+    /**
+     * Defines behavior in response to a failed authentication
+     * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+     *                      authentication
+     * @param userId The user Id for the requested authentication
+     */
+    public void onAuthenticationFailed(int requestReason, int userId) {
+        for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+            try {
+                listener.onAuthenticationFailed(requestReason, userId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception in notifying listener that authentication "
+                        + "failed", e);
+            }
+        }
+    }
+
     @Override
     public void binderDied() {
         // Do nothing, handled below
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 89b638b..89e08c1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors;
 
+import static com.android.server.biometrics.sensors.BiometricSchedulerOperation.STATE_STARTED;
+
 import android.annotation.IntDef;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
@@ -28,6 +30,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
@@ -35,6 +38,7 @@
 import com.android.modules.expresslog.Counter;
 import com.android.server.biometrics.BiometricSchedulerProto;
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
 import java.io.PrintWriter;
@@ -48,6 +52,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
 /**
  * A scheduler for biometric HAL operations. Maintains a queue of {@link BaseClientMonitor}
@@ -56,11 +61,16 @@
  *
  * We currently assume (and require) that each biometric sensor have its own instance of a
  * {@link BiometricScheduler}.
+ *
+ * @param <T> Hal instance for starting the user.
+ * @param <U> Session associated with the current user id.
+ *
+ * TODO: (b/304604965) Update thread annotation when FLAGS_DE_HIDL is removed.
  */
 @MainThread
-public class BiometricScheduler {
+public class BiometricScheduler<T, U> {
 
-    private static final String BASE_TAG = "BiometricScheduler";
+    private static final String TAG = "BiometricScheduler";
     // Number of recent operations to keep in our logs for dumpsys
     protected static final int LOG_NUM_RECENT_OPERATIONS = 50;
 
@@ -89,30 +99,6 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface SensorType {}
 
-    public static @SensorType int sensorTypeFromFingerprintProperties(
-            @NonNull FingerprintSensorPropertiesInternal props) {
-        if (props.isAnyUdfpsType()) {
-            return SENSOR_TYPE_UDFPS;
-        }
-
-        return SENSOR_TYPE_FP_OTHER;
-    }
-
-    public static String sensorTypeToString(@SensorType int sensorType) {
-        switch (sensorType) {
-            case SENSOR_TYPE_UNKNOWN:
-                return "Unknown";
-            case SENSOR_TYPE_FACE:
-                return "Face";
-            case SENSOR_TYPE_UDFPS:
-                return "Udfps";
-            case SENSOR_TYPE_FP_OTHER:
-                return "OtherFp";
-            default:
-                return "UnknownUnknown";
-        }
-    }
-
     private static final class CrashState {
         static final int NUM_ENTRIES = 10;
         final String timestamp;
@@ -145,8 +131,8 @@
         }
     }
 
-    @NonNull protected final String mBiometricTag;
-    private final @SensorType int mSensorType;
+    @SensorType
+    private final int mSensorType;
     @Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
     @NonNull private final IBiometricService mBiometricService;
     @NonNull protected final Handler mHandler;
@@ -157,6 +143,43 @@
     private int mTotalOperationsHandled;
     private final int mRecentOperationsLimit;
     @NonNull private final List<Integer> mRecentOperations;
+    @Nullable private StopUserClient<U> mStopUserClient;
+    @NonNull private Supplier<Integer> mCurrentUserRetriever;
+    @Nullable private UserSwitchProvider<T, U> mUserSwitchProvider;
+
+    private class UserSwitchClientCallback implements ClientMonitorCallback {
+        @NonNull private final BaseClientMonitor mOwner;
+
+        UserSwitchClientCallback(@NonNull BaseClientMonitor owner) {
+            mOwner = owner;
+        }
+
+        @Override
+        public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
+            mHandler.post(() -> {
+                Slog.d(TAG, "[Client finished] " + clientMonitor + ", success: " + success);
+
+                // Set mStopUserClient to null when StopUserClient fails. Otherwise it's possible
+                // for that the queue will wait indefinitely until the field is cleared.
+                if (clientMonitor instanceof StopUserClient<?>) {
+                    if (!success) {
+                        Slog.w(TAG, "StopUserClient failed(), is the HAL stuck? "
+                                + "Clearing mStopUserClient");
+                    }
+                    mStopUserClient = null;
+                }
+                if (mCurrentOperation != null && mCurrentOperation.isFor(mOwner)) {
+                    mCurrentOperation = null;
+                } else {
+                    // can happen if the hal dies and is usually okay
+                    // do not unset the current operation that may be newer
+                    Slog.w(TAG, "operation is already null or different (reset?): "
+                            + mCurrentOperation);
+                }
+                startNextOperationIfIdle();
+            });
+        }
+    }
 
     // Internal callback, notified when an operation is complete. Notifies the requester
     // that the operation is complete, before performing internal scheduler work (such as
@@ -164,26 +187,26 @@
     private final ClientMonitorCallback mInternalCallback = new ClientMonitorCallback() {
         @Override
         public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
-            Slog.d(getTag(), "[Started] " + clientMonitor);
+            Slog.d(TAG, "[Started] " + clientMonitor);
         }
 
         @Override
         public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
             mHandler.post(() -> {
                 if (mCurrentOperation == null) {
-                    Slog.e(getTag(), "[Finishing] " + clientMonitor
+                    Slog.e(TAG, "[Finishing] " + clientMonitor
                             + " but current operation is null, success: " + success
                             + ", possible lifecycle bug in clientMonitor implementation?");
                     return;
                 }
 
                 if (!mCurrentOperation.isFor(clientMonitor)) {
-                    Slog.e(getTag(), "[Ignoring Finish] " + clientMonitor + " does not match"
+                    Slog.e(TAG, "[Ignoring Finish] " + clientMonitor + " does not match"
                             + " current: " + mCurrentOperation);
                     return;
                 }
 
-                Slog.d(getTag(), "[Finishing] " + clientMonitor + ", success: " + success);
+                Slog.d(TAG, "[Finishing] " + clientMonitor + ", success: " + success);
 
                 if (mGestureAvailabilityDispatcher != null) {
                     mGestureAvailabilityDispatcher.markSensorActive(
@@ -202,13 +225,11 @@
     };
 
     @VisibleForTesting
-    public BiometricScheduler(@NonNull String tag,
-            @NonNull Handler handler,
+    public BiometricScheduler(@NonNull Handler handler,
             @SensorType int sensorType,
             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull IBiometricService biometricService,
             int recentOperationsLimit) {
-        mBiometricTag = tag;
         mHandler = handler;
         mSensorType = sensorType;
         mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
@@ -219,49 +240,140 @@
         mRecentOperations = new ArrayList<>();
     }
 
+    @VisibleForTesting
+    public BiometricScheduler(@NonNull Handler handler,
+            @SensorType int sensorType,
+            @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull IBiometricService biometricService,
+            int recentOperationsLimit,
+            @NonNull Supplier<Integer> currentUserRetriever,
+            @Nullable UserSwitchProvider<T, U> userSwitchProvider) {
+        mHandler = handler;
+        mSensorType = sensorType;
+        mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
+        mPendingOperations = new ArrayDeque<>();
+        mBiometricService = biometricService;
+        mCrashStates = new ArrayDeque<>();
+        mRecentOperationsLimit = recentOperationsLimit;
+        mRecentOperations = new ArrayList<>();
+        mCurrentUserRetriever = currentUserRetriever;
+        mUserSwitchProvider = userSwitchProvider;
+    }
+
+    public BiometricScheduler(@NonNull Handler handler,
+            @SensorType int sensorType,
+            @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull Supplier<Integer> currentUserRetriever,
+            @NonNull UserSwitchProvider<T, U> userSwitchProvider) {
+        this(handler, sensorType, gestureAvailabilityDispatcher,
+                IBiometricService.Stub.asInterface(ServiceManager.getService(
+                        Context.BIOMETRIC_SERVICE)), LOG_NUM_RECENT_OPERATIONS,
+                currentUserRetriever, userSwitchProvider);
+    }
+
     /**
      * Creates a new scheduler.
      *
-     * @param tag for the specific instance of the scheduler. Should be unique.
      * @param sensorType the sensorType that this scheduler is handling.
      * @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures
      *                                      (such as fingerprint swipe).
      */
-    public BiometricScheduler(@NonNull String tag,
-            @SensorType int sensorType,
+    public BiometricScheduler(@SensorType int sensorType,
             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
-        this(tag, new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher,
+        this(new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher,
                 IBiometricService.Stub.asInterface(
                         ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
                 LOG_NUM_RECENT_OPERATIONS);
     }
 
+    /**
+     * Returns sensor type for a fingerprint sensor.
+     */
+    @SensorType
+    public static int sensorTypeFromFingerprintProperties(
+            @NonNull FingerprintSensorPropertiesInternal props) {
+        if (props.isAnyUdfpsType()) {
+            return SENSOR_TYPE_UDFPS;
+        }
+
+        return SENSOR_TYPE_FP_OTHER;
+    }
+
     @VisibleForTesting
     public ClientMonitorCallback getInternalCallback() {
         return mInternalCallback;
     }
 
-    protected String getTag() {
-        return BASE_TAG + "/" + mBiometricTag;
+    protected void startNextOperationIfIdle() {
+        if (Flags.deHidl()) {
+            startNextOperation();
+        } else {
+            startNextOperationIfIdleLegacy();
+        }
     }
 
-    protected void startNextOperationIfIdle() {
+    protected void startNextOperation() {
         if (mCurrentOperation != null) {
-            Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation);
+            Slog.v(TAG, "Not idle, current operation: " + mCurrentOperation);
             return;
         }
         if (mPendingOperations.isEmpty()) {
-            Slog.d(getTag(), "No operations, returning to idle");
+            Slog.d(TAG, "No operations, returning to idle");
+            return;
+        }
+
+        final int currentUserId = mCurrentUserRetriever.get();
+        final int nextUserId = mPendingOperations.getFirst().getTargetUserId();
+
+        if (nextUserId == currentUserId || mPendingOperations.getFirst().isStartUserOperation()) {
+            startNextOperationIfIdleLegacy();
+        } else if (currentUserId == UserHandle.USER_NULL && mUserSwitchProvider != null) {
+            final BaseClientMonitor startClient =
+                    mUserSwitchProvider.getStartUserClient(nextUserId);
+            final UserSwitchClientCallback finishedCallback =
+                    new UserSwitchClientCallback(startClient);
+
+            Slog.d(TAG, "[Starting User] " + startClient);
+            mCurrentOperation = new BiometricSchedulerOperation(
+                    startClient, finishedCallback, STATE_STARTED);
+            startClient.start(finishedCallback);
+        } else if (mUserSwitchProvider != null) {
+            if (mStopUserClient != null) {
+                Slog.d(TAG, "[Waiting for StopUser] " + mStopUserClient);
+            } else {
+                mStopUserClient = mUserSwitchProvider
+                        .getStopUserClient(currentUserId);
+                final UserSwitchClientCallback finishedCallback =
+                        new UserSwitchClientCallback(mStopUserClient);
+
+                Slog.d(TAG, "[Stopping User] current: " + currentUserId
+                        + ", next: " + nextUserId + ". " + mStopUserClient);
+                mCurrentOperation = new BiometricSchedulerOperation(
+                        mStopUserClient, finishedCallback, STATE_STARTED);
+                mStopUserClient.start(finishedCallback);
+            }
+        } else {
+            Slog.e(TAG, "Cannot start next operation.");
+        }
+    }
+
+    protected void startNextOperationIfIdleLegacy() {
+        if (mCurrentOperation != null) {
+            Slog.v(TAG, "Not idle, current operation: " + mCurrentOperation);
+            return;
+        }
+        if (mPendingOperations.isEmpty()) {
+            Slog.d(TAG, "No operations, returning to idle");
             return;
         }
 
         mCurrentOperation = mPendingOperations.poll();
-        Slog.d(getTag(), "[Polled] " + mCurrentOperation);
+        Slog.d(TAG, "[Polled] " + mCurrentOperation);
 
         // If the operation at the front of the queue has been marked for cancellation, send
         // ERROR_CANCELED. No need to start this client.
         if (mCurrentOperation.isMarkedCanceling()) {
-            Slog.d(getTag(), "[Now Cancelling] " + mCurrentOperation);
+            Slog.d(TAG, "[Now Cancelling] " + mCurrentOperation);
             mCurrentOperation.cancel(mHandler, mInternalCallback);
             // Now we wait for the client to send its FinishCallback, which kicks off the next
             // operation.
@@ -289,7 +401,7 @@
                 // Note down current length of queue
                 final int pendingOperationsLength = mPendingOperations.size();
                 final BiometricSchedulerOperation lastOperation = mPendingOperations.peekLast();
-                Slog.e(getTag(), "[Unable To Start] " + mCurrentOperation
+                Slog.e(TAG, "[Unable To Start] " + mCurrentOperation
                         + ". Last pending operation: " + lastOperation);
 
                 // Then for each operation currently in the pending queue at the time of this
@@ -298,10 +410,10 @@
                 for (int i = 0; i < pendingOperationsLength; i++) {
                     final BiometricSchedulerOperation operation = mPendingOperations.pollFirst();
                     if (operation != null) {
-                        Slog.w(getTag(), "[Aborting Operation] " + operation);
+                        Slog.w(TAG, "[Aborting Operation] " + operation);
                         operation.abort();
                     } else {
-                        Slog.e(getTag(), "Null operation, index: " + i
+                        Slog.e(TAG, "Null operation, index: " + i
                                 + ", expected length: " + pendingOperationsLength);
                     }
                 }
@@ -317,9 +429,9 @@
                 mBiometricService.onReadyForAuthentication(
                         mCurrentOperation.getClientMonitor().getRequestId(), cookie);
             } catch (RemoteException e) {
-                Slog.e(getTag(), "Remote exception when contacting BiometricService", e);
+                Slog.e(TAG, "Remote exception when contacting BiometricService", e);
             }
-            Slog.d(getTag(), "Waiting for cookie before starting: " + mCurrentOperation);
+            Slog.d(TAG, "Waiting for cookie before starting: " + mCurrentOperation);
         }
     }
 
@@ -338,14 +450,14 @@
      */
     public void startPreparedClient(int cookie) {
         if (mCurrentOperation == null) {
-            Slog.e(getTag(), "Current operation is null");
+            Slog.e(TAG, "Current operation is null");
             return;
         }
 
         if (mCurrentOperation.startWithCookie(mInternalCallback, cookie)) {
-            Slog.d(getTag(), "[Started] Prepared client: " + mCurrentOperation);
+            Slog.d(TAG, "[Started] Prepared client: " + mCurrentOperation);
         } else {
-            Slog.e(getTag(), "[Unable To Start] Prepared client: " + mCurrentOperation);
+            Slog.e(TAG, "[Unable To Start] Prepared client: " + mCurrentOperation);
             mCurrentOperation = null;
             startNextOperationIfIdle();
         }
@@ -374,13 +486,13 @@
         if (clientMonitor.interruptsPrecedingClients()) {
             for (BiometricSchedulerOperation operation : mPendingOperations) {
                 if (operation.markCanceling()) {
-                    Slog.d(getTag(), "New client, marking pending op as canceling: " + operation);
+                    Slog.d(TAG, "New client, marking pending op as canceling: " + operation);
                 }
             }
         }
 
         mPendingOperations.add(new BiometricSchedulerOperation(clientMonitor, clientCallback));
-        Slog.d(getTag(), "[Added] " + clientMonitor
+        Slog.d(TAG, "[Added] " + clientMonitor
                 + ", new queue size: " + mPendingOperations.size());
 
         // If the new operation should interrupt preceding clients, and if the current operation is
@@ -389,7 +501,7 @@
                 && mCurrentOperation != null
                 && mCurrentOperation.isInterruptable()
                 && mCurrentOperation.isStarted()) {
-            Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation);
+            Slog.d(TAG, "[Cancelling Interruptable]: " + mCurrentOperation);
             mCurrentOperation.cancel(mHandler, mInternalCallback);
         } else {
             startNextOperationIfIdle();
@@ -401,16 +513,16 @@
      * @param token from the caller, should match the token passed in when requesting enrollment
      */
     public void cancelEnrollment(IBinder token, long requestId) {
-        Slog.d(getTag(), "cancelEnrollment, requestId: " + requestId);
+        Slog.d(TAG, "cancelEnrollment, requestId: " + requestId);
 
         if (mCurrentOperation != null
                 && canCancelEnrollOperation(mCurrentOperation, token, requestId)) {
-            Slog.d(getTag(), "Cancelling enrollment op: " + mCurrentOperation);
+            Slog.d(TAG, "Cancelling enrollment op: " + mCurrentOperation);
             mCurrentOperation.cancel(mHandler, mInternalCallback);
         } else {
             for (BiometricSchedulerOperation operation : mPendingOperations) {
                 if (canCancelEnrollOperation(operation, token, requestId)) {
-                    Slog.d(getTag(), "Cancelling pending enrollment op: " + operation);
+                    Slog.d(TAG, "Cancelling pending enrollment op: " + operation);
                     operation.markCanceling();
                 }
             }
@@ -423,16 +535,16 @@
      * @param requestId the id returned when requesting authentication
      */
     public void cancelAuthenticationOrDetection(IBinder token, long requestId) {
-        Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId);
+        Slog.d(TAG, "cancelAuthenticationOrDetection, requestId: " + requestId);
 
         if (mCurrentOperation != null
                 && canCancelAuthOperation(mCurrentOperation, token, requestId)) {
-            Slog.d(getTag(), "Cancelling auth/detect op: " + mCurrentOperation);
+            Slog.d(TAG, "Cancelling auth/detect op: " + mCurrentOperation);
             mCurrentOperation.cancel(mHandler, mInternalCallback);
         } else {
             for (BiometricSchedulerOperation operation : mPendingOperations) {
                 if (canCancelAuthOperation(operation, token, requestId)) {
-                    Slog.d(getTag(), "Cancelling pending auth/detect op: " + operation);
+                    Slog.d(TAG, "Cancelling pending auth/detect op: " + operation);
                     operation.markCanceling();
                 }
             }
@@ -504,11 +616,11 @@
                 mCurrentOperation != null ? mCurrentOperation.toString() : null,
                 pendingOperations);
         mCrashStates.add(crashState);
-        Slog.e(getTag(), "Recorded crash state: " + crashState.toString());
+        Slog.e(TAG, "Recorded crash state: " + crashState.toString());
     }
 
     public void dump(PrintWriter pw) {
-        pw.println("Dump of BiometricScheduler " + getTag());
+        pw.println("Dump of BiometricScheduler " + TAG);
         pw.println("Type: " + mSensorType);
         pw.println("Current operation: " + mCurrentOperation);
         pw.println("Pending operations: " + mPendingOperations.size());
@@ -548,7 +660,7 @@
      * HAL dies.
      */
     public void reset() {
-        Slog.d(getTag(), "Resetting scheduler");
+        Slog.d(TAG, "Resetting scheduler");
         mPendingOperations.clear();
         mCurrentOperation = null;
     }
@@ -562,11 +674,11 @@
             return;
         }
         for (BiometricSchedulerOperation pendingOperation : mPendingOperations) {
-            Slog.d(getTag(), "[Watchdog cancelling pending] "
+            Slog.d(TAG, "[Watchdog cancelling pending] "
                     + pendingOperation.getClientMonitor());
             pendingOperation.markCancelingForWatchdog();
         }
-        Slog.d(getTag(), "[Watchdog cancelling current] "
+        Slog.d(TAG, "[Watchdog cancelling current] "
                 + mCurrentOperation.getClientMonitor());
         mCurrentOperation.cancel(mHandler, getInternalCallback());
     }
@@ -590,9 +702,23 @@
     /**
      * Handle stop user client when user switching occurs.
      */
-    public void onUserStopped() {}
+    public void onUserStopped() {
+        if (mStopUserClient == null) {
+            Slog.e(TAG, "Unexpected onUserStopped");
+            return;
+        }
+
+        Slog.d(TAG, "[OnUserStopped]: " + mStopUserClient);
+        mStopUserClient.onUserStopped();
+        mStopUserClient = null;
+    }
 
     public Handler getHandler() {
         return mHandler;
     }
+
+    @Nullable
+    public StopUserClient<?> getStopUserClient() {
+        return mStopUserClient;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
index 1a682a9..1fc7c70 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
@@ -41,22 +41,42 @@
  * a common interface.
  */
 public class ClientMonitorCallbackConverter {
-    private IBiometricSensorReceiver mSensorReceiver; // BiometricService
-    private IFaceServiceReceiver mFaceServiceReceiver; // FaceManager
-    private IFingerprintServiceReceiver mFingerprintServiceReceiver; // FingerprintManager
+    private final IBiometricSensorReceiver mSensorReceiver; // BiometricService
+    private final IFaceServiceReceiver mFaceServiceReceiver; // FaceManager
+    private final IFingerprintServiceReceiver mFingerprintServiceReceiver; // FingerprintManager
 
     public ClientMonitorCallbackConverter(IBiometricSensorReceiver sensorReceiver) {
         mSensorReceiver = sensorReceiver;
+        mFaceServiceReceiver = null;
+        mFingerprintServiceReceiver = null;
     }
 
     public ClientMonitorCallbackConverter(IFaceServiceReceiver faceServiceReceiver) {
+        mSensorReceiver = null;
         mFaceServiceReceiver = faceServiceReceiver;
+        mFingerprintServiceReceiver = null;
     }
 
     public ClientMonitorCallbackConverter(IFingerprintServiceReceiver fingerprintServiceReceiver) {
+        mSensorReceiver = null;
+        mFaceServiceReceiver = null;
         mFingerprintServiceReceiver = fingerprintServiceReceiver;
     }
 
+    /**
+     * Returns an int representing the {@link BiometricAuthenticator.Modality} of the active
+     * ServiceReceiver
+     */
+    @BiometricAuthenticator.Modality
+    public int getModality() {
+        if (mFaceServiceReceiver != null) {
+            return BiometricAuthenticator.TYPE_FACE;
+        } else if (mFingerprintServiceReceiver != null) {
+            return BiometricAuthenticator.TYPE_FINGERPRINT;
+        }
+        return BiometricAuthenticator.TYPE_NONE;
+    }
+
     // The following apply to all clients
 
     public void onAcquired(int sensorId, int acquiredInfo, int vendorCode) throws RemoteException {
diff --git a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
index 03658ce..b0649b9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
@@ -19,8 +19,10 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
 import android.os.IBinder;
 
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.OperationContextExt;
@@ -58,7 +60,8 @@
         super(context, token, listener, userId, owner, cookie, sensorId,
                 biometricLogger, biometricContext);
         mLazyDaemon = lazyDaemon;
-        mOperationContext = new OperationContextExt(isBiometricPrompt());
+        int modality = listener != null ? listener.getModality() : BiometricAuthenticator.TYPE_NONE;
+        mOperationContext = new OperationContextExt(isBiometricPrompt(), modality);
     }
 
     @Nullable
@@ -91,6 +94,9 @@
     }
 
     protected OperationContextExt getOperationContext() {
+        if (Flags.deHidl()) {
+            return mOperationContext;
+        }
         return getBiometricContext().updateContext(mOperationContext, isCryptoOperation());
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
index e8654dc..e01c4ec 100644
--- a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
@@ -30,7 +30,10 @@
 
 /**
  * Abstract class for stopping a user.
- * @param <T> Interface for stopping the user.
+ *
+ * @param <T> Session for stopping the user. It should be either an instance of
+ *            {@link com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession} or
+ *            {@link com.android.server.biometrics.sensors.face.aidl.AidlSession}.
  */
 public abstract class StopUserClient<T> extends HalClientMonitor<T> {
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
index 3753bbd..7ca10e3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -33,10 +33,14 @@
 
 /**
  * A user-aware scheduler that requests user-switches based on scheduled operation's targetUserId.
+ * TODO (b/304604965): Remove class when Flags.FLAG_DE_HIDL is removed.
+ *
+ * @param <T> Hal instance for starting the user.
+ * @param <U> Session associated with the current user id.
  */
-public class UserAwareBiometricScheduler extends BiometricScheduler {
+public class UserAwareBiometricScheduler<T, U> extends BiometricScheduler<T, U> {
 
-    private static final String BASE_TAG = "UaBiometricScheduler";
+    private static final String TAG = "UaBiometricScheduler";
 
     /**
      * Interface to retrieve the owner's notion of the current userId. Note that even though
@@ -66,13 +70,13 @@
         @Override
         public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
             mHandler.post(() -> {
-                Slog.d(getTag(), "[Client finished] " + clientMonitor + ", success: " + success);
+                Slog.d(TAG, "[Client finished] " + clientMonitor + ", success: " + success);
 
                 // Set mStopUserClient to null when StopUserClient fails. Otherwise it's possible
                 // for that the queue will wait indefinitely until the field is cleared.
                 if (clientMonitor instanceof StopUserClient<?>) {
                     if (!success) {
-                        Slog.w(getTag(), "StopUserClient failed(), is the HAL stuck? "
+                        Slog.w(TAG, "StopUserClient failed(), is the HAL stuck? "
                                 + "Clearing mStopUserClient");
                     }
                     mStopUserClient = null;
@@ -82,7 +86,7 @@
                 } else {
                     // can happen if the hal dies and is usually okay
                     // do not unset the current operation that may be newer
-                    Slog.w(getTag(), "operation is already null or different (reset?): "
+                    Slog.w(TAG, "operation is already null or different (reset?): "
                             + mCurrentOperation);
                 }
                 startNextOperationIfIdle();
@@ -98,7 +102,7 @@
             @NonNull IBiometricService biometricService,
             @NonNull CurrentUserRetriever currentUserRetriever,
             @NonNull UserSwitchCallback userSwitchCallback) {
-        super(tag, handler, sensorType, gestureAvailabilityDispatcher, biometricService,
+        super(handler, sensorType, gestureAvailabilityDispatcher, biometricService,
                 LOG_NUM_RECENT_OPERATIONS);
 
         mCurrentUserRetriever = currentUserRetriever;
@@ -117,18 +121,13 @@
     }
 
     @Override
-    protected String getTag() {
-        return BASE_TAG + "/" + mBiometricTag;
-    }
-
-    @Override
     protected void startNextOperationIfIdle() {
         if (mCurrentOperation != null) {
-            Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation);
+            Slog.v(TAG, "Not idle, current operation: " + mCurrentOperation);
             return;
         }
         if (mPendingOperations.isEmpty()) {
-            Slog.d(getTag(), "No operations, returning to idle");
+            Slog.d(TAG, "No operations, returning to idle");
             return;
         }
 
@@ -143,20 +142,20 @@
             final ClientFinishedCallback finishedCallback =
                     new ClientFinishedCallback(startClient);
 
-            Slog.d(getTag(), "[Starting User] " + startClient);
+            Slog.d(TAG, "[Starting User] " + startClient);
             mCurrentOperation = new BiometricSchedulerOperation(
                     startClient, finishedCallback, STATE_STARTED);
             startClient.start(finishedCallback);
         } else {
             if (mStopUserClient != null) {
-                Slog.d(getTag(), "[Waiting for StopUser] " + mStopUserClient);
+                Slog.d(TAG, "[Waiting for StopUser] " + mStopUserClient);
             } else {
                 mStopUserClient = mUserSwitchCallback
                         .getStopUserClient(currentUserId);
                 final ClientFinishedCallback finishedCallback =
                         new ClientFinishedCallback(mStopUserClient);
 
-                Slog.d(getTag(), "[Stopping User] current: " + currentUserId
+                Slog.d(TAG, "[Stopping User] current: " + currentUserId
                         + ", next: " + nextUserId + ". " + mStopUserClient);
                 mCurrentOperation = new BiometricSchedulerOperation(
                         mStopUserClient, finishedCallback, STATE_STARTED);
@@ -168,11 +167,11 @@
     @Override
     public void onUserStopped() {
         if (mStopUserClient == null) {
-            Slog.e(getTag(), "Unexpected onUserStopped");
+            Slog.e(TAG, "Unexpected onUserStopped");
             return;
         }
 
-        Slog.d(getTag(), "[OnUserStopped]: " + mStopUserClient);
+        Slog.d(TAG, "[OnUserStopped]: " + mStopUserClient);
         mStopUserClient.onUserStopped();
         mStopUserClient = null;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserSwitchProvider.java b/services/core/java/com/android/server/biometrics/sensors/UserSwitchProvider.java
new file mode 100644
index 0000000..bc5c55b
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/UserSwitchProvider.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+import android.annotation.NonNull;
+
+/**
+ * Interface to get the appropriate start and stop user clients.
+ *
+ * @param <T> Hal instance for starting the user.
+ * @param <U> Session associated with the current user id.
+ */
+public interface UserSwitchProvider<T, U> {
+    @NonNull
+    StartUserClient<T, U> getStartUserClient(int newUserId);
+    @NonNull
+    StopUserClient<U> getStopUserClient(int userId);
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 0f964bb..321e951 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -24,6 +24,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
+import android.hardware.biometrics.AuthenticationStateListener;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IBiometricService;
@@ -63,6 +64,7 @@
 import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -99,6 +101,8 @@
     private final BiometricStateCallback<ServiceProvider, FaceSensorPropertiesInternal>
             mBiometricStateCallback;
     @NonNull
+    private final AuthenticationStateListeners mAuthenticationStateListeners;
+    @NonNull
     private final FaceProviderFunction mFaceProviderFunction;
     @NonNull private final Function<String, FaceProvider> mFaceProvider;
     @NonNull
@@ -143,7 +147,11 @@
             return proto.getBytes();
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        @android.annotation.EnforcePermission(
+                anyOf = {
+                        android.Manifest.permission.USE_BIOMETRIC_INTERNAL,
+                        android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION
+                })
         @Override // Binder call
         public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(
                 String opPackageName) {
@@ -285,6 +293,29 @@
                     restricted, statsClient, isKeyguard);
         }
 
+        @android.annotation.EnforcePermission(
+                android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION)
+        @Override // Binder call
+        public long authenticateInBackground(final IBinder token, final long operationId,
+                final IFaceServiceReceiver receiver, final FaceAuthenticateOptions options) {
+            // TODO(b/152413782): If the sensor supports face detect and the device is encrypted or
+            //  lockdown, something wrong happened. See similar path in FingerprintService.
+
+            super.authenticateInBackground_enforcePermission();
+
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+            if (provider == null) {
+                Slog.w(TAG, "Null provider for authenticate");
+                return -1;
+            }
+            options.setSensorId(provider.first);
+
+            return provider.second.scheduleAuthenticate(token, operationId,
+                    0 /* cookie */, new ClientMonitorCallbackConverter(receiver), options,
+                    false /* restricted */, BiometricsProtoEnums.CLIENT_UNKNOWN /* statsClient */,
+                    true /* allowBackgroundAuthentication */);
+        }
+
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public long detectFace(final IBinder token,
@@ -548,7 +579,11 @@
             return provider.getEnrolledFaces(sensorId, userId);
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        @android.annotation.EnforcePermission(
+                anyOf = {
+                        android.Manifest.permission.USE_BIOMETRIC_INTERNAL,
+                        android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION
+                })
         @Override // Binder call
         public boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName) {
             super.hasEnrolledFaces_enforcePermission();
@@ -664,7 +699,8 @@
             for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
                 providers.add(
                         Face10.newInstance(getContext(), mBiometricStateCallback,
-                                hidlSensor, mLockoutResetDispatcher));
+                                mAuthenticationStateListeners, hidlSensor,
+                                mLockoutResetDispatcher));
             }
 
             return providers;
@@ -799,6 +835,24 @@
         public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
             mBiometricStateCallback.registerBiometricStateListener(listener);
         }
+
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        @Override
+        public void registerAuthenticationStateListener(
+                @NonNull AuthenticationStateListener listener) {
+            super.registerAuthenticationStateListener_enforcePermission();
+
+            mAuthenticationStateListeners.registerAuthenticationStateListener(listener);
+        }
+
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        @Override
+        public void unregisterAuthenticationStateListener(
+                @NonNull AuthenticationStateListener listener) {
+            super.unregisterAuthenticationStateListener_enforcePermission();
+
+            mAuthenticationStateListeners.unregisterAuthenticationStateListener(listener);
+        }
     }
 
     public FaceService(Context context) {
@@ -817,6 +871,7 @@
         mLockoutResetDispatcher = new LockoutResetDispatcher(context);
         mLockPatternUtils = new LockPatternUtils(context);
         mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
+        mAuthenticationStateListeners = new AuthenticationStateListeners();
         mRegistry = new FaceServiceRegistry(mServiceWrapper, biometricServiceSupplier);
         mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
             @Override
@@ -837,8 +892,8 @@
             try {
                 final SensorProps[] props = face.getSensorProps();
                 return new FaceProvider(getContext(),
-                        mBiometricStateCallback, props, name, mLockoutResetDispatcher,
-                        BiometricContext.getInstance(getContext()),
+                        mBiometricStateCallback, mAuthenticationStateListeners, props, name,
+                        mLockoutResetDispatcher, BiometricContext.getInstance(getContext()),
                         false /* resetLockoutRequiresChallenge */);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
@@ -850,7 +905,7 @@
         if (Flags.deHidl()) {
             mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
                     ((filteredSensorProps, resetLockoutRequiresChallenge) -> new FaceProvider(
-                            getContext(), mBiometricStateCallback,
+                            getContext(), mBiometricStateCallback, mAuthenticationStateListeners,
                             filteredSensorProps.second,
                             filteredSensorProps.first, mLockoutResetDispatcher,
                             BiometricContext.getInstance(getContext()),
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
index af46f44..3d61f99 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
@@ -53,12 +53,10 @@
         mAidlResponseHandler = aidlResponseHandler;
     }
 
-    /** The underlying {@link ISession}. */
     @NonNull public ISession getSession() {
         return mSession;
     }
 
-    /** The user id associated with the session. */
     public int getUserId() {
         return mUserId;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 470dc4b..f35de93 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.face.aidl;
 
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.NotificationManager;
@@ -37,12 +39,14 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.OperationContextExt;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
@@ -76,6 +80,8 @@
     private ICancellationSignal mCancellationSignal;
     @Nullable
     private final SensorPrivacyManager mSensorPrivacyManager;
+    @NonNull
+    private final AuthenticationStateListeners mAuthenticationStateListeners;
     @FaceManager.FaceAcquired
     private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN;
 
@@ -88,11 +94,13 @@
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @NonNull UsageStats usageStats,
             @NonNull LockoutTracker lockoutCache, boolean allowBackgroundAuthentication,
-            @Authenticators.Types int sensorStrength) {
+            @Authenticators.Types int sensorStrength,
+            @NonNull AuthenticationStateListeners authenticationStateListeners) {
         this(context, lazyDaemon, token, requestId, listener, operationId,
                 restricted, options, cookie, requireConfirmation, logger, biometricContext,
                 isStrongBiometric, usageStats, lockoutCache, allowBackgroundAuthentication,
-                context.getSystemService(SensorPrivacyManager.class), sensorStrength);
+                context.getSystemService(SensorPrivacyManager.class), sensorStrength,
+                authenticationStateListeners);
     }
 
     @VisibleForTesting
@@ -106,7 +114,8 @@
             boolean isStrongBiometric, @NonNull UsageStats usageStats,
             @NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication,
             SensorPrivacyManager sensorPrivacyManager,
-            @Authenticators.Types int biometricStrength) {
+            @Authenticators.Types int biometricStrength,
+            @NonNull AuthenticationStateListeners authenticationStateListeners) {
         super(context, lazyDaemon, token, listener, operationId, restricted,
                 options, cookie, requireConfirmation, logger, biometricContext,
                 isStrongBiometric, null /* taskStackListener */, lockoutTracker,
@@ -117,6 +126,7 @@
         mNotificationManager = context.getSystemService(NotificationManager.class);
         mSensorPrivacyManager = sensorPrivacyManager;
         mAuthSessionCoordinator = biometricContext.getAuthSessionCoordinator();
+        mAuthenticationStateListeners = authenticationStateListeners;
 
         final Resources resources = getContext().getResources();
         mBiometricPromptIgnoreList = resources.getIntArray(
@@ -153,7 +163,11 @@
                         0 /* vendorCode */);
                 mCallback.onClientFinished(this, false /* success */);
             } else {
-                mCancellationSignal = doAuthenticate();
+                if (Flags.deHidl()) {
+                    startAuthenticate();
+                } else {
+                    mCancellationSignal = doAuthenticate();
+                }
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting auth", e);
@@ -182,6 +196,33 @@
         }
     }
 
+    private void startAuthenticate() throws RemoteException {
+        final AidlSession session = getFreshDaemon();
+
+        if (session.hasContextMethods()) {
+            final OperationContextExt opContext = getOperationContext();
+            getBiometricContext().subscribe(opContext, ctx -> {
+                try {
+                    mCancellationSignal = session.getSession().authenticateWithContext(
+                            mOperationId, ctx);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception when requesting auth", e);
+                    onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE,
+                            0 /* vendorCode */);
+                    mCallback.onClientFinished(this, false /* success */);
+                }
+            }, ctx -> {
+                try {
+                    session.getSession().onContextChanged(ctx);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to notify context changed", e);
+                }
+            }, getOptions());
+        } else {
+            mCancellationSignal = session.getSession().authenticate(mOperationId);
+        }
+    }
+
     @Override
     protected void stopHalOperation() {
         unsubscribeBiometricContext();
@@ -230,6 +271,16 @@
                 0 /* error */,
                 0 /* vendorError */,
                 getTargetUserId()));
+
+        if (reportBiometricAuthAttempts()) {
+            if (authenticated) {
+                mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+                        getTargetUserId());
+            } else {
+                mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+                        getTargetUserId());
+            }
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index a529fb9..5ddddda 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -29,6 +29,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.OperationContextExt;
@@ -111,7 +112,11 @@
         }
 
         try {
-            mCancellationSignal = doDetectInteraction();
+            if (Flags.deHidl()) {
+                startDetect();
+            } else {
+                mCancellationSignal = doDetectInteraction();
+            }
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting face detect", e);
             mCallback.onClientFinished(this, false /* success */);
@@ -138,6 +143,30 @@
         }
     }
 
+    private void startDetect() throws RemoteException {
+        final AidlSession session = getFreshDaemon();
+
+        if (session.hasContextMethods()) {
+            final OperationContextExt opContext = getOperationContext();
+            getBiometricContext().subscribe(opContext, ctx -> {
+                try {
+                    mCancellationSignal = session.getSession().detectInteractionWithContext(ctx);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception when requesting face detect", e);
+                    mCallback.onClientFinished(this, false /* success */);
+                }
+            }, ctx -> {
+                try {
+                    session.getSession().onContextChanged(ctx);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to notify context changed", e);
+                }
+            }, mOptions);
+        } else {
+            mCancellationSignal = session.getSession().detectInteraction();
+        }
+    }
+
     @Override
     public void onInteractionDetected() {
         vibrateSuccess();
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index 0af6e40..f5c4529 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -36,6 +36,7 @@
 import android.view.Surface;
 
 import com.android.internal.R;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
@@ -187,7 +188,11 @@
                 features[i] = featureList.get(i);
             }
 
-            mCancellationSignal = doEnroll(features);
+            if (Flags.deHidl()) {
+                startEnroll(features);
+            } else {
+                mCancellationSignal = doEnroll(features);
+            }
         } catch (RemoteException | IllegalArgumentException e) {
             Slog.e(TAG, "Exception when requesting enroll", e);
             onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
@@ -231,6 +236,48 @@
         }
     }
 
+    private void startEnroll(byte[] features) throws RemoteException {
+        final AidlSession session = getFreshDaemon();
+        final HardwareAuthToken hat =
+                HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
+
+        if (session.hasContextMethods()) {
+            final OperationContextExt opContext = getOperationContext();
+            getBiometricContext().subscribe(opContext, ctx -> {
+                try {
+                    if (session.supportsFaceEnrollOptions()) {
+                        FaceEnrollOptions options = new FaceEnrollOptions();
+                        options.hardwareAuthToken = hat;
+                        options.enrollmentType = EnrollmentType.DEFAULT;
+                        options.features = features;
+                        options.nativeHandlePreview = null;
+                        options.context = ctx;
+                        options.surfacePreview = mPreviewSurface;
+                        mCancellationSignal = session.getSession().enrollWithOptions(options);
+                    } else {
+                        mCancellationSignal = session.getSession().enrollWithContext(
+                                hat, EnrollmentType.DEFAULT, features, mHwPreviewHandle, ctx);
+                    }
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Exception when requesting enroll", e);
+                    onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS,
+                            0 /* vendorCode */);
+                    mCallback.onClientFinished(this, false /* success */);
+                }
+            }, ctx -> {
+                try {
+                    session.getSession().onContextChanged(ctx);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to notify context changed", e);
+                }
+            }, null /* options */);
+        } else {
+            mCancellationSignal = session.getSession().enroll(hat, EnrollmentType.DEFAULT, features,
+                    mHwPreviewHandle);
+        }
+    }
+
+
     @Override
     protected void stopHalOperation() {
         unsubscribeBiometricContext();
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 9fa15b8..d01c268 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -39,6 +39,7 @@
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Binder;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
@@ -58,6 +59,7 @@
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -88,6 +90,8 @@
  * Provider for a single instance of the {@link IFace} HAL.
  */
 public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
+
+    private static final String TAG = "FaceProvider";
     private static final int ENROLL_TIMEOUT_SEC = 75;
 
     private boolean mTestHalEnabled;
@@ -100,6 +104,8 @@
     @NonNull
     private final BiometricStateCallback mBiometricStateCallback;
     @NonNull
+    private final AuthenticationStateListeners mAuthenticationStateListeners;
+    @NonNull
     private final String mHalInstanceName;
     @NonNull
     private final Handler mHandler;
@@ -153,29 +159,38 @@
 
     public FaceProvider(@NonNull Context context,
             @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             @NonNull SensorProps[] props,
             @NonNull String halInstanceName,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull BiometricContext biometricContext,
             boolean resetLockoutRequiresChallenge) {
-        this(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
-                biometricContext, null /* daemon */, resetLockoutRequiresChallenge,
-                false /* testHalEnabled */);
+        this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
+                lockoutResetDispatcher, biometricContext, null /* daemon */, getHandler(),
+                resetLockoutRequiresChallenge, false /* testHalEnabled */);
     }
 
     @VisibleForTesting FaceProvider(@NonNull Context context,
             @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             @NonNull SensorProps[] props,
             @NonNull String halInstanceName,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull BiometricContext biometricContext,
-            @Nullable IFace daemon, boolean resetLockoutRequiresChallenge,
+            @Nullable IFace daemon,
+            @NonNull Handler handler,
+            boolean resetLockoutRequiresChallenge,
             boolean testHalEnabled) {
         mContext = context;
         mBiometricStateCallback = biometricStateCallback;
+        mAuthenticationStateListeners = authenticationStateListeners;
         mHalInstanceName = halInstanceName;
         mFaceSensors = new SensorList<>(ActivityManager.getService());
-        mHandler = new Handler(Looper.getMainLooper());
+        if (Flags.deHidl()) {
+            mHandler = handler;
+        } else {
+            mHandler = new Handler(Looper.getMainLooper());
+        }
         mUsageStats = new UsageStats(context);
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mActivityTaskManager = ActivityTaskManager.getInstance();
@@ -189,6 +204,13 @@
         initSensors(resetLockoutRequiresChallenge, props);
     }
 
+    @NonNull
+    private static Handler getHandler() {
+        HandlerThread handlerThread = new HandlerThread(TAG);
+        handlerThread.start();
+        return new Handler(handlerThread.getLooper());
+    }
+
     private void initAuthenticationBroadcastReceiver() {
         new AuthenticationStatsBroadcastReceiver(
                 mContext,
@@ -230,8 +252,8 @@
                         prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
                         prop.supportsDetectInteraction, prop.halControlsPreview,
                         false /* resetLockoutRequiresChallenge */);
-                final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this,
-                        mContext, mHandler, internalProp, mLockoutResetDispatcher,
+                final Sensor sensor = new Sensor(this,
+                        mContext, mHandler, internalProp,
                         mBiometricContext);
                 sensor.init(mLockoutResetDispatcher, this);
                 final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
@@ -250,9 +272,8 @@
 
     private void addHidlSensors(SensorProps prop, boolean resetLockoutRequiresChallenge) {
         final int sensorId = prop.commonProps.sensorId;
-        final Sensor sensor = new HidlToAidlSensorAdapter(getTag() + "/" + sensorId, this,
-                mContext, mHandler, prop, mLockoutResetDispatcher,
-                mBiometricContext, resetLockoutRequiresChallenge,
+        final Sensor sensor = new HidlToAidlSensorAdapter(this, mContext, mHandler, prop,
+                mLockoutResetDispatcher, mBiometricContext, resetLockoutRequiresChallenge,
                 () -> {
                     //TODO: update to make this testable
                     scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
@@ -279,8 +300,7 @@
 
     private void addAidlSensors(SensorProps prop, boolean resetLockoutRequiresChallenge) {
         final int sensorId = prop.commonProps.sensorId;
-        final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext,
-                mHandler, prop, mLockoutResetDispatcher, mBiometricContext,
+        final Sensor sensor = new Sensor(this, mContext, mHandler, prop, mBiometricContext,
                 resetLockoutRequiresChallenge);
         sensor.init(mLockoutResetDispatcher, this);
         final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
@@ -296,7 +316,7 @@
     }
 
     private String getTag() {
-        return "FaceProvider/" + mHalInstanceName;
+        return TAG + "/" + mHalInstanceName;
     }
 
     boolean hasHalInstance() {
@@ -596,7 +616,8 @@
                             mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric,
                     mUsageStats, lockoutTracker,
-                    allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
+                    allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId),
+                    mAuthenticationStateListeners);
             scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
                 @Override
                 public void onClientStarted(
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
index 0110ae9..e5ae8e3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.hardware.biometrics.face.ISession;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -30,10 +31,10 @@
 
 import java.util.function.Supplier;
 
-public class FaceStopUserClient extends StopUserClient<AidlSession> {
+public class FaceStopUserClient extends StopUserClient<ISession> {
     private static final String TAG = "FaceStopUserClient";
 
-    public FaceStopUserClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+    public FaceStopUserClient(@NonNull Context context, @NonNull Supplier<ISession> lazyDaemon,
             @Nullable IBinder token, int userId, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull UserStoppedCallback callback) {
@@ -49,7 +50,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().getSession().close();
+            getFreshDaemon().close();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
             getCallback().onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 3e5c599..635e79a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -58,6 +58,7 @@
 import com.android.server.biometrics.sensors.StartUserClient;
 import com.android.server.biometrics.sensors.StopUserClient;
 import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.UserSwitchProvider;
 import com.android.server.biometrics.sensors.face.FaceUtils;
 
 import java.util.ArrayList;
@@ -71,15 +72,16 @@
  */
 public class Sensor {
 
+    private static final String TAG = "Sensor";
+
     private boolean mTestHalEnabled;
 
-    @NonNull private final String mTag;
     @NonNull private final FaceProvider mProvider;
     @NonNull private final Context mContext;
     @NonNull private final IBinder mToken;
     @NonNull private final Handler mHandler;
     @NonNull private final FaceSensorPropertiesInternal mSensorProperties;
-    @NonNull private BiometricScheduler mScheduler;
+    @NonNull private BiometricScheduler<IFace, ISession> mScheduler;
     @Nullable private LockoutTracker mLockoutTracker;
     @NonNull private final Map<Integer, Long> mAuthenticatorIds;
 
@@ -88,11 +90,9 @@
     @NonNull BiometricContext mBiometricContext;
 
 
-    Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
+    Sensor(@NonNull FaceProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull BiometricContext biometricContext, @Nullable AidlSession session) {
-        mTag = tag;
+            @NonNull BiometricContext biometricContext) {
         mProvider = provider;
         mContext = context;
         mToken = new Binder();
@@ -102,105 +102,135 @@
         mAuthenticatorIds = new HashMap<>();
     }
 
-    Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
-            @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull BiometricContext biometricContext) {
-        this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
-                biometricContext, null);
-    }
-
-    public Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
+    public Sensor(@NonNull FaceProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull SensorProps prop,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull BiometricContext biometricContext,
             boolean resetLockoutRequiresChallenge) {
-        this(tag, provider, context, handler,
+        this(provider, context, handler,
                 getFaceSensorPropertiesInternal(prop, resetLockoutRequiresChallenge),
-                lockoutResetDispatcher, biometricContext, null);
+                biometricContext);
     }
 
     /**
      * Initialize biometric scheduler, lockout tracker and session for the sensor.
      */
-    public void init(LockoutResetDispatcher lockoutResetDispatcher,
+    public void init(@NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull FaceProvider provider) {
+        if (Flags.deHidl()) {
+            setScheduler(getBiometricSchedulerForInit(lockoutResetDispatcher, provider));
+        } else {
+            setScheduler(getUserAwareBiometricSchedulerForInit(lockoutResetDispatcher, provider));
+        }
+        mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
+        mLockoutTracker = new LockoutCache();
+    }
+
+    private BiometricScheduler<IFace, ISession> getBiometricSchedulerForInit(
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull FaceProvider provider) {
+        return new BiometricScheduler<>(mHandler,
+                BiometricScheduler.SENSOR_TYPE_FACE,
+                null /* gestureAvailabilityDispatcher */,
+                () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
+                new UserSwitchProvider<IFace, ISession>() {
+                    @NonNull
+                    @Override
+                    public StopUserClient<ISession> getStopUserClient(int userId) {
+                        return new FaceStopUserClient(mContext,
+                                () -> mLazySession.get().getSession(), mToken, userId,
+                                mSensorProperties.sensorId, BiometricLogger.ofUnknown(mContext),
+                                mBiometricContext, () -> mCurrentSession = null);
+                    }
+
+                    @NonNull
+                    @Override
+                    public StartUserClient<IFace, ISession> getStartUserClient(int newUserId) {
+                        final int sensorId = mSensorProperties.sensorId;
+                        final AidlResponseHandler resultController = new AidlResponseHandler(
+                                mContext, mScheduler, sensorId, newUserId,
+                                mLockoutTracker, lockoutResetDispatcher,
+                                mBiometricContext.getAuthSessionCoordinator(), () -> {
+                        },
+                                new AidlResponseHandler.AidlResponseHandlerCallback() {
+                                    @Override
+                                    public void onEnrollSuccess() {
+                                        mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId,
+                                                newUserId);
+                                        mProvider.scheduleInvalidationRequest(sensorId,
+                                                newUserId);
+                                    }
+
+                                    @Override
+                                    public void onHardwareUnavailable() {
+                                        Slog.e(TAG, "Face sensor hardware unavailable.");
+                                        mCurrentSession = null;
+                                    }
+                                });
+
+                        return Sensor.this.getStartUserClient(resultController, sensorId,
+                                newUserId, provider);
+                    }
+                });
+    }
+
+    private UserAwareBiometricScheduler<IFace, ISession> getUserAwareBiometricSchedulerForInit(
+            LockoutResetDispatcher lockoutResetDispatcher,
             FaceProvider provider) {
-        mScheduler = new UserAwareBiometricScheduler(mTag,
+        return new UserAwareBiometricScheduler<>(TAG,
                 BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */,
                 () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
                 new UserAwareBiometricScheduler.UserSwitchCallback() {
                     @NonNull
                     @Override
-                    public StopUserClient<?> getStopUserClient(int userId) {
-                        return new FaceStopUserClient(mContext, mLazySession, mToken, userId,
-                                mSensorProperties.sensorId,
-                                BiometricLogger.ofUnknown(mContext), mBiometricContext,
-                                () -> mCurrentSession = null);
+                    public StopUserClient<ISession> getStopUserClient(int userId) {
+                        return new FaceStopUserClient(mContext,
+                                () -> mLazySession.get().getSession(), mToken, userId,
+                                mSensorProperties.sensorId, BiometricLogger.ofUnknown(mContext),
+                                mBiometricContext, () -> mCurrentSession = null);
                     }
 
                     @NonNull
                     @Override
-                    public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+                    public StartUserClient<IFace, ISession> getStartUserClient(int newUserId) {
                         final int sensorId = mSensorProperties.sensorId;
+                        final AidlResponseHandler resultController = new AidlResponseHandler(
+                                mContext, mScheduler, sensorId, newUserId,
+                                mLockoutTracker, lockoutResetDispatcher,
+                                mBiometricContext.getAuthSessionCoordinator(), () -> {
+                                    Slog.e(TAG, "Face sensor hardware unavailable.");
+                                    mCurrentSession = null;
+                                });
 
-                        final AidlResponseHandler resultController;
-                        if (Flags.deHidl()) {
-                            resultController = new AidlResponseHandler(
-                                    mContext, mScheduler, sensorId, newUserId,
-                                    mLockoutTracker, lockoutResetDispatcher,
-                                    mBiometricContext.getAuthSessionCoordinator(), () -> {},
-                                    new AidlResponseHandler.AidlResponseHandlerCallback() {
-                                        @Override
-                                        public void onEnrollSuccess() {
-                                            mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId,
-                                                    newUserId);
-                                            mProvider.scheduleInvalidationRequest(sensorId,
-                                                    newUserId);
-                                        }
-
-                                        @Override
-                                        public void onHardwareUnavailable() {
-                                            Slog.e(mTag, "Face sensor hardware unavailable.");
-                                            mCurrentSession = null;
-                                        }
-                                    });
-                        } else {
-                            resultController = new AidlResponseHandler(
-                                    mContext, mScheduler, sensorId, newUserId,
-                                    mLockoutTracker, lockoutResetDispatcher,
-                                    mBiometricContext.getAuthSessionCoordinator(), () -> {
-                                Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
-                                mCurrentSession = null;
-                            });
-                        }
-
-                        final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
-                                (userIdStarted, newSession, halInterfaceVersion) -> {
-                                    Slog.d(mTag, "New session created for user: "
-                                            + userIdStarted + " with hal version: "
-                                            + halInterfaceVersion);
-                                    mCurrentSession = new AidlSession(halInterfaceVersion,
-                                            newSession, userIdStarted, resultController);
-                                    if (FaceUtils.getLegacyInstance(sensorId)
-                                            .isInvalidationInProgress(mContext, userIdStarted)) {
-                                        Slog.w(mTag,
-                                                "Scheduling unfinished invalidation request for "
-                                                        + "sensor: "
-                                                        + sensorId
-                                                        + ", user: " + userIdStarted);
-                                        provider.scheduleInvalidationRequest(sensorId,
-                                                userIdStarted);
-                                    }
-                                };
-
-                        return new FaceStartUserClient(mContext, provider::getHalInstance,
-                                mToken, newUserId, mSensorProperties.sensorId,
-                                BiometricLogger.ofUnknown(mContext), mBiometricContext,
-                                resultController, userStartedCallback);
+                        return Sensor.this.getStartUserClient(resultController, sensorId,
+                                newUserId, provider);
                     }
                 });
-        mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
-        mLockoutTracker = new LockoutCache();
+    }
+
+    private FaceStartUserClient getStartUserClient(@NonNull AidlResponseHandler resultController,
+            int sensorId, int newUserId, @NonNull FaceProvider provider) {
+        final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
+                (userIdStarted, newSession, halInterfaceVersion) -> {
+                    Slog.d(TAG, "New face session created for user: "
+                            + userIdStarted + " with hal version: "
+                            + halInterfaceVersion);
+                    mCurrentSession = new AidlSession(halInterfaceVersion,
+                            newSession, userIdStarted, resultController);
+                    if (FaceUtils.getLegacyInstance(sensorId)
+                            .isInvalidationInProgress(mContext, userIdStarted)) {
+                        Slog.w(TAG,
+                                "Scheduling unfinished invalidation request for "
+                                        + "face sensor: "
+                                        + sensorId
+                                        + ", user: " + userIdStarted);
+                        provider.scheduleInvalidationRequest(sensorId,
+                                userIdStarted);
+                    }
+                };
+
+        return new FaceStartUserClient(mContext, provider::getHalInstance, mToken, newUserId,
+                mSensorProperties.sensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext,
+                resultController, userStartedCallback);
     }
 
     private static FaceSensorPropertiesInternal getFaceSensorPropertiesInternal(SensorProps prop,
@@ -213,13 +243,11 @@
                         info.softwareVersion));
             }
         }
-        final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
+        return new FaceSensorPropertiesInternal(
                 prop.commonProps.sensorId, prop.commonProps.sensorStrength,
                 prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
                 prop.supportsDetectInteraction, prop.halControlsPreview,
                 resetLockoutRequiresChallenge);
-
-        return internalProp;
     }
 
     @NonNull public Supplier<AidlSession> getLazySession() {
@@ -243,7 +271,7 @@
                 mProvider, this);
     }
 
-    @NonNull public BiometricScheduler getScheduler() {
+    @NonNull public BiometricScheduler<IFace, ISession> getScheduler() {
         return mScheduler;
     }
 
@@ -259,17 +287,17 @@
     }
 
     void setTestHalEnabled(boolean enabled) {
-        Slog.w(mTag, "setTestHalEnabled: " + enabled);
+        Slog.w(TAG, "Face setTestHalEnabled: " + enabled);
         if (enabled != mTestHalEnabled) {
             // The framework should retrieve a new session from the HAL.
             try {
                 if (mCurrentSession != null) {
                     // TODO(181984005): This should be scheduled instead of directly invoked
-                    Slog.d(mTag, "Closing old session");
+                    Slog.d(TAG, "Closing old face session");
                     mCurrentSession.getSession().close();
                 }
             } catch (RemoteException e) {
-                Slog.e(mTag, "RemoteException", e);
+                Slog.e(TAG, "RemoteException", e);
             }
             mCurrentSession = null;
         }
@@ -308,7 +336,7 @@
     public void onBinderDied() {
         final BaseClientMonitor client = mScheduler.getCurrentClient();
         if (client != null && client.isInterruptable()) {
-            Slog.e(mTag, "Sending ERROR_HW_UNAVAILABLE for client: " + client);
+            Slog.e(TAG, "Sending face hardware unavailable error for client: " + client);
             final ErrorConsumer errorConsumer = (ErrorConsumer) client;
             errorConsumer.onError(FaceManager.FACE_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 46ce0b6..48a676c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -64,6 +64,7 @@
 import com.android.server.biometrics.sensors.AcquisitionClient;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -119,8 +120,10 @@
 
     @NonNull private final FaceSensorPropertiesInternal mSensorProperties;
     @NonNull private final BiometricStateCallback mBiometricStateCallback;
+    @NonNull
+    private final AuthenticationStateListeners mAuthenticationStateListeners;
     @NonNull private final Context mContext;
-    @NonNull private final BiometricScheduler mScheduler;
+    @NonNull private final BiometricScheduler<IBiometricsFace, AidlSession> mScheduler;
     @NonNull private final Handler mHandler;
     @NonNull private final Supplier<IBiometricsFace> mLazyDaemon;
     @NonNull private final LockoutHalImpl mLockoutTracker;
@@ -163,14 +166,15 @@
         private final int mSensorId;
         @NonNull private final Context mContext;
         @NonNull private final Handler mHandler;
-        @NonNull private final BiometricScheduler mScheduler;
+        @NonNull private final BiometricScheduler<IBiometricsFace, AidlSession> mScheduler;
         @Nullable private Callback mCallback;
         @NonNull private final LockoutHalImpl mLockoutTracker;
         @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
 
 
         HalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler,
-                @NonNull BiometricScheduler scheduler, @NonNull LockoutHalImpl lockoutTracker,
+                @NonNull BiometricScheduler<IBiometricsFace, AidlSession> scheduler,
+                @NonNull LockoutHalImpl lockoutTracker,
                 @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
             mSensorId = sensorId;
             mContext = context;
@@ -349,14 +353,16 @@
     @VisibleForTesting
     Face10(@NonNull Context context,
             @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             @NonNull FaceSensorPropertiesInternal sensorProps,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull Handler handler,
-            @NonNull BiometricScheduler scheduler,
+            @NonNull BiometricScheduler<IBiometricsFace, AidlSession> scheduler,
             @NonNull BiometricContext biometricContext) {
         mSensorProperties = sensorProps;
         mContext = context;
         mBiometricStateCallback = biometricStateCallback;
+        mAuthenticationStateListeners = authenticationStateListeners;
         mSensorId = sensorProps.sensorId;
         mScheduler = scheduler;
         mHandler = handler;
@@ -391,11 +397,13 @@
 
     public static Face10 newInstance(@NonNull Context context,
             @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             @NonNull FaceSensorPropertiesInternal sensorProps,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
         final Handler handler = new Handler(Looper.getMainLooper());
-        return new Face10(context, biometricStateCallback, sensorProps, lockoutResetDispatcher,
-                handler, new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
+        return new Face10(context, biometricStateCallback, authenticationStateListeners,
+                sensorProps, lockoutResetDispatcher, handler, new BiometricScheduler<>(
+                        BiometricScheduler.SENSOR_TYPE_FACE,
                         null /* gestureAvailabilityTracker */),
                 BiometricContext.getInstance(context));
     }
@@ -844,7 +852,8 @@
                         createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
                                 mAuthenticationStatsCollector), mBiometricContext,
                         isStrongBiometric, mUsageStats, mLockoutTracker,
-                        allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId));
+                        allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId),
+                        mAuthenticationStateListeners);
         mScheduler.scheduleClientMonitor(client);
     }
 
@@ -858,7 +867,8 @@
                 createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
                         mAuthenticationStatsCollector), mBiometricContext,
                 isStrongBiometric, mLockoutTracker, mUsageStats,
-                allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId));
+                allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId),
+                mAuthenticationStateListeners);
         mScheduler.scheduleClientMonitor(client);
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 8ab8892..e44b263 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.face.hidl;
 
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.res.Resources;
@@ -36,6 +38,7 @@
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -65,6 +68,8 @@
 
     private int mLastAcquire;
     private SensorPrivacyManager mSensorPrivacyManager;
+    @NonNull
+    private final AuthenticationStateListeners mAuthenticationStateListeners;
 
     FaceAuthenticationClient(@NonNull Context context,
             @NonNull Supplier<IBiometricsFace> lazyDaemon,
@@ -75,7 +80,8 @@
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker,
             @NonNull UsageStats usageStats, boolean allowBackgroundAuthentication,
-            @Authenticators.Types int sensorStrength) {
+            @Authenticators.Types int sensorStrength,
+            @NonNull AuthenticationStateListeners authenticationStateListeners) {
         super(context, lazyDaemon, token, listener, operationId, restricted,
                 options, cookie, requireConfirmation, logger, biometricContext,
                 isStrongBiometric, null /* taskStackListener */,
@@ -84,6 +90,7 @@
         setRequestId(requestId);
         mUsageStats = usageStats;
         mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+        mAuthenticationStateListeners = authenticationStateListeners;
 
         final Resources resources = getContext().getResources();
         mBiometricPromptIgnoreList = resources.getIntArray(
@@ -186,6 +193,16 @@
                 0 /* error */,
                 0 /* vendorError */,
                 getTargetUserId()));
+
+        if (reportBiometricAuthAttempts()) {
+            if (authenticated) {
+                mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+                        getTargetUserId());
+            } else {
+                mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+                        getTargetUserId());
+            }
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
index 6355cb5..a004cae4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.hardware.biometrics.face.ISession;
 import android.hardware.biometrics.face.SensorProps;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.os.Handler;
@@ -67,8 +68,7 @@
             };
     private LockoutHalImpl mLockoutTracker;
 
-    public HidlToAidlSensorAdapter(@NonNull String tag,
-            @NonNull FaceProvider provider,
+    public HidlToAidlSensorAdapter(@NonNull FaceProvider provider,
             @NonNull Context context,
             @NonNull Handler handler,
             @NonNull SensorProps prop,
@@ -76,15 +76,14 @@
             @NonNull BiometricContext biometricContext,
             boolean resetLockoutRequiresChallenge,
             @NonNull Runnable internalCleanupAndGetFeatureRunnable) {
-        this(tag, provider, context, handler, prop, lockoutResetDispatcher, biometricContext,
+        this(provider, context, handler, prop, lockoutResetDispatcher, biometricContext,
                 resetLockoutRequiresChallenge, internalCleanupAndGetFeatureRunnable,
                 new AuthSessionCoordinator(), null /* daemon */,
                 null /* onEnrollSuccessCallback */);
     }
 
     @VisibleForTesting
-    HidlToAidlSensorAdapter(@NonNull String tag,
-            @NonNull FaceProvider provider,
+    HidlToAidlSensorAdapter(@NonNull FaceProvider provider,
             @NonNull Context context,
             @NonNull Handler handler,
             @NonNull SensorProps prop,
@@ -95,7 +94,7 @@
             @NonNull AuthSessionCoordinator authSessionCoordinator,
             @Nullable IBiometricsFace daemon,
             @Nullable AidlResponseHandler.AidlResponseHandlerCallback aidlResponseHandlerCallback) {
-        super(tag, provider, context, handler, prop, lockoutResetDispatcher, biometricContext,
+        super(provider, context, handler, prop, biometricContext,
                 resetLockoutRequiresChallenge);
         mInternalCleanupAndGetFeatureRunnable = internalCleanupAndGetFeatureRunnable;
         mFaceProvider = provider;
@@ -124,7 +123,7 @@
 
     @Override
     public void serviceDied(long cookie) {
-        Slog.d(TAG, "HAL died.");
+        Slog.d(TAG, "Face HAL died.");
         mDaemon = null;
     }
 
@@ -140,10 +139,12 @@
     }
 
     @Override
-    public void init(LockoutResetDispatcher lockoutResetDispatcher,
-            FaceProvider provider) {
-        setScheduler(new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
-                null /* gestureAvailabilityTracker */));
+    public void init(@NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull FaceProvider provider) {
+        setScheduler(new BiometricScheduler<ISession, AidlSession>(getHandler(),
+                BiometricScheduler.SENSOR_TYPE_FACE,
+                null /* gestureAvailabilityTracker */, () -> mCurrentUserId,
+                null /* userSwitchProvider */));
         setLazySession(this::getSession);
         mLockoutTracker = new LockoutHalImpl();
     }
@@ -188,7 +189,7 @@
             return mDaemon;
         }
 
-        Slog.d(TAG, "Daemon was null, reconnecting, current operation: "
+        Slog.d(TAG, "Face daemon was null, reconnecting, current operation: "
                 + getScheduler().getCurrentClient());
 
         try {
@@ -213,7 +214,7 @@
     }
 
     @VisibleForTesting void handleUserChanged(int newUserId) {
-        Slog.d(TAG, "User changed. Current user is " + newUserId);
+        Slog.d(TAG, "User changed. Current user for face sensor is " + newUserId);
         mSession = null;
         mCurrentUserId = newUserId;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
index 5daf2d4..fa95361 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
@@ -282,7 +282,7 @@
 
     @Override
     public ICancellationSignal enrollWithOptions(FaceEnrollOptions options) {
-        //Unsupported in HIDL
+        Slog.e(TAG, "enrollWithOptions unsupported in HIDL");
         return null;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
index 8ff105b..0d4dac0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
@@ -51,12 +51,10 @@
         mAidlResponseHandler = aidlResponseHandler;
     }
 
-    /** The underlying {@link ISession}. */
     @NonNull public ISession getSession() {
         return mSession;
     }
 
-    /** The user id associated with the session. */
     public int getUserId() {
         return mUserId;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 29c5a3d..6912961 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
 import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
 
 import android.annotation.NonNull;
@@ -28,6 +30,7 @@
 import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
 import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationState;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintManager;
@@ -43,6 +46,7 @@
 import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.CallbackWithProbe;
@@ -230,8 +234,16 @@
             if (sidefpsControllerRefactor()) {
                 mAuthenticationStateListeners.onAuthenticationStopped();
             }
+            if (reportBiometricAuthAttempts()) {
+                mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+                        getTargetUserId());
+            }
         } else {
             mState = STATE_STARTED_PAUSED_ATTEMPTED;
+            if (reportBiometricAuthAttempts()) {
+                mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+                        getTargetUserId());
+            }
         }
     }
 
@@ -296,7 +308,11 @@
         }
 
         try {
-            mCancellationSignal = doAuthenticate();
+            if (Flags.deHidl()) {
+                startAuthentication();
+            } else {
+                mCancellationSignal = doAuthenticate();
+            }
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
             onError(
@@ -326,6 +342,12 @@
             if (session.hasContextMethods()) {
                 try {
                     session.getSession().onContextChanged(ctx);
+                    // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
+                    if (ctx.operationState != null && ctx.operationState.getTag()
+                            == OperationState.fingerprintOperationState) {
+                        session.getSession().setIgnoreDisplayTouches(
+                                ctx.operationState.getFingerprintOperationState().isHardwareIgnoringTouches);
+                    }
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Unable to notify context changed", e);
                 }
@@ -346,6 +368,51 @@
         return cancel;
     }
 
+    private void startAuthentication() {
+        final AidlSession session = getFreshDaemon();
+        final OperationContextExt opContext = getOperationContext();
+
+        getBiometricContext().subscribe(opContext, ctx -> {
+            try {
+                if (session.hasContextMethods()) {
+                    mCancellationSignal = session.getSession().authenticateWithContext(mOperationId,
+                            ctx);
+                } else {
+                    mCancellationSignal = session.getSession().authenticate(mOperationId);
+                }
+
+                if (getBiometricContext().isAwake()) {
+                    mALSProbeCallback.getProbe().enable();
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception", e);
+                onError(
+                        BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+                        0 /* vendorCode */);
+                mSensorOverlays.hide(getSensorId());
+                if (sidefpsControllerRefactor()) {
+                    mAuthenticationStateListeners.onAuthenticationStopped();
+                }
+                mCallback.onClientFinished(this, false /* success */);
+            }
+        }, ctx -> {
+            if (session.hasContextMethods()) {
+                try {
+                    session.getSession().onContextChanged(ctx);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to notify context changed", e);
+                }
+            }
+
+            final boolean isAwake = getBiometricContext().isAwake();
+            if (isAwake) {
+                mALSProbeCallback.getProbe().enable();
+            } else {
+                mALSProbeCallback.getProbe().disable();
+            }
+        }, getOptions());
+    }
+
     @Override
     protected void stopHalOperation() {
         mSensorOverlays.hide(getSensorId());
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index e58e5ae..a7fb774 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricRequestConstants;
 import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationState;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
@@ -30,6 +31,7 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.OperationContextExt;
@@ -104,7 +106,11 @@
                 this);
 
         try {
-            mCancellationSignal = doDetectInteraction();
+            if (Flags.deHidl()) {
+                startDetectInteraction();
+            } else {
+                mCancellationSignal = doDetectInteraction();
+            }
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting finger detect", e);
             mSensorOverlays.hide(getSensorId());
@@ -122,6 +128,12 @@
             getBiometricContext().subscribe(opContext, ctx -> {
                 try {
                     session.getSession().onContextChanged(ctx);
+                    // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
+                    if (ctx.operationState != null && ctx.operationState.getTag()
+                            == OperationState.fingerprintOperationState) {
+                        session.getSession().setIgnoreDisplayTouches(
+                                ctx.operationState.getFingerprintOperationState().isHardwareIgnoringTouches);
+                    }
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Unable to notify context changed", e);
                 }
@@ -132,6 +144,32 @@
         }
     }
 
+    private void startDetectInteraction() throws RemoteException {
+        final AidlSession session = getFreshDaemon();
+
+        if (session.hasContextMethods()) {
+            final OperationContextExt opContext = getOperationContext();
+            getBiometricContext().subscribe(opContext, ctx -> {
+                try {
+                    mCancellationSignal = session.getSession().detectInteractionWithContext(
+                            ctx);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to start detect interaction", e);
+                    mSensorOverlays.hide(getSensorId());
+                    mCallback.onClientFinished(this, false /* success */);
+                }
+            }, ctx -> {
+                try {
+                    session.getSession().onContextChanged(ctx);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to notify context changed", e);
+                }
+            }, mOptions);
+        } else {
+            mCancellationSignal = session.getSession().detectInteraction();
+        }
+    }
+
     @Override
     public void onInteractionDetected() {
         vibrateSuccess();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index c0761ed..3fb9223 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -26,6 +26,7 @@
 import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
 import android.hardware.biometrics.BiometricStateListener;
 import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationState;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
@@ -38,6 +39,7 @@
 import android.util.Slog;
 import android.view.accessibility.AccessibilityManager;
 
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
@@ -199,7 +201,11 @@
 
         BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
         try {
-            mCancellationSignal = doEnroll();
+            if (Flags.deHidl()) {
+                startEnroll();
+            } else {
+                mCancellationSignal = doEnroll();
+            }
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting enroll", e);
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS,
@@ -220,6 +226,12 @@
             getBiometricContext().subscribe(opContext, ctx -> {
                 try {
                     session.getSession().onContextChanged(ctx);
+                    // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
+                    if (ctx.operationState != null && ctx.operationState.getTag()
+                            == OperationState.fingerprintOperationState) {
+                        session.getSession().setIgnoreDisplayTouches(
+                                ctx.operationState.getFingerprintOperationState().isHardwareIgnoringTouches);
+                    }
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Unable to notify context changed", e);
                 }
@@ -230,6 +242,35 @@
         }
     }
 
+    private void startEnroll() throws RemoteException {
+        final AidlSession session = getFreshDaemon();
+        final HardwareAuthToken hat =
+                HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
+
+        if (session.hasContextMethods()) {
+            final OperationContextExt opContext = getOperationContext();
+            getBiometricContext().subscribe(opContext, ctx -> {
+                try {
+                    mCancellationSignal = session.getSession().enrollWithContext(
+                            hat, ctx);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception when requesting enroll", e);
+                    onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS,
+                            0 /* vendorCode */);
+                    mCallback.onClientFinished(this, false /* success */);
+                }
+            }, ctx -> {
+                try {
+                    session.getSession().onContextChanged(ctx);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to notify context changed", e);
+                }
+            }, null /* options */);
+        } else {
+            mCancellationSignal = session.getSession().enroll(hat);
+        }
+    }
+
     @Override
     protected void stopHalOperation() {
         mSensorOverlays.hide(getSensorId());
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 88a11d9..c0388d1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -46,6 +46,7 @@
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Binder;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
@@ -102,6 +103,8 @@
 @SuppressWarnings("deprecation")
 public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvider {
 
+    private static final String TAG = "FingerprintProvider";
+
     private boolean mTestHalEnabled;
 
     @NonNull
@@ -172,7 +175,7 @@
             boolean resetLockoutRequiresHardwareAuthToken) {
         this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
                 lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext,
-                null /* daemon */, resetLockoutRequiresHardwareAuthToken,
+                null /* daemon */, getHandler(), resetLockoutRequiresHardwareAuthToken,
                 false /* testHalEnabled */);
     }
 
@@ -184,6 +187,7 @@
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull BiometricContext biometricContext,
             @Nullable IFingerprint daemon,
+            @NonNull Handler handler,
             boolean resetLockoutRequiresHardwareAuthToken,
             boolean testHalEnabled) {
         mContext = context;
@@ -191,7 +195,11 @@
         mAuthenticationStateListeners = authenticationStateListeners;
         mHalInstanceName = halInstanceName;
         mFingerprintSensors = new SensorList<>(ActivityManager.getService());
-        mHandler = new Handler(Looper.getMainLooper());
+        if (Flags.deHidl()) {
+            mHandler = handler;
+        } else {
+            mHandler = new Handler(Looper.getMainLooper());
+        }
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mActivityTaskManager = ActivityTaskManager.getInstance();
         mTaskStackListener = new BiometricTaskStackListener();
@@ -204,6 +212,13 @@
         initSensors(resetLockoutRequiresHardwareAuthToken, props, gestureAvailabilityDispatcher);
     }
 
+    @NonNull
+    private static Handler getHandler() {
+        HandlerThread handlerThread = new HandlerThread(TAG);
+        handlerThread.start();
+        return new Handler(handlerThread.getLooper());
+    }
+
     private void initAuthenticationBroadcastReceiver() {
         new AuthenticationStatsBroadcastReceiver(
                 mContext,
@@ -262,11 +277,9 @@
                                                                 location.sensorLocationY,
                                                                 location.sensorRadius))
                                                 .collect(Collectors.toList()));
-                final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext,
-                        mHandler, internalProp, mLockoutResetDispatcher,
-                        gestureAvailabilityDispatcher, mBiometricContext);
-                sensor.init(gestureAvailabilityDispatcher,
-                        mLockoutResetDispatcher);
+                final Sensor sensor = new Sensor(this, mContext, mHandler, internalProp,
+                        mBiometricContext);
+                sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher);
                 final int sessionUserId =
                         sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
                                 sensor.getLazySession().get().getUserId();
@@ -286,10 +299,8 @@
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             boolean resetLockoutRequiresHardwareAuthToken) {
         final int sensorId = prop.commonProps.sensorId;
-        final Sensor sensor = new HidlToAidlSensorAdapter(getTag() + "/"
-                + sensorId, this, mContext, mHandler,
-                prop, mLockoutResetDispatcher, gestureAvailabilityDispatcher,
-                mBiometricContext, resetLockoutRequiresHardwareAuthToken,
+        final Sensor sensor = new HidlToAidlSensorAdapter(this, mContext, mHandler, prop,
+                mLockoutResetDispatcher, mBiometricContext, resetLockoutRequiresHardwareAuthToken,
                 () -> scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
                         null /* callback */));
         sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher);
@@ -307,14 +318,11 @@
 
     private void addAidlSensors(@NonNull SensorProps prop,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
-            List<SensorLocationInternal> workaroundLocations,
+            @NonNull List<SensorLocationInternal> workaroundLocations,
             boolean resetLockoutRequiresHardwareAuthToken) {
         final int sensorId = prop.commonProps.sensorId;
-        final Sensor sensor = new Sensor(getTag() + "/" + sensorId,
-                this, mContext, mHandler,
-                prop, mLockoutResetDispatcher, gestureAvailabilityDispatcher,
-                mBiometricContext, workaroundLocations,
-                resetLockoutRequiresHardwareAuthToken);
+        final Sensor sensor = new Sensor(this, mContext, mHandler, prop, mBiometricContext,
+                workaroundLocations, resetLockoutRequiresHardwareAuthToken);
         sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher);
         final int sessionUserId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
                 sensor.getLazySession().get().getUserId();
@@ -329,7 +337,7 @@
     }
 
     private String getTag() {
-        return "FingerprintProvider/" + mHalInstanceName;
+        return TAG + "/" + mHalInstanceName;
     }
 
     boolean hasHalInstance() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
index 2cc1879..394f045 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -30,11 +31,11 @@
 
 import java.util.function.Supplier;
 
-public class FingerprintStopUserClient extends StopUserClient<AidlSession> {
+public class FingerprintStopUserClient extends StopUserClient<ISession> {
     private static final String TAG = "FingerprintStopUserClient";
 
     public FingerprintStopUserClient(@NonNull Context context,
-            @NonNull Supplier<AidlSession> lazyDaemon, @Nullable IBinder token, int userId,
+            @NonNull Supplier<ISession> lazyDaemon, @Nullable IBinder token, int userId,
             int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull UserStoppedCallback callback) {
@@ -50,7 +51,7 @@
     @Override
     protected void startHalOperation() {
         try {
-            getFreshDaemon().getSession().close();
+            getFreshDaemon().close();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
             getCallback().onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index dd887bb..af88c62 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -59,6 +59,7 @@
 import com.android.server.biometrics.sensors.StartUserClient;
 import com.android.server.biometrics.sensors.StopUserClient;
 import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.UserSwitchProvider;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
@@ -77,15 +78,16 @@
 @SuppressWarnings("deprecation")
 public class Sensor {
 
+    private static final String TAG = "Sensor";
+
     private boolean mTestHalEnabled;
 
-    @NonNull private final String mTag;
     @NonNull private final FingerprintProvider mProvider;
     @NonNull private final Context mContext;
     @NonNull private final IBinder mToken;
     @NonNull private final Handler mHandler;
     @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
-    @NonNull private BiometricScheduler mScheduler;
+    @NonNull private BiometricScheduler<IFingerprint, ISession> mScheduler;
     @NonNull private LockoutTracker mLockoutTracker;
     @NonNull private final Map<Integer, Long> mAuthenticatorIds;
     @NonNull private final BiometricContext mBiometricContext;
@@ -93,13 +95,10 @@
     @Nullable AidlSession mCurrentSession;
     @NonNull private Supplier<AidlSession> mLazySession;
 
-    public Sensor(@NonNull String tag, @NonNull FingerprintProvider provider,
+    public Sensor(@NonNull FingerprintProvider provider,
             @NonNull Context context, @NonNull Handler handler,
             @NonNull FingerprintSensorPropertiesInternal sensorProperties,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull BiometricContext biometricContext, AidlSession session) {
-        mTag = tag;
         mProvider = provider;
         mContext = context;
         mToken = new Binder();
@@ -110,41 +109,52 @@
         mCurrentSession = session;
     }
 
-    Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
+    Sensor(@NonNull FingerprintProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull BiometricContext biometricContext) {
-        this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
-                gestureAvailabilityDispatcher, biometricContext, null);
+        this(provider, context, handler, sensorProperties,
+                biometricContext, null);
     }
 
-    Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
+    Sensor(@NonNull FingerprintProvider provider, @NonNull Context context,
             @NonNull Handler handler, @NonNull SensorProps sensorProp,
-            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull BiometricContext biometricContext,
             @NonNull List<SensorLocationInternal> workaroundLocation,
             boolean resetLockoutRequiresHardwareAuthToken) {
-        this(tag, provider, context, handler, getFingerprintSensorPropertiesInternal(sensorProp,
+        this(provider, context, handler, getFingerprintSensorPropertiesInternal(sensorProp,
                         workaroundLocation, resetLockoutRequiresHardwareAuthToken),
-                lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext, null);
+                biometricContext, null);
     }
 
     /**
      * Initialize biometric scheduler, lockout tracker and session for the sensor.
      */
-    public void init(GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
-            LockoutResetDispatcher lockoutResetDispatcher) {
-        mScheduler = new UserAwareBiometricScheduler(mTag,
+    public void init(@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+        if (Flags.deHidl()) {
+            setScheduler(getBiometricSchedulerForInit(gestureAvailabilityDispatcher,
+                    lockoutResetDispatcher));
+        } else {
+            setScheduler(getUserAwareBiometricSchedulerForInit(gestureAvailabilityDispatcher,
+                    lockoutResetDispatcher));
+        }
+        mLockoutTracker = new LockoutCache();
+        mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
+    }
+
+    private BiometricScheduler<IFingerprint, ISession> getBiometricSchedulerForInit(
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+        return new BiometricScheduler<>(mHandler,
                 BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties),
                 gestureAvailabilityDispatcher,
                 () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
-                new UserAwareBiometricScheduler.UserSwitchCallback() {
+                new UserSwitchProvider<IFingerprint, ISession>() {
                     @NonNull
                     @Override
-                    public StopUserClient<?> getStopUserClient(int userId) {
-                        return new FingerprintStopUserClient(mContext, mLazySession, mToken,
+                    public StopUserClient<ISession> getStopUserClient(int userId) {
+                        return new FingerprintStopUserClient(mContext,
+                                () -> mLazySession.get().getSession(), mToken,
                                 userId, mSensorProperties.sensorId,
                                 BiometricLogger.ofUnknown(mContext), mBiometricContext,
                                 () -> mCurrentSession = null);
@@ -152,69 +162,100 @@
 
                     @NonNull
                     @Override
-                    public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+                    public StartUserClient<IFingerprint, ISession> getStartUserClient(
+                            int newUserId) {
                         final int sensorId = mSensorProperties.sensorId;
-
-                        final AidlResponseHandler resultController;
-
-                        if (Flags.deHidl()) {
-                            resultController = new AidlResponseHandler(
-                                    mContext, mScheduler, sensorId, newUserId,
-                                    mLockoutTracker, lockoutResetDispatcher,
-                                    mBiometricContext.getAuthSessionCoordinator(), () -> {},
-                                    new AidlResponseHandler.AidlResponseHandlerCallback() {
-                                        @Override
-                                        public void onEnrollSuccess() {
-                                            mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId,
-                                                    newUserId);
-                                            mProvider.scheduleInvalidationRequest(sensorId,
-                                                    newUserId);
-                                        }
-
-                                        @Override
-                                        public void onHardwareUnavailable() {
-                                            Slog.e(mTag,
-                                                    "Fingerprint sensor hardware unavailable.");
-                                            mCurrentSession = null;
-                                        }
-                                    });
-                        } else {
-                            resultController = new AidlResponseHandler(
-                                    mContext, mScheduler, sensorId, newUserId,
-                                    mLockoutTracker, lockoutResetDispatcher,
-                                    mBiometricContext.getAuthSessionCoordinator(), () -> {
-                                Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
-                                mCurrentSession = null;
-                            });
-                        }
-
-                        final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
-                                (userIdStarted, newSession, halInterfaceVersion) -> {
-                                    Slog.d(mTag, "New session created for user: "
-                                            + userIdStarted + " with hal version: "
-                                            + halInterfaceVersion);
-                                    mCurrentSession = new AidlSession(halInterfaceVersion,
-                                            newSession, userIdStarted, resultController);
-                                    if (FingerprintUtils.getInstance(sensorId)
-                                            .isInvalidationInProgress(mContext, userIdStarted)) {
-                                        Slog.w(mTag,
-                                                "Scheduling unfinished invalidation request for "
-                                                        + "sensor: "
-                                                        + sensorId
-                                                        + ", user: " + userIdStarted);
+                        final AidlResponseHandler resultController = new AidlResponseHandler(
+                                mContext, mScheduler, sensorId, newUserId,
+                                mLockoutTracker, lockoutResetDispatcher,
+                                mBiometricContext.getAuthSessionCoordinator(), () -> {},
+                                new AidlResponseHandler.AidlResponseHandlerCallback() {
+                                    @Override
+                                    public void onEnrollSuccess() {
+                                        mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId,
+                                                newUserId);
                                         mProvider.scheduleInvalidationRequest(sensorId,
-                                                userIdStarted);
+                                                newUserId);
                                     }
-                                };
 
-                        return new FingerprintStartUserClient(mContext, mProvider::getHalInstance,
-                                mToken, newUserId, mSensorProperties.sensorId,
-                                BiometricLogger.ofUnknown(mContext), mBiometricContext,
-                                resultController, userStartedCallback);
+                                    @Override
+                                    public void onHardwareUnavailable() {
+                                        Slog.e(TAG,
+                                                "Fingerprint sensor hardware unavailable.");
+                                        mCurrentSession = null;
+                                    }
+                                });
+
+                        return Sensor.this.getStartUserClient(resultController, sensorId,
+                                newUserId);
                     }
                 });
-        mLockoutTracker = new LockoutCache();
-        mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
+    }
+
+    private UserAwareBiometricScheduler<ISession, AidlSession>
+            getUserAwareBiometricSchedulerForInit(
+                    GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+                    LockoutResetDispatcher lockoutResetDispatcher) {
+        return new UserAwareBiometricScheduler<>(TAG,
+                BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties),
+                gestureAvailabilityDispatcher,
+                () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
+                new UserAwareBiometricScheduler.UserSwitchCallback() {
+                    @NonNull
+                    @Override
+                    public StopUserClient<ISession> getStopUserClient(int userId) {
+                        return new FingerprintStopUserClient(mContext,
+                                () -> mLazySession.get().getSession(), mToken,
+                                userId, mSensorProperties.sensorId,
+                                BiometricLogger.ofUnknown(mContext), mBiometricContext,
+                                () -> mCurrentSession = null);
+                    }
+
+                    @NonNull
+                    @Override
+                    public StartUserClient<IFingerprint, ISession> getStartUserClient(
+                            int newUserId) {
+                        final int sensorId = mSensorProperties.sensorId;
+
+                        final AidlResponseHandler resultController = new AidlResponseHandler(
+                                mContext, mScheduler, sensorId, newUserId,
+                                mLockoutTracker, lockoutResetDispatcher,
+                                mBiometricContext.getAuthSessionCoordinator(), () -> {
+                                    Slog.e(TAG, "Fingerprint hardware unavailable.");
+                                    mCurrentSession = null;
+                                });
+
+                        return Sensor.this.getStartUserClient(resultController, sensorId,
+                                newUserId);
+                    }
+                });
+    }
+
+    private FingerprintStartUserClient getStartUserClient(AidlResponseHandler resultController,
+            int sensorId, int newUserId) {
+        final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
+                (userIdStarted, newSession, halInterfaceVersion) -> {
+                    Slog.d(TAG, "New fingerprint session created for user: "
+                            + userIdStarted + " with hal version: "
+                            + halInterfaceVersion);
+                    mCurrentSession = new AidlSession(halInterfaceVersion,
+                            newSession, userIdStarted, resultController);
+                    if (FingerprintUtils.getInstance(sensorId)
+                            .isInvalidationInProgress(mContext, userIdStarted)) {
+                        Slog.w(TAG,
+                                "Scheduling unfinished invalidation request for "
+                                        + "fingerprint sensor: "
+                                        + sensorId
+                                        + ", user: " + userIdStarted);
+                        mProvider.scheduleInvalidationRequest(sensorId,
+                                userIdStarted);
+                    }
+                };
+
+        return new FingerprintStartUserClient(mContext, mProvider::getHalInstance,
+                mToken, newUserId, mSensorProperties.sensorId,
+                BiometricLogger.ofUnknown(mContext), mBiometricContext,
+                resultController, userStartedCallback);
     }
 
     protected static FingerprintSensorPropertiesInternal getFingerprintSensorPropertiesInternal(
@@ -267,7 +308,7 @@
                 biometricStateCallback, mProvider, this);
     }
 
-    @NonNull public BiometricScheduler getScheduler() {
+    @NonNull public BiometricScheduler<IFingerprint, ISession> getScheduler() {
         return mScheduler;
     }
 
@@ -283,17 +324,17 @@
     }
 
     void setTestHalEnabled(boolean enabled) {
-        Slog.w(mTag, "setTestHalEnabled: " + enabled);
+        Slog.w(TAG, "Fingerprint setTestHalEnabled: " + enabled);
         if (enabled != mTestHalEnabled) {
             // The framework should retrieve a new session from the HAL.
             try {
                 if (mCurrentSession != null) {
                     // TODO(181984005): This should be scheduled instead of directly invoked
-                    Slog.d(mTag, "Closing old session");
+                    Slog.d(TAG, "Closing old fingerprint session");
                     mCurrentSession.getSession().close();
                 }
             } catch (RemoteException e) {
-                Slog.e(mTag, "RemoteException", e);
+                Slog.e(TAG, "RemoteException", e);
             }
             mCurrentSession = null;
         }
@@ -335,7 +376,7 @@
     public void onBinderDied() {
         final BaseClientMonitor client = mScheduler.getCurrentClient();
         if (client instanceof ErrorConsumer) {
-            Slog.e(mTag, "Sending ERROR_HW_UNAVAILABLE for client: " + client);
+            Slog.e(TAG, "Sending fingerprint hardware unavailable error for client: " + client);
             final ErrorConsumer errorConsumer = (ErrorConsumer) client;
             errorConsumer.onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index d3cecd0..4accf8f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -119,7 +119,7 @@
     @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners;
     private final ActivityTaskManager mActivityTaskManager;
     @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
-    private final BiometricScheduler mScheduler;
+    private final BiometricScheduler<IBiometricsFingerprint, AidlSession> mScheduler;
     private final Handler mHandler;
     private final LockoutResetDispatcher mLockoutResetDispatcher;
     private final LockoutFrameworkImpl mLockoutTracker;
@@ -198,11 +198,11 @@
         private final int mSensorId;
         @NonNull private final Context mContext;
         @NonNull final Handler mHandler;
-        @NonNull final BiometricScheduler mScheduler;
+        @NonNull final BiometricScheduler<IBiometricsFingerprint, AidlSession> mScheduler;
         @Nullable private Callback mCallback;
 
         HalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler,
-                @NonNull BiometricScheduler scheduler) {
+                @NonNull BiometricScheduler<IBiometricsFingerprint, AidlSession> scheduler) {
             mSensorId = sensorId;
             mContext = context;
             mHandler = handler;
@@ -336,7 +336,7 @@
             @NonNull BiometricStateCallback biometricStateCallback,
             @NonNull AuthenticationStateListeners authenticationStateListeners,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
-            @NonNull BiometricScheduler scheduler,
+            @NonNull BiometricScheduler<IBiometricsFingerprint, AidlSession> scheduler,
             @NonNull Handler handler,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull HalResultController controller,
@@ -389,8 +389,8 @@
             @NonNull Handler handler,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
-        final BiometricScheduler scheduler =
-                new BiometricScheduler(TAG,
+        final BiometricScheduler<IBiometricsFingerprint, AidlSession> scheduler =
+                new BiometricScheduler<>(
                         BiometricScheduler.sensorTypeFromFingerprintProperties(sensorProps),
                         gestureAvailabilityDispatcher);
         final HalResultController controller = new HalResultController(sensorProps.sensorId,
@@ -533,8 +533,8 @@
     private void scheduleUpdateActiveUserWithoutHandler(int targetUserId, boolean force) {
         final boolean hasEnrolled =
                 !getEnrolledFingerprints(mSensorProperties.sensorId, targetUserId).isEmpty();
-        final FingerprintUpdateActiveUserClient client =
-                new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId,
+        final FingerprintUpdateActiveUserClientLegacy client =
+                new FingerprintUpdateActiveUserClientLegacy(mContext, mLazyDaemon, targetUserId,
                         mContext.getOpPackageName(), mSensorProperties.sensorId,
                         createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
                                 BiometricsProtoEnums.CLIENT_UNKNOWN,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 88dae6f..9232e11 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -140,9 +140,9 @@
     private static class TestableBiometricScheduler extends BiometricScheduler {
         @NonNull private Fingerprint21UdfpsMock mFingerprint21;
 
-        TestableBiometricScheduler(@NonNull String tag, @NonNull Handler handler,
+        TestableBiometricScheduler(
                 @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
-            super(tag, BiometricScheduler.SENSOR_TYPE_FP_OTHER, gestureAvailabilityDispatcher);
+            super(BiometricScheduler.SENSOR_TYPE_FP_OTHER, gestureAvailabilityDispatcher);
         }
 
         void init(@NonNull Fingerprint21UdfpsMock fingerprint21) {
@@ -258,7 +258,7 @@
 
         final Handler handler = new Handler(Looper.getMainLooper());
         final TestableBiometricScheduler scheduler =
-                new TestableBiometricScheduler(TAG, handler, gestureAvailabilityDispatcher);
+                new TestableBiometricScheduler(gestureAvailabilityDispatcher);
         final MockHalResultController controller =
                 new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
         return new Fingerprint21UdfpsMock(context, biometricStateCallback,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 4c1d4d6..7a329e9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.fingerprint.hidl;
 
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
 import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
 
 import android.annotation.NonNull;
@@ -142,6 +144,10 @@
             if (sidefpsControllerRefactor()) {
                 mAuthenticationStateListeners.onAuthenticationStopped();
             }
+            if (reportBiometricAuthAttempts()) {
+                mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+                        getTargetUserId());
+            }
         } else {
             mState = STATE_STARTED_PAUSED_ATTEMPTED;
             final @LockoutTracker.LockoutMode int lockoutMode =
@@ -161,6 +167,10 @@
                 onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */);
                 cancel();
             }
+            if (reportBiometricAuthAttempts()) {
+                mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+                        getTargetUserId());
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
index 5c5b992..59e64cd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.biometrics.fingerprint.ISession;
 import android.os.Build;
 import android.os.Environment;
 import android.os.RemoteException;
@@ -39,8 +39,8 @@
 /**
  * Sets the HAL's current active user, and updates the framework's authenticatorId cache.
  */
-public class FingerprintUpdateActiveUserClient extends
-        StartUserClient<IBiometricsFingerprint, AidlSession> {
+public class FingerprintUpdateActiveUserClient extends StartUserClient<ISession,
+        AidlSession> {
 
     private static final String TAG = "FingerprintUpdateActiveUserClient";
     private static final String FP_DATA_DIR = "fpdata";
@@ -52,19 +52,7 @@
     private File mDirectory;
 
     FingerprintUpdateActiveUserClient(@NonNull Context context,
-            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
-            @NonNull String owner, int sensorId,
-            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
-            @NonNull Supplier<Integer> currentUserId,
-            boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
-            boolean forceUpdateAuthenticatorId) {
-        this(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext, currentUserId,
-                hasEnrolledBiometrics, authenticatorIds, forceUpdateAuthenticatorId,
-                (newUserId, newUser, halInterfaceVersion) -> {});
-    }
-
-    FingerprintUpdateActiveUserClient(@NonNull Context context,
-            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
+            @NonNull Supplier<ISession> lazyDaemon, int userId,
             @NonNull String owner, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull Supplier<Integer> currentUserId,
@@ -132,9 +120,10 @@
         try {
             final int targetId = getTargetUserId();
             Slog.d(TAG, "Setting active user: " + targetId);
-            getFreshDaemon().setActiveGroup(targetId, mDirectory.getAbsolutePath());
+            HidlToAidlSessionAdapter sessionAdapter = (HidlToAidlSessionAdapter) getFreshDaemon();
+            sessionAdapter.setActiveGroup(targetId, mDirectory.getAbsolutePath());
             mAuthenticatorIds.put(targetId, mHasEnrolledBiometrics
-                    ? getFreshDaemon().getAuthenticatorId() : 0L);
+                    ? sessionAdapter.getAuthenticatorIdForUpdateClient() : 0L);
             mUserStartedCallback.onUserStarted(targetId, null, 0);
             mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClientLegacy.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClientLegacy.java
new file mode 100644
index 0000000..fc85402
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClientLegacy.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.hidl;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.os.Build;
+import android.os.Environment;
+import android.os.RemoteException;
+import android.os.SELinux;
+import android.util.Slog;
+
+import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.HalClientMonitor;
+
+import java.io.File;
+import java.util.Map;
+import java.util.function.Supplier;
+
+/**
+ * TODO(b/304604965): Delete this class once Flags.DE_HIDL is ready for release.
+ */
+public class FingerprintUpdateActiveUserClientLegacy extends
+        HalClientMonitor<IBiometricsFingerprint> {
+    private static final String TAG = "FingerprintUpdateActiveUserClient";
+    private static final String FP_DATA_DIR = "fpdata";
+
+    private final Supplier<Integer> mCurrentUserId;
+    private final boolean mForceUpdateAuthenticatorId;
+    private final boolean mHasEnrolledBiometrics;
+    private final Map<Integer, Long> mAuthenticatorIds;
+    private File mDirectory;
+
+    FingerprintUpdateActiveUserClientLegacy(@NonNull Context context,
+            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
+            @NonNull String owner, int sensorId,
+            @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+            @NonNull Supplier<Integer> currentUserId,
+            boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
+            boolean forceUpdateAuthenticatorId) {
+        super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
+                0 /* cookie */, sensorId, logger, biometricContext);
+        mCurrentUserId = currentUserId;
+        mForceUpdateAuthenticatorId = forceUpdateAuthenticatorId;
+        mHasEnrolledBiometrics = hasEnrolledBiometrics;
+        mAuthenticatorIds = authenticatorIds;
+    }
+
+    @Override
+    public void start(@NonNull ClientMonitorCallback callback) {
+        super.start(callback);
+
+        if (mCurrentUserId.get() == getTargetUserId() && !mForceUpdateAuthenticatorId) {
+            Slog.d(TAG, "Already user: " + mCurrentUserId + ", returning");
+            callback.onClientFinished(this, true /* success */);
+            return;
+        }
+
+        int firstSdkInt = Build.VERSION.DEVICE_INITIAL_SDK_INT;
+        if (firstSdkInt < Build.VERSION_CODES.BASE) {
+            Slog.e(TAG, "First SDK version " + firstSdkInt + " is invalid; must be "
+                    + "at least VERSION_CODES.BASE");
+        }
+        File baseDir;
+        if (firstSdkInt <= Build.VERSION_CODES.O_MR1) {
+            baseDir = Environment.getUserSystemDirectory(getTargetUserId());
+        } else {
+            baseDir = Environment.getDataVendorDeDirectory(getTargetUserId());
+        }
+
+        mDirectory = new File(baseDir, FP_DATA_DIR);
+        if (!mDirectory.exists()) {
+            if (!mDirectory.mkdir()) {
+                Slog.e(TAG, "Cannot make directory: " + mDirectory.getAbsolutePath());
+                callback.onClientFinished(this, false /* success */);
+                return;
+            }
+            // Calling mkdir() from this process will create a directory with our
+            // permissions (inherited from the containing dir). This command fixes
+            // the label.
+            if (!SELinux.restorecon(mDirectory)) {
+                Slog.e(TAG, "Restorecons failed. Directory will have wrong label.");
+                callback.onClientFinished(this, false /* success */);
+                return;
+            }
+        }
+
+        startHalOperation();
+    }
+
+    @Override
+    public void unableToStart() {
+        // Nothing to do here
+    }
+
+    @Override
+    protected void startHalOperation() {
+        try {
+            final int targetId = getTargetUserId();
+            Slog.d(TAG, "Setting active user: " + targetId);
+            getFreshDaemon().setActiveGroup(targetId, mDirectory.getAbsolutePath());
+            mAuthenticatorIds.put(targetId, mHasEnrolledBiometrics
+                    ? getFreshDaemon().getAuthenticatorId() : 0L);
+            mCallback.onClientFinished(this, true /* success */);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to setActiveGroup: " + e);
+            mCallback.onClientFinished(this, false /* success */);
+        }
+    }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_UPDATE_ACTIVE_USER;
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
index 90da74c..47fdcdb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.os.Handler;
@@ -39,7 +40,7 @@
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.StartUserClient;
 import com.android.server.biometrics.sensors.StopUserClient;
-import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.UserSwitchProvider;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
@@ -71,37 +72,33 @@
                 }
             };
 
-    public HidlToAidlSensorAdapter(@NonNull String tag, @NonNull FingerprintProvider provider,
-            @NonNull Context context, @NonNull Handler handler,
+    public HidlToAidlSensorAdapter(@NonNull FingerprintProvider provider,
+            @NonNull Context context,
+            @NonNull Handler handler,
             @NonNull SensorProps prop,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull BiometricContext biometricContext,
             boolean resetLockoutRequiresHardwareAuthToken,
             @NonNull Runnable internalCleanupRunnable) {
-        this(tag, provider, context, handler, prop, lockoutResetDispatcher,
-                gestureAvailabilityDispatcher, biometricContext,
+        this(provider, context, handler, prop, lockoutResetDispatcher, biometricContext,
                 resetLockoutRequiresHardwareAuthToken, internalCleanupRunnable,
                 new AuthSessionCoordinator(), null /* daemon */,
                 null /* onEnrollSuccessCallback */);
     }
 
     @VisibleForTesting
-    HidlToAidlSensorAdapter(@NonNull String tag, @NonNull FingerprintProvider provider,
+    HidlToAidlSensorAdapter(@NonNull FingerprintProvider provider,
             @NonNull Context context, @NonNull Handler handler,
             @NonNull SensorProps prop,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull BiometricContext biometricContext,
             boolean resetLockoutRequiresHardwareAuthToken,
             @NonNull Runnable internalCleanupRunnable,
             @NonNull AuthSessionCoordinator authSessionCoordinator,
             @Nullable IBiometricsFingerprint daemon,
             @Nullable AidlResponseHandler.AidlResponseHandlerCallback aidlResponseHandlerCallback) {
-        super(tag, provider, context, handler, getFingerprintSensorPropertiesInternal(prop,
+        super(provider, context, handler, getFingerprintSensorPropertiesInternal(prop,
                         new ArrayList<>(), resetLockoutRequiresHardwareAuthToken),
-                lockoutResetDispatcher,
-                gestureAvailabilityDispatcher,
                 biometricContext, null /* session */);
         mLockoutResetDispatcher = lockoutResetDispatcher;
         mInternalCleanupRunnable = internalCleanupRunnable;
@@ -127,7 +124,7 @@
 
     @Override
     public void serviceDied(long cookie) {
-        Slog.d(TAG, "HAL died.");
+        Slog.d(TAG, "Fingerprint HAL died.");
         mSession = null;
         mDaemon = null;
     }
@@ -139,12 +136,12 @@
     }
 
     @Override
-    public void init(GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
-            LockoutResetDispatcher lockoutResetDispatcher) {
+    public void init(@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
         setLazySession(this::getSession);
-        setScheduler(new UserAwareBiometricScheduler(TAG,
+        setScheduler(new BiometricScheduler<ISession, AidlSession>(getHandler(),
                 BiometricScheduler.sensorTypeFromFingerprintProperties(getSensorProperties()),
-                gestureAvailabilityDispatcher, () -> mCurrentUserId, getUserSwitchCallback()));
+                gestureAvailabilityDispatcher, () -> mCurrentUserId, getUserSwitchProvider()));
         mLockoutTracker = new LockoutFrameworkImpl(getContext(),
                 userId -> mLockoutResetDispatcher.notifyLockoutResetCallbacks(
                         getSensorProperties().sensorId), getHandler());
@@ -152,6 +149,7 @@
 
     @Override
     @Nullable
+    @VisibleForTesting
     protected AidlSession getSessionForUser(int userId) {
         if (mSession != null && mSession.getUserId() == userId) {
             return mSession;
@@ -217,21 +215,18 @@
         }
 
         mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
-
-        Slog.d(TAG, "Fingerprint HAL ready");
-
         scheduleLoadAuthenticatorIds();
         mInternalCleanupRunnable.run();
         return mDaemon;
     }
 
-    private UserAwareBiometricScheduler.UserSwitchCallback getUserSwitchCallback() {
-        return new UserAwareBiometricScheduler.UserSwitchCallback() {
+    private UserSwitchProvider<ISession, AidlSession> getUserSwitchProvider() {
+        return new UserSwitchProvider<>() {
             @NonNull
             @Override
-            public StopUserClient<?> getStopUserClient(int userId) {
-                return new StopUserClient<IBiometricsFingerprint>(getContext(),
-                        HidlToAidlSensorAdapter.this::getIBiometricsFingerprint,
+            public StopUserClient<AidlSession> getStopUserClient(int userId) {
+                return new StopUserClient<>(getContext(),
+                        HidlToAidlSensorAdapter.this::getSession,
                         null /* token */, userId, getSensorProperties().sensorId,
                         BiometricLogger.ofUnknown(getContext()), getBiometricContext(),
                         () -> {
@@ -258,7 +253,7 @@
 
             @NonNull
             @Override
-            public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+            public StartUserClient<ISession, AidlSession> getStartUserClient(int newUserId) {
                 return getFingerprintUpdateActiveUserClient(newUserId,
                         false /* forceUpdateAuthenticatorId */);
             }
@@ -268,7 +263,7 @@
     private FingerprintUpdateActiveUserClient getFingerprintUpdateActiveUserClient(int newUserId,
             boolean forceUpdateAuthenticatorIds) {
         return new FingerprintUpdateActiveUserClient(getContext(),
-                this::getIBiometricsFingerprint, newUserId, TAG,
+                () -> getSession().getSession(), newUserId, TAG,
                 getSensorProperties().sensorId, BiometricLogger.ofUnknown(getContext()),
                 getBiometricContext(), () -> mCurrentUserId,
                 !FingerprintUtils.getInstance(getSensorProperties().sensorId)
@@ -290,7 +285,7 @@
     }
 
     @VisibleForTesting void handleUserChanged(int newUserId) {
-        Slog.d(TAG, "User changed. Current user is " + newUserId);
+        Slog.d(TAG, "User changed. Current user for fingerprint sensor is " + newUserId);
         mSession = null;
         mCurrentUserId = newUserId;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java
index 2fc00e1..b469752 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java
@@ -209,6 +209,14 @@
         return null;
     }
 
+    public long getAuthenticatorIdForUpdateClient() throws RemoteException {
+        return mSession.get().getAuthenticatorId();
+    }
+
+    public void setActiveGroup(int userId, String absolutePath) throws RemoteException {
+        mSession.get().setActiveGroup(userId, absolutePath);
+    }
+
     private void setCallback(AidlResponseHandler aidlResponseHandler) {
         mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
         try {
diff --git a/services/core/java/com/android/server/broadcastradio/TEST_MAPPING b/services/core/java/com/android/server/broadcastradio/TEST_MAPPING
new file mode 100644
index 0000000..ee4eeb6
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "imports": [
+    {
+      "path": "frameworks/base/core/tests/BroadcastRadioTests"
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index 823788f..b179783 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -137,9 +137,9 @@
     public abstract boolean isAppRunningOnAnyVirtualDevice(int uid);
 
     /**
-     * Returns true if the {@code displayId} is owned by any virtual device
+     * @return whether the input device with the given id was created by a virtual device.
      */
-    public abstract boolean isDisplayOwnedByAnyVirtualDevice(int displayId);
+    public abstract boolean isInputDeviceOwnedByVirtualDevice(int inputDeviceId);
 
     /**
      * Gets the ids of VirtualDisplays owned by a VirtualDevice.
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 9dd7daf..9102cfd 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -286,6 +286,9 @@
             return new CompatChange(changeId);
         });
         c.addPackageOverride(packageName, overrides, allowedState, versionCode);
+        Slog.d(TAG, (overrides.isEnabled() ? "Enabled" : "Disabled")
+                + " change " + changeId + (c.getName() != null ? " [" + c.getName() + "]" : "")
+                + " for " + packageName);
         invalidateCache();
         return alreadyKnown.get();
     }
@@ -372,7 +375,14 @@
         long changeId = change.getId();
         OverrideAllowedState allowedState =
                 mOverrideValidator.getOverrideAllowedState(changeId, packageName);
-        return change.removePackageOverride(packageName, allowedState, versionCode);
+        boolean overrideExists = change.removePackageOverride(packageName, allowedState,
+                versionCode);
+        if (overrideExists) {
+            Slog.d(TAG, "Reset change " + changeId
+                    + (change.getName() != null ? " [" + change.getName() + "]" : "")
+                    + " for " + packageName + " to default value.");
+        }
+        return overrideExists;
     }
 
     /**
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index df179a9..5b23364c 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -138,6 +138,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Random;
 import java.util.Set;
@@ -3870,8 +3871,12 @@
             final SyncStorageEngine.EndPoint info = syncOperation.target;
 
             if (activeSyncContext.mIsLinkedToDeath) {
-                activeSyncContext.mSyncAdapter.asBinder().unlinkToDeath(activeSyncContext, 0);
-                activeSyncContext.mIsLinkedToDeath = false;
+                try {
+                    activeSyncContext.mSyncAdapter.asBinder().unlinkToDeath(activeSyncContext, 0);
+                    activeSyncContext.mIsLinkedToDeath = false;
+                } catch (NoSuchElementException e) {
+                    Slog.wtf(TAG, "Failed to unlink active sync adapter to death", e);
+                }
             }
             final long elapsedTime = SystemClock.elapsedRealtime() - activeSyncContext.mStartTime;
             String historyMessage;
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 6ec6a12..77cb08b 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -204,6 +204,10 @@
         }
 
         @Override
+        public void onProcessStarted(int pid, int processUid, int packageUid, String packageName,
+                String processName) {}
+
+        @Override
         public void onProcessDied(int pid, int uid) {}
 
         @Override
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 8910b6e..082776a 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -624,10 +624,10 @@
         pw.println("  Current mode="
                 + autoBrightnessModeToString(mCurrentBrightnessMapper.getMode()));
 
-        pw.println();
         for (int i = 0; i < mBrightnessMappingStrategyMap.size(); i++) {
+            pw.println();
             pw.println("  Mapper for mode "
-                    + autoBrightnessModeToString(mBrightnessMappingStrategyMap.keyAt(i)) + "=");
+                    + autoBrightnessModeToString(mBrightnessMappingStrategyMap.keyAt(i)) + ":");
             mBrightnessMappingStrategyMap.valueAt(i).dump(pw,
                     mBrightnessRangeController.getNormalBrightnessMax());
         }
@@ -1159,7 +1159,7 @@
         if (mCurrentBrightnessMapper.getMode() == mode) {
             return;
         }
-        Slog.i(TAG, "Switching to mode " + mode);
+        Slog.i(TAG, "Switching to mode " + autoBrightnessModeToString(mode));
         if (mode == AUTO_BRIGHTNESS_MODE_IDLE
                 || mCurrentBrightnessMapper.getMode() == AUTO_BRIGHTNESS_MODE_IDLE) {
             switchModeAndShortTermModels(mode);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index fbac924..93addcd 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1656,14 +1656,16 @@
             ContentRecordingSession session = null;
             try {
                 if (projection != null) {
-                    IBinder launchCookie = projection.getLaunchCookie();
-                    if (launchCookie == null) {
+                    IBinder taskWindowContainerToken = projection.getLaunchCookie() == null ? null
+                            : projection.getLaunchCookie().binder;
+                    if (taskWindowContainerToken == null) {
                         // Record a particular display.
                         session = ContentRecordingSession.createDisplaySession(
                                 virtualDisplayConfig.getDisplayIdToMirror());
                     } else {
                         // Record a single task indicated by the launch cookie.
-                        session = ContentRecordingSession.createTaskSession(launchCookie);
+                        session = ContentRecordingSession.createTaskSession(
+                                taskWindowContainerToken);
                     }
                 }
             } catch (RemoteException e) {
@@ -2776,17 +2778,17 @@
     }
 
     private ScreenCapture.ScreenshotHardwareBuffer userScreenshotInternal(int displayId) {
+        final ScreenCapture.DisplayCaptureArgs captureArgs;
         synchronized (mSyncRoot) {
             final IBinder token = getDisplayToken(displayId);
             if (token == null) {
                 return null;
             }
 
-            final ScreenCapture.DisplayCaptureArgs captureArgs =
-                    new ScreenCapture.DisplayCaptureArgs.Builder(token)
+            captureArgs = new ScreenCapture.DisplayCaptureArgs.Builder(token)
                             .build();
-            return ScreenCapture.captureDisplay(captureArgs);
         }
+        return ScreenCapture.captureDisplay(captureArgs);
     }
 
     @VisibleForTesting
@@ -3390,17 +3392,10 @@
         // with the corresponding displaydevice.
         HighBrightnessModeMetadata hbmMetadata =
                 mHighBrightnessModeMetadataMapper.getHighBrightnessModeMetadataLocked(display);
-        if (mConfigParameterProvider.isNewPowerControllerFeatureEnabled()) {
-            displayPowerController = new DisplayPowerController2(
-                    mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
-                    mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
-                    () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted, mFlags);
-        } else {
-            displayPowerController = new DisplayPowerController(
-                    mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
-                    mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
-                    () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted, mFlags);
-        }
+        displayPowerController = new DisplayPowerController(
+                mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
+                mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
+                () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted, mFlags);
         mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController);
         return displayPowerController;
     }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 734381b..087cacf 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,11 +17,12 @@
 package com.android.server.display;
 
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+import static com.android.server.display.config.DisplayBrightnessMappingConfig.autoBrightnessPresetToString;
 
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
@@ -31,8 +32,6 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.hardware.display.AmbientBrightnessDayStats;
 import android.hardware.display.BrightnessChangeEvent;
@@ -45,6 +44,7 @@
 import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
@@ -56,12 +56,12 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.FloatProperty;
+import android.util.IndentingPrintWriter;
 import android.util.MathUtils;
 import android.util.MutableFloat;
 import android.util.MutableInt;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.TimeUtils;
 import android.view.Display;
 
 import com.android.internal.R;
@@ -78,10 +78,15 @@
 import com.android.server.display.RampAnimator.DualRampAnimator;
 import com.android.server.display.brightness.BrightnessEvent;
 import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.DisplayBrightnessController;
+import com.android.server.display.brightness.clamper.BrightnessClamperController;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
 import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
 import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.layout.Layout;
+import com.android.server.display.state.DisplayStateController;
 import com.android.server.display.utils.DebugUtils;
 import com.android.server.display.utils.SensorUtils;
 import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
@@ -119,12 +124,12 @@
     private static final String SCREEN_ON_BLOCKED_TRACE_NAME = "Screen on blocked";
     private static final String SCREEN_OFF_BLOCKED_TRACE_NAME = "Screen off blocked";
 
-    private static final String TAG = "DisplayPowerController";
+    private static final String TAG = "DisplayPowerController2";
     // To enable these logs, run:
-    // 'adb shell setprop persist.log.tag.DisplayPowerController DEBUG && adb reboot'
+    // 'adb shell setprop persist.log.tag.DisplayPowerController2 DEBUG && adb reboot'
     private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
-
-    private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false;
+    private static final String SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME =
+            "Screen on blocked by displayoffload";
 
     // If true, uses the color fade on animation.
     // We might want to turn this off if we cannot get a guarantee that the screen
@@ -138,36 +143,28 @@
     private static final int COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS = 400;
 
     private static final int MSG_UPDATE_POWER_STATE = 1;
-    private static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 2;
-    private static final int MSG_SCREEN_ON_UNBLOCKED = 3;
-    private static final int MSG_SCREEN_OFF_UNBLOCKED = 4;
-    private static final int MSG_CONFIGURE_BRIGHTNESS = 5;
-    private static final int MSG_SET_TEMPORARY_BRIGHTNESS = 6;
-    private static final int MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT = 7;
-    private static final int MSG_IGNORE_PROXIMITY = 8;
-    private static final int MSG_STOP = 9;
-    private static final int MSG_UPDATE_BRIGHTNESS = 10;
-    private static final int MSG_UPDATE_RBC = 11;
-    private static final int MSG_BRIGHTNESS_RAMP_DONE = 12;
-    private static final int MSG_STATSD_HBM_BRIGHTNESS = 13;
-    private static final int MSG_SWITCH_USER = 14;
-    private static final int MSG_BOOT_COMPLETED = 15;
-    private static final int MSG_SET_DWBC_STRONG_MODE = 16;
-    private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 17;
-    private static final int MSG_SET_DWBC_LOGGING_ENABLED = 18;
+    private static final int MSG_SCREEN_ON_UNBLOCKED = 2;
+    private static final int MSG_SCREEN_OFF_UNBLOCKED = 3;
+    private static final int MSG_CONFIGURE_BRIGHTNESS = 4;
+    private static final int MSG_SET_TEMPORARY_BRIGHTNESS = 5;
+    private static final int MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT = 6;
+    private static final int MSG_STOP = 7;
+    private static final int MSG_UPDATE_BRIGHTNESS = 8;
+    private static final int MSG_UPDATE_RBC = 9;
+    private static final int MSG_BRIGHTNESS_RAMP_DONE = 10;
+    private static final int MSG_STATSD_HBM_BRIGHTNESS = 11;
+    private static final int MSG_SWITCH_USER = 12;
+    private static final int MSG_BOOT_COMPLETED = 13;
+    private static final int MSG_SET_DWBC_STRONG_MODE = 14;
+    private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 15;
+    private static final int MSG_SET_DWBC_LOGGING_ENABLED = 16;
+    private static final int MSG_SET_BRIGHTNESS_FROM_OFFLOAD = 17;
+    private static final int MSG_OFFLOADING_SCREEN_ON_UNBLOCKED = 18;
 
-    private static final int PROXIMITY_UNKNOWN = -1;
-    private static final int PROXIMITY_NEGATIVE = 0;
-    private static final int PROXIMITY_POSITIVE = 1;
 
-    // Proximity sensor debounce delay in milliseconds for positive or negative transitions.
-    private static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0;
-    private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 250;
 
     private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500;
 
-    // Trigger proximity if distance is less than 5 cm.
-    private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f;
 
     // State machine constants for tracking initial brightness ramp skipping when enabled.
     private static final int RAMP_STATE_SKIP_NONE = 0;
@@ -181,6 +178,7 @@
     private static final int REPORTED_TO_POLICY_SCREEN_TURNING_OFF = 3;
 
     private static final int RINGBUFFER_MAX = 100;
+    private static final int RINGBUFFER_RBC_MAX = 20;
 
     private static final float[] BRIGHTNESS_RANGE_BOUNDARIES = {
         0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80,
@@ -236,10 +234,6 @@
     // Our handler.
     private final DisplayControllerHandler mHandler;
 
-    // Asynchronous callbacks into the power manager service.
-    // Only invoked from the handler thread while no locks are held.
-    private final DisplayPowerCallbacks mCallbacks;
-
     // Battery stats.
     @Nullable
     private final IBatteryStats mBatteryStats;
@@ -253,10 +247,10 @@
     // The display blanker.
     private final DisplayBlanker mBlanker;
 
-    // The LogicalDisplay tied to this DisplayPowerController.
+    // The LogicalDisplay tied to this DisplayPowerController2.
     private final LogicalDisplay mLogicalDisplay;
 
-    // The ID of the LogicalDisplay tied to this DisplayPowerController.
+    // The ID of the LogicalDisplay tied to this DisplayPowerController2.
     private final int mDisplayId;
 
     // The ID of the display which this display follows for brightness purposes.
@@ -272,34 +266,12 @@
     // Tracker for brightness settings changes.
     private final SettingsObserver mSettingsObserver;
 
-    // The proximity sensor, or null if not available or needed.
-    private Sensor mProximitySensor;
-
     // The doze screen brightness.
     private final float mScreenBrightnessDozeConfig;
 
-    // The dim screen brightness.
-    private final float mScreenBrightnessDimConfig;
-
-    // The minimum dim amount to use if the screen brightness is already below
-    // mScreenBrightnessDimConfig.
-    private final float mScreenBrightnessMinimumDimAmount;
-
-    private final float mScreenBrightnessDefault;
-
     // True if auto-brightness should be used.
     private boolean mUseSoftwareAutoBrightnessConfig;
 
-    // True if should use light sensor to automatically determine doze screen brightness.
-    private final boolean mAllowAutoBrightnessWhileDozingConfig;
-
-    // True if we want to persist the brightness value in nits even if the underlying display
-    // device changes.
-    private final boolean mPersistBrightnessNitsForDefaultDisplay;
-
-    // True if the brightness config has changed and the short-term model needs to be reset
-    private boolean mShouldResetShortTermModel;
-
     // Whether or not the color fade on screen on / off is enabled.
     private final boolean mColorFadeEnabled;
 
@@ -340,10 +312,6 @@
     @GuardedBy("mLock")
     private DisplayPowerRequest mPendingRequestLocked;
 
-    // True if a request has been made to wait for the proximity sensor to go negative.
-    @GuardedBy("mLock")
-    private boolean mPendingWaitForNegativeProximityLocked;
-
     // True if the pending power request or wait for negative proximity flag
     // has been changed since the last update occurred.
     @GuardedBy("mLock")
@@ -370,67 +338,36 @@
     // Must only be accessed on the handler thread.
     private DisplayPowerState mPowerState;
 
-    // True if the device should wait for negative proximity sensor before
-    // waking up the screen.  This is set to false as soon as a negative
-    // proximity sensor measurement is observed or when the device is forced to
-    // go to sleep by the user.  While true, the screen remains off.
-    private boolean mWaitingForNegativeProximity;
 
-    // True if the device should not take into account the proximity sensor
-    // until either the proximity sensor state changes, or there is no longer a
-    // request to listen to proximity sensor.
-    private boolean mIgnoreProximityUntilChanged;
-
-    // The actual proximity sensor threshold value.
-    private float mProximityThreshold;
-
-    // Set to true if the proximity sensor listener has been registered
-    // with the sensor manager.
-    private boolean mProximitySensorEnabled;
-
-    // The debounced proximity sensor state.
-    private int mProximity = PROXIMITY_UNKNOWN;
-
-    // The raw non-debounced proximity sensor state.
-    private int mPendingProximity = PROXIMITY_UNKNOWN;
-    private long mPendingProximityDebounceTime = -1; // -1 if fully debounced
-
-    // True if the screen was turned off because of the proximity sensor.
-    // When the screen turns on again, we report user activity to the power manager.
-    private boolean mScreenOffBecauseOfProximity;
 
     // The currently active screen on unblocker.  This field is non-null whenever
     // we are waiting for a callback to release it and unblock the screen.
     private ScreenOnUnblocker mPendingScreenOnUnblocker;
     private ScreenOffUnblocker mPendingScreenOffUnblocker;
+    private Runnable mPendingScreenOnUnblockerByDisplayOffload;
 
     // True if we were in the process of turning off the screen.
     // This allows us to recover more gracefully from situations where we abort
     // turning off the screen.
     private boolean mPendingScreenOff;
 
-    // True if we have unfinished business and are holding a suspend blocker.
-    private boolean mUnfinishedBusiness;
-
     // The elapsed real time when the screen on was blocked.
     private long mScreenOnBlockStartRealTime;
     private long mScreenOffBlockStartRealTime;
+    private long mScreenOnBlockByDisplayOffloadStartRealTime;
 
     // Screen state we reported to policy. Must be one of REPORTED_TO_POLICY_* fields.
     private int mReportedScreenStateToPolicy = REPORTED_TO_POLICY_UNREPORTED;
 
+    // Used to deduplicate the displayoffload blocking screen on logic. One block per turning on.
+    // This value is reset when screen on is reported or the blocking is cancelled.
+    private boolean mScreenTurningOnWasBlockedByDisplayOffload;
+
     // If the last recorded screen state was dozing or not.
     private boolean mDozing;
 
-    // Remembers whether certain kinds of brightness adjustments
-    // were recently applied so that we can decide how to transition.
-    private boolean mAppliedAutoBrightness;
     private boolean mAppliedDimming;
-    private boolean mAppliedLowPower;
-    private boolean mAppliedScreenBrightnessOverride;
-    private boolean mAppliedTemporaryBrightness;
-    private boolean mAppliedTemporaryAutoBrightnessAdjustment;
-    private boolean mAppliedBrightnessBoost;
+
     private boolean mAppliedThrottling;
 
     // Reason for which the brightness was last changed. See {@link BrightnessReason} for more
@@ -456,7 +393,7 @@
     private final boolean mSkipScreenOnBrightnessRamp;
 
     // Display white balance components.
-    // Critical methods must be called on DPC handler thread.
+    // Critical methods must be called on DPC2 handler thread.
     @Nullable
     private final DisplayWhiteBalanceSettings mDisplayWhiteBalanceSettings;
     @Nullable
@@ -468,21 +405,39 @@
 
     private final BrightnessRangeController mBrightnessRangeController;
 
-    @Nullable
-    private final HighBrightnessModeMetadata mHighBrightnessModeMetadata;
-
     private final BrightnessThrottler mBrightnessThrottler;
 
-    private final BrightnessSetting mBrightnessSetting;
+    private final BrightnessClamperController mBrightnessClamperController;
 
     private final Runnable mOnBrightnessChangeRunnable;
 
     private final BrightnessEvent mLastBrightnessEvent;
     private final BrightnessEvent mTempBrightnessEvent;
 
+    private final DisplayBrightnessController mDisplayBrightnessController;
+
     // Keeps a record of brightness changes for dumpsys.
     private RingBuffer<BrightnessEvent> mBrightnessEventRingBuffer;
 
+    // Keeps a record of rbc changes for dumpsys.
+    private final RingBuffer<BrightnessEvent> mRbcEventRingBuffer =
+            new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_RBC_MAX);
+
+    // Controls and tracks all the wakelocks that are acquired/released by the system. Also acts as
+    // a medium of communication between this class and the PowerManagerService.
+    private final WakelockController mWakelockController;
+
+    // Tracks and manages the proximity state of the associated display.
+    private final DisplayPowerProximityStateController mDisplayPowerProximityStateController;
+
+    // Tracks and manages the display state of the associated display.
+    private final DisplayStateController mDisplayStateController;
+
+
+    // Responsible for evaluating and tracking the automatic brightness relevant states.
+    // Todo: This is a temporary workaround. Ideally DPC2 should never talk to the strategies
+    private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
+
     // A record of state for skipping brightness ramps.
     private int mSkipRampState = RAMP_STATE_SKIP_NONE;
 
@@ -500,81 +455,18 @@
     private Sensor mLightSensor;
     private Sensor mScreenOffBrightnessSensor;
 
-    // The current brightness configuration.
-    @Nullable
-    private BrightnessConfiguration mBrightnessConfiguration;
-
-    // The last brightness that was set by the user and not temporary. Set to
-    // PowerManager.BRIGHTNESS_INVALID_FLOAT when a brightness has yet to be recorded.
-    private float mLastUserSetScreenBrightness = Float.NaN;
-
-    // The screen brightness setting has changed but not taken effect yet. If this is different
-    // from the current screen brightness setting then this is coming from something other than us
-    // and should be considered a user interaction.
-    private float mPendingScreenBrightnessSetting;
-
-    // The last observed screen brightness setting, either set by us or by the settings app on
-    // behalf of the user.
-    private float mCurrentScreenBrightnessSetting;
-
-    // The temporary screen brightness. Typically set when a user is interacting with the
-    // brightness slider but hasn't settled on a choice yet. Set to
-    // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary brightness set.
-    private float mTemporaryScreenBrightness;
-
-    // This brightness value is set in concurrent displays mode. It is the brightness value
-    // of the lead display that this DPC should follow.
-    private float mBrightnessToFollow;
-
-    // Indicates whether we should ramp slowly to the brightness value to follow.
-    private boolean mBrightnessToFollowSlowChange;
-
-    // The last auto brightness adjustment that was set by the user and not temporary. Set to
-    // Float.NaN when an auto-brightness adjustment hasn't been recorded yet.
-    private float mAutoBrightnessAdjustment;
-
-    // The pending auto brightness adjustment that will take effect on the next power state update.
-    private float mPendingAutoBrightnessAdjustment;
-
-    // The temporary auto brightness adjustment. Typically set when a user is interacting with the
-    // adjustment slider but hasn't settled on a choice yet. Set to
-    // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
-    private float mTemporaryAutoBrightnessAdjustment;
-
-    private boolean mUseAutoBrightness;
-
     private boolean mIsRbcActive;
 
-    // Whether there's a callback to tell listeners the display has changed scheduled to run. When
-    // true it implies a wakelock is being held to guarantee the update happens before we collapse
-    // into suspend and so needs to be cleaned up if the thread is exiting.
-    // Should only be accessed on the Handler thread.
-    private boolean mOnStateChangedPending;
-
-    // Count of proximity messages currently on this DPC's Handler. Used to keep track of how many
-    // suspend blocker acquisitions are pending when shutting down this DPC.
-    // Should only be accessed on the Handler thread.
-    private int mOnProximityPositiveMessages;
-    private int mOnProximityNegativeMessages;
-
     // Animators.
     private ObjectAnimator mColorFadeOnAnimator;
     private ObjectAnimator mColorFadeOffAnimator;
     private DualRampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator;
-    private BrightnessSetting.BrightnessSettingListener mBrightnessSettingListener;
 
-    // True if this DisplayPowerController has been stopped and should no longer be running.
+    // True if this DisplayPowerController2 has been stopped and should no longer be running.
     private boolean mStopped;
 
     private DisplayDeviceConfig mDisplayDeviceConfig;
 
-    // Identifiers for suspend blocker acquisition requests
-    private final String mSuspendBlockerIdUnfinishedBusiness;
-    private final String mSuspendBlockerIdOnStateChanged;
-    private final String mSuspendBlockerIdProxPositive;
-    private final String mSuspendBlockerIdProxNegative;
-    private final String mSuspendBlockerIdProxDebounce;
-
     private boolean mIsEnabled;
     private boolean mIsInTransition;
     private boolean mIsDisplayInternal;
@@ -585,13 +477,13 @@
     // DPCs following the brightness of this DPC. This is used in concurrent displays mode - there
     // is one lead display, the additional displays follow the brightness value of the lead display.
     @GuardedBy("mLock")
-    private final SparseArray<DisplayPowerControllerInterface> mDisplayBrightnessFollowers =
-            new SparseArray<>();
+    private SparseArray<DisplayPowerControllerInterface> mDisplayBrightnessFollowers =
+            new SparseArray();
 
     private boolean mBootCompleted;
     private final DisplayManagerFlags mFlags;
-    private int mDozeStateOverride = Display.STATE_UNKNOWN;
-    private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
+
+    private DisplayOffloadSession mDisplayOffloadSession;
 
     /**
      * Creates the display power controller.
@@ -607,26 +499,28 @@
         mClock = mInjector.getClock();
         mLogicalDisplay = logicalDisplay;
         mDisplayId = mLogicalDisplay.getDisplayIdLocked();
-        mTag = TAG + "[" + mDisplayId + "]";
-        mHighBrightnessModeMetadata = hbmMetadata;
-        mSuspendBlockerIdUnfinishedBusiness = getSuspendBlockerUnfinishedBusinessId(mDisplayId);
-        mSuspendBlockerIdOnStateChanged = getSuspendBlockerOnStateChangedId(mDisplayId);
-        mSuspendBlockerIdProxPositive = getSuspendBlockerProxPositiveId(mDisplayId);
-        mSuspendBlockerIdProxNegative = getSuspendBlockerProxNegativeId(mDisplayId);
-        mSuspendBlockerIdProxDebounce = getSuspendBlockerProxDebounceId(mDisplayId);
-
-        mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
-        mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
-        mDisplayStatsId = mUniqueDisplayId.hashCode();
+        mSensorManager = sensorManager;
+        mHandler = new DisplayControllerHandler(handler.getLooper());
+        mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked()
+                .getDisplayDeviceConfig();
         mIsEnabled = logicalDisplay.isEnabledLocked();
         mIsInTransition = logicalDisplay.isInTransitionLocked();
         mIsDisplayInternal = logicalDisplay.getPrimaryDisplayDeviceLocked()
                 .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL;
-        mHandler = new DisplayControllerHandler(handler.getLooper());
-        mLastBrightnessEvent = new BrightnessEvent(mDisplayId);
-        mTempBrightnessEvent = new BrightnessEvent(mDisplayId);
+        mWakelockController = mInjector.getWakelockController(mDisplayId, callbacks);
+        mDisplayPowerProximityStateController = mInjector.getDisplayPowerProximityStateController(
+                mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(),
+                () -> updatePowerState(), mDisplayId, mSensorManager);
+        mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
+        mTag = TAG + "[" + mDisplayId + "]";
         mThermalBrightnessThrottlingDataId =
                 logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
+        mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+        mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
+        mDisplayStatsId = mUniqueDisplayId.hashCode();
+
+        mLastBrightnessEvent = new BrightnessEvent(mDisplayId);
+        mTempBrightnessEvent = new BrightnessEvent(mDisplayId);
 
         if (mDisplayId == Display.DEFAULT_DISPLAY) {
             mBatteryStats = BatteryStatsService.getService();
@@ -635,14 +529,10 @@
         }
 
         mSettingsObserver = new SettingsObserver(mHandler);
-        mCallbacks = callbacks;
-        mSensorManager = sensorManager;
         mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class);
         mBlanker = blanker;
         mContext = context;
         mBrightnessTracker = brightnessTracker;
-        // TODO: b/186428377 update brightness setting when display changes
-        mBrightnessSetting = brightnessSetting;
         mOnBrightnessChangeRunnable = onBrightnessChangeRunnable;
 
         PowerManager pm = context.getSystemService(PowerManager.class);
@@ -650,30 +540,12 @@
         final Resources resources = context.getResources();
 
         // DOZE AND DIM SETTINGS
-        mScreenBrightnessDozeConfig = clampAbsoluteBrightness(
+        mScreenBrightnessDozeConfig = BrightnessUtils.clampAbsoluteBrightness(
                 pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE));
-        mScreenBrightnessDimConfig = clampAbsoluteBrightness(
-                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM));
-        mScreenBrightnessMinimumDimAmount = resources.getFloat(
-                com.android.internal.R.dimen.config_screenBrightnessMinimumDimAmountFloat);
-
-
-        // NORMAL SCREEN SETTINGS
-        mScreenBrightnessDefault = clampAbsoluteBrightness(
-                mLogicalDisplay.getDisplayInfoLocked().brightnessDefault);
-
-        mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
-                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing);
-
-        mPersistBrightnessNitsForDefaultDisplay = resources.getBoolean(
-                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay);
-
-        mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked()
-                .getDisplayDeviceConfig();
-
         loadBrightnessRampRates();
         mSkipScreenOnBrightnessRamp = resources.getBoolean(
-                com.android.internal.R.bool.config_skipScreenOnBrightnessRamp);
+                R.bool.config_skipScreenOnBrightnessRamp);
+
         Runnable modeChangeCallback = () -> {
             sendUpdatePowerState();
             postBrightnessChangeRunnable();
@@ -683,23 +555,38 @@
             }
         };
 
-        HighBrightnessModeController hbmController = createHbmControllerLocked(modeChangeCallback);
+        HighBrightnessModeController hbmController = createHbmControllerLocked(hbmMetadata,
+                modeChangeCallback);
+        mBrightnessThrottler = createBrightnessThrottlerLocked();
 
-        mBrightnessRangeController = new BrightnessRangeController(hbmController,
+        mBrightnessRangeController = mInjector.getBrightnessRangeController(hbmController,
                 modeChangeCallback, mDisplayDeviceConfig, mHandler, flags,
                 mDisplayDevice.getDisplayTokenLocked(),
                 mDisplayDevice.getDisplayDeviceInfoLocked());
 
-        mBrightnessThrottler = createBrightnessThrottlerLocked();
+        mDisplayBrightnessController =
+                new DisplayBrightnessController(context, null,
+                        mDisplayId, mLogicalDisplay.getDisplayInfoLocked().brightnessDefault,
+                        brightnessSetting, () -> postBrightnessChangeRunnable(),
+                        new HandlerExecutor(mHandler), flags);
 
+        mBrightnessClamperController = mInjector.getBrightnessClamperController(
+                mHandler, modeChangeCallback::run,
+                new BrightnessClamperController.DisplayDeviceData(
+                        mUniqueDisplayId,
+                        mThermalBrightnessThrottlingDataId,
+                        logicalDisplay.getPowerThrottlingDataIdLocked(),
+                        mDisplayDeviceConfig), mContext, flags);
         // Seed the cached brightness
         saveBrightnessInfo(getScreenBrightnessSetting());
+        mAutomaticBrightnessStrategy =
+                mDisplayBrightnessController.getAutomaticBrightnessStrategy();
 
         DisplayWhiteBalanceSettings displayWhiteBalanceSettings = null;
         DisplayWhiteBalanceController displayWhiteBalanceController = null;
         if (mDisplayId == Display.DEFAULT_DISPLAY) {
             try {
-                displayWhiteBalanceController = injector.getDisplayWhiteBalanceController(
+                displayWhiteBalanceController = mInjector.getDisplayWhiteBalanceController(
                         mHandler, mSensorManager, resources);
                 displayWhiteBalanceSettings = new DisplayWhiteBalanceSettings(mContext, mHandler);
                 displayWhiteBalanceSettings.setCallbacks(this);
@@ -715,21 +602,24 @@
 
         if (mDisplayId == Display.DEFAULT_DISPLAY) {
             mCdsi = LocalServices.getService(ColorDisplayServiceInternal.class);
-            boolean active = mCdsi.setReduceBrightColorsListener(new ReduceBrightColorsListener() {
-                @Override
-                public void onReduceBrightColorsActivationChanged(boolean activated,
-                        boolean userInitiated) {
-                    applyReduceBrightColorsSplineAdjustment();
+            if (mCdsi != null) {
+                boolean active = mCdsi.setReduceBrightColorsListener(
+                        new ReduceBrightColorsListener() {
+                            @Override
+                            public void onReduceBrightColorsActivationChanged(boolean activated,
+                                    boolean userInitiated) {
+                                applyReduceBrightColorsSplineAdjustment();
 
-                }
+                            }
 
-                @Override
-                public void onReduceBrightColorsStrengthChanged(int strength) {
+                            @Override
+                            public void onReduceBrightColorsStrengthChanged(int strength) {
+                                applyReduceBrightColorsSplineAdjustment();
+                            }
+                        });
+                if (active) {
                     applyReduceBrightColorsSplineAdjustment();
                 }
-            });
-            if (active) {
-                applyReduceBrightColorsSplineAdjustment();
             }
         } else {
             mCdsi = null;
@@ -737,27 +627,17 @@
 
         setUpAutoBrightness(context, handler);
 
-        mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic()
+        mColorFadeEnabled = mInjector.isColorFadeEnabled()
                 && !resources.getBoolean(
                   com.android.internal.R.bool.config_displayColorFadeDisabled);
         mColorFadeFadesConfig = resources.getBoolean(
-                com.android.internal.R.bool.config_animateScreenLights);
+                R.bool.config_animateScreenLights);
 
         mDisplayBlanksAfterDozeConfig = resources.getBoolean(
-                com.android.internal.R.bool.config_displayBlanksAfterDoze);
+                R.bool.config_displayBlanksAfterDoze);
 
         mBrightnessBucketsInDozeConfig = resources.getBoolean(
-                com.android.internal.R.bool.config_displayBrightnessBucketsInDoze);
-
-        loadProximitySensor();
-
-        loadNitBasedBrightnessSetting();
-        mBrightnessToFollow = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-        mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
-        mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-        mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-        mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-        mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+                R.bool.config_displayBrightnessBucketsInDoze);
 
         mBootCompleted = bootCompleted;
     }
@@ -785,7 +665,7 @@
      */
     @Override
     public boolean isProximitySensorAvailable() {
-        return mProximitySensor != null;
+        return mDisplayPowerProximityStateController.isProximitySensorAvailable();
     }
 
     /**
@@ -818,64 +698,6 @@
         }
     }
 
-    @Override
-    public int getDisplayId() {
-        return mDisplayId;
-    }
-
-    @Override
-    public int getLeadDisplayId() {
-        return mLeadDisplayId;
-    }
-
-    @Override
-    public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux,
-            boolean slowChange) {
-        mBrightnessRangeController.onAmbientLuxChange(ambientLux);
-        if (nits == BrightnessMappingStrategy.INVALID_NITS) {
-            mBrightnessToFollow = leadDisplayBrightness;
-        } else {
-            float brightness = getBrightnessFromNits(nits);
-            if (isValidBrightnessValue(brightness)) {
-                mBrightnessToFollow = brightness;
-            } else {
-                // The device does not support nits
-                mBrightnessToFollow = leadDisplayBrightness;
-            }
-        }
-        mBrightnessToFollowSlowChange = slowChange;
-        sendUpdatePowerState();
-    }
-
-    @Override
-    public void addDisplayBrightnessFollower(@NonNull DisplayPowerControllerInterface follower) {
-        synchronized (mLock) {
-            mDisplayBrightnessFollowers.append(follower.getDisplayId(), follower);
-            sendUpdatePowerStateLocked();
-        }
-    }
-
-    @Override
-    public void removeDisplayBrightnessFollower(@NonNull DisplayPowerControllerInterface follower) {
-        synchronized (mLock) {
-            mDisplayBrightnessFollowers.remove(follower.getDisplayId());
-            mHandler.postAtTime(() -> follower.setBrightnessToFollow(
-                    PowerManager.BRIGHTNESS_INVALID_FLOAT, BrightnessMappingStrategy.INVALID_NITS,
-                    /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void clearDisplayBrightnessFollowersLocked() {
-        for (int i = 0; i < mDisplayBrightnessFollowers.size(); i++) {
-            DisplayPowerControllerInterface follower = mDisplayBrightnessFollowers.valueAt(i);
-            mHandler.postAtTime(() -> follower.setBrightnessToFollow(
-                    PowerManager.BRIGHTNESS_INVALID_FLOAT, BrightnessMappingStrategy.INVALID_NITS,
-                    /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
-        }
-        mDisplayBrightnessFollowers.clear();
-    }
-
     @Nullable
     @Override
     public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(
@@ -923,13 +745,8 @@
                 return true;
             }
 
-            boolean changed = false;
-
-            if (waitForNegativeProximity
-                    && !mPendingWaitForNegativeProximityLocked) {
-                mPendingWaitForNegativeProximityLocked = true;
-                changed = true;
-            }
+            boolean changed = mDisplayPowerProximityStateController
+                    .setPendingWaitForNegativeProximityLocked(waitForNegativeProximity);
 
             if (mPendingRequestLocked == null) {
                 mPendingRequestLocked = new DisplayPowerRequest(request);
@@ -953,18 +770,23 @@
 
     @Override
     public void overrideDozeScreenState(int displayState) {
-        synchronized (mLock) {
-            if (mDisplayOffloadSession == null ||
-                    !DisplayOffloadSession.isSupportedOffloadState(displayState)) {
+        mHandler.postAtTime(() -> {
+            if (mDisplayOffloadSession == null
+                    || !(DisplayOffloadSession.isSupportedOffloadState(displayState)
+                    || displayState == Display.STATE_UNKNOWN)) {
                 return;
             }
-            mDozeStateOverride = displayState;
+            mDisplayStateController.overrideDozeScreenState(displayState);
             sendUpdatePowerState();
-        }
+        }, mClock.uptimeMillis());
     }
 
     @Override
     public void setDisplayOffloadSession(DisplayOffloadSession session) {
+        if (session == mDisplayOffloadSession) {
+            return;
+        }
+        unblockScreenOnByDisplayOffload();
         mDisplayOffloadSession = session;
     }
 
@@ -981,14 +803,14 @@
      * when displays get swapped on foldable devices.  For example, different brightness properties
      * of each display need to be properly reflected in AutomaticBrightnessController.
      *
-     * Make sure DisplayManagerService.mSyncRoot is held when this is called
+     * Make sure DisplayManagerService.mSyncRoot lock is held when this is called
      */
     @Override
     public void onDisplayChanged(HighBrightnessModeMetadata hbmMetadata, int leadDisplayId) {
         mLeadDisplayId = leadDisplayId;
         final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
         if (device == null) {
-            Slog.wtf(mTag, "Display Device is null in DisplayPowerController for display: "
+            Slog.wtf(mTag, "Display Device is null in DisplayPowerController2 for display: "
                     + mLogicalDisplay.getDisplayIdLocked());
             return;
         }
@@ -1004,6 +826,9 @@
                 .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL;
         final String thermalBrightnessThrottlingDataId =
                 mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
+        final String powerThrottlingDataId =
+                mLogicalDisplay.getPowerThrottlingDataIdLocked();
+
         mHandler.postAtTime(() -> {
             boolean changed = false;
             if (mDisplayDevice != device) {
@@ -1014,9 +839,9 @@
                 mDisplayDeviceConfig = config;
                 mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId;
                 loadFromDisplayDeviceConfig(token, info, hbmMetadata);
-                loadNitBasedBrightnessSetting();
+                mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(config);
 
-                /// Since the underlying display-device changed, we really don't know the
+                // Since the underlying display-device changed, we really don't know the
                 // last command that was sent to change it's state. Let's assume it is unknown so
                 // that we trigger a change immediately.
                 mPowerState.resetScreenState();
@@ -1034,7 +859,16 @@
                 mIsEnabled = isEnabled;
                 mIsInTransition = isInTransition;
             }
+
             mIsDisplayInternal = isDisplayInternal;
+            // using local variables here, when mBrightnessThrottler is removed,
+            // mThermalBrightnessThrottlingDataId could be removed as well
+            // changed = true will be not needed - clampers are maintaining their state and
+            // will call updatePowerState if needed.
+            mBrightnessClamperController.onDisplayChanged(
+                    new BrightnessClamperController.DisplayDeviceData(uniqueId,
+                        thermalBrightnessThrottlingDataId, powerThrottlingDataId, config));
+
             if (changed) {
                 updatePowerState();
             }
@@ -1044,7 +878,7 @@
     /**
      * Unregisters all listeners and interrupts all running threads; halting future work.
      *
-     * This method should be called when the DisplayPowerController is no longer in use; i.e. when
+     * This method should be called when the DisplayPowerController2 is no longer in use; i.e. when
      * the {@link #mDisplayId display} has been removed.
      */
     @Override
@@ -1060,9 +894,7 @@
                 mAutomaticBrightnessController.stop();
             }
 
-            if (mBrightnessSetting != null) {
-                mBrightnessSetting.unregisterListener(mBrightnessSettingListener);
-            }
+            mDisplayBrightnessController.stop();
 
             mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
         }
@@ -1073,11 +905,11 @@
         // All properties that depend on the associated DisplayDevice and the DDC must be
         // updated here.
         loadBrightnessRampRates();
-        loadProximitySensor();
         loadNitsRange(mContext.getResources());
         setUpAutoBrightness(mContext, mHandler);
         reloadReduceBrightColours();
         setAnimatorRampSpeeds(/* isIdleMode= */ false);
+
         mBrightnessRangeController.loadFromConfig(hbmMetadata, token, info, mDisplayDeviceConfig);
         mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
                 mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(),
@@ -1126,22 +958,30 @@
         noteScreenBrightness(mPowerState.getScreenBrightness());
 
         // Initialize all of the brightness tracking state
-        final float brightness = convertToAdjustedNits(mPowerState.getScreenBrightness());
+        final float brightness = mDisplayBrightnessController.convertToAdjustedNits(
+                mPowerState.getScreenBrightness());
         if (mBrightnessTracker != null && brightness >= PowerManager.BRIGHTNESS_MIN) {
             mBrightnessTracker.start(brightness);
         }
-        mBrightnessSettingListener = brightnessValue -> {
+
+        BrightnessSetting.BrightnessSettingListener brightnessSettingListener = brightnessValue -> {
             Message msg = mHandler.obtainMessage(MSG_UPDATE_BRIGHTNESS, brightnessValue);
             mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
         };
+        mDisplayBrightnessController
+                .registerBrightnessSettingChangeListener(brightnessSettingListener);
 
-        mBrightnessSetting.registerListener(mBrightnessSettingListener);
         mContext.getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
                 false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
         mContext.getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE),
                 false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+        if (mFlags.areAutoBrightnessModesEnabled()) {
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_ALS),
+                    /* notifyForDescendants= */ false, mSettingsObserver, UserHandle.USER_CURRENT);
+        }
         handleBrightnessModeChange();
     }
 
@@ -1165,12 +1005,21 @@
         if (isIdleScreenBrightnessEnabled) {
             BrightnessMappingStrategy idleModeBrightnessMapper =
                     BrightnessMappingStrategy.create(context, mDisplayDeviceConfig,
-                            AUTO_BRIGHTNESS_MODE_IDLE, mDisplayWhiteBalanceController);
+                            AUTO_BRIGHTNESS_MODE_IDLE,
+                            mDisplayWhiteBalanceController);
             if (idleModeBrightnessMapper != null) {
-                brightnessMappers.append(AUTO_BRIGHTNESS_MODE_IDLE, idleModeBrightnessMapper);
+                brightnessMappers.append(AUTO_BRIGHTNESS_MODE_IDLE,
+                        idleModeBrightnessMapper);
             }
         }
 
+        BrightnessMappingStrategy dozeModeBrightnessMapper =
+                BrightnessMappingStrategy.create(context, mDisplayDeviceConfig,
+                        AUTO_BRIGHTNESS_MODE_DOZE, mDisplayWhiteBalanceController);
+        if (mFlags.areAutoBrightnessModesEnabled() && dozeModeBrightnessMapper != null) {
+            brightnessMappers.put(AUTO_BRIGHTNESS_MODE_DOZE, dozeModeBrightnessMapper);
+        }
+
         float userLux = BrightnessMappingStrategy.INVALID_LUX;
         float userNits = BrightnessMappingStrategy.INVALID_NITS;
         if (mAutomaticBrightnessController != null) {
@@ -1180,7 +1029,7 @@
 
         if (defaultModeBrightnessMapper != null) {
             final float dozeScaleFactor = context.getResources().getFraction(
-                    com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
+                    R.fraction.config_screenAutoBrightnessDozeScaleFactor,
                     1, 1);
 
             // Ambient Lux - Active Mode Brightness Thresholds
@@ -1264,14 +1113,14 @@
             long darkeningLightDebounceIdle = mDisplayDeviceConfig
                     .getAutoBrightnessDarkeningLightDebounceIdle();
             boolean autoBrightnessResetAmbientLuxAfterWarmUp = context.getResources().getBoolean(
-                    com.android.internal.R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
+                    R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
 
             int lightSensorWarmUpTimeConfig = context.getResources().getInteger(
-                    com.android.internal.R.integer.config_lightSensorWarmupTime);
+                    R.integer.config_lightSensorWarmupTime);
             int lightSensorRate = context.getResources().getInteger(
-                    com.android.internal.R.integer.config_autoBrightnessLightSensorRate);
+                    R.integer.config_autoBrightnessLightSensorRate);
             int initialLightSensorRate = context.getResources().getInteger(
-                    com.android.internal.R.integer.config_autoBrightnessInitialLightSensorRate);
+                    R.integer.config_autoBrightnessInitialLightSensorRate);
             if (initialLightSensorRate == -1) {
                 initialLightSensorRate = lightSensorRate;
             } else if (initialLightSensorRate > lightSensorRate) {
@@ -1301,7 +1150,11 @@
                     screenBrightnessThresholdsIdle, mContext, mBrightnessRangeController,
                     mBrightnessThrottler, mDisplayDeviceConfig.getAmbientHorizonShort(),
                     mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userNits);
+            mDisplayBrightnessController.setAutomaticBrightnessController(
+                    mAutomaticBrightnessController);
 
+            mAutomaticBrightnessStrategy
+                    .setAutomaticBrightnessController(mAutomaticBrightnessController);
             mBrightnessEventRingBuffer =
                     new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX);
 
@@ -1351,7 +1204,7 @@
         } else {
             Slog.w(mTag, "Screen brightness nits configuration is unavailable; falling back");
             mNitsRange = BrightnessMappingStrategy.getFloatArray(resources
-                    .obtainTypedArray(com.android.internal.R.array.config_screenBrightnessNits));
+                    .obtainTypedArray(R.array.config_screenBrightnessNits));
         }
     }
 
@@ -1369,7 +1222,6 @@
             mAutomaticBrightnessController.switchMode(mode);
             setAnimatorRampSpeeds(isIdle);
         }
-
         Message msg = mHandler.obtainMessage();
         msg.what = MSG_SET_DWBC_STRONG_MODE;
         msg.arg1 = isIdle ? 1 : 0;
@@ -1421,28 +1273,14 @@
 
     /** Clean up all resources that are accessed via the {@link #mHandler} thread. */
     private void cleanupHandlerThreadAfterStop() {
-        setProximitySensorEnabled(false);
+        mDisplayPowerProximityStateController.cleanup();
         mBrightnessRangeController.stop();
         mBrightnessThrottler.stop();
+        mBrightnessClamperController.stop();
         mHandler.removeCallbacksAndMessages(null);
 
         // Release any outstanding wakelocks we're still holding because of pending messages.
-        if (mUnfinishedBusiness) {
-            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
-            mUnfinishedBusiness = false;
-        }
-        if (mOnStateChangedPending) {
-            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged);
-            mOnStateChangedPending = false;
-        }
-        for (int i = 0; i < mOnProximityPositiveMessages; i++) {
-            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive);
-        }
-        mOnProximityPositiveMessages = 0;
-        for (int i = 0; i < mOnProximityNegativeMessages; i++) {
-            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative);
-        }
-        mOnProximityNegativeMessages = 0;
+        mWakelockController.releaseAll();
 
         final float brightness = mPowerState != null
                 ? mPowerState.getScreenBrightness()
@@ -1457,10 +1295,10 @@
         if (mScreenOffBrightnessSensorController != null) {
             mScreenOffBrightnessSensorController.stop();
         }
+
         if (mDisplayWhiteBalanceController != null) {
             mDisplayWhiteBalanceController.setEnabled(false);
         }
-
     }
 
     // Call from handler thread
@@ -1476,7 +1314,6 @@
         final boolean mustNotify;
         final int previousPolicy;
         boolean mustInitialize = false;
-        int brightnessAdjustmentFlags = 0;
         mBrightnessReasonTemp.set(null);
         mTempBrightnessEvent.reset();
         SparseArray<DisplayPowerControllerInterface> displayBrightnessFollowers;
@@ -1491,7 +1328,7 @@
 
             if (mPowerRequest == null) {
                 mPowerRequest = new DisplayPowerRequest(mPendingRequestLocked);
-                updatePendingProximityRequestsLocked();
+                mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
                 mPendingRequestChangedLocked = false;
                 mustInitialize = true;
                 // Assume we're on and bright until told otherwise, since that's the state we turn
@@ -1500,7 +1337,7 @@
             } else if (mPendingRequestChangedLocked) {
                 previousPolicy = mPowerRequest.policy;
                 mPowerRequest.copyFrom(mPendingRequestLocked);
-                updatePendingProximityRequestsLocked();
+                mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
                 mPendingRequestChangedLocked = false;
                 mDisplayReadyLocked = false;
             } else {
@@ -1512,105 +1349,8 @@
             displayBrightnessFollowers = mDisplayBrightnessFollowers.clone();
         }
 
-        // Compute the basic display state using the policy.
-        // We might override this below based on other factors.
-        // Initialise brightness as invalid.
-        int state;
-        float brightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-        boolean performScreenOffTransition = false;
-        switch (mPowerRequest.policy) {
-            case DisplayPowerRequest.POLICY_OFF:
-                state = Display.STATE_OFF;
-                performScreenOffTransition = true;
-                break;
-            case DisplayPowerRequest.POLICY_DOZE:
-                if (mDozeStateOverride != Display.STATE_UNKNOWN) {
-                    state = mDozeStateOverride;
-                } else if (mPowerRequest.dozeScreenState != Display.STATE_UNKNOWN) {
-                    state = mPowerRequest.dozeScreenState;
-                } else {
-                    state = Display.STATE_DOZE;
-                }
-                if (!mAllowAutoBrightnessWhileDozingConfig) {
-                    brightnessState = mPowerRequest.dozeScreenBrightness;
-                    mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE);
-                }
-                break;
-            case DisplayPowerRequest.POLICY_DIM:
-            case DisplayPowerRequest.POLICY_BRIGHT:
-            default:
-                state = Display.STATE_ON;
-                break;
-        }
-        assert (state != Display.STATE_UNKNOWN);
-
-        if (mScreenOffBrightnessSensorController != null) {
-            mScreenOffBrightnessSensorController.setLightSensorEnabled(mUseAutoBrightness
-                    && mIsEnabled && (state == Display.STATE_OFF || (state == Display.STATE_DOZE
-                    && !mAllowAutoBrightnessWhileDozingConfig))
-                    && mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
-        }
-
-        boolean skipRampBecauseOfProximityChangeToNegative = false;
-        // Apply the proximity sensor.
-        if (mProximitySensor != null) {
-            if (mPowerRequest.useProximitySensor && state != Display.STATE_OFF) {
-                // At this point the policy says that the screen should be on, but we've been
-                // asked to listen to the prox sensor to adjust the display state, so lets make
-                // sure the sensor is on.
-                setProximitySensorEnabled(true);
-                if (!mScreenOffBecauseOfProximity
-                        && mProximity == PROXIMITY_POSITIVE
-                        && !mIgnoreProximityUntilChanged) {
-                    // Prox sensor already reporting "near" so we should turn off the screen.
-                    // Also checked that we aren't currently set to ignore the proximity sensor
-                    // temporarily.
-                    mScreenOffBecauseOfProximity = true;
-                    sendOnProximityPositiveWithWakelock();
-                }
-            } else if (mWaitingForNegativeProximity
-                    && mScreenOffBecauseOfProximity
-                    && mProximity == PROXIMITY_POSITIVE
-                    && state != Display.STATE_OFF) {
-                // The policy says that we should have the screen on, but it's off due to the prox
-                // and we've been asked to wait until the screen is far from the user to turn it
-                // back on. Let keep the prox sensor on so we can tell when it's far again.
-                setProximitySensorEnabled(true);
-            } else {
-                // We haven't been asked to use the prox sensor and we're not waiting on the screen
-                // to turn back on...so lets shut down the prox sensor.
-                setProximitySensorEnabled(false);
-                mWaitingForNegativeProximity = false;
-            }
-
-            if (mScreenOffBecauseOfProximity
-                    && (mProximity != PROXIMITY_POSITIVE || mIgnoreProximityUntilChanged)) {
-                // The screen *was* off due to prox being near, but now it's "far" so lets turn
-                // the screen back on.  Also turn it back on if we've been asked to ignore the
-                // prox sensor temporarily.
-                mScreenOffBecauseOfProximity = false;
-                skipRampBecauseOfProximityChangeToNegative = true;
-                sendOnProximityNegativeWithWakelock();
-            }
-        } else {
-            setProximitySensorEnabled(false);
-            mWaitingForNegativeProximity = false;
-            mIgnoreProximityUntilChanged = false;
-
-            if (mScreenOffBecauseOfProximity) {
-                // The screen *was* off due to prox being near, but now there's no prox sensor, so
-                // let's turn the screen back on.
-                mScreenOffBecauseOfProximity = false;
-                skipRampBecauseOfProximityChangeToNegative = true;
-                sendOnProximityNegativeWithWakelock();
-            }
-        }
-
-        if (!mIsEnabled
-                || mIsInTransition
-                || mScreenOffBecauseOfProximity) {
-            state = Display.STATE_OFF;
-        }
+        int state = mDisplayStateController
+                .updateDisplayState(mPowerRequest, mIsEnabled, mIsInTransition);
 
         // Initialize things the first time the power state is changed.
         if (mustInitialize) {
@@ -1620,157 +1360,100 @@
         // Animate the screen state change unless already animating.
         // The transition may be deferred, so after this point we will use the
         // actual state instead of the desired one.
-        final int oldState = mPowerState.getScreenState();
-        animateScreenStateChange(state, performScreenOffTransition);
+        animateScreenStateChange(state, mDisplayStateController.shouldPerformScreenOffTransition());
         state = mPowerState.getScreenState();
-        boolean slowChange = false;
 
-        if (state == Display.STATE_OFF) {
-            brightnessState = PowerManager.BRIGHTNESS_OFF_FLOAT;
-            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_SCREEN_OFF);
+        // Switch to doze auto-brightness mode if needed
+        if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null
+                && !mAutomaticBrightnessController.isInIdleMode()) {
+            setAutomaticScreenBrightnessMode(Display.isDozeState(state)
+                    ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
         }
 
-        if (Float.isNaN(brightnessState) && isValidBrightnessValue(mBrightnessToFollow)) {
-            brightnessState = mBrightnessToFollow;
-            slowChange = mBrightnessToFollowSlowChange;
-            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_FOLLOWER);
+        final boolean userSetBrightnessChanged = mDisplayBrightnessController
+                .updateUserSetScreenBrightness();
+
+        DisplayBrightnessState displayBrightnessState = mDisplayBrightnessController
+                .updateBrightness(mPowerRequest, state);
+        float brightnessState = displayBrightnessState.getBrightness();
+        float rawBrightnessState = displayBrightnessState.getBrightness();
+        mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason());
+        boolean slowChange = displayBrightnessState.isSlowChange();
+        // custom transition duration
+        float customAnimationRate = displayBrightnessState.getCustomAnimationRate();
+
+        // Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor
+        // doesn't yet have a valid lux value to use with auto-brightness.
+        if (mScreenOffBrightnessSensorController != null) {
+            mScreenOffBrightnessSensorController
+                    .setLightSensorEnabled(displayBrightnessState.getShouldUseAutoBrightness()
+                    && mIsEnabled && (state == Display.STATE_OFF
+                    || (state == Display.STATE_DOZE
+                    && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()))
+                    && mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
         }
 
-        if ((Float.isNaN(brightnessState))
-                && isValidBrightnessValue(mPowerRequest.screenBrightnessOverride)) {
-            brightnessState = mPowerRequest.screenBrightnessOverride;
-            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_OVERRIDE);
-            mAppliedScreenBrightnessOverride = true;
-        } else {
-            mAppliedScreenBrightnessOverride = false;
-        }
-
-        final boolean autoBrightnessEnabledInDoze =
-                mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state);
-        final boolean autoBrightnessEnabled = mUseAutoBrightness
-                && (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
-                && mBrightnessReasonTemp.getReason() != BrightnessReason.REASON_OVERRIDE
-                && mAutomaticBrightnessController != null;
-        final boolean autoBrightnessDisabledDueToDisplayOff = mUseAutoBrightness
-                && !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
-        final int autoBrightnessState = autoBrightnessEnabled
-                    && mBrightnessReasonTemp.getReason() != BrightnessReason.REASON_FOLLOWER
-                ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
-                : autoBrightnessDisabledDueToDisplayOff
-                        ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
-                        : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
-
-        final boolean userSetBrightnessChanged = updateUserSetScreenBrightness();
-
-        // Use the temporary screen brightness if there isn't an override, either from
-        // WindowManager or based on the display state.
-        if (isValidBrightnessValue(mTemporaryScreenBrightness)) {
-            brightnessState = mTemporaryScreenBrightness;
-            mAppliedTemporaryBrightness = true;
-            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_TEMPORARY);
-        } else {
-            mAppliedTemporaryBrightness = false;
-        }
-
-        final boolean autoBrightnessAdjustmentChanged = updateAutoBrightnessAdjustment();
-
-        // Use the autobrightness adjustment override if set.
-        final float autoBrightnessAdjustment;
-        if (!Float.isNaN(mTemporaryAutoBrightnessAdjustment)) {
-            autoBrightnessAdjustment = mTemporaryAutoBrightnessAdjustment;
-            brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO_TEMP;
-            mAppliedTemporaryAutoBrightnessAdjustment = true;
-        } else {
-            autoBrightnessAdjustment = mAutoBrightnessAdjustment;
-            brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO;
-            mAppliedTemporaryAutoBrightnessAdjustment = false;
-        }
-        // Apply brightness boost.
-        // We do this here after deciding whether auto-brightness is enabled so that we don't
-        // disable the light sensor during this temporary state.  That way when boost ends we will
-        // be able to resume normal auto-brightness behavior without any delay.
-        if (mPowerRequest.boostScreenBrightness
-                && brightnessState != PowerManager.BRIGHTNESS_OFF_FLOAT) {
-            brightnessState = PowerManager.BRIGHTNESS_MAX;
-            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_BOOST);
-            mAppliedBrightnessBoost = true;
-        } else {
-            mAppliedBrightnessBoost = false;
-        }
+        // Take note if the short term model was already active before applying the current
+        // request changes.
+        final boolean wasShortTermModelActive =
+                mAutomaticBrightnessStrategy.isShortTermModelActive();
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(state,
+                mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig(),
+                mBrightnessReasonTemp.getReason(), mPowerRequest.policy,
+                mDisplayBrightnessController.getLastUserSetScreenBrightness(),
+                userSetBrightnessChanged);
 
         // If the brightness is already set then it's been overridden by something other than the
         // user, or is a temporary adjustment.
         boolean userInitiatedChange = (Float.isNaN(brightnessState))
-                && (autoBrightnessAdjustmentChanged || userSetBrightnessChanged);
-        boolean wasShortTermModelActive = false;
-        // Configure auto-brightness.
-        if (mAutomaticBrightnessController != null) {
-            wasShortTermModelActive = mAutomaticBrightnessController.hasUserDataPoints();
-            mAutomaticBrightnessController.configure(autoBrightnessState,
-                    mBrightnessConfiguration,
-                    mLastUserSetScreenBrightness,
-                    userSetBrightnessChanged, autoBrightnessAdjustment,
-                    autoBrightnessAdjustmentChanged, mPowerRequest.policy,
-                    mShouldResetShortTermModel);
-            mShouldResetShortTermModel = false;
-        }
-        mBrightnessRangeController.setAutoBrightnessEnabled(autoBrightnessEnabled
+                && (mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged()
+                || userSetBrightnessChanged);
+
+        mBrightnessRangeController.setAutoBrightnessEnabled(
+                mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()
                 ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
-                : autoBrightnessDisabledDueToDisplayOff
+                : mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff()
                         ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
                         : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
 
-        if (mBrightnessTracker != null) {
-            mBrightnessTracker.setShouldCollectColorSample(mBrightnessConfiguration != null
-                    && mBrightnessConfiguration.shouldCollectColorSamples());
-        }
-
-        boolean updateScreenBrightnessSetting = false;
-        float rawBrightnessState = brightnessState;
-
+        boolean updateScreenBrightnessSetting =
+                displayBrightnessState.shouldUpdateScreenBrightnessSetting();
+        float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness();
         // Apply auto-brightness.
+        int brightnessAdjustmentFlags = 0;
         if (Float.isNaN(brightnessState)) {
-            float newAutoBrightnessAdjustment = autoBrightnessAdjustment;
-            if (autoBrightnessEnabled) {
-                rawBrightnessState = mAutomaticBrightnessController
-                        .getRawAutomaticScreenBrightness();
-                brightnessState = mAutomaticBrightnessController.getAutomaticScreenBrightness(
+            if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
+                brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
                         mTempBrightnessEvent);
-                newAutoBrightnessAdjustment =
-                        mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment();
-            }
-            if (isValidBrightnessValue(brightnessState)
-                    || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
-                // Use current auto-brightness value and slowly adjust to changes.
-                brightnessState = clampScreenBrightness(brightnessState);
-                if (mAppliedAutoBrightness && !autoBrightnessAdjustmentChanged) {
-                    slowChange = true; // slowly adapt to auto-brightness
+                if (BrightnessUtils.isValidBrightnessValue(brightnessState)
+                        || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
+                    rawBrightnessState = mAutomaticBrightnessController
+                            .getRawAutomaticScreenBrightness();
+                    brightnessState = clampScreenBrightness(brightnessState);
+                    // slowly adapt to auto-brightness
+                    // TODO(b/253226419): slowChange should be decided by strategy.updateBrightness
+                    slowChange = mAutomaticBrightnessStrategy.hasAppliedAutoBrightness()
+                            && !mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged();
+                    brightnessAdjustmentFlags =
+                            mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags();
+                    updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
+                    mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
+                    mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
+                    if (mScreenOffBrightnessSensorController != null) {
+                        mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
+                    }
+                } else {
+                    mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
                 }
-                updateScreenBrightnessSetting = mCurrentScreenBrightnessSetting != brightnessState;
-                mAppliedAutoBrightness = true;
-                mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
-                if (mScreenOffBrightnessSensorController != null) {
-                    mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
-                }
-            } else {
-                mAppliedAutoBrightness = false;
-            }
-            if (autoBrightnessAdjustment != newAutoBrightnessAdjustment) {
-                // If the autobrightness controller has decided to change the adjustment value
-                // used, make sure that's reflected in settings.
-                putAutoBrightnessAdjustmentSetting(newAutoBrightnessAdjustment);
-            } else {
-                // Adjustment values resulted in no change
-                brightnessAdjustmentFlags = 0;
             }
         } else {
             // Any non-auto-brightness values such as override or temporary should still be subject
             // to clamping so that they don't go beyond the current max as specified by HBM
             // Controller.
             brightnessState = clampScreenBrightness(brightnessState);
-            mAppliedAutoBrightness = false;
-            brightnessAdjustmentFlags = 0;
+            mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
         }
+
         // Use default brightness when dozing unless overridden.
         if ((Float.isNaN(brightnessState))
                 && Display.isDozeState(state)) {
@@ -1781,14 +1464,15 @@
 
         // The ALS is not available yet - use the screen off sensor to determine the initial
         // brightness
-        if (Float.isNaN(brightnessState) && autoBrightnessEnabled
+        if (Float.isNaN(brightnessState) && mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()
                 && mScreenOffBrightnessSensorController != null) {
             rawBrightnessState =
                     mScreenOffBrightnessSensorController.getAutomaticScreenBrightness();
             brightnessState = rawBrightnessState;
-            if (isValidBrightnessValue(brightnessState)) {
+            if (BrightnessUtils.isValidBrightnessValue(brightnessState)) {
                 brightnessState = clampScreenBrightness(brightnessState);
-                updateScreenBrightnessSetting = mCurrentScreenBrightnessSetting != brightnessState;
+                updateScreenBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness()
+                        != brightnessState;
                 mBrightnessReasonTemp.setReason(
                         BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR);
             }
@@ -1796,9 +1480,9 @@
 
         // Apply manual brightness.
         if (Float.isNaN(brightnessState)) {
-            rawBrightnessState = mCurrentScreenBrightnessSetting;
+            rawBrightnessState = currentBrightnessSetting;
             brightnessState = clampScreenBrightness(rawBrightnessState);
-            if (brightnessState != mCurrentScreenBrightnessSetting) {
+            if (brightnessState != currentBrightnessSetting) {
                 // The manually chosen screen brightness is outside of the currently allowed
                 // range (i.e., high-brightness-mode), make sure we tell the rest of the system
                 // by updating the setting.
@@ -1811,7 +1495,8 @@
                 : mAutomaticBrightnessController.getAmbientLux();
         for (int i = 0; i < displayBrightnessFollowers.size(); i++) {
             DisplayPowerControllerInterface follower = displayBrightnessFollowers.valueAt(i);
-            follower.setBrightnessToFollow(rawBrightnessState, convertToNits(rawBrightnessState),
+            follower.setBrightnessToFollow(rawBrightnessState,
+                    mDisplayBrightnessController.convertToNits(rawBrightnessState),
                     ambientLux, slowChange);
         }
 
@@ -1823,64 +1508,25 @@
         // Note throttling effectively changes the allowed brightness range, so, similarly to HBM,
         // we broadcast this change through setting.
         final float unthrottledBrightnessState = brightnessState;
-        if (mBrightnessThrottler.isThrottled()) {
-            mTempBrightnessEvent.setThermalMax(mBrightnessThrottler.getBrightnessCap());
-            brightnessState = Math.min(brightnessState, mBrightnessThrottler.getBrightnessCap());
-            mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_THROTTLED);
-            if (!mAppliedThrottling) {
-                // Brightness throttling is needed, so do so quickly.
-                // Later, when throttling is removed, we let other mechanisms decide on speed.
-                slowChange = false;
-            }
-            mAppliedThrottling = true;
-        } else if (mAppliedThrottling) {
-            mAppliedThrottling = false;
-        }
+        DisplayBrightnessState clampedState = mBrightnessClamperController.clamp(mPowerRequest,
+                brightnessState, slowChange);
+
+        brightnessState = clampedState.getBrightness();
+        slowChange = clampedState.isSlowChange();
+        // faster rate wins, at this point customAnimationRate == -1, strategy does not control
+        // customAnimationRate. Should be revisited if strategy start setting this value
+        customAnimationRate = Math.max(customAnimationRate, clampedState.getCustomAnimationRate());
+        mBrightnessReasonTemp.addModifier(clampedState.getBrightnessReason().getModifier());
 
         if (updateScreenBrightnessSetting) {
             // Tell the rest of the system about the new brightness in case we had to change it
             // for things like auto-brightness or high-brightness-mode. Note that we do this
-            // before applying the low power or dim transformations so that the slider
-            // accurately represents the full possible range, even if they range changes what
-            // it means in absolute terms.
-            updateScreenBrightnessSetting(brightnessState);
-        }
-
-        // Apply dimming by at least some minimum amount when user activity
-        // timeout is about to expire.
-        if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
-            if (brightnessState > PowerManager.BRIGHTNESS_MIN) {
-                brightnessState = Math.max(
-                        Math.min(brightnessState - mScreenBrightnessMinimumDimAmount,
-                                mScreenBrightnessDimConfig),
-                        PowerManager.BRIGHTNESS_MIN);
-                mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_DIMMED);
-            }
-            if (!mAppliedDimming) {
-                slowChange = false;
-            }
-            mAppliedDimming = true;
-        } else if (mAppliedDimming) {
-            slowChange = false;
-            mAppliedDimming = false;
-        }
-        // If low power mode is enabled, scale brightness by screenLowPowerBrightnessFactor
-        // as long as it is above the minimum threshold.
-        if (mPowerRequest.lowPowerMode) {
-            if (brightnessState > PowerManager.BRIGHTNESS_MIN) {
-                final float brightnessFactor =
-                        Math.min(mPowerRequest.screenLowPowerBrightnessFactor, 1);
-                final float lowPowerBrightnessFloat = (brightnessState * brightnessFactor);
-                brightnessState = Math.max(lowPowerBrightnessFloat, PowerManager.BRIGHTNESS_MIN);
-                mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_LOW_POWER);
-            }
-            if (!mAppliedLowPower) {
-                slowChange = false;
-            }
-            mAppliedLowPower = true;
-        } else if (mAppliedLowPower) {
-            slowChange = false;
-            mAppliedLowPower = false;
+            // only considering maxBrightness (ignoring brightness modifiers like low power or dim)
+            // so that the slider accurately represents the full possible range,
+            // even if they range changes what it means in absolute terms.
+            mDisplayBrightnessController.updateScreenBrightnessSetting(
+                    MathUtils.constrain(unthrottledBrightnessState,
+                            clampedState.getMinBrightness(), clampedState.getMaxBrightness()));
         }
 
         // The current brightness to use has been calculated at this point, and HbmController should
@@ -1889,13 +1535,15 @@
         // brightness sources (such as an app override) are not saved to the setting, but should be
         // reflected in HBM calculations.
         mBrightnessRangeController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
-                mBrightnessThrottler.getBrightnessMaxReason());
+                mBrightnessClamperController.getBrightnessMaxReason());
 
         // Animate the screen brightness when the screen is on or dozing.
-        // Skip the animation when the screen is off.
+        // Skip the animation when the screen is off or suspended.
         boolean brightnessAdjusted = false;
         final boolean brightnessIsTemporary =
-                mAppliedTemporaryBrightness || mAppliedTemporaryAutoBrightnessAdjustment;
+                (mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_TEMPORARY)
+                        || mAutomaticBrightnessStrategy
+                        .isTemporaryAutoBrightnessAdjustmentApplied();
         if (!mPendingScreenOff) {
             if (mSkipScreenOnBrightnessRamp) {
                 if (state == Display.STATE_ON) {
@@ -1916,7 +1564,8 @@
             }
 
             final boolean initialRampSkip = (state == Display.STATE_ON && mSkipRampState
-                    != RAMP_STATE_SKIP_NONE) || skipRampBecauseOfProximityChangeToNegative;
+                    != RAMP_STATE_SKIP_NONE) || mDisplayPowerProximityStateController
+                    .shouldSkipRampBecauseOfProximityChangeToNegative();
             // While dozing, sometimes the brightness is split into buckets. Rather than animating
             // through the buckets, which is unlikely to be smooth in the first place, just jump
             // right to the suggested brightness.
@@ -1950,13 +1599,25 @@
                 // We want to scale HDR brightness level with the SDR level, we also need to restore
                 // SDR brightness immediately when entering dim or low power mode.
                 animateValue = mBrightnessRangeController.getHdrBrightnessValue();
+                customAnimationRate = Math.max(customAnimationRate,
+                        mBrightnessRangeController.getHdrTransitionRate());
                 mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_HDR);
             }
 
+            // if doze or suspend state is requested, we want to finish brightnes animation fast
+            // to allow state animation to start
+            if (mPowerRequest.policy == DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE
+                    && (mPowerRequest.dozeScreenState == Display.STATE_UNKNOWN  // dozing
+                    || mPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND
+                    || mPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND)) {
+                customAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+                slowChange = false;
+            }
+
             final float currentBrightness = mPowerState.getScreenBrightness();
             final float currentSdrBrightness = mPowerState.getSdrScreenBrightness();
 
-            if (isValidBrightnessValue(animateValue)
+            if (BrightnessUtils.isValidBrightnessValue(animateValue)
                     && (animateValue != currentBrightness
                     || sdrAnimateValue != currentSdrBrightness)) {
                 boolean skipAnimation = initialRampSkip || hasBrightnessBuckets
@@ -1986,6 +1647,9 @@
                 if (skipAnimation) {
                     animateScreenBrightness(animateValue, sdrAnimateValue,
                             SCREEN_ANIMATION_RATE_MINIMUM);
+                } else if (customAnimationRate > 0) {
+                    animateScreenBrightness(animateValue, sdrAnimateValue,
+                            customAnimationRate, /* ignoreAnimationLimits = */true);
                 } else {
                     boolean isIncreasing = animateValue > currentBrightness;
                     final float rampSpeed;
@@ -2007,13 +1671,15 @@
             }
 
             notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange,
-                    wasShortTermModelActive, autoBrightnessEnabled, brightnessIsTemporary);
+                    wasShortTermModelActive, mAutomaticBrightnessStrategy.isAutoBrightnessEnabled(),
+                    brightnessIsTemporary, displayBrightnessState.getShouldUseAutoBrightness());
 
             // We save the brightness info *after* the brightness setting has been changed and
             // adjustments made so that the brightness info reflects the latest value.
-            brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting(), animateValue);
+            brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting(),
+                    animateValue, clampedState);
         } else {
-            brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting());
+            brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting(), clampedState);
         }
 
         // Only notify if the brightness adjustment is not temporary (i.e. slider has been released)
@@ -2049,13 +1715,20 @@
                 ? mCdsi.getReduceBrightColorsStrength() : -1);
         mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
         mTempBrightnessEvent.setWasShortTermModelActive(wasShortTermModelActive);
-        mTempBrightnessEvent.setAutomaticBrightnessEnabled(mUseAutoBrightness);
+        mTempBrightnessEvent.setDisplayBrightnessStrategyName(displayBrightnessState
+                .getDisplayBrightnessStrategyName());
+        mTempBrightnessEvent.setAutomaticBrightnessEnabled(
+                displayBrightnessState.getShouldUseAutoBrightness());
         // Temporary is what we use during slider interactions. We avoid logging those so that
         // we don't spam logcat when the slider is being used.
         boolean tempToTempTransition =
                 mTempBrightnessEvent.getReason().getReason() == BrightnessReason.REASON_TEMPORARY
                         && mLastBrightnessEvent.getReason().getReason()
                         == BrightnessReason.REASON_TEMPORARY;
+        // Purely for dumpsys;
+        final boolean isRbcEvent =
+                mLastBrightnessEvent.isRbcEnabled() != mTempBrightnessEvent.isRbcEnabled();
+
         if ((!mTempBrightnessEvent.equalsMainData(mLastBrightnessEvent) && !tempToTempTransition)
                 || brightnessAdjustmentFlags != 0) {
             mTempBrightnessEvent.setInitialBrightness(mLastBrightnessEvent.getBrightness());
@@ -2075,6 +1748,10 @@
             if (mBrightnessEventRingBuffer != null) {
                 mBrightnessEventRingBuffer.append(newEvent);
             }
+            if (isRbcEvent) {
+                mRbcEventRingBuffer.append(newEvent);
+            }
+
         }
 
         // Update display white-balance.
@@ -2092,6 +1769,7 @@
         // reporting the display is ready because we only need to ensure the screen is in the
         // right power state even as it continues to converge on the desired brightness.
         final boolean ready = mPendingScreenOnUnblocker == null
+                && mPendingScreenOnUnblockerByDisplayOffload == null
                 && (!mColorFadeEnabled || (!mColorFadeOnAnimator.isStarted()
                         && !mColorFadeOffAnimator.isStarted()))
                 && mPowerState.waitUntilClean(mCleanListener);
@@ -2106,12 +1784,8 @@
         }
 
         // Grab a wake lock if we have unfinished business.
-        if (!finished && !mUnfinishedBusiness) {
-            if (DEBUG) {
-                Slog.d(mTag, "Unfinished business...");
-            }
-            mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
-            mUnfinishedBusiness = true;
+        if (!finished) {
+            mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
         }
 
         // Notify the power manager when ready.
@@ -2130,12 +1804,8 @@
         }
 
         // Release the wake lock when we have no unfinished business.
-        if (finished && mUnfinishedBusiness) {
-            if (DEBUG) {
-                Slog.d(mTag, "Finished business...");
-            }
-            mUnfinishedBusiness = false;
-            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
+        if (finished) {
+            mWakelockController.releaseWakelock(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
         }
 
         // Record if dozing for future comparison.
@@ -2166,9 +1836,9 @@
 
     private void setDwbcLoggingEnabled(int arg) {
         if (mDisplayWhiteBalanceController != null) {
-            final boolean shouldEnable = (arg == 1);
-            mDisplayWhiteBalanceController.setLoggingEnabled(shouldEnable);
-            mDisplayWhiteBalanceSettings.setLoggingEnabled(shouldEnable);
+            final boolean enabled = (arg == 1);
+            mDisplayWhiteBalanceController.setLoggingEnabled(enabled);
+            mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled);
         }
     }
 
@@ -2183,7 +1853,7 @@
      */
     @Override
     public void ignoreProximitySensorUntilChanged() {
-        mHandler.sendEmptyMessage(MSG_IGNORE_PROXIMITY);
+        mDisplayPowerProximityStateController.ignoreProximitySensorUntilChanged();
     }
 
     @Override
@@ -2210,21 +1880,27 @@
 
     @Override
     public void setBrightnessFromOffload(float brightness) {
-        // The old DPC is no longer supported
+        Message msg = mHandler.obtainMessage(MSG_SET_BRIGHTNESS_FROM_OFFLOAD,
+                Float.floatToIntBits(brightness), 0 /*unused*/);
+        mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
     }
 
     @Override
     public float[] getAutoBrightnessLevels(
             @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
-        // The old DPC is no longer supported
-        return null;
+        int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+                Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
+        return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
     }
 
     @Override
     public float[] getAutoBrightnessLuxLevels(
             @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
-        // The old DPC is no longer supported
-        return null;
+        int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+                Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
+        return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
     }
 
     @Override
@@ -2241,18 +1917,29 @@
         }
     }
 
-    private boolean saveBrightnessInfo(float brightness) {
-        return saveBrightnessInfo(brightness, brightness);
+    @Override
+    public void onBootCompleted() {
+        Message msg = mHandler.obtainMessage(MSG_BOOT_COMPLETED);
+        mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
     }
 
-    private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) {
+    private boolean saveBrightnessInfo(float brightness) {
+        return saveBrightnessInfo(brightness, /* state= */ null);
+    }
+
+    private boolean saveBrightnessInfo(float brightness, @Nullable DisplayBrightnessState state) {
+        return saveBrightnessInfo(brightness, brightness, state);
+    }
+
+    private boolean saveBrightnessInfo(float brightness, float adjustedBrightness,
+            @Nullable DisplayBrightnessState state) {
         synchronized (mCachedBrightnessInfo) {
-            final float minBrightness = Math.min(
-                    mBrightnessRangeController.getCurrentBrightnessMin(),
-                    mBrightnessThrottler.getBrightnessCap());
+            float stateMax = state != null ? state.getMaxBrightness() : PowerManager.BRIGHTNESS_MAX;
+            float stateMin = state != null ? state.getMinBrightness() : PowerManager.BRIGHTNESS_MAX;
+            final float minBrightness = Math.max(stateMin, Math.min(
+                    mBrightnessRangeController.getCurrentBrightnessMin(), stateMax));
             final float maxBrightness = Math.min(
-                    mBrightnessRangeController.getCurrentBrightnessMax(),
-                    mBrightnessThrottler.getBrightnessCap());
+                    mBrightnessRangeController.getCurrentBrightnessMax(), stateMax);
             boolean changed = false;
 
             changed |=
@@ -2275,8 +1962,7 @@
                             mBrightnessRangeController.getTransitionPoint());
             changed |=
                     mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
-                            mBrightnessThrottler.getBrightnessMaxReason());
-
+                            mBrightnessClamperController.getBrightnessMaxReason());
             return changed;
         }
     }
@@ -2288,27 +1974,18 @@
     }
 
     private HighBrightnessModeController createHbmControllerLocked(
-            Runnable modeChangeCallback) {
-        final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
-        final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
-        final IBinder displayToken =
-                mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayTokenLocked();
-        final String displayUniqueId =
-                mLogicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
+            HighBrightnessModeMetadata hbmMetadata, Runnable modeChangeCallback) {
+        final DisplayDeviceConfig ddConfig = mDisplayDevice.getDisplayDeviceConfig();
+        final IBinder displayToken = mDisplayDevice.getDisplayTokenLocked();
+        final String displayUniqueId = mDisplayDevice.getUniqueId();
         final DisplayDeviceConfig.HighBrightnessModeData hbmData =
                 ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
-        final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+        final DisplayDeviceInfo info = mDisplayDevice.getDisplayDeviceInfoLocked();
         return mInjector.getHighBrightnessModeController(mHandler, info.width, info.height,
                 displayToken, displayUniqueId, PowerManager.BRIGHTNESS_MIN,
-                PowerManager.BRIGHTNESS_MAX, hbmData,
-                new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
-                    @Override
-                    public float getHdrBrightnessFromSdr(
-                            float sdrBrightness, float maxDesiredHdrSdrRatio) {
-                        return mDisplayDeviceConfig.getHdrBrightnessFromSdr(
-                                sdrBrightness, maxDesiredHdrSdrRatio);
-                    }
-                }, modeChangeCallback, mHighBrightnessModeMetadata, mContext);
+                PowerManager.BRIGHTNESS_MAX, hbmData, (sdrBrightness, maxDesiredHdrSdrRatio) ->
+                        mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness,
+                                maxDesiredHdrSdrRatio), modeChangeCallback, hbmMetadata, mContext);
     }
 
     private BrightnessThrottler createBrightnessThrottlerLocked() {
@@ -2359,18 +2036,72 @@
         }
     }
 
+    private void blockScreenOnByDisplayOffload(DisplayOffloadSession displayOffloadSession) {
+        if (mPendingScreenOnUnblockerByDisplayOffload != null || displayOffloadSession == null) {
+            return;
+        }
+        mScreenTurningOnWasBlockedByDisplayOffload = true;
+
+        Trace.asyncTraceBegin(
+                Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
+        mScreenOnBlockByDisplayOffloadStartRealTime = SystemClock.elapsedRealtime();
+
+        mPendingScreenOnUnblockerByDisplayOffload =
+                () -> onDisplayOffloadUnblockScreenOn(displayOffloadSession);
+        if (!displayOffloadSession.blockScreenOn(mPendingScreenOnUnblockerByDisplayOffload)) {
+            mPendingScreenOnUnblockerByDisplayOffload = null;
+            long delay =
+                    SystemClock.elapsedRealtime() - mScreenOnBlockByDisplayOffloadStartRealTime;
+            Slog.w(mTag, "Tried blocking screen on for offloading but failed. So, end trace after "
+                    + delay + " ms.");
+            Trace.asyncTraceEnd(
+                    Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
+            return;
+        }
+        Slog.i(mTag, "Blocking screen on for offloading.");
+    }
+
+    private void onDisplayOffloadUnblockScreenOn(DisplayOffloadSession displayOffloadSession) {
+        Message msg = mHandler.obtainMessage(MSG_OFFLOADING_SCREEN_ON_UNBLOCKED,
+                displayOffloadSession);
+        mHandler.sendMessage(msg);
+    }
+
+    private void unblockScreenOnByDisplayOffload() {
+        if (mPendingScreenOnUnblockerByDisplayOffload == null) {
+            return;
+        }
+        mPendingScreenOnUnblockerByDisplayOffload = null;
+        long delay = SystemClock.elapsedRealtime() - mScreenOnBlockByDisplayOffloadStartRealTime;
+        Slog.i(mTag, "Unblocked screen on for offloading after " + delay + " ms");
+        Trace.asyncTraceEnd(
+                Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
+    }
+
     private boolean setScreenState(int state) {
         return setScreenState(state, false /*reportOnly*/);
     }
 
     private boolean setScreenState(int state, boolean reportOnly) {
         final boolean isOff = (state == Display.STATE_OFF);
+        final boolean isOn = (state == Display.STATE_ON);
+        final boolean changed = mPowerState.getScreenState() != state;
 
-        if (mPowerState.getScreenState() != state
-                || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
+        // If the screen is turning on, give displayoffload a chance to do something before the
+        // screen actually turns on.
+        // TODO(b/316941732): add tests for this displayoffload screen-on blocker.
+        if (isOn && changed && !mScreenTurningOnWasBlockedByDisplayOffload) {
+            blockScreenOnByDisplayOffload(mDisplayOffloadSession);
+        } else if (!isOn && mScreenTurningOnWasBlockedByDisplayOffload) {
+            // No longer turning screen on, so unblock previous screen on blocking immediately.
+            unblockScreenOnByDisplayOffload();
+            mScreenTurningOnWasBlockedByDisplayOffload = false;
+        }
+
+        if (changed || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
             // If we are trying to turn screen off, give policy a chance to do something before we
             // actually turn the screen off.
-            if (isOff && !mScreenOffBecauseOfProximity) {
+            if (isOff && !mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) {
                 if (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_ON
                         || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
                     setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_OFF);
@@ -2383,8 +2114,9 @@
                 }
             }
 
-            if (!reportOnly && mPowerState.getScreenState() != state
-                    && readyToUpdateDisplayState()) {
+            if (!reportOnly && changed && readyToUpdateDisplayState()
+                    && mPendingScreenOffUnblocker == null
+                    && mPendingScreenOnUnblockerByDisplayOffload == null) {
                 Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state);
 
                 String propertyKey = "debug.tracing.screen_state";
@@ -2410,7 +2142,7 @@
         // it is only removed once the window manager tells us that the activity has
         // finished drawing underneath.
         if (isOff && mReportedScreenStateToPolicy != REPORTED_TO_POLICY_SCREEN_OFF
-                && !mScreenOffBecauseOfProximity) {
+                && !mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) {
             setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF);
             unblockScreenOn();
             mWindowManagerPolicy.screenTurnedOff(mDisplayId, mIsInTransition);
@@ -2436,12 +2168,16 @@
         }
 
         // Return true if the screen isn't blocked.
-        return mPendingScreenOnUnblocker == null;
+        return mPendingScreenOnUnblocker == null
+                && mPendingScreenOnUnblockerByDisplayOffload == null;
     }
 
     private void setReportedScreenState(int state) {
         Trace.traceCounter(Trace.TRACE_TAG_POWER, "ReportedScreenStateToPolicy", state);
         mReportedScreenStateToPolicy = state;
+        if (state == REPORTED_TO_POLICY_SCREEN_ON) {
+            mScreenTurningOnWasBlockedByDisplayOffload = false;
+        }
     }
 
     private void loadAmbientLightSensor() {
@@ -2456,18 +2192,6 @@
                 mDisplayDeviceConfig.getScreenOffBrightnessSensor(), SensorUtils.NO_FALLBACK);
     }
 
-    private void loadProximitySensor() {
-        if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT || mDisplayId != Display.DEFAULT_DISPLAY) {
-            return;
-        }
-        mProximitySensor = SensorUtils.findSensor(mSensorManager,
-                mDisplayDeviceConfig.getProximitySensor(), Sensor.TYPE_PROXIMITY);
-        if (mProximitySensor != null) {
-            mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
-                    TYPICAL_PROXIMITY_THRESHOLD);
-        }
-    }
-
     private float clampScreenBrightness(float value) {
         if (Float.isNaN(value)) {
             value = PowerManager.BRIGHTNESS_MIN;
@@ -2476,18 +2200,18 @@
                 mBrightnessRangeController.getCurrentBrightnessMax());
     }
 
-    // Checks whether the brightness is within the valid brightness range, not including off.
-    private boolean isValidBrightnessValue(float brightness) {
-        return brightness >= PowerManager.BRIGHTNESS_MIN
-                && brightness <= PowerManager.BRIGHTNESS_MAX;
+    private void animateScreenBrightness(float target, float sdrTarget, float rate) {
+        animateScreenBrightness(target, sdrTarget, rate, /* ignoreAnimationLimits = */false);
     }
 
-    private void animateScreenBrightness(float target, float sdrTarget, float rate) {
+    private void animateScreenBrightness(float target, float sdrTarget, float rate,
+            boolean ignoreAnimationLimits) {
         if (DEBUG) {
             Slog.d(mTag, "Animating brightness: target=" + target + ", sdrTarget=" + sdrTarget
                     + ", rate=" + rate);
         }
-        if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate, false)) {
+        if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate,
+                ignoreAnimationLimits)) {
             Trace.traceCounter(Trace.TRACE_TAG_POWER, "TargetScreenBrightness", (int) target);
 
             String propertyKey = "debug.tracing.screen_brightness";
@@ -2655,102 +2379,11 @@
 
     private final Runnable mCleanListener = this::sendUpdatePowerState;
 
-    private void setProximitySensorEnabled(boolean enable) {
-        if (enable) {
-            if (!mProximitySensorEnabled) {
-                // Register the listener.
-                // Proximity sensor state already cleared initially.
-                mProximitySensorEnabled = true;
-                mIgnoreProximityUntilChanged = false;
-                mSensorManager.registerListener(mProximitySensorListener, mProximitySensor,
-                        SensorManager.SENSOR_DELAY_NORMAL, mHandler);
-            }
-        } else {
-            if (mProximitySensorEnabled) {
-                // Unregister the listener.
-                // Clear the proximity sensor state for next time.
-                mProximitySensorEnabled = false;
-                mProximity = PROXIMITY_UNKNOWN;
-                mIgnoreProximityUntilChanged = false;
-                mPendingProximity = PROXIMITY_UNKNOWN;
-                mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
-                mSensorManager.unregisterListener(mProximitySensorListener);
-                clearPendingProximityDebounceTime(); // release wake lock (must be last)
-            }
-        }
-    }
-
-    private void handleProximitySensorEvent(long time, boolean positive) {
-        if (mProximitySensorEnabled) {
-            if (mPendingProximity == PROXIMITY_NEGATIVE && !positive) {
-                return; // no change
-            }
-            if (mPendingProximity == PROXIMITY_POSITIVE && positive) {
-                return; // no change
-            }
-
-            // Only accept a proximity sensor reading if it remains
-            // stable for the entire debounce delay.  We hold a wake lock while
-            // debouncing the sensor.
-            mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
-            if (positive) {
-                mPendingProximity = PROXIMITY_POSITIVE;
-                setPendingProximityDebounceTime(
-                        time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY); // acquire wake lock
-            } else {
-                mPendingProximity = PROXIMITY_NEGATIVE;
-                setPendingProximityDebounceTime(
-                        time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY); // acquire wake lock
-            }
-
-            // Debounce the new sensor reading.
-            debounceProximitySensor();
-        }
-    }
-
-    private void debounceProximitySensor() {
-        if (mProximitySensorEnabled
-                && mPendingProximity != PROXIMITY_UNKNOWN
-                && mPendingProximityDebounceTime >= 0) {
-            final long now = mClock.uptimeMillis();
-            if (mPendingProximityDebounceTime <= now) {
-                if (mProximity != mPendingProximity) {
-                    // if the status of the sensor changed, stop ignoring.
-                    mIgnoreProximityUntilChanged = false;
-                    Slog.i(mTag, "No longer ignoring proximity [" + mPendingProximity + "]");
-                }
-                // Sensor reading accepted.  Apply the change then release the wake lock.
-                mProximity = mPendingProximity;
-                updatePowerState();
-                clearPendingProximityDebounceTime(); // release wake lock (must be last)
-            } else {
-                // Need to wait a little longer.
-                // Debounce again later.  We continue holding a wake lock while waiting.
-                Message msg = mHandler.obtainMessage(MSG_PROXIMITY_SENSOR_DEBOUNCED);
-                mHandler.sendMessageAtTime(msg, mPendingProximityDebounceTime);
-            }
-        }
-    }
-
-    private void clearPendingProximityDebounceTime() {
-        if (mPendingProximityDebounceTime >= 0) {
-            mPendingProximityDebounceTime = -1;
-            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxDebounce);
-        }
-    }
-
-    private void setPendingProximityDebounceTime(long debounceTime) {
-        if (mPendingProximityDebounceTime < 0) {
-            mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxDebounce);
-        }
-        mPendingProximityDebounceTime = debounceTime;
-    }
-
     private void sendOnStateChangedWithWakelock() {
-        if (!mOnStateChangedPending) {
-            mOnStateChangedPending = true;
-            mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdOnStateChanged);
-            mHandler.post(mOnStateChangedRunnable);
+        boolean wakeLockAcquired = mWakelockController.acquireWakelock(
+                WakelockController.WAKE_LOCK_STATE_CHANGED);
+        if (wakeLockAcquired) {
+            mHandler.post(mWakelockController.getOnStateChangedRunnable());
         }
     }
 
@@ -2762,12 +2395,15 @@
     }
 
     private void handleSettingsChange(boolean userSwitch) {
-        mPendingScreenBrightnessSetting = getScreenBrightnessSetting();
-        mPendingAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
+        mDisplayBrightnessController
+                .setPendingScreenBrightness(mDisplayBrightnessController
+                        .getScreenBrightnessSetting());
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(userSwitch);
         if (userSwitch) {
             // Don't treat user switches as user initiated change.
-            setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
-            updateAutoBrightnessAdjustment();
+            mDisplayBrightnessController
+                    .setAndNotifyCurrentScreenBrightness(mDisplayBrightnessController
+                            .getPendingScreenBrightness());
             if (mAutomaticBrightnessController != null) {
                 mAutomaticBrightnessController.resetShortTermModel();
             }
@@ -2781,129 +2417,59 @@
                 Settings.System.SCREEN_BRIGHTNESS_MODE,
                 Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
         mHandler.postAtTime(() -> {
-            mUseAutoBrightness = screenBrightnessModeSetting
-                    == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+            mAutomaticBrightnessStrategy.setUseAutoBrightness(screenBrightnessModeSetting
+                    == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
             updatePowerState();
         }, mClock.uptimeMillis());
     }
 
-    private float getAutoBrightnessAdjustmentSetting() {
-        final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
-                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
-        return Float.isNaN(adj) ? 0.0f : clampAutoBrightnessAdjustment(adj);
-    }
 
     @Override
     public float getScreenBrightnessSetting() {
-        float brightness = mBrightnessSetting.getBrightness();
-        if (Float.isNaN(brightness)) {
-            brightness = mScreenBrightnessDefault;
-        }
-        return clampAbsoluteBrightness(brightness);
-    }
-
-    private void loadNitBasedBrightnessSetting() {
-        if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
-            float brightnessNitsForDefaultDisplay =
-                    mBrightnessSetting.getBrightnessNitsForDefaultDisplay();
-            if (brightnessNitsForDefaultDisplay >= 0) {
-                float brightnessForDefaultDisplay = getBrightnessFromNits(
-                        brightnessNitsForDefaultDisplay);
-                if (isValidBrightnessValue(brightnessForDefaultDisplay)) {
-                    mBrightnessSetting.setBrightness(brightnessForDefaultDisplay);
-                    mCurrentScreenBrightnessSetting = brightnessForDefaultDisplay;
-                    return;
-                }
-            }
-        }
-        mCurrentScreenBrightnessSetting = getScreenBrightnessSetting();
+        return mDisplayBrightnessController.getScreenBrightnessSetting();
     }
 
     @Override
     public void setBrightness(float brightnessValue, int userSerial) {
-        // Update the setting, which will eventually call back into DPC to have us actually update
-        // the display with the new value.
-        float clampedBrightnessValue = clampScreenBrightness(brightnessValue);
-        mBrightnessSetting.setUserSerial(userSerial);
-        mBrightnessSetting.setBrightness(clampedBrightnessValue);
-        if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
-            float nits = convertToNits(clampedBrightnessValue);
-            if (nits >= 0) {
-                mBrightnessSetting.setBrightnessNitsForDefaultDisplay(nits);
-            }
-        }
+        mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightnessValue),
+                userSerial);
     }
 
     @Override
-    public void onBootCompleted() {
-        Message msg = mHandler.obtainMessage(MSG_BOOT_COMPLETED);
-        mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
+    public int getDisplayId() {
+        return mDisplayId;
     }
 
-    private void updateScreenBrightnessSetting(float brightnessValue) {
-        if (!isValidBrightnessValue(brightnessValue)
-                || brightnessValue == mCurrentScreenBrightnessSetting) {
-            return;
-        }
-        setCurrentScreenBrightness(brightnessValue);
-        setBrightness(brightnessValue);
+    @Override
+    public int getLeadDisplayId() {
+        return mLeadDisplayId;
     }
 
-    private void setCurrentScreenBrightness(float brightnessValue) {
-        if (brightnessValue != mCurrentScreenBrightnessSetting) {
-            mCurrentScreenBrightnessSetting = brightnessValue;
-            postBrightnessChangeRunnable();
+    @Override
+    public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux,
+            boolean slowChange) {
+        mBrightnessRangeController.onAmbientLuxChange(ambientLux);
+        if (nits == BrightnessMappingStrategy.INVALID_NITS) {
+            mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness, slowChange);
+        } else {
+            float brightness = mDisplayBrightnessController.getBrightnessFromNits(nits);
+            if (BrightnessUtils.isValidBrightnessValue(brightness)) {
+                mDisplayBrightnessController.setBrightnessToFollow(brightness, slowChange);
+            } else {
+                // The device does not support nits
+                mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness,
+                        slowChange);
+            }
         }
-    }
-
-    private void putAutoBrightnessAdjustmentSetting(float adjustment) {
-        if (mDisplayId == Display.DEFAULT_DISPLAY) {
-            mAutoBrightnessAdjustment = adjustment;
-            Settings.System.putFloatForUser(mContext.getContentResolver(),
-                    Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adjustment,
-                    UserHandle.USER_CURRENT);
-        }
-    }
-
-    private boolean updateAutoBrightnessAdjustment() {
-        if (Float.isNaN(mPendingAutoBrightnessAdjustment)) {
-            return false;
-        }
-        if (mAutoBrightnessAdjustment == mPendingAutoBrightnessAdjustment) {
-            mPendingAutoBrightnessAdjustment = Float.NaN;
-            return false;
-        }
-        mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment;
-        mPendingAutoBrightnessAdjustment = Float.NaN;
-        mTemporaryAutoBrightnessAdjustment = Float.NaN;
-        return true;
-    }
-
-    // We want to return true if the user has set the screen brightness.
-    // RBC on, off, and intensity changes will return false.
-    // Slider interactions whilst in RBC will return true, just as when in non-rbc.
-    private boolean updateUserSetScreenBrightness() {
-        if ((Float.isNaN(mPendingScreenBrightnessSetting)
-                || mPendingScreenBrightnessSetting < 0.0f)) {
-            return false;
-        }
-        if (mCurrentScreenBrightnessSetting == mPendingScreenBrightnessSetting) {
-            mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-            mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-            return false;
-        }
-        setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
-        mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting;
-        mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-        mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-        return true;
+        sendUpdatePowerState();
     }
 
     private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
             boolean wasShortTermModelActive, boolean autobrightnessEnabled,
-            boolean brightnessIsTemporary) {
-        final float brightnessInNits = convertToAdjustedNits(brightness);
+            boolean brightnessIsTemporary, boolean shouldUseAutoBrightness) {
 
+        final float brightnessInNits =
+                mDisplayBrightnessController.convertToAdjustedNits(brightness);
         // Don't report brightness to brightnessTracker:
         // If brightness is temporary (ie the slider has not been released)
         // or if we are in idle screen brightness mode.
@@ -2915,12 +2481,13 @@
                 || mAutomaticBrightnessController.isInIdleMode()
                 || !autobrightnessEnabled
                 || mBrightnessTracker == null
-                || !mUseAutoBrightness
+                || !shouldUseAutoBrightness
                 || brightnessInNits < 0.0f) {
             return;
         }
 
-        if (userInitiated && !mAutomaticBrightnessController.hasValidAmbientLux()) {
+        if (userInitiated && (mAutomaticBrightnessController == null
+                || !mAutomaticBrightnessController.hasValidAmbientLux())) {
             // If we don't have a valid lux reading we can't report a valid
             // slider event so notify as if the system changed the brightness.
             userInitiated = false;
@@ -2939,96 +2506,33 @@
                 mAutomaticBrightnessController.getLastSensorTimestamps());
     }
 
-    private float convertToNits(float brightness) {
-        if (mAutomaticBrightnessController == null) {
-            return BrightnessMappingStrategy.INVALID_NITS;
+    @Override
+    public void addDisplayBrightnessFollower(DisplayPowerControllerInterface follower) {
+        synchronized (mLock) {
+            mDisplayBrightnessFollowers.append(follower.getDisplayId(), follower);
+            sendUpdatePowerStateLocked();
         }
-        return mAutomaticBrightnessController.convertToNits(brightness);
     }
 
-    private float convertToAdjustedNits(float brightness) {
-        if (mAutomaticBrightnessController == null) {
-            return BrightnessMappingStrategy.INVALID_NITS;
+    @Override
+    public void removeDisplayBrightnessFollower(DisplayPowerControllerInterface follower) {
+        synchronized (mLock) {
+            mDisplayBrightnessFollowers.remove(follower.getDisplayId());
+            mHandler.postAtTime(() -> follower.setBrightnessToFollow(
+                    PowerManager.BRIGHTNESS_INVALID_FLOAT, BrightnessMappingStrategy.INVALID_NITS,
+                    /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
         }
-        return mAutomaticBrightnessController.convertToAdjustedNits(brightness);
-    }
-
-    private float getBrightnessFromNits(float nits) {
-        if (mAutomaticBrightnessController == null) {
-            return PowerManager.BRIGHTNESS_INVALID_FLOAT;
-        }
-        return mAutomaticBrightnessController.getBrightnessFromNits(nits);
     }
 
     @GuardedBy("mLock")
-    private void updatePendingProximityRequestsLocked() {
-        mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked;
-        mPendingWaitForNegativeProximityLocked = false;
-
-        if (mIgnoreProximityUntilChanged) {
-            // Also, lets stop waiting for negative proximity if we're ignoring it.
-            mWaitingForNegativeProximity = false;
+    private void clearDisplayBrightnessFollowersLocked() {
+        for (int i = 0; i < mDisplayBrightnessFollowers.size(); i++) {
+            DisplayPowerControllerInterface follower = mDisplayBrightnessFollowers.valueAt(i);
+            mHandler.postAtTime(() -> follower.setBrightnessToFollow(
+                    PowerManager.BRIGHTNESS_INVALID_FLOAT, BrightnessMappingStrategy.INVALID_NITS,
+                    /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
         }
-    }
-
-    private void ignoreProximitySensorUntilChangedInternal() {
-        if (!mIgnoreProximityUntilChanged
-                && mProximity == PROXIMITY_POSITIVE) {
-            // Only ignore if it is still reporting positive (near)
-            mIgnoreProximityUntilChanged = true;
-            Slog.i(mTag, "Ignoring proximity");
-            updatePowerState();
-        }
-    }
-
-    private final Runnable mOnStateChangedRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mOnStateChangedPending = false;
-            mCallbacks.onStateChanged();
-            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged);
-        }
-    };
-
-    private void sendOnProximityPositiveWithWakelock() {
-        mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxPositive);
-        mHandler.post(mOnProximityPositiveRunnable);
-        mOnProximityPositiveMessages++;
-    }
-
-    private final Runnable mOnProximityPositiveRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mOnProximityPositiveMessages--;
-            mCallbacks.onProximityPositive();
-            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive);
-        }
-    };
-
-    private void sendOnProximityNegativeWithWakelock() {
-        mOnProximityNegativeMessages++;
-        mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxNegative);
-        mHandler.post(mOnProximityNegativeRunnable);
-    }
-
-    private final Runnable mOnProximityNegativeRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mOnProximityNegativeMessages--;
-            mCallbacks.onProximityNegative();
-            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative);
-        }
-    };
-
-    /**
-     * Indicates whether the display state is ready to update. If this is the default display, we
-     * want to update it right away so that we can draw the boot animation on it. If it is not
-     * the default display, drawing the boot animation on it would look incorrect, so we need
-     * to wait until boot is completed.
-     * @return True if the display state is ready to update
-     */
-    private boolean readyToUpdateDisplayState() {
-        return mDisplayId == Display.DEFAULT_DISPLAY || mBootCompleted;
+        mDisplayBrightnessFollowers.clear();
     }
 
     @Override
@@ -3040,31 +2544,23 @@
             pw.println("  mLeadDisplayId=" + mLeadDisplayId);
             pw.println("  mLightSensor=" + mLightSensor);
             pw.println("  mDisplayBrightnessFollowers=" + mDisplayBrightnessFollowers);
-            pw.println("  mDozeStateOverride=" + mDozeStateOverride);
 
             pw.println();
             pw.println("Display Power Controller Locked State:");
             pw.println("  mDisplayReadyLocked=" + mDisplayReadyLocked);
             pw.println("  mPendingRequestLocked=" + mPendingRequestLocked);
             pw.println("  mPendingRequestChangedLocked=" + mPendingRequestChangedLocked);
-            pw.println("  mPendingWaitForNegativeProximityLocked="
-                    + mPendingWaitForNegativeProximityLocked);
             pw.println("  mPendingUpdatePowerStateLocked=" + mPendingUpdatePowerStateLocked);
         }
 
         pw.println();
         pw.println("Display Power Controller Configuration:");
-        pw.println("  mScreenBrightnessRangeDefault=" + mScreenBrightnessDefault);
         pw.println("  mScreenBrightnessDozeConfig=" + mScreenBrightnessDozeConfig);
-        pw.println("  mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig);
         pw.println("  mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig);
-        pw.println("  mAllowAutoBrightnessWhileDozingConfig="
-                + mAllowAutoBrightnessWhileDozingConfig);
-        pw.println("  mPersistBrightnessNitsForDefaultDisplay="
-                + mPersistBrightnessNitsForDefaultDisplay);
         pw.println("  mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp);
         pw.println("  mColorFadeFadesConfig=" + mColorFadeFadesConfig);
         pw.println("  mColorFadeEnabled=" + mColorFadeEnabled);
+        pw.println("  mIsDisplayInternal=" + mIsDisplayInternal);
         synchronized (mCachedBrightnessInfo) {
             pw.println("  mCachedBrightnessInfo.brightness="
                     + mCachedBrightnessInfo.brightness.value);
@@ -3082,7 +2578,6 @@
         }
         pw.println("  mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
         pw.println("  mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
-
         mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
     }
 
@@ -3090,35 +2585,9 @@
         pw.println();
         pw.println("Display Power Controller Thread State:");
         pw.println("  mPowerRequest=" + mPowerRequest);
-        pw.println("  mUnfinishedBusiness=" + mUnfinishedBusiness);
-        pw.println("  mWaitingForNegativeProximity=" + mWaitingForNegativeProximity);
-        pw.println("  mProximitySensor=" + mProximitySensor);
-        pw.println("  mProximitySensorEnabled=" + mProximitySensorEnabled);
-        pw.println("  mProximityThreshold=" + mProximityThreshold);
-        pw.println("  mProximity=" + proximityToString(mProximity));
-        pw.println("  mPendingProximity=" + proximityToString(mPendingProximity));
-        pw.println("  mPendingProximityDebounceTime="
-                + TimeUtils.formatUptime(mPendingProximityDebounceTime));
-        pw.println("  mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity);
-        pw.println("  mLastUserSetScreenBrightness=" + mLastUserSetScreenBrightness);
-        pw.println("  mPendingScreenBrightnessSetting="
-                + mPendingScreenBrightnessSetting);
-        pw.println("  mTemporaryScreenBrightness=" + mTemporaryScreenBrightness);
-        pw.println("  mBrightnessToFollow=" + mBrightnessToFollow);
-        pw.println("  mBrightnessToFollowSlowChange=" + mBrightnessToFollowSlowChange);
-        pw.println("  mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
         pw.println("  mBrightnessReason=" + mBrightnessReason);
-        pw.println("  mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
-        pw.println("  mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment);
-        pw.println("  mAppliedAutoBrightness=" + mAppliedAutoBrightness);
         pw.println("  mAppliedDimming=" + mAppliedDimming);
-        pw.println("  mAppliedLowPower=" + mAppliedLowPower);
         pw.println("  mAppliedThrottling=" + mAppliedThrottling);
-        pw.println("  mAppliedScreenBrightnessOverride=" + mAppliedScreenBrightnessOverride);
-        pw.println("  mAppliedTemporaryBrightness=" + mAppliedTemporaryBrightness);
-        pw.println("  mAppliedTemporaryAutoBrightnessAdjustment="
-                + mAppliedTemporaryAutoBrightnessAdjustment);
-        pw.println("  mAppliedBrightnessBoost=" + mAppliedBrightnessBoost);
         pw.println("  mDozing=" + mDozing);
         pw.println("  mSkipRampState=" + skipRampStateToString(mSkipRampState));
         pw.println("  mScreenOnBlockStartRealTime=" + mScreenOnBlockStartRealTime);
@@ -3129,9 +2598,8 @@
         pw.println("  mReportedToPolicy="
                 + reportedToPolicyToString(mReportedScreenStateToPolicy));
         pw.println("  mIsRbcActive=" + mIsRbcActive);
-        pw.println("  mOnStateChangePending=" + mOnStateChangedPending);
-        pw.println("  mOnProximityPositiveMessages=" + mOnProximityPositiveMessages);
-        pw.println("  mOnProximityNegativeMessages=" + mOnProximityNegativeMessages);
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
+        mAutomaticBrightnessStrategy.dump(ipw);
 
         if (mScreenBrightnessRampAnimator != null) {
             pw.println("  mScreenBrightnessRampAnimator.isAnimating()="
@@ -3156,6 +2624,8 @@
             dumpBrightnessEvents(pw);
         }
 
+        dumpRbcEvents(pw);
+
         if (mScreenOffBrightnessSensorController != null) {
             mScreenOffBrightnessSensorController.dump(pw);
         }
@@ -3173,21 +2643,30 @@
             mDisplayWhiteBalanceController.dump(pw);
             mDisplayWhiteBalanceSettings.dump(pw);
         }
-    }
 
-    private static String proximityToString(int state) {
-        switch (state) {
-            case PROXIMITY_UNKNOWN:
-                return "Unknown";
-            case PROXIMITY_NEGATIVE:
-                return "Negative";
-            case PROXIMITY_POSITIVE:
-                return "Positive";
-            default:
-                return Integer.toString(state);
+        pw.println();
+
+        if (mWakelockController != null) {
+            mWakelockController.dumpLocal(pw);
+        }
+
+        pw.println();
+        if (mDisplayBrightnessController != null) {
+            mDisplayBrightnessController.dump(pw);
+        }
+
+        pw.println();
+        if (mDisplayStateController != null) {
+            mDisplayStateController.dumpsys(pw);
+        }
+
+        pw.println();
+        if (mBrightnessClamperController != null) {
+            mBrightnessClamperController.dump(ipw);
         }
     }
 
+
     private static String reportedToPolicyToString(int state) {
         switch (state) {
             case REPORTED_TO_POLICY_SCREEN_OFF:
@@ -3228,14 +2707,20 @@
         }
     }
 
-    private static float clampAbsoluteBrightness(float value) {
-        return MathUtils.constrain(value, PowerManager.BRIGHTNESS_MIN,
-                PowerManager.BRIGHTNESS_MAX);
+    private void dumpRbcEvents(PrintWriter pw) {
+        int size = mRbcEventRingBuffer.size();
+        if (size < 1) {
+            pw.println("No Reduce Bright Colors Adjustments");
+            return;
+        }
+
+        pw.println("Reduce Bright Colors Adjustments Last " + size + " Events: ");
+        BrightnessEvent[] eventArray = mRbcEventRingBuffer.toArray();
+        for (int i = 0; i < mRbcEventRingBuffer.size(); i++) {
+            pw.println("  " + eventArray[i]);
+        }
     }
 
-    private static float clampAutoBrightnessAdjustment(float value) {
-        return MathUtils.constrain(value, -1.0f, 1.0f);
-    }
 
     private void noteScreenState(int screenState) {
         // Log screen state change with display id
@@ -3363,20 +2848,21 @@
         // It's easier to check if the brightness is at maximum level using the brightness
         // value untouched by any modifiers
         boolean brightnessIsMax = unmodifiedBrightness == event.getHbmMax();
-        float brightnessInNits = convertToAdjustedNits(event.getBrightness());
+        float brightnessInNits =
+                mDisplayBrightnessController.convertToAdjustedNits(event.getBrightness());
         float appliedLowPowerMode = event.isLowPowerModeSet() ? event.getPowerFactor() : -1f;
         int appliedRbcStrength  = event.isRbcEnabled() ? event.getRbcStrength() : -1;
         float appliedHbmMaxNits =
                 event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
-                ? -1f : convertToAdjustedNits(event.getHbmMax());
+                ? -1f : mDisplayBrightnessController.convertToAdjustedNits(event.getHbmMax());
         // thermalCapNits set to -1 if not currently capping max brightness
         float appliedThermalCapNits =
                 event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
-                ? -1f : convertToAdjustedNits(event.getThermalMax());
-
+                ? -1f : mDisplayBrightnessController.convertToAdjustedNits(event.getThermalMax());
         if (mIsDisplayInternal) {
             FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
-                    convertToAdjustedNits(event.getInitialBrightness()),
+                    mDisplayBrightnessController
+                            .convertToAdjustedNits(event.getInitialBrightness()),
                     brightnessInNits,
                     event.getLux(),
                     event.getPhysicalDisplayId(),
@@ -3393,7 +2879,8 @@
                     event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
                     event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR,
                     (modifier & BrightnessReason.MODIFIER_LOW_POWER) > 0,
-                    mBrightnessThrottler.getBrightnessMaxReason(),
+                    mBrightnessClamperController.getBrightnessMaxReason(),
+                    // TODO: (flc) add brightnessMinReason here too.
                     (modifier & BrightnessReason.MODIFIER_DIMMED) > 0,
                     event.isRbcEnabled(),
                     (flags & BrightnessEvent.FLAG_INVALID_LUX) > 0,
@@ -3404,6 +2891,17 @@
         }
     }
 
+    /**
+     * Indicates whether the display state is ready to update. If this is the default display, we
+     * want to update it right away so that we can draw the boot animation on it. If it is not
+     * the default display, drawing the boot animation on it would look incorrect, so we need
+     * to wait until boot is completed.
+     * @return True if the display state is ready to update
+     */
+    private boolean readyToUpdateDisplayState() {
+        return mDisplayId == Display.DEFAULT_DISPLAY || mBootCompleted;
+    }
+
     private final class DisplayControllerHandler extends Handler {
         DisplayControllerHandler(Looper looper) {
             super(looper, null, true /*async*/);
@@ -3416,10 +2914,6 @@
                     updatePowerState();
                     break;
 
-                case MSG_PROXIMITY_SENSOR_DEBOUNCED:
-                    debounceProximitySensor();
-                    break;
-
                 case MSG_SCREEN_ON_UNBLOCKED:
                     if (mPendingScreenOnUnblocker == msg.obj) {
                         unblockScreenOn();
@@ -3432,27 +2926,38 @@
                         updatePowerState();
                     }
                     break;
+                case MSG_OFFLOADING_SCREEN_ON_UNBLOCKED:
+                    if (mDisplayOffloadSession == msg.obj) {
+                        unblockScreenOnByDisplayOffload();
+                        updatePowerState();
+                    }
+                    break;
                 case MSG_CONFIGURE_BRIGHTNESS:
-                    mBrightnessConfiguration = (BrightnessConfiguration) msg.obj;
-                    mShouldResetShortTermModel = msg.arg1 == 1;
+                    BrightnessConfiguration brightnessConfiguration =
+                            (BrightnessConfiguration) msg.obj;
+                    mAutomaticBrightnessStrategy.setBrightnessConfiguration(brightnessConfiguration,
+                            msg.arg1 == 1);
+                    if (mBrightnessTracker != null) {
+                        mBrightnessTracker
+                                .setShouldCollectColorSample(brightnessConfiguration != null
+                                        && brightnessConfiguration.shouldCollectColorSamples());
+                    }
                     updatePowerState();
                     break;
 
                 case MSG_SET_TEMPORARY_BRIGHTNESS:
                     // TODO: Should we have a a timeout for the temporary brightness?
-                    mTemporaryScreenBrightness = Float.intBitsToFloat(msg.arg1);
+                    mDisplayBrightnessController
+                            .setTemporaryBrightness(Float.intBitsToFloat(msg.arg1));
                     updatePowerState();
                     break;
 
                 case MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT:
-                    mTemporaryAutoBrightnessAdjustment = Float.intBitsToFloat(msg.arg1);
+                    mAutomaticBrightnessStrategy
+                            .setTemporaryAutoBrightnessAdjustment(Float.intBitsToFloat(msg.arg1));
                     updatePowerState();
                     break;
 
-                case MSG_IGNORE_PROXIMITY:
-                    ignoreProximitySensorUntilChangedInternal();
-                    break;
-
                 case MSG_STOP:
                     cleanupHandlerThreadAfterStop();
                     break;
@@ -3500,27 +3005,15 @@
                 case MSG_SET_DWBC_LOGGING_ENABLED:
                     setDwbcLoggingEnabled(msg.arg1);
                     break;
+                case MSG_SET_BRIGHTNESS_FROM_OFFLOAD:
+                    mDisplayBrightnessController.setBrightnessFromOffload(
+                            Float.intBitsToFloat(msg.arg1));
+                    updatePowerState();
+                    break;
             }
         }
     }
 
-    private final SensorEventListener mProximitySensorListener = new SensorEventListener() {
-        @Override
-        public void onSensorChanged(SensorEvent event) {
-            if (mProximitySensorEnabled) {
-                final long time = mClock.uptimeMillis();
-                final float distance = event.values[0];
-                boolean positive = distance >= 0.0f && distance < mProximityThreshold;
-                handleProximitySensorEvent(time, positive);
-            }
-        }
-
-        @Override
-        public void onAccuracyChanged(Sensor sensor, int accuracy) {
-            // Not used.
-        }
-    };
-
 
     private final class SettingsObserver extends ContentObserver {
         SettingsObserver(Handler handler) {
@@ -3531,6 +3024,16 @@
         public void onChange(boolean selfChange, Uri uri) {
             if (uri.equals(Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE))) {
                 handleBrightnessModeChange();
+            } else if (uri.equals(Settings.System.getUriFor(
+                    Settings.System.SCREEN_BRIGHTNESS_FOR_ALS))) {
+                int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+                        Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL,
+                        UserHandle.USER_CURRENT);
+                Slog.i(mTag, "Setting up auto-brightness for preset "
+                        + autoBrightnessPresetToString(preset));
+                setUpAutoBrightness(mContext, mHandler);
+                sendUpdatePowerState();
             } else {
                 handleSettingsChange(false /* userSwitch */);
             }
@@ -3581,28 +3084,6 @@
         msg.sendToTarget();
     }
 
-    @VisibleForTesting
-    String getSuspendBlockerUnfinishedBusinessId(int displayId) {
-        return "[" + displayId + "]unfinished business";
-    }
-
-    String getSuspendBlockerOnStateChangedId(int displayId) {
-        return "[" + displayId + "]on state changed";
-    }
-
-    String getSuspendBlockerProxPositiveId(int displayId) {
-        return "[" + displayId + "]prox positive";
-    }
-
-    String getSuspendBlockerProxNegativeId(int displayId) {
-        return "[" + displayId + "]prox negative";
-    }
-
-    @VisibleForTesting
-    String getSuspendBlockerProxDebounceId(int displayId) {
-        return "[" + displayId + "]prox debounce";
-    }
-
     /** Functional interface for providing time. */
     @VisibleForTesting
     interface Clock {
@@ -3629,6 +3110,20 @@
             return new DualRampAnimator(dps, firstProperty, secondProperty);
         }
 
+        WakelockController getWakelockController(int displayId,
+                DisplayPowerCallbacks displayPowerCallbacks) {
+            return new WakelockController(displayId, displayPowerCallbacks);
+        }
+
+        DisplayPowerProximityStateController getDisplayPowerProximityStateController(
+                WakelockController wakelockController, DisplayDeviceConfig displayDeviceConfig,
+                Looper looper, Runnable nudgeUpdatePowerState,
+                int displayId, SensorManager sensorManager) {
+            return new DisplayPowerProximityStateController(wakelockController, displayDeviceConfig,
+                    looper, nudgeUpdatePowerState,
+                    displayId, sensorManager, /* injector= */ null);
+        }
+
         AutomaticBrightnessController getAutomaticBrightnessController(
                 AutomaticBrightnessController.Callbacks callbacks, Looper looper,
                 SensorManager sensorManager, Sensor lightSensor,
@@ -3711,11 +3206,32 @@
                     hbmChangeCallback, hbmMetadata, context);
         }
 
+        BrightnessRangeController getBrightnessRangeController(
+                HighBrightnessModeController hbmController, Runnable modeChangeCallback,
+                DisplayDeviceConfig displayDeviceConfig, Handler handler,
+                DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
+            return new BrightnessRangeController(hbmController,
+                    modeChangeCallback, displayDeviceConfig, handler, flags, displayToken, info);
+        }
+
+        BrightnessClamperController getBrightnessClamperController(Handler handler,
+                BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+                BrightnessClamperController.DisplayDeviceData data, Context context,
+                DisplayManagerFlags flags) {
+
+            return new BrightnessClamperController(handler, clamperChangeListener, data, context,
+                    flags);
+        }
+
         DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
                 SensorManager sensorManager, Resources resources) {
             return DisplayWhiteBalanceFactory.create(handler,
                     sensorManager, resources);
         }
+
+        boolean isColorFadeEnabled() {
+            return !ActivityManager.isLowRamDeviceStatic();
+        }
     }
 
     static class CachedBrightnessInfo {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
deleted file mode 100644
index 2d860c0..0000000
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ /dev/null
@@ -1,3267 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display;
-
-import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
-import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
-import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
-import static com.android.server.display.config.DisplayBrightnessMappingConfig.autoBrightnessPresetToString;
-
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.pm.ParceledListSlice;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.hardware.Sensor;
-import android.hardware.SensorManager;
-import android.hardware.display.AmbientBrightnessDayStats;
-import android.hardware.display.BrightnessChangeEvent;
-import android.hardware.display.BrightnessConfiguration;
-import android.hardware.display.BrightnessInfo;
-import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
-import android.metrics.LogMaker;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.FloatProperty;
-import android.util.IndentingPrintWriter;
-import android.util.MathUtils;
-import android.util.MutableFloat;
-import android.util.MutableInt;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.view.Display;
-
-import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.display.BrightnessSynchronizer;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.RingBuffer;
-import com.android.server.LocalServices;
-import com.android.server.am.BatteryStatsService;
-import com.android.server.display.RampAnimator.DualRampAnimator;
-import com.android.server.display.brightness.BrightnessEvent;
-import com.android.server.display.brightness.BrightnessReason;
-import com.android.server.display.brightness.BrightnessUtils;
-import com.android.server.display.brightness.DisplayBrightnessController;
-import com.android.server.display.brightness.clamper.BrightnessClamperController;
-import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
-import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
-import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
-import com.android.server.display.feature.DisplayManagerFlags;
-import com.android.server.display.layout.Layout;
-import com.android.server.display.state.DisplayStateController;
-import com.android.server.display.utils.DebugUtils;
-import com.android.server.display.utils.SensorUtils;
-import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
-import com.android.server.display.whitebalance.DisplayWhiteBalanceFactory;
-import com.android.server.display.whitebalance.DisplayWhiteBalanceSettings;
-import com.android.server.policy.WindowManagerPolicy;
-
-import java.io.PrintWriter;
-import java.util.Objects;
-
-/**
- * Controls the power state of the display.
- *
- * Handles the proximity sensor, light sensor, and animations between states
- * including the screen off animation.
- *
- * This component acts independently of the rest of the power manager service.
- * In particular, it does not share any state and it only communicates
- * via asynchronous callbacks to inform the power manager that something has
- * changed.
- *
- * Everything this class does internally is serialized on its handler although
- * it may be accessed by other threads from the outside.
- *
- * Note that the power manager service guarantees that it will hold a suspend
- * blocker as long as the display is not ready.  So most of the work done here
- * does not need to worry about holding a suspend blocker unless it happens
- * independently of the display ready signal.
- *
- * For debugging, you can make the color fade and brightness animations run
- * slower by changing the "animator duration scale" option in Development Settings.
- */
-final class DisplayPowerController2 implements AutomaticBrightnessController.Callbacks,
-        DisplayWhiteBalanceController.Callbacks, DisplayPowerControllerInterface {
-    private static final String SCREEN_ON_BLOCKED_TRACE_NAME = "Screen on blocked";
-    private static final String SCREEN_OFF_BLOCKED_TRACE_NAME = "Screen off blocked";
-
-    private static final String TAG = "DisplayPowerController2";
-    // To enable these logs, run:
-    // 'adb shell setprop persist.log.tag.DisplayPowerController2 DEBUG && adb reboot'
-    private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
-    private static final String SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME =
-            "Screen on blocked by displayoffload";
-
-    // If true, uses the color fade on animation.
-    // We might want to turn this off if we cannot get a guarantee that the screen
-    // actually turns on and starts showing new content after the call to set the
-    // screen state returns.  Playing the animation can also be somewhat slow.
-    private static final boolean USE_COLOR_FADE_ON_ANIMATION = false;
-
-    private static final float SCREEN_ANIMATION_RATE_MINIMUM = 0.0f;
-
-    private static final int COLOR_FADE_ON_ANIMATION_DURATION_MILLIS = 250;
-    private static final int COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS = 400;
-
-    private static final int MSG_UPDATE_POWER_STATE = 1;
-    private static final int MSG_SCREEN_ON_UNBLOCKED = 2;
-    private static final int MSG_SCREEN_OFF_UNBLOCKED = 3;
-    private static final int MSG_CONFIGURE_BRIGHTNESS = 4;
-    private static final int MSG_SET_TEMPORARY_BRIGHTNESS = 5;
-    private static final int MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT = 6;
-    private static final int MSG_STOP = 7;
-    private static final int MSG_UPDATE_BRIGHTNESS = 8;
-    private static final int MSG_UPDATE_RBC = 9;
-    private static final int MSG_BRIGHTNESS_RAMP_DONE = 10;
-    private static final int MSG_STATSD_HBM_BRIGHTNESS = 11;
-    private static final int MSG_SWITCH_USER = 12;
-    private static final int MSG_BOOT_COMPLETED = 13;
-    private static final int MSG_SET_DWBC_STRONG_MODE = 14;
-    private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 15;
-    private static final int MSG_SET_DWBC_LOGGING_ENABLED = 16;
-    private static final int MSG_SET_BRIGHTNESS_FROM_OFFLOAD = 17;
-    private static final int MSG_OFFLOADING_SCREEN_ON_UNBLOCKED = 18;
-
-
-
-    private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500;
-
-
-    // State machine constants for tracking initial brightness ramp skipping when enabled.
-    private static final int RAMP_STATE_SKIP_NONE = 0;
-    private static final int RAMP_STATE_SKIP_INITIAL = 1;
-    private static final int RAMP_STATE_SKIP_AUTOBRIGHT = 2;
-
-    private static final int REPORTED_TO_POLICY_UNREPORTED = -1;
-    private static final int REPORTED_TO_POLICY_SCREEN_OFF = 0;
-    private static final int REPORTED_TO_POLICY_SCREEN_TURNING_ON = 1;
-    private static final int REPORTED_TO_POLICY_SCREEN_ON = 2;
-    private static final int REPORTED_TO_POLICY_SCREEN_TURNING_OFF = 3;
-
-    private static final int RINGBUFFER_MAX = 100;
-    private static final int RINGBUFFER_RBC_MAX = 20;
-
-    private static final float[] BRIGHTNESS_RANGE_BOUNDARIES = {
-        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80,
-        90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1200,
-        1400, 1600, 1800, 2000, 2250, 2500, 2750, 3000};
-    private static final int[] BRIGHTNESS_RANGE_INDEX = {
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_UNKNOWN,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_0_1,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_1_2,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_2_3,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_3_4,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_4_5,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_5_6,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_6_7,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_7_8,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_8_9,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_9_10,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_10_20,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_20_30,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_30_40,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_40_50,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_50_60,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_60_70,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_70_80,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_80_90,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_90_100,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_100_200,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_200_300,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_300_400,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_400_500,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_500_600,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_600_700,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_700_800,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_800_900,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_900_1000,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_1000_1200,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_1200_1400,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_1400_1600,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_1600_1800,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_1800_2000,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_2000_2250,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_2250_2500,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_2500_2750,
-        FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_2750_3000,
-    };
-
-    private final String mTag;
-
-    private final Object mLock = new Object();
-
-    private final Context mContext;
-
-    // Our handler.
-    private final DisplayControllerHandler mHandler;
-
-    // Battery stats.
-    @Nullable
-    private final IBatteryStats mBatteryStats;
-
-    // The sensor manager.
-    private final SensorManager mSensorManager;
-
-    // The window manager policy.
-    private final WindowManagerPolicy mWindowManagerPolicy;
-
-    // The display blanker.
-    private final DisplayBlanker mBlanker;
-
-    // The LogicalDisplay tied to this DisplayPowerController2.
-    private final LogicalDisplay mLogicalDisplay;
-
-    // The ID of the LogicalDisplay tied to this DisplayPowerController2.
-    private final int mDisplayId;
-
-    // The ID of the display which this display follows for brightness purposes.
-    private int mLeadDisplayId = Layout.NO_LEAD_DISPLAY;
-
-    // The unique ID of the primary display device currently tied to this logical display
-    private String mUniqueDisplayId;
-
-    // Tracker for brightness changes.
-    @Nullable
-    private final BrightnessTracker mBrightnessTracker;
-
-    // Tracker for brightness settings changes.
-    private final SettingsObserver mSettingsObserver;
-
-    // The doze screen brightness.
-    private final float mScreenBrightnessDozeConfig;
-
-    // True if auto-brightness should be used.
-    private boolean mUseSoftwareAutoBrightnessConfig;
-
-    // Whether or not the color fade on screen on / off is enabled.
-    private final boolean mColorFadeEnabled;
-
-    @GuardedBy("mCachedBrightnessInfo")
-    private final CachedBrightnessInfo mCachedBrightnessInfo = new CachedBrightnessInfo();
-
-    private DisplayDevice mDisplayDevice;
-
-    // True if we should fade the screen while turning it off, false if we should play
-    // a stylish color fade animation instead.
-    private final boolean mColorFadeFadesConfig;
-
-    // True if we need to fake a transition to off when coming out of a doze state.
-    // Some display hardware will blank itself when coming out of doze in order to hide
-    // artifacts. For these displays we fake a transition into OFF so that policy can appropriately
-    // blank itself and begin an appropriate power on animation.
-    private final boolean mDisplayBlanksAfterDozeConfig;
-
-    // True if there are only buckets of brightness values when the display is in the doze state,
-    // rather than a full range of values. If this is true, then we'll avoid animating the screen
-    // brightness since it'd likely be multiple jarring brightness transitions instead of just one
-    // to reach the final state.
-    private final boolean mBrightnessBucketsInDozeConfig;
-
-    private final Clock mClock;
-    private final Injector mInjector;
-
-    // Maximum time a ramp animation can take.
-    private long mBrightnessRampIncreaseMaxTimeMillis;
-    private long mBrightnessRampDecreaseMaxTimeMillis;
-
-    // Maximum time a ramp animation can take in idle mode.
-    private long mBrightnessRampIncreaseMaxTimeIdleMillis;
-    private long mBrightnessRampDecreaseMaxTimeIdleMillis;
-
-    // The pending power request.
-    // Initially null until the first call to requestPowerState.
-    @GuardedBy("mLock")
-    private DisplayPowerRequest mPendingRequestLocked;
-
-    // True if the pending power request or wait for negative proximity flag
-    // has been changed since the last update occurred.
-    @GuardedBy("mLock")
-    private boolean mPendingRequestChangedLocked;
-
-    // Set to true when the important parts of the pending power request have been applied.
-    // The important parts are mainly the screen state.  Brightness changes may occur
-    // concurrently.
-    @GuardedBy("mLock")
-    private boolean mDisplayReadyLocked;
-
-    // Set to true if a power state update is required.
-    @GuardedBy("mLock")
-    private boolean mPendingUpdatePowerStateLocked;
-
-    /* The following state must only be accessed by the handler thread. */
-
-    // The currently requested power state.
-    // The power controller will progressively update its internal state to match
-    // the requested power state.  Initially null until the first update.
-    private DisplayPowerRequest mPowerRequest;
-
-    // The current power state.
-    // Must only be accessed on the handler thread.
-    private DisplayPowerState mPowerState;
-
-
-
-    // The currently active screen on unblocker.  This field is non-null whenever
-    // we are waiting for a callback to release it and unblock the screen.
-    private ScreenOnUnblocker mPendingScreenOnUnblocker;
-    private ScreenOffUnblocker mPendingScreenOffUnblocker;
-    private Runnable mPendingScreenOnUnblockerByDisplayOffload;
-
-    // True if we were in the process of turning off the screen.
-    // This allows us to recover more gracefully from situations where we abort
-    // turning off the screen.
-    private boolean mPendingScreenOff;
-
-    // The elapsed real time when the screen on was blocked.
-    private long mScreenOnBlockStartRealTime;
-    private long mScreenOffBlockStartRealTime;
-    private long mScreenOnBlockByDisplayOffloadStartRealTime;
-
-    // Screen state we reported to policy. Must be one of REPORTED_TO_POLICY_* fields.
-    private int mReportedScreenStateToPolicy = REPORTED_TO_POLICY_UNREPORTED;
-
-    // Used to deduplicate the displayoffload blocking screen on logic. One block per turning on.
-    // This value is reset when screen on is reported or the blocking is cancelled.
-    private boolean mScreenTurningOnWasBlockedByDisplayOffload;
-
-    // If the last recorded screen state was dozing or not.
-    private boolean mDozing;
-
-    private boolean mAppliedDimming;
-
-    private boolean mAppliedThrottling;
-
-    // Reason for which the brightness was last changed. See {@link BrightnessReason} for more
-    // information.
-    // At the time of this writing, this value is changed within updatePowerState() only, which is
-    // limited to the thread used by DisplayControllerHandler.
-    private final BrightnessReason mBrightnessReason = new BrightnessReason();
-    private final BrightnessReason mBrightnessReasonTemp = new BrightnessReason();
-
-    // Brightness animation ramp rates in brightness units per second
-    private float mBrightnessRampRateFastDecrease;
-    private float mBrightnessRampRateFastIncrease;
-    private float mBrightnessRampRateSlowDecrease;
-    private float mBrightnessRampRateSlowIncrease;
-    private float mBrightnessRampRateSlowDecreaseIdle;
-    private float mBrightnessRampRateSlowIncreaseIdle;
-
-    // Report HBM brightness change to StatsD
-    private int mDisplayStatsId;
-    private float mLastStatsBrightness = PowerManager.BRIGHTNESS_MIN;
-
-    // Whether or not to skip the initial brightness ramps into STATE_ON.
-    private final boolean mSkipScreenOnBrightnessRamp;
-
-    // Display white balance components.
-    // Critical methods must be called on DPC2 handler thread.
-    @Nullable
-    private final DisplayWhiteBalanceSettings mDisplayWhiteBalanceSettings;
-    @Nullable
-    private final DisplayWhiteBalanceController mDisplayWhiteBalanceController;
-
-    @Nullable
-    private final ColorDisplayServiceInternal mCdsi;
-    private float[] mNitsRange;
-
-    private final BrightnessRangeController mBrightnessRangeController;
-
-    private final BrightnessThrottler mBrightnessThrottler;
-
-    private final BrightnessClamperController mBrightnessClamperController;
-
-    private final Runnable mOnBrightnessChangeRunnable;
-
-    private final BrightnessEvent mLastBrightnessEvent;
-    private final BrightnessEvent mTempBrightnessEvent;
-
-    private final DisplayBrightnessController mDisplayBrightnessController;
-
-    // Keeps a record of brightness changes for dumpsys.
-    private RingBuffer<BrightnessEvent> mBrightnessEventRingBuffer;
-
-    // Keeps a record of rbc changes for dumpsys.
-    private final RingBuffer<BrightnessEvent> mRbcEventRingBuffer =
-            new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_RBC_MAX);
-
-    // Controls and tracks all the wakelocks that are acquired/released by the system. Also acts as
-    // a medium of communication between this class and the PowerManagerService.
-    private final WakelockController mWakelockController;
-
-    // Tracks and manages the proximity state of the associated display.
-    private final DisplayPowerProximityStateController mDisplayPowerProximityStateController;
-
-    // Tracks and manages the display state of the associated display.
-    private final DisplayStateController mDisplayStateController;
-
-
-    // Responsible for evaluating and tracking the automatic brightness relevant states.
-    // Todo: This is a temporary workaround. Ideally DPC2 should never talk to the strategies
-    private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
-
-    // A record of state for skipping brightness ramps.
-    private int mSkipRampState = RAMP_STATE_SKIP_NONE;
-
-    // The first autobrightness value set when entering RAMP_STATE_SKIP_INITIAL.
-    private float mInitialAutoBrightness;
-
-    // The controller for the automatic brightness level.
-    @Nullable
-    private AutomaticBrightnessController mAutomaticBrightnessController;
-
-    // The controller for the sensor used to estimate ambient lux while the display is off.
-    @Nullable
-    private ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
-
-    private Sensor mLightSensor;
-    private Sensor mScreenOffBrightnessSensor;
-
-    private boolean mIsRbcActive;
-
-    // Animators.
-    private ObjectAnimator mColorFadeOnAnimator;
-    private ObjectAnimator mColorFadeOffAnimator;
-    private DualRampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator;
-
-    // True if this DisplayPowerController2 has been stopped and should no longer be running.
-    private boolean mStopped;
-
-    private DisplayDeviceConfig mDisplayDeviceConfig;
-
-    private boolean mIsEnabled;
-    private boolean mIsInTransition;
-    private boolean mIsDisplayInternal;
-
-    // The id of the thermal brightness throttling policy that should be used.
-    private String mThermalBrightnessThrottlingDataId;
-
-    // DPCs following the brightness of this DPC. This is used in concurrent displays mode - there
-    // is one lead display, the additional displays follow the brightness value of the lead display.
-    @GuardedBy("mLock")
-    private SparseArray<DisplayPowerControllerInterface> mDisplayBrightnessFollowers =
-            new SparseArray();
-
-    private boolean mBootCompleted;
-    private final DisplayManagerFlags mFlags;
-
-    private DisplayOffloadSession mDisplayOffloadSession;
-
-    /**
-     * Creates the display power controller.
-     */
-    DisplayPowerController2(Context context, Injector injector,
-            DisplayPowerCallbacks callbacks, Handler handler,
-            SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay,
-            BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting,
-            Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata,
-            boolean bootCompleted, DisplayManagerFlags flags) {
-        mFlags = flags;
-        mInjector = injector != null ? injector : new Injector();
-        mClock = mInjector.getClock();
-        mLogicalDisplay = logicalDisplay;
-        mDisplayId = mLogicalDisplay.getDisplayIdLocked();
-        mSensorManager = sensorManager;
-        mHandler = new DisplayControllerHandler(handler.getLooper());
-        mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked()
-                .getDisplayDeviceConfig();
-        mIsEnabled = logicalDisplay.isEnabledLocked();
-        mIsInTransition = logicalDisplay.isInTransitionLocked();
-        mIsDisplayInternal = logicalDisplay.getPrimaryDisplayDeviceLocked()
-                .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL;
-        mWakelockController = mInjector.getWakelockController(mDisplayId, callbacks);
-        mDisplayPowerProximityStateController = mInjector.getDisplayPowerProximityStateController(
-                mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(),
-                () -> updatePowerState(), mDisplayId, mSensorManager);
-        mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
-        mTag = TAG + "[" + mDisplayId + "]";
-        mThermalBrightnessThrottlingDataId =
-                logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
-        mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
-        mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
-        mDisplayStatsId = mUniqueDisplayId.hashCode();
-
-        mLastBrightnessEvent = new BrightnessEvent(mDisplayId);
-        mTempBrightnessEvent = new BrightnessEvent(mDisplayId);
-
-        if (mDisplayId == Display.DEFAULT_DISPLAY) {
-            mBatteryStats = BatteryStatsService.getService();
-        } else {
-            mBatteryStats = null;
-        }
-
-        mSettingsObserver = new SettingsObserver(mHandler);
-        mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class);
-        mBlanker = blanker;
-        mContext = context;
-        mBrightnessTracker = brightnessTracker;
-        mOnBrightnessChangeRunnable = onBrightnessChangeRunnable;
-
-        PowerManager pm = context.getSystemService(PowerManager.class);
-
-        final Resources resources = context.getResources();
-
-        // DOZE AND DIM SETTINGS
-        mScreenBrightnessDozeConfig = BrightnessUtils.clampAbsoluteBrightness(
-                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE));
-        loadBrightnessRampRates();
-        mSkipScreenOnBrightnessRamp = resources.getBoolean(
-                R.bool.config_skipScreenOnBrightnessRamp);
-
-        Runnable modeChangeCallback = () -> {
-            sendUpdatePowerState();
-            postBrightnessChangeRunnable();
-            // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
-            if (mAutomaticBrightnessController != null) {
-                mAutomaticBrightnessController.update();
-            }
-        };
-
-        HighBrightnessModeController hbmController = createHbmControllerLocked(hbmMetadata,
-                modeChangeCallback);
-        mBrightnessThrottler = createBrightnessThrottlerLocked();
-
-        mBrightnessRangeController = mInjector.getBrightnessRangeController(hbmController,
-                modeChangeCallback, mDisplayDeviceConfig, mHandler, flags,
-                mDisplayDevice.getDisplayTokenLocked(),
-                mDisplayDevice.getDisplayDeviceInfoLocked());
-
-        mDisplayBrightnessController =
-                new DisplayBrightnessController(context, null,
-                        mDisplayId, mLogicalDisplay.getDisplayInfoLocked().brightnessDefault,
-                        brightnessSetting, () -> postBrightnessChangeRunnable(),
-                        new HandlerExecutor(mHandler), flags);
-
-        mBrightnessClamperController = mInjector.getBrightnessClamperController(
-                mHandler, modeChangeCallback::run,
-                new BrightnessClamperController.DisplayDeviceData(
-                        mUniqueDisplayId,
-                        mThermalBrightnessThrottlingDataId,
-                        logicalDisplay.getPowerThrottlingDataIdLocked(),
-                        mDisplayDeviceConfig), mContext, flags);
-        // Seed the cached brightness
-        saveBrightnessInfo(getScreenBrightnessSetting());
-        mAutomaticBrightnessStrategy =
-                mDisplayBrightnessController.getAutomaticBrightnessStrategy();
-
-        DisplayWhiteBalanceSettings displayWhiteBalanceSettings = null;
-        DisplayWhiteBalanceController displayWhiteBalanceController = null;
-        if (mDisplayId == Display.DEFAULT_DISPLAY) {
-            try {
-                displayWhiteBalanceController = mInjector.getDisplayWhiteBalanceController(
-                        mHandler, mSensorManager, resources);
-                displayWhiteBalanceSettings = new DisplayWhiteBalanceSettings(mContext, mHandler);
-                displayWhiteBalanceSettings.setCallbacks(this);
-                displayWhiteBalanceController.setCallbacks(this);
-            } catch (Exception e) {
-                Slog.e(mTag, "failed to set up display white-balance: " + e);
-            }
-        }
-        mDisplayWhiteBalanceSettings = displayWhiteBalanceSettings;
-        mDisplayWhiteBalanceController = displayWhiteBalanceController;
-
-        loadNitsRange(resources);
-
-        if (mDisplayId == Display.DEFAULT_DISPLAY) {
-            mCdsi = LocalServices.getService(ColorDisplayServiceInternal.class);
-            if (mCdsi != null) {
-                boolean active = mCdsi.setReduceBrightColorsListener(
-                        new ReduceBrightColorsListener() {
-                            @Override
-                            public void onReduceBrightColorsActivationChanged(boolean activated,
-                                    boolean userInitiated) {
-                                applyReduceBrightColorsSplineAdjustment();
-
-                            }
-
-                            @Override
-                            public void onReduceBrightColorsStrengthChanged(int strength) {
-                                applyReduceBrightColorsSplineAdjustment();
-                            }
-                        });
-                if (active) {
-                    applyReduceBrightColorsSplineAdjustment();
-                }
-            }
-        } else {
-            mCdsi = null;
-        }
-
-        setUpAutoBrightness(context, handler);
-
-        mColorFadeEnabled = mInjector.isColorFadeEnabled()
-                && !resources.getBoolean(
-                  com.android.internal.R.bool.config_displayColorFadeDisabled);
-        mColorFadeFadesConfig = resources.getBoolean(
-                R.bool.config_animateScreenLights);
-
-        mDisplayBlanksAfterDozeConfig = resources.getBoolean(
-                R.bool.config_displayBlanksAfterDoze);
-
-        mBrightnessBucketsInDozeConfig = resources.getBoolean(
-                R.bool.config_displayBrightnessBucketsInDoze);
-
-        mBootCompleted = bootCompleted;
-    }
-
-    private void applyReduceBrightColorsSplineAdjustment() {
-        mHandler.obtainMessage(MSG_UPDATE_RBC).sendToTarget();
-        sendUpdatePowerState();
-    }
-
-    private void handleRbcChanged() {
-        if (mAutomaticBrightnessController == null) {
-            return;
-        }
-
-        float[] adjustedNits = new float[mNitsRange.length];
-        for (int i = 0; i < mNitsRange.length; i++) {
-            adjustedNits[i] = mCdsi.getReduceBrightColorsAdjustedBrightnessNits(mNitsRange[i]);
-        }
-        mIsRbcActive = mCdsi.isReduceBrightColorsActivated();
-        mAutomaticBrightnessController.recalculateSplines(mIsRbcActive, adjustedNits);
-    }
-
-    /**
-     * Returns true if the proximity sensor screen-off function is available.
-     */
-    @Override
-    public boolean isProximitySensorAvailable() {
-        return mDisplayPowerProximityStateController.isProximitySensorAvailable();
-    }
-
-    /**
-     * Get the {@link BrightnessChangeEvent}s for the specified user.
-     *
-     * @param userId         userId to fetch data for
-     * @param includePackage if false will null out the package name in events
-     */
-    @Nullable
-    @Override
-    public ParceledListSlice<BrightnessChangeEvent> getBrightnessEvents(
-            @UserIdInt int userId, boolean includePackage) {
-        if (mBrightnessTracker == null) {
-            return null;
-        }
-        return mBrightnessTracker.getEvents(userId, includePackage);
-    }
-
-    @Override
-    public void onSwitchUser(@UserIdInt int newUserId) {
-        Message msg = mHandler.obtainMessage(MSG_SWITCH_USER, newUserId);
-        mHandler.sendMessage(msg);
-    }
-
-    private void handleOnSwitchUser(@UserIdInt int newUserId) {
-        handleSettingsChange(true /* userSwitch */);
-        handleBrightnessModeChange();
-        if (mBrightnessTracker != null) {
-            mBrightnessTracker.onSwitchUser(newUserId);
-        }
-    }
-
-    @Nullable
-    @Override
-    public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(
-            @UserIdInt int userId) {
-        if (mBrightnessTracker == null) {
-            return null;
-        }
-        return mBrightnessTracker.getAmbientBrightnessStats(userId);
-    }
-
-    /**
-     * Persist the brightness slider events and ambient brightness stats to disk.
-     */
-    @Override
-    public void persistBrightnessTrackerState() {
-        if (mBrightnessTracker != null) {
-            mBrightnessTracker.persistBrightnessTrackerState();
-        }
-    }
-
-    /**
-     * Requests a new power state.
-     * The controller makes a copy of the provided object and then
-     * begins adjusting the power state to match what was requested.
-     *
-     * @param request                  The requested power state.
-     * @param waitForNegativeProximity If true, issues a request to wait for
-     *                                 negative proximity before turning the screen back on,
-     *                                 assuming the screen
-     *                                 was turned off by the proximity sensor.
-     * @return True if display is ready, false if there are important changes that must
-     * be made asynchronously (such as turning the screen on), in which case the caller
-     * should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged()}
-     * then try the request again later until the state converges.
-     */
-    public boolean requestPowerState(DisplayPowerRequest request,
-            boolean waitForNegativeProximity) {
-        if (DEBUG) {
-            Slog.d(mTag, "requestPowerState: "
-                    + request + ", waitForNegativeProximity=" + waitForNegativeProximity);
-        }
-
-        synchronized (mLock) {
-            if (mStopped) {
-                return true;
-            }
-
-            boolean changed = mDisplayPowerProximityStateController
-                    .setPendingWaitForNegativeProximityLocked(waitForNegativeProximity);
-
-            if (mPendingRequestLocked == null) {
-                mPendingRequestLocked = new DisplayPowerRequest(request);
-                changed = true;
-            } else if (!mPendingRequestLocked.equals(request)) {
-                mPendingRequestLocked.copyFrom(request);
-                changed = true;
-            }
-
-            if (changed) {
-                mDisplayReadyLocked = false;
-                if (!mPendingRequestChangedLocked) {
-                    mPendingRequestChangedLocked = true;
-                    sendUpdatePowerStateLocked();
-                }
-            }
-
-            return mDisplayReadyLocked;
-        }
-    }
-
-    @Override
-    public void overrideDozeScreenState(int displayState) {
-        mHandler.postAtTime(() -> {
-            if (mDisplayOffloadSession == null
-                    || !(DisplayOffloadSession.isSupportedOffloadState(displayState)
-                    || displayState == Display.STATE_UNKNOWN)) {
-                return;
-            }
-            mDisplayStateController.overrideDozeScreenState(displayState);
-            sendUpdatePowerState();
-        }, mClock.uptimeMillis());
-    }
-
-    @Override
-    public void setDisplayOffloadSession(DisplayOffloadSession session) {
-        if (session == mDisplayOffloadSession) {
-            return;
-        }
-        unblockScreenOnByDisplayOffload();
-        mDisplayOffloadSession = session;
-    }
-
-    @Override
-    public BrightnessConfiguration getDefaultBrightnessConfiguration() {
-        if (mAutomaticBrightnessController == null) {
-            return null;
-        }
-        return mAutomaticBrightnessController.getDefaultConfig();
-    }
-
-    /**
-     * Notified when the display is changed. We use this to apply any changes that might be needed
-     * when displays get swapped on foldable devices.  For example, different brightness properties
-     * of each display need to be properly reflected in AutomaticBrightnessController.
-     *
-     * Make sure DisplayManagerService.mSyncRoot lock is held when this is called
-     */
-    @Override
-    public void onDisplayChanged(HighBrightnessModeMetadata hbmMetadata, int leadDisplayId) {
-        mLeadDisplayId = leadDisplayId;
-        final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
-        if (device == null) {
-            Slog.wtf(mTag, "Display Device is null in DisplayPowerController2 for display: "
-                    + mLogicalDisplay.getDisplayIdLocked());
-            return;
-        }
-
-        final String uniqueId = device.getUniqueId();
-        final DisplayDeviceConfig config = device.getDisplayDeviceConfig();
-        final IBinder token = device.getDisplayTokenLocked();
-        final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
-        final boolean isEnabled = mLogicalDisplay.isEnabledLocked();
-        final boolean isInTransition = mLogicalDisplay.isInTransitionLocked();
-        final boolean isDisplayInternal = mLogicalDisplay.getPrimaryDisplayDeviceLocked() != null
-                && mLogicalDisplay.getPrimaryDisplayDeviceLocked()
-                .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL;
-        final String thermalBrightnessThrottlingDataId =
-                mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
-        final String powerThrottlingDataId =
-                mLogicalDisplay.getPowerThrottlingDataIdLocked();
-
-        mHandler.postAtTime(() -> {
-            boolean changed = false;
-            if (mDisplayDevice != device) {
-                changed = true;
-                mDisplayDevice = device;
-                mUniqueDisplayId = uniqueId;
-                mDisplayStatsId = mUniqueDisplayId.hashCode();
-                mDisplayDeviceConfig = config;
-                mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId;
-                loadFromDisplayDeviceConfig(token, info, hbmMetadata);
-                mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(config);
-
-                // Since the underlying display-device changed, we really don't know the
-                // last command that was sent to change it's state. Let's assume it is unknown so
-                // that we trigger a change immediately.
-                mPowerState.resetScreenState();
-            } else if (!Objects.equals(mThermalBrightnessThrottlingDataId,
-                    thermalBrightnessThrottlingDataId)) {
-                changed = true;
-                mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId;
-                mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
-                        config.getThermalBrightnessThrottlingDataMapByThrottlingId(),
-                        mThermalBrightnessThrottlingDataId,
-                        mUniqueDisplayId);
-            }
-            if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) {
-                changed = true;
-                mIsEnabled = isEnabled;
-                mIsInTransition = isInTransition;
-            }
-
-            mIsDisplayInternal = isDisplayInternal;
-            // using local variables here, when mBrightnessThrottler is removed,
-            // mThermalBrightnessThrottlingDataId could be removed as well
-            // changed = true will be not needed - clampers are maintaining their state and
-            // will call updatePowerState if needed.
-            mBrightnessClamperController.onDisplayChanged(
-                    new BrightnessClamperController.DisplayDeviceData(uniqueId,
-                        thermalBrightnessThrottlingDataId, powerThrottlingDataId, config));
-
-            if (changed) {
-                updatePowerState();
-            }
-        }, mClock.uptimeMillis());
-    }
-
-    /**
-     * Unregisters all listeners and interrupts all running threads; halting future work.
-     *
-     * This method should be called when the DisplayPowerController2 is no longer in use; i.e. when
-     * the {@link #mDisplayId display} has been removed.
-     */
-    @Override
-    public void stop() {
-        synchronized (mLock) {
-            clearDisplayBrightnessFollowersLocked();
-
-            mStopped = true;
-            Message msg = mHandler.obtainMessage(MSG_STOP);
-            mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
-
-            if (mAutomaticBrightnessController != null) {
-                mAutomaticBrightnessController.stop();
-            }
-
-            mDisplayBrightnessController.stop();
-
-            mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
-        }
-    }
-
-    private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info,
-            HighBrightnessModeMetadata hbmMetadata) {
-        // All properties that depend on the associated DisplayDevice and the DDC must be
-        // updated here.
-        loadBrightnessRampRates();
-        loadNitsRange(mContext.getResources());
-        setUpAutoBrightness(mContext, mHandler);
-        reloadReduceBrightColours();
-        setAnimatorRampSpeeds(/* isIdleMode= */ false);
-
-        mBrightnessRangeController.loadFromConfig(hbmMetadata, token, info, mDisplayDeviceConfig);
-        mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(
-                mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(),
-                mThermalBrightnessThrottlingDataId, mUniqueDisplayId);
-    }
-
-    private void sendUpdatePowerState() {
-        synchronized (mLock) {
-            sendUpdatePowerStateLocked();
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void sendUpdatePowerStateLocked() {
-        if (!mStopped && !mPendingUpdatePowerStateLocked) {
-            mPendingUpdatePowerStateLocked = true;
-            Message msg = mHandler.obtainMessage(MSG_UPDATE_POWER_STATE);
-            mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
-        }
-    }
-
-    private void initialize(int displayState) {
-        mPowerState = mInjector.getDisplayPowerState(mBlanker,
-                mColorFadeEnabled ? new ColorFade(mDisplayId) : null, mDisplayId, displayState);
-
-        if (mColorFadeEnabled) {
-            mColorFadeOnAnimator = ObjectAnimator.ofFloat(
-                    mPowerState, DisplayPowerState.COLOR_FADE_LEVEL, 0.0f, 1.0f);
-            mColorFadeOnAnimator.setDuration(COLOR_FADE_ON_ANIMATION_DURATION_MILLIS);
-            mColorFadeOnAnimator.addListener(mAnimatorListener);
-
-            mColorFadeOffAnimator = ObjectAnimator.ofFloat(
-                    mPowerState, DisplayPowerState.COLOR_FADE_LEVEL, 1.0f, 0.0f);
-            mColorFadeOffAnimator.setDuration(COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS);
-            mColorFadeOffAnimator.addListener(mAnimatorListener);
-        }
-
-        mScreenBrightnessRampAnimator = mInjector.getDualRampAnimator(mPowerState,
-                DisplayPowerState.SCREEN_BRIGHTNESS_FLOAT,
-                DisplayPowerState.SCREEN_SDR_BRIGHTNESS_FLOAT);
-        setAnimatorRampSpeeds(mAutomaticBrightnessController != null
-                && mAutomaticBrightnessController.isInIdleMode());
-        mScreenBrightnessRampAnimator.setListener(mRampAnimatorListener);
-
-        noteScreenState(mPowerState.getScreenState());
-        noteScreenBrightness(mPowerState.getScreenBrightness());
-
-        // Initialize all of the brightness tracking state
-        final float brightness = mDisplayBrightnessController.convertToAdjustedNits(
-                mPowerState.getScreenBrightness());
-        if (mBrightnessTracker != null && brightness >= PowerManager.BRIGHTNESS_MIN) {
-            mBrightnessTracker.start(brightness);
-        }
-
-        BrightnessSetting.BrightnessSettingListener brightnessSettingListener = brightnessValue -> {
-            Message msg = mHandler.obtainMessage(MSG_UPDATE_BRIGHTNESS, brightnessValue);
-            mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
-        };
-        mDisplayBrightnessController
-                .registerBrightnessSettingChangeListener(brightnessSettingListener);
-
-        mContext.getContentResolver().registerContentObserver(
-                Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
-                false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
-        mContext.getContentResolver().registerContentObserver(
-                Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE),
-                false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
-        if (mFlags.areAutoBrightnessModesEnabled()) {
-            mContext.getContentResolver().registerContentObserver(
-                    Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_ALS),
-                    /* notifyForDescendants= */ false, mSettingsObserver, UserHandle.USER_CURRENT);
-        }
-        handleBrightnessModeChange();
-    }
-
-    private void setUpAutoBrightness(Context context, Handler handler) {
-        mUseSoftwareAutoBrightnessConfig = mDisplayDeviceConfig.isAutoBrightnessAvailable();
-
-        if (!mUseSoftwareAutoBrightnessConfig) {
-            return;
-        }
-
-        SparseArray<BrightnessMappingStrategy> brightnessMappers = new SparseArray<>();
-
-        BrightnessMappingStrategy defaultModeBrightnessMapper =
-                mInjector.getDefaultModeBrightnessMapper(context, mDisplayDeviceConfig,
-                        mDisplayWhiteBalanceController);
-        brightnessMappers.append(AUTO_BRIGHTNESS_MODE_DEFAULT,
-                defaultModeBrightnessMapper);
-
-        final boolean isIdleScreenBrightnessEnabled = context.getResources().getBoolean(
-                R.bool.config_enableIdleScreenBrightnessMode);
-        if (isIdleScreenBrightnessEnabled) {
-            BrightnessMappingStrategy idleModeBrightnessMapper =
-                    BrightnessMappingStrategy.create(context, mDisplayDeviceConfig,
-                            AUTO_BRIGHTNESS_MODE_IDLE,
-                            mDisplayWhiteBalanceController);
-            if (idleModeBrightnessMapper != null) {
-                brightnessMappers.append(AUTO_BRIGHTNESS_MODE_IDLE,
-                        idleModeBrightnessMapper);
-            }
-        }
-
-        BrightnessMappingStrategy dozeModeBrightnessMapper =
-                BrightnessMappingStrategy.create(context, mDisplayDeviceConfig,
-                        AUTO_BRIGHTNESS_MODE_DOZE, mDisplayWhiteBalanceController);
-        if (mFlags.areAutoBrightnessModesEnabled() && dozeModeBrightnessMapper != null) {
-            brightnessMappers.put(AUTO_BRIGHTNESS_MODE_DOZE, dozeModeBrightnessMapper);
-        }
-
-        float userLux = BrightnessMappingStrategy.INVALID_LUX;
-        float userNits = BrightnessMappingStrategy.INVALID_NITS;
-        if (mAutomaticBrightnessController != null) {
-            userLux = mAutomaticBrightnessController.getUserLux();
-            userNits = mAutomaticBrightnessController.getUserNits();
-        }
-
-        if (defaultModeBrightnessMapper != null) {
-            final float dozeScaleFactor = context.getResources().getFraction(
-                    R.fraction.config_screenAutoBrightnessDozeScaleFactor,
-                    1, 1);
-
-            // Ambient Lux - Active Mode Brightness Thresholds
-            float[] ambientBrighteningThresholds =
-                    mDisplayDeviceConfig.getAmbientBrighteningPercentages();
-            float[] ambientDarkeningThresholds =
-                    mDisplayDeviceConfig.getAmbientDarkeningPercentages();
-            float[] ambientBrighteningLevels =
-                    mDisplayDeviceConfig.getAmbientBrighteningLevels();
-            float[] ambientDarkeningLevels =
-                    mDisplayDeviceConfig.getAmbientDarkeningLevels();
-            float ambientDarkeningMinThreshold =
-                    mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
-            float ambientBrighteningMinThreshold =
-                    mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
-            HysteresisLevels ambientBrightnessThresholds = mInjector.getHysteresisLevels(
-                    ambientBrighteningThresholds, ambientDarkeningThresholds,
-                    ambientBrighteningLevels, ambientDarkeningLevels, ambientDarkeningMinThreshold,
-                    ambientBrighteningMinThreshold);
-
-            // Display - Active Mode Brightness Thresholds
-            float[] screenBrighteningThresholds =
-                    mDisplayDeviceConfig.getScreenBrighteningPercentages();
-            float[] screenDarkeningThresholds =
-                    mDisplayDeviceConfig.getScreenDarkeningPercentages();
-            float[] screenBrighteningLevels =
-                    mDisplayDeviceConfig.getScreenBrighteningLevels();
-            float[] screenDarkeningLevels =
-                    mDisplayDeviceConfig.getScreenDarkeningLevels();
-            float screenDarkeningMinThreshold =
-                    mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
-            float screenBrighteningMinThreshold =
-                    mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
-            HysteresisLevels screenBrightnessThresholds = mInjector.getHysteresisLevels(
-                    screenBrighteningThresholds, screenDarkeningThresholds,
-                    screenBrighteningLevels, screenDarkeningLevels, screenDarkeningMinThreshold,
-                    screenBrighteningMinThreshold, true);
-
-            // Ambient Lux - Idle Screen Brightness Thresholds
-            float ambientDarkeningMinThresholdIdle =
-                    mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle();
-            float ambientBrighteningMinThresholdIdle =
-                    mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle();
-            float[] ambientBrighteningThresholdsIdle =
-                    mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle();
-            float[] ambientDarkeningThresholdsIdle =
-                    mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle();
-            float[] ambientBrighteningLevelsIdle =
-                    mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle();
-            float[] ambientDarkeningLevelsIdle =
-                    mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle();
-            HysteresisLevels ambientBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
-                    ambientBrighteningThresholdsIdle, ambientDarkeningThresholdsIdle,
-                    ambientBrighteningLevelsIdle, ambientDarkeningLevelsIdle,
-                    ambientDarkeningMinThresholdIdle, ambientBrighteningMinThresholdIdle);
-
-            // Display - Idle Screen Brightness Thresholds
-            float screenDarkeningMinThresholdIdle =
-                    mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
-            float screenBrighteningMinThresholdIdle =
-                    mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
-            float[] screenBrighteningThresholdsIdle =
-                    mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle();
-            float[] screenDarkeningThresholdsIdle =
-                    mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle();
-            float[] screenBrighteningLevelsIdle =
-                    mDisplayDeviceConfig.getScreenBrighteningLevelsIdle();
-            float[] screenDarkeningLevelsIdle =
-                    mDisplayDeviceConfig.getScreenDarkeningLevelsIdle();
-            HysteresisLevels screenBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
-                    screenBrighteningThresholdsIdle, screenDarkeningThresholdsIdle,
-                    screenBrighteningLevelsIdle, screenDarkeningLevelsIdle,
-                    screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
-
-            long brighteningLightDebounce = mDisplayDeviceConfig
-                    .getAutoBrightnessBrighteningLightDebounce();
-            long darkeningLightDebounce = mDisplayDeviceConfig
-                    .getAutoBrightnessDarkeningLightDebounce();
-            long brighteningLightDebounceIdle = mDisplayDeviceConfig
-                    .getAutoBrightnessBrighteningLightDebounceIdle();
-            long darkeningLightDebounceIdle = mDisplayDeviceConfig
-                    .getAutoBrightnessDarkeningLightDebounceIdle();
-            boolean autoBrightnessResetAmbientLuxAfterWarmUp = context.getResources().getBoolean(
-                    R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
-
-            int lightSensorWarmUpTimeConfig = context.getResources().getInteger(
-                    R.integer.config_lightSensorWarmupTime);
-            int lightSensorRate = context.getResources().getInteger(
-                    R.integer.config_autoBrightnessLightSensorRate);
-            int initialLightSensorRate = context.getResources().getInteger(
-                    R.integer.config_autoBrightnessInitialLightSensorRate);
-            if (initialLightSensorRate == -1) {
-                initialLightSensorRate = lightSensorRate;
-            } else if (initialLightSensorRate > lightSensorRate) {
-                Slog.w(mTag, "Expected config_autoBrightnessInitialLightSensorRate ("
-                        + initialLightSensorRate + ") to be less than or equal to "
-                        + "config_autoBrightnessLightSensorRate (" + lightSensorRate + ").");
-            }
-
-            loadAmbientLightSensor();
-            // BrightnessTracker should only use one light sensor, we want to use the light sensor
-            // from the default display and not e.g. temporary displays when switching layouts.
-            if (mBrightnessTracker != null && mDisplayId == Display.DEFAULT_DISPLAY) {
-                mBrightnessTracker.setLightSensor(mLightSensor);
-            }
-
-            if (mAutomaticBrightnessController != null) {
-                mAutomaticBrightnessController.stop();
-            }
-            mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController(
-                    this, handler.getLooper(), mSensorManager, mLightSensor,
-                    brightnessMappers, lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN,
-                    PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate,
-                    initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
-                    brighteningLightDebounceIdle, darkeningLightDebounceIdle,
-                    autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds,
-                    screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
-                    screenBrightnessThresholdsIdle, mContext, mBrightnessRangeController,
-                    mBrightnessThrottler, mDisplayDeviceConfig.getAmbientHorizonShort(),
-                    mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userNits);
-            mDisplayBrightnessController.setAutomaticBrightnessController(
-                    mAutomaticBrightnessController);
-
-            mAutomaticBrightnessStrategy
-                    .setAutomaticBrightnessController(mAutomaticBrightnessController);
-            mBrightnessEventRingBuffer =
-                    new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX);
-
-            if (mScreenOffBrightnessSensorController != null) {
-                mScreenOffBrightnessSensorController.stop();
-                mScreenOffBrightnessSensorController = null;
-            }
-            loadScreenOffBrightnessSensor();
-            int[] sensorValueToLux = mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux();
-            if (mScreenOffBrightnessSensor != null && sensorValueToLux != null) {
-                mScreenOffBrightnessSensorController =
-                        mInjector.getScreenOffBrightnessSensorController(
-                                mSensorManager,
-                                mScreenOffBrightnessSensor,
-                                mHandler,
-                                SystemClock::uptimeMillis,
-                                sensorValueToLux,
-                                defaultModeBrightnessMapper);
-            }
-        } else {
-            mUseSoftwareAutoBrightnessConfig = false;
-        }
-    }
-
-    private void loadBrightnessRampRates() {
-        mBrightnessRampRateFastDecrease = mDisplayDeviceConfig.getBrightnessRampFastDecrease();
-        mBrightnessRampRateFastIncrease = mDisplayDeviceConfig.getBrightnessRampFastIncrease();
-        mBrightnessRampRateSlowDecrease = mDisplayDeviceConfig.getBrightnessRampSlowDecrease();
-        mBrightnessRampRateSlowIncrease = mDisplayDeviceConfig.getBrightnessRampSlowIncrease();
-        mBrightnessRampRateSlowDecreaseIdle =
-                mDisplayDeviceConfig.getBrightnessRampSlowDecreaseIdle();
-        mBrightnessRampRateSlowIncreaseIdle =
-                mDisplayDeviceConfig.getBrightnessRampSlowIncreaseIdle();
-        mBrightnessRampDecreaseMaxTimeMillis =
-                mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis();
-        mBrightnessRampIncreaseMaxTimeMillis =
-                mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis();
-        mBrightnessRampDecreaseMaxTimeIdleMillis =
-                mDisplayDeviceConfig.getBrightnessRampDecreaseMaxIdleMillis();
-        mBrightnessRampIncreaseMaxTimeIdleMillis =
-                mDisplayDeviceConfig.getBrightnessRampIncreaseMaxIdleMillis();
-    }
-
-    private void loadNitsRange(Resources resources) {
-        if (mDisplayDeviceConfig != null && mDisplayDeviceConfig.getNits() != null) {
-            mNitsRange = mDisplayDeviceConfig.getNits();
-        } else {
-            Slog.w(mTag, "Screen brightness nits configuration is unavailable; falling back");
-            mNitsRange = BrightnessMappingStrategy.getFloatArray(resources
-                    .obtainTypedArray(R.array.config_screenBrightnessNits));
-        }
-    }
-
-    private void reloadReduceBrightColours() {
-        if (mCdsi != null && mCdsi.isReduceBrightColorsActivated()) {
-            applyReduceBrightColorsSplineAdjustment();
-        }
-    }
-
-    @Override
-    public void setAutomaticScreenBrightnessMode(
-            @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
-        boolean isIdle = mode == AUTO_BRIGHTNESS_MODE_IDLE;
-        if (mAutomaticBrightnessController != null) {
-            mAutomaticBrightnessController.switchMode(mode);
-            setAnimatorRampSpeeds(isIdle);
-        }
-        Message msg = mHandler.obtainMessage();
-        msg.what = MSG_SET_DWBC_STRONG_MODE;
-        msg.arg1 = isIdle ? 1 : 0;
-        mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
-    }
-
-    private void setAnimatorRampSpeeds(boolean isIdle) {
-        if (mScreenBrightnessRampAnimator == null) {
-            return;
-        }
-        if (mFlags.isAdaptiveTone1Enabled() && isIdle) {
-            mScreenBrightnessRampAnimator.setAnimationTimeLimits(
-                    mBrightnessRampIncreaseMaxTimeIdleMillis,
-                    mBrightnessRampDecreaseMaxTimeIdleMillis);
-        } else {
-            mScreenBrightnessRampAnimator.setAnimationTimeLimits(
-                    mBrightnessRampIncreaseMaxTimeMillis,
-                    mBrightnessRampDecreaseMaxTimeMillis);
-        }
-    }
-
-    private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
-        @Override
-        public void onAnimationStart(Animator animation) {
-        }
-
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            sendUpdatePowerState();
-        }
-
-        @Override
-        public void onAnimationRepeat(Animator animation) {
-        }
-
-        @Override
-        public void onAnimationCancel(Animator animation) {
-        }
-    };
-
-    private final RampAnimator.Listener mRampAnimatorListener = new RampAnimator.Listener() {
-        @Override
-        public void onAnimationEnd() {
-            sendUpdatePowerState();
-            Message msg = mHandler.obtainMessage(MSG_BRIGHTNESS_RAMP_DONE);
-            mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
-        }
-    };
-
-    /** Clean up all resources that are accessed via the {@link #mHandler} thread. */
-    private void cleanupHandlerThreadAfterStop() {
-        mDisplayPowerProximityStateController.cleanup();
-        mBrightnessRangeController.stop();
-        mBrightnessThrottler.stop();
-        mBrightnessClamperController.stop();
-        mHandler.removeCallbacksAndMessages(null);
-
-        // Release any outstanding wakelocks we're still holding because of pending messages.
-        mWakelockController.releaseAll();
-
-        final float brightness = mPowerState != null
-                ? mPowerState.getScreenBrightness()
-                : PowerManager.BRIGHTNESS_MIN;
-        reportStats(brightness);
-
-        if (mPowerState != null) {
-            mPowerState.stop();
-            mPowerState = null;
-        }
-
-        if (mScreenOffBrightnessSensorController != null) {
-            mScreenOffBrightnessSensorController.stop();
-        }
-
-        if (mDisplayWhiteBalanceController != null) {
-            mDisplayWhiteBalanceController.setEnabled(false);
-        }
-    }
-
-    // Call from handler thread
-    private void updatePowerState() {
-        Trace.traceBegin(Trace.TRACE_TAG_POWER,
-                "DisplayPowerController#updatePowerState");
-        updatePowerStateInternal();
-        Trace.traceEnd(Trace.TRACE_TAG_POWER);
-    }
-
-    private void updatePowerStateInternal() {
-        // Update the power state request.
-        final boolean mustNotify;
-        final int previousPolicy;
-        boolean mustInitialize = false;
-        mBrightnessReasonTemp.set(null);
-        mTempBrightnessEvent.reset();
-        SparseArray<DisplayPowerControllerInterface> displayBrightnessFollowers;
-        synchronized (mLock) {
-            if (mStopped) {
-                return;
-            }
-            mPendingUpdatePowerStateLocked = false;
-            if (mPendingRequestLocked == null) {
-                return; // wait until first actual power request
-            }
-
-            if (mPowerRequest == null) {
-                mPowerRequest = new DisplayPowerRequest(mPendingRequestLocked);
-                mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
-                mPendingRequestChangedLocked = false;
-                mustInitialize = true;
-                // Assume we're on and bright until told otherwise, since that's the state we turn
-                // on in.
-                previousPolicy = DisplayPowerRequest.POLICY_BRIGHT;
-            } else if (mPendingRequestChangedLocked) {
-                previousPolicy = mPowerRequest.policy;
-                mPowerRequest.copyFrom(mPendingRequestLocked);
-                mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
-                mPendingRequestChangedLocked = false;
-                mDisplayReadyLocked = false;
-            } else {
-                previousPolicy = mPowerRequest.policy;
-            }
-
-            mustNotify = !mDisplayReadyLocked;
-
-            displayBrightnessFollowers = mDisplayBrightnessFollowers.clone();
-        }
-
-        int state = mDisplayStateController
-                .updateDisplayState(mPowerRequest, mIsEnabled, mIsInTransition);
-
-        // Initialize things the first time the power state is changed.
-        if (mustInitialize) {
-            initialize(readyToUpdateDisplayState() ? state : Display.STATE_UNKNOWN);
-        }
-
-        // Animate the screen state change unless already animating.
-        // The transition may be deferred, so after this point we will use the
-        // actual state instead of the desired one.
-        animateScreenStateChange(state, mDisplayStateController.shouldPerformScreenOffTransition());
-        state = mPowerState.getScreenState();
-
-        // Switch to doze auto-brightness mode if needed
-        if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null
-                && !mAutomaticBrightnessController.isInIdleMode()) {
-            setAutomaticScreenBrightnessMode(Display.isDozeState(state)
-                    ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
-        }
-
-        final boolean userSetBrightnessChanged = mDisplayBrightnessController
-                .updateUserSetScreenBrightness();
-
-        DisplayBrightnessState displayBrightnessState = mDisplayBrightnessController
-                .updateBrightness(mPowerRequest, state);
-        float brightnessState = displayBrightnessState.getBrightness();
-        float rawBrightnessState = displayBrightnessState.getBrightness();
-        mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason());
-        boolean slowChange = displayBrightnessState.isSlowChange();
-        // custom transition duration
-        float customAnimationRate = displayBrightnessState.getCustomAnimationRate();
-
-        // Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor
-        // doesn't yet have a valid lux value to use with auto-brightness.
-        if (mScreenOffBrightnessSensorController != null) {
-            mScreenOffBrightnessSensorController
-                    .setLightSensorEnabled(displayBrightnessState.getShouldUseAutoBrightness()
-                    && mIsEnabled && (state == Display.STATE_OFF
-                    || (state == Display.STATE_DOZE
-                    && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()))
-                    && mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
-        }
-
-        // Take note if the short term model was already active before applying the current
-        // request changes.
-        final boolean wasShortTermModelActive =
-                mAutomaticBrightnessStrategy.isShortTermModelActive();
-        mAutomaticBrightnessStrategy.setAutoBrightnessState(state,
-                mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig(),
-                mBrightnessReasonTemp.getReason(), mPowerRequest.policy,
-                mDisplayBrightnessController.getLastUserSetScreenBrightness(),
-                userSetBrightnessChanged);
-
-        // If the brightness is already set then it's been overridden by something other than the
-        // user, or is a temporary adjustment.
-        boolean userInitiatedChange = (Float.isNaN(brightnessState))
-                && (mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged()
-                || userSetBrightnessChanged);
-
-        mBrightnessRangeController.setAutoBrightnessEnabled(
-                mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()
-                ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
-                : mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff()
-                        ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
-                        : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
-
-        boolean updateScreenBrightnessSetting =
-                displayBrightnessState.shouldUpdateScreenBrightnessSetting();
-        float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness();
-        // Apply auto-brightness.
-        int brightnessAdjustmentFlags = 0;
-        if (Float.isNaN(brightnessState)) {
-            if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
-                brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
-                        mTempBrightnessEvent);
-                if (BrightnessUtils.isValidBrightnessValue(brightnessState)
-                        || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
-                    rawBrightnessState = mAutomaticBrightnessController
-                            .getRawAutomaticScreenBrightness();
-                    brightnessState = clampScreenBrightness(brightnessState);
-                    // slowly adapt to auto-brightness
-                    // TODO(b/253226419): slowChange should be decided by strategy.updateBrightness
-                    slowChange = mAutomaticBrightnessStrategy.hasAppliedAutoBrightness()
-                            && !mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged();
-                    brightnessAdjustmentFlags =
-                            mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags();
-                    updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
-                    mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
-                    mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
-                    if (mScreenOffBrightnessSensorController != null) {
-                        mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
-                    }
-                } else {
-                    mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
-                }
-            }
-        } else {
-            // Any non-auto-brightness values such as override or temporary should still be subject
-            // to clamping so that they don't go beyond the current max as specified by HBM
-            // Controller.
-            brightnessState = clampScreenBrightness(brightnessState);
-            mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
-        }
-
-        // Use default brightness when dozing unless overridden.
-        if ((Float.isNaN(brightnessState))
-                && Display.isDozeState(state)) {
-            rawBrightnessState = mScreenBrightnessDozeConfig;
-            brightnessState = clampScreenBrightness(rawBrightnessState);
-            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT);
-        }
-
-        // The ALS is not available yet - use the screen off sensor to determine the initial
-        // brightness
-        if (Float.isNaN(brightnessState) && mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()
-                && mScreenOffBrightnessSensorController != null) {
-            rawBrightnessState =
-                    mScreenOffBrightnessSensorController.getAutomaticScreenBrightness();
-            brightnessState = rawBrightnessState;
-            if (BrightnessUtils.isValidBrightnessValue(brightnessState)) {
-                brightnessState = clampScreenBrightness(brightnessState);
-                updateScreenBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness()
-                        != brightnessState;
-                mBrightnessReasonTemp.setReason(
-                        BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR);
-            }
-        }
-
-        // Apply manual brightness.
-        if (Float.isNaN(brightnessState)) {
-            rawBrightnessState = currentBrightnessSetting;
-            brightnessState = clampScreenBrightness(rawBrightnessState);
-            if (brightnessState != currentBrightnessSetting) {
-                // The manually chosen screen brightness is outside of the currently allowed
-                // range (i.e., high-brightness-mode), make sure we tell the rest of the system
-                // by updating the setting.
-                updateScreenBrightnessSetting = true;
-            }
-            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL);
-        }
-
-        float ambientLux = mAutomaticBrightnessController == null ? 0
-                : mAutomaticBrightnessController.getAmbientLux();
-        for (int i = 0; i < displayBrightnessFollowers.size(); i++) {
-            DisplayPowerControllerInterface follower = displayBrightnessFollowers.valueAt(i);
-            follower.setBrightnessToFollow(rawBrightnessState,
-                    mDisplayBrightnessController.convertToNits(rawBrightnessState),
-                    ambientLux, slowChange);
-        }
-
-        // Now that a desired brightness has been calculated, apply brightness throttling. The
-        // dimming and low power transformations that follow can only dim brightness further.
-        //
-        // We didn't do this earlier through brightness clamping because we need to know both
-        // unthrottled (unclamped/ideal) and throttled brightness levels for subsequent operations.
-        // Note throttling effectively changes the allowed brightness range, so, similarly to HBM,
-        // we broadcast this change through setting.
-        final float unthrottledBrightnessState = brightnessState;
-        DisplayBrightnessState clampedState = mBrightnessClamperController.clamp(mPowerRequest,
-                brightnessState, slowChange);
-
-        brightnessState = clampedState.getBrightness();
-        slowChange = clampedState.isSlowChange();
-        // faster rate wins, at this point customAnimationRate == -1, strategy does not control
-        // customAnimationRate. Should be revisited if strategy start setting this value
-        customAnimationRate = Math.max(customAnimationRate, clampedState.getCustomAnimationRate());
-        mBrightnessReasonTemp.addModifier(clampedState.getBrightnessReason().getModifier());
-
-        if (updateScreenBrightnessSetting) {
-            // Tell the rest of the system about the new brightness in case we had to change it
-            // for things like auto-brightness or high-brightness-mode. Note that we do this
-            // only considering maxBrightness (ignoring brightness modifiers like low power or dim)
-            // so that the slider accurately represents the full possible range,
-            // even if they range changes what it means in absolute terms.
-            mDisplayBrightnessController.updateScreenBrightnessSetting(
-                    MathUtils.constrain(unthrottledBrightnessState,
-                            clampedState.getMinBrightness(), clampedState.getMaxBrightness()));
-        }
-
-        // The current brightness to use has been calculated at this point, and HbmController should
-        // be notified so that it can accurately calculate HDR or HBM levels. We specifically do it
-        // here instead of having HbmController listen to the brightness setting because certain
-        // brightness sources (such as an app override) are not saved to the setting, but should be
-        // reflected in HBM calculations.
-        mBrightnessRangeController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
-                mBrightnessClamperController.getBrightnessMaxReason());
-
-        // Animate the screen brightness when the screen is on or dozing.
-        // Skip the animation when the screen is off or suspended.
-        boolean brightnessAdjusted = false;
-        final boolean brightnessIsTemporary =
-                (mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_TEMPORARY)
-                        || mAutomaticBrightnessStrategy
-                        .isTemporaryAutoBrightnessAdjustmentApplied();
-        if (!mPendingScreenOff) {
-            if (mSkipScreenOnBrightnessRamp) {
-                if (state == Display.STATE_ON) {
-                    if (mSkipRampState == RAMP_STATE_SKIP_NONE && mDozing) {
-                        mInitialAutoBrightness = brightnessState;
-                        mSkipRampState = RAMP_STATE_SKIP_INITIAL;
-                    } else if (mSkipRampState == RAMP_STATE_SKIP_INITIAL
-                            && mUseSoftwareAutoBrightnessConfig
-                            && !BrightnessSynchronizer.floatEquals(brightnessState,
-                            mInitialAutoBrightness)) {
-                        mSkipRampState = RAMP_STATE_SKIP_AUTOBRIGHT;
-                    } else if (mSkipRampState == RAMP_STATE_SKIP_AUTOBRIGHT) {
-                        mSkipRampState = RAMP_STATE_SKIP_NONE;
-                    }
-                } else {
-                    mSkipRampState = RAMP_STATE_SKIP_NONE;
-                }
-            }
-
-            final boolean initialRampSkip = (state == Display.STATE_ON && mSkipRampState
-                    != RAMP_STATE_SKIP_NONE) || mDisplayPowerProximityStateController
-                    .shouldSkipRampBecauseOfProximityChangeToNegative();
-            // While dozing, sometimes the brightness is split into buckets. Rather than animating
-            // through the buckets, which is unlikely to be smooth in the first place, just jump
-            // right to the suggested brightness.
-            final boolean hasBrightnessBuckets =
-                    Display.isDozeState(state) && mBrightnessBucketsInDozeConfig;
-            // If the color fade is totally covering the screen then we can change the backlight
-            // level without it being a noticeable jump since any actual content isn't yet visible.
-            final boolean isDisplayContentVisible =
-                    mColorFadeEnabled && mPowerState.getColorFadeLevel() == 1.0f;
-            // We only want to animate the brightness if it is between 0.0f and 1.0f.
-            // brightnessState can contain the values -1.0f and NaN, which we do not want to
-            // animate to. To avoid this, we check the value first.
-            // If the brightnessState is off (-1.0f) we still want to animate to the minimum
-            // brightness (0.0f) to accommodate for LED displays, which can appear bright to the
-            // user even when the display is all black. We also clamp here in case some
-            // transformations to the brightness have pushed it outside of the currently
-            // allowed range.
-            float animateValue = clampScreenBrightness(brightnessState);
-
-            // If there are any HDR layers on the screen, we have a special brightness value that we
-            // use instead. We still preserve the calculated brightness for Standard Dynamic Range
-            // (SDR) layers, but the main brightness value will be the one for HDR.
-            float sdrAnimateValue = animateValue;
-            // TODO(b/216365040): The decision to prevent HBM for HDR in low power mode should be
-            // done in HighBrightnessModeController.
-            if (mBrightnessRangeController.getHighBrightnessMode()
-                    == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
-                    && (mBrightnessReasonTemp.getModifier() & BrightnessReason.MODIFIER_DIMMED) == 0
-                    && (mBrightnessReasonTemp.getModifier() & BrightnessReason.MODIFIER_LOW_POWER)
-                    == 0) {
-                // We want to scale HDR brightness level with the SDR level, we also need to restore
-                // SDR brightness immediately when entering dim or low power mode.
-                animateValue = mBrightnessRangeController.getHdrBrightnessValue();
-                customAnimationRate = Math.max(customAnimationRate,
-                        mBrightnessRangeController.getHdrTransitionRate());
-                mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_HDR);
-            }
-
-            // if doze or suspend state is requested, we want to finish brightnes animation fast
-            // to allow state animation to start
-            if (mPowerRequest.policy == DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE
-                    && (mPowerRequest.dozeScreenState == Display.STATE_UNKNOWN  // dozing
-                    || mPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND
-                    || mPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND)) {
-                customAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
-                slowChange = false;
-            }
-
-            final float currentBrightness = mPowerState.getScreenBrightness();
-            final float currentSdrBrightness = mPowerState.getSdrScreenBrightness();
-
-            if (BrightnessUtils.isValidBrightnessValue(animateValue)
-                    && (animateValue != currentBrightness
-                    || sdrAnimateValue != currentSdrBrightness)) {
-                boolean skipAnimation = initialRampSkip || hasBrightnessBuckets
-                        || !isDisplayContentVisible || brightnessIsTemporary;
-                final boolean isHdrOnlyChange = BrightnessSynchronizer.floatEquals(
-                        sdrAnimateValue, currentSdrBrightness);
-                if (mFlags.isFastHdrTransitionsEnabled() && !skipAnimation && isHdrOnlyChange) {
-                    // SDR brightness is unchanged, so animate quickly as this is only impacting
-                    // a likely minority amount of display content
-                    // ie, the highlights of an HDR video or UltraHDR image
-                    slowChange = false;
-
-                    // Going from HDR to no HDR; visually this should be a "no-op" anyway
-                    // as the remaining SDR content's brightness should be holding steady
-                    // due to the sdr brightness not shifting
-                    if (BrightnessSynchronizer.floatEquals(sdrAnimateValue, animateValue)) {
-                        skipAnimation = true;
-                    }
-
-                    // Going from no HDR to HDR; visually this is a significant scene change
-                    // and the animation just prevents advanced clients from doing their own
-                    // handling of enter/exit animations if they would like to do such a thing
-                    if (BrightnessSynchronizer.floatEquals(sdrAnimateValue, currentBrightness)) {
-                        skipAnimation = true;
-                    }
-                }
-                if (skipAnimation) {
-                    animateScreenBrightness(animateValue, sdrAnimateValue,
-                            SCREEN_ANIMATION_RATE_MINIMUM);
-                } else if (customAnimationRate > 0) {
-                    animateScreenBrightness(animateValue, sdrAnimateValue,
-                            customAnimationRate, /* ignoreAnimationLimits = */true);
-                } else {
-                    boolean isIncreasing = animateValue > currentBrightness;
-                    final float rampSpeed;
-                    final boolean idle = mAutomaticBrightnessController != null
-                            && mAutomaticBrightnessController.isInIdleMode();
-                    if (isIncreasing && slowChange) {
-                        rampSpeed = idle ? mBrightnessRampRateSlowIncreaseIdle
-                                : mBrightnessRampRateSlowIncrease;
-                    } else if (isIncreasing && !slowChange) {
-                        rampSpeed = mBrightnessRampRateFastIncrease;
-                    } else if (!isIncreasing && slowChange) {
-                        rampSpeed = idle ? mBrightnessRampRateSlowDecreaseIdle
-                                : mBrightnessRampRateSlowDecrease;
-                    } else {
-                        rampSpeed = mBrightnessRampRateFastDecrease;
-                    }
-                    animateScreenBrightness(animateValue, sdrAnimateValue, rampSpeed);
-                }
-            }
-
-            notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange,
-                    wasShortTermModelActive, mAutomaticBrightnessStrategy.isAutoBrightnessEnabled(),
-                    brightnessIsTemporary, displayBrightnessState.getShouldUseAutoBrightness());
-
-            // We save the brightness info *after* the brightness setting has been changed and
-            // adjustments made so that the brightness info reflects the latest value.
-            brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting(),
-                    animateValue, clampedState);
-        } else {
-            brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting(), clampedState);
-        }
-
-        // Only notify if the brightness adjustment is not temporary (i.e. slider has been released)
-        if (brightnessAdjusted && !brightnessIsTemporary) {
-            postBrightnessChangeRunnable();
-        }
-
-        // Log any changes to what is currently driving the brightness setting.
-        if (!mBrightnessReasonTemp.equals(mBrightnessReason) || brightnessAdjustmentFlags != 0) {
-            Slog.v(mTag, "Brightness [" + brightnessState + "] reason changing to: '"
-                    + mBrightnessReasonTemp.toString(brightnessAdjustmentFlags)
-                    + "', previous reason: '" + mBrightnessReason + "'.");
-            mBrightnessReason.set(mBrightnessReasonTemp);
-        } else if (mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_MANUAL
-                && userSetBrightnessChanged) {
-            Slog.v(mTag, "Brightness [" + brightnessState + "] manual adjustment.");
-        }
-
-
-        // Log brightness events when a detail of significance has changed. Generally this is the
-        // brightness itself changing, but also includes data like HBM cap, thermal throttling
-        // brightness cap, RBC state, etc.
-        mTempBrightnessEvent.setTime(System.currentTimeMillis());
-        mTempBrightnessEvent.setBrightness(brightnessState);
-        mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId);
-        mTempBrightnessEvent.setReason(mBrightnessReason);
-        mTempBrightnessEvent.setHbmMax(mBrightnessRangeController.getCurrentBrightnessMax());
-        mTempBrightnessEvent.setHbmMode(mBrightnessRangeController.getHighBrightnessMode());
-        mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
-                | (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0)
-                | (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0));
-        mTempBrightnessEvent.setRbcStrength(mCdsi != null
-                ? mCdsi.getReduceBrightColorsStrength() : -1);
-        mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
-        mTempBrightnessEvent.setWasShortTermModelActive(wasShortTermModelActive);
-        mTempBrightnessEvent.setDisplayBrightnessStrategyName(displayBrightnessState
-                .getDisplayBrightnessStrategyName());
-        mTempBrightnessEvent.setAutomaticBrightnessEnabled(
-                displayBrightnessState.getShouldUseAutoBrightness());
-        // Temporary is what we use during slider interactions. We avoid logging those so that
-        // we don't spam logcat when the slider is being used.
-        boolean tempToTempTransition =
-                mTempBrightnessEvent.getReason().getReason() == BrightnessReason.REASON_TEMPORARY
-                        && mLastBrightnessEvent.getReason().getReason()
-                        == BrightnessReason.REASON_TEMPORARY;
-        // Purely for dumpsys;
-        final boolean isRbcEvent =
-                mLastBrightnessEvent.isRbcEnabled() != mTempBrightnessEvent.isRbcEnabled();
-
-        if ((!mTempBrightnessEvent.equalsMainData(mLastBrightnessEvent) && !tempToTempTransition)
-                || brightnessAdjustmentFlags != 0) {
-            mTempBrightnessEvent.setInitialBrightness(mLastBrightnessEvent.getBrightness());
-            mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
-            BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
-            // Adjustment flags (and user-set flag) only get added after the equality checks since
-            // they are transient.
-            newEvent.setAdjustmentFlags(brightnessAdjustmentFlags);
-            newEvent.setFlags(newEvent.getFlags() | (userSetBrightnessChanged
-                    ? BrightnessEvent.FLAG_USER_SET : 0));
-            Slog.i(mTag, newEvent.toString(/* includeTime= */ false));
-
-            if (userSetBrightnessChanged
-                    || newEvent.getReason().getReason() != BrightnessReason.REASON_TEMPORARY) {
-                logBrightnessEvent(newEvent, unthrottledBrightnessState);
-            }
-            if (mBrightnessEventRingBuffer != null) {
-                mBrightnessEventRingBuffer.append(newEvent);
-            }
-            if (isRbcEvent) {
-                mRbcEventRingBuffer.append(newEvent);
-            }
-
-        }
-
-        // Update display white-balance.
-        if (mDisplayWhiteBalanceController != null) {
-            if (state == Display.STATE_ON && mDisplayWhiteBalanceSettings.isEnabled()) {
-                mDisplayWhiteBalanceController.setEnabled(true);
-                mDisplayWhiteBalanceController.updateDisplayColorTemperature();
-            } else {
-                mDisplayWhiteBalanceController.setEnabled(false);
-            }
-        }
-
-        // Determine whether the display is ready for use in the newly requested state.
-        // Note that we do not wait for the brightness ramp animation to complete before
-        // reporting the display is ready because we only need to ensure the screen is in the
-        // right power state even as it continues to converge on the desired brightness.
-        final boolean ready = mPendingScreenOnUnblocker == null
-                && mPendingScreenOnUnblockerByDisplayOffload == null
-                && (!mColorFadeEnabled || (!mColorFadeOnAnimator.isStarted()
-                        && !mColorFadeOffAnimator.isStarted()))
-                && mPowerState.waitUntilClean(mCleanListener);
-        final boolean finished = ready
-                && !mScreenBrightnessRampAnimator.isAnimating();
-
-        // Notify policy about screen turned on.
-        if (ready && state != Display.STATE_OFF
-                && mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_TURNING_ON) {
-            setReportedScreenState(REPORTED_TO_POLICY_SCREEN_ON);
-            mWindowManagerPolicy.screenTurnedOn(mDisplayId);
-        }
-
-        // Grab a wake lock if we have unfinished business.
-        if (!finished) {
-            mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
-        }
-
-        // Notify the power manager when ready.
-        if (ready && mustNotify) {
-            // Send state change.
-            synchronized (mLock) {
-                if (!mPendingRequestChangedLocked) {
-                    mDisplayReadyLocked = true;
-
-                    if (DEBUG) {
-                        Slog.d(mTag, "Display ready!");
-                    }
-                }
-            }
-            sendOnStateChangedWithWakelock();
-        }
-
-        // Release the wake lock when we have no unfinished business.
-        if (finished) {
-            mWakelockController.releaseWakelock(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
-        }
-
-        // Record if dozing for future comparison.
-        mDozing = state != Display.STATE_ON;
-
-        if (previousPolicy != mPowerRequest.policy) {
-            logDisplayPolicyChanged(mPowerRequest.policy);
-        }
-    }
-
-    private void setDwbcOverride(float cct) {
-        if (mDisplayWhiteBalanceController != null) {
-            mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct);
-            // The ambient color temperature override is only applied when the ambient color
-            // temperature changes or is updated, so it doesn't necessarily change the screen color
-            // temperature immediately. So, let's make it!
-            // We can call this directly, since we're already on the handler thread.
-            updatePowerState();
-        }
-    }
-
-    private void setDwbcStrongMode(int arg) {
-        if (mDisplayWhiteBalanceController != null) {
-            final boolean isIdle = (arg == 1);
-            mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
-        }
-    }
-
-    private void setDwbcLoggingEnabled(int arg) {
-        if (mDisplayWhiteBalanceController != null) {
-            final boolean enabled = (arg == 1);
-            mDisplayWhiteBalanceController.setLoggingEnabled(enabled);
-            mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled);
-        }
-    }
-
-    @Override
-    public void updateBrightness() {
-        sendUpdatePowerState();
-    }
-
-    /**
-     * Ignores the proximity sensor until the sensor state changes, but only if the sensor is
-     * currently enabled and forcing the screen to be dark.
-     */
-    @Override
-    public void ignoreProximitySensorUntilChanged() {
-        mDisplayPowerProximityStateController.ignoreProximitySensorUntilChanged();
-    }
-
-    @Override
-    public void setBrightnessConfiguration(BrightnessConfiguration c,
-            boolean shouldResetShortTermModel) {
-        Message msg = mHandler.obtainMessage(MSG_CONFIGURE_BRIGHTNESS,
-                shouldResetShortTermModel ? 1 : 0, /* unused */ 0, c);
-        msg.sendToTarget();
-    }
-
-    @Override
-    public void setTemporaryBrightness(float brightness) {
-        Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_BRIGHTNESS,
-                Float.floatToIntBits(brightness), 0 /*unused*/);
-        msg.sendToTarget();
-    }
-
-    @Override
-    public void setTemporaryAutoBrightnessAdjustment(float adjustment) {
-        Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT,
-                Float.floatToIntBits(adjustment), 0 /*unused*/);
-        msg.sendToTarget();
-    }
-
-    @Override
-    public void setBrightnessFromOffload(float brightness) {
-        Message msg = mHandler.obtainMessage(MSG_SET_BRIGHTNESS_FROM_OFFLOAD,
-                Float.floatToIntBits(brightness), 0 /*unused*/);
-        mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
-    }
-
-    @Override
-    public float[] getAutoBrightnessLevels(
-            @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
-        int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
-                Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
-        return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
-    }
-
-    @Override
-    public float[] getAutoBrightnessLuxLevels(
-            @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
-        int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
-                Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
-        return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
-    }
-
-    @Override
-    public BrightnessInfo getBrightnessInfo() {
-        synchronized (mCachedBrightnessInfo) {
-            return new BrightnessInfo(
-                    mCachedBrightnessInfo.brightness.value,
-                    mCachedBrightnessInfo.adjustedBrightness.value,
-                    mCachedBrightnessInfo.brightnessMin.value,
-                    mCachedBrightnessInfo.brightnessMax.value,
-                    mCachedBrightnessInfo.hbmMode.value,
-                    mCachedBrightnessInfo.hbmTransitionPoint.value,
-                    mCachedBrightnessInfo.brightnessMaxReason.value);
-        }
-    }
-
-    @Override
-    public void onBootCompleted() {
-        Message msg = mHandler.obtainMessage(MSG_BOOT_COMPLETED);
-        mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
-    }
-
-    private boolean saveBrightnessInfo(float brightness) {
-        return saveBrightnessInfo(brightness, /* state= */ null);
-    }
-
-    private boolean saveBrightnessInfo(float brightness, @Nullable DisplayBrightnessState state) {
-        return saveBrightnessInfo(brightness, brightness, state);
-    }
-
-    private boolean saveBrightnessInfo(float brightness, float adjustedBrightness,
-            @Nullable DisplayBrightnessState state) {
-        synchronized (mCachedBrightnessInfo) {
-            float stateMax = state != null ? state.getMaxBrightness() : PowerManager.BRIGHTNESS_MAX;
-            float stateMin = state != null ? state.getMinBrightness() : PowerManager.BRIGHTNESS_MAX;
-            final float minBrightness = Math.max(stateMin, Math.min(
-                    mBrightnessRangeController.getCurrentBrightnessMin(), stateMax));
-            final float maxBrightness = Math.min(
-                    mBrightnessRangeController.getCurrentBrightnessMax(), stateMax);
-            boolean changed = false;
-
-            changed |=
-                    mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightness,
-                            brightness);
-            changed |=
-                    mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.adjustedBrightness,
-                            adjustedBrightness);
-            changed |=
-                    mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMin,
-                            minBrightness);
-            changed |=
-                    mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMax,
-                            maxBrightness);
-            changed |=
-                    mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode,
-                            mBrightnessRangeController.getHighBrightnessMode());
-            changed |=
-                    mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint,
-                            mBrightnessRangeController.getTransitionPoint());
-            changed |=
-                    mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
-                            mBrightnessClamperController.getBrightnessMaxReason());
-            return changed;
-        }
-    }
-
-    void postBrightnessChangeRunnable() {
-        if (!mHandler.hasCallbacks(mOnBrightnessChangeRunnable)) {
-            mHandler.post(mOnBrightnessChangeRunnable);
-        }
-    }
-
-    private HighBrightnessModeController createHbmControllerLocked(
-            HighBrightnessModeMetadata hbmMetadata, Runnable modeChangeCallback) {
-        final DisplayDeviceConfig ddConfig = mDisplayDevice.getDisplayDeviceConfig();
-        final IBinder displayToken = mDisplayDevice.getDisplayTokenLocked();
-        final String displayUniqueId = mDisplayDevice.getUniqueId();
-        final DisplayDeviceConfig.HighBrightnessModeData hbmData =
-                ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
-        final DisplayDeviceInfo info = mDisplayDevice.getDisplayDeviceInfoLocked();
-        return mInjector.getHighBrightnessModeController(mHandler, info.width, info.height,
-                displayToken, displayUniqueId, PowerManager.BRIGHTNESS_MIN,
-                PowerManager.BRIGHTNESS_MAX, hbmData, (sdrBrightness, maxDesiredHdrSdrRatio) ->
-                        mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness,
-                                maxDesiredHdrSdrRatio), modeChangeCallback, hbmMetadata, mContext);
-    }
-
-    private BrightnessThrottler createBrightnessThrottlerLocked() {
-        final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
-        final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
-        return new BrightnessThrottler(mHandler,
-                () -> {
-                    sendUpdatePowerState();
-                    postBrightnessChangeRunnable();
-                }, mUniqueDisplayId,
-                mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId,
-                ddConfig.getThermalBrightnessThrottlingDataMapByThrottlingId());
-    }
-
-    private void blockScreenOn() {
-        if (mPendingScreenOnUnblocker == null) {
-            Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0);
-            mPendingScreenOnUnblocker = new ScreenOnUnblocker();
-            mScreenOnBlockStartRealTime = SystemClock.elapsedRealtime();
-            Slog.i(mTag, "Blocking screen on until initial contents have been drawn.");
-        }
-    }
-
-    private void unblockScreenOn() {
-        if (mPendingScreenOnUnblocker != null) {
-            mPendingScreenOnUnblocker = null;
-            long delay = SystemClock.elapsedRealtime() - mScreenOnBlockStartRealTime;
-            Slog.i(mTag, "Unblocked screen on after " + delay + " ms");
-            Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0);
-        }
-    }
-
-    private void blockScreenOff() {
-        if (mPendingScreenOffUnblocker == null) {
-            Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_OFF_BLOCKED_TRACE_NAME, 0);
-            mPendingScreenOffUnblocker = new ScreenOffUnblocker();
-            mScreenOffBlockStartRealTime = SystemClock.elapsedRealtime();
-            Slog.i(mTag, "Blocking screen off");
-        }
-    }
-
-    private void unblockScreenOff() {
-        if (mPendingScreenOffUnblocker != null) {
-            mPendingScreenOffUnblocker = null;
-            long delay = SystemClock.elapsedRealtime() - mScreenOffBlockStartRealTime;
-            Slog.i(mTag, "Unblocked screen off after " + delay + " ms");
-            Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, SCREEN_OFF_BLOCKED_TRACE_NAME, 0);
-        }
-    }
-
-    private void blockScreenOnByDisplayOffload(DisplayOffloadSession displayOffloadSession) {
-        if (mPendingScreenOnUnblockerByDisplayOffload != null || displayOffloadSession == null) {
-            return;
-        }
-        mScreenTurningOnWasBlockedByDisplayOffload = true;
-
-        Trace.asyncTraceBegin(
-                Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
-        mScreenOnBlockByDisplayOffloadStartRealTime = SystemClock.elapsedRealtime();
-
-        mPendingScreenOnUnblockerByDisplayOffload =
-                () -> onDisplayOffloadUnblockScreenOn(displayOffloadSession);
-        if (!displayOffloadSession.blockScreenOn(mPendingScreenOnUnblockerByDisplayOffload)) {
-            mPendingScreenOnUnblockerByDisplayOffload = null;
-            long delay =
-                    SystemClock.elapsedRealtime() - mScreenOnBlockByDisplayOffloadStartRealTime;
-            Slog.w(mTag, "Tried blocking screen on for offloading but failed. So, end trace after "
-                    + delay + " ms.");
-            Trace.asyncTraceEnd(
-                    Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
-            return;
-        }
-        Slog.i(mTag, "Blocking screen on for offloading.");
-    }
-
-    private void onDisplayOffloadUnblockScreenOn(DisplayOffloadSession displayOffloadSession) {
-        Message msg = mHandler.obtainMessage(MSG_OFFLOADING_SCREEN_ON_UNBLOCKED,
-                displayOffloadSession);
-        mHandler.sendMessage(msg);
-    }
-
-    private void unblockScreenOnByDisplayOffload() {
-        if (mPendingScreenOnUnblockerByDisplayOffload == null) {
-            return;
-        }
-        mPendingScreenOnUnblockerByDisplayOffload = null;
-        long delay = SystemClock.elapsedRealtime() - mScreenOnBlockByDisplayOffloadStartRealTime;
-        Slog.i(mTag, "Unblocked screen on for offloading after " + delay + " ms");
-        Trace.asyncTraceEnd(
-                Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
-    }
-
-    private boolean setScreenState(int state) {
-        return setScreenState(state, false /*reportOnly*/);
-    }
-
-    private boolean setScreenState(int state, boolean reportOnly) {
-        final boolean isOff = (state == Display.STATE_OFF);
-        final boolean isOn = (state == Display.STATE_ON);
-        final boolean changed = mPowerState.getScreenState() != state;
-
-        // If the screen is turning on, give displayoffload a chance to do something before the
-        // screen actually turns on.
-        // TODO(b/316941732): add tests for this displayoffload screen-on blocker.
-        if (isOn && changed && !mScreenTurningOnWasBlockedByDisplayOffload) {
-            blockScreenOnByDisplayOffload(mDisplayOffloadSession);
-        } else if (!isOn && mScreenTurningOnWasBlockedByDisplayOffload) {
-            // No longer turning screen on, so unblock previous screen on blocking immediately.
-            unblockScreenOnByDisplayOffload();
-            mScreenTurningOnWasBlockedByDisplayOffload = false;
-        }
-
-        if (changed || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
-            // If we are trying to turn screen off, give policy a chance to do something before we
-            // actually turn the screen off.
-            if (isOff && !mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) {
-                if (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_ON
-                        || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
-                    setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_OFF);
-                    blockScreenOff();
-                    mWindowManagerPolicy.screenTurningOff(mDisplayId, mPendingScreenOffUnblocker);
-                    unblockScreenOff();
-                } else if (mPendingScreenOffUnblocker != null) {
-                    // Abort doing the state change until screen off is unblocked.
-                    return false;
-                }
-            }
-
-            if (!reportOnly && changed && readyToUpdateDisplayState()
-                    && mPendingScreenOffUnblocker == null
-                    && mPendingScreenOnUnblockerByDisplayOffload == null) {
-                Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state);
-
-                String propertyKey = "debug.tracing.screen_state";
-                String propertyValue = String.valueOf(state);
-                try {
-                    // TODO(b/153319140) remove when we can get this from the above trace invocation
-                    SystemProperties.set(propertyKey, propertyValue);
-                } catch (RuntimeException e) {
-                    Slog.e(mTag, "Failed to set a system property: key=" + propertyKey
-                            + " value=" + propertyValue + " " + e.getMessage());
-                }
-
-                mPowerState.setScreenState(state);
-                // Tell battery stats about the transition.
-                noteScreenState(state);
-            }
-        }
-
-        // Tell the window manager policy when the screen is turned off or on unless it's due
-        // to the proximity sensor.  We temporarily block turning the screen on until the
-        // window manager is ready by leaving a black surface covering the screen.
-        // This surface is essentially the final state of the color fade animation and
-        // it is only removed once the window manager tells us that the activity has
-        // finished drawing underneath.
-        if (isOff && mReportedScreenStateToPolicy != REPORTED_TO_POLICY_SCREEN_OFF
-                && !mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) {
-            setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF);
-            unblockScreenOn();
-            mWindowManagerPolicy.screenTurnedOff(mDisplayId, mIsInTransition);
-        } else if (!isOff
-                && mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_TURNING_OFF) {
-
-            // We told policy already that screen was turning off, but now we changed our minds.
-            // Complete the full state transition on -> turningOff -> off.
-            unblockScreenOff();
-            mWindowManagerPolicy.screenTurnedOff(mDisplayId, mIsInTransition);
-            setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF);
-        }
-        if (!isOff
-                && (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_OFF
-                || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED)) {
-            setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_ON);
-            if (mPowerState.getColorFadeLevel() == 0.0f) {
-                blockScreenOn();
-            } else {
-                unblockScreenOn();
-            }
-            mWindowManagerPolicy.screenTurningOn(mDisplayId, mPendingScreenOnUnblocker);
-        }
-
-        // Return true if the screen isn't blocked.
-        return mPendingScreenOnUnblocker == null
-                && mPendingScreenOnUnblockerByDisplayOffload == null;
-    }
-
-    private void setReportedScreenState(int state) {
-        Trace.traceCounter(Trace.TRACE_TAG_POWER, "ReportedScreenStateToPolicy", state);
-        mReportedScreenStateToPolicy = state;
-        if (state == REPORTED_TO_POLICY_SCREEN_ON) {
-            mScreenTurningOnWasBlockedByDisplayOffload = false;
-        }
-    }
-
-    private void loadAmbientLightSensor() {
-        final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY
-                ? Sensor.TYPE_LIGHT : SensorUtils.NO_FALLBACK;
-        mLightSensor = SensorUtils.findSensor(mSensorManager,
-                mDisplayDeviceConfig.getAmbientLightSensor(), fallbackType);
-    }
-
-    private void loadScreenOffBrightnessSensor() {
-        mScreenOffBrightnessSensor = SensorUtils.findSensor(mSensorManager,
-                mDisplayDeviceConfig.getScreenOffBrightnessSensor(), SensorUtils.NO_FALLBACK);
-    }
-
-    private float clampScreenBrightness(float value) {
-        if (Float.isNaN(value)) {
-            value = PowerManager.BRIGHTNESS_MIN;
-        }
-        return MathUtils.constrain(value, mBrightnessRangeController.getCurrentBrightnessMin(),
-                mBrightnessRangeController.getCurrentBrightnessMax());
-    }
-
-    private void animateScreenBrightness(float target, float sdrTarget, float rate) {
-        animateScreenBrightness(target, sdrTarget, rate, /* ignoreAnimationLimits = */false);
-    }
-
-    private void animateScreenBrightness(float target, float sdrTarget, float rate,
-            boolean ignoreAnimationLimits) {
-        if (DEBUG) {
-            Slog.d(mTag, "Animating brightness: target=" + target + ", sdrTarget=" + sdrTarget
-                    + ", rate=" + rate);
-        }
-        if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate,
-                ignoreAnimationLimits)) {
-            Trace.traceCounter(Trace.TRACE_TAG_POWER, "TargetScreenBrightness", (int) target);
-
-            String propertyKey = "debug.tracing.screen_brightness";
-            String propertyValue = String.valueOf(target);
-            try {
-                // TODO(b/153319140) remove when we can get this from the above trace invocation
-                SystemProperties.set(propertyKey, propertyValue);
-            } catch (RuntimeException e) {
-                Slog.e(mTag, "Failed to set a system property: key=" + propertyKey
-                        + " value=" + propertyValue + " " + e.getMessage());
-            }
-
-            noteScreenBrightness(target);
-        }
-    }
-
-    private void animateScreenStateChange(int target, boolean performScreenOffTransition) {
-        // If there is already an animation in progress, don't interfere with it.
-        if (mColorFadeEnabled
-                && (mColorFadeOnAnimator.isStarted() || mColorFadeOffAnimator.isStarted())) {
-            if (target != Display.STATE_ON) {
-                return;
-            }
-            // If display state changed to on, proceed and stop the color fade and turn screen on.
-            mPendingScreenOff = false;
-        }
-
-        if (mDisplayBlanksAfterDozeConfig
-                && Display.isDozeState(mPowerState.getScreenState())
-                && !Display.isDozeState(target)) {
-            // Skip the screen off animation and add a black surface to hide the
-            // contents of the screen.
-            mPowerState.prepareColorFade(mContext,
-                    mColorFadeFadesConfig ? ColorFade.MODE_FADE : ColorFade.MODE_WARM_UP);
-            if (mColorFadeOffAnimator != null) {
-                mColorFadeOffAnimator.end();
-            }
-            // Some display hardware will blank itself on the transition between doze and non-doze
-            // but still on display states. In this case we want to report to policy that the
-            // display has turned off so it can prepare the appropriate power on animation, but we
-            // don't want to actually transition to the fully off state since that takes
-            // significantly longer to transition from.
-            setScreenState(Display.STATE_OFF, target != Display.STATE_OFF /*reportOnly*/);
-        }
-
-        // If we were in the process of turning off the screen but didn't quite
-        // finish.  Then finish up now to prevent a jarring transition back
-        // to screen on if we skipped blocking screen on as usual.
-        if (mPendingScreenOff && target != Display.STATE_OFF) {
-            setScreenState(Display.STATE_OFF);
-            mPendingScreenOff = false;
-            mPowerState.dismissColorFadeResources();
-        }
-
-        if (target == Display.STATE_ON) {
-            // Want screen on.  The contents of the screen may not yet
-            // be visible if the color fade has not been dismissed because
-            // its last frame of animation is solid black.
-            if (!setScreenState(Display.STATE_ON)) {
-                return; // screen on blocked
-            }
-            if (USE_COLOR_FADE_ON_ANIMATION && mColorFadeEnabled && mPowerRequest.isBrightOrDim()) {
-                // Perform screen on animation.
-                if (mPowerState.getColorFadeLevel() == 1.0f) {
-                    mPowerState.dismissColorFade();
-                } else if (mPowerState.prepareColorFade(mContext,
-                        mColorFadeFadesConfig
-                                ? ColorFade.MODE_FADE : ColorFade.MODE_WARM_UP)) {
-                    mColorFadeOnAnimator.start();
-                } else {
-                    mColorFadeOnAnimator.end();
-                }
-            } else {
-                // Skip screen on animation.
-                mPowerState.setColorFadeLevel(1.0f);
-                mPowerState.dismissColorFade();
-            }
-        } else if (target == Display.STATE_DOZE) {
-            // Want screen dozing.
-            // Wait for brightness animation to complete beforehand when entering doze
-            // from screen on to prevent a perceptible jump because brightness may operate
-            // differently when the display is configured for dozing.
-            if (mScreenBrightnessRampAnimator.isAnimating()
-                    && mPowerState.getScreenState() == Display.STATE_ON) {
-                return;
-            }
-
-            // Set screen state.
-            if (!setScreenState(Display.STATE_DOZE)) {
-                return; // screen on blocked
-            }
-
-            // Dismiss the black surface without fanfare.
-            mPowerState.setColorFadeLevel(1.0f);
-            mPowerState.dismissColorFade();
-        } else if (target == Display.STATE_DOZE_SUSPEND) {
-            // Want screen dozing and suspended.
-            // Wait for brightness animation to complete beforehand unless already
-            // suspended because we may not be able to change it after suspension.
-            if (mScreenBrightnessRampAnimator.isAnimating()
-                    && mPowerState.getScreenState() != Display.STATE_DOZE_SUSPEND) {
-                return;
-            }
-
-            // If not already suspending, temporarily set the state to doze until the
-            // screen on is unblocked, then suspend.
-            if (mPowerState.getScreenState() != Display.STATE_DOZE_SUSPEND) {
-                if (!setScreenState(Display.STATE_DOZE)) {
-                    return; // screen on blocked
-                }
-                setScreenState(Display.STATE_DOZE_SUSPEND); // already on so can't block
-            }
-
-            // Dismiss the black surface without fanfare.
-            mPowerState.setColorFadeLevel(1.0f);
-            mPowerState.dismissColorFade();
-        } else if (target == Display.STATE_ON_SUSPEND) {
-            // Want screen full-power and suspended.
-            // Wait for brightness animation to complete beforehand unless already
-            // suspended because we may not be able to change it after suspension.
-            if (mScreenBrightnessRampAnimator.isAnimating()
-                    && mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) {
-                return;
-            }
-
-            // If not already suspending, temporarily set the state to on until the
-            // screen on is unblocked, then suspend.
-            if (mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) {
-                if (!setScreenState(Display.STATE_ON)) {
-                    return;
-                }
-                setScreenState(Display.STATE_ON_SUSPEND);
-            }
-
-            // Dismiss the black surface without fanfare.
-            mPowerState.setColorFadeLevel(1.0f);
-            mPowerState.dismissColorFade();
-        } else {
-            // Want screen off.
-            mPendingScreenOff = true;
-            if (!mColorFadeEnabled) {
-                mPowerState.setColorFadeLevel(0.0f);
-            }
-
-            if (mPowerState.getColorFadeLevel() == 0.0f) {
-                // Turn the screen off.
-                // A black surface is already hiding the contents of the screen.
-                setScreenState(Display.STATE_OFF);
-                mPendingScreenOff = false;
-                mPowerState.dismissColorFadeResources();
-            } else if (performScreenOffTransition
-                    && mPowerState.prepareColorFade(mContext,
-                    mColorFadeFadesConfig
-                            ? ColorFade.MODE_FADE : ColorFade.MODE_COOL_DOWN)
-                    && mPowerState.getScreenState() != Display.STATE_OFF) {
-                // Perform the screen off animation.
-                mColorFadeOffAnimator.start();
-            } else {
-                // Skip the screen off animation and add a black surface to hide the
-                // contents of the screen.
-                mColorFadeOffAnimator.end();
-            }
-        }
-    }
-
-    private final Runnable mCleanListener = this::sendUpdatePowerState;
-
-    private void sendOnStateChangedWithWakelock() {
-        boolean wakeLockAcquired = mWakelockController.acquireWakelock(
-                WakelockController.WAKE_LOCK_STATE_CHANGED);
-        if (wakeLockAcquired) {
-            mHandler.post(mWakelockController.getOnStateChangedRunnable());
-        }
-    }
-
-    private void logDisplayPolicyChanged(int newPolicy) {
-        LogMaker log = new LogMaker(MetricsEvent.DISPLAY_POLICY);
-        log.setType(MetricsEvent.TYPE_UPDATE);
-        log.setSubtype(newPolicy);
-        MetricsLogger.action(log);
-    }
-
-    private void handleSettingsChange(boolean userSwitch) {
-        mDisplayBrightnessController
-                .setPendingScreenBrightness(mDisplayBrightnessController
-                        .getScreenBrightnessSetting());
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(userSwitch);
-        if (userSwitch) {
-            // Don't treat user switches as user initiated change.
-            mDisplayBrightnessController
-                    .setAndNotifyCurrentScreenBrightness(mDisplayBrightnessController
-                            .getPendingScreenBrightness());
-            if (mAutomaticBrightnessController != null) {
-                mAutomaticBrightnessController.resetShortTermModel();
-            }
-        }
-        sendUpdatePowerState();
-    }
-
-    private void handleBrightnessModeChange() {
-        final int screenBrightnessModeSetting = Settings.System.getIntForUser(
-                mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
-        mHandler.postAtTime(() -> {
-            mAutomaticBrightnessStrategy.setUseAutoBrightness(screenBrightnessModeSetting
-                    == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-            updatePowerState();
-        }, mClock.uptimeMillis());
-    }
-
-
-    @Override
-    public float getScreenBrightnessSetting() {
-        return mDisplayBrightnessController.getScreenBrightnessSetting();
-    }
-
-    @Override
-    public void setBrightness(float brightnessValue, int userSerial) {
-        mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightnessValue),
-                userSerial);
-    }
-
-    @Override
-    public int getDisplayId() {
-        return mDisplayId;
-    }
-
-    @Override
-    public int getLeadDisplayId() {
-        return mLeadDisplayId;
-    }
-
-    @Override
-    public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux,
-            boolean slowChange) {
-        mBrightnessRangeController.onAmbientLuxChange(ambientLux);
-        if (nits == BrightnessMappingStrategy.INVALID_NITS) {
-            mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness, slowChange);
-        } else {
-            float brightness = mDisplayBrightnessController.getBrightnessFromNits(nits);
-            if (BrightnessUtils.isValidBrightnessValue(brightness)) {
-                mDisplayBrightnessController.setBrightnessToFollow(brightness, slowChange);
-            } else {
-                // The device does not support nits
-                mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness,
-                        slowChange);
-            }
-        }
-        sendUpdatePowerState();
-    }
-
-    private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
-            boolean wasShortTermModelActive, boolean autobrightnessEnabled,
-            boolean brightnessIsTemporary, boolean shouldUseAutoBrightness) {
-
-        final float brightnessInNits =
-                mDisplayBrightnessController.convertToAdjustedNits(brightness);
-        // Don't report brightness to brightnessTracker:
-        // If brightness is temporary (ie the slider has not been released)
-        // or if we are in idle screen brightness mode.
-        // or display is not on
-        // or we shouldn't be using autobrightness
-        // or the nits is invalid.
-        if (brightnessIsTemporary
-                || mAutomaticBrightnessController == null
-                || mAutomaticBrightnessController.isInIdleMode()
-                || !autobrightnessEnabled
-                || mBrightnessTracker == null
-                || !shouldUseAutoBrightness
-                || brightnessInNits < 0.0f) {
-            return;
-        }
-
-        if (userInitiated && (mAutomaticBrightnessController == null
-                || !mAutomaticBrightnessController.hasValidAmbientLux())) {
-            // If we don't have a valid lux reading we can't report a valid
-            // slider event so notify as if the system changed the brightness.
-            userInitiated = false;
-        }
-
-        // We only want to track changes on devices that can actually map the display backlight
-        // values into a physical brightness unit since the value provided by the API is in
-        // nits and not using the arbitrary backlight units.
-        final float powerFactor = mPowerRequest.lowPowerMode
-                ? mPowerRequest.screenLowPowerBrightnessFactor
-                : 1.0f;
-        mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated,
-                powerFactor, wasShortTermModelActive,
-                mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId,
-                mAutomaticBrightnessController.getLastSensorValues(),
-                mAutomaticBrightnessController.getLastSensorTimestamps());
-    }
-
-    @Override
-    public void addDisplayBrightnessFollower(DisplayPowerControllerInterface follower) {
-        synchronized (mLock) {
-            mDisplayBrightnessFollowers.append(follower.getDisplayId(), follower);
-            sendUpdatePowerStateLocked();
-        }
-    }
-
-    @Override
-    public void removeDisplayBrightnessFollower(DisplayPowerControllerInterface follower) {
-        synchronized (mLock) {
-            mDisplayBrightnessFollowers.remove(follower.getDisplayId());
-            mHandler.postAtTime(() -> follower.setBrightnessToFollow(
-                    PowerManager.BRIGHTNESS_INVALID_FLOAT, BrightnessMappingStrategy.INVALID_NITS,
-                    /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void clearDisplayBrightnessFollowersLocked() {
-        for (int i = 0; i < mDisplayBrightnessFollowers.size(); i++) {
-            DisplayPowerControllerInterface follower = mDisplayBrightnessFollowers.valueAt(i);
-            mHandler.postAtTime(() -> follower.setBrightnessToFollow(
-                    PowerManager.BRIGHTNESS_INVALID_FLOAT, BrightnessMappingStrategy.INVALID_NITS,
-                    /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
-        }
-        mDisplayBrightnessFollowers.clear();
-    }
-
-    @Override
-    public void dump(final PrintWriter pw) {
-        synchronized (mLock) {
-            pw.println();
-            pw.println("Display Power Controller:");
-            pw.println("  mDisplayId=" + mDisplayId);
-            pw.println("  mLeadDisplayId=" + mLeadDisplayId);
-            pw.println("  mLightSensor=" + mLightSensor);
-            pw.println("  mDisplayBrightnessFollowers=" + mDisplayBrightnessFollowers);
-
-            pw.println();
-            pw.println("Display Power Controller Locked State:");
-            pw.println("  mDisplayReadyLocked=" + mDisplayReadyLocked);
-            pw.println("  mPendingRequestLocked=" + mPendingRequestLocked);
-            pw.println("  mPendingRequestChangedLocked=" + mPendingRequestChangedLocked);
-            pw.println("  mPendingUpdatePowerStateLocked=" + mPendingUpdatePowerStateLocked);
-        }
-
-        pw.println();
-        pw.println("Display Power Controller Configuration:");
-        pw.println("  mScreenBrightnessDozeConfig=" + mScreenBrightnessDozeConfig);
-        pw.println("  mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig);
-        pw.println("  mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp);
-        pw.println("  mColorFadeFadesConfig=" + mColorFadeFadesConfig);
-        pw.println("  mColorFadeEnabled=" + mColorFadeEnabled);
-        pw.println("  mIsDisplayInternal=" + mIsDisplayInternal);
-        synchronized (mCachedBrightnessInfo) {
-            pw.println("  mCachedBrightnessInfo.brightness="
-                    + mCachedBrightnessInfo.brightness.value);
-            pw.println("  mCachedBrightnessInfo.adjustedBrightness="
-                    + mCachedBrightnessInfo.adjustedBrightness.value);
-            pw.println("  mCachedBrightnessInfo.brightnessMin="
-                    + mCachedBrightnessInfo.brightnessMin.value);
-            pw.println("  mCachedBrightnessInfo.brightnessMax="
-                    + mCachedBrightnessInfo.brightnessMax.value);
-            pw.println("  mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode.value);
-            pw.println("  mCachedBrightnessInfo.hbmTransitionPoint="
-                    + mCachedBrightnessInfo.hbmTransitionPoint.value);
-            pw.println("  mCachedBrightnessInfo.brightnessMaxReason ="
-                    + mCachedBrightnessInfo.brightnessMaxReason.value);
-        }
-        pw.println("  mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
-        pw.println("  mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
-        mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
-    }
-
-    private void dumpLocal(PrintWriter pw) {
-        pw.println();
-        pw.println("Display Power Controller Thread State:");
-        pw.println("  mPowerRequest=" + mPowerRequest);
-        pw.println("  mBrightnessReason=" + mBrightnessReason);
-        pw.println("  mAppliedDimming=" + mAppliedDimming);
-        pw.println("  mAppliedThrottling=" + mAppliedThrottling);
-        pw.println("  mDozing=" + mDozing);
-        pw.println("  mSkipRampState=" + skipRampStateToString(mSkipRampState));
-        pw.println("  mScreenOnBlockStartRealTime=" + mScreenOnBlockStartRealTime);
-        pw.println("  mScreenOffBlockStartRealTime=" + mScreenOffBlockStartRealTime);
-        pw.println("  mPendingScreenOnUnblocker=" + mPendingScreenOnUnblocker);
-        pw.println("  mPendingScreenOffUnblocker=" + mPendingScreenOffUnblocker);
-        pw.println("  mPendingScreenOff=" + mPendingScreenOff);
-        pw.println("  mReportedToPolicy="
-                + reportedToPolicyToString(mReportedScreenStateToPolicy));
-        pw.println("  mIsRbcActive=" + mIsRbcActive);
-        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
-        mAutomaticBrightnessStrategy.dump(ipw);
-
-        if (mScreenBrightnessRampAnimator != null) {
-            pw.println("  mScreenBrightnessRampAnimator.isAnimating()="
-                    + mScreenBrightnessRampAnimator.isAnimating());
-        }
-
-        if (mColorFadeOnAnimator != null) {
-            pw.println("  mColorFadeOnAnimator.isStarted()="
-                    + mColorFadeOnAnimator.isStarted());
-        }
-        if (mColorFadeOffAnimator != null) {
-            pw.println("  mColorFadeOffAnimator.isStarted()="
-                    + mColorFadeOffAnimator.isStarted());
-        }
-
-        if (mPowerState != null) {
-            mPowerState.dump(pw);
-        }
-
-        if (mAutomaticBrightnessController != null) {
-            mAutomaticBrightnessController.dump(pw);
-            dumpBrightnessEvents(pw);
-        }
-
-        dumpRbcEvents(pw);
-
-        if (mScreenOffBrightnessSensorController != null) {
-            mScreenOffBrightnessSensorController.dump(pw);
-        }
-
-        if (mBrightnessRangeController != null) {
-            mBrightnessRangeController.dump(pw);
-        }
-
-        if (mBrightnessThrottler != null) {
-            mBrightnessThrottler.dump(pw);
-        }
-
-        pw.println();
-        if (mDisplayWhiteBalanceController != null) {
-            mDisplayWhiteBalanceController.dump(pw);
-            mDisplayWhiteBalanceSettings.dump(pw);
-        }
-
-        pw.println();
-
-        if (mWakelockController != null) {
-            mWakelockController.dumpLocal(pw);
-        }
-
-        pw.println();
-        if (mDisplayBrightnessController != null) {
-            mDisplayBrightnessController.dump(pw);
-        }
-
-        pw.println();
-        if (mDisplayStateController != null) {
-            mDisplayStateController.dumpsys(pw);
-        }
-
-        pw.println();
-        if (mBrightnessClamperController != null) {
-            mBrightnessClamperController.dump(ipw);
-        }
-    }
-
-
-    private static String reportedToPolicyToString(int state) {
-        switch (state) {
-            case REPORTED_TO_POLICY_SCREEN_OFF:
-                return "REPORTED_TO_POLICY_SCREEN_OFF";
-            case REPORTED_TO_POLICY_SCREEN_TURNING_ON:
-                return "REPORTED_TO_POLICY_SCREEN_TURNING_ON";
-            case REPORTED_TO_POLICY_SCREEN_ON:
-                return "REPORTED_TO_POLICY_SCREEN_ON";
-            default:
-                return Integer.toString(state);
-        }
-    }
-
-    private static String skipRampStateToString(int state) {
-        switch (state) {
-            case RAMP_STATE_SKIP_NONE:
-                return "RAMP_STATE_SKIP_NONE";
-            case RAMP_STATE_SKIP_INITIAL:
-                return "RAMP_STATE_SKIP_INITIAL";
-            case RAMP_STATE_SKIP_AUTOBRIGHT:
-                return "RAMP_STATE_SKIP_AUTOBRIGHT";
-            default:
-                return Integer.toString(state);
-        }
-    }
-
-    private void dumpBrightnessEvents(PrintWriter pw) {
-        int size = mBrightnessEventRingBuffer.size();
-        if (size < 1) {
-            pw.println("No Automatic Brightness Adjustments");
-            return;
-        }
-
-        pw.println("Automatic Brightness Adjustments Last " + size + " Events: ");
-        BrightnessEvent[] eventArray = mBrightnessEventRingBuffer.toArray();
-        for (int i = 0; i < mBrightnessEventRingBuffer.size(); i++) {
-            pw.println("  " + eventArray[i].toString());
-        }
-    }
-
-    private void dumpRbcEvents(PrintWriter pw) {
-        int size = mRbcEventRingBuffer.size();
-        if (size < 1) {
-            pw.println("No Reduce Bright Colors Adjustments");
-            return;
-        }
-
-        pw.println("Reduce Bright Colors Adjustments Last " + size + " Events: ");
-        BrightnessEvent[] eventArray = mRbcEventRingBuffer.toArray();
-        for (int i = 0; i < mRbcEventRingBuffer.size(); i++) {
-            pw.println("  " + eventArray[i]);
-        }
-    }
-
-
-    private void noteScreenState(int screenState) {
-        // Log screen state change with display id
-        FrameworkStatsLog.write(FrameworkStatsLog.SCREEN_STATE_CHANGED_V2,
-                screenState, mDisplayStatsId);
-        if (mBatteryStats != null) {
-            try {
-                // TODO(multi-display): make this multi-display
-                mBatteryStats.noteScreenState(screenState);
-            } catch (RemoteException e) {
-                // same process
-            }
-        }
-    }
-
-    @SuppressLint("AndroidFrameworkRequiresPermission")
-    private void noteScreenBrightness(float brightness) {
-        if (mBatteryStats != null) {
-            try {
-                // TODO(brightnessfloat): change BatteryStats to use float
-                int brightnessInt = mFlags.isBrightnessIntRangeUserPerceptionEnabled()
-                        ? BrightnessSynchronizer.brightnessFloatToIntSetting(mContext, brightness)
-                        : BrightnessSynchronizer.brightnessFloatToInt(brightness);
-                mBatteryStats.noteScreenBrightness(brightnessInt);
-            } catch (RemoteException e) {
-                // same process
-            }
-        }
-    }
-
-    private void reportStats(float brightness) {
-        if (mLastStatsBrightness == brightness) {
-            return;
-        }
-
-        float hbmTransitionPoint = PowerManager.BRIGHTNESS_MAX;
-        synchronized (mCachedBrightnessInfo) {
-            if (mCachedBrightnessInfo.hbmTransitionPoint == null) {
-                return;
-            }
-            hbmTransitionPoint = mCachedBrightnessInfo.hbmTransitionPoint.value;
-        }
-
-        final boolean aboveTransition = brightness > hbmTransitionPoint;
-        final boolean oldAboveTransition = mLastStatsBrightness > hbmTransitionPoint;
-
-        if (aboveTransition || oldAboveTransition) {
-            mLastStatsBrightness = brightness;
-            mHandler.removeMessages(MSG_STATSD_HBM_BRIGHTNESS);
-            if (aboveTransition != oldAboveTransition) {
-                // report immediately
-                logHbmBrightnessStats(brightness, mDisplayStatsId);
-            } else {
-                // delay for rate limiting
-                Message msg = mHandler.obtainMessage();
-                msg.what = MSG_STATSD_HBM_BRIGHTNESS;
-                msg.arg1 = Float.floatToIntBits(brightness);
-                msg.arg2 = mDisplayStatsId;
-                mHandler.sendMessageAtTime(msg, mClock.uptimeMillis()
-                        + BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS);
-            }
-        }
-    }
-
-    private void logHbmBrightnessStats(float brightness, int displayStatsId) {
-        synchronized (mHandler) {
-            FrameworkStatsLog.write(
-                    FrameworkStatsLog.DISPLAY_HBM_BRIGHTNESS_CHANGED, displayStatsId, brightness);
-        }
-    }
-
-    // Return bucket index of range_[left]_[right] where
-    // left <= nits < right
-    private int nitsToRangeIndex(float nits) {
-        for (int i = 0; i < BRIGHTNESS_RANGE_BOUNDARIES.length; i++) {
-            if (nits < BRIGHTNESS_RANGE_BOUNDARIES[i]) {
-                return BRIGHTNESS_RANGE_INDEX[i];
-            }
-        }
-        return FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__BUCKET_INDEX__RANGE_3000_INF;
-    }
-
-    private int convertBrightnessReasonToStatsEnum(int brightnessReason) {
-        switch(brightnessReason) {
-            case BrightnessReason.REASON_UNKNOWN:
-                return FrameworkStatsLog
-                    .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_UNKNOWN;
-            case BrightnessReason.REASON_MANUAL:
-                return FrameworkStatsLog
-                    .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_MANUAL;
-            case BrightnessReason.REASON_DOZE:
-                return FrameworkStatsLog
-                    .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_DOZE;
-            case BrightnessReason.REASON_DOZE_DEFAULT:
-                return FrameworkStatsLog
-                    .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_DOZE_DEFAULT;
-            case BrightnessReason.REASON_AUTOMATIC:
-                return FrameworkStatsLog
-                    .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_AUTOMATIC;
-            case BrightnessReason.REASON_SCREEN_OFF:
-                return FrameworkStatsLog
-                    .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_SCREEN_OFF;
-            case BrightnessReason.REASON_OVERRIDE:
-                return FrameworkStatsLog
-                    .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_OVERRIDE;
-            case BrightnessReason.REASON_TEMPORARY:
-                return FrameworkStatsLog
-                    .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_TEMPORARY;
-            case BrightnessReason.REASON_BOOST:
-                return FrameworkStatsLog
-                    .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_BOOST;
-            case BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR:
-                return FrameworkStatsLog
-                    .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_SCREEN_OFF_BRIGHTNESS_SENSOR;
-            case BrightnessReason.REASON_FOLLOWER:
-                return FrameworkStatsLog
-                    .DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_FOLLOWER;
-        }
-        return FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_UNKNOWN;
-    }
-
-    private void logBrightnessEvent(BrightnessEvent event, float unmodifiedBrightness) {
-        int modifier = event.getReason().getModifier();
-        int flags = event.getFlags();
-        // It's easier to check if the brightness is at maximum level using the brightness
-        // value untouched by any modifiers
-        boolean brightnessIsMax = unmodifiedBrightness == event.getHbmMax();
-        float brightnessInNits =
-                mDisplayBrightnessController.convertToAdjustedNits(event.getBrightness());
-        float appliedLowPowerMode = event.isLowPowerModeSet() ? event.getPowerFactor() : -1f;
-        int appliedRbcStrength  = event.isRbcEnabled() ? event.getRbcStrength() : -1;
-        float appliedHbmMaxNits =
-                event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
-                ? -1f : mDisplayBrightnessController.convertToAdjustedNits(event.getHbmMax());
-        // thermalCapNits set to -1 if not currently capping max brightness
-        float appliedThermalCapNits =
-                event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
-                ? -1f : mDisplayBrightnessController.convertToAdjustedNits(event.getThermalMax());
-        if (mIsDisplayInternal) {
-            FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
-                    mDisplayBrightnessController
-                            .convertToAdjustedNits(event.getInitialBrightness()),
-                    brightnessInNits,
-                    event.getLux(),
-                    event.getPhysicalDisplayId(),
-                    event.wasShortTermModelActive(),
-                    appliedLowPowerMode,
-                    appliedRbcStrength,
-                    appliedHbmMaxNits,
-                    appliedThermalCapNits,
-                    event.isAutomaticBrightnessEnabled(),
-                    FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL,
-                    convertBrightnessReasonToStatsEnum(event.getReason().getReason()),
-                    nitsToRangeIndex(brightnessInNits),
-                    brightnessIsMax,
-                    event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
-                    event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR,
-                    (modifier & BrightnessReason.MODIFIER_LOW_POWER) > 0,
-                    mBrightnessClamperController.getBrightnessMaxReason(),
-                    // TODO: (flc) add brightnessMinReason here too.
-                    (modifier & BrightnessReason.MODIFIER_DIMMED) > 0,
-                    event.isRbcEnabled(),
-                    (flags & BrightnessEvent.FLAG_INVALID_LUX) > 0,
-                    (flags & BrightnessEvent.FLAG_DOZE_SCALE) > 0,
-                    (flags & BrightnessEvent.FLAG_USER_SET) > 0,
-                    (flags & BrightnessEvent.FLAG_IDLE_CURVE) > 0,
-                    (flags & BrightnessEvent.FLAG_LOW_POWER_MODE) > 0);
-        }
-    }
-
-    /**
-     * Indicates whether the display state is ready to update. If this is the default display, we
-     * want to update it right away so that we can draw the boot animation on it. If it is not
-     * the default display, drawing the boot animation on it would look incorrect, so we need
-     * to wait until boot is completed.
-     * @return True if the display state is ready to update
-     */
-    private boolean readyToUpdateDisplayState() {
-        return mDisplayId == Display.DEFAULT_DISPLAY || mBootCompleted;
-    }
-
-    private final class DisplayControllerHandler extends Handler {
-        DisplayControllerHandler(Looper looper) {
-            super(looper, null, true /*async*/);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_UPDATE_POWER_STATE:
-                    updatePowerState();
-                    break;
-
-                case MSG_SCREEN_ON_UNBLOCKED:
-                    if (mPendingScreenOnUnblocker == msg.obj) {
-                        unblockScreenOn();
-                        updatePowerState();
-                    }
-                    break;
-                case MSG_SCREEN_OFF_UNBLOCKED:
-                    if (mPendingScreenOffUnblocker == msg.obj) {
-                        unblockScreenOff();
-                        updatePowerState();
-                    }
-                    break;
-                case MSG_OFFLOADING_SCREEN_ON_UNBLOCKED:
-                    if (mDisplayOffloadSession == msg.obj) {
-                        unblockScreenOnByDisplayOffload();
-                        updatePowerState();
-                    }
-                    break;
-                case MSG_CONFIGURE_BRIGHTNESS:
-                    BrightnessConfiguration brightnessConfiguration =
-                            (BrightnessConfiguration) msg.obj;
-                    mAutomaticBrightnessStrategy.setBrightnessConfiguration(brightnessConfiguration,
-                            msg.arg1 == 1);
-                    if (mBrightnessTracker != null) {
-                        mBrightnessTracker
-                                .setShouldCollectColorSample(brightnessConfiguration != null
-                                        && brightnessConfiguration.shouldCollectColorSamples());
-                    }
-                    updatePowerState();
-                    break;
-
-                case MSG_SET_TEMPORARY_BRIGHTNESS:
-                    // TODO: Should we have a a timeout for the temporary brightness?
-                    mDisplayBrightnessController
-                            .setTemporaryBrightness(Float.intBitsToFloat(msg.arg1));
-                    updatePowerState();
-                    break;
-
-                case MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT:
-                    mAutomaticBrightnessStrategy
-                            .setTemporaryAutoBrightnessAdjustment(Float.intBitsToFloat(msg.arg1));
-                    updatePowerState();
-                    break;
-
-                case MSG_STOP:
-                    cleanupHandlerThreadAfterStop();
-                    break;
-
-                case MSG_UPDATE_BRIGHTNESS:
-                    if (mStopped) {
-                        return;
-                    }
-                    handleSettingsChange(false /*userSwitch*/);
-                    break;
-
-                case MSG_UPDATE_RBC:
-                    handleRbcChanged();
-                    break;
-
-                case MSG_BRIGHTNESS_RAMP_DONE:
-                    if (mPowerState != null) {
-                        final float brightness = mPowerState.getScreenBrightness();
-                        reportStats(brightness);
-                    }
-                    break;
-
-                case MSG_STATSD_HBM_BRIGHTNESS:
-                    logHbmBrightnessStats(Float.intBitsToFloat(msg.arg1), msg.arg2);
-                    break;
-
-                case MSG_SWITCH_USER:
-                    handleOnSwitchUser(msg.arg1);
-                    break;
-
-                case MSG_BOOT_COMPLETED:
-                    mBootCompleted = true;
-                    updatePowerState();
-                    break;
-
-                case MSG_SET_DWBC_STRONG_MODE:
-                    setDwbcStrongMode(msg.arg1);
-                    break;
-
-                case MSG_SET_DWBC_COLOR_OVERRIDE:
-                    final float cct = Float.intBitsToFloat(msg.arg1);
-                    setDwbcOverride(cct);
-                    break;
-
-                case MSG_SET_DWBC_LOGGING_ENABLED:
-                    setDwbcLoggingEnabled(msg.arg1);
-                    break;
-                case MSG_SET_BRIGHTNESS_FROM_OFFLOAD:
-                    mDisplayBrightnessController.setBrightnessFromOffload(
-                            Float.intBitsToFloat(msg.arg1));
-                    updatePowerState();
-                    break;
-            }
-        }
-    }
-
-
-    private final class SettingsObserver extends ContentObserver {
-        SettingsObserver(Handler handler) {
-            super(handler);
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            if (uri.equals(Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE))) {
-                handleBrightnessModeChange();
-            } else if (uri.equals(Settings.System.getUriFor(
-                    Settings.System.SCREEN_BRIGHTNESS_FOR_ALS))) {
-                int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
-                        Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
-                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL,
-                        UserHandle.USER_CURRENT);
-                Slog.i(mTag, "Setting up auto-brightness for preset "
-                        + autoBrightnessPresetToString(preset));
-                setUpAutoBrightness(mContext, mHandler);
-                sendUpdatePowerState();
-            } else {
-                handleSettingsChange(false /* userSwitch */);
-            }
-        }
-    }
-
-    private final class ScreenOnUnblocker implements WindowManagerPolicy.ScreenOnListener {
-        @Override
-        public void onScreenOn() {
-            Message msg = mHandler.obtainMessage(MSG_SCREEN_ON_UNBLOCKED, this);
-            mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
-        }
-    }
-
-    private final class ScreenOffUnblocker implements WindowManagerPolicy.ScreenOffListener {
-        @Override
-        public void onScreenOff() {
-            Message msg = mHandler.obtainMessage(MSG_SCREEN_OFF_UNBLOCKED, this);
-            mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
-        }
-    }
-
-    @Override
-    public void setAutoBrightnessLoggingEnabled(boolean enabled) {
-        if (mAutomaticBrightnessController != null) {
-            mAutomaticBrightnessController.setLoggingEnabled(enabled);
-        }
-    }
-
-    @Override // DisplayWhiteBalanceController.Callbacks
-    public void updateWhiteBalance() {
-        sendUpdatePowerState();
-    }
-
-    @Override
-    public void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
-        Message msg = mHandler.obtainMessage();
-        msg.what = MSG_SET_DWBC_LOGGING_ENABLED;
-        msg.arg1 = enabled ? 1 : 0;
-        msg.sendToTarget();
-    }
-
-    @Override
-    public void setAmbientColorTemperatureOverride(float cct) {
-        Message msg = mHandler.obtainMessage();
-        msg.what = MSG_SET_DWBC_COLOR_OVERRIDE;
-        msg.arg1 = Float.floatToIntBits(cct);
-        msg.sendToTarget();
-    }
-
-    /** Functional interface for providing time. */
-    @VisibleForTesting
-    interface Clock {
-        /**
-         * Returns current time in milliseconds since boot, not counting time spent in deep sleep.
-         */
-        long uptimeMillis();
-    }
-
-    @VisibleForTesting
-    static class Injector {
-        Clock getClock() {
-            return SystemClock::uptimeMillis;
-        }
-
-        DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
-                int displayId, int displayState) {
-            return new DisplayPowerState(blanker, colorFade, displayId, displayState);
-        }
-
-        DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
-                FloatProperty<DisplayPowerState> firstProperty,
-                FloatProperty<DisplayPowerState> secondProperty) {
-            return new DualRampAnimator(dps, firstProperty, secondProperty);
-        }
-
-        WakelockController getWakelockController(int displayId,
-                DisplayPowerCallbacks displayPowerCallbacks) {
-            return new WakelockController(displayId, displayPowerCallbacks);
-        }
-
-        DisplayPowerProximityStateController getDisplayPowerProximityStateController(
-                WakelockController wakelockController, DisplayDeviceConfig displayDeviceConfig,
-                Looper looper, Runnable nudgeUpdatePowerState,
-                int displayId, SensorManager sensorManager) {
-            return new DisplayPowerProximityStateController(wakelockController, displayDeviceConfig,
-                    looper, nudgeUpdatePowerState,
-                    displayId, sensorManager, /* injector= */ null);
-        }
-
-        AutomaticBrightnessController getAutomaticBrightnessController(
-                AutomaticBrightnessController.Callbacks callbacks, Looper looper,
-                SensorManager sensorManager, Sensor lightSensor,
-                SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap,
-                int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
-                float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
-                long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
-                long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle,
-                boolean resetAmbientLuxAfterWarmUpConfig,
-                HysteresisLevels ambientBrightnessThresholds,
-                HysteresisLevels screenBrightnessThresholds,
-                HysteresisLevels ambientBrightnessThresholdsIdle,
-                HysteresisLevels screenBrightnessThresholdsIdle, Context context,
-                BrightnessRangeController brightnessModeController,
-                BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
-                int ambientLightHorizonLong, float userLux, float userNits) {
-            return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor,
-                    brightnessMappingStrategyMap, lightSensorWarmUpTime, brightnessMin,
-                    brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate,
-                    brighteningLightDebounceConfig, darkeningLightDebounceConfig,
-                    brighteningLightDebounceConfigIdle, darkeningLightDebounceConfigIdle,
-                    resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds,
-                    screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
-                    screenBrightnessThresholdsIdle, context, brightnessModeController,
-                    brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux,
-                    userNits);
-        }
-
-        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
-                DisplayDeviceConfig displayDeviceConfig,
-                DisplayWhiteBalanceController displayWhiteBalanceController) {
-            return BrightnessMappingStrategy.create(context, displayDeviceConfig,
-                    AUTO_BRIGHTNESS_MODE_DEFAULT, displayWhiteBalanceController);
-        }
-
-        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
-                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
-                float[] darkeningThresholdLevels, float minDarkeningThreshold,
-                float minBrighteningThreshold) {
-            return new HysteresisLevels(brighteningThresholdsPercentages,
-                    darkeningThresholdsPercentages, brighteningThresholdLevels,
-                    darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold);
-        }
-
-        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
-                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
-                float[] darkeningThresholdLevels, float minDarkeningThreshold,
-                float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
-            return new HysteresisLevels(brighteningThresholdsPercentages,
-                    darkeningThresholdsPercentages, brighteningThresholdLevels,
-                    darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold,
-                    potentialOldBrightnessRange);
-        }
-
-        ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
-                SensorManager sensorManager,
-                Sensor lightSensor,
-                Handler handler,
-                ScreenOffBrightnessSensorController.Clock clock,
-                int[] sensorValueToLux,
-                BrightnessMappingStrategy brightnessMapper) {
-            return new ScreenOffBrightnessSensorController(
-                    sensorManager,
-                    lightSensor,
-                    handler,
-                    clock,
-                    sensorValueToLux,
-                    brightnessMapper
-            );
-        }
-
-        HighBrightnessModeController getHighBrightnessModeController(Handler handler, int width,
-                int height, IBinder displayToken, String displayUniqueId, float brightnessMin,
-                float brightnessMax, DisplayDeviceConfig.HighBrightnessModeData hbmData,
-                HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg,
-                Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata,
-                Context context) {
-            return new HighBrightnessModeController(handler, width, height, displayToken,
-                    displayUniqueId, brightnessMin, brightnessMax, hbmData, hdrBrightnessCfg,
-                    hbmChangeCallback, hbmMetadata, context);
-        }
-
-        BrightnessRangeController getBrightnessRangeController(
-                HighBrightnessModeController hbmController, Runnable modeChangeCallback,
-                DisplayDeviceConfig displayDeviceConfig, Handler handler,
-                DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
-            return new BrightnessRangeController(hbmController,
-                    modeChangeCallback, displayDeviceConfig, handler, flags, displayToken, info);
-        }
-
-        BrightnessClamperController getBrightnessClamperController(Handler handler,
-                BrightnessClamperController.ClamperChangeListener clamperChangeListener,
-                BrightnessClamperController.DisplayDeviceData data, Context context,
-                DisplayManagerFlags flags) {
-
-            return new BrightnessClamperController(handler, clamperChangeListener, data, context,
-                    flags);
-        }
-
-        DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
-                SensorManager sensorManager, Resources resources) {
-            return DisplayWhiteBalanceFactory.create(handler,
-                    sensorManager, resources);
-        }
-
-        boolean isColorFadeEnabled() {
-            return !ActivityManager.isLowRamDeviceStatic();
-        }
-    }
-
-    static class CachedBrightnessInfo {
-        public MutableFloat brightness = new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        public MutableFloat adjustedBrightness =
-                new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        public MutableFloat brightnessMin =
-                new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        public MutableFloat brightnessMax =
-                new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        public MutableInt hbmMode = new MutableInt(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
-        public MutableFloat hbmTransitionPoint =
-                new MutableFloat(HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID);
-        public MutableInt brightnessMaxReason =
-                new MutableInt(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
-
-        public boolean checkAndSetFloat(MutableFloat mf, float f) {
-            if (mf.value != f) {
-                mf.value = f;
-                return true;
-            }
-            return false;
-        }
-
-        public boolean checkAndSetInt(MutableInt mi, int i) {
-            if (mi.value != i) {
-                mi.value = i;
-                return true;
-            }
-            return false;
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
index 544f490..e0bdda5 100644
--- a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
+++ b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
@@ -165,8 +165,15 @@
      */
     public float[] getLuxArray(@AutomaticBrightnessController.AutomaticBrightnessMode int mode,
             int preset) {
-        return mBrightnessLevelsLuxMap.get(
+        float[] luxArray = mBrightnessLevelsLuxMap.get(
                 autoBrightnessModeToString(mode) + "_" + autoBrightnessPresetToString(preset));
+        if (luxArray != null) {
+            return luxArray;
+        }
+
+        // No array for this preset, fall back to the normal preset
+        return mBrightnessLevelsLuxMap.get(autoBrightnessModeToString(mode) + "_"
+                + AutoBrightnessSettingName.normal.getRawName());
     }
 
     /**
@@ -184,8 +191,15 @@
      */
     public float[] getBrightnessArray(
             @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset) {
-        return mBrightnessLevelsMap.get(
+        float[] brightnessArray = mBrightnessLevelsMap.get(
                 autoBrightnessModeToString(mode) + "_" + autoBrightnessPresetToString(preset));
+        if (brightnessArray != null) {
+            return brightnessArray;
+        }
+
+        // No array for this preset, fall back to the normal preset
+        return mBrightnessLevelsMap.get(autoBrightnessModeToString(mode) + "_"
+                + AutoBrightnessSettingName.normal.getRawName());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
index 465584c..403dfbe 100644
--- a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
+++ b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
@@ -41,13 +41,6 @@
         mDeviceConfig = deviceConfig;
     }
 
-    // feature: revamping_display_power_controller_feature
-    // parameter: use_newly_structured_display_power_controller
-    public boolean isNewPowerControllerFeatureEnabled() {
-        return mDeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                DisplayManager.DeviceConfig.KEY_NEW_POWER_CONTROLLER, true);
-    }
-
     // feature: hdr_output_control
     // parameter: enable_hdr_output_control
     public boolean isHdrOutputControlFeatureEnabled() {
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index be48eb4..1ae2559 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -100,6 +100,10 @@
             Flags.FLAG_ENABLE_VSYNC_LOW_POWER_VOTE,
             Flags::enableVsyncLowPowerVote);
 
+    private final FlagState mVsyncLowLightVote = new FlagState(
+            Flags.FLAG_ENABLE_VSYNC_LOW_LIGHT_VOTE,
+            Flags::enableVsyncLowLightVote);
+
     private final FlagState mBrightnessWearBedtimeModeClamperFlagState = new FlagState(
             Flags.FLAG_BRIGHTNESS_WEAR_BEDTIME_MODE_CLAMPER,
             Flags::brightnessWearBedtimeModeClamper);
@@ -220,6 +224,10 @@
         return mVsyncLowPowerVote.isEnabled();
     }
 
+    public boolean isVsyncLowLightVoteEnabled() {
+        return mVsyncLowLightVote.isEnabled();
+    }
+
     public boolean isBrightnessWearBedtimeModeClamperEnabled() {
         return mBrightnessWearBedtimeModeClamperFlagState.isEnabled();
     }
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index c9569cb..c2f52b5 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -109,7 +109,7 @@
     name: "back_up_smooth_display_and_force_peak_refresh_rate"
     namespace: "display_manager"
     description: "Feature flag for backing up Smooth Display and Force Peak Refresh Rate"
-    bug: "211737588"
+    bug: "299552529"
     is_fixed_read_only: true
 }
 
@@ -125,7 +125,7 @@
     name: "brightness_int_range_user_perception"
     namespace: "display_manager"
     description: "Feature flag for converting the brightness integer range to the user perception scale"
-    bug: "183655602"
+    bug: "319236956"
     is_fixed_read_only: true
 }
 
@@ -146,6 +146,14 @@
 }
 
 flag {
+    name: "enable_vsync_low_light_vote"
+    namespace: "display_manager"
+    description: "Feature flag for vsync low light vote"
+    bug: "314921657"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "brightness_wear_bedtime_mode_clamper"
     namespace: "display_manager"
     description: "Feature flag for the Wear Bedtime mode brightness clamper"
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 50e9533..8b4e1ff 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -22,7 +22,6 @@
 import static android.view.Display.Mode.INVALID_MODE_ID;
 
 import static com.android.server.display.DisplayDeviceConfig.DEFAULT_LOW_REFRESH_RATE;
-import static com.android.internal.display.RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay;
 
 import android.annotation.IntegerRes;
 import android.annotation.NonNull;
@@ -216,7 +215,8 @@
         mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
         mSettingsObserver = new SettingsObserver(context, handler, mDvrrSupported,
                 displayManagerFlags);
-        mBrightnessObserver = new BrightnessObserver(context, handler, injector);
+        mBrightnessObserver = new BrightnessObserver(context, handler, injector, mDvrrSupported,
+                displayManagerFlags);
         mDefaultDisplayDeviceConfig = null;
         mUdfpsObserver = new UdfpsObserver();
         mVotesStorage = new VotesStorage(this::notifyDesiredDisplayModeSpecsChangedLocked,
@@ -238,8 +238,11 @@
      * is ready.
      */
     public void start(SensorManager sensorManager) {
-        mSettingsObserver.observe();
+        // This has to be called first to read the supported display modes that will be used by
+        // other observers
         mDisplayObserver.observe();
+
+        mSettingsObserver.observe();
         mBrightnessObserver.observe(sensorManager);
         mSensorObserver.observe();
         mHbmObserver.observe();
@@ -385,6 +388,16 @@
             boolean allowGroupSwitching =
                     mModeSwitchingType == DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS;
 
+            // Some external displays physical refresh rate modes are slightly above 60hz.
+            // SurfaceFlinger will not enable these display modes unless it is configured to allow
+            // render rate at least at this frame rate.
+            if (mDisplayObserver.isExternalDisplayLocked(displayId)) {
+                primarySummary.maxRenderFrameRate = Math.max(baseMode.getRefreshRate(),
+                        primarySummary.maxRenderFrameRate);
+                appRequestSummary.maxRenderFrameRate = Math.max(baseMode.getRefreshRate(),
+                        appRequestSummary.maxRenderFrameRate);
+            }
+
             return new DesiredDisplayModeSpecs(baseMode.getModeId(),
                     allowGroupSwitching,
                     new RefreshRateRanges(
@@ -620,11 +633,16 @@
     }
 
     @VisibleForTesting
+    DisplayObserver getDisplayObserver() {
+        return mDisplayObserver;
+    }
+
+    @VisibleForTesting
     DesiredDisplayModeSpecs getDesiredDisplayModeSpecsWithInjectedFpsSettings(
             float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) {
         synchronized (mLock) {
-            mSettingsObserver.updateRefreshRateSettingLocked(
-                    minRefreshRate, peakRefreshRate, defaultRefreshRate);
+            mSettingsObserver.updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate,
+                    defaultRefreshRate, Display.DEFAULT_DISPLAY);
             return getDesiredDisplayModeSpecs(Display.DEFAULT_DISPLAY);
         }
     }
@@ -897,19 +915,17 @@
                 if (defaultPeakRefreshRate == null) {
                     setDefaultPeakRefreshRate(mDefaultDisplayDeviceConfig,
                             /* attemptReadFromFeatureParams= */ false);
-                    updateRefreshRateSettingLocked();
                 } else if (mDefaultPeakRefreshRate != defaultPeakRefreshRate) {
                     mDefaultPeakRefreshRate = defaultPeakRefreshRate;
-                    updateRefreshRateSettingLocked();
                 }
+                updateRefreshRateSettingLocked();
             }
         }
 
         @Override
         public void onChange(boolean selfChange, Uri uri, int userId) {
             synchronized (mLock) {
-                if (mPeakRefreshRateSetting.equals(uri)
-                        || mMinRefreshRateSetting.equals(uri)) {
+                if (mPeakRefreshRateSetting.equals(uri) || mMinRefreshRateSetting.equals(uri)) {
                     updateRefreshRateSettingLocked();
                 } else if (mLowPowerModeSetting.equals(uri)) {
                     updateLowPowerModeSettingLocked();
@@ -969,9 +985,29 @@
             mBrightnessObserver.onLowPowerModeEnabledLocked(inLowPowerMode);
         }
 
+        /**
+         * Update refresh rate settings for all displays
+         */
+        @GuardedBy("mLock")
         private void updateRefreshRateSettingLocked() {
+            for (int i = 0; i < mSupportedModesByDisplay.size(); i++) {
+                updateRefreshRateSettingLocked(mSupportedModesByDisplay.keyAt(i));
+            }
+        }
+
+        /**
+         * Update refresh rate settings for a specific display
+         * @param displayId The display ID
+         */
+        @GuardedBy("mLock")
+        private void updateRefreshRateSettingLocked(int displayId) {
             final ContentResolver cr = mContext.getContentResolver();
-            float highestRefreshRate = findHighestRefreshRateForDefaultDisplay(mContext);
+            if (!mSupportedModesByDisplay.contains(displayId)) {
+                Slog.e(TAG, "Cannot update refresh rate setting: no supported modes for display "
+                        + displayId);
+                return;
+            }
+            float highestRefreshRate = getMaxRefreshRateLocked(displayId);
 
             float minRefreshRate = Settings.System.getFloatForUser(cr,
                     Settings.System.MIN_REFRESH_RATE, 0f, cr.getUserId());
@@ -1009,11 +1045,13 @@
                         Float.POSITIVE_INFINITY, cr.getUserId());
             }
 
-            updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate);
+            updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate,
+                    displayId);
         }
 
-        private void updateRefreshRateSettingLocked(
-                float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) {
+        @GuardedBy("mLock")
+        private void updateRefreshRateSettingLocked(float minRefreshRate, float peakRefreshRate,
+                float defaultRefreshRate, int displayId) {
             // TODO(b/156304339): The logic in here, aside from updating the refresh rate votes, is
             // used to predict if we're going to be doing frequent refresh rate switching, and if
             // so, enable the brightness observer. The logic here is more complicated and fragile
@@ -1021,9 +1059,9 @@
             Vote peakVote = peakRefreshRate == 0f
                     ? null
                     : Vote.forRenderFrameRates(0f, Math.max(minRefreshRate, peakRefreshRate));
-            mVotesStorage.updateGlobalVote(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
                     peakVote);
-            mVotesStorage.updateGlobalVote(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
                     Vote.forRenderFrameRates(minRefreshRate, Float.POSITIVE_INFINITY));
             Vote defaultVote =
                     defaultRefreshRate == 0f
@@ -1050,6 +1088,14 @@
             mBrightnessObserver.onRefreshRateSettingChangedLocked(minRefreshRate, maxRefreshRate);
         }
 
+        private void removeRefreshRateSetting(int displayId) {
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
+                    null);
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+                    null);
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE, null);
+        }
+
         private void updateModeSwitchingTypeSettingLocked() {
             final ContentResolver cr = mContext.getContentResolver();
             int switchingType = Settings.Secure.getIntForUser(
@@ -1180,7 +1226,8 @@
         }
     }
 
-    private final class DisplayObserver implements DisplayManager.DisplayListener {
+    @VisibleForTesting
+    public final class DisplayObserver implements DisplayManager.DisplayListener {
         // Note that we can never call into DisplayManager or any of the non-POD classes it
         // returns, while holding mLock since it may call into DMS, which might be simultaneously
         // calling into us already holding its own lock.
@@ -1227,11 +1274,10 @@
             // Populate existing displays
             SparseArray<Display.Mode[]> modes = new SparseArray<>();
             SparseArray<Display.Mode> defaultModes = new SparseArray<>();
-            DisplayInfo info = new DisplayInfo();
             Display[] displays = mInjector.getDisplays();
             for (Display d : displays) {
                 final int displayId = d.getDisplayId();
-                d.getDisplayInfo(info);
+                DisplayInfo info = getDisplayInfo(displayId);
                 modes.put(displayId, info.supportedModes);
                 defaultModes.put(displayId, info.getDefaultMode());
             }
@@ -1259,6 +1305,7 @@
             synchronized (mLock) {
                 mSupportedModesByDisplay.remove(displayId);
                 mDefaultModeByDisplay.remove(displayId);
+                mSettingsObserver.removeRefreshRateSetting(displayId);
             }
             updateLayoutLimitedFrameRate(displayId, null);
             removeUserSettingDisplayPreferredSize(displayId);
@@ -1274,6 +1321,10 @@
             updateUserSettingDisplayPreferredSize(displayInfo);
         }
 
+        boolean isExternalDisplayLocked(int displayId) {
+            return mExternalDisplaysConnected.contains(displayId);
+        }
+
         @Nullable
         private DisplayInfo getDisplayInfo(int displayId) {
             DisplayInfo info = new DisplayInfo();
@@ -1382,7 +1433,7 @@
                 return;
             }
             synchronized (mLock) {
-                if (!mExternalDisplaysConnected.contains(displayId)) {
+                if (!isExternalDisplayLocked(displayId)) {
                     return;
                 }
                 mExternalDisplaysConnected.remove(displayId);
@@ -1409,6 +1460,7 @@
                 }
                 if (changed) {
                     notifyDesiredDisplayModeSpecsChangedLocked();
+                    mSettingsObserver.updateRefreshRateSettingLocked(displayId);
                 }
             }
         }
@@ -1473,6 +1525,8 @@
         private final Injector mInjector;
         private final Handler mHandler;
 
+        private final boolean mVsyncLowLightBlockingVoteEnabled;
+
         private final IThermalEventListener.Stub mThermalListener =
                 new IThermalEventListener.Stub() {
                     @Override
@@ -1507,7 +1561,8 @@
         @GuardedBy("mLock")
         private @Temperature.ThrottlingStatus int mThermalStatus = Temperature.THROTTLING_NONE;
 
-        BrightnessObserver(Context context, Handler handler, Injector injector) {
+        BrightnessObserver(Context context, Handler handler, Injector injector,
+                boolean dvrrSupported , DisplayManagerFlags flags) {
             mContext = context;
             mHandler = handler;
             mInjector = injector;
@@ -1515,6 +1570,7 @@
                 /* attemptReadFromFeatureParams= */ false);
             mRefreshRateInHighZone = context.getResources().getInteger(
                     R.integer.config_fixedRefreshRateInHighZone);
+            mVsyncLowLightBlockingVoteEnabled = dvrrSupported && flags.isVsyncLowLightVoteEnabled();
         }
 
         /**
@@ -2094,7 +2150,17 @@
                                 Vote.forPhysicalRefreshRates(range.min, range.max);
                     }
                 }
-                refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching();
+
+                if (mVsyncLowLightBlockingVoteEnabled) {
+                    refreshRateSwitchingVote = Vote.forSupportedModesAndDisableRefreshRateSwitching(
+                            List.of(
+                                    new SupportedModesVote.SupportedMode(
+                                            /* peakRefreshRate= */ 60f, /* vsyncRate= */ 60f),
+                                    new SupportedModesVote.SupportedMode(
+                                            /* peakRefreshRate= */120f, /* vsyncRate= */ 120f)));
+                } else {
+                    refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching();
+                }
             }
 
             boolean insideHighZone = hasValidHighZone()
diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java
index 8f39570..e8d5a19 100644
--- a/services/core/java/com/android/server/display/mode/Vote.java
+++ b/services/core/java/com/android/server/display/mode/Vote.java
@@ -170,6 +170,13 @@
         return new SupportedModesVote(supportedModes);
     }
 
+
+    static Vote forSupportedModesAndDisableRefreshRateSwitching(
+            List<SupportedModesVote.SupportedMode> supportedModes) {
+        return new CombinedVote(
+                List.of(forDisableRefreshRateSwitching(), forSupportedModes(supportedModes)));
+    }
+
     static String priorityToString(int priority) {
         switch (priority) {
             case PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE:
diff --git a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
index a30c4d2..e80b9451 100644
--- a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
+++ b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
@@ -16,12 +16,19 @@
 
 package com.android.server.display.mode;
 
+import static com.android.internal.util.FrameworkStatsLog.DISPLAY_MODE_DIRECTOR_VOTE_CHANGED;
+import static com.android.internal.util.FrameworkStatsLog.DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ACTIVE;
+import static com.android.internal.util.FrameworkStatsLog.DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ADDED;
+import static com.android.internal.util.FrameworkStatsLog.DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_REMOVED;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Trace;
 import android.util.SparseArray;
 import android.view.Display;
 
+import com.android.internal.util.FrameworkStatsLog;
+
 /**
  * The VotesStatsReporter is responsible for collecting and sending Vote related statistics 
  */
@@ -31,42 +38,77 @@
     private final boolean mIgnoredRenderRate;
     private final boolean mFrameworkStatsLogReportingEnabled;
 
+    private int mLastMinPriorityReported = Vote.MAX_PRIORITY + 1;
+
     public VotesStatsReporter(boolean ignoreRenderRate, boolean refreshRateVotingTelemetryEnabled) {
         mIgnoredRenderRate = ignoreRenderRate;
         mFrameworkStatsLogReportingEnabled = refreshRateVotingTelemetryEnabled;
     }
 
-    void reportVoteAdded(int displayId, int priority,  @NonNull Vote vote) {
+    void reportVoteChanged(int displayId, int priority,  @Nullable Vote vote) {
+        if (vote == null) {
+            reportVoteRemoved(displayId, priority);
+        } else {
+            reportVoteAdded(displayId, priority, vote);
+        }
+    }
+
+    private void reportVoteAdded(int displayId, int priority,  @NonNull Vote vote) {
         int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
         Trace.traceCounter(Trace.TRACE_TAG_POWER,
                 TAG + "." + displayId + ":" + Vote.priorityToString(priority), maxRefreshRate);
-        // if ( mFrameworkStatsLogReportingEnabled) {
-        // FrameworkStatsLog.write(VOTE_CHANGED, displayID, priority, ADDED, maxRefreshRate, -1);
-        // }
+        if (mFrameworkStatsLogReportingEnabled) {
+            FrameworkStatsLog.write(
+                    DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
+                    DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ADDED,
+                    maxRefreshRate, -1);
+        }
     }
 
-    void reportVoteRemoved(int displayId, int priority) {
+    private void reportVoteRemoved(int displayId, int priority) {
         Trace.traceCounter(Trace.TRACE_TAG_POWER,
                 TAG + "." + displayId + ":" + Vote.priorityToString(priority), -1);
-        // if ( mFrameworkStatsLogReportingEnabled) {
-        // FrameworkStatsLog.write(VOTE_CHANGED, displayID, priority, REMOVED, -1, -1);
-        // }
+        if (mFrameworkStatsLogReportingEnabled) {
+            FrameworkStatsLog.write(
+                    DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
+                    DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_REMOVED, -1, -1);
+        }
     }
 
     void reportVotesActivated(int displayId, int minPriority, @Nullable Display.Mode baseMode,
             SparseArray<Vote> votes) {
-//        if (!mFrameworkStatsLogReportingEnabled) {
-//            return;
-//        }
-//        int selectedRefreshRate = baseMode != null ? (int) baseMode.getRefreshRate() : -1;
-//        for (int priority = minPriority; priority <= Vote.MAX_PRIORITY; priority ++) {
-//            Vote vote = votes.get(priority);
-//            if (vote != null) {
-//                int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
-//                FrameworkStatsLog.write(VOTE_CHANGED, displayId, priority,
-//                        ACTIVE, maxRefreshRate, selectedRefreshRate);
-//            }
-//        }
+        if (!mFrameworkStatsLogReportingEnabled) {
+            return;
+        }
+        int selectedRefreshRate = baseMode != null ? (int) baseMode.getRefreshRate() : -1;
+        for (int priority = Vote.MIN_PRIORITY; priority <= Vote.MAX_PRIORITY; priority++) {
+            if (priority < mLastMinPriorityReported && priority < minPriority) {
+                continue;
+            }
+            Vote vote = votes.get(priority);
+            if (vote == null) {
+                continue;
+            }
+
+            // Was previously reported ACTIVE, changed to ADDED
+            if (priority >= mLastMinPriorityReported && priority < minPriority) {
+                int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
+                FrameworkStatsLog.write(
+                        DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
+                        DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ADDED,
+                        maxRefreshRate, selectedRefreshRate);
+            }
+            // Was previously reported ADDED, changed to ACTIVE
+            if (priority >= minPriority && priority < mLastMinPriorityReported) {
+                int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
+                FrameworkStatsLog.write(
+                        DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority,
+                        DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ACTIVE,
+                        maxRefreshRate, selectedRefreshRate);
+            }
+
+            mLastMinPriorityReported = minPriority;
+        }
     }
 
     private static int getMaxRefreshRate(@NonNull Vote vote, boolean ignoreRenderRate) {
diff --git a/services/core/java/com/android/server/display/mode/VotesStorage.java b/services/core/java/com/android/server/display/mode/VotesStorage.java
index 7a1f7e9..56c7c18 100644
--- a/services/core/java/com/android/server/display/mode/VotesStorage.java
+++ b/services/core/java/com/android/server/display/mode/VotesStorage.java
@@ -117,22 +117,13 @@
             Slog.i(TAG, "Updated votes for display=" + displayId + " votes=" + votes);
         }
         if (changed) {
-            reportVoteStats(displayId, priority, vote);
+            if (mVotesStatsReporter != null) {
+                mVotesStatsReporter.reportVoteChanged(displayId, priority, vote);
+            }
             mListener.onChanged();
         }
     }
 
-    private void reportVoteStats(int displayId, int priority, @Nullable Vote vote) {
-        if (mVotesStatsReporter == null) {
-            return;
-        }
-        if (vote == null) {
-            mVotesStatsReporter.reportVoteRemoved(displayId, priority);
-        } else {
-            mVotesStatsReporter.reportVoteAdded(displayId, priority, vote);
-        }
-    }
-
     /** dump class values, for debugging */
     void dump(@NonNull PrintWriter pw) {
         SparseArray<SparseArray<Vote>> votesByDisplayLocal = new SparseArray<>();
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
index 1f59b57..c260f10 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
@@ -17,6 +17,7 @@
 package com.android.server.grammaticalinflection;
 
 import android.annotation.Nullable;
+import android.content.res.Configuration;
 
 /**
  * System-server internal interface to the {@link android.app.GrammaticalInflectionManager}.
@@ -37,5 +38,14 @@
      * at the time this is called, to be referenced later when the app is installed.
      */
     public abstract void stageAndApplyRestoredPayload(byte[] payload, int userId);
+
+    /**
+     * Get the current system grammatical gender of privileged application.
+     *
+     * @return the value of grammatical gender
+     *
+     * @see Configuration#getGrammaticalGender
+     */
+    public abstract @Configuration.GrammaticalGender int getSystemGrammaticalGender(int userId);
 }
 
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 68848a2..6eb7e95 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -19,9 +19,12 @@
 import static android.app.Flags.systemTermsOfAddressEnabled;
 import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
 
+import static com.android.server.grammaticalinflection.GrammaticalInflectionUtils.checkSystemGrammaticalGenderPermission;
+
 import android.annotation.Nullable;
 import android.app.GrammaticalInflectionManager;
 import android.app.IGrammaticalInflectionManager;
+import android.content.AttributionSource;
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
@@ -30,6 +33,7 @@
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.SystemProperties;
+import android.permission.PermissionManager;
 import android.util.AtomicFile;
 import android.util.Log;
 import android.util.SparseIntArray;
@@ -75,6 +79,8 @@
 
     private PackageManagerInternal mPackageManagerInternal;
     private GrammaticalInflectionService.GrammaticalInflectionBinderService mBinderService;
+    private PermissionManager mPermissionManager;
+    private Context mContext;
 
     /**
      * Initializes the system service.
@@ -88,11 +94,12 @@
      */
     public GrammaticalInflectionService(Context context) {
         super(context);
+        mContext = context;
         mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
-        mBackupHelper = new GrammaticalInflectionBackupHelper(
-                this, context.getPackageManager());
+        mBackupHelper = new GrammaticalInflectionBackupHelper(this, context.getPackageManager());
         mBinderService = new GrammaticalInflectionBinderService();
+        mPermissionManager = context.getSystemService(PermissionManager.class);
     }
 
     @Override
@@ -112,7 +119,7 @@
         }
 
         @Override
-        public void setSystemWideGrammaticalGender(int userId, int grammaticalGender) {
+        public void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
             checkCallerIsSystem();
             checkSystemTermsOfAddressIsEnabled();
             GrammaticalInflectionService.this.setSystemWideGrammaticalGender(grammaticalGender,
@@ -120,16 +127,17 @@
         }
 
         @Override
-        public int getSystemGrammaticalGender(int userId) {
+        public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
             checkSystemTermsOfAddressIsEnabled();
-            return GrammaticalInflectionService.this.getSystemGrammaticalGender(userId);
+            return GrammaticalInflectionService.this.getSystemGrammaticalGender(attributionSource,
+                    userId);
         }
 
         @Override
         public void onShellCommand(FileDescriptor in, FileDescriptor out,
                 FileDescriptor err, String[] args, ShellCallback callback,
                 ResultReceiver resultReceiver) {
-            (new GrammaticalInflectionShellCommand(mBinderService))
+            (new GrammaticalInflectionShellCommand(mBinderService, mContext.getAttributionSource()))
                     .exec(this, in, out, err, args, callback, resultReceiver);
         }
     };
@@ -148,6 +156,13 @@
         public void stageAndApplyRestoredPayload(byte[] payload, int userId) {
             mBackupHelper.stageAndApplyRestoredPayload(payload, userId);
         }
+
+        @Override
+        public int getSystemGrammaticalGender(int userId) {
+            checkCallerIsSystem();
+            return GrammaticalInflectionService.this.getSystemGrammaticalGender(
+                    mContext.getAttributionSource(), userId);
+        }
     }
 
     protected int getApplicationGrammaticalGender(String appPackageName, int userId) {
@@ -211,9 +226,24 @@
         }
     }
 
-    // TODO(b/298591009): Add a new AppOp value for the apps that want to access the grammatical
-    //  gender.
-    public int getSystemGrammaticalGender(int userId) {
+    public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
+        String packageName = attributionSource.getPackageName();
+        if (packageName == null) {
+            Log.d(TAG, "Package name is null.");
+            return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+        }
+
+        int callingUid = Binder.getCallingUid();
+        if (mPackageManagerInternal.getPackageUid(packageName, 0, userId) != callingUid) {
+            Log.d(TAG,
+                    "Package " + packageName + " does not belong to the calling uid " + callingUid);
+            return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+        }
+
+        if (!checkSystemGrammaticalGenderPermission(mPermissionManager, attributionSource)) {
+            return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+        }
+
         synchronized (mLock) {
             final File file = getGrammaticalGenderFile(userId);
             if (!file.exists()) {
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
index d223728..cdda692 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
@@ -21,6 +21,7 @@
 import android.app.ActivityManager;
 import android.app.GrammaticalInflectionManager;
 import android.app.IGrammaticalInflectionManager;
+import android.content.AttributionSource;
 import android.content.res.Configuration;
 import android.os.RemoteException;
 import android.os.ShellCommand;
@@ -44,9 +45,12 @@
     }
 
     private final IGrammaticalInflectionManager mBinderService;
+    private AttributionSource mAttributionSource;
 
-    GrammaticalInflectionShellCommand(IGrammaticalInflectionManager grammaticalInflectionManager) {
+    GrammaticalInflectionShellCommand(IGrammaticalInflectionManager grammaticalInflectionManager,
+            AttributionSource attributionSource) {
         mBinderService = grammaticalInflectionManager;
+        mAttributionSource = attributionSource;
     }
 
     @Override
@@ -115,7 +119,7 @@
         } while (true);
 
         try {
-            mBinderService.setSystemWideGrammaticalGender(userId, grammaticalGender);
+            mBinderService.setSystemWideGrammaticalGender(grammaticalGender, userId);
         } catch (RemoteException e) {
             getOutPrintWriter().println("Remote Exception: " + e);
         }
@@ -141,7 +145,8 @@
         } while (true);
 
         try {
-            int grammaticalGender = mBinderService.getSystemGrammaticalGender(userId);
+            int grammaticalGender = mBinderService.getSystemGrammaticalGender(mAttributionSource,
+                    userId);
             getOutPrintWriter().println(GRAMMATICAL_GENDER_MAP.get(grammaticalGender));
         } catch (RemoteException e) {
             getOutPrintWriter().println("Remote Exception: " + e);
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionUtils.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionUtils.java
new file mode 100644
index 0000000..f056561
--- /dev/null
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionUtils.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.grammaticalinflection;
+
+import static android.Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER;
+
+import android.annotation.NonNull;
+import android.content.AttributionSource;
+import android.permission.PermissionManager;
+import android.util.Log;
+
+/**
+ * Utility methods for system grammatical gender.
+ */
+public class GrammaticalInflectionUtils {
+
+    private static final String TAG = "GrammaticalInflectionUtils";
+
+    public static boolean checkSystemGrammaticalGenderPermission(
+            @NonNull PermissionManager permissionManager,
+            @NonNull AttributionSource attributionSource) {
+        int permissionCheckResult = permissionManager.checkPermissionForDataDelivery(
+                READ_SYSTEM_GRAMMATICAL_GENDER,
+                attributionSource, /* message= */ null);
+        if (permissionCheckResult != PermissionManager.PERMISSION_GRANTED) {
+            Log.v(TAG, "AttributionSource: " + attributionSource
+                    + " does not have READ_SYSTEM_GRAMMATICAL_GENDER permission.");
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index e827866..10c0186 100755
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -140,7 +140,13 @@
                     wrapUpAndFinish();
                     return;
                 }
-
+                // Check if the action was finished before the callback was called.
+                // See {@link HdmiCecFeatureAction#finish}.
+                if (mState == STATE_NONE) {
+                    Slog.v(TAG, "Action was already finished before the callback was called.");
+                    wrapUpAndFinish();
+                    return;
+                }
                 Slog.v(TAG, "Device detected: " + ackedAddress);
                 allocateDevices(ackedAddress);
                 if (mDelayPeriod > 0) {
@@ -453,7 +459,6 @@
             wrapUpAndFinish();
             return;
         }
-
         // If finished current stage, move on to next stage.
         if (mProcessedDeviceCount == mDevices.size()) {
             mProcessedDeviceCount = 0;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 9087354..a79fb88 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -160,6 +160,9 @@
     // This variable is used for testing, in order to delay the logical address allocation.
     private long mLogicalAddressAllocationDelay = 0;
 
+    // This variable is used for testing, in order to delay polling devices.
+    private long mPollDevicesDelay = 0;
+
     // Private constructor.  Use HdmiCecController.create().
     private HdmiCecController(
             HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter) {
@@ -463,6 +466,14 @@
     }
 
     /**
+     * This method is used for testing, in order to delay polling devices.
+     */
+    @VisibleForTesting
+    void setPollDevicesDelay(long delay) {
+        mPollDevicesDelay = delay;
+    }
+
+    /**
      * Returns true if the language code is well-formed.
      */
     @VisibleForTesting static boolean isLanguage(String language) {
@@ -523,7 +534,10 @@
         // Extract polling candidates. No need to poll against local devices.
         List<Integer> pollingCandidates = pickPollCandidates(pickStrategy);
         ArrayList<Integer> allocated = new ArrayList<>();
-        runDevicePolling(sourceAddress, pollingCandidates, retryCount, callback, allocated);
+        mControlHandler.postDelayed(
+                () -> runDevicePolling(
+                        sourceAddress, pollingCandidates, retryCount, callback, allocated),
+                mPollDevicesDelay);
     }
 
     private List<Integer> pickPollCandidates(int pickStrategy) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index ba4d320..731c78e 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -514,6 +514,18 @@
     protected int handleRoutingInformation(HdmiCecMessage message) {
         assertRunOnServiceThread();
         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
+        HdmiDeviceInfo sourceDevice = mService.getHdmiCecNetwork()
+                .getCecDeviceInfo(message.getSource());
+        // Ignore <Routing Information> messages pointing to the same physical address as the
+        // message sender. In this case, we shouldn't consider the sender to be the active source.
+        // See more b/321771821#comment7.
+        if (sourceDevice != null
+                && sourceDevice.getLogicalAddress() != Constants.ADDR_TV
+                && sourceDevice.getPhysicalAddress() == physicalAddress) {
+            Slog.d(TAG, "<Routing Information> is ignored, it is pointing to the same physical"
+                    + " address as the message sender");
+            return Constants.HANDLED;
+        }
         handleRoutingChangeAndInformation(physicalAddress, message);
         return Constants.HANDLED;
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 1cd267d..46061a5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -47,6 +47,7 @@
 import android.media.AudioProfile;
 import android.media.tv.TvInputInfo;
 import android.media.tv.TvInputManager.TvInputCallback;
+import android.os.Handler;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
 
@@ -97,9 +98,15 @@
     private boolean mSystemAudioMute = false;
 
     // If true, do not do routing control/send active source for internal source.
-    // Set to true when the device was woken up by <Text/Image View On>.
+    // Set to true for a short duration when the device is woken up by <Text/Image View On>.
     private boolean mSkipRoutingControl;
 
+    // Handler for posting a runnable to set `mSkipRoutingControl` to false after a delay
+    private final Handler mSkipRoutingControlHandler;
+
+    // Runnable that sets `mSkipRoutingControl` to false
+    private final Runnable mResetSkipRoutingControlRunnable = () -> mSkipRoutingControl = false;
+
     // Message buffer used to buffer selected messages to process later. <Active Source>
     // from a source device, for instance, needs to be buffered if the device is not
     // discovered yet. The buffered commands are taken out and when they are ready to
@@ -162,6 +169,7 @@
                 HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL)
                     == HdmiControlManager.SYSTEM_AUDIO_CONTROL_ENABLED;
         mStandbyHandler = new HdmiCecStandbyModeHandler(service, this);
+        mSkipRoutingControlHandler = new Handler(service.getServiceLooper());
     }
 
     @Override
@@ -184,7 +192,14 @@
         mService.getHdmiCecNetwork().addCecSwitch(
                 mService.getHdmiCecNetwork().getPhysicalAddress());  // TV is a CEC switch too.
         mTvInputs.clear();
+
         mSkipRoutingControl = (reason == HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE);
+        mSkipRoutingControlHandler.removeCallbacks(mResetSkipRoutingControlRunnable);
+        if (mSkipRoutingControl) {
+            mSkipRoutingControlHandler.postDelayed(mResetSkipRoutingControlRunnable,
+                    HdmiConfig.TIMEOUT_MS);
+        }
+
         launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC &&
                 reason != HdmiControlService.INITIATED_BY_BOOT_UP);
         resetSelectRequestBuffer();
@@ -467,6 +482,22 @@
     @Override
     @ServiceThreadOnly
     @Constants.HandleMessageResult
+    protected int handleStandby(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+
+        // Ignore <Standby> from non-active source device.
+        if (getActiveSource().logicalAddress != message.getSource()) {
+            Slog.d(TAG, "<Standby> was not sent by the current active source, ignoring."
+                    + " Current active source has logical address "
+                    + getActiveSource().logicalAddress);
+            return Constants.HANDLED;
+        }
+        return super.handleStandby(message);
+    }
+
+    @Override
+    @ServiceThreadOnly
+    @Constants.HandleMessageResult
     protected int handleInactiveSource(HdmiCecMessage message) {
         assertRunOnServiceThread();
         // Seq #10
@@ -1290,15 +1321,19 @@
             mService.getHdmiCecNetwork().removeCecSwitches(portId);
         }
 
-        // Turning System Audio Mode off when the AVR is unlugged or standby.
-        // When the device is not unplugged but reawaken from standby, we check if the System
-        // Audio Control Feature is enabled or not then decide if turning SAM on/off accordingly.
-        if (getAvrDeviceInfo() != null && portId == getAvrDeviceInfo().getPortId()) {
-            HdmiLogger.debug("Port ID:%d, 5v=%b", portId, connected);
-            if (!connected) {
-                setSystemAudioMode(false);
-            } else {
-                onNewAvrAdded(getAvrDeviceInfo());
+        if (!mService.isEarcEnabled() || !mService.isEarcSupported()) {
+            HdmiDeviceInfo avr = getAvrDeviceInfo();
+            if (avr != null
+                    && portId == avr.getPortId()
+                    && isConnectedToArcPort(avr.getPhysicalAddress())) {
+                HdmiLogger.debug("Port ID:%d, 5v=%b", portId, connected);
+                if (connected) {
+                    if (mArcEstablished) {
+                        enableAudioReturnChannel(true);
+                    }
+                } else {
+                    enableAudioReturnChannel(false);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index eaf754d..e0e825d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -3617,7 +3617,7 @@
         }
     }
 
-    @VisibleForTesting
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     protected boolean isEarcSupported() {
         synchronized (mLock) {
             return mEarcSupported;
diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
index f3532e5..b6c0e5d 100644
--- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java
+++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
@@ -53,6 +53,7 @@
     private int mVendorId;
     private String mDisplayName;
     private int mTimeoutRetry;
+    private HdmiDeviceInfo mOldDeviceInfo;
 
     /**
      * Constructor.
@@ -73,6 +74,38 @@
 
     @Override
     public boolean start() {
+        mOldDeviceInfo =
+            localDevice().mService.getHdmiCecNetwork().getCecDeviceInfo(mDeviceLogicalAddress);
+        // If there's deviceInfo with same (logical address, physical address) set
+        // Then addCecDevice should be delayed until system information process is finished
+        if (mOldDeviceInfo != null
+                && mOldDeviceInfo.getPhysicalAddress() == mDevicePhysicalAddress) {
+            Slog.d(TAG, "Start NewDeviceAction with old deviceInfo:["
+                    + mOldDeviceInfo.toString() + "]");
+        } else {
+            // Add the device ahead with default information to handle <Active Source>
+            // promptly, rather than waiting till the new device action is finished.
+            Slog.d(TAG, "Start NewDeviceAction with default deviceInfo");
+            HdmiDeviceInfo deviceInfo = HdmiDeviceInfo.cecDeviceBuilder()
+                    .setLogicalAddress(mDeviceLogicalAddress)
+                    .setPhysicalAddress(mDevicePhysicalAddress)
+                    .setPortId(tv().getPortId(mDevicePhysicalAddress))
+                    .setDeviceType(mDeviceType)
+                    .setVendorId(Constants.VENDOR_ID_UNKNOWN)
+                    .build();
+            // If a deviceInfo with same logical address but different physical address exists
+            // We should remove the old deviceInfo first
+            // This will happen if the interval between unplugging and plugging device is too short
+            // and HotplugDetection Action fails to remove the old deviceInfo, or when the newly
+            // plugged device violates HDMI Spec and uses an occupied logical address
+            if (mOldDeviceInfo != null) {
+                Slog.d(TAG, "Remove device by NewDeviceAction, logical address conflicts: "
+                        + mDevicePhysicalAddress);
+                localDevice().mService.getHdmiCecNetwork().removeCecDevice(
+                        localDevice(), mDeviceLogicalAddress);
+            }
+            localDevice().mService.getHdmiCecNetwork().addCecDevice(deviceInfo);
+        }
         requestOsdName(true);
         return true;
     }
@@ -182,14 +215,30 @@
                 .setVendorId(mVendorId)
                 .setDisplayName(mDisplayName)
                 .build();
-        localDevice().mService.getHdmiCecNetwork().updateCecDevice(deviceInfo);
 
-        // Consume CEC messages we already got for this newly found device.
-        tv().processDelayedMessages(mDeviceLogicalAddress);
+        // Check if oldDevice is same as newDevice
+        // If so, don't add newDevice info, preventing ARC or HDMI source re-connection
+        if (mOldDeviceInfo != null
+                && mOldDeviceInfo.getLogicalAddress() == mDeviceLogicalAddress
+                && mOldDeviceInfo.getPhysicalAddress() == mDevicePhysicalAddress
+                && mOldDeviceInfo.getDeviceType() == mDeviceType
+                && mOldDeviceInfo.getVendorId() == mVendorId
+                && mOldDeviceInfo.getDisplayName().equals(mDisplayName)) {
+            // Consume CEC messages we already got for this newly found device.
+            tv().processDelayedMessages(mDeviceLogicalAddress);
+            Slog.d(TAG, "Ignore NewDevice, deviceInfo is same as current device");
+            Slog.d(TAG, "Old:[" + mOldDeviceInfo.toString()
+                    + "]; New:[" + deviceInfo.toString() + "]");
+        } else {
+            Slog.d(TAG, "Add NewDevice:[" + deviceInfo.toString() + "]");
+            localDevice().mService.getHdmiCecNetwork().addCecDevice(deviceInfo);
 
-        if (HdmiUtils.isEligibleAddressForDevice(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM,
-                mDeviceLogicalAddress)) {
-            tv().onNewAvrAdded(deviceInfo);
+            // Consume CEC messages we already got for this newly found device.
+            tv().processDelayedMessages(mDeviceLogicalAddress);
+            if (HdmiUtils.isEligibleAddressForDevice(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM,
+                        mDeviceLogicalAddress)) {
+                tv().onNewAvrAdded(deviceInfo);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
index 1153cc3..8a3a56c 100644
--- a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
+++ b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
@@ -16,6 +16,8 @@
 
 package com.android.server.health;
 
+import static android.os.Flags.batteryPartStatusApi;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.hardware.health.BatteryHealthData;
@@ -150,6 +152,18 @@
                     healthData = service.getBatteryHealthData();
                     prop.setLong(healthData.batteryStateOfHealth);
                     break;
+                case BatteryManager.BATTERY_PROPERTY_SERIAL_NUMBER:
+                    if (batteryPartStatusApi()) {
+                        healthData = service.getBatteryHealthData();
+                        prop.setString(healthData.batterySerialNumber);
+                    }
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_PART_STATUS:
+                    if (batteryPartStatusApi()) {
+                        healthData = service.getBatteryHealthData();
+                        prop.setLong(healthData.batteryPartStatus);
+                    }
+                    break;
             }
         } catch (UnsupportedOperationException e) {
             // Leave prop untouched.
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 7e990c6..b963a4b 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -87,12 +87,16 @@
      * connected, the caller may be blocked for an arbitrary period of time.
      *
      * @return true if the pointer displayId was set successfully, or false if it fails.
+     *
+     * @deprecated TODO(b/293587049): Not needed - remove after Pointer Icon Refactor is complete.
      */
     public abstract boolean setVirtualMousePointerDisplayId(int pointerDisplayId);
 
     /**
      * Gets the display id that the MouseCursorController is being forced to target. Returns
      * {@link android.view.Display#INVALID_DISPLAY} if there is no override
+     *
+     * @deprecated TODO(b/293587049): Not needed - remove after Pointer Icon Refactor is complete.
      */
     public abstract int getVirtualMousePointerDisplayId();
 
@@ -101,13 +105,16 @@
      *
      * Returns NaN-s as the coordinates if the cursor is not available.
      */
-    public abstract PointF getCursorPosition();
+    public abstract PointF getCursorPosition(int displayId);
 
     /**
-     * Sets the pointer acceleration.
-     * See {@code frameworks/native/include/input/VelocityControl.h#VelocityControlParameters}.
+     * Enables or disables pointer acceleration for mouse movements.
+     *
+     * Note that this only affects pointer movements from mice (that is, pointing devices which send
+     * relative motions, including trackballs and pointing sticks), not from other pointer devices
+     * such as touchpads and styluses.
      */
-    public abstract void setPointerAcceleration(float acceleration, int displayId);
+    public abstract void setMousePointerAccelerationEnabled(boolean enabled, int displayId);
 
     /**
      * Sets the eligibility of windows on a given display for pointer capture. If a display is
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 36dac83..687def0 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -64,7 +64,6 @@
 import android.os.Environment;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.IInputConstants;
 import android.os.IVibratorStateListener;
 import android.os.InputEventInjectionResult;
 import android.os.InputEventInjectionSync;
@@ -412,6 +411,40 @@
     private boolean mShowKeyPresses = false;
     private boolean mShowRotaryInput = false;
 
+    @GuardedBy("mLoadedPointerIconsByDisplayAndType")
+    final SparseArray<SparseArray<PointerIcon>> mLoadedPointerIconsByDisplayAndType =
+            new SparseArray<>();
+    @GuardedBy("mLoadedPointerIconsByDisplayAndType")
+    boolean mUseLargePointerIcons = false;
+
+    final DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() {
+        @Override
+        public void onDisplayAdded(int displayId) {
+
+        }
+
+        @Override
+        public void onDisplayRemoved(int displayId) {
+            synchronized (mLoadedPointerIconsByDisplayAndType) {
+                mLoadedPointerIconsByDisplayAndType.remove(displayId);
+            }
+        }
+
+        @Override
+        public void onDisplayChanged(int displayId) {
+            synchronized (mLoadedPointerIconsByDisplayAndType) {
+                // The display density could have changed, so force all cached pointer icons to be
+                // reloaded for the display.
+                var iconsByType = mLoadedPointerIconsByDisplayAndType.get(displayId);
+                if (iconsByType == null) {
+                    return;
+                }
+                iconsByType.clear();
+            }
+            mNative.reloadPointerIcons();
+        }
+    };
+
     /** Point of injection for test dependencies. */
     @VisibleForTesting
     static class Injector {
@@ -579,6 +612,10 @@
             mWiredAccessoryCallbacks.systemReady();
         }
 
+        Objects.requireNonNull(
+                mContext.getSystemService(DisplayManager.class)).registerDisplayListener(
+                mDisplayListener, mHandler);
+
         mKeyboardLayoutManager.systemRunning();
         mBatteryController.systemRunning();
         mKeyboardBacklightController.systemRunning();
@@ -1293,6 +1330,8 @@
 
         updateAdditionalDisplayInputProperties(displayId, AdditionalDisplayInputProperties::reset);
 
+        // TODO(b/320763728): Rely on WindowInfosListener to determine when a display has been
+        //  removed in InputDispatcher instead of this callback.
         mNative.displayRemoved(displayId);
     }
 
@@ -1372,9 +1411,9 @@
         mNative.setPointerSpeed(speed);
     }
 
-    private void setPointerAcceleration(float acceleration, int displayId) {
+    private void setMousePointerAccelerationEnabled(boolean enabled, int displayId) {
         updateAdditionalDisplayInputProperties(displayId,
-                properties -> properties.pointerAcceleration = acceleration);
+                properties -> properties.mousePointerAccelerationEnabled = enabled);
     }
 
     private void setPointerIconVisible(boolean visible, int displayId) {
@@ -1409,6 +1448,10 @@
     }
 
     private boolean setVirtualMousePointerDisplayIdBlocking(int overrideDisplayId) {
+        if (com.android.input.flags.Flags.enablePointerChoreographer()) {
+            throw new IllegalStateException(
+                    "This must not be used when PointerChoreographer is enabled");
+        }
         final boolean isRemovingOverride = overrideDisplayId == Display.INVALID_DISPLAY;
 
         // Take care to not make calls to window manager while holding internal locks.
@@ -1447,6 +1490,10 @@
     }
 
     private int getVirtualMousePointerDisplayId() {
+        if (com.android.input.flags.Flags.enablePointerChoreographer()) {
+            throw new IllegalStateException(
+                    "This must not be used when PointerChoreographer is enabled");
+        }
         synchronized (mAdditionalDisplayInputPropertiesLock) {
             return mOverriddenPointerDisplayId;
         }
@@ -1772,8 +1819,6 @@
             mPointerIconType = icon.getType();
             mPointerIcon = mPointerIconType == PointerIcon.TYPE_CUSTOM ? icon : null;
 
-            if (!mCurrentDisplayProperties.pointerIconVisible) return false;
-
             return mNative.setPointerIcon(icon, displayId, deviceId, pointerId, inputToken);
         }
     }
@@ -2261,7 +2306,8 @@
                             + mAdditionalDisplayInputProperties.keyAt(i));
                     final AdditionalDisplayInputProperties properties =
                             mAdditionalDisplayInputProperties.valueAt(i);
-                    pw.println("pointerAcceleration: " + properties.pointerAcceleration);
+                    pw.println("mousePointerAccelerationEnabled: "
+                            + properties.mousePointerAccelerationEnabled);
                     pw.println("pointerIconVisible: " + properties.pointerIconVisible);
                 }
                 pw.decreaseIndent();
@@ -2721,8 +2767,22 @@
 
     // Native callback.
     @SuppressWarnings("unused")
-    private PointerIcon getPointerIcon(int displayId) {
-        return PointerIcon.getDefaultIcon(getContextForPointerIcon(displayId));
+    private @NonNull PointerIcon getLoadedPointerIcon(int displayId, int type) {
+        synchronized (mLoadedPointerIconsByDisplayAndType) {
+            SparseArray<PointerIcon> iconsByType = mLoadedPointerIconsByDisplayAndType.get(
+                    displayId);
+            if (iconsByType == null) {
+                iconsByType = new SparseArray<>();
+                mLoadedPointerIconsByDisplayAndType.put(displayId, iconsByType);
+            }
+            PointerIcon icon = iconsByType.get(type);
+            if (icon == null) {
+                icon = PointerIcon.getLoadedSystemIcon(getContextForPointerIcon(displayId), type,
+                        mUseLargePointerIcons);
+                iconsByType.put(type, icon);
+            }
+            return Objects.requireNonNull(icon);
+        }
     }
 
     // Native callback.
@@ -3280,8 +3340,8 @@
         }
 
         @Override
-        public PointF getCursorPosition() {
-            final float[] p = mNative.getMouseCursorPosition();
+        public PointF getCursorPosition(int displayId) {
+            final float[] p = mNative.getMouseCursorPosition(displayId);
             if (p == null || p.length != 2) {
                 throw new IllegalStateException("Failed to get mouse cursor position");
             }
@@ -3289,8 +3349,8 @@
         }
 
         @Override
-        public void setPointerAcceleration(float acceleration, int displayId) {
-            InputManagerService.this.setPointerAcceleration(acceleration, displayId);
+        public void setMousePointerAccelerationEnabled(boolean enabled, int displayId) {
+            InputManagerService.this.setMousePointerAccelerationEnabled(enabled, displayId);
         }
 
         @Override
@@ -3382,11 +3442,15 @@
     private static class AdditionalDisplayInputProperties {
 
         static final boolean DEFAULT_POINTER_ICON_VISIBLE = true;
-        static final float DEFAULT_POINTER_ACCELERATION =
-                (float) IInputConstants.DEFAULT_POINTER_ACCELERATION;
+        static final boolean DEFAULT_MOUSE_POINTER_ACCELERATION_ENABLED = true;
 
-        // The pointer acceleration for this display.
-        public float pointerAcceleration;
+        /**
+         * Whether to enable mouse pointer acceleration on this display. Note that this only affects
+         * pointer movements from mice (that is, pointing devices which send relative motions,
+         * including trackballs and pointing sticks), not from other pointer devices such as
+         * touchpads and styluses.
+         */
+        public boolean mousePointerAccelerationEnabled;
 
         // Whether the pointer icon should be visible or hidden on this display.
         public boolean pointerIconVisible;
@@ -3396,12 +3460,12 @@
         }
 
         public boolean allDefaults() {
-            return Float.compare(pointerAcceleration, DEFAULT_POINTER_ACCELERATION) == 0
+            return mousePointerAccelerationEnabled == DEFAULT_MOUSE_POINTER_ACCELERATION_ENABLED
                     && pointerIconVisible == DEFAULT_POINTER_ICON_VISIBLE;
         }
 
         public void reset() {
-            pointerAcceleration = DEFAULT_POINTER_ACCELERATION;
+            mousePointerAccelerationEnabled = DEFAULT_MOUSE_POINTER_ACCELERATION_ENABLED;
             pointerIconVisible = DEFAULT_POINTER_ICON_VISIBLE;
         }
     }
@@ -3419,6 +3483,10 @@
     private void applyAdditionalDisplayInputPropertiesLocked(
             AdditionalDisplayInputProperties properties) {
         // Handle changes to each of the individual properties.
+        // TODO(b/293587049): This approach for updating pointer display properties is only for when
+        //  PointerChoreographer is disabled. Remove this logic when PointerChoreographer is
+        //  permanently enabled.
+
         if (properties.pointerIconVisible != mCurrentDisplayProperties.pointerIconVisible) {
             mCurrentDisplayProperties.pointerIconVisible = properties.pointerIconVisible;
             if (properties.pointerIconVisible) {
@@ -3433,9 +3501,10 @@
             }
         }
 
-        if (properties.pointerAcceleration != mCurrentDisplayProperties.pointerAcceleration) {
-            mCurrentDisplayProperties.pointerAcceleration = properties.pointerAcceleration;
-            mNative.setPointerAcceleration(properties.pointerAcceleration);
+        if (properties.mousePointerAccelerationEnabled
+                != mCurrentDisplayProperties.mousePointerAccelerationEnabled) {
+            mCurrentDisplayProperties.mousePointerAccelerationEnabled =
+                    properties.mousePointerAccelerationEnabled;
         }
     }
 
@@ -3448,7 +3517,16 @@
                 properties = new AdditionalDisplayInputProperties();
                 mAdditionalDisplayInputProperties.put(displayId, properties);
             }
+            final boolean oldPointerIconVisible = properties.pointerIconVisible;
+            final boolean oldMouseAccelerationEnabled = properties.mousePointerAccelerationEnabled;
             updater.accept(properties);
+            if (oldPointerIconVisible != properties.pointerIconVisible) {
+                mNative.setPointerIconVisibility(displayId, properties.pointerIconVisible);
+            }
+            if (oldMouseAccelerationEnabled != properties.mousePointerAccelerationEnabled) {
+                mNative.setMousePointerAccelerationEnabled(displayId,
+                        properties.mousePointerAccelerationEnabled);
+            }
             if (properties.allDefaults()) {
                 mAdditionalDisplayInputProperties.remove(displayId);
             }
@@ -3544,12 +3622,31 @@
     }
 
     /**
+     * Sets Accessibility slow keys threshold in milliseconds.
+     */
+    public void setAccessibilitySlowKeysThreshold(int thresholdTimeMs) {
+        mNative.setAccessibilitySlowKeysThreshold(thresholdTimeMs);
+    }
+
+    /**
      * Sets whether Accessibility sticky keys is enabled.
      */
     public void setAccessibilityStickyKeysEnabled(boolean enabled) {
         mNative.setAccessibilityStickyKeysEnabled(enabled);
     }
 
+    void setUseLargePointerIcons(boolean useLargeIcons) {
+        synchronized (mLoadedPointerIconsByDisplayAndType) {
+            if (mUseLargePointerIcons == useLargeIcons) {
+                return;
+            }
+            mUseLargePointerIcons = useLargeIcons;
+            // Clear all cached icons on all displays.
+            mLoadedPointerIconsByDisplayAndType.clear();
+        }
+        mNative.reloadPointerIcons();
+    }
+
     interface KeyboardBacklightControllerInterface {
         default void incrementKeyboardBacklight(int deviceId) {}
         default void decrementKeyboardBacklight(int deviceId) {}
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index be8d2a4..165dfe4 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -16,6 +16,8 @@
 
 package com.android.server.input;
 
+import static com.android.input.flags.Flags.rateLimitUserActivityPokeInDispatcher;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -28,7 +30,6 @@
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.util.Log;
-import android.view.PointerIcon;
 import android.view.ViewConfiguration;
 
 import java.util.Map;
@@ -88,6 +89,8 @@
                         (reason) -> updateShowRotaryInput()),
                 Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS),
                         (reason) -> updateAccessibilityBounceKeys()),
+                Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SLOW_KEYS),
+                        (reason) -> updateAccessibilitySlowKeys()),
                 Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_STICKY_KEYS),
                         (reason) -> updateAccessibilityStickyKeys()));
     }
@@ -114,6 +117,8 @@
         for (Consumer<String> observer : mObservers.values()) {
             observer.accept("just booted");
         }
+
+        configureUserActivityPokeInterval();
     }
 
     @Override
@@ -178,8 +183,7 @@
         final int accessibilityConfig = Settings.Secure.getIntForUser(
                 mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,
                 0, UserHandle.USER_CURRENT);
-        PointerIcon.setUseLargeIcons(accessibilityConfig == 1);
-        mNative.reloadPointerIcons();
+        mService.setUseLargePointerIcons(accessibilityConfig == 1);
     }
 
     private void updateLongPressTimeout(String reason) {
@@ -226,8 +230,22 @@
                 InputSettings.getAccessibilityBounceKeysThreshold(mContext));
     }
 
+    private void updateAccessibilitySlowKeys() {
+        mService.setAccessibilitySlowKeysThreshold(
+                InputSettings.getAccessibilitySlowKeysThreshold(mContext));
+    }
+
     private void updateAccessibilityStickyKeys() {
         mService.setAccessibilityStickyKeysEnabled(
                 InputSettings.isAccessibilityStickyKeysEnabled(mContext));
     }
+
+    private void configureUserActivityPokeInterval() {
+        if (rateLimitUserActivityPokeInDispatcher()) {
+            final int intervalMillis = mContext.getResources().getInteger(
+                    com.android.internal.R.integer.config_minMillisBetweenInputUserActivityEvents);
+            Log.i(TAG, "Setting user activity interval (ms) of " + intervalMillis);
+            mNative.setMinTimeBetweenUserActivityPokes(intervalMillis);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 8580b96..46668de 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -76,6 +76,8 @@
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.XmlUtils;
+import com.android.server.LocalServices;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.input.KeyboardMetricsCollector.KeyboardConfigurationEvent;
 import com.android.server.input.KeyboardMetricsCollector.LayoutSelectionCriteria;
 import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -197,7 +199,7 @@
         final KeyboardIdentifier keyboardIdentifier = new KeyboardIdentifier(inputDevice);
         KeyboardConfiguration config = mConfiguredKeyboards.get(deviceId);
         if (config == null) {
-            config = new KeyboardConfiguration();
+            config = new KeyboardConfiguration(deviceId);
             mConfiguredKeyboards.put(deviceId, config);
         }
 
@@ -1093,19 +1095,26 @@
 
     @MainThread
     private void maybeUpdateNotification() {
-        if (mConfiguredKeyboards.size() == 0) {
-            hideKeyboardLayoutNotification();
-            return;
-        }
+        List<KeyboardConfiguration> configurations = new ArrayList<>();
         for (int i = 0; i < mConfiguredKeyboards.size(); i++) {
+            int deviceId = mConfiguredKeyboards.keyAt(i);
+            KeyboardConfiguration config = mConfiguredKeyboards.valueAt(i);
+            if (isVirtualDevice(deviceId)) {
+                continue;
+            }
             // If we have a keyboard with no selected layouts, we should always show missing
             // layout notification even if there are other keyboards that are configured properly.
-            if (!mConfiguredKeyboards.valueAt(i).hasConfiguredLayouts()) {
+            if (!config.hasConfiguredLayouts()) {
                 showMissingKeyboardLayoutNotification();
                 return;
             }
+            configurations.add(config);
         }
-        showConfiguredKeyboardLayoutNotification();
+        if (configurations.size() == 0) {
+            hideKeyboardLayoutNotification();
+            return;
+        }
+        showConfiguredKeyboardLayoutNotification(configurations);
     }
 
     @MainThread
@@ -1185,10 +1194,11 @@
     }
 
     @MainThread
-    private void showConfiguredKeyboardLayoutNotification() {
+    private void showConfiguredKeyboardLayoutNotification(
+            List<KeyboardConfiguration> configurations) {
         final Resources r = mContext.getResources();
 
-        if (mConfiguredKeyboards.size() != 1) {
+        if (configurations.size() != 1) {
             showKeyboardLayoutNotification(
                     r.getString(R.string.keyboard_layout_notification_multiple_selected_title),
                     r.getString(R.string.keyboard_layout_notification_multiple_selected_message),
@@ -1196,8 +1206,8 @@
             return;
         }
 
-        final InputDevice inputDevice = getInputDevice(mConfiguredKeyboards.keyAt(0));
-        final KeyboardConfiguration config = mConfiguredKeyboards.valueAt(0);
+        final KeyboardConfiguration config = configurations.get(0);
+        final InputDevice inputDevice = getInputDevice(config.getDeviceId());
         if (inputDevice == null || !config.hasConfiguredLayouts()) {
             return;
         }
@@ -1356,6 +1366,13 @@
         return false;
     }
 
+    @VisibleForTesting
+    public boolean isVirtualDevice(int deviceId) {
+        VirtualDeviceManagerInternal vdm = LocalServices.getService(
+                VirtualDeviceManagerInternal.class);
+        return vdm != null && vdm.isInputDeviceOwnedByVirtualDevice(deviceId);
+    }
+
     private static int[] getScriptCodes(@Nullable Locale locale) {
         if (locale == null) {
             return new int[0];
@@ -1430,11 +1447,22 @@
     }
 
     private static class KeyboardConfiguration {
+
         // If null or empty, it means no layout is configured for the device. And user needs to
         // manually set up the device.
         @Nullable
         private Set<String> mConfiguredLayouts;
 
+        private final int mDeviceId;
+
+        private KeyboardConfiguration(int deviceId) {
+            mDeviceId = deviceId;
+        }
+
+        private int getDeviceId() {
+            return mDeviceId;
+        }
+
         private boolean hasConfiguredLayouts() {
             return mConfiguredLayouts != null && !mConfiguredLayouts.isEmpty();
         }
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 829b660..bc82078 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.input;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.hardware.display.DisplayViewport;
 import android.hardware.input.InputSensorInfo;
@@ -107,6 +108,8 @@
 
     void setFocusedDisplay(int displayId);
 
+    void setMinTimeBetweenUserActivityPokes(long millis);
+
     boolean transferTouchFocus(IBinder fromChannelToken, IBinder toChannelToken,
             boolean isDragDrop);
 
@@ -118,7 +121,7 @@
 
     void setPointerSpeed(int speed);
 
-    void setPointerAcceleration(float acceleration);
+    void setMousePointerAccelerationEnabled(int displayId, boolean enabled);
 
     void setTouchpadPointerSpeed(int speed);
 
@@ -184,10 +187,12 @@
 
     void reloadPointerIcons();
 
-    void setCustomPointerIcon(PointerIcon icon);
+    void setCustomPointerIcon(@NonNull PointerIcon icon);
 
-    boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId, int pointerId,
-            IBinder inputToken);
+    boolean setPointerIcon(@NonNull PointerIcon icon, int displayId, int deviceId, int pointerId,
+            @NonNull IBinder inputToken);
+
+    void setPointerIconVisibility(int displayId, boolean visible);
 
     void requestPointerCapture(IBinder windowToken, boolean enabled);
 
@@ -228,14 +233,15 @@
     void setStylusButtonMotionEventsEnabled(boolean enabled);
 
     /**
-     * Get the current position of the mouse cursor.
+     * Get the current position of the mouse cursor on the given display.
      *
-     * If the mouse cursor is not currently shown, the coordinate values will be NaN-s.
+     * If the mouse cursor is not currently shown, the coordinate values will be NaN-s. Use
+     * {@link android.view.Display#INVALID_DISPLAY} to get the position of the default mouse cursor.
      *
      * NOTE: This will grab the PointerController's lock, so we must be careful about calling this
      * from the InputReader or Display threads, which may result in a deadlock.
      */
-    float[] getMouseCursorPosition();
+    float[] getMouseCursorPosition(int displayId);
 
     /** Set whether showing a pointer icon for styluses is enabled. */
     void setStylusPointerIconEnabled(boolean enabled);
@@ -252,6 +258,11 @@
     void setAccessibilityBounceKeysThreshold(int thresholdTimeMs);
 
     /**
+     * Notify if Accessibility slow keys threshold is changed from InputSettings.
+     */
+    void setAccessibilitySlowKeysThreshold(int thresholdTimeMs);
+
+    /**
      * Notify if Accessibility sticky keys is enabled/disabled from InputSettings.
      */
     void setAccessibilityStickyKeysEnabled(boolean enabled);
@@ -341,6 +352,9 @@
         public native void setFocusedDisplay(int displayId);
 
         @Override
+        public native void setMinTimeBetweenUserActivityPokes(long millis);
+
+        @Override
         public native boolean transferTouchFocus(IBinder fromChannelToken, IBinder toChannelToken,
                 boolean isDragDrop);
 
@@ -351,7 +365,7 @@
         public native void setPointerSpeed(int speed);
 
         @Override
-        public native void setPointerAcceleration(float acceleration);
+        public native void setMousePointerAccelerationEnabled(int displayId, boolean enabled);
 
         @Override
         public native void setTouchpadPointerSpeed(int speed);
@@ -451,6 +465,9 @@
                 int pointerId, IBinder inputToken);
 
         @Override
+        public native void setPointerIconVisibility(int displayId, boolean visible);
+
+        @Override
         public native void requestPointerCapture(IBinder windowToken, boolean enabled);
 
         @Override
@@ -503,7 +520,7 @@
         public native void setStylusButtonMotionEventsEnabled(boolean enabled);
 
         @Override
-        public native float[] getMouseCursorPosition();
+        public native float[] getMouseCursorPosition(int displayId);
 
         @Override
         public native void setStylusPointerIconEnabled(boolean enabled);
@@ -515,6 +532,9 @@
         public native void setAccessibilityBounceKeysThreshold(int thresholdTimeMs);
 
         @Override
+        public native void setAccessibilitySlowKeysThreshold(int thresholdTimeMs);
+
+        @Override
         public native void setAccessibilityStickyKeysEnabled(boolean enabled);
     }
 }
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
index c76ca2b..fba71fd 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
@@ -114,7 +114,7 @@
      * @param userId      The user ID to be associated with.
      */
     static void save(ArrayMap<String, List<InputMethodSubtype>> allSubtypes,
-            ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
+            InputMethodMap methodMap, @UserIdInt int userId) {
         final File inputMethodDir = getInputMethodDir(userId);
 
         if (allSubtypes.isEmpty()) {
@@ -143,7 +143,7 @@
 
     @VisibleForTesting
     static void saveToFile(ArrayMap<String, List<InputMethodSubtype>> allSubtypes,
-            ArrayMap<String, InputMethodInfo> methodMap, AtomicFile subtypesFile) {
+            InputMethodMap methodMap, AtomicFile subtypesFile) {
         // Safety net for the case that this function is called before methodMap is set.
         final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0;
         FileOutputStream fos = null;
diff --git a/services/core/java/com/android/server/inputmethod/ClientController.java b/services/core/java/com/android/server/inputmethod/ClientController.java
index 2934640..86f4db9 100644
--- a/services/core/java/com/android/server/inputmethod/ClientController.java
+++ b/services/core/java/com/android/server/inputmethod/ClientController.java
@@ -17,17 +17,21 @@
 package com.android.server.inputmethod;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.pm.PackageManagerInternal;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.ArrayMap;
-import android.util.SparseArray;
-import android.view.inputmethod.InputBinding;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.IInputMethodClient;
 import com.android.internal.inputmethod.IRemoteInputConnection;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
 /**
  * Store and manage {@link InputMethodManagerService} clients. This class was designed to be a
  * singleton in {@link InputMethodManagerService} since it stores information about all clients,
@@ -37,9 +41,7 @@
  * As part of the re-architecture plan (described in go/imms-rearchitecture-plan), the following
  * fields and methods will be moved out from IMMS and placed here:
  * <ul>
- * <li>mCurClient (ClientState)</li>
  * <li>mClients (ArrayMap of ClientState indexed by IBinder)</li>
- * <li>mLastSwitchUserId</li>
  * </ul>
  * <p>
  * Nested Classes (to move from IMMS):
@@ -54,7 +56,6 @@
  * <li>removeClient</li>
  * <li>verifyClientAndPackageMatch</li>
  * <li>setImeTraceEnabledForAllClients (make it reactive)</li>
- * <li>unbindCurrentClient</li>
  * </ul>
  */
 // TODO(b/314150112): Update the Javadoc above, by removing the re-architecture steps, once this
@@ -63,20 +64,34 @@
 
     // TODO(b/314150112): Make this field private when breaking the cycle with IMMS.
     @GuardedBy("ImfLock.class")
-    final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<>();
+    private final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<>();
+
+    @GuardedBy("ImfLock.class")
+    private final List<ClientControllerCallback> mCallbacks = new ArrayList<>();
 
     private final PackageManagerInternal mPackageManagerInternal;
 
+    interface ClientControllerCallback {
+
+        void onClientRemoved(ClientState client);
+    }
+
     ClientController(PackageManagerInternal packageManagerInternal) {
         mPackageManagerInternal = packageManagerInternal;
     }
 
     @GuardedBy("ImfLock.class")
-    void addClient(IInputMethodClientInvoker clientInvoker,
-            IRemoteInputConnection inputConnection,
-            int selfReportedDisplayId, IBinder.DeathRecipient deathRecipient, int callerUid,
+    ClientState addClient(IInputMethodClientInvoker clientInvoker,
+            IRemoteInputConnection inputConnection, int selfReportedDisplayId, int callerUid,
             int callerPid) {
-        // TODO: Optimize this linear search.
+        final IBinder.DeathRecipient deathRecipient = () -> {
+            // Exceptionally holding ImfLock here since this is a internal lambda expression.
+            synchronized (ImfLock.class) {
+                removeClientAsBinder(clientInvoker.asBinder());
+            }
+        };
+
+        // TODO(b/319457906): Optimize this linear search.
         final int numClients = mClients.size();
         for (int i = 0; i < numClients; ++i) {
             final ClientState state = mClients.valueAt(i);
@@ -101,62 +116,57 @@
         // have the client crash.  Thus we do not verify the display ID at all here.  Instead we
         // later check the display ID every time the client needs to interact with the specified
         // display.
-        mClients.put(clientInvoker.asBinder(), new ClientState(clientInvoker, inputConnection,
-                callerUid, callerPid, selfReportedDisplayId, deathRecipient));
+        final ClientState cs = new ClientState(clientInvoker, inputConnection,
+                callerUid, callerPid, selfReportedDisplayId, deathRecipient);
+        mClients.put(clientInvoker.asBinder(), cs);
+        return cs;
+    }
+
+    @VisibleForTesting
+    @GuardedBy("ImfLock.class")
+    boolean removeClient(IInputMethodClient client) {
+        return removeClientAsBinder(client.asBinder());
+    }
+
+    @GuardedBy("ImfLock.class")
+    private boolean removeClientAsBinder(IBinder binder) {
+        final ClientState cs = mClients.remove(binder);
+        if (cs == null) {
+            return false;
+        }
+        binder.unlinkToDeath(cs.mClientDeathRecipient, 0 /* flags */);
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            mCallbacks.get(i).onClientRemoved(cs);
+        }
+        return true;
+    }
+
+    @GuardedBy("ImfLock.class")
+    void addClientControllerCallback(ClientControllerCallback callback) {
+        mCallbacks.add(callback);
+    }
+
+    @GuardedBy("ImfLock.class")
+    @Nullable
+    ClientState getClient(IBinder binder) {
+        return mClients.get(binder);
+    }
+
+    @GuardedBy("ImfLock.class")
+    void forAllClients(Consumer<ClientState> consumer) {
+        for (int i = 0; i < mClients.size(); i++) {
+            consumer.accept(mClients.valueAt(i));
+        }
     }
 
     @GuardedBy("ImfLock.class")
     boolean verifyClientAndPackageMatch(
             @NonNull IInputMethodClient client, @NonNull String packageName) {
-        ClientState cs = mClients.get(client.asBinder());
+        final ClientState cs = mClients.get(client.asBinder());
         if (cs == null) {
             throw new IllegalArgumentException("unknown client " + client.asBinder());
         }
         return InputMethodUtils.checkIfPackageBelongsToUid(
                 mPackageManagerInternal, cs.mUid, packageName);
     }
-
-    static final class ClientState {
-        final IInputMethodClientInvoker mClient;
-        final IRemoteInputConnection mFallbackInputConnection;
-        final int mUid;
-        final int mPid;
-        final int mSelfReportedDisplayId;
-        final InputBinding mBinding;
-        final IBinder.DeathRecipient mClientDeathRecipient;
-
-        @GuardedBy("ImfLock.class")
-        boolean mSessionRequested;
-
-        @GuardedBy("ImfLock.class")
-        boolean mSessionRequestedForAccessibility;
-
-        @GuardedBy("ImfLock.class")
-        InputMethodManagerService.SessionState mCurSession;
-
-        @GuardedBy("ImfLock.class")
-        SparseArray<InputMethodManagerService.AccessibilitySessionState> mAccessibilitySessions =
-                new SparseArray<>();
-
-        @Override
-        public String toString() {
-            return "ClientState{" + Integer.toHexString(
-                    System.identityHashCode(this)) + " mUid=" + mUid
-                    + " mPid=" + mPid + " mSelfReportedDisplayId=" + mSelfReportedDisplayId + "}";
-        }
-
-        ClientState(IInputMethodClientInvoker client,
-                IRemoteInputConnection fallbackInputConnection,
-                int uid, int pid, int selfReportedDisplayId,
-                IBinder.DeathRecipient clientDeathRecipient) {
-            mClient = client;
-            mFallbackInputConnection = fallbackInputConnection;
-            mUid = uid;
-            mPid = pid;
-            mSelfReportedDisplayId = selfReportedDisplayId;
-            mBinding = new InputBinding(null /*conn*/, mFallbackInputConnection.asBinder(), mUid,
-                    mPid);
-            mClientDeathRecipient = clientDeathRecipient;
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/inputmethod/ClientState.java b/services/core/java/com/android/server/inputmethod/ClientState.java
new file mode 100644
index 0000000..e98a5a7
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ClientState.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.os.IBinder;
+import android.util.SparseArray;
+import android.view.inputmethod.InputBinding;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+
+final class ClientState {
+    final IInputMethodClientInvoker mClient;
+    final IRemoteInputConnection mFallbackInputConnection;
+    final int mUid;
+    final int mPid;
+    final int mSelfReportedDisplayId;
+    final InputBinding mBinding;
+    final IBinder.DeathRecipient mClientDeathRecipient;
+
+    @GuardedBy("ImfLock.class")
+    boolean mSessionRequested;
+
+    @GuardedBy("ImfLock.class")
+    boolean mSessionRequestedForAccessibility;
+
+    @GuardedBy("ImfLock.class")
+    InputMethodManagerService.SessionState mCurSession;
+
+    @GuardedBy("ImfLock.class")
+    SparseArray<InputMethodManagerService.AccessibilitySessionState> mAccessibilitySessions =
+            new SparseArray<>();
+
+    @Override
+    public String toString() {
+        return "ClientState{" + Integer.toHexString(
+                System.identityHashCode(this)) + " mUid=" + mUid
+                + " mPid=" + mPid + " mSelfReportedDisplayId=" + mSelfReportedDisplayId + "}";
+    }
+
+    ClientState(IInputMethodClientInvoker client,
+            IRemoteInputConnection fallbackInputConnection,
+            int uid, int pid, int selfReportedDisplayId,
+            IBinder.DeathRecipient clientDeathRecipient) {
+        mClient = client;
+        mFallbackInputConnection = fallbackInputConnection;
+        mUid = uid;
+        mPid = pid;
+        mSelfReportedDisplayId = selfReportedDisplayId;
+        mBinding = new InputBinding(null /*conn*/, mFallbackInputConnection.asBinder(), mUid,
+                mPid);
+        mClientDeathRecipient = clientDeathRecipient;
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
index 898d5a5..62adb25 100644
--- a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
+++ b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
@@ -20,7 +20,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
-import android.util.ArrayMap;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
@@ -44,23 +43,23 @@
         return mUserId;
     }
 
-    HardwareKeyboardShortcutController(
-            @NonNull ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
+    HardwareKeyboardShortcutController(@NonNull InputMethodMap methodMap, @UserIdInt int userId) {
         mUserId = userId;
         reset(methodMap);
     }
 
     @GuardedBy("ImfLock.class")
-    void reset(@NonNull ArrayMap<String, InputMethodInfo> methodMap) {
+    void reset(@NonNull InputMethodMap methodMap) {
         mSubtypeHandles.clear();
-        final InputMethodUtils.InputMethodSettings settings =
-                new InputMethodUtils.InputMethodSettings(methodMap, mUserId);
-        for (final InputMethodInfo imi : settings.getEnabledInputMethodListLocked()) {
+        final InputMethodSettings settings = InputMethodSettings.create(methodMap, mUserId);
+        final List<InputMethodInfo> inputMethods = settings.getEnabledInputMethodList();
+        for (int i = 0; i < inputMethods.size(); ++i) {
+            final InputMethodInfo imi = inputMethods.get(i);
             if (!imi.shouldShowInInputMethodPicker()) {
                 continue;
             }
             final List<InputMethodSubtype> subtypes =
-                    settings.getEnabledInputMethodSubtypeListLocked(imi, true);
+                    settings.getEnabledInputMethodSubtypeList(imi, true);
             if (subtypes.isEmpty()) {
                 mSubtypeHandles.add(InputMethodSubtypeHandle.of(imi, null));
             } else {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
index 2957935..6339686 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
@@ -23,7 +23,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.text.TextUtils;
-import android.util.ArrayMap;
 import android.util.Slog;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
@@ -46,7 +45,7 @@
     private static final String TAG = "InputMethodInfoUtils";
 
     /**
-     * Used in {@link #getFallbackLocaleForDefaultIme(ArrayList, Context)} to find the fallback IMEs
+     * Used in {@link #getFallbackLocaleForDefaultIme(List, Context)} to find the fallback IMEs
      * that are mainly used until the system becomes ready. Note that {@link Locale} in this array
      * is checked with {@link Locale#equals(Object)}, which means that {@code Locale.ENGLISH}
      * doesn't automatically match {@code Locale("en", "IN")}.
@@ -64,7 +63,7 @@
         @NonNull
         private final LinkedHashSet<InputMethodInfo> mInputMethodSet = new LinkedHashSet<>();
 
-        InputMethodListBuilder fillImes(ArrayList<InputMethodInfo> imis, Context context,
+        InputMethodListBuilder fillImes(List<InputMethodInfo> imis, Context context,
                 boolean checkDefaultAttribute, @Nullable Locale locale, boolean checkCountry,
                 String requiredSubtypeMode) {
             for (int i = 0; i < imis.size(); ++i) {
@@ -77,9 +76,7 @@
             return this;
         }
 
-        // TODO: The behavior of InputMethodSubtype#overridesImplicitlyEnabledSubtype() should be
-        // documented more clearly.
-        InputMethodListBuilder fillAuxiliaryImes(ArrayList<InputMethodInfo> imis, Context context) {
+        InputMethodListBuilder fillAuxiliaryImes(List<InputMethodInfo> imis, Context context) {
             // If one or more auxiliary input methods are available, OK to stop populating the list.
             for (final InputMethodInfo imi : mInputMethodSet) {
                 if (imi.isAuxiliaryIme()) {
@@ -120,7 +117,7 @@
     }
 
     private static InputMethodListBuilder getMinimumKeyboardSetWithSystemLocale(
-            ArrayList<InputMethodInfo> imis, Context context, @Nullable Locale systemLocale,
+            List<InputMethodInfo> imis, Context context, @Nullable Locale systemLocale,
             @Nullable Locale fallbackLocale) {
         // Once the system becomes ready, we pick up at least one keyboard in the following order.
         // Secondary users fall into this category in general.
@@ -169,7 +166,7 @@
     }
 
     static ArrayList<InputMethodInfo> getDefaultEnabledImes(
-            Context context, ArrayList<InputMethodInfo> imis, boolean onlyMinimum) {
+            Context context, List<InputMethodInfo> imis, boolean onlyMinimum) {
         final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context);
         // We will primarily rely on the system locale, but also keep relying on the fallback locale
         // as a last resort.
@@ -188,7 +185,7 @@
     }
 
     static ArrayList<InputMethodInfo> getDefaultEnabledImes(
-            Context context, ArrayList<InputMethodInfo> imis) {
+            Context context, List<InputMethodInfo> imis) {
         return getDefaultEnabledImes(context, imis, false /* onlyMinimum */);
     }
 
@@ -206,7 +203,7 @@
      */
     @Nullable
     static InputMethodInfo chooseSystemVoiceIme(
-            @NonNull ArrayMap<String, InputMethodInfo> methodMap,
+            @NonNull InputMethodMap methodMap,
             @Nullable String systemSpeechRecognizerPackageName,
             @Nullable String currentDefaultVoiceImeId) {
         if (TextUtils.isEmpty(systemSpeechRecognizerPackageName)) {
@@ -285,7 +282,7 @@
     }
 
     @Nullable
-    private static Locale getFallbackLocaleForDefaultIme(ArrayList<InputMethodInfo> imis,
+    private static Locale getFallbackLocaleForDefaultIme(List<InputMethodInfo> imis,
             Context context) {
         // At first, find the fallback locale from the IMEs that are declared as "default" in the
         // current locale.  Note that IME developers can declare an IME as "default" only for
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 25ec683..f031b7b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -48,7 +48,6 @@
 import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 
-import static com.android.server.inputmethod.ClientController.ClientState;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
@@ -116,7 +115,6 @@
 import android.util.Printer;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.InputChannel;
 import android.view.InputDevice;
@@ -183,7 +181,6 @@
 import com.android.server.input.InputManagerInternal;
 import com.android.server.inputmethod.InputMethodManagerInternal.InputMethodListListener;
 import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
-import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.utils.PriorityDump;
@@ -208,6 +205,7 @@
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
 import java.util.function.IntConsumer;
 
 /**
@@ -273,7 +271,6 @@
     @NonNull
     private final String[] mNonPreemptibleInputMethods;
 
-    // TODO(b/314150112): Move this to ClientController.
     @UserIdInt
     private int mLastSwitchUserId;
 
@@ -283,8 +280,6 @@
     @NonNull
     private InputMethodSettings mSettings;
     final SettingsObserver mSettingsObserver;
-    private final SparseBooleanArray mLoggedDeniedGetInputMethodWindowVisibleHeightForUid =
-            new SparseBooleanArray(0);
     final WindowManagerInternal mWindowManagerInternal;
     private final ActivityManagerInternal mActivityManagerInternal;
     final PackageManagerInternal mPackageManagerInternal;
@@ -315,10 +310,6 @@
     @Nullable
     private VirtualDeviceManagerInternal mVdmInternal = null;
 
-    // All known input methods.
-    final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
-    private final ArrayMap<String, InputMethodInfo> mMethodMap = new ArrayMap<>();
-
     // Mapping from deviceId to the device-specific imeId for that device.
     @GuardedBy("ImfLock.class")
     private final SparseArray<String> mVirtualDeviceMethodMap = new SparseArray<>();
@@ -331,7 +322,7 @@
     private HardwareKeyboardShortcutController mHardwareKeyboardShortcutController;
 
     /**
-     * Tracks how many times {@link #mMethodMap} was updated.
+     * Tracks how many times {@link #mSettings} was updated.
      */
     @GuardedBy("ImfLock.class")
     private int mMethodMapUpdateCount = 0;
@@ -473,13 +464,12 @@
     @GuardedBy("ImfLock.class")
     @Nullable
     InputMethodInfo queryInputMethodForCurrentUserLocked(@NonNull String imeId) {
-        return mMethodMap.get(imeId);
+        return mSettings.getMethodMap().get(imeId);
     }
 
     /**
      * The client that is currently bound to an input method.
      */
-    // TODO(b/314150112): Move this to ClientController.
     @Nullable
     private ClientState mCurClient;
 
@@ -1173,7 +1163,7 @@
                 // sender userId can be a real user ID or USER_ALL.
                 final int senderUserId = pendingResult.getSendingUserId();
                 if (senderUserId != UserHandle.USER_ALL) {
-                    if (senderUserId != mSettings.getCurrentUserId()) {
+                    if (senderUserId != mSettings.getUserId()) {
                         // A background user is trying to hide the dialog. Ignore.
                         return;
                     }
@@ -1251,7 +1241,7 @@
         @GuardedBy("ImfLock.class")
         private boolean isChangingPackagesOfCurrentUserLocked() {
             final int userId = getChangingUserId();
-            final boolean retval = userId == mSettings.getCurrentUserId();
+            final boolean retval = userId == mSettings.getUserId();
             if (DEBUG) {
                 if (!retval) {
                     Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
@@ -1267,10 +1257,11 @@
                     return false;
                 }
                 String curInputMethodId = mSettings.getSelectedInputMethod();
-                final int numImes = mMethodList.size();
+                final List<InputMethodInfo> methodList = mSettings.getMethodList();
+                final int numImes = methodList.size();
                 if (curInputMethodId != null) {
                     for (int i = 0; i < numImes; i++) {
-                        InputMethodInfo imi = mMethodList.get(i);
+                        InputMethodInfo imi = methodList.get(i);
                         if (imi.getId().equals(curInputMethodId)) {
                             for (String pkg : packages) {
                                 if (imi.getPackageName().equals(pkg)) {
@@ -1341,7 +1332,7 @@
         @Override
         public void onPackageDataCleared(String packageName, int uid) {
             boolean changed = false;
-            for (InputMethodInfo imi : mMethodList) {
+            for (InputMethodInfo imi : mSettings.getMethodList()) {
                 if (imi.getPackageName().equals(packageName)) {
                     mAdditionalSubtypeMap.remove(imi.getId());
                     changed = true;
@@ -1349,7 +1340,7 @@
             }
             if (changed) {
                 AdditionalSubtypeUtils.save(
-                        mAdditionalSubtypeMap, mMethodMap, mSettings.getCurrentUserId());
+                        mAdditionalSubtypeMap, mSettings.getMethodMap(), mSettings.getUserId());
                 mChangedPackages.add(packageName);
             }
         }
@@ -1360,13 +1351,6 @@
             clearPackageChangeState();
         }
 
-        @Override
-        public void onUidRemoved(int uid) {
-            synchronized (ImfLock.class) {
-                mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.delete(uid);
-            }
-        }
-
         private void clearPackageChangeState() {
             // No need to lock them because we access these fields only on getRegisteredHandler().
             mChangedPackages.clear();
@@ -1407,10 +1391,11 @@
 
                 InputMethodInfo curIm = null;
                 String curInputMethodId = mSettings.getSelectedInputMethod();
-                final int numImes = mMethodList.size();
+                final List<InputMethodInfo> methodList = mSettings.getMethodList();
+                final int numImes = methodList.size();
                 if (curInputMethodId != null) {
                     for (int i = 0; i < numImes; i++) {
-                        InputMethodInfo imi = mMethodList.get(i);
+                        InputMethodInfo imi = methodList.get(i);
                         final String imiId = imi.getId();
                         if (imiId.equals(curInputMethodId)) {
                             curIm = imi;
@@ -1428,8 +1413,7 @@
                                             + imi.getComponent());
                             mAdditionalSubtypeMap.remove(imi.getId());
                             AdditionalSubtypeUtils.save(mAdditionalSubtypeMap,
-                                    mMethodMap,
-                                    mSettings.getCurrentUserId());
+                                    mSettings.getMethodMap(), mSettings.getUserId());
                         }
                     }
                 }
@@ -1443,7 +1427,7 @@
                     if (change == PACKAGE_TEMPORARY_CHANGE
                             || change == PACKAGE_PERMANENT_CHANGE) {
                         final PackageManager userAwarePackageManager =
-                                getPackageManagerForUser(mContext, mSettings.getCurrentUserId());
+                                getPackageManagerForUser(mContext, mSettings.getUserId());
                         ServiceInfo si = null;
                         try {
                             si = userAwarePackageManager.getServiceInfo(curIm.getComponent(),
@@ -1579,14 +1563,14 @@
 
     void onUnlockUser(@UserIdInt int userId) {
         synchronized (ImfLock.class) {
-            final int currentUserId = mSettings.getCurrentUserId();
+            final int currentUserId = mSettings.getUserId();
             if (DEBUG) {
                 Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
             }
             if (userId != currentUserId) {
                 return;
             }
-            mSettings = new InputMethodSettings(mMethodMap, userId);
+            mSettings = InputMethodSettings.createEmptyMap(userId);
             if (mSystemReady) {
                 // We need to rebuild IMEs.
                 buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
@@ -1660,14 +1644,15 @@
         mLastSwitchUserId = userId;
 
         // mSettings should be created before buildInputMethodListLocked
-        mSettings = new InputMethodSettings(mMethodMap, userId);
+        mSettings = InputMethodSettings.createEmptyMap(userId);
 
         AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
         mSwitchingController =
-                InputMethodSubtypeSwitchingController.createInstanceLocked(context, mMethodMap,
-                        userId);
+                InputMethodSubtypeSwitchingController.createInstanceLocked(context,
+                        mSettings.getMethodMap(), userId);
         mHardwareKeyboardShortcutController =
-                new HardwareKeyboardShortcutController(mMethodMap, userId);
+                new HardwareKeyboardShortcutController(mSettings.getMethodMap(),
+                        mSettings.getUserId());
         mMenuController = new InputMethodMenuController(this);
         mBindingController =
                 bindingControllerForTesting != null
@@ -1677,7 +1662,11 @@
 
         mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
         mVisibilityApplier = new DefaultImeVisibilityApplier(this);
+
         mClientController = new ClientController(mPackageManagerInternal);
+        synchronized (ImfLock.class) {
+            mClientController.addClientControllerCallback(c -> onClientRemoved(c));
+        }
 
         mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
                 com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
@@ -1694,7 +1683,7 @@
     @GuardedBy("ImfLock.class")
     @UserIdInt
     int getCurrentImeUserIdLocked() {
-        return mSettings.getCurrentUserId();
+        return mSettings.getUserId();
     }
 
     private final class InkWindowInitializer implements Runnable {
@@ -1721,11 +1710,12 @@
     private void resetDefaultImeLocked(Context context) {
         // Do not reset the default (current) IME when it is a 3rd-party IME
         String selectedMethodId = getSelectedMethodIdLocked();
-        if (selectedMethodId != null && !mMethodMap.get(selectedMethodId).isSystem()) {
+        if (selectedMethodId != null
+                && !mSettings.getMethodMap().get(selectedMethodId).isSystem()) {
             return;
         }
         final List<InputMethodInfo> suitableImes = InputMethodInfoUtils.getDefaultEnabledImes(
-                context, mSettings.getEnabledInputMethodListLocked());
+                context, mSettings.getEnabledInputMethodList());
         if (suitableImes.isEmpty()) {
             Slog.i(TAG, "No default found");
             return;
@@ -1781,7 +1771,7 @@
             IInputMethodClientInvoker clientToBeReset) {
         if (DEBUG) {
             Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
-                    + " currentUserId=" + mSettings.getCurrentUserId());
+                    + " currentUserId=" + mSettings.getUserId());
         }
 
         maybeInitImeNavbarConfigLocked(newUserId);
@@ -1789,7 +1779,7 @@
         // ContentObserver should be registered again when the user is changed
         mSettingsObserver.registerContentObserverLocked(newUserId);
 
-        mSettings = new InputMethodSettings(mMethodMap, newUserId);
+        mSettings = InputMethodSettings.createEmptyMap(newUserId);
         // Additional subtypes should be reset when the user is changed
         AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, newUserId);
         final String defaultImiId = mSettings.getSelectedInputMethod();
@@ -1820,7 +1810,7 @@
         if (initialUserSwitch) {
             InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
                     getPackageManagerForUser(mContext, newUserId),
-                    mSettings.getEnabledInputMethodListLocked());
+                    mSettings.getEnabledInputMethodList());
         }
 
         if (DEBUG) {
@@ -1829,10 +1819,8 @@
         }
 
         mLastSwitchUserId = newUserId;
-
         if (mIsInteractive && clientToBeReset != null) {
-            final ClientState cs =
-                    mClientController.mClients.get(clientToBeReset.asBinder());
+            final ClientState cs = mClientController.getClient(clientToBeReset.asBinder());
             if (cs == null) {
                 // The client is already gone.
                 return;
@@ -1851,7 +1839,7 @@
             }
             if (!mSystemReady) {
                 mSystemReady = true;
-                final int currentUserId = mSettings.getCurrentUserId();
+                final int currentUserId = mSettings.getUserId();
                 mStatusBarManagerInternal =
                         LocalServices.getService(StatusBarManagerInternal.class);
                 hideStatusBarIconLocked();
@@ -1872,7 +1860,7 @@
                     // the "mImeDrawsImeNavBarResLazyInitFuture" field.
                     synchronized (ImfLock.class) {
                         mImeDrawsImeNavBarResLazyInitFuture = null;
-                        if (currentUserId != mSettings.getCurrentUserId()) {
+                        if (currentUserId != mSettings.getUserId()) {
                             // This means that the current user is already switched to other user
                             // before the background task is executed. In this scenario the relevant
                             // field should already be initialized.
@@ -1897,7 +1885,7 @@
                 updateFromSettingsLocked(true);
                 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
                         getPackageManagerForUser(mContext, currentUserId),
-                        mSettings.getEnabledInputMethodListLocked());
+                        mSettings.getEnabledInputMethodList());
             }
         }
     }
@@ -1944,7 +1932,7 @@
         }
         synchronized (ImfLock.class) {
             final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
-                    mSettings.getCurrentUserId(), null);
+                    mSettings.getUserId(), null);
             if (resolvedUserIds.length != 1) {
                 return Collections.emptyList();
             }
@@ -1967,7 +1955,7 @@
         }
         synchronized (ImfLock.class) {
             final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
-                    mSettings.getCurrentUserId(), null);
+                    mSettings.getUserId(), null);
             if (resolvedUserIds.length != 1) {
                 return Collections.emptyList();
             }
@@ -1994,14 +1982,14 @@
             }
 
             // Check if selected IME of current user supports handwriting.
-            if (userId == mSettings.getCurrentUserId()) {
+            if (userId == mSettings.getUserId()) {
                 return mBindingController.supportsStylusHandwriting();
             }
             //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
             //TODO(b/210039666): use cache.
-            final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
-            final InputMethodSettings settings = new InputMethodSettings(methodMap, userId);
-            final InputMethodInfo imi = methodMap.get(settings.getSelectedInputMethod());
+            final InputMethodSettings settings = queryMethodMapForUser(userId);
+            final InputMethodInfo imi = settings.getMethodMap().get(
+                    settings.getSelectedInputMethod());
             return imi != null && imi.supportsStylusHandwriting();
         }
     }
@@ -2021,23 +2009,19 @@
     @GuardedBy("ImfLock.class")
     private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId,
             @DirectBootAwareness int directBootAwareness, int callingUid) {
-        final ArrayList<InputMethodInfo> methodList;
         final InputMethodSettings settings;
-        if (userId == mSettings.getCurrentUserId()
+        if (userId == mSettings.getUserId()
                 && directBootAwareness == DirectBootAwareness.AUTO) {
-            // Create a copy.
-            methodList = new ArrayList<>(mMethodList);
             settings = mSettings;
         } else {
-            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-            methodList = new ArrayList<>();
             final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
                     new ArrayMap<>();
             AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
-            queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
-                    methodList, directBootAwareness);
-            settings = new InputMethodSettings(methodMap, userId);
+            settings = queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
+                    directBootAwareness);
         }
+        // Create a copy.
+        final ArrayList<InputMethodInfo> methodList = new ArrayList<>(settings.getMethodList());
         // filter caller's access to input methods
         methodList.removeIf(imi ->
                 !canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings));
@@ -2049,13 +2033,12 @@
             int callingUid) {
         final ArrayList<InputMethodInfo> methodList;
         final InputMethodSettings settings;
-        if (userId == mSettings.getCurrentUserId()) {
-            methodList = mSettings.getEnabledInputMethodListLocked();
+        if (userId == mSettings.getUserId()) {
+            methodList = mSettings.getEnabledInputMethodList();
             settings = mSettings;
         } else {
-            final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
-            settings = new InputMethodSettings(methodMap, userId);
-            methodList = settings.getEnabledInputMethodListLocked();
+            settings = queryMethodMapForUser(userId);
+            methodList = settings.getEnabledInputMethodList();
         }
         // filter caller's access to input methods
         methodList.removeIf(imi ->
@@ -2114,31 +2097,30 @@
     @GuardedBy("ImfLock.class")
     private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId,
             boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) {
-        if (userId == mSettings.getCurrentUserId()) {
+        if (userId == mSettings.getUserId()) {
             final InputMethodInfo imi;
             String selectedMethodId = getSelectedMethodIdLocked();
             if (imiId == null && selectedMethodId != null) {
-                imi = mMethodMap.get(selectedMethodId);
+                imi = mSettings.getMethodMap().get(selectedMethodId);
             } else {
-                imi = mMethodMap.get(imiId);
+                imi = mSettings.getMethodMap().get(imiId);
             }
             if (imi == null || !canCallerAccessInputMethod(
                     imi.getPackageName(), callingUid, userId, mSettings)) {
                 return Collections.emptyList();
             }
-            return mSettings.getEnabledInputMethodSubtypeListLocked(
+            return mSettings.getEnabledInputMethodSubtypeList(
                     imi, allowsImplicitlyEnabledSubtypes);
         }
-        final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
-        final InputMethodInfo imi = methodMap.get(imiId);
+        final InputMethodSettings settings = queryMethodMapForUser(userId);
+        final InputMethodInfo imi = settings.getMethodMap().get(imiId);
         if (imi == null) {
             return Collections.emptyList();
         }
-        final InputMethodSettings settings = new InputMethodSettings(methodMap, userId);
         if (!canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings)) {
             return Collections.emptyList();
         }
-        return settings.getEnabledInputMethodSubtypeListLocked(
+        return settings.getEnabledInputMethodSubtypeList(
                 imi, allowsImplicitlyEnabledSubtypes);
     }
 
@@ -2169,58 +2151,49 @@
         // actually running.
         final int callerUid = Binder.getCallingUid();
         final int callerPid = Binder.getCallingPid();
-
-        // TODO(b/314150112): Move the death recipient logic to ClientController when moving
-        //     removeClient method.
-        final IBinder.DeathRecipient deathRecipient = () -> removeClient(client);
         final IInputMethodClientInvoker clientInvoker =
                 IInputMethodClientInvoker.create(client, mHandler);
         synchronized (ImfLock.class) {
             mClientController.addClient(clientInvoker, inputConnection, selfReportedDisplayId,
-                    deathRecipient, callerUid, callerPid);
+                    callerUid, callerPid);
         }
     }
 
-    // TODO(b/314150112): Move this to ClientController.
-    void removeClient(IInputMethodClient client) {
-        synchronized (ImfLock.class) {
-            ClientState cs = mClientController.mClients.remove(client.asBinder());
-            if (cs != null) {
-                client.asBinder().unlinkToDeath(cs.mClientDeathRecipient, 0 /* flags */);
-                clearClientSessionLocked(cs);
-                clearClientSessionForAccessibilityLocked(cs);
-
-                if (mCurClient == cs) {
-                    hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
-                            null /* resultReceiver */, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
-                    if (mBoundToMethod) {
-                        mBoundToMethod = false;
-                        IInputMethodInvoker curMethod = getCurMethodLocked();
-                        if (curMethod != null) {
-                            // When we unbind input, we are unbinding the client, so we always
-                            // unbind ime and a11y together.
-                            curMethod.unbindInput();
-                            AccessibilityManagerInternal.get().unbindInput();
-                        }
-                    }
-                    mBoundToAccessibility = false;
-                    mCurClient = null;
+    // TODO(b/314150112): Move this method to InputMethodBindingController
+    /**
+     * Hide the IME if the removed user is the current user.
+     */
+    @GuardedBy("ImfLock.class")
+    private void onClientRemoved(ClientState client) {
+        clearClientSessionLocked(client);
+        clearClientSessionForAccessibilityLocked(client);
+        if (mCurClient == client) {
+            hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+                    null /* resultReceiver */, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
+            if (mBoundToMethod) {
+                mBoundToMethod = false;
+                IInputMethodInvoker curMethod = getCurMethodLocked();
+                if (curMethod != null) {
+                    // When we unbind input, we are unbinding the client, so we always
+                    // unbind ime and a11y together.
+                    curMethod.unbindInput();
+                    AccessibilityManagerInternal.get().unbindInput();
                 }
-                if (mCurFocusedWindowClient == cs) {
-                    mCurFocusedWindowClient = null;
-                    mCurFocusedWindowEditorInfo = null;
-                }
+            }
+            mBoundToAccessibility = false;
+            mCurClient = null;
+            if (mCurFocusedWindowClient == client) {
+                mCurFocusedWindowClient = null;
+                mCurFocusedWindowEditorInfo = null;
             }
         }
     }
 
-    // TODO(b/314150112): Move this to ClientController.
     @GuardedBy("ImfLock.class")
     void unbindCurrentClientLocked(@UnbindReason int unbindClientReason) {
         if (mCurClient != null) {
             if (DEBUG) {
-                Slog.v(TAG, "unbindCurrentInputLocked: client="
-                        + mCurClient.mClient.asBinder());
+                Slog.v(TAG, "unbindCurrentInputLocked: client=" + mCurClient.mClient.asBinder());
             }
             if (mBoundToMethod) {
                 mBoundToMethod = false;
@@ -2310,10 +2283,11 @@
 
         final boolean restarting = !initial;
         final Binder startInputToken = new Binder();
-        final StartInputInfo info = new StartInputInfo(mSettings.getCurrentUserId(),
+        final StartInputInfo info = new StartInputInfo(mSettings.getUserId(),
                 getCurTokenLocked(),
                 mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting,
-                UserHandle.getUserId(mCurClient.mUid), mCurClient.mSelfReportedDisplayId,
+                UserHandle.getUserId(mCurClient.mUid),
+                mCurClient.mSelfReportedDisplayId,
                 mCurFocusedWindow, mCurEditorInfo, mCurFocusedWindowSoftInputMode,
                 getSequenceNumberLocked());
         mImeTargetWindowMap.put(startInputToken, mCurFocusedWindow);
@@ -2324,14 +2298,14 @@
         // same-user scenarios.
         // That said ignoring cross-user scenario will never affect IMEs that do not have
         // INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case.
-        if (mSettings.getCurrentUserId() == UserHandle.getUserId(mCurClient.mUid)) {
-            mPackageManagerInternal.grantImplicitAccess(mSettings.getCurrentUserId(),
+        if (mSettings.getUserId() == UserHandle.getUserId(
+                mCurClient.mUid)) {
+            mPackageManagerInternal.grantImplicitAccess(mSettings.getUserId(),
                     null /* intent */, UserHandle.getAppId(getCurMethodUidLocked()),
                     mCurClient.mUid, true /* direct */);
         }
 
-        @InputMethodNavButtonFlags
-        final int navButtonFlags = getInputMethodNavButtonFlagsLocked();
+        @InputMethodNavButtonFlags final int navButtonFlags = getInputMethodNavButtonFlagsLocked();
         final SessionState session = mCurClient.mCurSession;
         setEnabledSessionLocked(session);
         session.mMethod.startInput(startInputToken, mCurInputConnection, mCurEditorInfo, restarting,
@@ -2347,7 +2321,7 @@
         }
 
         String curId = getCurIdLocked();
-        final InputMethodInfo curInputMethodInfo = mMethodMap.get(curId);
+        final InputMethodInfo curInputMethodInfo = mSettings.getMethodMap().get(curId);
         final boolean suppressesSpellChecker =
                 curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
         final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
@@ -2411,16 +2385,6 @@
             @StartInputReason int startInputReason,
             int unverifiedTargetSdkVersion,
             @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
-        String selectedMethodId = getSelectedMethodIdLocked();
-
-        if (!mSystemReady) {
-            // If the system is not yet ready, we shouldn't be running third
-            // party code.
-            return new InputBindResult(
-                    InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
-                    null, null, null, selectedMethodId, getSequenceNumberLocked(), false);
-        }
-
         if (!InputMethodUtils.checkIfPackageBelongsToUid(mPackageManagerInternal, cs.mUid,
                 editorInfo.packageName)) {
             Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
@@ -2441,6 +2405,7 @@
 
         // Potentially override the selected input method if the new display belongs to a virtual
         // device with a custom IME.
+        String selectedMethodId = getSelectedMethodIdLocked();
         if (oldDisplayIdToShowIme != mDisplayIdToShowIme) {
             final String deviceMethodId = computeCurrentDeviceMethodIdLocked(selectedMethodId);
             if (deviceMethodId == null) {
@@ -2563,7 +2528,7 @@
                 mVirtualDeviceMethodMap.get(mDeviceIdToShowIme, currentMethodId);
         if (Objects.equals(deviceMethodId, currentMethodId)) {
             return currentMethodId;
-        } else if (!mMethodMap.containsKey(deviceMethodId)) {
+        } else if (!mSettings.getMethodMap().containsKey(deviceMethodId)) {
             if (DEBUG) {
                 Slog.v(TAG, "Disabling IME on virtual device with id " + mDeviceIdToShowIme
                         + " because its custom input method is not available: " + deviceMethodId);
@@ -2605,7 +2570,7 @@
         if (isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)) {
             return false;
         }
-        final InputMethodInfo imi = mMethodMap.get(selectedMethodId);
+        final InputMethodInfo imi = mSettings.getMethodMap().get(selectedMethodId);
         if (imi == null) {
             return false;
         }
@@ -2751,8 +2716,8 @@
                         && curMethod.asBinder() == method.asBinder()) {
                     if (mCurClient != null) {
                         clearClientSessionLocked(mCurClient);
-                        mCurClient.mCurSession = new SessionState(mCurClient,
-                                method, session, channel);
+                        mCurClient.mCurSession = new SessionState(
+                                mCurClient, method, session, channel);
                         InputBindResult res = attachNewInputLocked(
                                 StartInputReason.SESSION_CREATED_BY_IME, true);
                         attachNewAccessibilityLocked(StartInputReason.SESSION_CREATED_BY_IME, true);
@@ -2914,11 +2879,16 @@
     @GuardedBy("ImfLock.class")
     void clearClientSessionsLocked() {
         if (getCurMethodLocked() != null) {
-            final int numClients = mClientController.mClients.size();
-            for (int i = 0; i < numClients; ++i) {
-                clearClientSessionLocked(mClientController.mClients.valueAt(i));
-                clearClientSessionForAccessibilityLocked(mClientController.mClients.valueAt(i));
-            }
+            // TODO(b/322816970): Replace this with lambda.
+            mClientController.forAllClients(new Consumer<ClientState>() {
+
+                @GuardedBy("ImfLock.class")
+                @Override
+                public void accept(ClientState c) {
+                    clearClientSessionLocked(c);
+                    clearClientSessionForAccessibilityLocked(c);
+                }
+            });
 
             finishSessionLocked(mEnabledSession);
             for (int i = 0; i < mEnabledAccessibilitySessions.size(); i++) {
@@ -2948,7 +2918,7 @@
                 } else if (packageName != null) {
                     if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
                     final PackageManager userAwarePackageManager =
-                            getPackageManagerForUser(mContext, mSettings.getCurrentUserId());
+                            getPackageManagerForUser(mContext, mSettings.getUserId());
                     ApplicationInfo applicationInfo = null;
                     try {
                         applicationInfo = userAwarePackageManager.getApplicationInfo(packageName,
@@ -3010,7 +2980,7 @@
             return false;
         }
         if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
-                && mWindowManagerInternal.isKeyguardSecure(mSettings.getCurrentUserId())) {
+                && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId())) {
             return false;
         }
         if ((visibility & InputMethodService.IME_ACTIVE) == 0
@@ -3027,7 +2997,7 @@
             return false;
         }
 
-        List<InputMethodInfo> imes = mSettings.getEnabledInputMethodListWithFilterLocked(
+        List<InputMethodInfo> imes = mSettings.getEnabledInputMethodListWithFilter(
                 InputMethodInfo::shouldShowInInputMethodPicker);
         final int numImes = imes.size();
         if (numImes > 2) return true;
@@ -3039,7 +3009,7 @@
         for (int i = 0; i < numImes; ++i) {
             final InputMethodInfo imi = imes.get(i);
             final List<InputMethodSubtype> subtypes =
-                    mSettings.getEnabledInputMethodSubtypeListLocked(imi, true);
+                    mSettings.getEnabledInputMethodSubtypeList(imi, true);
             final int subtypeCount = subtypes.size();
             if (subtypeCount == 0) {
                 ++nonAuxCount;
@@ -3189,9 +3159,9 @@
     void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
         if (enabledMayChange) {
             final PackageManager userAwarePackageManager = getPackageManagerForUser(mContext,
-                    mSettings.getCurrentUserId());
+                    mSettings.getUserId());
 
-            List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
+            List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList();
             for (int i = 0; i < enabled.size(); i++) {
                 // We allow the user to select "disabled until used" apps, so if they
                 // are enabling one of those here we now need to make it enabled.
@@ -3237,18 +3207,18 @@
         }
 
         // TODO: Instantiate mSwitchingController for each user.
-        if (mSettings.getCurrentUserId() == mSwitchingController.getUserId()) {
-            mSwitchingController.resetCircularListLocked(mMethodMap);
+        if (mSettings.getUserId() == mSwitchingController.getUserId()) {
+            mSwitchingController.resetCircularListLocked(mSettings.getMethodMap());
         } else {
             mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
-                    mContext, mMethodMap, mSettings.getCurrentUserId());
+                    mContext, mSettings.getMethodMap(), mSettings.getUserId());
         }
         // TODO: Instantiate mHardwareKeyboardShortcutController for each user.
-        if (mSettings.getCurrentUserId() == mHardwareKeyboardShortcutController.getUserId()) {
-            mHardwareKeyboardShortcutController.reset(mMethodMap);
+        if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+            mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap());
         } else {
             mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
-                    mMethodMap, mSettings.getCurrentUserId());
+                    mSettings.getMethodMap(), mSettings.getUserId());
         }
         sendOnNavButtonFlagsChangedLocked();
     }
@@ -3272,14 +3242,14 @@
 
     @GuardedBy("ImfLock.class")
     void setInputMethodLocked(String id, int subtypeId, int deviceId) {
-        InputMethodInfo info = mMethodMap.get(id);
+        InputMethodInfo info = mSettings.getMethodMap().get(id);
         if (info == null) {
             throw getExceptionForUnknownImeId(id);
         }
 
         // See if we need to notify a subtype change within the same IME.
         if (id.equals(getSelectedMethodIdLocked())) {
-            final int userId = mSettings.getCurrentUserId();
+            final int userId = mSettings.getUserId();
             final int subtypeCount = info.getSubtypeCount();
             if (subtypeCount <= 0) {
                 notifyInputMethodSubtypeChangedLocked(userId, info, null);
@@ -3685,7 +3655,6 @@
                         + "specified for cross-user startInputOrWindowGainedFocus()");
             }
         }
-
         if (windowToken == null) {
             Slog.e(TAG, "windowToken cannot be null.");
             return InputBindResult.NULL;
@@ -3697,6 +3666,14 @@
                     "InputMethodManagerService#startInputOrWindowGainedFocus");
             final InputBindResult result;
             synchronized (ImfLock.class) {
+                if (!mSystemReady) {
+                    // If the system is not yet ready, we shouldn't be running third arty code.
+                    return new InputBindResult(
+                            InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
+                            null /* method */, null /* accessibilitySessions */, null /* channel */,
+                            getSelectedMethodIdLocked(), getSequenceNumberLocked(),
+                            false /* isInputMethodSuppressingSpellChecker */);
+                }
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
@@ -3756,9 +3733,9 @@
             return InputBindResult.INVALID_USER;
         }
 
-        final ClientState cs = mClientController.mClients.get(client.asBinder());
+        final ClientState cs = mClientController.getClient(client.asBinder());
         if (cs == null) {
-            throw new IllegalArgumentException("unknown client " + client.asBinder());
+            throw new IllegalArgumentException("Unknown client " + client.asBinder());
         }
 
         final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus(
@@ -3790,7 +3767,7 @@
                 return InputBindResult.USER_SWITCHING;
             }
             final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
-                    mSettings.getCurrentUserId(), false /* enabledOnly */);
+                    mSettings.getUserId(), false /* enabledOnly */);
             for (int profileId : profileIdsWithDisabled) {
                 if (profileId == userId) {
                     scheduleSwitchUserTaskLocked(userId, cs.mClient);
@@ -3809,7 +3786,7 @@
             mVisibilityStateComputer.mShowForced = false;
         }
 
-        final int currentUserId = mSettings.getCurrentUserId();
+        final int currentUserId = mSettings.getUserId();
         if (userId != currentUserId) {
             if (ArrayUtils.contains(
                     mUserManagerInternal.getProfileIds(currentUserId, false), userId)) {
@@ -3930,8 +3907,7 @@
             // We need to check if this is the current client with
             // focus in the window manager, to allow this call to
             // be made before input is started in it.
-            final ClientState cs =
-                    mClientController.mClients.get(client.asBinder());
+            final ClientState cs = mClientController.getClient(client.asBinder());
             if (cs == null) {
                 ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
                 throw new IllegalArgumentException("unknown client " + client.asBinder());
@@ -3953,7 +3929,7 @@
                 && mCurFocusedWindowClient.mClient.asBinder() == client.asBinder()) {
             return true;
         }
-        if (mSettings.getCurrentUserId() != UserHandle.getUserId(uid)) {
+        if (mSettings.getUserId() != UserHandle.getUserId(uid)) {
             return false;
         }
         if (getCurIntentLocked() != null && InputMethodUtils.checkIfPackageBelongsToUid(
@@ -4021,7 +3997,7 @@
             if (!calledWithValidTokenLocked(token)) {
                 return;
             }
-            final InputMethodInfo imi = mMethodMap.get(id);
+            final InputMethodInfo imi = mSettings.getMethodMap().get(id);
             if (imi == null || !canCallerAccessInputMethod(
                     imi.getPackageName(), callingUid, userId, mSettings)) {
                 throw getExceptionForUnknownImeId(id);
@@ -4039,7 +4015,7 @@
             if (!calledWithValidTokenLocked(token)) {
                 return;
             }
-            final InputMethodInfo imi = mMethodMap.get(id);
+            final InputMethodInfo imi = mSettings.getMethodMap().get(id);
             if (imi == null || !canCallerAccessInputMethod(
                     imi.getPackageName(), callingUid, userId, mSettings)) {
                 throw getExceptionForUnknownImeId(id);
@@ -4059,10 +4035,10 @@
             if (!calledWithValidTokenLocked(token)) {
                 return false;
             }
-            final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
+            final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtype();
             final InputMethodInfo lastImi;
             if (lastIme != null) {
-                lastImi = mMethodMap.get(lastIme.first);
+                lastImi = mSettings.getMethodMap().get(lastIme.first);
             } else {
                 lastImi = null;
             }
@@ -4086,7 +4062,7 @@
                 // This is a safety net. If the currentSubtype can't be added to the history
                 // and the framework couldn't find the last ime, we will make the last ime be
                 // the most applicable enabled keyboard subtype of the system imes.
-                final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
+                final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList();
                 if (enabled != null) {
                     final int enabledCount = enabled.size();
                     final String locale;
@@ -4094,14 +4070,13 @@
                             && !TextUtils.isEmpty(mCurrentSubtype.getLocale())) {
                         locale = mCurrentSubtype.getLocale();
                     } else {
-                        locale = SystemLocaleWrapper.get(mSettings.getCurrentUserId()).get(0)
-                                .toString();
+                        locale = SystemLocaleWrapper.get(mSettings.getUserId()).get(0).toString();
                     }
                     for (int i = 0; i < enabledCount; ++i) {
                         final InputMethodInfo imi = enabled.get(i);
                         if (imi.getSubtypeCount() > 0 && imi.isSystem()) {
                             InputMethodSubtype keyboardSubtype =
-                                    SubtypeUtils.findLastResortApplicableSubtypeLocked(
+                                    SubtypeUtils.findLastResortApplicableSubtype(
                                             SubtypeUtils.getSubtypes(imi),
                                             SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
                             if (keyboardSubtype != null) {
@@ -4143,7 +4118,8 @@
     @GuardedBy("ImfLock.class")
     private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme) {
         final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
-                onlyCurrentIme, mMethodMap.get(getSelectedMethodIdLocked()), mCurrentSubtype);
+                onlyCurrentIme, mSettings.getMethodMap().get(getSelectedMethodIdLocked()),
+                mCurrentSubtype);
         if (nextSubtype == null) {
             return false;
         }
@@ -4159,8 +4135,8 @@
                 return false;
             }
             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
-                    false /* onlyCurrentIme */, mMethodMap.get(getSelectedMethodIdLocked()),
-                    mCurrentSubtype);
+                    false /* onlyCurrentIme */,
+                    mSettings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype);
             return nextSubtype != null;
         }
     }
@@ -4172,13 +4148,12 @@
                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         }
         synchronized (ImfLock.class) {
-            if (mSettings.getCurrentUserId() == userId) {
-                return mSettings.getLastInputMethodSubtypeLocked();
+            if (mSettings.getUserId() == userId) {
+                return mSettings.getLastInputMethodSubtype();
             }
 
-            final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
-            final InputMethodSettings settings = new InputMethodSettings(methodMap, userId);
-            return settings.getLastInputMethodSubtypeLocked();
+            final InputMethodSettings settings = queryMethodMapForUser(userId);
+            return settings.getLastInputMethodSubtype();
         }
     }
 
@@ -4208,7 +4183,7 @@
                 return;
             }
 
-            if (mSettings.getCurrentUserId() == userId) {
+            if (mSettings.getUserId() == userId) {
                 if (!mSettings.setAdditionalInputMethodSubtypes(imiId, toBeAdded,
                         mAdditionalSubtypeMap, mPackageManagerInternal, callingUid)) {
                     return;
@@ -4222,14 +4197,11 @@
                 return;
             }
 
-            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-            final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
             final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
                     new ArrayMap<>();
             AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
-            queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
-                    methodList, DirectBootAwareness.AUTO);
-            final InputMethodSettings settings = new InputMethodSettings(methodMap, userId);
+            final InputMethodSettings settings = queryInputMethodServicesInternal(mContext, userId,
+                    additionalSubtypeMap, DirectBootAwareness.AUTO);
             settings.setAdditionalInputMethodSubtypes(imiId, toBeAdded, additionalSubtypeMap,
                     mPackageManagerInternal, callingUid);
         }
@@ -4255,10 +4227,9 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (ImfLock.class) {
-                final boolean currentUser = (mSettings.getCurrentUserId() == userId);
+                final boolean currentUser = (mSettings.getUserId() == userId);
                 final InputMethodSettings settings = currentUser
-                        ? mSettings
-                        : new InputMethodSettings(queryMethodMapForUser(userId), userId);
+                        ? mSettings : queryMethodMapForUser(userId);
                 if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
                     return;
                 }
@@ -4293,10 +4264,6 @@
             synchronized (ImfLock.class) {
                 if (!canInteractWithImeLocked(callingUid, client,
                         "getInputMethodWindowVisibleHeight", null /* statsToken */)) {
-                    if (!mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.get(callingUid)) {
-                        EventLog.writeEvent(0x534e4554, "204906124", callingUid, "");
-                        mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.put(callingUid, true);
-                    }
                     return 0;
                 }
                 // This should probably use the caller's display id, but because this is unsupported
@@ -4551,16 +4518,17 @@
     @Override
     public void startImeTrace() {
         super.startImeTrace_enforcePermission();
-
         ImeTracing.getInstance().startTrace(null /* printwriter */);
-        ArrayMap<IBinder, ClientState> clients;
         synchronized (ImfLock.class) {
-            clients = new ArrayMap<>(mClientController.mClients);
-        }
-        for (ClientState state : clients.values()) {
-            if (state != null) {
-                state.mClient.setImeTraceEnabled(true /* enabled */);
-            }
+            // TODO(b/322816970): Replace this with lambda.
+            mClientController.forAllClients(new Consumer<ClientState>() {
+
+                @GuardedBy("ImfLock.class")
+                @Override
+                public void accept(ClientState c) {
+                    c.mClient.setImeTraceEnabled(true /* enabled */);
+                }
+            });
         }
     }
 
@@ -4571,14 +4539,16 @@
         super.stopImeTrace_enforcePermission();
 
         ImeTracing.getInstance().stopTrace(null /* printwriter */);
-        ArrayMap<IBinder, ClientState> clients;
         synchronized (ImfLock.class) {
-            clients = new ArrayMap<>(mClientController.mClients);
-        }
-        for (ClientState state : clients.values()) {
-            if (state != null) {
-                state.mClient.setImeTraceEnabled(false /* enabled */);
-            }
+            // TODO(b/322816970): Replace this with lambda.
+            mClientController.forAllClients(new Consumer<ClientState>() {
+
+                @GuardedBy("ImfLock.class")
+                @Override
+                public void accept(ClientState c) {
+                    c.mClient.setImeTraceEnabled(false /* enabled */);
+                }
+            });
         }
     }
 
@@ -4627,10 +4597,11 @@
                 }
                 return;
             }
-            if (mSettings.getCurrentUserId() != mSwitchingController.getUserId()) {
+            if (mSettings.getUserId() != mSwitchingController.getUserId()) {
                 return;
             }
-            final InputMethodInfo imi = mMethodMap.get(getSelectedMethodIdLocked());
+            final InputMethodInfo imi =
+                    mSettings.getMethodMap().get(getSelectedMethodIdLocked());
             if (imi != null) {
                 mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
             }
@@ -4688,8 +4659,8 @@
             return;
         } else {
             // Called with current IME's token.
-            if (mMethodMap.get(id) != null
-                    && mSettings.getEnabledInputMethodListWithFilterLocked(
+            if (mSettings.getMethodMap().get(id) != null
+                    && mSettings.getEnabledInputMethodListWithFilter(
                             (info) -> info.getId().equals(id)).isEmpty()) {
                 throw new IllegalStateException("Requested IME is not enabled: " + id);
             }
@@ -4861,16 +4832,15 @@
                 }
                 synchronized (ImfLock.class) {
                     final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked()
-                            && mWindowManagerInternal.isKeyguardSecure(
-                                    mSettings.getCurrentUserId());
+                            && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId());
                     final String lastInputMethodId = mSettings.getSelectedInputMethod();
                     int lastInputMethodSubtypeId =
                             mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
 
                     final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController
                             .getSortedInputMethodAndSubtypeList(
-                                    showAuxSubtypes, isScreenLocked, false, mContext,
-                                    mMethodMap, mSettings.getCurrentUserId());
+                                    showAuxSubtypes, isScreenLocked, true /* forImeMenu */,
+                                    mContext, mSettings.getMethodMap(), mSettings.getUserId());
                     mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId,
                             lastInputMethodId, lastInputMethodSubtypeId, imList);
                 }
@@ -5054,7 +5024,7 @@
     @GuardedBy("ImfLock.class")
     private boolean chooseNewDefaultIMELocked() {
         final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME(
-                mSettings.getEnabledInputMethodListLocked());
+                mSettings.getEnabledInputMethodList());
         if (imi != null) {
             if (DEBUG) {
                 Slog.d(TAG, "New default IME was selected: " + imi.getId());
@@ -5066,17 +5036,14 @@
         return false;
     }
 
-    static void queryInputMethodServicesInternal(Context context,
+    @NonNull
+    static InputMethodSettings queryInputMethodServicesInternal(Context context,
             @UserIdInt int userId, ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
-            ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
             @DirectBootAwareness int directBootAwareness) {
         final Context userAwareContext = context.getUserId() == userId
                 ? context
                 : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
 
-        methodList.clear();
-        methodMap.clear();
-
         final int directBootAwarenessFlags;
         switch (directBootAwareness) {
             case DirectBootAwareness.ANY:
@@ -5099,24 +5066,23 @@
                 new Intent(InputMethod.SERVICE_INTERFACE),
                 PackageManager.ResolveInfoFlags.of(flags));
 
-        methodList.ensureCapacity(services.size());
-        methodMap.ensureCapacity(services.size());
-
         // Note: This is a temporary solution for Bug 261723412.  If there is any better solution,
         // we should remove this data dependency.
         final List<String> enabledInputMethodList =
                 InputMethodUtils.getEnabledInputMethodIdsForFiltering(context, userId);
 
-        filterInputMethodServices(additionalSubtypeMap, methodMap, methodList,
-                enabledInputMethodList, userAwareContext, services);
+        final InputMethodMap methodMap = filterInputMethodServices(
+                additionalSubtypeMap, enabledInputMethodList, userAwareContext, services);
+        return InputMethodSettings.create(methodMap, userId);
     }
 
-    static void filterInputMethodServices(
+    @NonNull
+    static InputMethodMap filterInputMethodServices(
             ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
-            ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
             List<String> enabledInputMethodList, Context userAwareContext,
             List<ResolveInfo> services) {
         final ArrayMap<String, Integer> imiPackageCount = new ArrayMap<>();
+        final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(services.size());
 
         for (int i = 0; i < services.size(); ++i) {
             ResolveInfo ri = services.get(i);
@@ -5145,7 +5111,6 @@
                     imiPackageCount.put(packageName,
                             1 + imiPackageCount.getOrDefault(packageName, 0));
 
-                    methodList.add(imi);
                     methodMap.put(imi.getId(), imi);
                     if (DEBUG) {
                         Slog.d(TAG, "Found an input method " + imi);
@@ -5157,6 +5122,7 @@
                 Slog.wtf(TAG, "Unable to load input method " + imeId, e);
             }
         }
+        return InputMethodMap.of(methodMap);
     }
 
     @GuardedBy("ImfLock.class")
@@ -5172,8 +5138,8 @@
         mMethodMapUpdateCount++;
         mMyPackageMonitor.clearKnownImePackageNamesLocked();
 
-        queryInputMethodServicesInternal(mContext, mSettings.getCurrentUserId(),
-                mAdditionalSubtypeMap, mMethodMap, mMethodList, DirectBootAwareness.AUTO);
+        mSettings = queryInputMethodServicesInternal(mContext, mSettings.getUserId(),
+                mAdditionalSubtypeMap, DirectBootAwareness.AUTO);
 
         // Construct the set of possible IME packages for onPackageChanged() to avoid false
         // negatives when the package state remains to be the same but only the component state is
@@ -5185,7 +5151,7 @@
             final List<ResolveInfo> allInputMethodServices =
                     mContext.getPackageManager().queryIntentServicesAsUser(
                             new Intent(InputMethod.SERVICE_INTERFACE),
-                            PackageManager.MATCH_DISABLED_COMPONENTS, mSettings.getCurrentUserId());
+                            PackageManager.MATCH_DISABLED_COMPONENTS, mSettings.getUserId());
             final int numImes = allInputMethodServices.size();
             for (int i = 0; i < numImes; ++i) {
                 final ServiceInfo si = allInputMethodServices.get(i).serviceInfo;
@@ -5200,11 +5166,11 @@
         if (!resetDefaultEnabledIme) {
             boolean enabledImeFound = false;
             boolean enabledNonAuxImeFound = false;
-            final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodListLocked();
+            final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodList();
             final int numImes = enabledImes.size();
             for (int i = 0; i < numImes; ++i) {
                 final InputMethodInfo imi = enabledImes.get(i);
-                if (mMethodList.contains(imi)) {
+                if (mSettings.getMethodMap().containsKey(imi.getId())) {
                     enabledImeFound = true;
                     if (!imi.isAuxiliaryIme()) {
                         enabledNonAuxImeFound = true;
@@ -5228,7 +5194,7 @@
 
         if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) {
             final ArrayList<InputMethodInfo> defaultEnabledIme =
-                    InputMethodInfoUtils.getDefaultEnabledImes(mContext, mMethodList,
+                    InputMethodInfoUtils.getDefaultEnabledImes(mContext, mSettings.getMethodList(),
                             reenableMinimumNonAuxSystemImes);
             final int numImes = defaultEnabledIme.size();
             for (int i = 0; i < numImes; ++i) {
@@ -5242,7 +5208,7 @@
 
         final String defaultImiId = mSettings.getSelectedInputMethod();
         if (!TextUtils.isEmpty(defaultImiId)) {
-            if (!mMethodMap.containsKey(defaultImiId)) {
+            if (!mSettings.getMethodMap().containsKey(defaultImiId)) {
                 Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
                 if (chooseNewDefaultIMELocked()) {
                     updateInputMethodsFromSettingsLocked(true);
@@ -5256,26 +5222,26 @@
         updateDefaultVoiceImeIfNeededLocked();
 
         // TODO: Instantiate mSwitchingController for each user.
-        if (mSettings.getCurrentUserId() == mSwitchingController.getUserId()) {
-            mSwitchingController.resetCircularListLocked(mMethodMap);
+        if (mSettings.getUserId() == mSwitchingController.getUserId()) {
+            mSwitchingController.resetCircularListLocked(mSettings.getMethodMap());
         } else {
             mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
-                    mContext, mMethodMap, mSettings.getCurrentUserId());
+                    mContext, mSettings.getMethodMap(), mSettings.getUserId());
         }
         // TODO: Instantiate mHardwareKeyboardShortcutController for each user.
-        if (mSettings.getCurrentUserId() == mHardwareKeyboardShortcutController.getUserId()) {
-            mHardwareKeyboardShortcutController.reset(mMethodMap);
+        if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+            mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap());
         } else {
             mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
-                    mMethodMap, mSettings.getCurrentUserId());
+                    mSettings.getMethodMap(), mSettings.getUserId());
         }
 
         sendOnNavButtonFlagsChangedLocked();
 
         // Notify InputMethodListListeners of the new installed InputMethods.
-        final List<InputMethodInfo> inputMethodList = new ArrayList<>(mMethodList);
+        final List<InputMethodInfo> inputMethodList = mSettings.getMethodList();
         mHandler.obtainMessage(MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED,
-                mSettings.getCurrentUserId(), 0 /* unused */, inputMethodList).sendToTarget();
+                mSettings.getUserId(), 0 /* unused */, inputMethodList).sendToTarget();
     }
 
     @GuardedBy("ImfLock.class")
@@ -5294,7 +5260,7 @@
                 mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer);
         final String currentDefaultVoiceImeId = mSettings.getDefaultVoiceInputMethod();
         final InputMethodInfo newSystemVoiceIme = InputMethodInfoUtils.chooseSystemVoiceIme(
-                mMethodMap, systemSpeechRecognizer, currentDefaultVoiceImeId);
+                mSettings.getMethodMap(), systemSpeechRecognizer, currentDefaultVoiceImeId);
         if (newSystemVoiceIme == null) {
             if (DEBUG) {
                 Slog.i(TAG, "Found no valid default Voice IME. If the user is still locked,"
@@ -5345,9 +5311,9 @@
             return false;
         } else {
             final List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
-                    .getEnabledInputMethodsAndSubtypeListLocked();
+                    .getEnabledInputMethodsAndSubtypeList();
             StringBuilder builder = new StringBuilder();
-            if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+            if (mSettings.buildAndPutEnabledInputMethodsStrRemovingId(
                     builder, enabledInputMethodsList, id)) {
                 if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
                     // Disabled input method is currently selected, switch to another one.
@@ -5361,7 +5327,7 @@
                     // new default one but only update the settings.
                     InputMethodInfo newDefaultIme =
                             InputMethodInfoUtils.getMostApplicableDefaultIME(
-                                        mSettings.getEnabledInputMethodListLocked());
+                                        mSettings.getEnabledInputMethodList());
                     mSettings.putSelectedDefaultDeviceInputMethod(
                             newDefaultIme == null ? "" : newDefaultIme.getId());
                 }
@@ -5396,7 +5362,7 @@
                 mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
             }
         }
-        notifyInputMethodSubtypeChangedLocked(mSettings.getCurrentUserId(), imi, mCurrentSubtype);
+        notifyInputMethodSubtypeChangedLocked(mSettings.getUserId(), imi, mCurrentSubtype);
 
         if (!setSubtypeOnly) {
             // Set InputMethod here
@@ -5406,11 +5372,11 @@
 
     @GuardedBy("ImfLock.class")
     private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
-        InputMethodInfo imi = mMethodMap.get(newDefaultIme);
+        InputMethodInfo imi = mSettings.getMethodMap().get(newDefaultIme);
         int lastSubtypeId = NOT_A_SUBTYPE_ID;
         // newDefaultIme is empty when there is no candidate for the selected IME.
         if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
-            String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
+            String subtypeHashCode = mSettings.getLastSubtypeForInputMethod(newDefaultIme);
             if (subtypeHashCode != null) {
                 try {
                     lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi,
@@ -5437,12 +5403,11 @@
                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         }
         synchronized (ImfLock.class) {
-            if (mSettings.getCurrentUserId() == userId) {
+            if (mSettings.getUserId() == userId) {
                 return getCurrentInputMethodSubtypeLocked();
             }
 
-            final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
-            final InputMethodSettings settings = new InputMethodSettings(methodMap, userId);
+            final InputMethodSettings settings = queryMethodMapForUser(userId);
             return settings.getCurrentInputMethodSubtypeForNonCurrentUsers();
         }
     }
@@ -5464,7 +5429,7 @@
             return null;
         }
         final boolean subtypeIsSelected = mSettings.isSubtypeSelected();
-        final InputMethodInfo imi = mMethodMap.get(selectedMethodId);
+        final InputMethodInfo imi = mSettings.getMethodMap().get(selectedMethodId);
         if (imi == null || imi.getSubtypeCount() == 0) {
             return null;
         }
@@ -5476,19 +5441,19 @@
                 // the most applicable subtype from explicitly or implicitly enabled
                 // subtypes.
                 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
-                        mSettings.getEnabledInputMethodSubtypeListLocked(imi, true);
+                        mSettings.getEnabledInputMethodSubtypeList(imi, true);
                 // If there is only one explicitly or implicitly enabled subtype,
                 // just returns it.
                 if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
                     mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
                 } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
-                    final String locale = SystemLocaleWrapper.get(mSettings.getCurrentUserId())
+                    final String locale = SystemLocaleWrapper.get(mSettings.getUserId())
                             .get(0).toString();
-                    mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
+                    mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtype(
                             explicitlyOrImplicitlyEnabledSubtypes,
                             SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
                     if (mCurrentSubtype == null) {
-                        mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
+                        mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtype(
                                 explicitlyOrImplicitlyEnabledSubtypes, null, locale, true);
                     }
                 }
@@ -5505,46 +5470,42 @@
      */
     @GuardedBy("ImfLock.class")
     private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) {
-        if (userId == mSettings.getCurrentUserId()) {
-            return mMethodMap.get(mSettings.getSelectedInputMethod());
+        final InputMethodSettings settings;
+        if (userId == mSettings.getUserId()) {
+            settings = mSettings;
+        } else {
+            final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
+                    new ArrayMap<>();
+            AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+            settings = queryInputMethodServicesInternal(mContext, userId,
+                    additionalSubtypeMap, DirectBootAwareness.AUTO);
         }
-
-        final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-        final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
-        final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = new ArrayMap<>();
-        AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
-        queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
-                methodList, DirectBootAwareness.AUTO);
-        InputMethodSettings settings = new InputMethodSettings(methodMap, userId);
-        return methodMap.get(settings.getSelectedInputMethod());
+        return settings.getMethodMap().get(settings.getSelectedInputMethod());
     }
 
-    private ArrayMap<String, InputMethodInfo> queryMethodMapForUser(@UserIdInt int userId) {
-        final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-        final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
+    private InputMethodSettings queryMethodMapForUser(@UserIdInt int userId) {
         final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
                 new ArrayMap<>();
         AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
-        queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
-                methodMap, methodList, DirectBootAwareness.AUTO);
-        return methodMap;
+        return queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
+                DirectBootAwareness.AUTO);
     }
 
     @GuardedBy("ImfLock.class")
     private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) {
-        if (userId == mSettings.getCurrentUserId()) {
-            if (!mMethodMap.containsKey(imeId)
-                    || !mSettings.getEnabledInputMethodListLocked()
-                    .contains(mMethodMap.get(imeId))) {
+        if (userId == mSettings.getUserId()) {
+            if (!mSettings.getMethodMap().containsKey(imeId)
+                    || !mSettings.getEnabledInputMethodList()
+                    .contains(mSettings.getMethodMap().get(imeId))) {
                 return false; // IME is not found or not enabled.
             }
             setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
             return true;
         }
-        final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
-        final InputMethodSettings settings = new InputMethodSettings(methodMap, userId);
-        if (!methodMap.containsKey(imeId)
-                || !settings.getEnabledInputMethodListLocked().contains(methodMap.get(imeId))) {
+        final InputMethodSettings settings = queryMethodMapForUser(userId);
+        if (!settings.getMethodMap().containsKey(imeId)
+                || !settings.getEnabledInputMethodList().contains(
+                        settings.getMethodMap().get(imeId))) {
             return false; // IME is not found or not enabled.
         }
         settings.putSelectedInputMethod(imeId);
@@ -5582,7 +5543,8 @@
 
     @GuardedBy("ImfLock.class")
     private void switchKeyboardLayoutLocked(int direction) {
-        final InputMethodInfo currentImi = mMethodMap.get(getSelectedMethodIdLocked());
+        final InputMethodInfo currentImi = mSettings.getMethodMap().get(
+                getSelectedMethodIdLocked());
         if (currentImi == null) {
             return;
         }
@@ -5594,7 +5556,7 @@
         if (nextSubtypeHandle == null) {
             return;
         }
-        final InputMethodInfo nextImi = mMethodMap.get(nextSubtypeHandle.getImeId());
+        final InputMethodInfo nextImi = mSettings.getMethodMap().get(nextSubtypeHandle.getImeId());
         if (nextImi == null) {
             return;
         }
@@ -5673,16 +5635,15 @@
         @Override
         public boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) {
             synchronized (ImfLock.class) {
-                if (userId == mSettings.getCurrentUserId()) {
-                    if (!mMethodMap.containsKey(imeId)) {
+                if (userId == mSettings.getUserId()) {
+                    if (!mSettings.getMethodMap().containsKey(imeId)) {
                         return false; // IME is not found.
                     }
                     setInputMethodEnabledLocked(imeId, enabled);
                     return true;
                 }
-                final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
-                final InputMethodSettings settings = new InputMethodSettings(methodMap, userId);
-                if (!methodMap.containsKey(imeId)) {
+                final InputMethodSettings settings = queryMethodMapForUser(userId);
+                if (!settings.getMethodMap().containsKey(imeId)) {
                     return false; // IME is not found.
                 }
                 if (enabled) {
@@ -5693,9 +5654,9 @@
                         settings.putEnabledInputMethodsStr(newEnabledImeIdsStr);
                     }
                 } else {
-                    settings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+                    settings.buildAndPutEnabledInputMethodsStrRemovingId(
                             new StringBuilder(),
-                            settings.getEnabledInputMethodsAndSubtypeListLocked(), imeId);
+                            settings.getEnabledInputMethodsAndSubtypeList(), imeId);
                 }
                 return true;
             }
@@ -5777,8 +5738,10 @@
                 // TODO(b/305829876): Implement user ID verification
                 if (mCurClient != null) {
                     clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId);
-                    mCurClient.mAccessibilitySessions.put(accessibilityConnectionId,
-                            new AccessibilitySessionState(mCurClient, accessibilityConnectionId,
+                    mCurClient.mAccessibilitySessions.put(
+                            accessibilityConnectionId,
+                            new AccessibilitySessionState(mCurClient,
+                                    accessibilityConnectionId,
                                     session));
 
                     attachNewAccessibilityLocked(StartInputReason.SESSION_CREATED_BY_ACCESSIBILITY,
@@ -5812,17 +5775,22 @@
                     }
                     // A11yManagerService unbinds the disabled accessibility service. We don't need
                     // to do it here.
-                    mCurClient.mClient.onUnbindAccessibilityService(getSequenceNumberLocked(),
+                    mCurClient.mClient.onUnbindAccessibilityService(
+                            getSequenceNumberLocked(),
                             accessibilityConnectionId);
                 }
                 // We only have sessions when we bound to an input method. Remove this session
                 // from all clients.
                 if (getCurMethodLocked() != null) {
-                    final int numClients = mClientController.mClients.size();
-                    for (int i = 0; i < numClients; ++i) {
-                        clearClientSessionForAccessibilityLocked(
-                                mClientController.mClients.valueAt(i), accessibilityConnectionId);
-                    }
+                    // TODO(b/322816970): Replace this with lambda.
+                    mClientController.forAllClients(new Consumer<ClientState>() {
+
+                        @GuardedBy("ImfLock.class")
+                        @Override
+                        public void accept(ClientState c) {
+                            clearClientSessionForAccessibilityLocked(c, accessibilityConnectionId);
+                        }
+                    });
                     AccessibilitySessionState session = mEnabledAccessibilitySessions.get(
                             accessibilityConnectionId);
                     if (session != null) {
@@ -5998,26 +5966,34 @@
 
         synchronized (ImfLock.class) {
             p.println("Current Input Method Manager state:");
-            int numImes = mMethodList.size();
+            final List<InputMethodInfo> methodList = mSettings.getMethodList();
+            int numImes = methodList.size();
             p.println("  Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount);
             for (int i = 0; i < numImes; i++) {
-                InputMethodInfo info = mMethodList.get(i);
+                InputMethodInfo info = methodList.get(i);
                 p.println("  InputMethod #" + i + ":");
                 info.dump(p, "    ");
             }
+            // Dump ClientController#mClients
             p.println("  ClientStates:");
-            // TODO(b/314150112): move client related dump info to ClientController#dump
-            final int numClients = mClientController.mClients.size();
-            for (int i = 0; i < numClients; ++i) {
-                final ClientState ci = mClientController.mClients.valueAt(i);
-                p.println("  " + ci + ":");
-                p.println("    client=" + ci.mClient);
-                p.println("    fallbackInputConnection=" + ci.mFallbackInputConnection);
-                p.println("    sessionRequested=" + ci.mSessionRequested);
-                p.println("    sessionRequestedForAccessibility="
-                        + ci.mSessionRequestedForAccessibility);
-                p.println("    curSession=" + ci.mCurSession);
-            }
+            // TODO(b/322816970): Replace this with lambda.
+            mClientController.forAllClients(new Consumer<ClientState>() {
+
+                @GuardedBy("ImfLock.class")
+                @Override
+                public void accept(ClientState c) {
+                    p.println("  " + c + ":");
+                    p.println("    client=" + c.mClient);
+                    p.println("    fallbackInputConnection="
+                            + c.mFallbackInputConnection);
+                    p.println("    sessionRequested="
+                            + c.mSessionRequested);
+                    p.println(
+                            "    sessionRequestedForAccessibility="
+                                    + c.mSessionRequestedForAccessibility);
+                    p.println("    curSession=" + c.mCurSession);
+                }
+            });
             p.println("  mCurMethodId=" + getSelectedMethodIdLocked());
             client = mCurClient;
             p.println("  mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked());
@@ -6048,7 +6024,7 @@
             p.println("  mSwitchingController:");
             mSwitchingController.dump(p);
             p.println("  mSettings:");
-            mSettings.dumpLocked(p, "    ");
+            mSettings.dump(p, "    ");
 
             p.println("  mStartInputHistory:");
             mStartInputHistory.dump(pw, "    ");
@@ -6312,7 +6288,7 @@
         }
         synchronized (ImfLock.class) {
             final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
-                    mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter());
+                    mSettings.getUserId(), shellCommand.getErrPrintWriter());
             try (PrintWriter pr = shellCommand.getOutPrintWriter()) {
                 for (int userId : userIds) {
                     final List<InputMethodInfo> methods = all
@@ -6357,7 +6333,7 @@
              PrintWriter error = shellCommand.getErrPrintWriter()) {
             synchronized (ImfLock.class) {
                 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
-                        mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter());
+                        mSettings.getUserId(), shellCommand.getErrPrintWriter());
                 for (int userId : userIds) {
                     if (!userHasDebugPriv(userId, shellCommand)) {
                         continue;
@@ -6416,17 +6392,16 @@
             PrintWriter error) {
         boolean failedToEnableUnknownIme = false;
         boolean previouslyEnabled = false;
-        if (userId == mSettings.getCurrentUserId()) {
-            if (enabled && !mMethodMap.containsKey(imeId)) {
+        if (userId == mSettings.getUserId()) {
+            if (enabled && !mSettings.getMethodMap().containsKey(imeId)) {
                 failedToEnableUnknownIme = true;
             } else {
                 previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled);
             }
         } else {
-            final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
-            final InputMethodSettings settings = new InputMethodSettings(methodMap, userId);
+            final InputMethodSettings settings = queryMethodMapForUser(userId);
             if (enabled) {
-                if (!methodMap.containsKey(imeId)) {
+                if (!settings.getMethodMap().containsKey(imeId)) {
                     failedToEnableUnknownIme = true;
                 } else {
                     final String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
@@ -6439,9 +6414,9 @@
                 }
             } else {
                 previouslyEnabled =
-                        settings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+                        settings.buildAndPutEnabledInputMethodsStrRemovingId(
                                 new StringBuilder(),
-                                settings.getEnabledInputMethodsAndSubtypeListLocked(), imeId);
+                                settings.getEnabledInputMethodsAndSubtypeList(), imeId);
             }
         }
         if (failedToEnableUnknownIme) {
@@ -6479,7 +6454,7 @@
              PrintWriter error = shellCommand.getErrPrintWriter()) {
             synchronized (ImfLock.class) {
                 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
-                        mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter());
+                        mSettings.getUserId(), shellCommand.getErrPrintWriter());
                 for (int userId : userIds) {
                     if (!userHasDebugPriv(userId, shellCommand)) {
                         continue;
@@ -6519,7 +6494,7 @@
         synchronized (ImfLock.class) {
             try (PrintWriter out = shellCommand.getOutPrintWriter()) {
                 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
-                        mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter());
+                        mSettings.getUserId(), shellCommand.getErrPrintWriter());
                 for (int userId : userIds) {
                     if (!userHasDebugPriv(userId, shellCommand)) {
                         continue;
@@ -6531,16 +6506,16 @@
                     }
                     final String nextIme;
                     final List<InputMethodInfo> nextEnabledImes;
-                    if (userId == mSettings.getCurrentUserId()) {
+                    if (userId == mSettings.getUserId()) {
                         hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
                                 0 /* flags */, null /* resultReceiver */,
                                 SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
                         mBindingController.unbindCurrentMethod();
 
                         // Enable default IMEs, disable others
-                        var toDisable = mSettings.getEnabledInputMethodListLocked();
+                        var toDisable = mSettings.getEnabledInputMethodList();
                         var defaultEnabled = InputMethodInfoUtils.getDefaultEnabledImes(
-                                mContext, mMethodList);
+                                mContext, mSettings.getMethodList());
                         toDisable.removeAll(defaultEnabled);
                         for (InputMethodInfo info : toDisable) {
                             setInputMethodEnabledLocked(info.getId(), false);
@@ -6554,23 +6529,19 @@
                         }
                         updateInputMethodsFromSettingsLocked(true /* enabledMayChange */);
                         InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
-                                getPackageManagerForUser(mContext, mSettings.getCurrentUserId()),
-                                mSettings.getEnabledInputMethodListLocked());
+                                getPackageManagerForUser(mContext, mSettings.getUserId()),
+                                mSettings.getEnabledInputMethodList());
                         nextIme = mSettings.getSelectedInputMethod();
-                        nextEnabledImes = mSettings.getEnabledInputMethodListLocked();
+                        nextEnabledImes = mSettings.getEnabledInputMethodList();
                     } else {
-                        final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-                        final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
                         final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
                                 new ArrayMap<>();
                         AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
-                        queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
-                                methodMap, methodList, DirectBootAwareness.AUTO);
-                        final InputMethodSettings settings = new InputMethodSettings(
-                                methodMap, userId);
+                        final InputMethodSettings settings = queryInputMethodServicesInternal(
+                                mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO);
 
                         nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext,
-                                methodList);
+                                settings.getMethodList());
                         nextIme = InputMethodInfoUtils.getMostApplicableDefaultIME(
                                 nextEnabledImes).getId();
 
@@ -6626,14 +6597,16 @@
             }
         }
         boolean isImeTraceEnabled = ImeTracing.getInstance().isEnabled();
-        ArrayMap<IBinder, ClientState> clients;
         synchronized (ImfLock.class) {
-            clients = new ArrayMap<>(mClientController.mClients);
-        }
-        for (ClientState state : clients.values()) {
-            if (state != null) {
-                state.mClient.setImeTraceEnabled(isImeTraceEnabled);
-            }
+            // TODO(b/322816970): Replace this with lambda.
+            mClientController.forAllClients(new Consumer<ClientState>() {
+
+                @GuardedBy("ImfLock.class")
+                @Override
+                public void accept(ClientState c) {
+                    c.mClient.setImeTraceEnabled(isImeTraceEnabled);
+                }
+            });
         }
         return ShellCommandResult.SUCCESS;
     }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMap.java b/services/core/java/com/android/server/inputmethod/InputMethodMap.java
new file mode 100644
index 0000000..a8e5e2e
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMap.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.view.inputmethod.InputMethodInfo;
+
+import java.util.List;
+
+/**
+ * A map from IME ID to {@link InputMethodInfo}, which is guaranteed to be immutable thus
+ * thread-safe.
+ */
+final class InputMethodMap {
+    private static final ArrayMap<String, InputMethodInfo> EMPTY_MAP =
+            new ArrayMap<>();
+
+    private final ArrayMap<String, InputMethodInfo> mMap;
+
+    static InputMethodMap emptyMap() {
+        return new InputMethodMap(EMPTY_MAP);
+    }
+
+    static InputMethodMap of(@NonNull ArrayMap<String, InputMethodInfo> map) {
+        return new InputMethodMap(map);
+    }
+
+    private InputMethodMap(@NonNull ArrayMap<String, InputMethodInfo> map) {
+        mMap = map.isEmpty() ? EMPTY_MAP : new ArrayMap<>(map);
+    }
+
+    @AnyThread
+    @Nullable
+    InputMethodInfo get(@Nullable String imeId) {
+        return mMap.get(imeId);
+    }
+
+    @AnyThread
+    @NonNull
+    List<InputMethodInfo> values() {
+        return List.copyOf(mMap.values());
+    }
+
+    @AnyThread
+    @Nullable
+    InputMethodInfo valueAt(int index) {
+        return mMap.valueAt(index);
+    }
+
+    @AnyThread
+    boolean containsKey(@Nullable String imeId) {
+        return mMap.containsKey(imeId);
+    }
+
+    @AnyThread
+    @IntRange(from = 0)
+    int size() {
+        return mMap.size();
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
new file mode 100644
index 0000000..a51002b
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -0,0 +1,715 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.PackageManagerInternal;
+import android.os.LocaleList;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.IntArray;
+import android.util.Pair;
+import android.util.Printer;
+import android.util.Slog;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Predicate;
+
+/**
+ * Utility class for putting and getting settings for InputMethod.
+ *
+ * <p>This is used in two ways:</p>
+ * <ul>
+ *     <li>Singleton instance in {@link InputMethodManagerService}, which is updated on
+ *     user-switch to follow the current user.</li>
+ *     <li>On-demand instances when we need settings for non-current users.</li>
+ * </ul>
+ */
+final class InputMethodSettings {
+    public static final boolean DEBUG = false;
+    private static final String TAG = "InputMethodSettings";
+
+    private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
+    private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
+    private static final char INPUT_METHOD_SEPARATOR = InputMethodUtils.INPUT_METHOD_SEPARATOR;
+    private static final char INPUT_METHOD_SUBTYPE_SEPARATOR =
+            InputMethodUtils.INPUT_METHOD_SUBTYPE_SEPARATOR;
+
+    private final InputMethodMap mMethodMap;
+    private final List<InputMethodInfo> mMethodList;
+
+    @UserIdInt
+    private final int mUserId;
+
+    private static void buildEnabledInputMethodsSettingString(
+            StringBuilder builder, Pair<String, ArrayList<String>> ime) {
+        builder.append(ime.first);
+        // Inputmethod and subtypes are saved in the settings as follows:
+        // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
+        for (int i = 0; i < ime.second.size(); ++i) {
+            final String subtypeId = ime.second.get(i);
+            builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeId);
+        }
+    }
+
+    static InputMethodSettings createEmptyMap(@UserIdInt int userId) {
+        return new InputMethodSettings(InputMethodMap.emptyMap(), userId);
+    }
+
+    static InputMethodSettings create(InputMethodMap methodMap, @UserIdInt int userId) {
+        return new InputMethodSettings(methodMap, userId);
+    }
+
+    private InputMethodSettings(InputMethodMap methodMap, @UserIdInt int userId) {
+        mMethodMap = methodMap;
+        mMethodList = methodMap.values();
+        mUserId = userId;
+        String ime = getSelectedInputMethod();
+        String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
+        if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
+            putSelectedInputMethod(defaultDeviceIme);
+            putSelectedDefaultDeviceInputMethod(null);
+        }
+    }
+
+    @AnyThread
+    @NonNull
+    InputMethodMap getMethodMap() {
+        return mMethodMap;
+    }
+
+    @AnyThread
+    @NonNull
+    List<InputMethodInfo> getMethodList() {
+        return mMethodList;
+    }
+
+    private void putString(@NonNull String key, @Nullable String str) {
+        SecureSettingsWrapper.putString(key, str, mUserId);
+    }
+
+    @Nullable
+    private String getString(@NonNull String key, @Nullable String defaultValue) {
+        return SecureSettingsWrapper.getString(key, defaultValue, mUserId);
+    }
+
+    private void putInt(String key, int value) {
+        SecureSettingsWrapper.putInt(key, value, mUserId);
+    }
+
+    private int getInt(String key, int defaultValue) {
+        return SecureSettingsWrapper.getInt(key, defaultValue, mUserId);
+    }
+
+    ArrayList<InputMethodInfo> getEnabledInputMethodList() {
+        return getEnabledInputMethodListWithFilter(null /* matchingCondition */);
+    }
+
+    @NonNull
+    ArrayList<InputMethodInfo> getEnabledInputMethodListWithFilter(
+            @Nullable Predicate<InputMethodInfo> matchingCondition) {
+        return createEnabledInputMethodList(
+                getEnabledInputMethodsAndSubtypeList(), matchingCondition);
+    }
+
+    List<InputMethodSubtype> getEnabledInputMethodSubtypeList(
+            InputMethodInfo imi, boolean allowsImplicitlyEnabledSubtypes) {
+        List<InputMethodSubtype> enabledSubtypes =
+                getEnabledInputMethodSubtypeList(imi);
+        if (allowsImplicitlyEnabledSubtypes && enabledSubtypes.isEmpty()) {
+            enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypes(
+                    SystemLocaleWrapper.get(mUserId), imi);
+        }
+        return InputMethodSubtype.sort(imi, enabledSubtypes);
+    }
+
+    List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi) {
+        List<Pair<String, ArrayList<String>>> imsList =
+                getEnabledInputMethodsAndSubtypeList();
+        ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>();
+        if (imi != null) {
+            for (int i = 0; i < imsList.size(); ++i) {
+                final Pair<String, ArrayList<String>> imsPair = imsList.get(i);
+                final InputMethodInfo info = mMethodMap.get(imsPair.first);
+                if (info != null && info.getId().equals(imi.getId())) {
+                    final int subtypeCount = info.getSubtypeCount();
+                    for (int j = 0; j < subtypeCount; ++j) {
+                        final InputMethodSubtype ims = info.getSubtypeAt(j);
+                        for (int k = 0; k < imsPair.second.size(); ++k) {
+                            final String s = imsPair.second.get(k);
+                            if (String.valueOf(ims.hashCode()).equals(s)) {
+                                enabledSubtypes.add(ims);
+                            }
+                        }
+                    }
+                    break;
+                }
+            }
+        }
+        return enabledSubtypes;
+    }
+
+    List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeList() {
+        final String enabledInputMethodsStr = getEnabledInputMethodsStr();
+        final TextUtils.SimpleStringSplitter inputMethodSplitter =
+                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+        final TextUtils.SimpleStringSplitter subtypeSplitter =
+                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+        final ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>();
+        if (TextUtils.isEmpty(enabledInputMethodsStr)) {
+            return imsList;
+        }
+        inputMethodSplitter.setString(enabledInputMethodsStr);
+        while (inputMethodSplitter.hasNext()) {
+            String nextImsStr = inputMethodSplitter.next();
+            subtypeSplitter.setString(nextImsStr);
+            if (subtypeSplitter.hasNext()) {
+                ArrayList<String> subtypeHashes = new ArrayList<>();
+                // The first element is ime id.
+                String imeId = subtypeSplitter.next();
+                while (subtypeSplitter.hasNext()) {
+                    subtypeHashes.add(subtypeSplitter.next());
+                }
+                imsList.add(new Pair<>(imeId, subtypeHashes));
+            }
+        }
+        return imsList;
+    }
+
+    /**
+     * Build and put a string of EnabledInputMethods with removing specified Id.
+     *
+     * @return the specified id was removed or not.
+     */
+    boolean buildAndPutEnabledInputMethodsStrRemovingId(
+            StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
+        boolean isRemoved = false;
+        boolean needsAppendSeparator = false;
+        for (int i = 0; i < imsList.size(); ++i) {
+            final Pair<String, ArrayList<String>> ims = imsList.get(i);
+            final String curId = ims.first;
+            if (curId.equals(id)) {
+                // We are disabling this input method, and it is
+                // currently enabled.  Skip it to remove from the
+                // new list.
+                isRemoved = true;
+            } else {
+                if (needsAppendSeparator) {
+                    builder.append(INPUT_METHOD_SEPARATOR);
+                } else {
+                    needsAppendSeparator = true;
+                }
+                buildEnabledInputMethodsSettingString(builder, ims);
+            }
+        }
+        if (isRemoved) {
+            // Update the setting with the new list of input methods.
+            putEnabledInputMethodsStr(builder.toString());
+        }
+        return isRemoved;
+    }
+
+    private ArrayList<InputMethodInfo> createEnabledInputMethodList(
+            List<Pair<String, ArrayList<String>>> imsList,
+            Predicate<InputMethodInfo> matchingCondition) {
+        final ArrayList<InputMethodInfo> res = new ArrayList<>();
+        for (int i = 0; i < imsList.size(); ++i) {
+            final Pair<String, ArrayList<String>> ims = imsList.get(i);
+            final InputMethodInfo info = mMethodMap.get(ims.first);
+            if (info != null && !info.isVrOnly()
+                    && (matchingCondition == null || matchingCondition.test(info))) {
+                res.add(info);
+            }
+        }
+        return res;
+    }
+
+    void putEnabledInputMethodsStr(@Nullable String str) {
+        if (DEBUG) {
+            Slog.d(TAG, "putEnabledInputMethodStr: " + str);
+        }
+        if (TextUtils.isEmpty(str)) {
+            // OK to coalesce to null, since getEnabledInputMethodsStr() can take care of the
+            // empty data scenario.
+            putString(Settings.Secure.ENABLED_INPUT_METHODS, null);
+        } else {
+            putString(Settings.Secure.ENABLED_INPUT_METHODS, str);
+        }
+    }
+
+    @NonNull
+    String getEnabledInputMethodsStr() {
+        return getString(Settings.Secure.ENABLED_INPUT_METHODS, "");
+    }
+
+    private void saveSubtypeHistory(
+            List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
+        final StringBuilder builder = new StringBuilder();
+        boolean isImeAdded = false;
+        if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
+            builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
+                    newSubtypeId);
+            isImeAdded = true;
+        }
+        for (int i = 0; i < savedImes.size(); ++i) {
+            final Pair<String, String> ime = savedImes.get(i);
+            final String imeId = ime.first;
+            String subtypeId = ime.second;
+            if (TextUtils.isEmpty(subtypeId)) {
+                subtypeId = NOT_A_SUBTYPE_ID_STR;
+            }
+            if (isImeAdded) {
+                builder.append(INPUT_METHOD_SEPARATOR);
+            } else {
+                isImeAdded = true;
+            }
+            builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
+                    subtypeId);
+        }
+        // Remove the last INPUT_METHOD_SEPARATOR
+        putSubtypeHistoryStr(builder.toString());
+    }
+
+    private void addSubtypeToHistory(String imeId, String subtypeId) {
+        final List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistory();
+        for (int i = 0; i < subtypeHistory.size(); ++i) {
+            final Pair<String, String> ime = subtypeHistory.get(i);
+            if (ime.first.equals(imeId)) {
+                if (DEBUG) {
+                    Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
+                            + ime.second);
+                }
+                // We should break here
+                subtypeHistory.remove(ime);
+                break;
+            }
+        }
+        if (DEBUG) {
+            Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
+        }
+        saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
+    }
+
+    private void putSubtypeHistoryStr(@NonNull String str) {
+        if (DEBUG) {
+            Slog.d(TAG, "putSubtypeHistoryStr: " + str);
+        }
+        if (TextUtils.isEmpty(str)) {
+            // OK to coalesce to null, since getSubtypeHistoryStr() can take care of the empty
+            // data scenario.
+            putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, null);
+        } else {
+            putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
+        }
+    }
+
+    Pair<String, String> getLastInputMethodAndSubtype() {
+        // Gets the first one from the history
+        return getLastSubtypeForInputMethodInternal(null);
+    }
+
+    @Nullable
+    InputMethodSubtype getLastInputMethodSubtype() {
+        final Pair<String, String> lastIme = getLastInputMethodAndSubtype();
+        // TODO: Handle the case of the last IME with no subtypes
+        if (lastIme == null || TextUtils.isEmpty(lastIme.first)
+                || TextUtils.isEmpty(lastIme.second)) {
+            return null;
+        }
+        final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
+        if (lastImi == null) return null;
+        try {
+            final int lastSubtypeHash = Integer.parseInt(lastIme.second);
+            final int lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi,
+                    lastSubtypeHash);
+            if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
+                return null;
+            }
+            return lastImi.getSubtypeAt(lastSubtypeId);
+        } catch (NumberFormatException e) {
+            return null;
+        }
+    }
+
+    String getLastSubtypeForInputMethod(String imeId) {
+        Pair<String, String> ime = getLastSubtypeForInputMethodInternal(imeId);
+        if (ime != null) {
+            return ime.second;
+        } else {
+            return null;
+        }
+    }
+
+    private Pair<String, String> getLastSubtypeForInputMethodInternal(String imeId) {
+        final List<Pair<String, ArrayList<String>>> enabledImes =
+                getEnabledInputMethodsAndSubtypeList();
+        final List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistory();
+        for (int i = 0; i < subtypeHistory.size(); ++i) {
+            final Pair<String, String> imeAndSubtype = subtypeHistory.get(i);
+            final String imeInTheHistory = imeAndSubtype.first;
+            // If imeId is empty, returns the first IME and subtype in the history
+            if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
+                final String subtypeInTheHistory = imeAndSubtype.second;
+                final String subtypeHashCode =
+                        getEnabledSubtypeHashCodeForInputMethodAndSubtype(
+                                enabledImes, imeInTheHistory, subtypeInTheHistory);
+                if (!TextUtils.isEmpty(subtypeHashCode)) {
+                    if (DEBUG) {
+                        Slog.d(TAG,
+                                "Enabled subtype found in the history: " + subtypeHashCode);
+                    }
+                    return new Pair<>(imeInTheHistory, subtypeHashCode);
+                }
+            }
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "No enabled IME found in the history");
+        }
+        return null;
+    }
+
+    private String getEnabledSubtypeHashCodeForInputMethodAndSubtype(List<Pair<String,
+            ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
+        final LocaleList localeList = SystemLocaleWrapper.get(mUserId);
+        for (int i = 0; i < enabledImes.size(); ++i) {
+            final Pair<String, ArrayList<String>> enabledIme = enabledImes.get(i);
+            if (enabledIme.first.equals(imeId)) {
+                final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
+                final InputMethodInfo imi = mMethodMap.get(imeId);
+                if (explicitlyEnabledSubtypes.isEmpty()) {
+                    // If there are no explicitly enabled subtypes, applicable subtypes are
+                    // enabled implicitly.
+                    // If IME is enabled and no subtypes are enabled, applicable subtypes
+                    // are enabled implicitly, so needs to treat them to be enabled.
+                    if (imi != null && imi.getSubtypeCount() > 0) {
+                        List<InputMethodSubtype> implicitlyEnabledSubtypes =
+                                SubtypeUtils.getImplicitlyApplicableSubtypes(localeList,
+                                        imi);
+                        final int numSubtypes = implicitlyEnabledSubtypes.size();
+                        for (int j = 0; j < numSubtypes; ++j) {
+                            final InputMethodSubtype st = implicitlyEnabledSubtypes.get(j);
+                            if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
+                                return subtypeHashCode;
+                            }
+                        }
+                    }
+                } else {
+                    for (int j = 0; j < explicitlyEnabledSubtypes.size(); ++j) {
+                        final String s = explicitlyEnabledSubtypes.get(j);
+                        if (s.equals(subtypeHashCode)) {
+                            // If both imeId and subtypeId are enabled, return subtypeId.
+                            try {
+                                final int hashCode = Integer.parseInt(subtypeHashCode);
+                                // Check whether the subtype id is valid or not
+                                if (SubtypeUtils.isValidSubtypeId(imi, hashCode)) {
+                                    return s;
+                                } else {
+                                    return NOT_A_SUBTYPE_ID_STR;
+                                }
+                            } catch (NumberFormatException e) {
+                                return NOT_A_SUBTYPE_ID_STR;
+                            }
+                        }
+                    }
+                }
+                // If imeId was enabled but subtypeId was disabled.
+                return NOT_A_SUBTYPE_ID_STR;
+            }
+        }
+        // If both imeId and subtypeId are disabled, return null
+        return null;
+    }
+
+    private List<Pair<String, String>> loadInputMethodAndSubtypeHistory() {
+        ArrayList<Pair<String, String>> imsList = new ArrayList<>();
+        final String subtypeHistoryStr = getSubtypeHistoryStr();
+        if (TextUtils.isEmpty(subtypeHistoryStr)) {
+            return imsList;
+        }
+        final TextUtils.SimpleStringSplitter inputMethodSplitter =
+                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+        final TextUtils.SimpleStringSplitter subtypeSplitter =
+                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+        inputMethodSplitter.setString(subtypeHistoryStr);
+        while (inputMethodSplitter.hasNext()) {
+            String nextImsStr = inputMethodSplitter.next();
+            subtypeSplitter.setString(nextImsStr);
+            if (subtypeSplitter.hasNext()) {
+                String subtypeId = NOT_A_SUBTYPE_ID_STR;
+                // The first element is ime id.
+                String imeId = subtypeSplitter.next();
+                while (subtypeSplitter.hasNext()) {
+                    subtypeId = subtypeSplitter.next();
+                    break;
+                }
+                imsList.add(new Pair<>(imeId, subtypeId));
+            }
+        }
+        return imsList;
+    }
+
+    @NonNull
+    private String getSubtypeHistoryStr() {
+        final String history = getString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, "");
+        if (DEBUG) {
+            Slog.d(TAG, "getSubtypeHistoryStr: " + history);
+        }
+        return history;
+    }
+
+    void putSelectedInputMethod(String imeId) {
+        if (DEBUG) {
+            Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", " + mUserId);
+        }
+        putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
+    }
+
+    void putSelectedSubtype(int subtypeId) {
+        if (DEBUG) {
+            Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", " + mUserId);
+        }
+        putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
+    }
+
+    @Nullable
+    String getSelectedInputMethod() {
+        final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null);
+        if (DEBUG) {
+            Slog.d(TAG, "getSelectedInputMethodStr: " + imi);
+        }
+        return imi;
+    }
+
+    @Nullable
+    String getSelectedDefaultDeviceInputMethod() {
+        final String imi = getString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null);
+        if (DEBUG) {
+            Slog.d(TAG, "getSelectedDefaultDeviceInputMethodStr: " + imi + ", " + mUserId);
+        }
+        return imi;
+    }
+
+    void putSelectedDefaultDeviceInputMethod(String imeId) {
+        if (DEBUG) {
+            Slog.d(TAG, "putSelectedDefaultDeviceInputMethodStr: " + imeId + ", " + mUserId);
+        }
+        putString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, imeId);
+    }
+
+    void putDefaultVoiceInputMethod(String imeId) {
+        if (DEBUG) {
+            Slog.d(TAG, "putDefaultVoiceInputMethodStr: " + imeId + ", " + mUserId);
+        }
+        putString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, imeId);
+    }
+
+    @Nullable
+    String getDefaultVoiceInputMethod() {
+        final String imi = getString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, null);
+        if (DEBUG) {
+            Slog.d(TAG, "getDefaultVoiceInputMethodStr: " + imi);
+        }
+        return imi;
+    }
+
+    boolean isSubtypeSelected() {
+        return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
+    }
+
+    private int getSelectedInputMethodSubtypeHashCode() {
+        return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
+                NOT_A_SUBTYPE_ID);
+    }
+
+    @UserIdInt
+    public int getUserId() {
+        return mUserId;
+    }
+
+    int getSelectedInputMethodSubtypeId(String selectedImiId) {
+        final InputMethodInfo imi = mMethodMap.get(selectedImiId);
+        if (imi == null) {
+            return NOT_A_SUBTYPE_ID;
+        }
+        final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
+        return SubtypeUtils.getSubtypeIdFromHashCode(imi, subtypeHashCode);
+    }
+
+    void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId,
+            InputMethodSubtype currentSubtype) {
+        String subtypeId = NOT_A_SUBTYPE_ID_STR;
+        if (currentSubtype != null) {
+            subtypeId = String.valueOf(currentSubtype.hashCode());
+        }
+        if (InputMethodUtils.canAddToLastInputMethod(currentSubtype)) {
+            addSubtypeToHistory(curMethodId, subtypeId);
+        }
+    }
+
+    /**
+     * A variant of {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()} for
+     * non-current users.
+     *
+     * <p>TODO: Address code duplication between this and
+     * {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()}.</p>
+     *
+     * @return {@link InputMethodSubtype} if exists. {@code null} otherwise.
+     */
+    @Nullable
+    InputMethodSubtype getCurrentInputMethodSubtypeForNonCurrentUsers() {
+        final String selectedMethodId = getSelectedInputMethod();
+        if (selectedMethodId == null) {
+            return null;
+        }
+        final InputMethodInfo imi = mMethodMap.get(selectedMethodId);
+        if (imi == null || imi.getSubtypeCount() == 0) {
+            return null;
+        }
+
+        final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
+        if (subtypeHashCode != NOT_A_SUBTYPE_ID) {
+            final int subtypeIndex = SubtypeUtils.getSubtypeIdFromHashCode(imi,
+                    subtypeHashCode);
+            if (subtypeIndex >= 0) {
+                return imi.getSubtypeAt(subtypeIndex);
+            }
+        }
+
+        // If there are no selected subtypes, the framework will try to find the most applicable
+        // subtype from explicitly or implicitly enabled subtypes.
+        final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
+                getEnabledInputMethodSubtypeList(imi, true);
+        // If there is only one explicitly or implicitly enabled subtype, just returns it.
+        if (explicitlyOrImplicitlyEnabledSubtypes.isEmpty()) {
+            return null;
+        }
+        if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
+            return explicitlyOrImplicitlyEnabledSubtypes.get(0);
+        }
+        final String locale = SystemLocaleWrapper.get(mUserId).get(0).toString();
+        final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtype(
+                explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD,
+                locale, true);
+        if (subtype != null) {
+            return subtype;
+        }
+        return SubtypeUtils.findLastResortApplicableSubtype(
+                explicitlyOrImplicitlyEnabledSubtypes, null, locale, true);
+    }
+
+    boolean setAdditionalInputMethodSubtypes(@NonNull String imeId,
+            @NonNull ArrayList<InputMethodSubtype> subtypes,
+            @NonNull ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
+            @NonNull PackageManagerInternal packageManagerInternal, int callingUid) {
+        final InputMethodInfo imi = mMethodMap.get(imeId);
+        if (imi == null) {
+            return false;
+        }
+        if (!InputMethodUtils.checkIfPackageBelongsToUid(packageManagerInternal, callingUid,
+                imi.getPackageName())) {
+            return false;
+        }
+
+        if (subtypes.isEmpty()) {
+            additionalSubtypeMap.remove(imi.getId());
+        } else {
+            additionalSubtypeMap.put(imi.getId(), subtypes);
+        }
+        AdditionalSubtypeUtils.save(additionalSubtypeMap, mMethodMap, getUserId());
+        return true;
+    }
+
+    boolean setEnabledInputMethodSubtypes(@NonNull String imeId,
+            @NonNull int[] subtypeHashCodes) {
+        final InputMethodInfo imi = mMethodMap.get(imeId);
+        if (imi == null) {
+            return false;
+        }
+
+        final IntArray validSubtypeHashCodes = new IntArray(subtypeHashCodes.length);
+        for (int subtypeHashCode : subtypeHashCodes) {
+            if (subtypeHashCode == NOT_A_SUBTYPE_ID) {
+                continue;  // NOT_A_SUBTYPE_ID must not be saved
+            }
+            if (!SubtypeUtils.isValidSubtypeId(imi, subtypeHashCode)) {
+                continue;  // this subtype does not exist in InputMethodInfo.
+            }
+            if (validSubtypeHashCodes.indexOf(subtypeHashCode) >= 0) {
+                continue;  // The entry is already added.  No need to add anymore.
+            }
+            validSubtypeHashCodes.add(subtypeHashCode);
+        }
+
+        final String originalEnabledImesString = getEnabledInputMethodsStr();
+        final String updatedEnabledImesString = updateEnabledImeString(
+                originalEnabledImesString, imi.getId(), validSubtypeHashCodes);
+        if (TextUtils.equals(originalEnabledImesString, updatedEnabledImesString)) {
+            return false;
+        }
+
+        putEnabledInputMethodsStr(updatedEnabledImesString);
+        return true;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    static String updateEnabledImeString(@NonNull String enabledImesString,
+            @NonNull String imeId, @NonNull IntArray enabledSubtypeHashCodes) {
+        final TextUtils.SimpleStringSplitter imeSplitter =
+                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+        final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
+                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+
+        final StringBuilder sb = new StringBuilder();
+
+        imeSplitter.setString(enabledImesString);
+        boolean needsImeSeparator = false;
+        while (imeSplitter.hasNext()) {
+            final String nextImsStr = imeSplitter.next();
+            imeSubtypeSplitter.setString(nextImsStr);
+            if (imeSubtypeSplitter.hasNext()) {
+                if (needsImeSeparator) {
+                    sb.append(INPUT_METHOD_SEPARATOR);
+                }
+                if (TextUtils.equals(imeId, imeSubtypeSplitter.next())) {
+                    sb.append(imeId);
+                    for (int i = 0; i < enabledSubtypeHashCodes.size(); ++i) {
+                        sb.append(INPUT_METHOD_SUBTYPE_SEPARATOR);
+                        sb.append(enabledSubtypeHashCodes.get(i));
+                    }
+                } else {
+                    sb.append(nextImsStr);
+                }
+                needsImeSeparator = true;
+            }
+        }
+        return sb.toString();
+    }
+
+    void dump(final Printer pw, final String prefix) {
+        pw.println(prefix + "mUserId=" + mUserId);
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 4439b06..1379d16 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -23,7 +23,6 @@
 import android.content.Context;
 import android.os.UserHandle;
 import android.text.TextUtils;
-import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Printer;
 import android.util.Slog;
@@ -31,7 +30,6 @@
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -159,17 +157,17 @@
 
     static List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(
             boolean includeAuxiliarySubtypes, boolean isScreenLocked, boolean forImeMenu,
-            @NonNull Context context, @NonNull ArrayMap<String, InputMethodInfo> methodMap,
+            @NonNull Context context, @NonNull InputMethodMap methodMap,
             @UserIdInt int userId) {
         final Context userAwareContext = context.getUserId() == userId
                 ? context
                 : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
         final String mSystemLocaleStr = SystemLocaleWrapper.get(userId).get(0).toLanguageTag();
-        final InputMethodSettings settings = new InputMethodSettings(methodMap, userId);
+        final InputMethodSettings settings = InputMethodSettings.create(methodMap, userId);
 
-        final ArrayList<InputMethodInfo> imis = settings.getEnabledInputMethodListLocked();
+        final ArrayList<InputMethodInfo> imis = settings.getEnabledInputMethodList();
         if (imis.isEmpty()) {
-            return Collections.emptyList();
+            return new ArrayList<>();
         }
         if (isScreenLocked && includeAuxiliarySubtypes) {
             if (DEBUG) {
@@ -185,7 +183,7 @@
                 continue;
             }
             final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList =
-                    settings.getEnabledInputMethodSubtypeListLocked(imi, true);
+                    settings.getEnabledInputMethodSubtypeList(imi, true);
             final ArraySet<String> enabledSubtypeSet = new ArraySet<>();
             for (InputMethodSubtype subtype : explicitlyOrImplicitlyEnabledSubtypeList) {
                 enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
@@ -480,7 +478,7 @@
     private ControllerImpl mController;
 
     private InputMethodSubtypeSwitchingController(@NonNull Context context,
-            @NonNull ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
+            @NonNull InputMethodMap methodMap, @UserIdInt int userId) {
         mContext = context;
         mUserId = userId;
         mController = ControllerImpl.createFrom(null,
@@ -492,7 +490,7 @@
     @NonNull
     public static InputMethodSubtypeSwitchingController createInstanceLocked(
             @NonNull Context context,
-            @NonNull ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
+            @NonNull InputMethodMap methodMap, @UserIdInt int userId) {
         return new InputMethodSubtypeSwitchingController(context, methodMap, userId);
     }
 
@@ -512,8 +510,7 @@
         mController.onUserActionLocked(imi, subtype);
     }
 
-    public void resetCircularListLocked(
-            @NonNull ArrayMap<String, InputMethodInfo> methodMap) {
+    public void resetCircularListLocked(@NonNull InputMethodMap methodMap) {
         mController = ControllerImpl.createFrom(mController,
                 getSortedInputMethodAndSubtypeList(
                         false /* includeAuxiliarySubtypes */, false /* isScreenLocked */,
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index fb57c09..361cdbb 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -28,15 +28,10 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.os.Build;
-import android.os.LocaleList;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
-import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.IntArray;
-import android.util.Pair;
-import android.util.Printer;
 import android.util.Slog;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
@@ -51,10 +46,8 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Objects;
 import java.util.StringJoiner;
 import java.util.function.Consumer;
-import java.util.function.Predicate;
 
 /**
  * This class provides random static utility methods for {@link InputMethodManagerService} and its
@@ -68,12 +61,11 @@
     public static final boolean DEBUG = false;
     static final int NOT_A_SUBTYPE_ID = -1;
     private static final String TAG = "InputMethodUtils";
-    private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
 
     // The string for enabled input method is saved as follows:
     // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
-    private static final char INPUT_METHOD_SEPARATOR = ':';
-    private static final char INPUT_METHOD_SUBTYPE_SEPARATOR = ';';
+    static final char INPUT_METHOD_SEPARATOR = ':';
+    static final char INPUT_METHOD_SUBTYPE_SEPARATOR = ';';
 
     private InputMethodUtils() {
         // This utility class is not publicly instantiable.
@@ -200,648 +192,6 @@
             UserHandle.getUserId(uid));
     }
 
-    /**
-     * Utility class for putting and getting settings for InputMethod.
-     *
-     * This is used in two ways:
-     * - Singleton instance in {@link InputMethodManagerService}, which is updated on user-switch to
-     * follow the current user.
-     * - On-demand instances when we need settings for non-current users.
-     *
-     * TODO: Move all putters and getters of settings to this class.
-     */
-    @UserHandleAware
-    public static class InputMethodSettings {
-        private final ArrayMap<String, InputMethodInfo> mMethodMap;
-
-        @UserIdInt
-        private final int mCurrentUserId;
-
-        private static void buildEnabledInputMethodsSettingString(
-                StringBuilder builder, Pair<String, ArrayList<String>> ime) {
-            builder.append(ime.first);
-            // Inputmethod and subtypes are saved in the settings as follows:
-            // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
-            for (String subtypeId: ime.second) {
-                builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeId);
-            }
-        }
-
-        InputMethodSettings(ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
-            mMethodMap = methodMap;
-            mCurrentUserId = userId;
-            String ime = getSelectedInputMethod();
-            String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
-            if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
-                putSelectedInputMethod(defaultDeviceIme);
-                putSelectedDefaultDeviceInputMethod(null);
-            }
-        }
-
-        private void putString(@NonNull String key, @Nullable String str) {
-            SecureSettingsWrapper.putString(key, str, mCurrentUserId);
-        }
-
-        @Nullable
-        private String getString(@NonNull String key, @Nullable String defaultValue) {
-            return SecureSettingsWrapper.getString(key, defaultValue, mCurrentUserId);
-        }
-
-        private void putInt(String key, int value) {
-            SecureSettingsWrapper.putInt(key, value, mCurrentUserId);
-        }
-
-        private int getInt(String key, int defaultValue) {
-            return SecureSettingsWrapper.getInt(key, defaultValue, mCurrentUserId);
-        }
-
-        private void putBoolean(String key, boolean value) {
-            SecureSettingsWrapper.putBoolean(key, value, mCurrentUserId);
-        }
-
-        private boolean getBoolean(String key, boolean defaultValue) {
-            return SecureSettingsWrapper.getBoolean(key, defaultValue, mCurrentUserId);
-        }
-
-        ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
-            return getEnabledInputMethodListWithFilterLocked(null /* matchingCondition */);
-        }
-
-        @NonNull
-        ArrayList<InputMethodInfo> getEnabledInputMethodListWithFilterLocked(
-                @Nullable Predicate<InputMethodInfo> matchingCondition) {
-            return createEnabledInputMethodListLocked(
-                    getEnabledInputMethodsAndSubtypeListLocked(), matchingCondition);
-        }
-
-        List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
-                InputMethodInfo imi, boolean allowsImplicitlyEnabledSubtypes) {
-            List<InputMethodSubtype> enabledSubtypes =
-                    getEnabledInputMethodSubtypeListLocked(imi);
-            if (allowsImplicitlyEnabledSubtypes && enabledSubtypes.isEmpty()) {
-                enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                        SystemLocaleWrapper.get(mCurrentUserId), imi);
-            }
-            return InputMethodSubtype.sort(imi, enabledSubtypes);
-        }
-
-        List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi) {
-            List<Pair<String, ArrayList<String>>> imsList =
-                    getEnabledInputMethodsAndSubtypeListLocked();
-            ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>();
-            if (imi != null) {
-                for (Pair<String, ArrayList<String>> imsPair : imsList) {
-                    InputMethodInfo info = mMethodMap.get(imsPair.first);
-                    if (info != null && info.getId().equals(imi.getId())) {
-                        final int subtypeCount = info.getSubtypeCount();
-                        for (int i = 0; i < subtypeCount; ++i) {
-                            InputMethodSubtype ims = info.getSubtypeAt(i);
-                            for (String s: imsPair.second) {
-                                if (String.valueOf(ims.hashCode()).equals(s)) {
-                                    enabledSubtypes.add(ims);
-                                }
-                            }
-                        }
-                        break;
-                    }
-                }
-            }
-            return enabledSubtypes;
-        }
-
-        List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
-            final String enabledInputMethodsStr = getEnabledInputMethodsStr();
-            final TextUtils.SimpleStringSplitter inputMethodSplitter =
-                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
-            final TextUtils.SimpleStringSplitter subtypeSplitter =
-                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
-            final ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>();
-            if (TextUtils.isEmpty(enabledInputMethodsStr)) {
-                return imsList;
-            }
-            inputMethodSplitter.setString(enabledInputMethodsStr);
-            while (inputMethodSplitter.hasNext()) {
-                String nextImsStr = inputMethodSplitter.next();
-                subtypeSplitter.setString(nextImsStr);
-                if (subtypeSplitter.hasNext()) {
-                    ArrayList<String> subtypeHashes = new ArrayList<>();
-                    // The first element is ime id.
-                    String imeId = subtypeSplitter.next();
-                    while (subtypeSplitter.hasNext()) {
-                        subtypeHashes.add(subtypeSplitter.next());
-                    }
-                    imsList.add(new Pair<>(imeId, subtypeHashes));
-                }
-            }
-            return imsList;
-        }
-
-        /**
-         * Build and put a string of EnabledInputMethods with removing specified Id.
-         * @return the specified id was removed or not.
-         */
-        boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
-                StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
-            boolean isRemoved = false;
-            boolean needsAppendSeparator = false;
-            for (Pair<String, ArrayList<String>> ims: imsList) {
-                String curId = ims.first;
-                if (curId.equals(id)) {
-                    // We are disabling this input method, and it is
-                    // currently enabled.  Skip it to remove from the
-                    // new list.
-                    isRemoved = true;
-                } else {
-                    if (needsAppendSeparator) {
-                        builder.append(INPUT_METHOD_SEPARATOR);
-                    } else {
-                        needsAppendSeparator = true;
-                    }
-                    buildEnabledInputMethodsSettingString(builder, ims);
-                }
-            }
-            if (isRemoved) {
-                // Update the setting with the new list of input methods.
-                putEnabledInputMethodsStr(builder.toString());
-            }
-            return isRemoved;
-        }
-
-        private ArrayList<InputMethodInfo> createEnabledInputMethodListLocked(
-                List<Pair<String, ArrayList<String>>> imsList,
-                Predicate<InputMethodInfo> matchingCondition) {
-            final ArrayList<InputMethodInfo> res = new ArrayList<>();
-            for (Pair<String, ArrayList<String>> ims: imsList) {
-                InputMethodInfo info = mMethodMap.get(ims.first);
-                if (info != null && !info.isVrOnly()
-                        && (matchingCondition == null || matchingCondition.test(info))) {
-                    res.add(info);
-                }
-            }
-            return res;
-        }
-
-        void putEnabledInputMethodsStr(@Nullable String str) {
-            if (DEBUG) {
-                Slog.d(TAG, "putEnabledInputMethodStr: " + str);
-            }
-            if (TextUtils.isEmpty(str)) {
-                // OK to coalesce to null, since getEnabledInputMethodsStr() can take care of the
-                // empty data scenario.
-                putString(Settings.Secure.ENABLED_INPUT_METHODS, null);
-            } else {
-                putString(Settings.Secure.ENABLED_INPUT_METHODS, str);
-            }
-        }
-
-        @NonNull
-        String getEnabledInputMethodsStr() {
-            return getString(Settings.Secure.ENABLED_INPUT_METHODS, "");
-        }
-
-        private void saveSubtypeHistory(
-                List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
-            StringBuilder builder = new StringBuilder();
-            boolean isImeAdded = false;
-            if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
-                builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
-                        newSubtypeId);
-                isImeAdded = true;
-            }
-            for (Pair<String, String> ime: savedImes) {
-                String imeId = ime.first;
-                String subtypeId = ime.second;
-                if (TextUtils.isEmpty(subtypeId)) {
-                    subtypeId = NOT_A_SUBTYPE_ID_STR;
-                }
-                if (isImeAdded) {
-                    builder.append(INPUT_METHOD_SEPARATOR);
-                } else {
-                    isImeAdded = true;
-                }
-                builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
-                        subtypeId);
-            }
-            // Remove the last INPUT_METHOD_SEPARATOR
-            putSubtypeHistoryStr(builder.toString());
-        }
-
-        private void addSubtypeToHistory(String imeId, String subtypeId) {
-            List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
-            for (Pair<String, String> ime: subtypeHistory) {
-                if (ime.first.equals(imeId)) {
-                    if (DEBUG) {
-                        Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
-                                + ime.second);
-                    }
-                    // We should break here
-                    subtypeHistory.remove(ime);
-                    break;
-                }
-            }
-            if (DEBUG) {
-                Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
-            }
-            saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
-        }
-
-        private void putSubtypeHistoryStr(@NonNull String str) {
-            if (DEBUG) {
-                Slog.d(TAG, "putSubtypeHistoryStr: " + str);
-            }
-            if (TextUtils.isEmpty(str)) {
-                // OK to coalesce to null, since getSubtypeHistoryStr() can take care of the empty
-                // data scenario.
-                putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, null);
-            } else {
-                putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
-            }
-        }
-
-        Pair<String, String> getLastInputMethodAndSubtypeLocked() {
-            // Gets the first one from the history
-            return getLastSubtypeForInputMethodLockedInternal(null);
-        }
-
-        @Nullable
-        InputMethodSubtype getLastInputMethodSubtypeLocked() {
-            final Pair<String, String> lastIme = getLastInputMethodAndSubtypeLocked();
-            // TODO: Handle the case of the last IME with no subtypes
-            if (lastIme == null || TextUtils.isEmpty(lastIme.first)
-                    || TextUtils.isEmpty(lastIme.second)) return null;
-            final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
-            if (lastImi == null) return null;
-            try {
-                final int lastSubtypeHash = Integer.parseInt(lastIme.second);
-                final int lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi,
-                        lastSubtypeHash);
-                if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
-                    return null;
-                }
-                return lastImi.getSubtypeAt(lastSubtypeId);
-            } catch (NumberFormatException e) {
-                return null;
-            }
-        }
-
-        String getLastSubtypeForInputMethodLocked(String imeId) {
-            Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
-            if (ime != null) {
-                return ime.second;
-            } else {
-                return null;
-            }
-        }
-
-        private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
-            List<Pair<String, ArrayList<String>>> enabledImes =
-                    getEnabledInputMethodsAndSubtypeListLocked();
-            List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
-            for (Pair<String, String> imeAndSubtype : subtypeHistory) {
-                final String imeInTheHistory = imeAndSubtype.first;
-                // If imeId is empty, returns the first IME and subtype in the history
-                if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
-                    final String subtypeInTheHistory = imeAndSubtype.second;
-                    final String subtypeHashCode =
-                            getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
-                                    enabledImes, imeInTheHistory, subtypeInTheHistory);
-                    if (!TextUtils.isEmpty(subtypeHashCode)) {
-                        if (DEBUG) {
-                            Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
-                        }
-                        return new Pair<>(imeInTheHistory, subtypeHashCode);
-                    }
-                }
-            }
-            if (DEBUG) {
-                Slog.d(TAG, "No enabled IME found in the history");
-            }
-            return null;
-        }
-
-        private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
-                ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
-            final LocaleList localeList = SystemLocaleWrapper.get(mCurrentUserId);
-            for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
-                if (enabledIme.first.equals(imeId)) {
-                    final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
-                    final InputMethodInfo imi = mMethodMap.get(imeId);
-                    if (explicitlyEnabledSubtypes.size() == 0) {
-                        // If there are no explicitly enabled subtypes, applicable subtypes are
-                        // enabled implicitly.
-                        // If IME is enabled and no subtypes are enabled, applicable subtypes
-                        // are enabled implicitly, so needs to treat them to be enabled.
-                        if (imi != null && imi.getSubtypeCount() > 0) {
-                            List<InputMethodSubtype> implicitlyEnabledSubtypes =
-                                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(localeList,
-                                            imi);
-                            final int numSubtypes = implicitlyEnabledSubtypes.size();
-                            for (int i = 0; i < numSubtypes; ++i) {
-                                final InputMethodSubtype st = implicitlyEnabledSubtypes.get(i);
-                                if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
-                                    return subtypeHashCode;
-                                }
-                            }
-                        }
-                    } else {
-                        for (String s: explicitlyEnabledSubtypes) {
-                            if (s.equals(subtypeHashCode)) {
-                                // If both imeId and subtypeId are enabled, return subtypeId.
-                                try {
-                                    final int hashCode = Integer.parseInt(subtypeHashCode);
-                                    // Check whether the subtype id is valid or not
-                                    if (SubtypeUtils.isValidSubtypeId(imi, hashCode)) {
-                                        return s;
-                                    } else {
-                                        return NOT_A_SUBTYPE_ID_STR;
-                                    }
-                                } catch (NumberFormatException e) {
-                                    return NOT_A_SUBTYPE_ID_STR;
-                                }
-                            }
-                        }
-                    }
-                    // If imeId was enabled but subtypeId was disabled.
-                    return NOT_A_SUBTYPE_ID_STR;
-                }
-            }
-            // If both imeId and subtypeId are disabled, return null
-            return null;
-        }
-
-        private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
-            ArrayList<Pair<String, String>> imsList = new ArrayList<>();
-            final String subtypeHistoryStr = getSubtypeHistoryStr();
-            if (TextUtils.isEmpty(subtypeHistoryStr)) {
-                return imsList;
-            }
-            final TextUtils.SimpleStringSplitter inputMethodSplitter =
-                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
-            final TextUtils.SimpleStringSplitter subtypeSplitter =
-                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
-            inputMethodSplitter.setString(subtypeHistoryStr);
-            while (inputMethodSplitter.hasNext()) {
-                String nextImsStr = inputMethodSplitter.next();
-                subtypeSplitter.setString(nextImsStr);
-                if (subtypeSplitter.hasNext()) {
-                    String subtypeId = NOT_A_SUBTYPE_ID_STR;
-                    // The first element is ime id.
-                    String imeId = subtypeSplitter.next();
-                    while (subtypeSplitter.hasNext()) {
-                        subtypeId = subtypeSplitter.next();
-                        break;
-                    }
-                    imsList.add(new Pair<>(imeId, subtypeId));
-                }
-            }
-            return imsList;
-        }
-
-        @NonNull
-        private String getSubtypeHistoryStr() {
-            final String history = getString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, "");
-            if (DEBUG) {
-                Slog.d(TAG, "getSubtypeHistoryStr: " + history);
-            }
-            return history;
-        }
-
-        void putSelectedInputMethod(String imeId) {
-            if (DEBUG) {
-                Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
-                        + mCurrentUserId);
-            }
-            putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
-        }
-
-        void putSelectedSubtype(int subtypeId) {
-            if (DEBUG) {
-                Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
-                        + mCurrentUserId);
-            }
-            putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
-        }
-
-        @Nullable
-        String getSelectedInputMethod() {
-            final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null);
-            if (DEBUG) {
-                Slog.d(TAG, "getSelectedInputMethodStr: " + imi);
-            }
-            return imi;
-        }
-
-        @Nullable
-        String getSelectedDefaultDeviceInputMethod() {
-            final String imi = getString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null);
-            if (DEBUG) {
-                Slog.d(TAG, "getSelectedDefaultDeviceInputMethodStr: " + imi + ", "
-                        + mCurrentUserId);
-            }
-            return imi;
-        }
-
-        void putSelectedDefaultDeviceInputMethod(String imeId) {
-            if (DEBUG) {
-                Slog.d(TAG, "putSelectedDefaultDeviceInputMethodStr: " + imeId + ", "
-                        + mCurrentUserId);
-            }
-            putString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, imeId);
-        }
-
-        void putDefaultVoiceInputMethod(String imeId) {
-            if (DEBUG) {
-                Slog.d(TAG, "putDefaultVoiceInputMethodStr: " + imeId + ", " + mCurrentUserId);
-            }
-            putString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, imeId);
-        }
-
-        @Nullable
-        String getDefaultVoiceInputMethod() {
-            final String imi = getString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, null);
-            if (DEBUG) {
-                Slog.d(TAG, "getDefaultVoiceInputMethodStr: " + imi);
-            }
-            return imi;
-        }
-
-        boolean isSubtypeSelected() {
-            return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
-        }
-
-        private int getSelectedInputMethodSubtypeHashCode() {
-            return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID);
-        }
-
-        @UserIdInt
-        public int getCurrentUserId() {
-            return mCurrentUserId;
-        }
-
-        int getSelectedInputMethodSubtypeId(String selectedImiId) {
-            final InputMethodInfo imi = mMethodMap.get(selectedImiId);
-            if (imi == null) {
-                return NOT_A_SUBTYPE_ID;
-            }
-            final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
-            return SubtypeUtils.getSubtypeIdFromHashCode(imi, subtypeHashCode);
-        }
-
-        void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId,
-                InputMethodSubtype currentSubtype) {
-            String subtypeId = NOT_A_SUBTYPE_ID_STR;
-            if (currentSubtype != null) {
-                subtypeId = String.valueOf(currentSubtype.hashCode());
-            }
-            if (canAddToLastInputMethod(currentSubtype)) {
-                addSubtypeToHistory(curMethodId, subtypeId);
-            }
-        }
-
-        /**
-         * A variant of {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()} for
-         * non-current users.
-         *
-         * <p>TODO: Address code duplication between this and
-         * {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()}.</p>
-         *
-         * @return {@link InputMethodSubtype} if exists. {@code null} otherwise.
-         */
-        @Nullable
-        InputMethodSubtype getCurrentInputMethodSubtypeForNonCurrentUsers() {
-            final String selectedMethodId = getSelectedInputMethod();
-            if (selectedMethodId == null) {
-                return null;
-            }
-            final InputMethodInfo imi = mMethodMap.get(selectedMethodId);
-            if (imi == null || imi.getSubtypeCount() == 0) {
-                return null;
-            }
-
-            final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
-            if (subtypeHashCode != InputMethodUtils.NOT_A_SUBTYPE_ID) {
-                final int subtypeIndex = SubtypeUtils.getSubtypeIdFromHashCode(imi,
-                        subtypeHashCode);
-                if (subtypeIndex >= 0) {
-                    return imi.getSubtypeAt(subtypeIndex);
-                }
-            }
-
-            // If there are no selected subtypes, the framework will try to find the most applicable
-            // subtype from explicitly or implicitly enabled subtypes.
-            final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
-                    getEnabledInputMethodSubtypeListLocked(imi, true);
-            // If there is only one explicitly or implicitly enabled subtype, just returns it.
-            if (explicitlyOrImplicitlyEnabledSubtypes.isEmpty()) {
-                return null;
-            }
-            if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
-                return explicitlyOrImplicitlyEnabledSubtypes.get(0);
-            }
-            final String locale = SystemLocaleWrapper.get(mCurrentUserId).get(0).toString();
-            final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
-                    explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD,
-                    locale, true);
-            if (subtype != null) {
-                return subtype;
-            }
-            return SubtypeUtils.findLastResortApplicableSubtypeLocked(
-                    explicitlyOrImplicitlyEnabledSubtypes, null, locale, true);
-        }
-
-        boolean setAdditionalInputMethodSubtypes(@NonNull String imeId,
-                @NonNull ArrayList<InputMethodSubtype> subtypes,
-                @NonNull ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
-                @NonNull PackageManagerInternal packageManagerInternal, int callingUid) {
-            final InputMethodInfo imi = mMethodMap.get(imeId);
-            if (imi == null) {
-                return false;
-            }
-            if (!InputMethodUtils.checkIfPackageBelongsToUid(packageManagerInternal, callingUid,
-                    imi.getPackageName())) {
-                return false;
-            }
-
-            if (subtypes.isEmpty()) {
-                additionalSubtypeMap.remove(imi.getId());
-            } else {
-                additionalSubtypeMap.put(imi.getId(), subtypes);
-            }
-            AdditionalSubtypeUtils.save(additionalSubtypeMap, mMethodMap, getCurrentUserId());
-            return true;
-        }
-
-        boolean setEnabledInputMethodSubtypes(@NonNull String imeId,
-                @NonNull int[] subtypeHashCodes) {
-            final InputMethodInfo imi = mMethodMap.get(imeId);
-            if (imi == null) {
-                return false;
-            }
-
-            final IntArray validSubtypeHashCodes = new IntArray(subtypeHashCodes.length);
-            for (int subtypeHashCode : subtypeHashCodes) {
-                if (subtypeHashCode == NOT_A_SUBTYPE_ID) {
-                    continue;  // NOT_A_SUBTYPE_ID must not be saved
-                }
-                if (!SubtypeUtils.isValidSubtypeId(imi, subtypeHashCode)) {
-                    continue;  // this subtype does not exist in InputMethodInfo.
-                }
-                if (validSubtypeHashCodes.indexOf(subtypeHashCode) >= 0) {
-                    continue;  // The entry is already added.  No need to add anymore.
-                }
-                validSubtypeHashCodes.add(subtypeHashCode);
-            }
-
-            final String originalEnabledImesString = getEnabledInputMethodsStr();
-            final String updatedEnabledImesString = updateEnabledImeString(
-                    originalEnabledImesString, imi.getId(), validSubtypeHashCodes);
-            if (TextUtils.equals(originalEnabledImesString, updatedEnabledImesString)) {
-                return false;
-            }
-
-            putEnabledInputMethodsStr(updatedEnabledImesString);
-            return true;
-        }
-
-        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-        static String updateEnabledImeString(@NonNull String enabledImesString,
-                @NonNull String imeId, @NonNull IntArray enabledSubtypeHashCodes) {
-            final TextUtils.SimpleStringSplitter imeSplitter =
-                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
-            final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
-                    new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
-
-            final StringBuilder sb = new StringBuilder();
-
-            imeSplitter.setString(enabledImesString);
-            boolean needsImeSeparator = false;
-            while (imeSplitter.hasNext()) {
-                final String nextImsStr = imeSplitter.next();
-                imeSubtypeSplitter.setString(nextImsStr);
-                if (imeSubtypeSplitter.hasNext()) {
-                    if (needsImeSeparator) {
-                        sb.append(INPUT_METHOD_SEPARATOR);
-                    }
-                    if (TextUtils.equals(imeId, imeSubtypeSplitter.next())) {
-                        sb.append(imeId);
-                        for (int i = 0; i < enabledSubtypeHashCodes.size(); ++i) {
-                            sb.append(INPUT_METHOD_SUBTYPE_SEPARATOR);
-                            sb.append(enabledSubtypeHashCodes.get(i));
-                        }
-                    } else {
-                        sb.append(nextImsStr);
-                    }
-                    needsImeSeparator = true;
-                }
-            }
-            return sb.toString();
-        }
-
-        public void dumpLocked(final Printer pw, final String prefix) {
-            pw.println(prefix + "mCurrentUserId=" + mCurrentUserId);
-        }
-    }
-
     static boolean isSoftInputModeStateVisibleAllowed(int targetSdkVersion,
             @StartInputFlags int startInputFlags) {
         if (targetSdkVersion < Build.VERSION_CODES.P) {
diff --git a/services/core/java/com/android/server/inputmethod/LocaleUtils.java b/services/core/java/com/android/server/inputmethod/LocaleUtils.java
index 7d090db..0b16af2 100644
--- a/services/core/java/com/android/server/inputmethod/LocaleUtils.java
+++ b/services/core/java/com/android/server/inputmethod/LocaleUtils.java
@@ -212,10 +212,16 @@
 
     /**
      * Returns the language component of a given locale string.
-     * TODO: Use {@link Locale#toLanguageTag()} and {@link Locale#forLanguageTag(String)}
+     * TODO(b/321064051): Switch to {@link
+     * com.android.internal.inputmethod.SubtypeLocaleUtils#constructLocaleFromString(String)}
      */
     static String getLanguageFromLocaleString(String locale) {
-        return Locale.forLanguageTag(locale).getLanguage();
+        final int idx = locale.indexOf('_');
+        if (idx < 0) {
+            return locale;
+        } else {
+            return locale.substring(0, idx);
+        }
     }
 
     static Locale getSystemLocaleFromContext(Context context) {
diff --git a/services/core/java/com/android/server/inputmethod/OWNERS b/services/core/java/com/android/server/inputmethod/OWNERS
index aa638aa..e507c6b 100644
--- a/services/core/java/com/android/server/inputmethod/OWNERS
+++ b/services/core/java/com/android/server/inputmethod/OWNERS
@@ -6,5 +6,8 @@
 fstern@google.com
 cosminbaies@google.com
 
+# Automotive
+kanant@google.com
+
 ogunwale@google.com #{LAST_RESORT_SUGGESTION}
 jjaggi@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
index 95df998..3d5c8677 100644
--- a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
+++ b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
@@ -26,7 +26,6 @@
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -52,7 +51,7 @@
             "EnabledWhenDefaultIsNotAsciiCapable";
 
     // A temporary workaround for the performance concerns in
-    // #getImplicitlyApplicableSubtypesLocked(Resources, InputMethodInfo).
+    // #getImplicitlyApplicableSubtypes(Resources, InputMethodInfo).
     // TODO: Optimize all the critical paths including this one.
     // TODO(b/235661780): Make the cache supports multi-users.
     private static final Object sCacheLock = new Object();
@@ -121,9 +120,8 @@
     private static final LocaleUtils.LocaleExtractor<InputMethodSubtype> sSubtypeToLocale =
             source -> source != null ? source.getLocaleObject() : null;
 
-    @VisibleForTesting
     @NonNull
-    static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
+    static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypes(
             @NonNull LocaleList systemLocales, InputMethodInfo imi) {
         synchronized (sCacheLock) {
             // We intentionally do not use InputMethodInfo#equals(InputMethodInfo) here because
@@ -133,11 +131,11 @@
             }
         }
 
-        // Note: Only resource info in "res" is used in getImplicitlyApplicableSubtypesLockedImpl().
-        // TODO: Refactor getImplicitlyApplicableSubtypesLockedImpl() so that it can receive
+        // Note: Only resource info in "res" is used in getImplicitlyApplicableSubtypesImpl().
+        // TODO: Refactor getImplicitlyApplicableSubtypesImpl() so that it can receive
         // LocaleList rather than Resource.
         final ArrayList<InputMethodSubtype> result =
-                getImplicitlyApplicableSubtypesLockedImpl(systemLocales, imi);
+                getImplicitlyApplicableSubtypesImpl(systemLocales, imi);
         synchronized (sCacheLock) {
             // Both LocaleList and InputMethodInfo are immutable. No need to copy them here.
             sCachedSystemLocales = systemLocales;
@@ -147,7 +145,7 @@
         return result;
     }
 
-    private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLockedImpl(
+    private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesImpl(
             @NonNull LocaleList systemLocales, InputMethodInfo imi) {
         final List<InputMethodSubtype> subtypes = getSubtypes(imi);
         final String systemLocale = systemLocales.get(0).toString();
@@ -215,7 +213,7 @@
         }
 
         if (applicableSubtypes.isEmpty()) {
-            InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
+            InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtype(
                     subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
             if (lastResortKeyboardSubtype != null) {
                 applicableSubtypes.add(lastResortKeyboardSubtype);
@@ -244,7 +242,7 @@
      *
      * @return the most applicable subtypeId
      */
-    static InputMethodSubtype findLastResortApplicableSubtypeLocked(
+    static InputMethodSubtype findLastResortApplicableSubtype(
             List<InputMethodSubtype> subtypes, String mode, @NonNull String locale,
             boolean canIgnoreLocaleAsLastResort) {
         if (subtypes == null || subtypes.isEmpty()) {
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 9c4225d..39df5be 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -1384,7 +1384,8 @@
         try {
             reportLocation(LocationResult.wrap(location).validate());
         } catch (BadLocationException e) {
-            throw new IllegalArgumentException(e);
+            Log.e(TAG, "Dropping invalid location: " + e);
+            return;
         }
 
         if (mStarted) {
@@ -1759,7 +1760,8 @@
             try {
                 reportLocation(LocationResult.wrap(locations).validate());
             } catch (BadLocationException e) {
-                throw new IllegalArgumentException(e);
+                Log.e(TAG, "Dropping invalid locations: " + e);
+                return;
             }
         }
 
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index d02b6f4..171fbb6 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -25,6 +25,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
+import android.location.GnssMeasurement;
 import android.location.GnssMeasurementRequest;
 import android.location.GnssMeasurementsEvent;
 import android.location.IGnssMeasurementsListener;
@@ -33,6 +34,7 @@
 import android.stats.location.LocationStatsEnums;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.server.location.gnss.GnssConfiguration.HalInterfaceVersion;
 import com.android.server.location.gnss.hal.GnssNative;
 import com.android.server.location.injector.AppOpsHelper;
@@ -40,6 +42,8 @@
 import com.android.server.location.injector.LocationUsageLogger;
 import com.android.server.location.injector.SettingsHelper;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.Collection;
 
 /**
@@ -91,6 +95,9 @@
     private final LocationUsageLogger mLogger;
     private final GnssNative mGnssNative;
 
+    @GuardedBy("mMultiplexerLock")
+    private GnssMeasurementsEvent mLastGnssMeasurementsEvent;
+
     public GnssMeasurementsProvider(Injector injector, GnssNative gnssNative) {
         super(injector);
         mAppOpsHelper = injector.getAppOpsHelper();
@@ -264,5 +271,46 @@
                 return null;
             }
         });
+        synchronized (mMultiplexerLock) {
+            mLastGnssMeasurementsEvent = event;
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        super.dump(fd, pw, args);
+        pw.print("last measurements=");
+        pw.println(getLastMeasurementEventSummary());
+    }
+
+    /**
+     * Returns a string of GnssMeasurementsEvent summary including received time, satellite count
+     * and average baseband C/No.
+     */
+    private String getLastMeasurementEventSummary() {
+        synchronized (mMultiplexerLock) {
+            if (mLastGnssMeasurementsEvent == null) {
+                return null;
+            }
+            StringBuilder builder = new StringBuilder("[");
+            builder.append("elapsedRealtimeNs=").append(
+                    mLastGnssMeasurementsEvent.getClock().getElapsedRealtimeNanos());
+            builder.append(" measurementCount=").append(
+                    mLastGnssMeasurementsEvent.getMeasurements().size());
+
+            float sumBasebandCn0 = 0;
+            int countBasebandCn0 = 0;
+            for (GnssMeasurement measurement : mLastGnssMeasurementsEvent.getMeasurements()) {
+                if (measurement.hasBasebandCn0DbHz()) {
+                    sumBasebandCn0 += measurement.getBasebandCn0DbHz();
+                    countBasebandCn0++;
+                }
+            }
+            if (countBasebandCn0 > 0) {
+                builder.append(" avgBasebandCn0=").append(sumBasebandCn0 / countBasebandCn0);
+            }
+            builder.append("]");
+            return builder.toString();
+        }
     }
 }
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 403b421..71a9f54 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -248,6 +248,7 @@
             SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
             TelephonyManager telManager = mContext.getSystemService(TelephonyManager.class);
             if (subManager != null && telManager != null) {
+                subManager = subManager.createForAllUserProfiles();
                 List<SubscriptionInfo> subscriptionInfoList =
                         subManager.getActiveSubscriptionInfoList();
                 HashSet<Integer> activeSubIds = new HashSet<Integer>();
diff --git a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
index c772e08..5df0de8 100644
--- a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
@@ -22,12 +22,14 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.os.SystemClock;
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
 import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.flags.Flags;
 import com.android.server.FgThread;
 
 import java.util.Objects;
@@ -104,10 +106,26 @@
         boolean isInExtensionTime = mEmergencyCallEndRealtimeMs != Long.MIN_VALUE
                 && (SystemClock.elapsedRealtime() - mEmergencyCallEndRealtimeMs) < extensionTimeMs;
 
-        return mIsInEmergencyCall
-                || isInExtensionTime
-                || mTelephonyManager.getEmergencyCallbackMode()
-                || mTelephonyManager.isInEmergencySmsMode();
+        if (!Flags.enforceTelephonyFeatureMapping()) {
+            return mIsInEmergencyCall
+                    || isInExtensionTime
+                    || mTelephonyManager.getEmergencyCallbackMode()
+                    || mTelephonyManager.isInEmergencySmsMode();
+        } else {
+            boolean emergencyCallbackMode = false;
+            boolean emergencySmsMode = false;
+            PackageManager pm = mContext.getPackageManager();
+            if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING)) {
+                emergencyCallbackMode = mTelephonyManager.getEmergencyCallbackMode();
+            }
+            if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)) {
+                emergencySmsMode = mTelephonyManager.isInEmergencySmsMode();
+            }
+            return mIsInEmergencyCall
+                    || isInExtensionTime
+                    || emergencyCallbackMode
+                    || emergencySmsMode;
+        }
     }
 
     private class EmergencyCallTelephonyCallback extends TelephonyCallback implements
diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
index 5e38bca..2522f7b 100644
--- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
@@ -27,6 +27,7 @@
 
 import android.annotation.Nullable;
 import android.location.Location;
+import android.location.LocationRequest;
 import android.location.LocationResult;
 import android.location.provider.ProviderRequest;
 import android.os.SystemClock;
@@ -179,6 +180,7 @@
     private void onThrottlingChangedLocked(boolean deliverImmediate) {
         long throttlingIntervalMs = INTERVAL_DISABLED;
         if (mDeviceStationary && mDeviceIdle && !mIncomingRequest.isLocationSettingsIgnored()
+                && mIncomingRequest.getQuality() != LocationRequest.QUALITY_HIGH_ACCURACY
                 && mLastLocation != null
                 && mLastLocation.getElapsedRealtimeAgeMillis(mDeviceStationaryRealtimeMs)
                 <= MAX_STATIONARY_LOCATION_AGE_MS) {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 0c2eee5..a06607b 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.locksettings;
 
+import static android.security.Flags.reportPrimaryAuthAttempts;
 import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE;
 import static android.Manifest.permission.MANAGE_BIOMETRIC;
 import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS;
@@ -52,6 +53,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.IActivityManager;
@@ -74,6 +76,7 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.database.sqlite.SQLiteDatabase;
@@ -90,6 +93,7 @@
 import android.os.IBinder;
 import android.os.IProgressListener;
 import android.os.Process;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
@@ -135,6 +139,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.ICheckCredentialProgressCallback;
 import com.android.internal.widget.ILockSettings;
+import com.android.internal.widget.ILockSettingsStateListener;
 import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
 import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
 import com.android.internal.widget.LockPatternUtils;
@@ -303,7 +308,7 @@
     private boolean mThirdPartyAppsStarted;
 
     // Current password metrics for all secured users on the device. Updated when user unlocks the
-    // device or changes password. Removed when user is stopped.
+    // device or changes password. Removed if user is stopped with its CE key evicted.
     @GuardedBy("this")
     private final SparseArray<PasswordMetrics> mUserPasswordMetrics = new SparseArray<>();
     @VisibleForTesting
@@ -327,6 +332,9 @@
 
     private HashMap<UserHandle, UserManager> mUserManagerCache = new HashMap<>();
 
+    private final RemoteCallbackList<ILockSettingsStateListener> mLockSettingsStateListeners =
+            new RemoteCallbackList<>();
+
     // This class manages life cycle events for encrypted users on File Based Encryption (FBE)
     // devices. The most basic of these is to show/hide notifications about missing features until
     // the user unlocks the account and credential-encrypted storage is available.
@@ -793,13 +801,33 @@
     }
 
     @VisibleForTesting
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.QUERY_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
     void onUserStopped(int userId) {
         hideEncryptionNotification(new UserHandle(userId));
-        // User is stopped with its CE key evicted. Restore strong auth requirement to the default
-        // flags after boot since stopping and restarting a user later is equivalent to rebooting
-        // the device.
+
+        // Normally, CE storage is locked when a user is stopped, and restarting the user requires
+        // strong auth.  Therefore, reset the user's strong auth flags.  The exception is users that
+        // allow delayed locking; under some circumstances, biometric authentication is allowed to
+        // restart such users.  Don't reset the strong auth flags for such users.
+        //
+        // TODO(b/319142556): It might make more sense to reset the strong auth flags when CE
+        // storage is locked, instead of when the user is stopped.  This would ensure the flags get
+        // reset if CE storage is locked later for a user that allows delayed locking.
+        if (android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+            UserProperties userProperties = mUserManager.getUserProperties(UserHandle.of(userId));
+            if (userProperties != null && userProperties.getAllowStoppingUserWithDelayedLocking()) {
+                return;
+            }
+        }
         int strongAuthRequired = LockPatternUtils.StrongAuthTracker.getDefaultFlags(mContext);
         requireStrongAuth(strongAuthRequired, userId);
+
+        // Don't keep the password metrics in memory for a stopped user that will require strong
+        // auth to start again, since strong auth will make the password metrics available again.
         synchronized (this) {
             mUserPasswordMetrics.remove(userId);
         }
@@ -2118,11 +2146,10 @@
             Slogf.d(TAG, "CE storage for user %d is already unlocked", userId);
             return;
         }
-        final UserInfo userInfo = mUserManager.getUserInfo(userId);
         final String userType = isUserSecure(userId) ? "secured" : "unsecured";
         final byte[] secret = sp.deriveFileBasedEncryptionKey();
         try {
-            mStorageManager.unlockCeStorage(userId, userInfo.serialNumber, secret);
+            mStorageManager.unlockCeStorage(userId, secret);
             Slogf.i(TAG, "Unlocked CE storage for %s user %d", userType, userId);
         } catch (RemoteException e) {
             Slogf.wtf(TAG, e, "Failed to unlock CE storage for %s user %d", userType, userId);
@@ -2343,9 +2370,37 @@
                 requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
             }
         }
+        if (reportPrimaryAuthAttempts()) {
+            final boolean success =
+                    response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK;
+            notifyLockSettingsStateListeners(success, userId);
+        }
         return response;
     }
 
+    private void notifyLockSettingsStateListeners(boolean success, int userId) {
+        int i = mLockSettingsStateListeners.beginBroadcast();
+        try {
+            while (i > 0) {
+                i--;
+                try {
+                    if (success) {
+                        mLockSettingsStateListeners.getBroadcastItem(i)
+                                .onAuthenticationSucceeded(userId);
+                    } else {
+                        mLockSettingsStateListeners.getBroadcastItem(i)
+                                .onAuthenticationFailed(userId);
+                    }
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Exception while notifying LockSettingsStateListener:"
+                            + " success = " + success + ", userId = " + userId, e);
+                }
+            }
+        } finally {
+            mLockSettingsStateListeners.finishBroadcast();
+        }
+    }
+
     @Override
     public VerifyCredentialResponse verifyTiedProfileChallenge(LockscreenCredential credential,
             int userId, @LockPatternUtils.VerifyFlag int flags) {
@@ -3663,6 +3718,18 @@
         public void refreshStrongAuthTimeout(int userId) {
             mStrongAuth.refreshStrongAuthTimeout(userId);
         }
+
+        @Override
+        public void registerLockSettingsStateListener(
+                @NonNull ILockSettingsStateListener listener) {
+            mLockSettingsStateListeners.register(listener);
+        }
+
+        @Override
+        public void unregisterLockSettingsStateListener(
+                @NonNull ILockSettingsStateListener listener) {
+            mLockSettingsStateListeners.unregister(listener);
+        }
     }
 
     private class RebootEscrowCallbacks implements RebootEscrowManager.Callbacks {
diff --git a/services/core/java/com/android/server/media/MediaFeatureFlagManager.java b/services/core/java/com/android/server/media/MediaFeatureFlagManager.java
deleted file mode 100644
index f90f64a..0000000
--- a/services/core/java/com/android/server/media/MediaFeatureFlagManager.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.media;
-
-import android.annotation.StringDef;
-import android.app.ActivityThread;
-import android.app.Application;
-import android.provider.DeviceConfig;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/* package */ class MediaFeatureFlagManager {
-
-    /**
-     * Namespace for media better together features.
-     */
-    private static final String NAMESPACE_MEDIA_BETTER_TOGETHER = "media_better_together";
-
-    @StringDef(
-            prefix = "FEATURE_",
-            value = {
-                FEATURE_SCANNING_MINIMUM_PACKAGE_IMPORTANCE
-            })
-    @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
-    @Retention(RetentionPolicy.SOURCE)
-    /* package */ @interface MediaFeatureFlag {}
-
-    /**
-     * Whether to use IMPORTANCE_FOREGROUND (i.e. 100) or IMPORTANCE_FOREGROUND_SERVICE (i.e. 125)
-     * as the minimum package importance for scanning.
-     */
-    /* package */ static final @MediaFeatureFlag String
-            FEATURE_SCANNING_MINIMUM_PACKAGE_IMPORTANCE = "scanning_package_minimum_importance";
-
-    private static final MediaFeatureFlagManager sInstance = new MediaFeatureFlagManager();
-
-    private MediaFeatureFlagManager() {
-        // Empty to prevent instantiation.
-    }
-
-    /* package */ static MediaFeatureFlagManager getInstance() {
-        return sInstance;
-    }
-
-    /**
-     * Returns a boolean value from {@link DeviceConfig} from the system_time namespace, or
-     * {@code defaultValue} if there is no explicit value set.
-     */
-    public boolean getBoolean(@MediaFeatureFlag String key, boolean defaultValue) {
-        return DeviceConfig.getBoolean(NAMESPACE_MEDIA_BETTER_TOGETHER, key, defaultValue);
-    }
-
-    /**
-     * Returns an int value from {@link DeviceConfig} from the system_time namespace, or {@code
-     * defaultValue} if there is no explicit value set.
-     */
-    public int getInt(@MediaFeatureFlag String key, int defaultValue) {
-        return DeviceConfig.getInt(NAMESPACE_MEDIA_BETTER_TOGETHER, key, defaultValue);
-    }
-
-    /**
-     * Adds a listener to react for changes in media feature flags values. Future calls to this
-     * method with the same listener will replace the old namespace and executor.
-     *
-     * @param onPropertiesChangedListener The listener to add.
-     */
-    public void addOnPropertiesChangedListener(
-            DeviceConfig.OnPropertiesChangedListener onPropertiesChangedListener) {
-        Application currentApplication = ActivityThread.currentApplication();
-        if (currentApplication != null) {
-            DeviceConfig.addOnPropertiesChangedListener(
-                    NAMESPACE_MEDIA_BETTER_TOGETHER,
-                    currentApplication.getMainExecutor(),
-                    onPropertiesChangedListener);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 21e7bef..0ae6036 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -44,6 +44,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.media.flags.Flags;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -318,11 +319,19 @@
 
     @Override
     public void onBindingDied(ComponentName name) {
-        if (DEBUG) {
-            Slog.d(TAG, this + ": Service binding died");
-        }
         unbind();
-        if (shouldBind()) {
+        if (Flags.enablePreventionOfKeepAliveRouteProviders()) {
+            Slog.w(
+                    TAG,
+                    TextUtils.formatSimple(
+                            "Route provider service (%s) binding died, but we did not rebind.",
+                            name.toString()));
+        } else if (shouldBind()) {
+            Slog.w(
+                    TAG,
+                    TextUtils.formatSimple(
+                            "Rebound to provider service (%s) after binding died.",
+                            name.toString()));
             bind();
         }
     }
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
index bd252e7..3d717a8 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
@@ -33,6 +33,8 @@
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.media.flags.Flags;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -86,7 +88,9 @@
             filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
             filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
             filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
-            filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+            if (!Flags.enablePreventionOfKeepAliveRouteProviders()) {
+                filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+            }
             filter.addDataScheme("package");
             mContext.registerReceiverAsUser(mScanPackagesReceiver,
                     new UserHandle(mUserId), filter, null, mHandler);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 06a8d98..85a1315 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -16,7 +16,7 @@
 
 package com.android.server.media;
 
-import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.content.Intent.ACTION_SCREEN_OFF;
 import static android.content.Intent.ACTION_SCREEN_ON;
 import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
@@ -24,7 +24,6 @@
 import static android.media.MediaRouter2Utils.getProviderId;
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-import static com.android.server.media.MediaFeatureFlagManager.FEATURE_SCANNING_MINIMUM_PACKAGE_IMPORTANCE;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -55,7 +54,6 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.provider.DeviceConfig;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -97,11 +95,7 @@
     //       in MediaRouter2, remove this constant and replace the usages with the real request IDs.
     private static final long DUMMY_REQUEST_ID = -1;
 
-    private static int sPackageImportanceForScanning =
-            MediaFeatureFlagManager.getInstance()
-                    .getInt(
-                            FEATURE_SCANNING_MINIMUM_PACKAGE_IMPORTANCE,
-                            IMPORTANCE_FOREGROUND_SERVICE);
+    private static final int REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING = IMPORTANCE_FOREGROUND;
 
     /**
      * Contains the list of bluetooth permissions that are required to do system routing.
@@ -159,7 +153,7 @@
         mContext = context;
         mActivityManager = mContext.getSystemService(ActivityManager.class);
         mActivityManager.addOnUidImportanceListener(mOnUidImportanceListener,
-                sPackageImportanceForScanning);
+                REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING);
         mPowerManager = mContext.getSystemService(PowerManager.class);
         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
 
@@ -171,9 +165,6 @@
         }
 
         mContext.getPackageManager().addOnPermissionsChangeListener(this::onPermissionsChanged);
-
-        MediaFeatureFlagManager.getInstance()
-                .addOnPropertiesChangedListener(this::onDeviceConfigChange);
     }
 
     /**
@@ -774,7 +765,7 @@
                                 .generateDeviceRouteSelectedSessionInfo(packageName);
                     } else {
                         sessionInfos = userRecord.mHandler.mSystemProvider.getSessionInfos();
-                        if (sessionInfos != null && !sessionInfos.isEmpty()) {
+                        if (!sessionInfos.isEmpty()) {
                             // Return a copy of the current system session with no modification,
                             // except setting the client package name.
                             return new RoutingSessionInfo.Builder(sessionInfos.get(0))
@@ -1158,14 +1149,7 @@
         } else {
             if (route.isSystemRoute()
                     && !routerRecord.hasSystemRoutingPermission()
-                    && !TextUtils.equals(
-                            route.getId(),
-                            routerRecord
-                                    .mUserRecord
-                                    .mHandler
-                                    .mSystemProvider
-                                    .getDefaultRoute()
-                                    .getId())) {
+                    && !TextUtils.equals(route.getId(), MediaRoute2Info.ROUTE_ID_DEFAULT)) {
                 Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to"
                         + route);
                 routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
@@ -1252,11 +1236,9 @@
                         "transferToRouteWithRouter2 | router: %s(id: %d), route: %s",
                         routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
 
-        String defaultRouteId =
-                routerRecord.mUserRecord.mHandler.mSystemProvider.getDefaultRoute().getId();
         if (route.isSystemRoute()
                 && !routerRecord.hasSystemRoutingPermission()
-                && !TextUtils.equals(route.getId(), defaultRouteId)) {
+                && !TextUtils.equals(route.getId(), MediaRoute2Info.ROUTE_ID_DEFAULT)) {
             routerRecord.mUserRecord.mHandler.sendMessage(
                     obtainMessage(UserHandler::notifySessionCreationFailedToRouter,
                             routerRecord.mUserRecord.mHandler,
@@ -1452,8 +1434,13 @@
             return;
         }
 
-        Slog.i(TAG, TextUtils.formatSimple(
-                "startScan | manager: %d", managerRecord.mManagerId));
+        Slog.i(
+                TAG,
+                TextUtils.formatSimple(
+                        "startScan | manager: %d, ownerPackageName: %s, targetPackageName: %s",
+                        managerRecord.mManagerId,
+                        managerRecord.mOwnerPackageName,
+                        managerRecord.mTargetPackageName));
 
         managerRecord.startScan();
     }
@@ -1466,8 +1453,13 @@
             return;
         }
 
-        Slog.i(TAG, TextUtils.formatSimple(
-                "stopScan | manager: %d", managerRecord.mManagerId));
+        Slog.i(
+                TAG,
+                TextUtils.formatSimple(
+                        "stopScan | manager: %d, ownerPackageName: %s, targetPackageName: %s",
+                        managerRecord.mManagerId,
+                        managerRecord.mOwnerPackageName,
+                        managerRecord.mTargetPackageName));
 
         managerRecord.stopScan();
     }
@@ -1734,13 +1726,6 @@
 
     // End of locked methods that are used by both MediaRouter2 and MediaRouter2Manager.
 
-    private void onDeviceConfigChange(@NonNull DeviceConfig.Properties properties) {
-        sPackageImportanceForScanning =
-                properties.getInt(
-                        /* name */ FEATURE_SCANNING_MINIMUM_PACKAGE_IMPORTANCE,
-                        /* defaultValue */ IMPORTANCE_FOREGROUND_SERVICE);
-    }
-
     static long toUniqueRequestId(int requesterId, int originalRequestId) {
         return ((long) requesterId << 32) | originalRequestId;
     }
@@ -2351,6 +2336,11 @@
                     mSystemProvider.getDefaultRoute());
         }
 
+        private static String getPackageNameFromNullableRecord(
+                @Nullable RouterRecord routerRecord) {
+            return routerRecord != null ? routerRecord.mPackageName : "<null router record>";
+        }
+
         private static String toLoggingMessage(
                 String source, String providerId, ArrayList<MediaRoute2Info> routes) {
             String routesString =
@@ -2588,8 +2578,17 @@
 
             RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId);
             if (matchingRecord != routerRecord) {
-                Slog.w(TAG, "Ignoring " + description + " route from non-matching router. "
-                        + "packageName=" + routerRecord.mPackageName + " route=" + route);
+                Slog.w(
+                        TAG,
+                        "Ignoring "
+                                + description
+                                + " route from non-matching router."
+                                + " routerRecordPackageName="
+                                + getPackageNameFromNullableRecord(routerRecord)
+                                + " matchingRecordPackageName="
+                                + getPackageNameFromNullableRecord(matchingRecord)
+                                + " route="
+                                + route);
                 return false;
             }
 
@@ -2628,9 +2627,15 @@
                 @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId) {
             final RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId);
             if (matchingRecord != routerRecord) {
-                Slog.w(TAG, "Ignoring releasing session from non-matching router. packageName="
-                        + (routerRecord == null ? null : routerRecord.mPackageName)
-                        + " uniqueSessionId=" + uniqueSessionId);
+                Slog.w(
+                        TAG,
+                        "Ignoring releasing session from non-matching router."
+                                + " routerRecordPackageName="
+                                + getPackageNameFromNullableRecord(routerRecord)
+                                + " matchingRecordPackageName="
+                                + getPackageNameFromNullableRecord(matchingRecord)
+                                + " uniqueSessionId="
+                                + uniqueSessionId);
                 return;
             }
 
@@ -2761,11 +2766,10 @@
             if (manager != null) {
                 notifyRequestFailedToManager(
                         manager.mManager, toOriginalRequestId(uniqueRequestId), reason);
-                return;
             }
 
-            // Currently, only the manager can get notified of failures.
-            // TODO: Notify router too when the related callback is introduced.
+            // Currently, only manager records can get notified of failures.
+            // TODO(b/282936553): Notify regular routers of request failures.
         }
 
         private boolean handleSessionCreationRequestFailed(@NonNull MediaRoute2Provider provider,
@@ -2909,11 +2913,9 @@
                 currentSystemSessionInfo = mSystemProvider.getDefaultSessionInfo();
             }
 
-            if (currentRoutes.size() == 0) {
-                return;
+            if (!currentRoutes.isEmpty()) {
+                routerRecord.notifyRegistered(currentRoutes, currentSystemSessionInfo);
             }
-
-            routerRecord.notifyRegistered(currentRoutes, currentSystemSessionInfo);
         }
 
         private static void notifyRoutesUpdatedToRouterRecords(
@@ -3182,7 +3184,7 @@
                             record ->
                                     service.mActivityManager.getPackageImportance(
                                                     record.mPackageName)
-                                            <= sPackageImportanceForScanning)
+                                            <= REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING)
                     .collect(Collectors.toList());
         }
 
@@ -3199,7 +3201,7 @@
                                     manager.mIsScanning
                                             && service.mActivityManager.getPackageImportance(
                                                             manager.mOwnerPackageName)
-                                                    <= sPackageImportanceForScanning);
+                                                    <= REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING);
         }
 
         private MediaRoute2Provider findProvider(@Nullable String providerId) {
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 2cd3ab1..7fabdf2 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -34,6 +34,8 @@
 import android.app.KeyguardManager;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.usage.UsageStatsManager;
+import android.app.usage.UsageStatsManagerInternal;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -68,6 +70,7 @@
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Message;
+import android.os.PersistableBundle;
 import android.os.PowerExemptionManager;
 import android.os.PowerManager;
 import android.os.Process;
@@ -100,7 +103,9 @@
 import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * System implementation of MediaSessionManager
@@ -145,6 +150,8 @@
     private AudioManager mAudioManager;
     private boolean mHasFeatureLeanback;
     private ActivityManagerInternal mActivityManagerInternal;
+    private UsageStatsManagerInternal mUsageStatsManagerInternal;
+    private final Set<Integer> mUserEngagingSessions = new HashSet<>();
 
     // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
     // It's always not null after the MediaSessionService is started.
@@ -230,6 +237,7 @@
         mContext.registerReceiver(mNotificationListenerEnabledChangedReceiver, filter);
 
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+        mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class);
     }
 
     @Override
@@ -287,10 +295,14 @@
                 }
                 user.mPriorityStack.onSessionActiveStateChanged(record);
             }
-            setForegroundServiceAllowance(
-                    record,
-                    /* allowRunningInForeground= */ record.isActive()
-                            && (playbackState == null || playbackState.isActive()));
+            boolean allowRunningInForeground = record.isActive()
+                    && (playbackState == null || playbackState.isActive());
+
+            Log.d(TAG, "onSessionActiveStateChanged: "
+                    + "record=" + record
+                    + "playbackState=" + playbackState
+                    + "allowRunningInForeground=" + allowRunningInForeground);
+            setForegroundServiceAllowance(record, allowRunningInForeground);
             mHandler.postSessionsChanged(record);
         }
     }
@@ -388,10 +400,12 @@
             }
             user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
             if (playbackState != null) {
-                setForegroundServiceAllowance(
-                        record,
-                        /* allowRunningInForeground= */ playbackState.isActive()
-                                && record.isActive());
+                boolean allowRunningInForeground = playbackState.isActive() && record.isActive();
+                Log.d(TAG, "onSessionPlaybackStateChanged: "
+                        + "record=" + record
+                        + "playbackState=" + playbackState
+                        + "allowRunningInForeground=" + allowRunningInForeground);
+                setForegroundServiceAllowance(record, allowRunningInForeground);
             }
         }
     }
@@ -556,6 +570,8 @@
         }
 
         session.close();
+
+        Log.d(TAG, "destroySessionLocked: record=" + session);
         setForegroundServiceAllowance(session, /* allowRunningInForeground= */ false);
         mHandler.postSessionsChanged(session);
     }
@@ -574,12 +590,43 @@
         if (allowRunningInForeground) {
             mActivityManagerInternal.startForegroundServiceDelegate(
                     foregroundServiceDelegationOptions, /* connection= */ null);
+            reportMediaInteractionEvent(record, /* userEngaged= */ true);
         } else {
             mActivityManagerInternal.stopForegroundServiceDelegate(
                     foregroundServiceDelegationOptions);
+            reportMediaInteractionEvent(record, /* userEngaged= */ false);
         }
     }
 
+    private void reportMediaInteractionEvent(MediaSessionRecordImpl record, boolean userEngaged) {
+           if (!android.app.usage.Flags.userInteractionTypeApi()) {
+                return;
+            }
+
+            String packageName = record.getPackageName();
+            int sessionUid = record.getUid();
+            String actionToLog = null;
+            if (userEngaged) {
+                if (!mUserEngagingSessions.contains(sessionUid)) {
+                    actionToLog = "start";
+                }
+                mUserEngagingSessions.add(sessionUid);
+            } else {
+                if (mUserEngagingSessions.contains(sessionUid)) {
+                    actionToLog = "stop";
+                }
+                mUserEngagingSessions.remove(sessionUid);
+            }
+
+            if (actionToLog != null) {
+                PersistableBundle extras = new PersistableBundle();
+                extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, "android.media");
+                extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, actionToLog);
+                mUsageStatsManagerInternal.reportUserInteractionEvent(
+                        packageName, record.getUserId(), extras);
+            }
+    }
+
     void tempAllowlistTargetPkgIfPossible(int targetUid, String targetPackage,
             int callingPid, int callingUid, String callingPackage, String reason) {
         final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
index df612e6..bbe6d3a 100644
--- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
+++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
@@ -20,6 +20,7 @@
 import android.content.pm.PackageManager;
 import android.media.MediaMetrics;
 import android.media.metrics.BundleSession;
+import android.media.metrics.EditingEndedEvent;
 import android.media.metrics.IMediaMetricsManager;
 import android.media.metrics.NetworkEvent;
 import android.media.metrics.PlaybackErrorEvent;
@@ -346,6 +347,24 @@
             StatsLog.write(statsEvent);
         }
 
+        @Override
+        public void reportEditingEndedEvent(String sessionId, EditingEndedEvent event, int userId) {
+            int level = loggingLevel();
+            if (level == LOGGING_LEVEL_BLOCKED) {
+                return;
+            }
+            StatsEvent statsEvent =
+                    StatsEvent.newBuilder()
+                            .setAtomId(798)
+                            .writeString(sessionId)
+                            .writeInt(event.getFinalState())
+                            .writeInt(event.getErrorCode())
+                            .writeLong(event.getTimeSinceCreatedMillis())
+                            .usePooledBuffer()
+                            .build();
+            StatsLog.write(statsEvent);
+        }
+
         private int loggingLevel() {
             synchronized (mLock) {
                 int uid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 6deda46..bbb19e3 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -35,6 +35,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions.LaunchCookie;
 import android.app.AppOpsManager;
 import android.app.IProcessObserver;
 import android.app.compat.CompatChanges;
@@ -214,6 +215,11 @@
                 }
 
                 @Override
+                public void onProcessStarted(int pid, int processUid, int packageUid,
+                        String packageName, String processName) {
+                }
+
+                @Override
                 public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
                     MediaProjectionManagerService.this.handleForegroundServicesChanged(pid, uid,
                             serviceTypes);
@@ -304,7 +310,7 @@
     }
 
     @VisibleForTesting
-    void addCallback(final IMediaProjectionWatcherCallback callback) {
+    MediaProjectionInfo addCallback(final IMediaProjectionWatcherCallback callback) {
         IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
             @Override
             public void binderDied() {
@@ -314,6 +320,7 @@
         synchronized (mLock) {
             mCallbackDelegate.add(callback);
             linkDeathRecipientLocked(callback, deathRecipient);
+            return mProjectionGrant != null ? mProjectionGrant.getProjectionInfo() : null;
         }
     }
 
@@ -542,8 +549,11 @@
                             DEFAULT_DISPLAY));
                     break;
                 case RECORD_CONTENT_TASK:
-                    setReviewedConsentSessionLocked(ContentRecordingSession.createTaskSession(
-                            mProjectionGrant.getLaunchCookie()));
+                    IBinder taskWindowContainerToken =
+                            mProjectionGrant.getLaunchCookie() == null ? null
+                                    : mProjectionGrant.getLaunchCookie().binder;
+                    setReviewedConsentSessionLocked(
+                            ContentRecordingSession.createTaskSession(taskWindowContainerToken));
                     break;
             }
         }
@@ -653,14 +663,14 @@
         }
 
         @Override // Binder call
-        public boolean hasProjectionPermission(int uid, String packageName) {
+        public boolean hasProjectionPermission(int processUid, String packageName) {
             final long token = Binder.clearCallingIdentity();
             boolean hasPermission = false;
             try {
                 hasPermission |= checkPermission(packageName,
                         android.Manifest.permission.CAPTURE_VIDEO_OUTPUT)
                         || mAppOps.noteOpNoThrow(
-                                AppOpsManager.OP_PROJECT_MEDIA, uid, packageName)
+                                AppOpsManager.OP_PROJECT_MEDIA, processUid, packageName)
                         == AppOpsManager.MODE_ALLOWED;
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -669,7 +679,7 @@
         }
 
         @Override // Binder call
-        public IMediaProjection createProjection(int uid, String packageName, int type,
+        public IMediaProjection createProjection(int processUid, String packageName, int type,
                 boolean isPermanentGrant) {
             if (mContext.checkCallingPermission(MANAGE_MEDIA_PROJECTION)
                         != PackageManager.PERMISSION_GRANTED) {
@@ -680,13 +690,13 @@
                 throw new IllegalArgumentException("package name must not be empty");
             }
             final UserHandle callingUser = Binder.getCallingUserHandle();
-            return createProjectionInternal(uid, packageName, type, isPermanentGrant,
+            return createProjectionInternal(processUid, packageName, type, isPermanentGrant,
                     callingUser);
         }
 
         @Override // Binder call
         @EnforcePermission(MANAGE_MEDIA_PROJECTION)
-        public IMediaProjection getProjection(int uid, String packageName) {
+        public IMediaProjection getProjection(int processUid, String packageName) {
             getProjection_enforcePermission();
             if (packageName == null || packageName.isEmpty()) {
                 throw new IllegalArgumentException("package name must not be empty");
@@ -695,7 +705,7 @@
             MediaProjection projection;
             final long callingToken = Binder.clearCallingIdentity();
             try {
-                projection = getProjectionInternal(uid, packageName);
+                projection = getProjectionInternal(processUid, packageName);
             } finally {
                 Binder.restoreCallingIdentity(callingToken);
             }
@@ -786,11 +796,11 @@
 
         @Override //Binder call
         @EnforcePermission(MANAGE_MEDIA_PROJECTION)
-        public void addCallback(final IMediaProjectionWatcherCallback callback) {
+        public MediaProjectionInfo addCallback(final IMediaProjectionWatcherCallback callback) {
             addCallback_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
-                MediaProjectionManagerService.this.addCallback(callback);
+                return MediaProjectionManagerService.this.addCallback(callback);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -869,12 +879,13 @@
 
         @Override // Binder call
         @EnforcePermission(MANAGE_MEDIA_PROJECTION)
-        public void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource) {
+        public void notifyPermissionRequestInitiated(
+                int hostProcessUid, int sessionCreationSource) {
             notifyPermissionRequestInitiated_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 MediaProjectionManagerService.this.notifyPermissionRequestInitiated(
-                        hostUid, sessionCreationSource);
+                        hostProcessUid, sessionCreationSource);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -882,11 +893,11 @@
 
         @Override // Binder call
         @EnforcePermission(MANAGE_MEDIA_PROJECTION)
-        public void notifyPermissionRequestDisplayed(int hostUid) {
+        public void notifyPermissionRequestDisplayed(int hostProcessUid) {
             notifyPermissionRequestDisplayed_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
-                MediaProjectionManagerService.this.notifyPermissionRequestDisplayed(hostUid);
+                MediaProjectionManagerService.this.notifyPermissionRequestDisplayed(hostProcessUid);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -894,11 +905,11 @@
 
         @Override // Binder call
         @EnforcePermission(MANAGE_MEDIA_PROJECTION)
-        public void notifyPermissionRequestCancelled(int hostUid) {
+        public void notifyPermissionRequestCancelled(int hostProcessUid) {
             notifyPermissionRequestCancelled_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
-                MediaProjectionManagerService.this.notifyPermissionRequestCancelled(hostUid);
+                MediaProjectionManagerService.this.notifyPermissionRequestCancelled(hostProcessUid);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -906,11 +917,11 @@
 
         @Override // Binder call
         @EnforcePermission(MANAGE_MEDIA_PROJECTION)
-        public void notifyAppSelectorDisplayed(int hostUid) {
+        public void notifyAppSelectorDisplayed(int hostProcessUid) {
             notifyAppSelectorDisplayed_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
-                MediaProjectionManagerService.this.notifyAppSelectorDisplayed(hostUid);
+                MediaProjectionManagerService.this.notifyAppSelectorDisplayed(hostProcessUid);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -919,12 +930,12 @@
         @Override // Binder call
         @EnforcePermission(MANAGE_MEDIA_PROJECTION)
         public void notifyWindowingModeChanged(
-                int contentToRecord, int targetUid, int windowingMode) {
+                int contentToRecord, int targetProcessUid, int windowingMode) {
             notifyWindowingModeChanged_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 MediaProjectionManagerService.this.notifyWindowingModeChanged(
-                        contentToRecord, targetUid, windowingMode);
+                        contentToRecord, targetProcessUid, windowingMode);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -966,7 +977,7 @@
         private IBinder mToken;
         private IBinder.DeathRecipient mDeathEater;
         private boolean mRestoreSystemAlertWindow;
-        private IBinder mLaunchCookie = null;
+        private LaunchCookie mLaunchCookie = null;
 
         // Values for tracking token validity.
         // Timeout value to compare creation time against.
@@ -1179,14 +1190,14 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
         @Override // Binder call
-        public void setLaunchCookie(IBinder launchCookie) {
+        public void setLaunchCookie(LaunchCookie launchCookie) {
             setLaunchCookie_enforcePermission();
             mLaunchCookie = launchCookie;
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
         @Override // Binder call
-        public IBinder getLaunchCookie() {
+        public LaunchCookie getLaunchCookie() {
             getLaunchCookie_enforcePermission();
             return mLaunchCookie;
         }
@@ -1243,7 +1254,7 @@
         }
 
         public MediaProjectionInfo getProjectionInfo() {
-            return new MediaProjectionInfo(packageName, userHandle);
+            return new MediaProjectionInfo(packageName, userHandle, mLaunchCookie);
         }
 
         boolean requiresForegroundService() {
diff --git a/services/core/java/com/android/server/net/Android.bp b/services/core/java/com/android/server/net/Android.bp
new file mode 100644
index 0000000..71d8e6b
--- /dev/null
+++ b/services/core/java/com/android/server/net/Android.bp
@@ -0,0 +1,10 @@
+aconfig_declarations {
+    name: "net_flags",
+    package: "com.android.server.net",
+    srcs: ["*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "net_flags_lib",
+    aconfig_declarations: "net_flags",
+}
diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java
index 681d1a0..d25f529 100644
--- a/services/core/java/com/android/server/net/NetworkManagementService.java
+++ b/services/core/java/com/android/server/net/NetworkManagementService.java
@@ -17,6 +17,7 @@
 package com.android.server.net;
 
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -27,6 +28,7 @@
 import static android.net.INetd.FIREWALL_DENYLIST;
 import static android.net.INetd.FIREWALL_RULE_ALLOW;
 import static android.net.INetd.FIREWALL_RULE_DENY;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_BACKGROUND;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
@@ -187,6 +189,13 @@
      */
     @GuardedBy("mRulesLock")
     private final SparseIntArray mUidFirewallLowPowerStandbyRules = new SparseIntArray();
+
+    /**
+     * Contains the per-UID firewall rules that are used when Background chain is enabled.
+     */
+    @GuardedBy("mRulesLock")
+    private final SparseIntArray mUidFirewallBackgroundRules = new SparseIntArray();
+
     /** Set of states for the child firewall chains. True if the chain is active. */
     @GuardedBy("mRulesLock")
     final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray();
@@ -449,13 +458,15 @@
             syncFirewallChainLocked(FIREWALL_CHAIN_POWERSAVE, "powersave ");
             syncFirewallChainLocked(FIREWALL_CHAIN_RESTRICTED, "restricted ");
             syncFirewallChainLocked(FIREWALL_CHAIN_LOW_POWER_STANDBY, "low power standby ");
+            syncFirewallChainLocked(FIREWALL_CHAIN_BACKGROUND, FIREWALL_CHAIN_NAME_BACKGROUND);
 
             final int[] chains = {
                     FIREWALL_CHAIN_STANDBY,
                     FIREWALL_CHAIN_DOZABLE,
                     FIREWALL_CHAIN_POWERSAVE,
                     FIREWALL_CHAIN_RESTRICTED,
-                    FIREWALL_CHAIN_LOW_POWER_STANDBY
+                    FIREWALL_CHAIN_LOW_POWER_STANDBY,
+                    FIREWALL_CHAIN_BACKGROUND,
             };
 
             for (int chain : chains) {
@@ -1206,6 +1217,8 @@
                 return FIREWALL_CHAIN_NAME_RESTRICTED;
             case FIREWALL_CHAIN_LOW_POWER_STANDBY:
                 return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
+            case FIREWALL_CHAIN_BACKGROUND:
+                return FIREWALL_CHAIN_NAME_BACKGROUND;
             default:
                 throw new IllegalArgumentException("Bad child chain: " + chain);
         }
@@ -1223,6 +1236,8 @@
                 return FIREWALL_ALLOWLIST;
             case FIREWALL_CHAIN_LOW_POWER_STANDBY:
                 return FIREWALL_ALLOWLIST;
+            case FIREWALL_CHAIN_BACKGROUND:
+                return FIREWALL_ALLOWLIST;
             default:
                 return isFirewallEnabled() ? FIREWALL_ALLOWLIST : FIREWALL_DENYLIST;
         }
@@ -1343,6 +1358,8 @@
                 return mUidFirewallRestrictedRules;
             case FIREWALL_CHAIN_LOW_POWER_STANDBY:
                 return mUidFirewallLowPowerStandbyRules;
+            case FIREWALL_CHAIN_BACKGROUND:
+                return mUidFirewallBackgroundRules;
             case FIREWALL_CHAIN_NONE:
                 return mUidFirewallRules;
             default:
@@ -1395,6 +1412,10 @@
             pw.println(getFirewallChainState(FIREWALL_CHAIN_LOW_POWER_STANDBY));
             dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY,
                     mUidFirewallLowPowerStandbyRules);
+
+            pw.print("UID firewall background chain enabled: ");
+            pw.println(getFirewallChainState(FIREWALL_CHAIN_BACKGROUND));
+            dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_BACKGROUND, mUidFirewallBackgroundRules);
         }
 
         pw.print("Firewall enabled: "); pw.println(mFirewallEnabled);
@@ -1494,6 +1515,11 @@
                 if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of low power standby");
                 return true;
             }
+            if (getFirewallChainState(FIREWALL_CHAIN_BACKGROUND)
+                    && mUidFirewallBackgroundRules.get(uid) != FIREWALL_RULE_ALLOW) {
+                if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because it is in background");
+                return true;
+            }
             if (mUidRejectOnMetered.get(uid)) {
                 if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of no metered data"
                         + " in the background");
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index 4d19ead..8e2d778 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -16,6 +16,7 @@
 package com.android.server.net;
 
 import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -24,6 +25,7 @@
 import static android.net.INetd.FIREWALL_RULE_ALLOW;
 import static android.net.INetd.FIREWALL_RULE_DENY;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_BACKGROUND;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
@@ -42,7 +44,6 @@
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.annotations.Keep;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.RingBuffer;
 import com.android.server.am.ProcessList;
@@ -390,6 +391,8 @@
                 return FIREWALL_CHAIN_NAME_RESTRICTED;
             case FIREWALL_CHAIN_LOW_POWER_STANDBY:
                 return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
+            case FIREWALL_CHAIN_BACKGROUND:
+                return FIREWALL_CHAIN_NAME_BACKGROUND;
             default:
                 return String.valueOf(chain);
         }
@@ -414,7 +417,7 @@
         private static final Date sDate = new Date();
 
         public LogBuffer(int capacity) {
-            super(Data.class, capacity);
+            super(Data::new, Data[]::new, capacity);
         }
 
         public void uidStateChanged(int uid, int procState, long procStateSeq,
@@ -690,12 +693,8 @@
 
     /**
      * Container class for all networkpolicy events data.
-     *
-     * Note: This class needs to be public for RingBuffer class to be able to create
-     * new instances of this.
      */
-    @Keep
-    public static final class Data {
+    private static final class Data {
         public int type;
         public long timeStamp;
 
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index e4e48bd..f9ffb1c 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -47,6 +47,7 @@
 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
 import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
 import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
 import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
@@ -54,6 +55,7 @@
 import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
 import static android.net.ConnectivityManager.BLOCKED_REASON_RESTRICTED_MODE;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -77,6 +79,7 @@
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_FOREGROUND;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_NOT_IN_BACKGROUND;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_ALLOWLIST;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
@@ -96,6 +99,7 @@
 import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED;
 import static android.net.NetworkPolicyManager.allowedReasonsToString;
 import static android.net.NetworkPolicyManager.blockedReasonsToString;
+import static android.net.NetworkPolicyManager.isProcStateAllowedNetworkWhileBackground;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileInLowPowerStandby;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground;
@@ -202,12 +206,12 @@
 import android.os.MessageQueue.IdleHandler;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
+import android.os.PowerExemptionManager;
 import android.os.PowerExemptionManager.ReasonCode;
 import android.os.PowerManager;
 import android.os.PowerManager.ServiceType;
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
-import android.os.PowerWhitelistManager;
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -243,6 +247,7 @@
 import android.util.SparseIntArray;
 import android.util.SparseLongArray;
 import android.util.SparseSetArray;
+import android.util.TimeUtils;
 import android.util.Xml;
 
 import com.android.internal.R;
@@ -458,6 +463,12 @@
      */
     private static final int MSG_UIDS_BLOCKED_REASONS_CHANGED = 23;
 
+    /**
+     * Message to update background restriction rules for uids that should lose network access
+     * due to being in the background.
+     */
+    private static final int MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS = 24;
+
     private static final int UID_MSG_STATE_CHANGED = 100;
     private static final int UID_MSG_GONE = 101;
 
@@ -476,7 +487,7 @@
 
     private ConnectivityManager mConnManager;
     private PowerManagerInternal mPowerManagerInternal;
-    private PowerWhitelistManager mPowerWhitelistManager;
+    private PowerExemptionManager mPowerExemptionManager;
     @NonNull
     private final Dependencies mDeps;
 
@@ -491,6 +502,12 @@
     // Denotes the status of restrict background read from disk.
     private boolean mLoadedRestrictBackground;
 
+    /**
+     * Whether or not network for apps in proc-states greater than
+     * {@link NetworkPolicyManager#BACKGROUND_THRESHOLD_STATE} is always blocked.
+     */
+    private boolean mBackgroundNetworkRestricted;
+
     // See main javadoc for instructions on how to use these locks.
     final Object mUidRulesFirstLock = new Object();
     final Object mNetworkPoliciesSecondLock = new Object();
@@ -515,6 +532,15 @@
 
     private volatile boolean mNetworkManagerReady;
 
+    /**
+     * Delay after which a uid going into a process state greater than or equal to
+     * {@link NetworkPolicyManager#BACKGROUND_THRESHOLD_STATE} will lose network access.
+     * The delay is meant to prevent churn due to quick process-state changes.
+     * Note that there is no delay while granting network access.
+     */
+    @VisibleForTesting
+    long mBackgroundRestrictionDelayMs = TimeUnit.SECONDS.toMillis(5);
+
     /** Defined network policies. */
     @GuardedBy("mNetworkPoliciesSecondLock")
     final ArrayMap<NetworkTemplate, NetworkPolicy> mNetworkPolicy = new ArrayMap<>();
@@ -546,6 +572,8 @@
     @GuardedBy("mUidRulesFirstLock")
     final SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray();
     @GuardedBy("mUidRulesFirstLock")
+    final SparseIntArray mUidFirewallBackgroundRules = new SparseIntArray();
+    @GuardedBy("mUidRulesFirstLock")
     final SparseIntArray mUidFirewallRestrictedModeRules = new SparseIntArray();
     @GuardedBy("mUidRulesFirstLock")
     final SparseIntArray mUidFirewallLowPowerStandbyModeRules = new SparseIntArray();
@@ -625,6 +653,14 @@
     @GuardedBy("mUidRulesFirstLock")
     private final SparseArray<UidBlockedState> mTmpUidBlockedState = new SparseArray<>();
 
+    /**
+     * Stores a map of uids to the time their transition to background is considered complete. They
+     * will lose network access after this time. This is used to prevent churn in rules due to quick
+     * process-state transitions.
+     */
+    @GuardedBy("mUidRulesFirstLock")
+    private final SparseLongArray mBackgroundTransitioningUids = new SparseLongArray();
+
     /** Map from network ID to last observed meteredness state */
     @GuardedBy("mNetworkPoliciesSecondLock")
     private final SparseBooleanArray mNetworkMetered = new SparseBooleanArray();
@@ -824,7 +860,7 @@
         mContext = Objects.requireNonNull(context, "missing context");
         mActivityManager = Objects.requireNonNull(activityManager, "missing activityManager");
         mNetworkManager = Objects.requireNonNull(networkManagement, "missing networkManagement");
-        mPowerWhitelistManager = mContext.getSystemService(PowerWhitelistManager.class);
+        mPowerExemptionManager = mContext.getSystemService(PowerExemptionManager.class);
         mClock = Objects.requireNonNull(clock, "missing Clock");
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
@@ -860,15 +896,15 @@
 
     @GuardedBy("mUidRulesFirstLock")
     private void updatePowerSaveAllowlistUL() {
-        int[] whitelist = mPowerWhitelistManager.getWhitelistedAppIds(/* includingIdle */ false);
+        int[] allowlist = mPowerExemptionManager.getAllowListedAppIds(/* includingIdle */ false);
         mPowerSaveWhitelistExceptIdleAppIds.clear();
-        for (int uid : whitelist) {
+        for (int uid : allowlist) {
             mPowerSaveWhitelistExceptIdleAppIds.put(uid, true);
         }
 
-        whitelist = mPowerWhitelistManager.getWhitelistedAppIds(/* includingIdle */ true);
+        allowlist = mPowerExemptionManager.getAllowListedAppIds(/* includingIdle */ true);
         mPowerSaveWhitelistAppIds.clear();
-        for (int uid : whitelist) {
+        for (int uid : allowlist) {
             mPowerSaveWhitelistAppIds.put(uid, true);
         }
     }
@@ -1018,6 +1054,14 @@
                         writePolicyAL();
                     }
 
+                    // The flag is boot-stable.
+                    mBackgroundNetworkRestricted = Flags.networkBlockedForTopSleepingAndAbove();
+                    if (mBackgroundNetworkRestricted) {
+                        // Firewall rules and UidBlockedState will get updated in
+                        // updateRulesForGlobalChangeAL below.
+                        enableFirewallChainUL(FIREWALL_CHAIN_BACKGROUND, true);
+                    }
+
                     setRestrictBackgroundUL(mLoadedRestrictBackground, "init_service");
                     updateRulesForGlobalChangeAL(false);
                     updateNotificationsNL();
@@ -1028,17 +1072,22 @@
                 final int changes = ActivityManager.UID_OBSERVER_PROCSTATE
                         | ActivityManager.UID_OBSERVER_GONE
                         | ActivityManager.UID_OBSERVER_CAPABILITY;
+
+                final int cutpoint = mBackgroundNetworkRestricted ? PROCESS_STATE_UNKNOWN
+                        : NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE;
+                // TODO (b/319728914): Filter out the unnecessary changes when using no cutpoint.
+
                 mActivityManagerInternal.registerNetworkPolicyUidObserver(mUidObserver, changes,
-                        NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE, "android");
+                        cutpoint, "android");
                 mNetworkManager.registerObserver(mAlertObserver);
             } catch (RemoteException e) {
                 // ignored; both services live in system_server
             }
 
             // listen for changes to power save allowlist
-            final IntentFilter whitelistFilter = new IntentFilter(
+            final IntentFilter allowlistFilter = new IntentFilter(
                     PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
-            mContext.registerReceiver(mPowerSaveWhitelistReceiver, whitelistFilter, null, mHandler);
+            mContext.registerReceiver(mPowerSaveAllowlistReceiver, allowlistFilter, null, mHandler);
 
             // watch for network interfaces to be claimed
             final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION);
@@ -1189,12 +1238,15 @@
         }
     }
 
-    final private BroadcastReceiver mPowerSaveWhitelistReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mPowerSaveAllowlistReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             // on background handler thread, and POWER_SAVE_WHITELIST_CHANGED is protected
             synchronized (mUidRulesFirstLock) {
                 updatePowerSaveAllowlistUL();
+                if (mBackgroundNetworkRestricted) {
+                    updateRulesForBackgroundChainUL();
+                }
                 updateRulesForRestrictPowerUL();
                 updateRulesForAppIdleUL();
             }
@@ -3914,6 +3966,11 @@
                 }
 
                 fout.println();
+                fout.println("Flags:");
+                fout.println("Network blocked for TOP_SLEEPING and above: "
+                        + mBackgroundNetworkRestricted);
+
+                fout.println();
                 fout.println("mRestrictBackgroundLowPowerMode: " + mRestrictBackgroundLowPowerMode);
                 fout.println("mRestrictBackgroundBeforeBsm: " + mRestrictBackgroundBeforeBsm);
                 fout.println("mLoadedRestrictBackground: " + mLoadedRestrictBackground);
@@ -4055,6 +4112,22 @@
                     fout.decreaseIndent();
                 }
 
+                size = mBackgroundTransitioningUids.size();
+                if (size > 0) {
+                    final long nowUptime = SystemClock.uptimeMillis();
+                    fout.println("Uids transitioning to background:");
+                    fout.increaseIndent();
+                    for (int i = 0; i < size; i++) {
+                        fout.print("UID=");
+                        fout.print(mBackgroundTransitioningUids.keyAt(i));
+                        fout.print(", ");
+                        TimeUtils.formatDuration(mBackgroundTransitioningUids.valueAt(i), nowUptime,
+                                fout);
+                        fout.println();
+                    }
+                    fout.decreaseIndent();
+                }
+
                 final SparseBooleanArray knownUids = new SparseBooleanArray();
                 collectKeys(mUidState, knownUids);
                 synchronized (mUidBlockedState) {
@@ -4182,6 +4255,12 @@
         return isProcStateAllowedWhileInLowPowerStandby(uidState);
     }
 
+    @GuardedBy("mUidRulesFirstLock")
+    private boolean isUidExemptFromBackgroundRestrictions(int uid) {
+        return mBackgroundTransitioningUids.indexOfKey(uid) >= 0
+                || isProcStateAllowedNetworkWhileBackground(mUidState.get(uid));
+    }
+
     /**
      * Process state of UID changed; if needed, will trigger
      * {@link #updateRulesForDataUsageRestrictionsUL(int)} and
@@ -4207,6 +4286,8 @@
                 // state changed, push updated rules
                 mUidState.put(uid, newUidState);
                 updateRestrictBackgroundRulesOnUidStatusChangedUL(uid, oldUidState, newUidState);
+
+                boolean updatePowerRestrictionRules = false;
                 boolean allowedWhileIdleOrPowerSaveModeChanged =
                         isProcStateAllowedWhileIdleOrPowerSaveMode(oldUidState)
                                 != isProcStateAllowedWhileIdleOrPowerSaveMode(newUidState);
@@ -4218,19 +4299,44 @@
                     if (mRestrictPower) {
                         updateRuleForRestrictPowerUL(uid);
                     }
-                    updateRulesForPowerRestrictionsUL(uid, procState);
+                    updatePowerRestrictionRules = true;
+                }
+                if (mBackgroundNetworkRestricted) {
+                    final boolean wasAllowed = isProcStateAllowedNetworkWhileBackground(
+                            oldUidState);
+                    final boolean isAllowed = isProcStateAllowedNetworkWhileBackground(newUidState);
+                    if (!wasAllowed && isAllowed) {
+                        mBackgroundTransitioningUids.delete(uid);
+                        updateRuleForBackgroundUL(uid);
+                        updatePowerRestrictionRules = true;
+                    } else if (wasAllowed && !isAllowed) {
+                        final long completionTimeMs = SystemClock.uptimeMillis()
+                                + mBackgroundRestrictionDelayMs;
+                        if (mBackgroundTransitioningUids.indexOfKey(uid) < 0) {
+                            // This is just a defensive check in case the upstream code ever makes
+                            // multiple calls for the same process state change.
+                            mBackgroundTransitioningUids.put(uid, completionTimeMs);
+                        }
+                        if (!mHandler.hasMessages(MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS)) {
+                            // Many uids may be in this "transitioning" state at the same time, so
+                            // using one message at a time to avoid congestion in the MessageQueue.
+                            mHandler.sendEmptyMessageAtTime(
+                                    MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS, completionTimeMs);
+                        }
+                    }
                 }
                 if (mLowPowerStandbyActive) {
                     boolean allowedInLpsChanged =
                             isProcStateAllowedWhileInLowPowerStandby(oldUidState)
                                     != isProcStateAllowedWhileInLowPowerStandby(newUidState);
                     if (allowedInLpsChanged) {
-                        if (!allowedWhileIdleOrPowerSaveModeChanged) {
-                            updateRulesForPowerRestrictionsUL(uid, procState);
-                        }
                         updateRuleForLowPowerStandbyUL(uid);
+                        updatePowerRestrictionRules = true;
                     }
                 }
+                if (updatePowerRestrictionRules) {
+                    updateRulesForPowerRestrictionsUL(uid, procState);
+                }
                 return true;
             }
         } finally {
@@ -4253,6 +4359,12 @@
                 if (mRestrictPower) {
                     updateRuleForRestrictPowerUL(uid);
                 }
+                if (mBackgroundNetworkRestricted) {
+                    // Uid is no longer running, there is no point in any grace period of network
+                    // access during transitions to lower importance proc-states.
+                    mBackgroundTransitioningUids.delete(uid);
+                    updateRuleForBackgroundUL(uid);
+                }
                 updateRulesForPowerRestrictionsUL(uid);
                 if (mLowPowerStandbyActive) {
                     updateRuleForLowPowerStandbyUL(uid);
@@ -4460,11 +4572,41 @@
         }
     }
 
+    /**
+     * Updates the rules for apps allowlisted to use network while in the background.
+     */
+    @GuardedBy("mUidRulesFirstLock")
+    private void updateRulesForBackgroundChainUL() {
+        Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForBackgroundChainUL");
+        try {
+            final SparseIntArray uidRules = mUidFirewallBackgroundRules;
+            uidRules.clear();
+
+            final List<UserInfo> users = mUserManager.getUsers();
+            for (int ui = users.size() - 1; ui >= 0; ui--) {
+                final UserInfo user = users.get(ui);
+                updateRulesForAllowlistedAppIds(uidRules, mPowerSaveTempWhitelistAppIds, user.id);
+                updateRulesForAllowlistedAppIds(uidRules, mPowerSaveWhitelistAppIds, user.id);
+                updateRulesForAllowlistedAppIds(uidRules, mPowerSaveWhitelistExceptIdleAppIds,
+                        user.id);
+            }
+            for (int i = mUidState.size() - 1; i >= 0; i--) {
+                if (mBackgroundTransitioningUids.indexOfKey(mUidState.keyAt(i)) >= 0
+                        || isProcStateAllowedNetworkWhileBackground(mUidState.valueAt(i))) {
+                    uidRules.put(mUidState.keyAt(i), FIREWALL_RULE_ALLOW);
+                }
+            }
+            setUidFirewallRulesUL(FIREWALL_CHAIN_BACKGROUND, uidRules);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_NETWORK);
+        }
+    }
+
     private void updateRulesForAllowlistedAppIds(final SparseIntArray uidRules,
-            final SparseBooleanArray whitelistedAppIds, int userId) {
-        for (int i = whitelistedAppIds.size() - 1; i >= 0; --i) {
-            if (whitelistedAppIds.valueAt(i)) {
-                final int appId = whitelistedAppIds.keyAt(i);
+            final SparseBooleanArray allowlistedAppIds, int userId) {
+        for (int i = allowlistedAppIds.size() - 1; i >= 0; --i) {
+            if (allowlistedAppIds.valueAt(i)) {
+                final int appId = allowlistedAppIds.keyAt(i);
                 final int uid = UserHandle.getUid(userId, appId);
                 uidRules.put(uid, FIREWALL_RULE_ALLOW);
             }
@@ -4523,12 +4665,12 @@
     @GuardedBy("mUidRulesFirstLock")
     private boolean isAllowlistedFromPowerSaveUL(int uid, boolean deviceIdleMode) {
         final int appId = UserHandle.getAppId(uid);
-        boolean isWhitelisted = mPowerSaveTempWhitelistAppIds.get(appId)
+        boolean allowlisted = mPowerSaveTempWhitelistAppIds.get(appId)
                 || mPowerSaveWhitelistAppIds.get(appId);
         if (!deviceIdleMode) {
-            isWhitelisted = isWhitelisted || isAllowlistedFromPowerSaveExceptIdleUL(uid);
+            allowlisted = allowlisted || isAllowlistedFromPowerSaveExceptIdleUL(uid);
         }
-        return isWhitelisted;
+        return allowlisted;
     }
 
     /**
@@ -4617,6 +4759,38 @@
     }
 
     /**
+     * Update firewall rule for a single uid whenever there are any interesting changes in the uid.
+     * Currently, it is called when:
+     * - The uid is added to or removed from power allowlists
+     * - The uid undergoes a process-state change
+     * - A package belonging to this uid is added
+     * - The uid is evicted from memory
+     */
+    @GuardedBy("mUidRulesFirstLock")
+    void updateRuleForBackgroundUL(int uid) {
+        if (!isUidValidForAllowlistRulesUL(uid)) {
+            return;
+        }
+
+        Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRuleForBackgroundUL: " + uid);
+        try {
+            // The uid should be absent from mUidState and mBackgroundTransitioningUids if it is
+            // not running when this method is called. Then, the firewall state will depend on the
+            // allowlist alone. This is the desired behavior.
+            if (isAllowlistedFromPowerSaveUL(uid, false)
+                    || isUidExemptFromBackgroundRestrictions(uid)) {
+                setUidFirewallRuleUL(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_ALLOW);
+                if (LOGD) Log.d(TAG, "updateRuleForBackgroundUL ALLOW " + uid);
+            } else {
+                setUidFirewallRuleUL(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_DEFAULT);
+                if (LOGD) Log.d(TAG, "updateRuleForBackgroundUL " + uid + " to DEFAULT");
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+        }
+    }
+
+    /**
      * Toggle the firewall standby chain and inform listeners if the uid rules have effectively
      * changed.
      */
@@ -4663,6 +4837,9 @@
                     "updateRulesForGlobalChangeAL: " + (restrictedNetworksChanged ? "R" : "-"));
         }
         try {
+            if (mBackgroundNetworkRestricted) {
+                updateRulesForBackgroundChainUL();
+            }
             updateRulesForAppIdleUL();
             updateRulesForRestrictPowerUL();
             updateRulesForRestrictBackgroundUL();
@@ -4822,6 +4999,9 @@
             updateRuleForAppIdleUL(uid, PROCESS_STATE_UNKNOWN);
             updateRuleForDeviceIdleUL(uid);
             updateRuleForRestrictPowerUL(uid);
+            if (mBackgroundNetworkRestricted) {
+                updateRuleForBackgroundUL(uid);
+            }
             // Update internal rules.
             updateRulesForPowerRestrictionsUL(uid);
         }
@@ -4959,6 +5139,8 @@
         mUidFirewallStandbyRules.delete(uid);
         mUidFirewallDozableRules.delete(uid);
         mUidFirewallPowerSaveRules.delete(uid);
+        mUidFirewallBackgroundRules.delete(uid);
+        mBackgroundTransitioningUids.delete(uid);
         mPowerSaveWhitelistExceptIdleAppIds.delete(uid);
         mPowerSaveWhitelistAppIds.delete(uid);
         mPowerSaveTempWhitelistAppIds.delete(uid);
@@ -4992,6 +5174,9 @@
         updateRuleForDeviceIdleUL(uid);
         updateRuleForAppIdleUL(uid, PROCESS_STATE_UNKNOWN);
         updateRuleForRestrictPowerUL(uid);
+        if (mBackgroundNetworkRestricted) {
+            updateRuleForBackgroundUL(uid);
+        }
 
         // If the uid has the necessary permissions, then it should be added to the restricted mode
         // firewall allowlist.
@@ -5176,7 +5361,6 @@
      * Similar to above but ignores idle state if app standby is currently disabled by parole.
      *
      * @param uid the uid of the app to update rules for
-     * @param oldUidRules the current rules for the uid, in order to determine if there's a change
      * @param isUidIdle whether uid is idle or not
      */
     @GuardedBy("mUidRulesFirstLock")
@@ -5222,6 +5406,7 @@
             newBlockedReasons |= (mLowPowerStandbyActive ? BLOCKED_REASON_LOW_POWER_STANDBY : 0);
             newBlockedReasons |= (isUidIdle ? BLOCKED_REASON_APP_STANDBY : 0);
             newBlockedReasons |= (uidBlockedState.blockedReasons & BLOCKED_REASON_RESTRICTED_MODE);
+            newBlockedReasons |= mBackgroundNetworkRestricted ? BLOCKED_REASON_APP_BACKGROUND : 0;
 
             newAllowedReasons |= (isSystem(uid) ? ALLOWED_REASON_SYSTEM : 0);
             newAllowedReasons |= (isForeground ? ALLOWED_REASON_FOREGROUND : 0);
@@ -5234,6 +5419,9 @@
                     & ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS);
             newAllowedReasons |= (isAllowlistedFromLowPowerStandbyUL(uid))
                     ? ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST : 0;
+            newAllowedReasons |= (mBackgroundNetworkRestricted
+                    && isUidExemptFromBackgroundRestrictions(uid))
+                    ? ALLOWED_REASON_NOT_IN_BACKGROUND : 0;
 
             uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
                     & BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
@@ -5255,7 +5443,7 @@
 
             oldEffectiveBlockedReasons = previousUidBlockedState.effectiveBlockedReasons;
             newEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
-            uidRules = oldEffectiveBlockedReasons == newEffectiveBlockedReasons
+            uidRules = (oldEffectiveBlockedReasons == newEffectiveBlockedReasons)
                     ? RULE_NONE
                     : uidBlockedState.deriveUidRules();
         }
@@ -5448,6 +5636,28 @@
                     mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
                     return true;
                 }
+                case MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS: {
+                    final long now = SystemClock.uptimeMillis();
+                    long nextCheckTime = Long.MAX_VALUE;
+                    synchronized (mUidRulesFirstLock) {
+                        for (int i = mBackgroundTransitioningUids.size() - 1; i >= 0; i--) {
+                            final long completionTimeMs = mBackgroundTransitioningUids.valueAt(i);
+                            if (completionTimeMs > now) {
+                                nextCheckTime = Math.min(nextCheckTime, completionTimeMs);
+                                continue;
+                            }
+                            final int uid = mBackgroundTransitioningUids.keyAt(i);
+                            mBackgroundTransitioningUids.removeAt(i);
+                            updateRuleForBackgroundUL(uid);
+                            updateRulesForPowerRestrictionsUL(uid, false);
+                        }
+                    }
+                    if (nextCheckTime < Long.MAX_VALUE) {
+                        mHandler.sendEmptyMessageAtTime(MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS,
+                                nextCheckTime);
+                    }
+                    return true;
+                }
                 case MSG_POLICIES_CHANGED: {
                     final int uid = msg.arg1;
                     final int policy = msg.arg2;
@@ -5859,6 +6069,8 @@
                 mUidFirewallRestrictedModeRules.put(uid, rule);
             } else if (chain == FIREWALL_CHAIN_LOW_POWER_STANDBY) {
                 mUidFirewallLowPowerStandbyModeRules.put(uid, rule);
+            } else if (chain == FIREWALL_CHAIN_BACKGROUND) {
+                mUidFirewallBackgroundRules.put(uid, rule);
             }
 
             try {
@@ -5915,6 +6127,8 @@
                     FIREWALL_RULE_DEFAULT);
             mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid,
                     FIREWALL_RULE_DEFAULT);
+            mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, uid,
+                    FIREWALL_RULE_DEFAULT);
             mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, false);
             mLogger.meteredAllowlistChanged(uid, false);
             mNetworkManager.setUidOnMeteredNetworkDenylist(uid, false);
@@ -6441,10 +6655,12 @@
                 effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER;
                 effectiveBlockedReasons &= ~BLOCKED_REASON_DOZE;
                 effectiveBlockedReasons &= ~BLOCKED_REASON_APP_STANDBY;
+                effectiveBlockedReasons &= ~BLOCKED_REASON_APP_BACKGROUND;
             }
             if ((allowedReasons & ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST) != 0) {
                 effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER;
                 effectiveBlockedReasons &= ~BLOCKED_REASON_APP_STANDBY;
+                effectiveBlockedReasons &= ~BLOCKED_REASON_APP_BACKGROUND;
             }
             if ((allowedReasons & ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS) != 0) {
                 effectiveBlockedReasons &= ~BLOCKED_REASON_RESTRICTED_MODE;
@@ -6455,19 +6671,24 @@
             if ((allowedReasons & ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST) != 0) {
                 effectiveBlockedReasons &= ~BLOCKED_REASON_LOW_POWER_STANDBY;
             }
+            if ((allowedReasons & ALLOWED_REASON_NOT_IN_BACKGROUND) != 0) {
+                effectiveBlockedReasons &= ~BLOCKED_REASON_APP_BACKGROUND;
+            }
 
             return effectiveBlockedReasons;
         }
 
         static int getAllowedReasonsForProcState(int procState) {
-            if (procState > NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE) {
-                return ALLOWED_REASON_NONE;
-            } else if (procState <= NetworkPolicyManager.TOP_THRESHOLD_STATE) {
+            if (procState <= NetworkPolicyManager.TOP_THRESHOLD_STATE) {
                 return ALLOWED_REASON_TOP | ALLOWED_REASON_FOREGROUND
-                        | ALLOWED_METERED_REASON_FOREGROUND;
-            } else {
-                return ALLOWED_REASON_FOREGROUND | ALLOWED_METERED_REASON_FOREGROUND;
+                        | ALLOWED_METERED_REASON_FOREGROUND | ALLOWED_REASON_NOT_IN_BACKGROUND;
+            } else if (procState <= NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE) {
+                return ALLOWED_REASON_FOREGROUND | ALLOWED_METERED_REASON_FOREGROUND
+                        | ALLOWED_REASON_NOT_IN_BACKGROUND;
+            } else if (procState < NetworkPolicyManager.BACKGROUND_THRESHOLD_STATE) {
+                return ALLOWED_REASON_NOT_IN_BACKGROUND;
             }
+            return ALLOWED_REASON_NONE;
         }
 
         @Override
@@ -6492,6 +6713,7 @@
                 BLOCKED_REASON_APP_STANDBY,
                 BLOCKED_REASON_RESTRICTED_MODE,
                 BLOCKED_REASON_LOW_POWER_STANDBY,
+                BLOCKED_REASON_APP_BACKGROUND,
                 BLOCKED_METERED_REASON_DATA_SAVER,
                 BLOCKED_METERED_REASON_USER_RESTRICTED,
                 BLOCKED_METERED_REASON_ADMIN_DISABLED,
@@ -6505,6 +6727,7 @@
                 ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST,
                 ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS,
                 ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST,
+                ALLOWED_REASON_NOT_IN_BACKGROUND,
                 ALLOWED_METERED_REASON_USER_EXEMPTED,
                 ALLOWED_METERED_REASON_SYSTEM,
                 ALLOWED_METERED_REASON_FOREGROUND,
@@ -6524,6 +6747,8 @@
                     return "RESTRICTED_MODE";
                 case BLOCKED_REASON_LOW_POWER_STANDBY:
                     return "LOW_POWER_STANDBY";
+                case BLOCKED_REASON_APP_BACKGROUND:
+                    return "APP_BACKGROUND";
                 case BLOCKED_METERED_REASON_DATA_SAVER:
                     return "DATA_SAVER";
                 case BLOCKED_METERED_REASON_USER_RESTRICTED:
@@ -6554,6 +6779,8 @@
                     return "RESTRICTED_MODE_PERMISSIONS";
                 case ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST:
                     return "LOW_POWER_STANDBY_ALLOWLIST";
+                case ALLOWED_REASON_NOT_IN_BACKGROUND:
+                    return "NOT_IN_BACKGROUND";
                 case ALLOWED_METERED_REASON_USER_EXEMPTED:
                     return "METERED_USER_EXEMPTED";
                 case ALLOWED_METERED_REASON_SYSTEM:
@@ -6621,7 +6848,8 @@
             int powerBlockedReasons = BLOCKED_REASON_APP_STANDBY
                     | BLOCKED_REASON_DOZE
                     | BLOCKED_REASON_BATTERY_SAVER
-                    | BLOCKED_REASON_LOW_POWER_STANDBY;
+                    | BLOCKED_REASON_LOW_POWER_STANDBY
+                    | BLOCKED_REASON_APP_BACKGROUND;
             if ((effectiveBlockedReasons & powerBlockedReasons) != 0) {
                 uidRule |= RULE_REJECT_ALL;
             } else if ((blockedReasons & powerBlockedReasons) != 0) {
diff --git a/services/core/java/com/android/server/net/flags.aconfig b/services/core/java/com/android/server/net/flags.aconfig
new file mode 100644
index 0000000..419665a
--- /dev/null
+++ b/services/core/java/com/android/server/net/flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.net"
+
+flag {
+    name: "network_blocked_for_top_sleeping_and_above"
+    namespace: "backstage_power"
+    description: "Block network access for apps in a low importance background state"
+    bug: "304347838"
+}
diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
index 71a6b5e..ab650af 100644
--- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
+++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
@@ -16,7 +16,8 @@
 
 package com.android.server.notification;
 
-import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
 
 import android.app.UiModeManager;
 import android.app.WallpaperManager;
@@ -128,10 +129,9 @@
 
     private void updateNightModeImmediately(boolean useNightMode) {
         Binder.withCleanCallingIdentity(() -> {
-            // TODO: b/314285749 - Placeholder; use real APIs when available.
-            mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
-            mUiModeManager.setNightModeActivatedForCustomMode(MODE_NIGHT_CUSTOM_TYPE_BEDTIME,
-                    useNightMode);
+            mUiModeManager.setAttentionModeThemeOverlay(
+                    useNightMode ? MODE_ATTENTION_THEME_OVERLAY_NIGHT
+                            : MODE_ATTENTION_THEME_OVERLAY_OFF);
         });
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 85c4ffe..f852b81 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -57,6 +57,7 @@
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
@@ -71,7 +72,6 @@
 import com.android.server.EventLogTags;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
-import com.android.server.notification.Flags;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -81,6 +81,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * NotificationManagerService helper for handling notification attention effects:
@@ -100,6 +101,20 @@
     private static final int DEFAULT_NOTIFICATION_COOLDOWN_ALL = 1;
     private static final int DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED = 0;
 
+    @VisibleForTesting
+    static final Set<String> NOTIFICATION_AVALANCHE_TRIGGER_INTENTS = Set.of(
+            Intent.ACTION_AIRPLANE_MODE_CHANGED,
+            Intent.ACTION_BOOT_COMPLETED,
+            Intent.ACTION_USER_SWITCHED,
+            Intent.ACTION_MANAGED_PROFILE_AVAILABLE
+    );
+
+    @VisibleForTesting
+    static final Map<String, Pair<String, Boolean>> NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS = Map.of(
+            Intent.ACTION_AIRPLANE_MODE_CHANGED, new Pair<>("state", false),
+            Intent.ACTION_MANAGED_PROFILE_AVAILABLE, new Pair<>(Intent.EXTRA_QUIET_MODE, false)
+    );
+
     private final Context mContext;
     private final PackageManager mPackageManager;
     private final TelephonyManager mTelephonyManager;
@@ -191,7 +206,7 @@
         mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume);
 
         if (Flags.politeNotifications()) {
-            mStrategy = getPolitenessStrategy();
+            mStrategy = createPolitenessStrategy();
         } else {
             mStrategy = null;
         }
@@ -200,7 +215,7 @@
         loadUserSettings();
     }
 
-    private PolitenessStrategy getPolitenessStrategy() {
+    private PolitenessStrategy createPolitenessStrategy() {
         if (Flags.crossAppPoliteNotifications()) {
             PolitenessStrategy appStrategy = new StrategyPerApp(
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
@@ -209,11 +224,12 @@
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET));
 
-            return new StrategyGlobal(
+            return new StrategyAvalanche(
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_AVALANCHE_TIMEOUT),
                     appStrategy);
         } else {
             return new StrategyPerApp(
@@ -225,6 +241,11 @@
         }
     }
 
+    @VisibleForTesting
+    PolitenessStrategy getPolitenessStrategy() {
+        return mStrategy;
+    }
+
     public void onSystemReady() {
         mSystemReady = true;
 
@@ -259,6 +280,11 @@
         filter.addAction(Intent.ACTION_USER_REMOVED);
         filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
+        if (Flags.crossAppPoliteNotifications()) {
+            for (String avalancheIntent : NOTIFICATION_AVALANCHE_TRIGGER_INTENTS) {
+                filter.addAction(avalancheIntent);
+            }
+        }
         mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null);
 
         mContext.getContentResolver().registerContentObserver(
@@ -1052,7 +1078,8 @@
         }
     }
 
-    abstract private static class PolitenessStrategy {
+    @VisibleForTesting
+    abstract static class PolitenessStrategy {
         static final int POLITE_STATE_DEFAULT = 0;
         static final int POLITE_STATE_POLITE = 1;
         static final int POLITE_STATE_MUTED = 2;
@@ -1079,6 +1106,8 @@
         protected boolean mApplyPerPackage;
         protected final Map<String, Long> mLastUpdatedTimestampByPackage;
 
+        protected boolean mIsActive = true;
+
         public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite,
                 int volumeMuted) {
             mVolumeStates = new HashMap<>();
@@ -1218,6 +1247,10 @@
             }
             return nextState;
         }
+
+        boolean isActive() {
+            return mIsActive;
+        }
     }
 
     // TODO b/270456865: Only one of the two strategies will be released.
@@ -1289,55 +1322,60 @@
     }
 
     /**
-     * Global (cross-app) strategy.
+     * Avalanche (cross-app) strategy.
      */
-    private static class StrategyGlobal extends PolitenessStrategy {
+    private static class StrategyAvalanche extends PolitenessStrategy {
         private static final String COMMON_KEY = "cross_app_common_key";
 
         private final PolitenessStrategy mAppStrategy;
         private long mLastNotificationTimestamp = 0;
 
-        public StrategyGlobal(int timeoutPolite, int timeoutMuted, int volumePolite,
-                int volumeMuted, PolitenessStrategy appStrategy) {
+        private final int mTimeoutAvalanche;
+        private long mLastAvalancheTriggerTimestamp = 0;
+
+        StrategyAvalanche(int timeoutPolite, int timeoutMuted, int volumePolite,
+                    int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy) {
             super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
 
+            mTimeoutAvalanche = timeoutAvalanche;
             mAppStrategy = appStrategy;
 
             if (DEBUG) {
-                Log.i(TAG, "StrategyGlobal: " + timeoutPolite + " " + timeoutMuted);
+                Log.i(TAG, "StrategyAvalanche: " + timeoutPolite + " " + timeoutMuted + " "
+                        + timeoutAvalanche);
             }
         }
 
         @Override
         void onNotificationPosted(NotificationRecord record) {
-            if (shouldIgnoreNotification(record)) {
-                return;
-            }
+            if (isAvalancheActive()) {
+                if (shouldIgnoreNotification(record)) {
+                    return;
+                }
 
-            long timeSinceLastNotif =
+                long timeSinceLastNotif =
                     System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
 
-            final String key = getChannelKey(record);
-            @PolitenessState final int currState = getPolitenessState(record);
-            @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
+                final String key = getChannelKey(record);
+                @PolitenessState final int currState = getPolitenessState(record);
+                @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
 
-            if (DEBUG) {
-                Log.i(TAG, "StrategyGlobal onNotificationPosted time delta: " + timeSinceLastNotif
-                        + " vol state: " + nextState + " key: " + key);
+                if (DEBUG) {
+                    Log.i(TAG,
+                            "StrategyAvalanche onNotificationPosted time delta: "
+                            + timeSinceLastNotif
+                            + " vol state: " + nextState + " key: " + key);
+                }
+
+                mVolumeStates.put(key, nextState);
             }
 
-            mVolumeStates.put(key, nextState);
-
             mAppStrategy.onNotificationPosted(record);
         }
 
         @Override
         public float getSoundVolume(final NotificationRecord record) {
-            final @PolitenessState int globalVolState = getPolitenessState(record);
-            final @PolitenessState int appVolState = mAppStrategy.getPolitenessState(record);
-
-            // Prioritize the most polite outcome
-            if (globalVolState > appVolState) {
+            if (isAvalancheActive()) {
                 return super.getSoundVolume(record);
             } else {
                 return mAppStrategy.getSoundVolume(record);
@@ -1382,6 +1420,24 @@
             super.setApplyCooldownPerPackage(applyPerPackage);
             mAppStrategy.setApplyCooldownPerPackage(applyPerPackage);
         }
+
+        boolean isAvalancheActive() {
+            mIsActive = (System.currentTimeMillis() - mLastAvalancheTriggerTimestamp
+                    < mTimeoutAvalanche);
+            if (DEBUG) {
+                Log.i(TAG, "StrategyAvalanche: active " + mIsActive);
+            }
+            return mIsActive;
+        }
+
+        @Override
+        boolean isActive() {
+            return isAvalancheActive();
+        }
+
+        void setTriggerTimeMs(long timestamp) {
+            mLastAvalancheTriggerTimestamp = timestamp;
+        }
     }
 
     //======================  Observers  =============================
@@ -1415,6 +1471,30 @@
                         || action.equals(Intent.ACTION_USER_UNLOCKED)) {
                 loadUserSettings();
             }
+
+            if (Flags.crossAppPoliteNotifications()) {
+                if (NOTIFICATION_AVALANCHE_TRIGGER_INTENTS.contains(action)) {
+                    boolean enableAvalancheStrategy = true;
+                    // Some actions must also match extras, ie. airplane mode => disabled
+                    Pair<String, Boolean> expectedExtras =
+                            NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS.get(action);
+                    if (expectedExtras != null) {
+                        enableAvalancheStrategy =
+                                intent.getBooleanExtra(expectedExtras.first, false)
+                                == expectedExtras.second;
+                    }
+
+                    if (DEBUG) {
+                        Log.i(TAG, "Avalanche trigger intent received: " + action
+                                + ". Enabling avalanche strategy: " + enableAvalancheStrategy);
+                    }
+
+                    if (enableAvalancheStrategy && mStrategy instanceof StrategyAvalanche) {
+                        ((StrategyAvalanche) mStrategy)
+                                .setTriggerTimeMs(System.currentTimeMillis());
+                    }
+                }
+            }
         }
     };
 
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryManager.java b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
index e3880c3..deb95d8 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryManager.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
@@ -108,7 +108,7 @@
                 for (int i = 0; i < pendingPackageRemovals.size(); i++) {
                     userHistory.onPackageRemoved(pendingPackageRemovals.get(i));
                 }
-                mUserPendingPackageRemovals.put(userId, null);
+                mUserPendingPackageRemovals.remove(userId);
             }
 
             // delete history if it was disabled when the user was locked
@@ -133,7 +133,7 @@
         synchronized (mLock) {
             // Actual data deletion is handled by other parts of the system (the entire directory is
             // removed) - we just need clean up our internal state for GC
-            mUserPendingPackageRemovals.put(userId, null);
+            mUserPendingPackageRemovals.remove(userId);
             mHistoryEnabled.put(userId, false);
             mUserPendingHistoryDisables.put(userId, false);
             onUserStopped(userId);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 7aa7b7e..4da2cc9 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -69,6 +69,7 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
 import static android.app.Flags.lifetimeExtensionRefactor;
+import static android.app.NotificationManager.zenModeFromInterruptionFilter;
 import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
 import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
 import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT;
@@ -215,7 +216,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.LauncherApps;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
@@ -282,6 +282,7 @@
 import android.service.notification.StatusBarNotification;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeProto;
+import android.service.notification.ZenPolicy;
 import android.telecom.TelecomManager;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
@@ -373,6 +374,7 @@
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
+import java.time.Clock;
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -2466,8 +2468,8 @@
         mMetricsLogger = new MetricsLogger();
         mRankingHandler = rankingHandler;
         mConditionProviders = conditionProviders;
-        mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders,
-                flagResolver, new ZenModeEventLogger(mPackageManagerClient));
+        mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), Clock.systemUTC(),
+                mConditionProviders, flagResolver, new ZenModeEventLogger(mPackageManagerClient));
         mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
             @Override
             public void onConfigChanged() {
@@ -5311,18 +5313,41 @@
         @Override
         public void requestInterruptionFilterFromListener(INotificationListener token,
                 int interruptionFilter) throws RemoteException {
-            final int callingUid = Binder.getCallingUid();
-            final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi();
-            final long identity = Binder.clearCallingIdentity();
-            try {
+            if (android.app.Flags.modesApi()) {
+                final int callingUid = Binder.getCallingUid();
+                ManagedServiceInfo info;
                 synchronized (mNotificationLock) {
-                    final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
-                    mZenModeHelper.requestFromListener(info.component, interruptionFilter,
-                            callingUid, isSystemOrSystemUi);
-                    updateInterruptionFilterLocked();
+                    info = mListeners.checkServiceTokenLocked(token);
                 }
-            } finally {
-                Binder.restoreCallingIdentity(identity);
+
+                final int zenMode = zenModeFromInterruptionFilter(interruptionFilter, -1);
+                if (zenMode == -1) return;
+                if (!canManageGlobalZenPolicy(info.component.getPackageName(), callingUid)) {
+                    mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(
+                            info.component.getPackageName(), callingUid, zenMode);
+                } else {
+                    int origin = computeZenOrigin(/* fromUser= */ false);
+                    Binder.withCleanCallingIdentity(() -> {
+                        mZenModeHelper.setManualZenMode(zenMode, /* conditionId= */ null, origin,
+                                "listener:" + info.component.flattenToShortString(),
+                                /* caller= */ info.component.getPackageName(),
+                                callingUid);
+                    });
+                }
+            } else {
+                final int callingUid = Binder.getCallingUid();
+                final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi();
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    synchronized (mNotificationLock) {
+                        final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+                        mZenModeHelper.requestFromListener(info.component, interruptionFilter,
+                                callingUid, isSystemOrSystemUi);
+                        updateInterruptionFilterLocked();
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
             }
         }
 
@@ -5358,10 +5383,10 @@
         @Override
         public void setZenMode(int mode, Uri conditionId, String reason, boolean fromUser) {
             enforceSystemOrSystemUI("INotificationManager.setZenMode");
-            final int callingUid = Binder.getCallingUid();
-            final long identity = Binder.clearCallingIdentity();
             enforceUserOriginOnlyFromSystem(fromUser, "setZenMode");
 
+            final int callingUid = Binder.getCallingUid();
+            final long identity = Binder.clearCallingIdentity();
             try {
                 mZenModeHelper.setManualZenMode(mode, conditionId, computeZenOrigin(fromUser),
                         reason, /* caller= */ null, callingUid);
@@ -5554,7 +5579,7 @@
         @Override
         public void setInterruptionFilter(String pkg, int filter, boolean fromUser) {
             enforcePolicyAccess(pkg, "setInterruptionFilter");
-            final int zen = NotificationManager.zenModeFromInterruptionFilter(filter, -1);
+            final int zen = zenModeFromInterruptionFilter(filter, -1);
             if (zen == -1) throw new IllegalArgumentException("Invalid filter: " + filter);
             final int callingUid = Binder.getCallingUid();
             enforceUserOriginOnlyFromSystem(fromUser, "setInterruptionFilter");
@@ -5931,8 +5956,7 @@
                         newVisualEffects, policy.priorityConversationSenders);
 
                 if (shouldApplyAsImplicitRule) {
-                    mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy,
-                            origin);
+                    mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy);
                 } else {
                     ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion,
                             policy);
@@ -5945,6 +5969,20 @@
             }
         }
 
+        /**
+         * Gets the device-default zen policy as a ZenPolicy.
+         */
+        @Override
+        public ZenPolicy getDefaultZenPolicy() {
+            enforceSystemOrSystemUI("INotificationManager.getDefaultZenPolicy");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return mZenModeHelper.getDefaultZenPolicy();
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
         @Override
         public List<String> getEnabledNotificationListenerPackages() {
             checkCallerIsSystem();
@@ -10831,6 +10869,7 @@
             ArrayList<Notification.Action> smartActions = record.getSystemGeneratedSmartActions();
             ArrayList<CharSequence> smartReplies = record.getSmartReplies();
             if (redactSensitiveNotificationsFromUntrustedListeners()
+                    && info != null
                     && !mListeners.isUidTrusted(info.uid)
                     && mListeners.hasSensitiveContent(record)) {
                 smartActions = null;
@@ -12103,6 +12142,7 @@
                 return true;
             }
 
+            long token = Binder.clearCallingIdentity();
             try {
                 if (mPackageManager.checkUidPermission(RECEIVE_SENSITIVE_NOTIFICATIONS, uid)
                         == PERMISSION_GRANTED || mPackageManagerInternal.isPlatformSigned(pkg)) {
@@ -12129,6 +12169,8 @@
                 }
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to check trusted status of listener", e);
+            } finally {
+                Binder.restoreCallingIdentity(token);
             }
             return false;
         }
diff --git a/services/core/java/com/android/server/notification/ZenAdapters.java b/services/core/java/com/android/server/notification/ZenAdapters.java
index 91df04c..37b263c 100644
--- a/services/core/java/com/android/server/notification/ZenAdapters.java
+++ b/services/core/java/com/android/server/notification/ZenAdapters.java
@@ -59,9 +59,7 @@
         }
 
         if (Flags.modesApi()) {
-            zenPolicyBuilder.allowChannels(
-                    policy.allowPriorityChannels()
-                            ? ZenPolicy.CHANNEL_TYPE_PRIORITY : ZenPolicy.CHANNEL_TYPE_NONE);
+            zenPolicyBuilder.allowPriorityChannels(policy.allowPriorityChannels());
         }
 
         return zenPolicyBuilder.build();
diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
index 0145577..a90efe6 100644
--- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java
+++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
@@ -18,6 +18,8 @@
 
 import static android.app.NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND;
 import static android.provider.Settings.Global.ZEN_MODE_OFF;
+import static android.service.notification.NotificationServiceProto.CHANNEL_POLICY_PRIORITY;
+import static android.service.notification.NotificationServiceProto.CHANNEL_POLICY_NONE;
 import static android.service.notification.NotificationServiceProto.RULE_TYPE_AUTOMATIC;
 import static android.service.notification.NotificationServiceProto.RULE_TYPE_MANUAL;
 import static android.service.notification.NotificationServiceProto.RULE_TYPE_UNKNOWN;
@@ -551,8 +553,8 @@
                 if (Flags.modesApi()) {
                     proto.write(DNDPolicyProto.ALLOW_CHANNELS,
                             mNewPolicy.allowPriorityChannels()
-                                    ? ZenPolicy.CHANNEL_TYPE_PRIORITY
-                                    : ZenPolicy.CHANNEL_TYPE_NONE);
+                                    ? CHANNEL_POLICY_PRIORITY
+                                    : CHANNEL_POLICY_NONE);
                 }
             } else {
                 Log.wtf(TAG, "attempted to write zen mode log event with null policy");
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index db64a75..fb95632 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -32,6 +32,7 @@
 import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
 
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
+import static com.android.internal.util.Preconditions.checkArgument;
 
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
@@ -117,6 +118,9 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -130,9 +134,12 @@
     static final String TAG = "ZenModeHelper";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
+    private static final String PACKAGE_ANDROID = "android";
+
     // The amount of time rules instances can exist without their owning app being installed.
     private static final int RULE_INSTANCE_GRACE_PERIOD = 1000 * 60 * 60 * 72;
     static final int RULE_LIMIT_PER_PACKAGE = 100;
+    private static final Duration DELETED_RULE_KEPT_FOR = Duration.ofDays(30);
 
     private static final String IMPLICIT_RULE_ID_PREFIX = "implicit_"; // + pkg_name
 
@@ -148,6 +155,7 @@
 
     private final Context mContext;
     private final H mHandler;
+    private final Clock mClock;
     private final SettingsObserver mSettingsObserver;
     private final AppOpsManager mAppOps;
     private final NotificationManager mNotificationManager;
@@ -189,11 +197,13 @@
 
     private String[] mPriorityOnlyDndExemptPackages;
 
-    public ZenModeHelper(Context context, Looper looper, ConditionProviders conditionProviders,
+    public ZenModeHelper(Context context, Looper looper, Clock clock,
+            ConditionProviders conditionProviders,
             SystemUiSystemPropertiesFlags.FlagResolver flagResolver,
             ZenModeEventLogger zenModeEventLogger) {
         mContext = context;
         mHandler = new H(looper);
+        mClock = clock;
         addCallback(mMetrics);
         mAppOps = context.getSystemService(AppOpsManager.class);
         mNotificationManager = context.getSystemService(NotificationManager.class);
@@ -349,6 +359,7 @@
         return NotificationManager.zenModeToInterruptionFilter(mZenMode);
     }
 
+    // TODO: b/310620812 - Remove when MODES_API is inlined (no more callers).
     public void requestFromListener(ComponentName name, int filter, int callingUid,
             boolean fromSystemOrSystemUi) {
         final int newZen = NotificationManager.zenModeFromInterruptionFilter(filter, -1);
@@ -418,6 +429,7 @@
 
     public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule,
             @ConfigChangeOrigin int origin, String reason, int callingUid) {
+        requirePublicOrigin("addAutomaticZenRule", origin);
         if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) {
             PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner());
             if (component == null) {
@@ -452,7 +464,10 @@
             newConfig = mConfig.copy();
             ZenRule rule = new ZenRule();
             populateZenRule(pkg, automaticZenRule, rule, origin, /* isNew= */ true);
+            rule = maybeRestoreRemovedRule(newConfig, rule, automaticZenRule, origin);
             newConfig.automaticRules.put(rule.id, rule);
+            maybeReplaceDefaultRule(newConfig, automaticZenRule);
+
             if (setConfigLocked(newConfig, origin, reason, rule.component, true, callingUid)) {
                 return rule.id;
             } else {
@@ -461,8 +476,59 @@
         }
     }
 
+    private ZenRule maybeRestoreRemovedRule(ZenModeConfig config, ZenRule ruleToAdd,
+            AutomaticZenRule azrToAdd, @ConfigChangeOrigin int origin) {
+        if (!Flags.modesApi()) {
+            return ruleToAdd;
+        }
+        String deletedKey = ZenModeConfig.deletedRuleKey(ruleToAdd);
+        if (deletedKey == null) {
+            // Couldn't calculate the deletedRuleKey (condition or pkg null?). This should
+            // never happen for an app-provided rule because NMS validates both.
+            return ruleToAdd;
+        }
+        ZenRule ruleToRestore = config.deletedRules.get(deletedKey);
+        if (ruleToRestore == null) {
+            return ruleToAdd; // Cannot restore.
+        }
+
+        // We have found a previous rule to maybe restore. Whether we do that or not, we don't need
+        // to keep it around (if not restored now, it won't be in future calls either).
+        config.deletedRules.remove(deletedKey);
+        ruleToRestore.deletionInstant = null;
+
+        if (origin != UPDATE_ORIGIN_APP) {
+            return ruleToAdd; // Okay to create anew.
+        }
+
+        // "Preserve" the previous rule by considering the azrToAdd an update instead.
+        // Only app-modifiable fields will actually be modified.
+        populateZenRule(ruleToRestore.pkg, azrToAdd, ruleToRestore, origin, /* isNew= */ false);
+        return ruleToRestore;
+    }
+
+    private static void maybeReplaceDefaultRule(ZenModeConfig config, AutomaticZenRule addedRule) {
+        if (!Flags.modesApi()) {
+            return;
+        }
+        if (addedRule.getType() == AutomaticZenRule.TYPE_BEDTIME) {
+            // Delete a built-in disabled "Sleeping" rule when a BEDTIME rule is added; it may have
+            // smarter triggers and it will prevent confusion about which one to use.
+            // Note: we must not verify canManageAutomaticZenRule here, since most likely they
+            // won't have the same owner (sleeping - system; bedtime - DWB).
+            ZenRule sleepingRule = config.automaticRules.get(
+                    ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
+            if (sleepingRule != null
+                    && !sleepingRule.enabled
+                    && sleepingRule.canBeUpdatedByApp() /* meaning it's not user-customized */) {
+                config.automaticRules.remove(ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
+            }
+        }
+    }
+
     public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule,
             @ConfigChangeOrigin int origin, String reason, int callingUid) {
+        requirePublicOrigin("updateAutomaticZenRule", origin);
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return false;
@@ -538,13 +604,26 @@
                 ZenRule rule = newConfig.automaticRules.get(implicitRuleId(callingPkg));
                 if (rule == null) {
                     rule = newImplicitZenRule(callingPkg);
+
+                    // For new implicit rules, create a policy matching the current global
+                    // (manual rule) settings, for consistency with the policy that
+                    // would apply if changing the global interruption filter. We only do this
+                    // for newly created rules, as existing rules have a pre-existing policy
+                    // (whether initialized here or set via app or user).
+                    rule.zenPolicy = mConfig.toZenPolicy();
+
                     newConfig.automaticRules.put(rule.id, rule);
                 }
-                rule.zenMode = zenMode;
+                // If the user has changed the rule's *zenMode*, then don't let app overwrite it.
+                // We allow the update if the user has only changed other aspects of the rule.
+                if ((rule.userModifiedFields & AutomaticZenRule.FIELD_INTERRUPTION_FILTER) == 0) {
+                    rule.zenMode = zenMode;
+                }
                 rule.snoozing = false;
                 rule.condition = new Condition(rule.conditionId,
                         mContext.getString(R.string.zen_mode_implicit_activated),
                         Condition.STATE_TRUE);
+
                 setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
                         "applyGlobalZenModeAsImplicitZenRule", callingUid);
             }
@@ -563,7 +642,7 @@
      * {@link Global#ZEN_MODE_IMPORTANT_INTERRUPTIONS}.
      */
     void applyGlobalPolicyAsImplicitZenRule(String callingPkg, int callingUid,
-            NotificationManager.Policy policy, @ConfigChangeOrigin int origin) {
+            NotificationManager.Policy policy) {
         if (!android.app.Flags.modesApi()) {
             Log.wtf(TAG, "applyGlobalPolicyAsImplicitZenRule called with flag off!");
             return;
@@ -573,16 +652,35 @@
                 return;
             }
             ZenModeConfig newConfig = mConfig.copy();
+            boolean isNew = false;
             ZenRule rule = newConfig.automaticRules.get(implicitRuleId(callingPkg));
             if (rule == null) {
+                isNew = true;
                 rule = newImplicitZenRule(callingPkg);
                 rule.zenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
                 newConfig.automaticRules.put(rule.id, rule);
             }
-            // TODO: b/308673679 - Keep user customization of this rule!
-            rule.zenPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy);
-            setConfigLocked(newConfig, /* triggeringComponent= */ null, origin,
-                    "applyGlobalPolicyAsImplicitZenRule", callingUid);
+            // If the user has changed the rule's *ZenPolicy*, then don't let app overwrite it.
+            // We allow the update if the user has only changed other aspects of the rule.
+            if (rule.zenPolicyUserModifiedFields == 0) {
+                ZenPolicy newZenPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy);
+                if (isNew) {
+                    // For new rules only, fill anything underspecified in the new policy with
+                    // values from the global configuration, for consistency with the policy that
+                    // would take effect if changing the global policy.
+                    // Note that NotificationManager.Policy cannot have any unset priority
+                    // categories, but *can* have unset visual effects, which is why we do this.
+                    newZenPolicy = mConfig.toZenPolicy().overwrittenWith(newZenPolicy);
+                }
+                updatePolicy(
+                        rule,
+                        newZenPolicy,
+                        /* updateBitmask= */ false,
+                        isNew);
+
+                setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
+                        "applyGlobalPolicyAsImplicitZenRule", callingUid);
+            }
         }
     }
 
@@ -608,6 +706,11 @@
             }
             ZenRule implicitRule = mConfig.automaticRules.get(implicitRuleId(callingPkg));
             if (implicitRule != null && implicitRule.zenPolicy != null) {
+                // toNotificationPolicy takes defaults from mConfig, and technically, those are not
+                // the defaults that would apply if any fields were unset. However, all rules should
+                // have all fields set in their ZenPolicy objects upon rule creation, so in
+                // practice, this is only filling in the areChannelsBypassingDnd field, which is a
+                // state rather than a part of the policy.
                 return mConfig.toNotificationPolicy(implicitRule.zenPolicy);
             } else {
                 return getNotificationPolicy();
@@ -623,7 +726,7 @@
         ZenRule rule = new ZenRule();
         rule.id = implicitRuleId(pkg);
         rule.pkg = pkg;
-        rule.creationTime = System.currentTimeMillis();
+        rule.creationTime = mClock.millis();
 
         Binder.withCleanCallingIdentity(() -> {
             try {
@@ -643,7 +746,7 @@
         rule.condition = null;
         rule.conditionId = new Uri.Builder()
                 .scheme(Condition.SCHEME)
-                .authority("android")
+                .authority(PACKAGE_ANDROID)
                 .appendPath("implicit")
                 .appendPath(pkg)
                 .build();
@@ -664,6 +767,7 @@
 
     boolean removeAutomaticZenRule(String id, @ConfigChangeOrigin int origin, String reason,
             int callingUid) {
+        requirePublicOrigin("removeAutomaticZenRule", origin);
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return false;
@@ -672,7 +776,9 @@
             if (ruleToRemove == null) return false;
             if (canManageAutomaticZenRule(ruleToRemove)) {
                 newConfig.automaticRules.remove(id);
-                if (ruleToRemove.getPkg() != null && !"android".equals(ruleToRemove.getPkg())) {
+                maybePreserveRemovedRule(newConfig, ruleToRemove, origin);
+                if (ruleToRemove.getPkg() != null
+                        && !PACKAGE_ANDROID.equals(ruleToRemove.getPkg())) {
                     for (ZenRule currRule : newConfig.automaticRules.values()) {
                         if (currRule.getPkg() != null
                                 && currRule.getPkg().equals(ruleToRemove.getPkg())) {
@@ -694,6 +800,7 @@
 
     boolean removeAutomaticZenRules(String packageName, @ConfigChangeOrigin int origin,
             String reason, int callingUid) {
+        requirePublicOrigin("removeAutomaticZenRules", origin);
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return false;
@@ -702,14 +809,51 @@
                 ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i));
                 if (Objects.equals(rule.getPkg(), packageName) && canManageAutomaticZenRule(rule)) {
                     newConfig.automaticRules.removeAt(i);
+                    maybePreserveRemovedRule(newConfig, rule, origin);
+                }
+            }
+            // If the system is clearing all rules this means DND access is revoked or the package
+            // was uninstalled, so also clear the preserved-deleted rules.
+            if (origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI) {
+                for (int i = newConfig.deletedRules.size() - 1; i >= 0; i--) {
+                    ZenRule rule = newConfig.deletedRules.get(newConfig.deletedRules.keyAt(i));
+                    if (Objects.equals(rule.getPkg(), packageName)) {
+                        newConfig.deletedRules.removeAt(i);
+                    }
                 }
             }
             return setConfigLocked(newConfig, origin, reason, null, true, callingUid);
         }
     }
 
+    private void maybePreserveRemovedRule(ZenModeConfig config, ZenRule ruleToRemove,
+            @ConfigChangeOrigin int origin) {
+        if (!Flags.modesApi()) {
+            return;
+        }
+        // If an app deletes a previously customized rule, keep it around to preserve
+        // the user's customization when/if it's recreated later.
+        // We don't try to preserve system-owned rules because their conditionIds (used as
+        // deletedRuleKey) are not stable. This is almost moot anyway because an app cannot
+        // delete a system-owned rule.
+        if (origin == UPDATE_ORIGIN_APP && !ruleToRemove.canBeUpdatedByApp()
+                && !PACKAGE_ANDROID.equals(ruleToRemove.pkg)) {
+            String deletedKey = ZenModeConfig.deletedRuleKey(ruleToRemove);
+            if (deletedKey != null) {
+                ZenRule deletedRule = ruleToRemove.copy();
+                deletedRule.deletionInstant = Instant.now(mClock);
+                // If the rule is restored it shouldn't be active (or snoozed).
+                deletedRule.snoozing = false;
+                deletedRule.condition = null;
+                // Overwrites a previously-deleted rule with the same conditionId, but that's okay.
+                config.deletedRules.put(deletedKey, deletedRule);
+            }
+        }
+    }
+
     void setAutomaticZenRuleState(String id, Condition condition, @ConfigChangeOrigin int origin,
             int callingUid) {
+        requirePublicOrigin("setAutomaticZenRuleState", origin);
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return;
@@ -723,6 +867,7 @@
 
     void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition,
             @ConfigChangeOrigin int origin, int callingUid) {
+        requirePublicOrigin("setAutomaticZenRuleState", origin);
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return;
@@ -892,13 +1037,13 @@
         return null;
     }
 
-    void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
+    private void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
                          @ConfigChangeOrigin int origin, boolean isNew) {
         if (Flags.modesApi()) {
             // These values can always be edited by the app, so we apply changes immediately.
             if (isNew) {
                 rule.id = ZenModeConfig.newRuleId();
-                rule.creationTime = System.currentTimeMillis();
+                rule.creationTime = mClock.millis();
                 rule.component = automaticZenRule.getOwner();
                 rule.pkg = pkg;
             }
@@ -957,11 +1102,10 @@
             rule.zenMode = newZenMode;
 
             // Updates the bitmask and values for all policy fields, based on the origin.
-            rule.zenPolicy = updatePolicy(rule.zenPolicy, automaticZenRule.getZenPolicy(),
-                    updateBitmask);
+            updatePolicy(rule, automaticZenRule.getZenPolicy(), updateBitmask, isNew);
+
             // Updates the bitmask and values for all device effect fields, based on the origin.
-            rule.zenDeviceEffects = updateZenDeviceEffects(
-                    rule.zenDeviceEffects, automaticZenRule.getDeviceEffects(),
+            updateZenDeviceEffects(rule, automaticZenRule.getDeviceEffects(),
                     origin == UPDATE_ORIGIN_APP, updateBitmask);
         } else {
             if (rule.enabled != automaticZenRule.isEnabled()) {
@@ -973,13 +1117,6 @@
             rule.enabled = automaticZenRule.isEnabled();
             rule.modified = automaticZenRule.isModified();
             rule.zenPolicy = automaticZenRule.getZenPolicy();
-            if (Flags.modesApi()) {
-                rule.zenDeviceEffects = updateZenDeviceEffects(
-                        rule.zenDeviceEffects,
-                        automaticZenRule.getDeviceEffects(),
-                        origin == UPDATE_ORIGIN_APP,
-                        origin == UPDATE_ORIGIN_USER);
-            }
             rule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
                     automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF);
             rule.configurationActivity = automaticZenRule.getConfigurationActivity();
@@ -1003,28 +1140,43 @@
     }
 
     /**
-     * Modifies {@link ZenPolicy} that is being stored as part of a new or updated ZenRule.
-     * Returns a policy based on {@code oldPolicy}, but with fields updated to match
-     * {@code newPolicy} where they differ, and updating the internal user-modified bitmask to
-     * track these changes, if applicable based on {@code origin}.
+     * Modifies the {@link ZenPolicy} associated to a new or updated ZenRule.
+     *
+     * <p>The update takes any set fields in {@code newPolicy} as new policy settings for the
+     * provided {@code ZenRule}, keeping any pre-existing settings from {@code zenRule.zenPolicy}
+     * for any unset policy fields in {@code newPolicy}. The user-modified bitmask is updated to
+     * reflect the changes being applied (if applicable, i.e. if the update is from the user).
      */
-    @Nullable
-    private ZenPolicy updatePolicy(@Nullable ZenPolicy oldPolicy, @Nullable ZenPolicy newPolicy,
-                                   boolean updateBitmask) {
-        // If the update is to make the policy null, we don't need to update the bitmask,
-        // because it won't be stored anywhere anyway.
+    private void updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy,
+            boolean updateBitmask, boolean isNew) {
         if (newPolicy == null) {
-            return null;
+            if (isNew) {
+                // Newly created rule with no provided policy; fill in with the default.
+                zenRule.zenPolicy = mDefaultConfig.toZenPolicy();
+            }
+            // Otherwise, a null policy means no policy changes, so we can stop here.
+            return;
         }
 
         // If oldPolicy is null, we compare against the default policy when determining which
         // fields in the bitmask should be marked as updated.
-        if (oldPolicy == null) {
-            oldPolicy = mDefaultConfig.toZenPolicy();
-        }
+        ZenPolicy oldPolicy =
+                zenRule.zenPolicy != null ? zenRule.zenPolicy : mDefaultConfig.toZenPolicy();
 
-        int userModifiedFields = oldPolicy.getUserModifiedFields();
+        // If this is updating a rule rather than creating a new one, keep any fields from the
+        // old policy if they are unspecified in the new policy. For newly created rules, oldPolicy
+        // has been set to the default settings above, so any unspecified fields in a newly created
+        // policy are filled with default values. Then use the fully-specified version of the new
+        // policy for comparison below.
+        //
+        // Although we do not expect a policy update from the user to contain any unset fields,
+        // filling in fields here also guards against any unset fields counting as a "diff" when
+        // comparing fields for bitmask editing below.
+        newPolicy = oldPolicy.overwrittenWith(newPolicy);
+        zenRule.zenPolicy = newPolicy;
+
         if (updateBitmask) {
+            int userModifiedFields = zenRule.zenPolicyUserModifiedFields;
             if (oldPolicy.getPriorityMessageSenders() != newPolicy.getPriorityMessageSenders()) {
                 userModifiedFields |= ZenPolicy.FIELD_MESSAGES;
             }
@@ -1035,7 +1187,7 @@
                     != newPolicy.getPriorityConversationSenders()) {
                 userModifiedFields |= ZenPolicy.FIELD_CONVERSATIONS;
             }
-            if (oldPolicy.getAllowedChannels() != newPolicy.getAllowedChannels()) {
+            if (oldPolicy.getPriorityChannelsAllowed() != newPolicy.getPriorityChannelsAllowed()) {
                 userModifiedFields |= ZenPolicy.FIELD_ALLOW_CHANNELS;
             }
             if (oldPolicy.getPriorityCategoryReminders()
@@ -1082,66 +1234,47 @@
                     != newPolicy.getVisualEffectNotificationList()) {
                 userModifiedFields |= ZenPolicy.FIELD_VISUAL_EFFECT_NOTIFICATION_LIST;
             }
+            zenRule.zenPolicyUserModifiedFields = userModifiedFields;
         }
-
-        // After all bitmask changes have been made, sets the bitmask.
-        return new ZenPolicy.Builder(newPolicy).setUserModifiedFields(userModifiedFields).build();
     }
 
     /**
-     * Modifies {@link ZenDeviceEffects} that are being stored as part of a new or updated ZenRule.
-     * Returns a {@link ZenDeviceEffects} based on {@code oldEffects}, but with fields updated to
-     * match {@code newEffects} where they differ, and updating the internal user-modified bitmask
-     * to track these changes, if applicable based on {@code origin}.
-     * <ul>
-     *     <li> Apps cannot turn on hidden effects (those tagged as {@code @hide}) since they are
-     *     intended for platform-specific rules (e.g. wearables). If it's a new rule, we blank them
-     *     out; if it's an update, we preserve the previous values.
-     * </ul>
+     * Modifies the {@link ZenDeviceEffects} associated to a new or updated ZenRule.
+     *
+     * <p>The new value is {@code newEffects}, while the user-modified bitmask is updated to reflect
+     * the changes being applied (if applicable, i.e. if the update is from the user).
+     *
+     * <p>Apps cannot turn on hidden effects (those tagged as {@code @hide}), so those fields are
+     * treated especially: for a new rule, they are blanked out; for an updated rule, previous
+     * values are preserved.
      */
-    @Nullable
-    private static ZenDeviceEffects updateZenDeviceEffects(@Nullable ZenDeviceEffects oldEffects,
-                                                           @Nullable ZenDeviceEffects newEffects,
-                                                           boolean isFromApp,
-                                                           boolean updateBitmask) {
+    private static void updateZenDeviceEffects(ZenRule zenRule,
+            @Nullable ZenDeviceEffects newEffects, boolean isFromApp, boolean updateBitmask) {
         if (newEffects == null) {
-            return null;
+            zenRule.zenDeviceEffects = null;
+            return;
         }
 
-        // Since newEffects is not null, we want to adopt all the new provided device effects.
-        ZenDeviceEffects.Builder builder = new ZenDeviceEffects.Builder(newEffects);
+        ZenDeviceEffects oldEffects = zenRule.zenDeviceEffects != null
+                ? zenRule.zenDeviceEffects
+                : new ZenDeviceEffects.Builder().build();
 
         if (isFromApp) {
-            if (oldEffects != null) {
-                // We can do this because we know we don't need to update the bitmask FROM_APP.
-                return builder
-                        .setShouldDisableAutoBrightness(oldEffects.shouldDisableAutoBrightness())
-                        .setShouldDisableTapToWake(oldEffects.shouldDisableTapToWake())
-                        .setShouldDisableTiltToWake(oldEffects.shouldDisableTiltToWake())
-                        .setShouldDisableTouch(oldEffects.shouldDisableTouch())
-                        .setShouldMinimizeRadioUsage(oldEffects.shouldMinimizeRadioUsage())
-                        .setShouldMaximizeDoze(oldEffects.shouldMaximizeDoze())
-                        .build();
-            } else {
-                return builder
-                        .setShouldDisableAutoBrightness(false)
-                        .setShouldDisableTapToWake(false)
-                        .setShouldDisableTiltToWake(false)
-                        .setShouldDisableTouch(false)
-                        .setShouldMinimizeRadioUsage(false)
-                        .setShouldMaximizeDoze(false)
-                        .build();
-            }
+            // Don't allow apps to toggle hidden effects.
+            newEffects = new ZenDeviceEffects.Builder(newEffects)
+                    .setShouldDisableAutoBrightness(oldEffects.shouldDisableAutoBrightness())
+                    .setShouldDisableTapToWake(oldEffects.shouldDisableTapToWake())
+                    .setShouldDisableTiltToWake(oldEffects.shouldDisableTiltToWake())
+                    .setShouldDisableTouch(oldEffects.shouldDisableTouch())
+                    .setShouldMinimizeRadioUsage(oldEffects.shouldMinimizeRadioUsage())
+                    .setShouldMaximizeDoze(oldEffects.shouldMaximizeDoze())
+                    .build();
         }
 
-        // If oldEffects is null, we compare against the default device effects object when
-        // determining which fields in the bitmask should be marked as updated.
-        if (oldEffects == null) {
-            oldEffects = new ZenDeviceEffects.Builder().build();
-        }
+        zenRule.zenDeviceEffects = newEffects;
 
-        int userModifiedFields = oldEffects.getUserModifiedFields();
         if (updateBitmask) {
+            int userModifiedFields = zenRule.zenDeviceEffectsUserModifiedFields;
             if (oldEffects.shouldDisplayGrayscale() != newEffects.shouldDisplayGrayscale()) {
                 userModifiedFields |= ZenDeviceEffects.FIELD_GRAYSCALE;
             }
@@ -1174,11 +1307,8 @@
             if (oldEffects.shouldMaximizeDoze() != newEffects.shouldMaximizeDoze()) {
                 userModifiedFields |= ZenDeviceEffects.FIELD_MAXIMIZE_DOZE;
             }
+            zenRule.zenDeviceEffectsUserModifiedFields = userModifiedFields;
         }
-
-        // Since newEffects is not null, we want to adopt all the new provided device effects.
-        // Set the usermodifiedFields value separately, to reflect the updated bitmask.
-        return builder.setUserModifiedFields(userModifiedFields).build();
     }
 
     private AutomaticZenRule zenRuleToAutomaticZenRule(ZenRule rule) {
@@ -1197,7 +1327,6 @@
                     .setOwner(rule.component)
                     .setConfigurationActivity(rule.configurationActivity)
                     .setTriggerDescription(rule.triggerDescription)
-                    .setUserModifiedFields(rule.userModifiedFields)
                     .build();
         } else {
             azr = new AutomaticZenRule(rule.name, rule.component,
@@ -1358,7 +1487,7 @@
             boolean hasDefaultRules = config.automaticRules.containsAll(
                     ZenModeConfig.DEFAULT_RULE_IDS);
 
-            long time = System.currentTimeMillis();
+            long time = Flags.modesApi() ? mClock.millis() : System.currentTimeMillis();
             if (config.automaticRules != null && config.automaticRules.size() > 0) {
                 for (ZenRule automaticRule : config.automaticRules.values()) {
                     if (forRestore) {
@@ -1369,14 +1498,34 @@
                     }
 
                     allRulesDisabled &= !automaticRule.enabled;
+
+                    // Upon upgrading to a version with modes_api enabled, keep all behaviors of
+                    // rules with null ZenPolicies explicitly as a copy of the global policy.
+                    if (Flags.modesApi() && config.version < ZenModeConfig.XML_VERSION_MODES_API) {
+                        // Keep the manual ("global") policy that from config.
+                        ZenPolicy manualRulePolicy = config.toZenPolicy();
+                        if (automaticRule.zenPolicy == null) {
+                            automaticRule.zenPolicy = manualRulePolicy;
+                        } else {
+                            // newPolicy is a policy with all unset fields in the rule's zenPolicy
+                            // set to their values from the values in config. Then convert that back
+                            // to ZenPolicy to store with the automatic zen rule.
+                            automaticRule.zenPolicy =
+                                    manualRulePolicy.overwrittenWith(automaticRule.zenPolicy);
+                        }
+                    }
                 }
             }
 
             if (!hasDefaultRules && allRulesDisabled
-                    && (forRestore || config.version < ZenModeConfig.XML_VERSION)) {
+                    && (forRestore || config.version < ZenModeConfig.XML_VERSION_ZEN_UPGRADE)) {
                 // reset zen automatic rules to default on restore or upgrade if:
                 // - doesn't already have default rules and
                 // - all previous automatic rules were disabled
+                //
+                // Note: we don't need to check to avoid restoring the Sleeping rule if there is a
+                // TYPE_BEDTIME rule because the config is from an old version and thus by
+                // definition cannot have a rule with TYPE_BEDTIME (or any other type).
                 config.automaticRules = new ArrayMap<>();
                 for (ZenRule rule : mDefaultConfig.automaticRules.values()) {
                     config.automaticRules.put(rule.id, rule);
@@ -1386,7 +1535,7 @@
 
             // Resolve user id for settings.
             userId = userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM : userId;
-            if (config.version < ZenModeConfig.XML_VERSION) {
+            if (config.version < ZenModeConfig.XML_VERSION_ZEN_UPGRADE) {
                 Settings.Secure.putIntForUser(mContext.getContentResolver(),
                         Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 1, userId);
             } else {
@@ -1394,6 +1543,12 @@
                 Settings.Secure.putIntForUser(mContext.getContentResolver(),
                         Settings.Secure.ZEN_SETTINGS_UPDATED, 1, userId);
             }
+
+            if (Flags.modesApi() && forRestore) {
+                // Note: forBackup doesn't write deletedRules, but just in case.
+                config.deletedRules.clear();
+            }
+
             if (DEBUG) Log.d(TAG, reason);
             synchronized (mConfigLock) {
                 setConfigLocked(config, null,
@@ -1411,7 +1566,7 @@
                 if (forBackup && mConfigs.keyAt(i) != userId) {
                     continue;
                 }
-                mConfigs.valueAt(i).writeXml(out, version);
+                mConfigs.valueAt(i).writeXml(out, version, forBackup);
             }
         }
     }
@@ -1443,28 +1598,51 @@
     }
 
     /**
-     * Removes old rule instances whose owner is not installed.
+     * Cleans up obsolete rules:
+     * <ul>
+     *     <li>Rule instances whose owner is not installed.
+     *     <li>Deleted rules that were deleted more than 30 days ago.
+     * </ul>
      */
     private void cleanUpZenRules() {
-        long currentTime = System.currentTimeMillis();
+        Instant keptRuleThreshold = mClock.instant().minus(DELETED_RULE_KEPT_FOR);
         synchronized (mConfigLock) {
             final ZenModeConfig newConfig = mConfig.copy();
-            if (newConfig.automaticRules != null) {
-                for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) {
-                    ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i));
-                    if (RULE_INSTANCE_GRACE_PERIOD < (currentTime - rule.creationTime)) {
-                        try {
-                            if (rule.getPkg() != null) {
-                                mPm.getPackageInfo(rule.getPkg(), PackageManager.MATCH_ANY_USER);
-                            }
-                        } catch (PackageManager.NameNotFoundException e) {
-                            newConfig.automaticRules.removeAt(i);
-                        }
+
+            deleteRulesWithoutOwner(newConfig.automaticRules);
+            if (Flags.modesApi()) {
+                deleteRulesWithoutOwner(newConfig.deletedRules);
+                for (int i = newConfig.deletedRules.size() - 1; i >= 0; i--) {
+                    ZenRule deletedRule = newConfig.deletedRules.valueAt(i);
+                    if (deletedRule.deletionInstant == null
+                            || deletedRule.deletionInstant.isBefore(keptRuleThreshold)) {
+                        newConfig.deletedRules.removeAt(i);
                     }
                 }
             }
-            setConfigLocked(newConfig, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "cleanUpZenRules",
-                    Process.SYSTEM_UID);
+
+            if (!newConfig.equals(mConfig)) {
+                setConfigLocked(newConfig, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                        "cleanUpZenRules", Process.SYSTEM_UID);
+            }
+        }
+    }
+
+    private void deleteRulesWithoutOwner(ArrayMap<String, ZenRule> ruleList) {
+        long currentTime = Flags.modesApi() ? mClock.millis() : System.currentTimeMillis();
+        if (ruleList != null) {
+            for (int i = ruleList.size() - 1; i >= 0; i--) {
+                ZenRule rule = ruleList.valueAt(i);
+                if (RULE_INSTANCE_GRACE_PERIOD < (currentTime - rule.creationTime)) {
+                    try {
+                        if (rule.getPkg() != null) {
+                            mPm.getPackageInfo(rule.getPkg(), PackageManager.MATCH_ANY_USER);
+                        }
+                    } catch (PackageManager.NameNotFoundException e) {
+                        ruleList.removeAt(i);
+                    }
+                }
+            }
         }
     }
 
@@ -1484,6 +1662,14 @@
         return mConsolidatedPolicy.copy();
     }
 
+    /**
+     * Returns a copy of the device default policy as a ZenPolicy object.
+     */
+    @VisibleForTesting
+    protected ZenPolicy getDefaultZenPolicy() {
+        return mDefaultConfig.toZenPolicy();
+    }
+
     @GuardedBy("mConfigLock")
     private boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent,
             @ConfigChangeOrigin int origin, String reason, int callingUid) {
@@ -1667,7 +1853,7 @@
     }
 
     @GuardedBy("mConfigLock")
-    private void applyCustomPolicy(ZenPolicy policy, ZenRule rule) {
+    private void applyCustomPolicy(ZenPolicy policy, ZenRule rule, boolean useManualConfig) {
         if (rule.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
             policy.apply(new ZenPolicy.Builder()
                     .disallowAllSounds()
@@ -1681,8 +1867,22 @@
         } else if (rule.zenPolicy != null) {
             policy.apply(rule.zenPolicy);
         } else {
-            // active rule with no specified policy inherits the default settings
-            policy.apply(mConfig.toZenPolicy());
+            if (Flags.modesApi()) {
+                if (useManualConfig) {
+                    // manual rule is configured using the settings stored directly in mConfig
+                    policy.apply(mConfig.toZenPolicy());
+                } else {
+                    // under modes_api flag, an active automatic rule with no specified policy
+                    // inherits the device default settings as stored in mDefaultConfig. While the
+                    // rule's policy fields should be set upon creation, this is a fallback to
+                    // catch any that may have fallen through the cracks.
+                    Log.wtf(TAG, "active automatic rule found with no specified policy: " + rule);
+                    policy.apply(mDefaultConfig.toZenPolicy());
+                }
+            } else {
+                // active rule with no specified policy inherits the global config settings
+                policy.apply(mConfig.toZenPolicy());
+            }
         }
     }
 
@@ -1694,7 +1894,7 @@
             ZenPolicy policy = new ZenPolicy();
             ZenDeviceEffects.Builder deviceEffectsBuilder = new ZenDeviceEffects.Builder();
             if (mConfig.manualRule != null) {
-                applyCustomPolicy(policy, mConfig.manualRule);
+                applyCustomPolicy(policy, mConfig.manualRule, true);
                 if (Flags.modesApi()) {
                     deviceEffectsBuilder.add(mConfig.manualRule.zenDeviceEffects);
                 }
@@ -1706,7 +1906,7 @@
                     // policy. This is relevant in case some other active rule has a more
                     // restrictive INTERRUPTION_FILTER but a more lenient ZenPolicy!
                     if (!Flags.modesApi() || automaticRule.zenMode != Global.ZEN_MODE_OFF) {
-                        applyCustomPolicy(policy, automaticRule);
+                        applyCustomPolicy(policy, automaticRule, false);
                     }
                     if (Flags.modesApi()) {
                         deviceEffectsBuilder.add(automaticRule.zenDeviceEffects);
@@ -1714,6 +1914,14 @@
                 }
             }
 
+            // While mConfig.toNotificationPolicy fills in any unset fields from the provided
+            // config (which, in this case is the manual "global" config), under modes API changes,
+            // we should have no remaining unset fields: the manual policy gets every field from
+            // the global policy, and each automatic rule has all policy fields filled in on
+            // creation or update.
+            // However, the piece of information that comes from mConfig that we must keep is the
+            // areChannelsBypassingDnd bit, which is a state, rather than a policy, and even when
+            // all policy fields are set, this state comes to the new policy from this call.
             Policy newPolicy = mConfig.toNotificationPolicy(policy);
             if (!Objects.equals(mConsolidatedPolicy, newPolicy)) {
                 mConsolidatedPolicy = newPolicy;
@@ -1936,7 +2144,10 @@
                         /* optional LoggedZenMode zen_mode = 4 */ ROOT_CONFIG,
                         /* optional string id = 5 */ "", // empty for root config
                         /* optional int32 uid = 6 */ Process.SYSTEM_UID, // system owns root config
-                        /* optional DNDPolicyProto policy = 7 */ config.toZenPolicy().toProto()));
+                        /* optional DNDPolicyProto policy = 7 */ config.toZenPolicy().toProto(),
+                        /* optional int32 rule_modified_fields = 8 */ 0,
+                        /* optional int32 policy_modified_fields = 9 */ 0,
+                        /* optional int32 device_effects_modified_fields = 10 */ 0));
                 if (config.manualRule != null) {
                     ruleToProtoLocked(user, config.manualRule, true, events);
                 }
@@ -1978,7 +2189,11 @@
                 /* optional android.stats.dnd.ZenMode zen_mode = 4 */ rule.zenMode,
                 /* optional string id = 5 */ id,
                 /* optional int32 uid = 6 */ getPackageUid(pkg, user),
-                /* optional DNDPolicyProto policy = 7 */ policyProto));
+                /* optional DNDPolicyProto policy = 7 */ policyProto,
+                /* optional int32 rule_modified_fields = 8 */ rule.userModifiedFields,
+                /* optional int32 policy_modified_fields = 9 */ rule.zenPolicyUserModifiedFields,
+                /* optional int32 device_effects_modified_fields = 10 */
+                rule.zenDeviceEffectsUserModifiedFields));
     }
 
     private int getPackageUid(String pkg, int user) {
@@ -2240,6 +2455,19 @@
             return null;
         }
     }
+
+    /** Checks that the {@code origin} supplied to a ZenModeHelper "API" method makes sense. */
+    private static void requirePublicOrigin(String method, @ConfigChangeOrigin int origin) {
+        if (!Flags.modesApi()) {
+            return;
+        }
+        checkArgument(origin == UPDATE_ORIGIN_APP || origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                        || origin == UPDATE_ORIGIN_USER,
+                "Expected one of UPDATE_ORIGIN_APP, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, or "
+                        + "UPDATE_ORIGIN_USER for %s, but received '%s'.",
+                method, origin);
+    }
+
     private final class Metrics extends Callback {
         private static final String COUNTER_MODE_PREFIX = "dnd_mode_";
         private static final String COUNTER_TYPE_PREFIX = "dnd_type_";
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index 25a39cc..86d05d9 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -257,7 +257,7 @@
     private boolean matchesActorSignature(@NonNull AndroidPackage targetPackage,
             @NonNull AndroidPackage overlayPackage, int userId) {
         String targetOverlayableName = overlayPackage.getOverlayTargetOverlayableName();
-        if (targetOverlayableName != null) {
+        if (targetOverlayableName != null && !mPackageManager.getNamedActors().isEmpty()) {
             try {
                 OverlayableInfo overlayableInfo = mPackageManager.getOverlayableForTarget(
                         targetPackage.getPackageName(), targetOverlayableName, userId);
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index b9464d9..a61b03f 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -32,6 +32,7 @@
 import static android.os.Trace.TRACE_TAG_RRO;
 import static android.os.Trace.traceBegin;
 import static android.os.Trace.traceEnd;
+
 import static com.android.server.om.OverlayManagerServiceImpl.OperationFailedException;
 
 import android.annotation.NonNull;
@@ -362,7 +363,7 @@
                 defaultPackages.add(packageName);
             }
         }
-        return defaultPackages.toArray(new String[defaultPackages.size()]);
+        return defaultPackages.toArray(new String[0]);
     }
 
     private final class OverlayManagerPackageMonitor extends PackageMonitor {
@@ -1143,9 +1144,10 @@
     };
 
     private static final class PackageManagerHelperImpl implements PackageManagerHelper {
-        private static class PackageStateUsers {
+        private static final class PackageStateUsers {
             private PackageState mPackageState;
-            private final Set<Integer> mInstalledUsers = new ArraySet<>();
+            private Boolean mDefinesOverlayable = null;
+            private final ArraySet<Integer> mInstalledUsers = new ArraySet<>();
             private PackageStateUsers(@NonNull PackageState packageState) {
                 this.mPackageState = packageState;
             }
@@ -1160,7 +1162,7 @@
         // state may lead to contradictions within OMS. Better then to lag
         // behind until all pending intents have been processed.
         private final ArrayMap<String, PackageStateUsers> mCache = new ArrayMap<>();
-        private final Set<Integer> mInitializedUsers = new ArraySet<>();
+        private final ArraySet<Integer> mInitializedUsers = new ArraySet<>();
 
         PackageManagerHelperImpl(Context context) {
             mContext = context;
@@ -1176,8 +1178,7 @@
          */
         @NonNull
         public ArrayMap<String, PackageState> initializeForUser(final int userId) {
-            if (!mInitializedUsers.contains(userId)) {
-                mInitializedUsers.add(userId);
+            if (mInitializedUsers.add(userId)) {
                 mPackageManagerInternal.forEachPackageState((packageState -> {
                     if (packageState.getPkg() != null
                             && packageState.getUserStateOrDefault(userId).isInstalled()) {
@@ -1196,13 +1197,11 @@
             return userPackages;
         }
 
-        @Override
-        @Nullable
-        public PackageState getPackageStateForUser(@NonNull final String packageName,
+        private PackageStateUsers getRawPackageStateForUser(@NonNull final String packageName,
                 final int userId) {
             final PackageStateUsers pkg = mCache.get(packageName);
             if (pkg != null && pkg.mInstalledUsers.contains(userId)) {
-                return pkg.mPackageState;
+                return pkg;
             }
             try {
                 if (!mPackageManager.isPackageAvailable(packageName, userId)) {
@@ -1216,8 +1215,14 @@
             return addPackageUser(packageName, userId);
         }
 
-        @NonNull
-        private PackageState addPackageUser(@NonNull final String packageName,
+        @Override
+        public PackageState getPackageStateForUser(@NonNull final String packageName,
+                final int userId) {
+            final PackageStateUsers pkg = getRawPackageStateForUser(packageName, userId);
+            return pkg != null ? pkg.mPackageState : null;
+        }
+
+        private PackageStateUsers addPackageUser(@NonNull final String packageName,
                 final int user) {
             final PackageState pkg = mPackageManagerInternal.getPackageStateInternal(packageName);
             if (pkg == null) {
@@ -1229,20 +1234,20 @@
         }
 
         @NonNull
-        private PackageState addPackageUser(@NonNull final PackageState pkg,
+        private PackageStateUsers addPackageUser(@NonNull final PackageState pkg,
                 final int user) {
             PackageStateUsers pkgUsers = mCache.get(pkg.getPackageName());
             if (pkgUsers == null) {
                 pkgUsers = new PackageStateUsers(pkg);
                 mCache.put(pkg.getPackageName(), pkgUsers);
-            } else {
+            } else if (pkgUsers.mPackageState != pkg) {
                 pkgUsers.mPackageState = pkg;
+                pkgUsers.mDefinesOverlayable = null;
             }
             pkgUsers.mInstalledUsers.add(user);
-            return pkgUsers.mPackageState;
+            return pkgUsers;
         }
 
-
         @NonNull
         private void removePackageUser(@NonNull final String packageName, final int user) {
             final PackageStateUsers pkgUsers = mCache.get(packageName);
@@ -1260,15 +1265,15 @@
             }
         }
 
-        @Nullable
         public PackageState onPackageAdded(@NonNull final String packageName, final int userId) {
-            return addPackageUser(packageName, userId);
+            final var pu = addPackageUser(packageName, userId);
+            return pu != null ? pu.mPackageState : null;
         }
 
-        @Nullable
         public PackageState onPackageUpdated(@NonNull final String packageName,
                 final int userId) {
-            return addPackageUser(packageName, userId);
+            final var pu = addPackageUser(packageName, userId);
+            return pu != null ? pu.mPackageState : null;
         }
 
         public void onPackageRemoved(@NonNull final String packageName, final int userId) {
@@ -1308,22 +1313,30 @@
             return (pkgs.length == 0) ? null : pkgs[0];
         }
 
-        @Nullable
         @Override
         public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
                 @NonNull String targetOverlayableName, int userId)
                 throws IOException {
-            var packageState = getPackageStateForUser(packageName, userId);
-            var pkg = packageState == null ? null : packageState.getAndroidPackage();
+            final var psu = getRawPackageStateForUser(packageName, userId);
+            final var pkg = (psu == null || psu.mPackageState == null)
+                    ? null : psu.mPackageState.getAndroidPackage();
             if (pkg == null) {
                 throw new IOException("Unable to get target package");
             }
 
+            if (Boolean.FALSE.equals(psu.mDefinesOverlayable)) {
+                return null;
+            }
+
             ApkAssets apkAssets = null;
             try {
                 apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath(),
                         ApkAssets.PROPERTY_ONLY_OVERLAYABLES);
-                return apkAssets.getOverlayableInfo(targetOverlayableName);
+                if (psu.mDefinesOverlayable == null) {
+                    psu.mDefinesOverlayable = apkAssets.definesOverlayable();
+                }
+                return Boolean.FALSE.equals(psu.mDefinesOverlayable)
+                        ? null : apkAssets.getOverlayableInfo(targetOverlayableName);
             } finally {
                 if (apkAssets != null) {
                     try {
@@ -1337,24 +1350,29 @@
         @Override
         public boolean doesTargetDefineOverlayable(String targetPackageName, int userId)
                 throws IOException {
-            var packageState = getPackageStateForUser(targetPackageName, userId);
-            var pkg = packageState == null ? null : packageState.getAndroidPackage();
+            final var psu = getRawPackageStateForUser(targetPackageName, userId);
+            var pkg = (psu == null || psu.mPackageState == null)
+                    ? null : psu.mPackageState.getAndroidPackage();
             if (pkg == null) {
                 throw new IOException("Unable to get target package");
             }
 
-            ApkAssets apkAssets = null;
-            try {
-                apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath());
-                return apkAssets.definesOverlayable();
-            } finally {
-                if (apkAssets != null) {
-                    try {
-                        apkAssets.close();
-                    } catch (Throwable ignored) {
+            if (psu.mDefinesOverlayable == null) {
+                ApkAssets apkAssets = null;
+                try {
+                    apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath(),
+                            ApkAssets.PROPERTY_ONLY_OVERLAYABLES);
+                    psu.mDefinesOverlayable = apkAssets.definesOverlayable();
+                } finally {
+                    if (apkAssets != null) {
+                        try {
+                            apkAssets.close();
+                        } catch (Throwable ignored) {
+                        }
                     }
                 }
             }
+            return psu.mDefinesOverlayable;
         }
 
         @Override
@@ -1545,8 +1563,7 @@
                 final OverlayPaths frameworkOverlays =
                         mImpl.getEnabledOverlayPaths("android", userId, false);
                 for (final String targetPackageName : targetPackageNames) {
-                    final OverlayPaths.Builder list = new OverlayPaths.Builder();
-                    list.addAll(frameworkOverlays);
+                    final var list = new OverlayPaths.Builder(frameworkOverlays);
                     if (!"android".equals(targetPackageName)) {
                         list.addAll(mImpl.getEnabledOverlayPaths(targetPackageName, userId, true));
                     }
@@ -1558,17 +1575,21 @@
             final HashSet<String> invalidPackages = new HashSet<>();
             pm.setEnabledOverlayPackages(userId, pendingChanges, updatedPackages, invalidPackages);
 
-            for (final String targetPackageName : targetPackageNames) {
-                if (DEBUG) {
-                    Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=["
-                            + pendingChanges.get(targetPackageName)
-                            + "] userId=" + userId);
-                }
+            if (DEBUG || !invalidPackages.isEmpty()) {
+                for (final String targetPackageName : targetPackageNames) {
+                    if (DEBUG) {
+                        Slog.d(TAG,
+                                "-> Updating overlay: target=" + targetPackageName + " overlays=["
+                                        + pendingChanges.get(targetPackageName)
+                                        + "] userId=" + userId);
+                    }
 
-                if (invalidPackages.contains(targetPackageName)) {
-                    Slog.e(TAG, TextUtils.formatSimple(
-                            "Failed to change enabled overlays for %s user %d", targetPackageName,
-                            userId));
+                    if (invalidPackages.contains(targetPackageName)) {
+                        Slog.e(TAG, TextUtils.formatSimple(
+                                "Failed to change enabled overlays for %s user %d",
+                                targetPackageName,
+                                userId));
+                    }
                 }
             }
             return new ArrayList<>(updatedPackages);
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 972c78d..c1b6ccc 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -772,24 +772,20 @@
 
     OverlayPaths getEnabledOverlayPaths(@NonNull final String targetPackageName,
             final int userId, boolean includeImmutableOverlays) {
-        final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName,
-                userId);
-        final OverlayPaths.Builder paths = new OverlayPaths.Builder();
-        final int n = overlays.size();
-        for (int i = 0; i < n; i++) {
-            final OverlayInfo oi = overlays.get(i);
+        final var paths = new OverlayPaths.Builder();
+        mSettings.forEachMatching(userId, null, targetPackageName, oi -> {
             if (!oi.isEnabled()) {
-                continue;
+                return;
             }
             if (!includeImmutableOverlays && !oi.isMutable) {
-                continue;
+                return;
             }
             if (oi.isFabricated()) {
                 paths.addNonApkPath(oi.baseCodePath);
             } else {
                 paths.addApkPath(oi.baseCodePath);
             }
-        }
+        });
         return paths.build();
     }
 
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index eae614a..b8b49f3e 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -47,6 +47,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 
@@ -182,6 +183,23 @@
         return CollectionUtils.map(items, SettingsItem::getOverlayInfo);
     }
 
+    void forEachMatching(int userId, String overlayName, String targetPackageName,
+            @NonNull Consumer<OverlayInfo> consumer) {
+        for (int i = 0, n = mItems.size(); i < n; i++) {
+            final SettingsItem item = mItems.get(i);
+            if (item.getUserId() != userId) {
+                continue;
+            }
+            if (overlayName != null && !item.mOverlay.getPackageName().equals(overlayName)) {
+                continue;
+            }
+            if (targetPackageName != null && !item.mTargetPackageName.equals(targetPackageName)) {
+                continue;
+            }
+            consumer.accept(item.getOverlayInfo());
+        }
+    }
+
     ArrayMap<String, List<OverlayInfo>> getOverlaysForUser(final int userId) {
         final List<SettingsItem> items = selectWhereUser(userId);
 
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 1660c3e..8452c0e 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -152,14 +152,13 @@
         @RequiresPermission(value = android.Manifest.permission.INTERACT_ACROSS_USERS,
                 conditional = true)
         void ensureCallerPreviouslyGeneratedFile(
-                Context context, Pair<Integer, String> callingInfo, int userId,
-                String bugreportFile, boolean forceUpdateMapping) {
+                Context context, PackageManager packageManager, Pair<Integer, String> callingInfo,
+                int userId, String bugreportFile, boolean forceUpdateMapping) {
             synchronized (mLock) {
                 if (onboardingBugreportV2Enabled()) {
                     final int uidForUser = Binder.withCleanCallingIdentity(() -> {
                         try {
-                            return context.getPackageManager()
-                                    .getPackageUidAsUser(callingInfo.second, userId);
+                            return packageManager.getPackageUidAsUser(callingInfo.second, userId);
                         } catch (PackageManager.NameNotFoundException exception) {
                             throwInvalidBugreportFileForCallerException(
                                     bugreportFile, callingInfo.second);
@@ -441,8 +440,8 @@
         Slogf.i(TAG, "Retrieving bugreport for %s / %d", callingPackage, callingUid);
         try {
             mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
-                    mContext, new Pair<>(callingUid, callingPackage), userId, bugreportFile,
-                    /* forceUpdateMapping= */ false);
+                    mContext, mContext.getPackageManager(), new Pair<>(callingUid, callingPackage),
+                    userId, bugreportFile, /* forceUpdateMapping= */ false);
         } catch (IllegalArgumentException e) {
             Slog.e(TAG, e.getMessage());
             reportError(listener, IDumpstateListener.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index 9ce3cb3..f6e7ef3 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -41,13 +41,14 @@
 import android.system.StructStat;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoParseException;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.BootReceiver;
 import com.android.server.ServiceThread;
 import com.android.server.os.TombstoneProtos.Cause;
 import com.android.server.os.TombstoneProtos.Tombstone;
-import com.android.server.os.protobuf.CodedInputStream;
 
 import libcore.io.IoUtils;
 
@@ -129,21 +130,18 @@
             return;
         }
 
+        String processName = "UNKNOWN";
         final boolean isProtoFile = filename.endsWith(".pb");
-        if (!isProtoFile) {
-            return;
-        }
+        File protoPath = isProtoFile ? path : new File(path.getAbsolutePath() + ".pb");
 
-        Optional<ParsedTombstone> parsedTombstone = handleProtoTombstone(path, true);
+        Optional<TombstoneFile> parsedTombstone = handleProtoTombstone(protoPath, isProtoFile);
         if (parsedTombstone.isPresent()) {
-            BootReceiver.addTombstoneToDropBox(
-                    mContext, path, parsedTombstone.get().getTombstone(),
-                    parsedTombstone.get().getProcessName(), mTmpFileLock);
+            processName = parsedTombstone.get().getProcessName();
         }
+        BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile, processName, mTmpFileLock);
     }
 
-    private Optional<ParsedTombstone> handleProtoTombstone(
-            File path, boolean addToList) {
+    private Optional<TombstoneFile> handleProtoTombstone(File path, boolean addToList) {
         final String filename = path.getName();
         if (!filename.endsWith(".pb")) {
             Slog.w(TAG, "unexpected tombstone name: " + path);
@@ -173,7 +171,7 @@
             return Optional.empty();
         }
 
-        final Optional<ParsedTombstone> parsedTombstone = TombstoneFile.parse(pfd);
+        final Optional<TombstoneFile> parsedTombstone = TombstoneFile.parse(pfd);
         if (!parsedTombstone.isPresent()) {
             IoUtils.closeQuietly(pfd);
             return Optional.empty();
@@ -186,7 +184,7 @@
                     previous.dispose();
                 }
 
-                mTombstones.put(number, parsedTombstone.get().getTombstoneFile());
+                mTombstones.put(number, parsedTombstone.get());
             }
         }
 
@@ -334,27 +332,6 @@
         }
     }
 
-    static class ParsedTombstone {
-        TombstoneFile mTombstoneFile;
-        Tombstone mTombstone;
-        ParsedTombstone(TombstoneFile tombstoneFile, Tombstone tombstone) {
-            mTombstoneFile = tombstoneFile;
-            mTombstone = tombstone;
-        }
-
-        public String getProcessName() {
-            return mTombstoneFile.getProcessName();
-        }
-
-        public TombstoneFile getTombstoneFile() {
-            return mTombstoneFile;
-        }
-
-        public Tombstone getTombstone() {
-            return mTombstone;
-        }
-    }
-
     static class TombstoneFile {
         final ParcelFileDescriptor mPfd;
 
@@ -437,21 +414,67 @@
             }
         }
 
-        static Optional<ParsedTombstone> parse(ParcelFileDescriptor pfd) {
-            Tombstone tombstoneProto;
-            try (FileInputStream is = new FileInputStream(pfd.getFileDescriptor())) {
-                final byte[] tombstoneBytes = is.readAllBytes();
+        static Optional<TombstoneFile> parse(ParcelFileDescriptor pfd) {
+            final FileInputStream is = new FileInputStream(pfd.getFileDescriptor());
+            final ProtoInputStream stream = new ProtoInputStream(is);
 
-                tombstoneProto = Tombstone.parseFrom(
-                        CodedInputStream.newInstance(tombstoneBytes));
-            } catch (IOException ex) {
+            int pid = 0;
+            int uid = 0;
+            String processName = null;
+            String crashReason = "";
+            String selinuxLabel = "";
+
+            try {
+                while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                    switch (stream.getFieldNumber()) {
+                        case (int) Tombstone.PID:
+                            pid = stream.readInt(Tombstone.PID);
+                            break;
+
+                        case (int) Tombstone.UID:
+                            uid = stream.readInt(Tombstone.UID);
+                            break;
+
+                        case (int) Tombstone.COMMAND_LINE:
+                            if (processName == null) {
+                                processName = stream.readString(Tombstone.COMMAND_LINE);
+                            }
+                            break;
+
+                        case (int) Tombstone.CAUSES:
+                            if (!crashReason.equals("")) {
+                                // Causes appear in decreasing order of likelihood. For now we only
+                                // want the most likely crash reason here, so ignore all others.
+                                break;
+                            }
+                            long token = stream.start(Tombstone.CAUSES);
+                        cause:
+                            while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                                switch (stream.getFieldNumber()) {
+                                    case (int) Cause.HUMAN_READABLE:
+                                        crashReason = stream.readString(Cause.HUMAN_READABLE);
+                                        break cause;
+
+                                    default:
+                                        break;
+                                }
+                            }
+                            stream.end(token);
+                            break;
+
+                        case (int) Tombstone.SELINUX_LABEL:
+                            selinuxLabel = stream.readString(Tombstone.SELINUX_LABEL);
+                            break;
+
+                        default:
+                            break;
+                    }
+                }
+            } catch (IOException | ProtoParseException ex) {
                 Slog.e(TAG, "Failed to parse tombstone", ex);
                 return Optional.empty();
             }
 
-            int pid = tombstoneProto.getPid();
-            int uid = tombstoneProto.getUid();
-
             if (!UserHandle.isApp(uid)) {
                 Slog.e(TAG, "Tombstone's UID (" + uid + ") not an app, ignoring");
                 return Optional.empty();
@@ -468,7 +491,6 @@
             final int userId = UserHandle.getUserId(uid);
             final int appId = UserHandle.getAppId(uid);
 
-            String selinuxLabel = tombstoneProto.getSelinuxLabel();
             if (!selinuxLabel.startsWith("u:r:untrusted_app")) {
                 Slog.e(TAG, "Tombstone has invalid selinux label (" + selinuxLabel + "), ignoring");
                 return Optional.empty();
@@ -480,30 +502,11 @@
             result.mAppId = appId;
             result.mPid = pid;
             result.mUid = uid;
-            result.mProcessName = getCmdLineProcessName(tombstoneProto);
+            result.mProcessName = processName == null ? "" : processName;
             result.mTimestampMs = timestampMs;
-            result.mCrashReason = getCrashReason(tombstoneProto);
+            result.mCrashReason = crashReason;
 
-            return Optional.of(new ParsedTombstone(result, tombstoneProto));
-        }
-
-        private static String getCmdLineProcessName(Tombstone tombstoneProto) {
-            for (String cmdline : tombstoneProto.getCommandLineList()) {
-                if (cmdline != null) {
-                    return cmdline;
-                }
-            }
-            return "";
-        }
-
-        private static String getCrashReason(Tombstone tombstoneProto) {
-            for (Cause cause : tombstoneProto.getCausesList()) {
-                if (cause.getHumanReadable() != null
-                        && !cause.getHumanReadable().equals("")) {
-                    return cause.getHumanReadable();
-                }
-            }
-            return "";
+            return Optional.of(result);
         }
 
         public IParcelFileDescriptorRetriever getPfdRetriever() {
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 659c36c..5d71439e 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -276,7 +276,8 @@
      * Returns list of {@code packageName} of apks inside the given apex.
      * @param apexPackageName Package name of the apk container of apex
      */
-    abstract List<String> getApksInApex(String apexPackageName);
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public abstract List<String> getApksInApex(String apexPackageName);
 
     /**
      * Returns the apex module name for the given package name, if the package is an APEX. Otherwise
@@ -751,8 +752,9 @@
             }
         }
 
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
         @Override
-        List<String> getApksInApex(String apexPackageName) {
+        public List<String> getApksInApex(String apexPackageName) {
             synchronized (mLock) {
                 Preconditions.checkState(mPackageNameToApexModuleName != null,
                         "APEX packages have not been scanned");
diff --git a/services/core/java/com/android/server/pm/AppsFilterBase.java b/services/core/java/com/android/server/pm/AppsFilterBase.java
index a5bc2c3..98b7c96 100644
--- a/services/core/java/com/android/server/pm/AppsFilterBase.java
+++ b/services/core/java/com/android/server/pm/AppsFilterBase.java
@@ -24,6 +24,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.pm.Flags;
 import android.content.pm.SigningDetails;
 import android.os.Binder;
 import android.os.Handler;
@@ -318,6 +319,11 @@
                 existingSettings.untrackedStorage());
     }
 
+    private static boolean isQueryableBySdkSandbox(int callingUid, int targetUid) {
+        return Flags.allowSdkSandboxQueryIntentActivities()
+                && targetUid == Process.getAppUidForSdkSandboxUid(callingUid);
+    }
+
     /**
      * See
      * {@link AppsFilterSnapshot#shouldFilterApplication(PackageDataSnapshot, int, Object,
@@ -338,9 +344,11 @@
             } else if (Process.isSdkSandboxUid(callingAppId)) {
                 final int targetAppId = targetPkgSetting.getAppId();
                 final int targetUid = UserHandle.getUid(userId, targetAppId);
-                // we only allow sdk sandbox processes access to forcequeryable packages
+                // we only allow sdk sandbox processes access to forcequeryable packages or
+                // if the target app is the sandbox's client app
                 return !isForceQueryable(targetPkgSetting.getAppId())
-                      && !isImplicitlyQueryable(callingUid, targetUid);
+                        && !isImplicitlyQueryable(callingUid, targetUid)
+                        && !isQueryableBySdkSandbox(callingUid, targetUid);
             }
             // use cache
             if (mCacheReady && mCacheEnabled) {
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index f3df424..cc4c2b5 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -1031,12 +1031,12 @@
     private void recomputeComponentVisibility(
             ArrayMap<String, ? extends PackageStateInternal> existingSettings) {
         final WatchedArraySet<String> protectedBroadcasts;
-        final WatchedArraySet<Integer> forceQueryable;
+        final ArraySet<Integer> forceQueryable;
         synchronized (mProtectedBroadcastsLock) {
-            protectedBroadcasts = mProtectedBroadcasts.snapshot();
+            protectedBroadcasts = new WatchedArraySet<String>(mProtectedBroadcasts);
         }
         synchronized (mForceQueryableLock) {
-            forceQueryable = mForceQueryable.snapshot();
+            forceQueryable = new ArraySet<Integer>(mForceQueryable.untrackedStorage());
         }
         final ParallelComputeComponentVisibility computer = new ParallelComputeComponentVisibility(
                 existingSettings, forceQueryable, protectedBroadcasts);
diff --git a/services/core/java/com/android/server/pm/AppsFilterUtils.java b/services/core/java/com/android/server/pm/AppsFilterUtils.java
index 200734b..a02a1bc 100644
--- a/services/core/java/com/android/server/pm/AppsFilterUtils.java
+++ b/services/core/java/com/android/server/pm/AppsFilterUtils.java
@@ -198,12 +198,12 @@
         private static final int MAX_THREADS = 4;
 
         private final ArrayMap<String, ? extends PackageStateInternal> mExistingSettings;
-        private final WatchedArraySet<Integer> mForceQueryable;
+        private final ArraySet<Integer> mForceQueryable;
         private final WatchedArraySet<String> mProtectedBroadcasts;
 
         ParallelComputeComponentVisibility(
                 @NonNull ArrayMap<String, ? extends PackageStateInternal> existingSettings,
-                @NonNull WatchedArraySet<Integer> forceQueryable,
+                @NonNull ArraySet<Integer> forceQueryable,
                 @NonNull WatchedArraySet<String> protectedBroadcasts) {
             mExistingSettings = existingSettings;
             mForceQueryable = forceQueryable;
diff --git a/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS b/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
new file mode 100644
index 0000000..baa41a5
--- /dev/null
+++ b/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
@@ -0,0 +1,2 @@
+georgechan@google.com
+wenhaowang@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 7f0aadc..200b17b 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -16,7 +16,12 @@
 
 package com.android.server.pm;
 
+import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.app.Flags;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.Context;
@@ -27,6 +32,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Environment;
 import android.os.Handler;
@@ -69,28 +75,29 @@
     private static final String DISK_FILE_NAME = "states";
     private static final String DISK_DIR_NAME = "bic";
 
-    private static final int MAX_FOREGROUND_TIME_FRAMES_SIZE = 10;
+    private static final String ENFORCE_PERMISSION_ERROR_MSG =
+            "User is not permitted to call service: ";
 
+    private static final int MAX_FOREGROUND_TIME_FRAMES_SIZE = 10;
     private static final int MSG_USAGE_EVENT_RECEIVED = 0;
     private static final int MSG_PACKAGE_ADDED = 1;
     private static final int MSG_PACKAGE_REMOVED = 2;
 
-    private final Context mContext;
     private final BinderService mBinderService;
     private final PackageManager mPackageManager;
+    // TODO migrate all internal PackageManager calls to PackageManagerInternal where possible.
+    // b/310983905
     private final PackageManagerInternal mPackageManagerInternal;
-    private final UsageStatsManagerInternal mUsageStatsManagerInternal;
     private final PermissionManagerServiceInternal mPermissionManager;
     private final Handler mHandler;
     private final File mDiskFile;
-
+    private final Context mContext;
 
     private SparseSetArray<String> mBackgroundInstalledPackages = null;
 
     // User ID -> package name -> set of foreground time frame
-    private final SparseArrayMap<String,
-            TreeSet<ForegroundTimeFrame>> mInstallerForegroundTimeFrames =
-            new SparseArrayMap<>();
+    private final SparseArrayMap<String, TreeSet<ForegroundTimeFrame>>
+            mInstallerForegroundTimeFrames = new SparseArrayMap<>();
 
     public BackgroundInstallControlService(@NonNull Context context) {
         this(new InjectorImpl(context));
@@ -99,20 +106,18 @@
     @VisibleForTesting
     BackgroundInstallControlService(@NonNull Injector injector) {
         super(injector.getContext());
-        mContext = injector.getContext();
         mPackageManager = injector.getPackageManager();
         mPackageManagerInternal = injector.getPackageManagerInternal();
         mPermissionManager = injector.getPermissionManager();
         mHandler = new EventHandler(injector.getLooper(), this);
         mDiskFile = injector.getDiskFile();
-        mUsageStatsManagerInternal = injector.getUsageStatsManagerInternal();
-        mUsageStatsManagerInternal.registerListener(
+        mContext = injector.getContext();
+        UsageStatsManagerInternal usageStatsManagerInternal =
+                injector.getUsageStatsManagerInternal();
+        usageStatsManagerInternal.registerListener(
                 (userId, event) ->
-                        mHandler.obtainMessage(MSG_USAGE_EVENT_RECEIVED,
-                                userId,
-                                0,
-                                event).sendToTarget()
-        );
+                        mHandler.obtainMessage(MSG_USAGE_EVENT_RECEIVED, userId, 0, event)
+                                .sendToTarget());
         mBinderService = new BinderService(this);
     }
 
@@ -126,12 +131,17 @@
         @Override
         public ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
                 @PackageManager.PackageInfoFlagsBits long flags, int userId) {
+            if (Flags.bicClient()) {
+                mService.enforceCallerPermissions();
+            }
             if (!Build.IS_DEBUGGABLE) {
                 return mService.getBackgroundInstalledPackages(flags, userId);
             }
             // The debug.transparency.bg-install-apps (only works for debuggable builds)
             // is used to set mock list of background installed apps for testing.
             // The list of apps' names is delimited by ",".
+            // TODO: Remove after migrating test to new background install method using
+            // {@link BackgroundInstallControlCallbackHelperTest}.installPackage b/310983905
             String propertyString = SystemProperties.get("debug.transparency.bg-install-apps");
             if (TextUtils.isEmpty(propertyString)) {
                 return mService.getBackgroundInstalledPackages(flags, userId);
@@ -139,25 +149,36 @@
                 return mService.getMockBackgroundInstalledPackages(propertyString);
             }
         }
+
+    }
+
+    @RequiresPermission(GET_BACKGROUND_INSTALLED_PACKAGES)
+    void enforceCallerPermissions() throws SecurityException {
+        mContext.enforceCallingOrSelfPermission(GET_BACKGROUND_INSTALLED_PACKAGES,
+                ENFORCE_PERMISSION_ERROR_MSG + GET_BACKGROUND_INSTALLED_PACKAGES);
     }
 
     @VisibleForTesting
     ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
             @PackageManager.PackageInfoFlagsBits long flags, int userId) {
-        List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
+        final long token = Binder.clearCallingIdentity();
+        try {
+            List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
                     PackageManager.PackageInfoFlags.of(flags), userId);
 
-        initBackgroundInstalledPackages();
-
-        ListIterator<PackageInfo> iter = packages.listIterator();
-        while (iter.hasNext()) {
-            String packageName = iter.next().packageName;
-            if (!mBackgroundInstalledPackages.contains(userId, packageName)) {
-                iter.remove();
+            initBackgroundInstalledPackages();
+            ListIterator<PackageInfo> iter = packages.listIterator();
+            while (iter.hasNext()) {
+                String packageName = iter.next().packageName;
+                if (!mBackgroundInstalledPackages.contains(userId, packageName)) {
+                    iter.remove();
+                }
             }
-        }
 
-        return new ParceledListSlice<>(packages);
+            return new ParceledListSlice<>(packages);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     /**
@@ -170,8 +191,9 @@
         List<PackageInfo> mockPackages = new ArrayList<>();
         for (String name : mockPackageNames) {
             try {
-                PackageInfo packageInfo = mPackageManager.getPackageInfo(name,
-                        PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL));
+                PackageInfo packageInfo =
+                        mPackageManager.getPackageInfo(
+                                name, PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL));
                 mockPackages.add(packageInfo);
             } catch (PackageManager.NameNotFoundException e) {
                 Slog.w(TAG, "Package's PackageInfo not found " + name);
@@ -192,18 +214,16 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_USAGE_EVENT_RECEIVED: {
-                    mService.handleUsageEvent((UsageEvents.Event) msg.obj, msg.arg1 /* userId */);
+                case MSG_USAGE_EVENT_RECEIVED:
+                    mService.handleUsageEvent(
+                            (UsageEvents.Event) msg.obj, msg.arg1 /* userId */);
                     break;
-                }
-                case MSG_PACKAGE_ADDED: {
+                case MSG_PACKAGE_ADDED:
                     mService.handlePackageAdd((String) msg.obj, msg.arg1 /* userId */);
                     break;
-                }
-                case MSG_PACKAGE_REMOVED: {
+                case MSG_PACKAGE_REMOVED:
                     mService.handlePackageRemove((String) msg.obj, msg.arg1 /* userId */);
                     break;
-                }
                 default:
                     Slog.w(TAG, "Unknown message: " + msg.what);
             }
@@ -213,8 +233,9 @@
     void handlePackageAdd(String packageName, int userId) {
         ApplicationInfo appInfo = null;
         try {
-            appInfo = mPackageManager.getApplicationInfoAsUser(packageName,
-                    PackageManager.ApplicationInfoFlags.of(0), userId);
+            appInfo =
+                    mPackageManager.getApplicationInfoAsUser(
+                            packageName, PackageManager.ApplicationInfoFlags.of(0), userId);
         } catch (PackageManager.NameNotFoundException e) {
             Slog.w(TAG, "Package's appInfo not found " + packageName);
             return;
@@ -233,15 +254,18 @@
 
         // the installers without INSTALL_PACKAGES perm can't perform
         // the installation in background. So we can just filter out them.
-        if (mPermissionManager.checkPermission(installerPackageName,
-                android.Manifest.permission.INSTALL_PACKAGES, Context.DEVICE_ID_DEFAULT,
-                userId) != PackageManager.PERMISSION_GRANTED) {
+        if (mPermissionManager.checkPermission(
+                installerPackageName,
+                android.Manifest.permission.INSTALL_PACKAGES,
+                Context.DEVICE_ID_DEFAULT,
+                userId)
+                != PERMISSION_GRANTED) {
             return;
         }
 
         // convert up-time to current time.
-        final long installTimestamp = System.currentTimeMillis()
-                - (SystemClock.uptimeMillis() - appInfo.createTimestamp);
+        final long installTimestamp =
+                System.currentTimeMillis() - (SystemClock.uptimeMillis() - appInfo.createTimestamp);
 
         if (installedByAdb(initiatingPackageName)
                 || wasForegroundInstallation(installerPackageName, userId, installTimestamp)) {
@@ -259,8 +283,8 @@
         return PackageManagerServiceUtils.isInstalledByAdb(initiatingPackageName);
     }
 
-    private boolean wasForegroundInstallation(String installerPackageName,
-            int userId, long installTimestamp) {
+    private boolean wasForegroundInstallation(
+            String installerPackageName, int userId, long installTimestamp) {
         TreeSet<BackgroundInstallControlService.ForegroundTimeFrame> foregroundTimeFrames =
                 mInstallerForegroundTimeFrames.get(userId, installerPackageName);
 
@@ -349,12 +373,12 @@
             for (int i = 0; i < mBackgroundInstalledPackages.size(); i++) {
                 int userId = mBackgroundInstalledPackages.keyAt(i);
                 for (String packageName : mBackgroundInstalledPackages.get(userId)) {
-                    long token = protoOutputStream.start(
-                            BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+                    long token =
+                            protoOutputStream.start(
+                                    BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
                     protoOutputStream.write(
                             BackgroundInstalledPackageProto.PACKAGE_NAME, packageName);
-                    protoOutputStream.write(
-                            BackgroundInstalledPackageProto.USER_ID, userId + 1);
+                    protoOutputStream.write(BackgroundInstalledPackageProto.USER_ID, userId + 1);
                     protoOutputStream.end(token);
                 }
             }
@@ -387,23 +411,28 @@
                         != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
                     continue;
                 }
-                long token = protoInputStream.start(
-                        BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+                long token =
+                        protoInputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
                 String packageName = null;
                 int userId = UserHandle.USER_NULL;
                 while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                     switch (protoInputStream.getFieldNumber()) {
                         case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
-                            packageName = protoInputStream.readString(
-                                    BackgroundInstalledPackageProto.PACKAGE_NAME);
+                            packageName =
+                                    protoInputStream.readString(
+                                            BackgroundInstalledPackageProto.PACKAGE_NAME);
                             break;
                         case (int) BackgroundInstalledPackageProto.USER_ID:
-                            userId = protoInputStream.readInt(
-                                    BackgroundInstalledPackageProto.USER_ID) - 1;
+                            userId =
+                                    protoInputStream.readInt(
+                                            BackgroundInstalledPackageProto.USER_ID)
+                                            - 1;
                             break;
                         default:
-                            Slog.w(TAG, "Undefined field in proto: "
-                                    + protoInputStream.getFieldNumber());
+                            Slog.w(
+                                    TAG,
+                                    "Undefined field in proto: "
+                                            + protoInputStream.getFieldNumber());
                     }
                 }
                 protoInputStream.end(token);
@@ -432,9 +461,12 @@
         if (mInstallerForegroundTimeFrames.contains(userId, pkgName)) {
             return true;
         }
-        return mPermissionManager.checkPermission(pkgName,
-                android.Manifest.permission.INSTALL_PACKAGES, Context.DEVICE_ID_DEFAULT,
-                userId) == PackageManager.PERMISSION_GRANTED;
+        return mPermissionManager.checkPermission(
+                pkgName,
+                android.Manifest.permission.INSTALL_PACKAGES,
+                Context.DEVICE_ID_DEFAULT,
+                userId)
+                == PERMISSION_GRANTED;
     }
 
     @Override
@@ -448,21 +480,22 @@
             publishBinderService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE, mBinderService);
         }
 
-        mPackageManagerInternal.getPackageList(new PackageManagerInternal.PackageListObserver() {
-            @Override
-            public void onPackageAdded(String packageName, int uid) {
-                final int userId = UserHandle.getUserId(uid);
-                mHandler.obtainMessage(MSG_PACKAGE_ADDED,
-                        userId, 0, packageName).sendToTarget();
-            }
+        mPackageManagerInternal.getPackageList(
+                new PackageManagerInternal.PackageListObserver() {
+                    @Override
+                    public void onPackageAdded(String packageName, int uid) {
+                        final int userId = UserHandle.getUserId(uid);
+                        mHandler.obtainMessage(MSG_PACKAGE_ADDED, userId, 0, packageName)
+                                .sendToTarget();
+                    }
 
-            @Override
-            public void onPackageRemoved(String packageName, int uid) {
-                final int userId = UserHandle.getUserId(uid);
-                mHandler.obtainMessage(MSG_PACKAGE_REMOVED,
-                        userId, 0, packageName).sendToTarget();
-            }
-        });
+                    @Override
+                    public void onPackageRemoved(String packageName, int uid) {
+                        final int userId = UserHandle.getUserId(uid);
+                        mHandler.obtainMessage(MSG_PACKAGE_REMOVED, userId, 0, packageName)
+                                .sendToTarget();
+                    }
+                });
     }
 
     // The foreground time frame (ForegroundTimeFrame) represents the period
@@ -518,7 +551,7 @@
     }
 
     /**
-     * Dependency injector for {@link #BackgroundInstallControlService)}.
+     * Dependency injector for {@link BackgroundInstallControlService}.
      */
     interface Injector {
         Context getContext();
@@ -534,6 +567,7 @@
         Looper getLooper();
 
         File getDiskFile();
+
     }
 
     private static final class InjectorImpl implements Injector {
@@ -570,11 +604,11 @@
 
         @Override
         public Looper getLooper() {
-            ServiceThread serviceThread = new ServiceThread(TAG,
-                    android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
+            ServiceThread serviceThread =
+                    new ServiceThread(
+                            TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
             serviceThread.start();
             return serviceThread.getLooper();
-
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
index 888e1c2..c25cea6 100644
--- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java
+++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
@@ -47,7 +47,6 @@
 public class DataLoaderManagerService extends SystemService {
     private static final String TAG = "DataLoaderManager";
     private final Context mContext;
-    private final HandlerThread mThread;
     private final Handler mHandler;
     private final DataLoaderManagerBinderService mBinderService;
     private final SparseArray<DataLoaderServiceConnection> mServiceConnections =
@@ -57,10 +56,10 @@
         super(context);
         mContext = context;
 
-        mThread = new HandlerThread(TAG);
-        mThread.start();
+        HandlerThread thread = new HandlerThread(TAG);
+        thread.start();
 
-        mHandler = new Handler(mThread.getLooper());
+        mHandler = new Handler(thread.getLooper());
 
         mBinderService = new DataLoaderManagerBinderService();
     }
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index e3bbd2d..f987d4a 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -107,9 +107,6 @@
     @Nullable
     private final ComponentName mInstantAppResolverSettingsComponent;
 
-    @NonNull
-    private final String mRequiredSupplementalProcessPackage;
-
     @Nullable
     private final String mServicesExtensionPackageName;
 
@@ -125,7 +122,6 @@
             @NonNull PackageInstallerService installerService,
             @NonNull PackageProperty packageProperty, @NonNull ComponentName resolveComponentName,
             @Nullable ComponentName instantAppResolverSettingsComponent,
-            @NonNull String requiredSupplementalProcessPackage,
             @Nullable String servicesExtensionPackageName,
             @Nullable String sharedSystemSharedLibraryPackageName) {
         mService = service;
@@ -140,7 +136,6 @@
         mPackageProperty = packageProperty;
         mResolveComponentName = resolveComponentName;
         mInstantAppResolverSettingsComponent = instantAppResolverSettingsComponent;
-        mRequiredSupplementalProcessPackage = requiredSupplementalProcessPackage;
         mServicesExtensionPackageName = servicesExtensionPackageName;
         mSharedSystemSharedLibraryPackageName = sharedSystemSharedLibraryPackageName;
     }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index dd9541e..e70c5ea 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -576,6 +576,12 @@
                 mApexManager.registerApkInApex(pkg);
             }
 
+            if ((mPm.isDeviceUpgrading() && pkgSetting.isSystem()) || isReplace) {
+                for (int userId : mPm.mUserManager.getUserIds()) {
+                    pkgSetting.restoreComponentSettings(userId);
+                }
+            }
+
             // Don't add keysets for APEX as their package settings are not persisted and will
             // result in orphaned keysets.
             if ((scanFlags & SCAN_AS_APEX) == 0) {
@@ -698,7 +704,7 @@
                     pkgSetting.setUninstallReason(PackageManager.UNINSTALL_REASON_UNKNOWN, userId);
                     pkgSetting.setFirstInstallTime(System.currentTimeMillis(), userId);
                     // Clear any existing archive state.
-                    pkgSetting.setArchiveState(null, userId);
+                    mPm.mInstallerService.mPackageArchiver.clearArchiveState(packageName, userId);
                     mPm.mSettings.writePackageRestrictionsLPr(userId);
                     mPm.mSettings.writeKernelMappingLPr(pkgSetting);
                     installed = true;
@@ -1043,6 +1049,18 @@
                 } finally {
                     Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                 }
+                if (Flags.improveInstallFreeze()) {
+                    // Postpone freezer until after reconcile
+                    for (ReconciledPackage reconciledPkg : reconciledPackages) {
+                        InstallRequest installRequest = reconciledPkg.mInstallRequest;
+                        String packageName = installRequest.getParsedPackage().getPackageName();
+                        PackageFreezer freezer = freezePackageForInstall(packageName,
+                                UserHandle.USER_ALL, installRequest.getInstallFlags(),
+                                "installPackageLI", ApplicationExitInfo.REASON_PACKAGE_UPDATED,
+                                installRequest);
+                        installRequest.setFreezer(freezer);
+                    }
+                }
                 try {
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "commitPackages");
                     commitPackagesLocked(reconciledPackages, mPm.mUserManager.getUserIds());
@@ -1607,9 +1625,12 @@
             parsedPackage.setBaseApkPath(request.getApexInfo().modulePath);
         }
 
-        final PackageFreezer freezer =
-                freezePackageForInstall(pkgName, UserHandle.USER_ALL, installFlags,
-                        "installPackageLI", ApplicationExitInfo.REASON_PACKAGE_UPDATED, request);
+        PackageFreezer freezer = null;
+        if (!Flags.improveInstallFreeze()) {
+            freezer = freezePackageForInstall(pkgName, UserHandle.USER_ALL, installFlags,
+                    "installPackageLI", ApplicationExitInfo.REASON_PACKAGE_UPDATED, request);
+        }
+
 
         boolean shouldCloseFreezerBeforeReturn = true;
         try {
@@ -1859,9 +1880,11 @@
                     oldPackageState, parsedPackage, archivedPackage,
                     replace /* clearCodeCache */, sysPkg, ps, disabledPs);
         } finally {
-            request.setFreezer(freezer);
-            if (shouldCloseFreezerBeforeReturn) {
-                freezer.close();
+            if (freezer != null) {
+                request.setFreezer(freezer);
+                if (shouldCloseFreezerBeforeReturn) {
+                    freezer.close();
+                }
             }
         }
     }
@@ -2281,7 +2304,7 @@
                                 installerPackageName);
                     }
                     // Clear any existing archive state.
-                    ps.setArchiveState(null, userId);
+                    mPm.mInstallerService.mPackageArchiver.clearArchiveState(pkgName, userId);
                 } else if (allUsers != null) {
                     // The caller explicitly specified INSTALL_ALL_USERS flag.
                     // Thus, updating the settings to install the app for all users.
@@ -2305,7 +2328,8 @@
                                         installerPackageName);
                             }
                             // Clear any existing archive state.
-                            ps.setArchiveState(null, currentUserId);
+                            mPm.mInstallerService.mPackageArchiver.clearArchiveState(pkgName,
+                                    currentUserId);
                         } else {
                             ps.setInstalled(false, currentUserId);
                         }
@@ -2893,6 +2917,12 @@
                     // code is loaded by a new Activity before ApplicationInfo changes have
                     // propagated to all application threads.
                     mPm.scheduleDeferredNoKillPostDelete(args);
+                    if (Flags.improveInstallDontKill()) {
+                        synchronized (mPm.mInstallLock) {
+                            PackageManagerServiceUtils.linkSplitsToOldDirs(mPm.mInstaller,
+                                    packageName, pkgSetting.getPath(), pkgSetting.getOldPaths());
+                        }
+                    }
                 } else {
                     mRemovePackageHelper.cleanUpResources(packageName, args.getCodeFile(),
                             args.getInstructionSets());
@@ -4204,8 +4234,10 @@
             }
         }
 
+        final long firstInstallTime = Flags.fixSystemAppsFirstInstallTime()
+                ? System.currentTimeMillis() : 0;
         final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags,
-                scanFlags | SCAN_UPDATE_SIGNATURE, 0 /* currentTime */, user, null);
+                scanFlags | SCAN_UPDATE_SIGNATURE, firstInstallTime, user, null);
         return new Pair<>(scanResult, shouldHideSystemApp);
     }
 
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index d5471cb0..34903d1 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -1183,8 +1183,7 @@
      * Returns an auth token for the provided writable FD.
      *
      * @param authFd a file descriptor to proof that the caller can write to the file.
-     * @param appUid uid of the calling app.
-     * @param userId id of the user whose app file to enable fs-verity.
+     * @param uid uid of the calling app.
      *
      * @return authToken, or null if a remote call shouldn't be continued. See {@link
      * #checkBeforeRemote}.
@@ -1192,13 +1191,12 @@
      * @throws InstallerException if the remote call failed.
      */
     public IInstalld.IFsveritySetupAuthToken createFsveritySetupAuthToken(
-            ParcelFileDescriptor authFd, int appUid, @UserIdInt int userId)
-            throws InstallerException {
+            ParcelFileDescriptor authFd, int uid) throws InstallerException {
         if (!checkBeforeRemote()) {
             return null;
         }
         try {
-            return mInstalld.createFsveritySetupAuthToken(authFd, appUid, userId);
+            return mInstalld.createFsveritySetupAuthToken(authFd, uid);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 127bf49..b5346a3 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -32,6 +32,8 @@
 import static android.content.pm.LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS;
 import static android.content.pm.LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS;
 
+import static com.android.server.pm.PackageArchiver.isArchivingEnabled;
+
 import android.annotation.AppIdInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -56,7 +58,6 @@
 import android.content.LocusId;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.Flags;
 import android.content.pm.ILauncherApps;
 import android.content.pm.IOnAppsChangedListener;
 import android.content.pm.IPackageInstallerCallback;
@@ -507,7 +508,8 @@
             if (!canAccessProfile(userId, "cannot get shouldHideFromSuggestions")) {
                 return false;
             }
-            if (Flags.archiving() && packageName != null && isPackageArchived(packageName, user)) {
+            if (isArchivingEnabled() && packageName != null
+                    && isPackageArchived(packageName, user)) {
                 return true;
             }
             if (mPackageManagerInternal.filterAppAccess(
@@ -530,7 +532,7 @@
                                     .addCategory(Intent.CATEGORY_LAUNCHER)
                                     .setPackage(packageName),
                             user);
-            if (Flags.archiving()) {
+            if (isArchivingEnabled()) {
                 launcherActivities =
                         getActivitiesForArchivedApp(packageName, user, launcherActivities);
             }
@@ -701,7 +703,7 @@
                                 callingUid,
                                 user.getIdentifier());
                 if (activityInfo == null) {
-                    if (Flags.archiving()) {
+                    if (isArchivingEnabled()) {
                         return getMatchingArchivedAppActivityInfo(component, user);
                     }
                     return null;
@@ -984,7 +986,7 @@
                 long callingFlag =
                         PackageManager.MATCH_DIRECT_BOOT_AWARE
                                 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
-                if (Flags.archiving()) {
+                if (isArchivingEnabled()) {
                     callingFlag |= PackageManager.MATCH_ARCHIVED_PACKAGES;
                 }
                 final PackageInfo info =
@@ -1457,7 +1459,7 @@
             if (!canAccessProfile(user.getIdentifier(), "Cannot check component")) {
                 return false;
             }
-            if (Flags.archiving() && component != null && component.getPackageName() != null) {
+            if (isArchivingEnabled() && component != null && component.getPackageName() != null) {
                 List<LauncherActivityInfoInternal> archiveActivities =
                         generateLauncherActivitiesForArchivedApp(component.getPackageName(), user);
                 if (!archiveActivities.isEmpty()) {
@@ -1610,21 +1612,116 @@
                     "Can't access AppMarketActivity for another user")) {
                 return null;
             }
+            final int callingUser = getCallingUserId();
             final long identity = Binder.clearCallingIdentity();
+
             try {
-                // TODO(b/316118005): Add code to launch the app installer for the packageName.
-                Intent appMarketIntent = new Intent(Intent.ACTION_MAIN);
-                appMarketIntent.addCategory(Intent.CATEGORY_APP_MARKET);
-                final PendingIntent pi = PendingIntent.getActivityAsUser(
-                        mContext, /* requestCode */ 0, appMarketIntent, PendingIntent.FLAG_ONE_SHOT
-                                | PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT,
-                        /* options */ null, user);
-                return pi == null ? null : pi.getIntentSender();
+                if (packageName == null) {
+                    return buildAppMarketIntentSenderForUser(user);
+                }
+
+                String installerPackageName = getInstallerPackage(packageName, callingUser);
+                if (installerPackageName == null
+                        || mPackageManagerInternal.getPackageUid(
+                                        installerPackageName, /* flags= */ 0, user.getIdentifier())
+                                < 0) {
+                    if (DEBUG) {
+                        Log.d(
+                                TAG,
+                                "Can't find installer for "
+                                        + packageName
+                                        + " in user: "
+                                        + user.getIdentifier());
+                    }
+                    return buildAppMarketIntentSenderForUser(user);
+                }
+
+                Intent packageInfoIntent =
+                        buildMarketPackageInfoIntent(
+                                packageName, installerPackageName, callingPackage);
+                if (mPackageManagerInternal
+                        .queryIntentActivities(
+                                packageInfoIntent,
+                                packageInfoIntent.resolveTypeIfNeeded(
+                                        mContext.getContentResolver()),
+                                PackageManager.MATCH_ALL,
+                                Process.myUid(),
+                                user.getIdentifier())
+                        .isEmpty()) {
+                    if (DEBUG) {
+                        Log.d(
+                                TAG,
+                                "Can't resolve package info intent for package "
+                                        + packageName
+                                        + " and installer:  "
+                                        + installerPackageName);
+                    }
+                    return buildAppMarketIntentSenderForUser(user);
+                }
+
+                return buildIntentSenderForUser(packageInfoIntent, user);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
         }
 
+        @Nullable
+        private IntentSender buildAppMarketIntentSenderForUser(@NonNull UserHandle user) {
+            Intent appMarketIntent = new Intent(Intent.ACTION_MAIN);
+            appMarketIntent.addCategory(Intent.CATEGORY_APP_MARKET);
+            return buildIntentSenderForUser(appMarketIntent, user);
+        }
+
+        @Nullable
+        private IntentSender buildIntentSenderForUser(
+                @NonNull Intent intent, @NonNull UserHandle user) {
+            final PendingIntent pi =
+                    PendingIntent.getActivityAsUser(
+                            mContext,
+                            /* requestCode */ 0,
+                            intent,
+                            PendingIntent.FLAG_IMMUTABLE
+                                    | FLAG_UPDATE_CURRENT,
+                            /* options */ null,
+                            user);
+            return pi == null ? null : pi.getIntentSender();
+        }
+
+        @Nullable
+        private String getInstallerPackage(@NonNull String packageName, int callingUserId) {
+            String installerPackageName = null;
+            try {
+                installerPackageName =
+                        mIPM.getInstallSourceInfo(packageName, callingUserId)
+                                .getInstallingPackageName();
+            } catch (RemoteException re) {
+                Slog.e(TAG, "Couldn't find installer for " + packageName, re);
+            }
+
+            return installerPackageName;
+        }
+
+        @NonNull
+        private Intent buildMarketPackageInfoIntent(
+                @NonNull String packageName,
+                @NonNull String installerPackageName,
+                @NonNull String callingPackage) {
+            return new Intent(Intent.ACTION_VIEW)
+                    .setData(
+                            new Uri.Builder()
+                                    .scheme("market")
+                                    .authority("details")
+                                    .appendQueryParameter("id", packageName)
+                                    .build())
+                    .putExtra(
+                            Intent.EXTRA_REFERRER,
+                            new Uri.Builder()
+                                    .scheme("android-app")
+                                    .authority(callingPackage)
+                                    .build())
+                    .setPackage(installerPackageName);
+        }
+
         @Override
         public void startActivityAsUser(IApplicationThread caller, String callingPackage,
                 String callingFeatureId, ComponentName component, Rect sourceBounds,
@@ -1692,7 +1789,7 @@
                 }
                 if (!canLaunch
                         && includeArchivedApps
-                        && Flags.archiving()
+                        && isArchivingEnabled()
                         && getMatchingArchivedAppActivityInfo(component, user) != null) {
                     launchIntent.setPackage(null);
                     launchIntent.setComponent(component);
diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS
index 84324f2..c8bc56c 100644
--- a/services/core/java/com/android/server/pm/OWNERS
+++ b/services/core/java/com/android/server/pm/OWNERS
@@ -51,3 +51,5 @@
 per-file ShortcutService.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com
 per-file ShortcutUser.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com
 
+# background install control service
+per-file BackgroundInstall* = file:BACKGROUND_INSTALL_OWNERS
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index a4af5e7..32f5646 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -31,6 +31,7 @@
 import static android.content.pm.PackageManager.DELETE_ARCHIVE;
 import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
 import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
+import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;
 import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE;
 import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
 
@@ -51,12 +52,13 @@
 import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ArchivedActivityParcel;
+import android.content.pm.ArchivedPackageInfo;
 import android.content.pm.ArchivedPackageParcel;
+import android.content.pm.Flags;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.DeleteFlags;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.VersionedPackage;
@@ -65,17 +67,22 @@
 import android.graphics.Color;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
 import android.graphics.drawable.LayerDrawable;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Environment;
+import android.os.FileUtils;
 import android.os.IBinder;
 import android.os.ParcelableException;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SELinux;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.ExceptionUtils;
@@ -171,12 +178,15 @@
         return userState.getArchiveState() != null && !userState.isInstalled();
     }
 
+    public static boolean isArchivingEnabled() {
+        return Flags.archiving() || SystemProperties.getBoolean("pm.archiving.enabled", false);
+    }
+
     void requestArchive(
             @NonNull String packageName,
             @NonNull String callerPackageName,
             @NonNull IntentSender intentSender,
-            @NonNull UserHandle userHandle,
-            @DeleteFlags int flags) {
+            @NonNull UserHandle userHandle) {
         Objects.requireNonNull(packageName);
         Objects.requireNonNull(callerPackageName);
         Objects.requireNonNull(intentSender);
@@ -185,6 +195,7 @@
         Computer snapshot = mPm.snapshotComputer();
         int userId = userHandle.getIdentifier();
         int binderUid = Binder.getCallingUid();
+        int binderPid = Binder.getCallingPid();
         if (!PackageManagerServiceUtils.isSystemOrRootOrShell(binderUid)) {
             verifyCaller(snapshot.getPackageUid(callerPackageName, 0, userId), binderUid);
         }
@@ -216,10 +227,11 @@
                                     new VersionedPackage(packageName,
                                             PackageManager.VERSION_CODE_HIGHEST),
                                     callerPackageName,
-                                    DELETE_ARCHIVE | DELETE_KEEP_DATA | flags,
+                                    DELETE_ARCHIVE | DELETE_KEEP_DATA,
                                     intentSender,
                                     userId,
-                                    binderUid);
+                                    binderUid,
+                                    binderPid);
                         })
                 .exceptionally(
                         e -> {
@@ -309,6 +321,26 @@
         return false;
     }
 
+    void clearArchiveState(String packageName, int userId) {
+        synchronized (mPm.mLock) {
+            PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
+            if (ps != null) {
+                ps.setArchiveState(/* archiveState= */ null, userId);
+            }
+        }
+        mPm.mBackgroundHandler.post(
+                () -> {
+                    File iconsDir = getIconsDir(packageName, userId);
+                    if (!iconsDir.exists()) {
+                        return;
+                    }
+                    // TODO(b/319238030) Move this into installd.
+                    if (!FileUtils.deleteContentsAndDir(iconsDir)) {
+                        Slog.e(TAG, "Failed to clean up archive files for " + packageName);
+                    }
+                });
+    }
+
     @Nullable
     private String getCurrentLauncherPackageName(int userId) {
         ComponentName defaultLauncherComponent = mPm.snapshotComputer().getDefaultHomeActivity(
@@ -353,9 +385,8 @@
         verifyNotSystemApp(ps.getFlags());
         verifyInstalled(ps, userId);
         String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
-        verifyInstaller(responsibleInstallerPackage, userId);
-        ApplicationInfo installerInfo = snapshot.getApplicationInfo(
-                responsibleInstallerPackage, /* flags= */ 0, userId);
+        ApplicationInfo installerInfo = verifyInstaller(
+                snapshot, responsibleInstallerPackage, userId);
         verifyOptOutStatus(packageName,
                 UserHandle.getUid(userId, UserHandle.getUid(userId, ps.getAppId())));
 
@@ -381,23 +412,30 @@
                 installerPackage, /* flags= */ 0, userId);
         if (installerInfo == null) {
             // Should never happen because we just fetched the installerInfo.
-            Slog.e(TAG, "Couldnt find installer " + installerPackage);
+            Slog.e(TAG, "Couldn't find installer " + installerPackage);
             return null;
         }
+        final int iconSize = mContext.getSystemService(
+                ActivityManager.class).getLauncherLargeIconSize();
+
+        var info = new ArchivedPackageInfo(archivedPackage);
 
         try {
-            var packageName = archivedPackage.packageName;
-            var mainActivities = archivedPackage.archivedActivities;
-            List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.length);
-            for (int i = 0, size = mainActivities.length; i < size; ++i) {
-                var mainActivity = mainActivities[i];
-                Path iconPath = storeIconForParcel(packageName, mainActivity, userId, i);
+            var packageName = info.getPackageName();
+            var mainActivities = info.getLauncherActivities();
+            List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size());
+            for (int i = 0, size = mainActivities.size(); i < size; ++i) {
+                var mainActivity = mainActivities.get(i);
+                Path iconPath = storeAdaptiveDrawable(
+                        packageName, mainActivity.getIcon(), userId, i * 2 + 0, iconSize);
+                Path monochromePath = storeAdaptiveDrawable(
+                        packageName, mainActivity.getMonochromeIcon(), userId, i * 2 + 1, iconSize);
                 ArchiveActivityInfo activityInfo =
                         new ArchiveActivityInfo(
-                                mainActivity.title,
-                                mainActivity.originalComponentName,
+                                mainActivity.getLabel().toString(),
+                                mainActivity.getComponentName(),
                                 iconPath,
-                                null);
+                                monochromePath);
                 archiveActivityInfos.add(activityInfo);
             }
 
@@ -418,7 +456,8 @@
         List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size());
         for (int i = 0, size = mainActivities.size(); i < size; i++) {
             LauncherActivityInfo mainActivity = mainActivities.get(i);
-            Path iconPath = storeIcon(packageName, mainActivity, userId, i, iconSize);
+            Path iconPath = storeIcon(packageName, mainActivity, userId, i * 2 + 0, iconSize);
+            // i * 2 + 1 reserved for monochromeIcon
             ArchiveActivityInfo activityInfo =
                     new ArchiveActivityInfo(
                             mainActivity.getLabel().toString(),
@@ -431,21 +470,6 @@
         return new ArchiveState(archiveActivityInfos, installerTitle);
     }
 
-    // TODO(b/298452477) Handle monochrome icons.
-    private static Path storeIconForParcel(String packageName, ArchivedActivityParcel mainActivity,
-            @UserIdInt int userId, int index) throws IOException {
-        if (mainActivity.iconBitmap == null) {
-            return null;
-        }
-        File iconsDir = createIconsDir(userId);
-        File iconFile = new File(iconsDir, packageName + "-" + index + ".png");
-        try (FileOutputStream out = new FileOutputStream(iconFile)) {
-            out.write(mainActivity.iconBitmap);
-            out.flush();
-        }
-        return iconFile.toPath();
-    }
-
     @VisibleForTesting
     Path storeIcon(String packageName, LauncherActivityInfo mainActivity,
             @UserIdInt int userId, int index, int iconSize) throws IOException {
@@ -454,22 +478,53 @@
             // The app doesn't define an icon. No need to store anything.
             return null;
         }
-        File iconsDir = createIconsDir(userId);
-        File iconFile = new File(iconsDir, packageName + "-" + index + ".png");
-        Bitmap icon = drawableToBitmap(mainActivity.getIcon(/* density= */ 0), iconSize);
+        return storeDrawable(packageName, mainActivity.getIcon(/* density= */ 0), userId, index,
+                iconSize);
+    }
+
+    private static Path storeDrawable(String packageName, @Nullable Drawable iconDrawable,
+            @UserIdInt int userId, int index, int iconSize) throws IOException {
+        if (iconDrawable == null) {
+            return null;
+        }
+        File iconsDir = createIconsDir(packageName, userId);
+        File iconFile = new File(iconsDir, index + ".png");
+        Bitmap icon = drawableToBitmap(iconDrawable, iconSize);
         try (FileOutputStream out = new FileOutputStream(iconFile)) {
             // Note: Quality is ignored for PNGs.
             if (!icon.compress(Bitmap.CompressFormat.PNG, /* quality= */ 100, out)) {
                 throw new IOException(TextUtils.formatSimple("Failure to store icon file %s",
-                        iconFile.getName()));
+                        iconFile.getAbsolutePath()));
             }
             out.flush();
         }
         return iconFile.toPath();
     }
 
-    private void verifyInstaller(String installerPackageName, int userId)
-            throws PackageManager.NameNotFoundException {
+    /**
+     * Create an <a
+     * href="https://developer.android.com/develop/ui/views/launch/icon_design_adaptive">
+     * adaptive icon</a> from an icon.
+     * This is necessary so the icon can be displayed properly by different launchers.
+     */
+    private static Path storeAdaptiveDrawable(String packageName, @Nullable Drawable iconDrawable,
+            @UserIdInt int userId, int index, int iconSize) throws IOException {
+        if (iconDrawable == null) {
+            return null;
+        }
+
+        // see BaseIconFactory#createShapedIconBitmap
+        float inset = getExtraInsetFraction();
+        inset = inset / (1 + 2 * inset);
+        Drawable d = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK),
+                new InsetDrawable(iconDrawable/*d*/, inset, inset, inset, inset));
+
+        return storeDrawable(packageName, d, userId, index, iconSize);
+    }
+
+
+    private ApplicationInfo verifyInstaller(Computer snapshot, String installerPackageName,
+            int userId) throws PackageManager.NameNotFoundException {
         if (TextUtils.isEmpty(installerPackageName)) {
             throw new PackageManager.NameNotFoundException("No installer found");
         }
@@ -478,6 +533,12 @@
                 && !verifySupportsUnarchival(installerPackageName, userId)) {
             throw new PackageManager.NameNotFoundException("Installer does not support unarchival");
         }
+        ApplicationInfo appInfo = snapshot.getApplicationInfo(
+                installerPackageName, /* flags=*/ 0, userId);
+        if (appInfo == null) {
+            throw new PackageManager.NameNotFoundException("Failed to obtain Installer info");
+        }
+        return appInfo;
     }
 
     /**
@@ -543,7 +604,7 @@
         }
 
         try {
-            verifyInstaller(getResponsibleInstallerPackage(ps), userId);
+            verifyInstaller(snapshot, getResponsibleInstallerPackage(ps), userId);
             getLauncherActivityInfos(packageName, userId);
         } catch (PackageManager.NameNotFoundException e) {
             return false;
@@ -793,8 +854,20 @@
     }
 
     @VisibleForTesting
-    Bitmap decodeIcon(ArchiveActivityInfo archiveActivityInfo) {
-        return BitmapFactory.decodeFile(archiveActivityInfo.getIconBitmap().toString());
+    @Nullable
+    Bitmap decodeIcon(ArchiveActivityInfo activityInfo) {
+        Path iconBitmap = activityInfo.getIconBitmap();
+        if (iconBitmap == null) {
+            return null;
+        }
+        Bitmap bitmap = BitmapFactory.decodeFile(iconBitmap.toString());
+        // TODO(b/278553670) We should throw here after some time. Failing graciously now because
+        // we've just changed the place where we store icons.
+        if (bitmap == null) {
+            Slog.e(TAG, "Archived icon cannot be decoded " + iconBitmap.toAbsolutePath());
+            return null;
+        }
+        return bitmap;
     }
 
     Bitmap includeCloudOverlay(Bitmap bitmap) {
@@ -1075,8 +1148,9 @@
         }
     }
 
-    private static File createIconsDir(@UserIdInt int userId) throws IOException {
-        File iconsDir = getIconsDir(userId);
+    private static File createIconsDir(String packageName, @UserIdInt int userId)
+            throws IOException {
+        File iconsDir = getIconsDir(packageName, userId);
         if (!iconsDir.isDirectory()) {
             iconsDir.delete();
             iconsDir.mkdirs();
@@ -1088,8 +1162,10 @@
         return iconsDir;
     }
 
-    private static File getIconsDir(int userId) {
-        return new File(Environment.getDataSystemCeDirectory(userId), ARCHIVE_ICONS_DIR);
+    private static File getIconsDir(String packageName, int userId) {
+        return new File(
+                new File(Environment.getDataSystemCeDirectory(userId), ARCHIVE_ICONS_DIR),
+                packageName);
     }
 
     private static byte[] bytesFromBitmapFile(Path path) throws IOException {
@@ -1195,7 +1271,7 @@
             UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class);
             if (extraIntent != null && user != null
                     && mAppStateHelper.isAppTopVisible(
-                            getCurrentLauncherPackageName(user.getIdentifier()))) {
+                    getCurrentLauncherPackageName(user.getIdentifier()))) {
                 extraIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                 mContext.startActivityAsUser(extraIntent, user);
             }
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index ee5875e..68f6ca1 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -88,6 +88,13 @@
                 final boolean didRestore = (msg.arg2 != 0);
                 mPm.mRunningInstalls.delete(msg.arg1);
 
+                if (request == null) {
+                    if (DEBUG_INSTALL) {
+                        Slog.i(TAG, "InstallRequest is null. Nothing to do for post-install "
+                                + "token " + msg.arg1);
+                    }
+                    break;
+                }
                 request.closeFreezer();
                 request.onInstallCompleted();
                 request.runPostInstallRunnable();
@@ -116,10 +123,19 @@
                 }
             } break;
             case WRITE_SETTINGS: {
-                mPm.writeSettings(/*sync=*/false);
+                if (!mPm.tryWriteSettings(/*sync=*/false)) {
+                    // Failed to write.
+                    this.removeMessages(WRITE_SETTINGS);
+                    mPm.scheduleWriteSettings();
+                }
             } break;
             case WRITE_PACKAGE_LIST: {
-                mPm.writePackageList(msg.arg1);
+                int userId = msg.arg1;
+                if (!mPm.tryWritePackageList(userId)) {
+                    // Failed to write.
+                    this.removeMessages(WRITE_PACKAGE_LIST);
+                    mPm.scheduleWritePackageList(userId);
+                }
             } break;
             case CHECK_PENDING_VERIFICATION: {
                 final int verificationId = msg.arg1;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index a9118d4..c6d448d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -25,10 +25,12 @@
 import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED;
 import static android.content.pm.PackageInstaller.UNARCHIVAL_GENERIC_ERROR;
 import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
+import static android.content.pm.PackageManager.DELETE_ARCHIVE;
 import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
 import static android.os.Process.INVALID_UID;
 import static android.os.Process.SYSTEM_UID;
 
+import static com.android.server.pm.PackageArchiver.isArchivingEnabled;
 import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -786,6 +788,24 @@
             }
         }
 
+        if (Flags.recoverabilityDetection()) {
+            if (params.rollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH
+                    || params.rollbackImpactLevel
+                    == PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL) {
+                if ((params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) {
+                    throw new IllegalArgumentException(
+                            "Can't set rollbackImpactLevel when rollback is not enabled");
+                }
+                if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ROLLBACKS)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    throw new SecurityException(
+                            "Setting rollbackImpactLevel requires the MANAGE_ROLLBACKS permission");
+                }
+            } else if (params.rollbackImpactLevel < 0) {
+                throw new IllegalArgumentException("rollbackImpactLevel can't be negative.");
+            }
+        }
+
         boolean isApex = (params.installFlags & PackageManager.INSTALL_APEX) != 0;
         if (isApex) {
             if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGE_UPDATES)
@@ -825,7 +845,7 @@
         }
 
         params.installFlags &= ~PackageManager.INSTALL_UNARCHIVE;
-        if (Flags.archiving() && params.appPackageName != null) {
+        if (isArchivingEnabled() && params.appPackageName != null) {
             PackageStateInternal ps = mPm.snapshotComputer().getPackageStateInternal(
                     params.appPackageName, SYSTEM_UID);
             if (ps != null
@@ -1033,7 +1053,7 @@
     private int getExistingDraftSessionIdInternal(int installerUid,
             SessionParams sessionParams, int userId) {
         String appPackageName = sessionParams.appPackageName;
-        if (!Flags.archiving() || installerUid == INVALID_UID || appPackageName == null) {
+        if (!isArchivingEnabled() || installerUid == INVALID_UID || appPackageName == null) {
             return SessionInfo.INVALID_ID;
         }
 
@@ -1385,11 +1405,12 @@
                 flags,
                 statusReceiver,
                 userId,
-                Binder.getCallingUid());
+                Binder.getCallingUid(),
+                Binder.getCallingPid());
     }
 
     void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags,
-            IntentSender statusReceiver, int userId, int callingUid) {
+            IntentSender statusReceiver, int userId, int callingUid, int callingPid) {
         final Computer snapshot = mPm.snapshotComputer();
         snapshot.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
         if (!PackageManagerServiceUtils.isRootOrShell(callingUid)) {
@@ -1405,15 +1426,12 @@
 
         final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
                 statusReceiver, versionedPackage.getPackageName(),
-                canSilentlyInstallPackage, userId);
-        final boolean shouldShowConfirmationDialog =
-                (flags & PackageManager.DELETE_SHOW_DIALOG) != 0;
-        if (!shouldShowConfirmationDialog
-                && mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES)
-                    == PackageManager.PERMISSION_GRANTED) {
+                canSilentlyInstallPackage, userId, mPackageArchiver, flags);
+        if (mContext.checkPermission(Manifest.permission.DELETE_PACKAGES, callingPid, callingUid)
+                == PackageManager.PERMISSION_GRANTED) {
             // Sweet, call straight through!
             mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
-        } else if (!shouldShowConfirmationDialog && canSilentlyInstallPackage) {
+        } else if (canSilentlyInstallPackage) {
             // Allow the device owner and affiliated profile owner to silently delete packages
             // Need to clear the calling identity to get DELETE_PACKAGES permission
             final long ident = Binder.clearCallingIdentity();
@@ -1429,8 +1447,8 @@
         } else {
             ApplicationInfo appInfo = snapshot.getApplicationInfo(callerPackageName, 0, userId);
             if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P) {
-                mContext.enforceCallingOrSelfPermission(Manifest.permission.REQUEST_DELETE_PACKAGES,
-                        null);
+                mContext.enforcePermission(Manifest.permission.REQUEST_DELETE_PACKAGES, callingPid,
+                        callingUid, null);
             }
 
             // Take a short detour to confirm with user
@@ -1655,10 +1673,8 @@
             @NonNull String packageName,
             @NonNull String callerPackageName,
             @NonNull IntentSender intentSender,
-            @NonNull UserHandle userHandle,
-            @DeleteFlags int flags) {
-        mPackageArchiver.requestArchive(packageName, callerPackageName, intentSender,
-                userHandle, flags);
+            @NonNull UserHandle userHandle) {
+        mPackageArchiver.requestArchive(packageName, callerPackageName, intentSender, userHandle);
     }
 
     @Override
@@ -1759,7 +1775,7 @@
                         binderUid, unarchiveId));
             }
 
-            session.reportUnarchivalStatus(unarchiveId, status, requiredStorageBytes,
+            session.reportUnarchivalStatus(status, unarchiveId, requiredStorageBytes,
                     userActionIntent);
         }
     }
@@ -1828,9 +1844,23 @@
         private final IntentSender mTarget;
         private final String mPackageName;
         private final Notification mNotification;
+        private final int mUserId;
 
-        public PackageDeleteObserverAdapter(Context context, IntentSender target,
+        @DeleteFlags
+        private final int mFlags;
+
+        @Nullable
+        private final PackageArchiver mPackageArchiver;
+
+        PackageDeleteObserverAdapter(Context context, IntentSender target,
                 String packageName, boolean showNotification, int userId) {
+            this(context, target, packageName, showNotification, userId,
+                    /* packageArchiver= */ null, /* flags= */ 0);
+        }
+
+        PackageDeleteObserverAdapter(Context context, IntentSender target,
+                String packageName, boolean showNotification, int userId,
+                PackageArchiver packageArchiver, @DeleteFlags int flags) {
             mContext = context;
             mTarget = target;
             mPackageName = packageName;
@@ -1842,6 +1872,9 @@
             } else {
                 mNotification = null;
             }
+            mUserId = userId;
+            mPackageArchiver = packageArchiver;
+            mFlags = flags;
         }
 
         private String getDeviceOwnerDeletedPackageMsg() {
@@ -1883,6 +1916,11 @@
                         SystemMessage.NOTE_PACKAGE_STATE,
                         mNotification);
             }
+            if (mPackageArchiver != null
+                    && PackageManager.DELETE_SUCCEEDED != returnCode
+                    && (mFlags & DELETE_ARCHIVE) != 0) {
+                mPackageArchiver.clearArchiveState(mPackageName, mUserId);
+            }
             if (mTarget == null) {
                 return;
             }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 117d03f..27c3dad 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -54,6 +54,7 @@
 import static com.android.internal.util.XmlUtils.writeUriAttribute;
 import static com.android.server.pm.PackageInstallerService.prepareStageDir;
 import static com.android.server.pm.PackageManagerService.APP_METADATA_FILE_NAME;
+import static com.android.server.pm.PackageManagerService.DEFAULT_FILE_ACCESS_MODE;
 import static com.android.server.pm.PackageManagerServiceUtils.isInstalledByAdb;
 import static com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata;
 
@@ -1294,6 +1295,7 @@
             info.autoRevokePermissionsMode = params.autoRevokePermissionsMode;
             info.installFlags = params.installFlags;
             info.rollbackLifetimeMillis = params.rollbackLifetimeMillis;
+            info.rollbackImpactLevel = params.rollbackImpactLevel;
             info.isMultiPackage = params.isMultiPackage;
             info.isStaged = params.isStaged;
             info.rollbackDataPolicy = params.rollbackDataPolicy;
@@ -1831,7 +1833,7 @@
             try {
                 Os.link(path, sourcePath);
                 // Grant READ access for APK to be read successfully
-                Os.chmod(sourcePath, 0644);
+                Os.chmod(sourcePath, DEFAULT_FILE_ACCESS_MODE);
             } catch (ErrnoException e) {
                 e.rethrowAsIOException();
             }
@@ -1900,7 +1902,8 @@
 
             // If file is app metadata then set permission to 0640 to deny user read access since it
             // might contain sensitive information.
-            int mode = name.equals(APP_METADATA_FILE_NAME) ? APP_METADATA_FILE_ACCESS_MODE : 0644;
+            int mode = name.equals(APP_METADATA_FILE_NAME)
+                    ? APP_METADATA_FILE_ACCESS_MODE : DEFAULT_FILE_ACCESS_MODE;
             ParcelFileDescriptor targetPfd = openTargetInternal(target.getAbsolutePath(),
                     O_CREAT | O_WRONLY, mode);
             Os.chmod(target.getAbsolutePath(), mode);
@@ -4245,7 +4248,7 @@
                 throw new IOException("Failed to copy " + fromFile + " to " + tmpFile);
             }
             try {
-                Os.chmod(tmpFile.getAbsolutePath(), 0644);
+                Os.chmod(tmpFile.getAbsolutePath(), DEFAULT_FILE_ACCESS_MODE);
             } catch (ErrnoException e) {
                 throw new IOException("Failed to chmod " + tmpFile);
             }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 5225529..9617098 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -489,6 +489,9 @@
      */
     static final long WATCHDOG_TIMEOUT = 1000*60*10;     // ten minutes
 
+    // How long to wait for Lock in async writeSettings and writePackageList.
+    private static final long WRITE_LOCK_TIMEOUT_MS = 1000 * 10;   // 10 seconds
+
     /**
      * Default IncFs timeouts. Maximum values in IncFs is 1hr.
      *
@@ -593,6 +596,8 @@
 
     static final String APP_METADATA_FILE_NAME = "app.metadata";
 
+    static final int DEFAULT_FILE_ACCESS_MODE = 0644;
+
     final Handler mHandler;
     final Handler mBackgroundHandler;
 
@@ -1562,7 +1567,7 @@
         }
     }
 
-    private void scheduleWritePackageListLocked(int userId) {
+    void scheduleWritePackageList(int userId) {
         invalidatePackageInfoCache();
         if (!mHandler.hasMessages(WRITE_PACKAGE_LIST)) {
             Message msg = mHandler.obtainMessage(WRITE_PACKAGE_LIST);
@@ -1614,22 +1619,41 @@
         mSettings.writePackageRestrictions(dirtyUsers);
     }
 
-    void writeSettings(boolean sync) {
-        synchronized (mLock) {
+    private boolean tryUnderLock(boolean sync, long timeoutMs, Runnable runnable) {
+        try {
+            if (sync) {
+                mLock.lock();
+            } else if (!mLock.tryLock(timeoutMs, TimeUnit.MILLISECONDS)) {
+                return false;
+            }
+            try {
+                runnable.run();
+                return true;
+            } finally {
+                mLock.unlock();
+            }
+        } catch (InterruptedException e) {
+            Slog.e(TAG, "Failed to obtain mLock", e);
+        }
+        return false;
+    }
+
+    boolean tryWriteSettings(boolean sync) {
+        return tryUnderLock(sync, WRITE_LOCK_TIMEOUT_MS, () -> {
             mHandler.removeMessages(WRITE_SETTINGS);
             mBackgroundHandler.removeMessages(WRITE_DIRTY_PACKAGE_RESTRICTIONS);
             writeSettingsLPrTEMP(sync);
             synchronized (mDirtyUsers) {
                 mDirtyUsers.clear();
             }
-        }
+        });
     }
 
-    void writePackageList(int userId) {
-        synchronized (mLock) {
+    boolean tryWritePackageList(int userId) {
+        return tryUnderLock(/*sync=*/false, WRITE_LOCK_TIMEOUT_MS, () -> {
             mHandler.removeMessages(WRITE_PACKAGE_LIST);
             mSettings.writePackageListLPr(userId);
-        }
+        });
     }
 
     private static final Handler.Callback BACKGROUND_HANDLER_CALLBACK = new Handler.Callback() {
@@ -3054,7 +3078,9 @@
             if (mHandler.hasMessages(WRITE_SETTINGS)
                     || mBackgroundHandler.hasMessages(WRITE_DIRTY_PACKAGE_RESTRICTIONS)
                     || mHandler.hasMessages(WRITE_PACKAGE_LIST)) {
-                writeSettings(/*sync=*/true);
+                while (!tryWriteSettings(/*sync=*/true)) {
+                    Slog.wtf(TAG, "Failed to write settings on shutdown");
+                }
             }
         }
     }
@@ -4334,11 +4360,11 @@
                 mDirtyUsers.remove(userId);
             }
             mUserNeedsBadging.delete(userId);
-            mPermissionManager.onUserRemoved(userId);
+            mDeletePackageHelper.removeUnusedPackagesLPw(userManager, userId);
             mSettings.removeUserLPw(userId);
             mPendingBroadcasts.remove(userId);
-            mDeletePackageHelper.removeUnusedPackagesLPw(userManager, userId);
             mAppsFilter.onUserDeleted(snapshotComputer(), userId);
+            mPermissionManager.onUserRemoved(userId);
         }
         mInstantAppRegistry.onUserRemoved(userId);
         mPackageMonitorCallbackHelper.onUserRemoved(userId);
@@ -4361,7 +4387,7 @@
         }
         synchronized (mLock) {
             scheduleWritePackageRestrictions(userId);
-            scheduleWritePackageListLocked(userId);
+            scheduleWritePackageList(userId);
             mAppsFilter.onUserCreated(snapshotComputer(), userId);
         }
     }
@@ -4608,6 +4634,7 @@
             });
             // Send UNSTOPPED broadcast if necessary
             if (wasStopped && Flags.stayStopped()) {
+                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "unstoppedBroadcast");
                 final PackageManagerInternal pmi =
                         mInjector.getLocalService(PackageManagerInternal.class);
                 final int [] userIds = resolveUserIds(userId);
@@ -4627,6 +4654,7 @@
                 mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_UNSTOPPED,
                         packageName, extras, userIds, null /* instantUserIds */,
                         broadcastAllowList, mHandler, null /* filterExtras */);
+                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
             }
         }
     }
@@ -4659,8 +4687,7 @@
                     mPreferredActivityHelper, mResolveIntentHelper, mDomainVerificationManager,
                     mDomainVerificationConnection, mInstallerService, mPackageProperty,
                     mResolveComponentName, mInstantAppResolverSettingsComponent,
-                    mRequiredSdkSandboxPackage, mServicesExtensionPackageName,
-                    mSharedSystemSharedLibraryPackageName);
+                    mServicesExtensionPackageName, mSharedSystemSharedLibraryPackageName);
         }
 
         @Override
@@ -7780,7 +7807,8 @@
             mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
             mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS
                     | ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS
-                    | ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
+                    | ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES
+                    | ActivityInfo.FLAG_HARDWARE_ACCELERATED;
             mResolveActivity.theme = 0;
             mResolveActivity.exported = true;
             mResolveActivity.enabled = true;
@@ -7814,7 +7842,8 @@
                 mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER;
                 mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS
                         | ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY
-                        | ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
+                        | ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES
+                        | ActivityInfo.FLAG_HARDWARE_ACCELERATED;
                 mResolveActivity.theme = R.style.Theme_Material_Dialog_Alert;
                 mResolveActivity.exported = true;
                 mResolveActivity.enabled = true;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index cd34163..8531692 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -30,6 +30,7 @@
 import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
 import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING;
 import static com.android.server.pm.PackageManagerService.DEBUG_PREFERRED;
+import static com.android.server.pm.PackageManagerService.DEFAULT_FILE_ACCESS_MODE;
 import static com.android.server.pm.PackageManagerService.RANDOM_CODEPATH_PREFIX;
 import static com.android.server.pm.PackageManagerService.RANDOM_DIR_PREFIX;
 import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME;
@@ -69,6 +70,7 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Process;
+import android.os.SELinux;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.incremental.IncrementalManager;
@@ -129,10 +131,12 @@
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateException;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.zip.GZIPInputStream;
@@ -853,7 +857,7 @@
             FileUtils.copy(fileIn, outputStream);
             // Flush anything in buffer before chmod, because any writes after chmod will fail.
             outputStream.flush();
-            Os.fchmod(outputStream.getFD(), 0644);
+            Os.fchmod(outputStream.getFD(), DEFAULT_FILE_ACCESS_MODE);
             atomicFile.finishWrite(outputStream);
             return PackageManager.INSTALL_SUCCEEDED;
         } catch (IOException e) {
@@ -1081,8 +1085,8 @@
 
         final File targetFile = new File(targetDir, targetName);
         final FileDescriptor targetFd = Os.open(targetFile.getAbsolutePath(),
-                O_RDWR | O_CREAT, 0644);
-        Os.chmod(targetFile.getAbsolutePath(), 0644);
+                O_RDWR | O_CREAT, DEFAULT_FILE_ACCESS_MODE);
+        Os.chmod(targetFile.getAbsolutePath(), DEFAULT_FILE_ACCESS_MODE);
         FileInputStream source = null;
         try {
             source = new FileInputStream(sourcePath);
@@ -1552,4 +1556,72 @@
     public static boolean isInstalledByAdb(String initiatingPackageName) {
         return initiatingPackageName == null || SHELL_PACKAGE_NAME.equals(initiatingPackageName);
     }
+
+    public static void linkSplitsToOldDirs(@NonNull Installer installer,
+                                           @NonNull String packageName,
+                                           @NonNull File newPath,
+                                           @Nullable Set<File> oldPaths) {
+        if (oldPaths == null || oldPaths.isEmpty()) {
+            return;
+        }
+        if (IncrementalManager.isIncrementalPath(newPath.getPath())) {
+            //TODO(b/291212866): handle incremental installs
+            return;
+        }
+        final File[] filesInNewPath = newPath.listFiles();
+        if (filesInNewPath == null || filesInNewPath.length == 0) {
+            return;
+        }
+        final List<String> splitApkNames = new ArrayList<String>();
+        for (int i = 0; i < filesInNewPath.length; i++) {
+            if (!filesInNewPath[i].isDirectory() && filesInNewPath[i].toString().endsWith(".apk")) {
+                splitApkNames.add(filesInNewPath[i].getName());
+            }
+        }
+        final int numSplits = splitApkNames.size();
+        if (numSplits == 0) {
+            return;
+        }
+        for (File oldPath : oldPaths) {
+            if (!oldPath.exists()) {
+                continue;
+            }
+            for (int i = 0; i < numSplits; i++) {
+                final String splitApkName = splitApkNames.get(i);
+                final File linkedSplit = new File(oldPath, splitApkName);
+                if (linkedSplit.exists()) {
+                    if (DEBUG) {
+                        Slog.d(PackageManagerService.TAG, "Skipping existing linked split <"
+                                + linkedSplit + ">");
+                    }
+                    continue;
+                }
+                final File sourceSplit = new File(newPath, splitApkName);
+                try {
+                    installer.linkFile(packageName, splitApkName,
+                            newPath.getAbsolutePath(), oldPath.getAbsolutePath());
+                    if (DEBUG) {
+                        Slog.d(PackageManagerService.TAG, "Linked <"
+                                + sourceSplit + "> to <" + linkedSplit + ">");
+                    }
+                } catch (Installer.InstallerException e) {
+                    Slog.w(PackageManagerService.TAG, "Failed to link split <"
+                            + sourceSplit + " > to <" + linkedSplit + ">", e);
+                    continue;
+                }
+                try {
+                    Os.chmod(linkedSplit.getAbsolutePath(), DEFAULT_FILE_ACCESS_MODE);
+                } catch (ErrnoException e) {
+                    Slog.w(PackageManagerService.TAG, "Failed to set mode for linked split <"
+                            + linkedSplit + ">", e);
+                    continue;
+                }
+                if (!SELinux.restorecon(linkedSplit)) {
+                    Slog.w(PackageManagerService.TAG, "Failed to restorecon for linked split <"
+                            + linkedSplit + ">");
+                }
+            }
+        }
+        //TODO(b/291212866): support native libs
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 5724ee0..5c9c8c6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -28,6 +28,7 @@
 import static android.content.pm.PackageManager.RESTRICTION_NONE;
 
 import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
+import static com.android.server.pm.PackageManagerService.DEFAULT_FILE_ACCESS_MODE;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
 import android.accounts.IAccountManager;
@@ -297,6 +298,8 @@
                     return runSetHiddenSetting(true);
                 case "unhide":
                     return runSetHiddenSetting(false);
+                case "unstop":
+                    return runSetStoppedState(false);
                 case "suspend":
                     return runSuspend(true, 0);
                 case "suspend-quarantine":
@@ -2347,7 +2350,7 @@
                 Streams.copy(inStream, outStream);
             }
             // Give read permissions to the other group.
-            Os.chmod(outputProfilePath, /*mode*/ 0644 );
+            Os.chmod(outputProfilePath, /*mode*/ DEFAULT_FILE_ACCESS_MODE);
         } catch (IOException | ErrnoException e) {
             pw.println("Error when reading the profile fd: " + e.getMessage());
             e.printStackTrace(pw);
@@ -2662,6 +2665,26 @@
         return 0;
     }
 
+    private int runSetStoppedState(boolean state) throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+        String option = getNextOption();
+        if (option != null && option.equals("--user")) {
+            userId = UserHandle.parseUserArg(getNextArgRequired());
+        }
+
+        String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified");
+            return 1;
+        }
+        final int translatedUserId =
+                translateUserId(userId, UserHandle.USER_NULL, "runSetStoppedState");
+        mInterface.setPackageStoppedState(pkg, state, translatedUserId);
+        getOutPrintWriter().println("Package " + pkg + " new stopped state: "
+                + mInterface.isPackageStoppedForUser(pkg, translatedUserId));
+        return 0;
+    }
+
     private int runSetDistractingRestriction() {
         final PrintWriter pw = getOutPrintWriter();
         int userId = UserHandle.USER_SYSTEM;
@@ -3694,7 +3717,19 @@
                         // remember to set it themselves.
                         params.installerPackageName = "com.android.shell";
                     }
-                    sessionParams.installFlags |= PackageManager.INSTALL_ENABLE_ROLLBACK;
+                    int rollbackStrategy = PackageManager.ROLLBACK_DATA_POLICY_RESTORE;
+                    try {
+                        rollbackStrategy = Integer.parseInt(peekNextArg());
+                        if (rollbackStrategy < PackageManager.ROLLBACK_DATA_POLICY_RESTORE
+                                || rollbackStrategy > PackageManager.ROLLBACK_DATA_POLICY_RETAIN) {
+                            throw new IllegalArgumentException(
+                                    rollbackStrategy + " is not a valid rollback data policy.");
+                        }
+                        getNextArg(); // pop the argument
+                    } catch (NumberFormatException e) {
+                        // not followed by a number assume ROLLBACK_DATA_POLICY_RESTORE.
+                    }
+                    sessionParams.setEnableRollback(true, rollbackStrategy);
                     break;
                 case "--staged-ready-timeout":
                     params.stagedReadyTimeoutMs = Long.parseLong(getNextArgRequired());
@@ -3729,6 +3764,11 @@
         } else if (staged) {
             sessionParams.setStaged();
         }
+        if ((sessionParams.installFlags & PackageManager.INSTALL_APEX) != 0
+                && (sessionParams.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
+                && sessionParams.rollbackDataPolicy == PackageManager.ROLLBACK_DATA_POLICY_WIPE) {
+            throw new IllegalArgumentException("Data policy 'wipe' is not supported for apex.");
+        }
         return params;
     }
 
@@ -4645,7 +4685,7 @@
         try {
             mInterface.getPackageInstaller().requestArchive(packageName,
                     /* callerPackageName= */ "", receiver.getIntentSender(),
-                    new UserHandle(translatedUserId), 0);
+                    new UserHandle(translatedUserId));
         } catch (Exception e) {
             pw.println("Failure [" + e.getMessage() + "]");
             return 1;
@@ -4807,7 +4847,7 @@
         pw.println("       [--install-reason 0/1/2/3/4] [--originating-uri URI]");
         pw.println("       [--referrer URI] [--abi ABI_NAME] [--force-sdk]");
         pw.println("       [--preload] [--instant] [--full] [--dont-kill]");
-        pw.println("       [--enable-rollback]");
+        pw.println("       [--enable-rollback [0/1/2]]");
         pw.println("       [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
         pw.println("       [--apex] [--non-staged] [--force-non-staged]");
         pw.println("       [--staged-ready-timeout TIMEOUT] [--ignore-dexopt-profile]");
@@ -4831,6 +4871,8 @@
         pw.println("      --abi: override the default ABI of the platform");
         pw.println("      --instant: cause the app to be installed as an ephemeral install app");
         pw.println("      --full: cause the app to be installed as a non-ephemeral full app");
+        pw.println("      --enable-rollback: enable rollbacks for the upgrade.");
+        pw.println("          0=restore (default), 1=wipe, 2=retain");
         pw.println("      --install-location: force the install location:");
         pw.println("          0=auto, 1=internal only, 2=prefer external");
         pw.println("      --install-reason: indicates why the app is being installed:");
@@ -4934,6 +4976,8 @@
         pw.println("  hide [--user USER_ID] PACKAGE_OR_COMPONENT");
         pw.println("  unhide [--user USER_ID] PACKAGE_OR_COMPONENT");
         pw.println("");
+        pw.println("  unstop [--user USER_ID] PACKAGE");
+        pw.println("");
         pw.println("  suspend [--user USER_ID] PACKAGE [PACKAGE...]");
         pw.println("    Suspends the specified package(s) (as user).");
         pw.println("");
diff --git a/services/core/java/com/android/server/pm/PackageManagerTracedLock.java b/services/core/java/com/android/server/pm/PackageManagerTracedLock.java
index e15e8a8..75e1803f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerTracedLock.java
+++ b/services/core/java/com/android/server/pm/PackageManagerTracedLock.java
@@ -16,9 +16,11 @@
 
 package com.android.server.pm;
 
+import java.util.concurrent.locks.ReentrantLock;
+
 /**
  * This is a unique class that is used as the PackageManager lock.  It can be targeted for lock
  * injection, similar to {@link ActivityManagerGlobalLock}.
  */
-public class PackageManagerTracedLock {
+public class PackageManagerTracedLock extends ReentrantLock {
 }
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 45fc49a..74c482b 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -1112,6 +1112,29 @@
         return changed;
     }
 
+    void restoreComponentSettings(int userId) {
+        PackageUserStateImpl state = modifyUserStateComponents(userId, true, true);
+        WatchedArraySet<String> enabledComponents = state.getEnabledComponentsNoCopy();
+        WatchedArraySet<String> disabledComponents = state.getDisabledComponentsNoCopy();
+
+        boolean changed = false;
+        for (int i = enabledComponents.size() - 1; i >= 0; i--) {
+            if (!AndroidPackageUtils.hasComponentClassName(pkg, enabledComponents.valueAt(i))) {
+                enabledComponents.removeAt(i);
+                changed = true;
+            }
+        }
+        for (int i = disabledComponents.size() - 1; i >= 0; i--) {
+            if (!AndroidPackageUtils.hasComponentClassName(pkg, disabledComponents.valueAt(i))) {
+                disabledComponents.removeAt(i);
+                changed = true;
+            }
+        }
+        if (changed) {
+            onChanged();
+        }
+    }
+
     int getCurrentEnabledStateLPr(String componentName, int userId) {
         PackageUserStateInternal state = readUserState(userId);
         if (state.getEnabledComponentsNoCopy() != null
diff --git a/services/core/java/com/android/server/pm/PreferredComponent.java b/services/core/java/com/android/server/pm/PreferredComponent.java
index 18caafd..f3b1464 100644
--- a/services/core/java/com/android/server/pm/PreferredComponent.java
+++ b/services/core/java/com/android/server/pm/PreferredComponent.java
@@ -28,7 +28,8 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.LocalServices;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.PackageUserStateInternal;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -218,11 +219,15 @@
                 continue;
             }
 
-            // Avoid showing the disambiguation dialog if the package which is installed with
-            // reason INSTALL_REASON_DEVICE_SETUP.
-            final PackageUserState pkgUserState =
-                    pmi.getPackageStateInternal(ai.packageName).getUserStates().get(userId);
-            if (pkgUserState != null && pkgUserState.getInstallReason()
+            // Avoid showing the disambiguation dialog if the package is not installed or
+            // installed with reason INSTALL_REASON_DEVICE_SETUP.
+            final PackageStateInternal ps = pmi.getPackageStateInternal(ai.packageName);
+            if (ps == null) {
+                continue;
+            }
+            final PackageUserStateInternal pkgUserState = ps.getUserStates().get(userId);
+            if (pkgUserState == null
+                    || pkgUserState.getInstallReason()
                     == PackageManager.INSTALL_REASON_DEVICE_SETUP) {
                 continue;
             }
diff --git a/services/core/java/com/android/server/pm/ProtectedPackages.java b/services/core/java/com/android/server/pm/ProtectedPackages.java
index 98533725..524252c 100644
--- a/services/core/java/com/android/server/pm/ProtectedPackages.java
+++ b/services/core/java/com/android/server/pm/ProtectedPackages.java
@@ -57,11 +57,8 @@
     @GuardedBy("this")
     private final SparseArray<Set<String>> mOwnerProtectedPackages = new SparseArray<>();
 
-    private final Context mContext;
-
     public ProtectedPackages(Context context) {
-        mContext = context;
-        mDeviceProvisioningPackage = mContext.getResources().getString(
+        mDeviceProvisioningPackage = context.getResources().getString(
                 R.string.config_deviceProvisioningPackage);
     }
 
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 7bd6a43..70352be 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -67,7 +67,6 @@
     private final PackageManagerService mPm;
     private final IncrementalManager mIncrementalManager;
     private final Installer mInstaller;
-    private final UserManagerInternal mUserManagerInternal;
     private final PermissionManagerServiceInternal mPermissionManager;
     private final SharedLibrariesImpl mSharedLibraries;
     private final AppDataHelper mAppDataHelper;
@@ -79,7 +78,6 @@
         mPm = pm;
         mIncrementalManager = mPm.mInjector.getIncrementalManager();
         mInstaller = mPm.mInjector.getInstaller();
-        mUserManagerInternal = mPm.mInjector.getUserManagerInternal();
         mPermissionManager = mPm.mInjector.getPermissionManagerServiceInternal();
         mSharedLibraries = mPm.mInjector.getSharedLibrariesImpl();
         mAppDataHelper = appDataHelper;
@@ -394,8 +392,7 @@
             // Delete from mSettings
             final SparseBooleanArray changedUsers = new SparseBooleanArray();
             synchronized (mPm.mLock) {
-                mPm.mSettings.removePackageLPw(packageName);
-                outInfo.mIsAppIdRemoved = true;
+                outInfo.mIsAppIdRemoved = mPm.mSettings.removePackageAndAppIdLPw(packageName);
                 if (!mPm.mSettings.isDisabledSystemPackageLPr(packageName)) {
                     final SharedUserSetting sus = mPm.mSettings.getSharedUserSettingLPr(deletedPs);
                     // If we don't have a disabled system package to reinstall, the package is
@@ -487,8 +484,6 @@
         synchronized (mPm.mInstallLock) {
             cleanUpResourcesLI(codeFile, instructionSets);
         }
-        // TODO: open logging to help debug, will delete or add debug flag
-        Slog.d(TAG, "cleanUpResources for " + codeFile);
         if (packageName == null) {
             return;
         }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index b286b12..7cf1d33 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -92,7 +92,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
-import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.pkg.component.ParsedComponent;
 import com.android.internal.pm.pkg.component.ParsedIntentInfo;
 import com.android.internal.pm.pkg.component.ParsedPermission;
@@ -518,15 +517,6 @@
     @Watched(manual = true)
     private final AppIdSettingMap mAppIds;
 
-    // For reading/writing settings file.
-    @Watched
-    private final WatchedArrayList<Signature> mPastSignatures;
-    private final SnapshotCache<WatchedArrayList<Signature>> mPastSignaturesSnapshot;
-
-    @Watched
-    private final WatchedArrayMap<Long, Integer> mKeySetRefs;
-    private final SnapshotCache<WatchedArrayMap<Long, Integer>> mKeySetRefsSnapshot;
-
     // Packages that have been renamed since they were first installed.
     // Keys are the new names of the packages, values are the original
     // names.  The packages appear everywhere else under their original
@@ -614,8 +604,6 @@
         mNextAppLinkGeneration.registerObserver(mObserver);
         mPendingDefaultBrowser.registerObserver(mObserver);
         mPendingPackages.registerObserver(mObserver);
-        mPastSignatures.registerObserver(mObserver);
-        mKeySetRefs.registerObserver(mObserver);
     }
 
     // CONSTRUCTOR
@@ -642,12 +630,6 @@
         mCrossProfileIntentResolversSnapshot = new SnapshotCache.Auto<>(
                 mCrossProfileIntentResolvers, mCrossProfileIntentResolvers,
                 "Settings.mCrossProfileIntentResolvers");
-        mPastSignatures = new WatchedArrayList<>();
-        mPastSignaturesSnapshot = new SnapshotCache.Auto<>(mPastSignatures, mPastSignatures,
-                "Settings.mPastSignatures");
-        mKeySetRefs = new WatchedArrayMap<>();
-        mKeySetRefsSnapshot = new SnapshotCache.Auto<>(mKeySetRefs, mKeySetRefs,
-                "Settings.mKeySetRefs");
         mPendingPackages = new WatchedArrayList<>();
         mPendingPackagesSnapshot = new SnapshotCache.Auto<>(mPendingPackages, mPendingPackages,
                 "Settings.mPendingPackages");
@@ -703,12 +685,6 @@
         mCrossProfileIntentResolversSnapshot = new SnapshotCache.Auto<>(
                 mCrossProfileIntentResolvers, mCrossProfileIntentResolvers,
                 "Settings.mCrossProfileIntentResolvers");
-        mPastSignatures = new WatchedArrayList<>();
-        mPastSignaturesSnapshot = new SnapshotCache.Auto<>(mPastSignatures, mPastSignatures,
-                "Settings.mPastSignatures");
-        mKeySetRefs = new WatchedArrayMap<>();
-        mKeySetRefsSnapshot = new SnapshotCache.Auto<>(mKeySetRefs, mKeySetRefs,
-                "Settings.mKeySetRefs");
         mPendingPackages = new WatchedArrayList<>();
         mPendingPackagesSnapshot = new SnapshotCache.Auto<>(mPendingPackages, mPendingPackages,
                 "Settings.mPendingPackages");
@@ -800,11 +776,6 @@
         mSharedUsers.snapshot(r.mSharedUsers);
         mAppIds = r.mAppIds.snapshot();
 
-        mPastSignatures = r.mPastSignaturesSnapshot.snapshot();
-        mPastSignaturesSnapshot = new SnapshotCache.Sealed<>();
-        mKeySetRefs = r.mKeySetRefsSnapshot.snapshot();
-        mKeySetRefsSnapshot = new SnapshotCache.Sealed<>();
-
         mRenamedPackages.snapshot(r.mRenamedPackages);
         mNextAppLinkGeneration.snapshot(r.mNextAppLinkGeneration);
         mPendingDefaultBrowser.snapshot(r.mPendingDefaultBrowser);
@@ -1460,22 +1431,28 @@
         return false;
     }
 
-    int removePackageLPw(String name) {
+
+    /**
+     * Remove package from mPackages and its corresponding AppId.
+     *
+     * @return True if the AppId has been removed.
+     * False if the app doesn't exist, or if the app has a shared UID and there are other apps that
+     * still use the same shared UID even after the target app is removed.
+     */
+    boolean removePackageAndAppIdLPw(String name) {
         final PackageSetting p = mPackages.remove(name);
         if (p != null) {
             removeInstallerPackageStatus(name);
             SharedUserSetting sharedUserSetting = getSharedUserSettingLPr(p);
             if (sharedUserSetting != null) {
                 sharedUserSetting.removePackage(p);
-                if (checkAndPruneSharedUserLPw(sharedUserSetting, false)) {
-                    return sharedUserSetting.mAppId;
-                }
+                return checkAndPruneSharedUserLPw(sharedUserSetting, false);
             } else {
                 removeAppIdLPw(p.getAppId());
-                return p.getAppId();
+                return true;
             }
         }
-        return -1;
+        return false;
     }
 
     /**
@@ -2750,7 +2727,7 @@
         // right time.
         invalidatePackageCache();
 
-        mPastSignatures.clear();
+        ArrayList<Signature> writtenSignatures = new ArrayList<>();
 
         try (ResilientAtomicFile atomicFile = getSettingsFile()) {
             FileOutputStream str = null;
@@ -2798,7 +2775,7 @@
                         // load
                         continue;
                     }
-                    writePackageLPr(serializer, pkg);
+                    writePackageLPr(serializer, writtenSignatures, pkg);
                 }
 
                 for (final PackageSetting pkg : mDisabledSysPackages.values()) {
@@ -2814,7 +2791,7 @@
                     serializer.startTag(null, "shared-user");
                     serializer.attribute(null, ATTR_NAME, usr.name);
                     serializer.attributeInt(null, "userId", usr.mAppId);
-                    usr.signatures.writeXml(serializer, "sigs", mPastSignatures.untrackedStorage());
+                    usr.signatures.writeXml(serializer, "sigs", writtenSignatures);
                     serializer.endTag(null, "shared-user");
                 }
 
@@ -2854,6 +2831,7 @@
                 }
             }
         }
+
         //Debug.stopMethodTracing();
     }
 
@@ -3154,7 +3132,8 @@
         serializer.endTag(null, "updated-package");
     }
 
-    void writePackageLPr(TypedXmlSerializer serializer, final PackageSetting pkg)
+    void writePackageLPr(TypedXmlSerializer serializer, ArrayList<Signature> writtenSignatures,
+            PackageSetting pkg)
             throws java.io.IOException {
         serializer.startTag(null, "package");
         serializer.attribute(null, ATTR_NAME, pkg.getPackageName());
@@ -3254,11 +3233,11 @@
         writeUsesStaticLibLPw(serializer, pkg.getUsesStaticLibraries(),
                 pkg.getUsesStaticLibrariesVersions());
 
-        pkg.getSignatures().writeXml(serializer, "sigs", mPastSignatures.untrackedStorage());
+        pkg.getSignatures().writeXml(serializer, "sigs", writtenSignatures);
 
         if (installSource.mInitiatingPackageSignatures != null) {
             installSource.mInitiatingPackageSignatures.writeXml(
-                    serializer, "install-initiator-sigs", mPastSignatures.untrackedStorage());
+                    serializer, "install-initiator-sigs", writtenSignatures);
         }
 
         writeSigningKeySetLPr(serializer, pkg.getKeySetData());
@@ -3300,11 +3279,12 @@
     boolean readSettingsLPw(@NonNull Computer computer, @NonNull List<UserInfo> users,
             ArrayMap<String, Long> originalFirstInstallTimes) {
         mPendingPackages.clear();
-        mPastSignatures.clear();
-        mKeySetRefs.clear();
         mInstallerPackages.clear();
         originalFirstInstallTimes.clear();
 
+        ArrayMap<Long, Integer> keySetRefs = new ArrayMap<>();
+        ArrayList<Signature> readSignatures = new ArrayList<>();
+
         try (ResilientAtomicFile atomicFile = getSettingsFile()) {
             FileInputStream str = null;
             try {
@@ -3341,13 +3321,14 @@
 
                     String tagName = parser.getName();
                     if (tagName.equals("package")) {
-                        readPackageLPw(parser, users, originalFirstInstallTimes);
+                        readPackageLPw(parser, readSignatures, keySetRefs, users,
+                                originalFirstInstallTimes);
                     } else if (tagName.equals("permissions")) {
                         mPermissions.readPermissions(parser);
                     } else if (tagName.equals("permission-trees")) {
                         mPermissions.readPermissionTrees(parser);
                     } else if (tagName.equals("shared-user")) {
-                        readSharedUserLPw(parser, users);
+                        readSharedUserLPw(parser, readSignatures, users);
                     } else if (tagName.equals("preferred-packages")) {
                         // no longer used.
                     } else if (tagName.equals("preferred-activities")) {
@@ -3407,8 +3388,7 @@
                     } else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) {
                         // No longer used.
                     } else if (tagName.equals("keyset-settings")) {
-                        mKeySetManagerService.readKeySetsLPw(parser,
-                                mKeySetRefs.untrackedStorage());
+                        mKeySetManagerService.readKeySetsLPw(parser, keySetRefs);
                     } else if (TAG_VERSION.equals(tagName)) {
                         final String volumeUuid = XmlUtils.readStringAttribute(parser,
                                 ATTR_VOLUME_UUID);
@@ -3432,7 +3412,7 @@
                 }
 
                 str.close();
-            } catch (IOException | XmlPullParserException e) {
+            } catch (IOException | XmlPullParserException | ArrayIndexOutOfBoundsException e) {
                 // Remove corrupted file and retry.
                 atomicFile.failRead(str, e);
 
@@ -4002,7 +3982,8 @@
     private static final int PRE_M_APP_INFO_FLAG_CANT_SAVE_STATE = 1 << 28;
     private static final int PRE_M_APP_INFO_FLAG_PRIVILEGED = 1 << 30;
 
-    private void readPackageLPw(TypedXmlPullParser parser, List<UserInfo> users,
+    private void readPackageLPw(TypedXmlPullParser parser, ArrayList<Signature> readSignatures,
+            ArrayMap<Long, Integer> keySetRefs,  List<UserInfo> users,
             ArrayMap<String, Long> originalFirstInstallTimes)
             throws XmlPullParserException, IOException {
         String name = null;
@@ -4277,8 +4258,7 @@
                 } else if (tagName.equals(TAG_ENABLED_COMPONENTS)) {
                     readEnabledComponentsLPw(packageSetting, parser, 0);
                 } else if (tagName.equals("sigs")) {
-                    packageSetting.getSignatures()
-                            .readXml(parser,mPastSignatures.untrackedStorage());
+                    packageSetting.getSignatures().readXml(parser, readSignatures);
                 } else if (tagName.equals(TAG_PERMISSIONS)) {
                     final LegacyPermissionState legacyState;
                     if (packageSetting.hasSharedUser()) {
@@ -4295,11 +4275,11 @@
                     }
                 } else if (tagName.equals("proper-signing-keyset")) {
                     long id = parser.getAttributeLong(null, "identifier");
-                    Integer refCt = mKeySetRefs.get(id);
+                    Integer refCt = keySetRefs.get(id);
                     if (refCt != null) {
-                        mKeySetRefs.put(id, refCt + 1);
+                        keySetRefs.put(id, refCt + 1);
                     } else {
-                        mKeySetRefs.put(id, 1);
+                        keySetRefs.put(id, 1);
                     }
                     packageSetting.getKeySetData().setProperSigningKeySet(id);
                 } else if (tagName.equals("signing-keyset")) {
@@ -4310,16 +4290,16 @@
                 } else if (tagName.equals("defined-keyset")) {
                     long id = parser.getAttributeLong(null, "identifier");
                     String alias = parser.getAttributeValue(null, "alias");
-                    Integer refCt = mKeySetRefs.get(id);
+                    Integer refCt = keySetRefs.get(id);
                     if (refCt != null) {
-                        mKeySetRefs.put(id, refCt + 1);
+                        keySetRefs.put(id, refCt + 1);
                     } else {
-                        mKeySetRefs.put(id, 1);
+                        keySetRefs.put(id, 1);
                     }
                     packageSetting.getKeySetData().addDefinedKeySet(id, alias);
                 } else if (tagName.equals("install-initiator-sigs")) {
                     final PackageSignatures signatures = new PackageSignatures();
-                    signatures.readXml(parser, mPastSignatures.untrackedStorage());
+                    signatures.readXml(parser, readSignatures);
                     packageSetting.setInstallSource(
                             packageSetting.getInstallSource()
                                     .setInitiatingPackageSignatures(signatures));
@@ -4492,7 +4472,8 @@
         }
     }
 
-    private void readSharedUserLPw(TypedXmlPullParser parser, List<UserInfo> users)
+    private void readSharedUserLPw(TypedXmlPullParser parser, ArrayList<Signature> readSignatures,
+            List<UserInfo> users)
             throws XmlPullParserException, IOException {
         String name = null;
         int pkgFlags = 0;
@@ -4534,7 +4515,7 @@
 
                 String tagName = parser.getName();
                 if (tagName.equals("sigs")) {
-                    su.signatures.readXml(parser, mPastSignatures.untrackedStorage());
+                    su.signatures.readXml(parser, readSignatures);
                 } else if (tagName.equals("perms")) {
                     readInstallPermissionsLPr(parser, su.getLegacyPermissionState(), users);
                 } else {
diff --git a/services/core/java/com/android/server/pm/ShortcutNonPersistentUser.java b/services/core/java/com/android/server/pm/ShortcutNonPersistentUser.java
index 7f6f684..aa52522 100644
--- a/services/core/java/com/android/server/pm/ShortcutNonPersistentUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutNonPersistentUser.java
@@ -31,7 +31,6 @@
  * The access to it must be guarded with the shortcut manager lock.
  */
 public class ShortcutNonPersistentUser {
-    private final ShortcutService mService;
 
     private final int mUserId;
 
@@ -49,8 +48,7 @@
      */
     private final ArraySet<String> mHostPackageSet = new ArraySet<>();
 
-    public ShortcutNonPersistentUser(ShortcutService service, int userId) {
-        mService = service;
+    public ShortcutNonPersistentUser(int userId) {
         mUserId = userId;
     }
 
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index e993d9e..d644235 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -33,6 +33,7 @@
 import android.app.appsearch.SearchResults;
 import android.app.appsearch.SearchSpec;
 import android.app.appsearch.SetSchemaRequest;
+import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -47,6 +48,7 @@
 import android.os.Binder;
 import android.os.PersistableBundle;
 import android.os.StrictMode;
+import android.os.SystemClock;
 import android.text.format.Formatter;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -192,6 +194,9 @@
     private long mLastKnownForegroundElapsedTime;
 
     @GuardedBy("mLock")
+    private long mLastReportedTime;
+
+    @GuardedBy("mLock")
     private boolean mIsAppSearchSchemaUpToDate;
 
     private ShortcutPackage(ShortcutUser shortcutUser,
@@ -1673,6 +1678,26 @@
         return condition[0];
     }
 
+    void reportShortcutUsed(@NonNull final UsageStatsManagerInternal usageStatsManagerInternal,
+            @NonNull final String shortcutId) {
+        synchronized (mLock) {
+            final long currentTS = SystemClock.elapsedRealtime();
+            final ShortcutService s = mShortcutUser.mService;
+            if (currentTS - mLastReportedTime > s.mSaveDelayMillis) {
+                mLastReportedTime = currentTS;
+            } else {
+                return;
+            }
+            final long token = s.injectClearCallingIdentity();
+            try {
+                usageStatsManagerInternal.reportShortcutUsage(getPackageName(), shortcutId,
+                        getPackageUserId());
+            } finally {
+                s.injectRestoreCallingIdentity(token);
+            }
+        }
+    }
+
     public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
         pw.println();
 
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 3adeb4b..c23d2ab 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -77,6 +77,7 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.LocaleList;
 import android.os.Looper;
@@ -113,7 +114,6 @@
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
@@ -371,7 +371,7 @@
     private CompressFormat mIconPersistFormat;
     private int mIconPersistQuality;
 
-    private int mSaveDelayMillis;
+    int mSaveDelayMillis;
 
     private final IPackageManager mIPackageManager;
     private final PackageManagerInternal mPackageManagerInternal;
@@ -485,7 +485,14 @@
     }
 
     public ShortcutService(Context context) {
-        this(context, BackgroundThread.get().getLooper(), /*onyForPackgeManagerApis*/ false);
+        this(context, getBgLooper(), /*onyForPackgeManagerApis*/ false);
+    }
+
+    private static Looper getBgLooper() {
+        final HandlerThread handlerThread = new HandlerThread("shortcut",
+                android.os.Process.THREAD_PRIORITY_BACKGROUND);
+        handlerThread.start();
+        return handlerThread.getLooper();
     }
 
     @VisibleForTesting
@@ -1371,7 +1378,7 @@
     ShortcutNonPersistentUser getNonPersistentUserLocked(@UserIdInt int userId) {
         ShortcutNonPersistentUser ret = mShortcutNonPersistentUsers.get(userId);
         if (ret == null) {
-            ret = new ShortcutNonPersistentUser(this, userId);
+            ret = new ShortcutNonPersistentUser(userId);
             mShortcutNonPersistentUsers.put(userId, ret);
         }
         return ret;
@@ -2284,7 +2291,7 @@
 
         packageShortcutsChanged(ps, changedShortcuts, removedShortcuts);
 
-        reportShortcutUsedInternal(packageName, shortcut.getId(), userId);
+        ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcut.getId());
 
         verifyStates();
     }
@@ -2688,25 +2695,17 @@
             Slog.d(TAG, String.format("reportShortcutUsed: Shortcut %s package %s used on user %d",
                     shortcutId, packageName, userId));
         }
+        final ShortcutPackage ps;
         synchronized (mLock) {
             throwIfUserLockedL(userId);
-            final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
+            ps = getPackageShortcutsForPublisherLocked(packageName, userId);
             if (ps.findShortcutById(shortcutId) == null) {
                 Log.w(TAG, String.format("reportShortcutUsed: package %s doesn't have shortcut %s",
                         packageName, shortcutId));
                 return;
             }
         }
-        reportShortcutUsedInternal(packageName, shortcutId, userId);
-    }
-
-    private void reportShortcutUsedInternal(String packageName, String shortcutId, int userId) {
-        final long token = injectClearCallingIdentity();
-        try {
-            mUsageStatsManagerInternal.reportShortcutUsage(packageName, shortcutId, userId);
-        } finally {
-            injectRestoreCallingIdentity(token);
-        }
+        ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcutId);
     }
 
     @Override
@@ -5195,13 +5194,11 @@
     }
 
     // Injection point.
-    @VisibleForTesting
     long injectClearCallingIdentity() {
         return Binder.clearCallingIdentity();
     }
 
     // Injection point.
-    @VisibleForTesting
     void injectRestoreCallingIdentity(long token) {
         Binder.restoreCallingIdentity(token);
     }
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index 7d87d1b..cef3244 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -193,7 +193,7 @@
             }
 
             try {
-                sm.prepareUserStorage(volumeUuid, user.id, user.serialNumber, flags);
+                sm.prepareUserStorage(volumeUuid, user.id, flags);
                 synchronized (mPm.mInstallLock) {
                     appDataHelper.reconcileAppsDataLI(volumeUuid, user.id, flags,
                             true /* migrateAppData */);
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index 8adb566..1d41401 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -92,7 +92,7 @@
                 volumeUuid, userId, flags, isNewUser);
         try {
             // Prepare CE and/or DE storage.
-            storage.prepareUserStorage(volumeUuid, userId, userSerial, flags);
+            storage.prepareUserStorage(volumeUuid, userId, flags);
 
             // Ensure that the data directories of a removed user with the same ID are not being
             // reused.  New users must get fresh data directories, to avoid leaking data.
@@ -141,7 +141,7 @@
                 // If internal storage of the system user fails to prepare on first boot, then
                 // things are *really* broken, so we might as well reboot to recovery right away.
                 try {
-                    Log.wtf(TAG, "prepareUserData failed for user " + userId, e);
+                    Log.e(TAG, "prepareUserData failed for user " + userId, e);
                     if (isNewUser && userId == UserHandle.USER_SYSTEM && volumeUuid == null) {
                         RecoverySystem.rebootPromptAndWipeUserData(mContext,
                                 "failed to prepare internal storage for system user");
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 75b4531..c0596bb 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.pm;
 
+import static android.content.Intent.ACTION_SCREEN_OFF;
+import static android.content.Intent.ACTION_SCREEN_ON;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
@@ -41,11 +43,13 @@
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.StringRes;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityManagerNative;
+import android.app.ActivityOptions;
 import android.app.BroadcastOptions;
 import android.app.IActivityManager;
 import android.app.IStopUserCallback;
@@ -73,8 +77,10 @@
 import android.content.pm.parsing.FrameworkParsingPackageUtils;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.graphics.Bitmap;
 import android.multiuser.Flags;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -82,6 +88,7 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.IProgressListener;
 import android.os.IUserManager;
@@ -90,6 +97,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
+import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -173,6 +181,8 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
@@ -285,8 +295,6 @@
 
     private static final int USER_VERSION = 11;
 
-    private static final int MAX_USER_STRING_LENGTH = 500;
-
     private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms
 
     static final int WRITE_USER_MSG = 1;
@@ -295,6 +303,12 @@
 
     private static final long BOOT_USER_SET_TIMEOUT_MS = 300_000;
 
+    /**
+     * The time duration (in milliseconds) post device inactivity after which the private space
+     * should be auto-locked if the corresponding settings option is selected by the user.
+     */
+    private static final long PRIVATE_SPACE_AUTO_LOCK_INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000;
+
     // Tron counters
     private static final String TRON_GUEST_CREATED = "users_guest_created";
     private static final String TRON_USER_CREATED = "users_user_created";
@@ -320,6 +334,8 @@
 
     private final Handler mHandler;
 
+    private final ThreadPoolExecutor mInternalExecutor;
+
     private final File mUsersDir;
     private final File mUserListFile;
 
@@ -521,6 +537,36 @@
 
     private final LockPatternUtils mLockPatternUtils;
 
+    private KeyguardManager.KeyguardLockedStateListener mKeyguardLockedStateListener;
+
+    /** Token to identify and remove already scheduled private space auto-lock messages */
+    private static final Object PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN = new Object();
+
+    /** Content observer to get callbacks for privte space autolock settings changes */
+    private final SettingsObserver mPrivateSpaceAutoLockSettingsObserver;
+
+    private final class SettingsObserver extends ContentObserver {
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            if (isAutoLockForPrivateSpaceEnabled()) {
+                final String path = uri.getLastPathSegment();
+                if (TextUtils.equals(path, Settings.Secure.PRIVATE_SPACE_AUTO_LOCK)) {
+                    int autoLockPreference =
+                            Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                                    Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
+                                    Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER,
+                                    getMainUserIdUnchecked());
+                    Slog.i(LOG_TAG, "Auto-lock settings changed to " + autoLockPreference);
+                    setOrUpdateAutoLockPreferenceForPrivateProfile(autoLockPreference);
+                }
+            }
+        }
+    }
+
     private final String ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK =
             "com.android.server.pm.DISABLE_QUIET_MODE_AFTER_UNLOCK";
 
@@ -533,12 +579,173 @@
             final IntentSender target = intent.getParcelableExtra(Intent.EXTRA_INTENT, android.content.IntentSender.class);
             final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL);
             final String callingPackage = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
-            // Call setQuietModeEnabled on bg thread to avoid ANR
-            BackgroundThread.getHandler().post(() ->
-                    setQuietModeEnabled(userId, false, target, callingPackage));
+            setQuietModeEnabledAsync(userId, false, target, callingPackage);
         }
     };
 
+    /** Checks if the device inactivity broadcast receiver is already registered*/
+    private boolean mIsDeviceInactivityBroadcastReceiverRegistered = false;
+
+    private final BroadcastReceiver mDeviceInactivityBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (isAutoLockForPrivateSpaceEnabled()) {
+                if (ACTION_SCREEN_OFF.equals(intent.getAction())) {
+                    maybeScheduleMessageToAutoLockPrivateSpace();
+                } else if (ACTION_SCREEN_ON.equals(intent.getAction())) {
+                    // Remove any queued messages since the device is interactive again
+                    mHandler.removeCallbacksAndMessages(PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN);
+                }
+            }
+        }
+    };
+
+    @VisibleForTesting
+    void maybeScheduleMessageToAutoLockPrivateSpace() {
+        // No action needed if auto-lock on inactivity not selected
+        int privateSpaceAutoLockPreference =
+                Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                        Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
+                        Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER,
+                        getMainUserIdUnchecked());
+        if (privateSpaceAutoLockPreference
+                != Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY) {
+            return;
+        }
+        int privateProfileUserId = getPrivateProfileUserId();
+        if (privateProfileUserId != UserHandle.USER_NULL) {
+            scheduleMessageToAutoLockPrivateSpace(privateProfileUserId,
+                    PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN,
+                    PRIVATE_SPACE_AUTO_LOCK_INACTIVITY_TIMEOUT_MS);
+        }
+    }
+
+    @VisibleForTesting
+    void scheduleMessageToAutoLockPrivateSpace(int userId, Object token,
+            long delayInMillis) {
+        mHandler.postDelayed(() -> {
+            final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
+            if (powerManager != null && !powerManager.isInteractive()) {
+                Slog.i(LOG_TAG, "Auto-locking private space with user-id " + userId);
+                setQuietModeEnabledAsync(userId, true,
+                        /* target */ null, mContext.getPackageName());
+            } else {
+                Slog.i(LOG_TAG, "Device is interactive, skipping auto-lock");
+            }
+        }, token, delayInMillis);
+    }
+
+    @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
+    private void initializeAndRegisterKeyguardLockedStateListener() {
+        mKeyguardLockedStateListener = this::tryAutoLockingPrivateSpaceOnKeyguardChanged;
+        // Register with keyguard to send locked state events to the listener initialized above
+        try {
+            final KeyguardManager keyguardManager =
+                    mContext.getSystemService(KeyguardManager.class);
+            Slog.i(LOG_TAG, "Adding keyguard locked state listener");
+            keyguardManager.addKeyguardLockedStateListener(new HandlerExecutor(mHandler),
+                    mKeyguardLockedStateListener);
+        } catch (Exception e) {
+            Slog.e(LOG_TAG, "Error adding keyguard locked listener ", e);
+        }
+    }
+
+    @VisibleForTesting
+    @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
+    void setOrUpdateAutoLockPreferenceForPrivateProfile(
+            @Settings.Secure.PrivateSpaceAutoLockOption int autoLockPreference) {
+        int privateProfileUserId = getPrivateProfileUserId();
+        if (privateProfileUserId == UserHandle.USER_NULL) {
+            Slog.e(LOG_TAG, "Auto-lock preference updated but private space user not found");
+            return;
+        }
+
+        if (autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY) {
+            // Register inactivity broadcast
+            if (!mIsDeviceInactivityBroadcastReceiverRegistered) {
+                Slog.i(LOG_TAG, "Registering device inactivity broadcast receivers");
+                mContext.registerReceiver(mDeviceInactivityBroadcastReceiver,
+                        new IntentFilter(ACTION_SCREEN_OFF),
+                        null, mHandler);
+
+                mContext.registerReceiver(mDeviceInactivityBroadcastReceiver,
+                        new IntentFilter(ACTION_SCREEN_ON),
+                        null, mHandler);
+
+                mIsDeviceInactivityBroadcastReceiverRegistered = true;
+            }
+        } else {
+            // Unregister device inactivity broadcasts
+            if (mIsDeviceInactivityBroadcastReceiverRegistered) {
+                Slog.i(LOG_TAG, "Removing device inactivity broadcast receivers");
+                mHandler.removeCallbacksAndMessages(PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN);
+                mContext.unregisterReceiver(mDeviceInactivityBroadcastReceiver);
+                mIsDeviceInactivityBroadcastReceiverRegistered = false;
+            }
+        }
+
+        if (autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK) {
+            // Initialize and add keyguard state listener
+            initializeAndRegisterKeyguardLockedStateListener();
+        } else {
+            // Remove keyguard state listener
+            try {
+                final KeyguardManager keyguardManager =
+                        mContext.getSystemService(KeyguardManager.class);
+                Slog.i(LOG_TAG, "Removing keyguard locked state listener");
+                keyguardManager.removeKeyguardLockedStateListener(mKeyguardLockedStateListener);
+            } catch (Exception e) {
+                Slog.e(LOG_TAG, "Error adding keyguard locked state listener ", e);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void tryAutoLockingPrivateSpaceOnKeyguardChanged(boolean isKeyguardLocked) {
+        if (isAutoLockForPrivateSpaceEnabled()) {
+            int autoLockPreference = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                    Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
+                    Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER,
+                    getMainUserIdUnchecked());
+            boolean isAutoLockOnDeviceLockSelected =
+                    autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK;
+            if (isKeyguardLocked && isAutoLockOnDeviceLockSelected) {
+                autoLockPrivateSpace();
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void autoLockPrivateSpace() {
+        int privateProfileUserId = getPrivateProfileUserId();
+        if (privateProfileUserId != UserHandle.USER_NULL) {
+            Slog.i(LOG_TAG, "Auto-locking private space with user-id "
+                    + privateProfileUserId);
+            setQuietModeEnabledAsync(privateProfileUserId,
+                    /* enableQuietMode */true, /* target */ null,
+                    mContext.getPackageName());
+        }
+    }
+
+    @VisibleForTesting
+    void setQuietModeEnabledAsync(@UserIdInt int userId, boolean enableQuietMode,
+            IntentSender target, @Nullable String callingPackage) {
+        if (android.multiuser.Flags.moveQuietModeOperationsToSeparateThread()) {
+            // Call setQuietModeEnabled on a separate thread. Calling this operation on the main
+            // thread can cause ANRs, posting on a BackgroundThread can result in delays
+            Slog.d(LOG_TAG, "Calling setQuietModeEnabled for user " + userId
+                    + " on a separate thread");
+            mInternalExecutor.execute(() -> setQuietModeEnabled(userId, enableQuietMode, target,
+                    callingPackage));
+        } else {
+            // Call setQuietModeEnabled on bg thread to avoid ANR
+            BackgroundThread.getHandler().post(
+                    () -> setQuietModeEnabled(userId, enableQuietMode, target,
+                            callingPackage)
+            );
+        }
+    }
+
     /**
      * Cache the owner name string, since it could be read repeatedly on a critical code path
      * but hit by slow IO. This could be eliminated once we have the cached UserInfo in place.
@@ -587,7 +794,10 @@
         public void onFinished(int id, Bundle extras) {
             mHandler.post(() -> {
                 try {
-                    mContext.startIntentSender(mTarget, null, 0, 0, 0);
+                    ActivityOptions activityOptions =
+                            ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode(
+                                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+                    mContext.startIntentSender(mTarget, null, 0, 0, 0, activityOptions.toBundle());
                 } catch (IntentSender.SendIntentException e) {
                     Slog.e(LOG_TAG, "Failed to start the target in the callback", e);
                 }
@@ -762,6 +972,8 @@
         mPackagesLock = packagesLock;
         mUsers = users != null ? users : new SparseArray<>();
         mHandler = new MainHandler();
+        mInternalExecutor = new ThreadPoolExecutor(/* corePoolSize */ 0, /* maximumPoolSize */ 1,
+                /* keepAliveTime */ 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
         mUserVisibilityMediator = new UserVisibilityMediator(mHandler);
         mUserDataPreparer = userDataPreparer;
         mUserTypes = UserTypeFactory.getUserTypes();
@@ -786,9 +998,15 @@
         mLockPatternUtils = new LockPatternUtils(mContext);
         mUserStates.put(UserHandle.USER_SYSTEM, UserState.STATE_BOOTING);
         mUser0Allocations = DBG_ALLOCATION ? new AtomicInteger() : null;
+        mPrivateSpaceAutoLockSettingsObserver = new SettingsObserver(mHandler);
         emulateSystemUserModeIfNeeded();
     }
 
+    private static boolean isAutoLockForPrivateSpaceEnabled() {
+        return android.os.Flags.allowPrivateProfile()
+                && Flags.supportAutolockForPrivateSpace();
+    }
+
     void systemReady() {
         mAppOpsService = IAppOpsService.Stub.asInterface(
                 ServiceManager.getService(Context.APP_OPS_SERVICE));
@@ -805,9 +1023,34 @@
                 new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED),
                 null, mHandler);
 
+        if (isAutoLockForPrivateSpaceEnabled()) {
+
+            int mainUserId = getMainUserIdUnchecked();
+            if (mainUserId != UserHandle.USER_NULL) {
+                mContext.getContentResolver().registerContentObserverAsUser(
+                        Settings.Secure.getUriFor(
+                                Settings.Secure.PRIVATE_SPACE_AUTO_LOCK), false,
+                        mPrivateSpaceAutoLockSettingsObserver, UserHandle.of(mainUserId));
+
+                setOrUpdateAutoLockPreferenceForPrivateProfile(
+                        Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                                Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
+                                Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER, mainUserId));
+            }
+        }
+
+        if (isAutoLockingPrivateSpaceOnRestartsEnabled()) {
+            autoLockPrivateSpace();
+        }
+
         markEphemeralUsersForRemoval();
     }
 
+    private boolean isAutoLockingPrivateSpaceOnRestartsEnabled() {
+        return android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enablePrivateSpaceAutolockOnRestarts();
+    }
+
     /**
      * This method retrieves the  {@link UserManagerInternal} only for the purpose of
      * PackageManagerService construction.
@@ -969,6 +1212,18 @@
         return UserHandle.USER_NULL;
     }
 
+    private @UserIdInt int getPrivateProfileUserId() {
+        synchronized (mUsersLock) {
+            for (int userId : getUserIds()) {
+                UserInfo userInfo = getUserInfoLU(userId);
+                if (userInfo != null && userInfo.isPrivateProfile()) {
+                    return userInfo.id;
+                }
+            }
+        }
+        return UserHandle.USER_NULL;
+    }
+
     @Override
     public void setBootUser(@UserIdInt int userId) {
         checkCreateUsersPermission("Set boot user");
@@ -2337,8 +2592,18 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
-            if (telecomManager != null && telecomManager.isInCall()) {
-                flags |= UserManager.SWITCHABILITY_STATUS_USER_IN_CALL;
+            if (com.android.internal.telephony.flags
+                    .Flags.enforceTelephonyFeatureMappingForPublicApis()) {
+                if (mContext.getPackageManager().hasSystemFeature(
+                        PackageManager.FEATURE_TELECOM)) {
+                    if (telecomManager != null && telecomManager.isInCall()) {
+                        flags |= UserManager.SWITCHABILITY_STATUS_USER_IN_CALL;
+                    }
+                }
+            } else {
+                if (telecomManager != null && telecomManager.isInCall()) {
+                    flags |= UserManager.SWITCHABILITY_STATUS_USER_IN_CALL;
+                }
             }
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -4425,16 +4690,18 @@
         if (userData.persistSeedData) {
             if (userData.seedAccountName != null) {
                 serializer.attribute(null, ATTR_SEED_ACCOUNT_NAME,
-                        truncateString(userData.seedAccountName));
+                        truncateString(userData.seedAccountName,
+                                UserManager.MAX_ACCOUNT_STRING_LENGTH));
             }
             if (userData.seedAccountType != null) {
                 serializer.attribute(null, ATTR_SEED_ACCOUNT_TYPE,
-                        truncateString(userData.seedAccountType));
+                        truncateString(userData.seedAccountType,
+                                UserManager.MAX_ACCOUNT_STRING_LENGTH));
             }
         }
         if (userInfo.name != null) {
             serializer.startTag(null, TAG_NAME);
-            serializer.text(truncateString(userInfo.name));
+            serializer.text(truncateString(userInfo.name, UserManager.MAX_USER_NAME_LENGTH));
             serializer.endTag(null, TAG_NAME);
         }
         synchronized (mRestrictionsLock) {
@@ -4498,11 +4765,11 @@
         serializer.endDocument();
     }
 
-    private String truncateString(String original) {
-        if (original == null || original.length() <= MAX_USER_STRING_LENGTH) {
+    private String truncateString(String original, int limit) {
+        if (original == null || original.length() <= limit) {
             return original;
         }
-        return original.substring(0, MAX_USER_STRING_LENGTH);
+        return original.substring(0, limit);
     }
 
     /*
@@ -4969,7 +5236,7 @@
             @UserIdInt int parentId, boolean preCreate, @Nullable String[] disallowedPackages,
             @NonNull TimingsTraceAndSlog t, @Nullable Object token)
             throws UserManager.CheckedUserOperationException {
-        String truncatedName = truncateString(name);
+        String truncatedName = truncateString(name, UserManager.MAX_USER_NAME_LENGTH);
         final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
         if (userTypeDetails == null) {
             throwCheckedUserOperationException(
@@ -5136,7 +5403,7 @@
 
             t.traceBegin("createUserStorageKeys");
             final StorageManager storage = mContext.getSystemService(StorageManager.class);
-            storage.createUserStorageKeys(userId, userInfo.serialNumber, userInfo.isEphemeral());
+            storage.createUserStorageKeys(userId, userInfo.isEphemeral());
             t.traceEnd();
 
             // Only prepare DE storage here.  CE storage will be prepared later, when the user is
@@ -6554,9 +6821,14 @@
                     Slog.e(LOG_TAG, "No such user for settings seed data u=" + userId);
                     return;
                 }
-                userData.seedAccountName = truncateString(accountName);
-                userData.seedAccountType = truncateString(accountType);
-                userData.seedAccountOptions = accountOptions;
+                userData.seedAccountName = truncateString(accountName,
+                        UserManager.MAX_ACCOUNT_STRING_LENGTH);
+                userData.seedAccountType = truncateString(accountType,
+                        UserManager.MAX_ACCOUNT_STRING_LENGTH);
+                if (accountOptions != null && accountOptions.isBundleContentsWithinLengthLimit(
+                        UserManager.MAX_ACCOUNT_OPTIONS_LENGTH)) {
+                    userData.seedAccountOptions = accountOptions;
+                }
                 userData.persistSeedData = persist;
             }
             if (persist) {
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 14db70e..23d0230 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -288,6 +288,28 @@
      * configuration.
      */
     private static UserTypeDetails.Builder getDefaultTypeProfilePrivate() {
+        UserProperties.Builder userPropertiesBuilder = new UserProperties.Builder()
+                .setStartWithParent(true)
+                .setCredentialShareableWithParent(true)
+                .setAuthAlwaysRequiredToDisableQuietMode(true)
+                .setAllowStoppingUserWithDelayedLocking(true)
+                .setMediaSharedWithParent(false)
+                .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
+                .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
+                .setShowInQuietMode(
+                        UserProperties.SHOW_IN_QUIET_MODE_HIDDEN)
+                .setShowInSharingSurfaces(
+                        UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE)
+                .setCrossProfileIntentFilterAccessControl(
+                        UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
+                .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)
+                .setCrossProfileContentSharingStrategy(
+                        UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT);
+        if (android.multiuser.Flags.supportHidingProfiles()) {
+            userPropertiesBuilder.setProfileApiVisibility(
+                    UserProperties.PROFILE_API_VISIBILITY_HIDDEN);
+        }
+
         return new UserTypeDetails.Builder()
                 .setName(USER_TYPE_PROFILE_PRIVATE)
                 .setBaseType(FLAG_PROFILE)
@@ -306,23 +328,7 @@
                 .setDarkThemeBadgeColors(
                         R.color.white)
                 .setDefaultRestrictions(getDefaultProfileRestrictions())
-                .setDefaultUserProperties(new UserProperties.Builder()
-                        .setStartWithParent(true)
-                        .setCredentialShareableWithParent(true)
-                        .setAuthAlwaysRequiredToDisableQuietMode(true)
-                        .setAllowStoppingUserWithDelayedLocking(true)
-                        .setMediaSharedWithParent(false)
-                        .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
-                        .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
-                        .setShowInQuietMode(
-                                UserProperties.SHOW_IN_QUIET_MODE_HIDDEN)
-                        .setShowInSharingSurfaces(
-                                UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE)
-                        .setCrossProfileIntentFilterAccessControl(
-                                UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
-                        .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)
-                        .setCrossProfileContentSharingStrategy(
-                                UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT));
+                .setDefaultUserProperties(userPropertiesBuilder);
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index c6435ae..dd2b409 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -356,6 +356,12 @@
         if (verifierUser == UserHandle.ALL) {
             verifierUser = UserHandle.of(mPm.mUserManager.getCurrentUserId());
         }
+        // TODO(b/300965895): Remove when inconsistencies loading classpaths from apex for
+        // user > 1 are fixed. Tests should cover verifiers from apex classpaths run on
+        // primary user, secondary user and work profile.
+        if (pkgLite.isSdkLibrary) {
+            verifierUser = UserHandle.SYSTEM;
+        }
         final int verifierUserId = verifierUser.getIdentifier();
 
         List<String> requiredVerifierPackages = new ArrayList<>(
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index d0fe964..6ed2d31 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -421,6 +421,11 @@
         if (ai.isArchived) {
             ai.nonLocalizedLabel = state.getArchiveState().getActivityInfos().get(0).getTitle();
         }
+        if (!state.isInstalled() && !state.dataExists()
+                && android.content.pm.Flags.nullableDataDir()) {
+            // The data dir has been deleted
+            ai.dataDir = null;
+        }
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java b/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java
index 3efac81..d138606 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java
@@ -26,6 +26,7 @@
 public final class PermissionAllowlist {
     @NonNull
     private final ArrayMap<String, ArrayMap<String, Boolean>> mOemAppAllowlist = new ArrayMap<>();
+
     @NonNull
     private final ArrayMap<String, ArrayMap<String, Boolean>> mPrivilegedAppAllowlist =
             new ArrayMap<>();
@@ -43,6 +44,19 @@
             mApexPrivilegedAppAllowlists = new ArrayMap<>();
 
     @NonNull
+    private final ArrayMap<String, ArrayMap<String, Boolean>> mSignatureAppAllowlist =
+            new ArrayMap<>();
+    @NonNull
+    private final ArrayMap<String, ArrayMap<String, Boolean>> mVendorSignatureAppAllowlist =
+            new ArrayMap<>();
+    @NonNull
+    private final ArrayMap<String, ArrayMap<String, Boolean>> mProductSignatureAppAllowlist =
+            new ArrayMap<>();
+    @NonNull
+    private final ArrayMap<String, ArrayMap<String, Boolean>> mSystemExtSignatureAppAllowlist =
+            new ArrayMap<>();
+
+    @NonNull
     public ArrayMap<String, ArrayMap<String, Boolean>> getOemAppAllowlist() {
         return mOemAppAllowlist;
     }
@@ -73,6 +87,26 @@
         return mApexPrivilegedAppAllowlists;
     }
 
+    @NonNull
+    public ArrayMap<String, ArrayMap<String, Boolean>> getSignatureAppAllowlist() {
+        return mSignatureAppAllowlist;
+    }
+
+    @NonNull
+    public ArrayMap<String, ArrayMap<String, Boolean>> getVendorSignatureAppAllowlist() {
+        return mVendorSignatureAppAllowlist;
+    }
+
+    @NonNull
+    public ArrayMap<String, ArrayMap<String, Boolean>> getProductSignatureAppAllowlist() {
+        return mProductSignatureAppAllowlist;
+    }
+
+    @NonNull
+    public ArrayMap<String, ArrayMap<String, Boolean>> getSystemExtSignatureAppAllowlist() {
+        return mSystemExtSignatureAppAllowlist;
+    }
+
     @Nullable
     public Boolean getOemAppAllowlistState(@NonNull String packageName,
             @NonNull String permissionName) {
@@ -137,4 +171,44 @@
         }
         return permissions.get(permissionName);
     }
+
+    @Nullable
+    public Boolean getSignatureAppAllowlistState(@NonNull String packageName,
+            @NonNull String permissionName) {
+        ArrayMap<String, Boolean> permissions = mSignatureAppAllowlist.get(packageName);
+        if (permissions == null) {
+            return null;
+        }
+        return permissions.get(permissionName);
+    }
+
+    @Nullable
+    public Boolean getVendorSignatureAppAllowlistState(@NonNull String packageName,
+            @NonNull String permissionName) {
+        ArrayMap<String, Boolean> permissions = mVendorSignatureAppAllowlist.get(packageName);
+        if (permissions == null) {
+            return null;
+        }
+        return permissions.get(permissionName);
+    }
+
+    @Nullable
+    public Boolean getProductSignatureAppAllowlistState(@NonNull String packageName,
+            @NonNull String permissionName) {
+        ArrayMap<String, Boolean> permissions = mProductSignatureAppAllowlist.get(packageName);
+        if (permissions == null) {
+            return null;
+        }
+        return permissions.get(permissionName);
+    }
+
+    @Nullable
+    public Boolean getSystemExtSignatureAppAllowlistState(@NonNull String packageName,
+            @NonNull String permissionName) {
+        ArrayMap<String, Boolean> permissions = mSystemExtSignatureAppAllowlist.get(packageName);
+        if (permissions == null) {
+            return null;
+        }
+        return permissions.get(permissionName);
+    }
 }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 5d710d2..40f2264 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -29,6 +29,7 @@
 import static android.app.AppOpsManager.OP_BLUETOOTH_CONNECT;
 import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED;
 import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED;
+import static android.permission.flags.Flags.serverSideAttributionRegistration;
 
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
@@ -76,7 +77,6 @@
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.TriFunction;
 import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerService;
 import com.android.server.pm.permission.PermissionManagerServiceInternal.HotwordDetectionServiceProvider;
 import com.android.server.pm.pkg.AndroidPackage;
@@ -113,9 +113,6 @@
     /** Internal connection to the package manager */
     private final PackageManagerInternal mPackageManagerInt;
 
-    /** Internal connection to the user manager */
-    private final UserManagerInternal mUserManagerInt;
-
     /** Map of OneTimePermissionUserManagers keyed by userId */
     @GuardedBy("mLock")
     @NonNull
@@ -147,7 +144,6 @@
 
         mContext = context;
         mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
-        mUserManagerInt = LocalServices.getService(UserManagerInternal.class);
         mAppOpsManager = context.getSystemService(AppOpsManager.class);
 
         mAttributionSourceRegistry = new AttributionSourceRegistry(context);
@@ -439,10 +435,27 @@
         }
     }
 
+    /**
+     * Reference propagation over binder is affected by the ownership of the object. So if 
+     * the token is owned by client, references to the token on client side won't be 
+     * propagated to the server and the token may still be garbage collected on server side. 
+     * But if the token is owned by server, references to the token on client side will now 
+     * be propagated to the server since it's a foreign object to the client, and that will 
+     * keep the token referenced on the server side as long as the client is alive and 
+     * holding it.
+     */
     @Override
-    public void registerAttributionSource(@NonNull AttributionSourceState source) {
-        mAttributionSourceRegistry
-                .registerAttributionSource(new AttributionSource(source));
+    public IBinder registerAttributionSource(@NonNull AttributionSourceState source) {
+        if (serverSideAttributionRegistration()) {
+            Binder token = new Binder();
+            mAttributionSourceRegistry
+                    .registerAttributionSource(new AttributionSource(source).withToken(token));
+            return token;
+        } else {
+            mAttributionSourceRegistry
+                    .registerAttributionSource(new AttributionSource(source));
+            return source.token;
+        }
     }
 
     @Override
@@ -1080,12 +1093,10 @@
         private static final AtomicInteger sAttributionChainIds = new AtomicInteger(0);
 
         private final @NonNull Context mContext;
-        private final @NonNull AppOpsManager mAppOpsManager;
         private final @NonNull PermissionManagerServiceInternal mPermissionManagerServiceInternal;
 
         PermissionCheckerService(@NonNull Context context) {
             mContext = context;
-            mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
             mPermissionManagerServiceInternal =
                     LocalServices.getService(PermissionManagerServiceInternal.class);
         }
@@ -1218,7 +1229,6 @@
                 @Nullable String message, boolean forDataDelivery, boolean startDataDelivery,
                 boolean fromDatasource, int attributedOp) {
             PermissionInfo permissionInfo = sPlatformPermissions.get(permission);
-
             if (permissionInfo == null) {
                 try {
                     permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0);
@@ -1346,8 +1356,8 @@
 
                 // If the call is from a datasource we need to vet only the chain before it. This
                 // way we can avoid the datasource creating an attribution context for every call.
-                if (!(fromDatasource && current.equals(attributionSource))
-                        && next != null && !current.isTrusted(context)) {
+                boolean isDatasource = fromDatasource && current.equals(attributionSource);
+                if (!isDatasource && next != null && !current.isTrusted(context)) {
                     return PermissionChecker.PERMISSION_HARD_DENIED;
                 }
 
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 3afba39..6a57362 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -279,7 +279,6 @@
     @NonNull
     private final int[] mGlobalGids;
 
-    private final HandlerThread mHandlerThread;
     private final Handler mHandler;
     private final Context mContext;
     private final MetricsLogger mMetricsLogger = new MetricsLogger();
@@ -432,10 +431,10 @@
             }
         }
 
-        mHandlerThread = new ServiceThread(TAG,
+        HandlerThread handlerThread = new ServiceThread(TAG,
                 Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
+        handlerThread.start();
+        mHandler = new Handler(handlerThread.getLooper());
         Watchdog.getInstance().addThread(mHandler);
 
         SystemConfig systemConfig = SystemConfig.getInstance();
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
index c737283..f7603b5 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
@@ -27,6 +27,8 @@
 import com.android.server.pm.PackageKeySetData;
 import com.android.server.pm.permission.LegacyPermissionState;
 
+import java.io.File;
+import java.util.Set;
 import java.util.UUID;
 
 /**
@@ -111,4 +113,7 @@
      */
     @Nullable
     String getAppMetadataFilePath();
+
+    @Nullable
+    Set<File> getOldPaths();
 }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index bf669fb..1fdcc64 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2185,6 +2185,10 @@
         TalkbackShortcutController getTalkbackShortcutController() {
             return new TalkbackShortcutController(mContext);
         }
+
+        WindowWakeUpPolicy getWindowWakeUpPolicy() {
+            return new WindowWakeUpPolicy(mContext);
+        }
     }
 
     /** {@inheritDoc} */
@@ -2433,7 +2437,7 @@
                 com.android.internal.R.integer.config_keyguardDrawnTimeout);
         mKeyguardDelegate = injector.getKeyguardServiceDelegate();
         mTalkbackShortcutController = injector.getTalkbackShortcutController();
-        mWindowWakeUpPolicy = new WindowWakeUpPolicy(mContext);
+        mWindowWakeUpPolicy = injector.getWindowWakeUpPolicy();
         initKeyCombinationRules();
         initSingleKeyGestureRules(injector.getLooper());
         mButtonOverridePermissionChecker = injector.getButtonOverridePermissionChecker();
@@ -5634,7 +5638,7 @@
             Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn",
                     0 /* cookie */);
             updateScreenOffSleepToken(false /* acquire */, false /* isSwappingDisplay */);
-            mDefaultDisplayPolicy.screenTurnedOn(screenOnListener);
+            mDefaultDisplayPolicy.screenTurningOn(screenOnListener);
             mBootAnimationDismissable = false;
 
             synchronized (mLock) {
@@ -5676,6 +5680,7 @@
                 mKeyguardDelegate.onScreenTurnedOn();
             }
         }
+        mDefaultDisplayPolicy.screenTurnedOn();
         reportScreenStateToVrManager(true);
     }
 
diff --git a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
index 5e8b4de..7808c4e 100644
--- a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
+++ b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
@@ -30,6 +30,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.Signature;
 import android.os.Environment;
+import android.permission.flags.Flags;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -368,6 +369,7 @@
             dataOutputStream.writeUTF(profileOwner);
             dataOutputStream.writeInt(Settings.Global.getInt(mContext.getContentResolver(),
                     Settings.Global.DEVICE_DEMO_MODE, 0));
+            dataOutputStream.writeBoolean(Flags.walletRoleEnabled());
             dataOutputStream.flush();
         } catch (IOException e) {
             // Never happens for MessageDigestOutputStream and DataOutputStream.
diff --git a/services/core/java/com/android/server/policy/window_policy_flags.aconfig b/services/core/java/com/android/server/policy/window_policy_flags.aconfig
index ed981e0..2154a26 100644
--- a/services/core/java/com/android/server/policy/window_policy_flags.aconfig
+++ b/services/core/java/com/android/server/policy/window_policy_flags.aconfig
@@ -4,5 +4,5 @@
     name: "support_input_wakeup_delegate"
     namespace: "wear_frameworks"
     description: "Whether or not window policy allows injecting input wake-up delegate."
-    bug: "298055811"
+    bug: "319132073"
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 2128c991..a172de0 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -139,6 +139,7 @@
 import com.android.server.power.batterysaver.BatterySaverPolicy;
 import com.android.server.power.batterysaver.BatterySaverStateMachine;
 import com.android.server.power.batterysaver.BatterySavingStats;
+import com.android.server.power.feature.PowerManagerFlags;
 
 import dalvik.annotation.optimization.NeverCompile;
 
@@ -329,6 +330,8 @@
     // True if battery saver is supported on this device.
     private final boolean mBatterySaverSupported;
 
+    private final PowerManagerFlags mFeatureFlags;
+
     private boolean mDisableScreenWakeLocksWhileCached;
 
     private LightsManager mLightsManager;
@@ -1079,6 +1082,10 @@
         DeviceConfigParameterProvider createDeviceConfigParameterProvider() {
             return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
         }
+
+        PowerManagerFlags getFlags() {
+            return new PowerManagerFlags();
+        }
     }
 
     /** Interface for checking an app op permission */
@@ -1145,6 +1152,7 @@
         mNativeWrapper = injector.createNativeWrapper();
         mSystemProperties = injector.createSystemPropertiesWrapper();
         mClock = injector.createClock();
+        mFeatureFlags = injector.getFlags();
         mInjector = injector;
 
         mHandlerThread = new ServiceThread(TAG,
@@ -4405,8 +4413,8 @@
 
     private boolean setPowerModeInternal(int mode, boolean enabled) {
         // Maybe filter the event.
-        if (mBatterySaverStateMachine == null || (mode == Mode.LAUNCH && enabled
-                && mBatterySaverStateMachine.getBatterySaverController().isLaunchBoostDisabled())) {
+        if (mode == Mode.LAUNCH && enabled && mBatterySaverStateMachine != null
+                && mBatterySaverStateMachine.getBatterySaverController().isLaunchBoostDisabled()) {
             return false;
         }
         return mNativeWrapper.nativeSetPowerMode(mode, enabled);
@@ -4802,6 +4810,7 @@
         mAmbientDisplaySuppressionController.dump(pw);
 
         mLowPowerStandbyController.dump(pw);
+        mFeatureFlags.dump(pw);
     }
 
     private void dumpProto(FileDescriptor fd) {
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index 871e98b..4bf8a78 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -319,6 +319,11 @@
                 pd.setMax(100);
                 pd.setProgress(0);
                 pd.setIndeterminate(false);
+                boolean showPercent = context.getResources().getBoolean(
+                    com.android.internal.R.bool.config_showPercentageTextDuringRebootToUpdate);
+                if (!showPercent) {
+                    pd.setProgressPercentFormat(null);
+                }
                 pd.setProgressNumberFormat(null);
                 pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                 pd.setMessage(context.getText(
@@ -911,4 +916,4 @@
                     com.android.internal.R.string.config_defaultShutdownVibrationFile);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/power/feature/Android.bp b/services/core/java/com/android/server/power/feature/Android.bp
new file mode 100644
index 0000000..2295b41
--- /dev/null
+++ b/services/core/java/com/android/server/power/feature/Android.bp
@@ -0,0 +1,7 @@
+aconfig_declarations {
+    name: "power_flags",
+    package: "com.android.server.power.feature.flags",
+    srcs: [
+        "*.aconfig",
+    ],
+}
diff --git a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
new file mode 100644
index 0000000..a5a7069
--- /dev/null
+++ b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.feature;
+
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.server.power.feature.flags.Flags;
+
+import java.io.PrintWriter;
+import java.util.function.Supplier;
+
+/**
+ * Utility class to read the flags used in the power manager server.
+ */
+public class PowerManagerFlags {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "PowerManagerFlags";
+
+    private final FlagState mEarlyScreenTimeoutDetectorFlagState = new FlagState(
+            Flags.FLAG_ENABLE_EARLY_SCREEN_TIMEOUT_DETECTOR,
+            Flags::enableEarlyScreenTimeoutDetector);
+
+    /** Returns whether early-screen-timeout-detector is enabled on not. */
+    public boolean isEarlyScreenTimeoutDetectorEnabled() {
+        return mEarlyScreenTimeoutDetectorFlagState.isEnabled();
+    }
+
+    /**
+     * dumps all flagstates
+     * @param pw printWriter
+     */
+    public void dump(PrintWriter pw) {
+        pw.println("PowerManagerFlags:");
+        pw.println(" " + mEarlyScreenTimeoutDetectorFlagState);
+    }
+
+    private static class FlagState {
+
+        private final String mName;
+
+        private final Supplier<Boolean> mFlagFunction;
+        private boolean mEnabledSet;
+        private boolean mEnabled;
+
+        private FlagState(String name, Supplier<Boolean> flagFunction) {
+            mName = name;
+            mFlagFunction = flagFunction;
+        }
+
+        private boolean isEnabled() {
+            if (mEnabledSet) {
+                if (DEBUG) {
+                    Slog.d(TAG, mName + ": mEnabled. Recall = " + mEnabled);
+                }
+                return mEnabled;
+            }
+            mEnabled = mFlagFunction.get();
+            if (DEBUG) {
+                Slog.d(TAG, mName + ": mEnabled. Flag value = " + mEnabled);
+            }
+            mEnabledSet = true;
+            return mEnabled;
+        }
+
+        @Override
+        public String toString() {
+            // remove com.android.server.power.feature.flags. from the beginning of the name.
+            // align all isEnabled() values.
+            // Adjust lengths if we end up with longer names
+            final int nameLength = mName.length();
+            return TextUtils.substring(mName,  39, nameLength) + ": "
+                    + TextUtils.formatSimple("%" + (91 - nameLength) + "s%s", " " , isEnabled())
+                    + " (def:" + mFlagFunction.get() + ")";
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig
new file mode 100644
index 0000000..f5dfb5c
--- /dev/null
+++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig
@@ -0,0 +1,11 @@
+package: "com.android.server.power.feature.flags"
+
+# Important: Flags must be accessed through PowerManagerFlags.
+
+flag {
+    name: "enable_early_screen_timeout_detector"
+    namespace: "power"
+    description: "Feature flag for Early Screen Timeout detector"
+    bug: "309861917"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 03f3763..25e749f 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -138,6 +138,7 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.net.module.util.NetworkCapabilitiesUtils;
+import com.android.server.power.optimization.Flags;
 import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
 
 import libcore.util.EmptyArray;
@@ -185,20 +186,17 @@
     // TODO: remove "tcp" from network methods, since we measure total stats.
 
     // Current on-disk Parcel version. Must be updated when the format of the parcelable changes
-    public static final int VERSION = 214;
+    public static final int VERSION =
+            !Flags.disableSystemServicePowerAttr() ? 214 : 215;
 
     // The maximum number of names wakelocks we will keep track of
     // per uid; once the limit is reached, we batch the remaining wakelocks
     // in to one common name.
-    private static final int MAX_WAKELOCKS_PER_UID;
+    private static final int MAX_WAKELOCKS_PER_UID = isLowRamDevice() ? 40 : 200;
 
-    static {
-        if (ActivityManager.isLowRamDeviceStatic()) {
-            MAX_WAKELOCKS_PER_UID = 40;
-        } else {
-            MAX_WAKELOCKS_PER_UID = 200;
-        }
-    }
+    private static final int CELL_SIGNAL_STRENGTH_LEVEL_COUNT = getCellSignalStrengthLevelCount();
+
+    private static final int MODEM_TX_POWER_LEVEL_COUNT = getModemTxPowerLevelCount();
 
     // Number of transmit power states the Wifi controller can be in.
     private static final int NUM_WIFI_TX_LEVELS = 1;
@@ -278,11 +276,9 @@
     @VisibleForTesting
     protected KernelSingleUidTimeReader mKernelSingleUidTimeReader;
     @VisibleForTesting
-    protected SystemServerCpuThreadReader mSystemServerCpuThreadReader =
-            SystemServerCpuThreadReader.create();
+    protected SystemServerCpuThreadReader mSystemServerCpuThreadReader;
 
-    private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats
-            = new KernelMemoryBandwidthStats();
+    private KernelMemoryBandwidthStats mKernelMemoryBandwidthStats;
     private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();
     private int[] mCpuPowerBracketMap;
     private final CpuPowerStatsCollector mCpuPowerStatsCollector;
@@ -323,7 +319,7 @@
     private long mLastRpmStatsUpdateTimeMs = -RPM_STATS_UPDATE_FREQ_MS;
 
     /** Container for Rail Energy Data stats. */
-    private final RailStats mTmpRailStats = new RailStats();
+    private RailStats mTmpRailStats;
 
     /**
      * Estimate UID modem power usage based on their estimated mobile radio active time.
@@ -1031,7 +1027,7 @@
     int mPhoneSignalStrengthBin = -1;
     int mPhoneSignalStrengthBinRaw = -1;
     final StopwatchTimer[] mPhoneSignalStrengthsTimer =
-            new StopwatchTimer[CellSignalStrength.getNumSignalStrengthLevels()];
+            new StopwatchTimer[CELL_SIGNAL_STRENGTH_LEVEL_COUNT];
 
     StopwatchTimer mPhoneSignalScanningTimer;
 
@@ -1723,7 +1719,8 @@
     @VisibleForTesting
     public BatteryStatsImpl(Clock clock, File historyDirectory, @NonNull Handler handler,
             @NonNull PowerStatsUidResolver powerStatsUidResolver) {
-        init(clock);
+        mClock = clock;
+        initKernelStatsReaders();
         mBatteryStatsConfig = new BatteryStatsConfig.Builder().build();
         mHandler = handler;
         mPowerStatsUidResolver = powerStatsUidResolver;
@@ -1748,13 +1745,21 @@
         mCpuPowerStatsCollector = null;
     }
 
-    private void init(Clock clock) {
-        mClock = clock;
-        mCpuUidUserSysTimeReader = new KernelCpuUidUserSysTimeReader(true, clock);
-        mCpuUidFreqTimeReader = new KernelCpuUidFreqTimeReader(true, clock);
-        mCpuUidActiveTimeReader = new KernelCpuUidActiveTimeReader(true, clock);
-        mCpuUidClusterTimeReader = new KernelCpuUidClusterTimeReader(true, clock);
+    private void initKernelStatsReaders() {
+        if (!isKernelStatsAvailable()) {
+            return;
+        }
+
+        mCpuUidUserSysTimeReader = new KernelCpuUidUserSysTimeReader(true, mClock);
+        mCpuUidFreqTimeReader = new KernelCpuUidFreqTimeReader(true, mClock);
+        mCpuUidActiveTimeReader = new KernelCpuUidActiveTimeReader(true, mClock);
+        mCpuUidClusterTimeReader = new KernelCpuUidClusterTimeReader(true, mClock);
         mKernelWakelockReader = new KernelWakelockReader();
+        if (!Flags.disableSystemServicePowerAttr()) {
+            mSystemServerCpuThreadReader = SystemServerCpuThreadReader.create();
+        }
+        mKernelMemoryBandwidthStats = new KernelMemoryBandwidthStats();
+        mTmpRailStats = new RailStats();
     }
 
     /**
@@ -5878,7 +5883,7 @@
 
     @GuardedBy("this")
     void stopAllPhoneSignalStrengthTimersLocked(int except, long elapsedRealtimeMs) {
-        for (int i = 0; i < CellSignalStrength.getNumSignalStrengthLevels(); i++) {
+        for (int i = 0; i < CELL_SIGNAL_STRENGTH_LEVEL_COUNT; i++) {
             if (i == except) {
                 continue;
             }
@@ -8450,7 +8455,7 @@
         public ControllerActivityCounterImpl getOrCreateModemControllerActivityLocked() {
             if (mModemControllerActivity == null) {
                 mModemControllerActivity = new ControllerActivityCounterImpl(mBsi.mClock,
-                        mBsi.mOnBatteryTimeBase, ModemActivityInfo.getNumTxPowerLevels());
+                        mBsi.mOnBatteryTimeBase, mBsi.MODEM_TX_POWER_LEVEL_COUNT);
             }
             return mModemControllerActivity;
         }
@@ -10896,7 +10901,8 @@
             @NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile,
             @NonNull CpuScalingPolicies cpuScalingPolicies,
             @NonNull PowerStatsUidResolver powerStatsUidResolver) {
-        init(clock);
+        mClock = clock;
+        initKernelStatsReaders();
 
         mBatteryStatsConfig = config;
         mMonotonicClock = monotonicClock;
@@ -10992,7 +10998,7 @@
         mDeviceLightIdlingTimer = new StopwatchTimer(mClock, null, -15, null, mOnBatteryTimeBase);
         mDeviceIdlingTimer = new StopwatchTimer(mClock, null, -12, null, mOnBatteryTimeBase);
         mPhoneOnTimer = new StopwatchTimer(mClock, null, -3, null, mOnBatteryTimeBase);
-        for (int i = 0; i < CellSignalStrength.getNumSignalStrengthLevels(); i++) {
+        for (int i = 0; i < CELL_SIGNAL_STRENGTH_LEVEL_COUNT; i++) {
             mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(mClock, null, -200 - i, null,
                     mOnBatteryTimeBase);
         }
@@ -11012,7 +11018,7 @@
         mBluetoothActivity = new ControllerActivityCounterImpl(mClock, mOnBatteryTimeBase,
                 NUM_BT_TX_LEVELS);
         mModemActivity = new ControllerActivityCounterImpl(mClock, mOnBatteryTimeBase,
-                ModemActivityInfo.getNumTxPowerLevels());
+                MODEM_TX_POWER_LEVEL_COUNT);
         mMobileRadioActiveTimer = new StopwatchTimer(mClock, null, -400, null, mOnBatteryTimeBase);
         mMobileRadioActivePerAppTimer = new StopwatchTimer(mClock, null, -401, null,
                 mOnBatteryTimeBase);
@@ -11457,7 +11463,7 @@
     @Override
     public BatteryStatsHistoryIterator iterateBatteryStatsHistory(long startTimeMs,
             long endTimeMs) {
-        return mHistory.copy().iterate(startTimeMs, endTimeMs);
+        return mHistory.iterate(startTimeMs, endTimeMs);
     }
 
     @Override
@@ -11611,7 +11617,7 @@
         mFlashlightOnTimer.reset(false, elapsedRealtimeUs);
         mCameraOnTimer.reset(false, elapsedRealtimeUs);
         mBluetoothScanTimer.reset(false, elapsedRealtimeUs);
-        for (int i = 0; i < CellSignalStrength.getNumSignalStrengthLevels(); i++) {
+        for (int i = 0; i < CELL_SIGNAL_STRENGTH_LEVEL_COUNT; i++) {
             mPhoneSignalStrengthsTimer[i].reset(false, elapsedRealtimeUs);
         }
         mPhoneSignalScanningTimer.reset(false, elapsedRealtimeUs);
@@ -11700,7 +11706,9 @@
 
         EnergyConsumerStats.resetIfNotNull(mGlobalEnergyConsumerStats);
 
-        resetIfNotNull(mBinderThreadCpuTimesUs, false, elapsedRealtimeUs);
+        if (!Flags.disableSystemServicePowerAttr()) {
+            resetIfNotNull(mBinderThreadCpuTimesUs, false, elapsedRealtimeUs);
+        }
 
         mNumAllUidCpuTimeReads = 0;
         mNumUidsRemoved = 0;
@@ -11834,7 +11842,7 @@
     private String[] mWifiIfaces = EmptyArray.STRING;
 
     @GuardedBy("mWifiNetworkLock")
-    private NetworkStats mLastWifiNetworkStats = new NetworkStats(0, -1);
+    private NetworkStats mLastWifiNetworkStats;
 
     private final Object mModemNetworkLock = new Object();
 
@@ -11842,7 +11850,7 @@
     private String[] mModemIfaces = EmptyArray.STRING;
 
     @GuardedBy("mModemNetworkLock")
-    private NetworkStats mLastModemNetworkStats = new NetworkStats(0, -1);
+    private NetworkStats mLastModemNetworkStats;
 
     @VisibleForTesting
     protected NetworkStats readMobileNetworkStatsLocked(
@@ -11875,7 +11883,9 @@
         synchronized (mWifiNetworkLock) {
             final NetworkStats latestStats = readWifiNetworkStatsLocked(networkStatsManager);
             if (latestStats != null) {
-                delta = latestStats.subtract(mLastWifiNetworkStats);
+                delta = mLastWifiNetworkStats != null
+                        ? latestStats.subtract(mLastWifiNetworkStats)
+                        : latestStats.subtract(new NetworkStats(0, -1));
                 mLastWifiNetworkStats = latestStats;
             }
         }
@@ -12248,7 +12258,9 @@
         synchronized (mModemNetworkLock) {
             final NetworkStats latestStats = readMobileNetworkStatsLocked(networkStatsManager);
             if (latestStats != null) {
-                delta = latestStats.subtract(mLastModemNetworkStats);
+                delta = latestStats.subtract(mLastModemNetworkStats != null
+                        ? mLastModemNetworkStats
+                        : new NetworkStats(0, -1));
                 mLastModemNetworkStats = latestStats;
             }
         }
@@ -12301,7 +12313,7 @@
                         deltaInfo.getSleepTimeMillis());
                 mModemActivity.getOrCreateRxTimeCounter()
                         .increment(deltaInfo.getReceiveTimeMillis(), elapsedRealtimeMs);
-                for (int lvl = 0; lvl < ModemActivityInfo.getNumTxPowerLevels(); lvl++) {
+                for (int lvl = 0; lvl < MODEM_TX_POWER_LEVEL_COUNT; lvl++) {
                     mModemActivity.getOrCreateTxTimeCounters()[lvl]
                             .increment(deltaInfo.getTransmitDurationMillisAtPowerLevel(lvl),
                                     elapsedRealtimeMs);
@@ -12318,8 +12330,8 @@
                             mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE)
                             + deltaInfo.getReceiveTimeMillis() *
                             mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX);
-                    for (int i = 0; i < Math.min(ModemActivityInfo.getNumTxPowerLevels(),
-                            CellSignalStrength.getNumSignalStrengthLevels()); i++) {
+                    for (int i = 0; i < Math.min(MODEM_TX_POWER_LEVEL_COUNT,
+                            CELL_SIGNAL_STRENGTH_LEVEL_COUNT); i++) {
                         energyUsed += deltaInfo.getTransmitDurationMillisAtPowerLevel(i)
                                 * mPowerProfile.getAveragePower(
                                         PowerProfile.POWER_MODEM_CONTROLLER_TX, i);
@@ -12441,7 +12453,7 @@
                             }
 
                             if (totalTxPackets > 0 && entry.getTxPackets() > 0) {
-                                for (int lvl = 0; lvl < ModemActivityInfo.getNumTxPowerLevels();
+                                for (int lvl = 0; lvl < MODEM_TX_POWER_LEVEL_COUNT;
                                         lvl++) {
                                     long txMs = entry.getTxPackets()
                                             * deltaInfo.getTransmitDurationMillisAtPowerLevel(lvl);
@@ -12550,7 +12562,7 @@
                 && deltaInfo.getSpecificInfoFrequencyRange(0)
                 == ServiceState.FREQUENCY_RANGE_UNKNOWN) {
             // Specific info data unavailable. Proportionally smear Rx and Tx times across each RAT.
-            final int levelCount = CellSignalStrength.getNumSignalStrengthLevels();
+            final int levelCount = CELL_SIGNAL_STRENGTH_LEVEL_COUNT;
             long[] perSignalStrengthActiveTimeMs = new long[levelCount];
             long totalActiveTimeMs = 0;
 
@@ -12726,13 +12738,13 @@
             return;
         }
         int levelMaxTimeSpent = 0;
-        for (int i = 1; i < ModemActivityInfo.getNumTxPowerLevels(); i++) {
+        for (int i = 1; i < MODEM_TX_POWER_LEVEL_COUNT; i++) {
             if (activityInfo.getTransmitDurationMillisAtPowerLevel(i)
                     > activityInfo.getTransmitDurationMillisAtPowerLevel(levelMaxTimeSpent)) {
                 levelMaxTimeSpent = i;
             }
         }
-        if (levelMaxTimeSpent == ModemActivityInfo.getNumTxPowerLevels() - 1) {
+        if (levelMaxTimeSpent == MODEM_TX_POWER_LEVEL_COUNT - 1) {
             mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
                     HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG);
         }
@@ -13670,7 +13682,9 @@
                     mKernelCpuSpeedReaders[i].readDelta();
                 }
             }
-            mSystemServerCpuThreadReader.readDelta();
+            if (!Flags.disableSystemServicePowerAttr()) {
+                mSystemServerCpuThreadReader.readDelta();
+            }
             return;
         }
 
@@ -14821,12 +14835,12 @@
             timeInRatMs[i] = getPhoneDataConnectionTime(i, rawRealTimeUs, which) / 1000;
         }
         long[] timeInRxSignalStrengthLevelMs =
-                new long[CellSignalStrength.getNumSignalStrengthLevels()];
+                new long[CELL_SIGNAL_STRENGTH_LEVEL_COUNT];
         for (int i = 0; i < timeInRxSignalStrengthLevelMs.length; i++) {
             timeInRxSignalStrengthLevelMs[i] =
                 getPhoneSignalStrengthTime(i, rawRealTimeUs, which) / 1000;
         }
-        long[] txTimeMs = new long[Math.min(ModemActivityInfo.getNumTxPowerLevels(),
+        long[] txTimeMs = new long[Math.min(MODEM_TX_POWER_LEVEL_COUNT,
             counter.getTxTimeCounters().length)];
         long totalTxTimeMs = 0;
         for (int i = 0; i < txTimeMs.length; i++) {
@@ -15458,7 +15472,7 @@
 
         public Constants(Handler handler) {
             super(handler);
-            if (ActivityManager.isLowRamDeviceStatic()) {
+            if (isLowRamDevice()) {
                 MAX_HISTORY_FILES = DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE;
                 MAX_HISTORY_BUFFER = DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB * 1024;
             } else {
@@ -15528,12 +15542,10 @@
                         KEY_PROC_STATE_CHANGE_COLLECTION_DELAY_MS,
                         DEFAULT_PROC_STATE_CHANGE_COLLECTION_DELAY_MS);
                 MAX_HISTORY_FILES = mParser.getInt(KEY_MAX_HISTORY_FILES,
-                        ActivityManager.isLowRamDeviceStatic() ?
-                                DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE
-                        : DEFAULT_MAX_HISTORY_FILES);
+                        isLowRamDevice() ? DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE
+                                : DEFAULT_MAX_HISTORY_FILES);
                 MAX_HISTORY_BUFFER = mParser.getInt(KEY_MAX_HISTORY_BUFFER_KB,
-                        ActivityManager.isLowRamDeviceStatic() ?
-                                DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB
+                        isLowRamDevice() ? DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB
                                 : DEFAULT_MAX_HISTORY_BUFFER_KB)
                         * 1024;
                 final String perUidModemModel = mParser.getString(KEY_PER_UID_MODEM_POWER_MODEL,
@@ -15692,23 +15704,25 @@
             }
         }
 
-        updateSystemServiceCallStats();
-        if (mBinderThreadCpuTimesUs != null) {
-            pw.println("Per UID System server binder time in ms:");
-            long[] systemServiceTimeAtCpuSpeeds = getSystemServiceTimeAtCpuSpeeds();
-            for (int i = 0; i < size; i++) {
-                int u = mUidStats.keyAt(i);
-                Uid uid = mUidStats.get(u);
-                double proportionalSystemServiceUsage = uid.getProportionalSystemServiceUsage();
-                long timeUs = 0;
-                for (int j = systemServiceTimeAtCpuSpeeds.length - 1; j >= 0; j--) {
-                    timeUs += systemServiceTimeAtCpuSpeeds[j] * proportionalSystemServiceUsage;
-                }
+        if (!Flags.disableSystemServicePowerAttr()) {
+            updateSystemServiceCallStats();
+            if (mBinderThreadCpuTimesUs != null) {
+                pw.println("Per UID System server binder time in ms:");
+                long[] systemServiceTimeAtCpuSpeeds = getSystemServiceTimeAtCpuSpeeds();
+                for (int i = 0; i < size; i++) {
+                    int u = mUidStats.keyAt(i);
+                    Uid uid = mUidStats.get(u);
+                    double proportionalSystemServiceUsage = uid.getProportionalSystemServiceUsage();
+                    long timeUs = 0;
+                    for (int j = systemServiceTimeAtCpuSpeeds.length - 1; j >= 0; j--) {
+                        timeUs += systemServiceTimeAtCpuSpeeds[j] * proportionalSystemServiceUsage;
+                    }
 
-                pw.print("  ");
-                pw.print(u);
-                pw.print(": ");
-                pw.println(timeUs / 1000);
+                    pw.print("  ");
+                    pw.print(u);
+                    pw.print(": ");
+                    pw.println(timeUs / 1000);
+                }
             }
         }
     }
@@ -16014,7 +16028,7 @@
         mDeviceLightIdlingTimer.readSummaryFromParcelLocked(in);
         mDeviceIdlingTimer.readSummaryFromParcelLocked(in);
         mPhoneOnTimer.readSummaryFromParcelLocked(in);
-        for (int i = 0; i < CellSignalStrength.getNumSignalStrengthLevels(); i++) {
+        for (int i = 0; i < CELL_SIGNAL_STRENGTH_LEVEL_COUNT; i++) {
             mPhoneSignalStrengthsTimer[i].readSummaryFromParcelLocked(in);
         }
         mPhoneSignalScanningTimer.readSummaryFromParcelLocked(in);
@@ -16424,8 +16438,10 @@
             }
         }
 
-        mBinderThreadCpuTimesUs =
-                LongSamplingCounterArray.readSummaryFromParcelLocked(in, mOnBatteryTimeBase);
+        if (!Flags.disableSystemServicePowerAttr()) {
+            mBinderThreadCpuTimesUs =
+                    LongSamplingCounterArray.readSummaryFromParcelLocked(in, mOnBatteryTimeBase);
+        }
     }
 
     /**
@@ -16520,7 +16536,7 @@
         mDeviceLightIdlingTimer.writeSummaryFromParcelLocked(out, nowRealtime);
         mDeviceIdlingTimer.writeSummaryFromParcelLocked(out, nowRealtime);
         mPhoneOnTimer.writeSummaryFromParcelLocked(out, nowRealtime);
-        for (int i = 0; i < CellSignalStrength.getNumSignalStrengthLevels(); i++) {
+        for (int i = 0; i < CELL_SIGNAL_STRENGTH_LEVEL_COUNT; i++) {
             mPhoneSignalStrengthsTimer[i].writeSummaryFromParcelLocked(out, nowRealtime);
         }
         mPhoneSignalScanningTimer.writeSummaryFromParcelLocked(out, nowRealtime);
@@ -16969,7 +16985,9 @@
             }
         }
 
-        LongSamplingCounterArray.writeSummaryToParcelLocked(out, mBinderThreadCpuTimesUs);
+        if (!Flags.disableSystemServicePowerAttr()) {
+            LongSamplingCounterArray.writeSummaryToParcelLocked(out, mBinderThreadCpuTimesUs);
+        }
     }
 
     @GuardedBy("this")
@@ -16981,7 +16999,9 @@
         // if we had originally pulled a time before the RTC was set.
         getStartClockTime();
 
-        updateSystemServiceCallStats();
+        if (!Flags.disableSystemServicePowerAttr()) {
+            updateSystemServiceCallStats();
+        }
     }
 
     @GuardedBy("this")
@@ -17015,7 +17035,7 @@
             mDeviceIdlingTimer.logState(pr, "  ");
             pr.println("*** Phone timer:");
             mPhoneOnTimer.logState(pr, "  ");
-            for (int i = 0; i < CellSignalStrength.getNumSignalStrengthLevels(); i++) {
+            for (int i = 0; i < CELL_SIGNAL_STRENGTH_LEVEL_COUNT; i++) {
                 pr.println("*** Phone signal strength #" + i + ":");
                 mPhoneSignalStrengthsTimer[i].logState(pr, "  ");
             }
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index 32656b1..30b80ae 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -96,11 +96,13 @@
                 mPowerCalculators.add(new CustomEnergyConsumerPowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new UserPowerCalculator());
 
-                // It is important that SystemServicePowerCalculator be applied last,
-                // because it re-attributes some of the power estimated by the other
-                // calculators.
-                mPowerCalculators.add(
-                        new SystemServicePowerCalculator(mCpuScalingPolicies, mPowerProfile));
+                if (!com.android.server.power.optimization.Flags.disableSystemServicePowerAttr()) {
+                    // It is important that SystemServicePowerCalculator be applied last,
+                    // because it re-attributes some of the power estimated by the other
+                    // calculators.
+                    mPowerCalculators.add(
+                            new SystemServicePowerCalculator(mCpuScalingPolicies, mPowerProfile));
+                }
             }
         }
         return mPowerCalculators;
@@ -170,7 +172,11 @@
         final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold();
 
         final BatteryUsageStats.Builder batteryUsageStatsBuilder;
+        long monotonicStartTime, monotonicEndTime;
         synchronized (stats) {
+            monotonicStartTime = stats.getMonotonicStartTime();
+            monotonicEndTime = stats.getMonotonicEndTime();
+
             batteryUsageStatsBuilder = new BatteryUsageStats.Builder(
                     stats.getCustomEnergyConsumerNames(), includePowerModels,
                     includeProcessStateData, minConsumedPowerThreshold);
@@ -195,35 +201,36 @@
                                 UidBatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE,
                                 getProcessForegroundServiceTimeMs(uid, realtimeUs));
             }
-        }
 
-        final int[] powerComponents = query.getPowerComponents();
-        final List<PowerCalculator> powerCalculators = getPowerCalculators();
-        for (int i = 0, count = powerCalculators.size(); i < count; i++) {
-            PowerCalculator powerCalculator = powerCalculators.get(i);
-            if (powerComponents != null) {
-                boolean include = false;
-                for (int powerComponent : powerComponents) {
-                    if (powerCalculator.isPowerComponentSupported(powerComponent)) {
-                        include = true;
-                        break;
+            final int[] powerComponents = query.getPowerComponents();
+            final List<PowerCalculator> powerCalculators = getPowerCalculators();
+            for (int i = 0, count = powerCalculators.size(); i < count; i++) {
+                PowerCalculator powerCalculator = powerCalculators.get(i);
+                if (powerComponents != null) {
+                    boolean include = false;
+                    for (int powerComponent : powerComponents) {
+                        if (powerCalculator.isPowerComponentSupported(powerComponent)) {
+                            include = true;
+                            break;
+                        }
+                    }
+                    if (!include) {
+                        continue;
                     }
                 }
-                if (!include) {
-                    continue;
-                }
+                powerCalculator.calculate(batteryUsageStatsBuilder, stats, realtimeUs, uptimeUs,
+                        query);
             }
-            powerCalculator.calculate(batteryUsageStatsBuilder, stats, realtimeUs, uptimeUs, query);
+
+            if ((query.getFlags()
+                    & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY) != 0) {
+                batteryUsageStatsBuilder.setBatteryHistory(stats.copyHistory());
+            }
         }
 
         if (mPowerStatsExporterEnabled) {
             mPowerStatsExporter.exportAggregatedPowerStats(batteryUsageStatsBuilder,
-                    stats.getMonotonicStartTime(), stats.getMonotonicEndTime());
-        }
-
-        if ((query.getFlags()
-                & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY) != 0) {
-            batteryUsageStatsBuilder.setBatteryHistory(stats.copyHistory());
+                    monotonicStartTime, monotonicEndTime);
         }
 
         BatteryUsageStats batteryUsageStats = batteryUsageStatsBuilder.build();
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
index cd3db36..ba4c127 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
@@ -74,8 +74,7 @@
             boolean clockUpdateAdded = false;
             long baseTime = startTimeMs > 0 ? startTimeMs : UNINITIALIZED;
             long lastTime = 0;
-            try (BatteryStatsHistoryIterator iterator =
-                         mHistory.copy().iterate(startTimeMs, endTimeMs)) {
+            try (BatteryStatsHistoryIterator iterator = mHistory.iterate(startTimeMs, endTimeMs)) {
                 while (iterator.hasNext()) {
                     BatteryStats.HistoryItem item = iterator.next();
 
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index 0f13571..6546646 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -13,3 +13,11 @@
     description: "Feature flag for streamlined battery stats"
     bug: "285646152"
 }
+
+flag {
+    name: "disable_system_service_power_attr"
+    namespace: "backstage_power"
+    description: "Deprecation of system service power re-attribution"
+    bug: "311793616"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index 375ef61..3c0547e 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -65,6 +65,7 @@
 import com.android.internal.widget.RebootEscrowListener;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.Watchdog;
 import com.android.server.pm.ApexManager;
 import com.android.server.recoverysystem.hal.BootControlHIDL;
 
@@ -112,6 +113,9 @@
 
     private static final int SOCKET_CONNECTION_MAX_RETRY = 30;
 
+    /** How long to pause the watchdog for when rebooting the device. */
+    private static final int REBOOT_WATCHDOG_PAUSE_DURATION_MS = 20_000;
+
     static final String REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX = "_request_lskf_timestamp";
     static final String REQUEST_LSKF_COUNT_PREF_SUFFIX = "_request_lskf_count";
 
@@ -523,6 +527,7 @@
         if (DEBUG) Slog.d(TAG, "rebootRecoveryWithCommand: [" + command + "]");
         synchronized (sRequestLock) {
             if (!setupOrClearBcb(true, command)) {
+                Slog.e(TAG, "rebootRecoveryWithCommand failed to setup BCB");
                 return;
             }
 
@@ -899,7 +904,8 @@
 
         // Clear the metrics prefs after a successful RoR reboot.
         mInjector.getMetricsPrefs().deletePrefsFile();
-
+        Watchdog.getInstance().pauseWatchingCurrentThreadFor(
+                REBOOT_WATCHDOG_PAUSE_DURATION_MS, "reboot can be slow");
         PowerManager pm = mInjector.getPowerManager();
         pm.reboot(reason);
         return RESUME_ON_REBOOT_REBOOT_ERROR_UNSPECIFIED;
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java
index 141d4dc..9ee9b14 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java
@@ -48,6 +48,8 @@
                     return isLskfCaptured();
                 case "reboot-and-apply":
                     return rebootAndApply();
+                case "wipe":
+                    return wipe();
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -58,6 +60,18 @@
         }
     }
 
+    private int wipe() throws RemoteException {
+        PrintWriter pw = getOutPrintWriter();
+        String newFsType = getNextArg();
+        String command = "--wipe_data";
+        if (newFsType != null && !newFsType.isEmpty()) {
+            command += "\n--reformat_data=" + newFsType;
+        }
+        pw.println("Rebooting into recovery with " + command.replaceAll("\n", " "));
+        mService.rebootRecoveryWithCommand(command);
+        return 0;
+    }
+
     private int requestLskf() throws RemoteException {
         String packageName = getNextArgRequired();
         boolean success = mService.requestLskf(packageName, null);
@@ -104,5 +118,6 @@
         pw.println("  clear-lskf");
         pw.println("  is-lskf-captured <package_name>");
         pw.println("  reboot-and-apply <package_name> <reason>");
+        pw.println("  wipe <new filesystem type ext4/f2fs>");
     }
 }
diff --git a/services/core/java/com/android/server/rollback/README.md b/services/core/java/com/android/server/rollback/README.md
index 08800da..f6bcbd0 100644
--- a/services/core/java/com/android/server/rollback/README.md
+++ b/services/core/java/com/android/server/rollback/README.md
@@ -187,13 +187,22 @@
 
 ### Installing an App with Rollback Enabled
 
-The `adb install` command accepts the `--enable-rollback` flag to install an app
+The `adb install` command accepts the `--enable-rollback [0/1/2]` flag to install an app
 with rollback enabled. For example:
 
 ```
 $ adb install --enable-rollback FooV2.apk
 ```
 
+The default rollback data policy is `ROLLBACK_DATA_POLICY_RESTORE` (0). To use
+a different `RollbackDataPolicy`, like `ROLLBACK_DATA_POLICY_RETAIN` (1) or
+`ROLLBACK_DATA_POLICY_WIPE` (2), provide the int value after
+`--enable-rollback`. For example:
+
+```
+$ adb install --enable-rollback 1 FooV2.apk
+```
+
 ### Triggering Rollback Manually
 
 If rollback is available for an application, the pm command can be used to
@@ -217,7 +226,7 @@
   -state: committed
   -timestamp: 2019-04-23T14:57:35.944Z
   -packages:
-    com.android.tests.rollback.testapp.B 2 -> 1
+    com.android.tests.rollback.testapp.B 2 -> 1 [0]
   -causePackages:
   -committedSessionId: 1845805640
 649899517:
@@ -225,7 +234,7 @@
   -timestamp: 2019-04-23T12:55:21.342Z
   -stagedSessionId: 343374391
   -packages:
-    com.android.tests.rollback.testapex 2 -> 1
+    com.android.tests.rollback.testapex 2 -> 1 [0]
   -causePackages:
   -committedSessionId: 2096717281
 ```
@@ -233,7 +242,8 @@
 The example above shows two recently committed rollbacks. The update of
 com.android.tests.rollback.testapp.B from version 1 to version 2 was rolled
 back, and the update of com.android.tests.rollback.testapex from version 1 to
-version 2 was rolled back.
+version 2 was rolled back. For each package the value inside '[' and ']'
+indicates the `RollbackDataPolicy` for the rollback back.
 
 The state is 'available' or 'committed'. The timestamp gives the time when the
 rollback was first made available. If a stagedSessionId is present, then the
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index a5b90f1..d1f91c8 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -215,7 +215,8 @@
                 /* packages */ new ArrayList<>(),
                 /* isStaged */ isStaged,
                 /* causePackages */ new ArrayList<>(),
-                /* committedSessionId */ -1);
+                /* committedSessionId */ -1,
+                /* rollbackImpactLevel */ PackageManager.ROLLBACK_USER_IMPACT_LOW);
         mUserId = userId;
         mInstallerPackageName = installerPackageName;
         mBackupDir = backupDir;
@@ -394,7 +395,8 @@
      */
     @WorkerThread
     boolean enableForPackage(String packageName, long newVersion, long installedVersion,
-            boolean isApex, String sourceDir, String[] splitSourceDirs, int rollbackDataPolicy) {
+            boolean isApex, String sourceDir, String[] splitSourceDirs, int rollbackDataPolicy,
+            @PackageManager.RollbackImpactLevel int rollbackImpactLevel) {
         assertInWorkerThread();
         try {
             RollbackStore.backupPackageCodePath(this, packageName, sourceDir);
@@ -415,6 +417,10 @@
                 isApex, false /* isApkInApex */, new ArrayList<>(), rollbackDataPolicy);
 
         info.getPackages().add(packageRollbackInfo);
+
+        if (info.getRollbackImpactLevel() < rollbackImpactLevel) {
+            info.setRollbackImpactLevel(rollbackImpactLevel);
+        }
         return true;
     }
 
@@ -961,7 +967,8 @@
         for (PackageRollbackInfo pkg : info.getPackages()) {
             ipw.println(pkg.getPackageName()
                     + " " + pkg.getVersionRolledBackFrom().getLongVersionCode()
-                    + " -> " + pkg.getVersionRolledBackTo().getLongVersionCode());
+                    + " -> " + pkg.getVersionRolledBackTo().getLongVersionCode()
+                    + " [" + pkg.getRollbackDataPolicy() + "]");
         }
         ipw.decreaseIndent();
         if (isCommitted()) {
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 8d93408..359678b 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -954,7 +954,7 @@
         ApplicationInfo appInfo = pkgInfo.applicationInfo;
         return rollback.enableForPackage(packageName, newPackage.getVersionCode(),
                 pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir,
-                appInfo.splitSourceDirs, rollbackDataPolicy);
+                appInfo.splitSourceDirs, rollbackDataPolicy, session.rollbackImpactLevel);
     }
 
     @ExtThread
@@ -1279,8 +1279,8 @@
                 ipw.println();
             }
 
-            PackageWatchdog.getInstance(mContext).dump(ipw);
         });
+        PackageWatchdog.getInstance(mContext).dump(ipw);
     }
 
     @AnyThread
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index 0af137f..14539d5 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -193,16 +193,27 @@
         json.put("isStaged", rollback.isStaged());
         json.put("causePackages", versionedPackagesToJson(rollback.getCausePackages()));
         json.put("committedSessionId", rollback.getCommittedSessionId());
+        if (Flags.recoverabilityDetection()) {
+            json.put("rollbackImpactLevel", rollback.getRollbackImpactLevel());
+        }
         return json;
     }
 
     private static RollbackInfo rollbackInfoFromJson(JSONObject json) throws JSONException {
-        return new RollbackInfo(
+        RollbackInfo rollbackInfo = new RollbackInfo(
                 json.getInt("rollbackId"),
                 packageRollbackInfosFromJson(json.getJSONArray("packages")),
                 json.getBoolean("isStaged"),
                 versionedPackagesFromJson(json.getJSONArray("causePackages")),
                 json.getInt("committedSessionId"));
+
+        if (Flags.recoverabilityDetection()) {
+                // to make it backward compatible.
+            rollbackInfo.setRollbackImpactLevel(json.optInt("rollbackImpactLevel",
+                    PackageManager.ROLLBACK_USER_IMPACT_LOW));
+        }
+
+        return rollbackInfo;
     }
 
     /**
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
index a49df50..bb4876b 100644
--- a/services/core/java/com/android/server/security/FileIntegrityService.java
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -157,7 +157,7 @@
             Objects.requireNonNull(authFd);
             try {
                 var authToken = getStorageManagerInternal().createFsveritySetupAuthToken(authFd,
-                        Binder.getCallingUid(), Binder.getCallingUserHandle().getIdentifier());
+                        Binder.getCallingUid());
                 // fs-verity setup requires no writable fd to the file. Release the dup now that
                 // it's passed.
                 authFd.close();
diff --git a/services/core/java/com/android/server/selinux/QuotaLimiter.java b/services/core/java/com/android/server/selinux/QuotaLimiter.java
new file mode 100644
index 0000000..e89ddfd
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/QuotaLimiter.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.selinux;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.Clock;
+
+import java.time.Duration;
+import java.time.Instant;
+
+/**
+ * A QuotaLimiter allows to define a maximum number of Atom pushes within a specific time window.
+ *
+ * <p>The limiter divides the time line in windows of a fixed size. Every time a new permit is
+ * requested, the limiter checks whether the previous request was in the same time window as the
+ * current one. If the two windows are the same, it grants a permit only if the number of permits
+ * granted within the window does not exceed the quota. If the two windows are different, it resets
+ * the quota.
+ */
+public class QuotaLimiter {
+
+    private final Clock mClock;
+    private final Duration mWindowSize;
+    private final int mMaxPermits;
+
+    private long mCurrentWindow = 0;
+    private int mPermitsGranted = 0;
+
+    @VisibleForTesting
+    QuotaLimiter(Clock clock, Duration windowSize, int maxPermits) {
+        mClock = clock;
+        mWindowSize = windowSize;
+        mMaxPermits = maxPermits;
+    }
+
+    public QuotaLimiter(Duration windowSize, int maxPermits) {
+        this(Clock.SYSTEM_CLOCK, windowSize, maxPermits);
+    }
+
+    public QuotaLimiter(int maxPermitsPerDay) {
+        this(Clock.SYSTEM_CLOCK, Duration.ofDays(1), maxPermitsPerDay);
+    }
+
+    /**
+     * Acquires a permit if there is one available in the current time window.
+     *
+     * @return true if a permit was acquired.
+     */
+    boolean acquire() {
+        long nowWindow =
+                Duration.between(Instant.EPOCH, Instant.ofEpochMilli(mClock.currentTimeMillis()))
+                        .dividedBy(mWindowSize);
+        if (nowWindow > mCurrentWindow) {
+            mCurrentWindow = nowWindow;
+            mPermitsGranted = 0;
+        }
+
+        if (mPermitsGranted < mMaxPermits) {
+            mPermitsGranted++;
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/services/core/java/com/android/server/selinux/RateLimiter.java b/services/core/java/com/android/server/selinux/RateLimiter.java
new file mode 100644
index 0000000..599b840
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/RateLimiter.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.selinux;
+
+import android.os.SystemClock;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.Clock;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+
+/**
+ * Rate limiter to ensure Atoms are pushed only within the allowed QPS window. This class is not
+ * thread-safe.
+ *
+ * <p>The rate limiter is smoothed, meaning that a rate limiter allowing X permits per second (or X
+ * QPS) will grant permits at a ratio of one every 1/X seconds.
+ */
+public final class RateLimiter {
+
+    private Instant mNextPermit = Instant.EPOCH;
+
+    private final Clock mClock;
+    private final Duration mWindow;
+
+    @VisibleForTesting
+    RateLimiter(Clock clock, Duration window) {
+        mClock = clock;
+        // Truncating because the system clock does not support units smaller than milliseconds.
+        mWindow = window;
+    }
+
+    /**
+     * Create a rate limiter generating one permit every {@code window} of time, using the {@link
+     * Clock.SYSTEM_CLOCK}.
+     */
+    public RateLimiter(Duration window) {
+        this(Clock.SYSTEM_CLOCK, window);
+    }
+
+    /**
+     * Acquire a permit if allowed by the rate limiter. If not, wait until a permit becomes
+     * available.
+     */
+    public void acquire() {
+        Instant now = Instant.ofEpochMilli(mClock.currentTimeMillis());
+
+        if (mNextPermit.isAfter(now)) { // Sleep until we can acquire.
+            SystemClock.sleep(ChronoUnit.MILLIS.between(now, mNextPermit));
+            mNextPermit = mNextPermit.plus(mWindow);
+        } else {
+            mNextPermit = now.plus(mWindow);
+        }
+    }
+
+    /**
+     * Try to acquire a permit if allowed by the rate limiter. Non-blocking.
+     *
+     * @return true if a permit was acquired. Otherwise, return false.
+     */
+    public boolean tryAcquire() {
+        final Instant now = Instant.ofEpochMilli(mClock.currentTimeMillis());
+
+        if (mNextPermit.isAfter(now)) {
+            return false;
+        }
+        mNextPermit = now.plus(mWindow);
+        return true;
+    }
+}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java
new file mode 100644
index 0000000..8d8d596
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.selinux;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+/** Builder for SelinuxAuditLogs. */
+class SelinuxAuditLogBuilder {
+
+    // Currently logs collection is hardcoded for the sdk_sandbox_audit.
+    private static final String SDK_SANDBOX_AUDIT = "sdk_sandbox_audit";
+    static final Matcher SCONTEXT_MATCHER =
+            Pattern.compile(
+                            "u:r:(?<stype>"
+                                    + SDK_SANDBOX_AUDIT
+                                    + "):s0(:c)?(?<scategories>((,c)?\\d+)+)*")
+                    .matcher("");
+
+    static final Matcher TCONTEXT_MATCHER =
+            Pattern.compile("u:object_r:(?<ttype>\\w+):s0(:c)?(?<tcategories>((,c)?\\d+)+)*")
+                    .matcher("");
+
+    static final Matcher PATH_MATCHER =
+            Pattern.compile("\"(?<path>/\\w+(/\\w+)?)(/\\w+)*\"").matcher("");
+
+    private Iterator<String> mTokens;
+    private final SelinuxAuditLog mAuditLog = new SelinuxAuditLog();
+
+    void reset(String denialString) {
+        mTokens =
+                Arrays.asList(
+                                Optional.ofNullable(denialString)
+                                        .map(s -> s.split("\\s+|="))
+                                        .orElse(new String[0]))
+                        .iterator();
+        mAuditLog.reset();
+    }
+
+    SelinuxAuditLog build() {
+        while (mTokens.hasNext()) {
+            final String token = mTokens.next();
+
+            switch (token) {
+                case "granted":
+                    mAuditLog.mGranted = true;
+                    break;
+                case "denied":
+                    mAuditLog.mGranted = false;
+                    break;
+                case "{":
+                    Stream.Builder<String> permissionsStream = Stream.builder();
+                    boolean closed = false;
+                    while (!closed && mTokens.hasNext()) {
+                        String permission = mTokens.next();
+                        if ("}".equals(permission)) {
+                            closed = true;
+                        } else {
+                            permissionsStream.add(permission);
+                        }
+                    }
+                    if (!closed) {
+                        return null;
+                    }
+                    mAuditLog.mPermissions = permissionsStream.build().toArray(String[]::new);
+                    break;
+                case "scontext":
+                    if (!nextTokenMatches(SCONTEXT_MATCHER)) {
+                        return null;
+                    }
+                    mAuditLog.mSType = SCONTEXT_MATCHER.group("stype");
+                    mAuditLog.mSCategories = toCategories(SCONTEXT_MATCHER.group("scategories"));
+                    break;
+                case "tcontext":
+                    if (!nextTokenMatches(TCONTEXT_MATCHER)) {
+                        return null;
+                    }
+                    mAuditLog.mTType = TCONTEXT_MATCHER.group("ttype");
+                    mAuditLog.mTCategories = toCategories(TCONTEXT_MATCHER.group("tcategories"));
+                    break;
+                case "tclass":
+                    if (!mTokens.hasNext()) {
+                        return null;
+                    }
+                    mAuditLog.mTClass = mTokens.next();
+                    break;
+                case "path":
+                    if (nextTokenMatches(PATH_MATCHER)) {
+                        mAuditLog.mPath = PATH_MATCHER.group("path");
+                    }
+                    break;
+                case "permissive":
+                    if (!mTokens.hasNext()) {
+                        return null;
+                    }
+                    mAuditLog.mPermissive = "1".equals(mTokens.next());
+                    break;
+                default:
+                    break;
+            }
+        }
+        return mAuditLog;
+    }
+
+    boolean nextTokenMatches(Matcher matcher) {
+        return mTokens.hasNext() && matcher.reset(mTokens.next()).matches();
+    }
+
+    static int[] toCategories(String categories) {
+        return categories == null
+                ? null
+                : Arrays.stream(categories.split(",c")).mapToInt(Integer::parseInt).toArray();
+    }
+
+    static class SelinuxAuditLog {
+        boolean mGranted = false;
+        String[] mPermissions = null;
+        String mSType = null;
+        int[] mSCategories = null;
+        String mTType = null;
+        int[] mTCategories = null;
+        String mTClass = null;
+        String mPath = null;
+        boolean mPermissive = false;
+
+        private void reset() {
+            mGranted = false;
+            mPermissions = null;
+            mSType = null;
+            mSCategories = null;
+            mTType = null;
+            mTCategories = null;
+            mTClass = null;
+            mPath = null;
+            mPermissive = false;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
new file mode 100644
index 0000000..0219645
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.selinux;
+
+import android.util.EventLog;
+import android.util.EventLog.Event;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.selinux.SelinuxAuditLogBuilder.SelinuxAuditLog;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Class in charge of collecting SELinux audit logs and push the SELinux atoms. */
+class SelinuxAuditLogsCollector {
+
+    private static final String TAG = "SelinuxAuditLogs";
+
+    private static final String SELINUX_PATTERN = "^.*\\bavc:\\s+(?<denial>.*)$";
+
+    @VisibleForTesting
+    static final Matcher SELINUX_MATCHER = Pattern.compile(SELINUX_PATTERN).matcher("");
+
+    private final RateLimiter mRateLimiter;
+    private final QuotaLimiter mQuotaLimiter;
+
+    @VisibleForTesting Instant mLastWrite = Instant.MIN;
+
+    final AtomicBoolean mStopRequested = new AtomicBoolean(false);
+
+    SelinuxAuditLogsCollector(RateLimiter rateLimiter, QuotaLimiter quotaLimiter) {
+        mRateLimiter = rateLimiter;
+        mQuotaLimiter = quotaLimiter;
+    }
+
+    /**
+     * Collect and push SELinux audit logs for the provided {@code tagCode}.
+     *
+     * @return true if the job was completed. If the job was interrupted, return false.
+     */
+    boolean collect(int tagCode) {
+        Queue<Event> logLines = new ArrayDeque<>();
+        Instant latestTimestamp = collectLogLines(tagCode, logLines);
+
+        boolean quotaExceeded = writeAuditLogs(logLines);
+        if (quotaExceeded) {
+            Log.w(TAG, "Too many SELinux logs in the queue, I am giving up.");
+            mLastWrite = latestTimestamp; // next run we will ignore all these logs.
+            logLines.clear();
+        }
+
+        return logLines.isEmpty();
+    }
+
+    private Instant collectLogLines(int tagCode, Queue<Event> logLines) {
+        List<Event> events = new ArrayList<>();
+        try {
+            EventLog.readEvents(new int[] {tagCode}, events);
+        } catch (IOException e) {
+            Log.e(TAG, "Error reading event logs", e);
+        }
+
+        Instant latestTimestamp = mLastWrite;
+        for (Event event : events) {
+            Instant eventTime = Instant.ofEpochSecond(0, event.getTimeNanos());
+            if (eventTime.isAfter(latestTimestamp)) {
+                latestTimestamp = eventTime;
+            }
+            if (eventTime.isBefore(mLastWrite)) {
+                continue;
+            }
+            Object eventData = event.getData();
+            if (!(eventData instanceof String)) {
+                continue;
+            }
+            logLines.add(event);
+        }
+        return latestTimestamp;
+    }
+
+    private boolean writeAuditLogs(Queue<Event> logLines) {
+        final SelinuxAuditLogBuilder auditLogBuilder = new SelinuxAuditLogBuilder();
+
+        while (!mStopRequested.get() && !logLines.isEmpty()) {
+            Event event = logLines.poll();
+            String logLine = (String) event.getData();
+            Instant logTime = Instant.ofEpochSecond(0, event.getTimeNanos());
+            if (!SELINUX_MATCHER.reset(logLine).matches()) {
+                continue;
+            }
+
+            auditLogBuilder.reset(SELINUX_MATCHER.group("denial"));
+            final SelinuxAuditLog auditLog = auditLogBuilder.build();
+            if (auditLog == null) {
+                continue;
+            }
+
+            if (!mQuotaLimiter.acquire()) {
+                return true;
+            }
+            mRateLimiter.acquire();
+
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                    auditLog.mGranted,
+                    auditLog.mPermissions,
+                    auditLog.mSType,
+                    auditLog.mSCategories,
+                    auditLog.mTType,
+                    auditLog.mTCategories,
+                    auditLog.mTClass,
+                    auditLog.mPath,
+                    auditLog.mPermissive);
+
+            if (logTime.isAfter(mLastWrite)) {
+                mLastWrite = logTime;
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
new file mode 100644
index 0000000..8a661bc
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.selinux;
+
+import static com.android.sdksandbox.flags.Flags.selinuxSdkSandboxAudit;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.EventLog;
+import android.util.Log;
+
+import java.time.Duration;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Scheduled jobs related to logging of SELinux denials and audits. The job runs daily on idle
+ * devices.
+ */
+public class SelinuxAuditLogsService extends JobService {
+
+    private static final String TAG = "SelinuxAuditLogs";
+    private static final String SELINUX_AUDIT_NAMESPACE = "SelinuxAuditLogsNamespace";
+
+    static final int AUDITD_TAG_CODE = EventLog.getTagCode("auditd");
+
+    private static final int SELINUX_AUDIT_JOB_ID = 25327386;
+    private static final JobInfo SELINUX_AUDIT_JOB =
+            new JobInfo.Builder(
+                            SELINUX_AUDIT_JOB_ID,
+                            new ComponentName("android", SelinuxAuditLogsService.class.getName()))
+                    .setPeriodic(TimeUnit.DAYS.toMillis(1))
+                    .setRequiresDeviceIdle(true)
+                    .setRequiresCharging(true)
+                    .setRequiresBatteryNotLow(true)
+                    .build();
+
+    private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
+    private static final AtomicReference<Boolean> IS_RUNNING = new AtomicReference<>(false);
+
+    // Audit logging is subject to both rate and quota limiting. We can only push one atom every 10
+    // milliseconds, and no more than 50K atoms can be pushed each day.
+    private static final SelinuxAuditLogsCollector AUDIT_LOGS_COLLECTOR =
+            new SelinuxAuditLogsCollector(
+                    new RateLimiter(/* window= */ Duration.ofMillis(10)),
+                    new QuotaLimiter(/* maxPermitsPerDay= */ 50000));
+
+    /** Schedule jobs with the {@link JobScheduler}. */
+    public static void schedule(Context context) {
+        if (!selinuxSdkSandboxAudit()) {
+            Log.d(TAG, "SelinuxAuditLogsService not enabled");
+            return;
+        }
+
+        if (AUDITD_TAG_CODE == -1) {
+            Log.e(TAG, "auditd is not a registered tag on this system");
+            return;
+        }
+
+        if (context.getSystemService(JobScheduler.class)
+                        .forNamespace(SELINUX_AUDIT_NAMESPACE)
+                        .schedule(SELINUX_AUDIT_JOB)
+                == JobScheduler.RESULT_FAILURE) {
+            Log.e(TAG, "SelinuxAuditLogsService could not be started.");
+        }
+    }
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        if (params.getJobId() != SELINUX_AUDIT_JOB_ID) {
+            Log.e(TAG, "The job id does not match the expected selinux job id.");
+            return false;
+        }
+
+        AUDIT_LOGS_COLLECTOR.mStopRequested.set(false);
+        IS_RUNNING.set(true);
+        EXECUTOR_SERVICE.execute(new LogsCollectorJob(this, params));
+
+        return true; // the job is running
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        if (params.getJobId() != SELINUX_AUDIT_JOB_ID) {
+            return false;
+        }
+
+        AUDIT_LOGS_COLLECTOR.mStopRequested.set(true);
+        return IS_RUNNING.get();
+    }
+
+    private static class LogsCollectorJob implements Runnable {
+        private final JobService mAuditLogService;
+        private final JobParameters mParams;
+
+        LogsCollectorJob(JobService auditLogService, JobParameters params) {
+            mAuditLogService = auditLogService;
+            mParams = params;
+        }
+
+        @Override
+        public void run() {
+            IS_RUNNING.updateAndGet(
+                    isRunning -> {
+                        boolean done = AUDIT_LOGS_COLLECTOR.collect(AUDITD_TAG_CODE);
+                        if (done) {
+                            mAuditLogService.jobFinished(mParams, /* wantsReschedule= */ false);
+                        }
+                        return !done;
+                    });
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java b/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
index 6677e7e..1526230 100644
--- a/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
+++ b/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
@@ -36,10 +36,12 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
+import android.location.flags.Flags;
 import android.os.Bundle;
 import android.os.Process;
 import android.os.UserHandle;
 import android.util.Log;
+import android.util.TypedValue;
 
 import com.android.internal.util.Preconditions;
 import com.android.server.FgThread;
@@ -70,6 +72,10 @@
     private static final String EXTRA_SERVICE_VERSION = "serviceVersion";
     private static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
 
+    // a package value that will never match against any package (we can't use null since this will
+    // match against any package).
+    private static final String NO_MATCH_PACKAGE = "";
+
     private static final Comparator<BoundServiceInfo> sBoundServiceInfoComparator = (o1, o2) -> {
         if (o1 == o2) {
             return 0;
@@ -196,7 +202,19 @@
         Resources resources = context.getResources();
         boolean enableOverlay = resources.getBoolean(enableOverlayResId);
         if (!enableOverlay) {
-            return resources.getString(nonOverlayPackageResId);
+            if (Flags.fixServiceWatcher()) {
+                // we don't use getText() or similar because it won't return null values
+                TypedValue out = new TypedValue();
+                resources.getValue(nonOverlayPackageResId, out, true);
+                CharSequence explicitPackage = out.coerceToString();
+                if (explicitPackage == null) {
+                    return NO_MATCH_PACKAGE;
+                } else {
+                    return explicitPackage.toString();
+                }
+            } else {
+                return resources.getString(nonOverlayPackageResId);
+            }
         } else {
             return null;
         }
@@ -233,6 +251,10 @@
 
     @Override
     public boolean hasMatchingService() {
+        if (Flags.fixServiceWatcher() && NO_MATCH_PACKAGE.equals(mIntent.getPackage())) {
+            return false;
+        }
+
         int intentQueryFlags = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
         if (mMatchSystemAppsOnly) {
             intentQueryFlags |= MATCH_SYSTEM_ONLY;
@@ -268,6 +290,10 @@
 
     @Override
     public BoundServiceInfo getServiceInfo() {
+        if (Flags.fixServiceWatcher() && NO_MATCH_PACKAGE.equals(mIntent.getPackage())) {
+            return null;
+        }
+
         BoundServiceInfo bestServiceInfo = null;
 
         // only allow services in the correct direct boot state to match
diff --git a/services/core/java/com/android/server/stats/Android.bp b/services/core/java/com/android/server/stats/Android.bp
new file mode 100644
index 0000000..e597c3a
--- /dev/null
+++ b/services/core/java/com/android/server/stats/Android.bp
@@ -0,0 +1,12 @@
+aconfig_declarations {
+    name: "stats_flags",
+    package: "com.android.server.stats",
+    srcs: [
+        "stats_flags.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "stats_flags_lib",
+    aconfig_declarations: "stats_flags",
+}
diff --git a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
new file mode 100644
index 0000000..0de73a5
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.stats.pull;
+
+import android.app.ActivityManager;
+import android.app.StatsManager;
+import android.app.usage.NetworkStatsManager;
+import android.net.NetworkStats;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Trace;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.util.StatsEvent;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Aggregates Mobile Data Usage by process state per uid
+ */
+class AggregatedMobileDataStatsPuller {
+    private static final String TAG = "AggregatedMobileDataStatsPuller";
+
+    private static final boolean DEBUG = false;
+
+    private static class UidProcState {
+
+        private final int mUid;
+        private final int mState;
+
+        UidProcState(int uid, int state) {
+            mUid = uid;
+            mState = state;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof UidProcState key)) return false;
+            return mUid == key.mUid && mState == key.mState;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = mUid;
+            result = 31 * result + mState;
+            return result;
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
+        public int getState() {
+            return mState;
+        }
+
+    }
+
+    private static class MobileDataStats {
+        private long mRxPackets = 0;
+        private long mTxPackets = 0;
+        private long mRxBytes = 0;
+        private long mTxBytes = 0;
+
+        public long getRxPackets() {
+            return mRxPackets;
+        }
+
+        public long getTxPackets() {
+            return mTxPackets;
+        }
+
+        public long getRxBytes() {
+            return mRxBytes;
+        }
+
+        public long getTxBytes() {
+            return mTxBytes;
+        }
+
+        public void addRxPackets(long rxPackets) {
+            mRxPackets += rxPackets;
+        }
+
+        public void addTxPackets(long txPackets) {
+            mTxPackets += txPackets;
+        }
+
+        public void addRxBytes(long rxBytes) {
+            mRxBytes += rxBytes;
+        }
+
+        public void addTxBytes(long txBytes) {
+            mTxBytes += txBytes;
+        }
+
+        public boolean isEmpty() {
+            return mRxPackets == 0 && mTxPackets == 0 && mRxBytes == 0 && mTxBytes == 0;
+        }
+    }
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private final Map<UidProcState, MobileDataStats> mUidStats;
+
+    private final SparseIntArray mUidPreviousState;
+
+    private NetworkStats mLastMobileUidStats = new NetworkStats(0, -1);
+
+    private final NetworkStatsManager mNetworkStatsManager;
+
+    private final Handler mMobileDataStatsHandler;
+
+    AggregatedMobileDataStatsPuller(NetworkStatsManager networkStatsManager) {
+        mUidStats = new ArrayMap<>();
+        mUidPreviousState = new SparseIntArray();
+
+        mNetworkStatsManager = networkStatsManager;
+
+        if (mNetworkStatsManager != null) {
+            updateNetworkStats(mNetworkStatsManager);
+        }
+
+        HandlerThread mMobileDataStatsHandlerThread = new HandlerThread("MobileDataStatsHandler");
+        mMobileDataStatsHandlerThread.start();
+        mMobileDataStatsHandler = new Handler(mMobileDataStatsHandlerThread.getLooper());
+    }
+
+    public void noteUidProcessState(int uid, int state, long unusedElapsedRealtime,
+                                    long unusedUptime) {
+        mMobileDataStatsHandler.post(
+                () -> {
+                    noteUidProcessStateImpl(uid, state);
+                });
+    }
+
+    public int pullDataBytesTransfer(List<StatsEvent> data) {
+        synchronized (mLock) {
+            return pullDataBytesTransferLocked(data);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private MobileDataStats getUidStatsForPreviousStateLocked(int uid) {
+        final int previousState = mUidPreviousState.get(uid, ActivityManager.PROCESS_STATE_UNKNOWN);
+        if (DEBUG && previousState == ActivityManager.PROCESS_STATE_UNKNOWN) {
+            Slog.d(TAG, "getUidStatsForPreviousStateLocked() no prev state info for uid "
+                    + uid + ". Tracking stats with ActivityManager.PROCESS_STATE_UNKNOWN");
+        }
+
+        final UidProcState statsKey = new UidProcState(uid, previousState);
+        MobileDataStats stats;
+        if (mUidStats.containsKey(statsKey)) {
+            stats = mUidStats.get(statsKey);
+        } else {
+            stats = new MobileDataStats();
+            mUidStats.put(statsKey, stats);
+        }
+        return stats;
+    }
+
+    private void noteUidProcessStateImpl(int uid, int state) {
+        // noteUidProcessStateLocked can be called back to back several times while
+        // the updateNetworkStatsLocked loops over several stats for multiple uids
+        // and during the first call in a batch of proc state change event it can
+        // contain info for uid with unknown previous state yet which can happen due to a few
+        // reasons:
+        // - app was just started
+        // - app was started before the ActivityManagerService
+        // as result stats would be created with state == ActivityManager.PROCESS_STATE_UNKNOWN
+        if (mNetworkStatsManager != null) {
+            updateNetworkStats(mNetworkStatsManager);
+        } else {
+            Slog.w(TAG, "noteUidProcessStateLocked() can not get mNetworkStatsManager");
+        }
+        mUidPreviousState.put(uid, state);
+    }
+
+    private void updateNetworkStats(NetworkStatsManager networkStatsManager) {
+        if (DEBUG) {
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+                Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-updateNetworkStats");
+            }
+        }
+
+        final NetworkStats latestStats = networkStatsManager.getMobileUidStats();
+        if (isEmpty(latestStats)) {
+            if (DEBUG) {
+                Slog.w(TAG, "getMobileUidStats() failed");
+                Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+            }
+            return;
+        }
+        NetworkStats delta = latestStats.subtract(mLastMobileUidStats);
+        mLastMobileUidStats = latestStats;
+
+        if (!isEmpty(delta)) {
+            updateNetworkStatsDelta(delta);
+        } else if (DEBUG) {
+            Slog.w(TAG, "updateNetworkStats() no delta");
+        }
+        if (DEBUG) {
+            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+        }
+    }
+
+    private void updateNetworkStatsDelta(NetworkStats delta) {
+        synchronized (mLock) {
+            for (NetworkStats.Entry entry : delta) {
+                if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) {
+                    continue;
+                }
+                MobileDataStats stats = getUidStatsForPreviousStateLocked(entry.getUid());
+                stats.addTxBytes(entry.getTxBytes());
+                stats.addRxBytes(entry.getRxBytes());
+                stats.addTxPackets(entry.getTxPackets());
+                stats.addRxPackets(entry.getRxPackets());
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private int pullDataBytesTransferLocked(List<StatsEvent> pulledData) {
+        if (DEBUG) {
+            Slog.d(TAG, "pullDataBytesTransferLocked() start");
+        }
+        for (Map.Entry<UidProcState, MobileDataStats> uidStats : mUidStats.entrySet()) {
+            if (!uidStats.getValue().isEmpty()) {
+                MobileDataStats stats = uidStats.getValue();
+                pulledData.add(FrameworkStatsLog.buildStatsEvent(
+                        FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_PROC_STATE,
+                        uidStats.getKey().getUid(),
+                        ActivityManager.processStateAmToProto(uidStats.getKey().getState()),
+                        stats.getRxBytes(),
+                        stats.getRxPackets(),
+                        stats.getTxBytes(),
+                        stats.getTxPackets()));
+            }
+        }
+        if (DEBUG) {
+            Slog.d(TAG,
+                    "pullDataBytesTransferLocked() done. results count " + pulledData.size());
+        }
+        if (!pulledData.isEmpty()) {
+            return StatsManager.PULL_SUCCESS;
+        }
+        return StatsManager.PULL_SKIP;
+    }
+
+    private static boolean isEmpty(NetworkStats stats) {
+        long totalRxPackets = 0;
+        long totalTxPackets = 0;
+        for (NetworkStats.Entry entry : stats) {
+            if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) {
+                continue;
+            }
+            totalRxPackets += entry.getRxPackets();
+            totalTxPackets += entry.getTxPackets();
+            // at least one non empty entry located
+            break;
+        }
+        final long totalPackets = totalRxPackets + totalTxPackets;
+        return totalPackets == 0;
+    }
+}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index e876241..0ffd002 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -59,6 +59,7 @@
 import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__TELEPHONY;
 import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN;
 import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
+import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller;
 import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
 import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
 import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines;
@@ -409,6 +410,15 @@
     @GuardedBy("mKeystoreLock")
     private IKeystoreMetrics mIKeystoreMetrics;
 
+    private AggregatedMobileDataStatsPuller mAggregatedMobileDataStatsPuller = null;
+
+    /**
+     * Whether or not to enable the new puller with aggregation by process state per uid on a
+     * system server side.
+     */
+    public static final boolean ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER =
+                addMobileBytesTransferByProcStatePuller();
+
     // Puller locks
     private final Object mDataBytesTransferLock = new Object();
     private final Object mBluetoothBytesTransferLock = new Object();
@@ -469,6 +479,20 @@
         mContext = context;
     }
 
+    private final class StatsPullAtomServiceInternalImpl extends StatsPullAtomServiceInternal {
+
+        @Override
+        public void noteUidProcessState(int uid, int state) {
+            if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER
+                    && mAggregatedMobileDataStatsPuller != null) {
+                final long elapsedRealtime = SystemClock.elapsedRealtime();
+                final long uptime = SystemClock.uptimeMillis();
+                mAggregatedMobileDataStatsPuller.noteUidProcessState(uid, state, elapsedRealtime,
+                        uptime);
+            }
+        }
+    }
+
     private native void initializeNativePullers();
 
     /**
@@ -486,6 +510,11 @@
             }
             try {
                 switch (atomTag) {
+                    case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_PROC_STATE:
+                        if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER
+                                && mAggregatedMobileDataStatsPuller != null) {
+                            return mAggregatedMobileDataStatsPuller.pullDataBytesTransfer(data);
+                        }
                     case FrameworkStatsLog.WIFI_BYTES_TRANSFER:
                     case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG:
                     case FrameworkStatsLog.MOBILE_BYTES_TRANSFER:
@@ -776,7 +805,10 @@
 
     @Override
     public void onStart() {
-        // no op
+        if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) {
+            LocalServices.addService(StatsPullAtomServiceInternal.class,
+                    new StatsPullAtomServiceInternalImpl());
+        }
     }
 
     @Override
@@ -811,6 +843,9 @@
         mStatsSubscriptionsListener = new StatsSubscriptionsListener(mSubscriptionManager);
         mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class);
         mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class);
+
+        initMobileDataStatsPuller();
+
         // Initialize DiskIO
         mStoragedUidIoStatsReader = new StoragedUidIoStatsReader();
 
@@ -972,6 +1007,18 @@
         registerCachedAppsHighWatermarkPuller();
     }
 
+    private void initMobileDataStatsPuller() {
+        if (DEBUG) {
+            Slog.d(TAG,
+                    "ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER = "
+                            + ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER);
+        }
+        if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) {
+            mAggregatedMobileDataStatsPuller =
+                    new AggregatedMobileDataStatsPuller(mNetworkStatsManager);
+        }
+    }
+
     private void initAndRegisterNetworkStatsPullers() {
         if (DEBUG) {
             Slog.d(TAG, "Registering NetworkStats pullers with statsd");
@@ -1013,6 +1060,9 @@
         registerWifiBytesTransferBackground();
         registerMobileBytesTransfer();
         registerMobileBytesTransferBackground();
+        if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) {
+            registerMobileBytesTransferByProcState();
+        }
         registerBytesTransferByTagAndMetered();
         registerDataUsageBytesTransfer();
         registerOemManagedBytesTransfer();
@@ -1021,6 +1071,13 @@
         }
     }
 
+    private void registerMobileBytesTransferByProcState() {
+        final int tagId = FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_PROC_STATE;
+        PullAtomMetadata metadata =
+                new PullAtomMetadata.Builder().setAdditiveFields(new int[] {3, 4, 5, 6}).build();
+        mStatsManager.setPullAtomCallback(tagId, metadata, DIRECT_EXECUTOR, mStatsCallbackImpl);
+    }
+
     private void initAndRegisterDeferredPullers() {
         mUwbManager = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB)
             ? mContext.getSystemService(UwbManager.class) : null;
@@ -2001,7 +2058,8 @@
     }
 
     private void registerCpuCyclesPerThreadGroupCluster() {
-        if (KernelCpuBpfTracking.isSupported()) {
+        if (KernelCpuBpfTracking.isSupported()
+                && !com.android.server.power.optimization.Flags.disableSystemServicePowerAttr()) {
             int tagId = FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER;
             PullAtomMetadata metadata = new PullAtomMetadata.Builder()
                     .setAdditiveFields(new int[]{3, 4})
@@ -2016,6 +2074,10 @@
     }
 
     int pullCpuCyclesPerThreadGroupCluster(int atomTag, List<StatsEvent> pulledData) {
+        if (com.android.server.power.optimization.Flags.disableSystemServicePowerAttr()) {
+            return StatsManager.PULL_SKIP;
+        }
+
         SystemServiceCpuThreadTimes times = LocalServices.getService(BatteryStatsInternal.class)
                 .getSystemServiceCpuThreadTimes();
         if (times == null) {
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomServiceInternal.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomServiceInternal.java
new file mode 100644
index 0000000..06adbfc
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomServiceInternal.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.stats.pull;
+
+/**
+ * System-server internal interface to the {@link StatsPullAtomService}.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class StatsPullAtomServiceInternal {
+
+    /**
+     * @param state Process state from ActivityManager.java.
+     */
+    public abstract void noteUidProcessState(int uid, int state);
+
+}
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
new file mode 100644
index 0000000..5101a69
--- /dev/null
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.server.stats"
+
+flag {
+    name: "add_mobile_bytes_transfer_by_proc_state_puller"
+    namespace: "statsd"
+    description: "Adds mobile_bytes_transfer_by_proc_state atom with system server side aggregation"
+    bug: "309512867"
+    is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 5d716fc..e5a8a6d 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -44,6 +44,9 @@
 import android.graphics.drawable.Drawable;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.biometrics.SensorProperties;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -155,6 +158,8 @@
     private final LockPatternUtils mLockPatternUtils;
     private final UserManager mUserManager;
     private final ActivityManager mActivityManager;
+    private FingerprintManager mFingerprintManager;
+    private FaceManager mFaceManager;
     private VirtualDeviceManagerInternal mVirtualDeviceManager;
 
     private enum TrustState {
@@ -292,6 +297,8 @@
             mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
             mReceiver.register(mContext);
             mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
+            mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
+            mFaceManager = mContext.getSystemService(FaceManager.class);
         } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
             mTrustAgentsCanRun = true;
             refreshAgentList(UserHandle.USER_ALL);
@@ -893,7 +900,19 @@
 
     private void notifyKeystoreOfDeviceLockState(int userId, boolean isLocked) {
         if (isLocked) {
-            Authorization.onDeviceLocked(userId, getBiometricSids(userId));
+            if (android.security.Flags.fixUnlockedDeviceRequiredKeysV2()) {
+                // A profile with unified challenge is unlockable not by its own biometrics and
+                // trust agents, but rather by those of the parent user.  Therefore, when protecting
+                // the profile's UnlockedDeviceRequired keys, we must use the parent's list of
+                // biometric SIDs and weak unlock methods, not the profile's.
+                int authUserId = mLockPatternUtils.isProfileWithUnifiedChallenge(userId)
+                        ? resolveProfileParent(userId) : userId;
+
+                Authorization.onDeviceLocked(userId, getBiometricSids(authUserId),
+                        isWeakUnlockMethodEnabled(authUserId));
+            } else {
+                Authorization.onDeviceLocked(userId, getBiometricSids(userId), false);
+            }
         } else {
             // Notify Keystore that the device is now unlocked for the user.  Note that for unlocks
             // with LSKF, this is redundant with the call from LockSettingsService which provides
@@ -1440,16 +1459,59 @@
         if (biometricManager == null) {
             return new long[0];
         }
-        if (android.security.Flags.fixUnlockedDeviceRequiredKeysV2()
-                && mLockPatternUtils.isProfileWithUnifiedChallenge(userId)) {
-            // Profiles with unified challenge have their own set of biometrics, but the device
-            // unlock happens via the parent user.  In this case Keystore needs to be given the list
-            // of biometric SIDs from the parent user, not the profile.
-            userId = resolveProfileParent(userId);
-        }
         return biometricManager.getAuthenticatorIds(userId);
     }
 
+    // Returns whether the device can become unlocked for the specified user via one of that user's
+    // non-strong biometrics or trust agents.  This assumes that the device is currently locked, or
+    // is becoming locked, for the user.
+    private boolean isWeakUnlockMethodEnabled(int userId) {
+
+        // Check whether the system currently allows the use of non-strong biometrics for the user,
+        // *and* the user actually has a non-strong biometric enrolled.
+        //
+        // The biometrics framework ostensibly supports multiple sensors per modality.  However,
+        // that feature is unused and untested.  So, we simply consider one sensor per modality.
+        //
+        // Also, currently we just consider fingerprint and face, matching Keyguard.  If Keyguard
+        // starts supporting other biometric modalities, this will need to be updated.
+        if (mStrongAuthTracker.isBiometricAllowedForUser(/* isStrongBiometric= */ false, userId)) {
+            DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager();
+            int disabledFeatures = dpm.getKeyguardDisabledFeatures(null, userId);
+
+            if (mFingerprintManager != null
+                    && (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) == 0
+                    && mFingerprintManager.hasEnrolledTemplates(userId)
+                    && isWeakOrConvenienceSensor(
+                            mFingerprintManager.getSensorProperties().get(0))) {
+                Slog.i(TAG, "User is unlockable by non-strong fingerprint auth");
+                return true;
+            }
+
+            if (mFaceManager != null
+                    && (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_FACE) == 0
+                    && mFaceManager.hasEnrolledTemplates(userId)
+                    && isWeakOrConvenienceSensor(mFaceManager.getSensorProperties().get(0))) {
+                Slog.i(TAG, "User is unlockable by non-strong face auth");
+                return true;
+            }
+        }
+
+        // Check whether it's possible for the device to be actively unlocked by a trust agent.
+        if (getUserTrustStateInner(userId) == TrustState.TRUSTABLE
+                || (isAutomotive() && isTrustUsuallyManagedInternal(userId))) {
+            Slog.i(TAG, "User is unlockable by trust agent");
+            return true;
+        }
+
+        return false;
+    }
+
+    private static boolean isWeakOrConvenienceSensor(SensorProperties sensor) {
+        return sensor.getSensorStrength() == SensorProperties.STRENGTH_WEAK
+                || sensor.getSensorStrength() == SensorProperties.STRENGTH_CONVENIENCE;
+    }
+
     // User lifecycle
 
     @Override
@@ -1891,7 +1953,8 @@
         };
     }
 
-    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+    @VisibleForTesting
+    final PackageMonitor mPackageMonitor = new PackageMonitor() {
         @Override
         public void onSomePackagesChanged() {
             refreshAgentList(UserHandle.USER_ALL);
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index 58acbe0..92b5764 100755
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -60,6 +60,7 @@
 import android.util.SparseBooleanArray;
 import android.view.Surface;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
@@ -88,15 +89,25 @@
     private final Context mContext;
     private final Listener mListener;
     private final TvInputHal mHal = new TvInputHal(this);
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
     private final SparseArray<Connection> mConnections = new SparseArray<>();
+    @GuardedBy("mLock")
     private final List<TvInputHardwareInfo> mHardwareList = new ArrayList<>();
+    @GuardedBy("mLock")
     private final List<HdmiDeviceInfo> mHdmiDeviceList = new ArrayList<>();
     /* A map from a device ID to the matching TV input ID. */
+    @GuardedBy("mLock")
     private final SparseArray<String> mHardwareInputIdMap = new SparseArray<>();
     /* A map from a HDMI logical address to the matching TV input ID. */
+    @GuardedBy("mLock")
     private final SparseArray<String> mHdmiInputIdMap = new SparseArray<>();
+    @GuardedBy("mLock")
     private final Map<String, TvInputInfo> mInputMap = new ArrayMap<>();
     /* A map from a HDMI input parent ID to the related input IDs. */
+    @GuardedBy("mLock")
     private final Map<String, List<String>> mHdmiParentInputMap = new ArrayMap<>();
 
     private final AudioManager mAudioManager;
@@ -114,16 +125,16 @@
     private int mCurrentIndex = 0;
     private int mCurrentMaxIndex = 0;
 
+    @GuardedBy("mLock")
     private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
+    @GuardedBy("mLock")
     private final List<Message> mPendingHdmiDeviceEvents = new ArrayList<>();
-
+    @GuardedBy("mLock")
     private final List<Message> mPendingTvinputInfoEvents = new ArrayList<>();
 
     // Calls to mListener should happen here.
     private final Handler mHandler = new ListenerHandler();
 
-    private final Object mLock = new Object();
-
     public TvInputHardwareManager(Context context, Listener listener) {
         mContext = context;
         mListener = listener;
@@ -141,7 +152,9 @@
                     hdmiControlService.addDeviceEventListener(mHdmiDeviceEventListener);
                     hdmiControlService.addSystemAudioModeChangeListener(
                             mHdmiSystemAudioModeChangeListener);
-                    mHdmiDeviceList.addAll(hdmiControlService.getInputDevices());
+                    synchronized (mLock) {
+                        mHdmiDeviceList.addAll(hdmiControlService.getInputDevices());
+                    }
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Error registering listeners to HdmiControlService:", e);
                 }
@@ -172,6 +185,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void buildHardwareListLocked() {
         mHardwareList.clear();
         for (int i = 0; i < mConnections.size(); ++i) {
@@ -295,12 +309,31 @@
         }
     }
 
+    public SparseArray<String> getHardwareInputIdMap() {
+        synchronized (mLock) {
+            return mHardwareInputIdMap.clone();
+        }
+    }
+
+    public SparseArray<String> getHdmiInputIdMap() {
+        synchronized (mLock) {
+            return mHdmiInputIdMap.clone();
+        }
+    }
+
+    public Map<String, TvInputInfo> getInputMap() {
+        synchronized (mLock) {
+            return Collections.unmodifiableMap(mInputMap);
+        }
+    }
+
     public Map<String, List<String>> getHdmiParentInputMap() {
         synchronized (mLock) {
             return Collections.unmodifiableMap(mHdmiParentInputMap);
         }
     }
 
+    @GuardedBy("mLock")
     private boolean checkUidChangedLocked(
             Connection connection, int callingUid, int resolvedUserId) {
         Integer connectionCallingUid = connection.getCallingUidLocked();
@@ -496,6 +529,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private TvInputHardwareInfo findHardwareInfoForHdmiPortLocked(int port) {
         for (TvInputHardwareInfo hardwareInfo : mHardwareList) {
             if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI
@@ -506,6 +540,7 @@
         return null;
     }
 
+    @GuardedBy("mLock")
     private int findDeviceIdForInputIdLocked(String inputId) {
         for (int i = 0; i < mConnections.size(); ++i) {
             int key = mConnections.keyAt(i);
@@ -597,6 +632,7 @@
         return false;
     }
 
+    @GuardedBy("mLock")
     private void processPendingHdmiDeviceEventsLocked() {
         for (Iterator<Message> it = mPendingHdmiDeviceEvents.iterator(); it.hasNext(); ) {
             Message msg = it.next();
@@ -611,6 +647,7 @@
     }
 
 
+    @GuardedBy("mLock")
     private void processPendingTvInputInfoEventsLocked() {
         for (Iterator<Message> it = mPendingTvinputInfoEvents.iterator(); it.hasNext(); ) {
             Message msg = it.next();
@@ -748,6 +785,7 @@
 
         // *Locked methods assume TvInputHardwareManager.mLock is held.
 
+        @GuardedBy("mLock")
         public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback,
                 TvInputInfo info, Integer callingUid, Integer resolvedUserId,
                 ResourceClientProfile profile) {
@@ -776,50 +814,62 @@
             }
         }
 
+        @GuardedBy("mLock")
         public void updateConfigsLocked(TvStreamConfig[] configs) {
             mConfigs = configs;
         }
 
+        @GuardedBy("mLock")
         public TvInputHardwareInfo getHardwareInfoLocked() {
             return mHardwareInfo;
         }
 
+        @GuardedBy("mLock")
         public TvInputInfo getInfoLocked() {
             return mInfo;
         }
 
+        @GuardedBy("mLock")
         public ITvInputHardware getHardwareLocked() {
             return mHardware;
         }
 
+        @GuardedBy("mLock")
         public TvInputHardwareImpl getHardwareImplLocked() {
             return mHardware;
         }
 
+        @GuardedBy("mLock")
         public ITvInputHardwareCallback getCallbackLocked() {
             return mCallback;
         }
 
+        @GuardedBy("mLock")
         public TvStreamConfig[] getConfigsLocked() {
             return mConfigs;
         }
 
+        @GuardedBy("mLock")
         public Integer getCallingUidLocked() {
             return mCallingUid;
         }
 
+        @GuardedBy("mLock")
         public Integer getResolvedUserIdLocked() {
             return mResolvedUserId;
         }
 
+        @GuardedBy("mLock")
         public void setOnFirstFrameCapturedLocked(Runnable runnable) {
             mOnFirstFrameCaptured = runnable;
         }
 
+        @GuardedBy("mLock")
         public Runnable getOnFirstFrameCapturedLocked() {
             return mOnFirstFrameCaptured;
         }
 
+        @GuardedBy("mLock")
         public ResourceClientProfile getResourceClientProfileLocked() {
             return mResourceClientProfile;
         }
@@ -844,6 +894,7 @@
                     + " }";
         }
 
+        @GuardedBy("mLock")
         public boolean updateCableConnectionStatusLocked(int cableConnectionStatus) {
             // Update connection status only if it's not default value
             if (cableConnectionStatus != TvInputHardwareInfo.CABLE_CONNECTION_STATUS_UNKNOWN
@@ -855,10 +906,12 @@
             return mIsCableConnectionStatusUpdated;
         }
 
+        @GuardedBy("mLock")
         private int getConfigsLengthLocked() {
             return mConfigs == null ? 0 : mConfigs.length;
         }
 
+        @GuardedBy("mLock")
         private int getInputStateLocked() {
             int configsLength = getConfigsLengthLocked();
             if (configsLength > 0) {
@@ -880,7 +933,6 @@
 
     private class TvInputHardwareImpl extends ITvInputHardware.Stub {
         private final TvInputHardwareInfo mInfo;
-        private boolean mReleased = false;
         private final Object mImplLock = new Object();
 
         private final AudioManager.OnAudioPortUpdateListener mAudioListener =
@@ -909,28 +961,44 @@
                 }
             }
         };
+        @GuardedBy("mImplLock")
+        private boolean mReleased = false;
+        @GuardedBy("mImplLock")
         private int mOverrideAudioType = AudioManager.DEVICE_NONE;
+        @GuardedBy("mImplLock")
         private String mOverrideAudioAddress = "";
+        @GuardedBy("mImplLock")
         private AudioDevicePort mAudioSource;
+        @GuardedBy("mImplLock")
         private List<AudioDevicePort> mAudioSink = new ArrayList<>();
+        @GuardedBy("mImplLock")
         private AudioPatch mAudioPatch = null;
         // Set to an invalid value for a volume, so that current volume can be applied at the
         // first call to updateAudioConfigLocked().
+        @GuardedBy("mImplLock")
         private float mCommittedVolume = -1f;
+        @GuardedBy("mImplLock")
         private float mSourceVolume = 0.0f;
 
+        @GuardedBy("mImplLock")
         private TvStreamConfig mActiveConfig = null;
 
+        @GuardedBy("mImplLock")
         private int mDesiredSamplingRate = 0;
+        @GuardedBy("mImplLock")
         private int mDesiredChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT;
+        @GuardedBy("mImplLock")
         private int mDesiredFormat = AudioFormat.ENCODING_DEFAULT;
 
         public TvInputHardwareImpl(TvInputHardwareInfo info) {
             mInfo = info;
             mAudioManager.registerAudioPortUpdateListener(mAudioListener);
             if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) {
-                mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress());
-                findAudioSinkFromAudioPolicy(mAudioSink);
+                synchronized (mImplLock) {
+                    mAudioSource =
+                            findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress());
+                    findAudioSinkFromAudioPolicy(mAudioSink);
+                }
             }
         }
 
@@ -1025,6 +1093,7 @@
         /**
          * Update audio configuration (source, sink, patch) all up to current state.
          */
+        @GuardedBy("mImplLock")
         private void updateAudioConfigLocked() {
             boolean sinkUpdated = updateAudioSinkLocked();
             boolean sourceUpdated = updateAudioSourceLocked();
@@ -1204,6 +1273,7 @@
             }
         }
 
+        @GuardedBy("mImplLock")
         private boolean updateAudioSourceLocked() {
             if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) {
                 return false;
@@ -1214,6 +1284,7 @@
                     : !mAudioSource.equals(previousSource);
         }
 
+        @GuardedBy("mImplLock")
         private boolean updateAudioSinkLocked() {
             if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) {
                 return false;
@@ -1339,16 +1410,22 @@
                     String inputId = mHardwareInputIdMap.get(deviceId);
 
                     if (inputId != null) {
-                        if (connection.updateCableConnectionStatusLocked(cableConnectionStatus)) {
-                            if (previousCableConnectionStatus != connection.getInputStateLocked()) {
-                                mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
-                                    connection.getInputStateLocked(), 0, inputId).sendToTarget();
-                            }
-                        } else {
-                            if ((previousConfigsLength == 0)
-                                    != (connection.getConfigsLengthLocked() == 0)) {
-                                mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
-                                    connection.getInputStateLocked(), 0, inputId).sendToTarget();
+                        synchronized (mLock) {
+                            if (connection.updateCableConnectionStatusLocked(
+                                        cableConnectionStatus)) {
+                                if (previousCableConnectionStatus
+                                        != connection.getInputStateLocked()) {
+                                    mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
+                                                    connection.getInputStateLocked(), 0, inputId)
+                                            .sendToTarget();
+                                }
+                            } else {
+                                if ((previousConfigsLength == 0)
+                                        != (connection.getConfigsLengthLocked() == 0)) {
+                                    mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
+                                                    connection.getInputStateLocked(), 0, inputId)
+                                            .sendToTarget();
+                                }
                             }
                         }
                     }
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index d903ad4..e434df7 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -135,6 +135,7 @@
     private static final int APP_TAG_SELF = TunedInfo.APP_TAG_SELF;
     private static final String PERMISSION_ACCESS_WATCHED_PROGRAMS =
             "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS";
+    private static final long UPDATE_HARDWARE_TIS_BINDING_DELAY_IN_MILLIS = 10 * 1000; // 10 seconds
 
     // There are two different formats of DVB frontend devices. One is /dev/dvb%d.frontend%d,
     // another one is /dev/dvb/adapter%d/frontend%d. Followings are the patterns for selecting the
@@ -174,7 +175,7 @@
     @GuardedBy("mLock")
     private final Map<String, SessionState> mSessionIdToSessionStateMap = new HashMap<>();
 
-    private final WatchLogHandler mWatchLogHandler;
+    private final MessageHandler mMessageHandler;
 
     private final ActivityManager mActivityManager;
 
@@ -187,8 +188,8 @@
         super(context);
 
         mContext = context;
-        mWatchLogHandler = new WatchLogHandler(mContext.getContentResolver(),
-                IoThread.get().getLooper());
+        mMessageHandler =
+                new MessageHandler(mContext.getContentResolver(), IoThread.get().getLooper());
         mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
 
         mActivityManager =
@@ -372,10 +373,10 @@
                     // service to populate the hardware list.
                     serviceState = new ServiceState(component, userId);
                     userState.serviceStateMap.put(component, serviceState);
+                    updateServiceConnectionLocked(component, userId);
                 } else {
                     inputList.addAll(serviceState.hardwareInputMap.values());
                 }
-                updateServiceConnectionLocked(component, userId);
             } else {
                 try {
                     TvInputInfo info = new TvInputInfo.Builder(mContext, ri).build();
@@ -510,6 +511,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void startProfileLocked(int userId) {
         mRunningProfiles.add(userId);
         buildTvInputListLocked(userId, null);
@@ -538,8 +540,10 @@
             mCurrentUserId = userId;
             buildTvInputListLocked(userId, null);
             buildTvContentRatingSystemListLocked(userId);
-            mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_SWITCH_CONTENT_RESOLVER,
-                    getContentResolverForUser(userId)).sendToTarget();
+            mMessageHandler
+                    .obtainMessage(MessageHandler.MSG_SWITCH_CONTENT_RESOLVER,
+                            getContentResolverForUser(userId))
+                    .sendToTarget();
         }
     }
 
@@ -593,7 +597,7 @@
                         Slog.e(TAG, "error in unregisterCallback", e);
                     }
                 }
-                mContext.unbindService(serviceState.connection);
+                unbindService(serviceState);
                 it.remove();
             }
         }
@@ -661,7 +665,7 @@
                             Slog.e(TAG, "error in unregisterCallback", e);
                         }
                     }
-                    mContext.unbindService(serviceState.connection);
+                    unbindService(serviceState);
                 }
             }
             userState.serviceStateMap.clear();
@@ -774,7 +778,8 @@
 
         boolean shouldBind;
         if (userId == mCurrentUserId || mRunningProfiles.contains(userId)) {
-            shouldBind = !serviceState.sessionTokens.isEmpty() || serviceState.isHardware;
+            shouldBind = !serviceState.sessionTokens.isEmpty()
+                    || (serviceState.isHardware && serviceState.neverConnected);
         } else {
             // For a non-current user,
             // if sessionTokens is not empty, it contains recording sessions only
@@ -783,31 +788,14 @@
             shouldBind = !serviceState.sessionTokens.isEmpty();
         }
 
-        if (serviceState.service == null && shouldBind) {
-            // This means that the service is not yet connected but its state indicates that we
-            // have pending requests. Then, connect the service.
-            if (serviceState.bound) {
-                // We have already bound to the service so we don't try to bind again until after we
-                // unbind later on.
-                return;
+        // only bind/unbind when necessary.
+        if (shouldBind && !serviceState.bound) {
+            bindService(serviceState, userId);
+        } else if (!shouldBind && serviceState.bound) {
+            unbindService(serviceState);
+            if (!serviceState.isHardware) {
+                userState.serviceStateMap.remove(component);
             }
-            if (DEBUG) {
-                Slog.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
-            }
-
-            Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
-            serviceState.bound = mContext.bindServiceAsUser(
-                    i, serviceState.connection,
-                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
-                    new UserHandle(userId));
-        } else if (serviceState.service != null && !shouldBind) {
-            // This means that the service is already connected but its state indicates that we have
-            // nothing to do with it. Then, disconnect the service.
-            if (DEBUG) {
-                Slog.d(TAG, "unbindService(service=" + component + ")");
-            }
-            mContext.unbindService(serviceState.connection);
-            userState.serviceStateMap.remove(component);
         }
     }
 
@@ -829,7 +817,11 @@
             sendSessionTokenToClientLocked(sessionState.client,
                     sessionState.inputId, null, null, sessionState.seq);
         }
-        updateServiceConnectionLocked(serviceState.component, userId);
+        if (!serviceState.isHardware) {
+            updateServiceConnectionLocked(serviceState.component, userId);
+        } else {
+            updateHardwareServiceConnectionDelayed(userId);
+        }
     }
 
     @GuardedBy("mLock")
@@ -948,13 +940,17 @@
         if (serviceState != null) {
             serviceState.sessionTokens.remove(sessionToken);
         }
-        updateServiceConnectionLocked(sessionState.componentName, userId);
+        if (!serviceState.isHardware) {
+            updateServiceConnectionLocked(sessionState.componentName, userId);
+        } else {
+            updateHardwareServiceConnectionDelayed(userId);
+        }
 
         // Log the end of watch.
         SomeArgs args = SomeArgs.obtain();
         args.arg1 = sessionToken;
         args.arg2 = System.currentTimeMillis();
-        mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_END, args).sendToTarget();
+        mMessageHandler.obtainMessage(MessageHandler.MSG_LOG_WATCH_END, args).sendToTarget();
     }
 
     @GuardedBy("mLock")
@@ -1153,8 +1149,7 @@
         ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent());
         int oldState = inputState.state;
         inputState.state = state;
-        if (serviceState != null && serviceState.service == null
-                && (!serviceState.sessionTokens.isEmpty() || serviceState.isHardware)) {
+        if (serviceState != null && serviceState.reconnecting) {
             // We don't notify state change while reconnecting. It should remain disconnected.
             return;
         }
@@ -1881,7 +1876,7 @@
                         args.arg3 = ContentUris.parseId(channelUri);
                         args.arg4 = params;
                         args.arg5 = sessionToken;
-                        mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_START, args)
+                        mMessageHandler.obtainMessage(MessageHandler.MSG_LOG_WATCH_START, args)
                                 .sendToTarget();
                     } catch (RemoteException | SessionNotFoundException e) {
                         Slog.e(TAG, "error in tune", e);
@@ -2280,6 +2275,26 @@
         }
 
         @Override
+        public void setVideoFrozen(IBinder sessionToken, boolean isFrozen, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "setVideoFrozen");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
+                                .setVideoFrozen(isFrozen);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slog.e(TAG, "error in setVideoFrozen", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void setTvMessageEnabled(IBinder sessionToken, int type, boolean enabled,
                 int userId) {
             final int callingUid = Binder.getCallingUid();
@@ -3307,16 +3322,21 @@
         private final ComponentName component;
         private final boolean isHardware;
         private final Map<String, TvInputInfo> hardwareInputMap = new HashMap<>();
+        private final List<TvInputHardwareInfo> hardwareDeviceRemovedBuffer = new ArrayList<>();
+        private final List<HdmiDeviceInfo> hdmiDeviceRemovedBuffer = new ArrayList<>();
+        private final List<HdmiDeviceInfo> hdmiDeviceUpdatedBuffer = new ArrayList<>();
 
         private ITvInputService service;
         private ServiceCallback callback;
         private boolean bound;
         private boolean reconnecting;
+        private boolean neverConnected;
 
         private ServiceState(ComponentName component, int userId) {
             this.component = component;
             this.connection = new InputServiceConnection(component, userId);
             this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component);
+            this.neverConnected = true;
         }
     }
 
@@ -3429,6 +3449,97 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private void bindService(ServiceState serviceState, int userId) {
+        if (serviceState.bound) {
+            // We have already bound to the service so we don't try to bind again until after we
+            // unbind later on.
+            // For hardware services, call updateHardwareServiceConnectionDelayed() to delay the
+            // possible unbinding.
+            if (serviceState.isHardware) {
+                updateHardwareServiceConnectionDelayed(userId);
+            }
+            return;
+        }
+        if (DEBUG) {
+            Slog.d(TAG,
+                    "bindServiceAsUser(service=" + serviceState.component + ", userId=" + userId
+                            + ")");
+        }
+        Intent i =
+                new Intent(TvInputService.SERVICE_INTERFACE).setComponent(serviceState.component);
+        serviceState.bound = mContext.bindServiceAsUser(i, serviceState.connection,
+                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
+                new UserHandle(userId));
+        if (!serviceState.bound) {
+            Slog.e(TAG, "failed to bind " + serviceState.component + " for userId " + userId);
+            mContext.unbindService(serviceState.connection);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void unbindService(ServiceState serviceState) {
+        if (!serviceState.bound) {
+            return;
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "unbindService(service=" + serviceState.component + ")");
+        }
+        mContext.unbindService(serviceState.connection);
+        serviceState.bound = false;
+        serviceState.service = null;
+        serviceState.callback = null;
+    }
+
+    @GuardedBy("mLock")
+    private void updateHardwareTvInputServiceBindingLocked(int userId) {
+        PackageManager pm = mContext.getPackageManager();
+        List<ResolveInfo> services =
+                pm.queryIntentServicesAsUser(new Intent(TvInputService.SERVICE_INTERFACE),
+                        PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, userId);
+        for (ResolveInfo ri : services) {
+            ServiceInfo si = ri.serviceInfo;
+            if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
+                continue;
+            }
+            ComponentName component = new ComponentName(si.packageName, si.name);
+            if (hasHardwarePermission(pm, component)) {
+                updateServiceConnectionLocked(component, userId);
+            }
+        }
+    }
+
+    private void updateHardwareServiceConnectionDelayed(int userId) {
+        mMessageHandler.removeMessages(MessageHandler.MSG_UPDATE_HARDWARE_TIS_BINDING);
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = userId;
+        Message msg =
+                mMessageHandler.obtainMessage(MessageHandler.MSG_UPDATE_HARDWARE_TIS_BINDING, args);
+        mMessageHandler.sendMessageDelayed(msg, UPDATE_HARDWARE_TIS_BINDING_DELAY_IN_MILLIS);
+    }
+
+    @GuardedBy("mLock")
+    private void addHardwareInputLocked(
+            TvInputInfo inputInfo, ComponentName component, int userId) {
+        ServiceState serviceState = getServiceStateLocked(component, userId);
+        serviceState.hardwareInputMap.put(inputInfo.getId(), inputInfo);
+        buildTvInputListLocked(userId, null);
+    }
+
+    @GuardedBy("mLock")
+    private void removeHardwareInputLocked(String inputId, int userId) {
+        if (!mTvInputHardwareManager.getInputMap().containsKey(inputId)) {
+            return;
+        }
+        ComponentName component = mTvInputHardwareManager.getInputMap().get(inputId).getComponent();
+        ServiceState serviceState = getServiceStateLocked(component, userId);
+        boolean removed = serviceState.hardwareInputMap.remove(inputId) != null;
+        if (removed) {
+            buildTvInputListLocked(userId, null);
+            mTvInputHardwareManager.removeHardwareInput(inputId);
+        }
+    }
+
     private final class InputServiceConnection implements ServiceConnection {
         private final ComponentName mComponent;
         private final int mUserId;
@@ -3452,6 +3563,7 @@
                 }
                 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
                 serviceState.service = ITvInputService.Stub.asInterface(service);
+                serviceState.neverConnected = false;
 
                 // Register a callback, if we need to.
                 if (serviceState.isHardware && serviceState.callback == null) {
@@ -3463,6 +3575,58 @@
                     }
                 }
 
+                for (TvInputState inputState : userState.inputMap.values()) {
+                    if (inputState.info.getComponent().equals(component)
+                            && inputState.state != INPUT_STATE_CONNECTED) {
+                        notifyInputStateChangedLocked(userState, inputState.info.getId(),
+                                inputState.state, null);
+                    }
+                }
+
+                if (serviceState.isHardware) {
+                    for (TvInputHardwareInfo hardwareToBeRemoved :
+                            serviceState.hardwareDeviceRemovedBuffer) {
+                        try {
+                            serviceState.service.notifyHardwareRemoved(hardwareToBeRemoved);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "error in hardwareDeviceRemovedBuffer", e);
+                        }
+                    }
+                    serviceState.hardwareDeviceRemovedBuffer.clear();
+                    for (HdmiDeviceInfo hdmiDeviceToBeRemoved :
+                            serviceState.hdmiDeviceRemovedBuffer) {
+                        try {
+                            serviceState.service.notifyHdmiDeviceRemoved(hdmiDeviceToBeRemoved);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "error in hdmiDeviceRemovedBuffer", e);
+                        }
+                    }
+                    serviceState.hdmiDeviceRemovedBuffer.clear();
+                    for (TvInputHardwareInfo hardware : mTvInputHardwareManager.getHardwareList()) {
+                        try {
+                            serviceState.service.notifyHardwareAdded(hardware);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "error in notifyHardwareAdded", e);
+                        }
+                    }
+                    for (HdmiDeviceInfo device : mTvInputHardwareManager.getHdmiDeviceList()) {
+                        try {
+                            serviceState.service.notifyHdmiDeviceAdded(device);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
+                        }
+                    }
+                    for (HdmiDeviceInfo hdmiDeviceToBeUpdated :
+                            serviceState.hdmiDeviceUpdatedBuffer) {
+                        try {
+                            serviceState.service.notifyHdmiDeviceUpdated(hdmiDeviceToBeUpdated);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "error in hdmiDeviceUpdatedBuffer", e);
+                        }
+                    }
+                    serviceState.hdmiDeviceUpdatedBuffer.clear();
+                }
+
                 List<IBinder> tokensToBeRemoved = new ArrayList<>();
 
                 // And create sessions, if any.
@@ -3476,30 +3640,8 @@
                     removeSessionStateLocked(sessionToken, mUserId);
                 }
 
-                for (TvInputState inputState : userState.inputMap.values()) {
-                    if (inputState.info.getComponent().equals(component)
-                            && inputState.state != INPUT_STATE_CONNECTED) {
-                        notifyInputStateChangedLocked(userState, inputState.info.getId(),
-                                inputState.state, null);
-                    }
-                }
-
                 if (serviceState.isHardware) {
-                    serviceState.hardwareInputMap.clear();
-                    for (TvInputHardwareInfo hardware : mTvInputHardwareManager.getHardwareList()) {
-                        try {
-                            serviceState.service.notifyHardwareAdded(hardware);
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "error in notifyHardwareAdded", e);
-                        }
-                    }
-                    for (HdmiDeviceInfo device : mTvInputHardwareManager.getHdmiDeviceList()) {
-                        try {
-                            serviceState.service.notifyHdmiDeviceAdded(device);
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
-                        }
-                    }
+                    updateHardwareServiceConnectionDelayed(mUserId);
                 }
             }
         }
@@ -3550,13 +3692,6 @@
             }
         }
 
-        @GuardedBy("mLock")
-        private void addHardwareInputLocked(TvInputInfo inputInfo) {
-            ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
-            serviceState.hardwareInputMap.put(inputInfo.getId(), inputInfo);
-            buildTvInputListLocked(mUserId, null);
-        }
-
         public void addHardwareInput(int deviceId, TvInputInfo inputInfo) {
             ensureHardwarePermission();
             ensureValidInput(inputInfo);
@@ -3567,8 +3702,11 @@
                     if (serviceState.hardwareInputMap.containsKey(inputInfo.getId())) {
                         return;
                     }
+                    Slog.d("ServiceCallback",
+                            "addHardwareInput: device id " + deviceId + ", "
+                                    + inputInfo.toString());
                     mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo);
-                    addHardwareInputLocked(inputInfo);
+                    addHardwareInputLocked(inputInfo, mComponent, mUserId);
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -3586,7 +3724,7 @@
                         return;
                     }
                     mTvInputHardwareManager.addHdmiInput(id, inputInfo);
-                    addHardwareInputLocked(inputInfo);
+                    addHardwareInputLocked(inputInfo, mComponent, mUserId);
                     if (mOnScreenInputId != null && mOnScreenSessionState != null) {
                         if (TextUtils.equals(mOnScreenInputId, inputInfo.getParentId())) {
                             // catch the use case when a CEC device is plugged in an HDMI port,
@@ -3615,14 +3753,9 @@
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
-                    boolean removed = serviceState.hardwareInputMap.remove(inputId) != null;
-                    if (removed) {
-                        buildTvInputListLocked(mUserId, null);
-                        mTvInputHardwareManager.removeHardwareInput(inputId);
-                    } else {
-                        Slog.e(TAG, "failed to remove input " + inputId);
-                    }
+                    Slog.d("ServiceCallback",
+                            "removeHardwareInput " + inputId + " by " + mComponent);
+                    removeHardwareInputLocked(inputId, mUserId);
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -3840,6 +3973,23 @@
         }
 
         @Override
+        public void onVideoFreezeUpdated(boolean isFrozen) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onVideoFreezeUpdated(" + isFrozen + ")");
+                }
+                if (mSessionState.session == null || mSessionState.client == null) {
+                    return;
+                }
+                try {
+                    mSessionState.client.onVideoFreezeUpdated(isFrozen, mSessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onVideoFreezeUpdated", e);
+                }
+            }
+        }
+
+        @Override
         public void onContentAllowed() {
             synchronized (mLock) {
                 if (DEBUG) {
@@ -4189,11 +4339,12 @@
         return loggedReason;
     }
 
+    @GuardedBy("mLock")
     private UserState getUserStateLocked(int userId) {
         return mUserStates.get(userId);
     }
 
-    private static final class WatchLogHandler extends Handler {
+    private final class MessageHandler extends Handler {
         // There are only two kinds of watch events that can happen on the system:
         // 1. The current TV input session is tuned to a new channel.
         // 2. The session is released for some reason.
@@ -4205,10 +4356,11 @@
         static final int MSG_LOG_WATCH_START = 1;
         static final int MSG_LOG_WATCH_END = 2;
         static final int MSG_SWITCH_CONTENT_RESOLVER = 3;
+        static final int MSG_UPDATE_HARDWARE_TIS_BINDING = 4;
 
         private ContentResolver mContentResolver;
 
-        WatchLogHandler(ContentResolver contentResolver, Looper looper) {
+        MessageHandler(ContentResolver contentResolver, Looper looper) {
             super(looper);
             mContentResolver = contentResolver;
         }
@@ -4267,6 +4419,14 @@
                     mContentResolver = (ContentResolver) msg.obj;
                     break;
                 }
+                case MSG_UPDATE_HARDWARE_TIS_BINDING:
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    int userId = (int) args.arg1;
+                    synchronized (mLock) {
+                        updateHardwareTvInputServiceBindingLocked(userId);
+                    }
+                    args.recycle();
+                    break;
                 default: {
                     Slog.w(TAG, "unhandled message code: " + msg.what);
                     break;
@@ -4322,29 +4482,46 @@
                 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
                 // Broadcast the event to all hardware inputs.
                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
-                    if (!serviceState.isHardware || serviceState.service == null) continue;
+                    if (!serviceState.isHardware) {
+                        continue;
+                    }
                     try {
-                        serviceState.service.notifyHardwareAdded(info);
+                        bindService(serviceState, mCurrentUserId);
+                        if (serviceState.service != null) {
+                            serviceState.service.notifyHardwareAdded(info);
+                        }
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in notifyHardwareAdded", e);
                     }
                 }
+                updateHardwareServiceConnectionDelayed(mCurrentUserId);
             }
         }
 
         @Override
         public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
             synchronized (mLock) {
+                String relatedInputId =
+                        mTvInputHardwareManager.getHardwareInputIdMap().get(info.getDeviceId());
+                removeHardwareInputLocked(relatedInputId, mCurrentUserId);
                 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
                 // Broadcast the event to all hardware inputs.
                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
-                    if (!serviceState.isHardware || serviceState.service == null) continue;
+                    if (!serviceState.isHardware) {
+                        continue;
+                    }
                     try {
-                        serviceState.service.notifyHardwareRemoved(info);
+                        bindService(serviceState, mCurrentUserId);
+                        if (serviceState.service != null) {
+                            serviceState.service.notifyHardwareRemoved(info);
+                        } else {
+                            serviceState.hardwareDeviceRemovedBuffer.add(info);
+                        }
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in notifyHardwareRemoved", e);
                     }
                 }
+                updateHardwareServiceConnectionDelayed(mCurrentUserId);
             }
         }
 
@@ -4354,29 +4531,46 @@
                 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
                 // Broadcast the event to all hardware inputs.
                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
-                    if (!serviceState.isHardware || serviceState.service == null) continue;
+                    if (!serviceState.isHardware) {
+                        continue;
+                    }
                     try {
-                        serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
+                        bindService(serviceState, mCurrentUserId);
+                        if (serviceState.service != null) {
+                            serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
+                        }
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
                     }
                 }
+                updateHardwareServiceConnectionDelayed(mCurrentUserId);
             }
         }
 
         @Override
         public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
             synchronized (mLock) {
+                String relatedInputId =
+                        mTvInputHardwareManager.getHdmiInputIdMap().get(deviceInfo.getId());
+                removeHardwareInputLocked(relatedInputId, mCurrentUserId);
                 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
                 // Broadcast the event to all hardware inputs.
                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
-                    if (!serviceState.isHardware || serviceState.service == null) continue;
+                    if (!serviceState.isHardware) {
+                        continue;
+                    }
                     try {
-                        serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
+                        bindService(serviceState, mCurrentUserId);
+                        if (serviceState.service != null) {
+                            serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
+                        } else {
+                            serviceState.hdmiDeviceRemovedBuffer.add(deviceInfo);
+                        }
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
                     }
                 }
+                updateHardwareServiceConnectionDelayed(mCurrentUserId);
             }
         }
 
@@ -4404,13 +4598,21 @@
                 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
                 // Broadcast the event to all hardware inputs.
                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
-                    if (!serviceState.isHardware || serviceState.service == null) continue;
+                    if (!serviceState.isHardware) {
+                        continue;
+                    }
                     try {
-                        serviceState.service.notifyHdmiDeviceUpdated(deviceInfo);
+                        bindService(serviceState, mCurrentUserId);
+                        if (serviceState.service != null) {
+                            serviceState.service.notifyHdmiDeviceUpdated(deviceInfo);
+                        } else {
+                            serviceState.hdmiDeviceUpdatedBuffer.add(deviceInfo);
+                        }
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in notifyHdmiDeviceUpdated", e);
                     }
                 }
+                updateHardwareServiceConnectionDelayed(mCurrentUserId);
             }
         }
 
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index 0467d0c..eacd3f8 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -38,7 +38,16 @@
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvRecordingInfo;
 import android.media.tv.TvTrackInfo;
+import android.media.tv.ad.ITvAdClient;
 import android.media.tv.ad.ITvAdManager;
+import android.media.tv.ad.ITvAdManagerCallback;
+import android.media.tv.ad.ITvAdService;
+import android.media.tv.ad.ITvAdServiceCallback;
+import android.media.tv.ad.ITvAdSession;
+import android.media.tv.ad.ITvAdSessionCallback;
+import android.media.tv.ad.TvAdService;
+import android.media.tv.ad.TvAdServiceInfo;
+import android.media.tv.flags.Flags;
 import android.media.tv.interactive.AppLinkInfo;
 import android.media.tv.interactive.ITvInteractiveAppClient;
 import android.media.tv.interactive.ITvInteractiveAppManager;
@@ -110,6 +119,8 @@
     @GuardedBy("mLock")
     private boolean mGetServiceListCalled = false;
     @GuardedBy("mLock")
+    private boolean mGetAdServiceListCalled = false;
+    @GuardedBy("mLock")
     private boolean mGetAppLinkInfoListCalled = false;
 
     private final UserManager mUserManager;
@@ -256,6 +267,141 @@
     }
 
     @GuardedBy("mLock")
+    private void buildTvAdServiceListLocked(int userId, String[] updatedPackages) {
+        if (!Flags.enableAdServiceFw()) {
+            return;
+        }
+        UserState userState = getOrCreateUserStateLocked(userId);
+        userState.mPackageSet.clear();
+
+        if (DEBUG) {
+            Slogf.d(TAG, "buildTvAdServiceListLocked");
+        }
+        PackageManager pm = mContext.getPackageManager();
+        List<ResolveInfo> services = pm.queryIntentServicesAsUser(
+                new Intent(TvAdService.SERVICE_INTERFACE),
+                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
+                userId);
+        List<TvAdServiceInfo> serviceList = new ArrayList<>();
+
+        for (ResolveInfo ri : services) {
+            ServiceInfo si = ri.serviceInfo;
+            if (!android.Manifest.permission.BIND_TV_AD_SERVICE.equals(si.permission)) {
+                Slog.w(TAG, "Skipping TV AD service " + si.name
+                        + ": it does not require the permission "
+                        + android.Manifest.permission.BIND_TV_AD_SERVICE);
+                continue;
+            }
+
+            ComponentName component = new ComponentName(si.packageName, si.name);
+            try {
+                TvAdServiceInfo info = new TvAdServiceInfo(mContext, component);
+                serviceList.add(info);
+            } catch (Exception e) {
+                Slogf.e(TAG, "failed to load TV AD service " + si.name, e);
+                continue;
+            }
+            userState.mPackageSet.add(si.packageName);
+        }
+
+        // sort the service list by service id
+        Collections.sort(serviceList, Comparator.comparing(TvAdServiceInfo::getId));
+        Map<String, TvAdServiceState> adServiceMap = new HashMap<>();
+        for (TvAdServiceInfo info : serviceList) {
+            String serviceId = info.getId();
+            if (DEBUG) {
+                Slogf.d(TAG, "add " + serviceId);
+            }
+            TvAdServiceState adServiceState = userState.mAdServiceMap.get(serviceId);
+            if (adServiceState == null) {
+                adServiceState = new TvAdServiceState();
+            }
+            adServiceState.mInfo = info;
+            adServiceState.mUid = getAdServiceUid(info);
+            adServiceState.mComponentName = info.getComponent();
+            adServiceMap.put(serviceId, adServiceState);
+        }
+
+        for (String serviceId : adServiceMap.keySet()) {
+            if (!userState.mAdServiceMap.containsKey(serviceId)) {
+                notifyAdServiceAddedLocked(userState, serviceId);
+            } else if (updatedPackages != null) {
+                // Notify the package updates
+                ComponentName component = adServiceMap.get(serviceId).mInfo.getComponent();
+                for (String updatedPackage : updatedPackages) {
+                    if (component.getPackageName().equals(updatedPackage)) {
+                        updateAdServiceConnectionLocked(component, userId);
+                        notifyAdServiceUpdatedLocked(userState, serviceId);
+                        break;
+                    }
+                }
+            }
+        }
+
+        for (String serviceId : userState.mAdServiceMap.keySet()) {
+            if (!adServiceMap.containsKey(serviceId)) {
+                TvAdServiceInfo info = userState.mAdServiceMap.get(serviceId).mInfo;
+                AdServiceState serviceState = userState.mAdServiceStateMap.get(info.getComponent());
+                if (serviceState != null) {
+                    abortPendingCreateAdSessionRequestsLocked(serviceState, serviceId, userId);
+                }
+                notifyAdServiceRemovedLocked(userState, serviceId);
+            }
+        }
+
+        userState.mIAppMap.clear();
+        userState.mAdServiceMap = adServiceMap;
+    }
+
+    @GuardedBy("mLock")
+    private void notifyAdServiceAddedLocked(UserState userState, String serviceId) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyAdServiceAddedLocked(serviceId=" + serviceId + ")");
+        }
+        int n = userState.mAdCallbacks.beginBroadcast();
+        for (int i = 0; i < n; ++i) {
+            try {
+                userState.mAdCallbacks.getBroadcastItem(i).onAdServiceAdded(serviceId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "failed to report added AD service to callback", e);
+            }
+        }
+        userState.mAdCallbacks.finishBroadcast();
+    }
+
+    @GuardedBy("mLock")
+    private void notifyAdServiceRemovedLocked(UserState userState, String serviceId) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyAdServiceRemovedLocked(serviceId=" + serviceId + ")");
+        }
+        int n = userState.mAdCallbacks.beginBroadcast();
+        for (int i = 0; i < n; ++i) {
+            try {
+                userState.mAdCallbacks.getBroadcastItem(i).onAdServiceRemoved(serviceId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "failed to report removed AD service to callback", e);
+            }
+        }
+        userState.mAdCallbacks.finishBroadcast();
+    }
+
+    @GuardedBy("mLock")
+    private void notifyAdServiceUpdatedLocked(UserState userState, String serviceId) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyAdServiceUpdatedLocked(serviceId=" + serviceId + ")");
+        }
+        int n = userState.mAdCallbacks.beginBroadcast();
+        for (int i = 0; i < n; ++i) {
+            try {
+                userState.mAdCallbacks.getBroadcastItem(i).onAdServiceUpdated(serviceId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "failed to report updated AD service to callback", e);
+            }
+        }
+        userState.mAdCallbacks.finishBroadcast();
+    }
+
+    @GuardedBy("mLock")
     private void notifyInteractiveAppServiceAddedLocked(UserState userState, String iAppServiceId) {
         if (DEBUG) {
             Slog.d(TAG, "notifyInteractiveAppServiceAddedLocked(iAppServiceId="
@@ -340,6 +486,16 @@
         }
     }
 
+    private int getAdServiceUid(TvAdServiceInfo info) {
+        try {
+            return getContext().getPackageManager().getApplicationInfo(
+                    info.getServiceInfo().packageName, 0).uid;
+        } catch (PackageManager.NameNotFoundException e) {
+            Slogf.w(TAG, "Unable to get UID for  " + info, e);
+            return Process.INVALID_UID;
+        }
+    }
+
     @Override
     public void onStart() {
         if (DEBUG) {
@@ -357,6 +513,7 @@
             synchronized (mLock) {
                 buildTvInteractiveAppServiceListLocked(mCurrentUserId, null);
                 buildAppLinkInfoLocked(mCurrentUserId);
+                buildTvAdServiceListLocked(mCurrentUserId, null);
             }
         }
     }
@@ -372,6 +529,14 @@
                     }
                 }
             }
+            private void buildTvAdServiceList(String[] packages) {
+                int userId = getChangingUserId();
+                synchronized (mLock) {
+                    if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) {
+                        buildTvAdServiceListLocked(userId, packages);
+                    }
+                }
+            }
 
             @Override
             public void onPackageUpdateFinished(String packageName, int uid) {
@@ -379,6 +544,7 @@
                 // This callback is invoked when the TV interactive App service is reinstalled.
                 // In this case, isReplacing() always returns true.
                 buildTvInteractiveAppServiceList(new String[] { packageName });
+                buildTvAdServiceList(new String[] { packageName });
             }
 
             @Override
@@ -390,6 +556,7 @@
                 // available.
                 if (isReplacing()) {
                     buildTvInteractiveAppServiceList(packages);
+                    buildTvAdServiceList(packages);
                 }
             }
 
@@ -403,6 +570,7 @@
                 }
                 if (isReplacing()) {
                     buildTvInteractiveAppServiceList(packages);
+                    buildTvAdServiceList(packages);
                 }
             }
 
@@ -418,6 +586,7 @@
                     return;
                 }
                 buildTvInteractiveAppServiceList(null);
+                buildTvAdServiceList(null);
             }
 
             @Override
@@ -476,6 +645,7 @@
             mCurrentUserId = userId;
             buildTvInteractiveAppServiceListLocked(userId, null);
             buildAppLinkInfoLocked(userId);
+            buildTvAdServiceListLocked(userId, null);
         }
     }
 
@@ -562,6 +732,7 @@
         mRunningProfiles.add(userId);
         buildTvInteractiveAppServiceListLocked(userId, null);
         buildAppLinkInfoLocked(userId);
+        buildTvAdServiceListLocked(userId, null);
     }
 
     @GuardedBy("mLock")
@@ -619,7 +790,19 @@
                 Slog.e(TAG, "error in onSessionReleased", e);
             }
         }
-        removeSessionStateLocked(state.mSessionToken, state.mUserId);
+        removeAdSessionStateLocked(state.mSessionToken, state.mUserId);
+    }
+
+    @GuardedBy("mLock")
+    private void clearAdSessionAndNotifyClientLocked(AdSessionState state) {
+        if (state.mClient != null) {
+            try {
+                state.mClient.onSessionReleased(state.mSeq);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "error in onSessionReleased", e);
+            }
+        }
+        removeAdSessionStateLocked(state.mSessionToken, state.mUserId);
     }
 
     private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
@@ -655,6 +838,44 @@
     }
 
     @GuardedBy("mLock")
+    private AdSessionState getAdSessionStateLocked(
+            IBinder sessionToken, int callingUid, int userId) {
+        UserState userState = getOrCreateUserStateLocked(userId);
+        return getAdSessionStateLocked(sessionToken, callingUid, userState);
+    }
+
+    @GuardedBy("mLock")
+    private AdSessionState getAdSessionStateLocked(IBinder sessionToken, int callingUid,
+            UserState userState) {
+        AdSessionState sessionState = userState.mAdSessionStateMap.get(sessionToken);
+        if (sessionState == null) {
+            throw new SessionNotFoundException("Session state not found for token " + sessionToken);
+        }
+        // Only the application that requested this session or the system can access it.
+        if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.mCallingUid) {
+            throw new SecurityException("Illegal access to the session with token " + sessionToken
+                    + " from uid " + callingUid);
+        }
+        return sessionState;
+    }
+
+    @GuardedBy("mLock")
+    private ITvAdSession getAdSessionLocked(
+            IBinder sessionToken, int callingUid, int userId) {
+        return getAdSessionLocked(getAdSessionStateLocked(sessionToken, callingUid, userId));
+    }
+
+    @GuardedBy("mLock")
+    private ITvAdSession getAdSessionLocked(AdSessionState sessionState) {
+        ITvAdSession session = sessionState.mSession;
+        if (session == null) {
+            throw new IllegalStateException("Session not yet created for token "
+                    + sessionState.mSessionToken);
+        }
+        return session;
+    }
+
+    @GuardedBy("mLock")
     private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
         UserState userState = getOrCreateUserStateLocked(userId);
         return getSessionStateLocked(sessionToken, callingUid, userState);
@@ -691,8 +912,506 @@
         return session;
     }
     private final class TvAdBinderService extends ITvAdManager.Stub {
+
+        @Override
+        public List<TvAdServiceInfo> getTvAdServiceList(int userId) {
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+                    Binder.getCallingUid(), userId, "getTvAdServiceList");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    if (!mGetAdServiceListCalled) {
+                        buildTvAdServiceListLocked(userId, null);
+                        mGetAdServiceListCalled = true;
+                    }
+                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+                    List<TvAdServiceInfo> adServiceList = new ArrayList<>();
+                    for (TvAdServiceState state : userState.mAdServiceMap.values()) {
+                        adServiceList.add(state.mInfo);
+                    }
+                    return adServiceList;
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void createSession(final ITvAdClient client, final String serviceId, String type,
+                int seq, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
+                    userId, "createSession");
+            final long identity = Binder.clearCallingIdentity();
+
+            try {
+                synchronized (mLock) {
+                    if (userId != mCurrentUserId && !mRunningProfiles.contains(userId)) {
+                        // Only current user and its running profiles can create sessions.
+                        // Let the client get onConnectionFailed callback for this case.
+                        sendAdSessionTokenToClientLocked(client, serviceId, null, null, seq);
+                        return;
+                    }
+                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+                    TvAdServiceState adState = userState.mAdMap.get(serviceId);
+                    if (adState == null) {
+                        Slogf.w(TAG, "Failed to find state for serviceId=" + serviceId);
+                        sendAdSessionTokenToClientLocked(client, serviceId, null, null, seq);
+                        return;
+                    }
+                    AdServiceState serviceState =
+                            userState.mAdServiceStateMap.get(adState.mComponentName);
+                    if (serviceState == null) {
+                        int tasUid = PackageManager.getApplicationInfoAsUserCached(
+                                adState.mComponentName.getPackageName(), 0, resolvedUserId).uid;
+                        serviceState = new AdServiceState(
+                                adState.mComponentName, serviceId, resolvedUserId);
+                        userState.mAdServiceStateMap.put(adState.mComponentName, serviceState);
+                    }
+                    // Send a null token immediately while reconnecting.
+                    if (serviceState.mReconnecting) {
+                        sendAdSessionTokenToClientLocked(client, serviceId, null, null, seq);
+                        return;
+                    }
+
+                    // Create a new session token and a session state.
+                    IBinder sessionToken = new Binder();
+                    AdSessionState sessionState = new AdSessionState(sessionToken, serviceId, type,
+                            adState.mComponentName, client, seq, callingUid,
+                            callingPid, resolvedUserId);
+
+                    // Add them to the global session state map of the current user.
+                    userState.mAdSessionStateMap.put(sessionToken, sessionState);
+
+                    // Also, add them to the session state map of the current service.
+                    serviceState.mSessionTokens.add(sessionToken);
+
+                    if (serviceState.mService != null) {
+                        if (!createAdSessionInternalLocked(serviceState.mService, sessionToken,
+                                resolvedUserId)) {
+                            removeAdSessionStateLocked(sessionToken, resolvedUserId);
+                        }
+                    } else {
+                        updateAdServiceConnectionLocked(adState.mComponentName, resolvedUserId);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void releaseSession(IBinder sessionToken, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "releaseSession(sessionToken=" + sessionToken + ")");
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "releaseSession");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void setSurface(IBinder sessionToken, Surface surface, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "setSurface");
+            AdSessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getAdSessionLocked(sessionState).setSurface(surface);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in setSurface", e);
+                    }
+                }
+            } finally {
+                if (surface != null) {
+                    // surface is not used in TvAdManagerService.
+                    surface.release();
+                }
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
+                int height, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "dispatchSurfaceChanged");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        AdSessionState sessionState = getAdSessionStateLocked(
+                                sessionToken, callingUid, resolvedUserId);
+                        getAdSessionLocked(sessionState).dispatchSurfaceChanged(format, width,
+                                height);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in dispatchSurfaceChanged", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
         @Override
         public void startAdService(IBinder sessionToken, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "startAdService(userId=%d)", userId);
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "startAdService");
+            AdSessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getAdSessionLocked(sessionState).startAdService();
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in start", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void stopAdService(IBinder sessionToken, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "stopAdService(userId=%d)", userId);
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "stopAdService");
+            AdSessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getAdSessionLocked(sessionState).stopAdService();
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in stop", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void resetAdService(IBinder sessionToken, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "resetAdService(userId=%d)", userId);
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "resetAdService");
+            AdSessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getAdSessionLocked(sessionState).resetAdService();
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in reset", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void sendCurrentVideoBounds(IBinder sessionToken, Rect bounds, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "sendCurrentVideoBounds(bounds=%s)", bounds.toString());
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "sendCurrentVideoBounds");
+            AdSessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getAdSessionLocked(sessionState).sendCurrentVideoBounds(bounds);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in sendCurrentVideoBounds", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void sendCurrentChannelUri(IBinder sessionToken, Uri channelUri, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "sendCurrentChannelUri(channelUri=%s)", channelUri.toString());
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "sendCurrentChannelUri");
+            AdSessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getAdSessionLocked(sessionState).sendCurrentChannelUri(channelUri);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in sendCurrentChannelUri", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void sendTrackInfoList(IBinder sessionToken, List<TvTrackInfo> tracks, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "sendTrackInfoList(tracks=%s)", tracks.toString());
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "sendTrackInfoList");
+            AdSessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getAdSessionLocked(sessionState).sendTrackInfoList(tracks);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in sendTrackInfoList", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void sendCurrentTvInputId(IBinder sessionToken, String inputId, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "sendCurrentTvInputId(inputId=%s)", inputId);
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "sendCurrentTvInputId");
+            AdSessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getAdSessionLocked(sessionState).sendCurrentTvInputId(inputId);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in sendCurrentTvInputId", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void sendSigningResult(
+                IBinder sessionToken, String signingId, byte[] result, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "sendSigningResult(signingId=%s)", signingId);
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "sendSigningResult");
+            AdSessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getAdSessionLocked(sessionState).sendSigningResult(signingId, result);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in sendSigningResult", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void notifyError(IBinder sessionToken, String errMsg, Bundle params, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "notifyError(errMsg=%s)", errMsg);
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId =
+                    resolveCallingUserId(Binder.getCallingPid(), callingUid, userId, "notifyError");
+            AdSessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState =
+                                getAdSessionStateLocked(sessionToken, callingUid, resolvedUserId);
+                        getAdSessionLocked(sessionState).notifyError(errMsg, params);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyError", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void notifyTvMessage(IBinder sessionToken, int type, Bundle data, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "notifyTvMessage(type=%d)", type);
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+                    "notifyTvMessage");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        AdSessionState sessionState =
+                                getAdSessionStateLocked(sessionToken, callingUid, resolvedUserId);
+                        getAdSessionLocked(sessionState).notifyTvMessage(type, data);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyTvMessage", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void registerCallback(final ITvAdManagerCallback callback, int userId) {
+            int callingPid = Binder.getCallingPid();
+            int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+                    "registerCallback");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    final UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+                    if (!userState.mAdCallbacks.register(callback)) {
+                        Slog.e(TAG, "client process has already died");
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void unregisterCallback(ITvAdManagerCallback callback, int userId) {
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+                    Binder.getCallingUid(), userId, "unregisterCallback");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+                    userState.mAdCallbacks.unregister(callback);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void createMediaView(IBinder sessionToken, IBinder windowToken, Rect frame,
+                int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "createMediaView");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        getAdSessionLocked(sessionToken, callingUid, resolvedUserId)
+                                .createMediaView(windowToken, frame);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slog.e(TAG, "error in createMediaView", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void relayoutMediaView(IBinder sessionToken, Rect frame, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "relayoutMediaView");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        getAdSessionLocked(sessionToken, callingUid, resolvedUserId)
+                                .relayoutMediaView(frame);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slog.e(TAG, "error in relayoutMediaView", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void removeMediaView(IBinder sessionToken, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "removeMediaView");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        getAdSessionLocked(sessionToken, callingUid, resolvedUserId)
+                                .removeMediaView();
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slog.e(TAG, "error in removeMediaView", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
 
     }
@@ -927,7 +1646,7 @@
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
+                    releaseAdSessionLocked(sessionToken, callingUid, resolvedUserId);
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -1059,6 +1778,28 @@
         }
 
         @Override
+        public void notifyVideoFreezeUpdated(IBinder sessionToken, boolean isFrozen, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+                    "notifyVideoFreezeUpdated");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).notifyVideoFreezeUpdated(isFrozen);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyVideoFreezeUpdated", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void notifyContentAllowed(IBinder sessionToken, int userId) {
             final int callingUid = Binder.getCallingUid();
             final int callingPid = Binder.getCallingPid();
@@ -1146,7 +1887,6 @@
             }
         }
 
-
         @Override
         public void notifyRecordingStarted(IBinder sessionToken, String recordingId,
                 String requestId, int userId) {
@@ -1471,6 +2211,32 @@
         }
 
         @Override
+        public void sendSelectedTrackInfo(IBinder sessionToken, List<TvTrackInfo> tracks,
+                int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "sendSelectedTrackInfo(tracks=%s)", tracks.toString());
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "sendSelectedTrackInfo");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).sendSelectedTrackInfo(tracks);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in sendSelectedTrackInfo", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void sendCurrentTvInputId(IBinder sessionToken, String inputId, int userId) {
             if (DEBUG) {
                 Slogf.d(TAG, "sendCurrentTvInputId(inputId=%s)", inputId);
@@ -1624,6 +2390,33 @@
         }
 
         @Override
+        public void sendCertificate(IBinder sessionToken, String host, int port,
+                Bundle certBundle, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "sendCertificate(host=%s port=%d cert=%s)", host, port,
+                        certBundle);
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "sendCertificate");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).sendCertificate(host, port, certBundle);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in sendCertificate", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void notifyError(IBinder sessionToken, String errMsg, Bundle params, int userId) {
             if (DEBUG) {
                 Slogf.d(TAG, "notifyError(errMsg=%s)", errMsg);
@@ -2134,6 +2927,17 @@
     }
 
     @GuardedBy("mLock")
+    private void sendAdSessionTokenToClientLocked(
+            ITvAdClient client, String serviceId, IBinder sessionToken,
+            InputChannel channel, int seq) {
+        try {
+            client.onSessionCreated(serviceId, sessionToken, channel, seq);
+        } catch (RemoteException e) {
+            Slogf.e(TAG, "error in onSessionCreated", e);
+        }
+    }
+
+    @GuardedBy("mLock")
     private boolean createSessionInternalLocked(
             ITvInteractiveAppService service, IBinder sessionToken, int userId) {
         UserState userState = getOrCreateUserStateLocked(userId);
@@ -2163,6 +2967,58 @@
     }
 
     @GuardedBy("mLock")
+    private boolean createAdSessionInternalLocked(
+            ITvAdService service, IBinder sessionToken, int userId) {
+        UserState userState = getOrCreateUserStateLocked(userId);
+        AdSessionState sessionState = userState.mAdSessionStateMap.get(sessionToken);
+        if (DEBUG) {
+            Slogf.d(TAG, "createAdSessionInternalLocked(iAppServiceId="
+                    + sessionState.mAdServiceId + ")");
+        }
+        InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
+
+        // Set up a callback to send the session token.
+        ITvAdSessionCallback callback = new AdSessionCallback(sessionState, channels);
+
+        boolean created = true;
+        // Create a session. When failed, send a null token immediately.
+        try {
+            service.createSession(
+                    channels[1], callback, sessionState.mAdServiceId, sessionState.mType);
+        } catch (RemoteException e) {
+            Slogf.e(TAG, "error in createSession", e);
+            sendAdSessionTokenToClientLocked(sessionState.mClient, sessionState.mAdServiceId, null,
+                    null, sessionState.mSeq);
+            created = false;
+        }
+        channels[1].dispose();
+        return created;
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    private AdSessionState releaseAdSessionLocked(
+            IBinder sessionToken, int callingUid, int userId) {
+        AdSessionState sessionState = null;
+        try {
+            sessionState = getAdSessionStateLocked(sessionToken, callingUid, userId);
+            UserState userState = getOrCreateUserStateLocked(userId);
+            if (sessionState.mSession != null) {
+                sessionState.mSession.asBinder().unlinkToDeath(sessionState, 0);
+                sessionState.mSession.release();
+            }
+        } catch (RemoteException | SessionNotFoundException e) {
+            Slogf.e(TAG, "error in releaseSession", e);
+        } finally {
+            if (sessionState != null) {
+                sessionState.mSession = null;
+            }
+        }
+        removeAdSessionStateLocked(sessionToken, userId);
+        return sessionState;
+    }
+
+    @GuardedBy("mLock")
     @Nullable
     private SessionState releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
         SessionState sessionState = null;
@@ -2215,6 +3071,36 @@
     }
 
     @GuardedBy("mLock")
+    private void removeAdSessionStateLocked(IBinder sessionToken, int userId) {
+        UserState userState = getOrCreateUserStateLocked(userId);
+
+        // Remove the session state from the global session state map of the current user.
+        AdSessionState sessionState = userState.mAdSessionStateMap.remove(sessionToken);
+
+        if (sessionState == null) {
+            Slogf.e(TAG, "sessionState null, no more remove session action!");
+            return;
+        }
+
+        // Also remove the session token from the session token list of the current client and
+        // service.
+        ClientState clientState = userState.mClientStateMap.get(sessionState.mClient.asBinder());
+        if (clientState != null) {
+            clientState.mSessionTokens.remove(sessionToken);
+            if (clientState.isEmpty()) {
+                userState.mClientStateMap.remove(sessionState.mClient.asBinder());
+                sessionState.mClient.asBinder().unlinkToDeath(clientState, 0);
+            }
+        }
+
+        AdServiceState serviceState = userState.mAdServiceStateMap.get(sessionState.mComponent);
+        if (serviceState != null) {
+            serviceState.mSessionTokens.remove(sessionToken);
+        }
+        updateAdServiceConnectionLocked(sessionState.mComponent, userId);
+    }
+
+    @GuardedBy("mLock")
     private void abortPendingCreateSessionRequestsLocked(ServiceState serviceState,
             String iAppServiceId, int userId) {
         // Let clients know the create session requests are failed.
@@ -2237,6 +3123,28 @@
     }
 
     @GuardedBy("mLock")
+    private void abortPendingCreateAdSessionRequestsLocked(AdServiceState serviceState,
+            String serviceId, int userId) {
+        // Let clients know the create session requests are failed.
+        UserState userState = getOrCreateUserStateLocked(userId);
+        List<AdSessionState> sessionsToAbort = new ArrayList<>();
+        for (IBinder sessionToken : serviceState.mSessionTokens) {
+            AdSessionState sessionState = userState.mAdSessionStateMap.get(sessionToken);
+            if (sessionState.mSession == null
+                    && (serviceState == null
+                    || sessionState.mAdServiceId.equals(serviceId))) {
+                sessionsToAbort.add(sessionState);
+            }
+        }
+        for (AdSessionState sessionState : sessionsToAbort) {
+            removeAdSessionStateLocked(sessionState.mSessionToken, sessionState.mUserId);
+            sendAdSessionTokenToClientLocked(sessionState.mClient,
+                    sessionState.mAdServiceId, null, null, sessionState.mSeq);
+        }
+        updateAdServiceConnectionLocked(serviceState.mComponent, userId);
+    }
+
+    @GuardedBy("mLock")
     private void updateServiceConnectionLocked(ComponentName component, int userId) {
         UserState userState = getOrCreateUserStateLocked(userId);
         ServiceState serviceState = userState.mServiceStateMap.get(component);
@@ -2284,10 +3192,64 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private void updateAdServiceConnectionLocked(ComponentName component, int userId) {
+        UserState userState = getOrCreateUserStateLocked(userId);
+        AdServiceState serviceState = userState.mAdServiceStateMap.get(component);
+        if (serviceState == null) {
+            return;
+        }
+        if (serviceState.mReconnecting) {
+            if (!serviceState.mSessionTokens.isEmpty()) {
+                // wait until all the sessions are removed.
+                return;
+            }
+            serviceState.mReconnecting = false;
+        }
+
+        boolean shouldBind = (!serviceState.mSessionTokens.isEmpty())
+                || (!serviceState.mPendingAppLinkCommand.isEmpty());
+
+        if (serviceState.mService == null && shouldBind) {
+            // This means that the service is not yet connected but its state indicates that we
+            // have pending requests. Then, connect the service.
+            if (serviceState.mBound) {
+                // We have already bound to the service so we don't try to bind again until after we
+                // unbind later on.
+                return;
+            }
+            if (DEBUG) {
+                Slogf.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
+            }
+
+            Intent i = new Intent(TvAdService.SERVICE_INTERFACE).setComponent(component);
+            serviceState.mBound = mContext.bindServiceAsUser(
+                    i, serviceState.mConnection,
+                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
+                    new UserHandle(userId));
+        } else if (serviceState.mService != null && !shouldBind) {
+            // This means that the service is already connected but its state indicates that we have
+            // nothing to do with it. Then, disconnect the service.
+            if (DEBUG) {
+                Slogf.d(TAG, "unbindService(service=" + component + ")");
+            }
+            mContext.unbindService(serviceState.mConnection);
+            userState.mAdServiceStateMap.remove(component);
+        }
+    }
+
     private static final class UserState {
         private final int mUserId;
+        // A mapping from the TV AD service ID to its TvAdServiceState.
+        private Map<String, TvAdServiceState> mAdMap = new HashMap<>();
+        // A mapping from the name of a TV Interactive App service to its state.
+        private final Map<ComponentName, AdServiceState> mAdServiceStateMap = new HashMap<>();
+        // A mapping from the token of a TV Interactive App session to its state.
+        private final Map<IBinder, AdSessionState> mAdSessionStateMap = new HashMap<>();
         // A mapping from the TV Interactive App ID to its TvInteractiveAppState.
         private Map<String, TvInteractiveAppState> mIAppMap = new HashMap<>();
+        // A mapping from the TV AD service ID to its TvAdServiceState.
+        private Map<String, TvAdServiceState> mAdServiceMap = new HashMap<>();
         // A mapping from the token of a client to its state.
         private final Map<IBinder, ClientState> mClientStateMap = new HashMap<>();
         // A mapping from the name of a TV Interactive App service to its state.
@@ -2299,6 +3261,8 @@
         private final Set<String> mPackageSet = new HashSet<>();
         // A list of all app link infos.
         private final List<AppLinkInfo> mAppLinkInfoList = new ArrayList<>();
+        private final RemoteCallbackList<ITvAdManagerCallback> mAdCallbacks =
+                new RemoteCallbackList<>();
 
         // A list of callbacks.
         private final RemoteCallbackList<ITvInteractiveAppManagerCallback> mCallbacks =
@@ -2317,7 +3281,16 @@
         private int mIAppNumber;
     }
 
+    private static final class TvAdServiceState {
+        private String mAdServiceId;
+        private ComponentName mComponentName;
+        private TvAdServiceInfo mInfo;
+        private int mUid;
+        private int mAdNumber;
+    }
+
     private final class SessionState implements IBinder.DeathRecipient {
+        // TODO: rename SessionState and reorganize classes / methods of this file
         private final IBinder mSessionToken;
         private ITvInteractiveAppSession mSession;
         private final String mIAppServiceId;
@@ -2359,6 +3332,49 @@
         }
     }
 
+    private final class AdSessionState implements IBinder.DeathRecipient {
+        private final IBinder mSessionToken;
+        private ITvAdSession mSession;
+        private final String mAdServiceId;
+
+        private final String mType;
+        private final ITvAdClient mClient;
+        private final int mSeq;
+        private final ComponentName mComponent;
+
+        // The UID of the application that created the session.
+        // The application is usually the TV app.
+        private final int mCallingUid;
+
+        // The PID of the application that created the session.
+        // The application is usually the TV app.
+        private final int mCallingPid;
+
+        private final int mUserId;
+
+        private AdSessionState(IBinder sessionToken, String serviceId, String type,
+                ComponentName componentName, ITvAdClient client, int seq,
+                int callingUid, int callingPid, int userId) {
+            mSessionToken = sessionToken;
+            mAdServiceId = serviceId;
+            mType = type;
+            mComponent = componentName;
+            mClient = client;
+            mSeq = seq;
+            mCallingUid = callingUid;
+            mCallingPid = callingPid;
+            mUserId = userId;
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (mLock) {
+                mSession = null;
+                clearAdSessionAndNotifyClientLocked(this);
+            }
+        }
+    }
+
     private final class ClientState implements IBinder.DeathRecipient {
         private final List<IBinder> mSessionTokens = new ArrayList<>();
 
@@ -2429,6 +3445,29 @@
         }
     }
 
+    private final class AdServiceState {
+        private final List<IBinder> mSessionTokens = new ArrayList<>();
+        private final ServiceConnection mConnection;
+        private final ComponentName mComponent;
+        private final String mAdServiceId;
+        private final List<Bundle> mPendingAppLinkCommand = new ArrayList<>();
+
+        private ITvAdService mService;
+        private AdServiceCallback mCallback;
+        private boolean mBound;
+        private boolean mReconnecting;
+
+        private AdServiceState(ComponentName component, String tasId, int userId) {
+            mComponent = component;
+            mConnection = new AdServiceConnection(component, userId);
+            mAdServiceId = tasId;
+        }
+
+        private void addPendingAppLinkCommand(Bundle command) {
+            mPendingAppLinkCommand.add(command);
+        }
+    }
+
     private final class InteractiveAppServiceConnection implements ServiceConnection {
         private final ComponentName mComponent;
         private final int mUserId;
@@ -2542,6 +3581,98 @@
         }
     }
 
+    private final class AdServiceConnection implements ServiceConnection {
+        private final ComponentName mComponent;
+        private final int mUserId;
+
+        private AdServiceConnection(ComponentName component, int userId) {
+            mComponent = component;
+            mUserId = userId;
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName component, IBinder service) {
+            if (DEBUG) {
+                Slogf.d(TAG, "onServiceConnected(component=" + component + ")");
+            }
+            synchronized (mLock) {
+                UserState userState = getUserStateLocked(mUserId);
+                if (userState == null) {
+                    // The user was removed while connecting.
+                    mContext.unbindService(this);
+                    return;
+                }
+                AdServiceState serviceState = userState.mAdServiceStateMap.get(mComponent);
+                serviceState.mService = ITvAdService.Stub.asInterface(service);
+
+                // Register a callback, if we need to.
+                if (serviceState.mCallback == null) {
+                    serviceState.mCallback = new AdServiceCallback(mComponent, mUserId);
+                    try {
+                        serviceState.mService.registerCallback(serviceState.mCallback);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "error in registerCallback", e);
+                    }
+                }
+
+                if (!serviceState.mPendingAppLinkCommand.isEmpty()) {
+                    for (Iterator<Bundle> it = serviceState.mPendingAppLinkCommand.iterator();
+                            it.hasNext(); ) {
+                        Bundle command = it.next();
+                        final long identity = Binder.clearCallingIdentity();
+                        try {
+                            serviceState.mService.sendAppLinkCommand(command);
+                            it.remove();
+                        } catch (RemoteException e) {
+                            Slogf.e(TAG, "error in sendAppLinkCommand(" + command
+                                    + ") when onServiceConnected", e);
+                        } finally {
+                            Binder.restoreCallingIdentity(identity);
+                        }
+                    }
+                }
+
+                List<IBinder> tokensToBeRemoved = new ArrayList<>();
+
+                // And create sessions, if any.
+                for (IBinder sessionToken : serviceState.mSessionTokens) {
+                    if (!createAdSessionInternalLocked(
+                            serviceState.mService, sessionToken, mUserId)) {
+                        tokensToBeRemoved.add(sessionToken);
+                    }
+                }
+
+                for (IBinder sessionToken : tokensToBeRemoved) {
+                    removeAdSessionStateLocked(sessionToken, mUserId);
+                }
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName component) {
+            if (DEBUG) {
+                Slogf.d(TAG, "onServiceDisconnected(component=" + component + ")");
+            }
+            if (!mComponent.equals(component)) {
+                throw new IllegalArgumentException("Mismatched ComponentName: "
+                        + mComponent + " (expected), " + component + " (actual).");
+            }
+            synchronized (mLock) {
+                UserState userState = getOrCreateUserStateLocked(mUserId);
+                AdServiceState serviceState = userState.mAdServiceStateMap.get(mComponent);
+                if (serviceState != null) {
+                    serviceState.mReconnecting = true;
+                    serviceState.mBound = false;
+                    serviceState.mService = null;
+                    serviceState.mCallback = null;
+
+                    abortPendingCreateAdSessionRequestsLocked(serviceState, null, mUserId);
+                }
+            }
+        }
+    }
+
+
     private final class ServiceCallback extends ITvInteractiveAppServiceCallback.Stub {
         private final ComponentName mComponent;
         private final int mUserId;
@@ -2567,6 +3698,17 @@
         }
     }
 
+
+    private final class AdServiceCallback extends ITvAdServiceCallback.Stub {
+        private final ComponentName mComponent;
+        private final int mUserId;
+
+        AdServiceCallback(ComponentName component, int userId) {
+            mComponent = component;
+            mUserId = userId;
+        }
+    }
+
     private final class SessionCallback extends ITvInteractiveAppSessionCallback.Stub {
         private final SessionState mSessionState;
         private final InputChannel[] mInputChannels;
@@ -2798,6 +3940,23 @@
         }
 
         @Override
+        public void onRequestSelectedTrackInfo() {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onRequestSelectedTrackInfo");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onRequestSelectedTrackInfo(mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onRequestSelectedTrackInfo", e);
+                }
+            }
+        }
+
+        @Override
         public void onRequestCurrentTvInputId() {
             synchronized (mLock) {
                 if (DEBUG) {
@@ -2993,6 +4152,43 @@
         }
 
         @Override
+        public void onRequestSigning2(String id, String algorithm, String host,
+                int port, byte[] data) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onRequestSigning");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onRequestSigning2(
+                            id, algorithm, host, port, data, mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onRequestSigning", e);
+                }
+            }
+        }
+
+        @Override
+        public void onRequestCertificate(String host, int port) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onRequestCertificate");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onRequestCertificate(host, port, mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onRequestCertificate", e);
+                }
+            }
+        }
+
+
+        @Override
         public void onAdRequest(AdRequest request) {
             synchronized (mLock) {
                 if (DEBUG) {
@@ -3110,6 +4306,85 @@
         }
     }
 
+    private final class AdSessionCallback extends ITvAdSessionCallback.Stub {
+        private final AdSessionState mSessionState;
+        private final InputChannel[] mInputChannels;
+
+        AdSessionCallback(AdSessionState sessionState, InputChannel[] channels) {
+            mSessionState = sessionState;
+            mInputChannels = channels;
+        }
+
+        @Override
+        public void onSessionCreated(ITvAdSession session) {
+            if (DEBUG) {
+                Slogf.d(TAG, "onSessionCreated(adServiceId="
+                        + mSessionState.mAdServiceId + ")");
+            }
+            synchronized (mLock) {
+                mSessionState.mSession = session;
+                if (session != null && addAdSessionTokenToClientStateLocked(session)) {
+                    sendAdSessionTokenToClientLocked(
+                            mSessionState.mClient,
+                            mSessionState.mAdServiceId,
+                            mSessionState.mSessionToken,
+                            mInputChannels[0],
+                            mSessionState.mSeq);
+                } else {
+                    removeAdSessionStateLocked(mSessionState.mSessionToken, mSessionState.mUserId);
+                    sendAdSessionTokenToClientLocked(mSessionState.mClient,
+                            mSessionState.mAdServiceId, null, null, mSessionState.mSeq);
+                }
+                mInputChannels[0].dispose();
+            }
+        }
+
+        @Override
+        public void onLayoutSurface(int left, int top, int right, int bottom) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top
+                            + ", right=" + right + ", bottom=" + bottom + ",)");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onLayoutSurface(left, top, right, bottom,
+                            mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onLayoutSurface", e);
+                }
+            }
+        }
+
+        @GuardedBy("mLock")
+        private boolean addAdSessionTokenToClientStateLocked(ITvAdSession session) {
+            try {
+                session.asBinder().linkToDeath(mSessionState, 0);
+            } catch (RemoteException e) {
+                Slogf.e(TAG, "session process has already died", e);
+                return false;
+            }
+
+            IBinder clientToken = mSessionState.mClient.asBinder();
+            UserState userState = getOrCreateUserStateLocked(mSessionState.mUserId);
+            ClientState clientState = userState.mClientStateMap.get(clientToken);
+            if (clientState == null) {
+                clientState = new ClientState(clientToken, mSessionState.mUserId);
+                try {
+                    clientToken.linkToDeath(clientState, 0);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "client process has already died", e);
+                    return false;
+                }
+                userState.mClientStateMap.put(clientToken, clientState);
+            }
+            clientState.mSessionTokens.add(mSessionState.mSessionToken);
+            return true;
+        }
+    }
+
     private static class SessionNotFoundException extends IllegalArgumentException {
         SessionNotFoundException(String name) {
             super(name);
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
index e54b40e..03c75e0 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
@@ -37,7 +37,17 @@
     void revokeUriPermission(String targetPackage, int callingUid,
             GrantUri grantUri, final int modeFlags);
 
-    boolean checkUriPermission(GrantUri grantUri, int uid, final int modeFlags);
+    /**
+     * Check if the uid has permission to the URI in grantUri.
+     *
+     * @param isFullAccessForContentUri If true, the URI has to be a content URI
+     *                                  and the method will consider full access.
+     *                                  Otherwise, the method will only consider
+     *                                  URI grants.
+     */
+    boolean checkUriPermission(GrantUri grantUri, int uid, int modeFlags,
+            boolean isFullAccessForContentUri);
+
     int checkGrantUriPermission(
             int callingUid, String targetPkg, Uri uri, int modeFlags, int userId);
 
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index e501b9d..ce2cbed 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -25,6 +25,7 @@
 import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
 import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
@@ -1103,7 +1104,8 @@
      */
     private int checkGrantUriPermissionUnlocked(int callingUid, String targetPkg, GrantUri grantUri,
             int modeFlags, int lastTargetUid) {
-        if (!Intent.isAccessUriMode(modeFlags)) {
+        if (!isContentUriWithAccessModeFlags(grantUri, modeFlags,
+                /* logAction */ "grant URI permission")) {
             return -1;
         }
 
@@ -1111,12 +1113,6 @@
             if (DEBUG) Slog.v(TAG, "Checking grant " + targetPkg + " permission to " + grantUri);
         }
 
-        // If this is not a content: uri, we can't do anything with it.
-        if (!ContentResolver.SCHEME_CONTENT.equals(grantUri.uri.getScheme())) {
-            if (DEBUG) Slog.v(TAG, "Can't grant URI permission for non-content URI: " + grantUri);
-            return -1;
-        }
-
         // Bail early if system is trying to hand out permissions directly; it
         // must always grant permissions on behalf of someone explicit.
         final int callingAppId = UserHandle.getAppId(callingUid);
@@ -1137,7 +1133,7 @@
 
         final String authority = grantUri.uri.getAuthority();
         final ProviderInfo pi = getProviderInfo(authority, grantUri.sourceUserId,
-                MATCH_DEBUG_TRIAGED_MISSING, callingUid);
+                MATCH_DIRECT_BOOT_AUTO, callingUid);
         if (pi == null) {
             Slog.w(TAG, "No content provider found for permission check: " +
                     grantUri.uri.toSafeString());
@@ -1285,6 +1281,65 @@
         return targetUid;
     }
 
+    private boolean isContentUriWithAccessModeFlags(GrantUri grantUri, int modeFlags,
+            String logAction) {
+        if (!Intent.isAccessUriMode(modeFlags)) {
+            if (DEBUG) Slog.v(TAG, "Mode flags are not access URI mode flags: " + modeFlags);
+            return false;
+        }
+
+        if (!ContentResolver.SCHEME_CONTENT.equals(grantUri.uri.getScheme())) {
+            if (DEBUG) {
+                Slog.v(TAG, "Can't " + logAction + " on non-content URI: " + grantUri);
+            }
+            return false;
+        }
+
+        return true;
+    }
+
+    /** Check if the uid has permission to the content URI in grantUri. */
+    private boolean checkContentUriPermissionFullUnlocked(GrantUri grantUri, int uid,
+            int modeFlags) {
+        if (uid < 0) {
+            throw new IllegalArgumentException("Uid must be positive for the content URI "
+                    + "permission check of " + grantUri.uri.toSafeString());
+        }
+
+        if (!isContentUriWithAccessModeFlags(grantUri, modeFlags,
+                /* logAction */ "check content URI permission")) {
+            throw new IllegalArgumentException("The URI must be a content URI and the mode "
+                    + "flags must be at least read and/or write for the content URI permission "
+                    + "check of " + grantUri.uri.toSafeString());
+        }
+
+        final int appId = UserHandle.getAppId(uid);
+        if ((appId == SYSTEM_UID) || (appId == ROOT_UID)) {
+            return true;
+        }
+
+        // Retrieve the URI's content provider
+        final String authority = grantUri.uri.getAuthority();
+        ProviderInfo pi = getProviderInfo(authority, grantUri.sourceUserId, MATCH_DIRECT_BOOT_AUTO,
+                uid);
+
+        if (pi == null) {
+            Slog.w(TAG, "No content provider found for content URI permission check: "
+                    + grantUri.uri.toSafeString());
+            return false;
+        }
+
+        // Check if it has general permission to the URI's content provider
+        if (checkHoldingPermissionsUnlocked(pi, grantUri, uid, modeFlags)) {
+            return true;
+        }
+
+        // Check if it has explicitly granted permissions to the URI
+        synchronized (mLock) {
+            return checkUriPermissionLocked(grantUri, uid, modeFlags);
+        }
+    }
+
     /**
      * @param userId The userId in which the uri is to be resolved.
      */
@@ -1482,7 +1537,12 @@
         }
 
         @Override
-        public boolean checkUriPermission(GrantUri grantUri, int uid, int modeFlags) {
+        public boolean checkUriPermission(GrantUri grantUri, int uid, int modeFlags,
+                boolean isFullAccessForContentUri) {
+            if (isFullAccessForContentUri) {
+                return UriGrantsManagerService.this.checkContentUriPermissionFullUnlocked(grantUri,
+                        uid, modeFlags);
+            }
             synchronized (mLock) {
                 return UriGrantsManagerService.this.checkUriPermissionLocked(grantUri, uid,
                         modeFlags);
diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
index e3aba0f..743005a 100644
--- a/services/core/java/com/android/server/utils/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -96,22 +96,16 @@
     private static boolean DEBUG = false;
 
     /**
-     * The trace tag.
+     * The trace tag is the same usd by ActivityManager.
      */
     private static final long TRACE_TAG = Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
     /**
-     * Enable tracing from the time a timer expires until it is accepted or discarded.  This is
-     * used to diagnose long latencies in the client.
-     */
-    private static final boolean ENABLE_TRACING = false;
-
-    /**
      * Return true if the feature is enabled.  By default, the value is take from the Flags class
      * but it can be changed for local testing.
      */
     private static boolean anrTimerServiceEnabled() {
-        return Flags.anrTimerServiceEnabled();
+        return Flags.anrTimerService();
     }
 
     /**
@@ -320,24 +314,21 @@
     }
 
     /**
-     * Start a trace on the timer.  The trace is laid down in the AnrTimerTrack.
+     * Generate a trace point with full timer information.  The meaning of milliseconds depends on
+     * the caller.
      */
-    private void traceBegin(int timerId, int pid, int uid, String what) {
-        if (ENABLE_TRACING) {
-            final String label = formatSimple("%s(%d,%d,%s)", what, pid, uid, mLabel);
-            final int cookie = timerId;
-            Trace.asyncTraceForTrackBegin(TRACE_TAG, TRACK, label, cookie);
-        }
+    private void trace(String op, int timerId, int pid, int uid, long milliseconds) {
+        final String label =
+                formatSimple("%s(%d,%d,%d,%s,%d)", op, timerId, pid, uid, mLabel, milliseconds);
+        Trace.instantForTrack(TRACE_TAG, TRACK, label);
     }
 
     /**
-     * End a trace on the timer.
+     * Generate a trace point with just the timer ID.
      */
-    private void traceEnd(int timerId) {
-        if (ENABLE_TRACING) {
-            final int cookie = timerId;
-            Trace.asyncTraceForTrackEnd(TRACE_TAG, TRACK, cookie);
-        }
+    private void trace(String op, int timerId) {
+        final String label = formatSimple("%s(%d)", op, timerId);
+        Trace.instantForTrack(TRACE_TAG, TRACK, label);
     }
 
     /**
@@ -492,7 +483,7 @@
                     return false;
                 }
                 nativeAnrTimerAccept(mNative, timer);
-                traceEnd(timer);
+                trace("accept", timer);
                 return true;
             }
         }
@@ -511,7 +502,7 @@
                     return false;
                 }
                 nativeAnrTimerDiscard(mNative, timer);
-                traceEnd(timer);
+                trace("discard", timer);
                 return true;
             }
         }
@@ -629,13 +620,18 @@
     }
 
     /**
-     * The notifier that a timer has fired.  The timerId and original pid/uid are supplied.  This
-     * method is called from native code.  This method takes mLock so that a timer cannot expire
-     * in the middle of another operation (like start or cancel).
+     * The notifier that a timer has fired.  The timerId and original pid/uid are supplied.  The
+     * elapsed time is the actual time since the timer was scheduled, which may be different from
+     * the original timeout if the timer was extended or if other delays occurred. This method
+     * takes mLock so that a timer cannot expire in the middle of another operation (like start or
+     * cancel).
+     *
+     * This method is called from native code.  The function must return true if the expiration
+     * message is delivered to the upper layers and false if it could not be delivered.
      */
     @Keep
-    private boolean expire(int timerId, int pid, int uid) {
-        traceBegin(timerId, pid, uid, "expired");
+    private boolean expire(int timerId, int pid, int uid, long elapsedMs) {
+        trace("expired", timerId, pid, uid, elapsedMs);
         V arg = null;
         synchronized (mLock) {
             arg = mTimerArgMap.get(timerId);
@@ -815,9 +811,4 @@
 
     /** Prod the native library to log a few statistics. */
     private static native void nativeAnrTimerDump(long service, boolean verbose);
-
-    // This is not a native method but it is a native interface, in the sense that it is called from
-    // the native layer to report timer expiration.  The function must return true if the expiration
-    // message is delivered to the upper layers and false if it could not be delivered.
-    // private boolean expire(int timerId, int pid, int uid);
 }
diff --git a/services/core/java/com/android/server/utils/flags.aconfig b/services/core/java/com/android/server/utils/flags.aconfig
index 489e21a..163116b 100644
--- a/services/core/java/com/android/server/utils/flags.aconfig
+++ b/services/core/java/com/android/server/utils/flags.aconfig
@@ -1,7 +1,7 @@
 package: "com.android.server.utils"
 
 flag {
-     name: "anr_timer_service_enabled"
+     name: "anr_timer_service"
      namespace: "system_performance"
      is_fixed_read_only: true
      description: "Feature flag for the ANR timer service"
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index 9213d96..ed04e5f 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -34,6 +34,7 @@
     @NonNull private final Looper mLooper;
     @NonNull private final VcnNetworkProvider mVcnNetworkProvider;
     @NonNull private final FeatureFlags mFeatureFlags;
+    @NonNull private final com.android.net.flags.FeatureFlags mCoreNetFeatureFlags;
     private final boolean mIsInTestMode;
 
     public VcnContext(
@@ -48,6 +49,7 @@
 
         // Auto-generated class
         mFeatureFlags = new FeatureFlagsImpl();
+        mCoreNetFeatureFlags = new com.android.net.flags.FeatureFlagsImpl();
     }
 
     @NonNull
@@ -69,11 +71,23 @@
         return mIsInTestMode;
     }
 
+    public boolean isFlagNetworkMetricMonitorEnabled() {
+        return mFeatureFlags.networkMetricMonitor();
+    }
+
+    public boolean isFlagIpSecTransformStateEnabled() {
+        return mCoreNetFeatureFlags.ipsecTransformState();
+    }
+
     @NonNull
     public FeatureFlags getFeatureFlags() {
         return mFeatureFlags;
     }
 
+    public boolean isFlagSafeModeTimeoutConfigEnabled() {
+        return mFeatureFlags.safeModeTimeoutConfig();
+    }
+
     /**
      * Verifies that the caller is running on the VcnContext Thread.
      *
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 54c97dd..3094b18 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -915,9 +915,11 @@
             // TODO(b/180132994): explore safely removing this Thread check
             mVcnContext.ensureRunningOnLooperThread();
 
-            logInfo(
-                    "Selected underlying network changed: "
-                            + (underlying == null ? null : underlying.network));
+            if (!UnderlyingNetworkRecord.isSameNetwork(mUnderlying, underlying)) {
+                logInfo(
+                        "Selected underlying network changed: "
+                                + (underlying == null ? null : underlying.network));
+            }
 
             // TODO(b/179091925): Move the delayed-message handling to BaseState
 
@@ -1242,9 +1244,28 @@
                 createScheduledAlarm(
                         SAFEMODE_TIMEOUT_ALARM,
                         delayedMessage,
-                        mVcnContext.isInTestMode()
-                                ? TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS_TEST_MODE)
-                                : TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
+                        getSafeModeTimeoutMs(mVcnContext, mLastSnapshot, mSubscriptionGroup));
+    }
+
+    /** Gets the safe mode timeout */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static long getSafeModeTimeoutMs(
+            VcnContext vcnContext, TelephonySubscriptionSnapshot snapshot, ParcelUuid subGrp) {
+        final int defaultSeconds =
+                vcnContext.isInTestMode()
+                        ? SAFEMODE_TIMEOUT_SECONDS_TEST_MODE
+                        : SAFEMODE_TIMEOUT_SECONDS;
+
+        final PersistableBundleWrapper carrierConfig = snapshot.getCarrierConfigForSubGrp(subGrp);
+        int resultSeconds = defaultSeconds;
+
+        if (vcnContext.isFlagSafeModeTimeoutConfigEnabled() && carrierConfig != null) {
+            resultSeconds =
+                    carrierConfig.getInt(
+                            VcnManager.VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY, defaultSeconds);
+        }
+
+        return TimeUnit.SECONDS.toMillis(resultSeconds);
     }
 
     private void cancelSafeModeAlarm() {
@@ -1889,6 +1910,12 @@
                 // Transforms do not need to be persisted; the IkeSession will keep them alive
                 mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform);
 
+                if (direction == IpSecManager.DIRECTION_IN
+                        && mVcnContext.isFlagNetworkMetricMonitorEnabled()
+                        && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+                    mUnderlyingNetworkController.updateInboundTransform(mUnderlying, transform);
+                }
+
                 // For inbound transforms, additionally allow forwarded traffic to bridge to DUN (as
                 // needed)
                 final Set<Integer> exposedCaps = mConnectionConfig.getAllExposedCapabilities();
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
new file mode 100644
index 0000000..5f4852f
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vcn.routeselection;
+
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.IpSecTransformState;
+import android.net.Network;
+import android.net.vcn.VcnManager;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.OutcomeReceiver;
+import android.os.PowerManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.server.vcn.VcnContext;
+
+import java.util.BitSet;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * IpSecPacketLossDetector is responsible for continuously monitoring IPsec packet loss
+ *
+ * <p>When the packet loss rate surpass the threshold, IpSecPacketLossDetector will report it to the
+ * caller
+ *
+ * <p>IpSecPacketLossDetector will start monitoring when the network being monitored is selected AND
+ * an inbound IpSecTransform has been applied to this network.
+ *
+ * <p>This class is flag gated by "network_metric_monitor" and "ipsec_tramsform_state"
+ */
+public class IpSecPacketLossDetector extends NetworkMetricMonitor {
+    private static final String TAG = IpSecPacketLossDetector.class.getSimpleName();
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int PACKET_LOSS_UNAVALAIBLE = -1;
+
+    // For VoIP, losses between 5% and 10% of the total packet stream will affect the quality
+    // significantly (as per "Computer Networking for LANS to WANS: Hardware, Software and
+    // Security"). For audio and video streaming, above 10-12% packet loss is unacceptable (as per
+    // "ICTP-SDU: About PingER"). Thus choose 12% as a conservative default threshold to declare a
+    // validation failure.
+    private static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT = 12;
+
+    private static final int POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT = 20;
+
+    private long mPollIpSecStateIntervalMs;
+    private final int mPacketLossRatePercentThreshold;
+
+    @NonNull private final Handler mHandler;
+    @NonNull private final PowerManager mPowerManager;
+    @NonNull private final Object mCancellationToken = new Object();
+    @NonNull private final PacketLossCalculator mPacketLossCalculator;
+
+    @Nullable private IpSecTransformWrapper mInboundTransform;
+    @Nullable private IpSecTransformState mLastIpSecTransformState;
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public IpSecPacketLossDetector(
+            @NonNull VcnContext vcnContext,
+            @NonNull Network network,
+            @Nullable PersistableBundleWrapper carrierConfig,
+            @NonNull NetworkMetricMonitorCallback callback,
+            @NonNull Dependencies deps)
+            throws IllegalAccessException {
+        super(vcnContext, network, carrierConfig, callback);
+
+        Objects.requireNonNull(deps, "Missing deps");
+
+        if (!vcnContext.isFlagIpSecTransformStateEnabled()) {
+            // Caller error
+            logWtf("ipsecTransformState flag disabled");
+            throw new IllegalAccessException("ipsecTransformState flag disabled");
+        }
+
+        mHandler = new Handler(getVcnContext().getLooper());
+
+        mPowerManager = getVcnContext().getContext().getSystemService(PowerManager.class);
+
+        mPacketLossCalculator = deps.getPacketLossCalculator();
+
+        mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
+        mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
+
+        // Register for system broadcasts to monitor idle mode change
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+        getVcnContext()
+                .getContext()
+                .registerReceiver(
+                        new BroadcastReceiver() {
+                            @Override
+                            public void onReceive(Context context, Intent intent) {
+                                if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(
+                                                intent.getAction())
+                                        && mPowerManager.isDeviceIdleMode()) {
+                                    mLastIpSecTransformState = null;
+                                }
+                            }
+                        },
+                        intentFilter,
+                        null /* broadcastPermission not required */,
+                        mHandler);
+    }
+
+    public IpSecPacketLossDetector(
+            @NonNull VcnContext vcnContext,
+            @NonNull Network network,
+            @Nullable PersistableBundleWrapper carrierConfig,
+            @NonNull NetworkMetricMonitorCallback callback)
+            throws IllegalAccessException {
+        this(vcnContext, network, carrierConfig, callback, new Dependencies());
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class Dependencies {
+        public PacketLossCalculator getPacketLossCalculator() {
+            return new PacketLossCalculator();
+        }
+    }
+
+    private static long getPollIpSecStateIntervalMs(
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        final int seconds;
+
+        if (carrierConfig != null) {
+            seconds =
+                    carrierConfig.getInt(
+                            VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY,
+                            POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT);
+        } else {
+            seconds = POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT;
+        }
+
+        return TimeUnit.SECONDS.toMillis(seconds);
+    }
+
+    private static int getPacketLossRatePercentThreshold(
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        if (carrierConfig != null) {
+            return carrierConfig.getInt(
+                    VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY,
+                    IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT);
+        }
+        return IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT;
+    }
+
+    @Override
+    protected void onSelectedUnderlyingNetworkChanged() {
+        if (!isSelectedUnderlyingNetwork()) {
+            mInboundTransform = null;
+            stop();
+        }
+
+        // No action when the underlying network got selected. Wait for the inbound transform to
+        // start the monitor
+    }
+
+    @Override
+    public void setInboundTransformInternal(@NonNull IpSecTransformWrapper inboundTransform) {
+        Objects.requireNonNull(inboundTransform, "inboundTransform is null");
+
+        if (Objects.equals(inboundTransform, mInboundTransform)) {
+            return;
+        }
+
+        if (!isSelectedUnderlyingNetwork()) {
+            logWtf("setInboundTransform called but network not selected");
+            return;
+        }
+
+        // When multiple parallel inbound transforms are created, NetworkMetricMonitor will be
+        // enabled on the last one as a sample
+        mInboundTransform = inboundTransform;
+        start();
+    }
+
+    @Override
+    public void setCarrierConfig(@Nullable PersistableBundleWrapper carrierConfig) {
+        // The already scheduled event will not be affected. The followup events will be scheduled
+        // with the new interval
+        mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
+    }
+
+    @Override
+    protected void start() {
+        super.start();
+        clearTransformStateAndPollingEvents();
+        mHandler.postDelayed(new PollIpSecStateRunnable(), mCancellationToken, 0L);
+    }
+
+    @Override
+    public void stop() {
+        super.stop();
+        clearTransformStateAndPollingEvents();
+    }
+
+    private void clearTransformStateAndPollingEvents() {
+        mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
+        mLastIpSecTransformState = null;
+    }
+
+    @Override
+    public void close() {
+        super.close();
+
+        if (mInboundTransform != null) {
+            mInboundTransform.close();
+        }
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    @Nullable
+    public IpSecTransformState getLastTransformState() {
+        return mLastIpSecTransformState;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PROTECTED)
+    @Nullable
+    public IpSecTransformWrapper getInboundTransformInternal() {
+        return mInboundTransform;
+    }
+
+    private class PollIpSecStateRunnable implements Runnable {
+        @Override
+        public void run() {
+            if (!isStarted()) {
+                logWtf("Monitor stopped but PollIpSecStateRunnable not removed from Handler");
+                return;
+            }
+
+            getInboundTransformInternal()
+                    .getIpSecTransformState(
+                            new HandlerExecutor(mHandler), new IpSecTransformStateReceiver());
+
+            // Schedule for next poll
+            mHandler.postDelayed(
+                    new PollIpSecStateRunnable(), mCancellationToken, mPollIpSecStateIntervalMs);
+        }
+    }
+
+    private class IpSecTransformStateReceiver
+            implements OutcomeReceiver<IpSecTransformState, RuntimeException> {
+        @Override
+        public void onResult(@NonNull IpSecTransformState state) {
+            getVcnContext().ensureRunningOnLooperThread();
+
+            if (!isStarted()) {
+                return;
+            }
+
+            onIpSecTransformStateReceived(state);
+        }
+
+        @Override
+        public void onError(@NonNull RuntimeException error) {
+            getVcnContext().ensureRunningOnLooperThread();
+
+            // Nothing we can do here
+            logW("TransformStateReceiver#onError " + error.toString());
+        }
+    }
+
+    private void onIpSecTransformStateReceived(@NonNull IpSecTransformState state) {
+        if (mLastIpSecTransformState == null) {
+            // This is first time to poll the state
+            mLastIpSecTransformState = state;
+            return;
+        }
+
+        final int packetLossRate =
+                mPacketLossCalculator.getPacketLossRatePercentage(
+                        mLastIpSecTransformState, state, getLogPrefix());
+
+        if (packetLossRate == PACKET_LOSS_UNAVALAIBLE) {
+            return;
+        }
+
+        final String logMsg =
+                "packetLossRate: "
+                        + packetLossRate
+                        + "% in the past "
+                        + (state.getTimestamp() - mLastIpSecTransformState.getTimestamp())
+                        + "ms";
+
+        mLastIpSecTransformState = state;
+        if (packetLossRate < mPacketLossRatePercentThreshold) {
+            logV(logMsg);
+            onValidationResultReceivedInternal(false /* isFailed */);
+        } else {
+            logInfo(logMsg);
+            onValidationResultReceivedInternal(true /* isFailed */);
+        }
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class PacketLossCalculator {
+        /** Calculate the packet loss rate between two timestamps */
+        public int getPacketLossRatePercentage(
+                @NonNull IpSecTransformState oldState,
+                @NonNull IpSecTransformState newState,
+                String logPrefix) {
+            logVIpSecTransform("oldState", oldState, logPrefix);
+            logVIpSecTransform("newState", newState, logPrefix);
+
+            final int replayWindowSize = oldState.getReplayBitmap().length * 8;
+            final long oldSeqHi = oldState.getRxHighestSequenceNumber();
+            final long oldSeqLow = Math.max(0L, oldSeqHi - replayWindowSize + 1);
+            final long newSeqHi = newState.getRxHighestSequenceNumber();
+            final long newSeqLow = Math.max(0L, newSeqHi - replayWindowSize + 1);
+
+            if (oldSeqHi == newSeqHi || newSeqHi < replayWindowSize) {
+                // The replay window did not proceed and all packets might have been delivered out
+                // of order
+                return PACKET_LOSS_UNAVALAIBLE;
+            }
+
+            // Get the expected packet count by assuming there is no packet loss. In this case, SA
+            // should receive all packets whose sequence numbers are smaller than the lower bound of
+            // the replay window AND the packets received within the window.
+            // When the lower bound is 0, it's not possible to tell whether packet with seqNo 0 is
+            // received or not. For simplicity just assume that packet is received.
+            final long newExpectedPktCnt = newSeqLow + getPacketCntInReplayWindow(newState);
+            final long oldExpectedPktCnt = oldSeqLow + getPacketCntInReplayWindow(oldState);
+
+            final long expectedPktCntDiff = newExpectedPktCnt - oldExpectedPktCnt;
+            final long actualPktCntDiff = newState.getPacketCount() - oldState.getPacketCount();
+
+            logV(
+                    TAG,
+                    logPrefix
+                            + " expectedPktCntDiff: "
+                            + expectedPktCntDiff
+                            + " actualPktCntDiff: "
+                            + actualPktCntDiff);
+
+            if (expectedPktCntDiff < 0
+                    || expectedPktCntDiff == 0
+                    || actualPktCntDiff < 0
+                    || actualPktCntDiff > expectedPktCntDiff) {
+                logWtf(TAG, "Impossible values for expectedPktCntDiff or" + " actualPktCntDiff");
+                return PACKET_LOSS_UNAVALAIBLE;
+            }
+
+            return 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff);
+        }
+    }
+
+    private static void logVIpSecTransform(
+            String transformTag, IpSecTransformState state, String logPrefix) {
+        final String stateString =
+                " seqNo: "
+                        + state.getRxHighestSequenceNumber()
+                        + " | pktCnt: "
+                        + state.getPacketCount()
+                        + " | pktCntInWindow: "
+                        + getPacketCntInReplayWindow(state);
+        logV(TAG, logPrefix + " " + transformTag + stateString);
+    }
+
+    /** Get the number of received packets within the replay window */
+    private static long getPacketCntInReplayWindow(@NonNull IpSecTransformState state) {
+        return BitSet.valueOf(state.getReplayBitmap()).cardinality();
+    }
+}
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
new file mode 100644
index 0000000..a79f188
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vcn.routeselection;
+
+import static com.android.server.VcnManagementService.LOCAL_LOG;
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.IpSecTransform;
+import android.net.IpSecTransformState;
+import android.net.Network;
+import android.os.OutcomeReceiver;
+import android.util.CloseGuard;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.server.vcn.VcnContext;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * NetworkMetricMonitor is responsible for managing metric monitoring and tracking validation
+ * results.
+ *
+ * <p>This class is flag gated by "network_metric_monitor"
+ */
+public abstract class NetworkMetricMonitor implements AutoCloseable {
+    private static final String TAG = NetworkMetricMonitor.class.getSimpleName();
+
+    private static final boolean VDBG = false; // STOPSHIP: if true
+
+    @NonNull private final CloseGuard mCloseGuard = new CloseGuard();
+
+    @NonNull private final VcnContext mVcnContext;
+    @NonNull private final Network mNetwork;
+    @NonNull private final NetworkMetricMonitorCallback mCallback;
+
+    private boolean mIsSelectedUnderlyingNetwork;
+    private boolean mIsStarted;
+    private boolean mIsValidationFailed;
+
+    protected NetworkMetricMonitor(
+            @NonNull VcnContext vcnContext,
+            @NonNull Network network,
+            @Nullable PersistableBundleWrapper carrierConfig,
+            @NonNull NetworkMetricMonitorCallback callback)
+            throws IllegalAccessException {
+        if (!vcnContext.isFlagNetworkMetricMonitorEnabled()) {
+            // Caller error
+            logWtf("networkMetricMonitor flag disabled");
+            throw new IllegalAccessException("networkMetricMonitor flag disabled");
+        }
+
+        mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
+        mNetwork = Objects.requireNonNull(network, "Missing network");
+        mCallback = Objects.requireNonNull(callback, "Missing callback");
+
+        mIsSelectedUnderlyingNetwork = false;
+        mIsStarted = false;
+        mIsValidationFailed = false;
+    }
+
+    /** Callback to notify caller of the validation result */
+    public interface NetworkMetricMonitorCallback {
+        /** Called when there is a validation result is ready */
+        void onValidationResultReceived();
+    }
+
+    /**
+     * Start monitoring
+     *
+     * <p>This method might be called on a an already started monitor for updating monitor
+     * properties (e.g. IpSecTransform, carrier config)
+     *
+     * <p>Subclasses MUST call super.start() when overriding this method
+     */
+    protected void start() {
+        mIsStarted = true;
+    }
+
+    /**
+     * Stop monitoring
+     *
+     * <p>Subclasses MUST call super.stop() when overriding this method
+     */
+    public void stop() {
+        mIsValidationFailed = false;
+        mIsStarted = false;
+    }
+
+    /** Called by the subclasses when the validation result is ready */
+    protected void onValidationResultReceivedInternal(boolean isFailed) {
+        mIsValidationFailed = isFailed;
+        mCallback.onValidationResultReceived();
+    }
+
+    /** Called when the underlying network changes to selected or unselected */
+    protected abstract void onSelectedUnderlyingNetworkChanged();
+
+    /**
+     * Mark the network being monitored selected or unselected
+     *
+     * <p>Subclasses MUST call super when overriding this method
+     */
+    public void setIsSelectedUnderlyingNetwork(boolean isSelectedUnderlyingNetwork) {
+        if (mIsSelectedUnderlyingNetwork == isSelectedUnderlyingNetwork) {
+            return;
+        }
+
+        mIsSelectedUnderlyingNetwork = isSelectedUnderlyingNetwork;
+        onSelectedUnderlyingNetworkChanged();
+    }
+
+    /** Wrapper that allows injection for testing purposes */
+    @VisibleForTesting(visibility = Visibility.PROTECTED)
+    public static class IpSecTransformWrapper {
+        @NonNull public final IpSecTransform ipSecTransform;
+
+        public IpSecTransformWrapper(@NonNull IpSecTransform ipSecTransform) {
+            this.ipSecTransform = ipSecTransform;
+        }
+
+        /** Poll an IpSecTransformState */
+        public void getIpSecTransformState(
+                @NonNull Executor executor,
+                @NonNull OutcomeReceiver<IpSecTransformState, RuntimeException> callback) {
+            ipSecTransform.getIpSecTransformState(executor, callback);
+        }
+
+        /** Close this instance and release the underlying resources */
+        public void close() {
+            ipSecTransform.close();
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(ipSecTransform);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof IpSecTransformWrapper)) {
+                return false;
+            }
+
+            final IpSecTransformWrapper other = (IpSecTransformWrapper) o;
+
+            return Objects.equals(ipSecTransform, other.ipSecTransform);
+        }
+    }
+
+    /** Set the IpSecTransform that applied to the Network being monitored */
+    public void setInboundTransform(@NonNull IpSecTransform inTransform) {
+        setInboundTransformInternal(new IpSecTransformWrapper(inTransform));
+    }
+
+    /**
+     * Set the IpSecTransform that applied to the Network being monitored *
+     *
+     * <p>Subclasses MUST call super when overriding this method
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public void setInboundTransformInternal(@NonNull IpSecTransformWrapper inTransform) {
+        // Subclasses MUST override it if they care
+    }
+
+    /** Update the carrierconfig */
+    public void setCarrierConfig(@Nullable PersistableBundleWrapper carrierConfig) {
+        // Subclasses MUST override it if they care
+    }
+
+    public boolean isValidationFailed() {
+        return mIsValidationFailed;
+    }
+
+    public boolean isSelectedUnderlyingNetwork() {
+        return mIsSelectedUnderlyingNetwork;
+    }
+
+    public boolean isStarted() {
+        return mIsStarted;
+    }
+
+    @NonNull
+    public VcnContext getVcnContext() {
+        return mVcnContext;
+    }
+
+    // Override methods for AutoCloseable. Subclasses MUST call super when overriding this method
+    @Override
+    public void close() {
+        mCloseGuard.close();
+
+        stop();
+    }
+
+    // Override #finalize() to use closeGuard for flagging that #close() was not called
+    @SuppressWarnings("Finalize")
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private String getClassName() {
+        return this.getClass().getSimpleName();
+    }
+
+    protected String getLogPrefix() {
+        return " [Network " + mNetwork + "] ";
+    }
+
+    protected void logV(String msg) {
+        if (VDBG) {
+            Slog.v(getClassName(), getLogPrefix() + msg);
+            LOCAL_LOG.log("[VERBOSE ] " + getClassName() + getLogPrefix() + msg);
+        }
+    }
+
+    protected void logInfo(String msg) {
+        Slog.i(getClassName(), getLogPrefix() + msg);
+        LOCAL_LOG.log("[INFO ] " + getClassName() + getLogPrefix() + msg);
+    }
+
+    protected void logW(String msg) {
+        Slog.w(getClassName(), getLogPrefix() + msg);
+        LOCAL_LOG.log("[WARN ] " + getClassName() + getLogPrefix() + msg);
+    }
+
+    protected void logWtf(String msg) {
+        Slog.wtf(getClassName(), getLogPrefix() + msg);
+        LOCAL_LOG.log("[WTF ] " + getClassName() + getLogPrefix() + msg);
+    }
+
+    protected static void logV(String className, String msgWithPrefix) {
+        if (VDBG) {
+            Slog.wtf(className, msgWithPrefix);
+            LOCAL_LOG.log("[VERBOSE ] " + className + msgWithPrefix);
+        }
+    }
+
+    protected static void logWtf(String className, String msgWithPrefix) {
+        Slog.wtf(className, msgWithPrefix);
+        LOCAL_LOG.log("[WTF ] " + className + msgWithPrefix);
+    }
+}
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
index 7f129ea..d32e5cc 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
@@ -47,7 +47,6 @@
 
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
 
 /** @hide */
@@ -86,7 +85,6 @@
      * <p>VCN MUST never select a non-INTERNET network that are unvalidated or fail to match any
      * template as the underlying network.
      */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
     static final int PRIORITY_INVALID = -1;
 
     /** Gives networks a priority class, based on configured VcnGatewayConnectionConfig */
@@ -96,7 +94,7 @@
             List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
             ParcelUuid subscriptionGroup,
             TelephonySubscriptionSnapshot snapshot,
-            UnderlyingNetworkRecord currentlySelected,
+            boolean isSelected,
             PersistableBundleWrapper carrierConfig) {
         // mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED
 
@@ -118,7 +116,7 @@
                     networkRecord,
                     subscriptionGroup,
                     snapshot,
-                    currentlySelected,
+                    isSelected,
                     carrierConfig)) {
                 return priorityIndex;
             }
@@ -140,12 +138,9 @@
             UnderlyingNetworkRecord networkRecord,
             ParcelUuid subscriptionGroup,
             TelephonySubscriptionSnapshot snapshot,
-            UnderlyingNetworkRecord currentlySelected,
+            boolean isSelected,
             PersistableBundleWrapper carrierConfig) {
         final NetworkCapabilities caps = networkRecord.networkCapabilities;
-        final boolean isSelectedUnderlyingNetwork =
-                currentlySelected != null
-                        && Objects.equals(currentlySelected.network, networkRecord.network);
 
         final int meteredMatch = networkPriority.getMetered();
         final boolean isMetered = !caps.hasCapability(NET_CAPABILITY_NOT_METERED);
@@ -159,7 +154,7 @@
         if (caps.getLinkUpstreamBandwidthKbps() < networkPriority.getMinExitUpstreamBandwidthKbps()
                 || (caps.getLinkUpstreamBandwidthKbps()
                                 < networkPriority.getMinEntryUpstreamBandwidthKbps()
-                        && !isSelectedUnderlyingNetwork)) {
+                        && !isSelected)) {
             return false;
         }
 
@@ -167,7 +162,7 @@
                         < networkPriority.getMinExitDownstreamBandwidthKbps()
                 || (caps.getLinkDownstreamBandwidthKbps()
                                 < networkPriority.getMinEntryDownstreamBandwidthKbps()
-                        && !isSelectedUnderlyingNetwork)) {
+                        && !isSelected)) {
             return false;
         }
 
@@ -191,7 +186,7 @@
             return checkMatchesWifiPriorityRule(
                     (VcnWifiUnderlyingNetworkTemplate) networkPriority,
                     networkRecord,
-                    currentlySelected,
+                    isSelected,
                     carrierConfig);
         }
 
@@ -214,7 +209,7 @@
     public static boolean checkMatchesWifiPriorityRule(
             VcnWifiUnderlyingNetworkTemplate networkPriority,
             UnderlyingNetworkRecord networkRecord,
-            UnderlyingNetworkRecord currentlySelected,
+            boolean isSelected,
             PersistableBundleWrapper carrierConfig) {
         final NetworkCapabilities caps = networkRecord.networkCapabilities;
 
@@ -223,7 +218,7 @@
         }
 
         // TODO: Move the Network Quality check to the network metric monitor framework.
-        if (!isWifiRssiAcceptable(networkRecord, currentlySelected, carrierConfig)) {
+        if (!isWifiRssiAcceptable(networkRecord, isSelected, carrierConfig)) {
             return false;
         }
 
@@ -237,15 +232,11 @@
 
     private static boolean isWifiRssiAcceptable(
             UnderlyingNetworkRecord networkRecord,
-            UnderlyingNetworkRecord currentlySelected,
+            boolean isSelected,
             PersistableBundleWrapper carrierConfig) {
         final NetworkCapabilities caps = networkRecord.networkCapabilities;
-        final boolean isSelectedNetwork =
-                currentlySelected != null
-                        && networkRecord.network.equals(currentlySelected.network);
 
-        if (isSelectedNetwork
-                && caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)) {
+        if (isSelected && caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)) {
             return true;
         }
 
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
index 6afa795..3f8d39e 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -30,6 +30,7 @@
 import android.annotation.Nullable;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
+import android.net.IpSecTransform;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -48,9 +49,11 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.VcnContext;
+import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
 import com.android.server.vcn.util.LogUtils;
 
 import java.util.ArrayList;
@@ -83,6 +86,9 @@
     @NonNull private final TelephonyCallback mActiveDataSubIdListener =
             new VcnActiveDataSubscriptionIdListener();
 
+    private final Map<Network, UnderlyingNetworkEvaluator> mUnderlyingNetworkRecords =
+            new ArrayMap<>();
+
     @NonNull private final List<NetworkCallback> mCellBringupCallbacks = new ArrayList<>();
     @Nullable private NetworkCallback mWifiBringupCallback;
     @Nullable private NetworkCallback mWifiEntryRssiThresholdCallback;
@@ -105,7 +111,8 @@
         this(vcnContext, connectionConfig, subscriptionGroup, snapshot, cb, new Dependencies());
     }
 
-    private UnderlyingNetworkController(
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    UnderlyingNetworkController(
             @NonNull VcnContext vcnContext,
             @NonNull VcnGatewayConnectionConfig connectionConfig,
             @NonNull ParcelUuid subscriptionGroup,
@@ -197,6 +204,15 @@
         List<NetworkCallback> oldCellCallbacks = new ArrayList<>(mCellBringupCallbacks);
         mCellBringupCallbacks.clear();
 
+        if (mVcnContext.isFlagNetworkMetricMonitorEnabled()
+                && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+            for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+                evaluator.close();
+            }
+        }
+
+        mUnderlyingNetworkRecords.clear();
+
         // Register new callbacks. Make-before-break; always register new callbacks before removal
         // of old callbacks
         if (!mIsQuitting) {
@@ -395,15 +411,58 @@
         // Update carrier config
         mCarrierConfig = mLastSnapshot.getCarrierConfigForSubGrp(mSubscriptionGroup);
 
+        // Make sure all evaluators use the same updated TelephonySubscriptionSnapshot and carrier
+        // config to calculate their cached priority classes. For simplicity, the
+        // UnderlyingNetworkController does not listen for changes in VCN-related carrier config
+        // keys, and changes are applied at restart of the VcnGatewayConnection
+        for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+            evaluator.reevaluate(
+                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+                    mSubscriptionGroup,
+                    mLastSnapshot,
+                    mCarrierConfig);
+        }
+
         // Only trigger re-registration if subIds in this group have changed
         if (oldSnapshot
                 .getAllSubIdsInGroup(mSubscriptionGroup)
                 .equals(newSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))) {
+
+            if (mVcnContext.isFlagNetworkMetricMonitorEnabled()
+                    && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+                reevaluateNetworks();
+            }
             return;
         }
         registerOrUpdateNetworkRequests();
     }
 
+    /**
+     * Pass the IpSecTransform of the VCN to UnderlyingNetworkController for metric monitoring
+     *
+     * <p>Caller MUST call it when IpSecTransforms have been created for VCN creation or migration
+     */
+    public void updateInboundTransform(
+            @NonNull UnderlyingNetworkRecord currentNetwork, @NonNull IpSecTransform transform) {
+        if (!mVcnContext.isFlagNetworkMetricMonitorEnabled()
+                || !mVcnContext.isFlagIpSecTransformStateEnabled()) {
+            logWtf("#updateInboundTransform: unexpected call; flags missing");
+            return;
+        }
+
+        Objects.requireNonNull(currentNetwork, "currentNetwork is null");
+        Objects.requireNonNull(transform, "transform is null");
+
+        if (mCurrentRecord == null
+                || mRouteSelectionCallback == null
+                || !Objects.equals(currentNetwork.network, mCurrentRecord.network)) {
+            // The caller (VcnGatewayConnection) is out-of-dated. Ignore this call.
+            return;
+        }
+
+        mUnderlyingNetworkRecords.get(mCurrentRecord.network).setInboundTransform(transform);
+    }
+
     /** Tears down this Tracker, and releases all underlying network requests. */
     public void teardown() {
         mVcnContext.ensureRunningOnLooperThread();
@@ -418,32 +477,62 @@
                 .unregisterTelephonyCallback(mActiveDataSubIdListener);
     }
 
+    private TreeSet<UnderlyingNetworkEvaluator> getSortedUnderlyingNetworks() {
+        TreeSet<UnderlyingNetworkEvaluator> sorted =
+                new TreeSet<>(UnderlyingNetworkEvaluator.getComparator(mVcnContext));
+
+        for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+            if (evaluator.getPriorityClass() != NetworkPriorityClassifier.PRIORITY_INVALID) {
+                sorted.add(evaluator);
+            }
+        }
+
+        return sorted;
+    }
+
     private void reevaluateNetworks() {
         if (mIsQuitting || mRouteSelectionCallback == null) {
             return; // UnderlyingNetworkController has quit.
         }
 
-        TreeSet<UnderlyingNetworkRecord> sorted =
-                mRouteSelectionCallback.getSortedUnderlyingNetworks();
-        UnderlyingNetworkRecord candidate = sorted.isEmpty() ? null : sorted.first();
+        TreeSet<UnderlyingNetworkEvaluator> sorted = getSortedUnderlyingNetworks();
+
+        UnderlyingNetworkEvaluator candidateEvaluator = sorted.isEmpty() ? null : sorted.first();
+        UnderlyingNetworkRecord candidate =
+                candidateEvaluator == null ? null : candidateEvaluator.getNetworkRecord();
         if (Objects.equals(mCurrentRecord, candidate)) {
             return;
         }
 
         String allNetworkPriorities = "";
-        for (UnderlyingNetworkRecord record : sorted) {
+        for (UnderlyingNetworkEvaluator recordEvaluator : sorted) {
             if (!allNetworkPriorities.isEmpty()) {
                 allNetworkPriorities += ", ";
             }
-            allNetworkPriorities += record.network + ": " + record.priorityClass;
+            allNetworkPriorities +=
+                    recordEvaluator.getNetwork() + ": " + recordEvaluator.getPriorityClass();
         }
-        logInfo(
-                "Selected network changed to "
-                        + (candidate == null ? null : candidate.network)
-                        + ", selected from list: "
-                        + allNetworkPriorities);
+
+        if (!UnderlyingNetworkRecord.isSameNetwork(mCurrentRecord, candidate)) {
+            logInfo(
+                    "Selected network changed to "
+                            + (candidate == null ? null : candidate.network)
+                            + ", selected from list: "
+                            + allNetworkPriorities);
+        }
+
         mCurrentRecord = candidate;
         mCb.onSelectedUnderlyingNetworkChanged(mCurrentRecord);
+
+        // Need to update all evaluators to ensure the previously selected one is unselected
+        for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+            evaluator.setIsSelected(
+                    candidateEvaluator == evaluator,
+                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+                    mSubscriptionGroup,
+                    mLastSnapshot,
+                    mCarrierConfig);
+        }
     }
 
     /**
@@ -463,46 +552,32 @@
      */
     @VisibleForTesting
     class UnderlyingNetworkListener extends NetworkCallback {
-        private final Map<Network, UnderlyingNetworkRecord.Builder>
-                mUnderlyingNetworkRecordBuilders = new ArrayMap<>();
-
         UnderlyingNetworkListener() {
             super(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO);
         }
 
-        private TreeSet<UnderlyingNetworkRecord> getSortedUnderlyingNetworks() {
-            TreeSet<UnderlyingNetworkRecord> sorted =
-                    new TreeSet<>(UnderlyingNetworkRecord.getComparator());
-
-            for (UnderlyingNetworkRecord.Builder builder :
-                    mUnderlyingNetworkRecordBuilders.values()) {
-                if (builder.isValid()) {
-                    final UnderlyingNetworkRecord record =
-                            builder.build(
-                                    mVcnContext,
-                                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
-                                    mSubscriptionGroup,
-                                    mLastSnapshot,
-                                    mCurrentRecord,
-                                    mCarrierConfig);
-                    if (record.priorityClass != NetworkPriorityClassifier.PRIORITY_INVALID) {
-                        sorted.add(record);
-                    }
-                }
-            }
-
-            return sorted;
-        }
-
         @Override
         public void onAvailable(@NonNull Network network) {
-            mUnderlyingNetworkRecordBuilders.put(
-                    network, new UnderlyingNetworkRecord.Builder(network));
+            mUnderlyingNetworkRecords.put(
+                    network,
+                    mDeps.newUnderlyingNetworkEvaluator(
+                            mVcnContext,
+                            network,
+                            mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+                            mSubscriptionGroup,
+                            mLastSnapshot,
+                            mCarrierConfig,
+                            new NetworkEvaluatorCallbackImpl()));
         }
 
         @Override
         public void onLost(@NonNull Network network) {
-            mUnderlyingNetworkRecordBuilders.remove(network);
+            if (mVcnContext.isFlagNetworkMetricMonitorEnabled()
+                    && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+                mUnderlyingNetworkRecords.get(network).close();
+            }
+
+            mUnderlyingNetworkRecords.remove(network);
 
             reevaluateNetworks();
         }
@@ -510,15 +585,20 @@
         @Override
         public void onCapabilitiesChanged(
                 @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
-            final UnderlyingNetworkRecord.Builder builder =
-                    mUnderlyingNetworkRecordBuilders.get(network);
-            if (builder == null) {
+            final UnderlyingNetworkEvaluator evaluator = mUnderlyingNetworkRecords.get(network);
+            if (evaluator == null) {
                 logWtf("Got capabilities change for unknown key: " + network);
                 return;
             }
 
-            builder.setNetworkCapabilities(networkCapabilities);
-            if (builder.isValid()) {
+            evaluator.setNetworkCapabilities(
+                    networkCapabilities,
+                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+                    mSubscriptionGroup,
+                    mLastSnapshot,
+                    mCarrierConfig);
+
+            if (evaluator.isValid()) {
                 reevaluateNetworks();
             }
         }
@@ -526,35 +606,60 @@
         @Override
         public void onLinkPropertiesChanged(
                 @NonNull Network network, @NonNull LinkProperties linkProperties) {
-            final UnderlyingNetworkRecord.Builder builder =
-                    mUnderlyingNetworkRecordBuilders.get(network);
-            if (builder == null) {
+            final UnderlyingNetworkEvaluator evaluator = mUnderlyingNetworkRecords.get(network);
+            if (evaluator == null) {
                 logWtf("Got link properties change for unknown key: " + network);
                 return;
             }
 
-            builder.setLinkProperties(linkProperties);
-            if (builder.isValid()) {
+            evaluator.setLinkProperties(
+                    linkProperties,
+                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+                    mSubscriptionGroup,
+                    mLastSnapshot,
+                    mCarrierConfig);
+
+            if (evaluator.isValid()) {
                 reevaluateNetworks();
             }
         }
 
         @Override
         public void onBlockedStatusChanged(@NonNull Network network, boolean isBlocked) {
-            final UnderlyingNetworkRecord.Builder builder =
-                    mUnderlyingNetworkRecordBuilders.get(network);
-            if (builder == null) {
+            final UnderlyingNetworkEvaluator evaluator = mUnderlyingNetworkRecords.get(network);
+            if (evaluator == null) {
                 logWtf("Got blocked status change for unknown key: " + network);
                 return;
             }
 
-            builder.setIsBlocked(isBlocked);
-            if (builder.isValid()) {
+            evaluator.setIsBlocked(
+                    isBlocked,
+                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+                    mSubscriptionGroup,
+                    mLastSnapshot,
+                    mCarrierConfig);
+
+            if (evaluator.isValid()) {
                 reevaluateNetworks();
             }
         }
     }
 
+    @VisibleForTesting
+    class NetworkEvaluatorCallbackImpl implements NetworkEvaluatorCallback {
+        @Override
+        public void onEvaluationResultChanged() {
+            if (!mVcnContext.isFlagNetworkMetricMonitorEnabled()
+                    || !mVcnContext.isFlagIpSecTransformStateEnabled()) {
+                logWtf("#onEvaluationResultChanged: unexpected call; flags missing");
+                return;
+            }
+
+            mVcnContext.ensureRunningOnLooperThread();
+            reevaluateNetworks();
+        }
+    }
+
     private String getLogPrefix() {
         return "("
                 + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup)
@@ -614,16 +719,8 @@
         pw.println("Underlying networks:");
         pw.increaseIndent();
         if (mRouteSelectionCallback != null) {
-            for (UnderlyingNetworkRecord record :
-                    mRouteSelectionCallback.getSortedUnderlyingNetworks()) {
-                record.dump(
-                        mVcnContext,
-                        pw,
-                        mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
-                        mSubscriptionGroup,
-                        mLastSnapshot,
-                        mCurrentRecord,
-                        mCarrierConfig);
+            for (UnderlyingNetworkEvaluator recordEvaluator : getSortedUnderlyingNetworks()) {
+                recordEvaluator.dump(pw);
             }
         }
         pw.decreaseIndent();
@@ -653,5 +750,24 @@
                 @Nullable UnderlyingNetworkRecord underlyingNetworkRecord);
     }
 
-    private static class Dependencies {}
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class Dependencies {
+        public UnderlyingNetworkEvaluator newUnderlyingNetworkEvaluator(
+                @NonNull VcnContext vcnContext,
+                @NonNull Network network,
+                @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+                @NonNull ParcelUuid subscriptionGroup,
+                @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+                @Nullable PersistableBundleWrapper carrierConfig,
+                @NonNull NetworkEvaluatorCallback evaluatorCallback) {
+            return new UnderlyingNetworkEvaluator(
+                    vcnContext,
+                    network,
+                    underlyingNetworkTemplates,
+                    subscriptionGroup,
+                    lastSnapshot,
+                    carrierConfig,
+                    evaluatorCallback);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
new file mode 100644
index 0000000..2f4cf5e
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vcn.routeselection;
+
+import static com.android.server.VcnManagementService.LOCAL_LOG;
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.IpSecTransform;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.vcn.VcnManager;
+import android.net.vcn.VcnUnderlyingNetworkTemplate;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.VcnContext;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * UnderlyingNetworkEvaluator evaluates the quality and priority class of a network candidate for
+ * route selection.
+ *
+ * @hide
+ */
+public class UnderlyingNetworkEvaluator {
+    private static final String TAG = UnderlyingNetworkEvaluator.class.getSimpleName();
+
+    private static final int[] PENALTY_TIMEOUT_MINUTES_DEFAULT = new int[] {5};
+
+    @NonNull private final VcnContext mVcnContext;
+    @NonNull private final Handler mHandler;
+    @NonNull private final Object mCancellationToken = new Object();
+
+    @NonNull private final UnderlyingNetworkRecord.Builder mNetworkRecordBuilder;
+
+    @NonNull private final NetworkEvaluatorCallback mEvaluatorCallback;
+    @NonNull private final List<NetworkMetricMonitor> mMetricMonitors = new ArrayList<>();
+
+    @NonNull private final Dependencies mDependencies;
+
+    // TODO: Support back-off timeouts
+    private long mPenalizedTimeoutMs;
+
+    private boolean mIsSelected;
+    private boolean mIsPenalized;
+    private int mPriorityClass = NetworkPriorityClassifier.PRIORITY_INVALID;
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public UnderlyingNetworkEvaluator(
+            @NonNull VcnContext vcnContext,
+            @NonNull Network network,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig,
+            @NonNull NetworkEvaluatorCallback evaluatorCallback,
+            @NonNull Dependencies dependencies) {
+        mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
+        mHandler = new Handler(mVcnContext.getLooper());
+
+        mDependencies = Objects.requireNonNull(dependencies, "Missing dependencies");
+        mEvaluatorCallback = Objects.requireNonNull(evaluatorCallback, "Missing deps");
+
+        Objects.requireNonNull(underlyingNetworkTemplates, "Missing underlyingNetworkTemplates");
+        Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
+        Objects.requireNonNull(lastSnapshot, "Missing lastSnapshot");
+
+        mNetworkRecordBuilder =
+                new UnderlyingNetworkRecord.Builder(
+                        Objects.requireNonNull(network, "Missing network"));
+        mIsSelected = false;
+        mIsPenalized = false;
+        mPenalizedTimeoutMs = getPenaltyTimeoutMs(carrierConfig);
+
+        updatePriorityClass(
+                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+
+        if (isIpSecPacketLossDetectorEnabled()) {
+            try {
+                mMetricMonitors.add(
+                        mDependencies.newIpSecPacketLossDetector(
+                                mVcnContext,
+                                mNetworkRecordBuilder.getNetwork(),
+                                carrierConfig,
+                                new MetricMonitorCallbackImpl()));
+            } catch (IllegalAccessException e) {
+                // No action. Do not add anything to mMetricMonitors
+            }
+        }
+    }
+
+    public UnderlyingNetworkEvaluator(
+            @NonNull VcnContext vcnContext,
+            @NonNull Network network,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig,
+            @NonNull NetworkEvaluatorCallback evaluatorCallback) {
+        this(
+                vcnContext,
+                network,
+                underlyingNetworkTemplates,
+                subscriptionGroup,
+                lastSnapshot,
+                carrierConfig,
+                evaluatorCallback,
+                new Dependencies());
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class Dependencies {
+        /** Get an IpSecPacketLossDetector instance */
+        public IpSecPacketLossDetector newIpSecPacketLossDetector(
+                @NonNull VcnContext vcnContext,
+                @NonNull Network network,
+                @Nullable PersistableBundleWrapper carrierConfig,
+                @NonNull NetworkMetricMonitor.NetworkMetricMonitorCallback callback)
+                throws IllegalAccessException {
+            return new IpSecPacketLossDetector(vcnContext, network, carrierConfig, callback);
+        }
+    }
+
+    /** Callback to notify caller to reevaluate network selection */
+    public interface NetworkEvaluatorCallback {
+        /**
+         * Called when mIsPenalized changed
+         *
+         * <p>When receiving this call, UnderlyingNetworkController should reevaluate all network
+         * candidates for VCN underlying network selection
+         */
+        void onEvaluationResultChanged();
+    }
+
+    private class MetricMonitorCallbackImpl
+            implements NetworkMetricMonitor.NetworkMetricMonitorCallback {
+        public void onValidationResultReceived() {
+            mVcnContext.ensureRunningOnLooperThread();
+
+            handleValidationResult();
+        }
+    }
+
+    private void updatePriorityClass(
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        if (mNetworkRecordBuilder.isValid()) {
+            mPriorityClass =
+                    NetworkPriorityClassifier.calculatePriorityClass(
+                            mVcnContext,
+                            mNetworkRecordBuilder.build(),
+                            underlyingNetworkTemplates,
+                            subscriptionGroup,
+                            lastSnapshot,
+                            mIsSelected,
+                            carrierConfig);
+        } else {
+            mPriorityClass = NetworkPriorityClassifier.PRIORITY_INVALID;
+        }
+    }
+
+    private boolean isIpSecPacketLossDetectorEnabled() {
+        return isIpSecPacketLossDetectorEnabled(mVcnContext);
+    }
+
+    private static boolean isIpSecPacketLossDetectorEnabled(VcnContext vcnContext) {
+        return vcnContext.isFlagIpSecTransformStateEnabled()
+                && vcnContext.isFlagNetworkMetricMonitorEnabled();
+    }
+
+    /** Get the comparator for UnderlyingNetworkEvaluator */
+    public static Comparator<UnderlyingNetworkEvaluator> getComparator(VcnContext vcnContext) {
+        return (left, right) -> {
+            if (isIpSecPacketLossDetectorEnabled(vcnContext)) {
+                if (left.mIsPenalized != right.mIsPenalized) {
+                    // A penalized network should have lower priority which means a larger index
+                    return left.mIsPenalized ? 1 : -1;
+                }
+            }
+
+            final int leftIndex = left.mPriorityClass;
+            final int rightIndex = right.mPriorityClass;
+
+            // In the case of networks in the same priority class, prioritize based on other
+            // criteria (eg. actively selected network, link metrics, etc)
+            if (leftIndex == rightIndex) {
+                // TODO: Improve the strategy of network selection when both UnderlyingNetworkRecord
+                // fall into the same priority class.
+                if (left.mIsSelected) {
+                    return -1;
+                }
+                if (right.mIsSelected) {
+                    return 1;
+                }
+            }
+            return Integer.compare(leftIndex, rightIndex);
+        };
+    }
+
+    private static long getPenaltyTimeoutMs(@Nullable PersistableBundleWrapper carrierConfig) {
+        final int[] timeoutMinuteList;
+
+        if (carrierConfig != null) {
+            timeoutMinuteList =
+                    carrierConfig.getIntArray(
+                            VcnManager.VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY,
+                            PENALTY_TIMEOUT_MINUTES_DEFAULT);
+        } else {
+            timeoutMinuteList = PENALTY_TIMEOUT_MINUTES_DEFAULT;
+        }
+
+        // TODO: Add the support of back-off timeouts and return the full list
+        return TimeUnit.MINUTES.toMillis(timeoutMinuteList[0]);
+    }
+
+    private void handleValidationResult() {
+        final boolean wasPenalized = mIsPenalized;
+        mIsPenalized = false;
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            mIsPenalized |= monitor.isValidationFailed();
+        }
+
+        if (wasPenalized == mIsPenalized) {
+            return;
+        }
+
+        logInfo(
+                "#handleValidationResult: wasPenalized "
+                        + wasPenalized
+                        + " mIsPenalized "
+                        + mIsPenalized);
+
+        if (mIsPenalized) {
+            mHandler.postDelayed(
+                    new ExitPenaltyBoxRunnable(), mCancellationToken, mPenalizedTimeoutMs);
+        } else {
+            // Exit the penalty box
+            mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
+        }
+        mEvaluatorCallback.onEvaluationResultChanged();
+    }
+
+    public class ExitPenaltyBoxRunnable implements Runnable {
+        @Override
+        public void run() {
+            if (!mIsPenalized) {
+                logWtf("Evaluator not being penalized but ExitPenaltyBoxRunnable was scheduled");
+                return;
+            }
+
+            // TODO: There might be a future metric monitor (e.g. ping) that will require the
+            // validation to pass before exiting the penalty box.
+            mIsPenalized = false;
+            mEvaluatorCallback.onEvaluationResultChanged();
+        }
+    }
+
+    /** Set the NetworkCapabilities */
+    public void setNetworkCapabilities(
+            @NonNull NetworkCapabilities nc,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        mNetworkRecordBuilder.setNetworkCapabilities(nc);
+
+        updatePriorityClass(
+                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+    }
+
+    /** Set the LinkProperties */
+    public void setLinkProperties(
+            @NonNull LinkProperties lp,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        mNetworkRecordBuilder.setLinkProperties(lp);
+
+        updatePriorityClass(
+                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+    }
+
+    /** Set whether the network is blocked */
+    public void setIsBlocked(
+            boolean isBlocked,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        mNetworkRecordBuilder.setIsBlocked(isBlocked);
+
+        updatePriorityClass(
+                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+    }
+
+    /** Set whether the network is selected as VCN's underlying network */
+    public void setIsSelected(
+            boolean isSelected,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        mIsSelected = isSelected;
+
+        updatePriorityClass(
+                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            monitor.setIsSelectedUnderlyingNetwork(isSelected);
+        }
+    }
+
+    /**
+     * Update the last TelephonySubscriptionSnapshot and carrier config to reevaluate the network
+     */
+    public void reevaluate(
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        updatePriorityClass(
+                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+
+        // The already scheduled event will not be affected. The followup events will be scheduled
+        // with the new timeout
+        mPenalizedTimeoutMs = getPenaltyTimeoutMs(carrierConfig);
+
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            monitor.setCarrierConfig(carrierConfig);
+        }
+    }
+
+    /** Update the inbound IpSecTransform applied to the network */
+    public void setInboundTransform(@NonNull IpSecTransform transform) {
+        if (!mIsSelected) {
+            logWtf("setInboundTransform on an unselected evaluator");
+            return;
+        }
+
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            monitor.setInboundTransform(transform);
+        }
+    }
+
+    /** Close the evaluator and stop all the underlying network metric monitors */
+    public void close() {
+        mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
+
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            monitor.close();
+        }
+    }
+
+    /** Return whether this network evaluator is valid */
+    public boolean isValid() {
+        return mNetworkRecordBuilder.isValid();
+    }
+
+    /** Return the network */
+    public Network getNetwork() {
+        return mNetworkRecordBuilder.getNetwork();
+    }
+
+    /** Return the network record */
+    public UnderlyingNetworkRecord getNetworkRecord() {
+        return mNetworkRecordBuilder.build();
+    }
+
+    /** Return the priority class for network selection */
+    public int getPriorityClass() {
+        return mPriorityClass;
+    }
+
+    /** Return whether the network is being penalized */
+    public boolean isPenalized() {
+        return mIsPenalized;
+    }
+
+    /** Dump the information of this instance */
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("UnderlyingNetworkEvaluator:");
+        pw.increaseIndent();
+
+        if (mNetworkRecordBuilder.isValid()) {
+            getNetworkRecord().dump(pw);
+        } else {
+            pw.println(
+                    "UnderlyingNetworkRecord incomplete: mNetwork: "
+                            + mNetworkRecordBuilder.getNetwork());
+        }
+
+        pw.println("mIsSelected: " + mIsSelected);
+        pw.println("mPriorityClass: " + mPriorityClass);
+        pw.println("mIsPenalized: " + mIsPenalized);
+
+        pw.decreaseIndent();
+    }
+
+    private String getLogPrefix() {
+        return "[Network " + mNetworkRecordBuilder.getNetwork() + "] ";
+    }
+
+    private void logInfo(String msg) {
+        Slog.i(TAG, getLogPrefix() + msg);
+        LOCAL_LOG.log("[INFO ] " + TAG + getLogPrefix() + msg);
+    }
+
+    private void logWtf(String msg) {
+        Slog.wtf(TAG, getLogPrefix() + msg);
+        LOCAL_LOG.log("[WTF ] " + TAG + getLogPrefix() + msg);
+    }
+}
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
index aea9f4d..7ab8e55 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
@@ -16,24 +16,17 @@
 
 package com.android.server.vcn.routeselection;
 
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
-import android.net.vcn.VcnUnderlyingNetworkTemplate;
-import android.os.ParcelUuid;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
-import com.android.server.vcn.VcnContext;
 
-import java.util.Comparator;
-import java.util.List;
 import java.util.Objects;
 
 /**
@@ -46,54 +39,17 @@
     @NonNull public final NetworkCapabilities networkCapabilities;
     @NonNull public final LinkProperties linkProperties;
     public final boolean isBlocked;
-    public final boolean isSelected;
-    public final int priorityClass;
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     public UnderlyingNetworkRecord(
             @NonNull Network network,
             @NonNull NetworkCapabilities networkCapabilities,
             @NonNull LinkProperties linkProperties,
-            boolean isBlocked,
-            VcnContext vcnContext,
-            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
-            ParcelUuid subscriptionGroup,
-            TelephonySubscriptionSnapshot snapshot,
-            UnderlyingNetworkRecord currentlySelected,
-            PersistableBundleWrapper carrierConfig) {
+            boolean isBlocked) {
         this.network = network;
         this.networkCapabilities = networkCapabilities;
         this.linkProperties = linkProperties;
         this.isBlocked = isBlocked;
-
-        this.isSelected = isSelected(this.network, currentlySelected);
-
-        priorityClass =
-                NetworkPriorityClassifier.calculatePriorityClass(
-                        vcnContext,
-                        this,
-                        underlyingNetworkTemplates,
-                        subscriptionGroup,
-                        snapshot,
-                        currentlySelected,
-                        carrierConfig);
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public UnderlyingNetworkRecord(
-            @NonNull Network network,
-            @NonNull NetworkCapabilities networkCapabilities,
-            @NonNull LinkProperties linkProperties,
-            boolean isBlocked,
-            boolean isSelected,
-            int priorityClass) {
-        this.network = network;
-        this.networkCapabilities = networkCapabilities;
-        this.linkProperties = linkProperties;
-        this.isBlocked = isBlocked;
-        this.isSelected = isSelected;
-
-        this.priorityClass = priorityClass;
     }
 
     @Override
@@ -113,64 +69,20 @@
         return Objects.hash(network, networkCapabilities, linkProperties, isBlocked);
     }
 
-    /** Returns if two records are equal including their priority classes. */
-    public static boolean isEqualIncludingPriorities(
-            UnderlyingNetworkRecord left, UnderlyingNetworkRecord right) {
-        if (left != null && right != null) {
-            return left.equals(right)
-                    && left.isSelected == right.isSelected
-                    && left.priorityClass == right.priorityClass;
-        }
-
-        return left == right;
-    }
-
-    static Comparator<UnderlyingNetworkRecord> getComparator() {
-        return (left, right) -> {
-            final int leftIndex = left.priorityClass;
-            final int rightIndex = right.priorityClass;
-
-            // In the case of networks in the same priority class, prioritize based on other
-            // criteria (eg. actively selected network, link metrics, etc)
-            if (leftIndex == rightIndex) {
-                // TODO: Improve the strategy of network selection when both UnderlyingNetworkRecord
-                // fall into the same priority class.
-                if (left.isSelected) {
-                    return -1;
-                }
-                if (right.isSelected) {
-                    return 1;
-                }
-            }
-            return Integer.compare(leftIndex, rightIndex);
-        };
-    }
-
-    private static boolean isSelected(
-            Network networkToCheck, UnderlyingNetworkRecord currentlySelected) {
-        if (currentlySelected == null) {
-            return false;
-        }
-        if (currentlySelected.network.equals(networkToCheck)) {
-            return true;
-        }
-        return false;
+    /** Return whether two records represent the same network */
+    public static boolean isSameNetwork(
+            @Nullable UnderlyingNetworkRecord leftRecord,
+            @Nullable UnderlyingNetworkRecord rightRecord) {
+        final Network left = leftRecord == null ? null : leftRecord.network;
+        final Network right = rightRecord == null ? null : rightRecord.network;
+        return Objects.equals(left, right);
     }
 
     /** Dumps the state of this record for logging and debugging purposes. */
-    void dump(
-            VcnContext vcnContext,
-            IndentingPrintWriter pw,
-            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
-            ParcelUuid subscriptionGroup,
-            TelephonySubscriptionSnapshot snapshot,
-            UnderlyingNetworkRecord currentlySelected,
-            PersistableBundleWrapper carrierConfig) {
+    void dump(IndentingPrintWriter pw) {
         pw.println("UnderlyingNetworkRecord:");
         pw.increaseIndent();
 
-        pw.println("priorityClass: " + priorityClass);
-        pw.println("isSelected: " + isSelected);
         pw.println("mNetwork: " + network);
         pw.println("mNetworkCapabilities: " + networkCapabilities);
         pw.println("mLinkProperties: " + linkProperties);
@@ -218,29 +130,14 @@
             return mNetworkCapabilities != null && mLinkProperties != null && mWasIsBlockedSet;
         }
 
-        UnderlyingNetworkRecord build(
-                VcnContext vcnContext,
-                List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
-                ParcelUuid subscriptionGroup,
-                TelephonySubscriptionSnapshot snapshot,
-                UnderlyingNetworkRecord currentlySelected,
-                PersistableBundleWrapper carrierConfig) {
+        UnderlyingNetworkRecord build() {
             if (!isValid()) {
                 throw new IllegalArgumentException(
                         "Called build before UnderlyingNetworkRecord was valid");
             }
 
             return new UnderlyingNetworkRecord(
-                    mNetwork,
-                    mNetworkCapabilities,
-                    mLinkProperties,
-                    mIsBlocked,
-                    vcnContext,
-                    underlyingNetworkTemplates,
-                    subscriptionGroup,
-                    snapshot,
-                    currentlySelected,
-                    carrierConfig);
+                    mNetwork, mNetworkCapabilities, mLinkProperties, mIsBlocked);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index b773ade..8549957 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -16,21 +16,28 @@
 
 package com.android.server.wallpaper;
 
+import static android.app.WallpaperManager.getOrientation;
+import static android.app.WallpaperManager.getRotatedOrientation;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.server.wallpaper.WallpaperUtils.RECORD_FILE;
 import static com.android.server.wallpaper.WallpaperUtils.RECORD_LOCK_FILE;
 import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER;
 import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
+import static com.android.window.flags.Flags.multiCrop;
 
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.ImageDecoder;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.FileUtils;
 import android.os.SELinux;
+import android.text.TextUtils;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.view.DisplayInfo;
+import android.view.View;
 
 import com.android.server.utils.TimingsTraceAndSlog;
 
@@ -39,28 +46,334 @@
 import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
 
 /**
  * Helper file for wallpaper cropping
- * Meant to have a single instance, only used by the WallpaperManagerService
+ * Meant to have a single instance, only used internally by system_server
+ * @hide
  */
-class WallpaperCropper {
+public class WallpaperCropper {
 
     private static final String TAG = WallpaperCropper.class.getSimpleName();
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_CROP = true;
 
+    /**
+     * Maximum acceptable parallax.
+     * A value of 1 means "the additional width for parallax is at most 100% of the screen width"
+     */
+    private static final float MAX_PARALLAX = 1f;
+
+    /**
+     * We define three ways to adjust a crop. These modes are used depending on the situation:
+     *   - When going from unfolded to folded, we want to remove content
+     *   - When going from folded to unfolded, we want to add content
+     *   - For a screen rotation, we want to keep the same amount of content
+     */
+    private static final int ADD = 1;
+    private static final int REMOVE = 2;
+    private static final int BALANCE = 3;
+
+
     private final WallpaperDisplayHelper mWallpaperDisplayHelper;
 
+    /**
+     * Helpers exposed to the window manager part (WallpaperController)
+     */
+    public interface WallpaperCropUtils {
+
+        /**
+         * Equivalent to {@link #getCrop(Point, Point, SparseArray, boolean)}
+         */
+        Rect getCrop(Point displaySize, Point bitmapSize,
+                SparseArray<Rect> suggestedCrops, boolean rtl);
+    }
+
     WallpaperCropper(WallpaperDisplayHelper wallpaperDisplayHelper) {
         mWallpaperDisplayHelper = wallpaperDisplayHelper;
     }
 
     /**
-     * Once a new wallpaper has been written via setWallpaper(...), it needs to be cropped
-     * for display.
+     * Given the dimensions of the original wallpaper image, some optional suggested crops
+     * (either defined by the user, or coming from a backup), and whether the device is RTL,
+     * generate a crop for the current display. This is done through the following process:
+     * <ul>
+     *     <li> If no suggested crops are provided, center the full image on the display. </li>
+     *     <li> If there is a suggested crop the given displaySize, reuse the suggested crop and
+     *     adjust it using {@link #getAdjustedCrop}. </li>
+     *     <li> If there are suggested crops, but not for the orientation of the given displaySize,
+     *     reuse one of the suggested crop for another orientation and adjust if using
+     *     {@link #getAdjustedCrop}. </li>
+     * </ul>
      *
-     * This will generate the crop and write it in the file
+     * @param displaySize     The dimensions of the surface where we want to render the wallpaper
+     * @param bitmapSize      The dimensions of the wallpaper bitmap
+     * @param rtl             Whether the device is right-to-left
+     * @param suggestedCrops  An optional list of user-defined crops for some orientations.
+     *                        If there is a suggested crop for
+     *
+     * @return  A Rect indicating how to crop the bitmap for the current display.
+     */
+    public Rect getCrop(Point displaySize, Point bitmapSize,
+            SparseArray<Rect> suggestedCrops, boolean rtl) {
+
+        // Case 1: if no crops are provided, center align the full image
+        if (suggestedCrops == null || suggestedCrops.size() == 0) {
+            Rect crop = new Rect(0, 0, displaySize.x, displaySize.y);
+            float scale = Math.min(
+                    ((float) bitmapSize.x) / displaySize.x,
+                    ((float) bitmapSize.y) / displaySize.y);
+            crop.scale(scale);
+            crop.offset((bitmapSize.x - crop.width()) / 2,
+                    (bitmapSize.y - crop.height()) / 2);
+            return crop;
+        }
+        int orientation = getOrientation(displaySize);
+
+        // Case 2: if the orientation exists in the suggested crops, adjust the suggested crop
+        Rect suggestedCrop = suggestedCrops.get(orientation);
+        if (suggestedCrop != null) {
+            if (suggestedCrop.left < 0 || suggestedCrop.top < 0
+                    || suggestedCrop.right > bitmapSize.x || suggestedCrop.bottom > bitmapSize.y) {
+                Slog.w(TAG, "invalid suggested crop: " + suggestedCrop);
+                Rect fullImage = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+                return getAdjustedCrop(fullImage, bitmapSize, displaySize, true, rtl, ADD);
+            } else {
+                return getAdjustedCrop(suggestedCrop, bitmapSize, displaySize, true, rtl, ADD);
+            }
+        }
+
+        // Case 3: if we have the 90° rotated orientation in the suggested crops, reuse it and
+        // trying to preserve the zoom level and the center of the image
+        SparseArray<Point> defaultDisplaySizes = mWallpaperDisplayHelper.getDefaultDisplaySizes();
+        int rotatedOrientation = getRotatedOrientation(orientation);
+        suggestedCrop = suggestedCrops.get(rotatedOrientation);
+        Point suggestedDisplaySize = defaultDisplaySizes.get(rotatedOrientation);
+        if (suggestedCrop != null) {
+            // only keep the visible part (without parallax)
+            Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
+            return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, BALANCE);
+        }
+
+        // Case 4: if the device is a foldable, if we're looking for a folded orientation and have
+        // the suggested crop of the relative unfolded orientation, reuse it by removing content.
+        int unfoldedOrientation = mWallpaperDisplayHelper.getUnfoldedOrientation(orientation);
+        suggestedCrop = suggestedCrops.get(unfoldedOrientation);
+        suggestedDisplaySize = defaultDisplaySizes.get(unfoldedOrientation);
+        if (suggestedCrop != null) {
+            // only keep the visible part (without parallax)
+            Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
+            return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, REMOVE);
+        }
+
+        // Case 5: if the device is a foldable, if we're looking for an unfolded orientation and
+        // have the suggested crop of the relative folded orientation, reuse it by adding content.
+        int foldedOrientation = mWallpaperDisplayHelper.getFoldedOrientation(orientation);
+        suggestedCrop = suggestedCrops.get(foldedOrientation);
+        suggestedDisplaySize = defaultDisplaySizes.get(foldedOrientation);
+        if (suggestedCrop != null) {
+            // only keep the visible part (without parallax)
+            Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
+            return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, ADD);
+        }
+
+        // Case 6: for a foldable device, try to combine case 3 + case 4 or 5:
+        // rotate, then fold or unfold
+        Point rotatedDisplaySize = defaultDisplaySizes.get(rotatedOrientation);
+        if (rotatedDisplaySize != null) {
+            int rotatedFolded = mWallpaperDisplayHelper.getFoldedOrientation(rotatedOrientation);
+            int rotateUnfolded = mWallpaperDisplayHelper.getUnfoldedOrientation(rotatedOrientation);
+            for (int suggestedOrientation : new int[]{rotatedFolded, rotateUnfolded}) {
+                suggestedCrop = suggestedCrops.get(suggestedOrientation);
+                if (suggestedCrop != null) {
+                    Rect rotatedCrop = getCrop(rotatedDisplaySize, bitmapSize, suggestedCrops, rtl);
+                    SparseArray<Rect> rotatedCropMap = new SparseArray<>();
+                    rotatedCropMap.put(rotatedOrientation, rotatedCrop);
+                    return getCrop(displaySize, bitmapSize, rotatedCropMap, rtl);
+                }
+            }
+        }
+
+        // Case 7: could not properly reuse the suggested crops. Fall back to case 1.
+        Slog.w(TAG, "Could not find a proper default crop for display: " + displaySize
+                + ", bitmap size: " + bitmapSize + ", suggested crops: " + suggestedCrops
+                + ", orientation: " + orientation + ", rtl: " + rtl
+                + ", defaultDisplaySizes: " + defaultDisplaySizes);
+        return getCrop(displaySize, bitmapSize, new SparseArray<>(), rtl);
+    }
+
+    /**
+     * Given a crop, a displaySize for the orientation of that crop, compute the visible part of the
+     * crop. This removes any additional width used for parallax. No-op if displaySize == null.
+     */
+    private static Rect noParallax(Rect crop, Point displaySize, Point bitmapSize, boolean rtl) {
+        if (displaySize == null) return crop;
+        Rect adjustedCrop = getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD);
+        // only keep the visible part (without parallax)
+        float suggestedDisplayRatio = 1f * displaySize.x / displaySize.y;
+        int widthToRemove = (int) (adjustedCrop.width()
+                - (((float) adjustedCrop.height()) * suggestedDisplayRatio) + 0.5f);
+        if (rtl) {
+            adjustedCrop.left += widthToRemove;
+        } else {
+            adjustedCrop.right -= widthToRemove;
+        }
+        return adjustedCrop;
+    }
+
+    /**
+     * Adjust a given crop:
+     * <ul>
+     *     <li>If parallax = true, make sure we have a parallax of at most {@link #MAX_PARALLAX},
+     *     by removing content from the right (or left if RTL) if necessary.
+     *     </li>
+     *     <li>If parallax = false, make sure we do not have additional width for parallax. If we
+     *     have additional width for parallax, remove half of the additional width on both sides.
+     *     </li>
+     *     <li>Make sure the crop fills the screen, i.e. that the width/height ratio of the crop
+     *     is at least the width/height ratio of the screen. If it is less, add width to the crop
+     *     (if possible on both sides) to fill the screen. If not enough width available, remove
+     *     height to the crop.
+     *     </li>
+     * </ul>
+     */
+    private static Rect getAdjustedCrop(Rect crop, Point bitmapSize, Point screenSize,
+            boolean parallax, boolean rtl, int mode) {
+        Rect adjustedCrop = new Rect(crop);
+        float cropRatio = ((float) crop.width()) / crop.height();
+        float screenRatio = ((float) screenSize.x) / screenSize.y;
+        if (cropRatio >= screenRatio) {
+            if (!parallax) {
+                // rotate everything 90 degrees clockwise, compute the result, and rotate back
+                int newLeft = bitmapSize.y - crop.bottom;
+                int newRight = newLeft + crop.height();
+                int newTop = crop.left;
+                int newBottom = newTop + crop.width();
+                Rect rotatedCrop = new Rect(newLeft, newTop, newRight, newBottom);
+                Point rotatedBitmap = new Point(bitmapSize.y, bitmapSize.x);
+                Point rotatedScreen = new Point(screenSize.y, screenSize.x);
+                Rect rect = getAdjustedCrop(rotatedCrop, rotatedBitmap, rotatedScreen, false, rtl,
+                        mode);
+                int resultLeft = rect.top;
+                int resultRight = resultLeft + rect.height();
+                int resultTop = rotatedBitmap.x - rect.right;
+                int resultBottom = resultTop + rect.width();
+                return new Rect(resultLeft, resultTop, resultRight, resultBottom);
+            }
+            float additionalWidthForParallax = cropRatio / screenRatio - 1f;
+            if (additionalWidthForParallax > MAX_PARALLAX) {
+                int widthToRemove = (int) Math.ceil(
+                        (additionalWidthForParallax - MAX_PARALLAX) * screenRatio * crop.height());
+                if (rtl) {
+                    adjustedCrop.left += widthToRemove;
+                } else {
+                    adjustedCrop.right -= widthToRemove;
+                }
+            }
+        } else {
+            int widthToAdd = mode == REMOVE ? 0
+                    : mode == ADD ? (int) (0.5 + crop.height() * screenRatio - crop.width())
+                    : (int) (0.5 + crop.height() - crop.width());
+            int availableWidth = bitmapSize.x - crop.width();
+            if (availableWidth >= widthToAdd) {
+                int widthToAddLeft = widthToAdd / 2;
+                int widthToAddRight = widthToAdd / 2 + widthToAdd % 2;
+
+                if (crop.left < widthToAddLeft) {
+                    widthToAddRight += (widthToAddLeft - crop.left);
+                    widthToAddLeft = crop.left;
+                } else if (bitmapSize.x - crop.right < widthToAddRight) {
+                    widthToAddLeft += (widthToAddRight - (bitmapSize.x - crop.right));
+                    widthToAddRight = bitmapSize.x - crop.right;
+                }
+                adjustedCrop.left -= widthToAddLeft;
+                adjustedCrop.right += widthToAddRight;
+            } else {
+                adjustedCrop.left = 0;
+                adjustedCrop.right = bitmapSize.x;
+            }
+            int heightToRemove = (int) (crop.height() - (adjustedCrop.width() / screenRatio));
+            adjustedCrop.top += heightToRemove / 2 + heightToRemove % 2;
+            adjustedCrop.bottom -= heightToRemove / 2;
+        }
+        return adjustedCrop;
+    }
+
+    /**
+     * To find the smallest sub-image that contains all the given crops.
+     * This is used in {@link #generateCrop(WallpaperData)}
+     * to determine how the file from {@link WallpaperData#getCropFile()} needs to be cropped.
+     *
+     * @param crops a list of rectangles
+     * @return the smallest rectangle that contains them all.
+     */
+    public static Rect getTotalCrop(SparseArray<Rect> crops) {
+        int left = Integer.MAX_VALUE, top = Integer.MAX_VALUE;
+        int right = Integer.MIN_VALUE, bottom = Integer.MIN_VALUE;
+        for (int i = 0; i < crops.size(); i++) {
+            Rect rect = crops.valueAt(i);
+            left = Math.min(left, rect.left);
+            top = Math.min(top, rect.top);
+            right = Math.max(right, rect.right);
+            bottom = Math.max(bottom, rect.bottom);
+        }
+        return new Rect(left, top, right, bottom);
+    }
+
+    /**
+     * The crops stored in {@link WallpaperData#mCropHints} are relative to the original image.
+     * This computes the crops relative to the sub-image that will actually be rendered on a window.
+     */
+    SparseArray<Rect> getRelativeCropHints(WallpaperData wallpaper) {
+        SparseArray<Rect> result = new SparseArray<>();
+        for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
+            Rect adjustedRect = new Rect(wallpaper.mCropHints.valueAt(i));
+            adjustedRect.offset(-wallpaper.cropHint.left, -wallpaper.cropHint.top);
+            adjustedRect.scale(1f / wallpaper.mSampleSize);
+            result.put(wallpaper.mCropHints.keyAt(i), adjustedRect);
+        }
+        return result;
+    }
+
+    /**
+     * Inverse operation of {@link #getRelativeCropHints}
+     */
+    static List<Rect> getOriginalCropHints(
+            WallpaperData wallpaper, List<Rect> relativeCropHints) {
+        List<Rect> result = new ArrayList<>();
+        for (Rect crop : relativeCropHints) {
+            Rect originalRect = new Rect(crop);
+            originalRect.scale(wallpaper.mSampleSize);
+            originalRect.offset(wallpaper.cropHint.left, wallpaper.cropHint.right);
+            result.add(originalRect);
+        }
+        return result;
+    }
+
+    /**
+     * Given some suggested crops, find cropHints for all orientations of the default display.
+     */
+    SparseArray<Rect> getDefaultCrops(SparseArray<Rect> suggestedCrops, Point bitmapSize) {
+        SparseArray<Rect> result = new SparseArray<>();
+        // add missing cropHints for all orientation of the default display
+        SparseArray<Point> defaultDisplaySizes = mWallpaperDisplayHelper.getDefaultDisplaySizes();
+        boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
+                == View.LAYOUT_DIRECTION_RTL;
+        for (int i = 0; i < defaultDisplaySizes.size(); i++) {
+            int orientation = defaultDisplaySizes.keyAt(i);
+            Point displaySize = defaultDisplaySizes.valueAt(i);
+            Rect newCrop = getCrop(displaySize, bitmapSize, suggestedCrops, rtl);
+            result.put(orientation, newCrop);
+        }
+        return result;
+    }
+
+    /**
+     * Once a new wallpaper has been written via setWallpaper(...), it needs to be cropped
+     * for display. This will generate the crop and write it in the file.
      */
     void generateCrop(WallpaperData wallpaper) {
         TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
@@ -75,27 +388,48 @@
         // Only generate crop for default display.
         final WallpaperDisplayHelper.DisplayData wpData =
                 mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
-        final Rect cropHint = new Rect(wallpaper.cropHint);
         final DisplayInfo displayInfo = mWallpaperDisplayHelper.getDisplayInfo(DEFAULT_DISPLAY);
 
-        if (DEBUG) {
-            Slog.v(TAG, "Generating crop for new wallpaper(s): 0x"
-                    + Integer.toHexString(wallpaper.mWhich)
-                    + " to " + wallpaper.getCropFile().getName()
-                    + " crop=(" + cropHint.width() + 'x' + cropHint.height()
-                    + ") dim=(" + wpData.mWidth + 'x' + wpData.mHeight + ')');
-        }
-
         // Analyse the source; needed in multiple cases
         BitmapFactory.Options options = new BitmapFactory.Options();
         options.inJustDecodeBounds = true;
         BitmapFactory.decodeFile(wallpaper.getWallpaperFile().getAbsolutePath(), options);
         if (options.outWidth <= 0 || options.outHeight <= 0) {
             Slog.w(TAG, "Invalid wallpaper data");
-            success = false;
         } else {
             boolean needCrop = false;
             boolean needScale;
+            boolean multiCrop = multiCrop() && wallpaper.mSupportsMultiCrop;
+
+            Point bitmapSize = new Point(options.outWidth, options.outHeight);
+
+            final Rect cropHint;
+            if (multiCrop) {
+                SparseArray<Rect> defaultDisplayCrops =
+                        getDefaultCrops(wallpaper.mCropHints, bitmapSize);
+                // adapt the entries in wallpaper.mCropHints for the actual display
+                SparseArray<Rect> updatedCropHints = new SparseArray<>();
+                for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
+                    int orientation = wallpaper.mCropHints.keyAt(i);
+                    Rect defaultCrop = defaultDisplayCrops.get(orientation);
+                    if (defaultCrop != null) {
+                        updatedCropHints.put(orientation, defaultCrop);
+                    }
+                }
+                wallpaper.mCropHints = updatedCropHints;
+                cropHint = getTotalCrop(defaultDisplayCrops);
+                wallpaper.cropHint.set(cropHint);
+            } else {
+                cropHint = new Rect(wallpaper.cropHint);
+            }
+
+            if (DEBUG) {
+                Slog.v(TAG, "Generating crop for new wallpaper(s): 0x"
+                        + Integer.toHexString(wallpaper.mWhich)
+                        + " to " + wallpaper.getCropFile().getName()
+                        + " crop=(" + cropHint.width() + 'x' + cropHint.height()
+                        + ") dim=(" + wpData.mWidth + 'x' + wpData.mHeight + ')');
+            }
 
             // Empty crop means use the full image
             if (cropHint.isEmpty()) {
@@ -128,7 +462,7 @@
                     || cropHint.width() > GLHelper.getMaxTextureSize();
 
             //make sure screen aspect ratio is preserved if width is scaled under screen size
-            if (needScale) {
+            if (needScale && !multiCrop) {
                 final float scaleByHeight = (float) wpData.mHeight / (float) cropHint.height();
                 final int newWidth = (int) (cropHint.width() * scaleByHeight);
                 if (newWidth < displayInfo.logicalWidth) {
@@ -171,7 +505,7 @@
                 BufferedOutputStream bos = null;
                 try {
                     // This actually downsamples only by powers of two, but that's okay; we do
-                    // a proper scaling blit later.  This is to minimize transient RAM use.
+                    // a proper scaling a bit later.  This is to minimize transient RAM use.
                     // We calculate the largest power-of-two under the actual ratio rather than
                     // just let the decode take care of it because we also want to remap where the
                     // cropHint rectangle lies in the decoded [super]rect.
@@ -185,19 +519,31 @@
 
                     final Rect estimateCrop = new Rect(cropHint);
                     estimateCrop.scale(1f / options.inSampleSize);
-                    final float hRatio = (float) wpData.mHeight / estimateCrop.height();
+                    float hRatio = (float) wpData.mHeight / estimateCrop.height();
+                    if (multiCrop) {
+                        // make sure the crop height is at most the display largest dimension
+                        hRatio = (float) mWallpaperDisplayHelper.getDefaultDisplayLargestDimension()
+                                / estimateCrop.height();
+                        hRatio = Math.min(hRatio, 1f);
+                    }
                     final int destHeight = (int) (estimateCrop.height() * hRatio);
                     final int destWidth = (int) (estimateCrop.width() * hRatio);
 
                     // We estimated an invalid crop, try to adjust the cropHint to get a valid one.
                     if (destWidth > GLHelper.getMaxTextureSize()) {
+                        if (DEBUG) {
+                            Slog.w(TAG, "Invalid crop dimensions, trying to adjust.");
+                        }
+                        if (multiCrop) {
+                            // clear custom crop guidelines, fallback to system default
+                            wallpaper.mCropHints.clear();
+                            generateCropInternal(wallpaper);
+                            return;
+                        }
+
                         int newHeight = (int) (wpData.mHeight / hRatio);
                         int newWidth = (int) (wpData.mWidth / hRatio);
 
-                        if (DEBUG) {
-                            Slog.v(TAG, "Invalid crop dimensions, trying to adjust.");
-                        }
-
                         estimateCrop.set(cropHint);
                         estimateCrop.left += (cropHint.width() - newWidth) / 2;
                         estimateCrop.top += (cropHint.height() - newHeight) / 2;
@@ -210,8 +556,8 @@
                     // We've got the safe cropHint; now we want to scale it properly to
                     // the desired rectangle.
                     // That's a height-biased operation: make it fit the hinted height.
-                    final int safeHeight = (int) (estimateCrop.height() * hRatio);
-                    final int safeWidth = (int) (estimateCrop.width() * hRatio);
+                    final int safeHeight = (int) (estimateCrop.height() * hRatio + 0.5f);
+                    final int safeWidth = (int) (estimateCrop.width() * hRatio + 0.5f);
 
                     if (DEBUG_CROP) {
                         Slog.v(TAG, "Decode parameters:");
@@ -248,6 +594,12 @@
                         // We are safe to create final crop with safe dimensions now.
                         final Bitmap finalCrop = Bitmap.createScaledBitmap(cropped,
                                 safeWidth, safeHeight, true);
+
+                        if (multiCrop) {
+                            wallpaper.mSampleSize =
+                                    ((float) cropHint.height()) / finalCrop.getHeight();
+                        }
+
                         if (DEBUG) {
                             Slog.v(TAG, "Final extract:");
                             Slog.v(TAG, "  dims: w=" + wpData.mWidth
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java
index 5c86701..02594d2 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperData.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java
@@ -17,6 +17,7 @@
 package com.android.server.wallpaper;
 
 import static android.app.WallpaperManager.FLAG_LOCK;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
 
 import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER;
 import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_CROP;
@@ -26,6 +27,7 @@
 
 import android.app.IWallpaperManagerCallback;
 import android.app.WallpaperColors;
+import android.app.WallpaperManager.ScreenOrientation;
 import android.app.WallpaperManager.SetWallpaperFlags;
 import android.content.ComponentName;
 import android.graphics.Rect;
@@ -126,10 +128,16 @@
     RemoteCallbackList<IWallpaperManagerCallback> callbacks = new RemoteCallbackList<>();
 
     /**
-     * The crop hint supplied for displaying a subset of the source image
+     * Defines which part of the {@link #getWallpaperFile()} image is in the {@link #getCropFile()}.
      */
     final Rect cropHint = new Rect(0, 0, 0, 0);
 
+    /**
+     * How much the crop is sub-sampled. A value > 1 means that the image quality was reduced.
+     * This is the ratio between the cropHint height and the actual {@link #getCropFile()} height.
+     */
+    float mSampleSize = 1f;
+
     // Describes the context of a call to WallpaperManagerService#bindWallpaperComponentLocked
     enum BindSource {
         UNKNOWN,
@@ -156,6 +164,23 @@
     private final SparseArray<File> mWallpaperFiles = new SparseArray<>();
     private final SparseArray<File> mCropFiles = new SparseArray<>();
 
+    /**
+     * Mapping of {@link ScreenOrientation} -> crop hint. The crop hints are relative to the
+     * original image stored in {@link #getWallpaperFile()}.
+     * Only used when multi crop flag is enabled.
+     */
+    SparseArray<Rect> mCropHints = new SparseArray<>();
+
+    /**
+     * cropHints will be ignored if this flag is false
+     */
+    boolean mSupportsMultiCrop;
+
+    /**
+     * The phone orientation when the wallpaper was set. Only relevant for image wallpapers
+     */
+    int mOrientationWhenSet = ORIENTATION_UNKNOWN;
+
     WallpaperData(int userId, @SetWallpaperFlags int wallpaperType) {
         this.userId = userId;
         this.mWhich = wallpaperType;
@@ -176,6 +201,10 @@
         this.mWhich = source.mWhich;
         this.wallpaperId = source.wallpaperId;
         this.cropHint.set(source.cropHint);
+        if (source.mCropHints != null) {
+            this.mCropHints = source.mCropHints.clone();
+        }
+        this.mSupportsMultiCrop = source.mSupportsMultiCrop;
         this.allowBackup = source.allowBackup;
         this.primaryColors = source.primaryColors;
         this.mWallpaperDimAmount = source.mWallpaperDimAmount;
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index de98df5..88e9672 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -18,6 +18,7 @@
 
 import static android.app.WallpaperManager.FLAG_LOCK;
 import static android.app.WallpaperManager.FLAG_SYSTEM;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.server.wallpaper.WallpaperDisplayHelper.DisplayData;
@@ -26,9 +27,11 @@
 import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_INFO;
 import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
 import static com.android.server.wallpaper.WallpaperUtils.makeWallpaperIdLocked;
+import static com.android.window.flags.Flags.multiCrop;
 
 import android.annotation.Nullable;
 import android.app.WallpaperColors;
+import android.app.WallpaperManager;
 import android.app.WallpaperManager.SetWallpaperFlags;
 import android.app.backup.WallpaperBackupHelper;
 import android.content.ComponentName;
@@ -36,7 +39,9 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.Color;
+import android.graphics.Rect;
 import android.os.FileUtils;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.Xml;
@@ -60,14 +65,16 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
  * Helper for the wallpaper loading / saving / xml parsing
  * Only meant to be used lock held by WallpaperManagerService
  * Only meant to be instantiated once by WallpaperManagerService
+ * @hide
  */
-class WallpaperDataParser {
+public class WallpaperDataParser {
 
     private static final String TAG = WallpaperDataParser.class.getSimpleName();
     private static final boolean DEBUG = false;
@@ -132,6 +139,7 @@
      */
     public WallpaperLoadingResult loadSettingsLocked(int userId, boolean keepDimensionHints,
             boolean migrateFromOld, @SetWallpaperFlags int which) {
+        // TODO(b/270726737) remove the "keepDimensionHints" arg when removing the multi crop flag
         JournaledFile journal = makeJournaledFile(userId);
         FileInputStream stream = null;
         File file = journal.chooseForRead();
@@ -174,7 +182,9 @@
                         WallpaperData wallpaperToParse =
                                 "wp".equals(tag) ? wallpaper : lockWallpaper;
 
-                        parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints);
+                        if (!multiCrop()) {
+                            parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints);
+                        }
 
                         String comp = parser.getAttributeValue(null, "component");
                         wallpaperToParse.nextWallpaperComponent = comp != null
@@ -186,6 +196,10 @@
                             wallpaperToParse.nextWallpaperComponent = mImageWallpaper;
                         }
 
+                        if (multiCrop()) {
+                            parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints);
+                        }
+
                         if (DEBUG) {
                             Slog.v(TAG, "mWidth:" + wpdData.mWidth);
                             Slog.v(TAG, "mHeight:" + wpdData.mHeight);
@@ -300,20 +314,48 @@
             wallpaper.wallpaperId = makeWallpaperIdLocked();
         }
 
-        final DisplayData wpData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
-
-        if (!keepDimensionHints) {
-            wpData.mWidth = parser.getAttributeInt(null, "width");
-            wpData.mHeight = parser.getAttributeInt(null, "height");
+        Rect totalCropHint = new Rect(
+                getAttributeInt(parser, "totalCropLeft", 0),
+                getAttributeInt(parser, "totalCropTop", 0),
+                getAttributeInt(parser, "totalCropRight", 0),
+                getAttributeInt(parser, "totalCropBottom", 0));
+        wallpaper.mSupportsMultiCrop = multiCrop() && (
+                parser.getAttributeBoolean(null, "supportsMultiCrop", false)
+                || mImageWallpaper.equals(wallpaper.wallpaperComponent));
+        if (wallpaper.mSupportsMultiCrop) {
+            wallpaper.mCropHints = new SparseArray<>();
+            for (Pair<Integer, String> pair: screenDimensionPairs()) {
+                Rect cropHint = new Rect(
+                        parser.getAttributeInt(null, "cropLeft" + pair.second, 0),
+                        parser.getAttributeInt(null, "cropTop" + pair.second, 0),
+                        parser.getAttributeInt(null, "cropRight" + pair.second, 0),
+                        parser.getAttributeInt(null, "cropBottom" + pair.second, 0));
+                if (!cropHint.isEmpty()) wallpaper.mCropHints.put(pair.first, cropHint);
+            }
+            if (wallpaper.mCropHints.size() == 0) {
+                // migration case: the crops per screen orientation are not specified.
+                // use the old attributes to find the crop for one screen orientation.
+                Integer orientation = totalCropHint.width() < totalCropHint.height()
+                        ? WallpaperManager.PORTRAIT : WallpaperManager.LANDSCAPE;
+                if (!totalCropHint.isEmpty()) wallpaper.mCropHints.put(orientation, totalCropHint);
+            } else {
+                wallpaper.cropHint.set(totalCropHint);
+            }
+        } else {
+            wallpaper.cropHint.set(totalCropHint);
         }
-        wallpaper.cropHint.left = getAttributeInt(parser, "cropLeft", 0);
-        wallpaper.cropHint.top = getAttributeInt(parser, "cropTop", 0);
-        wallpaper.cropHint.right = getAttributeInt(parser, "cropRight", 0);
-        wallpaper.cropHint.bottom = getAttributeInt(parser, "cropBottom", 0);
-        wpData.mPadding.left = getAttributeInt(parser, "paddingLeft", 0);
-        wpData.mPadding.top = getAttributeInt(parser, "paddingTop", 0);
-        wpData.mPadding.right = getAttributeInt(parser, "paddingRight", 0);
-        wpData.mPadding.bottom = getAttributeInt(parser, "paddingBottom", 0);
+        final DisplayData wpData = mWallpaperDisplayHelper
+                .getDisplayDataOrCreate(DEFAULT_DISPLAY);
+        if (!keepDimensionHints && !multiCrop()) {
+            wpData.mWidth = parser.getAttributeInt(null, "width", 0);
+            wpData.mHeight = parser.getAttributeInt(null, "height", 0);
+        }
+        if (!multiCrop()) {
+            wpData.mPadding.left = getAttributeInt(parser, "paddingLeft", 0);
+            wpData.mPadding.top = getAttributeInt(parser, "paddingTop", 0);
+            wpData.mPadding.right = getAttributeInt(parser, "paddingRight", 0);
+            wpData.mPadding.bottom = getAttributeInt(parser, "paddingBottom", 0);
+        }
         wallpaper.mWallpaperDimAmount = getAttributeFloat(parser, "dimAmount", 0f);
         BindSource bindSource;
         try {
@@ -365,11 +407,11 @@
         wallpaper.allowBackup = parser.getAttributeBoolean(null, "backup", false);
     }
 
-    private int getAttributeInt(TypedXmlPullParser parser, String name, int defValue) {
+    private static int getAttributeInt(TypedXmlPullParser parser, String name, int defValue) {
         return parser.getAttributeInt(null, name, defValue);
     }
 
-    private float getAttributeFloat(TypedXmlPullParser parser, String name, float defValue) {
+    private static float getAttributeFloat(TypedXmlPullParser parser, String name, float defValue) {
         return parser.getAttributeFloat(null, name, defValue);
     }
 
@@ -412,28 +454,66 @@
         if (DEBUG) {
             Slog.v(TAG, "writeWallpaperAttributes id=" + wallpaper.wallpaperId);
         }
-        final DisplayData wpdData = mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
         out.startTag(null, tag);
         out.attributeInt(null, "id", wallpaper.wallpaperId);
-        out.attributeInt(null, "width", wpdData.mWidth);
-        out.attributeInt(null, "height", wpdData.mHeight);
 
-        out.attributeInt(null, "cropLeft", wallpaper.cropHint.left);
-        out.attributeInt(null, "cropTop", wallpaper.cropHint.top);
-        out.attributeInt(null, "cropRight", wallpaper.cropHint.right);
-        out.attributeInt(null, "cropBottom", wallpaper.cropHint.bottom);
+        out.attributeBoolean(null, "supportsMultiCrop", wallpaper.mSupportsMultiCrop);
 
-        if (wpdData.mPadding.left != 0) {
-            out.attributeInt(null, "paddingLeft", wpdData.mPadding.left);
-        }
-        if (wpdData.mPadding.top != 0) {
-            out.attributeInt(null, "paddingTop", wpdData.mPadding.top);
-        }
-        if (wpdData.mPadding.right != 0) {
-            out.attributeInt(null, "paddingRight", wpdData.mPadding.right);
-        }
-        if (wpdData.mPadding.bottom != 0) {
-            out.attributeInt(null, "paddingBottom", wpdData.mPadding.bottom);
+        if (multiCrop() && wallpaper.mSupportsMultiCrop) {
+            if (wallpaper.mCropHints == null) {
+                Slog.e(TAG, "cropHints should not be null when saved");
+                wallpaper.mCropHints = new SparseArray<>();
+            }
+            for (Pair<Integer, String> pair : screenDimensionPairs()) {
+                Rect cropHint = wallpaper.mCropHints.get(pair.first);
+                if (cropHint == null) continue;
+                out.attributeInt(null, "cropLeft" + pair.second, cropHint.left);
+                out.attributeInt(null, "cropTop" + pair.second, cropHint.top);
+                out.attributeInt(null, "cropRight" + pair.second, cropHint.right);
+                out.attributeInt(null, "cropBottom" + pair.second, cropHint.bottom);
+
+                // to support back compatibility in B&R, save the crops for one orientation in the
+                // legacy "cropLeft", "cropTop", "cropRight", "cropBottom" entries
+                int orientationToPutInLegacyCrop = wallpaper.mOrientationWhenSet;
+                if (mWallpaperDisplayHelper.isFoldable()) {
+                    int unfoldedOrientation = mWallpaperDisplayHelper
+                            .getUnfoldedOrientation(orientationToPutInLegacyCrop);
+                    if (unfoldedOrientation != ORIENTATION_UNKNOWN) {
+                        orientationToPutInLegacyCrop = unfoldedOrientation;
+                    }
+                }
+                if (pair.first == orientationToPutInLegacyCrop) {
+                    out.attributeInt(null, "cropLeft", cropHint.left);
+                    out.attributeInt(null, "cropTop", cropHint.top);
+                    out.attributeInt(null, "cropRight", cropHint.right);
+                    out.attributeInt(null, "cropBottom", cropHint.bottom);
+                }
+            }
+            out.attributeInt(null, "totalCropLeft", wallpaper.cropHint.left);
+            out.attributeInt(null, "totalCropTop", wallpaper.cropHint.top);
+            out.attributeInt(null, "totalCropRight", wallpaper.cropHint.right);
+            out.attributeInt(null, "totalCropBottom", wallpaper.cropHint.bottom);
+        } else if (!multiCrop()) {
+            final DisplayData wpdData =
+                    mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
+            out.attributeInt(null, "width", wpdData.mWidth);
+            out.attributeInt(null, "height", wpdData.mHeight);
+            out.attributeInt(null, "cropLeft", wallpaper.cropHint.left);
+            out.attributeInt(null, "cropTop", wallpaper.cropHint.top);
+            out.attributeInt(null, "cropRight", wallpaper.cropHint.right);
+            out.attributeInt(null, "cropBottom", wallpaper.cropHint.bottom);
+            if (wpdData.mPadding.left != 0) {
+                out.attributeInt(null, "paddingLeft", wpdData.mPadding.left);
+            }
+            if (wpdData.mPadding.top != 0) {
+                out.attributeInt(null, "paddingTop", wpdData.mPadding.top);
+            }
+            if (wpdData.mPadding.right != 0) {
+                out.attributeInt(null, "paddingRight", wpdData.mPadding.right);
+            }
+            if (wpdData.mPadding.bottom != 0) {
+                out.attributeInt(null, "paddingBottom", wpdData.mPadding.bottom);
+            }
         }
 
         out.attributeFloat(null, "dimAmount", wallpaper.mWallpaperDimAmount);
@@ -564,4 +644,12 @@
         }
         return false;
     }
+
+    private static List<Pair<Integer, String>> screenDimensionPairs() {
+        return List.of(
+                new Pair<>(WallpaperManager.PORTRAIT, "Portrait"),
+                new Pair<>(WallpaperManager.LANDSCAPE, "Landscape"),
+                new Pair<>(WallpaperManager.SQUARE_PORTRAIT, "SquarePortrait"),
+                new Pair<>(WallpaperManager.SQUARE_LANDSCAPE, "SquareLandscape"));
+    }
 }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
index f48178c..9e1b5d2 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
@@ -16,19 +16,31 @@
 
 package com.android.server.wallpaper;
 
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
+import static android.app.WallpaperManager.getRotatedOrientation;
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.window.flags.Flags.multiCrop;
+
+import android.app.WallpaperManager;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.os.Binder;
 import android.os.Debug;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayInfo;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
 
 import com.android.server.wm.WindowManagerInternal;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
 import java.util.function.Consumer;
 
 /**
@@ -50,12 +62,56 @@
     private final SparseArray<DisplayData> mDisplayDatas = new SparseArray<>();
     private final DisplayManager mDisplayManager;
     private final WindowManagerInternal mWindowManagerInternal;
+    private final SparseArray<Point> mDefaultDisplaySizes = new SparseArray<>();
+
+    // related orientations pairs for foldable (folded orientation, unfolded orientation)
+    private final List<Pair<Integer, Integer>> mFoldableOrientationPairs = new ArrayList<>();
+
+    private boolean mIsFoldable;
 
     WallpaperDisplayHelper(
             DisplayManager displayManager,
-            WindowManagerInternal windowManagerInternal) {
+            WindowManager windowManager,
+            WindowManagerInternal windowManagerInternal,
+            boolean isFoldable) {
         mDisplayManager = displayManager;
         mWindowManagerInternal = windowManagerInternal;
+        mIsFoldable = isFoldable;
+        if (!multiCrop()) return;
+        Set<WindowMetrics> metrics = windowManager.getPossibleMaximumWindowMetrics(DEFAULT_DISPLAY);
+        boolean populateOrientationPairs = isFoldable && metrics.size() == 2;
+        float surface = 0;
+        int firstOrientation = -1;
+        for (WindowMetrics metric: metrics) {
+            Rect bounds = metric.getBounds();
+            Point displaySize = new Point(bounds.width(), bounds.height());
+            Point reversedDisplaySize = new Point(displaySize.y, displaySize.x);
+            for (Point point : List.of(displaySize, reversedDisplaySize)) {
+                int orientation = WallpaperManager.getOrientation(point);
+                // don't add an entry if there is already a larger display of the same orientation
+                Point display = mDefaultDisplaySizes.get(orientation);
+                if (display == null || display.x * display.y < point.x * point.y) {
+                    mDefaultDisplaySizes.put(orientation, point);
+                }
+            }
+            if (populateOrientationPairs) {
+                int orientation = WallpaperManager.getOrientation(displaySize);
+                float newSurface = displaySize.x * displaySize.y
+                        / (metric.getDensity() * metric.getDensity());
+                if (surface <= 0) {
+                    surface = newSurface;
+                    firstOrientation = orientation;
+                } else {
+                    Pair<Integer, Integer> pair = (newSurface > surface)
+                            ? new Pair<>(firstOrientation, orientation)
+                            : new Pair<>(orientation, firstOrientation);
+                    Pair<Integer, Integer> rotatedPair = new Pair<>(
+                            getRotatedOrientation(pair.first), getRotatedOrientation(pair.second));
+                    mFoldableOrientationPairs.add(pair);
+                    mFoldableOrientationPairs.add(rotatedPair);
+                }
+            }
+        }
     }
 
     DisplayData getDisplayDataOrCreate(int displayId) {
@@ -68,6 +124,12 @@
         return wpdData;
     }
 
+    int getDefaultDisplayCurrentOrientation() {
+        Point displaySize = new Point();
+        mDisplayManager.getDisplay(DEFAULT_DISPLAY).getSize(displaySize);
+        return WallpaperManager.getOrientation(displaySize);
+    }
+
     void removeDisplayData(int displayId) {
         mDisplayDatas.remove(displayId);
     }
@@ -133,4 +195,46 @@
     boolean isValidDisplay(int displayId) {
         return mDisplayManager.getDisplay(displayId) != null;
     }
+
+    SparseArray<Point> getDefaultDisplaySizes() {
+        return mDefaultDisplaySizes;
+    }
+
+    /** Return the number of pixel of the largest dimension of the default display */
+    int getDefaultDisplayLargestDimension() {
+        int result = -1;
+        for (int i = 0; i < mDefaultDisplaySizes.size(); i++) {
+            Point size = mDefaultDisplaySizes.valueAt(i);
+            result = Math.max(result, Math.max(size.x, size.y));
+        }
+        return result;
+    }
+
+    boolean isFoldable() {
+        return mIsFoldable;
+    }
+
+    /**
+     * If a given orientation corresponds to an unfolded orientation on foldable, return the
+     * corresponding folded orientation. Otherwise, return UNKNOWN. Always return UNKNOWN if the
+     * device is not a foldable.
+     */
+    int getFoldedOrientation(int orientation) {
+        for (Pair<Integer, Integer> pair : mFoldableOrientationPairs) {
+            if (pair.second.equals(orientation)) return pair.first;
+        }
+        return ORIENTATION_UNKNOWN;
+    }
+
+    /**
+     * If a given orientation corresponds to a folded orientation on foldable, return the
+     * corresponding unfolded orientation. Otherwise, return UNKNOWN. Always return UNKNOWN if the
+     * device is not a foldable.
+     */
+    int getUnfoldedOrientation(int orientation) {
+        for (Pair<Integer, Integer> pair : mFoldableOrientationPairs) {
+            if (pair.first.equals(orientation)) return pair.second;
+        }
+        return ORIENTATION_UNKNOWN;
+    }
 }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 3782b42..8c27bb8 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -22,6 +22,7 @@
 import static android.app.WallpaperManager.COMMAND_REAPPLY;
 import static android.app.WallpaperManager.FLAG_LOCK;
 import static android.app.WallpaperManager.FLAG_SYSTEM;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.ParcelFileDescriptor.MODE_CREATE;
@@ -74,6 +75,7 @@
 import android.content.pm.UserInfo;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.hardware.display.DisplayManager;
@@ -103,12 +105,15 @@
 import android.service.wallpaper.WallpaperService;
 import android.system.ErrnoException;
 import android.system.Os;
+import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -137,6 +142,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -189,8 +195,6 @@
     }
 
     private final Object mLock = new Object();
-    /** True to support different crops for different display dimensions */
-    private final boolean mIsMultiCropEnabled;
     /** Tracks wallpaper being migrated from system+lock to lock when setting static wp. */
     WallpaperDestinationChangeHandler mPendingMigrationViaStatic;
 
@@ -804,6 +808,12 @@
                     null /* options */);
             mWindowManagerInternal.setWallpaperShowWhenLocked(
                     mToken, (wallpaper.mWhich & FLAG_LOCK) != 0);
+            if (multiCrop() && wallpaper.mSupportsMultiCrop) {
+                mWindowManagerInternal.setWallpaperCropHints(mToken,
+                        mWallpaperCropper.getRelativeCropHints(wallpaper));
+            } else {
+                mWindowManagerInternal.setWallpaperCropHints(mToken, new SparseArray<>());
+            }
             final DisplayData wpdData =
                     mWallpaperDisplayHelper.getDisplayDataOrCreate(mDisplayId);
             try {
@@ -1256,7 +1266,7 @@
                         // migrateStaticSystemToLockWallpaperLocked() and added to the lock wp map
                         // if successful.
                         WallpaperData lockWp = mLockWallpaperMap.get(mNewWallpaper.userId);
-                        if (lockWp != null) {
+                        if (lockWp != null && mOriginalSystem.connection != null) {
                             // Successful rename, set old system+lock to the pending lock wp
                             if (DEBUG) {
                                 Slog.v(TAG, "static system+lock to system success");
@@ -1479,10 +1489,15 @@
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mIPackageManager = AppGlobals.getPackageManager();
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
-        DisplayManager dm = mContext.getSystemService(DisplayManager.class);
-        dm.registerDisplayListener(mDisplayListener, null /* handler */);
-        mWallpaperDisplayHelper = new WallpaperDisplayHelper(dm, mWindowManagerInternal);
+        DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+        displayManager.registerDisplayListener(mDisplayListener, null /* handler */);
+        WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+        boolean isFoldable = mContext.getResources()
+                .getIntArray(R.array.config_foldedDeviceStates).length > 0;
+        mWallpaperDisplayHelper = new WallpaperDisplayHelper(
+                displayManager, windowManager, mWindowManagerInternal, isFoldable);
         mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper);
+        mWindowManagerInternal.setWallpaperCropUtils(mWallpaperCropper::getCrop);
         mActivityManager = mContext.getSystemService(ActivityManager.class);
 
         if (mContext.getResources().getBoolean(
@@ -1522,7 +1537,6 @@
         mColorsChangedListeners = new SparseArray<>();
         mWallpaperDataParser = new WallpaperDataParser(mContext, mWallpaperDisplayHelper,
                 mWallpaperCropper);
-        mIsMultiCropEnabled = multiCrop();
         LocalServices.addService(WallpaperManagerInternal.class, new LocalService());
     }
 
@@ -2199,6 +2213,66 @@
         }
     }
 
+    @Override
+    public List<Rect> getBitmapCrops(List<Point> displaySizes, @SetWallpaperFlags int which,
+            boolean originalBitmap, int userId) {
+        userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), userId, false, true, "getBitmapCrop", null);
+        synchronized (mLock) {
+            checkPermission(READ_WALLPAPER_INTERNAL);
+            WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
+                    : mWallpaperMap.get(userId);
+            if (wallpaper == null || !wallpaper.mSupportsMultiCrop) return null;
+            SparseArray<Rect> relativeSuggestedCrops =
+                    mWallpaperCropper.getRelativeCropHints(wallpaper);
+            Point croppedBitmapSize =
+                    new Point(wallpaper.cropHint.width(), wallpaper.cropHint.height());
+            SparseArray<Rect> relativeDefaultCrops =
+                    mWallpaperCropper.getDefaultCrops(relativeSuggestedCrops, croppedBitmapSize);
+            SparseArray<Rect> adjustedRelativeSuggestedCrops = new SparseArray<>();
+            for (int i = 0; i < relativeDefaultCrops.size(); i++) {
+                int key = relativeDefaultCrops.keyAt(i);
+                if (relativeSuggestedCrops.contains(key)) {
+                    adjustedRelativeSuggestedCrops.put(key, relativeDefaultCrops.get(key));
+                }
+            }
+            List<Rect> result = new ArrayList<>();
+            boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
+                    == View.LAYOUT_DIRECTION_RTL;
+            for (Point displaySize : displaySizes) {
+                result.add(mWallpaperCropper.getCrop(
+                        displaySize, croppedBitmapSize, adjustedRelativeSuggestedCrops, rtl));
+            }
+            if (originalBitmap) result = WallpaperCropper.getOriginalCropHints(wallpaper, result);
+            return result;
+        }
+    }
+
+    @Override
+    public List<Rect> getFutureBitmapCrops(Point bitmapSize, List<Point> displaySizes,
+            int[] screenOrientations, List<Rect> crops) {
+        SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops, ORIENTATION_UNKNOWN);
+        SparseArray<Rect> defaultCrops = mWallpaperCropper.getDefaultCrops(cropMap, bitmapSize);
+        List<Rect> result = new ArrayList<>();
+        boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
+                == View.LAYOUT_DIRECTION_RTL;
+        for (Point displaySize : displaySizes) {
+            result.add(mWallpaperCropper.getCrop(displaySize, bitmapSize, defaultCrops, rtl));
+        }
+        return result;
+    }
+
+    @Override
+    public Rect getBitmapCrop(Point bitmapSize, int[] screenOrientations, List<Rect> crops) {
+        if (!multiCrop()) {
+            throw new UnsupportedOperationException(
+                    "This method should only be called with the multi crop flag enabled");
+        }
+        SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops, ORIENTATION_UNKNOWN);
+        SparseArray<Rect> defaultCrops = mWallpaperCropper.getDefaultCrops(cropMap, bitmapSize);
+        return WallpaperCropper.getTotalCrop(defaultCrops);
+    }
+
     private boolean hasPermission(String permission) {
         return mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED;
     }
@@ -2755,8 +2829,18 @@
 
     @Override
     public ParcelFileDescriptor setWallpaper(String name, String callingPackage,
-            Rect cropHint, boolean allowBackup, Bundle extras, int which,
-            IWallpaperManagerCallback completion, int userId) {
+            int[] screenOrientations, List<Rect> crops, boolean allowBackup,
+            Bundle extras, int which, IWallpaperManagerCallback completion, int userId) {
+
+        if (DEBUG) {
+            Slog.d(TAG, "setWallpaper: name = " + name + ", callingPackage = " + callingPackage
+                    + ", screenOrientations = "
+                    + (screenOrientations == null ? null
+                            : Arrays.stream(screenOrientations).boxed().toList())
+                    + ", crops = " + crops
+                    + ", allowBackup = " + allowBackup);
+        }
+
         userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
                 false /* all */, true /* full */, "changing wallpaper", null /* pkg */);
         checkPermission(android.Manifest.permission.SET_WALLPAPER);
@@ -2771,10 +2855,17 @@
             return null;
         }
 
+        int currentOrientation = mWallpaperDisplayHelper.getDefaultDisplayCurrentOrientation();
+        SparseArray<Rect> cropMap = !multiCrop() ? null
+                : getCropMap(screenOrientations, crops, currentOrientation);
+        Rect cropHint = multiCrop() || crops == null ? null : crops.get(0);
+        final boolean fromForegroundApp = !multiCrop() ? false
+                : isFromForegroundApp(callingPackage);
+
         // "null" means the no-op crop, preserving the full input image
-        if (cropHint == null) {
+        if (cropHint == null && !multiCrop()) {
             cropHint = new Rect(0, 0, 0, 0);
-        } else {
+        } else if (!multiCrop()) {
             if (cropHint.width() < 0 || cropHint.height() < 0
                     || cropHint.left < 0
                     || cropHint.top < 0) {
@@ -2814,10 +2905,14 @@
                     wallpaper.mSystemWasBoth = systemIsBoth;
                     wallpaper.mWhich = which;
                     wallpaper.setComplete = completion;
-                    wallpaper.fromForegroundApp = isFromForegroundApp(callingPackage);
-                    wallpaper.cropHint.set(cropHint);
+                    wallpaper.fromForegroundApp = multiCrop() ? fromForegroundApp
+                            : isFromForegroundApp(callingPackage);
+                    if (!multiCrop()) wallpaper.cropHint.set(cropHint);
+                    if (multiCrop()) wallpaper.mSupportsMultiCrop = true;
+                    if (multiCrop()) wallpaper.mCropHints = cropMap;
                     wallpaper.allowBackup = allowBackup;
                     wallpaper.mWallpaperDimAmount = getWallpaperDimAmount();
+                    wallpaper.mOrientationWhenSet = currentOrientation;
                 }
                 return pfd;
             } finally {
@@ -2826,11 +2921,47 @@
         }
     }
 
+    private SparseArray<Rect> getCropMap(int[] screenOrientations, List<Rect> crops,
+            int currentOrientation) {
+        if ((crops == null ^ screenOrientations == null)
+                || (crops != null && crops.size() != screenOrientations.length)) {
+            throw new IllegalArgumentException(
+                    "Illegal crops/orientations lists: must both be null, or both the same size");
+        }
+        SparseArray<Rect> cropMap = new SparseArray<>();
+        boolean unknown = false;
+        if (crops != null && crops.size() != 0) {
+            for (int i = 0; i < crops.size(); i++) {
+                Rect crop = crops.get(i);
+                int width = crop.width(), height = crop.height();
+                if (width < 0 || height < 0 || crop.left < 0 || crop.top < 0) {
+                    throw new IllegalArgumentException("Invalid crop rect supplied: " + crop);
+                }
+                int orientation = screenOrientations[i];
+                if (orientation == ORIENTATION_UNKNOWN) {
+                    if (currentOrientation == ORIENTATION_UNKNOWN) {
+                        throw new IllegalArgumentException(
+                                "Invalid orientation: " + ORIENTATION_UNKNOWN);
+                    }
+                    unknown = true;
+                    orientation = currentOrientation;
+                }
+                cropMap.put(orientation, crop);
+            }
+        }
+        if (unknown && cropMap.size() > 1) {
+            throw new IllegalArgumentException("Invalid crops supplied: the UNKNOWN screen "
+                    + "orientation should only be used in a singleton map (in which case it"
+                    + "represents the current orientation of the default display)");
+        }
+        return cropMap;
+    }
+
     private void migrateStaticSystemToLockWallpaperLocked(int userId) {
         WallpaperData sysWP = mWallpaperMap.get(userId);
         if (sysWP == null) {
             if (DEBUG) {
-                Slog.i(TAG, "No system wallpaper?  Not tracking for lock-only");
+                Slog.i(TAG, "No system wallpaper? Not tracking for lock-only");
             }
             return;
         }
@@ -2839,6 +2970,10 @@
         WallpaperData lockWP = new WallpaperData(userId, FLAG_LOCK);
         lockWP.wallpaperId = sysWP.wallpaperId;
         lockWP.cropHint.set(sysWP.cropHint);
+        lockWP.mSupportsMultiCrop = sysWP.mSupportsMultiCrop;
+        if (sysWP.mCropHints != null) {
+            lockWP.mCropHints = sysWP.mCropHints.clone();
+        }
         lockWP.allowBackup = sysWP.allowBackup;
         lockWP.primaryColors = sysWP.primaryColors;
         lockWP.mWallpaperDimAmount = sysWP.mWallpaperDimAmount;
@@ -2956,6 +3091,7 @@
             final long ident = Binder.clearCallingIdentity();
 
             try {
+                newWallpaper.mSupportsMultiCrop = mImageWallpaper.equals(name);
                 newWallpaper.imageWallpaperPending = false;
                 newWallpaper.mWhich = which;
                 newWallpaper.mSystemWasBoth = systemIsBoth;
@@ -3428,11 +3564,6 @@
         return (wallpaper != null) ? wallpaper.allowBackup : false;
     }
 
-    @Override
-    public boolean isMultiCropEnabled() {
-        return mIsMultiCropEnabled;
-    }
-
     private void onDisplayReadyInternal(int displayId) {
         synchronized (mLock) {
             if (mLastWallpaper == null) {
diff --git a/services/core/java/com/android/server/wearable/OWNERS b/services/core/java/com/android/server/wearable/OWNERS
index 073e2d7..eca48b7 100644
--- a/services/core/java/com/android/server/wearable/OWNERS
+++ b/services/core/java/com/android/server/wearable/OWNERS
@@ -1,3 +1 @@
-charliewang@google.com
-oni@google.com
-volnov@google.com
\ No newline at end of file
+include /core/java/android/app/wearable/OWNERS
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index cd48f5d..4cc2c02 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -48,6 +48,7 @@
 import java.io.FileDescriptor;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.Consumer;
 
 /**
  * System service for managing sensing {@link AmbientContextEvent}s on Wearables.
@@ -191,9 +192,23 @@
         }
     }
 
+    private void callPerUserServiceIfExist(
+            Consumer<WearableSensingManagerPerUserService> serviceConsumer,
+            RemoteCallback statusCallback) {
+        int userId = UserHandle.getCallingUserId();
+        synchronized (mLock) {
+            WearableSensingManagerPerUserService service = getServiceForUserLocked(userId);
+            if (service == null) {
+                Slog.w(TAG, "Service not available for userId " + userId);
+                WearableSensingManagerPerUserService.notifyStatusCallback(statusCallback,
+                        WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+            serviceConsumer.accept(service);
+        }
+    }
+
     private final class WearableSensingManagerInternal extends IWearableSensingManager.Stub {
-        final WearableSensingManagerPerUserService mService = getServiceForUserLocked(
-                UserHandle.getCallingUserId());
 
         @Override
         public void provideDataStream(
@@ -210,7 +225,9 @@
                         WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
                 return;
             }
-            mService.onProvideDataStream(parcelFileDescriptor, callback);
+            callPerUserServiceIfExist(
+                    service -> service.onProvideDataStream(parcelFileDescriptor, callback),
+                    callback);
         }
 
         @Override
@@ -218,7 +235,7 @@
                 PersistableBundle data,
                 SharedMemory sharedMemory,
                 RemoteCallback callback) {
-            Slog.i(TAG, "WearableSensingManagerInternal provideData.");
+            Slog.d(TAG, "WearableSensingManagerInternal provideData.");
             Objects.requireNonNull(data);
             Objects.requireNonNull(callback);
             mContext.enforceCallingOrSelfPermission(
@@ -229,7 +246,9 @@
                         WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
                 return;
             }
-            mService.onProvidedData(data, sharedMemory, callback);
+            callPerUserServiceIfExist(
+                    service -> service.onProvidedData(data, sharedMemory, callback),
+                    callback);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 60dc4ff..d95b431 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -348,6 +348,10 @@
 
     private void pinWebviewIfRequired(ApplicationInfo appInfo) {
         PinnerService pinnerService = LocalServices.getService(PinnerService.class);
+        if (pinnerService == null) {
+            // This happens in unit tests which do not have services.
+            return;
+        }
         int webviewPinQuota = pinnerService.getWebviewPinQuota();
         if (webviewPinQuota <= 0) {
             return;
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
index 29782d9..f4fb1a1 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -159,11 +159,28 @@
         }
     }
 
+    private boolean shouldTriggerRepairLocked() {
+        if (mCurrentWebViewPackage == null) {
+            return true;
+        }
+        WebViewProviderInfo defaultProvider = getDefaultWebViewPackage();
+        if (mCurrentWebViewPackage.packageName.equals(defaultProvider.packageName)) {
+            List<UserPackage> userPackages =
+                    mSystemInterface.getPackageInfoForProviderAllUsers(
+                            mContext, defaultProvider);
+            return !isInstalledAndEnabledForAllUsers(userPackages);
+        } else {
+            return false;
+        }
+    }
+
     @Override
     public void prepareWebViewInSystemServer() {
         try {
+            boolean repairNeeded = true;
             synchronized (mLock) {
                 mCurrentWebViewPackage = findPreferredWebViewPackage();
+                repairNeeded = shouldTriggerRepairLocked();
                 String userSetting = mSystemInterface.getUserChosenWebViewProvider(mContext);
                 if (userSetting != null
                         && !userSetting.equals(mCurrentWebViewPackage.packageName)) {
@@ -177,26 +194,25 @@
                 }
                 onWebViewProviderChanged(mCurrentWebViewPackage);
             }
+
+            if (repairNeeded) {
+                // We didn't find a valid WebView implementation. Try explicitly re-enabling the
+                // default package for all users in case it was disabled, even if we already did the
+                // one-time migration before. If this actually changes the state, we will see the
+                // PackageManager broadcast shortly and try again.
+                WebViewProviderInfo defaultProvider = getDefaultWebViewPackage();
+                Slog.w(
+                        TAG,
+                        "No provider available for all users, trying to enable "
+                                + defaultProvider.packageName);
+                mSystemInterface.enablePackageForAllUsers(
+                        mContext, defaultProvider.packageName, true);
+            }
+
         } catch (Throwable t) {
             // Log and discard errors at this stage as we must not crash the system server.
             Slog.e(TAG, "error preparing webview provider from system server", t);
         }
-
-        if (getCurrentWebViewPackage() == null) {
-            // We didn't find a valid WebView implementation. Try explicitly re-enabling the
-            // fallback package for all users in case it was disabled, even if we already did the
-            // one-time migration before. If this actually changes the state, we will see the
-            // PackageManager broadcast shortly and try again.
-            WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
-            WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
-            if (fallbackProvider != null) {
-                Slog.w(TAG, "No valid provider, trying to enable " + fallbackProvider.packageName);
-                mSystemInterface.enablePackageForAllUsers(mContext, fallbackProvider.packageName,
-                                                          true);
-            } else {
-                Slog.e(TAG, "No valid provider and no fallback available.");
-            }
-        }
     }
 
     private void startZygoteWhenReady() {
@@ -421,42 +437,43 @@
 
     /**
      * Returns either the package info of the WebView provider determined in the following way:
-     * If the user has chosen a provider then use that if it is valid,
-     * otherwise use the first package in the webview priority list that is valid.
-     *
+     * If the user has chosen a provider then use that if it is valid, enabled and installed
+     * for all users, otherwise use the default provider.
      */
     private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException {
-        ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();
-
-        String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext);
-
         // If the user has chosen provider, use that (if it's installed and enabled for all
         // users).
-        for (ProviderAndPackageInfo providerAndPackage : providers) {
-            if (providerAndPackage.provider.packageName.equals(userChosenProvider)) {
-                // userPackages can contain null objects.
-                List<UserPackage> userPackages =
-                        mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
-                                providerAndPackage.provider);
-                if (isInstalledAndEnabledForAllUsers(userPackages)) {
-                    return providerAndPackage.packageInfo;
+        String userChosenPackageName = mSystemInterface.getUserChosenWebViewProvider(mContext);
+        WebViewProviderInfo userChosenProvider =
+                getWebViewProviderForPackage(userChosenPackageName);
+        if (userChosenProvider != null) {
+            try {
+                PackageInfo packageInfo =
+                        mSystemInterface.getPackageInfoForProvider(userChosenProvider);
+                if (validityResult(userChosenProvider, packageInfo) == VALIDITY_OK) {
+                    List<UserPackage> userPackages =
+                            mSystemInterface.getPackageInfoForProviderAllUsers(
+                                    mContext, userChosenProvider);
+                    if (isInstalledAndEnabledForAllUsers(userPackages)) {
+                        return packageInfo;
+                    }
                 }
+            } catch (NameNotFoundException e) {
+                Slog.w(TAG, "User chosen WebView package (" + userChosenPackageName
+                        + ") not found");
             }
         }
 
-        // User did not choose, or the choice failed; use the most stable provider that is
-        // installed and enabled for all users, and available by default (not through
-        // user choice).
-        for (ProviderAndPackageInfo providerAndPackage : providers) {
-            if (providerAndPackage.provider.availableByDefault) {
-                // userPackages can contain null objects.
-                List<UserPackage> userPackages =
-                        mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
-                                providerAndPackage.provider);
-                if (isInstalledAndEnabledForAllUsers(userPackages)) {
-                    return providerAndPackage.packageInfo;
-                }
+        // User did not choose, or the choice failed; return the default provider even if it is not
+        // installed or enabled for all users.
+        WebViewProviderInfo defaultProvider = getDefaultWebViewPackage();
+        try {
+            PackageInfo packageInfo = mSystemInterface.getPackageInfoForProvider(defaultProvider);
+            if (validityResult(defaultProvider, packageInfo) == VALIDITY_OK) {
+                return packageInfo;
             }
+        } catch (NameNotFoundException e) {
+            Slog.w(TAG, "Default WebView package (" + defaultProvider.packageName + ") not found");
         }
 
         // This should never happen during normal operation (only with modified system images).
@@ -464,6 +481,16 @@
         throw new WebViewPackageMissingException("Could not find a loadable WebView package");
     }
 
+    private WebViewProviderInfo getWebViewProviderForPackage(String packageName) {
+        WebViewProviderInfo[] allProviders = getWebViewPackages();
+        for (int n = 0; n < allProviders.length; n++) {
+            if (allProviders[n].packageName.equals(packageName)) {
+                return allProviders[n];
+            }
+        }
+        return null;
+    }
+
     /**
      * Return true iff {@param packageInfos} point to only installed and enabled packages.
      * The given packages {@param packageInfos} should all be pointing to the same package, but each
diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
index 05da9df..e5c743c 100644
--- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
@@ -18,6 +18,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -180,16 +181,8 @@
         if (snapshot == null) {
             return null;
         }
-        final HardwareBuffer buffer = snapshot.getHardwareBuffer();
-        if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
-            buffer.close();
-            Slog.e(TAG, "Invalid snapshot dimensions " + buffer.getWidth() + "x"
-                    + buffer.getHeight());
-            return null;
-        } else {
-            mCache.putSnapshot(source, snapshot);
-            return snapshot;
-        }
+        mCache.putSnapshot(source, snapshot);
+        return snapshot;
     }
 
     @VisibleForTesting
@@ -210,6 +203,11 @@
 
     @Nullable
     TaskSnapshot snapshot(TYPE source) {
+        return snapshot(source, mHighResSnapshotScale);
+    }
+
+    @Nullable
+    TaskSnapshot snapshot(TYPE source, float scale) {
         TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
         final Rect crop = prepareTaskSnapshot(source, builder);
         if (crop == null) {
@@ -218,7 +216,7 @@
         }
         Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "createSnapshot");
         final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer = createSnapshot(source,
-                mHighResSnapshotScale, crop, builder);
+                scale, crop, builder);
         Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
         if (screenshotBuffer == null) {
             // Failed to acquire image. Has been logged.
@@ -227,7 +225,19 @@
         builder.setCaptureTime(SystemClock.elapsedRealtimeNanos());
         builder.setSnapshot(screenshotBuffer.getHardwareBuffer());
         builder.setColorSpace(screenshotBuffer.getColorSpace());
-        return builder.build();
+        final TaskSnapshot snapshot = builder.build();
+        return validateSnapshot(snapshot);
+    }
+
+    private static TaskSnapshot validateSnapshot(@NonNull TaskSnapshot snapshot) {
+        final HardwareBuffer buffer = snapshot.getHardwareBuffer();
+        if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
+            buffer.close();
+            Slog.e(TAG, "Invalid snapshot dimensions " + buffer.getWidth() + "x"
+                    + buffer.getHeight());
+            return null;
+        }
+        return snapshot;
     }
 
     @Nullable
@@ -432,7 +442,7 @@
         InsetUtils.addInsets(contentInsets, letterboxInsets);
         // Note, the app theme snapshot is never translucent because we enforce a non-translucent
         // color above
-        return new TaskSnapshot(
+        final TaskSnapshot taskSnapshot = new TaskSnapshot(
                 System.currentTimeMillis() /* id */,
                 SystemClock.elapsedRealtimeNanos() /* captureTime */,
                 topActivity.mActivityComponent, hwBitmap.getHardwareBuffer(),
@@ -441,6 +451,7 @@
                 contentInsets, letterboxInsets, false /* isLowResolution */,
                 false /* isRealSnapshot */, source.getWindowingMode(),
                 getAppearance(source), false /* isTranslucent */, false /* hasImeSurface */);
+        return validateSnapshot(taskSnapshot);
     }
 
     static Rect getSystemBarInsets(Rect frame, InsetsState state) {
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 2d584c4..fa8c35a 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -462,17 +462,16 @@
         }
     }
 
-    // TODO(b/318327737): Remove parameter 't' when removing flag DRAW_IN_WM_LOCK.
-    void drawMagnifiedRegionBorderIfNeeded(int displayId, SurfaceControl.Transaction t) {
+    void drawMagnifiedRegionBorderIfNeeded(int displayId) {
         if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
             mAccessibilityTracing.logTrace(
                     TAG + ".drawMagnifiedRegionBorderIfNeeded",
                     FLAGS_MAGNIFICATION_CALLBACK,
-                    "displayId=" + displayId + "; transaction={" + t + "}");
+                    "displayId=" + displayId);
         }
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
         if (displayMagnifier != null) {
-            displayMagnifier.drawMagnifiedRegionBorderIfNeeded(t);
+            displayMagnifier.drawMagnifiedRegionBorderIfNeeded();
         }
         // Not relevant for the window observer.
     }
@@ -870,12 +869,12 @@
                     .sendToTarget();
         }
 
-        void drawMagnifiedRegionBorderIfNeeded(SurfaceControl.Transaction t) {
+        void drawMagnifiedRegionBorderIfNeeded() {
             if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
                 mAccessibilityTracing.logTrace(LOG_TAG + ".drawMagnifiedRegionBorderIfNeeded",
-                        FLAGS_MAGNIFICATION_CALLBACK, "transition={" + t + "}");
+                        FLAGS_MAGNIFICATION_CALLBACK);
             }
-            mMagnifedViewport.drawWindowIfNeeded(t);
+            mMagnifedViewport.drawWindowIfNeeded();
         }
 
         void dump(PrintWriter pw, String prefix) {
@@ -1121,14 +1120,6 @@
             }
 
             void setMagnifiedRegionBorderShown(boolean shown, boolean animate) {
-                if (ViewportWindow.DRAW_IN_WM_LOCK) {
-                    if (shown) {
-                        mFullRedrawNeeded = true;
-                        mOldMagnificationRegion.set(0, 0, 0, 0);
-                    }
-                    mWindow.setShown(shown, animate);
-                    return;
-                }
                 if (mWindow.setShown(shown, animate)) {
                     mFullRedrawNeeded = true;
                     // Clear the old region, so recomputeBounds will refresh the current region.
@@ -1151,12 +1142,8 @@
                 return mMagnificationSpec;
             }
 
-            void drawWindowIfNeeded(SurfaceControl.Transaction t) {
+            void drawWindowIfNeeded() {
                 recomputeBounds();
-                if (ViewportWindow.DRAW_IN_WM_LOCK) {
-                    mWindow.drawOrRemoveIfNeeded(t);
-                    return;
-                }
                 mWindow.postDrawIfNeeded();
             }
 
@@ -1187,8 +1174,6 @@
 
             private final class ViewportWindow implements Runnable {
                 private static final String SURFACE_TITLE = "Magnification Overlay";
-                // TODO(b/318327737): Remove if it is stable.
-                static final boolean DRAW_IN_WM_LOCK = !Flags.drawMagnifierBorderOutsideWmlock();
 
                 private final Region mBounds = new Region();
                 private final Rect mDirtyRect = new Rect();
@@ -1328,14 +1313,14 @@
 
                 @Override
                 public void run() {
-                    drawOrRemoveIfNeeded(mTransaction);
+                    drawOrRemoveIfNeeded();
                 }
 
                 /**
                  * This method must only be called by animation handler directly to make sure
                  * thread safe and there is no lock held outside.
                  */
-                private void drawOrRemoveIfNeeded(SurfaceControl.Transaction t) {
+                private void drawOrRemoveIfNeeded() {
                     // Drawing variables (alpha, dirty rect, and bounds) access is synchronized
                     // using WindowManagerGlobalLock. Grab copies of these values before
                     // drawing on the canvas so that drawing can be performed outside of the lock.
@@ -1343,7 +1328,7 @@
                     Rect drawingRect = null;
                     Region drawingBounds = null;
                     synchronized (mService.mGlobalLock) {
-                        if (!DRAW_IN_WM_LOCK && mBlastBufferQueue.mNativeObject == 0) {
+                        if (mBlastBufferQueue.mNativeObject == 0) {
                             // Complete removal since releaseSurface has been called.
                             if (mSurface.isValid()) {
                                 mTransaction.remove(mSurfaceControl).apply();
@@ -1388,16 +1373,8 @@
                         mPaint.setAlpha(alpha);
                         canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint);
                         mSurface.unlockCanvasAndPost(canvas);
-                        if (DRAW_IN_WM_LOCK) {
-                            t.show(mSurfaceControl);
-                            return;
-                        }
                         showSurface = true;
                     } else {
-                        if (DRAW_IN_WM_LOCK) {
-                            t.hide(mSurfaceControl);
-                            return;
-                        }
                         showSurface = false;
                     }
 
@@ -1413,11 +1390,6 @@
                 @GuardedBy("mService.mGlobalLock")
                 void releaseSurface() {
                     mBlastBufferQueue.destroy();
-                    if (DRAW_IN_WM_LOCK) {
-                        mService.mTransactionFactory.get().remove(mSurfaceControl).apply();
-                        mSurface.release();
-                        return;
-                    }
                     // Post to perform cleanup on the thread which handles mSurface.
                     mService.mAnimationHandler.post(this);
                 }
@@ -1568,16 +1540,12 @@
 
         private static final boolean DEBUG = false;
 
-        private final List<AccessibilityWindow> mTempA11yWindows = new ArrayList<>();
-
         private final Set<IBinder> mTempBinderSet = new ArraySet<>();
 
         private final Point mTempPoint = new Point();
 
         private final Region mTempRegion = new Region();
 
-        private final Region mTempRegion1 = new Region();
-
         private final Region mTempRegion2 = new Region();
 
         private final WindowManagerService mService;
@@ -1645,7 +1613,8 @@
                 Slog.i(LOG_TAG, "computeChangedWindows()");
             }
 
-            List<WindowInfo> windows = new ArrayList<>();
+            final List<WindowInfo> windows = new ArrayList<>();
+            final List<AccessibilityWindow> visibleWindows = new ArrayList<>();
             final int topFocusedDisplayId;
             IBinder topFocusedWindowToken = null;
 
@@ -1680,7 +1649,6 @@
                 Region unaccountedSpace = mTempRegion;
                 unaccountedSpace.set(0, 0, screenWidth, screenHeight);
 
-                final List<AccessibilityWindow> visibleWindows = mTempA11yWindows;
                 mA11yWindowsPopulator.populateVisibleWindowsOnScreenLocked(
                         mDisplayId, visibleWindows);
                 Set<IBinder> addedWindows = mTempBinderSet;
@@ -1737,7 +1705,6 @@
                     }
                 }
 
-                visibleWindows.clear();
                 addedWindows.clear();
 
                 // Gets the top focused display Id and window token for supporting multi-display.
@@ -1748,7 +1715,9 @@
                     topFocusedWindowToken, windows);
 
             // Recycle the windows as we do not need them.
-            clearAndRecycleWindows(windows);
+            for (final AccessibilityWindowsPopulator.AccessibilityWindow window : visibleWindows) {
+                window.getWindowInfo().recycle();
+            }
             mInitialized = true;
         }
 
@@ -1830,13 +1799,6 @@
             tokenOut.add(window.token);
         }
 
-        private static void clearAndRecycleWindows(List<WindowInfo> windows) {
-            final int windowCount = windows.size();
-            for (int i = windowCount - 1; i >= 0; i--) {
-                windows.remove(i).recycle();
-            }
-        }
-
         private static boolean isReportedWindowType(int windowType) {
             return (windowType != WindowManager.LayoutParams.TYPE_WALLPAPER
                     && windowType != WindowManager.LayoutParams.TYPE_BOOT_PROGRESS
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 4b55bec..2e0546e 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -778,17 +778,22 @@
         try {
             synchronized (mGlobalLock) {
                 final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
+                if (r == null) {
+                    return false;
+                }
                 // Create a transition if the activity is playing in case the below activity didn't
                 // commit invisible. That's because if any activity below this one has changed its
                 // visibility while playing transition, there won't able to commit visibility until
                 // the running transition finish.
-                final Transition transition = r != null
-                        && r.mTransitionController.inPlayingTransition(r)
+                final Transition transition = r.mTransitionController.isShellTransitionsEnabled()
                         && !r.mTransitionController.isCollecting()
                         ? r.mTransitionController.createTransition(TRANSIT_TO_BACK) : null;
-                final boolean changed = r != null && r.setOccludesParent(true);
+                final boolean changed = r.setOccludesParent(true);
                 if (transition != null) {
                     if (changed) {
+                        // Always set as scene transition because it expects to be a jump-cut.
+                        transition.setOverrideAnimation(TransitionInfo.AnimationOptions
+                                .makeSceneTransitionAnimOptions(), null, null);
                         r.mTransitionController.requestStartTransition(transition,
                                 null /*startTask */, null /* remoteTransition */,
                                 null /* displayChange */);
@@ -1166,11 +1171,12 @@
             transition.abort();
             return;
         }
-        transition.collect(topFocusedRootTask);
-        executeMultiWindowFullscreenRequest(fullscreenRequest, topFocusedRootTask);
-        r.mTransitionController.requestStartTransition(transition, topFocusedRootTask,
+        final Task requestingTask = r.getTask();
+        transition.collect(requestingTask);
+        executeMultiWindowFullscreenRequest(fullscreenRequest, requestingTask);
+        r.mTransitionController.requestStartTransition(transition, requestingTask,
                 null /* remoteTransition */, null /* displayChange */);
-        transition.setReady(topFocusedRootTask, true);
+        transition.setReady(requestingTask, true);
     }
 
     private static void reportMultiwindowFullscreenRequestValidatingResult(IRemoteCallback callback,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3a792d0..03d55d9 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -53,6 +53,7 @@
 import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
 import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
 import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
+import static android.content.Context.CONTEXT_RESTRICTED;
 import static android.content.Intent.ACTION_MAIN;
 import static android.content.Intent.CATEGORY_HOME;
 import static android.content.Intent.CATEGORY_LAUNCHER;
@@ -141,6 +142,7 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SWITCH;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN;
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
@@ -168,6 +170,7 @@
 import static com.android.server.wm.ActivityRecordProto.FRONT_OF_TASK;
 import static com.android.server.wm.ActivityRecordProto.IN_SIZE_COMPAT_MODE;
 import static com.android.server.wm.ActivityRecordProto.IS_ANIMATING;
+import static com.android.server.wm.ActivityRecordProto.IS_USER_FULLSCREEN_OVERRIDE_ENABLED;
 import static com.android.server.wm.ActivityRecordProto.IS_WAITING_FOR_TRANSITION_START;
 import static com.android.server.wm.ActivityRecordProto.LAST_ALL_DRAWN;
 import static com.android.server.wm.ActivityRecordProto.LAST_DROP_INPUT_MODE;
@@ -182,6 +185,7 @@
 import static com.android.server.wm.ActivityRecordProto.PROVIDES_MAX_BOUNDS;
 import static com.android.server.wm.ActivityRecordProto.REPORTED_DRAWN;
 import static com.android.server.wm.ActivityRecordProto.REPORTED_VISIBLE;
+import static com.android.server.wm.ActivityRecordProto.SHOULD_ENABLE_USER_ASPECT_RATIO_SETTINGS;
 import static com.android.server.wm.ActivityRecordProto.SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT;
 import static com.android.server.wm.ActivityRecordProto.SHOULD_IGNORE_ORIENTATION_REQUEST_LOOP;
 import static com.android.server.wm.ActivityRecordProto.SHOULD_OVERRIDE_FORCE_RESIZE_APP;
@@ -989,6 +993,9 @@
     private CustomAppTransition mCustomOpenTransition;
     private CustomAppTransition mCustomCloseTransition;
 
+    /** Non-zero to pause dispatching configuration changes to the client. */
+    int mPauseConfigurationDispatchCount = 0;
+
     private final Runnable mPauseTimeoutRunnable = new Runnable() {
         @Override
         public void run() {
@@ -2225,7 +2232,17 @@
 
         mOptInOnBackInvoked = WindowOnBackInvokedDispatcher
                 .isOnBackInvokedCallbackEnabled(info, info.applicationInfo,
-                        () -> ent != null ? ent.array : null, false);
+                        () -> {
+                            Context appContext = null;
+                            try {
+                                appContext = mAtmService.mContext.createPackageContextAsUser(
+                                        info.packageName, CONTEXT_RESTRICTED,
+                                        UserHandle.of(mUserId));
+                                appContext.setTheme(theme);
+                            } catch (PackageManager.NameNotFoundException ignore) {
+                            }
+                            return appContext;
+                        });
     }
 
     /**
@@ -2629,10 +2646,20 @@
         if (snapshot == null) {
             return false;
         }
-        if (!snapshot.getTopActivityComponent().equals(mActivityComponent)) {
-            // Obsoleted snapshot.
-            return false;
-        }
+        return isSnapshotComponentCompatible(snapshot) && isSnapshotOrientationCompatible(snapshot);
+    }
+
+    /**
+     * Returns {@code true} if the top activity component of task snapshot equals to this activity.
+     */
+    boolean isSnapshotComponentCompatible(@NonNull TaskSnapshot snapshot) {
+        return snapshot.getTopActivityComponent().equals(mActivityComponent);
+    }
+
+    /**
+     * Returns {@code true} if the orientation of task snapshot is compatible with this activity.
+     */
+    boolean isSnapshotOrientationCompatible(@NonNull TaskSnapshot snapshot) {
         final int rotation = mDisplayContent.rotationForActivityInDifferentOrientation(this);
         final int currentRotation = task.getWindowConfiguration().getRotation();
         final int targetRotation = rotation != ROTATION_UNDEFINED
@@ -3091,7 +3118,6 @@
         final boolean changed = occludesParent != mOccludesParent;
         mOccludesParent = occludesParent;
         setMainWindowOpaque(occludesParent);
-        mWmService.mWindowPlacerLocked.requestTraversal();
 
         if (changed && task != null && !occludesParent) {
             getRootTask().convertActivityToTranslucent(this);
@@ -3964,20 +3990,6 @@
         return removedFromHistory;
     }
 
-    boolean safelyDestroy(String reason) {
-        if (isDestroyable()) {
-            if (DEBUG_SWITCH) {
-                final Task task = getTask();
-                Slog.v(TAG_SWITCH, "Safely destroying " + this + " in state " + getState()
-                        + " resumed=" + task.getTopResumedActivity()
-                        + " pausing=" + task.getTopPausingActivity()
-                        + " for reason " + reason);
-            }
-            return destroyImmediately(reason);
-        }
-        return false;
-    }
-
     /** Note: call {@link #cleanUp(boolean, boolean)} before this method. */
     void removeFromHistory(String reason) {
         finishActivityResults(Activity.RESULT_CANCELED,
@@ -4046,10 +4058,6 @@
         }
     }
 
-    boolean isFinishing() {
-        return finishing;
-    }
-
     /**
      * This method is to only be called from the client via binder when the activity is destroyed
      * AND finished.
@@ -7977,6 +7985,7 @@
         if (mLetterboxUiController.shouldIgnoreRequestedOrientation(requestedOrientation)) {
             return;
         }
+        final int originalRelaunchingCount = mPendingRelaunchCount;
         // This is necessary in order to avoid going into size compat mode when the orientation
         // change request comes from the app
         if (getRequestedConfigurationOrientation(false, requestedOrientation)
@@ -7994,8 +8003,10 @@
         // the request is handled at task level with letterbox.
         if (!getMergedOverrideConfiguration().equals(
                 mLastReportedConfiguration.getMergedConfiguration())) {
-            ensureActivityConfiguration(
-                    false /* ignoreVisibility */, true /* isRequestedOrientationChanged */);
+            ensureActivityConfiguration(false /* ignoreVisibility */);
+            if (mPendingRelaunchCount > originalRelaunchingCount) {
+                mLetterboxUiController.setRelaunchingAfterRequestedOrientationChanged(true);
+            }
             if (mTransitionController.inPlayingTransition(this)) {
                 mTransitionController.mValidateActivityCompat.add(this);
             }
@@ -9290,6 +9301,59 @@
         }
     }
 
+    @Override
+    void dispatchConfigurationToChild(WindowState child, Configuration config) {
+        if (isConfigurationDispatchPaused()) {
+            return;
+        }
+        super.dispatchConfigurationToChild(child, config);
+    }
+
+    /**
+     * Pauses dispatch of configuration changes to the client. This includes any
+     * configuration-triggered lifecycle changes, WindowState configs, and surface changes. If
+     * a lifecycle change comes from another source (eg. stop), it will still run but will use the
+     * paused configuration.
+     *
+     * The main way this works is by blocking calls to {@link #updateReportedConfigurationAndSend}.
+     * That method is responsible for evaluating whether the activity needs to be relaunched and
+     * sending configurations.
+     */
+    void pauseConfigurationDispatch() {
+        ++mPauseConfigurationDispatchCount;
+        if (mPauseConfigurationDispatchCount == 1) {
+            ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Pausing configuration dispatch for "
+                    + " %s", this);
+        }
+    }
+
+    /** @return `true` if configuration actually changed. */
+    boolean resumeConfigurationDispatch() {
+        --mPauseConfigurationDispatchCount;
+        if (mPauseConfigurationDispatchCount > 0) {
+            return false;
+        }
+        ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Resuming configuration dispatch for %s", this);
+        if (mPauseConfigurationDispatchCount < 0) {
+            Slog.wtf(TAG, "Trying to resume non-paused configuration dispatch");
+            mPauseConfigurationDispatchCount = 0;
+            return false;
+        }
+        if (mLastReportedDisplayId == getDisplayId()
+                && getConfiguration().equals(mLastReportedConfiguration.getMergedConfiguration())) {
+            return false;
+        }
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            dispatchConfigurationToChild(getChildAt(i), getConfiguration());
+        }
+        updateReportedConfigurationAndSend();
+        return true;
+    }
+
+    boolean isConfigurationDispatchPaused() {
+        return mPauseConfigurationDispatchCount > 0;
+    }
+
     private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
             Rect containingBounds) {
         return applyAspectRatio(outBounds, containingAppBounds, containingBounds,
@@ -9501,11 +9565,6 @@
         return ensureActivityConfiguration(false /* ignoreVisibility */);
     }
 
-    boolean ensureActivityConfiguration(boolean ignoreVisibility) {
-        return ensureActivityConfiguration(ignoreVisibility,
-                false /* isRequestedOrientationChanged */);
-    }
-
     /**
      * Make sure the given activity matches the current configuration. Ensures the HistoryRecord
      * is updated with the correct configuration and all other bookkeeping is handled.
@@ -9514,13 +9573,10 @@
      *                         (stopped state). This is useful for the case where we know the
      *                         activity will be visible soon and we want to ensure its configuration
      *                         before we make it visible.
-     * @param isRequestedOrientationChanged whether this is triggered in response to an app calling
-     *                                      {@link android.app.Activity#setRequestedOrientation}.
      * @return False if the activity was relaunched and true if it wasn't relaunched because we
      *         can't or the app handles the specific configuration that is changing.
      */
-    boolean ensureActivityConfiguration(boolean ignoreVisibility,
-            boolean isRequestedOrientationChanged) {
+    boolean ensureActivityConfiguration(boolean ignoreVisibility) {
         final Task rootTask = getRootTask();
         if (rootTask.mConfigWillChange) {
             ProtoLog.v(WM_DEBUG_CONFIGURATION, "Skipping config check "
@@ -9547,6 +9603,17 @@
             return true;
         }
 
+        if (isConfigurationDispatchPaused()) {
+            return true;
+        }
+
+        return updateReportedConfigurationAndSend();
+    }
+
+    boolean updateReportedConfigurationAndSend() {
+        if (isConfigurationDispatchPaused()) {
+            Slog.wtf(TAG, "trying to update reported(client) config while dispatch is paused");
+        }
         ProtoLog.v(WM_DEBUG_CONFIGURATION, "Ensuring correct "
                 + "configuration: %s", this);
 
@@ -9657,9 +9724,6 @@
             } else {
                 mRelaunchReason = RELAUNCH_REASON_NONE;
             }
-            if (isRequestedOrientationChanged) {
-                mLetterboxUiController.setRelaunchingAfterRequestedOrientationChanged(true);
-            }
             if (mState == PAUSING) {
                 // A little annoying: we are waiting for this activity to finish pausing. Let's not
                 // do anything now, but just flag that it needs to be restarted when done pausing.
@@ -10338,6 +10402,10 @@
                 mLetterboxUiController.shouldIgnoreOrientationRequestLoop());
         proto.write(SHOULD_OVERRIDE_FORCE_RESIZE_APP,
                 mLetterboxUiController.shouldOverrideForceResizeApp());
+        proto.write(SHOULD_ENABLE_USER_ASPECT_RATIO_SETTINGS,
+                mLetterboxUiController.shouldEnableUserAspectRatioSettings());
+        proto.write(IS_USER_FULLSCREEN_OVERRIDE_ENABLED,
+                mLetterboxUiController.isUserFullscreenOverrideEnabled());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
index f1a2159..db27f60 100644
--- a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
+++ b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
@@ -43,7 +43,7 @@
     static final String DOC_LINK = "go/android-asm";
 
     /** Used to determine which version of the ASM logic was used in logs while we iterate */
-    static final int ASM_VERSION = 8;
+    static final int ASM_VERSION = 9;
 
     private static final String NAMESPACE = NAMESPACE_WINDOW_MANAGER;
     private static final String KEY_ASM_PREFIX = "ActivitySecurity__";
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 7af494c..1f013b9 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -25,17 +25,17 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.util.ArraySet;
+import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.window.TaskSnapshot;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
 import com.android.window.flags.Flags;
 
 import java.io.File;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 
 /**
@@ -136,28 +136,37 @@
                 false /* enableLowResSnapshots */, 0 /* lowResScaleFactor */, use16BitFormat);
     }
 
-    /** Retrieves a snapshot for an activity from cache. */
+    /**
+     * Retrieves a snapshot for a set of activities from cache.
+     * This will only return the snapshot IFF input activities exist entirely in the snapshot.
+     * Sample: If the snapshot was captured with activity A and B, here will return null if the
+     * input activity is only [A] or [B], it must be [A, B]
+     */
     @Nullable
-    TaskSnapshot getSnapshot(ActivityRecord ar) {
-        final int code = getSystemHashCode(ar);
-        return mCache.getSnapshot(code);
+    TaskSnapshot getSnapshot(@NonNull ActivityRecord[] activities) {
+        if (activities.length == 0) {
+            return null;
+        }
+        final UserSavedFile tmpUsf = findSavedFile(activities[0]);
+        if (tmpUsf == null || tmpUsf.mActivityIds.size() != activities.length) {
+            return null;
+        }
+        int fileId = 0;
+        for (int i = activities.length - 1; i >= 0; --i) {
+            fileId ^= getSystemHashCode(activities[i]);
+        }
+        return tmpUsf.mFileId == fileId ? mCache.getSnapshot(tmpUsf.mActivityIds.get(0)) : null;
     }
 
     private void cleanUpUserFiles(int userId) {
         synchronized (mSnapshotPersistQueue.getLock()) {
             mSnapshotPersistQueue.sendToQueueLocked(
-                    new SnapshotPersistQueue.WriteQueueItem(mPersistInfoProvider) {
-                        @Override
-                        boolean isReady() {
-                            final UserManagerInternal mUserManagerInternal =
-                                    LocalServices.getService(UserManagerInternal.class);
-                            return mUserManagerInternal.isUserUnlocked(userId);
-                        }
+                    new SnapshotPersistQueue.WriteQueueItem(mPersistInfoProvider, userId) {
 
                         @Override
                         void write() {
                             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "cleanUpUserFiles");
-                            final File file = mPersistInfoProvider.getDirectory(userId);
+                            final File file = mPersistInfoProvider.getDirectory(mUserId);
                             if (file.exists()) {
                                 final File[] contents = file.listFiles();
                                 if (contents != null) {
@@ -229,33 +238,16 @@
                     + " load " + mPendingLoadActivity);
         }
         // load snapshot to cache
-        for (int i = mPendingLoadActivity.size() - 1; i >= 0; i--) {
-            final ActivityRecord ar = mPendingLoadActivity.valueAt(i);
-            final int code = getSystemHashCode(ar);
-            final int userId = ar.mUserId;
-            if (mCache.getSnapshot(code) != null) {
-                // already in cache, skip
-                continue;
-            }
-            if (containsFile(code, userId)) {
-                synchronized (mSnapshotPersistQueue.getLock()) {
-                    mSnapshotPersistQueue.insertQueueAtFirstLocked(
-                            new LoadActivitySnapshotItem(ar, code, userId, mPersistInfoProvider));
-                }
-            }
-        }
+        loadActivitySnapshot();
         // clear mTmpRemoveActivity from cache
         for (int i = mPendingRemoveActivity.size() - 1; i >= 0; i--) {
             final ActivityRecord ar = mPendingRemoveActivity.valueAt(i);
-            final int code = getSystemHashCode(ar);
-            mCache.onIdRemoved(code);
+            removeCachedFiles(ar);
         }
         // clear snapshot on cache and delete files
         for (int i = mPendingDeleteActivity.size() - 1; i >= 0; i--) {
             final ActivityRecord ar = mPendingDeleteActivity.valueAt(i);
-            final int code = getSystemHashCode(ar);
-            mCache.onIdRemoved(code);
-            removeIfUserSavedFileExist(code, ar.mUserId);
+            removeIfUserSavedFileExist(ar);
         }
         // don't keep any reference
         resetTmpFields();
@@ -263,29 +255,37 @@
 
     class LoadActivitySnapshotItem extends SnapshotPersistQueue.WriteQueueItem {
         private final int mCode;
-        private final int mUserId;
-        private final ActivityRecord mActivityRecord;
+        private final ActivityRecord[] mActivities;
 
-        LoadActivitySnapshotItem(@NonNull ActivityRecord ar, int code, int userId,
+        LoadActivitySnapshotItem(@NonNull ActivityRecord[] activities, int code, int userId,
                 @NonNull PersistInfoProvider persistInfoProvider) {
-            super(persistInfoProvider);
-            mActivityRecord = ar;
+            super(persistInfoProvider, userId);
+            mActivities = activities;
             mCode = code;
-            mUserId = userId;
         }
 
         @Override
         void write() {
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
-                    "load_activity_snapshot");
-            final TaskSnapshot snapshot = mSnapshotLoader.loadTask(mCode,
-                    mUserId, false /* loadLowResolutionBitmap */);
-            synchronized (mService.getWindowManagerLock()) {
-                if (snapshot != null && !mActivityRecord.finishing) {
-                    mCache.putSnapshot(mActivityRecord, snapshot);
+            try {
+                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
+                        "load_activity_snapshot");
+                final TaskSnapshot snapshot = mSnapshotLoader.loadTask(mCode,
+                        mUserId, false /* loadLowResolutionBitmap */);
+                if (snapshot == null) {
+                    return;
                 }
+                synchronized (mService.getWindowManagerLock()) {
+                    // Verify the snapshot is still needed, and the activity is not finishing
+                    if (!hasRecord(mActivities[0])) {
+                        return;
+                    }
+                    for (ActivityRecord ar : mActivities) {
+                        mCache.putSnapshot(ar, snapshot);
+                    }
+                }
+            } finally {
+                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             }
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
 
         @Override
@@ -295,20 +295,88 @@
             return mCode == other.mCode && mUserId == other.mUserId
                     && mPersistInfoProvider == other.mPersistInfoProvider;
         }
+
+        @Override
+        public String toString() {
+            return "LoadActivitySnapshotItem{code=" + mCode + ", UserId=" + mUserId + "}";
+        }
     }
 
-    void recordSnapshot(ActivityRecord activity) {
-        if (shouldDisableSnapshots()) {
+    void loadActivitySnapshot() {
+        if (mPendingLoadActivity.isEmpty()) {
+            return;
+        }
+        // Only load if saved file exists.
+        final ArraySet<UserSavedFile> loadingFiles = new ArraySet<>();
+        for (int i = mPendingLoadActivity.size() - 1; i >= 0; i--) {
+            final ActivityRecord ar = mPendingLoadActivity.valueAt(i);
+            final UserSavedFile usf = findSavedFile(ar);
+            if (usf != null) {
+                loadingFiles.add(usf);
+            }
+        }
+        // Filter out the activity if the snapshot was removed.
+        for (int i = loadingFiles.size() - 1; i >= 0; i--) {
+            final UserSavedFile usf = loadingFiles.valueAt(i);
+            final ActivityRecord[] activities = usf.filterExistActivities(mPendingLoadActivity);
+            if (activities == null) {
+                continue;
+            }
+            if (getSnapshot(activities) != null) {
+                // Found the cache in memory, so skip loading from file.
+                continue;
+            }
+            loadSnapshotInner(activities, usf);
+        }
+    }
+
+    @VisibleForTesting
+    void loadSnapshotInner(ActivityRecord[] activities, UserSavedFile usf) {
+        synchronized (mSnapshotPersistQueue.getLock()) {
+            mSnapshotPersistQueue.insertQueueAtFirstLocked(new LoadActivitySnapshotItem(
+                    activities, usf.mFileId, usf.mUserId, mPersistInfoProvider));
+        }
+    }
+
+    /**
+     * Record one or multiple activities within a snapshot where those activities must belong to
+     * the same task.
+     * @param activity If the request activity is more than one, try to record those activities
+     *                 as a single snapshot, so those activities should belong to the same task.
+     */
+    void recordSnapshot(@NonNull ArrayList<ActivityRecord> activity) {
+        if (shouldDisableSnapshots() || activity.isEmpty()) {
             return;
         }
         if (DEBUG) {
             Slog.d(TAG, "ActivitySnapshotController#recordSnapshot " + activity);
         }
-        final TaskSnapshot snapshot = recordSnapshotInner(activity);
-        if (snapshot != null) {
-            final int code = getSystemHashCode(activity);
-            addUserSavedFile(code, activity.mUserId, snapshot);
+        final int size = activity.size();
+        final int[] mixedCode = new int[size];
+        if (size == 1) {
+            final ActivityRecord singleActivity = activity.get(0);
+            final TaskSnapshot snapshot = recordSnapshotInner(singleActivity);
+            if (snapshot != null) {
+                mixedCode[0] = getSystemHashCode(singleActivity);
+                addUserSavedFile(singleActivity.mUserId, snapshot, mixedCode);
+            }
+            return;
         }
+
+        final Task mainTask = activity.get(0).getTask();
+        // Snapshot by task controller with activity's scale.
+        final TaskSnapshot snapshot = mService.mTaskSnapshotController
+                .snapshot(mainTask, mHighResSnapshotScale);
+        if (snapshot == null) {
+            return;
+        }
+
+        for (int i = 0; i < activity.size(); ++i) {
+            final ActivityRecord next = activity.get(i);
+            mCache.putSnapshot(next, snapshot);
+            mixedCode[i] = getSystemHashCode(next);
+        }
+        addUserSavedFile(mainTask.mUserId, snapshot, mixedCode);
     }
 
     /**
@@ -331,7 +399,8 @@
         }
     }
 
-    private static int getSystemHashCode(ActivityRecord activity) {
+    @VisibleForTesting
+    static int getSystemHashCode(ActivityRecord activity) {
         return System.identityHashCode(activity);
     }
 
@@ -362,7 +431,13 @@
         if (ar.isVisibleRequested()) {
             mPendingDeleteActivity.add(ar);
             // load next one if exists.
-            addBelowActivityIfExist(ar, mPendingLoadActivity, true, "load-snapshot");
+            // Note if this transition is happen between two TaskFragment, the next N - 1 activity
+            // may not participant in this transition.
+            // Sample:
+            //   [TF1] close
+            //   [TF2] open
+            //   Bottom Activity <- Able to load this even it didn't participant the transition.
+            addBelowActivityIfExist(ar, mPendingLoadActivity, false, "load-snapshot");
         } else {
             // remove the snapshot for the one below close
             addBelowActivityIfExist(ar, mPendingRemoveActivity, true, "remove-snapshot");
@@ -478,10 +553,8 @@
     }
 
     private void adjustSavedFileOrder(Task nextTopTask) {
-        final int userId = nextTopTask.mUserId;
         nextTopTask.forAllActivities(ar -> {
-            final int code = getSystemHashCode(ar);
-            final UserSavedFile usf = getUserFiles(userId).get(code);
+            final UserSavedFile usf = findSavedFile(ar);
             if (usf != null) {
                 mSavedFilesInOrder.remove(usf);
                 mSavedFilesInOrder.add(usf);
@@ -494,9 +567,7 @@
         if (shouldDisableSnapshots()) {
             return;
         }
-        super.onAppRemoved(activity);
-        final int code = getSystemHashCode(activity);
-        removeIfUserSavedFileExist(code, activity.mUserId);
+        removeIfUserSavedFileExist(activity);
         if (DEBUG) {
             Slog.d(TAG, "ActivitySnapshotController#onAppRemoved delete snapshot " + activity);
         }
@@ -507,9 +578,7 @@
         if (shouldDisableSnapshots()) {
             return;
         }
-        super.onAppDied(activity);
-        final int code = getSystemHashCode(activity);
-        removeIfUserSavedFileExist(code, activity.mUserId);
+        removeIfUserSavedFileExist(activity);
         if (DEBUG) {
             Slog.d(TAG, "ActivitySnapshotController#onAppDied delete snapshot " + activity);
         }
@@ -558,78 +627,173 @@
         return mUserSavedFiles.get(userId);
     }
 
-    private void removeIfUserSavedFileExist(int code, int userId) {
-        final UserSavedFile usf = getUserFiles(userId).get(code);
+    UserSavedFile findSavedFile(@NonNull ActivityRecord ar) {
+        final int code = getSystemHashCode(ar);
+        return findSavedFile(ar.mUserId, code);
+    }
+
+    UserSavedFile findSavedFile(int userId, int code) {
+        final SparseArray<UserSavedFile> usfs = getUserFiles(userId);
+        return usfs.get(code);
+    }
+
+    private void removeCachedFiles(ActivityRecord ar) {
+        final UserSavedFile usf = findSavedFile(ar);
         if (usf != null) {
-            mUserSavedFiles.get(userId).remove(code);
-            mSavedFilesInOrder.remove(usf);
-            mPersister.removeSnapshot(code, userId);
+            for (int i = usf.mActivityIds.size() - 1; i >= 0; --i) {
+                final int activityId = usf.mActivityIds.get(i);
+                mCache.onIdRemoved(activityId);
+            }
         }
     }
 
-    private boolean containsFile(int code, int userId) {
-        return getUserFiles(userId).get(code) != null;
+    private void removeIfUserSavedFileExist(ActivityRecord ar) {
+        final UserSavedFile usf = findSavedFile(ar);
+        if (usf != null) {
+            final SparseArray<UserSavedFile> usfs = getUserFiles(ar.mUserId);
+            for (int i = usf.mActivityIds.size() - 1; i >= 0; --i) {
+                final int activityId = usf.mActivityIds.get(i);
+                usf.remove(activityId);
+                mCache.onIdRemoved(activityId);
+                usfs.remove(activityId);
+            }
+            mSavedFilesInOrder.remove(usf);
+            mPersister.removeSnapshot(usf.mFileId, ar.mUserId);
+        }
     }
 
-    private void addUserSavedFile(int code, int userId, TaskSnapshot snapshot) {
-        final SparseArray<UserSavedFile> savedFiles = getUserFiles(userId);
-        final UserSavedFile savedFile = savedFiles.get(code);
-        if (savedFile == null) {
-            final UserSavedFile usf = new UserSavedFile(code, userId);
-            savedFiles.put(code, usf);
-            mSavedFilesInOrder.add(usf);
-            mPersister.persistSnapshot(code, userId, snapshot);
+    @VisibleForTesting
+    boolean hasRecord(@NonNull ActivityRecord ar) {
+        return findSavedFile(ar) != null;
+    }
 
-            if (mSavedFilesInOrder.size() > MAX_PERSIST_SNAPSHOT_COUNT * 2) {
-                purgeSavedFile();
-            }
+    @VisibleForTesting
+    void addUserSavedFile(int userId, TaskSnapshot snapshot, @NonNull int[] code) {
+        final UserSavedFile savedFile = findSavedFile(userId, code[0]);
+        if (savedFile != null) {
+            Slog.w(TAG, "Duplicate request for recording activity snapshot " + savedFile);
+            return;
+        }
+        int fileId = 0;
+        for (int i = code.length - 1; i >= 0; --i) {
+            fileId ^= code[i];
+        }
+        final UserSavedFile usf = new UserSavedFile(fileId, userId);
+        SparseArray<UserSavedFile> usfs = getUserFiles(userId);
+        for (int i = code.length - 1; i >= 0; --i) {
+            usfs.put(code[i], usf);
+        }
+        usf.mActivityIds.addAll(code);
+        mSavedFilesInOrder.add(usf);
+        mPersister.persistSnapshot(fileId, userId, snapshot);
+
+        if (mSavedFilesInOrder.size() > MAX_PERSIST_SNAPSHOT_COUNT * 2) {
+            purgeSavedFile();
         }
     }
 
     private void purgeSavedFile() {
         final int savedFileCount = mSavedFilesInOrder.size();
         final int removeCount = savedFileCount - MAX_PERSIST_SNAPSHOT_COUNT;
-        final ArrayList<UserSavedFile> usfs = new ArrayList<>();
-        if (removeCount > 0) {
-            final int removeTillIndex = savedFileCount - removeCount;
-            for (int i = savedFileCount - 1; i > removeTillIndex; --i) {
-                final UserSavedFile usf = mSavedFilesInOrder.remove(i);
-                if (usf != null) {
-                    final SparseArray<UserSavedFile> records = getUserFiles(usf.mUserId);
-                    records.remove(usf.mFileId);
-                    usfs.add(usf);
-                }
-            }
+        if (removeCount < 1) {
+            return;
         }
-        if (usfs.size() > 0) {
-            removeSnapshotFiles(usfs);
+
+        final ArrayList<UserSavedFile> removeTargets = new ArrayList<>();
+        for (int i = removeCount - 1; i >= 0; --i) {
+            final UserSavedFile usf = mSavedFilesInOrder.remove(i);
+            final SparseArray<UserSavedFile> files = mUserSavedFiles.get(usf.mUserId);
+            for (int j = usf.mActivityIds.size() - 1; j >= 0; --j) {
+                mCache.removeRunningEntry(usf.mActivityIds.get(j));
+                files.remove(usf.mActivityIds.get(j));
+            }
+            removeTargets.add(usf);
+        }
+        for (int i = removeTargets.size() - 1; i >= 0; --i) {
+            final UserSavedFile usf = removeTargets.get(i);
+            mPersister.removeSnapshot(usf.mFileId, usf.mUserId);
         }
     }
 
-    private void removeSnapshotFiles(ArrayList<UserSavedFile> files) {
-        synchronized (mSnapshotPersistQueue.getLock()) {
-            mSnapshotPersistQueue.sendToQueueLocked(
-                    new SnapshotPersistQueue.WriteQueueItem(mPersistInfoProvider) {
-                        @Override
-                        void write() {
-                            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activity_remove_files");
-                            for (int i = files.size() - 1; i >= 0; --i) {
-                                final UserSavedFile usf = files.get(i);
-                                mSnapshotPersistQueue.deleteSnapshot(
-                                        usf.mFileId, usf.mUserId, mPersistInfoProvider);
-                            }
-                            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-                        }
-                    });
+    @Override
+    void dump(PrintWriter pw, String prefix) {
+        super.dump(pw, prefix);
+        final String doublePrefix = prefix + "  ";
+        final String triplePrefix = doublePrefix + "  ";
+        for (int i = mUserSavedFiles.size() - 1; i >= 0; --i) {
+            final SparseArray<UserSavedFile> usfs = mUserSavedFiles.valueAt(i);
+            pw.println(doublePrefix + "UserSavedFile userId=" + mUserSavedFiles.keyAt(i));
+            final ArraySet<UserSavedFile> sets = new ArraySet<>();
+            for (int j = usfs.size() - 1; j >= 0; --j) {
+                sets.add(usfs.valueAt(j));
+            }
+            for (int j = sets.size() - 1; j >= 0; --j) {
+                pw.println(triplePrefix + "SavedFile=" + sets.valueAt(j));
+            }
         }
     }
 
     static class UserSavedFile {
-        int mFileId;
-        int mUserId;
+        // The unique id as filename.
+        final int mFileId;
+        final int mUserId;
+
+        /**
+         * The Id of all activities which are includes in the snapshot.
+         */
+        final IntArray mActivityIds = new IntArray();
+
         UserSavedFile(int fileId, int userId) {
             mFileId = fileId;
             mUserId = userId;
         }
+
+        boolean contains(int code) {
+            return mActivityIds.contains(code);
+        }
+
+        void remove(int code) {
+            final int index = mActivityIds.indexOf(code);
+            if (index >= 0) {
+                mActivityIds.remove(index);
+            }
+        }
+
+        ActivityRecord[] filterExistActivities(
+                @NonNull ArraySet<ActivityRecord> pendingLoadActivity) {
+            ArrayList<ActivityRecord> matchedActivities = null;
+            for (int i = pendingLoadActivity.size() - 1; i >= 0; --i) {
+                final ActivityRecord ar = pendingLoadActivity.valueAt(i);
+                if (contains(getSystemHashCode(ar))) {
+                    if (matchedActivities == null) {
+                        matchedActivities = new ArrayList<>();
+                    }
+                    matchedActivities.add(ar);
+                }
+            }
+            if (matchedActivities == null || matchedActivities.size() != mActivityIds.size()) {
+                return null;
+            }
+            return matchedActivities.toArray(new ActivityRecord[0]);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("UserSavedFile{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" fileId=");
+            sb.append(Integer.toHexString(mFileId));
+            sb.append(", activityIds=[");
+            for (int i = mActivityIds.size() - 1; i >= 0; --i) {
+                sb.append(Integer.toHexString(mActivityIds.get(i)));
+                if (i > 0) {
+                    sb.append(',');
+                }
+            }
+            sb.append("]");
+            sb.append("}");
+            return sb.toString();
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index e7621ff..182e1c1 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY;
 import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
 import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.app.PendingIntent.FLAG_ONE_SHOT;
@@ -144,22 +145,20 @@
     }
 
     private IntentSender createIntentSenderForOriginalIntent(int callingUid, int flags) {
-        Bundle bOptions = deferCrossProfileAppsAnimationIfNecessary();
+        ActivityOptions activityOptions = deferCrossProfileAppsAnimationIfNecessary();
+        activityOptions.setPendingIntentCreatorBackgroundActivityStartMode(
+                MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
         final TaskFragment taskFragment = getLaunchTaskFragment();
         // If the original intent is going to be embedded, try to forward the embedding TaskFragment
         // and its task id to embed back the original intent.
         if (taskFragment != null) {
-            ActivityOptions activityOptions = bOptions != null
-                    ? ActivityOptions.fromBundle(bOptions)
-                    : ActivityOptions.makeBasic();
             activityOptions.setLaunchTaskFragmentToken(taskFragment.getFragmentToken());
-            bOptions = activityOptions.toBundle();
         }
         final IIntentSender target = mService.getIntentSenderLocked(
                 INTENT_SENDER_ACTIVITY, mCallingPackage, mCallingFeatureId, callingUid, mUserId,
                 null /*token*/, null /*resultCode*/, 0 /*requestCode*/,
                 new Intent[] { mIntent }, new String[] { mResolvedType },
-                flags, bOptions);
+                flags, activityOptions.toBundle());
         return new IntentSender(target);
     }
 
@@ -272,12 +271,12 @@
      *
      * @return the activity option used to start the original intent.
      */
-    private Bundle deferCrossProfileAppsAnimationIfNecessary() {
+    private ActivityOptions deferCrossProfileAppsAnimationIfNecessary() {
         if (hasCrossProfileAnimation()) {
             mActivityOptions = null;
-            return ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle();
+            return ActivityOptions.makeOpenCrossProfileAppsAnimation();
         }
-        return null;
+        return ActivityOptions.makeBasic();
     }
 
     private boolean interceptQuietProfileIfNeeded() {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 13f7152..d6f52b8 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -60,6 +60,7 @@
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
+import static com.android.server.pm.PackageArchiver.isArchivingEnabled;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
@@ -104,7 +105,6 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.AuxiliaryResolveInfo;
-import android.content.pm.Flags;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
@@ -1034,7 +1034,7 @@
         }
 
         if (err == ActivityManager.START_SUCCESS && aInfo == null) {
-            if (Flags.archiving()) {
+            if (isArchivingEnabled()) {
                 PackageArchiver packageArchiver = mService
                         .getPackageManagerInternalLocked()
                         .getPackageArchiver();
@@ -2053,8 +2053,8 @@
         }
 
         if (!mSupervisor.getBackgroundActivityLaunchController().checkActivityAllowedToStart(
-                mSourceRecord, r, newTask, targetTask, mLaunchFlags, mBalCode, mCallingUid,
-                mRealCallingUid)) {
+                mSourceRecord, r, newTask, avoidMoveToFront(), targetTask, mLaunchFlags, mBalCode,
+                mCallingUid, mRealCallingUid)) {
             return START_ABORTED;
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 5604b1a..ed556a5 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -796,4 +796,17 @@
      * @param token The activity token.
      */
     public abstract int getDisplayId(IBinder token);
+
+    /**
+     * Register a {@link CompatScaleProvider}.
+     */
+    public abstract void registerCompatScaleProvider(
+            @CompatScaleProvider.CompatScaleModeOrderId int id,
+            @NonNull CompatScaleProvider provider);
+
+    /**
+     * Unregister a {@link CompatScaleProvider}.
+     */
+    public abstract void unregisterCompatScaleProvider(
+            @CompatScaleProvider.CompatScaleModeOrderId int id);
 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 3959a5e..3397a3d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -7277,6 +7277,18 @@
         public void unregisterTaskStackListener(ITaskStackListener listener) {
             ActivityTaskManagerService.this.unregisterTaskStackListener(listener);
         }
+
+        @Override
+        public void registerCompatScaleProvider(@CompatScaleProvider.CompatScaleModeOrderId int id,
+                @NonNull CompatScaleProvider provider) {
+            ActivityTaskManagerService.this.registerCompatScaleProvider(id, provider);
+        }
+
+        @Override
+        public void unregisterCompatScaleProvider(
+                @CompatScaleProvider.CompatScaleModeOrderId int id) {
+            ActivityTaskManagerService.this.unregisterCompatScaleProvider(id);
+        }
     }
 
     static boolean isPip2ExperimentEnabled() {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 22d17b5..83ccbdc 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.view.View.FOCUS_FORWARD;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_NONE;
@@ -60,6 +61,7 @@
 import com.android.internal.policy.TransitionAnimation;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.wm.utils.InsetUtils;
+import com.android.window.flags.Flags;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -101,10 +103,6 @@
     static final boolean sPredictBackEnable =
             SystemProperties.getBoolean("persist.wm.debug.predictive_back", true);
 
-    static boolean isScreenshotEnabled() {
-        return SystemProperties.getInt("persist.wm.debug.predictive_back_screenshot", 0) != 0;
-    }
-
     // Notify focus window changed
     void onFocusChanged(WindowState newFocus) {
         mNavigationMonitor.onFocusWindowChanged(newFocus);
@@ -167,6 +165,24 @@
                 return null;
             }
 
+            // Move focus to the adjacent embedded window if it is higher than this window
+            final TaskFragment taskFragment = window.getTaskFragment();
+            final TaskFragment adjacentTaskFragment =
+                    taskFragment != null ? taskFragment.getAdjacentTaskFragment() : null;
+            if (adjacentTaskFragment != null && taskFragment.isEmbedded()
+                    && Flags.embeddedActivityBackNavFlag()) {
+                final WindowContainer parent = taskFragment.getParent();
+                if (parent.mChildren.indexOf(taskFragment) < parent.mChildren.indexOf(
+                        adjacentTaskFragment)) {
+                    mWindowManagerService.moveFocusToAdjacentWindow(window, FOCUS_FORWARD);
+                    window = wmService.getFocusedWindowLocked();
+                    if (window == null) {
+                        Slog.e(TAG, "Adjacent window is null, returning null.");
+                        return null;
+                    }
+                }
+            }
+
             // This is needed to bridge the old and new back behavior with recents.  While in
             // Overview with live tile enabled, the previous app is technically focused but we
             // add an input consumer to capture all input that would otherwise go to the apps
@@ -290,9 +306,11 @@
                     // keyguard locked and activities are unable to show when locked.
                     backType = BackNavigationInfo.TYPE_CALLBACK;
                 }
+            } else if (currentTask.mAtmService.getLockTaskController().isTaskLocked(currentTask)) {
+                // Do not predict if current task is in task locked.
+                backType = BackNavigationInfo.TYPE_CALLBACK;
             } else {
-                // TODO(208789724): Create single source of truth for this, maybe in
-                //  RootWindowContainer
+                // Check back-to-home or cross-task
                 prevTask = currentTask.mRootWindowContainer.getTask(t -> {
                     if (t.showToCurrentUser() && !t.mChildren.isEmpty()) {
                         final ActivityRecord ar = t.getTopNonFinishingActivity();
@@ -938,6 +956,18 @@
                 return;
             }
 
+            // Start fixed rotation for previous activity before create animation.
+            if (openingActivities.length == 1) {
+                final ActivityRecord next = openingActivities[0];
+                final DisplayContent dc = next.mDisplayContent;
+                dc.rotateInDifferentOrientationIfNeeded(next);
+                if (next.hasFixedRotationTransform()) {
+                    // Set the record so we can recognize it to continue to update display
+                    // orientation if the previous activity becomes the top later.
+                    dc.setFixedRotationLaunchingApp(next,
+                            next.getWindowConfiguration().getRotation());
+                }
+            }
             mOpenAnimAdaptor = new BackWindowAnimationAdaptorWrapper(true, mSwitchType, open);
             if (!mOpenAnimAdaptor.isValid()) {
                 Slog.w(TAG, "compose animations fail, skip");
@@ -1311,7 +1341,7 @@
                 Rect insets;
                 if (mainWindow != null) {
                     insets = mainWindow.getInsetsStateWithVisibilityOverride().calculateInsets(
-                            mBounds, WindowInsets.Type.systemBars(),
+                            mBounds, WindowInsets.Type.tappableElement(),
                             false /* ignoreVisibility */).toRect();
                     InsetUtils.addInsets(insets, mainWindow.mActivityRecord.getLetterboxInsets());
                 } else {
@@ -1327,7 +1357,8 @@
                 return mAnimationTarget;
             }
 
-            void createStartingSurface(@NonNull WindowContainer closeWindow) {
+            void createStartingSurface(@NonNull WindowContainer closeWindow,
+                    @NonNull ActivityRecord[] visibleOpenActivities) {
                 if (!mIsOpen) {
                     return;
                 }
@@ -1346,7 +1377,7 @@
                 if (mainActivity == null) {
                     return;
                 }
-                final TaskSnapshot snapshot = getSnapshot(mTarget);
+                final TaskSnapshot snapshot = getSnapshot(mTarget, visibleOpenActivities);
                 mRequestedStartingSurfaceId = openTask.mAtmService.mTaskOrganizerController
                         .addWindowlessStartingSurface(openTask, mainActivity,
                                 // Choose configuration from closeWindow, because the configuration
@@ -1489,7 +1520,8 @@
                         // Try to draw two snapshot within a WindowlessStartingWindow, or find
                         // another key for StartingWindowRecordManager.
                         && openAnimationAdaptor.length == 1) {
-                    openAnimationAdaptor[0].createStartingSurface(closeWindow);
+                    openAnimationAdaptor[0].createStartingSurface(closeWindow,
+                            visibleOpenActivities);
                 } else {
                     for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
                         setLaunchBehind(visibleOpenActivities[i]);
@@ -1589,27 +1621,18 @@
 
     private static void setLaunchBehind(@NonNull ActivityRecord activity) {
         if (!activity.isVisibleRequested()) {
-            activity.setVisibility(true);
             // The transition could commit the visibility and in the finishing state, that could
             // skip commitVisibility call in setVisibility cause the activity won't visible here.
             // Call it again to make sure the activity could be visible while handling the pending
             // animation.
-            activity.commitVisibility(true, true);
+            // Do not performLayout during prepare animation, because it could cause focus window
+            // change. Let that happen after the BackNavigationInfo has returned to shell.
+            activity.commitVisibility(true, false /* performLayout */);
             activity.mTransitionController.mSnapshotController
                     .mActivitySnapshotController.addOnBackPressedActivity(activity);
         }
         activity.mLaunchTaskBehind = true;
 
-        // Handle fixed rotation launching app.
-        final DisplayContent dc = activity.mDisplayContent;
-        dc.rotateInDifferentOrientationIfNeeded(activity);
-        if (activity.hasFixedRotationTransform()) {
-            // Set the record so we can recognize it to continue to update display
-            // orientation if the previous activity becomes the top later.
-            dc.setFixedRotationLaunchingApp(activity,
-                    activity.getWindowConfiguration().getRotation());
-        }
-
         ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
                 "Setting Activity.mLauncherTaskBehind to true. Activity=%s", activity);
         activity.mTaskSupervisor.mStoppingActivities.remove(activity);
@@ -1665,27 +1688,50 @@
         ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "onBackNavigationDone backType=%s, "
                 + "triggerBack=%b", backType, triggerBack);
 
-        mNavigationMonitor.stopMonitorForRemote();
-        mBackAnimationInProgress = false;
-        mShowWallpaper = false;
-        mPendingAnimationBuilder = null;
+        synchronized (mWindowManagerService.mGlobalLock) {
+            mNavigationMonitor.stopMonitorForRemote();
+            mBackAnimationInProgress = false;
+            mShowWallpaper = false;
+            // All animation should be done, clear any un-send animation.
+            mPendingAnimation = null;
+            mPendingAnimationBuilder = null;
+        }
     }
 
-    static TaskSnapshot getSnapshot(@NonNull WindowContainer w) {
+    static TaskSnapshot getSnapshot(@NonNull WindowContainer w,
+            ActivityRecord[] visibleOpenActivities) {
+        TaskSnapshot snapshot = null;
         if (w.asTask() != null) {
             final Task task = w.asTask();
-            return task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot(
+            snapshot = task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot(
                     task.mTaskId, task.mUserId, false /* restoreFromDisk */,
                     false /* isLowResolution */);
+        } else if (w.asActivityRecord() != null) {
+            final ActivityRecord ar = w.asActivityRecord();
+            snapshot = ar.mWmService.mSnapshotController.mActivitySnapshotController
+                    .getSnapshot(visibleOpenActivities);
         }
 
-        if (w.asActivityRecord() != null) {
-            final ActivityRecord ar = w.asActivityRecord();
-            return ar.mWmService.mSnapshotController.mActivitySnapshotController.getSnapshot(ar);
-        }
-        return null;
+        return isSnapshotCompatible(snapshot, visibleOpenActivities) ? snapshot : null;
     }
 
+    static boolean isSnapshotCompatible(@NonNull TaskSnapshot snapshot,
+            @NonNull ActivityRecord[] visibleOpenActivities) {
+        if (snapshot == null) {
+            return false;
+        }
+        boolean oneComponentMatch = false;
+        for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
+            final ActivityRecord ar = visibleOpenActivities[i];
+            if (!ar.isSnapshotOrientationCompatible(snapshot)) {
+                return false;
+            }
+            oneComponentMatch |= ar.isSnapshotComponentCompatible(snapshot);
+        }
+        return oneComponentMatch;
+    }
+
+
     void setWindowManager(WindowManagerService wm) {
         mWindowManagerService = wm;
         mAnimationHandler = new AnimationHandler(wm);
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index fc3a338..688a3b5 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -18,6 +18,10 @@
 
 import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+import static android.app.ComponentOptions.BackgroundActivityStartMode;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
@@ -30,7 +34,9 @@
 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
 import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
+import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck;
 import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
+import static com.android.window.flags.Flags.balRequireOptInSameUid;
 import static com.android.window.flags.Flags.balShowToasts;
 import static com.android.window.flags.Flags.balShowToastsBlocked;
 import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
@@ -52,6 +58,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.os.Process;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.util.ArraySet;
@@ -64,7 +71,6 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.UiThread;
 import com.android.server.am.PendingIntentRecord;
-import com.android.window.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.util.HashMap;
@@ -94,9 +100,9 @@
     public static final ActivityOptions ACTIVITY_OPTIONS_SYSTEM_DEFINED =
             ActivityOptions.makeBasic()
                     .setPendingIntentBackgroundActivityStartMode(
-                            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED)
+                            MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED)
                     .setPendingIntentCreatorBackgroundActivityStartMode(
-                            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
+                            MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
 
     private final ActivityTaskManagerService mService;
 
@@ -120,38 +126,56 @@
 
     static final int BAL_BLOCK = 0;
 
-    static final int BAL_ALLOW_DEFAULT = 1;
+    static final int BAL_ALLOW_DEFAULT =
+            FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_DEFAULT;
 
     // Following codes are in order of precedence
 
     /** Important UIDs which should be always allowed to launch activities */
-    static final int BAL_ALLOW_ALLOWLISTED_UID = 2;
+    static final int BAL_ALLOW_ALLOWLISTED_UID =
+            FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_ALLOWLISTED_UID;
 
     /** Apps that fulfill a certain role that can can always launch new tasks */
-    static final int BAL_ALLOW_ALLOWLISTED_COMPONENT = 3;
+    static final int BAL_ALLOW_ALLOWLISTED_COMPONENT =
+            FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_ALLOWLISTED_COMPONENT;
 
-    /** Apps which currently have a visible window or are bound by a service with a visible
-     * window */
-    static final int BAL_ALLOW_VISIBLE_WINDOW = 4;
+    /**
+     * Apps which currently have a visible window or are bound by a service with a visible
+     * window
+     */
+    static final int BAL_ALLOW_VISIBLE_WINDOW =
+            FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_VISIBLE_WINDOW;
 
     /** Allowed due to the PendingIntent sender */
-    static final int BAL_ALLOW_PENDING_INTENT = 5;
+    static final int BAL_ALLOW_PENDING_INTENT =
+            FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_PENDING_INTENT;
 
-    /** App has START_ACTIVITIES_FROM_BACKGROUND permission or BAL instrumentation privileges
-     * granted to it */
-    static final int BAL_ALLOW_PERMISSION = 6;
+    /**
+     * App has START_ACTIVITIES_FROM_BACKGROUND permission or BAL instrumentation privileges
+     * granted to it
+     */
+    static final int BAL_ALLOW_PERMISSION =
+            FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_BAL_PERMISSION;
 
     /** Process has SYSTEM_ALERT_WINDOW permission granted to it */
-    static final int BAL_ALLOW_SAW_PERMISSION = 7;
+    static final int BAL_ALLOW_SAW_PERMISSION =
+            FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_SAW_PERMISSION;
 
     /** App is in grace period after an activity was started or finished */
-    static final int BAL_ALLOW_GRACE_PERIOD = 8;
+    static final int BAL_ALLOW_GRACE_PERIOD =
+            FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_GRACE_PERIOD;
 
     /** App is in a foreground task or bound to a foreground service (but not itself visible) */
-    static final int BAL_ALLOW_FOREGROUND = 9;
+    static final int BAL_ALLOW_FOREGROUND =
+            FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_FOREGROUND;
 
     /** Process belongs to a SDK sandbox */
-    static final int BAL_ALLOW_SDK_SANDBOX = 10;
+    static final int BAL_ALLOW_SDK_SANDBOX =
+            FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_SDK_SANDBOX;
+
+    /** Process belongs to a SDK sandbox */
+    static final int BAL_ALLOW_NON_APP_VISIBLE_WINDOW =
+            FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_NON_APP_VISIBLE_WINDOW;
 
     static String balCodeToString(@BalCode int balCode) {
         return switch (balCode) {
@@ -160,6 +184,7 @@
             case BAL_ALLOW_DEFAULT -> "BAL_ALLOW_DEFAULT";
             case BAL_ALLOW_FOREGROUND -> "BAL_ALLOW_FOREGROUND";
             case BAL_ALLOW_GRACE_PERIOD -> "BAL_ALLOW_GRACE_PERIOD";
+            case BAL_ALLOW_NON_APP_VISIBLE_WINDOW -> "BAL_ALLOW_NON_APP_VISIBLE_WINDOW";
             case BAL_ALLOW_PENDING_INTENT -> "BAL_ALLOW_PENDING_INTENT";
             case BAL_ALLOW_PERMISSION -> "BAL_ALLOW_PERMISSION";
             case BAL_ALLOW_SAW_PERMISSION -> "BAL_ALLOW_SAW_PERMISSION";
@@ -221,6 +246,7 @@
         private final WindowProcessController mRealCallerApp;
         private final boolean mIsCallForResult;
         private final ActivityOptions mCheckedOptions;
+        private final String mAutoOptInReason;
         private BalVerdict mResultForCaller;
         private BalVerdict mResultForRealCaller;
 
@@ -244,28 +270,41 @@
             mRealCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
             mIsCallForResult = resultRecord != null;
             mCheckedOptions = checkedOptions;
-            if (balRequireOptInByPendingIntentCreator() // auto-opt in introduced with this feature
-                    && (originatingPendingIntent == null // not a PendingIntent
-                    || mIsCallForResult) // sent for result
-            ) {
+            @BackgroundActivityStartMode int callerBackgroundActivityStartMode =
+                    checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode();
+            @BackgroundActivityStartMode int realCallerBackgroundActivityStartMode =
+                    checkedOptions.getPendingIntentBackgroundActivityStartMode();
+
+            if (!balImproveRealCallerVisibilityCheck()) {
+                // without this fix the auto-opt ins below would violate CTS tests
+                mAutoOptInReason = null;
+            } else if (mIsCallForResult) {
+                mAutoOptInReason = "callForResult";
+            } else if (originatingPendingIntent == null) {
+                mAutoOptInReason = "notPendingIntent";
+            } else if (callingUid == realCallingUid && !balRequireOptInSameUid()) {
+                mAutoOptInReason = "sameUid";
+            } else {
+                mAutoOptInReason = null;
+            }
+
+            if (mAutoOptInReason != null) {
                 // grant BAL privileges unless explicitly opted out
                 mBalAllowedByPiCreatorWithHardening = mBalAllowedByPiCreator =
-                        checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
-                                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
+                        callerBackgroundActivityStartMode == MODE_BACKGROUND_ACTIVITY_START_DENIED
                                 ? BackgroundStartPrivileges.NONE
                                 : BackgroundStartPrivileges.ALLOW_BAL;
-                mBalAllowedByPiSender =
-                        checkedOptions.getPendingIntentBackgroundActivityStartMode()
-                                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
-                                ? BackgroundStartPrivileges.NONE
-                                : BackgroundStartPrivileges.ALLOW_BAL;
+                mBalAllowedByPiSender = realCallerBackgroundActivityStartMode
+                        == MODE_BACKGROUND_ACTIVITY_START_DENIED
+                        ? BackgroundStartPrivileges.NONE
+                        : BackgroundStartPrivileges.ALLOW_BAL;
             } else {
                 // for PendingIntents we restrict BAL based on target_sdk
                 mBalAllowedByPiCreatorWithHardening = getBackgroundStartPrivilegesAllowedByCreator(
                         callingUid, callingPackage, checkedOptions);
                 final BackgroundStartPrivileges mBalAllowedByPiCreatorWithoutHardening =
-                        checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
-                                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
+                        callerBackgroundActivityStartMode
+                                == MODE_BACKGROUND_ACTIVITY_START_DENIED
                                 ? BackgroundStartPrivileges.NONE
                                 : BackgroundStartPrivileges.ALLOW_BAL;
                 mBalAllowedByPiCreator = balRequireOptInByPendingIntentCreator()
@@ -307,11 +346,11 @@
         private BackgroundStartPrivileges getBackgroundStartPrivilegesAllowedByCreator(
                 int callingUid, String callingPackage, ActivityOptions checkedOptions) {
             switch (checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()) {
-                case ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
+                case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
                     return BackgroundStartPrivileges.ALLOW_BAL;
-                case ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED:
+                case MODE_BACKGROUND_ACTIVITY_START_DENIED:
                     return BackgroundStartPrivileges.NONE;
-                case ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
+                case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
                     // no explicit choice by the app - let us decide what to do
                     if (callingPackage != null) {
                         // determine based on the calling/creating package
@@ -412,6 +451,7 @@
             sb.append("; hasRealCaller: ").append(hasRealCaller());
             sb.append("; isCallForResult: ").append(mIsCallForResult);
             sb.append("; isPendingIntent: ").append(isPendingIntent());
+            sb.append("; autoOptInReason: ").append(mAutoOptInReason);
             if (hasRealCaller()) {
                 sb.append("; realCallingPackage: ")
                         .append(getDebugPackageName(mRealCallingPackage, mRealCallingUid));
@@ -437,6 +477,50 @@
             sb.append("]");
             return sb.toString();
         }
+
+        public boolean isPendingIntentBalAllowedByPermission() {
+            return PendingIntentRecord.isPendingIntentBalAllowedByPermission(mCheckedOptions);
+        }
+
+        public boolean callerExplicitOptInOrAutoOptIn() {
+            if (mAutoOptInReason == null) {
+                return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+                        == MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+            } else {
+                return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+                        != MODE_BACKGROUND_ACTIVITY_START_DENIED;
+            }
+        }
+
+        public boolean realCallerExplicitOptInOrAutoOptIn() {
+            if (mAutoOptInReason == null) {
+                return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+                        == MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+            } else {
+                return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+                        != MODE_BACKGROUND_ACTIVITY_START_DENIED;
+            }
+        }
+
+        public boolean callerExplicitOptOut() {
+            return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+                    == MODE_BACKGROUND_ACTIVITY_START_DENIED;
+        }
+
+        public boolean realCallerExplicitOptOut() {
+            return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+                    == MODE_BACKGROUND_ACTIVITY_START_DENIED;
+        }
+
+        public boolean callerExplicitOptInOrOut() {
+            return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+                    != MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+        }
+
+        public boolean realCallerExplicitOptInOrOut() {
+            return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+                    != MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+        }
     }
 
     static class BalVerdict {
@@ -605,7 +689,7 @@
         // PendingIntents is null).
         BalVerdict resultForRealCaller = state.callerIsRealCaller() && resultForCaller.allows()
                 ? resultForCaller
-                : checkBackgroundActivityStartAllowedBySender(state, checkedOptions)
+                : checkBackgroundActivityStartAllowedBySender(state)
                         .setBasedOnRealCaller();
         state.setResultForRealCaller(resultForRealCaller);
 
@@ -615,18 +699,14 @@
         }
 
         // Handle cases with explicit opt-in
-        if (resultForCaller.allows()
-                && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
-                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) {
+        if (resultForCaller.allows() && state.callerExplicitOptInOrAutoOptIn()) {
             if (DEBUG_ACTIVITY_STARTS) {
                 Slog.d(TAG, "Activity start explicitly allowed by caller. "
                         + state.dump());
             }
             return allowBasedOnCaller(state);
         }
-        if (resultForRealCaller.allows()
-                && checkedOptions.getPendingIntentBackgroundActivityStartMode()
-                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) {
+        if (resultForRealCaller.allows() && state.realCallerExplicitOptInOrAutoOptIn()) {
             if (DEBUG_ACTIVITY_STARTS) {
                 Slog.d(TAG, "Activity start explicitly allowed by real caller. "
                         + state.dump());
@@ -634,77 +714,35 @@
             return allowBasedOnRealCaller(state);
         }
         // Handle PendingIntent cases with default behavior next
-        boolean callerCanAllow = resultForCaller.allows()
-                && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
-                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+        boolean callerCanAllow = resultForCaller.allows() && !state.callerExplicitOptOut();
         boolean realCallerCanAllow = resultForRealCaller.allows()
-                && checkedOptions.getPendingIntentBackgroundActivityStartMode()
-                == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
-        if (callerCanAllow && realCallerCanAllow) {
-            // Both caller and real caller allow with system defined behavior
-            if (state.mBalAllowedByPiCreatorWithHardening.allowsBackgroundActivityStarts()) {
-                // Will be allowed even with BAL hardening.
-                if (DEBUG_ACTIVITY_STARTS) {
-                    Slog.d(TAG, "Activity start allowed by caller. "
-                            + state.dump());
-                }
-                return allowBasedOnCaller(state);
-            }
-            if (state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts()) {
-                Slog.wtf(TAG,
-                        "With Android 15 BAL hardening this activity start may be blocked"
-                                + " if the PI creator upgrades target_sdk to 35+"
-                                + " AND the PI sender upgrades target_sdk to 34+! "
-                                + state.dump());
-                showBalRiskToast();
-                return allowBasedOnCaller(state);
-            }
-            Slog.wtf(TAG,
-                    "Without Android 15 BAL hardening this activity start would be allowed"
-                            + " (missing opt in by PI creator or sender)! "
-                            + state.dump());
-            return abortLaunch(state);
-        }
+                && !state.realCallerExplicitOptOut();
         if (callerCanAllow) {
             // Allowed before V by creator
-            if (state.mBalAllowedByPiCreatorWithHardening.allowsBackgroundActivityStarts()) {
-                // Will be allowed even with BAL hardening.
-                if (DEBUG_ACTIVITY_STARTS) {
-                    Slog.d(TAG, "Activity start allowed by caller. "
-                            + state.dump());
-                }
-                return allowBasedOnCaller(state);
-            }
             if (state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts()) {
-                Slog.wtf(TAG,
-                        "With Android 15 BAL hardening this activity start may be blocked"
+                Slog.wtf(TAG, "With Android 15 BAL hardening this activity start may be blocked"
                                 + " if the PI creator upgrades target_sdk to 35+! "
                                 + " (missing opt in by PI creator)! "
                                 + state.dump());
                 showBalRiskToast();
                 return allowBasedOnCaller(state);
             }
-            Slog.wtf(TAG,
-                    "Without Android 15 BAL hardening this activity start would be allowed"
-                            + " (missing opt in by PI creator)! "
-                            + state.dump());
-            return abortLaunch(state);
         }
         if (realCallerCanAllow) {
             // Allowed before U by sender
             if (state.mBalAllowedByPiSender.allowsBackgroundActivityStarts()) {
-                Slog.wtf(TAG,
-                        "With Android 14 BAL hardening this activity start will be blocked"
+                Slog.wtf(TAG, "With Android 14 BAL hardening this activity start will be blocked"
                                 + " if the PI sender upgrades target_sdk to 34+! "
                                 + " (missing opt in by PI sender)! "
                                 + state.dump());
                 showBalRiskToast();
                 return allowBasedOnRealCaller(state);
             }
-            Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed"
-                    + " (missing opt in by PI sender)! "
-                    + state.dump());
-            return abortLaunch(state);
+        }
+        // caller or real caller could start the activity, but would need to explicitly opt in
+        if (callerCanAllow || realCallerCanAllow) {
+            Slog.wtf(TAG, "Without BAL hardening this activity start would be allowed "
+                            + state.dump());
         }
         // neither the caller not the realCaller can allow or have explicitly opted out
         return abortLaunch(state);
@@ -788,7 +826,7 @@
                     /*background*/ false, "callingUid has visible window");
         }
         if (mService.mActiveUids.hasNonAppVisibleWindow(callingUid)) {
-            return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
+            return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
                     /*background*/ false, "callingUid has non-app visible window");
         }
 
@@ -861,11 +899,9 @@
      * @return A code denoting which BAL rule allows an activity to be started,
      * or {@link #BAL_BLOCK} if the launch should be blocked
      */
-    BalVerdict checkBackgroundActivityStartAllowedBySender(
-            BalState state,
-            ActivityOptions checkedOptions) {
+    BalVerdict checkBackgroundActivityStartAllowedBySender(BalState state) {
 
-        if (PendingIntentRecord.isPendingIntentBalAllowedByPermission(checkedOptions)
+        if (state.isPendingIntentBalAllowedByPermission()
                 && ActivityManager.checkComponentPermission(
                 android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
                 state.mRealCallingUid, NO_PROCESS_UID, true) == PackageManager.PERMISSION_GRANTED) {
@@ -878,13 +914,13 @@
         // is allowed, or apps like live wallpaper with non app visible window will be allowed.
         final boolean appSwitchAllowedOrFg = state.mAppSwitchState == APP_SWITCH_ALLOW
                 || state.mAppSwitchState == APP_SWITCH_FG_ONLY;
-        if (Flags.balImproveRealCallerVisibilityCheck()) {
+        if (balImproveRealCallerVisibilityCheck()) {
             if (appSwitchAllowedOrFg && state.mRealCallingUidHasAnyVisibleWindow) {
                 return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
                         /*background*/ false, "realCallingUid has visible window");
             }
             if (mService.mActiveUids.hasNonAppVisibleWindow(state.mRealCallingUid)) {
-                return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
+                return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
                         /*background*/ false, "realCallingUid has non-app visible window");
             }
         } else {
@@ -953,7 +989,7 @@
                         BalVerdict balAllowedForUid = proc.areBackgroundActivityStartsAllowed(
                                 state.mAppSwitchState);
                         if (balAllowedForUid.allows()) {
-                            return balAllowedForCaller.withProcessInfo("process", proc);
+                            return balAllowedForUid.withProcessInfo("process", proc);
                         }
                     }
                 }
@@ -971,8 +1007,9 @@
      * create a new task or bring an existing one into the foreground
      */
     boolean checkActivityAllowedToStart(@Nullable ActivityRecord sourceRecord,
-            @NonNull ActivityRecord targetRecord, boolean newTask, @NonNull Task targetTask,
-            int launchFlags, int balCode, int callingUid, int realCallingUid) {
+            @NonNull ActivityRecord targetRecord, boolean newTask, boolean avoidMoveTaskToFront,
+            @Nullable Task targetTask, int launchFlags, int balCode, int callingUid,
+            int realCallingUid) {
         // BAL Exception allowed in all cases
         if (balCode == BAL_ALLOW_ALLOWLISTED_UID) {
             return true;
@@ -989,20 +1026,43 @@
                     || balCode == BAL_ALLOW_PERMISSION
                     || balCode == BAL_ALLOW_PENDING_INTENT
                     || balCode == BAL_ALLOW_SAW_PERMISSION
-                    || balCode == BAL_ALLOW_VISIBLE_WINDOW) {
+                    || balCode == BAL_ALLOW_VISIBLE_WINDOW
+                    || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW) {
                 return true;
             }
         }
 
         if (balCode == BAL_ALLOW_GRACE_PERIOD) {
+            // Allow if launching into new task, and caller matches most recently finished activity
             if (taskToFront && mTopFinishedActivity != null
                     && mTopFinishedActivity.mUid == callingUid) {
                 return true;
-            } else if (!taskToFront) {
-                FinishedActivityEntry finishedEntry =
-                        mTaskIdToFinishedActivity.get(targetTask.mTaskId);
-                if (finishedEntry != null && finishedEntry.mUid == callingUid) {
-                    return true;
+            }
+
+            // Launching into existing task - allow if matches most recently finished activity
+            // within the task.
+            // We can reach here multiple ways:
+            // 1. activity in fg fires intent (taskToFront = false, sourceRecord is available)
+            // 2. activity in bg fires intent (taskToFront = false, sourceRecord is available)
+            // 3. activity in bg fires intent with NEW_FLAG (taskToFront = true,
+            //         avoidMoveTaskToFront = true, sourceRecord is available)
+            // 4. activity in bg fires PI (taskToFront = true, avoidMoveTaskToFront = true,
+            //         sourceRecord is not available, targetTask may be available)
+            if (!taskToFront || avoidMoveTaskToFront) {
+                if (targetTask != null) {
+                    FinishedActivityEntry finishedEntry =
+                            mTaskIdToFinishedActivity.get(targetTask.mTaskId);
+                    if (finishedEntry != null && finishedEntry.mUid == callingUid) {
+                        return true;
+                    }
+                }
+
+                if (sourceRecord != null) {
+                    FinishedActivityEntry finishedEntry =
+                            mTaskIdToFinishedActivity.get(sourceRecord.getTask().mTaskId);
+                    if (finishedEntry != null && finishedEntry.mUid == callingUid) {
+                        return true;
+                    }
                 }
             }
         }
@@ -1026,7 +1086,7 @@
                 bas = isTopActivityMatchingUidAbsentForAsm(taskToCheck, sourceRecord.getUid(),
                         sourceRecord);
             }
-        } else if (!taskToFront) {
+        } else if (targetTask != null && (!taskToFront || avoidMoveTaskToFront)) {
             // We don't have a sourceRecord, and we're launching into an existing task.
             // Allow if callingUid is top of stack.
             bas = isTopActivityMatchingUidAbsentForAsm(targetTask, callingUid,
@@ -1039,12 +1099,14 @@
 
         // ASM rules have failed. Log why
         return logAsmFailureAndCheckFeatureEnabled(sourceRecord, callingUid, realCallingUid,
-                newTask, targetTask, targetRecord, balCode, launchFlags, bas, taskToFront);
+                newTask, avoidMoveTaskToFront, targetTask, targetRecord, balCode, launchFlags,
+                bas, taskToFront);
     }
 
     private boolean logAsmFailureAndCheckFeatureEnabled(ActivityRecord sourceRecord, int callingUid,
-            int realCallingUid, boolean newTask, Task targetTask, ActivityRecord targetRecord,
-            @BalCode int balCode, int launchFlags, BlockActivityStart bas, boolean taskToFront) {
+            int realCallingUid, boolean newTask, boolean avoidMoveTaskToFront, Task targetTask,
+            ActivityRecord targetRecord, @BalCode int balCode, int launchFlags,
+            BlockActivityStart bas, boolean taskToFront) {
 
         ActivityRecord targetTopActivity = targetTask == null ? null
                 : targetTask.getActivity(ar -> !ar.finishing && !ar.isAlwaysOnTop());
@@ -1061,7 +1123,7 @@
 
         String asmDebugInfo = getDebugInfoForActivitySecurity("Launch", sourceRecord,
                 targetRecord, targetTask, targetTopActivity, realCallingUid, balCode,
-                blockActivityStartAndFeatureEnabled, taskToFront);
+                blockActivityStartAndFeatureEnabled, taskToFront, avoidMoveTaskToFront);
 
         FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
                 /* caller_uid */
@@ -1193,7 +1255,7 @@
 
             Slog.i(TAG, getDebugInfoForActivitySecurity("Clear Top", sourceRecord, targetRecord,
                     targetTask, targetTaskTop, realCallingUid, balCode, shouldBlockActivityStart,
-                    /* taskToFront */ true));
+                    /* taskToFront */ true, /* avoidMoveTaskToFront */ false));
         }
     }
 
@@ -1307,7 +1369,7 @@
     private BlockActivityStart isTopActivityMatchingUidAbsentForAsm(@NonNull Task task,
             int uid, @Nullable ActivityRecord sourceRecord) {
         // If the source is visible, consider it 'top'.
-        if (sourceRecord != null && sourceRecord.isVisible()) {
+        if (sourceRecord != null && sourceRecord.isVisibleRequested()) {
             return new BlockActivityStart(false, false);
         }
 
@@ -1317,6 +1379,12 @@
             return new BlockActivityStart(false, false);
         }
 
+        // If UID is visible in target task, allow launch
+        if (task.forAllActivities((Predicate<ActivityRecord>)
+                ar -> ar.isUid(uid) && ar.isVisibleRequested())) {
+            return new BlockActivityStart(false, false);
+        }
+
         // Consider the source activity, whether or not it is finishing. Do not consider any other
         // finishing activity.
         Predicate<ActivityRecord> topOfStackPredicate = (ar) -> ar.equals(sourceRecord)
@@ -1408,27 +1476,26 @@
             @Nullable ActivityRecord sourceRecord, @NonNull ActivityRecord targetRecord,
             @Nullable Task targetTask, @Nullable ActivityRecord targetTopActivity,
             int realCallingUid, @BalCode int balCode,
-            boolean blockActivityStartAndFeatureEnabled, boolean taskToFront) {
+            boolean blockActivityStartAndFeatureEnabled, boolean taskToFront,
+            boolean avoidMoveTaskToFront) {
         final String prefix = "[ASM] ";
         Function<ActivityRecord, String> recordToString = (ar) -> {
             if (ar == null) {
                 return null;
             }
-            return (ar == sourceRecord ? " [source]=> "
+
+            return (ar == sourceRecord ?        " [source]=> "
                     : ar == targetTopActivity ? " [ top  ]=> "
-                    : ar == targetRecord ? " [target]=> "
-                    : "         => ")
-                    + ar
-                    + " :: visible=" + ar.isVisible()
-                    + ", finishing=" + ar.isFinishing()
-                    + ", alwaysOnTop=" + ar.isAlwaysOnTop()
-                    + ", taskFragment=" + ar.getTaskFragment();
+                    : ar == targetRecord ?      " [target]=> "
+                    :                           "         => ")
+                    + getDebugStringForActivityRecord(ar);
         };
 
         StringJoiner joiner = new StringJoiner("\n");
         joiner.add(prefix + "------ Activity Security " + action + " Debug Logging Start ------");
         joiner.add(prefix + "Block Enabled: " + blockActivityStartAndFeatureEnabled);
         joiner.add(prefix + "ASM Version: " + ActivitySecurityModelFeatureFlags.ASM_VERSION);
+        joiner.add(prefix + "System Time: " + SystemClock.uptimeMillis());
 
         boolean targetTaskMatchesSourceTask = targetTask != null
                 && sourceRecord != null && sourceRecord.getTask() == targetTask;
@@ -1440,6 +1507,8 @@
             joiner.add(prefix + "Real Calling Uid Package: " + realCallingPackage);
         } else {
             joiner.add(prefix + "Source Record: " + recordToString.apply(sourceRecord));
+            joiner.add(prefix + "Source Launch Package: " + sourceRecord.launchedFromPackage);
+            joiner.add(prefix + "Source Launch Intent: " + sourceRecord.intent);
             if (targetTaskMatchesSourceTask) {
                 joiner.add(prefix + "Source/Target Task: " + sourceRecord.getTask());
                 joiner.add(prefix + "Source/Target Task Stack: ");
@@ -1464,7 +1533,30 @@
         joiner.add(prefix + "Target Record: " + recordToString.apply(targetRecord));
         joiner.add(prefix + "Intent: " + targetRecord.intent);
         joiner.add(prefix + "TaskToFront: " + taskToFront);
+        joiner.add(prefix + "AvoidMoveToFront: " + avoidMoveTaskToFront);
         joiner.add(prefix + "BalCode: " + balCodeToString(balCode));
+        joiner.add(prefix + "LastResumedActivity: "
+                       + recordToString.apply(mService.mLastResumedActivity));
+
+        if (mTopFinishedActivity != null) {
+            joiner.add(prefix + "TopFinishedActivity: " + mTopFinishedActivity.mDebugInfo);
+        }
+
+        if (!mTaskIdToFinishedActivity.isEmpty()) {
+            joiner.add(prefix + "TaskIdToFinishedActivity: ");
+            mTaskIdToFinishedActivity.values().forEach(
+                    (fae) -> joiner.add(prefix + "  " + fae.mDebugInfo));
+        }
+
+        if (balCode == BAL_ALLOW_VISIBLE_WINDOW || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW
+                || balCode == BAL_ALLOW_FOREGROUND) {
+            Task task = sourceRecord != null ? sourceRecord.getTask() : targetTask;
+            if (task != null && task.getDisplayArea() != null) {
+                joiner.add(prefix + "Tasks: ");
+                task.getDisplayArea().forAllTasks((Consumer<Task>)
+                        t -> joiner.add(prefix + "   T: " + t.toFullString()));
+            }
+        }
 
         joiner.add(prefix + "------ Activity Security " + action + " Debug Logging End ------");
         return joiner.toString();
@@ -1501,7 +1593,8 @@
         Intent intent = state.mIntent;
 
         if (code == BAL_ALLOW_PENDING_INTENT
-                && (callingUid == Process.SYSTEM_UID || realCallingUid == Process.SYSTEM_UID)) {
+                && (callingUid < Process.FIRST_APPLICATION_UID
+                || realCallingUid < Process.FIRST_APPLICATION_UID)) {
             String activityName = intent != null
                     ? requireNonNull(intent.getComponent()).flattenToShortString() : "";
             writeBalAllowedLog(activityName, BAL_ALLOW_PENDING_INTENT,
@@ -1524,13 +1617,11 @@
                 state.mRealCallingUid,
                 state.mResultForCaller == null ? BAL_BLOCK : state.mResultForCaller.getRawCode(),
                 state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts(),
-                state.mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
-                        != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED,
+                state.callerExplicitOptInOrOut(),
                 state.mResultForRealCaller == null ? BAL_BLOCK
                         : state.mResultForRealCaller.getRawCode(),
                 state.mBalAllowedByPiSender.allowsBackgroundActivityStarts(),
-                state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
-                        != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED
+                state.realCallerExplicitOptInOrOut()
         );
     }
 
@@ -1549,7 +1640,7 @@
             return;
         }
 
-        if (!finishActivity.mVisibleRequested
+        if (!finishActivity.isVisibleRequested()
                 && finishActivity != finishActivity.getTask().getTopMostActivity()) {
             return;
         }
@@ -1595,10 +1686,22 @@
         }
     }
 
+    private static String getDebugStringForActivityRecord(ActivityRecord ar) {
+        return ar
+                + " :: visible=" + ar.isVisible()
+                + ", visibleRequested=" + ar.isVisibleRequested()
+                + ", finishing=" + ar.finishing
+                + ", alwaysOnTop=" + ar.isAlwaysOnTop()
+                + ", lastLaunchTime=" + ar.lastLaunchTime
+                + ", lastVisibleTime=" + ar.lastVisibleTime
+                + ", taskFragment=" + ar.getTaskFragment();
+    }
+
     private class FinishedActivityEntry {
         int mUid;
         int mTaskId;
         int mLaunchCount;
+        String mDebugInfo;
 
         FinishedActivityEntry(ActivityRecord ar) {
             FinishedActivityEntry entry = mTaskIdToFinishedActivity.get(ar.getTask().mTaskId);
@@ -1606,6 +1709,7 @@
             this.mUid = ar.getUid();
             this.mTaskId = taskId;
             this.mLaunchCount = entry == null || !ar.isUid(entry.mUid) ? 1 : entry.mLaunchCount + 1;
+            this.mDebugInfo = getDebugStringForActivityRecord(ar);
 
             mService.mH.postDelayed(() -> {
                 synchronized (mService.mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/CompatModePackages.java b/services/core/java/com/android/server/wm/CompatModePackages.java
index 1a8927e..b795987f 100644
--- a/services/core/java/com/android/server/wm/CompatModePackages.java
+++ b/services/core/java/com/android/server/wm/CompatModePackages.java
@@ -25,7 +25,6 @@
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
-import android.app.GameManagerInternal;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.Disabled;
@@ -52,7 +51,6 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.LocalServices;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -345,7 +343,6 @@
     }
 
     private final ActivityTaskManagerService mService;
-    private GameManagerInternal mGameManager;
     private final AtomicFile mFile;
     private final HashMap<String, Integer> mPackages = new HashMap<>();
     private final SparseBooleanArray mLegacyScreenCompatPackages = new SparseBooleanArray();
@@ -517,17 +514,6 @@
             }
         }
         final UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
-        if (mGameManager == null) {
-            mGameManager = LocalServices.getService(GameManagerInternal.class);
-        }
-        if (mGameManager != null) {
-            final int userId = userHandle.getIdentifier();
-            final float scalingFactor = mGameManager.getResolutionScalingFactor(packageName,
-                    userId);
-            if (scalingFactor > 0) {
-                return 1f / scalingFactor;
-            }
-        }
 
         final boolean isDownscaledEnabled = CompatChanges.isChangeEnabled(
                 DOWNSCALED, packageName, userHandle);
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 0f9e5b0..7052982 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -183,9 +183,6 @@
                         getCurrentDisplayChange(fromRotation, startBounds);
                 mDisplayContent.mTransitionController.requestStartTransition(transition,
                         /* startTask= */ null, /* remoteTransition= */ null, displayChange);
-                mDisplayContent.mTransitionController.setDisplaySyncMethod(displayChange,
-                        mDisplayContent);
-                transition.setAllReady();
             }
         });
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e7ecf52..82dbf8d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -160,6 +160,7 @@
 import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
 import static com.android.server.wm.utils.RegionUtils.forEachRectReverse;
 import static com.android.server.wm.utils.RegionUtils.rectListToRegion;
+import static com.android.window.flags.Flags.deferDisplayUpdates;
 import static com.android.window.flags.Flags.explicitRefreshRateHints;
 
 import android.annotation.IntDef;
@@ -174,7 +175,6 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
 import android.graphics.ColorSpace;
 import android.graphics.Insets;
 import android.graphics.Matrix;
@@ -246,7 +246,6 @@
 import android.window.DisplayWindowPolicyController;
 import android.window.IDisplayAreaOrganizer;
 import android.window.ScreenCapture;
-import android.window.ScreenCapture.SynchronousScreenCaptureListener;
 import android.window.SystemPerformanceHinter;
 import android.window.TransitionRequestInfo;
 
@@ -276,7 +275,6 @@
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
-import static com.android.window.flags.Flags.deferDisplayUpdates;
 
 /**
  * Utility class for keeping track of the WindowStates and other pertinent contents of a
@@ -1636,12 +1634,19 @@
         if (configChanged) {
             mWaitingForConfig = true;
             if (mTransitionController.isShellTransitionsEnabled()) {
-                final TransitionRequestInfo.DisplayChange change =
-                        mTransitionController.isCollecting()
+                final Rect startBounds = currentDisplayConfig.windowConfiguration.getBounds();
+                final Rect endBounds = mTmpConfiguration.windowConfiguration.getBounds();
+                final Transition transition = mTransitionController.getCollectingTransition();
+                final TransitionRequestInfo.DisplayChange change = transition != null
                                 ? null : new TransitionRequestInfo.DisplayChange(mDisplayId);
                 if (change != null) {
-                    change.setStartAbsBounds(currentDisplayConfig.windowConfiguration.getBounds());
-                    change.setEndAbsBounds(mTmpConfiguration.windowConfiguration.getBounds());
+                    change.setStartAbsBounds(startBounds);
+                    change.setEndAbsBounds(endBounds);
+                } else {
+                    transition.setKnownConfigChanges(this, changes);
+                    // A collecting transition is existed. The sync method must be set before
+                    // collecting this display, so WindowState#prepareSync can use the sync method.
+                    mTransitionController.setDisplaySyncMethod(startBounds, endBounds, this);
                 }
                 requestChangeTransitionIfNeeded(changes, change);
             } else if (mLastHasContent) {
@@ -5207,10 +5212,9 @@
     }
 
     /**
-     * Takes a snapshot of the display.  In landscape mode this grabs the whole screen.
-     * In portrait mode, it grabs the full screenshot.
+     * Creates a LayerCaptureArgs object to represent the entire DisplayContent
      */
-    Bitmap screenshotDisplayLocked() {
+    ScreenCapture.LayerCaptureArgs getLayerCaptureArgs() {
         if (!mWmService.mPolicy.isScreenOn()) {
             if (DEBUG_SCREENSHOT) {
                 Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
@@ -5218,24 +5222,10 @@
             return null;
         }
 
-        SynchronousScreenCaptureListener syncScreenCapture =
-                ScreenCapture.createSyncCaptureListener();
-
         getBounds(mTmpRect);
         mTmpRect.offsetTo(0, 0);
-        ScreenCapture.LayerCaptureArgs args =
-                new ScreenCapture.LayerCaptureArgs.Builder(getSurfaceControl())
-                        .setSourceCrop(mTmpRect).build();
-
-        ScreenCapture.captureLayers(args, syncScreenCapture);
-
-        final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
-                syncScreenCapture.getBuffer();
-        final Bitmap bitmap = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
-        if (bitmap == null) {
-            Slog.w(TAG_WM, "Failed to take screenshot");
-        }
-        return bitmap;
+        return new ScreenCapture.LayerCaptureArgs.Builder(getSurfaceControl())
+                .setSourceCrop(mTmpRect).build();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index 7f785af..a1799b4 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -92,35 +92,39 @@
         mRotation = rotation;
         mWidth = w;
         mHeight = h;
-        final Rect unrestricted = mUnrestricted;
-        unrestricted.set(0, 0, w, h);
-        state.setDisplayFrame(unrestricted);
+        final Rect u = mUnrestricted;
+        u.set(0, 0, w, h);
+        state.setDisplayFrame(u);
         state.setDisplayCutout(displayCutout);
         state.setRoundedCorners(roundedCorners);
         state.setPrivacyIndicatorBounds(indicatorBounds);
         state.setDisplayShape(displayShape);
         state.getDisplayCutoutSafe(safe);
-        if (safe.left > unrestricted.left) {
-            state.getOrCreateSource(ID_DISPLAY_CUTOUT_LEFT, displayCutout()).setFrame(
-                    unrestricted.left, unrestricted.top, safe.left, unrestricted.bottom);
+        if (safe.left > u.left) {
+            state.getOrCreateSource(ID_DISPLAY_CUTOUT_LEFT, displayCutout())
+                    .setFrame(u.left, u.top, safe.left, u.bottom)
+                    .updateSideHint(u);
         } else {
             state.removeSource(ID_DISPLAY_CUTOUT_LEFT);
         }
-        if (safe.top > unrestricted.top) {
-            state.getOrCreateSource(ID_DISPLAY_CUTOUT_TOP, displayCutout()).setFrame(
-                    unrestricted.left, unrestricted.top, unrestricted.right, safe.top);
+        if (safe.top > u.top) {
+            state.getOrCreateSource(ID_DISPLAY_CUTOUT_TOP, displayCutout())
+                    .setFrame(u.left, u.top, u.right, safe.top)
+                    .updateSideHint(u);
         } else {
             state.removeSource(ID_DISPLAY_CUTOUT_TOP);
         }
-        if (safe.right < unrestricted.right) {
-            state.getOrCreateSource(ID_DISPLAY_CUTOUT_RIGHT, displayCutout()).setFrame(
-                    safe.right, unrestricted.top, unrestricted.right, unrestricted.bottom);
+        if (safe.right < u.right) {
+            state.getOrCreateSource(ID_DISPLAY_CUTOUT_RIGHT, displayCutout())
+                    .setFrame(safe.right, u.top, u.right, u.bottom)
+                    .updateSideHint(u);
         } else {
             state.removeSource(ID_DISPLAY_CUTOUT_RIGHT);
         }
-        if (safe.bottom < unrestricted.bottom) {
-            state.getOrCreateSource(ID_DISPLAY_CUTOUT_BOTTOM, displayCutout()).setFrame(
-                    unrestricted.left, safe.bottom, unrestricted.right, unrestricted.bottom);
+        if (safe.bottom < u.bottom) {
+            state.getOrCreateSource(ID_DISPLAY_CUTOUT_BOTTOM, displayCutout())
+                    .setFrame(u.left, safe.bottom, u.right, u.bottom)
+                    .updateSideHint(u);
         } else {
             state.removeSource(ID_DISPLAY_CUTOUT_BOTTOM);
         }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 63ca592..e2bc59b 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -794,6 +794,9 @@
             }
             mService.mAtmService.mKeyguardController.updateDeferTransitionForAod(
                     mAwake /* waiting */);
+            if (!awake) {
+                mDisplayContent.mWallpaperController.onDisplaySwitchFinished();
+            }
         }
     }
 
@@ -836,7 +839,8 @@
         mRemoteInsetsControllerControlsSystemBars = remoteInsetsControllerControlsSystemBars;
     }
 
-    public void screenTurnedOn(ScreenOnListener screenOnListener) {
+    /** Prepares to turn on screen. The given listener is used to notify that it is ready. */
+    public void screenTurningOn(ScreenOnListener screenOnListener) {
         WindowProcessController visibleDozeUiProcess = null;
         synchronized (mLock) {
             mScreenOnEarly = true;
@@ -858,6 +862,11 @@
         }
     }
 
+    /** It is called after {@link #finishScreenTurningOn}. This runs on PowerManager's thread. */
+    public void screenTurnedOn() {
+        mDisplayContent.mWallpaperController.onDisplaySwitchFinished();
+    }
+
     public void screenTurnedOff() {
         synchronized (mLock) {
             mScreenOnEarly = false;
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 32d60c5..6a3cf43 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -469,8 +469,7 @@
 
                 case MSG_REMOVE_DRAG_SURFACE_TIMEOUT: {
                     synchronized (mService.mGlobalLock) {
-                        mService.mTransactionFactory.get()
-                                .reparent((SurfaceControl) msg.obj, null).apply();
+                        mService.mTransactionFactory.get().remove((SurfaceControl) msg.obj).apply();
                     }
                     break;
                 }
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index adbe3bc..d302f06 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -270,7 +270,7 @@
         }
         if (mSurfaceControl != null) {
             if (!mRelinquishDragSurfaceToDropTarget && !relinquishDragSurfaceToDragSource()) {
-                mTransaction.reparent(mSurfaceControl, null).apply();
+                mTransaction.remove(mSurfaceControl).apply();
             } else {
                 mDragDropController.sendTimeoutMessage(MSG_REMOVE_DRAG_SURFACE_TIMEOUT,
                         mSurfaceControl, DragDropController.DRAG_TIMEOUT_MS);
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index bf30af3..8035a29 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -112,7 +112,7 @@
      * z-layering reference so that we can place the recents input consumer above it.
      */
     private WeakReference<ActivityRecord> mActiveRecentsActivity = null;
-    private WeakReference<ActivityRecord> mActiveRecentsLayerRef = null;
+    private WeakReference<Task> mActiveRecentsLayerRef = null;
 
     private class UpdateInputWindows implements Runnable {
         @Override
@@ -389,9 +389,9 @@
     /**
      * Inform InputMonitor when recents is active so it can enable the recents input consumer.
      * @param activity The active recents activity. {@code null} means recents is not active.
-     * @param layer An activity whose Z-layer is used as a reference for how to sort the consumer.
+     * @param layer A task whose Z-layer is used as a reference for how to sort the consumer.
      */
-    void setActiveRecents(@Nullable ActivityRecord activity, @Nullable ActivityRecord layer) {
+    void setActiveRecents(@Nullable ActivityRecord activity, @Nullable Task layer) {
         final boolean clear = activity == null;
         final boolean wasActive = mActiveRecentsActivity != null && mActiveRecentsLayerRef != null;
         mActiveRecentsActivity = clear ? null : new WeakReference<>(activity);
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 9d5ddf3..d9dda4a 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -62,6 +62,8 @@
  */
 class InsetsSourceProvider {
 
+    private static final Rect EMPTY_RECT = new Rect();
+
     protected final DisplayContent mDisplayContent;
     protected final @NonNull InsetsSource mSource;
     protected WindowContainer mWindowContainer;
@@ -286,12 +288,15 @@
 
     private void updateSourceFrameForServerVisibility() {
         // Make sure we set the valid source frame only when server visible is true, because the
-        // frame may not yet determined that server side doesn't think the window is ready to
+        // frame may not yet be determined that server side doesn't think the window is ready to
         // visible. (i.e. No surface, pending insets that were given during layout, etc..)
-        if (mServerVisible) {
-            mSource.setFrame(mSourceFrame);
-        } else {
-            mSource.setFrame(0, 0, 0, 0);
+        final Rect frame = mServerVisible ? mSourceFrame : EMPTY_RECT;
+        if (mSource.getFrame().equals(frame)) {
+            return;
+        }
+        mSource.setFrame(frame);
+        if (mWindowContainer != null) {
+            mSource.updateSideHint(mWindowContainer.getBounds());
         }
     }
 
@@ -631,7 +636,7 @@
         }
         pw.print(prefix);
         pw.print("mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching);
-        pw.print("mHasPendingPosition="); pw.print(mHasPendingPosition);
+        pw.print(" mHasPendingPosition="); pw.print(mHasPendingPosition);
         pw.println();
         if (mWindowContainer != null) {
             pw.print(prefix + "mWindowContainer=");
diff --git a/services/core/java/com/android/server/wm/LegacyTransitionTracer.java b/services/core/java/com/android/server/wm/LegacyTransitionTracer.java
new file mode 100644
index 0000000..fb2d5be
--- /dev/null
+++ b/services/core/java/com/android/server/wm/LegacyTransitionTracer.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.os.Build.IS_USER;
+
+import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER;
+import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER_H;
+import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER_L;
+import static com.android.server.wm.shell.TransitionTraceProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.TraceBuffer;
+import com.android.server.wm.Transition.ChangeInfo;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper class to collect and dump transition traces.
+ */
+class LegacyTransitionTracer implements TransitionTracer {
+
+    private static final String LOG_TAG = "TransitionTracer";
+
+    private static final int ALWAYS_ON_TRACING_CAPACITY = 15 * 1024; // 15 KB
+    private static final int ACTIVE_TRACING_BUFFER_CAPACITY = 5000 * 1024; // 5 MB
+
+    // This will be the size the proto output streams are initialized to.
+    // Ideally this should fit most or all the proto objects we will create and be no bigger than
+    // that to ensure to don't use excessive amounts of memory.
+    private static final int CHUNK_SIZE = 64;
+
+    static final String WINSCOPE_EXT = ".winscope";
+    private static final String TRACE_FILE =
+            "/data/misc/wmtrace/wm_transition_trace" + WINSCOPE_EXT;
+    private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+
+    private final TraceBuffer mTraceBuffer = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY);
+
+    private final Object mEnabledLock = new Object();
+    private volatile boolean mActiveTracingEnabled = false;
+
+    /**
+     * Records key information about a transition that has been sent to Shell to be played.
+     * More information will be appended to the same proto object once the transition is finished or
+     * aborted.
+     * Transition information won't be added to the trace buffer until
+     * {@link #logFinishedTransition} or {@link #logAbortedTransition} is called for this
+     * transition.
+     *
+     * @param transition The transition that has been sent to Shell.
+     * @param targets Information about the target windows of the transition.
+     */
+    @Override
+    public void logSentTransition(Transition transition, ArrayList<ChangeInfo> targets) {
+        try {
+            final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
+            final long protoToken = outputStream
+                    .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
+            outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
+            outputStream.write(com.android.server.wm.shell.Transition.CREATE_TIME_NS,
+                    transition.mLogger.mCreateTimeNs);
+            outputStream.write(com.android.server.wm.shell.Transition.SEND_TIME_NS,
+                    transition.mLogger.mSendTimeNs);
+            outputStream.write(com.android.server.wm.shell.Transition.START_TRANSACTION_ID,
+                    transition.getStartTransaction().getId());
+            outputStream.write(com.android.server.wm.shell.Transition.FINISH_TRANSACTION_ID,
+                    transition.getFinishTransaction().getId());
+            dumpTransitionTargetsToProto(outputStream, transition, targets);
+            outputStream.end(protoToken);
+
+            mTraceBuffer.add(outputStream);
+        } catch (Exception e) {
+            // Don't let any errors in the tracing cause the transition to fail
+            Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
+        }
+    }
+
+    /**
+     * Completes the information dumped in {@link #logSentTransition} for a transition
+     * that has finished or aborted, and add the proto object to the trace buffer.
+     *
+     * @param transition The transition that has finished.
+     */
+    @Override
+    public void logFinishedTransition(Transition transition) {
+        try {
+            final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
+            final long protoToken = outputStream
+                    .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
+            outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
+            outputStream.write(com.android.server.wm.shell.Transition.FINISH_TIME_NS,
+                    transition.mLogger.mFinishTimeNs);
+            outputStream.end(protoToken);
+
+            mTraceBuffer.add(outputStream);
+        } catch (Exception e) {
+            // Don't let any errors in the tracing cause the transition to fail
+            Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
+        }
+    }
+
+    /**
+     * Same as {@link #logFinishedTransition} but don't add the transition to the trace buffer
+     * unless actively tracing.
+     *
+     * @param transition The transition that has been aborted
+     */
+    @Override
+    public void logAbortedTransition(Transition transition) {
+        try {
+            final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
+            final long protoToken = outputStream
+                    .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
+            outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
+            outputStream.write(com.android.server.wm.shell.Transition.ABORT_TIME_NS,
+                    transition.mLogger.mAbortTimeNs);
+            outputStream.end(protoToken);
+
+            mTraceBuffer.add(outputStream);
+        } catch (Exception e) {
+            // Don't let any errors in the tracing cause the transition to fail
+            Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
+        }
+    }
+
+    @Override
+    public void logRemovingStartingWindow(@NonNull StartingData startingData) {
+        if (startingData.mTransitionId == 0) {
+            return;
+        }
+        try {
+            final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
+            final long protoToken = outputStream
+                    .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
+            outputStream.write(com.android.server.wm.shell.Transition.ID,
+                    startingData.mTransitionId);
+            outputStream.write(
+                    com.android.server.wm.shell.Transition.STARTING_WINDOW_REMOVE_TIME_NS,
+                    SystemClock.elapsedRealtimeNanos());
+            outputStream.end(protoToken);
+
+            mTraceBuffer.add(outputStream);
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
+        }
+    }
+
+    private void dumpTransitionTargetsToProto(ProtoOutputStream outputStream,
+            Transition transition, ArrayList<ChangeInfo> targets) {
+        Trace.beginSection("TransitionTracer#dumpTransitionTargetsToProto");
+        if (mActiveTracingEnabled) {
+            outputStream.write(com.android.server.wm.shell.Transition.ID,
+                    transition.getSyncId());
+        }
+
+        outputStream.write(com.android.server.wm.shell.Transition.TYPE, transition.mType);
+        outputStream.write(com.android.server.wm.shell.Transition.FLAGS, transition.getFlags());
+
+        for (int i = 0; i < targets.size(); ++i) {
+            final long changeToken = outputStream
+                    .start(com.android.server.wm.shell.Transition.TARGETS);
+
+            final Transition.ChangeInfo target = targets.get(i);
+
+            final int layerId;
+            if (target.mContainer.mSurfaceControl.isValid()) {
+                layerId = target.mContainer.mSurfaceControl.getLayerId();
+            } else {
+                layerId = -1;
+            }
+
+            outputStream.write(com.android.server.wm.shell.Target.MODE, target.mReadyMode);
+            outputStream.write(com.android.server.wm.shell.Target.FLAGS, target.mReadyFlags);
+            outputStream.write(com.android.server.wm.shell.Target.LAYER_ID, layerId);
+
+            if (mActiveTracingEnabled) {
+                // What we use in the WM trace
+                final int windowId = System.identityHashCode(target.mContainer);
+                outputStream.write(com.android.server.wm.shell.Target.WINDOW_ID, windowId);
+            }
+
+            outputStream.end(changeToken);
+        }
+
+        Trace.endSection();
+    }
+
+    /**
+     * Starts collecting transitions for the trace.
+     * If called while a trace is already running, this will reset the trace.
+     */
+    @Override
+    public void startTrace(@Nullable PrintWriter pw) {
+        if (IS_USER) {
+            LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
+            return;
+        }
+        Trace.beginSection("TransitionTracer#startTrace");
+        LogAndPrintln.i(pw, "Starting shell transition trace.");
+        synchronized (mEnabledLock) {
+            mActiveTracingEnabled = true;
+            mTraceBuffer.resetBuffer();
+            mTraceBuffer.setCapacity(ACTIVE_TRACING_BUFFER_CAPACITY);
+        }
+        Trace.endSection();
+    }
+
+    /**
+     * Stops collecting the transition trace and dump to trace to file.
+     *
+     * Dumps the trace to @link{TRACE_FILE}.
+     */
+    @Override
+    public void stopTrace(@Nullable PrintWriter pw) {
+        stopTrace(pw, new File(TRACE_FILE));
+    }
+
+    /**
+     * Stops collecting the transition trace and dump to trace to file.
+     * @param outputFile The file to dump the transition trace to.
+     */
+    public void stopTrace(@Nullable PrintWriter pw, File outputFile) {
+        if (IS_USER) {
+            LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
+            return;
+        }
+        Trace.beginSection("TransitionTracer#stopTrace");
+        LogAndPrintln.i(pw, "Stopping shell transition trace.");
+        synchronized (mEnabledLock) {
+            mActiveTracingEnabled = false;
+            writeTraceToFileLocked(pw, outputFile);
+            mTraceBuffer.resetBuffer();
+            mTraceBuffer.setCapacity(ALWAYS_ON_TRACING_CAPACITY);
+        }
+        Trace.endSection();
+    }
+
+    /**
+     * Being called while taking a bugreport so that tracing files can be included in the bugreport.
+     *
+     * @param pw Print writer
+     */
+    @Override
+    public void saveForBugreport(@Nullable PrintWriter pw) {
+        if (IS_USER) {
+            LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
+            return;
+        }
+        Trace.beginSection("TransitionTracer#saveForBugreport");
+        synchronized (mEnabledLock) {
+            final File outputFile = new File(TRACE_FILE);
+            writeTraceToFileLocked(pw, outputFile);
+        }
+        Trace.endSection();
+    }
+
+    @Override
+    public boolean isTracing() {
+        return mActiveTracingEnabled;
+    }
+
+    private void writeTraceToFileLocked(@Nullable PrintWriter pw, File file) {
+        Trace.beginSection("TransitionTracer#writeTraceToFileLocked");
+        try {
+            ProtoOutputStream proto = new ProtoOutputStream(CHUNK_SIZE);
+            proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+            long timeOffsetNs =
+                    TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
+                            - SystemClock.elapsedRealtimeNanos();
+            proto.write(REAL_TO_ELAPSED_TIME_OFFSET_NANOS, timeOffsetNs);
+            int pid = android.os.Process.myPid();
+            LogAndPrintln.i(pw, "Writing file to " + file.getAbsolutePath()
+                    + " from process " + pid);
+            mTraceBuffer.writeTraceToFile(file, proto);
+        } catch (IOException e) {
+            LogAndPrintln.e(pw, "Unable to write buffer to file", e);
+        }
+        Trace.endSection();
+    }
+
+    private static class LogAndPrintln {
+        private static void i(@Nullable PrintWriter pw, String msg) {
+            Log.i(LOG_TAG, msg);
+            if (pw != null) {
+                pw.println(msg);
+                pw.flush();
+            }
+        }
+
+        private static void e(@Nullable PrintWriter pw, String msg) {
+            Log.e(LOG_TAG, msg);
+            if (pw != null) {
+                pw.println("ERROR: " + msg);
+                pw.flush();
+            }
+        }
+
+        private static void e(@Nullable PrintWriter pw, String msg, @NonNull Exception e) {
+            Log.e(LOG_TAG, msg, e);
+            if (pw != null) {
+                pw.println("ERROR: " + msg + " ::\n " + e);
+                pw.flush();
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 47972b3..0e2d3d1 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -148,7 +148,7 @@
 final class LetterboxUiController {
 
     private static final Predicate<ActivityRecord> FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE =
-            activityRecord -> activityRecord.fillsParent() && !activityRecord.isFinishing();
+            ActivityRecord::occludesParent;
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM;
 
@@ -1187,16 +1187,23 @@
                 && mUserAspectRatio != USER_MIN_ASPECT_RATIO_FULLSCREEN;
     }
 
-    boolean shouldApplyUserFullscreenOverride() {
+    boolean isUserFullscreenOverrideEnabled() {
         if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride)
                 || FALSE.equals(mBooleanPropertyAllowUserAspectRatioFullscreenOverride)
                 || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()) {
             return false;
         }
+        return true;
+    }
 
-        mUserAspectRatio = getUserMinAspectRatioOverrideCode();
+    boolean shouldApplyUserFullscreenOverride() {
+        if (isUserFullscreenOverrideEnabled()) {
+            mUserAspectRatio = getUserMinAspectRatioOverrideCode();
 
-        return mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN;
+            return mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN;
+        }
+
+        return false;
     }
 
     boolean isSystemOverrideToFullscreenEnabled() {
@@ -1417,7 +1424,7 @@
 
     @VisibleForTesting
     boolean shouldShowLetterboxUi(WindowState mainWindow) {
-        if (mIsRelaunchingAfterRequestedOrientationChanged || !isSurfaceReadyToShow(mainWindow)) {
+        if (mIsRelaunchingAfterRequestedOrientationChanged) {
             return mLastShouldShowLetterboxUi;
         }
 
@@ -1435,13 +1442,6 @@
     }
 
     @VisibleForTesting
-    boolean isSurfaceReadyToShow(WindowState mainWindow) {
-        return mainWindow.isDrawn() // Regular case
-                // Waiting for relayoutWindow to call preserveSurface
-                || mainWindow.isDragResizeChanged();
-    }
-
-    @VisibleForTesting
     boolean isSurfaceVisible(WindowState mainWindow) {
         return mainWindow.isOnScreen() && (mActivityRecord.isVisible()
                 || mActivityRecord.isVisibleRequested());
diff --git a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
new file mode 100644
index 0000000..eae9951
--- /dev/null
+++ b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.annotation.NonNull;
+import android.internal.perfetto.protos.PerfettoTrace;
+import android.os.SystemClock;
+import android.tracing.perfetto.DataSourceParams;
+import android.tracing.perfetto.InitArguments;
+import android.tracing.perfetto.Producer;
+import android.tracing.transition.TransitionDataSource;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+
+class PerfettoTransitionTracer implements TransitionTracer {
+    private final AtomicInteger mActiveTraces = new AtomicInteger(0);
+    private final TransitionDataSource mDataSource =
+            new TransitionDataSource(this.mActiveTraces::incrementAndGet, () -> {},
+                    this.mActiveTraces::decrementAndGet);
+
+    PerfettoTransitionTracer() {
+        Producer.init(InitArguments.DEFAULTS);
+        mDataSource.register(DataSourceParams.DEFAULTS);
+    }
+
+    /**
+     * Records key information about a transition that has been sent to Shell to be played.
+     * More information will be appended to the same proto object once the transition is finished or
+     * aborted.
+     * Transition information won't be added to the trace buffer until
+     * {@link #logFinishedTransition} or {@link #logAbortedTransition} is called for this
+     * transition.
+     *
+     * @param transition The transition that has been sent to Shell.
+     * @param targets Information about the target windows of the transition.
+     */
+    @Override
+    public void logSentTransition(Transition transition, ArrayList<Transition.ChangeInfo> targets) {
+        if (!isTracing()) {
+            return;
+        }
+
+        mDataSource.trace((ctx) -> {
+            final ProtoOutputStream os = ctx.newTracePacket();
+
+            final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+
+            os.write(PerfettoTrace.ShellTransition.ID, transition.getSyncId());
+            os.write(PerfettoTrace.ShellTransition.CREATE_TIME_NS,
+                    transition.mLogger.mCreateTimeNs);
+            os.write(PerfettoTrace.ShellTransition.SEND_TIME_NS, transition.mLogger.mSendTimeNs);
+            os.write(PerfettoTrace.ShellTransition.START_TRANSACTION_ID,
+                    transition.getStartTransaction().getId());
+            os.write(PerfettoTrace.ShellTransition.FINISH_TRANSACTION_ID,
+                    transition.getFinishTransaction().getId());
+            os.write(PerfettoTrace.ShellTransition.TYPE, transition.mType);
+            os.write(PerfettoTrace.ShellTransition.FLAGS, transition.getFlags());
+
+            addTransitionTargetsToProto(os, targets);
+
+            os.end(token);
+        });
+    }
+
+    /**
+     * Completes the information dumped in {@link #logSentTransition} for a transition
+     * that has finished or aborted, and add the proto object to the trace buffer.
+     *
+     * @param transition The transition that has finished.
+     */
+    @Override
+    public void logFinishedTransition(Transition transition) {
+        if (!isTracing()) {
+            return;
+        }
+
+        mDataSource.trace((ctx) -> {
+            final ProtoOutputStream os = ctx.newTracePacket();
+
+            final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+            os.write(PerfettoTrace.ShellTransition.ID, transition.getSyncId());
+            os.write(PerfettoTrace.ShellTransition.FINISH_TIME_NS,
+                    transition.mLogger.mFinishTimeNs);
+            os.end(token);
+        });
+    }
+
+    /**
+     * Same as {@link #logFinishedTransition} but don't add the transition to the trace buffer
+     * unless actively tracing.
+     *
+     * @param transition The transition that has been aborted
+     */
+    @Override
+    public void logAbortedTransition(Transition transition) {
+        if (!isTracing()) {
+            return;
+        }
+
+        mDataSource.trace((ctx) -> {
+            final ProtoOutputStream os = ctx.newTracePacket();
+
+            final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+            os.write(PerfettoTrace.ShellTransition.ID, transition.getSyncId());
+            os.write(PerfettoTrace.ShellTransition.WM_ABORT_TIME_NS,
+                    transition.mLogger.mAbortTimeNs);
+            os.end(token);
+        });
+    }
+
+    @Override
+    public void logRemovingStartingWindow(@NonNull StartingData startingData) {
+        if (!isTracing()) {
+            return;
+        }
+
+        mDataSource.trace((ctx) -> {
+            final ProtoOutputStream os = ctx.newTracePacket();
+
+            final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+            os.write(PerfettoTrace.ShellTransition.ID, startingData.mTransitionId);
+            os.write(PerfettoTrace.ShellTransition.STARTING_WINDOW_REMOVE_TIME_NS,
+                    SystemClock.elapsedRealtimeNanos());
+            os.end(token);
+        });
+    }
+
+    @Override
+    public void startTrace(PrintWriter pw) {
+        // No-op
+    }
+
+    @Override
+    public void stopTrace(PrintWriter pw) {
+        // No-op
+    }
+
+    @Override
+    public void saveForBugreport(PrintWriter pw) {
+        // Nothing to do here. Handled by Perfetto.
+    }
+
+    @Override
+    public boolean isTracing() {
+        return mActiveTraces.get() > 0;
+    }
+
+    private void addTransitionTargetsToProto(
+            ProtoOutputStream os,
+            ArrayList<Transition.ChangeInfo> targets
+    ) {
+        for (int i = 0; i < targets.size(); ++i) {
+            final Transition.ChangeInfo target = targets.get(i);
+
+            final int layerId;
+            if (target.mContainer.mSurfaceControl.isValid()) {
+                layerId = target.mContainer.mSurfaceControl.getLayerId();
+            } else {
+                layerId = -1;
+            }
+            final int windowId = System.identityHashCode(target.mContainer);
+
+            final long token = os.start(PerfettoTrace.ShellTransition.TARGETS);
+            os.write(PerfettoTrace.ShellTransition.Target.MODE, target.mReadyMode);
+            os.write(PerfettoTrace.ShellTransition.Target.FLAGS, target.mReadyFlags);
+            os.write(PerfettoTrace.ShellTransition.Target.LAYER_ID, layerId);
+            os.write(PerfettoTrace.ShellTransition.Target.WINDOW_ID, windowId);
+            os.end(token);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 10405ec..e027eb6 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1135,16 +1135,17 @@
                     if (!mFreezeTaskListReordering) {
                         // Simple case: this is not an affiliated task, so we just move it to the
                         // front unless overridden by the provided activity options
+                        int indexToAdd = findIndexToAdd(task);
                         mTasks.remove(taskIndex);
-                        mTasks.add(0, task);
+                        mTasks.add(indexToAdd, task);
                         if (taskIndex != 0) {
                             // Only notify when position changes
                             mTaskNotificationController.notifyTaskListUpdated();
                         }
 
                         if (DEBUG_RECENTS) {
-                            Slog.d(TAG_RECENTS, "addRecent: moving to top " + task
-                                    + " from " + taskIndex);
+                            Slog.d(TAG_RECENTS, "addRecent: moving " + task + " to index "
+                                    + indexToAdd + " from " + taskIndex);
                         }
                     }
                     notifyTaskPersisterLocked(task, false);
@@ -1231,6 +1232,37 @@
         notifyTaskPersisterLocked(task, false /* flush */);
     }
 
+    // Looks for a new index to move the recent Task. Note that the recent Task should not be
+    // placed higher than another recent Task that has higher hierarchical z-ordering.
+    private int findIndexToAdd(Task task) {
+        int indexToAdd = 0;
+        for (int i = 0; i < mTasks.size(); i++) {
+            final Task otherTask = mTasks.get(i);
+            if (task == otherTask) {
+                break;
+            }
+
+            if (!otherTask.isAttached()) {
+                // Stop searching if not attached.
+                break;
+            }
+
+            if (otherTask.inPinnedWindowingMode()) {
+                // Skip pip task without increasing index since pip is always on screen.
+                continue;
+            }
+
+            // Stop searching if the task has higher z-ordering, or increase the index and
+            // continue the search.
+            if (task.compareTo(otherTask) > 0) {
+                break;
+            }
+
+            indexToAdd = i + 1;
+        }
+        return indexToAdd;
+    }
+
     /**
      * Add the task to the bottom if possible.
      */
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 6033220..587cc74 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -808,7 +808,6 @@
         mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
         mWmService.mAtmService.mTaskFragmentOrganizerController.dispatchPendingEvents();
         mWmService.mSyncEngine.onSurfacePlacement();
-        mWmService.mAnimator.executeAfterPrepareSurfacesRunnables();
 
         checkAppTransitionReady(surfacePlacer);
 
@@ -2784,6 +2783,9 @@
         } else {
             throw new RuntimeException("Create the same sleep token twice: " + token);
         }
+        if (isSwappingDisplay) {
+            display.mWallpaperController.onDisplaySwitchStarted();
+        }
         return token;
     }
 
diff --git a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
new file mode 100644
index 0000000..bdb4588
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.content.Context.MEDIA_PROJECTION_SERVICE;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_ERROR;
+
+import android.media.projection.IMediaProjectionManager;
+import android.media.projection.IMediaProjectionWatcherCallback;
+import android.media.projection.MediaProjectionInfo;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.view.ContentRecordingSession;
+import android.window.IScreenRecordingCallback;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.Set;
+
+public class ScreenRecordingCallbackController {
+
+    private final class Callback implements IBinder.DeathRecipient {
+
+        IScreenRecordingCallback mCallback;
+        int mUid;
+
+        Callback(IScreenRecordingCallback callback, int uid) {
+            this.mCallback = callback;
+            this.mUid = uid;
+        }
+
+        public void binderDied() {
+            unregister(mCallback);
+        }
+    }
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private final Map<IBinder, Callback> mCallbacks = new ArrayMap<>();
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private final Map<Integer /*UID*/, Boolean> mLastInvokedStateByUid = new ArrayMap<>();
+
+    private final WindowManagerService mWms;
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private WindowContainer<WindowContainer> mRecordedWC;
+
+    private boolean mWatcherCallbackRegistered = false;
+
+    private final class MediaProjectionWatcherCallback extends
+            IMediaProjectionWatcherCallback.Stub {
+        @Override
+        public void onStart(MediaProjectionInfo mediaProjectionInfo) {
+            onScreenRecordingStart(mediaProjectionInfo);
+        }
+
+        @Override
+        public void onStop(MediaProjectionInfo mediaProjectionInfo) {
+            onScreenRecordingStop();
+        }
+
+        @Override
+        public void onRecordingSessionSet(MediaProjectionInfo mediaProjectionInfo,
+                ContentRecordingSession contentRecordingSession) {
+        }
+    }
+
+    ScreenRecordingCallbackController(WindowManagerService wms) {
+        mWms = wms;
+    }
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private void setRecordedWindowContainer(MediaProjectionInfo mediaProjectionInfo) {
+        if (mediaProjectionInfo.getLaunchCookie() == null) {
+            mRecordedWC = (WindowContainer) mWms.mRoot.getDefaultDisplay();
+        } else {
+            mRecordedWC = mWms.mRoot.getActivity(activity -> activity.mLaunchCookie
+                    == mediaProjectionInfo.getLaunchCookie().binder).getTask();
+        }
+    }
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private void ensureMediaProjectionWatcherCallbackRegistered() {
+        if (mWatcherCallbackRegistered) {
+            return;
+        }
+
+        IBinder binder = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
+        IMediaProjectionManager mediaProjectionManager =
+                IMediaProjectionManager.Stub.asInterface(binder);
+
+        long identityToken = Binder.clearCallingIdentity();
+        MediaProjectionInfo mediaProjectionInfo = null;
+        try {
+            mediaProjectionInfo = mediaProjectionManager.addCallback(
+                    new MediaProjectionWatcherCallback());
+            mWatcherCallbackRegistered = true;
+        } catch (RemoteException e) {
+            ProtoLog.e(WM_ERROR, "Failed to register MediaProjectionWatcherCallback");
+        } finally {
+            Binder.restoreCallingIdentity(identityToken);
+        }
+
+        if (mediaProjectionInfo != null) {
+            setRecordedWindowContainer(mediaProjectionInfo);
+        }
+    }
+
+    boolean register(IScreenRecordingCallback callback) {
+        synchronized (mWms.mGlobalLock) {
+            ensureMediaProjectionWatcherCallbackRegistered();
+
+            IBinder binder = callback.asBinder();
+            int uid = Binder.getCallingUid();
+
+            if (mCallbacks.containsKey(binder)) {
+                return mLastInvokedStateByUid.get(uid);
+            }
+
+            Callback callbackInfo = new Callback(callback, uid);
+            try {
+                binder.linkToDeath(callbackInfo, 0);
+            } catch (RemoteException e) {
+                return false;
+            }
+
+            boolean uidInRecording = uidHasRecordedActivity(callbackInfo.mUid);
+            mLastInvokedStateByUid.put(callbackInfo.mUid, uidInRecording);
+            mCallbacks.put(binder, callbackInfo);
+            return uidInRecording;
+        }
+    }
+
+    void unregister(IScreenRecordingCallback callback) {
+        synchronized (mWms.mGlobalLock) {
+            IBinder binder = callback.asBinder();
+            Callback callbackInfo = mCallbacks.remove(binder);
+            binder.unlinkToDeath(callbackInfo, 0);
+
+            boolean uidHasCallback = false;
+            for (Callback cb : mCallbacks.values()) {
+                if (cb.mUid == callbackInfo.mUid) {
+                    uidHasCallback = true;
+                    break;
+                }
+            }
+            if (!uidHasCallback) {
+                mLastInvokedStateByUid.remove(callbackInfo.mUid);
+            }
+        }
+    }
+
+    private void onScreenRecordingStart(MediaProjectionInfo mediaProjectionInfo) {
+        synchronized (mWms.mGlobalLock) {
+            setRecordedWindowContainer(mediaProjectionInfo);
+            dispatchCallbacks(getRecordedUids(), true /* visibleInScreenRecording*/);
+        }
+    }
+
+    private void onScreenRecordingStop() {
+        synchronized (mWms.mGlobalLock) {
+            dispatchCallbacks(getRecordedUids(), false /*visibleInScreenRecording*/);
+            mRecordedWC = null;
+        }
+    }
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    void onProcessActivityVisibilityChanged(int uid, boolean processVisible) {
+        // If recording isn't active or there's no registered callback for the uid, there's nothing
+        // to do on this visibility change.
+        if (mRecordedWC == null || !mLastInvokedStateByUid.containsKey(uid)) {
+            return;
+        }
+
+        // If the callbacks are already in the correct state, avoid making duplicate callbacks for
+        // the same state. This can happen when:
+        // * a process becomes visible but its UID already has a recorded activity from another
+        //   process.
+        // * a process becomes invisible but its UID already doesn't have any recorded activities.
+        if (processVisible == mLastInvokedStateByUid.get(uid)) {
+            return;
+        }
+
+        // If the process visibility change doesn't change the visibility of the UID, avoid making
+        // duplicate callbacks for the same state. This can happen when:
+        // * a process becomes visible but the newly visible activity isn't in the recorded window
+        //   container.
+        // * a process becomes invisible but there are still activities being recorded for the UID.
+        boolean uidInRecording = uidHasRecordedActivity(uid);
+        if ((processVisible && !uidInRecording) || (!processVisible && uidInRecording)) {
+            return;
+        }
+
+        dispatchCallbacks(Set.of(uid), processVisible);
+    }
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private boolean uidHasRecordedActivity(int uid) {
+        if (mRecordedWC == null) {
+            return false;
+        }
+        boolean[] hasRecordedActivity = {false};
+        mRecordedWC.forAllActivities(activityRecord -> {
+            if (activityRecord.getUid() == uid && activityRecord.isVisibleRequested()) {
+                hasRecordedActivity[0] = true;
+                return true;
+            }
+            return false;
+        }, true /*traverseTopToBottom*/);
+        return hasRecordedActivity[0];
+    }
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private Set<Integer> getRecordedUids() {
+        Set<Integer> result = new ArraySet<>();
+        if (mRecordedWC == null) {
+            return result;
+        }
+        mRecordedWC.forAllActivities(activityRecord -> {
+            if (activityRecord.isVisibleRequested() && mLastInvokedStateByUid.containsKey(
+                    activityRecord.getUid())) {
+                result.add(activityRecord.getUid());
+            }
+        }, true /*traverseTopToBottom*/);
+        return result;
+    }
+
+    @GuardedBy("WindowManagerService.mGlobalLock")
+    private void dispatchCallbacks(Set<Integer> uids, boolean visibleInScreenRecording) {
+        if (uids.isEmpty()) {
+            return;
+        }
+
+        for (Integer uid : uids) {
+            mLastInvokedStateByUid.put(uid, visibleInScreenRecording);
+        }
+
+        for (Callback callback : mCallbacks.values()) {
+            if (!uids.contains(callback.mUid)) {
+                continue;
+            }
+            try {
+                callback.mCallback.onScreenRecordingStateChanged(visibleInScreenRecording);
+            } catch (RemoteException e) {
+                // Client has died. Cleanup is handled via DeathRecipient.
+            }
+        }
+    }
+
+    void dump(PrintWriter pw) {
+        pw.format("ScreenRecordingCallbackController:\n");
+        pw.format("  Registered callbacks:\n");
+        for (Map.Entry<IBinder, Callback> entry : mCallbacks.entrySet()) {
+            pw.format("    callback=%s uid=%s\n", entry.getKey(), entry.getValue().mUid);
+        }
+        pw.format("  Last invoked states:\n");
+        for (Map.Entry<Integer, Boolean> entry : mLastInvokedStateByUid.entrySet()) {
+            pw.format("    uid=%s isVisibleInScreenRecording=%s\n", entry.getKey(),
+                    entry.getValue());
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/SensitiveContentPackages.java b/services/core/java/com/android/server/wm/SensitiveContentPackages.java
index 3862b82..a7d6903b 100644
--- a/services/core/java/com/android/server/wm/SensitiveContentPackages.java
+++ b/services/core/java/com/android/server/wm/SensitiveContentPackages.java
@@ -21,7 +21,6 @@
 
 import java.io.PrintWriter;
 import java.util.Objects;
-import java.util.Set;
 
 /**
  * Cache of distinct package/uid pairs that require being blocked from screen capture. This class is
@@ -41,10 +40,33 @@
         return false;
     }
 
-    /** Replaces the set of package/uid pairs to set that should be blocked from screen capture */
-    public void setShouldBlockScreenCaptureForApp(@NonNull Set<PackageInfo> packageInfos) {
-        mProtectedPackages.clear();
+    /**
+     * Adds the set of package/uid pairs to set that should be blocked from screen capture
+     *
+     * @param packageInfos packages to be blocked
+     * @return {@code true} if packages set is modified, {@code false} otherwise.
+     */
+    public boolean addBlockScreenCaptureForApps(@NonNull ArraySet<PackageInfo> packageInfos) {
+        if (mProtectedPackages.equals(packageInfos)) {
+            // new set is equal to current set of packages, no need to update
+            return false;
+        }
         mProtectedPackages.addAll(packageInfos);
+        return true;
+    }
+
+    /**
+     * Clears the set of package/uid pairs that should be blocked from screen capture
+     *
+     * @return {@code true} if packages set is modified, {@code false} otherwise.
+     */
+    public boolean clearBlockedApps() {
+        if (mProtectedPackages.isEmpty()) {
+            // set was already empty
+            return false;
+        }
+        mProtectedPackages.clear();
+        return true;
     }
 
     void dump(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index ed54ea8..083872a 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -75,6 +75,7 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.view.View;
+import android.view.View.FocusDirection;
 import android.view.WindowInsets;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowManager;
@@ -668,7 +669,7 @@
     }
 
     @Override
-    public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
+    public void sendWallpaperCommand(IBinder window, String action, int x, int y,
             int z, Bundle extras, boolean sync) {
         synchronized (mService.mGlobalLock) {
             final long ident = Binder.clearCallingIdentity();
@@ -679,10 +680,9 @@
                 if (mCanAlwaysUpdateWallpaper
                         || windowState == wallpaperController.getWallpaperTarget()
                         || windowState == wallpaperController.getPrevWallpaperTarget()) {
-                    return wallpaperController.sendWindowWallpaperCommandUnchecked(
+                    wallpaperController.sendWindowWallpaperCommandUnchecked(
                             windowState, action, x, y, z, extras, sync);
                 }
-                return null;
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -1000,6 +1000,17 @@
         }
         return didTransfer;
     }
+
+    @Override
+    public boolean moveFocusToAdjacentWindow(IWindow fromWindow, @FocusDirection int direction) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return mService.moveFocusToAdjacentWindow(this, fromWindow, direction);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
     @Override
     public void generateDisplayHash(IWindow window, Rect boundsInWindow, String hashAlgorithm,
             RemoteCallback callback) {
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index b6f040a..cb388ef 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -160,9 +160,7 @@
                 if (!allOpensOptInOnBackInvoked() || mCloseActivities.isEmpty()) {
                     return;
                 }
-                for (int i = mCloseActivities.size() - 1; i >= 0; --i) {
-                    controller.recordSnapshot(mCloseActivities.get(i));
-                }
+                controller.recordSnapshot(mCloseActivities);
             }
         }
     }
@@ -213,5 +211,6 @@
     void dump(PrintWriter pw, String prefix) {
         mTaskSnapshotController.dump(pw, prefix);
         mActivitySnapshotController.dump(pw, prefix);
+        mSnapshotPersistQueue.dump(pw, prefix);
     }
 }
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index bffdf54..3578971 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -41,6 +41,7 @@
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.util.ArrayDeque;
 
 /**
@@ -155,7 +156,9 @@
     void deleteSnapshot(int index, int userId, PersistInfoProvider provider) {
         final File protoFile = provider.getProtoFile(index, userId);
         final File bitmapLowResFile = provider.getLowResolutionBitmapFile(index, userId);
-        protoFile.delete();
+        if (protoFile.exists()) {
+            protoFile.delete();
+        }
         if (bitmapLowResFile.exists()) {
             bitmapLowResFile.delete();
         }
@@ -177,7 +180,7 @@
                     } else {
                         next = mWriteQueue.poll();
                         if (next != null) {
-                            if (next.isReady()) {
+                            if (next.isReady(mUserManagerInternal)) {
                                 isReadyToWrite = true;
                                 next.onDequeuedLocked();
                             } else {
@@ -210,14 +213,16 @@
 
     abstract static class WriteQueueItem {
         protected final PersistInfoProvider mPersistInfoProvider;
-        WriteQueueItem(@NonNull PersistInfoProvider persistInfoProvider) {
+        protected final int mUserId;
+        WriteQueueItem(@NonNull PersistInfoProvider persistInfoProvider, int userId) {
             mPersistInfoProvider = persistInfoProvider;
+            mUserId = userId;
         }
         /**
          * @return {@code true} if item is ready to have {@link WriteQueueItem#write} called
          */
-        boolean isReady() {
-            return true;
+        boolean isReady(UserManagerInternal userManager) {
+            return userManager.isUserUnlocked(mUserId);
         }
 
         abstract void write();
@@ -242,14 +247,12 @@
 
     class StoreWriteQueueItem extends WriteQueueItem {
         private final int mId;
-        private final int mUserId;
         private final TaskSnapshot mSnapshot;
 
         StoreWriteQueueItem(int id, int userId, TaskSnapshot snapshot,
                 PersistInfoProvider provider) {
-            super(provider);
+            super(provider, userId);
             mId = id;
-            mUserId = userId;
             mSnapshot = snapshot;
         }
 
@@ -268,13 +271,10 @@
         }
 
         @Override
-        boolean isReady() {
-            return mUserManagerInternal.isUserUnlocked(mUserId);
-        }
-
-        @Override
         void write() {
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "StoreWriteQueueItem");
+            if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "StoreWriteQueueItem#" + mId);
+            }
             if (!mPersistInfoProvider.createDirectory(mUserId)) {
                 Slog.e(TAG, "Unable to create snapshot directory for user dir="
                         + mPersistInfoProvider.getDirectory(mUserId));
@@ -389,6 +389,11 @@
             return mId == other.mId && mUserId == other.mUserId
                     && mPersistInfoProvider == other.mPersistInfoProvider;
         }
+
+        @Override
+        public String toString() {
+            return "StoreWriteQueueItem{ID=" + mId + ", UserId=" + mUserId + "}";
+        }
     }
 
     DeleteWriteQueueItem createDeleteWriteQueueItem(int id, int userId,
@@ -398,12 +403,10 @@
 
     private class DeleteWriteQueueItem extends WriteQueueItem {
         private final int mId;
-        private final int mUserId;
 
         DeleteWriteQueueItem(int id, int userId, PersistInfoProvider provider) {
-            super(provider);
+            super(provider, userId);
             mId = id;
-            mUserId = userId;
         }
 
         @Override
@@ -412,5 +415,24 @@
             deleteSnapshot(mId, mUserId, mPersistInfoProvider);
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
+
+        @Override
+        public String toString() {
+            return "DeleteWriteQueueItem{ID=" + mId + ", UserId=" + mUserId + "}";
+        }
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        final WriteQueueItem[] items;
+        synchronized (mLock) {
+            items = mWriteQueue.toArray(new WriteQueueItem[0]);
+        }
+        if (items.length == 0) {
+            return;
+        }
+        pw.println(prefix + "PersistQueue contains:");
+        for (int i = items.length - 1; i >= 0; --i) {
+            pw.println(prefix + "  " + items[i] + "");
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a7a6bf2..8f9ed83 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -208,6 +208,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
@@ -1702,6 +1703,8 @@
         final ActivityRecord r = findActivityInHistory(newR.mActivityComponent, newR.mUserId);
         if (r == null) return null;
 
+        moveTaskFragmentsToBottomIfNeeded(r, finishCount);
+
         final PooledPredicate f = PooledLambda.obtainPredicate(
                 (ActivityRecord ar, ActivityRecord boundaryActivity) ->
                         finishActivityAbove(ar, boundaryActivity, finishCount),
@@ -1722,6 +1725,50 @@
         return r;
     }
 
+    /**
+     * Moves {@link TaskFragment}s to the bottom if the flag
+     * {@link TaskFragment#isMoveToBottomIfClearWhenLaunch} is {@code true}.
+     */
+    @VisibleForTesting
+    void moveTaskFragmentsToBottomIfNeeded(@NonNull ActivityRecord r, @NonNull int[] finishCount) {
+        final int activityIndex = mChildren.indexOf(r);
+        if (activityIndex < 0) {
+            return;
+        }
+
+        List<TaskFragment> taskFragmentsToMove = null;
+
+        // Find the TaskFragments that need to be moved
+        for (int i = mChildren.size() - 1; i > activityIndex; i--) {
+            final TaskFragment taskFragment = mChildren.get(i).asTaskFragment();
+            if (taskFragment != null && taskFragment.isMoveToBottomIfClearWhenLaunch()) {
+                if (taskFragmentsToMove == null) {
+                    taskFragmentsToMove = new ArrayList<>();
+                }
+                taskFragmentsToMove.add(taskFragment);
+            }
+        }
+        if (taskFragmentsToMove == null) {
+            return;
+        }
+
+        // Move the TaskFragments to the bottom of the Task. Their relative orders are preserved.
+        final int size = taskFragmentsToMove.size();
+        for (int i = 0; i < size; i++) {
+            final TaskFragment taskFragment = taskFragmentsToMove.get(i);
+
+            // The visibility of the TaskFragment may change. Collect it in the transition so that
+            // transition animation can be properly played.
+            mTransitionController.collect(taskFragment);
+
+            positionChildAt(POSITION_BOTTOM, taskFragment, false /* includeParents */);
+        }
+
+        // Treat it as if the TaskFragments are finished so that a transition animation can be
+        // played to send the TaskFragments back and bring the activity to front.
+        finishCount[0] += size;
+    }
+
     private static boolean finishActivityAbove(ActivityRecord r, ActivityRecord boundaryActivity,
             @NonNull int[] finishCount) {
         // Stop operation once we reach the boundary activity.
@@ -3511,9 +3558,11 @@
                 && top.mLetterboxUiController.isSystemOverrideToFullscreenEnabled();
         appCompatTaskInfo.isFromLetterboxDoubleTap = top != null
                 && top.mLetterboxUiController.isFromDoubleTap();
-        if (appCompatTaskInfo.isLetterboxDoubleTapEnabled) {
+        if (top != null) {
             appCompatTaskInfo.topActivityLetterboxWidth = top.getBounds().width();
             appCompatTaskInfo.topActivityLetterboxHeight = top.getBounds().height();
+        }
+        if (appCompatTaskInfo.isLetterboxDoubleTapEnabled) {
             if (appCompatTaskInfo.isTopActivityPillarboxed()) {
                 // Pillarboxed
                 appCompatTaskInfo.topActivityLetterboxHorizontalPosition =
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index f56759f..11e7bb0 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.Manifest.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE;
 import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
@@ -45,7 +46,6 @@
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
 import static com.android.server.wm.ActivityRecord.State.PAUSED;
 import static com.android.server.wm.ActivityRecord.State.PAUSING;
@@ -89,7 +89,6 @@
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
 import android.os.IBinder;
 import android.os.UserHandle;
 import android.util.DisplayMetrics;
@@ -99,7 +98,6 @@
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.window.ITaskFragmentOrganizer;
-import android.window.ScreenCapture;
 import android.window.TaskFragmentAnimationParams;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentOrganizerToken;
@@ -113,7 +111,6 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -363,6 +360,21 @@
      */
     private boolean mIsolatedNav;
 
+    /**
+     * Whether the TaskFragment should move to bottom of task when any activity below it is
+     * launched in clear top mode.
+     */
+    private boolean mMoveToBottomIfClearWhenLaunch;
+
+    /**
+     * If {@code true}, transitions are allowed even if this TaskFragment is empty. If
+     * {@code false}, transitions will wait until this TaskFragment becomes non-empty or other
+     * conditions are met. Default to {@code false}.
+     *
+     * @see #isReadyToTransit
+     */
+    private boolean mAllowTransitionWhenEmpty;
+
     /** When set, will force the task to report as invisible. */
     static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1;
     static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
@@ -388,10 +400,6 @@
     /** For calculating app bounds, i.e. the area without the nav bar and display cutout. */
     private final Rect mTmpNonDecorBounds = new Rect();
 
-    //TODO(b/207481538) Remove once the infrastructure to support per-activity screenshot is
-    // implemented
-    HashMap<String, ScreenCapture.ScreenshotHardwareBuffer> mBackScreenshots = new HashMap<>();
-
     private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper =
             new EnsureActivitiesVisibleHelper(this);
 
@@ -503,6 +511,19 @@
         mIsolatedNav = isolatedNav;
     }
 
+    /**
+     * Sets whether transitions are allowed when the TaskFragment is empty. If {@code true},
+     * transitions are allowed when the TaskFragment is empty. If {@code false}, transitions
+     * will wait until the TaskFragment becomes non-empty or other conditions are met. Default
+     * to {@code false}.
+     */
+    void setAllowTransitionWhenEmpty(boolean allowTransitionWhenEmpty) {
+        if (!isEmbedded()) {
+            return;
+        }
+        mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
+    }
+
     /** @see #mIsolatedNav */
     boolean isIsolatedNav() {
         return isEmbedded() && mIsolatedNav;
@@ -705,6 +726,9 @@
             // TaskFragment to have bounds outside of the parent bounds.
             return false;
         }
+        if (hasEmbedAnyAppInUntrustedModePermission(mTaskFragmentOrganizerUid)) {
+            return true;
+        }
         return (a.info.flags & FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING)
                 == FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
     }
@@ -726,17 +750,30 @@
             return true;
         }
 
-        Set<String> knownActivityEmbeddingCerts = a.info.getKnownActivityEmbeddingCerts();
-        if (knownActivityEmbeddingCerts.isEmpty()) {
-            // An application must either declare that it allows untrusted embedding, or specify
-            // a set of app certificates that are allowed to embed it in trusted mode.
-            return false;
-        }
-
-        AndroidPackage hostPackage = mAtmService.getPackageManagerInternalLocked()
+        final AndroidPackage hostPackage = mAtmService.getPackageManagerInternalLocked()
                 .getPackage(uid);
 
-        return hostPackage != null && hostPackage.getSigningDetails().hasAncestorOrSelfWithDigest(
+        return hostPackage != null
+                && isAllowedToEmbedActivityInTrustedModeByHostPackage(a, hostPackage);
+    }
+
+    @VisibleForTesting
+    boolean isAllowedToEmbedActivityInTrustedModeByHostPackage(
+            @NonNull ActivityRecord a, @NonNull AndroidPackage hostPackage) {
+
+        // Known certs declared in the <activity> tag
+        Set<String> knownActivityEmbeddingCerts = a.info.getKnownActivityEmbeddingCerts();
+
+        // If the activity-level value is specified, it takes precedence. Otherwise, we read the
+        // application-level value.
+        if (knownActivityEmbeddingCerts.isEmpty()) {
+            // Known certs declared in the <application> tag
+            knownActivityEmbeddingCerts = a.info.applicationInfo.getKnownActivityEmbeddingCerts();
+        }
+
+        // An application must specify a set of app certificates that are allowed to embed it in
+        // trusted mode.
+        return hostPackage.getSigningDetails().hasAncestorOrSelfWithDigest(
                 knownActivityEmbeddingCerts);
     }
 
@@ -763,6 +800,15 @@
     }
 
     /**
+     * Checks if a particular app uid has the {@link EMBED_ANY_APP_IN_UNTRUSTED_MODE} permission.
+     */
+    private static boolean hasEmbedAnyAppInUntrustedModePermission(int uid) {
+        return Flags.untrustedEmbeddingAnyAppPermission()
+                && checkPermission(EMBED_ANY_APP_IN_UNTRUSTED_MODE,
+                PermissionChecker.PID_UNKNOWN, uid) == PackageManager.PERMISSION_GRANTED;
+    }
+
+    /**
      * Checks if all activities in the task fragment are embedded as fully trusted.
      * @see #isFullyTrustedEmbedding(ActivityRecord, int)
      * @param uid   uid of the TaskFragment organizer.
@@ -2051,17 +2097,6 @@
         super.addChild(child, index);
 
         if (isAddingActivity && task != null) {
-            // TODO(b/207481538): temporary per-activity screenshoting
-            if (r != null && BackNavigationController.isScreenshotEnabled()) {
-                ProtoLog.v(WM_DEBUG_BACK_PREVIEW, "Screenshotting Activity %s",
-                        r.mActivityComponent.flattenToString());
-                Rect outBounds = r.getBounds();
-                ScreenCapture.ScreenshotHardwareBuffer backBuffer = ScreenCapture.captureLayers(
-                        r.mSurfaceControl,
-                        new Rect(0, 0, outBounds.width(), outBounds.height()),
-                        1f);
-                mBackScreenshots.put(r.mActivityComponent.flattenToString(), backBuffer);
-            }
             addingActivity.inHistory = true;
             task.onDescendantActivityAdded(taskHadActivity, activityType, addingActivity);
         }
@@ -2808,8 +2843,9 @@
             return true;
         }
         // We don't want to start the transition if the organized TaskFragment is empty, unless
-        // it is requested to be removed.
-        if (getTopNonFinishingActivity() != null || mIsRemovalRequested) {
+        // it is requested to be removed or the mAllowTransitionWhenEmpty flag is true.
+        if (getTopNonFinishingActivity() != null || mIsRemovalRequested
+                || mAllowTransitionWhenEmpty) {
             return true;
         }
         // Organizer shouldn't change embedded TaskFragment in PiP.
@@ -2863,19 +2899,6 @@
         return !mCreatedByOrganizer || mIsRemovalRequested;
     }
 
-    @Nullable
-    HardwareBuffer getSnapshotForActivityRecord(@Nullable ActivityRecord r) {
-        if (!BackNavigationController.isScreenshotEnabled()) {
-            return null;
-        }
-        if (r != null && r.mActivityComponent != null) {
-            ScreenCapture.ScreenshotHardwareBuffer backBuffer =
-                    mBackScreenshots.get(r.mActivityComponent.flattenToString());
-            return backBuffer != null ? backBuffer.getHardwareBuffer() : null;
-        }
-        return null;
-    }
-
     @Override
     void removeChild(WindowContainer child) {
         removeChild(child, true /* removeSelfIfPossible */);
@@ -2884,13 +2907,6 @@
     void removeChild(WindowContainer child, boolean removeSelfIfPossible) {
         super.removeChild(child);
         final ActivityRecord r = child.asActivityRecord();
-        if (BackNavigationController.isScreenshotEnabled()) {
-            //TODO(b/207481538) Remove once the infrastructure to support per-activity screenshot is
-            // implemented
-            if (r != null) {
-                mBackScreenshots.remove(r.mActivityComponent.flattenToString());
-            }
-        }
         final WindowProcessController hostProcess = getOrganizerProcessIfDifferent(r);
         if (hostProcess != null) {
             hostProcess.removeEmbeddedActivity(r);
@@ -3045,6 +3061,14 @@
         mEmbeddedDimArea = embeddedDimArea;
     }
 
+    void setMoveToBottomIfClearWhenLaunch(boolean moveToBottomIfClearWhenLaunch) {
+        mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch;
+    }
+
+    boolean isMoveToBottomIfClearWhenLaunch() {
+        return mMoveToBottomIfClearWhenLaunch;
+    }
+
     @VisibleForTesting
     boolean isDimmingOnParentTask() {
         return mEmbeddedDimArea == EMBEDDED_DIM_AREA_PARENT_TASK;
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 4e7a9bd..f4e9957 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -303,8 +303,15 @@
         } else {
             if (DEBUG) appendLog("non-freeform-task-display-area");
         }
+        final boolean isUpdatingExistingTaskWindowingMode = task != null
+                && task.getRequestedOverrideWindowingMode() != WINDOWING_MODE_UNDEFINED
+                && launchMode != task.getRequestedOverrideWindowingMode();
+        if (DEBUG && isUpdatingExistingTaskWindowingMode) {
+            appendLog("updating-existing-task-windowing-mode");
+        }
         // If launch mode matches display windowing mode, let it inherit from display.
         outParams.mWindowingMode = launchMode == suggestedDisplayArea.getWindowingMode()
+                && !isUpdatingExistingTaskWindowingMode
                 ? WINDOWING_MODE_UNDEFINED : launchMode;
 
         if (phase == PHASE_WINDOWING_MODE) {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 233daad..87be74a 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -19,11 +19,13 @@
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 
 import android.os.Trace;
+import android.os.UserHandle;
 import android.util.ArraySet;
 import android.window.TaskSnapshot;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.UserManagerInternal;
 
 import java.io.File;
 import java.util.Arrays;
@@ -84,6 +86,9 @@
      *                       model.
      */
     void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
+        if (runningUserIds.length == 0) {
+            return;
+        }
         synchronized (mLock) {
             mPersistedTaskIdsSinceLastRemoveObsolete.clear();
             mSnapshotPersistQueue.sendToQueueLocked(new RemoveObsoleteFilesQueueItem(
@@ -99,12 +104,22 @@
         @VisibleForTesting
         RemoveObsoleteFilesQueueItem(ArraySet<Integer> persistentTaskIds,
                 int[] runningUserIds, PersistInfoProvider provider) {
-            super(provider);
+            super(provider, runningUserIds.length > 0 ? runningUserIds[0] : UserHandle.USER_SYSTEM);
             mPersistentTaskIds = new ArraySet<>(persistentTaskIds);
             mRunningUserIds = Arrays.copyOf(runningUserIds, runningUserIds.length);
         }
 
         @Override
+        boolean isReady(UserManagerInternal userManagerInternal) {
+            for (int i = mRunningUserIds.length - 1; i >= 0; --i) {
+                if (!userManagerInternal.isUserUnlocked(mRunningUserIds[i])) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
         void write() {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RemoveObsoleteFilesQueueItem");
             final ArraySet<Integer> newPersistedTaskIds;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index e1bf8f8..2accf9a 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1933,29 +1933,29 @@
                 dc.getInputMonitor().getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
         ActivityRecord recentsActivity = null;
         if (recentsAnimationInputConsumer != null) {
-            // find the top-most going-away activity and the recents activity. The top-most
+            // Find the top-most going-away task and the recents activity. The top-most
             // is used as layer reference while the recents is used for registering the consumer
             // override.
-            ActivityRecord topActivity = null;
+            Task topNonRecentsTask = null;
             for (int i = 0; i < info.getChanges().size(); ++i) {
-                final TransitionInfo.Change change = info.getChanges().get(i);
-                if (change.getTaskInfo() == null) continue;
-                final Task task = Task.fromWindowContainerToken(
-                        info.getChanges().get(i).getTaskInfo().token);
+                final ActivityManager.RunningTaskInfo taskInfo =
+                        info.getChanges().get(i).getTaskInfo();
+                if (taskInfo == null) continue;
+                final Task task = Task.fromWindowContainerToken(taskInfo.token);
                 if (task == null) continue;
-                final int activityType = change.getTaskInfo().topActivityType;
+                final int activityType = taskInfo.topActivityType;
                 final boolean isRecents = activityType == ACTIVITY_TYPE_HOME
                         || activityType == ACTIVITY_TYPE_RECENTS;
                 if (isRecents && recentsActivity == null) {
                     recentsActivity = task.getTopVisibleActivity();
-                } else if (!isRecents && topActivity == null) {
-                    topActivity = task.getTopNonFinishingActivity();
+                } else if (!isRecents && topNonRecentsTask == null) {
+                    topNonRecentsTask = task;
                 }
             }
-            if (recentsActivity != null && topActivity != null) {
+            if (recentsActivity != null && topNonRecentsTask != null) {
                 recentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(
-                        topActivity.getBounds());
-                dc.getInputMonitor().setActiveRecents(recentsActivity, topActivity);
+                        topNonRecentsTask.getBounds());
+                dc.getInputMonitor().setActiveRecents(recentsActivity, topNonRecentsTask);
             }
         }
 
@@ -2020,16 +2020,17 @@
         }
         mController.mNavigationBarAttachedToApp = false;
 
-        if (mRecentsDisplayId == INVALID_DISPLAY) {
-            Slog.e(TAG, "Reparented navigation bar without a valid display");
-            mRecentsDisplayId = DEFAULT_DISPLAY;
+        int recentsDisplayId = mRecentsDisplayId;
+        if (recentsDisplayId == INVALID_DISPLAY) {
+            Slog.i(TAG, "Restore parent surface of navigation bar by another transition");
+            recentsDisplayId = DEFAULT_DISPLAY;
         }
 
         final DisplayContent dc =
-                mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId);
+                mController.mAtm.mRootWindowContainer.getDisplayContent(recentsDisplayId);
         final StatusBarManagerInternal bar = dc.getDisplayPolicy().getStatusBarManagerInternal();
         if (bar != null) {
-            bar.setNavigationBarLumaSamplingEnabled(mRecentsDisplayId, true);
+            bar.setNavigationBarLumaSamplingEnabled(recentsDisplayId, true);
         }
         final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar();
         if (navWindow == null) return;
@@ -2840,6 +2841,19 @@
         }
     }
 
+    /** Returns {@code true} if the display should use high performance hint for this transition. */
+    boolean shouldUsePerfHint(@NonNull DisplayContent dc) {
+        if (mOverrideOptions != null
+                && mOverrideOptions.getType() == ActivityOptions.ANIM_SCENE_TRANSITION
+                && mType == TRANSIT_TO_BACK && mParticipants.size() == 1) {
+            // This should be from convertFromTranslucent that makes the occluded activity invisible
+            // without animation. So do not use perf hint (especially early-wakeup) that may disturb
+            // SurfaceFlinger scheduling around the last frame.
+            return false;
+        }
+        return mTargetDisplays.contains(dc);
+    }
+
     /**
      * Returns {@code true} if the transition and the corresponding transaction should be applied
      * on display thread. Currently, this only checks for display rotation change because the order
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 708d63e..25b5630f 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -496,6 +496,9 @@
         if (mCollectingTransition != null && mCollectingTransition.isInTransientHide(task)) {
             return true;
         }
+        for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
+            if (mWaitingTransitions.get(i).isInTransientHide(task)) return true;
+        }
         for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
             if (mPlayingTransitions.get(i).isInTransientHide(task)) return true;
         }
@@ -506,6 +509,9 @@
         if (mCollectingTransition != null && mCollectingTransition.isTransientVisible(task)) {
             return true;
         }
+        for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
+            if (mWaitingTransitions.get(i).isTransientVisible(task)) return true;
+        }
         for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
             if (mPlayingTransitions.get(i).isTransientVisible(task)) return true;
         }
@@ -634,11 +640,16 @@
     }
 
     /** Sets the sync method for the display change. */
-    void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange,
+    private void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange,
             @NonNull DisplayContent displayContent) {
         final Rect startBounds = displayChange.getStartAbsBounds();
         final Rect endBounds = displayChange.getEndAbsBounds();
         if (startBounds == null || endBounds == null) return;
+        setDisplaySyncMethod(startBounds, endBounds, displayContent);
+    }
+
+    void setDisplaySyncMethod(@NonNull Rect startBounds, @NonNull Rect endBounds,
+            @NonNull DisplayContent displayContent) {
         final int startWidth = startBounds.width();
         final int startHeight = startBounds.height();
         final int endWidth = endBounds.width();
@@ -1237,8 +1248,15 @@
             // enableHighPerfTransition(true) is also called in Transition#recordDisplay.
             for (int i = mAtm.mRootWindowContainer.getChildCount() - 1; i >= 0; i--) {
                 final DisplayContent dc = mAtm.mRootWindowContainer.getChildAt(i);
-                if (isTransitionOnDisplay(dc)) {
+                if (mCollectingTransition != null && mCollectingTransition.shouldUsePerfHint(dc)) {
                     dc.enableHighPerfTransition(true);
+                    continue;
+                }
+                for (int j = mPlayingTransitions.size() - 1; j >= 0; j--) {
+                    if (mPlayingTransitions.get(j).shouldUsePerfHint(dc)) {
+                        dc.enableHighPerfTransition(true);
+                        break;
+                    }
                 }
             }
             // Usually transitions put quite a load onto the system already (with all the things
diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java
index c59d2d3..0f3fe22 100644
--- a/services/core/java/com/android/server/wm/TransitionTracer.java
+++ b/services/core/java/com/android/server/wm/TransitionTracer.java
@@ -1,323 +1,19 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
 package com.android.server.wm;
 
-import static android.os.Build.IS_USER;
-
-import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER;
-import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER_H;
-import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER_L;
-import static com.android.server.wm.shell.TransitionTraceProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.util.Log;
-import android.util.proto.ProtoOutputStream;
 
-import com.android.internal.util.TraceBuffer;
-import com.android.server.wm.Transition.ChangeInfo;
-
-import java.io.File;
-import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.concurrent.TimeUnit;
 
-/**
- * Helper class to collect and dump transition traces.
- */
-public class TransitionTracer {
+interface TransitionTracer {
+    void logSentTransition(Transition transition, ArrayList<Transition.ChangeInfo> targets);
+    void logFinishedTransition(Transition transition);
+    void logAbortedTransition(Transition transition);
+    void logRemovingStartingWindow(@NonNull StartingData startingData);
 
-    private static final String LOG_TAG = "TransitionTracer";
-
-    private static final int ALWAYS_ON_TRACING_CAPACITY = 15 * 1024; // 15 KB
-    private static final int ACTIVE_TRACING_BUFFER_CAPACITY = 5000 * 1024; // 5 MB
-
-    // This will be the size the proto output streams are initialized to.
-    // Ideally this should fit most or all the proto objects we will create and be no bigger than
-    // that to ensure to don't use excessive amounts of memory.
-    private static final int CHUNK_SIZE = 64;
-
-    static final String WINSCOPE_EXT = ".winscope";
-    private static final String TRACE_FILE =
-            "/data/misc/wmtrace/wm_transition_trace" + WINSCOPE_EXT;
-    private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
-
-    private final TraceBuffer mTraceBuffer = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY);
-
-    private final Object mEnabledLock = new Object();
-    private volatile boolean mActiveTracingEnabled = false;
-
-    /**
-     * Records key information about a transition that has been sent to Shell to be played.
-     * More information will be appended to the same proto object once the transition is finished or
-     * aborted.
-     * Transition information won't be added to the trace buffer until
-     * {@link #logFinishedTransition} or {@link #logAbortedTransition} is called for this
-     * transition.
-     *
-     * @param transition The transition that has been sent to Shell.
-     * @param targets Information about the target windows of the transition.
-     */
-    public void logSentTransition(Transition transition, ArrayList<ChangeInfo> targets) {
-        try {
-            final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
-            final long protoToken = outputStream
-                    .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
-            outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
-            outputStream.write(com.android.server.wm.shell.Transition.CREATE_TIME_NS,
-                    transition.mLogger.mCreateTimeNs);
-            outputStream.write(com.android.server.wm.shell.Transition.SEND_TIME_NS,
-                    transition.mLogger.mSendTimeNs);
-            outputStream.write(com.android.server.wm.shell.Transition.START_TRANSACTION_ID,
-                    transition.getStartTransaction().getId());
-            outputStream.write(com.android.server.wm.shell.Transition.FINISH_TRANSACTION_ID,
-                    transition.getFinishTransaction().getId());
-            dumpTransitionTargetsToProto(outputStream, transition, targets);
-            outputStream.end(protoToken);
-
-            mTraceBuffer.add(outputStream);
-        } catch (Exception e) {
-            // Don't let any errors in the tracing cause the transition to fail
-            Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
-        }
-    }
-
-    /**
-     * Completes the information dumped in {@link #logSentTransition} for a transition
-     * that has finished or aborted, and add the proto object to the trace buffer.
-     *
-     * @param transition The transition that has finished.
-     */
-    public void logFinishedTransition(Transition transition) {
-        try {
-            final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
-            final long protoToken = outputStream
-                    .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
-            outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
-            outputStream.write(com.android.server.wm.shell.Transition.FINISH_TIME_NS,
-                    transition.mLogger.mFinishTimeNs);
-            outputStream.end(protoToken);
-
-            mTraceBuffer.add(outputStream);
-        } catch (Exception e) {
-            // Don't let any errors in the tracing cause the transition to fail
-            Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
-        }
-    }
-
-    /**
-     * Same as {@link #logFinishedTransition} but don't add the transition to the trace buffer
-     * unless actively tracing.
-     *
-     * @param transition The transition that has been aborted
-     */
-    public void logAbortedTransition(Transition transition) {
-        try {
-            final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
-            final long protoToken = outputStream
-                    .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
-            outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
-            outputStream.write(com.android.server.wm.shell.Transition.ABORT_TIME_NS,
-                    transition.mLogger.mAbortTimeNs);
-            outputStream.end(protoToken);
-
-            mTraceBuffer.add(outputStream);
-        } catch (Exception e) {
-            // Don't let any errors in the tracing cause the transition to fail
-            Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
-        }
-    }
-
-    void logRemovingStartingWindow(@NonNull StartingData startingData) {
-        if (startingData.mTransitionId == 0) {
-            return;
-        }
-        try {
-            final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
-            final long protoToken = outputStream
-                    .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
-            outputStream.write(com.android.server.wm.shell.Transition.ID,
-                    startingData.mTransitionId);
-            outputStream.write(
-                    com.android.server.wm.shell.Transition.STARTING_WINDOW_REMOVE_TIME_NS,
-                    SystemClock.elapsedRealtimeNanos());
-            outputStream.end(protoToken);
-
-            mTraceBuffer.add(outputStream);
-        } catch (Exception e) {
-            Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
-        }
-    }
-
-    private void dumpTransitionTargetsToProto(ProtoOutputStream outputStream,
-            Transition transition, ArrayList<ChangeInfo> targets) {
-        Trace.beginSection("TransitionTracer#dumpTransitionTargetsToProto");
-        if (mActiveTracingEnabled) {
-            outputStream.write(com.android.server.wm.shell.Transition.ID,
-                    transition.getSyncId());
-        }
-
-        outputStream.write(com.android.server.wm.shell.Transition.TYPE, transition.mType);
-        outputStream.write(com.android.server.wm.shell.Transition.FLAGS, transition.getFlags());
-
-        for (int i = 0; i < targets.size(); ++i) {
-            final long changeToken = outputStream
-                    .start(com.android.server.wm.shell.Transition.TARGETS);
-
-            final Transition.ChangeInfo target = targets.get(i);
-
-            final int layerId;
-            if (target.mContainer.mSurfaceControl.isValid()) {
-                layerId = target.mContainer.mSurfaceControl.getLayerId();
-            } else {
-                layerId = -1;
-            }
-
-            outputStream.write(com.android.server.wm.shell.Target.MODE, target.mReadyMode);
-            outputStream.write(com.android.server.wm.shell.Target.FLAGS, target.mReadyFlags);
-            outputStream.write(com.android.server.wm.shell.Target.LAYER_ID, layerId);
-
-            if (mActiveTracingEnabled) {
-                // What we use in the WM trace
-                final int windowId = System.identityHashCode(target.mContainer);
-                outputStream.write(com.android.server.wm.shell.Target.WINDOW_ID, windowId);
-            }
-
-            outputStream.end(changeToken);
-        }
-
-        Trace.endSection();
-    }
-
-    /**
-     * Starts collecting transitions for the trace.
-     * If called while a trace is already running, this will reset the trace.
-     */
-    public void startTrace(@Nullable PrintWriter pw) {
-        if (IS_USER) {
-            LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
-            return;
-        }
-        Trace.beginSection("TransitionTracer#startTrace");
-        LogAndPrintln.i(pw, "Starting shell transition trace.");
-        synchronized (mEnabledLock) {
-            mActiveTracingEnabled = true;
-            mTraceBuffer.resetBuffer();
-            mTraceBuffer.setCapacity(ACTIVE_TRACING_BUFFER_CAPACITY);
-        }
-        Trace.endSection();
-    }
-
-    /**
-     * Stops collecting the transition trace and dump to trace to file.
-     *
-     * Dumps the trace to @link{TRACE_FILE}.
-     */
-    public void stopTrace(@Nullable PrintWriter pw) {
-        stopTrace(pw, new File(TRACE_FILE));
-    }
-
-    /**
-     * Stops collecting the transition trace and dump to trace to file.
-     * @param outputFile The file to dump the transition trace to.
-     */
-    public void stopTrace(@Nullable PrintWriter pw, File outputFile) {
-        if (IS_USER) {
-            LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
-            return;
-        }
-        Trace.beginSection("TransitionTracer#stopTrace");
-        LogAndPrintln.i(pw, "Stopping shell transition trace.");
-        synchronized (mEnabledLock) {
-            mActiveTracingEnabled = false;
-            writeTraceToFileLocked(pw, outputFile);
-            mTraceBuffer.resetBuffer();
-            mTraceBuffer.setCapacity(ALWAYS_ON_TRACING_CAPACITY);
-        }
-        Trace.endSection();
-    }
-
-    /**
-     * Being called while taking a bugreport so that tracing files can be included in the bugreport.
-     *
-     * @param pw Print writer
-     */
-    public void saveForBugreport(@Nullable PrintWriter pw) {
-        if (IS_USER) {
-            LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
-            return;
-        }
-        Trace.beginSection("TransitionTracer#saveForBugreport");
-        synchronized (mEnabledLock) {
-            final File outputFile = new File(TRACE_FILE);
-            writeTraceToFileLocked(pw, outputFile);
-        }
-        Trace.endSection();
-    }
-
-    boolean isActiveTracingEnabled() {
-        return mActiveTracingEnabled;
-    }
-
-    private void writeTraceToFileLocked(@Nullable PrintWriter pw, File file) {
-        Trace.beginSection("TransitionTracer#writeTraceToFileLocked");
-        try {
-            ProtoOutputStream proto = new ProtoOutputStream(CHUNK_SIZE);
-            proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
-            long timeOffsetNs =
-                    TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
-                            - SystemClock.elapsedRealtimeNanos();
-            proto.write(REAL_TO_ELAPSED_TIME_OFFSET_NANOS, timeOffsetNs);
-            int pid = android.os.Process.myPid();
-            LogAndPrintln.i(pw, "Writing file to " + file.getAbsolutePath()
-                    + " from process " + pid);
-            mTraceBuffer.writeTraceToFile(file, proto);
-        } catch (IOException e) {
-            LogAndPrintln.e(pw, "Unable to write buffer to file", e);
-        }
-        Trace.endSection();
-    }
-
-    private static class LogAndPrintln {
-        private static void i(@Nullable PrintWriter pw, String msg) {
-            Log.i(LOG_TAG, msg);
-            if (pw != null) {
-                pw.println(msg);
-                pw.flush();
-            }
-        }
-
-        private static void e(@Nullable PrintWriter pw, String msg) {
-            Log.e(LOG_TAG, msg);
-            if (pw != null) {
-                pw.println("ERROR: " + msg);
-                pw.flush();
-            }
-        }
-
-        private static void e(@Nullable PrintWriter pw, String msg, @NonNull Exception e) {
-            Log.e(LOG_TAG, msg, e);
-            if (pw != null) {
-                pw.println("ERROR: " + msg + " ::\n " + e);
-                pw.flush();
-            }
-        }
-    }
+    void startTrace(@Nullable PrintWriter pw);
+    void stopTrace(@Nullable PrintWriter pw);
+    boolean isTracing();
+    void saveForBugreport(@Nullable PrintWriter pw);
 }
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index a9f0554..6949a87 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.WallpaperManager.COMMAND_DISPLAY_SWITCH;
 import static android.app.WallpaperManager.COMMAND_FREEZE;
 import static android.app.WallpaperManager.COMMAND_UNFREEZE;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -34,6 +35,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.H.WALLPAPER_DRAW_PENDING_TIMEOUT;
+import static com.android.window.flags.Flags.multiCrop;
 
 import android.annotation.Nullable;
 import android.content.res.Resources;
@@ -48,6 +50,7 @@
 import android.util.ArraySet;
 import android.util.MathUtils;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.SurfaceControl;
@@ -60,6 +63,7 @@
 import com.android.internal.protolog.ProtoLogImpl;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ToBooleanFunction;
+import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -73,6 +77,7 @@
 class WallpaperController {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperController" : TAG_WM;
     private WindowManagerService mService;
+    private WallpaperCropUtils mWallpaperCropUtils = null;
     private DisplayContent mDisplayContent;
 
     private final ArrayList<WallpaperWindowToken> mWallpaperTokens = new ArrayList<>();
@@ -116,6 +121,11 @@
 
     private boolean mShouldOffsetWallpaperCenter;
 
+    /**
+     * Whether the wallpaper has been notified about a physical display switch event is started.
+     */
+    private volatile boolean mIsWallpaperNotifiedOnDisplaySwitch;
+
     private final Consumer<WindowState> mFindWallpapers = w -> {
         if (w.mAttrs.type == TYPE_WALLPAPER) {
             WallpaperWindowToken token = w.mToken.asWallpaperToken();
@@ -240,9 +250,8 @@
         mMinWallpaperScale =
                 resources.getFloat(com.android.internal.R.dimen.config_wallpaperMinScale);
         mMaxWallpaperScale = resources.getFloat(R.dimen.config_wallpaperMaxScale);
-        mShouldOffsetWallpaperCenter =
-                resources.getBoolean(
-                        com.android.internal.R.bool.config_offsetWallpaperToCenterOfLargestDisplay);
+        mShouldOffsetWallpaperCenter = resources.getBoolean(
+                com.android.internal.R.bool.config_offsetWallpaperToCenterOfLargestDisplay);
     }
 
     void resetLargestDisplay(Display display) {
@@ -266,17 +275,18 @@
     }
 
     @Nullable private Point findLargestDisplaySize() {
-        if (!mShouldOffsetWallpaperCenter) {
+        if (!mShouldOffsetWallpaperCenter || multiCrop()) {
             return null;
         }
         Point largestDisplaySize = new Point();
+        float largestWidth = 0;
         List<DisplayInfo> possibleDisplayInfo =
                 mService.getPossibleDisplayInfoLocked(DEFAULT_DISPLAY);
         for (int i = 0; i < possibleDisplayInfo.size(); i++) {
             DisplayInfo displayInfo = possibleDisplayInfo.get(i);
-            if (displayInfo.type == Display.TYPE_INTERNAL
-                    && Math.max(displayInfo.logicalWidth, displayInfo.logicalHeight)
-                    > Math.max(largestDisplaySize.x, largestDisplaySize.y)) {
+            float width = (float) displayInfo.logicalWidth / displayInfo.physicalXDpi;
+            if (displayInfo.type == Display.TYPE_INTERNAL && width > largestWidth) {
+                largestWidth = width;
                 largestDisplaySize.set(displayInfo.logicalWidth,
                         displayInfo.logicalHeight);
             }
@@ -284,6 +294,10 @@
         return largestDisplaySize;
     }
 
+    void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils) {
+        mWallpaperCropUtils = wallpaperCropUtils;
+    }
+
     WindowState getWallpaperTarget() {
         return mWallpaperTarget;
     }
@@ -357,26 +371,92 @@
     boolean updateWallpaperOffset(WindowState wallpaperWin, boolean sync) {
         // Size of the display the wallpaper is rendered on.
         final Rect lastWallpaperBounds = wallpaperWin.getParentFrame();
-        // Full size of the wallpaper (usually larger than bounds above to parallax scroll when
-        // swiping through Launcher pages).
-        final Rect wallpaperFrame = wallpaperWin.getFrame();
+        int screenWidth = lastWallpaperBounds.width();
+        int screenHeight = lastWallpaperBounds.height();
+        float screenRatio = ((float) screenWidth) / screenHeight;
+        Point screenSize = new Point(screenWidth, screenHeight);
+
         WallpaperWindowToken token = wallpaperWin.mToken.asWallpaperToken();
 
-        final int diffWidth = wallpaperFrame.width() - lastWallpaperBounds.width();
-        final int diffHeight = wallpaperFrame.height() - lastWallpaperBounds.height();
-        if ((wallpaperWin.mAttrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0
-                && Math.abs(diffWidth) > 1 && Math.abs(diffHeight) > 1) {
-            Slog.d(TAG, "Skip wallpaper offset with inconsistent orientation, bounds="
-                    + lastWallpaperBounds + " frame=" + wallpaperFrame);
-            // With FLAG_SCALED, the requested size should at least make the frame match one of
-            // side. If both sides contain differences, the client side may not have updated the
-            // latest size according to the current orientation. So skip calculating the offset to
-            // avoid the wallpaper not filling the screen.
-            return false;
+        /*
+         * TODO(b/270726737) adapt comments once flag gets removed and multiCrop is always true
+         * Size of the wallpaper. May have more width/height ratio than the screen for parallax.
+         *
+         * If multiCrop is true, we use a map, cropHints, defining which sub-area of the wallpaper
+         * to show for a given screen orientation. In this case, wallpaperFrame represents the
+         * sub-area of WallpaperWin to show for the current screen size.
+         *
+         * If multiCrop is false, don't show a custom sub-area of the wallpaper. Just show the
+         * whole wallpaperWin if possible, and center and zoom if necessary.
+         */
+        final Rect wallpaperFrame;
+
+        /*
+         * The values cropZoom, cropOffsetX and cropOffsetY are only used if multiCrop is true.
+         * Zoom and offsets to be applied in order to show wallpaperFrame on screen.
+         */
+        final float cropZoom;
+        final int cropOffsetX;
+        final int cropOffsetY;
+
+        /*
+         * Difference of width/height between the wallpaper and the screen.
+         * This is the additional room that we have to apply offsets (i.e. parallax).
+         */
+        final int diffWidth;
+        final int diffHeight;
+
+        /*
+         * zoom, offsetX and offsetY are not related to cropping the wallpaper:
+         *  - zoom is used to apply an additional zoom (e.g. for launcher animations).
+         *  - offsetX, offsetY are used to apply an offset to the wallpaper (e.g. parallax effect).
+         */
+        final float zoom;
+        int offsetX;
+        int offsetY;
+
+        if (multiCrop()) {
+            if (mWallpaperCropUtils == null) {
+                Slog.e(TAG, "Update wallpaper offsets before the system is ready. Aborting");
+                return false;
+            }
+            Point bitmapSize = new Point(
+                    wallpaperWin.mRequestedWidth, wallpaperWin.mRequestedHeight);
+            SparseArray<Rect> cropHints = token.getCropHints();
+            wallpaperFrame = mWallpaperCropUtils.getCrop(
+                    screenSize, bitmapSize, cropHints, wallpaperWin.isRtl());
+
+            cropZoom = wallpaperFrame.isEmpty() ? 1f
+                    : ((float) screenHeight) / wallpaperFrame.height() / wallpaperWin.mVScale;
+
+            // A positive x / y offset shifts the wallpaper to the right / bottom respectively.
+            cropOffsetX = -wallpaperFrame.left
+                    + (int) ((cropZoom - 1f) * wallpaperFrame.height() * screenRatio / 2f);
+            cropOffsetY = -wallpaperFrame.top
+                    + (int) ((cropZoom - 1f) * wallpaperFrame.height() / 2f);
+
+            diffWidth = (int) (wallpaperFrame.width() * wallpaperWin.mHScale) - screenWidth;
+            diffHeight = (int) (wallpaperFrame.height() * wallpaperWin.mVScale) - screenHeight;
+        } else {
+            wallpaperFrame = wallpaperWin.getFrame();
+            cropZoom = 1f;
+            cropOffsetX = 0;
+            cropOffsetY = 0;
+            diffWidth = wallpaperFrame.width() - screenWidth;
+            diffHeight = wallpaperFrame.height() - screenHeight;
+
+            if ((wallpaperWin.mAttrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0
+                    && Math.abs(diffWidth) > 1 && Math.abs(diffHeight) > 1) {
+                Slog.d(TAG, "Skip wallpaper offset with inconsistent orientation, bounds="
+                        + lastWallpaperBounds + " frame=" + wallpaperFrame);
+                // With FLAG_SCALED, the requested size should at least make the frame match one of
+                // side. If both sides contain differences, the client side may not have updated the
+                // latest size according to the current orientation. So skip calculating the offset
+                // to avoid the wallpaper not filling the screen.
+                return false;
+            }
         }
 
-        int newXOffset = 0;
-        int newYOffset = 0;
         boolean rawChanged = false;
         // Set the default wallpaper x-offset to either edge of the screen (depending on RTL), to
         // match the behavior of most Launchers
@@ -396,17 +476,17 @@
         int displayOffset = getDisplayWidthOffset(availw, lastWallpaperBounds,
                 wallpaperWin.isRtl());
         availw -= displayOffset;
-        int offset = availw > 0 ? -(int)(availw * wpx + .5f) : 0;
+        offsetX = availw > 0 ? -(int) (availw * wpx + .5f) : 0;
         if (token.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
             // if device is LTR, then offset wallpaper to the left (the wallpaper is drawn
             // always starting from the left of the screen).
-            offset += token.mWallpaperDisplayOffsetX;
+            offsetX += token.mWallpaperDisplayOffsetX;
         } else if (!wallpaperWin.isRtl()) {
             // In RTL the offset is calculated so that the wallpaper ends up right aligned (see
             // offset above).
-            offset -= displayOffset;
+            offsetX -= displayOffset;
         }
-        newXOffset = offset;
+        offsetX += cropOffsetX * wallpaperWin.mHScale;
 
         if (wallpaperWin.mWallpaperX != wpx || wallpaperWin.mWallpaperXStep != wpxs) {
             wallpaperWin.mWallpaperX = wpx;
@@ -416,11 +496,11 @@
 
         float wpy = token.mWallpaperY >= 0 ? token.mWallpaperY : 0.5f;
         float wpys = token.mWallpaperYStep >= 0 ? token.mWallpaperYStep : -1.0f;
-        offset = diffHeight > 0 ? -(int) (diffHeight * wpy + .5f) : 0;
+        offsetY = diffHeight > 0 ? -(int) (diffHeight * wpy + .5f) : 0;
         if (token.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
-            offset += token.mWallpaperDisplayOffsetY;
+            offsetY += token.mWallpaperDisplayOffsetY;
         }
-        newYOffset = offset;
+        offsetY += cropOffsetY * wallpaperWin.mVScale;
 
         if (wallpaperWin.mWallpaperY != wpy || wallpaperWin.mWallpaperYStep != wpys) {
             wallpaperWin.mWallpaperY = wpy;
@@ -432,10 +512,10 @@
             wallpaperWin.mWallpaperZoomOut = mLastWallpaperZoomOut;
             rawChanged = true;
         }
-
-        boolean changed = wallpaperWin.setWallpaperOffset(newXOffset, newYOffset,
-                wallpaperWin.mShouldScaleWallpaper
-                        ? zoomOutToScale(wallpaperWin.mWallpaperZoomOut) : 1);
+        zoom = wallpaperWin.mShouldScaleWallpaper
+                ? zoomOutToScale(wallpaperWin.mWallpaperZoomOut) : 1f;
+        final float totalZoom = zoom * cropZoom;
+        boolean changed = wallpaperWin.setWallpaperOffset(offsetX, offsetY, totalZoom);
 
         if (rawChanged && (wallpaperWin.mAttrs.privateFlags &
                 WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS) != 0) {
@@ -496,7 +576,7 @@
      * display).
      */
     private int getDisplayWidthOffset(int availWidth, Rect displayFrame, boolean isRtl) {
-        if (!mShouldOffsetWallpaperCenter) {
+        if (!mShouldOffsetWallpaperCenter || multiCrop()) {
             return 0;
         }
         if (mLargestDisplaySize == null) {
@@ -562,11 +642,10 @@
         }
     }
 
-    Bundle sendWindowWallpaperCommandUnchecked(
+    void sendWindowWallpaperCommandUnchecked(
             WindowState window, String action, int x, int y, int z,
             Bundle extras, boolean sync) {
         sendWindowWallpaperCommand(action, x, y, z, extras, sync);
-        return null;
     }
 
     private void sendWindowWallpaperCommand(
@@ -1010,6 +1089,52 @@
     }
 
     /**
+     * Notifies the wallpaper that the display turns off when switching physical device. If the
+     * wallpaper is currently visible, its client visibility will be preserved until the display is
+     * confirmed to be off or on.
+     */
+    void onDisplaySwitchStarted() {
+        mIsWallpaperNotifiedOnDisplaySwitch = notifyDisplaySwitch(true /* start */);
+    }
+
+    /**
+     * Called when the screen has finished turning on or the device goes to sleep. This is no-op if
+     * the operation is not part of a display switch.
+     */
+    void onDisplaySwitchFinished() {
+        // The method can be called outside WM lock (turned on), so only acquire lock if needed.
+        // This is to optimize the common cases that regular devices don't have display switch.
+        if (mIsWallpaperNotifiedOnDisplaySwitch) {
+            synchronized (mService.mGlobalLock) {
+                mIsWallpaperNotifiedOnDisplaySwitch = false;
+                notifyDisplaySwitch(false /* start */);
+            }
+        }
+    }
+
+    private boolean notifyDisplaySwitch(boolean start) {
+        boolean notified = false;
+        for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
+            final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
+            for (int i = token.getChildCount() - 1; i >= 0; i--) {
+                final WindowState w = token.getChildAt(i);
+                if (start && !w.mWinAnimator.getShown()) {
+                    continue;
+                }
+                try {
+                    w.mClient.dispatchWallpaperCommand(COMMAND_DISPLAY_SWITCH, 0 /* x */, 0 /* y */,
+                            start ? 1 : 0 /* use z as start or finish */,
+                            null /* bundle */, false /* sync */);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to dispatch COMMAND_DISPLAY_SWITCH " + e);
+                }
+                notified = true;
+            }
+        }
+        return notified;
+    }
+
+    /**
      * Each window can request a zoom, example:
      * - User is in overview, zoomed out.
      * - User also pulls down the shade.
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 15bd607..1bcd882 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -25,9 +25,11 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.Nullable;
+import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.SparseArray;
 import android.view.animation.Animation;
 
 import com.android.internal.protolog.common.ProtoLog;
@@ -49,6 +51,12 @@
     int mWallpaperDisplayOffsetX = Integer.MIN_VALUE;
     int mWallpaperDisplayOffsetY = Integer.MIN_VALUE;
 
+    /**
+     * Map from {@link android.app.WallpaperManager.ScreenOrientation} to crop rectangles.
+     * Crop rectangles represent the part of the wallpaper displayed for each screen orientation.
+     */
+    private SparseArray<Rect> mCropHints = new SparseArray<>();
+
     WallpaperWindowToken(WindowManagerService service, IBinder token, boolean explicit,
             DisplayContent dc, boolean ownerCanManageAppTokens) {
         this(service, token, explicit, dc, ownerCanManageAppTokens, null /* options */);
@@ -98,6 +106,14 @@
         return mShowWhenLocked;
     }
 
+    void setCropHints(SparseArray<Rect> cropHints) {
+        mCropHints = cropHints.clone();
+    }
+
+    SparseArray<Rect> getCropHints() {
+        return mCropHints;
+    }
+
     void sendWindowWallpaperCommand(
             String action, int x, int y, int z, Bundle extras, boolean sync) {
         for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 750fd50..b43a454 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -146,10 +146,11 @@
             for (int i = 0; i < numDisplays; i++) {
                 final DisplayContent dc = root.getChildAt(i);
 
-                dc.checkAppWindowsReadyToShow();
+                if (!useShellTransition) {
+                    dc.checkAppWindowsReadyToShow();
+                }
                 if (accessibilityController.hasCallbacks()) {
-                    accessibilityController.drawMagnifiedRegionBorderIfNeeded(dc.mDisplayId,
-                            mTransaction);
+                    accessibilityController.drawMagnifiedRegionBorderIfNeeded(dc.mDisplayId);
                 }
 
                 if (dc.isAnimating(animationFlags, ANIMATION_TYPE_ALL)) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index bdea1bc..286182e 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -465,7 +465,7 @@
             }
         }
         final InsetsSource source = new InsetsSource(id, provider.getType());
-        source.setFrame(provider.getArbitraryRectangle());
+        source.setFrame(provider.getArbitraryRectangle()).updateSideHint(getBounds());
         mLocalInsetsSources.put(id, source);
         mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 22b690e..d0b9a6e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -32,7 +32,9 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Message;
+import android.util.ArraySet;
 import android.util.Pair;
+import android.util.SparseArray;
 import android.view.ContentRecordingSession;
 import android.view.Display;
 import android.view.IInputFilter;
@@ -51,6 +53,7 @@
 import com.android.internal.policy.KeyInterceptionInfo;
 import com.android.server.input.InputManagerService;
 import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
 import com.android.server.wm.SensitiveContentPackages.PackageInfo;
 
 import java.lang.annotation.Retention;
@@ -698,6 +701,21 @@
     public abstract void setWallpaperShowWhenLocked(IBinder windowToken, boolean showWhenLocked);
 
     /**
+     * Sets the crop hints of a {@link WallpaperWindowToken}. Only effective for image wallpapers.
+     *
+     * @param windowToken wallpaper token previously added via {@link #addWindowToken}
+     * @param cropHints a map that represents which part of the wallpaper should be shown, for
+     *                       each type of {@link android.app.WallpaperManager.ScreenOrientation}.
+     */
+    public abstract void setWallpaperCropHints(IBinder windowToken, SparseArray<Rect> cropHints);
+
+    /**
+     * Transmits the {@link WallpaperCropUtils} instance to {@link WallpaperController}.
+     * {@link WallpaperCropUtils} contains the helpers to properly position the wallpaper.
+     */
+    public abstract void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils);
+
+    /**
      * Returns {@code true} if a Window owned by {@code uid} has focus.
      */
     public abstract boolean isUidFocused(int uid);
@@ -1020,5 +1038,13 @@
      *
      * @param packageInfos set of {@link PackageInfo} whose windows should be blocked from capture
      */
-    public abstract void setShouldBlockScreenCaptureForApp(@NonNull Set<PackageInfo> packageInfos);
+    public abstract void addBlockScreenCaptureForApps(@NonNull ArraySet<PackageInfo> packageInfos);
+
+    /**
+     * Clears apps added to collection of apps in which screen capture should be disabled.
+     *
+     * <p> This clears and resets any existing set or added applications from
+     * * {@link #addBlockScreenCaptureForApps(ArraySet)}
+     */
+    public abstract void clearBlockedApps();
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c63cc43..426694d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -153,6 +153,7 @@
 import static com.android.server.wm.WindowManagerServiceDumpProto.POLICY;
 import static com.android.server.wm.WindowManagerServiceDumpProto.ROOT_WINDOW_CONTAINER;
 import static com.android.server.wm.WindowManagerServiceDumpProto.WINDOW_FRAMES_VALID;
+import static com.android.window.flags.Flags.multiCrop;
 
 import android.Manifest;
 import android.Manifest.permission;
@@ -238,6 +239,7 @@
 import android.util.MergedConfiguration;
 import android.util.Pair;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
@@ -287,6 +289,7 @@
 import android.view.SurfaceSession;
 import android.view.TaskTransitionSpec;
 import android.view.View;
+import android.view.View.FocusDirection;
 import android.view.ViewDebug;
 import android.view.WindowContentFrameStats;
 import android.view.WindowInsets;
@@ -302,6 +305,7 @@
 import android.view.inputmethod.ImeTracker;
 import android.window.AddToSurfaceSyncGroupResult;
 import android.window.ClientWindowFrames;
+import android.window.IScreenRecordingCallback;
 import android.window.ISurfaceSyncGroupCompletedListener;
 import android.window.ITaskFpsCallback;
 import android.window.ITrustedPresentationListener;
@@ -340,6 +344,7 @@
 import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
 import com.android.server.power.ShutdownThread;
 import com.android.server.utils.PriorityDump;
+import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
 
 import dalvik.annotation.optimization.NeverCompile;
 
@@ -368,7 +373,6 @@
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
@@ -1103,6 +1107,8 @@
         void onAppFreezeTimeout();
     }
 
+    private final ScreenRecordingCallbackController mScreenRecordingCallbackController;
+
     public static WindowManagerService main(final Context context, final InputManagerService im,
             final boolean showBootMsgs, WindowManagerPolicy policy,
             ActivityTaskManagerService atm) {
@@ -1212,7 +1218,12 @@
 
         mWindowTracing = WindowTracing.createDefaultAndStartLooper(this,
                 Choreographer.getInstance());
-        mTransitionTracer = new TransitionTracer();
+
+        if (android.tracing.Flags.perfettoTransitionTracing()) {
+            mTransitionTracer = new PerfettoTransitionTracer();
+        } else {
+            mTransitionTracer = new LegacyTransitionTracer();
+        }
 
         LocalServices.addService(WindowManagerPolicy.class, mPolicy);
 
@@ -1339,6 +1350,7 @@
         mBlurController = new BlurController(mContext, mPowerManager);
         mTaskFpsCallbackController = new TaskFpsCallbackController(mContext);
         mAccessibilityController = new AccessibilityController(this);
+        mScreenRecordingCallbackController = new ScreenRecordingCallbackController(this);
         mSystemPerformanceHinter = new SystemPerformanceHinter(mContext, displayId -> {
             synchronized (mGlobalLock) {
                 DisplayContent dc = mRoot.getDisplayContent(displayId);
@@ -4075,7 +4087,7 @@
             throw new SecurityException("Requires READ_FRAME_BUFFER permission");
         }
 
-        final Bitmap bm;
+        ScreenCapture.LayerCaptureArgs captureArgs;
         synchronized (mGlobalLock) {
             final DisplayContent displayContent = mRoot.getDisplayContent(DEFAULT_DISPLAY);
             if (displayContent == null) {
@@ -4083,12 +4095,30 @@
                     Slog.i(TAG_WM, "Screenshot returning null. No Display for displayId="
                             + DEFAULT_DISPLAY);
                 }
-                bm = null;
+                captureArgs = null;
             } else {
-                bm = displayContent.screenshotDisplayLocked();
+                captureArgs = displayContent.getLayerCaptureArgs();
             }
         }
 
+        final Bitmap bm;
+        if (captureArgs != null) {
+            ScreenCapture.SynchronousScreenCaptureListener syncScreenCapture =
+                    ScreenCapture.createSyncCaptureListener();
+
+            ScreenCapture.captureLayers(captureArgs, syncScreenCapture);
+
+            final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
+                    syncScreenCapture.getBuffer();
+            bm = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
+        } else {
+            bm = null;
+        }
+
+        if (bm == null) {
+            Slog.w(TAG_WM, "Failed to take screenshot");
+        }
+
         FgThread.getHandler().post(() -> {
             try {
                 receiver.onHandleAssistScreenshot(bm);
@@ -6086,7 +6116,7 @@
 
     @Override
     public boolean isTransitionTraceEnabled() {
-        return mTransitionTracer.isActiveTracingEnabled();
+        return mTransitionTracer.isTracing();
     }
 
     @Override
@@ -7182,6 +7212,7 @@
             mSystemPerformanceHinter.dump(pw, "");
             mTrustedPresentationListenerController.dump(pw);
             mSensitiveContentPackages.dump(pw);
+            mScreenRecordingCallbackController.dump(pw);
         }
     }
 
@@ -8118,6 +8149,25 @@
         }
 
         @Override
+        public void setWallpaperCropHints(IBinder binder, SparseArray<Rect> cropHints) {
+            synchronized (mGlobalLock) {
+                final WindowToken token = mRoot.getWindowToken(binder);
+                if (token == null || token.asWallpaperToken() == null) {
+                    ProtoLog.w(WM_ERROR,
+                            "setWallpaperCropHints: non-existent wallpaper token: %s", binder);
+                    return;
+                }
+                token.asWallpaperToken().setCropHints(cropHints);
+            }
+        }
+
+        @Override
+        public void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils) {
+            mRoot.getDisplayContent(DEFAULT_DISPLAY).mWallpaperController
+                    .setWallpaperCropUtils(wallpaperCropUtils);
+        }
+
+        @Override
         public boolean isUidFocused(int uid) {
             synchronized (mGlobalLock) {
                 for (int i = mRoot.getChildCount() - 1; i >= 0; i--) {
@@ -8565,10 +8615,23 @@
         }
 
         @Override
-        public void setShouldBlockScreenCaptureForApp(Set<PackageInfo> packageInfos) {
+        public void addBlockScreenCaptureForApps(ArraySet<PackageInfo> packageInfos) {
             synchronized (mGlobalLock) {
-                mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(packageInfos);
-                WindowManagerService.this.refreshScreenCaptureDisabled();
+                boolean modified =
+                        mSensitiveContentPackages.addBlockScreenCaptureForApps(packageInfos);
+                if (modified) {
+                    WindowManagerService.this.refreshScreenCaptureDisabled();
+                }
+            }
+        }
+
+        @Override
+        public void clearBlockedApps() {
+            synchronized (mGlobalLock) {
+                boolean modified = mSensitiveContentPackages.clearBlockedApps();
+                if (modified) {
+                    WindowManagerService.this.refreshScreenCaptureDisabled();
+                }
             }
         }
     }
@@ -9104,6 +9167,74 @@
                 win.mClient);
     }
 
+    boolean moveFocusToAdjacentWindow(Session session, IWindow fromWindow,
+            @FocusDirection int direction) {
+        synchronized (mGlobalLock) {
+            final WindowState fromWin = windowForClientLocked(session, fromWindow, false);
+            if (fromWin == null || !fromWin.isFocused()) {
+                return false;
+            }
+            return moveFocusToAdjacentWindow(fromWin, direction);
+        }
+    }
+
+    boolean moveFocusToAdjacentWindow(WindowState fromWin, @FocusDirection int direction) {
+        final TaskFragment fromFragment = fromWin.getTaskFragment();
+        if (fromFragment == null) {
+            return false;
+        }
+        final TaskFragment adjacentFragment = fromFragment.getAdjacentTaskFragment();
+        if (adjacentFragment == null || adjacentFragment.asTask() != null) {
+            // Don't move the focus to another task.
+            return false;
+        }
+        if (adjacentFragment.isIsolatedNav()) {
+            // Don't move the focus if the adjacent TF is isolated navigation.
+            return false;
+        }
+        final Rect fromBounds = fromFragment.getBounds();
+        final Rect adjacentBounds = adjacentFragment.getBounds();
+        switch (direction) {
+            case View.FOCUS_LEFT:
+                if (adjacentBounds.left >= fromBounds.left) {
+                    return false;
+                }
+                break;
+            case View.FOCUS_UP:
+                if (adjacentBounds.top >= fromBounds.top) {
+                    return false;
+                }
+                break;
+            case View.FOCUS_RIGHT:
+                if (adjacentBounds.right <= fromBounds.right) {
+                    return false;
+                }
+                break;
+            case View.FOCUS_DOWN:
+                if (adjacentBounds.bottom <= fromBounds.bottom) {
+                    return false;
+                }
+                break;
+            case View.FOCUS_BACKWARD:
+            case View.FOCUS_FORWARD:
+                // These are not absolute directions. Skip checking the bounds.
+                break;
+            default:
+                return false;
+        }
+        final ActivityRecord topRunningActivity = adjacentFragment.topRunningActivity(
+                true /* focusableOnly */);
+        if (topRunningActivity == null) {
+            return false;
+        }
+        moveDisplayToTopInternal(topRunningActivity.getDisplayId());
+        handleTaskFocusChange(topRunningActivity.getTask(), topRunningActivity);
+        if (fromWin.isFocused()) {
+            return false;
+        }
+        return true;
+    }
+
     /** Return whether layer tracing is enabled */
     public boolean isLayerTracing() {
         if (!checkCallingPermission(
@@ -9283,7 +9414,8 @@
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
-                if (!mAtmService.isCallerRecents(callingUid)) {
+                if (!mAtmService.isCallerRecents(callingUid)
+                        && (!multiCrop() || callingUid != SYSTEM_UID)) {
                     Slog.e(TAG, "Unable to verify uid for getPossibleDisplayInfo"
                             + " on uid " + callingUid);
                     return new ArrayList<>();
@@ -9823,4 +9955,18 @@
             int id) {
         mTrustedPresentationListenerController.unregisterListener(listener, id);
     }
+
+    @Override
+    public boolean registerScreenRecordingCallback(IScreenRecordingCallback callback) {
+        return mScreenRecordingCallbackController.register(callback);
+    }
+
+    @Override
+    public void unregisterScreenRecordingCallback(IScreenRecordingCallback callback) {
+        mScreenRecordingCallbackController.unregister(callback);
+    }
+
+    void onProcessActivityVisibilityChanged(int uid, boolean visible) {
+        mScreenRecordingCallbackController.onProcessActivityVisibilityChanged(uid, visible);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 59d0210..3f889c0 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -36,6 +36,7 @@
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_RELATIVE_BOUNDS;
 import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN;
@@ -1034,8 +1035,14 @@
                 launchOpts.remove(WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID);
                 final SafeActivityOptions safeOptions =
                         SafeActivityOptions.fromBundle(launchOpts, caller.mPid, caller.mUid);
+                if (transition != null) {
+                    transition.deferTransitionReady();
+                }
                 waitAsyncStart(() -> mService.mTaskSupervisor.startActivityFromRecents(
                         caller.mPid, caller.mUid, taskId, safeOptions));
+                if (transition != null) {
+                    transition.continueTransitionReady();
+                }
                 break;
             }
             case HIERARCHY_OP_TYPE_REORDER:
@@ -1113,11 +1120,17 @@
                     activityOptions.setCallerDisplayId(DEFAULT_DISPLAY);
                 }
                 final Bundle options = activityOptions != null ? activityOptions.toBundle() : null;
+                if (transition != null) {
+                    transition.deferTransitionReady();
+                }
                 int res = waitAsyncStart(() -> mService.mAmInternal.sendIntentSender(
                         hop.getPendingIntent().getTarget(),
                         hop.getPendingIntent().getWhitelistToken(), 0 /* code */,
                         hop.getActivityIntent(), resolvedType, null /* finishReceiver */,
                         null /* requiredPermission */, options));
+                if (transition != null) {
+                    transition.continueTransitionReady();
+                }
                 if (ActivityManager.isStartResultSuccessful(res)) {
                     effects |= TRANSACT_EFFECTS_LIFECYCLE;
                 }
@@ -1458,7 +1471,12 @@
                         final int index = task.mChildren.indexOf(topTaskFragment);
                         task.mChildren.remove(taskFragment);
                         task.mChildren.add(index, taskFragment);
-                        effects |= TRANSACT_EFFECTS_LIFECYCLE;
+                        if (taskFragment.hasChild()) {
+                            effects |= TRANSACT_EFFECTS_LIFECYCLE;
+                        } else {
+                            // Ensure that the child layers are updated if the TaskFragment is empty
+                            task.assignChildLayers();
+                        }
                     }
                 }
                 break;
@@ -1473,7 +1491,12 @@
                 if (task != null) {
                     task.mChildren.remove(taskFragment);
                     task.mChildren.add(0, taskFragment);
-                    effects |= TRANSACT_EFFECTS_LIFECYCLE;
+                    if (taskFragment.hasChild()) {
+                        effects |= TRANSACT_EFFECTS_LIFECYCLE;
+                    } else {
+                        // Ensure that the child layers are updated if the TaskFragment is empty.
+                        task.assignChildLayers();
+                    }
                 }
                 break;
             }
@@ -1482,7 +1505,12 @@
                 if (task != null) {
                     task.mChildren.remove(taskFragment);
                     task.mChildren.add(taskFragment);
-                    effects |= TRANSACT_EFFECTS_LIFECYCLE;
+                    if (taskFragment.hasChild()) {
+                        effects |= TRANSACT_EFFECTS_LIFECYCLE;
+                    } else {
+                        // Ensure that the child layers are updated if the TaskFragment is empty.
+                        task.assignChildLayers();
+                    }
                 }
                 break;
             }
@@ -1502,6 +1530,11 @@
                         : EMBEDDED_DIM_AREA_TASK_FRAGMENT);
                 break;
             }
+            case OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH: {
+                taskFragment.setMoveToBottomIfClearWhenLaunch(
+                        operation.isMoveToBottomIfClearWhenLaunch());
+                break;
+            }
         }
         return effects;
     }
@@ -1554,6 +1587,17 @@
             return false;
         }
 
+        if ((opType == OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH)
+                && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
+            final Throwable exception = new SecurityException(
+                    "Only a system organizer can perform "
+                            + "OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH."
+            );
+            sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
+                    opType, exception);
+            return false;
+        }
+
         final IBinder secondaryFragmentToken = operation.getSecondaryFragmentToken();
         return secondaryFragmentToken == null
                 || validateTaskFragment(mLaunchTaskFragments.get(secondaryFragmentToken), opType,
@@ -2173,6 +2217,7 @@
         }
         final TaskFragment taskFragment = new TaskFragment(mService,
                 creationParams.getFragmentToken(), true /* createdByOrganizer */);
+        taskFragment.setAllowTransitionWhenEmpty(creationParams.getAllowTransitionWhenEmpty());
         // Set task fragment organizer immediately, since it might have to be notified about further
         // actions.
         TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer();
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index b8fa5e5..6d2e8cc 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -1271,8 +1271,10 @@
                 & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0;
         if (!wasAnyVisible && anyVisible) {
             mAtm.mVisibleActivityProcessTracker.onAnyActivityVisible(this);
+            mAtm.mWindowManager.onProcessActivityVisibilityChanged(mUid, true /*visible*/);
         } else if (wasAnyVisible && !anyVisible) {
             mAtm.mVisibleActivityProcessTracker.onAllActivitiesInvisible(this);
+            mAtm.mWindowManager.onProcessActivityVisibilityChanged(mUid, false /*visible*/);
         } else if (wasAnyVisible && !wasResumed && hasResumedActivity()) {
             mAtm.mVisibleActivityProcessTracker.onActivityResumedWhileVisible(this);
         }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 56f2bc3..24e50c5 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -169,6 +169,7 @@
 import static com.android.server.wm.WindowStateProto.REMOVED;
 import static com.android.server.wm.WindowStateProto.REMOVE_ON_EXIT;
 import static com.android.server.wm.WindowStateProto.REQUESTED_HEIGHT;
+import static com.android.server.wm.WindowStateProto.REQUESTED_VISIBLE_TYPES;
 import static com.android.server.wm.WindowStateProto.REQUESTED_WIDTH;
 import static com.android.server.wm.WindowStateProto.STACK_ID;
 import static com.android.server.wm.WindowStateProto.SURFACE_INSETS;
@@ -3988,6 +3989,7 @@
         proto.write(FORCE_SEAMLESS_ROTATION, mForceSeamlesslyRotate);
         proto.write(HAS_COMPAT_SCALE, hasCompatScale());
         proto.write(GLOBAL_SCALE, mGlobalScale);
+        proto.write(REQUESTED_VISIBLE_TYPES, mRequestedVisibleTypes);
         for (Rect r : mKeepClearAreas) {
             r.dumpDebug(proto, KEEP_CLEAR_AREAS);
         }
@@ -5187,6 +5189,11 @@
         if (mSurfaceControl == null) {
             return;
         }
+        if (mActivityRecord != null && mActivityRecord.isConfigurationDispatchPaused()) {
+            // Don't update surface-position while dispatch paused. This is calculated from
+            // the server-side activity configuration so return early.
+            return;
+        }
 
         if ((mWmService.mWindowPlacerLocked.isLayoutDeferred() || isGoneForLayout())
                 && !mSurfacePlacementNeeded) {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 5048cef..13e1ba78 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -639,9 +639,12 @@
 
     @Override
     void updateSurfacePosition(SurfaceControl.Transaction t) {
+        final ActivityRecord r = asActivityRecord();
+        if (r != null && r.isConfigurationDispatchPaused()) {
+            return;
+        }
         super.updateSurfacePosition(t);
         if (!mTransitionController.isShellTransitionsEnabled() && isFixedRotationTransforming()) {
-            final ActivityRecord r = asActivityRecord();
             final Task rootTask = r != null ? r.getRootTask() : null;
             // Don't transform the activity in PiP because the PiP task organizer will handle it.
             if (rootTask == null || !rootTask.inPinnedWindowingMode()) {
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 775570c..dfa9dce 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -37,7 +37,6 @@
         "com_android_server_adb_AdbDebuggingManager.cpp",
         "com_android_server_am_BatteryStatsService.cpp",
         "com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
-        "com_android_server_BootReceiver.cpp",
         "com_android_server_ConsumerIrService.cpp",
         "com_android_server_companion_virtual_InputController.cpp",
         "com_android_server_devicepolicy_CryptoTestHelper.cpp",
@@ -95,16 +94,6 @@
     header_libs: [
         "bionic_libc_platform_headers",
     ],
-
-    static_libs: [
-        "libunwindstack",
-    ],
-
-    whole_static_libs: [
-        "libdebuggerd_tombstone_proto_to_text",
-    ],
-
-    runtime_libs: ["libdexfile"],
 }
 
 cc_defaults {
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 15eb7c6..cc08488 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -33,7 +33,3 @@
 
 # Bug component : 158088 = per-file *AnrTimer*
 per-file *AnrTimer* = file:/PERFORMANCE_OWNERS
-
-# Bug component : 158088 = per-file com_android_server_utils_AnrTimer*.java
-per-file com_android_server_utils_AnrTimer*.java = file:/PERFORMANCE_OWNERS
-per-file com_android_server_BootReceiver.cpp = file:/STABILITY_OWNERS
diff --git a/services/core/jni/com_android_server_BootReceiver.cpp b/services/core/jni/com_android_server_BootReceiver.cpp
deleted file mode 100644
index 3892d28..0000000
--- a/services/core/jni/com_android_server_BootReceiver.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <libdebuggerd/tombstone.h>
-#include <nativehelper/JNIHelp.h>
-
-#include <sstream>
-
-#include "jni.h"
-#include "tombstone.pb.h"
-
-namespace android {
-
-static void writeToString(std::stringstream& ss, const std::string& line, bool should_log) {
-    ss << line << std::endl;
-}
-
-static jstring com_android_server_BootReceiver_getTombstoneText(JNIEnv* env, jobject,
-                                                                jbyteArray tombstoneBytes) {
-    Tombstone tombstone;
-    tombstone.ParseFromArray(env->GetByteArrayElements(tombstoneBytes, 0),
-                             env->GetArrayLength(tombstoneBytes));
-
-    std::stringstream tombstoneString;
-
-    tombstone_proto_to_text(tombstone,
-                            std::bind(&writeToString, std::ref(tombstoneString),
-                                      std::placeholders::_1, std::placeholders::_2));
-
-    return env->NewStringUTF(tombstoneString.str().c_str());
-}
-
-static const JNINativeMethod sMethods[] = {
-        /* name, signature, funcPtr */
-        {"getTombstoneText", "([B)Ljava/lang/String;",
-         (jstring*)com_android_server_BootReceiver_getTombstoneText},
-};
-
-int register_com_android_server_BootReceiver(JNIEnv* env) {
-    return jniRegisterNativeMethods(env, "com/android/server/BootReceiver", sMethods,
-                                    NELEM(sMethods));
-}
-
-} // namespace android
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 7b08413..4403bce 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -571,8 +571,8 @@
 }
 
 static jboolean com_android_server_am_CachedAppOptimizer_isFreezerProfileValid(JNIEnv* env) {
-    int uid = getuid();
-    int pid = getpid();
+    uid_t uid = getuid();
+    pid_t pid = getpid();
 
     return isProfileValidForProcess("Frozen", uid, pid) &&
             isProfileValidForProcess("Unfrozen", uid, pid);
diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
index 4cd018b..50d48b7 100644
--- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp
+++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
@@ -44,6 +44,7 @@
     MOUSE,
     TOUCHSCREEN,
     DPAD,
+    STYLUS,
 };
 
 static unique_fd invalidFd() {
@@ -98,6 +99,24 @@
             ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOUCH_MAJOR);
             ioctl(fd, UI_SET_ABSBIT, ABS_MT_PRESSURE);
             ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT);
+            break;
+        case DeviceType::STYLUS:
+            ioctl(fd, UI_SET_EVBIT, EV_ABS);
+            ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH);
+            ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS);
+            ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS2);
+            ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_PEN);
+            ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_RUBBER);
+            ioctl(fd, UI_SET_ABSBIT, ABS_X);
+            ioctl(fd, UI_SET_ABSBIT, ABS_Y);
+            ioctl(fd, UI_SET_ABSBIT, ABS_TILT_X);
+            ioctl(fd, UI_SET_ABSBIT, ABS_TILT_Y);
+            ioctl(fd, UI_SET_ABSBIT, ABS_PRESSURE);
+            ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT);
+            break;
+        default:
+            ALOGE("Invalid input device type %d", static_cast<int32_t>(deviceType));
+            return invalidFd();
     }
 
     int version;
@@ -158,6 +177,47 @@
                 ALOGE("Error creating touchscreen uinput tracking ids: %s", strerror(errno));
                 return invalidFd();
             }
+        } else if (deviceType == DeviceType::STYLUS) {
+            uinput_abs_setup xAbsSetup;
+            xAbsSetup.code = ABS_X;
+            xAbsSetup.absinfo.maximum = screenWidth - 1;
+            xAbsSetup.absinfo.minimum = 0;
+            if (ioctl(fd, UI_ABS_SETUP, &xAbsSetup) != 0) {
+                ALOGE("Error creating stylus uinput x axis: %s", strerror(errno));
+                return invalidFd();
+            }
+            uinput_abs_setup yAbsSetup;
+            yAbsSetup.code = ABS_Y;
+            yAbsSetup.absinfo.maximum = screenHeight - 1;
+            yAbsSetup.absinfo.minimum = 0;
+            if (ioctl(fd, UI_ABS_SETUP, &yAbsSetup) != 0) {
+                ALOGE("Error creating stylus uinput y axis: %s", strerror(errno));
+                return invalidFd();
+            }
+            uinput_abs_setup tiltXAbsSetup;
+            tiltXAbsSetup.code = ABS_TILT_X;
+            tiltXAbsSetup.absinfo.maximum = 90;
+            tiltXAbsSetup.absinfo.minimum = -90;
+            if (ioctl(fd, UI_ABS_SETUP, &tiltXAbsSetup) != 0) {
+                ALOGE("Error creating stylus uinput tilt x axis: %s", strerror(errno));
+                return invalidFd();
+            }
+            uinput_abs_setup tiltYAbsSetup;
+            tiltYAbsSetup.code = ABS_TILT_Y;
+            tiltYAbsSetup.absinfo.maximum = 90;
+            tiltYAbsSetup.absinfo.minimum = -90;
+            if (ioctl(fd, UI_ABS_SETUP, &tiltYAbsSetup) != 0) {
+                ALOGE("Error creating stylus uinput tilt y axis: %s", strerror(errno));
+                return invalidFd();
+            }
+            uinput_abs_setup pressureAbsSetup;
+            pressureAbsSetup.code = ABS_PRESSURE;
+            pressureAbsSetup.absinfo.maximum = 255;
+            pressureAbsSetup.absinfo.minimum = 0;
+            if (ioctl(fd, UI_ABS_SETUP, &pressureAbsSetup) != 0) {
+                ALOGE("Error creating touchscreen uinput pressure axis: %s", strerror(errno));
+                return invalidFd();
+            }
         }
         if (ioctl(fd, UI_DEV_SETUP, &setup) != 0) {
             ALOGE("Error creating uinput device: %s", strerror(errno));
@@ -182,6 +242,17 @@
             fallback.absmax[ABS_MT_TOUCH_MAJOR] = screenWidth - 1;
             fallback.absmin[ABS_MT_PRESSURE] = 0;
             fallback.absmax[ABS_MT_PRESSURE] = 255;
+        } else if (deviceType == DeviceType::STYLUS) {
+            fallback.absmin[ABS_X] = 0;
+            fallback.absmax[ABS_X] = screenWidth - 1;
+            fallback.absmin[ABS_Y] = 0;
+            fallback.absmax[ABS_Y] = screenHeight - 1;
+            fallback.absmin[ABS_TILT_X] = -90;
+            fallback.absmax[ABS_TILT_X] = 90;
+            fallback.absmin[ABS_TILT_Y] = -90;
+            fallback.absmax[ABS_TILT_Y] = 90;
+            fallback.absmin[ABS_PRESSURE] = 0;
+            fallback.absmax[ABS_PRESSURE] = 255;
         }
         if (TEMP_FAILURE_RETRY(write(fd, &fallback, sizeof(fallback))) != sizeof(fallback)) {
             ALOGE("Error creating uinput device: %s", strerror(errno));
@@ -234,6 +305,13 @@
     return fd.ok() ? reinterpret_cast<jlong>(new VirtualTouchscreen(std::move(fd))) : INVALID_PTR;
 }
 
+static jlong nativeOpenUinputStylus(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
+                                    jint productId, jstring phys, jint height, jint width) {
+    auto fd =
+            openUinputJni(env, name, vendorId, productId, phys, DeviceType::STYLUS, height, width);
+    return fd.ok() ? reinterpret_cast<jlong>(new VirtualStylus(std::move(fd))) : INVALID_PTR;
+}
+
 static void nativeCloseUinput(JNIEnv* env, jobject thiz, jlong ptr) {
     VirtualInputDevice* virtualInputDevice = reinterpret_cast<VirtualInputDevice*>(ptr);
     delete virtualInputDevice;
@@ -287,6 +365,22 @@
                                           std::chrono::nanoseconds(eventTimeNanos));
 }
 
+// Native methods for VirtualStylus
+static bool nativeWriteStylusMotionEvent(JNIEnv* env, jobject thiz, jlong ptr, jint toolType,
+                                         jint action, jint locationX, jint locationY, jint pressure,
+                                         jint tiltX, jint tiltY, jlong eventTimeNanos) {
+    VirtualStylus* virtualStylus = reinterpret_cast<VirtualStylus*>(ptr);
+    return virtualStylus->writeMotionEvent(toolType, action, locationX, locationY, pressure, tiltX,
+                                           tiltY, std::chrono::nanoseconds(eventTimeNanos));
+}
+
+static bool nativeWriteStylusButtonEvent(JNIEnv* env, jobject thiz, jlong ptr, jint buttonCode,
+                                         jint action, jlong eventTimeNanos) {
+    VirtualStylus* virtualStylus = reinterpret_cast<VirtualStylus*>(ptr);
+    return virtualStylus->writeButtonEvent(buttonCode, action,
+                                           std::chrono::nanoseconds(eventTimeNanos));
+}
+
 static JNINativeMethod methods[] = {
         {"nativeOpenUinputDpad", "(Ljava/lang/String;IILjava/lang/String;)J",
          (void*)nativeOpenUinputDpad},
@@ -296,6 +390,8 @@
          (void*)nativeOpenUinputMouse},
         {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IILjava/lang/String;II)J",
          (void*)nativeOpenUinputTouchscreen},
+        {"nativeOpenUinputStylus", "(Ljava/lang/String;IILjava/lang/String;II)J",
+         (void*)nativeOpenUinputStylus},
         {"nativeCloseUinput", "(J)V", (void*)nativeCloseUinput},
         {"nativeWriteDpadKeyEvent", "(JIIJ)Z", (void*)nativeWriteDpadKeyEvent},
         {"nativeWriteKeyEvent", "(JIIJ)Z", (void*)nativeWriteKeyEvent},
@@ -303,6 +399,8 @@
         {"nativeWriteTouchEvent", "(JIIIFFFFJ)Z", (void*)nativeWriteTouchEvent},
         {"nativeWriteRelativeEvent", "(JFFJ)Z", (void*)nativeWriteRelativeEvent},
         {"nativeWriteScrollEvent", "(JFFJ)Z", (void*)nativeWriteScrollEvent},
+        {"nativeWriteStylusMotionEvent", "(JIIIIIIIJ)Z", (void*)nativeWriteStylusMotionEvent},
+        {"nativeWriteStylusButtonEvent", "(JIIJ)Z", (void*)nativeWriteStylusButtonEvent},
 };
 
 int register_android_server_companion_virtual_InputController(JNIEnv* env) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index afb0b20..cbc301b 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -46,6 +46,7 @@
 #include <com_android_input_flags.h>
 #include <input/Input.h>
 #include <input/PointerController.h>
+#include <input/PrintTools.h>
 #include <input/SpriteController.h>
 #include <inputflinger/InputManager.h>
 #include <limits.h>
@@ -136,11 +137,10 @@
     jmethodID getDoubleTapTimeout;
     jmethodID getLongPressTimeout;
     jmethodID getPointerLayer;
-    jmethodID getPointerIcon;
+    jmethodID getLoadedPointerIcon;
     jmethodID getKeyboardLayoutOverlay;
     jmethodID getDeviceAlias;
     jmethodID getTouchCalibrationForInputDevice;
-    jmethodID getContextForDisplay;
     jmethodID notifyDropWindow;
     jmethodID getParentSurfaceForPointers;
 } gServiceClassInfo;
@@ -231,28 +231,14 @@
     return a > b ? a : b;
 }
 
-static inline const char* toString(bool value) {
-    return value ? "true" : "false";
-}
-
-static void loadSystemIconAsSpriteWithPointerIcon(JNIEnv* env, jobject contextObj,
-                                                  PointerIconStyle style,
-                                                  PointerIcon* outPointerIcon,
-                                                  SpriteIcon* outSpriteIcon) {
-    status_t status = android_view_PointerIcon_loadSystemIcon(env,
-            contextObj, style, outPointerIcon);
-    if (!status) {
-        outSpriteIcon->bitmap = outPointerIcon->bitmap.copy(ANDROID_BITMAP_FORMAT_RGBA_8888);
-        outSpriteIcon->style = outPointerIcon->style;
-        outSpriteIcon->hotSpotX = outPointerIcon->hotSpotX;
-        outSpriteIcon->hotSpotY = outPointerIcon->hotSpotY;
-    }
-}
-
-static void loadSystemIconAsSprite(JNIEnv* env, jobject contextObj, PointerIconStyle style,
-                                   SpriteIcon* outSpriteIcon) {
-    PointerIcon pointerIcon;
-    loadSystemIconAsSpriteWithPointerIcon(env, contextObj, style, &pointerIcon, outSpriteIcon);
+static SpriteIcon toSpriteIcon(PointerIcon pointerIcon) {
+    // As a minor optimization, do not make a copy of the PointerIcon bitmap here. The loaded
+    // PointerIcons are only cached by InputManagerService in java, so we can safely assume they
+    // will not be modified. This is safe because the native bitmap object holds a strong reference
+    // to the underlying bitmap, so even if the java object is released, we will still have access
+    // to it.
+    return SpriteIcon(pointerIcon.bitmap, pointerIcon.style, pointerIcon.hotSpotX,
+                      pointerIcon.hotSpotY);
 }
 
 enum {
@@ -295,11 +281,12 @@
     void displayRemoved(JNIEnv* env, int32_t displayId);
     void setFocusedApplication(JNIEnv* env, int32_t displayId, jobject applicationHandleObj);
     void setFocusedDisplay(int32_t displayId);
+    void setMinTimeBetweenUserActivityPokes(int64_t intervalMillis);
     void setInputDispatchMode(bool enabled, bool frozen);
     void setSystemUiLightsOut(bool lightsOut);
     void setPointerDisplayId(int32_t displayId);
     void setPointerSpeed(int32_t speed);
-    void setPointerAcceleration(float acceleration);
+    void setMousePointerAccelerationEnabled(int32_t displayId, bool enabled);
     void setTouchpadPointerSpeed(int32_t speed);
     void setTouchpadNaturalScrollingEnabled(bool enabled);
     void setTouchpadTapToClickEnabled(bool enabled);
@@ -315,10 +302,11 @@
     bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
                         int32_t displayId, DeviceId deviceId, int32_t pointerId,
                         const sp<IBinder>& inputToken);
+    void setPointerIconVisibility(int32_t displayId, bool visible);
     void setMotionClassifierEnabled(bool enabled);
     std::optional<std::string> getBluetoothAddress(int32_t deviceId);
     void setStylusButtonMotionEventsEnabled(bool enabled);
-    FloatPoint getMouseCursorPosition();
+    FloatPoint getMouseCursorPosition(int32_t displayId);
     void setStylusPointerIconEnabled(bool enabled);
 
     /* --- InputReaderPolicyInterface implementation --- */
@@ -411,8 +399,8 @@
         // Pointer speed.
         int32_t pointerSpeed{0};
 
-        // Pointer acceleration.
-        float pointerAcceleration{android::os::IInputConstants::DEFAULT_POINTER_ACCELERATION};
+        // Displays on which its associated mice will have pointer acceleration disabled.
+        std::set<int32_t> displaysWithMousePointerAccelerationDisabled{};
 
         // True if pointer gestures are enabled.
         bool pointerGesturesEnabled{true};
@@ -473,6 +461,7 @@
 
     void forEachPointerControllerLocked(std::function<void(PointerController&)> apply)
             REQUIRES(mLock);
+    PointerIcon loadPointerIcon(JNIEnv* env, int32_t displayId, PointerIconStyle type);
 
     static inline JNIEnv* jniEnv() { return AndroidRuntime::getJNIEnv(); }
 };
@@ -502,7 +491,8 @@
         dump += StringPrintf(INDENT "System UI Lights Out: %s\n",
                              toString(mLocked.systemUiLightsOut));
         dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed);
-        dump += StringPrintf(INDENT "Pointer Acceleration: %0.3f\n", mLocked.pointerAcceleration);
+        dump += StringPrintf(INDENT "Display with Mouse Pointer Acceleration Disabled: %s\n",
+                             dumpSet(mLocked.displaysWithMousePointerAccelerationDisabled).c_str());
         dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
                 toString(mLocked.pointerGesturesEnabled));
         dump += StringPrintf(INDENT "Show Touches: %s\n", toString(mLocked.showTouches));
@@ -684,9 +674,16 @@
     { // acquire lock
         std::scoped_lock _l(mLock);
 
-        outConfig->pointerVelocityControlParameters.scale = exp2f(mLocked.pointerSpeed
-                * POINTER_SPEED_EXPONENT);
-        outConfig->pointerVelocityControlParameters.acceleration = mLocked.pointerAcceleration;
+        outConfig->mousePointerSpeed = mLocked.pointerSpeed;
+        outConfig->displaysWithMousePointerAccelerationDisabled =
+                mLocked.displaysWithMousePointerAccelerationDisabled;
+        outConfig->pointerVelocityControlParameters.scale =
+                exp2f(mLocked.pointerSpeed * POINTER_SPEED_EXPONENT);
+        outConfig->pointerVelocityControlParameters.acceleration =
+                mLocked.displaysWithMousePointerAccelerationDisabled.count(
+                        mLocked.pointerDisplayId) == 0
+                ? android::os::IInputConstants::DEFAULT_POINTER_ACCELERATION
+                : 1;
         outConfig->pointerGesturesEnabled = mLocked.pointerGesturesEnabled;
 
         outConfig->showTouches = mLocked.showTouches;
@@ -747,6 +744,27 @@
     }
 }
 
+PointerIcon NativeInputManager::loadPointerIcon(JNIEnv* env, int32_t displayId,
+                                                PointerIconStyle type) {
+    if (type == PointerIconStyle::TYPE_CUSTOM) {
+        LOG(FATAL) << __func__ << ": Cannot load non-system icon type";
+    }
+    if (type == PointerIconStyle::TYPE_NULL) {
+        return PointerIcon();
+    }
+
+    ScopedLocalRef<jobject> pointerIconObj(env,
+                                           env->CallObjectMethod(mServiceObj,
+                                                                 gServiceClassInfo
+                                                                         .getLoadedPointerIcon,
+                                                                 displayId, type));
+    if (checkAndClearExceptionFromCallback(env, "getLoadedPointerIcon")) {
+        LOG(FATAL) << __func__ << ": Failed to load pointer icon";
+    }
+
+    return android_view_PointerIcon_toNative(env, pointerIconObj.get());
+}
+
 // TODO(b/293587049): Remove the old way of obtaining PointerController when the
 //  PointerChoreographer refactoring is complete.
 std::shared_ptr<PointerControllerInterface> NativeInputManager::obtainPointerController(
@@ -1152,6 +1170,11 @@
     mInputManager->getDispatcher().setFocusedDisplay(displayId);
 }
 
+void NativeInputManager::setMinTimeBetweenUserActivityPokes(int64_t intervalMillis) {
+    mInputManager->getDispatcher().setMinTimeBetweenUserActivityPokes(
+            std::chrono::milliseconds(intervalMillis));
+}
+
 void NativeInputManager::setInputDispatchMode(bool enabled, bool frozen) {
     mInputManager->getDispatcher().setInputDispatchMode(enabled, frozen);
 }
@@ -1207,16 +1230,23 @@
             InputReaderConfiguration::Change::POINTER_SPEED);
 }
 
-void NativeInputManager::setPointerAcceleration(float acceleration) {
+void NativeInputManager::setMousePointerAccelerationEnabled(int32_t displayId, bool enabled) {
     { // acquire lock
         std::scoped_lock _l(mLock);
 
-        if (mLocked.pointerAcceleration == acceleration) {
+        const bool oldEnabled =
+                mLocked.displaysWithMousePointerAccelerationDisabled.count(displayId) == 0;
+        if (oldEnabled == enabled) {
             return;
         }
 
-        ALOGI("Setting pointer acceleration to %0.3f", acceleration);
-        mLocked.pointerAcceleration = acceleration;
+        ALOGI("Setting mouse pointer acceleration to %s on display %d", toString(enabled),
+              displayId);
+        if (enabled) {
+            mLocked.displaysWithMousePointerAccelerationDisabled.erase(displayId);
+        } else {
+            mLocked.displaysWithMousePointerAccelerationDisabled.emplace(displayId);
+        }
     } // release lock
 
     mInputManager->getReader().requestRefreshConfiguration(
@@ -1380,6 +1410,13 @@
     return mInputManager->getChoreographer().setPointerIcon(std::move(icon), displayId, deviceId);
 }
 
+void NativeInputManager::setPointerIconVisibility(int32_t displayId, bool visible) {
+    if (!ENABLE_POINTER_CHOREOGRAPHER) {
+        return;
+    }
+    mInputManager->getChoreographer().setPointerIconVisibility(displayId, visible);
+}
+
 TouchAffineTransformation NativeInputManager::getTouchAffineTransformation(
         JNIEnv *env, jfloatArray matrixArr) {
     ATRACE_CALL();
@@ -1664,40 +1701,19 @@
 void NativeInputManager::loadPointerIcon(SpriteIcon* icon, int32_t displayId) {
     ATRACE_CALL();
     JNIEnv* env = jniEnv();
-
-    ScopedLocalRef<jobject> pointerIconObj(env, env->CallObjectMethod(
-            mServiceObj, gServiceClassInfo.getPointerIcon, displayId));
-    if (checkAndClearExceptionFromCallback(env, "getPointerIcon")) {
-        return;
-    }
-
-    ScopedLocalRef<jobject> displayContext(env, env->CallObjectMethod(
-            mServiceObj, gServiceClassInfo.getContextForDisplay, displayId));
-
-    PointerIcon pointerIcon;
-    status_t status = android_view_PointerIcon_load(env, pointerIconObj.get(),
-            displayContext.get(), &pointerIcon);
-    if (!status && !pointerIcon.isNullIcon()) {
-        *icon = SpriteIcon(
-                pointerIcon.bitmap, pointerIcon.style, pointerIcon.hotSpotX, pointerIcon.hotSpotY);
-    } else {
-        *icon = SpriteIcon();
-    }
+    *icon = toSpriteIcon(loadPointerIcon(env, displayId, PointerIconStyle::TYPE_ARROW));
 }
 
 void NativeInputManager::loadPointerResources(PointerResources* outResources, int32_t displayId) {
     ATRACE_CALL();
     JNIEnv* env = jniEnv();
 
-    ScopedLocalRef<jobject> displayContext(env, env->CallObjectMethod(
-            mServiceObj, gServiceClassInfo.getContextForDisplay, displayId));
-
-    loadSystemIconAsSprite(env, displayContext.get(), PointerIconStyle::TYPE_SPOT_HOVER,
-                           &outResources->spotHover);
-    loadSystemIconAsSprite(env, displayContext.get(), PointerIconStyle::TYPE_SPOT_TOUCH,
-                           &outResources->spotTouch);
-    loadSystemIconAsSprite(env, displayContext.get(), PointerIconStyle::TYPE_SPOT_ANCHOR,
-                           &outResources->spotAnchor);
+    outResources->spotHover =
+            toSpriteIcon(loadPointerIcon(env, displayId, PointerIconStyle::TYPE_SPOT_HOVER));
+    outResources->spotTouch =
+            toSpriteIcon(loadPointerIcon(env, displayId, PointerIconStyle::TYPE_SPOT_TOUCH));
+    outResources->spotAnchor =
+            toSpriteIcon(loadPointerIcon(env, displayId, PointerIconStyle::TYPE_SPOT_ANCHOR));
 }
 
 void NativeInputManager::loadAdditionalMouseResources(
@@ -1706,15 +1722,11 @@
     ATRACE_CALL();
     JNIEnv* env = jniEnv();
 
-    ScopedLocalRef<jobject> displayContext(env, env->CallObjectMethod(
-            mServiceObj, gServiceClassInfo.getContextForDisplay, displayId));
-
     for (int32_t iconId = static_cast<int32_t>(PointerIconStyle::TYPE_CONTEXT_MENU);
          iconId <= static_cast<int32_t>(PointerIconStyle::TYPE_HANDWRITING); ++iconId) {
         const PointerIconStyle pointerIconStyle = static_cast<PointerIconStyle>(iconId);
-        PointerIcon pointerIcon;
-        loadSystemIconAsSpriteWithPointerIcon(env, displayContext.get(), pointerIconStyle,
-                                              &pointerIcon, &((*outResources)[pointerIconStyle]));
+        PointerIcon pointerIcon = loadPointerIcon(env, displayId, pointerIconStyle);
+        (*outResources)[pointerIconStyle] = toSpriteIcon(pointerIcon);
         if (!pointerIcon.bitmapFrames.empty()) {
             PointerAnimation& animationData = (*outAnimationResources)[pointerIconStyle];
             size_t numFrames = pointerIcon.bitmapFrames.size() + 1;
@@ -1731,8 +1743,9 @@
             }
         }
     }
-    loadSystemIconAsSprite(env, displayContext.get(), PointerIconStyle::TYPE_NULL,
-                           &((*outResources)[PointerIconStyle::TYPE_NULL]));
+
+    (*outResources)[PointerIconStyle::TYPE_NULL] =
+            toSpriteIcon(loadPointerIcon(env, displayId, PointerIconStyle::TYPE_NULL));
 }
 
 PointerIconStyle NativeInputManager::getDefaultPointerIconId() {
@@ -1771,10 +1784,12 @@
             InputReaderConfiguration::Change::STYLUS_BUTTON_REPORTING);
 }
 
-FloatPoint NativeInputManager::getMouseCursorPosition() {
+FloatPoint NativeInputManager::getMouseCursorPosition(int32_t displayId) {
     if (ENABLE_POINTER_CHOREOGRAPHER) {
-        return mInputManager->getChoreographer().getMouseCursorPosition(ADISPLAY_ID_NONE);
+        return mInputManager->getChoreographer().getMouseCursorPosition(displayId);
     }
+    // To maintain the status-quo, the displayId parameter (used when PointerChoreographer is
+    // enabled) is ignored in the old pipeline.
     std::scoped_lock _l(mLock);
     const auto pc = mLocked.legacyPointerController.lock();
     if (!pc) return {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION};
@@ -2115,6 +2130,13 @@
     im->setFocusedDisplay(displayId);
 }
 
+static void nativeSetUserActivityPokeInterval(JNIEnv* env, jobject nativeImplObj,
+                                              jlong intervalMillis) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+    im->setMinTimeBetweenUserActivityPokes(intervalMillis);
+}
+
 static void nativeRequestPointerCapture(JNIEnv* env, jobject nativeImplObj, jobject tokenObj,
                                         jboolean enabled) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2174,10 +2196,11 @@
     im->setPointerSpeed(speed);
 }
 
-static void nativeSetPointerAcceleration(JNIEnv* env, jobject nativeImplObj, jfloat acceleration) {
+static void nativeSetMousePointerAccelerationEnabled(JNIEnv* env, jobject nativeImplObj,
+                                                     jint displayId, jboolean enabled) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
-    im->setPointerAcceleration(acceleration);
+    im->setMousePointerAccelerationEnabled(displayId, enabled);
 }
 
 static void nativeSetTouchpadPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) {
@@ -2532,17 +2555,7 @@
 
 static void nativeSetCustomPointerIcon(JNIEnv* env, jobject nativeImplObj, jobject iconObj) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
-
-    PointerIcon pointerIcon;
-    status_t result = android_view_PointerIcon_getLoadedIcon(env, iconObj, &pointerIcon);
-    if (result) {
-        jniThrowRuntimeException(env, "Failed to load custom pointer icon.");
-        return;
-    }
-
-    SpriteIcon spriteIcon(pointerIcon.bitmap.copy(ANDROID_BITMAP_FORMAT_RGBA_8888),
-                          pointerIcon.style, pointerIcon.hotSpotX, pointerIcon.hotSpotY);
-    im->setCustomPointerIcon(spriteIcon);
+    im->setCustomPointerIcon(toSpriteIcon(android_view_PointerIcon_toNative(env, iconObj)));
 }
 
 static bool nativeSetPointerIcon(JNIEnv* env, jobject nativeImplObj, jobject iconObj,
@@ -2550,12 +2563,7 @@
                                  jobject inputTokenObj) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
-    PointerIcon pointerIcon;
-    status_t result = android_view_PointerIcon_getLoadedIcon(env, iconObj, &pointerIcon);
-    if (result) {
-        jniThrowRuntimeException(env, "Failed to load pointer icon.");
-        return false;
-    }
+    PointerIcon pointerIcon = android_view_PointerIcon_toNative(env, iconObj);
 
     std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon;
     if (pointerIcon.style == PointerIconStyle::TYPE_CUSTOM) {
@@ -2571,6 +2579,13 @@
                               ibinderForJavaObject(env, inputTokenObj));
 }
 
+static void nativeSetPointerIconVisibility(JNIEnv* env, jobject nativeImplObj, jint displayId,
+                                           jboolean visible) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+    im->setPointerIconVisibility(displayId, visible);
+}
+
 static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jobject nativeImplObj, jint deviceId,
                                            jint displayId) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2738,9 +2753,10 @@
     im->setStylusButtonMotionEventsEnabled(enabled);
 }
 
-static jfloatArray nativeGetMouseCursorPosition(JNIEnv* env, jobject nativeImplObj) {
+static jfloatArray nativeGetMouseCursorPosition(JNIEnv* env, jobject nativeImplObj,
+                                                jint displayId) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
-    const auto p = im->getMouseCursorPosition();
+    const auto p = im->getMouseCursorPosition(displayId);
     const std::array<float, 2> arr = {{p.x, p.y}};
     jfloatArray outArr = env->NewFloatArray(2);
     env->SetFloatArrayRegion(outArr, 0, arr.size(), arr.data());
@@ -2762,6 +2778,15 @@
     }
 }
 
+static void nativeSetAccessibilitySlowKeysThreshold(JNIEnv* env, jobject nativeImplObj,
+                                                    jint thresholdTimeMs) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+    if (ENABLE_INPUT_FILTER_RUST) {
+        im->getInputManager()->getInputFilter().setAccessibilitySlowKeysThreshold(
+                static_cast<nsecs_t>(thresholdTimeMs) * 1000000);
+    }
+}
+
 static void nativeSetAccessibilityStickyKeysEnabled(JNIEnv* env, jobject nativeImplObj,
                                                     jboolean enabled) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2805,6 +2830,7 @@
         {"setFocusedApplication", "(ILandroid/view/InputApplicationHandle;)V",
          (void*)nativeSetFocusedApplication},
         {"setFocusedDisplay", "(I)V", (void*)nativeSetFocusedDisplay},
+        {"setMinTimeBetweenUserActivityPokes", "(J)V", (void*)nativeSetUserActivityPokeInterval},
         {"requestPointerCapture", "(Landroid/os/IBinder;Z)V", (void*)nativeRequestPointerCapture},
         {"setInputDispatchMode", "(ZZ)V", (void*)nativeSetInputDispatchMode},
         {"setSystemUiLightsOut", "(Z)V", (void*)nativeSetSystemUiLightsOut},
@@ -2812,7 +2838,8 @@
          (void*)nativeTransferTouchFocus},
         {"transferTouch", "(Landroid/os/IBinder;I)Z", (void*)nativeTransferTouch},
         {"setPointerSpeed", "(I)V", (void*)nativeSetPointerSpeed},
-        {"setPointerAcceleration", "(F)V", (void*)nativeSetPointerAcceleration},
+        {"setMousePointerAccelerationEnabled", "(IZ)V",
+         (void*)nativeSetMousePointerAccelerationEnabled},
         {"setTouchpadPointerSpeed", "(I)V", (void*)nativeSetTouchpadPointerSpeed},
         {"setTouchpadNaturalScrollingEnabled", "(Z)V",
          (void*)nativeSetTouchpadNaturalScrollingEnabled},
@@ -2848,6 +2875,7 @@
          (void*)nativeSetCustomPointerIcon},
         {"setPointerIcon", "(Landroid/view/PointerIcon;IIILandroid/os/IBinder;)Z",
          (void*)nativeSetPointerIcon},
+        {"setPointerIconVisibility", "(IZ)V", (void*)nativeSetPointerIconVisibility},
         {"canDispatchToDisplay", "(II)Z", (void*)nativeCanDispatchToDisplay},
         {"notifyPortAssociationsChanged", "()V", (void*)nativeNotifyPortAssociationsChanged},
         {"changeUniqueIdAssociation", "()V", (void*)nativeChangeUniqueIdAssociation},
@@ -2867,10 +2895,12 @@
         {"getBluetoothAddress", "(I)Ljava/lang/String;", (void*)nativeGetBluetoothAddress},
         {"setStylusButtonMotionEventsEnabled", "(Z)V",
          (void*)nativeSetStylusButtonMotionEventsEnabled},
-        {"getMouseCursorPosition", "()[F", (void*)nativeGetMouseCursorPosition},
+        {"getMouseCursorPosition", "(I)[F", (void*)nativeGetMouseCursorPosition},
         {"setStylusPointerIconEnabled", "(Z)V", (void*)nativeSetStylusPointerIconEnabled},
         {"setAccessibilityBounceKeysThreshold", "(I)V",
          (void*)nativeSetAccessibilityBounceKeysThreshold},
+        {"setAccessibilitySlowKeysThreshold", "(I)V",
+         (void*)nativeSetAccessibilitySlowKeysThreshold},
         {"setAccessibilityStickyKeysEnabled", "(Z)V",
          (void*)nativeSetAccessibilityStickyKeysEnabled},
 };
@@ -3010,8 +3040,8 @@
     GET_METHOD_ID(gServiceClassInfo.getPointerLayer, clazz,
             "getPointerLayer", "()I");
 
-    GET_METHOD_ID(gServiceClassInfo.getPointerIcon, clazz,
-            "getPointerIcon", "(I)Landroid/view/PointerIcon;");
+    GET_METHOD_ID(gServiceClassInfo.getLoadedPointerIcon, clazz, "getLoadedPointerIcon",
+                  "(II)Landroid/view/PointerIcon;");
 
     GET_METHOD_ID(gServiceClassInfo.getKeyboardLayoutOverlay, clazz, "getKeyboardLayoutOverlay",
                   "(Landroid/hardware/input/InputDeviceIdentifier;Ljava/lang/String;Ljava/lang/"
@@ -3024,9 +3054,6 @@
             "getTouchCalibrationForInputDevice",
             "(Ljava/lang/String;I)Landroid/hardware/input/TouchCalibration;");
 
-    GET_METHOD_ID(gServiceClassInfo.getContextForDisplay, clazz, "getContextForDisplay",
-                  "(I)Landroid/content/Context;");
-
     GET_METHOD_ID(gServiceClassInfo.getParentSurfaceForPointers, clazz,
                   "getParentSurfaceForPointers", "(I)J");
 
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 11c40d7..9c033e2 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -675,7 +675,8 @@
     options.enableCorrVecOutputs = enableCorrVecOutputs;
     options.intervalMs = intervalMs;
 
-    return gnssMeasurementIface->setCallback(std::make_unique<gnss::GnssMeasurementCallback>(),
+    return gnssMeasurementIface->setCallback(std::make_unique<gnss::GnssMeasurementCallback>(
+                                                     gnssMeasurementIface->getInterfaceVersion()),
                                              options);
 }
 
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index 6ab98fe..d0b290c 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -31,6 +31,7 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
 #include <binder/IServiceManager.h>
+#include <com_android_input_flags.h>
 #include <gui/SurfaceComposerClient.h>
 #include <hardware_legacy/power.h>
 #include <hidl/ServiceManagement.h>
@@ -109,10 +110,12 @@
                 eventTime = now;
             }
 
-            if (gLastEventTime[eventType] + MIN_TIME_BETWEEN_USERACTIVITIES > eventTime) {
-                return;
+            if (!com::android::input::flags::rate_limit_user_activity_poke_in_dispatcher()) {
+                if (gLastEventTime[eventType] + MIN_TIME_BETWEEN_USERACTIVITIES > eventTime) {
+                    return;
+                }
+                gLastEventTime[eventType] = eventTime;
             }
-            gLastEventTime[eventType] = eventTime;
 
             // Tell the power HAL when user activity occurs.
             setPowerBoost(Boost::INTERACTION, 0);
@@ -285,9 +288,11 @@
     GET_METHOD_ID(gPowerManagerServiceClassInfo.userActivityFromNative, clazz,
             "userActivityFromNative", "(JIII)V");
 
-    // Initialize
-    for (int i = 0; i <= USER_ACTIVITY_EVENT_LAST; i++) {
-        gLastEventTime[i] = LLONG_MIN;
+    if (!com::android::input::flags::rate_limit_user_activity_poke_in_dispatcher()) {
+        // Initialize
+        for (int i = 0; i <= USER_ACTIVITY_EVENT_LAST; i++) {
+            gLastEventTime[i] = LLONG_MIN;
+        }
     }
     gPowerManagerServiceObj = NULL;
     return 0;
diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
index 1e48ace..8ca5333 100644
--- a/services/core/jni/com_android_server_utils_AnrTimer.cpp
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -113,8 +113,10 @@
     static const timer_id_t NOTIMER = 0;
 
     // A notifier is called with a timer ID, the timer's tag, and the client's cookie.  The pid
-    // and uid that were originally assigned to the timer are passed as well.
-    using notifier_t = bool (*)(timer_id_t, int pid, int uid, void* cookie, jweak object);
+    // and uid that were originally assigned to the timer are passed as well.  The elapsed time
+    // is the time since the timer was scheduled.
+    using notifier_t = bool (*)(timer_id_t, int pid, int uid, nsecs_t elapsed,
+                                void* cookie, jweak object);
 
     enum Status {
         Invalid,
@@ -278,6 +280,9 @@
     // The state of this timer.
     Status status;
 
+    // The time at which the timer was started.
+    nsecs_t started;
+
     // The scheduled timeout.  This is an absolute time.  It may be extended.
     nsecs_t scheduled;
 
@@ -297,6 +302,7 @@
             timeout(0),
             extend(false),
             status(Invalid),
+            started(0),
             scheduled(0),
             extended(false) {
     }
@@ -310,6 +316,7 @@
             timeout(0),
             extend(false),
             status(Invalid),
+            started(0),
             scheduled(0),
             extended(false) {
     }
@@ -322,7 +329,8 @@
             timeout(timeout),
             extend(extend),
             status(Running),
-            scheduled(now() + timeout),
+            started(now()),
+            scheduled(started + timeout),
             extended(false) {
         if (extend && pid != 0) {
             initial.fill(pid);
@@ -714,6 +722,7 @@
     // Save the timer attributes for the notification
     int pid = 0;
     int uid = 0;
+    nsecs_t elapsed = 0;
     bool expired = false;
     {
         AutoMutex _l(lock_);
@@ -727,11 +736,14 @@
             // accept or discard).
             insert(t);
         }
+        pid = t.pid;
+        uid = t.uid;
+        elapsed = now() - t.started;
     }
 
     // Deliver the notification outside of the lock.
     if (expired) {
-        if (!notifier_(timerId, pid, uid, notifierCookie_, notifierObject_)) {
+        if (!notifier_(timerId, pid, uid, elapsed, notifierCookie_, notifierObject_)) {
             AutoMutex _l(lock_);
             // Notification failed, which means the listener will never call accept() or
             // discard().  Do not reinsert the timer.
@@ -804,7 +816,7 @@
 static AnrArgs gAnrArgs;
 
 // The cookie is the address of the AnrArgs object to which the notification should be sent.
-static bool anrNotify(AnrTimerService::timer_id_t timerId, int pid, int uid,
+static bool anrNotify(AnrTimerService::timer_id_t timerId, int pid, int uid, nsecs_t elapsed,
                       void* cookie, jweak jtimer) {
     AutoMutex _l(gAnrLock);
     AnrArgs* target = reinterpret_cast<AnrArgs* >(cookie);
@@ -816,7 +828,8 @@
     jboolean r = false;
     jobject timer = env->NewGlobalRef(jtimer);
     if (timer != nullptr) {
-        r = env->CallBooleanMethod(timer, target->func, timerId, pid, uid);
+        // Convert the elsapsed time from ns (native) to ms (Java)
+        r = env->CallBooleanMethod(timer, target->func, timerId, pid, uid, ns2ms(elapsed));
         env->DeleteGlobalRef(timer);
     }
     target->vm->DetachCurrentThread();
@@ -909,7 +922,7 @@
 
     jclass service = FindClassOrDie(env, className);
     gAnrArgs.clazz = MakeGlobalRefOrDie(env, service);
-    gAnrArgs.func = env->GetMethodID(gAnrArgs.clazz, "expire", "(III)Z");
+    gAnrArgs.func = env->GetMethodID(gAnrArgs.clazz, "expire", "(IIIJ)Z");
     env->GetJavaVM(&gAnrArgs.vm);
 
     nativeSupportEnabled = NATIVE_SUPPORT;
diff --git a/services/core/jni/gnss/Gnss.cpp b/services/core/jni/gnss/Gnss.cpp
index 8934c3a..da8928b5 100644
--- a/services/core/jni/gnss/Gnss.cpp
+++ b/services/core/jni/gnss/Gnss.cpp
@@ -196,7 +196,8 @@
 
 jboolean GnssHal::setCallback() {
     if (gnssHalAidl != nullptr) {
-        sp<IGnssCallbackAidl> gnssCbIfaceAidl = new GnssCallbackAidl();
+        sp<IGnssCallbackAidl> gnssCbIfaceAidl =
+                new GnssCallbackAidl(gnssHalAidl->getInterfaceVersion());
         auto status = gnssHalAidl->setCallback(gnssCbIfaceAidl);
         if (!checkAidlStatus(status, "IGnssAidl setCallback() failed.")) {
             return JNI_FALSE;
diff --git a/services/core/jni/gnss/GnssCallback.cpp b/services/core/jni/gnss/GnssCallback.cpp
index 60eed8e6..3d598f7 100644
--- a/services/core/jni/gnss/GnssCallback.cpp
+++ b/services/core/jni/gnss/GnssCallback.cpp
@@ -120,7 +120,7 @@
 
 Status GnssCallbackAidl::gnssSetCapabilitiesCb(const int capabilities) {
     ALOGD("%s: %du\n", __func__, capabilities);
-    bool isAdrCapabilityKnown = (getInterfaceVersion() >= 3) ? true : false;
+    bool isAdrCapabilityKnown = (interfaceVersion >= 3) ? true : false;
     JNIEnv* env = getJniEnv();
     env->CallVoidMethod(mCallbacksObj, method_setTopHalCapabilities, capabilities,
                         isAdrCapabilityKnown);
@@ -178,7 +178,7 @@
 
 Status GnssCallbackAidl::gnssNmeaCb(const int64_t timestamp, const std::string& nmea) {
     // In AIDL v1, if no listener is registered, do not report nmea to the framework.
-    if (getInterfaceVersion() <= 1) {
+    if (interfaceVersion <= 1) {
         if (!isNmeaRegistered) {
             return Status::ok();
         }
diff --git a/services/core/jni/gnss/GnssCallback.h b/services/core/jni/gnss/GnssCallback.h
index 33acec8..0622e53 100644
--- a/services/core/jni/gnss/GnssCallback.h
+++ b/services/core/jni/gnss/GnssCallback.h
@@ -60,6 +60,7 @@
  */
 class GnssCallbackAidl : public hardware::gnss::BnGnssCallback {
 public:
+    GnssCallbackAidl(int version) : interfaceVersion(version){};
     binder::Status gnssSetCapabilitiesCb(const int capabilities) override;
     binder::Status gnssSetSignalTypeCapabilitiesCb(
             const std::vector<android::hardware::gnss::GnssSignalType>& signalTypes) override;
@@ -73,6 +74,9 @@
     binder::Status gnssRequestTimeCb() override;
     binder::Status gnssRequestLocationCb(const bool independentFromGnss,
                                          const bool isUserEmergency) override;
+
+private:
+    const int interfaceVersion;
 };
 
 /*
diff --git a/services/core/jni/gnss/GnssMeasurement.h b/services/core/jni/gnss/GnssMeasurement.h
index 7a95db8..20400fd 100644
--- a/services/core/jni/gnss/GnssMeasurement.h
+++ b/services/core/jni/gnss/GnssMeasurement.h
@@ -41,6 +41,7 @@
             const std::unique_ptr<GnssMeasurementCallback>& callback,
             const android::hardware::gnss::IGnssMeasurementInterface::Options& options) = 0;
     virtual jboolean close() = 0;
+    virtual int getInterfaceVersion() = 0;
 };
 
 class GnssMeasurement : public GnssMeasurementInterface {
@@ -50,6 +51,9 @@
             const std::unique_ptr<GnssMeasurementCallback>& callback,
             const android::hardware::gnss::IGnssMeasurementInterface::Options& options) override;
     jboolean close() override;
+    int getInterfaceVersion() override {
+        return mIGnssMeasurement->getInterfaceVersion();
+    }
 
 private:
     const sp<android::hardware::gnss::IGnssMeasurementInterface> mIGnssMeasurement;
@@ -63,6 +67,9 @@
             const std::unique_ptr<GnssMeasurementCallback>& callback,
             const android::hardware::gnss::IGnssMeasurementInterface::Options& options) override;
     jboolean close() override;
+    int getInterfaceVersion() override {
+        return 0;
+    }
 
 private:
     const sp<android::hardware::gnss::V1_0::IGnssMeasurement> mIGnssMeasurement_V1_0;
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.cpp b/services/core/jni/gnss/GnssMeasurementCallback.cpp
index 2982546..ebab4c3 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.cpp
+++ b/services/core/jni/gnss/GnssMeasurementCallback.cpp
@@ -392,7 +392,7 @@
 
     jobjectArray gnssAgcArray = nullptr;
     gnssAgcArray = translateAllGnssAgcs(env, data.gnssAgcs);
-    if (this->getInterfaceVersion() >= 3) {
+    if (interfaceVersion >= 3) {
         setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray,
                            /*hasIsFullTracking=*/true, data.isFullTracking);
     } else {
@@ -467,7 +467,7 @@
                                            satellitePvt.tropoDelayMeters);
         }
 
-        if (this->getInterfaceVersion() >= 2) {
+        if (interfaceVersion >= 2) {
             callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
                                            method_satellitePvtBuilderSetTimeOfClock,
                                            satellitePvt.timeOfClockSeconds);
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.h b/services/core/jni/gnss/GnssMeasurementCallback.h
index b3de486..3cb47ce 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.h
+++ b/services/core/jni/gnss/GnssMeasurementCallback.h
@@ -53,7 +53,8 @@
 
 class GnssMeasurementCallbackAidl : public hardware::gnss::BnGnssMeasurementCallback {
 public:
-    GnssMeasurementCallbackAidl() : mCallbacksObj(getCallbacksObj()) {}
+    GnssMeasurementCallbackAidl(int version)
+          : mCallbacksObj(getCallbacksObj()), interfaceVersion(version) {}
     android::binder::Status gnssMeasurementCb(const hardware::gnss::GnssData& data) override;
 
 private:
@@ -71,6 +72,7 @@
     void translateGnssClock(JNIEnv* env, const hardware::gnss::GnssData& data, JavaObject& object);
 
     jobject& mCallbacksObj;
+    const int interfaceVersion;
 };
 
 /*
@@ -110,10 +112,10 @@
 
 class GnssMeasurementCallback {
 public:
-    GnssMeasurementCallback() {}
+    GnssMeasurementCallback(int version) : interfaceVersion(version) {}
     sp<GnssMeasurementCallbackAidl> getAidl() {
         if (callbackAidl == nullptr) {
-            callbackAidl = sp<GnssMeasurementCallbackAidl>::make();
+            callbackAidl = sp<GnssMeasurementCallbackAidl>::make(interfaceVersion);
         }
         return callbackAidl;
     }
@@ -128,6 +130,7 @@
 private:
     sp<GnssMeasurementCallbackAidl> callbackAidl;
     sp<GnssMeasurementCallbackHidl> callbackHidl;
+    const int interfaceVersion;
 };
 
 template <class T>
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index a4b1f84..5d1eb49 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -66,7 +66,6 @@
 int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env);
 int register_android_server_companion_virtual_InputController(JNIEnv* env);
 int register_android_server_app_GameManagerService(JNIEnv* env);
-int register_com_android_server_BootReceiver(JNIEnv* env);
 int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env);
 int register_com_android_server_display_DisplayControl(JNIEnv* env);
 int register_com_android_server_SystemClockTime(JNIEnv* env);
@@ -129,7 +128,6 @@
     register_android_server_sensor_SensorService(vm, env);
     register_android_server_companion_virtual_InputController(env);
     register_android_server_app_GameManagerService(env);
-    register_com_android_server_BootReceiver(env);
     register_com_android_server_wm_TaskFpsCallbackController(env);
     register_com_android_server_display_DisplayControl(env);
     register_com_android_server_SystemClockTime(env);
diff --git a/services/core/lint-baseline.xml b/services/core/lint-baseline.xml
index 070bd4b..2ccd1e4 100644
--- a/services/core/lint-baseline.xml
+++ b/services/core/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 7.2.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NonUserGetterCalled"
@@ -145,4 +145,4 @@
             line="7158"/>
     </issue>
 
-</issues>
+</issues>
\ No newline at end of file
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 3cbceec..a469165 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -625,6 +625,9 @@
 
     If no mode is specified, the mapping will be used for the default mode.
     If no setting is specified, the mapping will be used for the normal brightness setting.
+
+    If no mapping is defined for one of the settings, the mapping for the normal setting will be
+    used as a fallback.
     -->
     <xs:complexType name="luxToBrightnessMapping">
         <xs:element name="map" type="nonNegativeFloatToFloatMap">
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index 69a5e5c..b1349ea 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -23,8 +23,8 @@
 import android.credentials.ClearCredentialStateRequest;
 import android.credentials.CredentialProviderInfo;
 import android.credentials.IClearCredentialStateCallback;
-import android.credentials.ui.ProviderData;
-import android.credentials.ui.RequestInfo;
+import android.credentials.selection.ProviderData;
+import android.credentials.selection.RequestInfo;
 import android.os.CancellationSignal;
 import android.os.RemoteException;
 import android.service.credentials.CallingAppInfo;
@@ -50,7 +50,8 @@
             long startedTimestamp) {
         super(context, sessionCallback, lock, userId, callingUid, request, callback,
                 RequestInfo.TYPE_UNDEFINED,
-                callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp);
+                callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp,
+                /*shouldBindClientToDeath=*/ true);
     }
 
     /**
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 31409ab..3dcf42d 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -27,8 +27,8 @@
 import android.credentials.CredentialManager;
 import android.credentials.CredentialProviderInfo;
 import android.credentials.ICreateCredentialCallback;
-import android.credentials.ui.ProviderData;
-import android.credentials.ui.RequestInfo;
+import android.credentials.selection.ProviderData;
+import android.credentials.selection.RequestInfo;
 import android.os.CancellationSignal;
 import android.os.RemoteException;
 import android.service.credentials.CallingAppInfo;
@@ -63,7 +63,8 @@
             long startedTimestamp) {
         super(context, sessionCallback, lock, userId, callingUid, request, callback,
                 RequestInfo.TYPE_CREATE,
-                callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp);
+                callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp,
+                /*shouldBindClientToDeath=*/ true);
         mRequestSessionMetric.collectCreateFlowInitialMetricInfo(
                 /*origin=*/request.getOrigin() != null, request);
         mPrimaryProviders = primaryProviders;
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index dfb5a57..281fb1c 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -27,6 +27,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -63,7 +64,6 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.view.autofill.IAutoFillManagerClient;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.credentials.metrics.ApiName;
@@ -202,7 +202,7 @@
     @SuppressWarnings("GuardedBy") // ErrorProne requires service.mLock which is the same
     // this.mLock
     protected void handlePackageRemovedMultiModeLocked(String packageName, int userId) {
-        updateProvidersWhenPackageRemoved(mContext, packageName);
+        updateProvidersWhenPackageRemoved(new SettingsWrapper(mContext), packageName);
 
         List<CredentialManagerServiceImpl> services = peekServiceListForUserLocked(userId);
         if (services == null) {
@@ -484,7 +484,7 @@
         public ICancellationSignal getCandidateCredentials(
                 GetCredentialRequest request,
                 IGetCandidateCredentialsCallback callback,
-                IAutoFillManagerClient clientCallback,
+                IBinder clientBinder,
                 final String callingPackage) {
             Slog.i(TAG, "starting getCandidateCredentials with callingPackage: "
                     + callingPackage);
@@ -506,7 +506,7 @@
                             constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
                             getEnabledProvidersForUser(userId),
                             CancellationSignal.fromTransport(cancelTransport),
-                            clientCallback
+                            clientBinder
                     );
             addSessionLocked(userId, session);
 
@@ -517,6 +517,8 @@
                                     .map(CredentialOption::getType)
                                     .collect(Collectors.toList()));
 
+            finalizeAndEmitInitialPhaseMetric(session);
+
             if (providerSessions.isEmpty()) {
                 try {
                     callback.onError(
@@ -776,6 +778,13 @@
             providerSessions.forEach(ProviderSession::invokeSession);
         }
 
+        private void finalizeAndEmitInitialPhaseMetric(GetCandidateRequestSession session) {
+            var initMetric = session.mRequestSessionMetric.getInitialPhaseMetric();
+            initMetric.setAutofillSessionId(session.getAutofillSessionId());
+            initMetric.setAutofillRequestId(session.getAutofillRequestId());
+            finalizeAndEmitInitialPhaseMetric((RequestSession) session);
+        }
+
         private void finalizeAndEmitInitialPhaseMetric(RequestSession session) {
             try {
                 var initMetric = session.mRequestSessionMetric.getInitialPhaseMetric();
@@ -1126,13 +1135,14 @@
     }
 
     /** Updates the list of providers when an app is uninstalled. */
-    public static void updateProvidersWhenPackageRemoved(Context context, String packageName) {
+    public static void updateProvidersWhenPackageRemoved(
+            SettingsWrapper settingsWrapper, String packageName) {
+        Slog.i(TAG, "updateProvidersWhenPackageRemoved");
+
         // Get the current providers.
         String rawProviders =
-                Settings.Secure.getStringForUser(
-                    context.getContentResolver(),
-                    Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
-                    UserHandle.myUserId());
+                settingsWrapper.getStringForUser(
+                        Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, UserHandle.myUserId());
         if (rawProviders == null) {
             Slog.w(TAG, "settings key is null");
             return;
@@ -1140,44 +1150,44 @@
 
         // Remove any providers from the primary setting that contain the package name
         // being removed.
-        Set<String> primaryProviders =
-                getStoredProviders(rawProviders, packageName);
-        if (!Settings.Secure.putString(
-                context.getContentResolver(),
+        Set<String> primaryProviders = getStoredProviders(rawProviders, packageName);
+        if (!settingsWrapper.putStringForUser(
                 Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
-                String.join(":", primaryProviders))) {
-            Slog.w(TAG, "Failed to remove primary package: " + packageName);
+                String.join(":", primaryProviders),
+                UserHandle.myUserId(),
+                /* overrideableByRestore= */ true)) {
+            Slog.e(TAG, "Failed to remove primary package: " + packageName);
             return;
         }
 
         // Read the autofill provider so we don't accidentally erase it.
         String autofillProvider =
-                Settings.Secure.getStringForUser(
-                    context.getContentResolver(),
-                    Settings.Secure.AUTOFILL_SERVICE,
-                    UserHandle.myUserId());
+                settingsWrapper.getStringForUser(
+                        Settings.Secure.AUTOFILL_SERVICE, UserHandle.myUserId());
 
         // If there is an autofill provider and it is the placeholder indicating
         // that the currently selected primary provider does not support autofill
         // then we should wipe the setting to keep it in sync.
         if (autofillProvider != null && primaryProviders.isEmpty()) {
             if (autofillProvider.equals(AUTOFILL_PLACEHOLDER_VALUE)) {
-                if (!Settings.Secure.putString(
-                        context.getContentResolver(),
+                if (!settingsWrapper.putStringForUser(
                         Settings.Secure.AUTOFILL_SERVICE,
-                        "")) {
-                    Slog.w(TAG, "Failed to remove autofill package: " + packageName);
+                        "",
+                        UserHandle.myUserId(),
+                        /* overrideableByRestore= */ true)) {
+                    Slog.e(TAG, "Failed to remove autofill package: " + packageName);
                 }
             } else {
                 // If the existing autofill provider is from the app being removed
                 // then erase the autofill service setting.
                 ComponentName cn = ComponentName.unflattenFromString(autofillProvider);
                 if (cn != null && cn.getPackageName().equals(packageName)) {
-                   if (!Settings.Secure.putString(
-                            context.getContentResolver(),
+                    if (!settingsWrapper.putStringForUser(
                             Settings.Secure.AUTOFILL_SERVICE,
-                            "")) {
-                        Slog.w(TAG, "Failed to remove autofill package: " + packageName);
+                            "",
+                            UserHandle.myUserId(),
+                            /* overrideableByRestore= */ true)) {
+                        Slog.e(TAG, "Failed to remove autofill package: " + packageName);
                     }
                 }
             }
@@ -1185,19 +1195,17 @@
 
         // Read the credential providers to remove any reference of the removed app.
         String rawCredentialProviders =
-                Settings.Secure.getStringForUser(
-                    context.getContentResolver(),
-                    Settings.Secure.CREDENTIAL_SERVICE,
-                    UserHandle.myUserId());
+                settingsWrapper.getStringForUser(
+                        Settings.Secure.CREDENTIAL_SERVICE, UserHandle.myUserId());
 
         // Remove any providers that belong to the removed app.
-        Set<String> credentialProviders =
-                getStoredProviders(rawCredentialProviders, packageName);
-        if (!Settings.Secure.putString(
-                context.getContentResolver(),
+        Set<String> credentialProviders = getStoredProviders(rawCredentialProviders, packageName);
+        if (!settingsWrapper.putStringForUser(
                 Settings.Secure.CREDENTIAL_SERVICE,
-                String.join(":", credentialProviders))) {
-            Slog.w(TAG, "Failed to remove secondary package: " + packageName);
+                String.join(":", credentialProviders),
+                UserHandle.myUserId(),
+                /* overrideableByRestore= */ true)) {
+            Slog.e(TAG, "Failed to remove secondary package: " + packageName);
         }
     }
 
@@ -1224,4 +1232,38 @@
 
         return providers;
     }
+
+    /** A wrapper class that can be used by tests for intercepting reads/writes. */
+    public static class SettingsWrapper {
+        private final Context mContext;
+
+        public SettingsWrapper(@NonNull Context context) {
+            this.mContext = context;
+        }
+
+        ContentResolver getContentResolver() {
+            return mContext.getContentResolver();
+        }
+
+        /** Retrieves the string value of a system setting */
+        public String getStringForUser(String name, int userHandle) {
+            return Settings.Secure.getStringForUser(getContentResolver(), name, userHandle);
+        }
+
+        /** Updates the string value of a system setting */
+        public boolean putStringForUser(
+                String name,
+                String value,
+                int userHandle,
+                boolean overrideableByRestore) {
+            return Settings.Secure.putStringForUser(
+                    getContentResolver(),
+                    name,
+                    value,
+                    null,
+                    false,
+                    userHandle,
+                    overrideableByRestore);
+        }
+    }
 }
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index f092dcc..4203576 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -22,11 +22,11 @@
 import android.content.Intent;
 import android.credentials.CredentialManager;
 import android.credentials.CredentialProviderInfo;
-import android.credentials.ui.DisabledProviderData;
-import android.credentials.ui.IntentFactory;
-import android.credentials.ui.ProviderData;
-import android.credentials.ui.RequestInfo;
-import android.credentials.ui.UserSelectionDialogResult;
+import android.credentials.selection.DisabledProviderData;
+import android.credentials.selection.IntentFactory;
+import android.credentials.selection.ProviderData;
+import android.credentials.selection.RequestInfo;
+import android.credentials.selection.UserSelectionDialogResult;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index d165171..7e709fe 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -26,15 +26,15 @@
 import android.credentials.GetCredentialRequest;
 import android.credentials.GetCredentialResponse;
 import android.credentials.IGetCandidateCredentialsCallback;
-import android.credentials.ui.GetCredentialProviderData;
-import android.credentials.ui.ProviderData;
-import android.credentials.ui.RequestInfo;
+import android.credentials.selection.GetCredentialProviderData;
+import android.credentials.selection.ProviderData;
+import android.credentials.selection.RequestInfo;
 import android.os.CancellationSignal;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.service.credentials.CallingAppInfo;
 import android.service.credentials.PermissionUtils;
 import android.util.Slog;
-import android.view.autofill.IAutoFillManagerClient;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -49,7 +49,12 @@
         implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
     private static final String TAG = "GetCandidateRequestSession";
 
-    private final IAutoFillManagerClient mAutoFillCallback;
+    private static final String SESSION_ID_KEY = "autofill_session_id";
+    private static final String REQUEST_ID_KEY = "autofill_request_id";
+
+    private final IBinder mClientBinder;
+    private final int mAutofillSessionId;
+    private final int mAutofillRequestId;
 
     public GetCandidateRequestSession(
             Context context, SessionLifetime sessionCallback,
@@ -57,11 +62,16 @@
             IGetCandidateCredentialsCallback callback, GetCredentialRequest request,
             CallingAppInfo callingAppInfo, Set<ComponentName> enabledProviders,
             CancellationSignal cancellationSignal,
-            IAutoFillManagerClient autoFillCallback) {
+            IBinder clientBinder) {
         super(context, sessionCallback, lock, userId, callingUid, request, callback,
                 RequestInfo.TYPE_GET, callingAppInfo, enabledProviders,
-                cancellationSignal, 0L);
-        mAutoFillCallback = autoFillCallback;
+                cancellationSignal, 0L, /*shouldBindClientToDeath=*/ false);
+        mClientBinder = clientBinder;
+        mAutofillSessionId = request.getData().getInt(SESSION_ID_KEY, -1);
+        mAutofillRequestId = request.getData().getInt(REQUEST_ID_KEY, -1);
+        if (mClientBinder != null) {
+            setUpClientCallbackListener(mClientBinder);
+        }
     }
 
     /**
@@ -137,17 +147,27 @@
     @Override
     public void onFinalErrorReceived(ComponentName componentName, String errorType,
             String message) {
-        // Not applicable for session without UI
+        respondToClientWithErrorAndFinish(errorType, message);
     }
 
     @Override
     public void onUiCancellation(boolean isUserCancellation) {
-        // Not applicable for session without UI
+        String exception = GetCandidateCredentialsException.TYPE_USER_CANCELED;
+        String message = "User cancelled the selector";
+        if (!isUserCancellation) {
+            exception = GetCandidateCredentialsException.TYPE_INTERRUPTED;
+            message = "The UI was interrupted - please try again.";
+        }
+        mRequestSessionMetric.collectFrameworkException(exception);
+        respondToClientWithErrorAndFinish(exception, message);
     }
 
     @Override
     public void onUiSelectorInvocationFailure() {
-        // Not applicable for session without UI
+        String exception = GetCandidateCredentialsException.TYPE_NO_CREDENTIAL;
+        mRequestSessionMetric.collectFrameworkException(exception);
+        respondToClientWithErrorAndFinish(exception,
+                "No credentials available.");
     }
 
     @Override
@@ -177,4 +197,19 @@
         Slog.d(TAG, "onFinalResponseReceived");
         respondToClientWithResponseAndFinish(new GetCandidateCredentialsResponse(response));
     }
+
+    /**
+     * Returns autofill session id. Returns -1 if unavailable.
+     */
+    public int getAutofillSessionId() {
+        return mAutofillSessionId;
+    }
+
+    /**
+     * Returns autofill request id. Returns -1 if unavailable.
+     */
+    public int getAutofillRequestId() {
+        return mAutofillRequestId;
+
+    }
 }
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 3f57c80..b33f531 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -26,8 +26,8 @@
 import android.credentials.GetCredentialRequest;
 import android.credentials.GetCredentialResponse;
 import android.credentials.IGetCredentialCallback;
-import android.credentials.ui.ProviderData;
-import android.credentials.ui.RequestInfo;
+import android.credentials.selection.ProviderData;
+import android.credentials.selection.RequestInfo;
 import android.os.Binder;
 import android.os.CancellationSignal;
 import android.os.RemoteException;
@@ -57,7 +57,7 @@
             long startedTimestamp) {
         super(context, sessionCallback, lock, userId, callingUid, request, callback,
                 getRequestInfoFromRequest(request), callingAppInfo, enabledProviders,
-                cancellationSignal, startedTimestamp);
+                cancellationSignal, startedTimestamp, /*shouldBindClientToDeath=*/ true);
         mRequestSessionMetric.collectGetFlowInitialMetricInfo(request);
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index b36de0b..23aa374 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -426,7 +426,11 @@
                     /* per_classtype_counts */
                     initialPhaseMetric.getUniqueRequestCounts(),
                     /* origin_specified */
-                    initialPhaseMetric.isOriginSpecified()
+                    initialPhaseMetric.isOriginSpecified(),
+                    /* autofill_session_id */
+                    initialPhaseMetric.getAutofillSessionId(),
+                    /* autofill_request_id */
+                    initialPhaseMetric.getAutofillRequestId()
             );
         } catch (Exception e) {
             Slog.w(TAG, "Unexpected error during initial metric emit: " + e);
diff --git a/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
index efb394d..21ac9e4 100644
--- a/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
+++ b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
@@ -22,7 +22,7 @@
 import android.credentials.CreateCredentialResponse;
 import android.credentials.GetCredentialException;
 import android.credentials.GetCredentialResponse;
-import android.credentials.ui.ProviderPendingIntentResponse;
+import android.credentials.selection.ProviderPendingIntentResponse;
 import android.service.credentials.BeginGetCredentialResponse;
 import android.service.credentials.CredentialProviderService;
 
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index fbfc9ca..30af567 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -26,9 +26,9 @@
 import android.credentials.IGetCredentialCallback;
 import android.credentials.IPrepareGetCredentialCallback;
 import android.credentials.PrepareGetCredentialResponseInternal;
-import android.credentials.ui.GetCredentialProviderData;
-import android.credentials.ui.ProviderData;
-import android.credentials.ui.RequestInfo;
+import android.credentials.selection.GetCredentialProviderData;
+import android.credentials.selection.ProviderData;
+import android.credentials.selection.RequestInfo;
 import android.os.CancellationSignal;
 import android.os.RemoteException;
 import android.service.credentials.CallingAppInfo;
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index d4b8800..6a1b1db7 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -21,8 +21,8 @@
 import android.content.Context;
 import android.credentials.ClearCredentialStateException;
 import android.credentials.CredentialProviderInfo;
-import android.credentials.ui.ProviderData;
-import android.credentials.ui.ProviderPendingIntentResponse;
+import android.credentials.selection.ProviderData;
+import android.credentials.selection.ProviderPendingIntentResponse;
 import android.os.ICancellationSignal;
 import android.service.credentials.CallingAppInfo;
 import android.service.credentials.ClearCredentialStateRequest;
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 6f79852..6361aeb 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -25,9 +25,9 @@
 import android.credentials.CreateCredentialException;
 import android.credentials.CreateCredentialResponse;
 import android.credentials.CredentialProviderInfo;
-import android.credentials.ui.CreateCredentialProviderData;
-import android.credentials.ui.Entry;
-import android.credentials.ui.ProviderPendingIntentResponse;
+import android.credentials.selection.CreateCredentialProviderData;
+import android.credentials.selection.Entry;
+import android.credentials.selection.ProviderPendingIntentResponse;
 import android.os.Bundle;
 import android.os.ICancellationSignal;
 import android.service.credentials.BeginCreateCredentialRequest;
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 7bd1cc4..fcaef9f 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -26,10 +26,10 @@
 import android.credentials.CredentialProviderInfo;
 import android.credentials.GetCredentialException;
 import android.credentials.GetCredentialResponse;
-import android.credentials.ui.AuthenticationEntry;
-import android.credentials.ui.Entry;
-import android.credentials.ui.GetCredentialProviderData;
-import android.credentials.ui.ProviderPendingIntentResponse;
+import android.credentials.selection.AuthenticationEntry;
+import android.credentials.selection.Entry;
+import android.credentials.selection.GetCredentialProviderData;
+import android.credentials.selection.ProviderPendingIntentResponse;
 import android.os.ICancellationSignal;
 import android.service.autofill.Flags;
 import android.service.credentials.Action;
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index bafa4a5..f162916 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -25,10 +25,10 @@
 import android.credentials.CredentialOption;
 import android.credentials.GetCredentialException;
 import android.credentials.GetCredentialResponse;
-import android.credentials.ui.Entry;
-import android.credentials.ui.GetCredentialProviderData;
-import android.credentials.ui.ProviderData;
-import android.credentials.ui.ProviderPendingIntentResponse;
+import android.credentials.selection.Entry;
+import android.credentials.selection.GetCredentialProviderData;
+import android.credentials.selection.ProviderData;
+import android.credentials.selection.ProviderPendingIntentResponse;
 import android.os.ICancellationSignal;
 import android.service.credentials.CallingAppInfo;
 import android.service.credentials.CredentialEntry;
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index f2055d0..c16e232 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -25,8 +25,8 @@
 import android.content.pm.PackageManager;
 import android.credentials.Credential;
 import android.credentials.CredentialProviderInfo;
-import android.credentials.ui.ProviderData;
-import android.credentials.ui.ProviderPendingIntentResponse;
+import android.credentials.selection.ProviderData;
+import android.credentials.selection.ProviderPendingIntentResponse;
 import android.os.ICancellationSignal;
 import android.os.RemoteException;
 import android.util.Slog;
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index da44aac..bf7df86 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -24,8 +24,8 @@
 import android.content.Intent;
 import android.credentials.CredentialProviderInfo;
 import android.credentials.flags.Flags;
-import android.credentials.ui.ProviderData;
-import android.credentials.ui.UserSelectionDialogResult;
+import android.credentials.selection.ProviderData;
+import android.credentials.selection.UserSelectionDialogResult;
 import android.os.Binder;
 import android.os.CancellationSignal;
 import android.os.Handler;
@@ -122,7 +122,8 @@
             @NonNull String requestType,
             CallingAppInfo callingAppInfo,
             Set<ComponentName> enabledProviders,
-            CancellationSignal cancellationSignal, long timestampStarted) {
+            CancellationSignal cancellationSignal, long timestampStarted,
+            boolean shouldBindClientToDeath) {
         mContext = context;
         mLock = lock;
         mSessionCallback = sessionCallback;
@@ -146,16 +147,18 @@
         mRequestSessionMetric.collectInitialPhaseMetricInfo(timestampStarted,
                 mCallingUid, ApiName.getMetricCodeFromRequestInfo(mRequestType));
         setCancellationListener();
-        if (Flags.clearSessionEnabled()) {
-            setUpClientCallbackListener();
+        if (shouldBindClientToDeath && Flags.clearSessionEnabled()) {
+            if (mClientCallback != null && mClientCallback instanceof IInterface) {
+                setUpClientCallbackListener(((IInterface) mClientCallback).asBinder());
+            }
         }
     }
 
-    private void setUpClientCallbackListener() {
+    protected void setUpClientCallbackListener(IBinder clientBinder) {
         if (mClientCallback != null && mClientCallback instanceof IInterface) {
             IInterface callback = (IInterface) mClientCallback;
             try {
-                callback.asBinder().linkToDeath(mDeathRecipient, 0);
+                clientBinder.linkToDeath(mDeathRecipient, 0);
             } catch (RemoteException e) {
                 Slog.e(TAG, e.getMessage());
             }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ApiName.java b/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
index d828349..23db11f 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
@@ -16,10 +16,10 @@
 
 package com.android.server.credentials.metrics;
 
-import static android.credentials.ui.RequestInfo.TYPE_CREATE;
-import static android.credentials.ui.RequestInfo.TYPE_GET;
-import static android.credentials.ui.RequestInfo.TYPE_GET_VIA_REGISTRY;
-import static android.credentials.ui.RequestInfo.TYPE_UNDEFINED;
+import static android.credentials.selection.RequestInfo.TYPE_CREATE;
+import static android.credentials.selection.RequestInfo.TYPE_GET;
+import static android.credentials.selection.RequestInfo.TYPE_GET_VIA_REGISTRY;
+import static android.credentials.selection.RequestInfo.TYPE_UNDEFINED;
 
 import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_CLEAR_CREDENTIAL;
 import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_CREATE_CREDENTIAL;
@@ -32,7 +32,7 @@
 import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_UNKNOWN;
 import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_UNREGISTER_CREDENTIAL_DESCRIPTION;
 
-import android.credentials.ui.RequestInfo;
+import android.credentials.selection.RequestInfo;
 import android.util.Slog;
 
 import java.util.AbstractMap;
diff --git a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
index 8e965e3..8a4e86c 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
@@ -49,6 +49,12 @@
     // Stores the deduped request information, particularly {"req":5}
     private Map<String, Integer> mRequestCounts = new LinkedHashMap<>();
 
+    // The session id of autofill if the request is from autofill, defaults to -1
+    private int mAutofillSessionId = -1;
+
+    // The request id of autofill if the request is from autofill, defaults to -1
+    private int mAutofillRequestId = -1;
+
 
     public InitialPhaseMetric(int sessionIdTrackOne) {
         mSessionIdCaller = sessionIdTrackOne;
@@ -126,6 +132,24 @@
         return mOriginSpecified;
     }
 
+    /* ------ Autofill Integration ------ */
+
+    public void setAutofillSessionId(int autofillSessionId) {
+        mAutofillSessionId = autofillSessionId;
+    }
+
+    public int getAutofillSessionId() {
+        return mAutofillSessionId;
+    }
+
+    public void setAutofillRequestId(int autofillRequestId) {
+        mAutofillRequestId = autofillRequestId;
+    }
+
+    public int getAutofillRequestId() {
+        return mAutofillRequestId;
+    }
+
     /* ------ Unique Request Counts Map Information ------ */
 
     public void setRequestCounts(Map<String, Integer> requestCounts) {
diff --git a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
index 83b57c4..8adcfbc 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
@@ -33,7 +33,7 @@
 import android.content.ComponentName;
 import android.credentials.CreateCredentialRequest;
 import android.credentials.GetCredentialRequest;
-import android.credentials.ui.UserSelectionDialogResult;
+import android.credentials.selection.UserSelectionDialogResult;
 import android.util.Slog;
 
 import com.android.server.credentials.MetricUtilities;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f288103..519c9bb 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -71,6 +71,7 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_TIME;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER;
@@ -484,6 +485,7 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.net.module.util.ProxyUtils;
+import com.android.net.thread.flags.Flags;
 import com.android.server.AlarmManagerInternal;
 import com.android.server.LocalManagerRegistry;
 import com.android.server.LocalServices;
@@ -13339,6 +13341,11 @@
                 UserManager.DISALLOW_SMS, new String[]{MANAGE_DEVICE_POLICY_SMS});
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, new String[]{MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS});
+        if (Flags.threadUserRestrictionEnabled()) {
+            USER_RESTRICTION_PERMISSIONS.put(
+                    UserManager.DISALLOW_THREAD_NETWORK,
+                    new String[]{MANAGE_DEVICE_POLICY_THREAD_NETWORK});
+        }
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION});
         USER_RESTRICTION_PERMISSIONS.put(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
index 532823a..e8c5658 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
@@ -17,6 +17,7 @@
 package com.android.server.devicepolicy;
 
 import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
+import static android.app.admin.flags.Flags.defaultSmsPersonalAppSuspensionFixEnabled;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.Nullable;
@@ -42,6 +43,7 @@
 import android.view.inputmethod.InputMethodInfo;
 
 import com.android.internal.R;
+import com.android.internal.telephony.SmsApplication;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.utils.Slogf;
 
@@ -97,7 +99,7 @@
         result.removeAll(getSystemLauncherPackages());
         result.removeAll(getAccessibilityServices());
         result.removeAll(getInputMethodPackages());
-        result.remove(Telephony.Sms.getDefaultSmsPackage(mContext));
+        result.remove(getDefaultSmsPackage());
         result.remove(getSettingsPackageName());
 
         final String[] unsuspendablePackages =
@@ -202,6 +204,17 @@
         return resolveInfos != null && !resolveInfos.isEmpty();
     }
 
+    private String getDefaultSmsPackage() {
+        //TODO(b/319449037): Unflag the following change.
+        if (defaultSmsPersonalAppSuspensionFixEnabled()) {
+            return SmsApplication.getDefaultSmsApplicationAsUser(
+                            mContext, /*updateIfNeeded=*/ false, mContext.getUser())
+                    .getPackageName();
+        } else {
+            return Telephony.Sms.getDefaultSmsPackage(mContext);
+        }
+    }
+
 
     void dump(IndentingPrintWriter pw) {
         pw.println("PersonalAppsSuspensionHelper");
@@ -212,7 +225,7 @@
         DevicePolicyManagerService.dumpApps(pw, "accessibility services",
                 getAccessibilityServices());
         DevicePolicyManagerService.dumpApps(pw, "input method packages", getInputMethodPackages());
-        pw.printf("SMS package: %s\n", Telephony.Sms.getDefaultSmsPackage(mContext));
+        pw.printf("SMS package: %s\n", getDefaultSmsPackage());
         pw.printf("Settings package: %s\n", getSettingsPackageName());
         DevicePolicyManagerService.dumpApps(pw, "Packages subject to suspension",
                 getPersonalAppsForSuspension());
diff --git a/services/foldables/devicestateprovider/proguard.flags b/services/foldables/devicestateprovider/proguard.flags
index 069cbc6..b810cad 100644
--- a/services/foldables/devicestateprovider/proguard.flags
+++ b/services/foldables/devicestateprovider/proguard.flags
@@ -1 +1 @@
--keep,allowoptimization,allowaccessmodification class com.android.server.policy.TentModeDeviceStatePolicy { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.policy.BookStyleDeviceStatePolicy { *; }
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
new file mode 100644
index 0000000..d5a3cff
--- /dev/null
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import static android.hardware.SensorManager.SENSOR_DELAY_NORMAL;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.server.policy.BookStylePreferredScreenCalculator.PreferredScreen.OUTER;
+import static com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle.ANGLE_0;
+import static com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle.ANGLE_0_TO_45;
+import static com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle.ANGLE_45_TO_90;
+import static com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle.ANGLE_90_TO_180;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.util.ArraySet;
+import android.view.Display;
+import android.view.Surface;
+
+import com.android.server.policy.BookStylePreferredScreenCalculator.PreferredScreen;
+import com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle;
+import com.android.server.policy.BookStylePreferredScreenCalculator.StateTransition;
+import com.android.server.policy.BookStyleClosedStatePredicate.ConditionSensorListener.SensorSubscription;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+/**
+ * 'Closed' state predicate that takes into account the posture of the device
+ * It accepts list of state transitions that control how the device moves between
+ * device states.
+ * See {@link BookStyleStateTransitions} for detailed description of the default behavior.
+ */
+public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceStateProvider>,
+        DisplayManager.DisplayListener {
+
+    private final BookStylePreferredScreenCalculator mClosedStateCalculator;
+    private final Handler mHandler = new Handler();
+    private final PostureEstimator mPostureEstimator;
+    private final DisplayManager mDisplayManager;
+
+    /**
+     * Creates {@link BookStyleClosedStatePredicate}. It is expected that the device has a pair
+     * of accelerometer sensors (one for each movable part of the device), see parameter
+     * descriptions for the behaviour when these sensors are not available.
+     * @param context context that could be used to get system services
+     * @param updatesListener callback that will be executed whenever the predicate should be
+     *                        checked again
+     * @param leftAccelerometerSensor accelerometer sensor that is located in the half of the
+     *                                device that has the outer screen, in case if this sensor is
+     *                                not provided, tent/wedge mode will be detected only using
+     *                                orientation sensor and screen rotation, so this mode won't
+     *                                be accessible by putting the device on a flat surface
+     * @param rightAccelerometerSensor accelerometer sensor that is located on the opposite side
+     *                                 across the hinge from the previous accelerometer sensor,
+     *                                 in case if this sensor is not provided, reverse wedge mode
+     *                                 won't be detected, so the device will use closed state using
+     *                                 constant angle when folding
+     * @param stateTransitions definition of all possible state transitions, see
+     *                         {@link BookStyleStateTransitions} for sample and more details
+     */
+
+    public BookStyleClosedStatePredicate(@NonNull Context context,
+            @NonNull ClosedStateUpdatesListener updatesListener,
+            @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
+            @NonNull List<StateTransition> stateTransitions) {
+        mDisplayManager = context.getSystemService(DisplayManager.class);
+        mDisplayManager.registerDisplayListener(this, mHandler);
+
+        mClosedStateCalculator = new BookStylePreferredScreenCalculator(stateTransitions);
+
+        final SensorManager sensorManager = context.getSystemService(SensorManager.class);
+        final Sensor orientationSensor = sensorManager.getDefaultSensor(
+                Sensor.TYPE_DEVICE_ORIENTATION);
+
+        mPostureEstimator = new PostureEstimator(mHandler, sensorManager,
+                leftAccelerometerSensor, rightAccelerometerSensor, orientationSensor,
+                updatesListener::onClosedStateUpdated);
+    }
+
+    /**
+     * Based on the current sensor readings and current state, returns true if the device should use
+     * 'CLOSED' device state and false if it should not use 'CLOSED' state (e.g. could use half-open
+     * or open states).
+     */
+    @Override
+    public boolean test(FoldableDeviceStateProvider foldableDeviceStateProvider) {
+        final HingeAngle hingeAngle = hingeAngleFromFloat(
+                foldableDeviceStateProvider.getHingeAngle());
+
+        mPostureEstimator.onDeviceClosedStatusChanged(hingeAngle == ANGLE_0);
+
+        final PreferredScreen preferredScreen = mClosedStateCalculator.
+                calculatePreferredScreen(hingeAngle, mPostureEstimator.isLikelyTentOrWedgeMode(),
+                        mPostureEstimator.isLikelyReverseWedgeMode(hingeAngle));
+
+        return preferredScreen == OUTER;
+    }
+
+    private HingeAngle hingeAngleFromFloat(float hingeAngle) {
+        if (hingeAngle == 0f) {
+            return ANGLE_0;
+        } else if (hingeAngle < 45f) {
+            return ANGLE_0_TO_45;
+        } else if (hingeAngle < 90f) {
+            return ANGLE_45_TO_90;
+        } else {
+            return ANGLE_90_TO_180;
+        }
+    }
+
+    @Override
+    public void onDisplayChanged(int displayId) {
+        if (displayId == DEFAULT_DISPLAY) {
+            final Display display = mDisplayManager.getDisplay(displayId);
+            int displayState = display.getState();
+            boolean isDisplayOn = displayState == Display.STATE_ON;
+            mPostureEstimator.onDisplayPowerStatusChanged(isDisplayOn);
+            mPostureEstimator.onDisplayRotationChanged(display.getRotation());
+        }
+    }
+
+    @Override
+    public void onDisplayAdded(int displayId) {
+
+    }
+
+    @Override
+    public void onDisplayRemoved(int displayId) {
+
+    }
+
+    public interface ClosedStateUpdatesListener {
+        void onClosedStateUpdated();
+    }
+
+    /**
+     * Estimates if the device is going to enter wedge/tent mode based on the sensor data
+     */
+    private static class PostureEstimator implements SensorEventListener {
+
+
+        private static final int FLAT_INCLINATION_THRESHOLD_DEGREES = 8;
+
+        /**
+         * Alpha parameter of the accelerometer low pass filter: the lower the value, the less high
+         * frequency noise it filter but reduces the latency.
+         */
+        private static final float GRAVITY_VECTOR_LOW_PASS_ALPHA_VALUE = 0.8f;
+
+
+        @Nullable
+        private final Sensor mLeftAccelerometerSensor;
+        @Nullable
+        private final Sensor mRightAccelerometerSensor;
+        private final Sensor mOrientationSensor;
+        private final Runnable mOnSensorUpdatedListener;
+
+        private final ConditionSensorListener mConditionedSensorListener;
+
+        @Nullable
+        private float[] mRightGravityVector;
+
+        @Nullable
+        private float[] mLeftGravityVector;
+
+        @Nullable
+        private Integer mLastScreenRotation;
+
+        @Nullable
+        private SensorEvent mLastDeviceOrientationSensorEvent = null;
+
+        private boolean mScreenTurnedOn = false;
+        private boolean mDeviceClosed = false;
+
+        public PostureEstimator(Handler handler, SensorManager sensorManager,
+                @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
+                Sensor orientationSensor, Runnable onSensorUpdated) {
+            mLeftAccelerometerSensor = leftAccelerometerSensor;
+            mRightAccelerometerSensor = rightAccelerometerSensor;
+            mOrientationSensor = orientationSensor;
+
+            mOnSensorUpdatedListener = onSensorUpdated;
+
+            final List<SensorSubscription> sensorSubscriptions = new ArrayList<>();
+            if (mLeftAccelerometerSensor != null) {
+                sensorSubscriptions.add(new SensorSubscription(
+                        mLeftAccelerometerSensor,
+                        /* allowedToListen= */ () -> mScreenTurnedOn && !mDeviceClosed,
+                        /* cleanup= */ () -> mLeftGravityVector = null));
+            }
+
+            if (mRightAccelerometerSensor != null) {
+                sensorSubscriptions.add(new SensorSubscription(
+                        mRightAccelerometerSensor,
+                        /* allowedToListen= */ () -> mScreenTurnedOn,
+                        /* cleanup= */ () -> mRightGravityVector = null));
+            }
+
+            sensorSubscriptions.add(new SensorSubscription(mOrientationSensor,
+                    /* allowedToListen= */ () -> mScreenTurnedOn,
+                    /* cleanup= */ () -> mLastDeviceOrientationSensorEvent = null));
+
+            mConditionedSensorListener = new ConditionSensorListener(sensorManager, this, handler,
+                    sensorSubscriptions);
+        }
+
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+            if (event.sensor == mRightAccelerometerSensor) {
+                if (mRightGravityVector == null) {
+                    mRightGravityVector = new float[3];
+                }
+                setNewValueWithHighPassFilter(mRightGravityVector, event.values);
+
+                final boolean isRightMostlyFlat = Objects.equals(
+                        isGravityVectorMostlyFlat(mRightGravityVector), Boolean.TRUE);
+
+                if (isRightMostlyFlat) {
+                    // Reset orientation sensor when the device becomes flat
+                    mLastDeviceOrientationSensorEvent = null;
+                }
+            } else if (event.sensor == mLeftAccelerometerSensor) {
+                if (mLeftGravityVector == null) {
+                    mLeftGravityVector = new float[3];
+                }
+                setNewValueWithHighPassFilter(mLeftGravityVector, event.values);
+            } else if (event.sensor == mOrientationSensor) {
+                mLastDeviceOrientationSensorEvent = event;
+            }
+
+            mOnSensorUpdatedListener.run();
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+
+        }
+
+        private void setNewValueWithHighPassFilter(float[] output, float[] newValues) {
+            final float alpha = GRAVITY_VECTOR_LOW_PASS_ALPHA_VALUE;
+            output[0] = alpha * output[0] + (1 - alpha) * newValues[0];
+            output[1] = alpha * output[1] + (1 - alpha) * newValues[1];
+            output[2] = alpha * output[2] + (1 - alpha) * newValues[2];
+        }
+
+        /**
+         * Returns true if the phone likely in reverse wedge mode (when a foldable phone is lying
+         * on the outer screen mostly flat to the ground)
+         */
+        public boolean isLikelyReverseWedgeMode(HingeAngle hingeAngle) {
+            return hingeAngle != ANGLE_0 && Objects.equals(
+                    isGravityVectorMostlyFlat(mLeftGravityVector), Boolean.TRUE);
+        }
+
+        /**
+         * Returns true if the phone is likely in tent or wedge mode when unfolding. Tent mode
+         * is detected by checking if the phone is in seascape position, screen is rotated to
+         * landscape or seascape, or if the right side of the device is mostly flat.
+         */
+        public boolean isLikelyTentOrWedgeMode() {
+            boolean isScreenLandscapeOrSeascape = Objects.equals(mLastScreenRotation,
+                    Surface.ROTATION_270) || Objects.equals(mLastScreenRotation,
+                    Surface.ROTATION_90);
+            if (isScreenLandscapeOrSeascape) {
+                return true;
+            }
+
+            boolean isRightMostlyFlat = Objects.equals(
+                    isGravityVectorMostlyFlat(mRightGravityVector), Boolean.TRUE);
+            if (isRightMostlyFlat) {
+                return true;
+            }
+
+            boolean isSensorSeaScape = Objects.equals(getOrientationSensorRotation(),
+                    Surface.ROTATION_270);
+            if (isSensorSeaScape) {
+                return true;
+            }
+
+            return false;
+        }
+
+        /**
+         * Returns true if the passed gravity vector implies that the phone is mostly flat (the
+         * vector is close to be perpendicular to the ground and has a positive Z component).
+         * Returns null if there is no data from the sensor.
+         */
+        private Boolean isGravityVectorMostlyFlat(@Nullable float[] vector) {
+            if (vector == null) return null;
+            if (vector[0] == 0.0f && vector[1] == 0.0f && vector[2] == 0.0f) {
+                // Likely we haven't received the actual data yet, treat it as no data
+                return null;
+            }
+
+            double vectorMagnitude = Math.sqrt(
+                    vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]);
+            float normalizedGravityZ = (float) (vector[2] / vectorMagnitude);
+
+            final int inclination = (int) Math.round(Math.toDegrees(Math.acos(normalizedGravityZ)));
+            return inclination < FLAT_INCLINATION_THRESHOLD_DEGREES;
+        }
+
+        private Integer getOrientationSensorRotation() {
+            if (mLastDeviceOrientationSensorEvent == null) return null;
+            return (int) mLastDeviceOrientationSensorEvent.values[0];
+        }
+
+        /**
+         * Called whenever display status changes, we use this signal to start/stop listening
+         * to sensors when the display is off to save battery. Using display state instead of
+         * general power state to reduce the time when sensors are on, we don't need to listen
+         * to the extra sensors when the screen is off.
+         */
+        public void onDisplayPowerStatusChanged(boolean screenTurnedOn) {
+            mScreenTurnedOn = screenTurnedOn;
+            mConditionedSensorListener.updateListeningState();
+        }
+
+        /**
+         * Called whenever we display rotation might have been updated
+         * @param rotation new rotation
+         */
+        public void onDisplayRotationChanged(int rotation) {
+            mLastScreenRotation = rotation;
+        }
+
+        /**
+         * Called whenever foldable device becomes fully closed or opened
+         */
+        public void onDeviceClosedStatusChanged(boolean deviceClosed) {
+            mDeviceClosed = deviceClosed;
+            mConditionedSensorListener.updateListeningState();
+        }
+    }
+
+    /**
+     * Helper class that subscribes or unsubscribes from a sensor based on a condition specified
+     * in {@link SensorSubscription}
+     */
+    static class ConditionSensorListener {
+        private final List<SensorSubscription> mSensorSubscriptions;
+        private final ArraySet<Sensor> mIsListening = new ArraySet<>();
+
+        private final SensorManager mSensorManager;
+        private final SensorEventListener mSensorEventListener;
+
+        private final Handler mHandler;
+
+        public ConditionSensorListener(SensorManager sensorManager,
+                SensorEventListener sensorEventListener, Handler handler,
+                List<SensorSubscription> sensorSubscriptions) {
+            mSensorManager = sensorManager;
+            mSensorEventListener = sensorEventListener;
+            mSensorSubscriptions = sensorSubscriptions;
+            mHandler = handler;
+        }
+
+        /**
+         * Updates current listening state of the sensor based on the provided conditions
+         */
+        public void updateListeningState() {
+            for (int i = 0; i < mSensorSubscriptions.size(); i++) {
+                final SensorSubscription subscription = mSensorSubscriptions.get(i);
+                final Sensor sensor = subscription.mSensor;
+
+                final boolean shouldBeListening = subscription.mAllowedToListenSupplier.get();
+                final boolean isListening = mIsListening.contains(sensor);
+                final boolean shouldUpdateListening = isListening != shouldBeListening;
+
+                if (shouldUpdateListening) {
+                    if (shouldBeListening) {
+                        mIsListening.add(sensor);
+                        mSensorManager.registerListener(mSensorEventListener, sensor,
+                                SENSOR_DELAY_NORMAL, mHandler);
+                    } else {
+                        mIsListening.remove(sensor);
+                        mSensorManager.unregisterListener(mSensorEventListener, sensor);
+                        subscription.mOnUnsubscribe.run();
+                    }
+                }
+            }
+        }
+
+        /**
+         * Represents a configuration of a single sensor subscription
+         */
+        public static class SensorSubscription {
+            private final Sensor mSensor;
+            private final Supplier<Boolean> mAllowedToListenSupplier;
+            private final Runnable mOnUnsubscribe;
+
+            /**
+             * @param sensor sensor to listen to
+             * @param allowedToListen return true when it is allowed to listen to the sensor
+             * @param cleanup a runnable that will be closed just before unsubscribing from the
+             *                sensor
+             */
+
+            public SensorSubscription(Sensor sensor, Supplier<Boolean> allowedToListen,
+                    Runnable cleanup) {
+                mSensor = sensor;
+                mAllowedToListenSupplier = allowedToListen;
+                mOnUnsubscribe = cleanup;
+            }
+        }
+    }
+}
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
new file mode 100644
index 0000000..ad938af
--- /dev/null
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
+import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
+import static com.android.server.devicestate.DeviceState.FLAG_EMULATED_ONLY;
+import static com.android.server.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
+import static com.android.server.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
+import static com.android.server.policy.BookStyleStateTransitions.DEFAULT_STATE_TRANSITIONS;
+import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createConfig;
+import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createTentModeClosedState;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManager;
+
+import com.android.server.devicestate.DeviceStatePolicy;
+import com.android.server.devicestate.DeviceStateProvider;
+import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration;
+import com.android.server.policy.feature.flags.FeatureFlags;
+import com.android.server.policy.feature.flags.FeatureFlagsImpl;
+
+import java.util.function.Predicate;
+
+/**
+ * Device state policy for a foldable device with two screens in a book style, where the hinge is
+ * located on the left side of the device when in folded posture.
+ * The policy supports tent/wedge mode: a mode when the device keeps the outer display on
+ * until reaching certain conditions like hinge angle threshold.
+ *
+ * Contains configuration for {@link FoldableDeviceStateProvider}.
+ */
+public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements
+        BookStyleClosedStatePredicate.ClosedStateUpdatesListener {
+
+    private static final int DEVICE_STATE_CLOSED = 0;
+    private static final int DEVICE_STATE_HALF_OPENED = 1;
+    private static final int DEVICE_STATE_OPENED = 2;
+    private static final int DEVICE_STATE_REAR_DISPLAY_STATE = 3;
+    private static final int DEVICE_STATE_CONCURRENT_INNER_DEFAULT = 4;
+
+    private static final int TENT_MODE_SWITCH_ANGLE_DEGREES = 90;
+    private static final int TABLE_TOP_MODE_SWITCH_ANGLE_DEGREES = 125;
+    private static final int MIN_CLOSED_ANGLE_DEGREES = 0;
+    private static final int MAX_CLOSED_ANGLE_DEGREES = 5;
+
+    private final FoldableDeviceStateProvider mProvider;
+
+    private final boolean mIsDualDisplayBlockingEnabled;
+    private final boolean mEnablePostureBasedClosedState;
+    private static final Predicate<FoldableDeviceStateProvider> ALLOWED = p -> true;
+    private static final Predicate<FoldableDeviceStateProvider> NOT_ALLOWED = p -> false;
+
+    /**
+     * Creates TentModeDeviceStatePolicy
+     *
+     * @param context           Android context
+     * @param hingeAngleSensor  hinge angle sensor that will be used to switch between states
+     * @param hallSensor        hall sensor that will be used to switch between states
+     * @param closeAngleDegrees if non-zero, this angle will be used as a threshold to switch
+     *                          between folded and unfolded modes, otherwise when folding the
+     *                          display switch will happen at 0 degrees
+     */
+    public BookStyleDeviceStatePolicy(@NonNull FeatureFlags featureFlags, @NonNull Context context,
+            @NonNull Sensor hingeAngleSensor, @NonNull Sensor hallSensor,
+            @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
+            Integer closeAngleDegrees) {
+        super(context);
+
+        final SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
+        final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+
+        mEnablePostureBasedClosedState = featureFlags.enableFoldablesPostureBasedClosedState();
+        mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking();
+
+        final DeviceStateConfiguration[] configuration = createConfiguration(
+                leftAccelerometerSensor, rightAccelerometerSensor, closeAngleDegrees);
+
+        mProvider = new FoldableDeviceStateProvider(mContext, sensorManager,
+                hingeAngleSensor, hallSensor, displayManager, configuration);
+    }
+
+    private DeviceStateConfiguration[] createConfiguration(@Nullable Sensor leftAccelerometerSensor,
+            @Nullable Sensor rightAccelerometerSensor, Integer closeAngleDegrees) {
+        return new DeviceStateConfiguration[]{
+                createClosedConfiguration(leftAccelerometerSensor, rightAccelerometerSensor,
+                        closeAngleDegrees),
+                createConfig(DEVICE_STATE_HALF_OPENED,
+                        /* name= */ "HALF_OPENED",
+                        /* activeStatePredicate= */ (provider) -> {
+                            final float hingeAngle = provider.getHingeAngle();
+                            return hingeAngle >= MAX_CLOSED_ANGLE_DEGREES
+                                    && hingeAngle <= TABLE_TOP_MODE_SWITCH_ANGLE_DEGREES;
+                        }),
+                createConfig(DEVICE_STATE_OPENED,
+                        /* name= */ "OPENED",
+                        /* activeStatePredicate= */ ALLOWED),
+                createConfig(DEVICE_STATE_REAR_DISPLAY_STATE,
+                        /* name= */ "REAR_DISPLAY_STATE",
+                        /* flags= */ FLAG_EMULATED_ONLY,
+                        /* activeStatePredicate= */ NOT_ALLOWED),
+                createConfig(DEVICE_STATE_CONCURRENT_INNER_DEFAULT,
+                        /* name= */ "CONCURRENT_INNER_DEFAULT",
+                        /* flags= */ FLAG_EMULATED_ONLY | FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP
+                                | FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
+                                | FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE,
+                        /* activeStatePredicate= */ NOT_ALLOWED,
+                        /* availabilityPredicate= */
+                        provider -> !mIsDualDisplayBlockingEnabled
+                                || provider.hasNoConnectedExternalDisplay())
+        };
+    }
+
+    private DeviceStateConfiguration createClosedConfiguration(
+            @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
+            @Nullable Integer closeAngleDegrees) {
+        if (closeAngleDegrees != null) {
+            // Switch displays at closeAngleDegrees in both ways (folding and unfolding)
+            return createConfig(
+                    DEVICE_STATE_CLOSED,
+                    /* name= */ "CLOSED",
+                    /* flags= */ FLAG_CANCEL_OVERRIDE_REQUESTS,
+                    /* activeStatePredicate= */ (provider) -> {
+                        final float hingeAngle = provider.getHingeAngle();
+                        return hingeAngle <= closeAngleDegrees;
+                    }
+            );
+        }
+
+        if (mEnablePostureBasedClosedState) {
+            // Use smart closed state predicate that will use different switch angles
+            // based on the device posture (e.g. wedge mode, tent mode, reverse wedge mode)
+            return createConfig(
+                    DEVICE_STATE_CLOSED,
+                    /* name= */ "CLOSED",
+                    /* flags= */ FLAG_CANCEL_OVERRIDE_REQUESTS,
+                    /* activeStatePredicate= */ new BookStyleClosedStatePredicate(mContext,
+                            this, leftAccelerometerSensor, rightAccelerometerSensor,
+                            DEFAULT_STATE_TRANSITIONS)
+            );
+        }
+
+        // Switch to the outer display only at 0 degrees but use TENT_MODE_SWITCH_ANGLE_DEGREES
+        // angle when switching to the inner display
+        return createTentModeClosedState(DEVICE_STATE_CLOSED,
+                /* name= */ "CLOSED",
+                /* flags= */ FLAG_CANCEL_OVERRIDE_REQUESTS,
+                MIN_CLOSED_ANGLE_DEGREES,
+                MAX_CLOSED_ANGLE_DEGREES,
+                TENT_MODE_SWITCH_ANGLE_DEGREES);
+    }
+
+    @Override
+    public void onClosedStateUpdated() {
+        mProvider.notifyDeviceStateChangedIfNeeded();
+    }
+
+    @Override
+    public DeviceStateProvider getDeviceStateProvider() {
+        return mProvider;
+    }
+
+    @Override
+    public void configureDeviceForState(int state, @NonNull Runnable onComplete) {
+        onComplete.run();
+    }
+}
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStylePreferredScreenCalculator.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStylePreferredScreenCalculator.java
new file mode 100644
index 0000000..8977422
--- /dev/null
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStylePreferredScreenCalculator.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import android.annotation.Nullable;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Calculates if we should use outer or inner display on foldable devices based on a several
+ * inputs like device orientation, hinge angle signals.
+ *
+ * This is a stateful class and acts like a state machine with fixed number of states
+ * and transitions. It allows to list all possible state transitions instead of performing
+ * imperative logic to make sure that we cover all scenarios and improve debuggability.
+ *
+ * See {@link BookStyleStateTransitions} for detailed description of the default behavior.
+ */
+public class BookStylePreferredScreenCalculator {
+
+    /**
+     * When calculating the new state we will re-calculate it until it settles down. We re-calculate
+     * it because the new state might trigger another state transition and this might happen
+     * several times. We don't want to have infinite loops in state calculation, so this value
+     * limits the number of such state transitions.
+     * For example, in the default configuration {@link BookStyleStateTransitions}, after each
+     * transition with 'set sticky flag' output it will perform a transition to a state without
+     * 'set sticky flag' output.
+     * We also have a unit test covering all possible states which checks that we don't have such
+     * states that could end up in an infinite transition. See sample test for the default
+     * transitions in {@link BookStyleClosedStateCalculatorTest}.
+     */
+    private static final int MAX_STATE_CHANGES = 16;
+
+    private State mState = new State(
+            /* stickyKeepOuterUntil90Degrees= */ false,
+            /* stickyKeepInnerUntil45Degrees= */ false,
+            PreferredScreen.INVALID);
+
+    private final List<StateTransition> mStateTransitions;
+
+    /**
+     * Creates BookStyleClosedStateCalculator
+     * @param stateTransitions list of all state transitions
+     */
+    public BookStylePreferredScreenCalculator(List<StateTransition> stateTransitions) {
+        mStateTransitions = stateTransitions;
+    }
+
+    /**
+     * Calculates updated {@link PreferredScreen} based on the current inputs and the current state.
+     * The calculation is done based on defined {@link StateTransition}s, it might perform
+     * multiple transitions until we settle down on a single state. Multiple transitions could be
+     * performed in case if {@link StateTransition} causes another update of the state.
+     * There is a limit of maximum {@link MAX_STATE_CHANGES} state transitions, after which
+     * this method will throw an {@link IllegalStateException}.
+     *
+     * @param angle current hinge angle
+     * @param likelyTentOrWedge true if the device is likely in tent or wedge mode
+     * @param likelyReverseWedge true if the device is likely in reverse wedge mode
+     * @return updated {@link PreferredScreen}
+     */
+    public PreferredScreen calculatePreferredScreen(HingeAngle angle, boolean likelyTentOrWedge,
+            boolean likelyReverseWedge) {
+        int attempts = 0;
+        State newState = calculateNewState(mState, angle, likelyTentOrWedge, likelyReverseWedge);
+        while (attempts < MAX_STATE_CHANGES && !Objects.equals(mState, newState)) {
+            mState = newState;
+            newState = calculateNewState(mState, angle, likelyTentOrWedge, likelyReverseWedge);
+            attempts++;
+        }
+
+        if (attempts >= MAX_STATE_CHANGES) {
+            throw new IllegalStateException(
+                    "Can't settle state " + mState + ", inputs: hingeAngle = " + angle
+                            + ", likelyTentOrWedge = " + likelyTentOrWedge
+                            + ", likelyReverseWedge = " + likelyReverseWedge);
+        }
+
+        final State oldState = mState;
+        mState = newState;
+
+        if (mState.mPreferredScreen == PreferredScreen.INVALID) {
+            throw new IllegalStateException(
+                    "Reached invalid state " + mState + ", inputs: hingeAngle = " + angle
+                            + ", likelyTentOrWedge = " + likelyTentOrWedge
+                            + ", likelyReverseWedge = " + likelyReverseWedge + ", old state: "
+                            + oldState);
+        }
+
+        return mState.mPreferredScreen;
+    }
+
+    /**
+     * Returns the current state of the calculator
+     */
+    public State getState() {
+        return mState;
+    }
+
+    private State calculateNewState(State current, HingeAngle hingeAngle, boolean likelyTentOrWedge,
+            boolean likelyReverseWedge) {
+        for (int i = 0; i < mStateTransitions.size(); i++) {
+            final State newState = mStateTransitions.get(i).tryTransition(hingeAngle,
+                    likelyTentOrWedge, likelyReverseWedge, current);
+            if (newState != null) {
+                return newState;
+            }
+        }
+
+        throw new IllegalArgumentException(
+                "Entry not found for state: " + current + ", hingeAngle = " + hingeAngle
+                        + ", likelyTentOrWedge = " + likelyTentOrWedge + ", likelyReverseWedge = "
+                        + likelyReverseWedge);
+    }
+
+    /**
+     * The angle between two halves of the foldable device in degrees. The angle is '0' when
+     * the device is fully closed and '180' when the device is fully open and flat.
+     */
+    public enum HingeAngle {
+        ANGLE_0,
+        ANGLE_0_TO_45,
+        ANGLE_45_TO_90,
+        ANGLE_90_TO_180
+    }
+
+    /**
+     * Resulting closed state of the device, where OPEN state indicates that the device should use
+     * the inner display and CLOSED means that it should use the outer (cover) screen.
+     */
+    public enum PreferredScreen {
+        INNER,
+        OUTER,
+        INVALID
+    }
+
+    /**
+     * Describes a state transition for the posture based active screen calculator
+     */
+    public static class StateTransition {
+        private final Input mInput;
+        private final State mOutput;
+
+        public StateTransition(HingeAngle hingeAngle, boolean likelyTentOrWedge,
+                boolean likelyReverseWedge,
+                boolean stickyKeepOuterUntil90Degrees, boolean stickyKeepInnerUntil45Degrees,
+                PreferredScreen preferredScreen, Boolean setStickyKeepOuterUntil90Degrees,
+                Boolean setStickyKeepInnerUntil45Degrees) {
+            mInput = new Input(hingeAngle, likelyTentOrWedge, likelyReverseWedge,
+                    stickyKeepOuterUntil90Degrees, stickyKeepInnerUntil45Degrees);
+            mOutput = new State(setStickyKeepOuterUntil90Degrees,
+                    setStickyKeepInnerUntil45Degrees, preferredScreen);
+        }
+
+        /**
+         * Returns true if the state transition is applicable for the given inputs
+         */
+        private boolean isApplicable(HingeAngle hingeAngle, boolean likelyTentOrWedge,
+                boolean likelyReverseWedge, State currentState) {
+            return mInput.hingeAngle == hingeAngle
+                    && mInput.likelyTentOrWedge == likelyTentOrWedge
+                    && mInput.likelyReverseWedge == likelyReverseWedge
+                    && Objects.equals(mInput.stickyKeepOuterUntil90Degrees,
+                    currentState.stickyKeepOuterUntil90Degrees)
+                    && Objects.equals(mInput.stickyKeepInnerUntil45Degrees,
+                    currentState.stickyKeepInnerUntil45Degrees);
+        }
+
+        /**
+         * Try to perform transition for the inputs, returns new state if this
+         * transition is applicable for the given state and inputs
+         */
+        @Nullable
+        State tryTransition(HingeAngle hingeAngle, boolean likelyTentOrWedge,
+                boolean likelyReverseWedge, State currentState) {
+            if (!isApplicable(hingeAngle, likelyTentOrWedge, likelyReverseWedge, currentState)) {
+                return null;
+            }
+
+            boolean stickyKeepOuterUntil90Degrees = currentState.stickyKeepOuterUntil90Degrees;
+            boolean stickyKeepInnerUntil45Degrees = currentState.stickyKeepInnerUntil45Degrees;
+
+            if (mOutput.stickyKeepOuterUntil90Degrees != null) {
+                stickyKeepOuterUntil90Degrees =
+                        mOutput.stickyKeepOuterUntil90Degrees;
+            }
+
+            if (mOutput.stickyKeepInnerUntil45Degrees != null) {
+                stickyKeepInnerUntil45Degrees =
+                        mOutput.stickyKeepInnerUntil45Degrees;
+            }
+
+            return new State(stickyKeepOuterUntil90Degrees, stickyKeepInnerUntil45Degrees,
+                    mOutput.mPreferredScreen);
+        }
+    }
+
+    /**
+     * The input part of the {@link StateTransition}, these are the values that are used
+     * to decide which {@link State} output to choose.
+     */
+    private static class Input {
+        final HingeAngle hingeAngle;
+        final boolean likelyTentOrWedge;
+        final boolean likelyReverseWedge;
+        final boolean stickyKeepOuterUntil90Degrees;
+        final boolean stickyKeepInnerUntil45Degrees;
+
+        public Input(HingeAngle hingeAngle, boolean likelyTentOrWedge,
+                boolean likelyReverseWedge,
+                boolean stickyKeepOuterUntil90Degrees, boolean stickyKeepInnerUntil45Degrees) {
+            this.hingeAngle = hingeAngle;
+            this.likelyTentOrWedge = likelyTentOrWedge;
+            this.likelyReverseWedge = likelyReverseWedge;
+            this.stickyKeepOuterUntil90Degrees = stickyKeepOuterUntil90Degrees;
+            this.stickyKeepInnerUntil45Degrees = stickyKeepInnerUntil45Degrees;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof Input)) return false;
+            Input that = (Input) o;
+            return likelyTentOrWedge == that.likelyTentOrWedge
+                    && likelyReverseWedge == that.likelyReverseWedge
+                    && stickyKeepOuterUntil90Degrees == that.stickyKeepOuterUntil90Degrees
+                    && stickyKeepInnerUntil45Degrees == that.stickyKeepInnerUntil45Degrees
+                    && hingeAngle == that.hingeAngle;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(hingeAngle, likelyTentOrWedge, likelyReverseWedge,
+                    stickyKeepOuterUntil90Degrees, stickyKeepInnerUntil45Degrees);
+        }
+
+        @Override
+        public String toString() {
+            return "InputState{" +
+                    "hingeAngle=" + hingeAngle +
+                    ", likelyTentOrWedge=" + likelyTentOrWedge +
+                    ", likelyReverseWedge=" + likelyReverseWedge +
+                    ", stickyKeepOuterUntil90Degrees=" + stickyKeepOuterUntil90Degrees +
+                    ", stickyKeepInnerUntil45Degrees=" + stickyKeepInnerUntil45Degrees +
+                    '}';
+        }
+    }
+
+    /**
+     * Class that holds a state of the calculator, it could be used to store the current
+     * state or to define the target (output) state based on some input in {@link StateTransition}.
+     */
+    public static class State {
+        public Boolean stickyKeepOuterUntil90Degrees;
+        public Boolean stickyKeepInnerUntil45Degrees;
+
+        PreferredScreen mPreferredScreen;
+
+        public State(Boolean stickyKeepOuterUntil90Degrees,
+                Boolean stickyKeepInnerUntil45Degrees,
+                PreferredScreen preferredScreen) {
+            this.stickyKeepOuterUntil90Degrees = stickyKeepOuterUntil90Degrees;
+            this.stickyKeepInnerUntil45Degrees = stickyKeepInnerUntil45Degrees;
+            this.mPreferredScreen = preferredScreen;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof State)) return false;
+            State that = (State) o;
+            return Objects.equals(stickyKeepOuterUntil90Degrees,
+                    that.stickyKeepOuterUntil90Degrees) && Objects.equals(
+                    stickyKeepInnerUntil45Degrees, that.stickyKeepInnerUntil45Degrees)
+                    && mPreferredScreen == that.mPreferredScreen;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(stickyKeepOuterUntil90Degrees, stickyKeepInnerUntil45Degrees,
+                    mPreferredScreen);
+        }
+
+        @Override
+        public String toString() {
+            return "State{" +
+                    "stickyKeepOuterUntil90Degrees=" + stickyKeepOuterUntil90Degrees +
+                    ", stickyKeepInnerUntil90Degrees=" + stickyKeepInnerUntil45Degrees +
+                    ", closedState=" + mPreferredScreen +
+                    '}';
+        }
+    }
+}
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleStateTransitions.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleStateTransitions.java
new file mode 100644
index 0000000..16daacb
--- /dev/null
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleStateTransitions.java
@@ -0,0 +1,722 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import com.android.server.policy.BookStylePreferredScreenCalculator.PreferredScreen;
+import com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle;
+import com.android.server.policy.BookStylePreferredScreenCalculator.StateTransition;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Describes all possible state transitions for {@link BookStylePreferredScreenCalculator}.
+ * It contains a default configuration for a foldable device that has two screens: smaller outer
+ * screen which has portrait natural orientation and a larger inner screen and allows to use the
+ * device in tent mode or wedge mode.
+ *
+ * As the output state could affect calculating of the new state, it could potentially cause
+ * infinite loop and make the state never settle down. This could be avoided using automated test
+ * that checks all possible inputs and asserts that the final state is valid.
+ * See sample test for the default transitions in {@link BookStyleClosedStateCalculatorTest}.
+ *
+ * - Tent mode is defined as a posture when the device is partially opened and placed on the ground
+ *   on the edges that are parallel to the hinge.
+ * - Wedge mode is when the device is partially opened and placed flat on the ground with the part
+ *   of the device that doesn't have the display
+ * - Reverse wedge mode is when the device is partially opened and placed flat on the ground with
+ *   the outer screen down, so the outer screen is not accessible
+ *
+ * Behavior description:
+ * - When unfolding with screens off we assume that no sensor data available except hinge angle
+ *   (based on hall sensor), so we switch to the inner screen immediately
+ *
+ * - When unfolding when screen is 'on' we can check if we are likely in tent or wedge mode
+ *   - If not likely tent/wedge mode or sensors data not available, then we unfold immediately
+ *     After unfolding, the state of the inner screen 'on' is sticky between 0 and 45 degrees, so
+ *     it won't jump back to the outer screen even if you move the phone into tent/wedge mode. The
+ *     stickiness is reset after fully closing the device or unfolding past 45 degrees.
+ *   - If likely tent or wedge mode, switch only at 90 degrees
+ *     Tent/wedge mode is 'sticky' between 0 and 90 degrees, so it won't reset until you either
+ *     fully close the device or unfold past 90 degrees.
+ *
+ * - When folding we can check if we are likely in reverse wedge mode
+ *   - If not likely in reverse wedge mode or sensor data is not available we switch to the outer
+ *     screen at 45 degrees and enable sticky tent/wedge mode as before, this allows to enter
+ *     tent/wedge mode even if you are not on an even surface or holding phone in landscape
+ *   - If likely in reverse wedge mode, switch to the outer screen only at 0 degrees to allow
+ *     some use cases like using camera in this posture, the check happens after passing 45 degrees
+ *     and inner screen becomes sticky turned 'on' until fully closing or unfolding past 45 degrees
+ */
+public class BookStyleStateTransitions {
+
+    public static final List<StateTransition> DEFAULT_STATE_TRANSITIONS = new ArrayList<>();
+
+    static {
+        // region Angle 0
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ true
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ false,
+                /* setStickyKeepInnerUntil45Degrees */ true
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INVALID,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ true
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ false,
+                /* setStickyKeepInnerUntil45Degrees */ true
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INVALID,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ false
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ false,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INVALID,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ false
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INVALID,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        // endregion
+
+        // region Angle 0-45
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0_TO_45,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ true,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0_TO_45,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0_TO_45,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0_TO_45,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INVALID,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0_TO_45,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ true
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0_TO_45,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0_TO_45,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0_TO_45,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INVALID,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0_TO_45,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ true,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0_TO_45,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0_TO_45,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0_TO_45,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INVALID,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0_TO_45,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ true
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0_TO_45,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0_TO_45,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_0_TO_45,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INVALID,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        // endregion
+
+        // region Angle 45-90
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_45_TO_90,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_45_TO_90,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ false
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_45_TO_90,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_45_TO_90,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INVALID,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_45_TO_90,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ true
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_45_TO_90,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_45_TO_90,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_45_TO_90,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INVALID,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_45_TO_90,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_45_TO_90,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ false
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_45_TO_90,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_45_TO_90,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INVALID,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_45_TO_90,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ true
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_45_TO_90,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_45_TO_90,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.OUTER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_45_TO_90,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INVALID,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        // endregion
+
+        // region Angle 90-180
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_90_TO_180,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_90_TO_180,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ false
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_90_TO_180,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ false,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_90_TO_180,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INVALID,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_90_TO_180,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_90_TO_180,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ false
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_90_TO_180,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ false,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_90_TO_180,
+                /* likelyTentOrWedge */ false,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INVALID,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_90_TO_180,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_90_TO_180,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ false
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_90_TO_180,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ false,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_90_TO_180,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ false,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INVALID,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_90_TO_180,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_90_TO_180,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ false,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ false
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_90_TO_180,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ false,
+                PreferredScreen.INNER,
+                /* setStickyKeepOuterUntil90Degrees */ false,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        DEFAULT_STATE_TRANSITIONS.add(new StateTransition(
+                HingeAngle.ANGLE_90_TO_180,
+                /* likelyTentOrWedge */ true,
+                /* likelyReverseWedge */ true,
+                /* stickyKeepOuterUntil90Degrees */ true,
+                /* stickyKeepInnerUntil45Degrees */ true,
+                PreferredScreen.INVALID,
+                /* setStickyKeepOuterUntil90Degrees */ null,
+                /* setStickyKeepInnerUntil45Degrees */ null
+        ));
+        // endregion
+    }
+}
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
index 4c487a7..ba72977 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
@@ -367,8 +367,11 @@
         // TODO(b/312397262): consider virtual displays cases
         synchronized (mLock) {
             if (mIsDualDisplayBlockingEnabled
-                    && !mExternalDisplaysConnected.get(displayId, false)
-                    && mDisplayManager.getDisplay(displayId).getType() == TYPE_EXTERNAL) {
+                    && !mExternalDisplaysConnected.get(displayId, false)) {
+                var display = mDisplayManager.getDisplay(displayId);
+                if (display == null || display.getType() != TYPE_EXTERNAL) {
+                    return;
+                }
                 mExternalDisplaysConnected.put(displayId, true);
 
                 // Only update the supported state when going from 0 external display to 1
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java
deleted file mode 100644
index 5968b63..0000000
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.policy;
-
-import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
-import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
-import static com.android.server.devicestate.DeviceState.FLAG_EMULATED_ONLY;
-import static com.android.server.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
-import static com.android.server.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
-import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createConfig;
-import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createTentModeClosedState;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.Sensor;
-import android.hardware.SensorManager;
-import android.hardware.display.DisplayManager;
-
-import com.android.server.devicestate.DeviceStatePolicy;
-import com.android.server.devicestate.DeviceStateProvider;
-import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration;
-import com.android.server.policy.feature.flags.FeatureFlags;
-import com.android.server.policy.feature.flags.FeatureFlagsImpl;
-
-import java.util.function.Predicate;
-
-/**
- * Device state policy for a foldable device that supports tent mode: a mode when the device
- * keeps the outer display on until reaching a certain hinge angle threshold.
- *
- * Contains configuration for {@link FoldableDeviceStateProvider}.
- */
-public class TentModeDeviceStatePolicy extends DeviceStatePolicy {
-
-    private static final int DEVICE_STATE_CLOSED = 0;
-    private static final int DEVICE_STATE_HALF_OPENED = 1;
-    private static final int DEVICE_STATE_OPENED = 2;
-    private static final int DEVICE_STATE_REAR_DISPLAY_STATE = 3;
-    private static final int DEVICE_STATE_CONCURRENT_INNER_DEFAULT = 4;
-
-    private static final int TENT_MODE_SWITCH_ANGLE_DEGREES = 90;
-    private static final int TABLE_TOP_MODE_SWITCH_ANGLE_DEGREES = 125;
-    private static final int MIN_CLOSED_ANGLE_DEGREES = 0;
-    private static final int MAX_CLOSED_ANGLE_DEGREES = 5;
-
-    private final DeviceStateProvider mProvider;
-
-    private final boolean mIsDualDisplayBlockingEnabled;
-    private static final Predicate<FoldableDeviceStateProvider> ALLOWED = p -> true;
-    private static final Predicate<FoldableDeviceStateProvider> NOT_ALLOWED = p -> false;
-
-    /**
-     * Creates TentModeDeviceStatePolicy
-     *
-     * @param context           Android context
-     * @param hingeAngleSensor  hinge angle sensor that will be used to switch between states
-     * @param hallSensor        hall sensor that will be used to switch between states
-     * @param closeAngleDegrees if non-zero, this angle will be used as a threshold to switch
-     *                          between folded and unfolded modes, otherwise when folding the
-     *                          display switch will happen at 0 degrees
-     */
-    public TentModeDeviceStatePolicy(@NonNull Context context,
-            @NonNull Sensor hingeAngleSensor, @NonNull Sensor hallSensor, int closeAngleDegrees) {
-        this(new FeatureFlagsImpl(), context, hingeAngleSensor, hallSensor, closeAngleDegrees);
-    }
-
-    public TentModeDeviceStatePolicy(@NonNull FeatureFlags featureFlags, @NonNull Context context,
-                                     @NonNull Sensor hingeAngleSensor, @NonNull Sensor hallSensor,
-                                     int closeAngleDegrees) {
-        super(context);
-
-        final SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
-        final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
-
-        final DeviceStateConfiguration[] configuration = createConfiguration(closeAngleDegrees);
-
-        mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking();
-
-        mProvider = new FoldableDeviceStateProvider(mContext, sensorManager,
-                hingeAngleSensor, hallSensor, displayManager, configuration);
-    }
-
-    private DeviceStateConfiguration[] createConfiguration(int closeAngleDegrees) {
-        return new DeviceStateConfiguration[]{
-                createClosedConfiguration(closeAngleDegrees),
-                createConfig(DEVICE_STATE_HALF_OPENED,
-                        /* name= */ "HALF_OPENED",
-                        /* activeStatePredicate= */ (provider) -> {
-                            final float hingeAngle = provider.getHingeAngle();
-                            return hingeAngle >= MAX_CLOSED_ANGLE_DEGREES
-                                    && hingeAngle <= TABLE_TOP_MODE_SWITCH_ANGLE_DEGREES;
-                        }),
-                createConfig(DEVICE_STATE_OPENED,
-                        /* name= */ "OPENED",
-                        /* activeStatePredicate= */ ALLOWED),
-                createConfig(DEVICE_STATE_REAR_DISPLAY_STATE,
-                        /* name= */ "REAR_DISPLAY_STATE",
-                        /* flags= */ FLAG_EMULATED_ONLY,
-                        /* activeStatePredicate= */ NOT_ALLOWED),
-                createConfig(DEVICE_STATE_CONCURRENT_INNER_DEFAULT,
-                        /* name= */ "CONCURRENT_INNER_DEFAULT",
-                        /* flags= */ FLAG_EMULATED_ONLY | FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP
-                                | FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
-                                | FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE,
-                        /* activeStatePredicate= */ NOT_ALLOWED,
-                        /* availabilityPredicate= */
-                        provider -> !mIsDualDisplayBlockingEnabled
-                                || provider.hasNoConnectedExternalDisplay())
-        };
-    }
-
-    private DeviceStateConfiguration createClosedConfiguration(int closeAngleDegrees) {
-        if (closeAngleDegrees > 0) {
-            // Switch displays at closeAngleDegrees in both ways (folding and unfolding)
-            return createConfig(
-                    DEVICE_STATE_CLOSED,
-                    /* name= */ "CLOSED",
-                    /* flags= */ FLAG_CANCEL_OVERRIDE_REQUESTS,
-                    /* activeStatePredicate= */ (provider) -> {
-                        final float hingeAngle = provider.getHingeAngle();
-                        return hingeAngle <= closeAngleDegrees;
-                    }
-            );
-        }
-
-        // Switch to the outer display only at 0 degrees but use TENT_MODE_SWITCH_ANGLE_DEGREES
-        // angle when switching to the inner display
-        return createTentModeClosedState(DEVICE_STATE_CLOSED,
-                /* name= */ "CLOSED",
-                /* flags= */ FLAG_CANCEL_OVERRIDE_REQUESTS,
-                MIN_CLOSED_ANGLE_DEGREES,
-                MAX_CLOSED_ANGLE_DEGREES,
-                TENT_MODE_SWITCH_ANGLE_DEGREES);
-    }
-
-    @Override
-    public DeviceStateProvider getDeviceStateProvider() {
-        return mProvider;
-    }
-
-    @Override
-    public void configureDeviceForState(int state, @NonNull Runnable onComplete) {
-        onComplete.run();
-    }
-}
diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
new file mode 100644
index 0000000..8d01b7a
--- /dev/null
+++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
@@ -0,0 +1,703 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.STATE_OFF;
+import static android.view.Display.STATE_ON;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Instrumentation;
+import android.content.res.Configuration;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManager;
+import android.hardware.input.InputSensorInfo;
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.view.Display;
+import android.view.Surface;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.devicestate.DeviceStateProvider;
+import com.android.server.devicestate.DeviceStateProvider.Listener;
+import com.android.server.policy.feature.flags.FakeFeatureFlagsImpl;
+import com.android.server.policy.feature.flags.Flags;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.internal.util.reflection.FieldSetter;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link BookStyleDeviceStatePolicy.Provider}.
+ * <p/>
+ * Run with <code>atest BookStyleDeviceStatePolicyTest</code>.
+ */
+@RunWith(AndroidTestingRunner.class)
+public final class BookStyleDeviceStatePolicyTest {
+
+    private static final int DEVICE_STATE_CLOSED = 0;
+    private static final int DEVICE_STATE_HALF_OPENED = 1;
+    private static final int DEVICE_STATE_OPENED = 2;
+
+    @Captor
+    private ArgumentCaptor<Integer> mDeviceStateCaptor;
+    @Captor
+    private ArgumentCaptor<DisplayManager.DisplayListener> mDisplayListenerCaptor;
+    @Mock
+    private SensorManager mSensorManager;
+    @Mock
+    private InputSensorInfo mInputSensorInfo;
+    @Mock
+    private Listener mListener;
+    @Mock
+    DisplayManager mDisplayManager;
+    @Mock
+    private Display mDisplay;
+
+    private final FakeFeatureFlagsImpl mFakeFeatureFlags = new FakeFeatureFlagsImpl();
+
+    private final Configuration mConfiguration = new Configuration();
+
+    private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            mInstrumentation.getTargetContext());
+
+    private Sensor mHallSensor;
+    private Sensor mOrientationSensor;
+    private Sensor mHingeAngleSensor;
+    private Sensor mLeftAccelerometer;
+    private Sensor mRightAccelerometer;
+
+    private Map<Sensor, List<SensorEventListener>> mSensorEventListeners = new HashMap<>();
+    private DeviceStateProvider mProvider;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mFakeFeatureFlags.setFlag(Flags.FLAG_ENABLE_FOLDABLES_POSTURE_BASED_CLOSED_STATE, true);
+        mFakeFeatureFlags.setFlag(Flags.FLAG_ENABLE_DUAL_DISPLAY_BLOCKING, true);
+
+        when(mInputSensorInfo.getName()).thenReturn("hall-effect");
+        mHallSensor = new Sensor(mInputSensorInfo);
+        when(mInputSensorInfo.getName()).thenReturn("hinge-angle");
+        mHingeAngleSensor = new Sensor(mInputSensorInfo);
+        when(mInputSensorInfo.getName()).thenReturn("left-accelerometer");
+        mLeftAccelerometer = new Sensor(mInputSensorInfo);
+        when(mInputSensorInfo.getName()).thenReturn("right-accelerometer");
+        mRightAccelerometer = new Sensor(mInputSensorInfo);
+        when(mInputSensorInfo.getName()).thenReturn("orientation");
+        mOrientationSensor = new Sensor(mInputSensorInfo);
+
+        mContext.addMockSystemService(SensorManager.class, mSensorManager);
+
+        when(mSensorManager.getDefaultSensor(eq(Sensor.TYPE_HINGE_ANGLE), eq(true)))
+                .thenReturn(mHingeAngleSensor);
+        when(mSensorManager.getDefaultSensor(eq(Sensor.TYPE_DEVICE_ORIENTATION)))
+                .thenReturn(mOrientationSensor);
+
+        when(mDisplayManager.getDisplay(eq(DEFAULT_DISPLAY))).thenReturn(mDisplay);
+        mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
+
+        mContext.ensureTestableResources();
+        when(mContext.getResources().getConfiguration()).thenReturn(mConfiguration);
+
+        final List<Sensor> sensors = new ArrayList<>();
+        sensors.add(mHallSensor);
+        sensors.add(mHingeAngleSensor);
+        sensors.add(mOrientationSensor);
+        sensors.add(mLeftAccelerometer);
+        sensors.add(mRightAccelerometer);
+
+        when(mSensorManager.registerListener(any(), any(), anyInt(), any())).thenAnswer(
+                invocation -> {
+                    final SensorEventListener listener = invocation.getArgument(0);
+                    final Sensor sensor = invocation.getArgument(1);
+                    addSensorListener(sensor, listener);
+                    return true;
+                });
+        when(mSensorManager.registerListener(any(), any(), anyInt())).thenAnswer(
+                invocation -> {
+                    final SensorEventListener listener = invocation.getArgument(0);
+                    final Sensor sensor = invocation.getArgument(1);
+                    addSensorListener(sensor, listener);
+                    return true;
+                });
+
+        doAnswer(invocation -> {
+            final SensorEventListener listener = invocation.getArgument(0);
+            final boolean[] removed = {false};
+            mSensorEventListeners.forEach((sensor, sensorEventListeners) ->
+                    removed[0] |= sensorEventListeners.remove(listener));
+
+            if (!removed[0]) {
+                throw new IllegalArgumentException(
+                        "Trying to unregister listener " + listener + " that was not registered");
+            }
+
+            return null;
+        }).when(mSensorManager).unregisterListener(any(SensorEventListener.class));
+
+        doAnswer(invocation -> {
+            final SensorEventListener listener = invocation.getArgument(0);
+            final Sensor sensor = invocation.getArgument(1);
+
+            boolean removed = mSensorEventListeners.get(sensor).remove(listener);
+            if (!removed) {
+                throw new IllegalArgumentException(
+                        "Trying to unregister listener " + listener
+                                + " that was not registered for sensor " + sensor);
+            }
+
+            return null;
+        }).when(mSensorManager).unregisterListener(any(SensorEventListener.class),
+                any(Sensor.class));
+
+        try {
+            FieldSetter.setField(mHallSensor, mHallSensor.getClass()
+                    .getDeclaredField("mStringType"), "com.google.sensor.hall_effect");
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException(e);
+        }
+
+        when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
+                .thenReturn(sensors);
+
+        mInstrumentation.runOnMainSync(() -> mProvider = createProvider());
+
+        verify(mDisplayManager, atLeastOnce()).registerDisplayListener(
+                mDisplayListenerCaptor.capture(), nullable(Handler.class));
+        setScreenOn(true);
+    }
+
+    @Test
+    public void test_noSensorEventsYet_reportOpenedState() {
+        mProvider.setListener(mListener);
+        verify(mListener).onStateChanged(mDeviceStateCaptor.capture());
+        assertEquals(DEVICE_STATE_OPENED, mDeviceStateCaptor.getValue().intValue());
+    }
+
+    @Test
+    public void test_deviceClosedSensorEventsBecameAvailable_reportsClosedState() {
+        mProvider.setListener(mListener);
+        clearInvocations(mListener);
+
+        sendHingeAngle(0f);
+
+        verify(mListener).onStateChanged(mDeviceStateCaptor.capture());
+        assertEquals(DEVICE_STATE_CLOSED, mDeviceStateCaptor.getValue().intValue());
+    }
+
+    @Test
+    public void test_hingeAngleClosed_reportsClosedState() {
+        sendHingeAngle(0f);
+
+        mProvider.setListener(mListener);
+        verify(mListener).onStateChanged(mDeviceStateCaptor.capture());
+        assertEquals(DEVICE_STATE_CLOSED, mDeviceStateCaptor.getValue().intValue());
+    }
+
+    @Test
+    public void test_hingeAngleFullyOpened_reportsOpenedState() {
+        sendHingeAngle(180f);
+
+        mProvider.setListener(mListener);
+        verify(mListener).onStateChanged(mDeviceStateCaptor.capture());
+        assertEquals(DEVICE_STATE_OPENED, mDeviceStateCaptor.getValue().intValue());
+    }
+
+    @Test
+    public void test_unfoldingFromClosedToFullyOpened_reportsOpenedEvent() {
+        sendHingeAngle(0f);
+        mProvider.setListener(mListener);
+        clearInvocations(mListener);
+
+        sendHingeAngle(180f);
+
+        verify(mListener).onStateChanged(mDeviceStateCaptor.capture());
+        assertEquals(DEVICE_STATE_OPENED, mDeviceStateCaptor.getValue().intValue());
+    }
+
+    @Test
+    public void test_foldingFromFullyOpenToFullyClosed_movesToClosedState() {
+        sendHingeAngle(180f);
+
+        sendHingeAngle(0f);
+
+        mProvider.setListener(mListener);
+        verify(mListener).onStateChanged(mDeviceStateCaptor.capture());
+        assertEquals(DEVICE_STATE_CLOSED, mDeviceStateCaptor.getValue().intValue());
+    }
+
+    @Test
+    public void test_slowUnfolding_reportsEventsInOrder() {
+        sendHingeAngle(0f);
+        mProvider.setListener(mListener);
+
+        sendHingeAngle(5f);
+        sendHingeAngle(10f);
+        sendHingeAngle(60f);
+        sendHingeAngle(100f);
+        sendHingeAngle(180f);
+
+        verify(mListener, atLeastOnce()).onStateChanged(mDeviceStateCaptor.capture());
+        assertThat(mDeviceStateCaptor.getAllValues()).containsExactly(
+                DEVICE_STATE_CLOSED,
+                DEVICE_STATE_HALF_OPENED,
+                DEVICE_STATE_OPENED
+        );
+    }
+
+    @Test
+    public void test_slowFolding_reportsEventsInOrder() {
+        sendHingeAngle(180f);
+        mProvider.setListener(mListener);
+
+        sendHingeAngle(180f);
+        sendHingeAngle(100f);
+        sendHingeAngle(60f);
+        sendHingeAngle(10f);
+        sendHingeAngle(5f);
+
+        verify(mListener, atLeastOnce()).onStateChanged(mDeviceStateCaptor.capture());
+        assertThat(mDeviceStateCaptor.getAllValues()).containsExactly(
+                DEVICE_STATE_OPENED,
+                DEVICE_STATE_HALF_OPENED,
+                DEVICE_STATE_CLOSED
+        );
+    }
+
+    @Test
+    public void test_hingeAngleOpen_screenOff_reportsHalfFolded() {
+        sendHingeAngle(0f);
+        setScreenOn(false);
+        mProvider.setListener(mListener);
+
+        sendHingeAngle(10f);
+
+        verify(mListener, atLeastOnce()).onStateChanged(mDeviceStateCaptor.capture());
+        assertThat(mDeviceStateCaptor.getAllValues()).containsExactly(
+                DEVICE_STATE_CLOSED,
+                DEVICE_STATE_HALF_OPENED
+        );
+    }
+
+    @Test
+    public void test_slowUnfoldingWithScreenOff_reportsEventsInOrder() {
+        sendHingeAngle(0f);
+        setScreenOn(false);
+        mProvider.setListener(mListener);
+
+        sendHingeAngle(5f);
+        assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+        sendHingeAngle(10f);
+        assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+        sendHingeAngle(60f);
+        assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+        sendHingeAngle(100f);
+        sendHingeAngle(180f);
+        assertLatestReportedState(DEVICE_STATE_OPENED);
+
+        verify(mListener, atLeastOnce()).onStateChanged(mDeviceStateCaptor.capture());
+        assertThat(mDeviceStateCaptor.getAllValues()).containsExactly(
+                DEVICE_STATE_CLOSED,
+                DEVICE_STATE_HALF_OPENED,
+                DEVICE_STATE_OPENED
+        );
+    }
+
+    @Test
+    public void test_unfoldWithScreenOff_reportsHalfOpened() {
+        sendHingeAngle(0f);
+        setScreenOn(false);
+        mProvider.setListener(mListener);
+
+        sendHingeAngle(5f);
+        sendHingeAngle(10f);
+
+        verify(mListener, atLeastOnce()).onStateChanged(mDeviceStateCaptor.capture());
+        assertThat(mDeviceStateCaptor.getAllValues()).containsExactly(
+                DEVICE_STATE_CLOSED,
+                DEVICE_STATE_HALF_OPENED
+        );
+    }
+
+    @Test
+    public void test_slowUnfoldingAndFolding_reportsEventsInOrder() {
+        sendHingeAngle(0f);
+        mProvider.setListener(mListener);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+
+        // Started unfolding
+        sendHingeAngle(5f);
+        sendHingeAngle(30f);
+        assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+        sendHingeAngle(60f);
+        assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+        sendHingeAngle(100f);
+        assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+        sendHingeAngle(180f);
+        assertLatestReportedState(DEVICE_STATE_OPENED);
+
+        // Started folding
+        sendHingeAngle(100f);
+        assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+        sendHingeAngle(60f);
+        assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+        sendHingeAngle(30f);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+        sendHingeAngle(5f);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+
+        verify(mListener, atLeastOnce()).onStateChanged(mDeviceStateCaptor.capture());
+        assertThat(mDeviceStateCaptor.getAllValues()).containsExactly(
+                DEVICE_STATE_CLOSED,
+                DEVICE_STATE_HALF_OPENED,
+                DEVICE_STATE_OPENED,
+                DEVICE_STATE_HALF_OPENED,
+                DEVICE_STATE_CLOSED
+        );
+    }
+
+    @Test
+    public void test_unfoldTo30Degrees_screenOnRightSideMostlyFlat_keepsClosedState() {
+        sendHingeAngle(0f);
+        sendRightSideFlatSensorEvent(true);
+        mProvider.setListener(mListener);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+        clearInvocations(mListener);
+
+        sendHingeAngle(30f);
+
+        verify(mListener, never()).onStateChanged(mDeviceStateCaptor.capture());
+    }
+
+    @Test
+    public void test_unfoldTo30Degrees_seascapeDeviceOrientation_keepsClosedState() {
+        sendHingeAngle(0f);
+        sendRightSideFlatSensorEvent(false);
+        sendDeviceOrientation(Surface.ROTATION_270);
+        mProvider.setListener(mListener);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+        clearInvocations(mListener);
+
+        sendHingeAngle(30f);
+
+        verify(mListener, never()).onStateChanged(mDeviceStateCaptor.capture());
+    }
+
+    @Test
+    public void test_unfoldTo30Degrees_landscapeScreenRotation_keepsClosedState() {
+        sendHingeAngle(0f);
+        sendRightSideFlatSensorEvent(false);
+        sendScreenRotation(Surface.ROTATION_90);
+        mProvider.setListener(mListener);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+        clearInvocations(mListener);
+
+        sendHingeAngle(30f);
+
+        verify(mListener, never()).onStateChanged(mDeviceStateCaptor.capture());
+    }
+
+    @Test
+    public void test_unfoldTo30Degrees_seascapeScreenRotation_keepsClosedState() {
+        sendHingeAngle(0f);
+        sendRightSideFlatSensorEvent(false);
+        sendScreenRotation(Surface.ROTATION_270);
+        mProvider.setListener(mListener);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+        clearInvocations(mListener);
+
+        sendHingeAngle(30f);
+
+        verify(mListener, never()).onStateChanged(mDeviceStateCaptor.capture());
+    }
+
+    @Test
+    public void test_unfoldTo30Degrees_screenOnRightSideNotFlat_switchesToHalfOpenState() {
+        sendHingeAngle(0f);
+        sendRightSideFlatSensorEvent(false);
+        mProvider.setListener(mListener);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+        clearInvocations(mListener);
+
+        sendHingeAngle(30f);
+
+        verify(mListener).onStateChanged(DEVICE_STATE_HALF_OPENED);
+    }
+
+    @Test
+    public void test_unfoldTo30Degrees_screenOffRightSideFlat_switchesToHalfOpenState() {
+        sendHingeAngle(0f);
+        setScreenOn(false);
+        // This sensor event should be ignored as screen is off
+        sendRightSideFlatSensorEvent(true);
+        mProvider.setListener(mListener);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+        clearInvocations(mListener);
+
+        sendHingeAngle(30f);
+
+        verify(mListener).onStateChanged(DEVICE_STATE_HALF_OPENED);
+    }
+
+    @Test
+    public void test_unfoldTo60Degrees_andFoldTo10_switchesToClosedState() {
+        sendHingeAngle(0f);
+        sendRightSideFlatSensorEvent(false);
+        mProvider.setListener(mListener);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+        sendHingeAngle(60f);
+        assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+        clearInvocations(mListener);
+
+        sendHingeAngle(10f);
+
+        verify(mListener).onStateChanged(DEVICE_STATE_CLOSED);
+    }
+
+    @Test
+    public void test_foldTo10AndUnfoldTo85Degrees_keepsClosedState() {
+        sendHingeAngle(0f);
+        sendRightSideFlatSensorEvent(false);
+        mProvider.setListener(mListener);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+        sendHingeAngle(180f);
+        assertLatestReportedState(DEVICE_STATE_OPENED);
+        sendHingeAngle(10f);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+
+        sendHingeAngle(85f);
+
+        // Keeps 'tent'/'wedge' mode even when right side is not flat
+        // as user manually folded the device not all the way
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+    }
+
+    @Test
+    public void test_foldTo0AndUnfoldTo85Degrees_doesNotKeepClosedState() {
+        sendHingeAngle(0f);
+        sendRightSideFlatSensorEvent(false);
+        mProvider.setListener(mListener);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+        sendHingeAngle(180f);
+        assertLatestReportedState(DEVICE_STATE_OPENED);
+        sendHingeAngle(0f);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+
+        sendHingeAngle(85f);
+
+        // Do not enter 'tent'/'wedge' mode when right side is not flat
+        // as user fully folded the device before that
+        assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+    }
+
+    @Test
+    public void test_foldTo10_leftSideIsFlat_keepsInnerScreenForReverseWedge() {
+        sendHingeAngle(180f);
+        sendLeftSideFlatSensorEvent(true);
+        mProvider.setListener(mListener);
+        assertLatestReportedState(DEVICE_STATE_OPENED);
+
+        sendHingeAngle(10f);
+
+        // Keep the inner screen for reverse wedge mode (e.g. for astrophotography use case)
+        assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+    }
+
+    @Test
+    public void test_foldTo10_leftSideIsNotFlat_switchesToOuterScreen() {
+        sendHingeAngle(180f);
+        sendLeftSideFlatSensorEvent(false);
+        mProvider.setListener(mListener);
+        assertLatestReportedState(DEVICE_STATE_OPENED);
+
+        sendHingeAngle(10f);
+
+        // Do not keep the inner screen as it is not reverse wedge mode
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+    }
+
+    @Test
+    public void test_foldTo10_noAccelerometerEvents_switchesToOuterScreen() {
+        sendHingeAngle(180f);
+        mProvider.setListener(mListener);
+        assertLatestReportedState(DEVICE_STATE_OPENED);
+
+        sendHingeAngle(10f);
+
+        // Do not keep the inner screen as it is not reverse wedge mode
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+    }
+
+    @Test
+    public void test_deviceClosed_screenIsOff_noSensorListeners() {
+        mProvider.setListener(mListener);
+
+        sendHingeAngle(0f);
+        setScreenOn(false);
+
+        assertNoListenersForSensor(mLeftAccelerometer);
+        assertNoListenersForSensor(mRightAccelerometer);
+        assertNoListenersForSensor(mOrientationSensor);
+    }
+
+    @Test
+    public void test_deviceClosed_screenIsOn_doesNotListenForOneAccelerometer() {
+        mProvider.setListener(mListener);
+
+        sendHingeAngle(0f);
+        setScreenOn(true);
+
+        assertNoListenersForSensor(mLeftAccelerometer);
+        assertListensForSensor(mRightAccelerometer);
+        assertListensForSensor(mOrientationSensor);
+    }
+
+    @Test
+    public void test_deviceOpened_screenIsOn_listensToSensors() {
+        mProvider.setListener(mListener);
+
+        sendHingeAngle(180f);
+        setScreenOn(true);
+
+        assertListensForSensor(mLeftAccelerometer);
+        assertListensForSensor(mRightAccelerometer);
+        assertListensForSensor(mOrientationSensor);
+    }
+
+    private void assertLatestReportedState(int state) {
+        final ArgumentCaptor<Integer> integerCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mListener, atLeastOnce()).onStateChanged(integerCaptor.capture());
+        assertEquals(state, integerCaptor.getValue().intValue());
+    }
+
+    private void sendHingeAngle(float angle) {
+        sendSensorEvent(mHingeAngleSensor, new float[]{angle});
+    }
+
+    private void sendDeviceOrientation(int orientation) {
+        sendSensorEvent(mOrientationSensor, new float[]{orientation});
+    }
+
+    private void sendScreenRotation(int rotation) {
+        when(mDisplay.getRotation()).thenReturn(rotation);
+        mDisplayListenerCaptor.getAllValues().forEach((l) -> l.onDisplayChanged(DEFAULT_DISPLAY));
+    }
+
+    private void sendRightSideFlatSensorEvent(boolean flat) {
+        sendAccelerometerFlatEvents(mRightAccelerometer, flat);
+    }
+
+    private void sendLeftSideFlatSensorEvent(boolean flat) {
+        sendAccelerometerFlatEvents(mLeftAccelerometer, flat);
+    }
+
+    private static final int ACCELEROMETER_EVENTS = 10;
+
+    private void sendAccelerometerFlatEvents(Sensor sensor, boolean flat) {
+        final float[] values = flat ? new float[]{0.00021f, -0.00013f, 9.7899f} :
+                new float[]{6.124f, 4.411f, -1.7899f};
+        // Send the same values multiple times to bypass noise filter
+        for (int i = 0; i < ACCELEROMETER_EVENTS; i++) {
+            sendSensorEvent(sensor, values);
+        }
+    }
+
+    private void setScreenOn(boolean isOn) {
+        int state = isOn ? STATE_ON : STATE_OFF;
+        when(mDisplay.getState()).thenReturn(state);
+        mDisplayListenerCaptor.getAllValues().forEach((l) -> l.onDisplayChanged(DEFAULT_DISPLAY));
+    }
+
+    private void sendSensorEvent(Sensor sensor, float[] values) {
+        SensorEvent event = mock(SensorEvent.class);
+        event.sensor = sensor;
+        try {
+            FieldSetter.setField(event, event.getClass().getField("values"),
+                    values);
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException(e);
+        }
+
+        List<SensorEventListener> listeners = mSensorEventListeners.get(sensor);
+        if (listeners != null) {
+            listeners.forEach(sensorEventListener -> sensorEventListener.onSensorChanged(event));
+        }
+    }
+
+    private void assertNoListenersForSensor(Sensor sensor) {
+        final List<SensorEventListener> listeners = mSensorEventListeners.getOrDefault(sensor,
+                new ArrayList<>());
+        assertWithMessage("Expected no listeners for sensor " + sensor + " but found some").that(
+                listeners).isEmpty();
+    }
+
+    private void assertListensForSensor(Sensor sensor) {
+        final List<SensorEventListener> listeners = mSensorEventListeners.getOrDefault(sensor,
+                new ArrayList<>());
+        assertWithMessage(
+                "Expected at least one listener for sensor " + sensor).that(
+                listeners).isNotEmpty();
+    }
+
+    private void addSensorListener(Sensor sensor, SensorEventListener listener) {
+        List<SensorEventListener> listeners = mSensorEventListeners.computeIfAbsent(
+                sensor, k -> new ArrayList<>());
+        listeners.add(listener);
+    }
+
+    private DeviceStateProvider createProvider() {
+        return new BookStyleDeviceStatePolicy(mFakeFeatureFlags, mContext, mHingeAngleSensor,
+                mHallSensor, mLeftAccelerometer, mRightAccelerometer,
+                /* closeAngleDegrees= */ null).getDeviceStateProvider();
+    }
+}
diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStylePreferredScreenCalculatorTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStylePreferredScreenCalculatorTest.java
new file mode 100644
index 0000000..ae05b3f
--- /dev/null
+++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStylePreferredScreenCalculatorTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+
+import static com.android.server.policy.BookStyleStateTransitions.DEFAULT_STATE_TRANSITIONS;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.testing.AndroidTestingRunner;
+
+import com.android.server.policy.BookStylePreferredScreenCalculator.PreferredScreen;
+import com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle;
+
+import com.google.common.collect.Lists;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link BookStylePreferredScreenCalculator}.
+ * <p/>
+ * Run with <code>atest BookStyleClosedStateCalculatorTest</code>.
+ */
+@RunWith(AndroidTestingRunner.class)
+public final class BookStylePreferredScreenCalculatorTest {
+
+    private final BookStylePreferredScreenCalculator mCalculator =
+            new BookStylePreferredScreenCalculator(DEFAULT_STATE_TRANSITIONS);
+
+    private final List<HingeAngle> mHingeAngleValues = Arrays.asList(HingeAngle.values());
+    private final List<Boolean> mLikelyTentModeValues = Arrays.asList(true, false);
+    private final List<Boolean> mLikelyReverseWedgeModeValues = Arrays.asList(true, false);
+
+    @Test
+    public void transitionAllStates_noCrashes() {
+        final List<List<Object>> arguments = Lists.cartesianProduct(Arrays.asList(
+                mHingeAngleValues,
+                mLikelyTentModeValues,
+                mLikelyReverseWedgeModeValues
+        ));
+
+        arguments.forEach(objects -> {
+            final HingeAngle hingeAngle = (HingeAngle) objects.get(0);
+            final boolean likelyTent = (boolean) objects.get(1);
+            final boolean likelyReverseWedge = (boolean) objects.get(2);
+
+            final String description =
+                    "Input: hinge angle = " + hingeAngle + ", likelyTent = " + likelyTent
+                            + ", likelyReverseWedge = " + likelyReverseWedge;
+
+            // Verify that there are no crashes because of infinite state transitions and
+            // that it returns a valid active state
+            try {
+                PreferredScreen preferredScreen = mCalculator.calculatePreferredScreen(hingeAngle, likelyTent,
+                        likelyReverseWedge);
+
+                assertWithMessage(description).that(preferredScreen).isNotEqualTo(PreferredScreen.INVALID);
+            } catch (Throwable exception) {
+                throw new AssertionError(description, exception);
+            }
+        });
+    }
+}
diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java
index ddf4a08..04cebab 100644
--- a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java
+++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java
@@ -591,6 +591,20 @@
     }
 
     @Test
+    public void testOnDisplayAddedWithNullDisplayDoesNotThrowNPE() {
+        createProvider(
+                createConfig(
+                        /* identifier= */ 1, /* name= */ "ONE",
+                        /* flags= */0, (c) -> true,
+                        FoldableDeviceStateProvider::hasNoConnectedExternalDisplay)
+        );
+
+        when(mDisplayManager.getDisplay(1)).thenReturn(null);
+        // This call should not throw NPE.
+        mProvider.onDisplayAdded(1);
+    }
+
+    @Test
     public void hasNoConnectedDisplay_afterExternalDisplayAddedAndRemoved_returnsTrue() {
         createProvider(
                 createConfig(
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 86ad494..2b8bcc7 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -203,6 +203,7 @@
 import com.android.server.security.KeyAttestationApplicationIdProviderService;
 import com.android.server.security.KeyChainSystemService;
 import com.android.server.security.rkp.RemoteProvisioningService;
+import com.android.server.selinux.SelinuxAuditLogsService;
 import com.android.server.sensorprivacy.SensorPrivacyService;
 import com.android.server.sensors.SensorService;
 import com.android.server.signedconfig.SignedConfigService;
@@ -433,6 +434,9 @@
     private static final String ROLE_SERVICE_CLASS = "com.android.role.RoleService";
     private static final String GAME_MANAGER_SERVICE_CLASS =
             "com.android.server.app.GameManagerService$Lifecycle";
+    private static final String ENHANCED_CONFIRMATION_SERVICE_CLASS =
+            "com.android.ecm.EnhancedConfirmationService";
+
     private static final String UWB_APEX_SERVICE_JAR_PATH =
             "/apex/com.android.uwb/javalib/service-uwb.jar";
     private static final String UWB_SERVICE_CLASS = "com.android.server.uwb.UwbService";
@@ -1592,6 +1596,12 @@
             mSystemServiceManager.startService(DropBoxManagerService.class);
             t.traceEnd();
 
+            if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()) {
+                t.traceBegin("StartEnhancedConfirmationService");
+                mSystemServiceManager.startService(ENHANCED_CONFIRMATION_SERVICE_CLASS);
+                t.traceEnd();
+            }
+
             // Grants default permissions and defines roles
             t.traceBegin("StartRoleManagerService");
             LocalManagerRegistry.addManager(RoleServicePlatformHelper.class,
@@ -2609,6 +2619,14 @@
                 t.traceEnd();
             }
 
+            t.traceBegin("StartSelinuxAuditLogsService");
+            try {
+                SelinuxAuditLogsService.schedule(context);
+            } catch (Throwable e) {
+                reportWtf("starting SelinuxAuditLogsService", e);
+            }
+            t.traceEnd();
+
             // LauncherAppsService uses ShortcutService.
             t.traceBegin("StartShortcutServiceLifecycle");
             mSystemServiceManager.startService(ShortcutService.Lifecycle.class);
diff --git a/services/lint-baseline.xml b/services/lint-baseline.xml
index 8489c17..a311d07 100644
--- a/services/lint-baseline.xml
+++ b/services/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="8.1.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="SimpleManualPermissionEnforcement"
@@ -56,4 +56,4 @@
             column="13"/>
     </issue>
 
-</issues>
+</issues>
\ No newline at end of file
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
index 94caf28..c8a6545 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
@@ -17,6 +17,7 @@
 package com.android.server.permission.access.appop
 
 import android.app.AppOpsManager
+import android.util.Slog
 import com.android.server.permission.access.GetStateScope
 import com.android.server.permission.access.MutableAccessState
 import com.android.server.permission.access.MutateStateScope
@@ -84,6 +85,10 @@
         appOpName: String,
         mode: Int
     ): Boolean {
+        if (userId !in newState.userStates) {
+            Slog.e(LOG_TAG, "Unable to set app op mode for missing user $userId")
+            return false
+        }
         val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
         val oldMode =
             newState.userStates[userId]!!
@@ -152,4 +157,8 @@
          */
         abstract fun onStateMutated()
     }
+
+    companion object {
+        private val LOG_TAG = AppIdAppOpPolicy::class.java.simpleName
+    }
 }
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
index 0d9470e..2f15dc7 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
@@ -17,6 +17,7 @@
 package com.android.server.permission.access.appop
 
 import android.app.AppOpsManager
+import android.util.Slog
 import com.android.server.permission.access.GetStateScope
 import com.android.server.permission.access.MutableAccessState
 import com.android.server.permission.access.MutateStateScope
@@ -87,6 +88,10 @@
         appOpName: String,
         mode: Int
     ): Boolean {
+        if (userId !in newState.userStates) {
+            Slog.e(LOG_TAG, "Unable to set app op mode for missing user $userId")
+            return false
+        }
         val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
         val oldMode =
             newState.userStates[userId]!!
@@ -155,4 +160,8 @@
          */
         abstract fun onStateMutated()
     }
+
+    companion object {
+        private val LOG_TAG = PackageAppOpPolicy::class.java.simpleName
+    }
 }
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 022268d..4c74878 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -22,6 +22,7 @@
 import android.content.pm.PermissionInfo
 import android.content.pm.SigningDetails
 import android.os.Build
+import android.permission.flags.Flags
 import android.util.Slog
 import com.android.internal.os.RoSystemProperties
 import com.android.internal.pm.permission.CompatibilityPermissionInfo
@@ -134,7 +135,9 @@
     ) {
         val changedPermissionNames = MutableIndexedSet<String>()
         packageNames.forEachIndexed { _, packageName ->
-            val packageState = newState.externalState.packageStates[packageName]!!
+            // The package may still be removed even if it was once notified as installed.
+            val packageState = newState.externalState.packageStates[packageName]
+                ?: return@forEachIndexed
             adoptPermissions(packageState, changedPermissionNames)
             addPermissionGroups(packageState)
             addPermissions(packageState, changedPermissionNames)
@@ -147,12 +150,14 @@
         }
 
         packageNames.forEachIndexed { _, packageName ->
-            val packageState = newState.externalState.packageStates[packageName]!!
+            val packageState = newState.externalState.packageStates[packageName]
+                ?: return@forEachIndexed
             val installedPackageState = if (isSystemUpdated) packageState else null
             evaluateAllPermissionStatesForPackage(packageState, installedPackageState)
         }
         packageNames.forEachIndexed { _, packageName ->
-            val packageState = newState.externalState.packageStates[packageName]!!
+            val packageState = newState.externalState.packageStates[packageName]
+                ?: return@forEachIndexed
             newState.externalState.userIds.forEachIndexed { _, userId ->
                 inheritImplicitPermissionStates(packageState.appId, userId)
             }
@@ -1193,15 +1198,80 @@
             newState.externalState.packageStates[PLATFORM_PACKAGE_NAME]!!
                 .androidPackage!!
                 .signingDetails
-        return sourceSigningDetails?.hasCommonSignerWithCapability(
-            packageSigningDetails,
-            SigningDetails.CertCapabilities.PERMISSION
-        ) == true ||
-            packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) ||
-            platformSigningDetails.checkCapability(
+        val hasCommonSigner =
+            sourceSigningDetails?.hasCommonSignerWithCapability(
                 packageSigningDetails,
                 SigningDetails.CertCapabilities.PERMISSION
-            )
+            ) == true ||
+                packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) ||
+                platformSigningDetails.checkCapability(
+                    packageSigningDetails,
+                    SigningDetails.CertCapabilities.PERMISSION
+                )
+        if (!Flags.signaturePermissionAllowlistEnabled()) {
+            return hasCommonSigner;
+        }
+        if (!hasCommonSigner) {
+            return false
+        }
+        // A platform signature permission also needs to be allowlisted on non-debuggable builds.
+        if (permission.packageName == PLATFORM_PACKAGE_NAME) {
+            val isRequestedByFactoryApp =
+                if (packageState.isSystem) {
+                    // For updated system applications, a signature permission still needs to be
+                    // allowlisted if it wasn't requested by the original application.
+                    if (packageState.isUpdatedSystemApp) {
+                        val disabledSystemPackage =
+                            newState.externalState.disabledSystemPackageStates[
+                                    packageState.packageName]
+                                ?.androidPackage
+                        disabledSystemPackage != null &&
+                            permission.name in disabledSystemPackage.requestedPermissions
+                    } else {
+                        true
+                    }
+                } else {
+                    false
+                }
+            if (
+                !(isRequestedByFactoryApp ||
+                    getSignaturePermissionAllowlistState(packageState, permission.name) == true)
+            ) {
+                Slog.w(
+                    LOG_TAG,
+                    "Signature permission ${permission.name} for package" +
+                        " ${packageState.packageName} (${packageState.path}) not in" +
+                        " signature permission allowlist"
+                )
+                if (!Build.isDebuggable()) {
+                    return false
+                }
+            }
+        }
+        return true
+    }
+
+    private fun MutateStateScope.getSignaturePermissionAllowlistState(
+        packageState: PackageState,
+        permissionName: String
+    ): Boolean? {
+        val permissionAllowlist = newState.externalState.permissionAllowlist
+        val packageName = packageState.packageName
+        return when {
+            packageState.isVendor || packageState.isOdm ->
+                permissionAllowlist.getVendorSignatureAppAllowlistState(packageName, permissionName)
+            packageState.isProduct ->
+                permissionAllowlist.getProductSignatureAppAllowlistState(
+                    packageName,
+                    permissionName
+                )
+            packageState.isSystemExt ->
+                permissionAllowlist.getSystemExtSignatureAppAllowlistState(
+                    packageName,
+                    permissionName
+                )
+            else -> permissionAllowlist.getSignatureAppAllowlistState(packageName, permissionName)
+        }
     }
 
     private fun MutateStateScope.checkPrivilegedPermissionAllowlist(
@@ -1607,6 +1677,13 @@
         flagMask: Int,
         flagValues: Int
     ): Boolean {
+        if (userId !in newState.userStates) {
+            // Despite that we check UserManagerInternal.exists() in PermissionService, we may still
+            // sometimes get race conditions between that check and the actual mutateState() call.
+            // This should rarely happen but at least we should not crash.
+            Slog.e(LOG_TAG, "Unable to update permission flags for missing user $userId")
+            return false
+        }
         val oldFlags =
             newState.userStates[userId]!!
                 .appIdPermissionFlags[appId]
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
index a0fb013..3284cf1 100644
--- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
@@ -94,7 +94,9 @@
         isSystemUpdated: Boolean
     ) {
         packageNames.forEachIndexed { _, packageName ->
-            val packageState = newState.externalState.packageStates[packageName]!!
+            // The package may still be removed even if it was once notified as installed.
+            val packageState = newState.externalState.packageStates[packageName]
+                ?: return@forEachIndexed
             trimPermissionStates(packageState.appId)
         }
     }
@@ -127,7 +129,10 @@
         val packageState = newState.externalState.packageStates[packageName] ?: return
         val androidPackage = packageState.androidPackage ?: return
         val appId = packageState.appId
-        val appIdPermissionFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags
+        // The user may happen removed due to DeletePackageHelper.removeUnusedPackagesLPw() calling
+        // deletePackageX() asynchronously.
+        val userState = newState.userStates[userId] ?: return
+        val devicePermissionFlags = userState.appIdDevicePermissionFlags[appId] ?: return
         androidPackage.requestedPermissions.forEach { permissionName ->
             val isRequestedByOtherPackages =
                 anyPackageInAppId(appId) {
@@ -137,7 +142,7 @@
             if (isRequestedByOtherPackages) {
                 return@forEach
             }
-            appIdPermissionFlags[appId]?.forEachIndexed { _, deviceId, _ ->
+            devicePermissionFlags.forEachIndexed { _, deviceId, _ ->
                 setPermissionFlags(appId, deviceId, userId, permissionName, 0)
             }
         }
@@ -245,6 +250,13 @@
         flagMask: Int,
         flagValues: Int
     ): Boolean {
+        if (userId !in newState.userStates) {
+            // Despite that we check UserManagerInternal.exists() in PermissionService, we may still
+            // sometimes get race conditions between that check and the actual mutateState() call.
+            // This should rarely happen but at least we should not crash.
+            Slog.e(LOG_TAG, "Unable to update permission flags for missing user $userId")
+            return false
+        }
         val oldFlags =
             newState.userStates[userId]!!
                 .appIdDevicePermissionFlags[appId]
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index f469ab5..097d73a 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -960,8 +960,8 @@
 
         if (permissionName !in androidPackage.requestedPermissions && oldFlags == 0) {
             if (reportError) {
-                throw SecurityException(
-                    "Permission $permissionName isn't requested by package $packageName"
+                Slog.e(
+                    LOG_TAG, "Permission $permissionName isn't requested by package $packageName"
                 )
             }
             return
diff --git a/services/print/lint-baseline.xml b/services/print/lint-baseline.xml
index 1bf031a..11c0cc8e 100644
--- a/services/print/lint-baseline.xml
+++ b/services/print/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="8.1.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="SimpleManualPermissionEnforcement"
@@ -12,4 +12,4 @@
             column="13"/>
     </issue>
 
-</issues>
+</issues>
\ No newline at end of file
diff --git a/services/profcollect/Android.bp b/services/profcollect/Android.bp
index 2040bb6..fe431f5 100644
--- a/services/profcollect/Android.bp
+++ b/services/profcollect/Android.bp
@@ -22,24 +22,25 @@
 }
 
 filegroup {
-  name: "services.profcollect-javasources",
-  srcs: ["src/**/*.java"],
-  path: "src",
-  visibility: ["//frameworks/base/services"],
+    name: "services.profcollect-javasources",
+    srcs: ["src/**/*.java"],
+    path: "src",
+    visibility: ["//frameworks/base/services"],
 }
 
 filegroup {
-  name: "services.profcollect-sources",
-  srcs: [
-    ":services.profcollect-javasources",
-    ":profcollectd_aidl",
-  ],
-  visibility: ["//frameworks/base/services:__subpackages__"],
+    name: "services.profcollect-sources",
+    srcs: [
+        ":services.profcollect-javasources",
+        ":profcollectd_aidl",
+    ],
+    visibility: ["//frameworks/base/services:__subpackages__"],
 }
 
 java_library_static {
-  name: "services.profcollect",
-  defaults: ["platform_service_defaults"],
-  srcs: [":services.profcollect-sources"],
-  libs: ["services.core"],
+    name: "services.profcollect",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.profcollect-sources"],
+    static_libs: ["services.core"],
+    libs: ["service-art.stubs.system_server"],
 }
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 4007672..582b712 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -41,12 +41,15 @@
 import com.android.internal.R;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.IoThread;
+import com.android.server.LocalManagerRegistry;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.art.ArtManagerLocal;
 import com.android.server.wm.ActivityMetricsLaunchObserver;
 import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
+import java.util.concurrent.ForkJoinPool;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.TimeUnit;
 
@@ -261,6 +264,7 @@
         BackgroundThread.get().getThreadHandler().post(
                 () -> {
                     registerAppLaunchObserver();
+                    registerDex2oatObserver();
                     registerOTAObserver();
                 });
     }
@@ -304,6 +308,44 @@
         }
     }
 
+    private void registerDex2oatObserver() {
+        ArtManagerLocal aml = LocalManagerRegistry.getManager(ArtManagerLocal.class);
+        if (aml == null) {
+            Log.w(LOG_TAG, "Couldn't get ArtManagerLocal");
+            return;
+        }
+        aml.setBatchDexoptStartCallback(ForkJoinPool.commonPool(),
+                (snapshot, reason, defaultPackages, builder, passedSignal) -> {
+                    traceOnDex2oatStart();
+                });
+    }
+
+    private void traceOnDex2oatStart() {
+        if (mIProfcollect == null) {
+            return;
+        }
+        // Sample for a fraction of dex2oat runs.
+        final int traceFrequency =
+            DeviceConfig.getInt(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
+                "dex2oat_trace_freq", 10);
+        int randomNum = ThreadLocalRandom.current().nextInt(100);
+        if (randomNum < traceFrequency) {
+            if (DEBUG) {
+                Log.d(LOG_TAG, "Tracing on dex2oat event");
+            }
+            BackgroundThread.get().getThreadHandler().post(() -> {
+                try {
+                    // Dex2oat could take a while before it starts. Add a short delay before start
+                    // tracing.
+                    Thread.sleep(1000);
+                    mIProfcollect.trace_once("dex2oat");
+                } catch (RemoteException | InterruptedException e) {
+                    Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
+                }
+            });
+        }
+    }
+
     private void registerOTAObserver() {
         UpdateEngine updateEngine = new UpdateEngine();
         updateEngine.bind(new UpdateEngineCallback() {
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/Android.bp b/services/tests/BackgroundInstallControlServiceTests/host/Android.bp
index 4fcdbfc..d479e52 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/Android.bp
+++ b/services/tests/BackgroundInstallControlServiceTests/host/Android.bp
@@ -11,7 +11,6 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-
 package {
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java b/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java
index 7450607..c99e712 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java
+++ b/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java
@@ -41,17 +41,26 @@
     private static final String MOCK_APK_FILE_1 = "BackgroundInstallControlMockApp1.apk";
     private static final String MOCK_APK_FILE_2 = "BackgroundInstallControlMockApp2.apk";
 
+    // TODO: Move the silent installs to test-app using {@link
+    // BackgroundInstallControlServiceTest#installPackage(String, String)} and remove deviceConfig
+    // branch in BICS.
+    // b/310983905
     @Test
     public void testGetMockBackgroundInstalledPackages() throws Exception {
-        installPackage(TEST_DATA_DIR  + MOCK_APK_FILE_1);
+        installPackage(TEST_DATA_DIR + MOCK_APK_FILE_1);
         installPackage(TEST_DATA_DIR + MOCK_APK_FILE_2);
 
         assertThat(getDevice().getAppPackageInfo(MOCK_PACKAGE_NAME_1)).isNotNull();
         assertThat(getDevice().getAppPackageInfo(MOCK_PACKAGE_NAME_2)).isNotNull();
 
-        assertThat(getDevice().setProperty("debug.transparency.bg-install-apps",
-                    MOCK_PACKAGE_NAME_1 + "," + MOCK_PACKAGE_NAME_2)).isTrue();
-        runDeviceTest("testGetMockBackgroundInstalledPackages");
+        assertThat(
+                getDevice()
+                        .setProperty(
+                                "debug.transparency.bg-install-apps",
+                                MOCK_PACKAGE_NAME_1 + "," + MOCK_PACKAGE_NAME_2))
+                .isTrue();
+        runDeviceTest(
+                "BackgroundInstallControlServiceTest", "testGetMockBackgroundInstalledPackages");
         assertThat(getDevice().uninstallPackage(MOCK_PACKAGE_NAME_1)).isNull();
         assertThat(getDevice().uninstallPackage(MOCK_PACKAGE_NAME_2)).isNull();
 
@@ -65,10 +74,10 @@
         assertThat(result.getStatus() == CommandStatus.SUCCESS).isTrue();
     }
 
-    private void runDeviceTest(String method) throws DeviceNotAvailableException {
+    private void runDeviceTest(String testName, String method) throws DeviceNotAvailableException {
         var options = new DeviceTestRunOptions(PACKAGE_NAME);
-        options.setTestClassName(PACKAGE_NAME + ".BackgroundInstallControlServiceTest");
+        options.setTestClassName(PACKAGE_NAME + "." + testName);
         options.setTestMethodName(method);
         runDeviceTests(options);
     }
-}
+}
\ No newline at end of file
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml
index 1fa1f84..cbe58a8 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml
+++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml
@@ -24,4 +24,4 @@
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
         android:label="APCT tests for background install control service"
         android:targetPackage="com.android.server.pm.test.app" />
-</manifest>
+</manifest>
\ No newline at end of file
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java
index b74e561..b23f591 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java
+++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java
@@ -16,6 +16,10 @@
 
 package com.android.server.pm.test.app;
 
+import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
@@ -23,12 +27,15 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.UserHandle;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -39,31 +46,52 @@
 @RunWith(AndroidJUnit4.class)
 public class BackgroundInstallControlServiceTest {
     private static final String TAG = "BackgroundInstallControlServiceTest";
+    private static final String MOCK_PACKAGE_NAME = "com.android.servicestests.apps.bicmockapp3";
 
     private IBackgroundInstallControlService mIBics;
 
     @Before
     public void setUp() {
-        mIBics = IBackgroundInstallControlService.Stub.asInterface(
-                ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE));
+        mIBics =
+                IBackgroundInstallControlService.Stub.asInterface(
+                        ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE));
         assertThat(mIBics).isNotNull();
     }
 
+    @After
+    public void tearDown() {
+        runShellCommand("pm uninstall " + MOCK_PACKAGE_NAME);
+    }
+
     @Test
     public void testGetMockBackgroundInstalledPackages() throws RemoteException {
-        ParceledListSlice<PackageInfo> slice = mIBics.getBackgroundInstalledPackages(
-                    PackageManager.MATCH_ALL,
-                    UserHandle.USER_ALL);
+        ParceledListSlice<PackageInfo> slice =
+                ShellIdentityUtils.invokeMethodWithShellPermissions(
+                        mIBics,
+                        (bics) -> {
+                            try {
+                                return bics.getBackgroundInstalledPackages(
+                                        PackageManager.MATCH_ALL, Process.myUserHandle()
+                                                .getIdentifier());
+                            } catch (RemoteException e) {
+                                throw new RuntimeException(e);
+                            }
+                        },
+                        GET_BACKGROUND_INSTALLED_PACKAGES);
         assertThat(slice).isNotNull();
 
         var packageList = slice.getList();
         assertThat(packageList).isNotNull();
         assertThat(packageList).hasSize(2);
 
-        var expectedPackageNames = Set.of("com.android.servicestests.apps.bicmockapp1",
-                "com.android.servicestests.apps.bicmockapp2");
-        var actualPackageNames = packageList.stream().map((packageInfo) -> packageInfo.packageName)
-                .collect(Collectors.toSet());
+        var expectedPackageNames =
+                Set.of(
+                        "com.android.servicestests.apps.bicmockapp1",
+                        "com.android.servicestests.apps.bicmockapp2");
+        var actualPackageNames =
+                packageList.stream()
+                        .map((packageInfo) -> packageInfo.packageName)
+                        .collect(Collectors.toSet());
         assertThat(actualPackageNames).containsExactlyElementsIn(expectedPackageNames);
     }
-}
+}
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java
new file mode 100644
index 0000000..0edb3df
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.icu.util.ULocale;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+
+import java.io.File;
+import java.util.List;
+
+public final class AdditionalSubtypeUtilsTest {
+
+    @Test
+    public void testSaveAndLoad() throws Exception {
+        // Prepares the data to be saved.
+        InputMethodSubtype subtype1 = new InputMethodSubtype.InputMethodSubtypeBuilder()
+                .setSubtypeNameOverride("Subtype1")
+                .setLanguageTag("en-US")
+                .build();
+        InputMethodSubtype subtype2 = new InputMethodSubtype.InputMethodSubtypeBuilder()
+                .setSubtypeNameOverride("Subtype2")
+                .setLanguageTag("zh-CN")
+                .setPhysicalKeyboardHint(new ULocale("en_US"), "qwerty")
+                .build();
+        String fakeImeId = "fakeImeId";
+        ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+        methodMap.put(fakeImeId, new InputMethodInfo("", "", "", ""));
+        ArrayMap<String, List<InputMethodSubtype>> allSubtypes = new ArrayMap<>();
+        allSubtypes.put(fakeImeId, List.of(subtype1, subtype2));
+
+        // Save & load.
+        AtomicFile atomicFile = new AtomicFile(
+                new File(InstrumentationRegistry.getContext().getCacheDir(), "subtypes.xml"));
+        AdditionalSubtypeUtils.saveToFile(allSubtypes, InputMethodMap.of(methodMap), atomicFile);
+        ArrayMap<String, List<InputMethodSubtype>> loadedSubtypes = new ArrayMap<>();
+        AdditionalSubtypeUtils.loadFromFile(loadedSubtypes, atomicFile);
+
+        // Verifies the loaded data.
+        assertEquals(1, loadedSubtypes.size());
+        List<InputMethodSubtype> subtypes = loadedSubtypes.get(fakeImeId);
+        assertNotNull(subtypes);
+        assertEquals(2, subtypes.size());
+
+        verifySubtype(subtypes.get(0), subtype1);
+        verifySubtype(subtypes.get(1), subtype2);
+    }
+
+    private void verifySubtype(InputMethodSubtype subtype, InputMethodSubtype expectedSubtype) {
+        assertEquals(expectedSubtype.getLanguageTag(), subtype.getLanguageTag());
+        assertEquals(expectedSubtype.getPhysicalKeyboardHintLanguageTag(),
+                subtype.getPhysicalKeyboardHintLanguageTag());
+        assertEquals(expectedSubtype.getPhysicalKeyboardHintLayoutType(),
+                subtype.getPhysicalKeyboardHintLayoutType());
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
index 3c8f5c9..dc9631a 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
@@ -15,9 +15,17 @@
  */
 package com.android.server.inputmethod;
 
+import static com.android.server.inputmethod.ClientController.ClientControllerCallback;
+
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.pm.PackageManagerInternal;
@@ -38,12 +46,15 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 // This test is designed to run on both device and host (Ravenwood) side.
 public final class ClientControllerTest {
     private static final int ANY_DISPLAY_ID = Display.DEFAULT_DISPLAY;
     private static final int ANY_CALLER_UID = 1;
     private static final int ANY_CALLER_PID = 1;
+    private static final String SOME_PACKAGE_NAME = "some.package";
 
     @Rule
     public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
@@ -58,9 +69,6 @@
     @Mock
     private IRemoteInputConnection mConnection;
 
-    @Mock
-    private IBinder.DeathRecipient mDeathRecipient;
-
     private Handler mHandler;
 
     private ClientController mController;
@@ -68,30 +76,123 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        when(mClient.asBinder()).thenReturn((IBinder) mClient);
+
         mHandler = new Handler(Looper.getMainLooper());
         mController = new ClientController(mMockPackageManagerInternal);
-        when(mClient.asBinder()).thenReturn((IBinder) mClient);
     }
 
     @Test
-    // TODO(b/314150112): Enable host side mode for this test once b/315544364 is fixed.
+    // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
+    //  inputmethod server classes.
     @IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
     public void testAddClient_cannotAddTheSameClientTwice() {
         var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
 
         synchronized (ImfLock.class) {
-            mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, mDeathRecipient,
-                    ANY_CALLER_UID, ANY_CALLER_PID);
+            mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
+                    ANY_CALLER_PID);
 
             SecurityException thrown = assertThrows(SecurityException.class,
                     () -> {
                         synchronized (ImfLock.class) {
                             mController.addClient(invoker, mConnection, ANY_DISPLAY_ID,
-                                    mDeathRecipient, ANY_CALLER_UID, ANY_CALLER_PID);
+                                    ANY_CALLER_UID, ANY_CALLER_PID);
                         }
                     });
             assertThat(thrown.getMessage()).isEqualTo(
                     "uid=1/pid=1/displayId=0 is already registered");
         }
     }
+
+    @Test
+    // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
+    //  inputmethod server classes.
+    @IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
+    public void testAddClient() throws Exception {
+        synchronized (ImfLock.class) {
+            var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
+            var added = mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
+                    ANY_CALLER_PID);
+
+            verify(invoker.asBinder()).linkToDeath(any(IBinder.DeathRecipient.class), eq(0));
+            assertThat(mController.getClient(invoker.asBinder())).isSameInstanceAs(added);
+        }
+    }
+
+    @Test
+    // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
+    //  inputmethod server classes.
+    @IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
+    public void testRemoveClient() {
+        var callback = new TestClientControllerCallback();
+        ClientState added;
+        synchronized (ImfLock.class) {
+            mController.addClientControllerCallback(callback);
+
+            var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
+            added = mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
+                    ANY_CALLER_PID);
+            assertThat(mController.getClient(invoker.asBinder())).isSameInstanceAs(added);
+            assertThat(mController.removeClient(mClient)).isTrue();
+        }
+
+        // Test callback
+        var removed = callback.waitForRemovedClient(5, TimeUnit.SECONDS);
+        assertThat(removed).isSameInstanceAs(added);
+    }
+
+    @Test
+    // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
+    //  inputmethod server classes and updated to newer Mockito with static mock support (mock
+    //  InputMethodUtils#checkIfPackageBelongsToUid instead of PackageManagerInternal#isSameApp)
+    @IgnoreUnderRavenwood(blockedBy = {InputMethodUtils.class})
+    public void testVerifyClientAndPackageMatch() {
+        when(mMockPackageManagerInternal.isSameApp(eq(SOME_PACKAGE_NAME),  /* flags= */
+                anyLong(), eq(ANY_CALLER_UID), /* userId= */ anyInt())).thenReturn(true);
+
+        synchronized (ImfLock.class) {
+            var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
+            mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
+                    ANY_CALLER_PID);
+            assertThat(
+                    mController.verifyClientAndPackageMatch(mClient, SOME_PACKAGE_NAME)).isTrue();
+        }
+    }
+
+    @Test
+    public void testVerifyClientAndPackageMatch_unknownClient() {
+        synchronized (ImfLock.class) {
+            assertThrows(IllegalArgumentException.class,
+                    () -> {
+                        synchronized (ImfLock.class) {
+                            mController.verifyClientAndPackageMatch(mClient, SOME_PACKAGE_NAME);
+                        }
+                    });
+        }
+    }
+
+    private static class TestClientControllerCallback implements ClientControllerCallback {
+
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+
+        private ClientState mRemoved;
+
+        @Override
+        public void onClientRemoved(ClientState removed) {
+            mRemoved = removed;
+            mLatch.countDown();
+        }
+
+        ClientState waitForRemovedClient(long timeout, TimeUnit unit) {
+            try {
+                assertWithMessage("ClientController callback wasn't called on user removed").that(
+                        mLatch.await(timeout, unit)).isTrue();
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw new IllegalStateException("Unexpected thread interruption", e);
+            }
+            return mRemoved;
+        }
+    }
 }
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 438bea4..1c71a62 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -22,7 +22,6 @@
 import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT;
 import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SWITCH_USER;
 import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT;
-import static com.android.server.inputmethod.ClientController.ClientState;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_NOT_ALWAYS;
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java
new file mode 100644
index 0000000..b7223d6
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+public final class HardwareKeyboardShortcutControllerTest {
+
+    @Test
+    public void testForwardRotation() {
+        final List<String> handles = Arrays.asList("0", "1", "2", "3");
+        assertEquals("2", HardwareKeyboardShortcutController.getNeighborItem(handles, "1", true));
+        assertEquals("3", HardwareKeyboardShortcutController.getNeighborItem(handles, "2", true));
+        assertEquals("0", HardwareKeyboardShortcutController.getNeighborItem(handles, "3", true));
+    }
+
+    @Test
+    public void testBackwardRotation() {
+        final List<String> handles = Arrays.asList("0", "1", "2", "3");
+        assertEquals("0", HardwareKeyboardShortcutController.getNeighborItem(handles, "1", false));
+        assertEquals("3", HardwareKeyboardShortcutController.getNeighborItem(handles, "0", false));
+        assertEquals("2", HardwareKeyboardShortcutController.getNeighborItem(handles, "3", false));
+    }
+
+    @Test
+    public void testNotMatching() {
+        final List<String> handles = Arrays.asList("0", "1", "2", "3");
+        assertNull(HardwareKeyboardShortcutController.getNeighborItem(handles, "X", true));
+        assertNull(HardwareKeyboardShortcutController.getNeighborItem(handles, "X", false));
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java
index 7cbfc52..71752ba 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java
@@ -28,6 +28,7 @@
 import android.util.ArrayMap;
 import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
@@ -123,11 +124,11 @@
 
     private List<InputMethodInfo> filterInputMethodServices(List<ResolveInfo> resolveInfoList,
             List<String> enabledComponents) {
-        final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-        final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
-        InputMethodManagerService.filterInputMethodServices(new ArrayMap<>(), methodMap, methodList,
-                enabledComponents, mContext, resolveInfoList);
-        return methodList;
+        final ArrayMap<String, List<InputMethodSubtype>> emptyAdditionalSubtypeMap =
+                new ArrayMap<>();
+        final InputMethodMap methodMap = InputMethodManagerService.filterInputMethodServices(
+                emptyAdditionalSubtypeMap, enabledComponents, mContext, resolveInfoList);
+        return methodMap.values();
     }
 
     private ResolveInfo createFakeSystemResolveInfo(String packageName, String componentName) {
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
new file mode 100644
index 0000000..a33e52f
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+
+import org.junit.Test;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+public final class InputMethodManagerServiceTests {
+    static final int SYSTEM_DECORATION_SUPPORT_DISPLAY_ID = 2;
+    static final int NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID = 3;
+
+    static InputMethodManagerService.ImeDisplayValidator sChecker =
+            (displayId) -> {
+                switch (displayId) {
+                    case SYSTEM_DECORATION_SUPPORT_DISPLAY_ID:
+                        return DISPLAY_IME_POLICY_LOCAL;
+                    case NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID:
+                        return DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
+                    default:
+                        throw new IllegalArgumentException("Unknown displayId=" + displayId);
+                }
+            };
+
+    static InputMethodManagerService.ImeDisplayValidator sMustNotBeCalledChecker =
+            (displayId) -> {
+                fail("Should not pass to display config check for this test case.");
+                return DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
+            };
+
+    @Test
+    public void testComputeImeDisplayId_defaultDisplayId() {
+        // Make sure that there is a short-circuit for DEFAULT_DISPLAY.
+        assertEquals(DEFAULT_DISPLAY,
+                InputMethodManagerService.computeImeDisplayIdForTarget(
+                        DEFAULT_DISPLAY, sMustNotBeCalledChecker));
+    }
+
+    @Test
+    public void testComputeImeDisplayId_InvalidDisplayId() {
+        // Make sure that there is a short-circuit for INVALID_DISPLAY.
+        assertEquals(DEFAULT_DISPLAY,
+                InputMethodManagerService.computeImeDisplayIdForTarget(
+                        INVALID_DISPLAY, sMustNotBeCalledChecker));
+    }
+
+    @Test
+    public void testComputeImeDisplayId_noSystemDecorationSupportDisplay() {
+        // Presume display didn't support system decoration.
+        // Make sure IME displayId is DEFAULT_DISPLAY.
+        assertEquals(DEFAULT_DISPLAY,
+                InputMethodManagerService.computeImeDisplayIdForTarget(
+                        NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, sChecker));
+    }
+
+    @Test
+    public void testComputeImeDisplayId_withSystemDecorationSupportDisplay() {
+        // Presume display support system decoration.
+        // Make sure IME displayId is the same display.
+        assertEquals(SYSTEM_DECORATION_SUPPORT_DISPLAY_ID,
+                InputMethodManagerService.computeImeDisplayIdForTarget(
+                        SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, sChecker));
+    }
+
+    @Test
+    public void testSoftInputShowHideHistoryDump_withNulls_doesntThrow() {
+        var writer = new StringWriter();
+        var history = new InputMethodManagerService.SoftInputShowHideHistory();
+        history.addEntry(new InputMethodManagerService.SoftInputShowHideHistory.Entry(
+                null,
+                null,
+                null,
+                SOFT_INPUT_STATE_UNSPECIFIED,
+                SoftInputShowHideReason.SHOW_SOFT_INPUT,
+                false,
+                null,
+                null,
+                null,
+                null));
+
+        history.dump(new PrintWriter(writer), "" /* prefix */);
+
+        // Asserts that dump doesn't throw an NPE.
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSettingsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSettingsTest.java
new file mode 100644
index 0000000..75118ea
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSettingsTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+
+import android.text.TextUtils;
+import android.util.IntArray;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Test;
+
+public final class InputMethodSettingsTest {
+    private static void verifyUpdateEnabledImeString(@NonNull String expectedEnabledImeStr,
+            @NonNull String initialEnabledImeStr, @NonNull String imeId,
+            @NonNull String enabledSubtypeHashCodesStr) {
+        assertEquals(expectedEnabledImeStr,
+                InputMethodSettings.updateEnabledImeString(initialEnabledImeStr,
+                        imeId, createSubtypeHashCodeArrayFromStr(enabledSubtypeHashCodesStr)));
+    }
+
+    private static IntArray createSubtypeHashCodeArrayFromStr(String subtypeHashCodesStr) {
+        final IntArray subtypes = new IntArray();
+        final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
+                new TextUtils.SimpleStringSplitter(';');
+        if (TextUtils.isEmpty(subtypeHashCodesStr)) {
+            return subtypes;
+        }
+        imeSubtypeSplitter.setString(subtypeHashCodesStr);
+        while (imeSubtypeSplitter.hasNext()) {
+            subtypes.add(Integer.parseInt(imeSubtypeSplitter.next()));
+        }
+        return subtypes;
+    }
+
+    @Test
+    public void updateEnabledImeStringTest() {
+        // No change cases
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1",
+                "com.android/.ime1", "com.android/.ime1", "");
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1",
+                "com.android/.ime1", "com.android/.ime2", "");
+
+        // To enable subtypes
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1",
+                "com.android/.ime1", "com.android/.ime2", "");
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1;1",
+                "com.android/.ime1", "com.android/.ime1", "1");
+
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1;1;2;3",
+                "com.android/.ime1", "com.android/.ime1", "1;2;3");
+
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1;1;2;3:com.android/.ime2",
+                "com.android/.ime1:com.android/.ime2", "com.android/.ime1", "1;2;3");
+        verifyUpdateEnabledImeString(
+                "com.android/.ime0:com.android/.ime1;1;2;3",
+                "com.android/.ime0:com.android/.ime1", "com.android/.ime1", "1;2;3");
+        verifyUpdateEnabledImeString(
+                "com.android/.ime0:com.android/.ime1;1;2;3:com.android/.ime2",
+                "com.android/.ime0:com.android/.ime1:com.android/.ime2", "com.android/.ime1",
+                "1;2;3");
+
+        // To reset enabled subtypes
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1",
+                "com.android/.ime1;1", "com.android/.ime1", "");
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1",
+                "com.android/.ime1;1;2;3", "com.android/.ime1", "");
+        verifyUpdateEnabledImeString(
+                "com.android/.ime1:com.android/.ime2",
+                "com.android/.ime1;1;2;3:com.android/.ime2", "com.android/.ime1", "");
+
+        verifyUpdateEnabledImeString(
+                "com.android/.ime0:com.android/.ime1",
+                "com.android/.ime0:com.android/.ime1;1;2;3", "com.android/.ime1", "");
+        verifyUpdateEnabledImeString(
+                "com.android/.ime0:com.android/.ime1:com.android/.ime2",
+                "com.android/.ime0:com.android/.ime1;1;2;3:com.android/.ime2", "com.android/.ime1",
+                "");
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
new file mode 100644
index 0000000..fbe384a
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
+
+import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl;
+import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public final class InputMethodSubtypeSwitchingControllerTest {
+    private static final String DUMMY_PACKAGE_NAME = "dummy package name";
+    private static final String DUMMY_IME_LABEL = "dummy ime label";
+    private static final String DUMMY_SETTING_ACTIVITY_NAME = "";
+    private static final boolean DUMMY_IS_AUX_IME = false;
+    private static final boolean DUMMY_FORCE_DEFAULT = false;
+    private static final boolean DUMMY_IS_VR_IME = false;
+    private static final int DUMMY_IS_DEFAULT_RES_ID = 0;
+    private static final String SYSTEM_LOCALE = "en_US";
+    private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
+
+    private static InputMethodSubtype createDummySubtype(final String locale) {
+        final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder();
+        return builder.setSubtypeNameResId(0)
+                .setSubtypeIconResId(0)
+                .setSubtypeLocale(locale)
+                .setIsAsciiCapable(true)
+                .build();
+    }
+
+    private static void addDummyImeSubtypeListItems(List<ImeSubtypeListItem> items,
+            String imeName, String imeLabel, List<String> subtypeLocales,
+            boolean supportsSwitchingToNextInputMethod) {
+        final ResolveInfo ri = new ResolveInfo();
+        final ServiceInfo si = new ServiceInfo();
+        final ApplicationInfo ai = new ApplicationInfo();
+        ai.packageName = DUMMY_PACKAGE_NAME;
+        ai.enabled = true;
+        si.applicationInfo = ai;
+        si.enabled = true;
+        si.packageName = DUMMY_PACKAGE_NAME;
+        si.name = imeName;
+        si.exported = true;
+        si.nonLocalizedLabel = imeLabel;
+        ri.serviceInfo = si;
+        List<InputMethodSubtype> subtypes = null;
+        if (subtypeLocales != null) {
+            subtypes = new ArrayList<InputMethodSubtype>();
+            for (String subtypeLocale : subtypeLocales) {
+                subtypes.add(createDummySubtype(subtypeLocale));
+            }
+        }
+        final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME,
+                DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID,
+                DUMMY_FORCE_DEFAULT, supportsSwitchingToNextInputMethod, DUMMY_IS_VR_IME);
+        if (subtypes == null) {
+            items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi,
+                    NOT_A_SUBTYPE_ID, null, SYSTEM_LOCALE));
+        } else {
+            for (int i = 0; i < subtypes.size(); ++i) {
+                final String subtypeLocale = subtypeLocales.get(i);
+                items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale,
+                        SYSTEM_LOCALE));
+            }
+        }
+    }
+
+    private static ImeSubtypeListItem createDummyItem(ComponentName imeComponentName,
+            String imeName, String subtypeName, String subtypeLocale, int subtypeIndex,
+            String systemLocale) {
+        final ResolveInfo ri = new ResolveInfo();
+        final ServiceInfo si = new ServiceInfo();
+        final ApplicationInfo ai = new ApplicationInfo();
+        ai.packageName = imeComponentName.getPackageName();
+        ai.enabled = true;
+        si.applicationInfo = ai;
+        si.enabled = true;
+        si.packageName = imeComponentName.getPackageName();
+        si.name = imeComponentName.getClassName();
+        si.exported = true;
+        si.nonLocalizedLabel = DUMMY_IME_LABEL;
+        ri.serviceInfo = si;
+        ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+        subtypes.add(new InputMethodSubtypeBuilder()
+                .setSubtypeNameResId(0)
+                .setSubtypeIconResId(0)
+                .setSubtypeLocale(subtypeLocale)
+                .setIsAsciiCapable(true)
+                .build());
+        final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME,
+                DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID,
+                DUMMY_FORCE_DEFAULT, true /* supportsSwitchingToNextInputMethod */,
+                DUMMY_IS_VR_IME);
+        return new ImeSubtypeListItem(imeName, subtypeName, imi, subtypeIndex, subtypeLocale,
+                systemLocale);
+    }
+
+    private static List<ImeSubtypeListItem> createEnabledImeSubtypes() {
+        final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>();
+        addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr"),
+                true /* supportsSwitchingToNextInputMethod*/);
+        addDummyImeSubtypeListItems(items, "switchUnawareLatinIme", "switchUnawareLatinIme",
+                Arrays.asList("en_UK", "hi"),
+                false /* supportsSwitchingToNextInputMethod*/);
+        addDummyImeSubtypeListItems(items, "subtypeUnawareIme", "subtypeUnawareIme", null,
+                false /* supportsSwitchingToNextInputMethod*/);
+        addDummyImeSubtypeListItems(items, "JapaneseIme", "JapaneseIme", Arrays.asList("ja_JP"),
+                true /* supportsSwitchingToNextInputMethod*/);
+        addDummyImeSubtypeListItems(items, "switchUnawareJapaneseIme", "switchUnawareJapaneseIme",
+                Arrays.asList("ja_JP"), false /* supportsSwitchingToNextInputMethod*/);
+        return items;
+    }
+
+    private static List<ImeSubtypeListItem> createDisabledImeSubtypes() {
+        final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>();
+        addDummyImeSubtypeListItems(items,
+                "UnknownIme", "UnknownIme",
+                Arrays.asList("en_US", "hi"),
+                true /* supportsSwitchingToNextInputMethod*/);
+        addDummyImeSubtypeListItems(items,
+                "UnknownSwitchingUnawareIme", "UnknownSwitchingUnawareIme",
+                Arrays.asList("en_US"),
+                false /* supportsSwitchingToNextInputMethod*/);
+        addDummyImeSubtypeListItems(items, "UnknownSubtypeUnawareIme",
+                "UnknownSubtypeUnawareIme", null,
+                false /* supportsSwitchingToNextInputMethod*/);
+        return items;
+    }
+
+    private void assertNextInputMethod(final ControllerImpl controller,
+            final boolean onlyCurrentIme,
+            final ImeSubtypeListItem currentItem, final ImeSubtypeListItem nextItem) {
+        InputMethodSubtype subtype = null;
+        if (currentItem.mSubtypeName != null) {
+            subtype = createDummySubtype(currentItem.mSubtypeName.toString());
+        }
+        final ImeSubtypeListItem nextIme = controller.getNextInputMethod(onlyCurrentIme,
+                currentItem.mImi, subtype);
+        assertEquals(nextItem, nextIme);
+    }
+
+    private void assertRotationOrder(final ControllerImpl controller,
+            final boolean onlyCurrentIme,
+            final ImeSubtypeListItem... expectedRotationOrderOfImeSubtypeList) {
+        final int numItems = expectedRotationOrderOfImeSubtypeList.length;
+        for (int i = 0; i < numItems; i++) {
+            final int currentIndex = i;
+            final int nextIndex = (currentIndex + 1) % numItems;
+            final ImeSubtypeListItem currentItem =
+                    expectedRotationOrderOfImeSubtypeList[currentIndex];
+            final ImeSubtypeListItem nextItem = expectedRotationOrderOfImeSubtypeList[nextIndex];
+            assertNextInputMethod(controller, onlyCurrentIme, currentItem, nextItem);
+        }
+    }
+
+    private void onUserAction(final ControllerImpl controller,
+            final ImeSubtypeListItem subtypeListItem) {
+        InputMethodSubtype subtype = null;
+        if (subtypeListItem.mSubtypeName != null) {
+            subtype = createDummySubtype(subtypeListItem.mSubtypeName.toString());
+        }
+        controller.onUserActionLocked(subtypeListItem.mImi, subtype);
+    }
+
+    @Test
+    public void testControllerImpl() throws Exception {
+        final List<ImeSubtypeListItem> disabledItems = createDisabledImeSubtypes();
+        final ImeSubtypeListItem disabledIme_en_us = disabledItems.get(0);
+        final ImeSubtypeListItem disabledIme_hi = disabledItems.get(1);
+        final ImeSubtypeListItem disabledSwitchingUnawareIme = disabledItems.get(2);
+        final ImeSubtypeListItem disabledSubtypeUnawareIme = disabledItems.get(3);
+
+        final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes();
+        final ImeSubtypeListItem latinIme_en_us = enabledItems.get(0);
+        final ImeSubtypeListItem latinIme_fr = enabledItems.get(1);
+        final ImeSubtypeListItem switchingUnawareLatinIme_en_uk = enabledItems.get(2);
+        final ImeSubtypeListItem switchingUnawareLatinIme_hi = enabledItems.get(3);
+        final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4);
+        final ImeSubtypeListItem japaneseIme_ja_jp = enabledItems.get(5);
+        final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(6);
+
+        final ControllerImpl controller = ControllerImpl.createFrom(
+                null /* currentInstance */, enabledItems);
+
+        // switching-aware loop
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
+                latinIme_en_us, latinIme_fr, japaneseIme_ja_jp);
+
+        // switching-unaware loop
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
+                switchingUnawareLatinIme_en_uk, switchingUnawareLatinIme_hi, subtypeUnawareIme,
+                switchUnawareJapaneseIme_ja_jp);
+
+        // test onlyCurrentIme == true
+        assertRotationOrder(controller, true /* onlyCurrentIme */,
+                latinIme_en_us, latinIme_fr);
+        assertRotationOrder(controller, true /* onlyCurrentIme */,
+                switchingUnawareLatinIme_en_uk, switchingUnawareLatinIme_hi);
+        assertNextInputMethod(controller, true /* onlyCurrentIme */,
+                subtypeUnawareIme, null);
+        assertNextInputMethod(controller, true /* onlyCurrentIme */,
+                japaneseIme_ja_jp, null);
+        assertNextInputMethod(controller, true /* onlyCurrentIme */,
+                switchUnawareJapaneseIme_ja_jp, null);
+
+        // Make sure that disabled IMEs are not accepted.
+        assertNextInputMethod(controller, false /* onlyCurrentIme */,
+                disabledIme_en_us, null);
+        assertNextInputMethod(controller, false /* onlyCurrentIme */,
+                disabledIme_hi, null);
+        assertNextInputMethod(controller, false /* onlyCurrentIme */,
+                disabledSwitchingUnawareIme, null);
+        assertNextInputMethod(controller, false /* onlyCurrentIme */,
+                disabledSubtypeUnawareIme, null);
+        assertNextInputMethod(controller, true /* onlyCurrentIme */,
+                disabledIme_en_us, null);
+        assertNextInputMethod(controller, true /* onlyCurrentIme */,
+                disabledIme_hi, null);
+        assertNextInputMethod(controller, true /* onlyCurrentIme */,
+                disabledSwitchingUnawareIme, null);
+        assertNextInputMethod(controller, true /* onlyCurrentIme */,
+                disabledSubtypeUnawareIme, null);
+    }
+
+    @Test
+    public void testControllerImplWithUserAction() throws Exception {
+        final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes();
+        final ImeSubtypeListItem latinIme_en_us = enabledItems.get(0);
+        final ImeSubtypeListItem latinIme_fr = enabledItems.get(1);
+        final ImeSubtypeListItem switchingUnawarelatinIme_en_uk = enabledItems.get(2);
+        final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3);
+        final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4);
+        final ImeSubtypeListItem japaneseIme_ja_jp = enabledItems.get(5);
+        final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(6);
+
+        final ControllerImpl controller = ControllerImpl.createFrom(
+                null /* currentInstance */, enabledItems);
+
+        // === switching-aware loop ===
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
+                latinIme_en_us, latinIme_fr, japaneseIme_ja_jp);
+        // Then notify that a user did something for latinIme_fr.
+        onUserAction(controller, latinIme_fr);
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
+                latinIme_fr, latinIme_en_us, japaneseIme_ja_jp);
+        // Then notify that a user did something for latinIme_fr again.
+        onUserAction(controller, latinIme_fr);
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
+                latinIme_fr, latinIme_en_us, japaneseIme_ja_jp);
+        // Then notify that a user did something for japaneseIme_ja_JP.
+        onUserAction(controller, latinIme_fr);
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
+                japaneseIme_ja_jp, latinIme_fr, latinIme_en_us);
+        // Check onlyCurrentIme == true.
+        assertNextInputMethod(controller, true /* onlyCurrentIme */,
+                japaneseIme_ja_jp, null);
+        assertRotationOrder(controller, true /* onlyCurrentIme */,
+                latinIme_fr, latinIme_en_us);
+        assertRotationOrder(controller, true /* onlyCurrentIme */,
+                latinIme_en_us, latinIme_fr);
+
+        // === switching-unaware loop ===
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
+                switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi, subtypeUnawareIme,
+                switchUnawareJapaneseIme_ja_jp);
+        // User action should be ignored for switching unaware IMEs.
+        onUserAction(controller, switchingUnawarelatinIme_hi);
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
+                switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi, subtypeUnawareIme,
+                switchUnawareJapaneseIme_ja_jp);
+        // User action should be ignored for switching unaware IMEs.
+        onUserAction(controller, switchUnawareJapaneseIme_ja_jp);
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
+                switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi, subtypeUnawareIme,
+                switchUnawareJapaneseIme_ja_jp);
+        // Check onlyCurrentIme == true.
+        assertRotationOrder(controller, true /* onlyCurrentIme */,
+                switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi);
+        assertNextInputMethod(controller, true /* onlyCurrentIme */,
+                subtypeUnawareIme, null);
+        assertNextInputMethod(controller, true /* onlyCurrentIme */,
+                switchUnawareJapaneseIme_ja_jp, null);
+
+        // Rotation order should be preserved when created with the same subtype list.
+        final List<ImeSubtypeListItem> sameEnabledItems = createEnabledImeSubtypes();
+        final ControllerImpl newController = ControllerImpl.createFrom(controller,
+                sameEnabledItems);
+        assertRotationOrder(newController, false /* onlyCurrentIme */,
+                japaneseIme_ja_jp, latinIme_fr, latinIme_en_us);
+        assertRotationOrder(newController, false /* onlyCurrentIme */,
+                switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi, subtypeUnawareIme,
+                switchUnawareJapaneseIme_ja_jp);
+
+        // Rotation order should be initialized when created with a different subtype list.
+        final List<ImeSubtypeListItem> differentEnabledItems = Arrays.asList(
+                latinIme_en_us, latinIme_fr, switchingUnawarelatinIme_en_uk,
+                switchUnawareJapaneseIme_ja_jp);
+        final ControllerImpl anotherController = ControllerImpl.createFrom(controller,
+                differentEnabledItems);
+        assertRotationOrder(anotherController, false /* onlyCurrentIme */,
+                latinIme_en_us, latinIme_fr);
+        assertRotationOrder(anotherController, false /* onlyCurrentIme */,
+                switchingUnawarelatinIme_en_uk, switchUnawareJapaneseIme_ja_jp);
+    }
+
+    @Test
+    public void testImeSubtypeListItem() throws Exception {
+        final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>();
+        addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme",
+                Arrays.asList("en_US", "fr", "en", "en_uk", "enn", "e", "EN_US"),
+                true /* supportsSwitchingToNextInputMethod*/);
+        final ImeSubtypeListItem item_en_us = items.get(0);
+        final ImeSubtypeListItem item_fr = items.get(1);
+        final ImeSubtypeListItem item_en = items.get(2);
+        final ImeSubtypeListItem item_enn = items.get(3);
+        final ImeSubtypeListItem item_e = items.get(4);
+        final ImeSubtypeListItem item_en_us_allcaps = items.get(5);
+
+        assertTrue(item_en_us.mIsSystemLocale);
+        assertFalse(item_fr.mIsSystemLocale);
+        assertFalse(item_en.mIsSystemLocale);
+        assertFalse(item_en.mIsSystemLocale);
+        assertFalse(item_enn.mIsSystemLocale);
+        assertFalse(item_e.mIsSystemLocale);
+        assertFalse(item_en_us_allcaps.mIsSystemLocale);
+
+        assertTrue(item_en_us.mIsSystemLanguage);
+        assertFalse(item_fr.mIsSystemLanguage);
+        assertTrue(item_en.mIsSystemLanguage);
+        assertFalse(item_enn.mIsSystemLocale);
+        assertFalse(item_e.mIsSystemLocale);
+        assertFalse(item_en_us_allcaps.mIsSystemLocale);
+    }
+
+    @SuppressWarnings("SelfComparison")
+    @Test
+    public void testImeSubtypeListComparator() throws Exception {
+        final ComponentName imeX1 = new ComponentName("com.example.imeX", "Ime1");
+        final ComponentName imeX2 = new ComponentName("com.example.imeX", "Ime2");
+        final ComponentName imeY1 = new ComponentName("com.example.imeY", "Ime1");
+        final ComponentName imeZ1 = new ComponentName("com.example.imeZ", "Ime1");
+        {
+            final List<ImeSubtypeListItem> items = Arrays.asList(
+                    // Subtypes of two IMEs that have the same display name "X".
+                    // Subtypes that has the same locale of the system's.
+                    createDummyItem(imeX1, "X", "E", "en_US", 0, "en_US"),
+                    createDummyItem(imeX2, "X", "E", "en_US", 0, "en_US"),
+                    createDummyItem(imeX1, "X", "Z", "en_US", 3, "en_US"),
+                    createDummyItem(imeX2, "X", "Z", "en_US", 3, "en_US"),
+                    createDummyItem(imeX1, "X", "", "en_US", 6, "en_US"),
+                    createDummyItem(imeX2, "X", "", "en_US", 6, "en_US"),
+                    // Subtypes that has the same language of the system's.
+                    createDummyItem(imeX1, "X", "E", "en", 1, "en_US"),
+                    createDummyItem(imeX2, "X", "E", "en", 1, "en_US"),
+                    createDummyItem(imeX1, "X", "Z", "en", 4, "en_US"),
+                    createDummyItem(imeX2, "X", "Z", "en", 4, "en_US"),
+                    createDummyItem(imeX1, "X", "", "en", 7, "en_US"),
+                    createDummyItem(imeX2, "X", "", "en", 7, "en_US"),
+                    // Subtypes that has different language than the system's.
+                    createDummyItem(imeX1, "X", "A", "hi_IN", 27, "en_US"),
+                    createDummyItem(imeX2, "X", "A", "hi_IN", 27, "en_US"),
+                    createDummyItem(imeX1, "X", "E", "ja", 2, "en_US"),
+                    createDummyItem(imeX2, "X", "E", "ja", 2, "en_US"),
+                    createDummyItem(imeX1, "X", "Z", "ja", 5, "en_US"),
+                    createDummyItem(imeX2, "X", "Z", "ja", 5, "en_US"),
+                    createDummyItem(imeX1, "X", "", "ja", 8, "en_US"),
+                    createDummyItem(imeX2, "X", "", "ja", 8, "en_US"),
+
+                    // Subtypes of IME "Y".
+                    // Subtypes that has the same locale of the system's.
+                    createDummyItem(imeY1, "Y", "E", "en_US", 9, "en_US"),
+                    createDummyItem(imeY1, "Y", "Z", "en_US", 12, "en_US"),
+                    createDummyItem(imeY1, "Y", "", "en_US", 15, "en_US"),
+                    // Subtypes that has the same language of the system's.
+                    createDummyItem(imeY1, "Y", "E", "en", 10, "en_US"),
+                    createDummyItem(imeY1, "Y", "Z", "en", 13, "en_US"),
+                    createDummyItem(imeY1, "Y", "", "en", 16, "en_US"),
+                    // Subtypes that has different language than the system's.
+                    createDummyItem(imeY1, "Y", "A", "hi_IN", 28, "en_US"),
+                    createDummyItem(imeY1, "Y", "E", "ja", 11, "en_US"),
+                    createDummyItem(imeY1, "Y", "Z", "ja", 14, "en_US"),
+                    createDummyItem(imeY1, "Y", "", "ja", 17, "en_US"),
+
+                    // Subtypes of IME Z.
+                    // Subtypes that has the same locale of the system's.
+                    createDummyItem(imeZ1, "", "E", "en_US", 18, "en_US"),
+                    createDummyItem(imeZ1, "", "Z", "en_US", 21, "en_US"),
+                    createDummyItem(imeZ1, "", "", "en_US", 24, "en_US"),
+                    // Subtypes that has the same language of the system's.
+                    createDummyItem(imeZ1, "", "E", "en", 19, "en_US"),
+                    createDummyItem(imeZ1, "", "Z", "en", 22, "en_US"),
+                    createDummyItem(imeZ1, "", "", "en", 25, "en_US"),
+                    // Subtypes that has different language than the system's.
+                    createDummyItem(imeZ1, "", "A", "hi_IN", 29, "en_US"),
+                    createDummyItem(imeZ1, "", "E", "ja", 20, "en_US"),
+                    createDummyItem(imeZ1, "", "Z", "ja", 23, "en_US"),
+                    createDummyItem(imeZ1, "", "", "ja", 26, "en_US"));
+
+            // Ensure {@link java.lang.Comparable#compareTo} contracts are satisfied.
+            for (int i = 0; i < items.size(); ++i) {
+                final ImeSubtypeListItem item1 = items.get(i);
+                // Ensures sgn(x.compareTo(y)) == -sgn(y.compareTo(x)).
+                assertTrue(item1 + " has the same order of itself", item1.compareTo(item1) == 0);
+                // Ensures (x.compareTo(y) > 0 && y.compareTo(z) > 0) implies x.compareTo(z) > 0.
+                for (int j = i + 1; j < items.size(); ++j) {
+                    final ImeSubtypeListItem item2 = items.get(j);
+                    // Ensures sgn(x.compareTo(y)) == -sgn(y.compareTo(x)).
+                    assertTrue(item1 + " is less than " + item2, item1.compareTo(item2) < 0);
+                    assertTrue(item2 + " is greater than " + item1, item2.compareTo(item1) > 0);
+                }
+            }
+        }
+
+        {
+            // Following two items have the same priority.
+            final ImeSubtypeListItem nonSystemLocale1 =
+                    createDummyItem(imeX1, "X", "A", "ja_JP", 0, "en_US");
+            final ImeSubtypeListItem nonSystemLocale2 =
+                    createDummyItem(imeX1, "X", "A", "hi_IN", 1, "en_US");
+            assertTrue(nonSystemLocale1.compareTo(nonSystemLocale2) == 0);
+            assertTrue(nonSystemLocale2.compareTo(nonSystemLocale1) == 0);
+            // But those aren't equal to each other.
+            assertFalse(nonSystemLocale1.equals(nonSystemLocale2));
+            assertFalse(nonSystemLocale2.equals(nonSystemLocale1));
+        }
+
+        {
+            // Check if ComponentName is also taken into account when comparing two items.
+            final ImeSubtypeListItem ime1 = createDummyItem(imeX1, "X", "A", "ja_JP", 0, "en_US");
+            final ImeSubtypeListItem ime2 = createDummyItem(imeX2, "X", "A", "ja_JP", 0, "en_US");
+            assertTrue(ime1.compareTo(ime2) < 0);
+            assertTrue(ime2.compareTo(ime1) > 0);
+            // But those aren't equal to each other.
+            assertFalse(ime1.equals(ime2));
+            assertFalse(ime2.equals(ime1));
+        }
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
new file mode 100644
index 0000000..2857619
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -0,0 +1,1261 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.in;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.util.ArrayMap;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
+
+import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.inputmethod.StartInputFlags;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+public final class InputMethodUtilsTest {
+    private static final boolean IS_AUX = true;
+    private static final boolean IS_DEFAULT = true;
+    private static final boolean IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE = true;
+    private static final boolean IS_ASCII_CAPABLE = true;
+    private static final boolean IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE = true;
+    private static final boolean CHECK_COUNTRY = true;
+    private static final Locale LOCALE_EN = new Locale("en");
+    private static final Locale LOCALE_EN_US = new Locale("en", "US");
+    private static final Locale LOCALE_EN_GB = new Locale("en", "GB");
+    private static final Locale LOCALE_EN_IN = new Locale("en", "IN");
+    private static final Locale LOCALE_FI = new Locale("fi");
+    private static final Locale LOCALE_FI_FI = new Locale("fi", "FI");
+    private static final Locale LOCALE_FIL = new Locale("fil");
+    private static final Locale LOCALE_FIL_PH = new Locale("fil", "PH");
+    private static final Locale LOCALE_FR = new Locale("fr");
+    private static final Locale LOCALE_FR_CA = new Locale("fr", "CA");
+    private static final Locale LOCALE_HI = new Locale("hi");
+    private static final Locale LOCALE_JA_JP = new Locale("ja", "JP");
+    private static final Locale LOCALE_ZH_CN = new Locale("zh", "CN");
+    private static final Locale LOCALE_ZH_TW = new Locale("zh", "TW");
+    private static final Locale LOCALE_IN = new Locale("in");
+    private static final Locale LOCALE_ID = new Locale("id");
+    private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
+    private static final String SUBTYPE_MODE_VOICE = "voice";
+    private static final String SUBTYPE_MODE_HANDWRITING = "handwriting";
+    private static final String SUBTYPE_MODE_ANY = null;
+    private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
+    private static final String EXTRA_VALUE_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
+            "EnabledWhenDefaultIsNotAsciiCapable";
+
+    @Test
+    public void testVoiceImes() throws Exception {
+        // locale: en_US
+        assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US,
+                "FakeDefaultEnKeyboardIme", "FakeDefaultAutoVoiceIme");
+        assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US,
+                "FakeDefaultEnKeyboardIme", "FakeNonDefaultAutoVoiceIme0",
+                "FakeNonDefaultAutoVoiceIme1");
+        assertDefaultEnabledMinimumImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US,
+                "FakeDefaultEnKeyboardIme");
+        assertDefaultEnabledMinimumImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US,
+                "FakeDefaultEnKeyboardIme");
+
+        // locale: en_GB
+        assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB,
+                "FakeDefaultEnKeyboardIme", "FakeDefaultAutoVoiceIme");
+        assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB,
+                "FakeDefaultEnKeyboardIme", "FakeNonDefaultAutoVoiceIme0",
+                "FakeNonDefaultAutoVoiceIme1");
+        assertDefaultEnabledMinimumImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB,
+                "FakeDefaultEnKeyboardIme");
+        assertDefaultEnabledMinimumImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB,
+                "FakeDefaultEnKeyboardIme");
+
+        // locale: ja_JP
+        assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP,
+                "FakeDefaultEnKeyboardIme", "FakeDefaultAutoVoiceIme");
+        assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP,
+                "FakeDefaultEnKeyboardIme", "FakeNonDefaultAutoVoiceIme0",
+                "FakeNonDefaultAutoVoiceIme1");
+        assertDefaultEnabledMinimumImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP,
+                "FakeDefaultEnKeyboardIme");
+        assertDefaultEnabledMinimumImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP,
+                "FakeDefaultEnKeyboardIme");
+    }
+
+    @Test
+    public void testKeyboardImes() throws Exception {
+        // locale: en_US
+        assertDefaultEnabledImes(getSamplePreinstalledImes("en-rUS"), LOCALE_EN_US,
+                "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
+        assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("en-rUS"), LOCALE_EN_US,
+                "com.android.apps.inputmethod.latin");
+
+        // locale: en_GB
+        assertDefaultEnabledImes(getSamplePreinstalledImes("en-rGB"), LOCALE_EN_GB,
+                "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
+        assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("en-rGB"), LOCALE_EN_GB,
+                "com.android.apps.inputmethod.latin");
+
+        // locale: en_IN
+        assertDefaultEnabledImes(getSamplePreinstalledImes("en-rIN"), LOCALE_EN_IN,
+                "com.android.apps.inputmethod.hindi",
+                "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
+        assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("en-rIN"), LOCALE_EN_IN,
+                "com.android.apps.inputmethod.hindi",
+                "com.android.apps.inputmethod.latin");
+
+        // locale: hi
+        assertDefaultEnabledImes(getSamplePreinstalledImes("hi"), LOCALE_HI,
+                "com.android.apps.inputmethod.hindi", "com.android.apps.inputmethod.latin",
+                "com.android.apps.inputmethod.voice");
+        assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("hi"), LOCALE_HI,
+                "com.android.apps.inputmethod.hindi", "com.android.apps.inputmethod.latin");
+
+        // locale: ja_JP
+        assertDefaultEnabledImes(getSamplePreinstalledImes("ja-rJP"), LOCALE_JA_JP,
+                "com.android.apps.inputmethod.japanese", "com.android.apps.inputmethod.voice");
+        assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("ja-rJP"), LOCALE_JA_JP,
+                "com.android.apps.inputmethod.japanese");
+
+        // locale: zh_CN
+        assertDefaultEnabledImes(getSamplePreinstalledImes("zh-rCN"), LOCALE_ZH_CN,
+                "com.android.apps.inputmethod.pinyin", "com.android.apps.inputmethod.voice");
+        assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("zh-rCN"), LOCALE_ZH_CN,
+                "com.android.apps.inputmethod.pinyin");
+
+        // locale: zh_TW
+        // Note: In this case, no IME is suitable for the system locale. Hence we will pick up a
+        // fallback IME regardless of the "default" attribute.
+        assertDefaultEnabledImes(getSamplePreinstalledImes("zh-rTW"), LOCALE_ZH_TW,
+                "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
+        assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("zh-rTW"), LOCALE_ZH_TW,
+                "com.android.apps.inputmethod.latin");
+    }
+
+    @Test
+    public void testParcelable() throws Exception {
+        final ArrayList<InputMethodInfo> originalList = getSamplePreinstalledImes("en-rUS");
+        final List<InputMethodInfo> clonedList = cloneViaParcel(originalList);
+        assertNotNull(clonedList);
+        final List<InputMethodInfo> clonedClonedList = cloneViaParcel(clonedList);
+        assertNotNull(clonedClonedList);
+        assertEquals(originalList, clonedList);
+        assertEquals(clonedList, clonedClonedList);
+        assertEquals(originalList.size(), clonedList.size());
+        assertEquals(clonedList.size(), clonedClonedList.size());
+        for (int imeIndex = 0; imeIndex < originalList.size(); ++imeIndex) {
+            verifyEquality(originalList.get(imeIndex), clonedList.get(imeIndex));
+            verifyEquality(clonedList.get(imeIndex), clonedClonedList.get(imeIndex));
+        }
+    }
+
+    @Test
+    public void testGetImplicitlyApplicableSubtypesLocked() throws Exception {
+        final InputMethodSubtype nonAutoEnUS = createFakeInputMethodSubtype("en_US",
+                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
+                IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        final InputMethodSubtype nonAutoEnGB = createFakeInputMethodSubtype("en_GB",
+                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
+                IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        final InputMethodSubtype nonAutoFrCA = createFakeInputMethodSubtype("fr_CA",
+                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
+                IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        final InputMethodSubtype nonAutoFr = createFakeInputMethodSubtype("fr_CA",
+                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
+                IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        final InputMethodSubtype nonAutoFil = createFakeInputMethodSubtype("fil",
+                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
+                IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        final InputMethodSubtype nonAutoIn = createFakeInputMethodSubtype("in",
+                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
+                IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        final InputMethodSubtype nonAutoId = createFakeInputMethodSubtype("id",
+                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
+                IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        final InputMethodSubtype autoSubtype = createFakeInputMethodSubtype("auto",
+                SUBTYPE_MODE_KEYBOARD, !IS_AUX, IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
+                IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        final InputMethodSubtype nonAutoJa = createFakeInputMethodSubtype("ja",
+                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
+                !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        final InputMethodSubtype nonAutoHi = createFakeInputMethodSubtype("hi",
+                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
+                !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        final InputMethodSubtype nonAutoSrCyrl = createFakeInputMethodSubtype("sr",
+                "sr-Cyrl", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+                !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
+                !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        final InputMethodSubtype nonAutoSrLatn = createFakeInputMethodSubtype("sr_ZZ",
+                "sr-Latn", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+                !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
+                !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        final InputMethodSubtype nonAutoHandwritingEn = createFakeInputMethodSubtype("en",
+                SUBTYPE_MODE_HANDWRITING, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
+                !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        final InputMethodSubtype nonAutoHandwritingFr = createFakeInputMethodSubtype("fr",
+                SUBTYPE_MODE_HANDWRITING, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
+                !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        final InputMethodSubtype nonAutoHandwritingSrCyrl = createFakeInputMethodSubtype("sr",
+                "sr-Cyrl", SUBTYPE_MODE_HANDWRITING, !IS_AUX,
+                !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
+                !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        final InputMethodSubtype nonAutoHandwritingSrLatn = createFakeInputMethodSubtype("sr_ZZ",
+                "sr-Latn", SUBTYPE_MODE_HANDWRITING, !IS_AUX,
+                !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
+                !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        final InputMethodSubtype nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype =
+                createFakeInputMethodSubtype("zz", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+                        !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
+                        IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        final InputMethodSubtype nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2 =
+                createFakeInputMethodSubtype("zz", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+                        !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
+                        IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+
+        // Make sure that an automatic subtype (overridesImplicitlyEnabledSubtype:true) is
+        // selected no matter what locale is specified.
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoEnUS);
+            subtypes.add(nonAutoEnGB);
+            subtypes.add(nonAutoJa);
+            subtypes.add(nonAutoFil);
+            subtypes.add(autoSubtype);  // overridesImplicitlyEnabledSubtype == true
+            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
+            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
+            subtypes.add(nonAutoHandwritingEn);
+            subtypes.add(nonAutoHandwritingFr);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            final ArrayList<InputMethodSubtype> result =
+                    SubtypeUtils.getImplicitlyApplicableSubtypes(
+                            new LocaleList(LOCALE_EN_US), imi);
+            assertEquals(1, result.size());
+            verifyEquality(autoSubtype, result.get(0));
+        }
+
+        // Make sure that a subtype whose locale is exactly equal to the specified locale is
+        // selected as long as there is no no automatic subtype
+        // (overridesImplicitlyEnabledSubtype:true) in the given list.
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoEnUS);  // locale == "en_US"
+            subtypes.add(nonAutoEnGB);
+            subtypes.add(nonAutoJa);
+            subtypes.add(nonAutoFil);
+            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
+            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
+            subtypes.add(nonAutoHandwritingEn);
+            subtypes.add(nonAutoHandwritingFr);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            final ArrayList<InputMethodSubtype> result =
+                    SubtypeUtils.getImplicitlyApplicableSubtypes(
+                            new LocaleList(LOCALE_EN_US), imi);
+            assertEquals(2, result.size());
+            verifyEquality(nonAutoEnUS, result.get(0));
+            verifyEquality(nonAutoHandwritingEn, result.get(1));
+        }
+
+        // Make sure that a subtype whose locale is exactly equal to the specified locale is
+        // selected as long as there is no automatic subtype
+        // (overridesImplicitlyEnabledSubtype:true) in the given list.
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoEnUS);
+            subtypes.add(nonAutoEnGB); // locale == "en_GB"
+            subtypes.add(nonAutoJa);
+            subtypes.add(nonAutoFil);
+            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
+            subtypes.add(nonAutoHandwritingEn);
+            subtypes.add(nonAutoHandwritingFr);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            final ArrayList<InputMethodSubtype> result =
+                    SubtypeUtils.getImplicitlyApplicableSubtypes(
+                            new LocaleList(LOCALE_EN_GB), imi);
+            assertEquals(2, result.size());
+            verifyEquality(nonAutoEnGB, result.get(0));
+            verifyEquality(nonAutoHandwritingEn, result.get(1));
+        }
+
+        // If there is no automatic subtype (overridesImplicitlyEnabledSubtype:true) and
+        // any subtype whose locale is exactly equal to the specified locale in the given list,
+        // try to find a subtype whose language is equal to the language part of the given locale.
+        // Here make sure that a subtype (locale: "fr_CA") can be found with locale: "fr".
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoFrCA);  // locale == "fr_CA"
+            subtypes.add(nonAutoJa);
+            subtypes.add(nonAutoFil);
+            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
+            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
+            subtypes.add(nonAutoHandwritingEn);
+            subtypes.add(nonAutoHandwritingFr);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            final ArrayList<InputMethodSubtype> result =
+                    SubtypeUtils.getImplicitlyApplicableSubtypes(
+                            new LocaleList(LOCALE_FR), imi);
+            assertEquals(2, result.size());
+            verifyEquality(nonAutoFrCA, result.get(0));
+            verifyEquality(nonAutoHandwritingFr, result.get(1));
+        }
+        // Then make sure that a subtype (locale: "fr") can be found with locale: "fr_CA".
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoFr);  // locale == "fr"
+            subtypes.add(nonAutoJa);
+            subtypes.add(nonAutoFil);
+            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
+            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
+            subtypes.add(nonAutoHandwritingEn);
+            subtypes.add(nonAutoHandwritingFr);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            final ArrayList<InputMethodSubtype> result =
+                    SubtypeUtils.getImplicitlyApplicableSubtypes(
+                            new LocaleList(LOCALE_FR_CA), imi);
+            assertEquals(2, result.size());
+            verifyEquality(nonAutoFrCA, result.get(0));
+            verifyEquality(nonAutoHandwritingFr, result.get(1));
+        }
+
+        // Make sure that subtypes which have "EnabledWhenDefaultIsNotAsciiCapable" in its
+        // extra value is selected if and only if all other selected IMEs are not AsciiCapable.
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoEnUS);
+            subtypes.add(nonAutoJa);    // not ASCII capable
+            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
+            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
+            subtypes.add(nonAutoHandwritingEn);
+            subtypes.add(nonAutoHandwritingFr);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            final ArrayList<InputMethodSubtype> result =
+                    SubtypeUtils.getImplicitlyApplicableSubtypes(
+                            new LocaleList(LOCALE_JA_JP), imi);
+            assertEquals(3, result.size());
+            verifyEquality(nonAutoJa, result.get(0));
+            verifyEquality(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype, result.get(1));
+            verifyEquality(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2, result.get(2));
+        }
+
+        // Make sure that if there is no subtype that matches the language requested, then we just
+        // use the first keyboard subtype.
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoHi);
+            subtypes.add(nonAutoEnUS);
+            subtypes.add(nonAutoHandwritingEn);
+            subtypes.add(nonAutoHandwritingFr);
+            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            final ArrayList<InputMethodSubtype> result =
+                    SubtypeUtils.getImplicitlyApplicableSubtypes(
+                            new LocaleList(LOCALE_JA_JP), imi);
+            assertEquals(1, result.size());
+            verifyEquality(nonAutoHi, result.get(0));
+        }
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoEnUS);
+            subtypes.add(nonAutoHi);
+            subtypes.add(nonAutoHandwritingEn);
+            subtypes.add(nonAutoHandwritingFr);
+            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            final ArrayList<InputMethodSubtype> result =
+                    SubtypeUtils.getImplicitlyApplicableSubtypes(
+                            new LocaleList(LOCALE_JA_JP), imi);
+            assertEquals(1, result.size());
+            verifyEquality(nonAutoEnUS, result.get(0));
+        }
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoHandwritingEn);
+            subtypes.add(nonAutoHandwritingFr);
+            subtypes.add(nonAutoEnUS);
+            subtypes.add(nonAutoHi);
+            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            final ArrayList<InputMethodSubtype> result =
+                    SubtypeUtils.getImplicitlyApplicableSubtypes(
+                            new LocaleList(LOCALE_JA_JP), imi);
+            assertEquals(1, result.size());
+            verifyEquality(nonAutoEnUS, result.get(0));
+        }
+
+        // Make sure that both language and script are taken into account to find the best matching
+        // subtype.
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoEnUS);
+            subtypes.add(nonAutoSrCyrl);
+            subtypes.add(nonAutoSrLatn);
+            subtypes.add(nonAutoHandwritingEn);
+            subtypes.add(nonAutoHandwritingFr);
+            subtypes.add(nonAutoHandwritingSrCyrl);
+            subtypes.add(nonAutoHandwritingSrLatn);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            final ArrayList<InputMethodSubtype> result =
+                    SubtypeUtils.getImplicitlyApplicableSubtypes(
+                            new LocaleList(Locale.forLanguageTag("sr-Latn-RS")), imi);
+            assertEquals(2, result.size());
+            assertThat(nonAutoSrLatn, is(in(result)));
+            assertThat(nonAutoHandwritingSrLatn, is(in(result)));
+        }
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoEnUS);
+            subtypes.add(nonAutoSrCyrl);
+            subtypes.add(nonAutoSrLatn);
+            subtypes.add(nonAutoHandwritingEn);
+            subtypes.add(nonAutoHandwritingFr);
+            subtypes.add(nonAutoHandwritingSrCyrl);
+            subtypes.add(nonAutoHandwritingSrLatn);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            final ArrayList<InputMethodSubtype> result =
+                    SubtypeUtils.getImplicitlyApplicableSubtypes(
+                            new LocaleList(Locale.forLanguageTag("sr-Cyrl-RS")), imi);
+            assertEquals(2, result.size());
+            assertThat(nonAutoSrCyrl, is(in(result)));
+            assertThat(nonAutoHandwritingSrCyrl, is(in(result)));
+        }
+
+        // Make sure that secondary locales are taken into account to find the best matching
+        // subtype.
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoEnUS);
+            subtypes.add(nonAutoEnGB);
+            subtypes.add(nonAutoSrCyrl);
+            subtypes.add(nonAutoSrLatn);
+            subtypes.add(nonAutoFr);
+            subtypes.add(nonAutoFrCA);
+            subtypes.add(nonAutoHandwritingEn);
+            subtypes.add(nonAutoHandwritingFr);
+            subtypes.add(nonAutoHandwritingSrCyrl);
+            subtypes.add(nonAutoHandwritingSrLatn);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            final ArrayList<InputMethodSubtype> result =
+                    SubtypeUtils.getImplicitlyApplicableSubtypes(
+                            new LocaleList(
+                                    Locale.forLanguageTag("sr-Latn-RS-x-android"),
+                                    Locale.forLanguageTag("ja-JP"),
+                                    Locale.forLanguageTag("fr-FR"),
+                                    Locale.forLanguageTag("en-GB"),
+                                    Locale.forLanguageTag("en-US")),
+                            imi);
+            assertEquals(6, result.size());
+            assertThat(nonAutoEnGB, is(in(result)));
+            assertThat(nonAutoFr, is(in(result)));
+            assertThat(nonAutoSrLatn, is(in(result)));
+            assertThat(nonAutoHandwritingEn, is(in(result)));
+            assertThat(nonAutoHandwritingFr, is(in(result)));
+            assertThat(nonAutoHandwritingSrLatn, is(in(result)));
+        }
+
+        // Make sure that 3-letter language code can be handled.
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoEnUS);
+            subtypes.add(nonAutoFil);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            final ArrayList<InputMethodSubtype> result =
+                    SubtypeUtils.getImplicitlyApplicableSubtypes(
+                            new LocaleList(LOCALE_FIL_PH), imi);
+            assertEquals(1, result.size());
+            verifyEquality(nonAutoFil, result.get(0));
+        }
+
+        // Make sure that we never end up matching "fi" (finnish) with "fil" (filipino).
+        // Also make sure that the first subtype will be used as the last-resort candidate.
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoJa);
+            subtypes.add(nonAutoEnUS);
+            subtypes.add(nonAutoFil);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            final ArrayList<InputMethodSubtype> result =
+                    SubtypeUtils.getImplicitlyApplicableSubtypes(
+                            new LocaleList(LOCALE_FI), imi);
+            assertEquals(1, result.size());
+            verifyEquality(nonAutoJa, result.get(0));
+        }
+
+        // Make sure that "in" and "id" conversion is taken into account.
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoIn);
+            subtypes.add(nonAutoEnUS);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            final ArrayList<InputMethodSubtype> result =
+                    SubtypeUtils.getImplicitlyApplicableSubtypes(
+                            new LocaleList(LOCALE_IN), imi);
+            assertEquals(1, result.size());
+            verifyEquality(nonAutoIn, result.get(0));
+        }
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoIn);
+            subtypes.add(nonAutoEnUS);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            final ArrayList<InputMethodSubtype> result =
+                    SubtypeUtils.getImplicitlyApplicableSubtypes(
+                            new LocaleList(LOCALE_ID), imi);
+            assertEquals(1, result.size());
+            verifyEquality(nonAutoIn, result.get(0));
+        }
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoId);
+            subtypes.add(nonAutoEnUS);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            final ArrayList<InputMethodSubtype> result =
+                    SubtypeUtils.getImplicitlyApplicableSubtypes(
+                            new LocaleList(LOCALE_IN), imi);
+            assertEquals(1, result.size());
+            verifyEquality(nonAutoId, result.get(0));
+        }
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoId);
+            subtypes.add(nonAutoEnUS);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            final ArrayList<InputMethodSubtype> result =
+                    SubtypeUtils.getImplicitlyApplicableSubtypes(
+                            new LocaleList(LOCALE_ID), imi);
+            assertEquals(1, result.size());
+            verifyEquality(nonAutoId, result.get(0));
+        }
+
+        // If there is no automatic subtype (overridesImplicitlyEnabledSubtype:true) and the system
+        // provides multiple locales, we try to enable multiple subtypes.
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoEnUS);
+            subtypes.add(nonAutoFrCA);
+            subtypes.add(nonAutoIn);
+            subtypes.add(nonAutoJa);
+            subtypes.add(nonAutoFil);
+            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
+            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            final ArrayList<InputMethodSubtype> result =
+                    SubtypeUtils.getImplicitlyApplicableSubtypes(
+                            new LocaleList(LOCALE_FR, LOCALE_EN_US, LOCALE_JA_JP), imi);
+            assertThat(nonAutoFrCA, is(in(result)));
+            assertThat(nonAutoEnUS, is(in(result)));
+            assertThat(nonAutoJa, is(in(result)));
+            assertThat(nonAutoIn, not(is(in(result))));
+            assertThat(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype, not(is(in(result))));
+            assertThat(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype, not(is(in(result))));
+        }
+    }
+
+    @Test
+    public void testContainsSubtypeOf() throws Exception {
+        final InputMethodSubtype nonAutoEnUS = createFakeInputMethodSubtype("en_US",
+                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
+                IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        final InputMethodSubtype nonAutoFil = createFakeInputMethodSubtype("fil",
+                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
+                IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        final InputMethodSubtype nonAutoFilPH = createFakeInputMethodSubtype("fil_PH",
+                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
+                IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        final InputMethodSubtype nonAutoIn = createFakeInputMethodSubtype("in",
+                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
+                IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        final InputMethodSubtype nonAutoId = createFakeInputMethodSubtype("id",
+                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
+                IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoEnUS);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+
+            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN, !CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN, CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY,
+                    SUBTYPE_MODE_VOICE));
+            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY,
+                    SUBTYPE_MODE_VOICE));
+            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY,
+                    SUBTYPE_MODE_ANY));
+            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY,
+                    SUBTYPE_MODE_ANY));
+
+            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_GB, !CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_GB, CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+        }
+
+        // Make sure that 3-letter language code ("fil") can be handled.
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoFil);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL, !CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL, CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, !CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+
+            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI, !CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI, CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI_FI, !CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI_FI, CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+        }
+
+        // Make sure that 3-letter language code ("fil_PH") can be handled.
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoFilPH);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL, !CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL, CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, !CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+
+            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI, !CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI, CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI_FI, !CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI_FI, CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+        }
+
+        // Make sure that a subtype whose locale is "in" can be queried with "id".
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoIn);
+            subtypes.add(nonAutoEnUS);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_IN, !CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_IN, CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_ID, !CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_ID, CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+        }
+
+        // Make sure that a subtype whose locale is "id" can be queried with "in".
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(nonAutoId);
+            subtypes.add(nonAutoEnUS);
+            final InputMethodInfo imi = createFakeInputMethodInfo(
+                    "com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
+                    subtypes);
+            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_IN, !CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_IN, CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_ID, !CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_ID, CHECK_COUNTRY,
+                    SUBTYPE_MODE_KEYBOARD));
+        }
+    }
+
+    @Test
+    public void testChooseSystemVoiceIme() throws Exception {
+        final InputMethodInfo systemIme = createFakeInputMethodInfo("SystemIme", "fake.voice0",
+                true /* isSystem */);
+
+        // Returns null when the config value is null.
+        {
+            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+            methodMap.put(systemIme.getId(), systemIme);
+            assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(InputMethodMap.of(methodMap),
+                    null, ""));
+        }
+
+        // Returns null when the config value is empty.
+        {
+            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+            methodMap.put(systemIme.getId(), systemIme);
+            assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(InputMethodMap.of(methodMap), "",
+                    ""));
+        }
+
+        // Returns null when the configured package doesn't have an IME.
+        {
+            assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(
+                    InputMethodMap.emptyMap(),
+                    systemIme.getPackageName(), ""));
+        }
+
+        // Returns the right one when the current default is null.
+        {
+            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+            methodMap.put(systemIme.getId(), systemIme);
+            assertEquals(systemIme, InputMethodInfoUtils.chooseSystemVoiceIme(
+                    InputMethodMap.of(methodMap),
+                    systemIme.getPackageName(), null));
+        }
+
+        // Returns the right one when the current default is empty.
+        {
+            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+            methodMap.put(systemIme.getId(), systemIme);
+            assertEquals(systemIme, InputMethodInfoUtils.chooseSystemVoiceIme(
+                    InputMethodMap.of(methodMap),
+                    systemIme.getPackageName(), ""));
+        }
+
+        // Returns null when the current default isn't found.
+        {
+            assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(
+                    InputMethodMap.emptyMap(),
+                    systemIme.getPackageName(), systemIme.getId()));
+        }
+
+        // Returns null when there are multiple IMEs defined by the config package.
+        {
+            final InputMethodInfo secondIme = createFakeInputMethodInfo(systemIme.getPackageName(),
+                    "fake.voice1", true /* isSystem */);
+            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+            methodMap.put(systemIme.getId(), systemIme);
+            methodMap.put(secondIme.getId(), secondIme);
+            assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(InputMethodMap.of(methodMap),
+                    systemIme.getPackageName(), ""));
+        }
+
+        // Returns the current one when the current default and config point to the same package.
+        {
+            final InputMethodInfo secondIme = createFakeInputMethodInfo("SystemIme", "fake.voice1",
+                    true /* isSystem */);
+            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+            methodMap.put(systemIme.getId(), systemIme);
+            methodMap.put(secondIme.getId(), secondIme);
+            assertEquals(systemIme, InputMethodInfoUtils.chooseSystemVoiceIme(
+                    InputMethodMap.of(methodMap),
+                    systemIme.getPackageName(), systemIme.getId()));
+        }
+
+        // Doesn't return the current default if it isn't a system app.
+        {
+            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+            final InputMethodInfo nonSystemIme = createFakeInputMethodInfo("NonSystemIme",
+                    "fake.voice0", false /* isSystem */);
+            methodMap.put(nonSystemIme.getId(), nonSystemIme);
+            assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(InputMethodMap.of(methodMap),
+                    nonSystemIme.getPackageName(), nonSystemIme.getId()));
+        }
+
+        // Returns null if the configured one isn't a system app.
+        {
+            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+            final InputMethodInfo nonSystemIme = createFakeInputMethodInfo(
+                    "FakeDefaultAutoVoiceIme", "fake.voice0", false /* isSystem */);
+            methodMap.put(systemIme.getId(), systemIme);
+            methodMap.put(nonSystemIme.getId(), nonSystemIme);
+            assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(InputMethodMap.of(methodMap),
+                    nonSystemIme.getPackageName(), ""));
+        }
+    }
+
+    private void assertDefaultEnabledImes(final ArrayList<InputMethodInfo> preinstalledImes,
+            final Locale systemLocale, String... expectedImeNames) {
+        final Context context = createTargetContextWithLocales(new LocaleList(systemLocale));
+        final String[] actualImeNames = getPackageNames(
+                InputMethodInfoUtils.getDefaultEnabledImes(context, preinstalledImes));
+        assertEquals(expectedImeNames.length, actualImeNames.length);
+        for (int i = 0; i < expectedImeNames.length; ++i) {
+            assertEquals(expectedImeNames[i], actualImeNames[i]);
+        }
+    }
+
+    private void assertDefaultEnabledMinimumImes(final ArrayList<InputMethodInfo> preinstalledImes,
+            final Locale systemLocale, String... expectedImeNames) {
+        final Context context = createTargetContextWithLocales(new LocaleList(systemLocale));
+        final String[] actualImeNames = getPackageNames(
+                InputMethodInfoUtils.getDefaultEnabledImes(context, preinstalledImes,
+                        true /* onlyMinimum */));
+        assertEquals(expectedImeNames.length, actualImeNames.length);
+        for (int i = 0; i < expectedImeNames.length; ++i) {
+            assertEquals(expectedImeNames[i], actualImeNames[i]);
+        }
+    }
+
+    private static List<InputMethodInfo> cloneViaParcel(final List<InputMethodInfo> list) {
+        Parcel p = null;
+        try {
+            p = Parcel.obtain();
+            p.writeTypedList(list);
+            p.setDataPosition(0);
+            return p.createTypedArrayList(InputMethodInfo.CREATOR);
+        } finally {
+            if (p != null) {
+                p.recycle();
+            }
+        }
+    }
+
+    private Context createTargetContextWithLocales(final LocaleList locales) {
+        final Configuration resourceConfiguration = new Configuration();
+        resourceConfiguration.setLocales(locales);
+        return InstrumentationRegistry.getInstrumentation()
+                .getTargetContext()
+                .createConfigurationContext(resourceConfiguration);
+    }
+
+    private String[] getPackageNames(final ArrayList<InputMethodInfo> imis) {
+        final String[] packageNames = new String[imis.size()];
+        for (int i = 0; i < imis.size(); ++i) {
+            packageNames[i] = imis.get(i).getPackageName();
+        }
+        return packageNames;
+    }
+
+    private static void verifyEquality(InputMethodInfo expected, InputMethodInfo actual) {
+        assertEquals(expected, actual);
+        assertEquals(expected.getSubtypeCount(), actual.getSubtypeCount());
+        for (int subtypeIndex = 0; subtypeIndex < expected.getSubtypeCount(); ++subtypeIndex) {
+            final InputMethodSubtype expectedSubtype = expected.getSubtypeAt(subtypeIndex);
+            final InputMethodSubtype actualSubtype = actual.getSubtypeAt(subtypeIndex);
+            verifyEquality(expectedSubtype, actualSubtype);
+        }
+    }
+
+    private static void verifyEquality(InputMethodSubtype expected, InputMethodSubtype actual) {
+        assertEquals(expected, actual);
+        assertEquals(expected.hashCode(), actual.hashCode());
+    }
+
+    private static InputMethodInfo createFakeInputMethodInfo(String packageName, String name,
+            boolean isSystem) {
+        final ResolveInfo ri = new ResolveInfo();
+        final ServiceInfo si = new ServiceInfo();
+        final ApplicationInfo ai = new ApplicationInfo();
+        ai.packageName = packageName;
+        ai.enabled = true;
+        if (isSystem) {
+            ai.flags |= ApplicationInfo.FLAG_SYSTEM;
+        }
+        si.applicationInfo = ai;
+        si.enabled = true;
+        si.packageName = packageName;
+        si.name = name;
+        si.exported = true;
+        ri.serviceInfo = si;
+        return new InputMethodInfo(ri, false, "", Collections.emptyList(), 1, true);
+    }
+
+    private static InputMethodInfo createFakeInputMethodInfo(String packageName, String name,
+            CharSequence label, boolean isAuxIme, boolean isDefault,
+            List<InputMethodSubtype> subtypes) {
+        final ResolveInfo ri = new ResolveInfo();
+        final ServiceInfo si = new ServiceInfo();
+        final ApplicationInfo ai = new ApplicationInfo();
+        ai.packageName = packageName;
+        ai.enabled = true;
+        ai.flags |= ApplicationInfo.FLAG_SYSTEM;
+        si.applicationInfo = ai;
+        si.enabled = true;
+        si.packageName = packageName;
+        si.name = name;
+        si.exported = true;
+        si.nonLocalizedLabel = label;
+        ri.serviceInfo = si;
+        return new InputMethodInfo(ri, isAuxIme, "", subtypes, 1, isDefault);
+    }
+
+    private static InputMethodSubtype createFakeInputMethodSubtype(String locale, String mode,
+            boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype,
+            boolean isAsciiCapable, boolean isEnabledWhenDefaultIsNotAsciiCapable) {
+        return createFakeInputMethodSubtype(locale, null /* languageTag */, mode, isAuxiliary,
+                overridesImplicitlyEnabledSubtype, isAsciiCapable,
+                isEnabledWhenDefaultIsNotAsciiCapable);
+    }
+
+    private static InputMethodSubtype createFakeInputMethodSubtype(String locale,
+            String languageTag, String mode, boolean isAuxiliary,
+            boolean overridesImplicitlyEnabledSubtype, boolean isAsciiCapable,
+            boolean isEnabledWhenDefaultIsNotAsciiCapable) {
+        final StringBuilder subtypeExtraValue = new StringBuilder();
+        if (isEnabledWhenDefaultIsNotAsciiCapable) {
+            subtypeExtraValue.append(EXTRA_VALUE_PAIR_SEPARATOR);
+            subtypeExtraValue.append(EXTRA_VALUE_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
+        }
+
+        return new InputMethodSubtypeBuilder()
+                .setSubtypeNameResId(0)
+                .setSubtypeIconResId(0)
+                .setSubtypeLocale(locale)
+                .setLanguageTag(languageTag)
+                .setSubtypeMode(mode)
+                .setSubtypeExtraValue(subtypeExtraValue.toString())
+                .setIsAuxiliary(isAuxiliary)
+                .setOverridesImplicitlyEnabledSubtype(overridesImplicitlyEnabledSubtype)
+                .setIsAsciiCapable(isAsciiCapable)
+                .build();
+    }
+
+    private static ArrayList<InputMethodInfo> getImesWithDefaultVoiceIme() {
+        ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>();
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(createFakeInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
+                    IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
+                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
+            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
+                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
+                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
+            preinstalledImes.add(createFakeInputMethodInfo("FakeDefaultAutoVoiceIme",
+                    "fake.voice0", "FakeVoice0", IS_AUX, IS_DEFAULT, subtypes));
+        }
+        preinstalledImes.addAll(getImesWithoutDefaultVoiceIme());
+        return preinstalledImes;
+    }
+
+    private static ArrayList<InputMethodInfo> getImesWithoutDefaultVoiceIme() {
+        ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>();
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(createFakeInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
+                    IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
+                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
+            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
+                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
+                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
+            preinstalledImes.add(createFakeInputMethodInfo("FakeNonDefaultAutoVoiceIme0",
+                    "fake.voice1", "FakeVoice1", IS_AUX, !IS_DEFAULT, subtypes));
+        }
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(createFakeInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
+                    IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
+                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
+            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
+                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
+                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
+            preinstalledImes.add(createFakeInputMethodInfo("FakeNonDefaultAutoVoiceIme1",
+                    "fake.voice2", "FakeVoice2", IS_AUX, !IS_DEFAULT, subtypes));
+        }
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
+                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
+                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
+            preinstalledImes.add(createFakeInputMethodInfo("FakeNonDefaultVoiceIme2",
+                    "fake.voice3", "FakeVoice3", IS_AUX, !IS_DEFAULT, subtypes));
+        }
+        {
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
+                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
+            preinstalledImes.add(createFakeInputMethodInfo("FakeDefaultEnKeyboardIme",
+                    "fake.keyboard0", "FakeKeyboard0", !IS_AUX, IS_DEFAULT, subtypes));
+        }
+        return preinstalledImes;
+    }
+
+    private static boolean contains(final String[] textList, final String textToBeChecked) {
+        if (textList == null) {
+            return false;
+        }
+        for (final String text : textList) {
+            if (Objects.equals(textToBeChecked, text)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static ArrayList<InputMethodInfo> getSamplePreinstalledImes(final String localeString) {
+        ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>();
+
+        // a fake Voice IME
+        {
+            final boolean isDefaultIme = false;
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(createFakeInputMethodSubtype("", SUBTYPE_MODE_VOICE, IS_AUX,
+                    IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
+                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
+            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.voice",
+                    "com.android.inputmethod.voice", "FakeVoiceIme", IS_AUX, isDefaultIme,
+                    subtypes));
+        }
+        // a fake Hindi IME
+        {
+            final boolean isDefaultIme = contains(new String[]{ "hi", "en-rIN" }, localeString);
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            // TODO: This subtype should be marked as IS_ASCII_CAPABLE
+            subtypes.add(createFakeInputMethodSubtype("en_IN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
+                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
+            subtypes.add(createFakeInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
+                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
+            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.hindi",
+                    "com.android.inputmethod.hindi", "FakeHindiIme", !IS_AUX, isDefaultIme,
+                    subtypes));
+        }
+
+        // a fake Pinyin IME
+        {
+            final boolean isDefaultIme = contains(new String[]{ "zh-rCN" }, localeString);
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(createFakeInputMethodSubtype("zh_CN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
+                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
+            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.pinyin",
+                    "com.android.apps.inputmethod.pinyin", "FakePinyinIme", !IS_AUX, isDefaultIme,
+                    subtypes));
+        }
+
+        // a fake Korean IME
+        {
+            final boolean isDefaultIme = contains(new String[]{ "ko" }, localeString);
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(createFakeInputMethodSubtype("ko", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
+                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
+            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.korean",
+                    "com.android.apps.inputmethod.korean", "FakeKoreanIme", !IS_AUX, isDefaultIme,
+                    subtypes));
+        }
+
+        // a fake Latin IME
+        {
+            final boolean isDefaultIme = contains(
+                    new String[]{ "en-rUS", "en-rGB", "en-rIN", "en", "hi" }, localeString);
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
+                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
+            subtypes.add(createFakeInputMethodSubtype("en_GB", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
+                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
+            subtypes.add(createFakeInputMethodSubtype("en_IN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
+                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
+            subtypes.add(createFakeInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
+                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
+            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.latin",
+                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, isDefaultIme,
+                    subtypes));
+        }
+
+        // a fake Japanese IME
+        {
+            final boolean isDefaultIme = contains(new String[]{ "ja", "ja-rJP" }, localeString);
+            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+            subtypes.add(createFakeInputMethodSubtype("ja", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
+                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
+            subtypes.add(createFakeInputMethodSubtype("emoji", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
+                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
+                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
+            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.japanese",
+                    "com.android.apps.inputmethod.japanese", "FakeJapaneseIme", !IS_AUX,
+                    isDefaultIme, subtypes));
+        }
+
+        return preinstalledImes;
+    }
+
+    @Test
+    public void testIsSoftInputModeStateVisibleAllowed() {
+        // On pre-P devices, SOFT_INPUT_STATE_VISIBLE/SOFT_INPUT_STATE_ALWAYS_VISIBLE are always
+        // allowed, regardless of the focused view state.
+        assertTrue(InputMethodUtils.isSoftInputModeStateVisibleAllowed(
+                Build.VERSION_CODES.O_MR1, 0));
+        assertTrue(InputMethodUtils.isSoftInputModeStateVisibleAllowed(
+                Build.VERSION_CODES.O_MR1, StartInputFlags.VIEW_HAS_FOCUS));
+        assertTrue(InputMethodUtils.isSoftInputModeStateVisibleAllowed(
+                Build.VERSION_CODES.O_MR1,
+                StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR));
+
+        // On P+ devices, SOFT_INPUT_STATE_VISIBLE/SOFT_INPUT_STATE_ALWAYS_VISIBLE are allowed only
+        // when there is a focused View and its View#onCheckIsTextEditor() returns true.
+        assertFalse(InputMethodUtils.isSoftInputModeStateVisibleAllowed(
+                Build.VERSION_CODES.P, 0));
+        assertFalse(InputMethodUtils.isSoftInputModeStateVisibleAllowed(
+                Build.VERSION_CODES.P, StartInputFlags.VIEW_HAS_FOCUS));
+        assertTrue(InputMethodUtils.isSoftInputModeStateVisibleAllowed(
+                Build.VERSION_CODES.P,
+                StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR));
+    }
+
+    private static void verifySplitEnabledImeStr(@NonNull String enabledImeStr,
+            @NonNull String... expected) {
+        final ArrayList<String> actual = new ArrayList<>();
+        InputMethodUtils.splitEnabledImeStr(enabledImeStr, actual::add);
+        if (expected.length == 0) {
+            Truth.assertThat(actual).isEmpty();
+        } else {
+            Truth.assertThat(actual).containsExactlyElementsIn(expected);
+        }
+    }
+
+    @Test
+    public void testSplitEnabledImeStr() {
+        verifySplitEnabledImeStr("");
+        verifySplitEnabledImeStr("com.android/.ime1", "com.android/.ime1");
+        verifySplitEnabledImeStr("com.android/.ime1;1;2;3", "com.android/.ime1");
+        verifySplitEnabledImeStr("com.android/.ime1;1;2;3:com.android/.ime2",
+                "com.android/.ime1", "com.android/.ime2");
+        verifySplitEnabledImeStr("com.android/.ime1:com.android/.ime2",
+                "com.android/.ime1", "com.android/.ime2");
+        verifySplitEnabledImeStr("com.android/.ime1:com.android/.ime2:com.android/.ime3",
+                "com.android/.ime1", "com.android/.ime2", "com.android/.ime3");
+        verifySplitEnabledImeStr("com.android/.ime1;1:com.android/.ime2;1:com.android/.ime3;1",
+                "com.android/.ime1", "com.android/.ime2", "com.android/.ime3");
+    }
+
+    @Test
+    public void testConcatEnabledImeIds() {
+        Truth.assertThat(InputMethodUtils.concatEnabledImeIds("")).isEmpty();
+        Truth.assertThat(InputMethodUtils.concatEnabledImeIds("", "com.android/.ime1"))
+                .isEqualTo("com.android/.ime1");
+        Truth.assertThat(InputMethodUtils.concatEnabledImeIds(
+                        "com.android/.ime1", "com.android/.ime1"))
+                .isEqualTo("com.android/.ime1");
+        Truth.assertThat(InputMethodUtils.concatEnabledImeIds(
+                        "com.android/.ime1", "com.android/.ime2"))
+                .isEqualTo("com.android/.ime1:com.android/.ime2");
+        Truth.assertThat(InputMethodUtils.concatEnabledImeIds(
+                        "com.android/.ime1", "com.android/.ime2", "com.android/.ime3"))
+                .isEqualTo("com.android/.ime1:com.android/.ime2:com.android/.ime3");
+        Truth.assertThat(InputMethodUtils.concatEnabledImeIds(
+                        "com.android/.ime1:com.android/.ime2", "com.android/.ime1"))
+                .isEqualTo("com.android/.ime1:com.android/.ime2");
+        Truth.assertThat(InputMethodUtils.concatEnabledImeIds(
+                        "com.android/.ime1:com.android/.ime2", "com.android/.ime3"))
+                .isEqualTo("com.android/.ime1:com.android/.ime2:com.android/.ime3");
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/LocaleUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/LocaleUtilsTest.java
new file mode 100644
index 0000000..d0b46f5
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/LocaleUtilsTest.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.LocaleList;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+public final class LocaleUtilsTest {
+
+    private static final LocaleUtils.LocaleExtractor<Locale> sIdentityMapper = source -> source;
+
+    @Test
+    public void testFilterByLanguageEmptyLanguageList() throws Exception {
+        final ArrayList<Locale> availableLocales = new ArrayList<>();
+        availableLocales.add(Locale.forLanguageTag("en-US"));
+        availableLocales.add(Locale.forLanguageTag("fr-CA"));
+        availableLocales.add(Locale.forLanguageTag("in"));
+        availableLocales.add(Locale.forLanguageTag("ja"));
+        availableLocales.add(Locale.forLanguageTag("fil"));
+
+        final LocaleList preferredLocales = LocaleList.getEmptyLocaleList();
+
+        final ArrayList<Locale> dest = new ArrayList<>();
+        LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+        assertEquals(0, dest.size());
+    }
+
+    @Test
+    public void testFilterDoesNotMatchAnything() throws Exception {
+        final ArrayList<Locale> availableLocales = new ArrayList<>();
+        availableLocales.add(Locale.forLanguageTag("en-US"));
+        availableLocales.add(Locale.forLanguageTag("fr-CA"));
+        availableLocales.add(Locale.forLanguageTag("in"));
+        availableLocales.add(Locale.forLanguageTag("ja"));
+        availableLocales.add(Locale.forLanguageTag("fil"));
+
+        final LocaleList preferredLocales = LocaleList.forLanguageTags("zh-Hans-TW");
+
+        final ArrayList<Locale> dest = new ArrayList<>();
+        LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+        assertEquals(0, dest.size());
+    }
+
+    @Test
+    public void testFilterByLanguageEmptySource() throws Exception {
+        final ArrayList<Locale> availableLocales = new ArrayList<>();
+
+        final LocaleList preferredLocales = LocaleList.forLanguageTags("fr,en-US,ja-JP");
+
+        final ArrayList<Locale> dest = new ArrayList<>();
+        LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+        assertEquals(0, dest.size());
+    }
+
+    @Test
+    public void testFilterByLanguageNullAvailableLocales() throws Exception {
+        {
+            final LocaleList preferredLocales =
+                    LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(null);
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(0, dest.size());
+        }
+        {
+            final LocaleList preferredLocales =
+                    LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(null);
+            availableLocales.add(null);
+            availableLocales.add(null);
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(0, dest.size());
+        }
+        {
+            final LocaleList preferredLocales =
+                    LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(null);
+            availableLocales.add(Locale.forLanguageTag("en-US"));
+            availableLocales.add(null);
+            availableLocales.add(null);
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(1, dest.size());
+            assertEquals(availableLocales.get(1), dest.get(0));  // "en-US"
+        }
+        {
+            final LocaleList preferredLocales =
+                    LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(null);
+            availableLocales.add(Locale.forLanguageTag("en"));
+            availableLocales.add(null);
+            availableLocales.add(null);
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(1, dest.size());
+            assertEquals(availableLocales.get(1), dest.get(0));  // "en"
+        }
+        {
+            final LocaleList preferredLocales =
+                    LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(null);
+            availableLocales.add(Locale.forLanguageTag("ja-JP"));
+            availableLocales.add(null);
+            availableLocales.add(null);
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(0, dest.size());
+        }
+    }
+
+    @Test
+    public void testFilterByLanguage() throws Exception {
+        {
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(Locale.forLanguageTag("en-US"));
+            availableLocales.add(Locale.forLanguageTag("fr-CA"));
+            availableLocales.add(Locale.forLanguageTag("in"));
+            availableLocales.add(Locale.forLanguageTag("ja"));
+            availableLocales.add(Locale.forLanguageTag("fil"));
+
+            final LocaleList preferredLocales = LocaleList.forLanguageTags("fr,en-US,ja-JP");
+
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(3, dest.size());
+            assertEquals(availableLocales.get(1), dest.get(0));  // "fr-CA"
+            assertEquals(availableLocales.get(0), dest.get(1));  // "en-US"
+            assertEquals(availableLocales.get(3), dest.get(2));  // "ja"
+        }
+        {
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(Locale.forLanguageTag("en-US"));
+            availableLocales.add(Locale.forLanguageTag("en-GB"));
+            availableLocales.add(Locale.forLanguageTag("en-IN"));
+
+            final LocaleList preferredLocales = LocaleList.forLanguageTags("en-US");
+
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(1, dest.size());
+            assertEquals(availableLocales.get(0), dest.get(0));  // "en-US"
+        }
+    }
+
+    @Test
+    public void testFilterByLanguageTheSameLanguage() throws Exception {
+        {
+            final LocaleList preferredLocales =
+                    LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(Locale.forLanguageTag("fr-CA"));
+            availableLocales.add(Locale.forLanguageTag("en-US"));
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(1, dest.size());
+            assertEquals(availableLocales.get(1), dest.get(0));  // "en-US"
+        }
+        {
+            final LocaleList preferredLocales =
+                    LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(Locale.forLanguageTag("fr-CA"));
+            availableLocales.add(Locale.forLanguageTag("en"));
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(1, dest.size());
+            assertEquals(availableLocales.get(1), dest.get(0));  // "en"
+        }
+        {
+            final LocaleList preferredLocales =
+                    LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(Locale.forLanguageTag("fr-CA"));
+            availableLocales.add(Locale.forLanguageTag("en-CA"));
+            availableLocales.add(Locale.forLanguageTag("en-IN"));
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(1, dest.size());
+            assertEquals(availableLocales.get(2), dest.get(0));  // "en-IN"
+        }
+        {
+            final LocaleList preferredLocales =
+                    LocaleList.forLanguageTags("en-AU,en-GB,en-US,en-IN");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(Locale.forLanguageTag("fr-CA"));
+            availableLocales.add(Locale.forLanguageTag("en-CA"));
+            availableLocales.add(Locale.forLanguageTag("en-NZ"));
+            availableLocales.add(Locale.forLanguageTag("en-BZ"));
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(1, dest.size());
+            assertEquals(availableLocales.get(1), dest.get(0));  // "en-CA"
+        }
+    }
+
+    @Test
+    public void testFilterByLanguageFallbackRules() throws Exception {
+        {
+            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn-RS");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-BA"));
+            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-CS"));
+            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-ME"));
+            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-RS"));
+            availableLocales.add(Locale.forLanguageTag("sr-Latn-BA"));
+            availableLocales.add(Locale.forLanguageTag("sr-Latn-CS"));
+            availableLocales.add(Locale.forLanguageTag("sr-Latn-ME"));
+            availableLocales.add(Locale.forLanguageTag("sr-Latn-RS"));
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(1, dest.size());
+            assertEquals(availableLocales.get(7), dest.get(0));  // "sr-Latn-RS"
+        }
+        {
+            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn-RS-x-android");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-BA"));
+            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-CS"));
+            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-ME"));
+            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-RS"));
+            availableLocales.add(Locale.forLanguageTag("sr-Latn-BA"));
+            availableLocales.add(Locale.forLanguageTag("sr-Latn-CS"));
+            availableLocales.add(Locale.forLanguageTag("sr-Latn-ME"));
+            availableLocales.add(Locale.forLanguageTag("sr-Latn-RS"));
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(1, dest.size());
+            assertEquals(availableLocales.get(7), dest.get(0));  // "sr-Latn-RS"
+        }
+        {
+            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn-RS");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-BA-x-android"));
+            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-CS-x-android"));
+            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-ME-x-android"));
+            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-RS-x-android"));
+            availableLocales.add(Locale.forLanguageTag("sr-Latn-BA-x-android"));
+            availableLocales.add(Locale.forLanguageTag("sr-Latn-CS-x-android"));
+            availableLocales.add(Locale.forLanguageTag("sr-Latn-ME-x-android"));
+            availableLocales.add(Locale.forLanguageTag("sr-Latn-RS-x-android"));
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(1, dest.size());
+            assertEquals(availableLocales.get(7), dest.get(0));  // "sr-Latn-RS-x-android"
+        }
+
+        {
+            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn-RS");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(Locale.forLanguageTag("sr"));
+            availableLocales.add(Locale.forLanguageTag("sr-Cyrl"));
+            availableLocales.add(Locale.forLanguageTag("sr-Latn"));
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(1, dest.size());
+            assertEquals(availableLocales.get(2), dest.get(0));  // "sr-Latn"
+        }
+
+        {
+            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-RS");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(Locale.forLanguageTag("sr"));
+            availableLocales.add(Locale.forLanguageTag("sr-RS"));
+            availableLocales.add(Locale.forLanguageTag("sr-Latn"));
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(1, dest.size());
+            assertEquals(availableLocales.get(0), dest.get(0));  // "sr"
+        }
+
+        {
+            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(Locale.forLanguageTag("sr"));
+            availableLocales.add(Locale.forLanguageTag("sr-RS"));
+            availableLocales.add(Locale.forLanguageTag("sr-Latn"));
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(1, dest.size());
+            assertEquals(availableLocales.get(2), dest.get(0));  // "sr-Latn"
+        }
+
+        {
+            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(Locale.forLanguageTag("sr"));
+            availableLocales.add(Locale.forLanguageTag("sr-RS"));
+            availableLocales.add(Locale.forLanguageTag("sr-Latn"));
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(1, dest.size());
+            assertEquals(availableLocales.get(0), dest.get(0));  // "sr"
+        }
+        {
+            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(Locale.forLanguageTag("sr-Latn"));
+            availableLocales.add(Locale.forLanguageTag("sr-RS"));
+            availableLocales.add(Locale.forLanguageTag("sr"));
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(1, dest.size());
+            assertEquals(availableLocales.get(1), dest.get(0));  // "sr-RS"
+        }
+
+        {
+            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-RS"));
+            availableLocales.add(Locale.forLanguageTag("sr-Latn-RS"));
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(1, dest.size());
+            assertEquals(availableLocales.get(0), dest.get(0));  // "sr-Cyrl-RS"
+        }
+        {
+            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(Locale.forLanguageTag("sr-Latn-RS"));
+            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-RS"));
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(1, dest.size());
+            assertEquals(availableLocales.get(0), dest.get(0));  // "sr-Latn-RS"
+        }
+    }
+
+    @Test
+    public void testFilterKnownLimitation() throws Exception {
+        // Following test cases are not for intentional behavior but checks for preventing the
+        // behavior from becoming worse.
+        {
+            final LocaleList preferredLocales = LocaleList.forLanguageTags("ja-Hrkt");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(Locale.forLanguageTag("ja-Jpan"));
+            availableLocales.add(Locale.forLanguageTag("ja-Hrkt"));
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(1, dest.size());
+            // Should be ja-Jpan since it supports ja-Hrkt and listed before ja-Hrkt.
+            assertEquals(availableLocales.get(1), dest.get(0));
+        }
+        {
+            final LocaleList preferredLocales = LocaleList.forLanguageTags("zh-Hani");
+            final ArrayList<Locale> availableLocales = new ArrayList<>();
+            availableLocales.add(Locale.forLanguageTag("zh-Hans"));
+            availableLocales.add(Locale.forLanguageTag("zh-Hant"));
+            availableLocales.add(Locale.forLanguageTag("zh-Hanb"));
+            availableLocales.add(Locale.forLanguageTag("zh-Hani"));
+            final ArrayList<Locale> dest = new ArrayList<>();
+            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+            assertEquals(1, dest.size());
+            // Should be zh-Hans since it supports zh-Hani. Also zh-Hant, zh-Hanb supports zh-Hani.
+            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/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index 3aca1ca..f8accc3 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -103,6 +103,7 @@
         ":PackageParserTestApp4",
         ":PackageParserTestApp5",
         ":PackageParserTestApp6",
+        ":PackageParserTestApp7",
     ],
     resource_zips: [":PackageManagerServiceServerTests_apks_as_resources"],
 
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index 71f5c75..a0e0e1e 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -15,6 +15,17 @@
  */
 package com.android.server.pm;
 
+import static android.content.UriRelativeFilter.PATH;
+import static android.content.UriRelativeFilter.QUERY;
+import static android.content.UriRelativeFilter.FRAGMENT;
+import static android.content.UriRelativeFilterGroup.ACTION_ALLOW;
+import static android.content.UriRelativeFilterGroup.ACTION_BLOCK;
+import static android.os.PatternMatcher.PATTERN_ADVANCED_GLOB;
+import static android.os.PatternMatcher.PATTERN_LITERAL;
+import static android.os.PatternMatcher.PATTERN_PREFIX;
+import static android.os.PatternMatcher.PATTERN_SIMPLE_GLOB;
+import static android.os.PatternMatcher.PATTERN_SUFFIX;
+
 import static com.android.internal.pm.permission.CompatibilityPermissionInfo.COMPAT_PERMS;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -36,11 +47,15 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.content.IntentFilter;
+import android.content.UriRelativeFilter;
+import android.content.UriRelativeFilterGroup;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ConfigurationInfo;
 import android.content.pm.FeatureGroupInfo;
 import android.content.pm.FeatureInfo;
+import android.content.pm.Flags;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager.Property;
 import android.content.pm.ServiceInfo;
@@ -50,6 +65,9 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.util.ArraySet;
 
 import androidx.annotation.Nullable;
@@ -106,6 +124,7 @@
 import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -123,6 +142,9 @@
     @Rule
     public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private File mTmpDir;
     private static final File FRAMEWORK = new File("/system/framework/framework-res.apk");
     private static final String TEST_APP1_APK = "PackageParserTestApp1.apk";
@@ -131,6 +153,7 @@
     private static final String TEST_APP4_APK = "PackageParserTestApp4.apk";
     private static final String TEST_APP5_APK = "PackageParserTestApp5.apk";
     private static final String TEST_APP6_APK = "PackageParserTestApp6.apk";
+    private static final String TEST_APP7_APK = "PackageParserTestApp7.apk";
     private static final String PACKAGE_NAME = "com.android.servicestests.apps.packageparserapp";
 
     @Before
@@ -375,6 +398,87 @@
         assertNotEquals("$automotive", actualDisplayCategory);
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
+    public void testParseUriRelativeFilterGroups() throws Exception {
+        final File testFile = extractFile(TEST_APP7_APK);
+        try {
+            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+            final List<ParsedActivity> activities = pkg.getActivities();
+            final List<ParsedIntentInfo> intents = activities.get(0).getIntents();
+            final IntentFilter intentFilter = intents.get(0).getIntentFilter();
+            assertEquals(7, intentFilter.countUriRelativeFilterGroups());
+
+            UriRelativeFilterGroup group = intentFilter.getUriRelativeFilterGroup(0);
+            Collection<UriRelativeFilter> filters = group.getUriRelativeFilters();
+            assertEquals(ACTION_BLOCK, group.getAction());
+            assertEquals(3, filters.size());
+            assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_PREFIX, "/gizmos")));
+            assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_SIMPLE_GLOB,
+                    ".*query=string.*")));
+            assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_LITERAL,
+                    "fragment")));
+
+            group = intentFilter.getUriRelativeFilterGroup(1);
+            filters = group.getUriRelativeFilters();
+            assertEquals(ACTION_ALLOW, group.getAction());
+            assertEquals(2, filters.size());
+            assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_LITERAL,
+                    "query=string")));
+            assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_SUFFIX,
+                    "fragment")));
+
+            group = intentFilter.getUriRelativeFilterGroup(2);
+            filters = group.getUriRelativeFilters();
+            assertEquals(ACTION_ALLOW, group.getAction());
+            assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_LITERAL, "/gizmos")));
+            assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_LITERAL,
+                    ".*query=string.*")));
+            assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_LITERAL,
+                    "fragment")));
+
+            group = intentFilter.getUriRelativeFilterGroup(3);
+            filters = group.getUriRelativeFilters();
+            assertEquals(ACTION_ALLOW, group.getAction());
+            assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_PREFIX, "/gizmos")));
+            assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_PREFIX,
+                    ".*query=string.*")));
+            assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_PREFIX,
+                    "fragment")));
+
+            group = intentFilter.getUriRelativeFilterGroup(4);
+            filters = group.getUriRelativeFilters();
+            assertEquals(ACTION_ALLOW, group.getAction());
+            assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_SIMPLE_GLOB,
+                    "/gizmos")));
+            assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_SIMPLE_GLOB,
+                    ".*query=string.*")));
+            assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_SIMPLE_GLOB,
+                    "fragment")));
+
+            group = intentFilter.getUriRelativeFilterGroup(5);
+            filters = group.getUriRelativeFilters();
+            assertEquals(ACTION_ALLOW, group.getAction());
+            assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_ADVANCED_GLOB,
+                    "/gizmos")));
+            assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_ADVANCED_GLOB,
+                    ".*query=string.*")));
+            assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_ADVANCED_GLOB,
+                    "fragment")));
+
+            group = intentFilter.getUriRelativeFilterGroup(6);
+            filters = group.getUriRelativeFilters();
+            assertEquals(ACTION_ALLOW, group.getAction());
+            assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_SUFFIX, "/gizmos")));
+            assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_SUFFIX,
+                    ".*query=string.*")));
+            assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_SUFFIX,
+                    "fragment")));
+        } finally {
+            testFile.delete();
+        }
+    }
+
     private static final int PROPERTY_TYPE_BOOLEAN = 1;
     private static final int PROPERTY_TYPE_FLOAT = 2;
     private static final int PROPERTY_TYPE_INTEGER = 3;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
index 9780440..a185ad9 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
@@ -419,7 +419,7 @@
                 "installerTitle");
         packageUserState.setArchiveState(archiveState);
         assertEquals(archiveState, packageUserState.getArchiveState());
-        assertTrue(archiveState.getArchiveTimeMillis() > currentTimeMillis);
+        assertTrue(archiveState.getArchiveTimeMillis() >= currentTimeMillis);
     }
 
     @Test
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
index e5be4d9..9e11fa2 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
@@ -50,7 +50,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 
-// atest PackageManagerServiceTest:com.android.server.pm.UserDataPreparerTest
+// atest PackageManagerServiceServerTests:com.android.server.pm.UserDataPreparerTest
 @RunWith(AndroidJUnit4.class)
 @Presubmit
 @SmallTest
@@ -99,7 +99,7 @@
         systemDeDir.mkdirs();
         mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_DE);
         verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
-                eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_DE));
+                eq(StorageManager.FLAG_STORAGE_DE));
         verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID),
                 eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_DE));
         int serialNumber = UserDataPreparer.getSerialNumber(userDeDir);
@@ -116,7 +116,7 @@
         systemCeDir.mkdirs();
         mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_CE);
         verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
-                eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_CE));
+                eq(StorageManager.FLAG_STORAGE_CE));
         verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID),
                 eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_CE));
         int serialNumber = UserDataPreparer.getSerialNumber(userCeDir);
@@ -129,7 +129,7 @@
     public void testPrepareUserData_forNewUser_destroysOnFailure() throws Exception {
         TEST_USER.lastLoggedInTime = 0;
         doThrow(new IllegalStateException("expected exception for test")).when(mStorageManagerMock)
-                .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID), eq(TEST_USER_SERIAL),
+                .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
                         eq(StorageManager.FLAG_STORAGE_CE));
         mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_CE);
         verify(mStorageManagerMock).destroyUserStorage(isNull(String.class), eq(TEST_USER_ID),
@@ -140,7 +140,7 @@
     public void testPrepareUserData_forExistingUser_doesNotDestroyOnFailure() throws Exception {
         TEST_USER.lastLoggedInTime = System.currentTimeMillis();
         doThrow(new IllegalStateException("expected exception for test")).when(mStorageManagerMock)
-                .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID), eq(TEST_USER_SERIAL),
+                .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
                         eq(StorageManager.FLAG_STORAGE_CE));
         mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_CE);
         verify(mStorageManagerMock, never()).destroyUserStorage(isNull(String.class),
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index c67e7c5..b29fc88 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -824,6 +824,16 @@
                 mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
                         AUTO_BRIGHTNESS_MODE_DOZE,
                         Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT), SMALL_DELTA);
+
+        // Should fall back to the normal preset
+        assertArrayEquals(new float[]{0.0f, 95},
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+                        AUTO_BRIGHTNESS_MODE_DOZE,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM), ZERO_DELTA);
+        assertArrayEquals(new float[]{0.35f, 0.45f},
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+                        AUTO_BRIGHTNESS_MODE_DOZE,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM), SMALL_DELTA);
     }
 
     @Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 02e3ef4..75febd9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -61,6 +61,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityOptions.LaunchCookie;
 import android.app.PropertyInvalidatedCache;
 import android.companion.virtual.IVirtualDevice;
 import android.companion.virtual.IVirtualDeviceManager;
@@ -1557,7 +1558,7 @@
         when(mMockProjectionService
                 .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
                 .thenReturn(true);
-        doReturn(mock(IBinder.class)).when(projection).getLaunchCookie();
+        doReturn(new LaunchCookie()).when(projection).getLaunchCookie();
         doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
 
         final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
deleted file mode 100644
index 4cc68cf..0000000
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ /dev/null
@@ -1,1971 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
-import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
-import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
-
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isA;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.Sensor;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.hardware.display.BrightnessInfo;
-import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.os.test.TestLooper;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.provider.Settings;
-import android.testing.TestableContext;
-import android.util.FloatProperty;
-import android.util.SparseArray;
-import android.view.Display;
-import android.view.DisplayInfo;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.modules.utils.testing.ExtendedMockitoRule;
-import com.android.server.LocalServices;
-import com.android.server.am.BatteryStatsService;
-import com.android.server.display.RampAnimator.DualRampAnimator;
-import com.android.server.display.brightness.BrightnessEvent;
-import com.android.server.display.brightness.clamper.BrightnessClamperController;
-import com.android.server.display.brightness.clamper.HdrClamper;
-import com.android.server.display.color.ColorDisplayService;
-import com.android.server.display.config.SensorData;
-import com.android.server.display.feature.DisplayManagerFlags;
-import com.android.server.display.feature.flags.Flags;
-import com.android.server.display.layout.Layout;
-import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
-import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.testutils.OffsettableClock;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.quality.Strictness;
-import org.mockito.stubbing.Answer;
-
-import java.util.List;
-
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public final class DisplayPowerController2Test {
-    private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
-    private static final String UNIQUE_ID = "unique_id_test123";
-    private static final int FOLLOWER_DISPLAY_ID = DISPLAY_ID + 1;
-    private static final String FOLLOWER_UNIQUE_ID = "unique_id_456";
-    private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
-    private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
-    private static final float PROX_SENSOR_MAX_RANGE = 5;
-    private static final float BRIGHTNESS_RAMP_RATE_MINIMUM = 0.0f;
-    private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f;
-    private static final float BRIGHTNESS_RAMP_RATE_FAST_INCREASE = 0.4f;
-    private static final float BRIGHTNESS_RAMP_RATE_SLOW_DECREASE = 0.1f;
-    private static final float BRIGHTNESS_RAMP_RATE_SLOW_INCREASE = 0.2f;
-    private static final float BRIGHTNESS_RAMP_RATE_SLOW_INCREASE_IDLE = 0.5f;
-    private static final float BRIGHTNESS_RAMP_RATE_SLOW_DECREASE_IDLE = 0.6f;
-
-    private static final long BRIGHTNESS_RAMP_INCREASE_MAX = 1000;
-    private static final long BRIGHTNESS_RAMP_DECREASE_MAX = 2000;
-    private static final long BRIGHTNESS_RAMP_INCREASE_MAX_IDLE = 3000;
-    private static final long BRIGHTNESS_RAMP_DECREASE_MAX_IDLE = 4000;
-
-    private OffsettableClock mClock;
-    private TestLooper mTestLooper;
-    private Handler mHandler;
-    private DisplayPowerControllerHolder mHolder;
-    private Sensor mProxSensor;
-
-    @Mock
-    private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
-    @Mock
-    private SensorManager mSensorManagerMock;
-    @Mock
-    private DisplayBlanker mDisplayBlankerMock;
-    @Mock
-    private BrightnessTracker mBrightnessTrackerMock;
-    @Mock
-    private WindowManagerPolicy mWindowManagerPolicyMock;
-    @Mock
-    private PowerManager mPowerManagerMock;
-    @Mock
-    private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
-    @Mock
-    private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock;
-    @Mock
-    private DisplayManagerFlags mDisplayManagerFlagsMock;
-    @Mock
-    private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
-    @Captor
-    private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
-
-    @Rule
-    public final TestableContext mContext = new TestableContext(
-            InstrumentationRegistry.getInstrumentation().getContext());
-
-    @Rule
-    public final ExtendedMockitoRule mExtendedMockitoRule =
-            new ExtendedMockitoRule.Builder(this)
-                    .setStrictness(Strictness.LENIENT)
-                    .spyStatic(SystemProperties.class)
-                    .spyStatic(BatteryStatsService.class)
-                    .spyStatic(ActivityManager.class)
-                    .build();
-
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
-    @Before
-    public void setUp() throws Exception {
-        mClock = new OffsettableClock.Stopped();
-        mTestLooper = new TestLooper(mClock::now);
-        mHandler = new Handler(mTestLooper.getLooper());
-
-        // Set some settings to minimize unexpected events and have a consistent starting state
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
-        Settings.System.putFloatForUser(mContext.getContentResolver(),
-                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0, UserHandle.USER_CURRENT);
-
-        addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
-        addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
-                mCdsiMock);
-
-        mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
-
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.config_displayColorFadeDisabled, false);
-
-        doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
-                SystemProperties.set(anyString(), any()));
-        doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
-        doAnswer((Answer<Boolean>) invocationOnMock -> false)
-                .when(ActivityManager::isLowRamDeviceStatic);
-
-        setUpSensors();
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-    }
-
-    @After
-    public void tearDown() {
-        LocalServices.removeServiceForTest(WindowManagerPolicy.class);
-        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
-    }
-
-    @Test
-    public void testReleaseProxSuspendBlockersOnExit() throws Exception {
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // Send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState to start listener for the prox sensor
-        advanceTime(1);
-
-        SensorEventListener listener = getSensorEventListener(mProxSensor);
-        assertNotNull(listener);
-
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 5));
-        advanceTime(1);
-
-        // two times, one for unfinished business and one for proximity
-        verify(mHolder.wakelockController, times(2)).acquireWakelock(
-                WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
-        verify(mHolder.wakelockController).acquireWakelock(
-                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
-
-        mHolder.dpc.stop();
-        advanceTime(1);
-        // two times, one for unfinished business and one for proximity
-        verify(mHolder.wakelockController, times(2)).acquireWakelock(
-                WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
-        verify(mHolder.wakelockController).acquireWakelock(
-                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
-    }
-
-    @Test
-    public void testScreenOffBecauseOfProximity() throws Exception {
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // Send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState to start listener for the prox sensor
-        advanceTime(1);
-
-        SensorEventListener listener = getSensorEventListener(mProxSensor);
-        assertNotNull(listener);
-
-        // Send a positive proximity event
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
-        advanceTime(1);
-
-        // The display should have been turned off
-        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
-
-        clearInvocations(mHolder.displayPowerState);
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
-        // Send a negative proximity event
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor,
-                (int) PROX_SENSOR_MAX_RANGE + 1));
-        // Advance time by less than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
-        advanceTime(1);
-
-        // The prox sensor is debounced so the display should not have been turned back on yet
-        verify(mHolder.displayPowerState, never()).setScreenState(Display.STATE_ON);
-
-        // Advance time by more than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
-        advanceTime(1000);
-
-        // The display should have been turned back on
-        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
-    }
-
-    @Test
-    public void testScreenOffBecauseOfProximity_ProxSensorGone() throws Exception {
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // Send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState to start listener for the prox sensor
-        advanceTime(1);
-
-        SensorEventListener listener = getSensorEventListener(mProxSensor);
-        assertNotNull(listener);
-
-        // Send a positive proximity event
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
-        advanceTime(1);
-
-        // The display should have been turned off
-        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
-
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
-        // The display device changes and we no longer have a prox sensor
-        reset(mSensorManagerMock);
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-
-        advanceTime(1); // Run updatePowerState
-
-        // The display should have been turned back on and the listener should have been
-        // unregistered
-        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
-        verify(mSensorManagerMock).unregisterListener(listener);
-    }
-
-    @Test
-    public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() {
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        final DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-        followerDpc.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState
-        advanceTime(1);
-
-        verify(mSensorManagerMock, never()).registerListener(any(SensorEventListener.class),
-                eq(mProxSensor), anyInt(), any(Handler.class));
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
-        // Test different float scale values
-        float leadBrightness = 0.3f;
-        float followerBrightness = 0.4f;
-        float nits = 300;
-        when(mHolder.automaticBrightnessController.convertToNits(leadBrightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
-                .thenReturn(followerBrightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(leadBrightness);
-        listener.onBrightnessChanged(leadBrightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(), anyFloat(), eq(false));
-        verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
-                anyFloat(), eq(false));
-
-        clearInvocations(mHolder.animator, followerDpc.animator);
-
-        // Test the same float scale value
-        float brightness = 0.6f;
-        nits = 600;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
-                .thenReturn(brightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
-        float brightness = 0.3f;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(300f);
-        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(anyFloat()))
-                .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
-        float brightness = 0.3f;
-        when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
-        float brightness = 0.3f;
-        when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
-        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(anyFloat()))
-                .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_AutomaticBrightness() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        float leadBrightness = 0.1f;
-        float rawLeadBrightness = 0.3f;
-        float followerBrightness = 0.4f;
-        float nits = 300;
-        float ambientLux = 3000;
-        when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
-                .thenReturn(rawLeadBrightness);
-        when(mHolder.automaticBrightnessController
-                .getAutomaticScreenBrightness(any(BrightnessEvent.class)))
-                .thenReturn(leadBrightness);
-        when(mHolder.automaticBrightnessController.convertToNits(rawLeadBrightness))
-                .thenReturn(nits);
-        when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
-        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
-                .thenReturn(followerBrightness);
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-        // One triggered by handleBrightnessModeChange, another triggered by setBrightnessToFollow
-        verify(followerDpc.hbmController, times(2)).onAmbientLuxChange(ambientLux);
-        verify(followerDpc.animator, times(2)).animateTo(eq(followerBrightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-
-        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(leadBrightness);
-        when(followerDpc.displayPowerState.getScreenBrightness()).thenReturn(followerBrightness);
-        clearInvocations(mHolder.animator, followerDpc.animator);
-
-        leadBrightness = 0.05f;
-        rawLeadBrightness = 0.2f;
-        followerBrightness = 0.3f;
-        nits = 200;
-        ambientLux = 2000;
-        when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
-                .thenReturn(rawLeadBrightness);
-        when(mHolder.automaticBrightnessController
-                .getAutomaticScreenBrightness(any(BrightnessEvent.class)))
-                .thenReturn(leadBrightness);
-        when(mHolder.automaticBrightnessController.convertToNits(rawLeadBrightness))
-                .thenReturn(nits);
-        when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
-        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
-                .thenReturn(followerBrightness);
-
-        mHolder.dpc.updateBrightness();
-        advanceTime(1); // Run updatePowerState
-
-        // The second time, the animation rate should be slow
-        verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE), eq(false));
-        verify(followerDpc.hbmController).onAmbientLuxChange(ambientLux);
-        verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE), eq(false));
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() {
-        DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID,
-                FOLLOWER_UNIQUE_ID);
-        DisplayPowerControllerHolder secondFollowerDpc = createDisplayPowerController(
-                SECOND_FOLLOWER_DISPLAY_ID, SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
-        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(secondFollowerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        secondFollowerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        // Set the initial brightness on the DPC we're going to remove so we have a fixed value for
-        // it to return to.
-        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(followerDpc.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
-        final float initialFollowerBrightness = 0.3f;
-        when(followerDpc.brightnessSetting.getBrightness()).thenReturn(initialFollowerBrightness);
-        followerListener.onBrightnessChanged(initialFollowerBrightness);
-        advanceTime(1);
-        verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-
-        when(followerDpc.displayPowerState.getScreenBrightness())
-                .thenReturn(initialFollowerBrightness);
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-        mHolder.dpc.addDisplayBrightnessFollower(secondFollowerDpc.dpc);
-        clearInvocations(followerDpc.animator);
-
-        // Validate both followers are correctly registered and receiving brightness updates
-        float brightness = 0.6f;
-        float nits = 600;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
-                .thenReturn(brightness);
-        when(secondFollowerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
-                .thenReturn(brightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-        verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-
-        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
-        when(followerDpc.displayPowerState.getScreenBrightness()).thenReturn(brightness);
-        when(secondFollowerDpc.displayPowerState.getScreenBrightness()).thenReturn(brightness);
-        clearInvocations(mHolder.animator, followerDpc.animator, secondFollowerDpc.animator);
-
-        // Remove the first follower and validate it goes back to its original brightness.
-        mHolder.dpc.removeDisplayBrightnessFollower(followerDpc.dpc);
-        advanceTime(1);
-        verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), eq(false));
-
-        when(followerDpc.displayPowerState.getScreenBrightness())
-                .thenReturn(initialFollowerBrightness);
-        clearInvocations(followerDpc.animator);
-
-        // Change the brightness of the lead display and validate only the second follower responds
-        brightness = 0.7f;
-        nits = 700;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
-                .thenReturn(brightness);
-        when(secondFollowerDpc.automaticBrightnessController.getBrightnessFromNits(nits))
-                .thenReturn(brightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-        verify(followerDpc.animator, never()).animateTo(anyFloat(), anyFloat(), anyFloat(),
-                anyBoolean());
-        verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowersRemoval_RemoveAllFollowers() {
-        DisplayPowerControllerHolder followerHolder =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-        DisplayPowerControllerHolder secondFollowerHolder =
-                createDisplayPowerController(SECOND_FOLLOWER_DISPLAY_ID,
-                        SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
-        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(followerHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(secondFollowerHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        secondFollowerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        // Set the initial brightness on the DPCs we're going to remove so we have a fixed value for
-        // it to return to.
-        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(followerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
-        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(secondFollowerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener secondFollowerListener =
-                listenerCaptor.getValue();
-        final float initialFollowerBrightness = 0.3f;
-        when(followerHolder.brightnessSetting.getBrightness()).thenReturn(
-                initialFollowerBrightness);
-        when(secondFollowerHolder.brightnessSetting.getBrightness()).thenReturn(
-                initialFollowerBrightness);
-        followerListener.onBrightnessChanged(initialFollowerBrightness);
-        secondFollowerListener.onBrightnessChanged(initialFollowerBrightness);
-        advanceTime(1);
-        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-        verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-
-        when(followerHolder.displayPowerState.getScreenBrightness())
-                .thenReturn(initialFollowerBrightness);
-        when(secondFollowerHolder.displayPowerState.getScreenBrightness())
-                .thenReturn(initialFollowerBrightness);
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerHolder.dpc);
-        mHolder.dpc.addDisplayBrightnessFollower(secondFollowerHolder.dpc);
-        clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
-
-        // Validate both followers are correctly registered and receiving brightness updates
-        float brightness = 0.6f;
-        float nits = 600;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerHolder.automaticBrightnessController.getBrightnessFromNits(nits))
-                .thenReturn(brightness);
-        when(secondFollowerHolder.automaticBrightnessController.getBrightnessFromNits(nits))
-                .thenReturn(brightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-        verify(followerHolder.animator).animateTo(eq(brightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-        verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-
-        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
-        when(followerHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
-        when(secondFollowerHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
-        clearInvocations(mHolder.animator, followerHolder.animator, secondFollowerHolder.animator);
-
-        // Stop the lead DPC and validate that the followers go back to their original brightness.
-        mHolder.dpc.stop();
-        advanceTime(1);
-        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), eq(false));
-        verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), eq(false));
-        clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FAST_HDR_TRANSITIONS)
-    public void testDisplayBrightnessHdr_SkipAnimationOnHdrAppearance() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-        final float sdrBrightness = 0.1f;
-        final float hdrBrightness = 0.3f;
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
-                any(BrightnessEvent.class))).thenReturn(sdrBrightness);
-        when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(1.0f);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-
-        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(sdrBrightness);
-        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness);
-
-        when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
-                BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
-        when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
-        clearInvocations(mHolder.animator);
-
-        mHolder.dpc.updateBrightness();
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness),
-                eq(BRIGHTNESS_RAMP_RATE_MINIMUM), eq(false));
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FAST_HDR_TRANSITIONS)
-    public void testDisplayBrightnessHdr_SkipAnimationOnHdrRemoval() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-        final float sdrBrightness = 0.1f;
-        final float hdrBrightness = 0.3f;
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(mHolder.automaticBrightnessController.isInIdleMode()).thenReturn(true);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
-                any(BrightnessEvent.class))).thenReturn(sdrBrightness);
-        when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(1.0f);
-
-        when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
-                BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
-        when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-
-        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(hdrBrightness);
-        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness);
-        when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
-                BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
-
-        clearInvocations(mHolder.animator);
-
-        mHolder.dpc.updateBrightness();
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness),
-                eq(BRIGHTNESS_RAMP_RATE_MINIMUM), eq(false));
-    }
-
-    @Test
-    public void testDoesNotSetScreenStateForNonDefaultDisplayUntilBootCompleted() {
-        // We should still set screen state for the default display
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-        verify(mHolder.displayPowerState, times(2)).setScreenState(anyInt());
-
-        mHolder = createDisplayPowerController(42, UNIQUE_ID);
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-        verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
-
-        mHolder.dpc.onBootCompleted();
-        advanceTime(1); // Run updatePowerState
-        verify(mHolder.displayPowerState).setScreenState(anyInt());
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_OFF;
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(true);
-
-        // The display turns on and we use the brightness value recommended by
-        // ScreenOffBrightnessSensorController
-        clearInvocations(mHolder.screenOffBrightnessSensorController);
-        float brightness = 0.14f;
-        when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
-                .thenReturn(brightness);
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
-                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .getAutomaticScreenBrightness();
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat(), eq(false));
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() {
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(true);
-
-        // The display turns on and we use the brightness value recommended by
-        // ScreenOffBrightnessSensorController
-        clearInvocations(mHolder.screenOffBrightnessSensorController);
-        float brightness = 0.14f;
-        when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
-                .thenReturn(brightness);
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
-                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .getAutomaticScreenBrightness();
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat(), eq(false));
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() {
-        // Tests are set up with manual brightness by default, so no need to set it here.
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_OFF;
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(false);
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(false);
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsOn() {
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(false);
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsAFollower() {
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_OFF;
-
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, /* leadDisplayId= */ 42);
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(false);
-    }
-
-    @Test
-    public void testStopScreenOffBrightnessSensorControllerWhenDisplayDeviceChanges() {
-        // New display device
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
-
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController).stop();
-    }
-
-    @Test
-    public void testAutoBrightnessEnabled_DisplayIsOn() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.automaticBrightnessController).configure(
-                AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
-                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
-                /* userChangedBrightness= */ false, /* adjustment= */ 0,
-                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
-                /* shouldResetShortTermModel= */ false
-        );
-        verify(mHolder.hbmController)
-                .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
-    }
-
-    @Test
-    public void testAutoBrightnessEnabled_DisplayIsInDoze() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.automaticBrightnessController).configure(
-                AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
-                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
-                /* userChangedBrightness= */ false, /* adjustment= */ 0,
-                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
-                /* shouldResetShortTermModel= */ false
-        );
-        verify(mHolder.hbmController)
-                .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
-    }
-
-    @Test
-    public void testAutoBrightnessDisabled_ManualBrightnessMode() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        // One triggered by the test, the other by handleBrightnessModeChange
-        verify(mHolder.automaticBrightnessController, times(2)).configure(
-                AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
-                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
-                /* userChangedBrightness= */ false, /* adjustment= */ 0,
-                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
-                /* shouldResetShortTermModel= */ false
-        );
-        verify(mHolder.hbmController, times(2))
-                .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
-    }
-
-    @Test
-    public void testAutoBrightnessDisabled_DisplayIsOff() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_OFF;
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.automaticBrightnessController).configure(
-                AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
-                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
-                /* userChangedBrightness= */ false, /* adjustment= */ 0,
-                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_OFF,
-                /* shouldResetShortTermModel= */ false
-        );
-        verify(mHolder.hbmController).setAutoBrightnessEnabled(
-                AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
-    }
-
-    @Test
-    public void testAutoBrightnessDisabled_DisplayIsInDoze() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.automaticBrightnessController).configure(
-                AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
-                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
-                /* userChangedBrightness= */ false, /* adjustment= */ 0,
-                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
-                /* shouldResetShortTermModel= */ false
-        );
-        verify(mHolder.hbmController).setAutoBrightnessEnabled(
-                AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
-    }
-
-    @Test
-    public void testAutoBrightnessDisabled_FollowerDisplay() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-        mHolder.dpc.setBrightnessToFollow(0.3f, -1, 0, /* slowChange= */ false);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        // One triggered by the test, the other by handleBrightnessModeChange
-        verify(mHolder.automaticBrightnessController, times(2)).configure(
-                AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
-                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
-                /* userChangedBrightness= */ false, /* adjustment= */ 0,
-                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
-                /* shouldResetShortTermModel= */ false
-        );
-
-        // HBM should be allowed for the follower display
-        verify(mHolder.hbmController)
-                .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
-    }
-
-    @Test
-    public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
-        float brightness = 0.3f;
-        float nits = 500;
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
-                true);
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-
-        mHolder.dpc.setBrightness(brightness);
-        verify(mHolder.brightnessSetting).setBrightnessNitsForDefaultDisplay(nits);
-
-        float newBrightness = 0.4f;
-        when(mHolder.brightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
-        when(mHolder.automaticBrightnessController.getBrightnessFromNits(nits))
-                .thenReturn(newBrightness);
-        // New display device
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-        // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged
-        verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat(),
-                eq(false));
-    }
-
-    @Test
-    public void testShortTermModelPersistsWhenDisplayDeviceChanges() {
-        float lux = 2000;
-        float nits = 500;
-        when(mHolder.automaticBrightnessController.getUserLux()).thenReturn(lux);
-        when(mHolder.automaticBrightnessController.getUserNits()).thenReturn(nits);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1);
-        clearInvocations(mHolder.injector);
-
-        // New display device
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-        advanceTime(1);
-
-        verify(mHolder.injector).getAutomaticBrightnessController(
-                any(AutomaticBrightnessController.Callbacks.class),
-                any(Looper.class),
-                eq(mSensorManagerMock),
-                /* lightSensor= */ any(),
-                /* brightnessMappingStrategyMap= */ any(SparseArray.class),
-                /* lightSensorWarmUpTime= */ anyInt(),
-                /* brightnessMin= */ anyFloat(),
-                /* brightnessMax= */ anyFloat(),
-                /* dozeScaleFactor */ anyFloat(),
-                /* lightSensorRate= */ anyInt(),
-                /* initialLightSensorRate= */ anyInt(),
-                /* brighteningLightDebounceConfig */ anyLong(),
-                /* darkeningLightDebounceConfig */ anyLong(),
-                /* brighteningLightDebounceConfigIdle= */ anyLong(),
-                /* darkeningLightDebounceConfigIdle= */ anyLong(),
-                /* resetAmbientLuxAfterWarmUpConfig= */ anyBoolean(),
-                any(HysteresisLevels.class),
-                any(HysteresisLevels.class),
-                any(HysteresisLevels.class),
-                any(HysteresisLevels.class),
-                eq(mContext),
-                any(BrightnessRangeController.class),
-                any(BrightnessThrottler.class),
-                /* ambientLightHorizonShort= */ anyInt(),
-                /* ambientLightHorizonLong= */ anyInt(),
-                eq(lux),
-                eq(nits)
-        );
-    }
-
-    @Test
-    public void testUpdateBrightnessThrottlingDataId() {
-        mHolder.display.getDisplayInfoLocked().thermalBrightnessThrottlingDataId =
-                "throttling-data-id";
-        clearInvocations(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig());
-
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig())
-                .getThermalBrightnessThrottlingDataMapByThrottlingId();
-    }
-
-    @Test
-    public void testSetBrightness_BrightnessShouldBeClamped() {
-        float clampedBrightness = 0.6f;
-        when(mHolder.hbmController.getCurrentBrightnessMax()).thenReturn(clampedBrightness);
-
-        mHolder.dpc.setBrightness(PowerManager.BRIGHTNESS_MAX);
-
-        verify(mHolder.brightnessSetting).setBrightness(clampedBrightness);
-    }
-
-    @Test
-    public void testDwbcCallsHappenOnHandler() {
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-
-        mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
-        verify(mDisplayWhiteBalanceControllerMock, never()).setStrongModeEnabled(true);
-
-        // dispatch handler looper
-        advanceTime(1);
-        verify(mDisplayWhiteBalanceControllerMock, times(1)).setStrongModeEnabled(true);
-    }
-
-    @Test
-    public void testRampRatesIdle() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-        float brightness = 0.6f;
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(mHolder.automaticBrightnessController.isInIdleMode()).thenReturn(true);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
-                any(BrightnessEvent.class))).thenReturn(brightness);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-
-        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
-        brightness = 0.05f;
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
-                any(BrightnessEvent.class))).thenReturn(brightness);
-
-        mHolder.dpc.updateBrightness();
-        advanceTime(1); // Run updatePowerState
-
-        // The second time, the animation rate should be slow
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE_IDLE), eq(false));
-
-        brightness = 0.9f;
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
-                any(BrightnessEvent.class))).thenReturn(brightness);
-
-        mHolder.dpc.updateBrightness();
-        advanceTime(1); // Run updatePowerState
-        // The third time, the animation rate should be slow
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE_IDLE), eq(false));
-    }
-
-    @Test
-    public void testRampRateForHdrContent_HdrClamperOff() {
-        float hdrBrightness = 0.8f;
-        float clampedBrightness = 0.6f;
-        float transitionRate = 1.5f;
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
-        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
-        when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
-                BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
-        when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
-        when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(clampedBrightness);
-        when(mHolder.hdrClamper.getTransitionRate()).thenReturn(transitionRate);
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.animator, atLeastOnce()).animateTo(eq(hdrBrightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-    }
-
-    @Test
-    public void testRampRateForHdrContent_HdrClamperOn() {
-        float clampedBrightness = 0.6f;
-        float transitionRate = 1.5f;
-        when(mDisplayManagerFlagsMock.isHdrClamperEnabled()).thenReturn(true);
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
-        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
-        when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
-                BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
-        when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(PowerManager.BRIGHTNESS_MAX);
-        when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(clampedBrightness);
-        when(mHolder.hdrClamper.getTransitionRate()).thenReturn(transitionRate);
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.animator, atLeastOnce()).animateTo(eq(clampedBrightness), anyFloat(),
-                eq(transitionRate), eq(true));
-    }
-
-    @Test
-    public void testRampRateForClampersControllerApplied() {
-        float transitionRate = 1.5f;
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
-        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
-        when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
-                invocation -> DisplayBrightnessState.builder()
-                        .setIsSlowChange(invocation.getArgument(2))
-                        .setBrightness(invocation.getArgument(1))
-                        .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
-                        .setCustomAnimationRate(transitionRate).build());
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.animator, atLeastOnce()).animateTo(anyFloat(), anyFloat(),
-                eq(transitionRate), anyBoolean());
-    }
-
-    @Test
-    public void testRampRateForClampersControllerNotApplied_ifDoze() {
-        float transitionRate = 1.5f;
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
-        dpr.dozeScreenState = Display.STATE_UNKNOWN;
-        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
-        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
-        when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
-                invocation -> DisplayBrightnessState.builder()
-                        .setIsSlowChange(invocation.getArgument(2))
-                        .setBrightness(invocation.getArgument(1))
-                        .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
-                        .setCustomAnimationRate(transitionRate).build());
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.animator, atLeastOnce()).animateTo(anyFloat(), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), anyBoolean());
-        verify(mHolder.animator, never()).animateTo(anyFloat(), anyFloat(),
-                eq(transitionRate), anyBoolean());
-    }
-
-    @Test
-    @RequiresFlagsDisabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1)
-    public void testRampMaxTimeInteractiveThenIdle() {
-        // Send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState
-        advanceTime(1);
-
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mHolder.config, /* isEnabled= */ true);
-        verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
-                BRIGHTNESS_RAMP_DECREASE_MAX);
-
-        // switch to idle mode
-        mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
-        advanceTime(1);
-
-        // A second time, when switching to idle mode.
-        verify(mHolder.animator, times(2)).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
-                BRIGHTNESS_RAMP_DECREASE_MAX);
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1)
-    public void testRampMaxTimeInteractiveThenIdle_DifferentValues() {
-        when(mDisplayManagerFlagsMock.isAdaptiveTone1Enabled()).thenReturn(true);
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true);
-
-        // Send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState
-        advanceTime(1);
-
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mHolder.config, /* isEnabled= */ true);
-        verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
-                BRIGHTNESS_RAMP_DECREASE_MAX);
-
-        // switch to idle mode
-        mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
-        advanceTime(1);
-
-        // A second time, when switching to idle mode.
-        verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX_IDLE,
-                BRIGHTNESS_RAMP_DECREASE_MAX_IDLE);
-    }
-
-    @Test
-    @RequiresFlagsDisabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1)
-    public void testRampMaxTimeIdle() {
-        // Send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-        // Run updatePowerState
-        advanceTime(1);
-        // Once on setup
-        verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
-                BRIGHTNESS_RAMP_DECREASE_MAX);
-
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mHolder.config, /* isEnabled= */ true);
-
-        // switch to idle mode
-        mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
-
-        // A second time when switching to idle mode.
-        verify(mHolder.animator, times(2)).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
-                BRIGHTNESS_RAMP_DECREASE_MAX);
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1)
-    public void testRampMaxTimeIdle_DifferentValues() {
-        when(mDisplayManagerFlagsMock.isAdaptiveTone1Enabled()).thenReturn(true);
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true);
-
-        // Send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState
-        advanceTime(1);
-
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mHolder.config, /* isEnabled= */ true);
-
-        // switch to idle mode
-        mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
-
-        verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX_IDLE,
-                BRIGHTNESS_RAMP_DECREASE_MAX_IDLE);
-    }
-
-    @Test
-    public void testDozeScreenStateOverride_toSupportedOffloadStateFromDoze_DisplayStateChanges() {
-        // set up.
-        int initState = Display.STATE_DOZE;
-        int supportedTargetState = Display.STATE_DOZE_SUSPEND;
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        doAnswer(invocation -> {
-            when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
-            return null;
-        }).when(mHolder.displayPowerState).setScreenState(anyInt());
-        mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
-
-        // start with DOZE.
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(initState);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        mHolder.dpc.overrideDozeScreenState(supportedTargetState);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.displayPowerState).setScreenState(supportedTargetState);
-    }
-
-    @Test
-    public void testDozeScreenStateOverride_toUnSupportedOffloadStateFromDoze_stateRemains() {
-        // set up.
-        int initState = Display.STATE_DOZE;
-        int unSupportedTargetState = Display.STATE_ON;
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        doAnswer(invocation -> {
-            when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
-            return null;
-        }).when(mHolder.displayPowerState).setScreenState(anyInt());
-        mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
-
-        // start with DOZE.
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(initState);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        mHolder.dpc.overrideDozeScreenState(unSupportedTargetState);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
-    }
-
-    @Test
-    public void testDozeScreenStateOverride_toSupportedOffloadStateFromOFF_stateRemains() {
-        // set up.
-        int initState = Display.STATE_OFF;
-        int supportedTargetState = Display.STATE_DOZE_SUSPEND;
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-        doAnswer(invocation -> {
-            when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0));
-            return null;
-        }).when(mHolder.displayPowerState).setScreenState(anyInt());
-        mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
-
-        // start with OFF.
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(initState);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_OFF;
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        mHolder.dpc.overrideDozeScreenState(supportedTargetState);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
-    }
-
-    @Test
-    public void testBrightnessFromOffload() {
-        when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-        float brightness = 0.34f;
-        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
-                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
-
-        mHolder.dpc.setBrightnessFromOffload(brightness);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        // One triggered by handleBrightnessModeChange, another triggered by
-        // setBrightnessFromOffload
-        verify(mHolder.animator, times(2)).animateTo(eq(brightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-    }
-
-    @Test
-    public void testSwitchToDozeAutoBrightnessMode() {
-        when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        // One triggered by handleBrightnessModeChange, another triggered by requestPowerState
-        verify(mHolder.automaticBrightnessController, times(2))
-                .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
-
-        // Back to default mode
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.automaticBrightnessController).switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT);
-    }
-
-    @Test
-    public void testDoesNotSwitchFromIdleToDozeAutoBrightnessMode() {
-        when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
-        when(mHolder.automaticBrightnessController.isInIdleMode()).thenReturn(true);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.automaticBrightnessController, never())
-                .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
-    }
-
-    @Test
-    public void testDoesNotSwitchDozeAutoBrightnessModeIfFeatureFlagOff() {
-        when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(false);
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.automaticBrightnessController, never())
-                .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
-    }
-
-    /**
-     * Creates a mock and registers it to {@link LocalServices}.
-     */
-    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
-        LocalServices.removeServiceForTest(clazz);
-        LocalServices.addService(clazz, mock);
-    }
-
-    private void advanceTime(long timeMs) {
-        mClock.fastForward(timeMs);
-        mTestLooper.dispatchAll();
-    }
-
-    private void setUpSensors() throws Exception {
-        mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY,
-                PROX_SENSOR_MAX_RANGE);
-        Sensor screenOffBrightnessSensor = TestUtils.createSensor(
-                Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
-        when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
-                .thenReturn(List.of(mProxSensor, screenOffBrightnessSensor));
-    }
-
-    private SensorEventListener getSensorEventListener(Sensor sensor) {
-        verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(),
-                eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class));
-        return mSensorEventListenerCaptor.getValue();
-    }
-
-    private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
-            DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock,
-            boolean isEnabled) {
-        DisplayInfo info = new DisplayInfo();
-        DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
-        deviceInfo.uniqueId = uniqueId;
-
-        when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
-        when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock);
-        when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
-        when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
-        when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
-        when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
-        when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
-        when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
-        when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
-                new SensorData(Sensor.STRING_TYPE_PROXIMITY, null));
-        when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
-        when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
-        when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
-                new SensorData());
-        when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
-                new SensorData(Sensor.STRING_TYPE_LIGHT, null));
-        when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
-                .thenReturn(new int[0]);
-
-        when(displayDeviceConfigMock.getBrightnessRampFastDecrease())
-                .thenReturn(BRIGHTNESS_RAMP_RATE_FAST_DECREASE);
-        when(displayDeviceConfigMock.getBrightnessRampFastIncrease())
-                .thenReturn(BRIGHTNESS_RAMP_RATE_FAST_INCREASE);
-        when(displayDeviceConfigMock.getBrightnessRampSlowDecrease())
-                .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE);
-        when(displayDeviceConfigMock.getBrightnessRampSlowIncrease())
-                .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE);
-        when(displayDeviceConfigMock.getBrightnessRampSlowIncreaseIdle())
-                .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE_IDLE);
-        when(displayDeviceConfigMock.getBrightnessRampSlowDecreaseIdle())
-                .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE_IDLE);
-
-        when(displayDeviceConfigMock.getBrightnessRampIncreaseMaxMillis())
-                .thenReturn(BRIGHTNESS_RAMP_INCREASE_MAX);
-        when(displayDeviceConfigMock.getBrightnessRampDecreaseMaxMillis())
-                .thenReturn(BRIGHTNESS_RAMP_DECREASE_MAX);
-        when(displayDeviceConfigMock.getBrightnessRampIncreaseMaxIdleMillis())
-                .thenReturn(BRIGHTNESS_RAMP_INCREASE_MAX_IDLE);
-        when(displayDeviceConfigMock.getBrightnessRampDecreaseMaxIdleMillis())
-                .thenReturn(BRIGHTNESS_RAMP_DECREASE_MAX_IDLE);
-    }
-
-    private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
-            String uniqueId) {
-        return createDisplayPowerController(displayId, uniqueId, /* isEnabled= */ true);
-    }
-
-    private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
-            String uniqueId, boolean isEnabled) {
-        final DisplayPowerState displayPowerState = mock(DisplayPowerState.class);
-        final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class);
-        final AutomaticBrightnessController automaticBrightnessController =
-                mock(AutomaticBrightnessController.class);
-        final WakelockController wakelockController = mock(WakelockController.class);
-        final BrightnessMappingStrategy brightnessMappingStrategy =
-                mock(BrightnessMappingStrategy.class);
-        final HysteresisLevels hysteresisLevels = mock(HysteresisLevels.class);
-        final ScreenOffBrightnessSensorController screenOffBrightnessSensorController =
-                mock(ScreenOffBrightnessSensorController.class);
-        final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
-        final HdrClamper hdrClamper = mock(HdrClamper.class);
-        BrightnessClamperController clamperController = mock(BrightnessClamperController.class);
-
-        when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
-        when(clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
-                invocation -> DisplayBrightnessState.builder()
-                        .setIsSlowChange(invocation.getArgument(2))
-                        .setBrightness(invocation.getArgument(1))
-                        .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
-                        .setCustomAnimationRate(-1).build());
-
-        TestInjector injector = spy(new TestInjector(displayPowerState, animator,
-                automaticBrightnessController, wakelockController, brightnessMappingStrategy,
-                hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper,
-                clamperController, mDisplayManagerFlagsMock));
-
-        final LogicalDisplay display = mock(LogicalDisplay.class);
-        final DisplayDevice device = mock(DisplayDevice.class);
-        final HighBrightnessModeMetadata hbmMetadata = mock(HighBrightnessModeMetadata.class);
-        final BrightnessSetting brightnessSetting = mock(BrightnessSetting.class);
-        final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class);
-
-        setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
-
-        final DisplayPowerController2 dpc = new DisplayPowerController2(
-                mContext, injector, mDisplayPowerCallbacksMock, mHandler,
-                mSensorManagerMock, mDisplayBlankerMock, display,
-                mBrightnessTrackerMock, brightnessSetting, () -> {
-        },
-                hbmMetadata, /* bootCompleted= */ false, mDisplayManagerFlagsMock);
-
-        return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
-                animator, automaticBrightnessController, wakelockController,
-                screenOffBrightnessSensorController, hbmController, hdrClamper, clamperController,
-                hbmMetadata, brightnessMappingStrategy, injector, config);
-    }
-
-    /**
-     * A class for holding a DisplayPowerController under test and all the mocks specifically
-     * related to it.
-     */
-    private static class DisplayPowerControllerHolder {
-        public final DisplayPowerController2 dpc;
-        public final LogicalDisplay display;
-        public final DisplayPowerState displayPowerState;
-        public final BrightnessSetting brightnessSetting;
-        public final DualRampAnimator<DisplayPowerState> animator;
-        public final AutomaticBrightnessController automaticBrightnessController;
-        public final WakelockController wakelockController;
-        public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController;
-        public final HighBrightnessModeController hbmController;
-
-        public final HdrClamper hdrClamper;
-        public final BrightnessClamperController clamperController;
-        public final HighBrightnessModeMetadata hbmMetadata;
-        public final BrightnessMappingStrategy brightnessMappingStrategy;
-        public final DisplayPowerController2.Injector injector;
-        public final DisplayDeviceConfig config;
-
-        DisplayPowerControllerHolder(DisplayPowerController2 dpc, LogicalDisplay display,
-                DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting,
-                DualRampAnimator<DisplayPowerState> animator,
-                AutomaticBrightnessController automaticBrightnessController,
-                WakelockController wakelockController,
-                ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
-                HighBrightnessModeController hbmController,
-                HdrClamper hdrClamper,
-                BrightnessClamperController clamperController,
-                HighBrightnessModeMetadata hbmMetadata,
-                BrightnessMappingStrategy brightnessMappingStrategy,
-                DisplayPowerController2.Injector injector,
-                DisplayDeviceConfig config) {
-            this.dpc = dpc;
-            this.display = display;
-            this.displayPowerState = displayPowerState;
-            this.brightnessSetting = brightnessSetting;
-            this.animator = animator;
-            this.automaticBrightnessController = automaticBrightnessController;
-            this.wakelockController = wakelockController;
-            this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
-            this.hbmController = hbmController;
-            this.hdrClamper = hdrClamper;
-            this.clamperController = clamperController;
-            this.hbmMetadata = hbmMetadata;
-            this.brightnessMappingStrategy = brightnessMappingStrategy;
-            this.injector = injector;
-            this.config = config;
-        }
-    }
-
-    private class TestInjector extends DisplayPowerController2.Injector {
-        private final DisplayPowerState mDisplayPowerState;
-        private final DualRampAnimator<DisplayPowerState> mAnimator;
-        private final AutomaticBrightnessController mAutomaticBrightnessController;
-        private final WakelockController mWakelockController;
-        private final BrightnessMappingStrategy mBrightnessMappingStrategy;
-        private final HysteresisLevels mHysteresisLevels;
-        private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
-        private final HighBrightnessModeController mHighBrightnessModeController;
-
-        private final HdrClamper mHdrClamper;
-
-        private final BrightnessClamperController mClamperController;
-
-        private final DisplayManagerFlags mFlags;
-
-        TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
-                AutomaticBrightnessController automaticBrightnessController,
-                WakelockController wakelockController,
-                BrightnessMappingStrategy brightnessMappingStrategy,
-                HysteresisLevels hysteresisLevels,
-                ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
-                HighBrightnessModeController highBrightnessModeController,
-                HdrClamper hdrClamper,
-                BrightnessClamperController clamperController,
-                DisplayManagerFlags flags) {
-            mDisplayPowerState = dps;
-            mAnimator = animator;
-            mAutomaticBrightnessController = automaticBrightnessController;
-            mWakelockController = wakelockController;
-            mBrightnessMappingStrategy = brightnessMappingStrategy;
-            mHysteresisLevels = hysteresisLevels;
-            mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
-            mHighBrightnessModeController = highBrightnessModeController;
-            mHdrClamper = hdrClamper;
-            mClamperController = clamperController;
-            mFlags = flags;
-        }
-
-        @Override
-        DisplayPowerController2.Clock getClock() {
-            return mClock::now;
-        }
-
-        @Override
-        DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
-                int displayId, int displayState) {
-            return mDisplayPowerState;
-        }
-
-        @Override
-        DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
-                FloatProperty<DisplayPowerState> firstProperty,
-                FloatProperty<DisplayPowerState> secondProperty) {
-            return mAnimator;
-        }
-
-        @Override
-        WakelockController getWakelockController(int displayId,
-                DisplayPowerCallbacks displayPowerCallbacks) {
-            return mWakelockController;
-        }
-
-        @Override
-        DisplayPowerProximityStateController getDisplayPowerProximityStateController(
-                WakelockController wakelockController, DisplayDeviceConfig displayDeviceConfig,
-                Looper looper, Runnable nudgeUpdatePowerState, int displayId,
-                SensorManager sensorManager) {
-            return new DisplayPowerProximityStateController(wakelockController,
-                    displayDeviceConfig, looper, nudgeUpdatePowerState, displayId,
-                    sensorManager,
-                    new DisplayPowerProximityStateController.Injector() {
-                        @Override
-                        DisplayPowerProximityStateController.Clock createClock() {
-                            return mClock::now;
-                        }
-                    });
-        }
-
-        @Override
-        AutomaticBrightnessController getAutomaticBrightnessController(
-                AutomaticBrightnessController.Callbacks callbacks, Looper looper,
-                SensorManager sensorManager, Sensor lightSensor,
-                SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap,
-                int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
-                float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
-                long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
-                long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle,
-                boolean resetAmbientLuxAfterWarmUpConfig,
-                HysteresisLevels ambientBrightnessThresholds,
-                HysteresisLevels screenBrightnessThresholds,
-                HysteresisLevels ambientBrightnessThresholdsIdle,
-                HysteresisLevels screenBrightnessThresholdsIdle, Context context,
-                BrightnessRangeController brightnessRangeController,
-                BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
-                int ambientLightHorizonLong, float userLux, float userNits) {
-            return mAutomaticBrightnessController;
-        }
-
-        @Override
-        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
-                DisplayDeviceConfig displayDeviceConfig,
-                DisplayWhiteBalanceController displayWhiteBalanceController) {
-            return mBrightnessMappingStrategy;
-        }
-
-        @Override
-        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
-                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
-                float[] darkeningThresholdLevels, float minDarkeningThreshold,
-                float minBrighteningThreshold) {
-            return mHysteresisLevels;
-        }
-
-        @Override
-        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
-                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
-                float[] darkeningThresholdLevels, float minDarkeningThreshold,
-                float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
-            return mHysteresisLevels;
-        }
-
-        @Override
-        ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
-                SensorManager sensorManager, Sensor lightSensor, Handler handler,
-                ScreenOffBrightnessSensorController.Clock clock, int[] sensorValueToLux,
-                BrightnessMappingStrategy brightnessMapper) {
-            return mScreenOffBrightnessSensorController;
-        }
-
-        @Override
-        HighBrightnessModeController getHighBrightnessModeController(Handler handler, int width,
-                int height, IBinder displayToken, String displayUniqueId, float brightnessMin,
-                float brightnessMax, DisplayDeviceConfig.HighBrightnessModeData hbmData,
-                HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg,
-                Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata,
-                Context context) {
-            return mHighBrightnessModeController;
-        }
-
-        @Override
-        BrightnessRangeController getBrightnessRangeController(
-                HighBrightnessModeController hbmController, Runnable modeChangeCallback,
-                DisplayDeviceConfig displayDeviceConfig, Handler handler,
-                DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
-            return new BrightnessRangeController(hbmController, modeChangeCallback,
-                    displayDeviceConfig, mHdrClamper, mFlags, displayToken, info);
-        }
-
-        @Override
-        BrightnessClamperController getBrightnessClamperController(Handler handler,
-                BrightnessClamperController.ClamperChangeListener clamperChangeListener,
-                BrightnessClamperController.DisplayDeviceData data, Context context,
-                DisplayManagerFlags flags) {
-            return mClamperController;
-        }
-
-        @Override
-        DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
-                SensorManager sensorManager, Resources resources) {
-            return mDisplayWhiteBalanceControllerMock;
-        }
-    }
-}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 943862f..88a9758 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -18,6 +18,8 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
 
 import static org.junit.Assert.assertNotNull;
@@ -67,15 +69,16 @@
 import android.view.DisplayInfo;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.internal.util.test.LocalServiceKeeperRule;
 import com.android.modules.utils.testing.ExtendedMockitoRule;
+import com.android.server.LocalServices;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.display.RampAnimator.DualRampAnimator;
 import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.brightness.clamper.BrightnessClamperController;
+import com.android.server.display.brightness.clamper.HdrClamper;
 import com.android.server.display.color.ColorDisplayService;
 import com.android.server.display.config.SensorData;
 import com.android.server.display.feature.DisplayManagerFlags;
@@ -85,6 +88,7 @@
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.testutils.OffsettableClock;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -147,7 +151,6 @@
     private DisplayManagerFlags mDisplayManagerFlagsMock;
     @Mock
     private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
-
     @Captor
     private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
 
@@ -165,9 +168,6 @@
                     .build();
 
     @Rule
-    public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule();
-
-    @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Before
@@ -183,10 +183,9 @@
         Settings.System.putFloatForUser(mContext.getContentResolver(),
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0, UserHandle.USER_CURRENT);
 
-        mLocalServiceKeeperRule.overrideLocalService(
-                WindowManagerPolicy.class, mWindowManagerPolicyMock);
-        mLocalServiceKeeperRule.overrideLocalService(
-                ColorDisplayService.ColorDisplayServiceInternal.class, mCdsiMock);
+        addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
+        addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
+                mCdsiMock);
 
         mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
 
@@ -203,6 +202,12 @@
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
     }
 
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(WindowManagerPolicy.class);
+        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
+    }
+
     @Test
     public void testReleaseProxSuspendBlockersOnExit() throws Exception {
         when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
@@ -222,19 +227,18 @@
         advanceTime(1);
 
         // two times, one for unfinished business and one for proximity
-        verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
-                mHolder.dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
-        verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
-                mHolder.dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+        verify(mHolder.wakelockController, times(2)).acquireWakelock(
+                WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
+        verify(mHolder.wakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
 
         mHolder.dpc.stop();
         advanceTime(1);
-
         // two times, one for unfinished business and one for proximity
-        verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
-                mHolder.dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
-        verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
-                mHolder.dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+        verify(mHolder.wakelockController, times(2)).acquireWakelock(
+                WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
+        verify(mHolder.wakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
     }
 
     @Test
@@ -316,14 +320,13 @@
 
     @Test
     public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-
         when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
         // send a display power request
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
         dpr.useProximitySensor = true;
+        final DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
         followerDpc.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
 
         // Run updatePowerState
@@ -334,7 +337,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 294107062)
     public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
         DisplayPowerControllerHolder followerDpc =
                 createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
@@ -363,13 +365,10 @@
         when(mHolder.brightnessSetting.getBrightness()).thenReturn(leadBrightness);
         listener.onBrightnessChanged(leadBrightness);
         advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+        verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(), anyFloat(), eq(false));
         verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+                anyFloat(), eq(false));
 
-        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(leadBrightness);
-        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(followerBrightness);
         clearInvocations(mHolder.animator, followerDpc.animator);
 
         // Test the same float scale value
@@ -388,7 +387,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 294107062)
     public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() {
         DisplayPowerControllerHolder followerDpc =
                 createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
@@ -421,7 +419,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 294107062)
     public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() {
         DisplayPowerControllerHolder followerDpc =
                 createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
@@ -452,7 +449,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 294107062)
     public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() {
         DisplayPowerControllerHolder followerDpc =
                 createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
@@ -485,7 +481,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 294107062)
     public void testDisplayBrightnessFollowers_AutomaticBrightness() {
         Settings.System.putInt(mContext.getContentResolver(),
                 Settings.System.SCREEN_BRIGHTNESS_MODE,
@@ -557,7 +552,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 294107062)
     public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() {
         DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID,
                 FOLLOWER_UNIQUE_ID);
@@ -650,7 +644,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 294107062)
     public void testDisplayBrightnessFollowersRemoval_RemoveAllFollowers() {
         DisplayPowerControllerHolder followerHolder =
                 createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
@@ -737,6 +730,82 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FAST_HDR_TRANSITIONS)
+    public void testDisplayBrightnessHdr_SkipAnimationOnHdrAppearance() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        final float sdrBrightness = 0.1f;
+        final float hdrBrightness = 0.3f;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+                any(BrightnessEvent.class))).thenReturn(sdrBrightness);
+        when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(sdrBrightness);
+        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness);
+
+        when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+                BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
+        when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
+        clearInvocations(mHolder.animator);
+
+        mHolder.dpc.updateBrightness();
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness),
+                eq(BRIGHTNESS_RAMP_RATE_MINIMUM), eq(false));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FAST_HDR_TRANSITIONS)
+    public void testDisplayBrightnessHdr_SkipAnimationOnHdrRemoval() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        final float sdrBrightness = 0.1f;
+        final float hdrBrightness = 0.3f;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.automaticBrightnessController.isInIdleMode()).thenReturn(true);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+                any(BrightnessEvent.class))).thenReturn(sdrBrightness);
+        when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(1.0f);
+
+        when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+                BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
+        when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(hdrBrightness);
+        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness);
+        when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+                BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
+
+        clearInvocations(mHolder.animator);
+
+        mHolder.dpc.updateBrightness();
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness),
+                eq(BRIGHTNESS_RAMP_RATE_MINIMUM), eq(false));
+    }
+
+    @Test
     public void testDoesNotSetScreenStateForNonDefaultDisplayUntilBootCompleted() {
         // We should still set screen state for the default display
         DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -761,6 +830,8 @@
                 Settings.System.SCREEN_BRIGHTNESS_MODE,
                 Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
 
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_OFF;
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
@@ -798,6 +869,7 @@
                 Settings.System.SCREEN_BRIGHTNESS_MODE,
                 Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
 
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_DOZE;
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
@@ -842,7 +914,6 @@
         Settings.System.putInt(mContext.getContentResolver(),
                 Settings.System.SCREEN_BRIGHTNESS_MODE,
                 Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -1048,7 +1119,6 @@
         mContext.getOrCreateTestableResources().addOverride(
                 com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
                 true);
-
         mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
         when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
 
@@ -1199,76 +1269,98 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FAST_HDR_TRANSITIONS)
-    public void testDisplayBrightnessHdr_SkipAnimationOnHdrAppearance() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-        final float sdrBrightness = 0.1f;
-        final float hdrBrightness = 0.3f;
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
-                any(BrightnessEvent.class))).thenReturn(sdrBrightness);
+    public void testRampRateForHdrContent_HdrClamperOff() {
+        float hdrBrightness = 0.8f;
+        float clampedBrightness = 0.6f;
+        float transitionRate = 1.5f;
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
-
-        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(sdrBrightness);
-        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness);
-
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
+        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
         when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
                 BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
         when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
-        clearInvocations(mHolder.animator);
+        when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(clampedBrightness);
+        when(mHolder.hdrClamper.getTransitionRate()).thenReturn(transitionRate);
 
-        mHolder.dpc.updateBrightness();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
         advanceTime(1); // Run updatePowerState
 
-        verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness),
-                eq(BRIGHTNESS_RAMP_RATE_MINIMUM), eq(false));
+        verify(mHolder.animator, atLeastOnce()).animateTo(eq(hdrBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_FAST_HDR_TRANSITIONS)
-    public void testDisplayBrightnessHdr_SkipAnimationOnHdrRemoval() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-        final float sdrBrightness = 0.1f;
-        final float hdrBrightness = 0.3f;
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
-                any(BrightnessEvent.class))).thenReturn(sdrBrightness);
-
-        when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
-                BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
-        when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
+    public void testRampRateForHdrContent_HdrClamperOn() {
+        float clampedBrightness = 0.6f;
+        float transitionRate = 1.5f;
+        when(mDisplayManagerFlagsMock.isHdrClamperEnabled()).thenReturn(true);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true);
 
         DisplayPowerRequest dpr = new DisplayPowerRequest();
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
+        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
+        when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+                BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
+        when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(PowerManager.BRIGHTNESS_MAX);
+        when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(clampedBrightness);
+        when(mHolder.hdrClamper.getTransitionRate()).thenReturn(transitionRate);
+
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
         advanceTime(1); // Run updatePowerState
 
-        verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness),
-                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+        verify(mHolder.animator, atLeastOnce()).animateTo(eq(clampedBrightness), anyFloat(),
+                eq(transitionRate), eq(true));
+    }
 
-        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(hdrBrightness);
-        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness);
-        when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
-                BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
+    @Test
+    public void testRampRateForClampersControllerApplied() {
+        float transitionRate = 1.5f;
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
+        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
+        when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+                invocation -> DisplayBrightnessState.builder()
+                        .setIsSlowChange(invocation.getArgument(2))
+                        .setBrightness(invocation.getArgument(1))
+                        .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
+                        .setCustomAnimationRate(transitionRate).build());
 
-        clearInvocations(mHolder.animator);
-
-        mHolder.dpc.updateBrightness();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
         advanceTime(1); // Run updatePowerState
 
-        verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness),
-                eq(BRIGHTNESS_RAMP_RATE_MINIMUM), eq(false));
+        verify(mHolder.animator, atLeastOnce()).animateTo(anyFloat(), anyFloat(),
+                eq(transitionRate), anyBoolean());
+    }
+
+    @Test
+    public void testRampRateForClampersControllerNotApplied_ifDoze() {
+        float transitionRate = 1.5f;
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        dpr.dozeScreenState = Display.STATE_UNKNOWN;
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
+        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
+        when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+                invocation -> DisplayBrightnessState.builder()
+                        .setIsSlowChange(invocation.getArgument(2))
+                        .setBrightness(invocation.getArgument(1))
+                        .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
+                        .setCustomAnimationRate(transitionRate).build());
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator, atLeastOnce()).animateTo(anyFloat(), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), anyBoolean());
+        verify(mHolder.animator, never()).animateTo(anyFloat(), anyFloat(),
+                eq(transitionRate), anyBoolean());
     }
 
     @Test
@@ -1285,14 +1377,14 @@
 
         setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
                 mHolder.config, /* isEnabled= */ true);
-
         verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
                 BRIGHTNESS_RAMP_DECREASE_MAX);
 
-        // switch to idle
+        // switch to idle mode
         mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
         advanceTime(1);
 
+        // A second time, when switching to idle mode.
         verify(mHolder.animator, times(2)).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
                 BRIGHTNESS_RAMP_DECREASE_MAX);
     }
@@ -1301,6 +1393,8 @@
     @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1)
     public void testRampMaxTimeInteractiveThenIdle_DifferentValues() {
         when(mDisplayManagerFlagsMock.isAdaptiveTone1Enabled()).thenReturn(true);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true);
+
         // Send a display power request
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
@@ -1312,14 +1406,14 @@
 
         setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
                 mHolder.config, /* isEnabled= */ true);
-
         verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
                 BRIGHTNESS_RAMP_DECREASE_MAX);
 
-        // switch to idle
+        // switch to idle mode
         mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
         advanceTime(1);
 
+        // A second time, when switching to idle mode.
         verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX_IDLE,
                 BRIGHTNESS_RAMP_DECREASE_MAX_IDLE);
     }
@@ -1332,11 +1426,9 @@
         dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
         dpr.useProximitySensor = true;
         mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
         // Run updatePowerState
         advanceTime(1);
-
-        // once on setup
+        // Once on setup
         verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
                 BRIGHTNESS_RAMP_DECREASE_MAX);
 
@@ -1346,7 +1438,7 @@
         // switch to idle mode
         mHolder.dpc.setAutomaticScreenBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
 
-        // second time when switching to idle screen brightness mode
+        // A second time when switching to idle mode.
         verify(mHolder.animator, times(2)).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX,
                 BRIGHTNESS_RAMP_DECREASE_MAX);
     }
@@ -1355,6 +1447,7 @@
     @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1)
     public void testRampMaxTimeIdle_DifferentValues() {
         when(mDisplayManagerFlagsMock.isAdaptiveTone1Enabled()).thenReturn(true);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true);
 
         // Send a display power request
         DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -1374,6 +1467,7 @@
         verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX_IDLE,
                 BRIGHTNESS_RAMP_DECREASE_MAX_IDLE);
     }
+
     @Test
     public void testDozeScreenStateOverride_toSupportedOffloadStateFromDoze_DisplayStateChanges() {
         // set up.
@@ -1451,6 +1545,89 @@
         verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
     }
 
+    @Test
+    public void testBrightnessFromOffload() {
+        when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        float brightness = 0.34f;
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+
+        mHolder.dpc.setBrightnessFromOffload(brightness);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        // One triggered by handleBrightnessModeChange, another triggered by
+        // setBrightnessFromOffload
+        verify(mHolder.animator, times(2)).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+    }
+
+    @Test
+    public void testSwitchToDozeAutoBrightnessMode() {
+        when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        // One triggered by handleBrightnessModeChange, another triggered by requestPowerState
+        verify(mHolder.automaticBrightnessController, times(2))
+                .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+
+        // Back to default mode
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController).switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT);
+    }
+
+    @Test
+    public void testDoesNotSwitchFromIdleToDozeAutoBrightnessMode() {
+        when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+        when(mHolder.automaticBrightnessController.isInIdleMode()).thenReturn(true);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController, never())
+                .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+    }
+
+    @Test
+    public void testDoesNotSwitchDozeAutoBrightnessModeIfFeatureFlagOff() {
+        when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(false);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController, never())
+                .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+    }
+
+    /**
+     * Creates a mock and registers it to {@link LocalServices}.
+     */
+    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+        LocalServices.removeServiceForTest(clazz);
+        LocalServices.addService(clazz, mock);
+    }
+
     private void advanceTime(long timeMs) {
         mClock.fastForward(timeMs);
         mTestLooper.dispatchAll();
@@ -1505,10 +1682,10 @@
                 .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE);
         when(displayDeviceConfigMock.getBrightnessRampSlowIncrease())
                 .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE);
-        when(displayDeviceConfigMock.getBrightnessRampSlowDecreaseIdle())
-                .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE_IDLE);
         when(displayDeviceConfigMock.getBrightnessRampSlowIncreaseIdle())
                 .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE_IDLE);
+        when(displayDeviceConfigMock.getBrightnessRampSlowDecreaseIdle())
+                .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE_IDLE);
 
         when(displayDeviceConfigMock.getBrightnessRampIncreaseMaxMillis())
                 .thenReturn(BRIGHTNESS_RAMP_INCREASE_MAX);
@@ -1531,18 +1708,28 @@
         final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class);
         final AutomaticBrightnessController automaticBrightnessController =
                 mock(AutomaticBrightnessController.class);
+        final WakelockController wakelockController = mock(WakelockController.class);
         final BrightnessMappingStrategy brightnessMappingStrategy =
                 mock(BrightnessMappingStrategy.class);
         final HysteresisLevels hysteresisLevels = mock(HysteresisLevels.class);
         final ScreenOffBrightnessSensorController screenOffBrightnessSensorController =
                 mock(ScreenOffBrightnessSensorController.class);
         final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
+        final HdrClamper hdrClamper = mock(HdrClamper.class);
+        BrightnessClamperController clamperController = mock(BrightnessClamperController.class);
 
         when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
+        when(clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+                invocation -> DisplayBrightnessState.builder()
+                        .setIsSlowChange(invocation.getArgument(2))
+                        .setBrightness(invocation.getArgument(1))
+                        .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
+                        .setCustomAnimationRate(-1).build());
 
-        DisplayPowerController.Injector injector = spy(new TestInjector(displayPowerState, animator,
-                automaticBrightnessController, brightnessMappingStrategy, hysteresisLevels,
-                screenOffBrightnessSensorController, hbmController));
+        TestInjector injector = spy(new TestInjector(displayPowerState, animator,
+                automaticBrightnessController, wakelockController, brightnessMappingStrategy,
+                hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper,
+                clamperController, mDisplayManagerFlagsMock));
 
         final LogicalDisplay display = mock(LogicalDisplay.class);
         final DisplayDevice device = mock(DisplayDevice.class);
@@ -1555,12 +1742,14 @@
         final DisplayPowerController dpc = new DisplayPowerController(
                 mContext, injector, mDisplayPowerCallbacksMock, mHandler,
                 mSensorManagerMock, mDisplayBlankerMock, display,
-                mBrightnessTrackerMock, brightnessSetting, () -> {},
+                mBrightnessTrackerMock, brightnessSetting, () -> {
+        },
                 hbmMetadata, /* bootCompleted= */ false, mDisplayManagerFlagsMock);
 
         return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
-                animator, automaticBrightnessController, screenOffBrightnessSensorController,
-                hbmController, hbmMetadata, brightnessMappingStrategy, injector, config);
+                animator, automaticBrightnessController, wakelockController,
+                screenOffBrightnessSensorController, hbmController, hdrClamper, clamperController,
+                hbmMetadata, brightnessMappingStrategy, injector, config);
     }
 
     /**
@@ -1574,8 +1763,12 @@
         public final BrightnessSetting brightnessSetting;
         public final DualRampAnimator<DisplayPowerState> animator;
         public final AutomaticBrightnessController automaticBrightnessController;
+        public final WakelockController wakelockController;
         public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController;
         public final HighBrightnessModeController hbmController;
+
+        public final HdrClamper hdrClamper;
+        public final BrightnessClamperController clamperController;
         public final HighBrightnessModeMetadata hbmMetadata;
         public final BrightnessMappingStrategy brightnessMappingStrategy;
         public final DisplayPowerController.Injector injector;
@@ -1585,8 +1778,11 @@
                 DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting,
                 DualRampAnimator<DisplayPowerState> animator,
                 AutomaticBrightnessController automaticBrightnessController,
+                WakelockController wakelockController,
                 ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
                 HighBrightnessModeController hbmController,
+                HdrClamper hdrClamper,
+                BrightnessClamperController clamperController,
                 HighBrightnessModeMetadata hbmMetadata,
                 BrightnessMappingStrategy brightnessMappingStrategy,
                 DisplayPowerController.Injector injector,
@@ -1597,8 +1793,11 @@
             this.brightnessSetting = brightnessSetting;
             this.animator = animator;
             this.automaticBrightnessController = automaticBrightnessController;
+            this.wakelockController = wakelockController;
             this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
             this.hbmController = hbmController;
+            this.hdrClamper = hdrClamper;
+            this.clamperController = clamperController;
             this.hbmMetadata = hbmMetadata;
             this.brightnessMappingStrategy = brightnessMappingStrategy;
             this.injector = injector;
@@ -1610,24 +1809,39 @@
         private final DisplayPowerState mDisplayPowerState;
         private final DualRampAnimator<DisplayPowerState> mAnimator;
         private final AutomaticBrightnessController mAutomaticBrightnessController;
+        private final WakelockController mWakelockController;
         private final BrightnessMappingStrategy mBrightnessMappingStrategy;
         private final HysteresisLevels mHysteresisLevels;
         private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
         private final HighBrightnessModeController mHighBrightnessModeController;
 
+        private final HdrClamper mHdrClamper;
+
+        private final BrightnessClamperController mClamperController;
+
+        private final DisplayManagerFlags mFlags;
+
         TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
                 AutomaticBrightnessController automaticBrightnessController,
+                WakelockController wakelockController,
                 BrightnessMappingStrategy brightnessMappingStrategy,
                 HysteresisLevels hysteresisLevels,
                 ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
-                HighBrightnessModeController highBrightnessModeController) {
+                HighBrightnessModeController highBrightnessModeController,
+                HdrClamper hdrClamper,
+                BrightnessClamperController clamperController,
+                DisplayManagerFlags flags) {
             mDisplayPowerState = dps;
             mAnimator = animator;
             mAutomaticBrightnessController = automaticBrightnessController;
+            mWakelockController = wakelockController;
             mBrightnessMappingStrategy = brightnessMappingStrategy;
             mHysteresisLevels = hysteresisLevels;
             mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
             mHighBrightnessModeController = highBrightnessModeController;
+            mHdrClamper = hdrClamper;
+            mClamperController = clamperController;
+            mFlags = flags;
         }
 
         @Override
@@ -1649,6 +1863,28 @@
         }
 
         @Override
+        WakelockController getWakelockController(int displayId,
+                DisplayPowerCallbacks displayPowerCallbacks) {
+            return mWakelockController;
+        }
+
+        @Override
+        DisplayPowerProximityStateController getDisplayPowerProximityStateController(
+                WakelockController wakelockController, DisplayDeviceConfig displayDeviceConfig,
+                Looper looper, Runnable nudgeUpdatePowerState, int displayId,
+                SensorManager sensorManager) {
+            return new DisplayPowerProximityStateController(wakelockController,
+                    displayDeviceConfig, looper, nudgeUpdatePowerState, displayId,
+                    sensorManager,
+                    new DisplayPowerProximityStateController.Injector() {
+                        @Override
+                        DisplayPowerProximityStateController.Clock createClock() {
+                            return mClock::now;
+                        }
+                    });
+        }
+
+        @Override
         AutomaticBrightnessController getAutomaticBrightnessController(
                 AutomaticBrightnessController.Callbacks callbacks, Looper looper,
                 SensorManager sensorManager, Sensor lightSensor,
@@ -1710,6 +1946,23 @@
         }
 
         @Override
+        BrightnessRangeController getBrightnessRangeController(
+                HighBrightnessModeController hbmController, Runnable modeChangeCallback,
+                DisplayDeviceConfig displayDeviceConfig, Handler handler,
+                DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
+            return new BrightnessRangeController(hbmController, modeChangeCallback,
+                    displayDeviceConfig, mHdrClamper, mFlags, displayToken, info);
+        }
+
+        @Override
+        BrightnessClamperController getBrightnessClamperController(Handler handler,
+                BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+                BrightnessClamperController.DisplayDeviceData data, Context context,
+                DisplayManagerFlags flags) {
+            return mClamperController;
+        }
+
+        @Override
         DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
                 SensorManager sensorManager, Resources resources) {
             return mDisplayWhiteBalanceControllerMock;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java
index 5c50acb..a8af98f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java
@@ -72,14 +72,18 @@
 
     @Test
     public void testFindHighestRefreshRateForDefaultDisplay() {
+        when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock);
+        assertEquals(120,
+                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext),
+                /* delta= */ 0);
+    }
+
+    @Test
+    public void testFindHighestRefreshRate_DisplayIsNull() {
         when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(null);
         assertEquals(DEFAULT_REFRESH_RATE,
                 RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext),
                 /* delta= */ 0);
 
-        when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock);
-        assertEquals(120,
-                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext),
-                /* delta= */ 0);
     }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
new file mode 100644
index 0000000..638924e
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.mode
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.hardware.display.BrightnessInfo
+import android.view.Display
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SmallTest
+import com.android.server.display.DisplayDeviceConfig
+import com.android.server.display.feature.DisplayManagerFlags
+import com.android.server.testutils.TestHandler
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.junit.testparameterinjector.TestParameter
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(TestParameterInjector::class)
+class BrightnessObserverTest {
+
+    @get:Rule
+    val mockitoRule = MockitoJUnit.rule()
+
+    private lateinit var spyContext: Context
+    private val mockInjector = mock<DisplayModeDirector.Injector>()
+    private val mockFlags = mock<DisplayManagerFlags>()
+    private val mockDeviceConfig = mock<DisplayDeviceConfig>()
+
+    private val testHandler = TestHandler(null)
+
+    @Before
+    fun setUp() {
+        spyContext = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+    }
+
+    @Test
+    fun testLowLightBlockingZoneVotes(@TestParameter testCase: LowLightTestCase) {
+        setUpLowBrightnessZone()
+        whenever(mockFlags.isVsyncLowLightVoteEnabled).thenReturn(testCase.vsyncLowLightVoteEnabled)
+        val displayModeDirector = DisplayModeDirector(
+                spyContext, testHandler, mockInjector, mockFlags)
+        val brightnessObserver = displayModeDirector.BrightnessObserver(
+                spyContext, testHandler, mockInjector, testCase.vrrSupported, mockFlags)
+
+        brightnessObserver.onRefreshRateSettingChangedLocked(0.0f, 120.0f)
+        brightnessObserver.updateBlockingZoneThresholds(mockDeviceConfig, false)
+        brightnessObserver.onDeviceConfigRefreshRateInLowZoneChanged(60)
+
+        brightnessObserver.onDisplayChanged(Display.DEFAULT_DISPLAY)
+
+        assertThat(displayModeDirector.getVote(VotesStorage.GLOBAL_ID,
+                Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH)).isEqualTo(testCase.expectedVote)
+    }
+
+    private fun setUpLowBrightnessZone() {
+        whenever(mockInjector.getBrightnessInfo(Display.DEFAULT_DISPLAY)).thenReturn(
+                BrightnessInfo(/* brightness = */ 0.05f, /* adjustedBrightness = */ 0.05f,
+                        /* brightnessMinimum = */ 0.0f, /* brightnessMaximum = */ 1.0f,
+                        BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+                        /* highBrightnessTransitionPoint = */ 1.0f,
+                        BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE))
+        whenever(mockDeviceConfig.highDisplayBrightnessThresholds).thenReturn(floatArrayOf())
+        whenever(mockDeviceConfig.highAmbientBrightnessThresholds).thenReturn(floatArrayOf())
+        whenever(mockDeviceConfig.lowDisplayBrightnessThresholds).thenReturn(floatArrayOf(0.1f))
+        whenever(mockDeviceConfig.lowAmbientBrightnessThresholds).thenReturn(floatArrayOf(10f))
+    }
+
+    enum class LowLightTestCase(
+            val vrrSupported: Boolean,
+            val vsyncLowLightVoteEnabled: Boolean,
+            internal val expectedVote: Vote
+    ) {
+        ALL_ENABLED(true, true, CombinedVote(
+                listOf(DisableRefreshRateSwitchingVote(true),
+                        SupportedModesVote(
+                                listOf(SupportedModesVote.SupportedMode(60f, 60f),
+                                        SupportedModesVote.SupportedMode(120f, 120f)))))),
+        VRR_NOT_SUPPORTED(false, true, DisableRefreshRateSwitchingVote(true)),
+        VSYNC_VOTE_DISABLED(true, false, DisableRefreshRateSwitchingVote(true))
+    }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index a0e5fd8..64076e6 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -27,8 +27,6 @@
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -290,12 +288,14 @@
     };
 
     private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
+    private static final int DISPLAY_ID_2 = Display.DEFAULT_DISPLAY + 1;
     private static final int MODE_ID = 1;
     private static final float TRANSITION_POINT = 0.763f;
 
     private static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY;
 
     private Context mContext;
+    private Resources mResources;
     private FakesInjector mInjector;
     private Handler mHandler;
     @Rule
@@ -319,6 +319,8 @@
     @Before
     public void setUp() throws Exception {
         mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+        mResources = mockResources();
+        when(mContext.getResources()).thenReturn(mResources);
         final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
         when(mContext.getContentResolver()).thenReturn(resolver);
         mInjector = spy(new FakesInjector(mDisplayManagerInternalMock, mStatusBarMock,
@@ -326,6 +328,60 @@
         mHandler = new Handler(Looper.getMainLooper());
     }
 
+    private Resources mockResources() {
+        var resources = mock(Resources.class);
+        when(resources.getBoolean(R.bool.config_ignoreUdfpsVote))
+                .thenReturn(false);
+        when(resources.getBoolean(R.bool.config_refreshRateSynchronizationEnabled))
+                .thenReturn(false);
+        when(resources.getBoolean(R.bool.config_supportsDvrr))
+                .thenReturn(false);
+        when(resources.getInteger(R.integer.config_displayWhiteBalanceBrightnessFilterHorizon))
+                .thenReturn(10000);
+        when(resources.getInteger(R.integer.config_defaultPeakRefreshRate))
+                .thenReturn(0);
+        when(resources.getInteger(R.integer.config_externalDisplayPeakRefreshRate))
+                .thenReturn(0);
+        when(resources.getInteger(R.integer.config_externalDisplayPeakWidth))
+                .thenReturn(0);
+        when(resources.getInteger(R.integer.config_externalDisplayPeakHeight))
+                .thenReturn(0);
+        when(resources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
+                .thenReturn(0);
+        when(resources.getInteger(R.integer.config_defaultRefreshRateInZone))
+                .thenReturn(0);
+        when(resources.getInteger(R.integer.config_defaultRefreshRate))
+                .thenReturn(60);
+        when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmHdr))
+                .thenReturn(0);
+        when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmSunlight))
+                .thenReturn(0);
+
+        when(resources.getString(R.string.config_displayLightSensorType))
+                .thenReturn(null);
+
+        when(resources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
+                .thenReturn(new int[]{});
+        when(resources.getIntArray(
+                R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+                .thenReturn(new int[]{});
+        when(resources.getIntArray(
+                R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+                .thenReturn(new int[]{});
+        when(resources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
+                .thenReturn(new int[]{});
+
+        doAnswer(invocation -> {
+            TypedValue value = invocation.getArgument(1);
+            value.type = TypedValue.TYPE_FLOAT;
+            value.data = Float.floatToIntBits(10f);
+            return null; // void method, so return null
+        }).when(resources).getValue(eq(R.dimen.config_displayWhiteBalanceBrightnessFilterIntercept),
+                any(), eq(true));
+
+        return resources;
+    }
+
     private DisplayModeDirector createDirectorFromRefreshRateArray(
             float[] refreshRates, int baseModeId) {
         return createDirectorFromRefreshRateArray(refreshRates, baseModeId, refreshRates[0]);
@@ -1550,23 +1606,39 @@
     public void testPeakRefreshRate_FlagEnabled() {
         when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
                 .thenReturn(true);
-        float highestRefreshRate = 130;
-        doReturn(highestRefreshRate).when(() ->
-                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext));
         DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+                new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
         director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
 
+        Display.Mode[] modes1 = new Display.Mode[] {
+                new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 60),
+                new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 130),
+        };
+        Display.Mode[] modes2 = new Display.Mode[] {
+                new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 60),
+                new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 140),
+        };
+        SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
+        supportedModesByDisplay.put(DISPLAY_ID, modes1);
+        supportedModesByDisplay.put(DISPLAY_ID_2, modes2);
+
         Sensor lightSensor = createLightSensor();
         SensorManager sensorManager = createMockSensorManager(lightSensor);
         director.start(sensorManager);
+        director.injectSupportedModesByDisplay(supportedModesByDisplay);
 
         setPeakRefreshRate(Float.POSITIVE_INFINITY);
 
-        Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
+        Vote vote1 = director.getVote(DISPLAY_ID,
                 Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                highestRefreshRate);
+        Vote vote2 = director.getVote(DISPLAY_ID_2,
+                Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0, /* frameRateHigh= */ 130);
+        assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0, /* frameRateHigh= */ 140);
     }
 
     @Test
@@ -1584,32 +1656,117 @@
 
         setPeakRefreshRate(peakRefreshRate);
 
-        Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
+                /* frameRateHigh= */ peakRefreshRate);
+    }
+
+    @Test
+    public void testPeakRefreshRate_DisplayChanged() {
+        when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
+                .thenReturn(true);
+        DisplayModeDirector director =
+                new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+        mInjector.mDisplayInfo.supportedModes = new Display.Mode[] {
+                new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 60),
+                new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 130),
+        };
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+        director.start(sensorManager);
+
+        setPeakRefreshRate(Float.POSITIVE_INFINITY);
+
+        Vote vote = director.getVote(DISPLAY_ID,
                 Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */
-                peakRefreshRate);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ 130);
+
+        // The highest refresh rate of the display changes
+        mInjector.mDisplayInfo.supportedModes = new Display.Mode[] {
+                new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 60),
+                new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 140),
+        };
+        director.getDisplayObserver().onDisplayChanged(DISPLAY_ID);
+
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ 140);
+    }
+
+    @Test
+    @Parameters({
+        "true, true, 60",
+        "false, true, 50",
+        "true, false, 50"
+    })
+    public void testExternalDisplayMaxRefreshRate(boolean isRefreshRateSynchronizationEnabled,
+            boolean isExternalDisplay, float expectedMaxRenderFrameRate) {
+        when(mDisplayManagerFlags.isDisplaysRefreshRatesSynchronizationEnabled())
+                .thenReturn(isRefreshRateSynchronizationEnabled);
+        when(mResources.getBoolean(R.bool.config_refreshRateSynchronizationEnabled))
+                .thenReturn(isRefreshRateSynchronizationEnabled);
+        mInjector.mDisplayInfo.type =
+                isExternalDisplay ? Display.TYPE_EXTERNAL : Display.TYPE_INTERNAL;
+        mInjector.mDisplayInfo.displayId = DISPLAY_ID_2;
+
+        DisplayModeDirector director = createDirectorFromModeArray(TEST_MODES, DEFAULT_MODE_60);
+
+        SparseArray<Vote> votes = new SparseArray<>();
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 50f));
+
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID_2, votes);
+
+        director.getDisplayObserver().onDisplayAdded(DISPLAY_ID_2);
+        director.injectVotesByDisplay(votesByDisplay);
+
+        var desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID_2);
+        assertThat(desiredSpecs.primary.render.max).isEqualTo(expectedMaxRenderFrameRate);
+        assertThat(desiredSpecs.appRequest.render.max).isEqualTo(expectedMaxRenderFrameRate);
     }
 
     @Test
     public void testMinRefreshRate_FlagEnabled() {
         when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
                 .thenReturn(true);
-        float highestRefreshRate = 130;
-        doReturn(highestRefreshRate).when(() ->
-                RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext));
         DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+                new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
         director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
 
+        Display.Mode[] modes1 = new Display.Mode[] {
+                new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 60),
+                new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 130),
+        };
+        Display.Mode[] modes2 = new Display.Mode[] {
+                new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 60),
+                new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 140),
+        };
+        SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
+        supportedModesByDisplay.put(DISPLAY_ID, modes1);
+        supportedModesByDisplay.put(DISPLAY_ID_2, modes2);
+
         Sensor lightSensor = createLightSensor();
         SensorManager sensorManager = createMockSensorManager(lightSensor);
         director.start(sensorManager);
+        director.injectSupportedModesByDisplay(supportedModesByDisplay);
 
         setMinRefreshRate(Float.POSITIVE_INFINITY);
 
-        Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
+        Vote vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        Vote vote2 = director.getVote(DISPLAY_ID_2,
                 Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
-        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ highestRefreshRate,
+        assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 130,
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+        assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 140,
                 /* frameRateHigh= */ Float.POSITIVE_INFINITY);
     }
 
@@ -1628,13 +1785,50 @@
 
         setMinRefreshRate(minRefreshRate);
 
-        Vote vote = director.getVote(Display.DEFAULT_DISPLAY,
-                Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
         assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ minRefreshRate,
                 /* frameRateHigh= */ Float.POSITIVE_INFINITY);
     }
 
     @Test
+    public void testMinRefreshRate_DisplayChanged() {
+        when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
+                .thenReturn(true);
+        DisplayModeDirector director =
+                new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+        mInjector.mDisplayInfo.supportedModes = new Display.Mode[] {
+                new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 60),
+                new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 130),
+        };
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+        director.start(sensorManager);
+
+        setMinRefreshRate(Float.POSITIVE_INFINITY);
+
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 130,
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+
+        // The highest refresh rate of the display changes
+        mInjector.mDisplayInfo.supportedModes = new Display.Mode[] {
+                new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 60),
+                new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+                        /* refreshRate= */ 140),
+        };
+        director.getDisplayObserver().onDisplayChanged(DISPLAY_ID);
+
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+        assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 140,
+                /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+    }
+
+    @Test
     public void testSensorRegistration() {
         // First, configure brightness zones or DMD won't register for sensor data.
         final FakeDeviceConfig config = mInjector.getDeviceConfig();
@@ -2806,31 +3000,31 @@
 
     @Test
     public void testNotifyDefaultDisplayDeviceUpdated() {
-        Resources resources = mock(Resources.class);
-        when(mContext.getResources()).thenReturn(resources);
-        when(resources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate))
+        when(mResources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate))
             .thenReturn(75);
-        when(resources.getInteger(R.integer.config_defaultRefreshRate))
+        when(mResources.getInteger(R.integer.config_defaultRefreshRate))
             .thenReturn(45);
-        when(resources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
+        when(mResources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
             .thenReturn(65);
-        when(resources.getInteger(R.integer.config_defaultRefreshRateInZone))
+        when(mResources.getInteger(R.integer.config_defaultRefreshRateInZone))
             .thenReturn(85);
-        when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmHdr))
+        when(mResources.getInteger(R.integer.config_defaultRefreshRateInHbmHdr))
             .thenReturn(95);
-        when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmSunlight))
+        when(mResources.getInteger(R.integer.config_defaultRefreshRateInHbmSunlight))
             .thenReturn(100);
-        when(resources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
+        when(mResources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
             .thenReturn(new int[]{5});
-        when(resources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
+        when(mResources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
             .thenReturn(new int[]{10});
         when(
-            resources.getIntArray(R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+            mResources.getIntArray(
+                    R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
             .thenReturn(new int[]{250});
         when(
-            resources.getIntArray(R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+            mResources.getIntArray(
+                    R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
             .thenReturn(new int[]{7000});
-        when(resources.getInteger(
+        when(mResources.getInteger(
             com.android.internal.R.integer.config_displayWhiteBalanceBrightnessFilterHorizon))
             .thenReturn(3);
         ArgumentCaptor<TypedValue> valueArgumentCaptor = ArgumentCaptor.forClass(TypedValue.class);
@@ -2838,7 +3032,7 @@
             valueArgumentCaptor.getValue().type = 4;
             valueArgumentCaptor.getValue().data = 13;
             return null;
-        }).when(resources).getValue(eq(com.android.internal.R.dimen
+        }).when(mResources).getValue(eq(com.android.internal.R.dimen
                 .config_displayWhiteBalanceBrightnessFilterIntercept),
                 valueArgumentCaptor.capture(), eq(true));
         DisplayModeDirector director =
@@ -3329,7 +3523,7 @@
     public static class FakesInjector implements DisplayModeDirector.Injector {
         private final FakeDeviceConfig mDeviceConfig;
         private final DisplayInfo mDisplayInfo;
-        private final Display mDisplay;
+        private final Map<Integer, Display> mDisplays;
         private boolean mDisplayInfoValid = true;
         private final DisplayManagerInternal mDisplayManagerInternal;
         private final StatusBarManagerInternal mStatusBarManagerInternal;
@@ -3350,7 +3544,8 @@
             mDisplayInfo.defaultModeId = MODE_ID;
             mDisplayInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID,
                     800, 600, /* refreshRate= */ 60)};
-            mDisplay = createDisplay(DISPLAY_ID);
+            mDisplays = Map.of(DISPLAY_ID, createDisplay(DISPLAY_ID),
+                    DISPLAY_ID_2, createDisplay(DISPLAY_ID_2));
             mDisplayManagerInternal = displayManagerInternal;
             mStatusBarManagerInternal = statusBarManagerInternal;
             mSensorManagerInternal = sensorManagerInternal;
@@ -3381,12 +3576,12 @@
 
         @Override
         public Display getDisplay(int displayId) {
-            return mDisplay;
+            return mDisplays.get(displayId);
         }
 
         @Override
         public Display[] getDisplays() {
-            return new Display[] { mDisplay };
+            return mDisplays.values().toArray(new Display[0]);
         }
 
         @Override
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
index ff91d34..92016df 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
@@ -20,11 +20,10 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.Mode.INVALID_MODE_ID;
 
-
 import static com.android.server.display.mode.DisplayModeDirector.SYNCHRONIZED_REFRESH_RATE_TOLERANCE;
 import static com.android.server.display.mode.Vote.PRIORITY_LIMIT_MODE;
-import static com.android.server.display.mode.Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE;
 import static com.android.server.display.mode.Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE;
+import static com.android.server.display.mode.Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE;
 import static com.android.server.display.mode.VotesStorage.GLOBAL_ID;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -43,6 +42,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.provider.DeviceConfigInterface;
+import android.test.mock.MockContentResolver;
 import android.view.Display;
 import android.view.DisplayInfo;
 
@@ -51,21 +51,26 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.sensors.SensorManagerInternal;
 
+import junitparams.JUnitParamsRunner;
+
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import junitparams.JUnitParamsRunner;
-
-
 @SmallTest
 @RunWith(JUnitParamsRunner.class)
 public class DisplayObserverTest {
+    @Rule
+    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
     private static final int EXTERNAL_DISPLAY = 1;
     private static final int MAX_WIDTH = 1920;
     private static final int MAX_HEIGHT = 1080;
@@ -120,6 +125,8 @@
         mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         mResources = mock(Resources.class);
         when(mContext.getResources()).thenReturn(mResources);
+        MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
+        when(mContext.getContentResolver()).thenReturn(resolver);
         when(mResources.getInteger(R.integer.config_externalDisplayPeakRefreshRate))
                 .thenReturn(0);
         when(mResources.getInteger(R.integer.config_externalDisplayPeakWidth))
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
index b363fd4..d781433 100644
--- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
@@ -20,11 +20,13 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.media.projection.MediaProjectionInfo;
@@ -35,6 +37,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
 import android.testing.TestableLooper.RunWithLooper;
+import android.util.ArraySet;
 
 import androidx.test.filters.SmallTest;
 
@@ -52,7 +55,6 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Collections;
 import java.util.Set;
 
 @SmallTest
@@ -68,6 +70,8 @@
     private static final int NOTIFICATION_UID_1 = 5;
     private static final int NOTIFICATION_UID_2 = 6;
 
+    private static final ArraySet<PackageInfo> EMPTY_SET = new ArraySet<>();
+
     @Rule
     public final TestableContext mContext =
             new TestableContext(getInstrumentation().getTargetContext(), null);
@@ -107,6 +111,9 @@
 
         mSensitiveContentProtectionManagerService.mNotificationListener =
                 spy(mSensitiveContentProtectionManagerService.mNotificationListener);
+        doCallRealMethod()
+                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                .onListenerConnected();
 
         // Setup RankingMap and two possilbe rankings
         when(mSensitiveRanking.hasSensitiveContent()).thenReturn(true);
@@ -128,7 +135,7 @@
         mSensitiveContentProtectionManagerService.onDestroy();
     }
 
-    private Set<PackageInfo> setupSensitiveNotification() {
+    private ArraySet<PackageInfo> setupSensitiveNotification() {
         // Setup Notification Values
         when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
         when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
@@ -149,10 +156,11 @@
         when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
                 .thenReturn(mNonSensitiveRanking);
 
-        return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1));
+        return new ArraySet<>(
+                Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1)));
     }
 
-    private Set<PackageInfo> setupMultipleSensitiveNotificationsFromSamePackageAndUid() {
+    private ArraySet<PackageInfo> setupMultipleSensitiveNotificationsFromSamePackageAndUid() {
         // Setup Notification Values
         when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
         when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
@@ -173,10 +181,11 @@
         when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
                 .thenReturn(mSensitiveRanking);
 
-        return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1));
+        return new ArraySet<>(
+                Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1)));
     }
 
-    private Set<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentPackage() {
+    private ArraySet<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentPackage() {
         // Setup Notification Values
         when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
         when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
@@ -197,11 +206,12 @@
         when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
                 .thenReturn(mSensitiveRanking);
 
-        return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
-                new PackageInfo(NOTIFICATION_PKG_2, NOTIFICATION_UID_1));
+        return new ArraySet<>(
+                Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
+                        new PackageInfo(NOTIFICATION_PKG_2, NOTIFICATION_UID_1)));
     }
 
-    private Set<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentUid() {
+    private ArraySet<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentUid() {
         // Setup Notification Values
         when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
         when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
@@ -222,8 +232,9 @@
         when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
                 .thenReturn(mSensitiveRanking);
 
-        return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
-                new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_2));
+        return new ArraySet<>(
+                Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
+                        new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_2)));
     }
 
     private void setupNoSensitiveNotifications() {
@@ -251,11 +262,11 @@
 
     @Test
     public void mediaProjectionOnStart_onProjectionStart_setWmBlockedPackages() {
-        Set<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+        ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+        verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
     }
 
     @Test
@@ -264,7 +275,7 @@
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
     }
 
     @Test
@@ -273,37 +284,37 @@
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
     }
 
     @Test
     public void mediaProjectionOnStart_multipleNotifications_setWmBlockedPackages() {
-        Set<PackageInfo> expectedBlockedPackages =
+        ArraySet<PackageInfo> expectedBlockedPackages =
                 setupMultipleSensitiveNotificationsFromSamePackageAndUid();
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+        verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
     }
 
     @Test
     public void mediaProjectionOnStart_multiplePackages_setWmBlockedPackages() {
-        Set<PackageInfo> expectedBlockedPackages =
+        ArraySet<PackageInfo> expectedBlockedPackages =
                 setupMultipleSensitiveNotificationsFromDifferentPackage();
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+        verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
     }
 
     @Test
     public void mediaProjectionOnStart_multipleUid_setWmBlockedPackages() {
-        Set<PackageInfo> expectedBlockedPackages =
+        ArraySet<PackageInfo> expectedBlockedPackages =
                 setupMultipleSensitiveNotificationsFromDifferentUid();
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+        verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
     }
 
     @Test
@@ -316,12 +327,12 @@
 
         mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo);
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        verify(mWindowManager).clearBlockedApps();
     }
 
     @Test
     public void mediaProjectionOnStart_afterOnStop_onProjectionStart_setWmBlockedPackages() {
-        Set<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+        ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
 
         MediaProjectionInfo mediaProjectionInfo = mock(MediaProjectionInfo.class);
         mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
@@ -330,7 +341,7 @@
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+        verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
     }
 
     @Test
@@ -341,7 +352,7 @@
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
     }
 
     @Test
@@ -352,7 +363,7 @@
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
     }
 
     @Test
@@ -363,7 +374,7 @@
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
     }
 
     @Test
@@ -376,6 +387,314 @@
 
         mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
 
-        verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+    }
+
+    @Test
+    public void nlsOnListenerConnected_projectionNotStarted_noop() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+
+        mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    @Test
+    public void nlsOnListenerConnected_projectionStopped_noop() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    @Test
+    public void nlsOnListenerConnected_projectionStarted_setWmBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
+    }
+
+    @Test
+    public void nlsOnListenerConnected_noSensitiveNotifications_noBlockedPackages() {
+        setupNoSensitiveNotifications();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+    }
+
+    @Test
+    public void nlsOnListenerConnected_noNotifications_noBlockedPackages() {
+        setupNoNotifications();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+    }
+
+    @Test
+    public void nlsOnListenerConnected_nullRankingMap_noBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+        doReturn(null)
+                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                .getCurrentRanking();
+
+        mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+    }
+
+    @Test
+    public void nlsOnListenerConnected_missingRanking_noBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+        when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null);
+        doReturn(mRankingMap)
+                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                .getCurrentRanking();
+
+        mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+    }
+
+    @Test
+    public void nlsOnNotificationRankingUpdate_projectionNotStarted_noop() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationRankingUpdate(mRankingMap);
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    @Test
+    public void nlsOnNotificationRankingUpdate_projectionStopped_noop() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationRankingUpdate(mRankingMap);
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    @Test
+    public void nlsOnNotificationRankingUpdate_projectionStarted_setWmBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationRankingUpdate(mRankingMap);
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
+    }
+
+    @Test
+    public void nlsOnNotificationRankingUpdate_noSensitiveNotifications_noBlockedPackages() {
+        setupNoSensitiveNotifications();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationRankingUpdate(mRankingMap);
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+    }
+
+    @Test
+    public void nlsOnNotificationRankingUpdate_noNotifications_noBlockedPackages() {
+        setupNoNotifications();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationRankingUpdate(mRankingMap);
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+    }
+
+    @Test
+    public void nlsOnNotificationRankingUpdate_nullRankingMap_noBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationRankingUpdate(null);
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+    }
+
+    @Test
+    public void nlsOnNotificationRankingUpdate_missingRanking_noBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+        when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null);
+        doReturn(mRankingMap)
+                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                .getCurrentRanking();
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationRankingUpdate(mRankingMap);
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+    }
+
+    @Test
+    public void nlsOnNotificationRankingUpdate_getActiveNotificationsThrows_noBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        doThrow(SecurityException.class)
+                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                .getActiveNotifications();
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationRankingUpdate(mRankingMap);
+
+        verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+    }
+
+    @Test
+    public void nlsOnNotificationPosted_projectionNotStarted_noop() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationPosted(mNotification1, mRankingMap);
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    @Test
+    public void nlsOnNotificationPosted_projectionStopped_noop() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationPosted(mNotification1, mRankingMap);
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    @Test
+    public void nlsOnNotificationPosted_projectionStarted_setWmBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationPosted(mNotification1, mRankingMap);
+
+        ArraySet<PackageInfo> expectedBlockedPackages = new ArraySet<>(
+                Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1)));
+        verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
+    }
+
+    @Test
+    public void nlsOnNotificationPosted_noSensitiveNotifications_noBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationPosted(mNotification2, mRankingMap);
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    @Test
+    public void nlsOnNotificationPosted_noNotifications_noBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationPosted(null, mRankingMap);
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    @Test
+    public void nlsOnNotificationPosted_nullRankingMap_noBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationPosted(mNotification1, null);
+
+        verifyZeroInteractions(mWindowManager);
+    }
+
+    @Test
+    public void nlsOnNotificationPosted_missingRanking_noBlockedPackages() {
+        // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+        // as non-sensitive
+        setupSensitiveNotification();
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+        Mockito.reset(mWindowManager);
+        when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener
+                .onNotificationPosted(mNotification1, mRankingMap);
+
+        verifyZeroInteractions(mWindowManager);
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActiveServicesTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActiveServicesTest.java
index e2c338a..7e1dc08 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActiveServicesTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActiveServicesTest.java
@@ -202,7 +202,7 @@
         final ServiceInfo regularService = new ServiceInfo();
         regularService.processName = "com.foo";
         String processName = ActiveServices.getProcessNameForService(regularService, null, null,
-                null, false, false);
+                null, false, false, false);
         assertEquals("com.foo", processName);
 
         // Isolated service
@@ -211,29 +211,90 @@
         isolatedService.flags = ServiceInfo.FLAG_ISOLATED_PROCESS;
         final ComponentName component = new ComponentName("com.foo", "barService");
         processName = ActiveServices.getProcessNameForService(isolatedService, component,
-                null, null, false, false);
+                null, null, false, false, false);
         assertEquals("com.foo:barService", processName);
 
+        // Isolated Service in package private process.
+        final ServiceInfo isolatedService1 = new ServiceInfo();
+        isolatedService1.processName = "com.foo:trusted_isolated";
+        isolatedService1.flags = ServiceInfo.FLAG_ISOLATED_PROCESS;
+        final ComponentName componentName = new ComponentName("com.foo", "barService");
+        processName = ActiveServices.getProcessNameForService(isolatedService1, componentName,
+                null, null, false, false, false);
+        assertEquals("com.foo:trusted_isolated:barService", processName);
+
+        // Isolated service in package-private shared process (main process)
+        final ServiceInfo isolatedPackageSharedService = new ServiceInfo();
+        final ComponentName componentName1 = new ComponentName("com.foo", "barService");
+        isolatedPackageSharedService.processName = "com.foo";
+        isolatedPackageSharedService.applicationInfo = new ApplicationInfo();
+        isolatedPackageSharedService.applicationInfo.processName = "com.foo";
+        isolatedPackageSharedService.flags = ServiceInfo.FLAG_ISOLATED_PROCESS;
+        String packageSharedIsolatedProcessName = ActiveServices.getProcessNameForService(
+                isolatedPackageSharedService, componentName1, null, null, false, false, true);
+        assertEquals("com.foo:barService", packageSharedIsolatedProcessName);
+
+        // Isolated service in package-private shared process
+        final ServiceInfo isolatedPackageSharedService1 = new ServiceInfo(
+                isolatedPackageSharedService);
+        isolatedPackageSharedService1.processName = "com.foo:trusted_isolated";
+        isolatedPackageSharedService1.applicationInfo = new ApplicationInfo();
+        isolatedPackageSharedService1.applicationInfo.processName = "com.foo";
+        isolatedPackageSharedService1.flags = ServiceInfo.FLAG_ISOLATED_PROCESS;
+        packageSharedIsolatedProcessName = ActiveServices.getProcessNameForService(
+                isolatedPackageSharedService1, componentName1, null, null, false, false, true);
+        assertEquals("com.foo:trusted_isolated", packageSharedIsolatedProcessName);
+
+
+        // Bind another one in the same isolated process
+        final ServiceInfo isolatedPackageSharedService2 = new ServiceInfo(
+                isolatedPackageSharedService1);
+        packageSharedIsolatedProcessName = ActiveServices.getProcessNameForService(
+                isolatedPackageSharedService2, componentName1, null, null, false, false, true);
+        assertEquals("com.foo:trusted_isolated", packageSharedIsolatedProcessName);
+
+        // Simulate another app trying to do the bind.
+        final ServiceInfo isolatedPackageSharedService3 = new ServiceInfo(
+                isolatedPackageSharedService1);
+        final String auxCallingPackage = "com.bar";
+        packageSharedIsolatedProcessName = ActiveServices.getProcessNameForService(
+                isolatedPackageSharedService3, componentName1, auxCallingPackage, null,
+                false, false, true);
+        assertEquals("com.foo:trusted_isolated", packageSharedIsolatedProcessName);
+
+        // Simulate another app owning the service
+        final ServiceInfo isolatedOtherPackageSharedService = new ServiceInfo(
+                isolatedPackageSharedService1);
+        final ComponentName componentName2 = new ComponentName("com.bar", "barService");
+        isolatedOtherPackageSharedService.processName = "com.bar:isolated";
+        isolatedPackageSharedService.applicationInfo.processName = "com.bar";
+        final String mainCallingPackage = "com.foo";
+        packageSharedIsolatedProcessName = ActiveServices.getProcessNameForService(
+                isolatedOtherPackageSharedService, componentName2, mainCallingPackage,
+                null, false, false, true);
+        assertEquals("com.bar:isolated", packageSharedIsolatedProcessName);
+
         // Isolated service in shared isolated process
         final ServiceInfo isolatedServiceShared1 = new ServiceInfo();
         isolatedServiceShared1.flags = ServiceInfo.FLAG_ISOLATED_PROCESS;
         final String instanceName = "pool";
         final String callingPackage = "com.foo";
         final String sharedIsolatedProcessName1 = ActiveServices.getProcessNameForService(
-                isolatedServiceShared1, null, callingPackage, instanceName, false, true);
+                isolatedServiceShared1, null, callingPackage, instanceName, false, true, false);
         assertEquals("com.foo:ishared:pool", sharedIsolatedProcessName1);
 
         // Bind another one in the same isolated process
         final ServiceInfo isolatedServiceShared2 = new ServiceInfo(isolatedServiceShared1);
         final String sharedIsolatedProcessName2 = ActiveServices.getProcessNameForService(
-                isolatedServiceShared2, null, callingPackage, instanceName, false, true);
+                isolatedServiceShared2, null, callingPackage, instanceName, false, true, false);
         assertEquals(sharedIsolatedProcessName1, sharedIsolatedProcessName2);
 
         // Simulate another app trying to do the bind
         final ServiceInfo isolatedServiceShared3 = new ServiceInfo(isolatedServiceShared1);
         final String otherCallingPackage = "com.bar";
         final String sharedIsolatedProcessName3 = ActiveServices.getProcessNameForService(
-                isolatedServiceShared3, null, otherCallingPackage, instanceName, false, true);
+                isolatedServiceShared3, null, otherCallingPackage, instanceName, false, true,
+                false);
         Assert.assertNotEquals(sharedIsolatedProcessName2, sharedIsolatedProcessName3);
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
new file mode 100644
index 0000000..7d3a110
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.am.ActivityManagerService.Injector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.app.ApplicationStartInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.platform.test.annotations.Presubmit;
+import android.text.TextUtils;
+
+import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
+import com.android.server.appop.AppOpsService;
+import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+
+/**
+ * Test class for {@link android.app.ApplicationStartInfo}.
+ *
+ * Build/Install/Run:
+ * atest ApplicationStartInfoTest
+ */
+@Presubmit
+public class ApplicationStartInfoTest {
+
+    private static final String TAG = ApplicationStartInfoTest.class.getSimpleName();
+    private static final ComponentName COMPONENT = new ComponentName("com.android.test", ".Foo");
+
+    @Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
+    @Mock private AppOpsService mAppOpsService;
+    @Mock private PackageManagerInternal mPackageManagerInt;
+
+    private Context mContext = getInstrumentation().getTargetContext();
+    private TestInjector mInjector;
+    private ActivityManagerService mAms;
+    private ProcessList mProcessList;
+    private AppStartInfoTracker mAppStartInfoTracker;
+    private Handler mHandler;
+    private HandlerThread mHandlerThread;
+
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        mProcessList = spy(new ProcessList());
+        mAppStartInfoTracker = spy(new AppStartInfoTracker());
+        mAppStartInfoTracker.mEnabled = true;
+        setFieldValue(ProcessList.class, mProcessList, "mAppStartInfoTracker",
+                mAppStartInfoTracker);
+        mInjector = new TestInjector(mContext);
+        mAms = new ActivityManagerService(mInjector, mServiceThreadRule.getThread());
+        mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+        mAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+        mAms.mAtmInternal = spy(mAms.mActivityTaskManager.getAtmInternal());
+        mAms.mPackageManagerInt = mPackageManagerInt;
+        mAppStartInfoTracker.mService = mAms;
+        doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+        doReturn("com.android.test").when(mPackageManagerInt).getNameForUid(anyInt());
+        // Remove stale instance of PackageManagerInternal if there is any
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+    }
+
+    @After
+    public void tearDown() {
+        mHandlerThread.quit();
+    }
+
+    @Test
+    public void testApplicationStartInfo() throws Exception {
+        mAppStartInfoTracker.clearProcessStartInfo(true);
+        mAppStartInfoTracker.mAppStartInfoLoaded.set(true);
+        mAppStartInfoTracker.mAppStartInfoHistoryListSize =
+                mAppStartInfoTracker.APP_START_INFO_HISTORY_LIST_SIZE;
+        mAppStartInfoTracker.mProcStartStoreDir = new File(mContext.getFilesDir(),
+                AppStartInfoTracker.APP_START_STORE_DIR);
+        assertTrue(FileUtils.createDir(mAppStartInfoTracker.mProcStartStoreDir));
+        mAppStartInfoTracker.mProcStartInfoFile = new File(mAppStartInfoTracker.mProcStartStoreDir,
+                AppStartInfoTracker.APP_START_INFO_FILE);
+
+        doNothing().when(mAppStartInfoTracker).schedulePersistProcessStartInfo(anyBoolean());
+
+        final int app1Uid = 10123;
+        final int app1Pid1 = 12345;
+        final int app1Pid2 = 12346;
+        final int app1DefiningUid = 23456;
+        final int app1UidUser2 = 1010123;
+        final int app1PidUser2 = 12347;
+        final String app1ProcessName = "com.android.test.stub1:process";
+        final String app1PackageName = "com.android.test.stub1";
+        final long appStartTimestampIntentStarted = 1000000;
+        final long appStartTimestampActivityLaunchFinished = 2000000;
+        final long appStartTimestampReportFullyDrawn = 3000000;
+        final long appStartTimestampService = 4000000;
+        final long appStartTimestampBroadcast = 5000000;
+        final long appStartTimestampRContentProvider = 6000000;
+
+        ProcessRecord app = makeProcessRecord(
+                app1Pid1,                    // pid
+                app1Uid,                     // uid
+                app1Uid,                     // packageUid
+                null,                        // definingUid
+                app1ProcessName,             // processName
+                app1PackageName);            // packageName
+
+        ArrayList<ApplicationStartInfo> list = new ArrayList<ApplicationStartInfo>();
+
+        // Case 1: Activity start intent failed
+        mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT),
+                appStartTimestampIntentStarted);
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        verifyInProgressRecordsSize(1);
+        assertEquals(list.size(), 0);
+
+        verifyInProgApplicationStartInfo(
+                0,                                                    // index
+                0,                                                    // pid
+                0,                                                    // uid
+                0,                                                    // packageUid
+                null,                                                 // definingUid
+                null,                                                 // processName
+                ApplicationStartInfo.START_REASON_START_ACTIVITY,     // reason
+                ApplicationStartInfo.STARTUP_STATE_STARTED,           // startup state
+                ApplicationStartInfo.START_TYPE_UNSET,                // state type
+                ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
+
+        mAppStartInfoTracker.onIntentFailed(appStartTimestampIntentStarted);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        verifyInProgressRecordsSize(0);
+        assertEquals(list.size(), 0);
+
+        mAppStartInfoTracker.clearProcessStartInfo(true);
+
+        // Case 2: Activity start launch cancelled
+        mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT),
+                appStartTimestampIntentStarted);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        verifyInProgressRecordsSize(1);
+        assertEquals(list.size(), 0);
+
+        mAppStartInfoTracker.onActivityLaunched(appStartTimestampIntentStarted, COMPONENT,
+                ApplicationStartInfo.START_TYPE_COLD, app);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        verifyInProgressRecordsSize(1);
+        assertEquals(list.size(), 1);
+
+        verifyInProgApplicationStartInfo(
+                0,                                                    // index
+                app1Pid1,                                             // pid
+                app1Uid,                                              // uid
+                app1Uid,                                              // packageUid
+                null,                                                 // definingUid
+                app1ProcessName,                                      // processName
+                ApplicationStartInfo.START_REASON_START_ACTIVITY,     // reason
+                ApplicationStartInfo.STARTUP_STATE_STARTED,           // startup state
+                ApplicationStartInfo.START_TYPE_COLD,                 // state type
+                ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
+
+        mAppStartInfoTracker.onActivityLaunchCancelled(appStartTimestampIntentStarted);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        verifyInProgressRecordsSize(0);
+        assertEquals(list.size(), 1);
+
+        verifyApplicationStartInfo(
+                list.get(0),                                          // info
+                app1Pid1,                                             // pid
+                app1Uid,                                              // uid
+                app1Uid,                                              // packageUid
+                null,                                                 // definingUid
+                app1ProcessName,                                      // processName
+                ApplicationStartInfo.START_REASON_START_ACTIVITY,     // reason
+                ApplicationStartInfo.STARTUP_STATE_ERROR,             // startup state
+                ApplicationStartInfo.START_TYPE_COLD,                 // state type
+                ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
+
+        mAppStartInfoTracker.clearProcessStartInfo(true);
+
+        // Case 3: Activity start success
+        mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT),
+                appStartTimestampIntentStarted);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        verifyInProgressRecordsSize(1);
+        assertEquals(list.size(), 0);
+
+        mAppStartInfoTracker.onActivityLaunched(appStartTimestampIntentStarted, COMPONENT,
+                ApplicationStartInfo.START_TYPE_COLD, app);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        verifyInProgressRecordsSize(1);
+        assertEquals(list.size(), 1);
+
+        verifyInProgApplicationStartInfo(
+                0,                                                    // index
+                app1Pid1,                                             // pid
+                app1Uid,                                              // uid
+                app1Uid,                                              // packageUid
+                null,                                                 // definingUid
+                app1ProcessName,                                      // processName
+                ApplicationStartInfo.START_REASON_START_ACTIVITY,     // reason
+                ApplicationStartInfo.STARTUP_STATE_STARTED,           // startup state
+                ApplicationStartInfo.START_TYPE_COLD,                 // state type
+                ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
+
+        verifyApplicationStartInfo(
+                list.get(0),                                          // info
+                app1Pid1,                                             // pid
+                app1Uid,                                              // uid
+                app1Uid,                                              // packageUid
+                null,                                                 // definingUid
+                app1ProcessName,                                      // processName
+                ApplicationStartInfo.START_REASON_START_ACTIVITY,     // reason
+                ApplicationStartInfo.STARTUP_STATE_STARTED,           // startup state
+                ApplicationStartInfo.START_TYPE_COLD,                 // state type
+                ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
+
+        mAppStartInfoTracker.onActivityLaunchFinished(appStartTimestampIntentStarted, COMPONENT,
+                appStartTimestampActivityLaunchFinished, ApplicationStartInfo.LAUNCH_MODE_STANDARD);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        verifyInProgressRecordsSize(1);
+        assertEquals(list.size(), 1);
+
+        verifyInProgApplicationStartInfo(
+                0,                                                    // index
+                app1Pid1,                                             // pid
+                app1Uid,                                              // uid
+                app1Uid,                                              // packageUid
+                null,                                                 // definingUid
+                app1ProcessName,                                      // processName
+                ApplicationStartInfo.START_REASON_START_ACTIVITY,     // reason
+                ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN, // startup state
+                ApplicationStartInfo.START_TYPE_COLD,                 // state type
+                ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
+
+        mAppStartInfoTracker.onReportFullyDrawn(appStartTimestampIntentStarted,
+                appStartTimestampReportFullyDrawn);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        verifyInProgressRecordsSize(0);
+        assertEquals(list.size(), 1);
+
+        verifyApplicationStartInfo(
+                list.get(0),                                          // info
+                app1Pid1,                                             // pid
+                app1Uid,                                              // uid
+                app1Uid,                                              // packageUid
+                null,                                                 // definingUid
+                app1ProcessName,                                      // processName
+                ApplicationStartInfo.START_REASON_START_ACTIVITY,     // reason
+                ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN, // startup state
+                ApplicationStartInfo.START_TYPE_COLD,                 // state type
+                ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
+
+        // Don't clear records for use in subsequent cases.
+
+        // Case 4: Create an other app1 record with different pid started for a service
+        sleep(1);
+        app = makeProcessRecord(
+                app1Pid2,                    // pid
+                app1Uid,                     // uid
+                app1Uid,                     // packageUid
+                app1DefiningUid,             // definingUid
+                app1ProcessName,             // processName
+                app1PackageName);            // packageName
+        ServiceRecord service = ServiceRecord.newEmptyInstanceForTest(mAms);
+
+        mAppStartInfoTracker.handleProcessServiceStart(appStartTimestampService, app, service,
+                false);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, 0, 0, list);
+        assertEquals(list.size(), 2);
+
+        verifyApplicationStartInfo(
+                list.get(0),                                          // info
+                app1Pid2,                                             // pid
+                app1Uid,                                              // uid
+                app1Uid,                                              // packageUid
+                app1DefiningUid,                                      // definingUid
+                app1ProcessName,                                      // processName
+                ApplicationStartInfo.START_REASON_SERVICE,            // reason
+                ApplicationStartInfo.STARTUP_STATE_STARTED,           // startup state
+                ApplicationStartInfo.START_TYPE_WARM,                 // state type
+                ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
+
+        // Case 5: Create an instance of app1 with a different user started for a broadcast
+        sleep(1);
+        app = makeProcessRecord(
+                app1PidUser2,                    // pid
+                app1UidUser2,                    // uid
+                app1UidUser2,                    // packageUid
+                null,                            // definingUid
+                app1ProcessName,                 // processName
+                app1PackageName);                // packageName
+
+        mAppStartInfoTracker.handleProcessBroadcastStart(appStartTimestampBroadcast, app,
+                null, true /* isColdStart */);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list);
+        assertEquals(list.size(), 1);
+
+        verifyApplicationStartInfo(
+                list.get(0),                                          // info
+                app1PidUser2,                                         // pid
+                app1UidUser2,                                         // uid
+                app1UidUser2,                                         // packageUid
+                null,                                                 // definingUid
+                app1ProcessName,                                      // processName
+                ApplicationStartInfo.START_REASON_BROADCAST,          // reason
+                ApplicationStartInfo.STARTUP_STATE_STARTED,           // startup state
+                ApplicationStartInfo.START_TYPE_COLD,                 // state type
+                ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
+
+        // Case 6: User 2 gets removed
+        mAppStartInfoTracker.onPackageRemoved(app1PackageName, app1UidUser2, false);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list);
+        assertEquals(list.size(), 0);
+
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1PidUser2, 0, list);
+        assertEquals(list.size(), 2);
+
+
+        // Case 7: Create a process from another package started for a content provider
+        final int app2UidUser2 = 1010234;
+        final int app2PidUser2 = 12348;
+        final String app2ProcessName = "com.android.test.stub2:process";
+        final String app2PackageName = "com.android.test.stub2";
+
+        sleep(1);
+
+        app = makeProcessRecord(
+                app2PidUser2,                    // pid
+                app2UidUser2,                    // uid
+                app2UidUser2,                    // packageUid
+                null,                            // definingUid
+                app2ProcessName,                 // processName
+                app2PackageName);                // packageName
+
+        mAppStartInfoTracker.handleProcessContentProviderStart(appStartTimestampRContentProvider,
+                app, false);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(app2PackageName, app2UidUser2, app2PidUser2, 0, list);
+        assertEquals(list.size(), 1);
+
+        verifyApplicationStartInfo(
+                list.get(0),                                          // info
+                app2PidUser2,                                         // pid
+                app2UidUser2,                                         // uid
+                app2UidUser2,                                         // packageUid
+                null,                                                 // definingUid
+                app2ProcessName,                                      // processName
+                ApplicationStartInfo.START_REASON_CONTENT_PROVIDER,   // reason
+                ApplicationStartInfo.STARTUP_STATE_STARTED,           // startup state
+                ApplicationStartInfo.START_TYPE_WARM,                 // state type
+                ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
+
+        // Case 8: Save and load again
+        ArrayList<ApplicationStartInfo> original = new ArrayList<ApplicationStartInfo>();
+        mAppStartInfoTracker.getStartInfo(null, app1Uid, 0, 0, original);
+        assertTrue(original.size() > 0);
+
+        mAppStartInfoTracker.persistProcessStartInfo();
+        assertTrue(mAppStartInfoTracker.mProcStartInfoFile.exists());
+
+        mAppStartInfoTracker.clearProcessStartInfo(false);
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(null, app1Uid, 0, 0, list);
+        assertEquals(0, list.size());
+
+        mAppStartInfoTracker.loadExistingProcessStartInfo();
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(null, app1Uid, 0, 0, list);
+        assertEquals(original.size(), list.size());
+
+        for (int i = list.size() - 1; i >= 0; i--) {
+            assertTrue(list.get(i).equals(original.get(i)));
+        }
+    }
+
+    private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
+        try {
+            Field field = clazz.getDeclaredField(fieldName);
+            field.setAccessible(true);
+            Field mfield = Field.class.getDeclaredField("accessFlags");
+            mfield.setAccessible(true);
+            mfield.setInt(field, mfield.getInt(field) & ~(Modifier.FINAL | Modifier.PRIVATE));
+            field.set(obj, val);
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+        }
+    }
+
+    private void sleep(long ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException e) {
+        }
+    }
+
+    private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
+            String processName, String packageName) {
+        return makeProcessRecord(pid, uid, packageUid, definingUid, processName, packageName, mAms);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    static ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
+            String processName, String packageName, ActivityManagerService ams) {
+        ApplicationInfo ai = new ApplicationInfo();
+        ai.packageName = packageName;
+        ProcessRecord app = new ProcessRecord(ams, ai, processName, uid);
+        app.setPid(pid);
+        app.info.uid = packageUid;
+        if (definingUid != null) {
+            app.setHostingRecord(HostingRecord.byAppZygote(COMPONENT, "", definingUid, ""));
+        }
+        return app;
+    }
+
+    private static Intent buildIntent(ComponentName componentName) throws Exception {
+        Intent intent = new Intent();
+        intent.setComponent(componentName);
+        intent.setPackage(componentName.getPackageName());
+        return intent;
+    }
+
+    private void verifyInProgressRecordsSize(int expectedSize) {
+        synchronized (mAppStartInfoTracker.mLock) {
+            assertEquals(mAppStartInfoTracker.mInProgRecords.size(), expectedSize);
+        }
+    }
+
+    private void verifyInProgApplicationStartInfo(int index,
+            Integer pid, Integer uid, Integer packageUid,
+            Integer definingUid, String processName,
+            Integer reason, Integer startupState, Integer startType, Integer launchMode) {
+        synchronized (mAppStartInfoTracker.mLock) {
+            verifyApplicationStartInfo(mAppStartInfoTracker.mInProgRecords.valueAt(index),
+                    pid, uid, packageUid, definingUid, processName, reason, startupState,
+                    startType, launchMode);
+        }
+    }
+
+    private void verifyApplicationStartInfo(ApplicationStartInfo info,
+            Integer pid, Integer uid, Integer packageUid,
+            Integer definingUid, String processName,
+            Integer reason, Integer startupState, Integer startType, Integer launchMode) {
+        assertNotNull(info);
+
+        if (pid != null) {
+            assertEquals(pid.intValue(), info.getPid());
+        }
+        if (uid != null) {
+            assertEquals(uid.intValue(), info.getRealUid());
+        }
+        if (packageUid != null) {
+            assertEquals(packageUid.intValue(), info.getPackageUid());
+        }
+        if (definingUid != null) {
+            assertEquals(definingUid.intValue(), info.getDefiningUid());
+        }
+        if (processName != null) {
+            assertTrue(TextUtils.equals(processName, info.getProcessName()));
+        }
+        if (reason != null) {
+            assertEquals(reason.intValue(), info.getReason());
+        }
+        if (startupState != null) {
+            assertEquals(startupState.intValue(), info.getStartupState());
+        }
+        if (startType != null) {
+            assertEquals(startType.intValue(), info.getStartType());
+        }
+        if (launchMode != null) {
+            assertEquals(launchMode.intValue(), info.getLaunchMode());
+        }
+    }
+
+    private class TestInjector extends Injector {
+        TestInjector(Context context) {
+            super(context);
+        }
+
+        @Override
+        public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+                Handler handler) {
+            return mAppOpsService;
+        }
+
+        @Override
+        public Handler getUiHandler(ActivityManagerService service) {
+            return mHandler;
+        }
+
+        @Override
+        public ProcessList getProcessList(ActivityManagerService service) {
+            return mProcessList;
+        }
+    }
+
+    static class ServiceThreadRule implements TestRule {
+
+        private ServiceThread mThread;
+
+        ServiceThread getThread() {
+            return mThread;
+        }
+
+        @Override
+        public Statement apply(Statement base, Description description) {
+            return new Statement() {
+                @Override
+                public void evaluate() throws Throwable {
+                    mThread = new ServiceThread("TestServiceThread",
+                            Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */);
+                    mThread.start();
+                    try {
+                        base.evaluate();
+                    } finally {
+                        mThread.getThreadHandler().runWithScissors(mThread::quit, 0 /* timeout */);
+                    }
+                }
+            };
+        }
+    }
+
+    // TODO: [b/302724778] Remove manual JNI load
+    static {
+        System.loadLibrary("mockingservicestestjni");
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index e4b6206..d876dae 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -101,6 +101,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.ArrayMap;
 import android.util.SparseArray;
 
@@ -111,7 +112,9 @@
 
 import org.junit.After;
 import org.junit.AfterClass;
+import org.junit.Before;
 import org.junit.BeforeClass;
+import org.junit.Rule;
 import org.junit.Test;
 
 import java.io.File;
@@ -161,6 +164,9 @@
     private static PackageManagerInternal sPackageManagerInternal;
     private static ActivityManagerService sService;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @SuppressWarnings("GuardedBy")
     @BeforeClass
     public static void setUpOnce() {
@@ -227,6 +233,11 @@
         }
     }
 
+    @Before
+    public void setUp() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_NEW_FGS_RESTRICTION_LOGIC);
+    }
+
     @AfterClass
     public static void tearDownOnce() {
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
@@ -540,6 +551,7 @@
         sService.mConstants.mShortFgsProcStateExtraWaitDuration = 200_000;
 
         ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(sService);
+        s.appInfo = new ApplicationInfo();
         s.startRequested = true;
         s.isForeground = true;
         s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
@@ -582,6 +594,7 @@
 
         // SHORT_SERVICE, timed out already.
         s = ServiceRecord.newEmptyInstanceForTest(sService);
+        s.appInfo = new ApplicationInfo();
         s.startRequested = true;
         s.isForeground = true;
         s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
@@ -1100,6 +1113,7 @@
 
         // In order to trick OomAdjuster to think it has a short-service, we need this logic.
         ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(sService);
+        s.appInfo = new ApplicationInfo();
         s.startRequested = true;
         s.isForeground = true;
         s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
@@ -1130,6 +1144,7 @@
 
         // In order to trick OomAdjuster to think it has a short-service, we need this logic.
         ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(sService);
+        s.appInfo = new ApplicationInfo();
         s.startRequested = true;
         s.isForeground = true;
         s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
@@ -1421,6 +1436,7 @@
 
         // In order to trick OomAdjuster to think it has a short-service, we need this logic.
         ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(sService);
+        s.appInfo = new ApplicationInfo();
         s.startRequested = true;
         s.isForeground = true;
         s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
new file mode 100644
index 0000000..fcf761f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.os.Process.myPid;
+import static android.os.Process.myUid;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManagerInternal;
+import android.app.IApplicationThread;
+import android.app.IProcessObserver;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.util.Log;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.DropBoxManagerInternal;
+import com.android.server.LocalServices;
+import com.android.server.am.ActivityManagerService.Injector;
+import com.android.server.appop.AppOpsService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.Arrays;
+
+
+/**
+ * Tests to verify that process events are dispatched to process observers.
+ */
+@MediumTest
+@SuppressWarnings("GuardedBy")
+public class ProcessObserverTest {
+    private static final String TAG = "ProcessObserverTest";
+
+    private static final String PACKAGE = "com.foo";
+
+    @Rule
+    public final ApplicationExitInfoTest.ServiceThreadRule
+            mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
+
+    private Context mContext;
+    private HandlerThread mHandlerThread;
+
+    @Mock
+    private AppOpsService mAppOpsService;
+    @Mock
+    private DropBoxManagerInternal mDropBoxManagerInt;
+    @Mock
+    private PackageManagerInternal mPackageManagerInt;
+    @Mock
+    private UsageStatsManagerInternal mUsageStatsManagerInt;
+    @Mock
+    private ActivityManagerInternal mActivityManagerInt;
+    @Mock
+    private ActivityTaskManagerInternal mActivityTaskManagerInt;
+    @Mock
+    private BatteryStatsService mBatteryStatsService;
+
+    private ActivityManagerService mRealAms;
+    private ActivityManagerService mAms;
+
+    private ProcessList mRealProcessList = new ProcessList();
+    private ProcessList mProcessList;
+
+    final IProcessObserver mProcessObserver = mock(IProcessObserver.Stub.class);
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+
+        LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+        LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
+
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+
+        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+        LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInt);
+
+        LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+        LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInt);
+
+        doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+        doReturn(true).when(mActivityTaskManagerInt).attachApplication(any());
+        doNothing().when(mActivityTaskManagerInt).onProcessMapped(anyInt(), any());
+
+        mRealAms = new ActivityManagerService(
+                new TestInjector(mContext), mServiceThreadRule.getThread());
+        mRealAms.mConstants.loadDeviceConfigConstants();
+        mRealAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+        mRealAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+        mRealAms.mAtmInternal = mActivityTaskManagerInt;
+        mRealAms.mPackageManagerInt = mPackageManagerInt;
+        mRealAms.mUsageStatsService = mUsageStatsManagerInt;
+        mRealAms.mProcessesReady = true;
+        mAms = spy(mRealAms);
+        mRealProcessList.mService = mAms;
+        mProcessList = spy(mRealProcessList);
+
+        doReturn(mProcessObserver).when(mProcessObserver).asBinder();
+        mProcessList.registerProcessObserver(mProcessObserver);
+
+        doAnswer((invocation) -> {
+            Log.v(TAG, "Intercepting isProcStartValidLocked() for "
+                    + Arrays.toString(invocation.getArguments()));
+            return null;
+        }).when(mProcessList).isProcStartValidLocked(any(), anyLong());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quit();
+    }
+
+    private class TestInjector extends Injector {
+        TestInjector(Context context) {
+            super(context);
+        }
+
+        @Override
+        public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+                Handler handler) {
+            return mAppOpsService;
+        }
+
+        @Override
+        public Handler getUiHandler(ActivityManagerService service) {
+            return mHandlerThread.getThreadHandler();
+        }
+
+        @Override
+        public ProcessList getProcessList(ActivityManagerService service) {
+            return mRealProcessList;
+        }
+
+        @Override
+        public BatteryStatsService getBatteryStatsService() {
+            return mBatteryStatsService;
+        }
+    }
+
+    private ProcessRecord makeActiveProcessRecord(String packageName)
+            throws Exception {
+        final ApplicationInfo ai = makeApplicationInfo(packageName);
+        return makeActiveProcessRecord(ai);
+    }
+
+    private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai)
+            throws Exception {
+        final IApplicationThread thread = mock(IApplicationThread.class);
+        final IBinder threadBinder = new Binder();
+        doReturn(threadBinder).when(thread).asBinder();
+        doAnswer((invocation) -> {
+            Log.v(TAG, "Intercepting bindApplication() for "
+                    + Arrays.toString(invocation.getArguments()));
+            if (mRealAms.mConstants.mEnableWaitForFinishAttachApplication) {
+                mRealAms.finishAttachApplication(0);
+            }
+            return null;
+        }).when(thread).bindApplication(
+                any(), any(),
+                any(), any(), anyBoolean(),
+                any(), any(),
+                any(), any(),
+                any(),
+                any(), anyInt(),
+                anyBoolean(), anyBoolean(),
+                anyBoolean(), anyBoolean(), any(),
+                any(), any(), any(),
+                any(), any(),
+                any(), any(),
+                any(),
+                anyLong(), anyLong());
+        final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
+        r.setPid(myPid());
+        r.setStartUid(myUid());
+        r.setHostingRecord(new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST));
+        r.makeActive(thread, mAms.mProcessStats);
+        doNothing().when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean(),
+                anyBoolean());
+        return r;
+    }
+
+    static ApplicationInfo makeApplicationInfo(String packageName) {
+        final ApplicationInfo ai = new ApplicationInfo();
+        ai.packageName = packageName;
+        ai.processName = packageName;
+        ai.uid = myUid();
+        return ai;
+    }
+
+    /**
+     * Verify that a process start event is dispatched to process observers.
+     */
+    @Test
+    public void testNormal() throws Exception {
+        ProcessRecord app = startProcess();
+        verify(mProcessObserver).onProcessStarted(
+                app.getPid(), app.uid, app.info.uid, PACKAGE, PACKAGE);
+    }
+
+    private ProcessRecord startProcess() throws Exception {
+        final ProcessRecord app = makeActiveProcessRecord(PACKAGE);
+        final ApplicationInfo appInfo = makeApplicationInfo(PACKAGE);
+        mProcessList.handleProcessStartedLocked(app, app.getPid(), /* usingWrapper */ false,
+                /* expectedStartSeq */ 0, /* procAttached */ false);
+        app.getThread().bindApplication(PACKAGE, appInfo,
+                null, null, false,
+                null,
+                null,
+                null, null,
+                null,
+                null, 0,
+                false, false,
+                true, false,
+                null,
+                null, null,
+                null,
+                null, null, null,
+                null, null,
+                0, 0);
+        return app;
+    }
+
+    // TODO: [b/302724778] Remove manual JNI load
+    static {
+        System.loadLibrary("mockingservicestestjni");
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
new file mode 100644
index 0000000..2f12a3b
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
@@ -0,0 +1,677 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_HOME;
+import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
+import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
+import static android.content.Context.BIND_AUTO_CREATE;
+import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
+import static android.content.Context.BIND_WAIVE_PRIORITY;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED;
+import static android.os.UserHandle.USER_SYSTEM;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+
+import static com.android.server.am.ProcessList.HOME_APP_ADJ;
+import static com.android.server.am.ProcessList.PERCEPTIBLE_APP_ADJ;
+import static com.android.server.am.ProcessList.SERVICE_ADJ;
+import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
+
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.IApplicationThread;
+import android.app.IServiceConnection;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.DropBoxManagerInternal;
+import com.android.server.LocalServices;
+import com.android.server.am.ActivityManagerService.Injector;
+import com.android.server.am.ApplicationExitInfoTest.ServiceThreadRule;
+import com.android.server.appop.AppOpsService;
+import com.android.server.firewall.IntentFirewall;
+import com.android.server.wm.ActivityTaskManagerService;
+import com.android.server.wm.WindowProcessController;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.verification.VerificationMode;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.function.Consumer;
+
+/**
+ * Test class for the service timeout.
+ *
+ * Build/Install/Run:
+ *  atest ServiceBindingOomAdjPolicyTest
+ */
+@Presubmit
+public final class ServiceBindingOomAdjPolicyTest {
+    private static final String TAG = ServiceBindingOomAdjPolicyTest.class.getSimpleName();
+
+    private static final String TEST_APP1_NAME = "com.example.foo";
+    private static final String TEST_SERVICE1_NAME = "com.example.foo.Foobar";
+    private static final int TEST_APP1_UID = 10123;
+    private static final int TEST_APP1_PID = 12345;
+
+    private static final String TEST_APP2_NAME = "com.example.bar";
+    private static final String TEST_SERVICE2_NAME = "com.example.bar.Buz";
+    private static final int TEST_APP2_UID = 10124;
+    private static final int TEST_APP2_PID = 12346;
+
+    @Rule
+    public final ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
+    private Context mContext;
+    private HandlerThread mHandlerThread;
+
+    @Mock
+    private AppOpsService mAppOpsService;
+    @Mock
+    private DropBoxManagerInternal mDropBoxManagerInt;
+    @Mock
+    private PackageManagerInternal mPackageManagerInt;
+    @Mock
+    private UsageStatsManagerInternal mUsageStatsManagerInt;
+    @Mock
+    private AppErrors mAppErrors;
+    @Mock
+    private IntentFirewall mIntentFirewall;
+
+    private ActivityManagerService mAms;
+    private ProcessList mProcessList;
+    private ActiveServices mActiveServices;
+
+    private int mCurrentCallingUid;
+    private int mCurrentCallingPid;
+
+    /** Run at the test class initialization */
+    @BeforeClass
+    public static void setUpOnce() {
+        System.setProperty("dexmaker.share_classloader", "true");
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        final ProcessList realProcessList = new ProcessList();
+        mProcessList = spy(realProcessList);
+
+        LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+        LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+        doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+
+        final ActivityManagerService realAms = new ActivityManagerService(
+                new TestInjector(mContext), mServiceThreadRule.getThread());
+        final ActivityTaskManagerService realAtm = new ActivityTaskManagerService(mContext);
+        realAtm.initialize(null, null, mContext.getMainLooper());
+        realAms.mActivityTaskManager = spy(realAtm);
+        realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
+        realAms.mOomAdjuster = spy(realAms.mOomAdjuster);
+        realAms.mOomAdjuster.mCachedAppOptimizer = spy(realAms.mOomAdjuster.mCachedAppOptimizer);
+        realAms.mPackageManagerInt = mPackageManagerInt;
+        realAms.mUsageStatsService = mUsageStatsManagerInt;
+        realAms.mAppProfiler = spy(realAms.mAppProfiler);
+        realAms.mProcessesReady = true;
+        mAms = spy(realAms);
+        realProcessList.mService = mAms;
+
+        doReturn(false).when(mPackageManagerInt).filterAppAccess(anyString(), anyInt(), anyInt());
+        doReturn(true).when(mIntentFirewall).checkService(any(), any(), anyInt(), anyInt(), any(),
+                any());
+        doReturn(false).when(mAms.mAtmInternal).hasSystemAlertWindowPermission(anyInt(), anyInt(),
+                any());
+        doReturn(true).when(mAms.mOomAdjuster.mCachedAppOptimizer).useFreezer();
+        doNothing().when(mAms.mOomAdjuster.mCachedAppOptimizer).freezeAppAsyncInternalLSP(
+                any(), anyLong(), anyBoolean());
+        doReturn(false).when(mAms.mAppProfiler).updateLowMemStateLSP(anyInt(), anyInt(),
+                anyInt(), anyLong());
+
+        mCurrentCallingUid = TEST_APP1_UID;
+        mCurrentCallingPid = TEST_APP1_PID;
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        mHandlerThread.quit();
+    }
+
+    @Test
+    public void testServiceSelfBindingOomAdj() throws Exception {
+        // Enable the flags.
+        mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+        // Verify that there should be 0 oom adj updates.
+        performTestServiceSelfBindingOomAdj(never(), never());
+
+        // Disable the flags.
+        mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+        // Verify that there should be at least 1 oom adj update.
+        performTestServiceSelfBindingOomAdj(atLeastOnce(), atLeastOnce());
+    }
+
+    @SuppressWarnings("GuardedBy")
+    private void performTestServiceSelfBindingOomAdj(VerificationMode bindMode,
+            VerificationMode unbindMode) throws Exception {
+        final ProcessRecord app = addProcessRecord(
+                TEST_APP1_PID,           // pid
+                TEST_APP1_UID,           // uid
+                PROCESS_STATE_SERVICE,   // procstate
+                SERVICE_ADJ,             // adj
+                PROCESS_CAPABILITY_NONE, // capabilities
+                TEST_APP1_NAME           // packageName
+        );
+        final Intent serviceIntent = createServiceIntent(TEST_APP1_NAME, TEST_SERVICE1_NAME,
+                TEST_APP1_UID);
+        final IServiceConnection serviceConnection = mock(IServiceConnection.class);
+
+        // Make a self binding.
+        assertNotEquals(0, mAms.bindService(
+                app.getThread(),         // caller
+                null,                    // token
+                serviceIntent,           // service
+                null,                    // resolveType
+                serviceConnection,       // connection
+                BIND_AUTO_CREATE,        // flags
+                TEST_APP1_NAME,          // callingPackage
+                USER_SYSTEM              // userId
+        ));
+
+        verify(mAms.mOomAdjuster, bindMode).updateOomAdjPendingTargetsLocked(anyInt());
+        clearInvocations(mAms.mOomAdjuster);
+
+        // Unbind the service.
+        mAms.unbindService(serviceConnection);
+
+        verify(mAms.mOomAdjuster, unbindMode).updateOomAdjPendingTargetsLocked(anyInt());
+        clearInvocations(mAms.mOomAdjuster);
+
+        removeProcessRecord(app);
+    }
+
+    @Test
+    public void testServiceDistinctBindingOomAdjMoreImportant() throws Exception {
+        // Enable the flags.
+        mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+        // Verify that there should be at least 1 oom adj update
+        // because the client is more important.
+        performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+                PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+                PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+                this::setHasForegroundServices,
+                TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME,
+                HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+                this::setHomeProcess,
+                BIND_AUTO_CREATE,
+                atLeastOnce(), atLeastOnce());
+
+        // Disable the flags.
+        mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+        // Verify that there should be at least 1 oom adj update
+        // because the client is more important.
+        performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+                PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+                PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+                this::setHasForegroundServices,
+                TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME,
+                HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+                this::setHomeProcess,
+                BIND_AUTO_CREATE,
+                atLeastOnce(), atLeastOnce());
+    }
+
+    @Test
+    public void testServiceDistinctBindingOomAdjLessImportant() throws Exception {
+        // Enable the flags.
+        mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+        // Verify that there should be 0 oom adj update
+        performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+                PROCESS_STATE_HOME, HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+                this::setHomeProcess,
+                TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+                PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+                this::setHasForegroundServices,
+                BIND_AUTO_CREATE,
+                never(), never());
+
+        // Disable the flags.
+        mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+        // Verify that there should be at least 1 oom adj update
+        performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+                PROCESS_STATE_HOME, HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+                this::setHomeProcess,
+                TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+                PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+                this::setHasForegroundServices,
+                BIND_AUTO_CREATE,
+                atLeastOnce(), atLeastOnce());
+    }
+
+    @Test
+    public void testServiceDistinctBindingOomAdjWaivePriority() throws Exception {
+        // Enable the flags.
+        mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+        // Verify that there should be 0 oom adj update for binding
+        // because we're using the BIND_WAIVE_PRIORITY;
+        // but for the unbinding, because client is better than service, we can't skip it safely.
+        performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+                PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+                PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+                this::setHasForegroundServices,
+                TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME,
+                HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+                this::setHomeProcess,
+                BIND_AUTO_CREATE | BIND_WAIVE_PRIORITY,
+                never(), atLeastOnce());
+
+        // Verify that there should be 0 oom adj update
+        // because we're using the BIND_WAIVE_PRIORITY;
+        performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+                PROCESS_STATE_HOME, HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+                this::setHomeProcess,
+                TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+                PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+                this::setHasForegroundServices,
+                BIND_AUTO_CREATE | BIND_WAIVE_PRIORITY,
+                never(), never());
+
+        // Disable the flags.
+        mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+        // Verify that there should be at least 1 oom adj update
+        // because the client is more important.
+        performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+                PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+                PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+                this::setHasForegroundServices,
+                TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME,
+                HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+                this::setHomeProcess,
+                BIND_AUTO_CREATE,
+                atLeastOnce(), atLeastOnce());
+    }
+
+    @Test
+    public void testServiceDistinctBindingOomAdjNoIncludeCapabilities() throws Exception {
+        // Enable the flags.
+        mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+        // Verify that there should be 0 oom adj update
+        // because we didn't specify the "BIND_INCLUDE_CAPABILITIES"
+        performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+                PROCESS_STATE_HOME, HOME_APP_ADJ,
+                PROCESS_CAPABILITY_FOREGROUND_MICROPHONE, TEST_APP1_NAME,
+                this::setHomeProcess,
+                TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+                PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+                this::setHasForegroundServices,
+                BIND_AUTO_CREATE,
+                never(), never());
+
+        // Disable the flags.
+        mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+        // Verify that there should be at least 1 oom adj update
+        performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+                PROCESS_STATE_HOME, HOME_APP_ADJ,
+                PROCESS_CAPABILITY_FOREGROUND_MICROPHONE, TEST_APP1_NAME,
+                this::setHomeProcess,
+                TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+                PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+                this::setHasForegroundServices,
+                BIND_AUTO_CREATE,
+                atLeastOnce(), atLeastOnce());
+    }
+
+    @Test
+    public void testServiceDistinctBindingOomAdjWithIncludeCapabilities() throws Exception {
+        // Enable the flags.
+        mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+        // Verify that there should be at least 1 oom adj update
+        // because we use the "BIND_INCLUDE_CAPABILITIES"
+        performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+                PROCESS_STATE_HOME, HOME_APP_ADJ,
+                PROCESS_CAPABILITY_FOREGROUND_MICROPHONE, TEST_APP1_NAME,
+                this::setHomeProcess,
+                TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+                PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+                this::setHasForegroundServices,
+                BIND_AUTO_CREATE | BIND_INCLUDE_CAPABILITIES,
+                atLeastOnce(), atLeastOnce());
+
+        // Disable the flags.
+        mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+        // Verify that there should be at least 1 oom adj update
+        performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+                PROCESS_STATE_HOME, HOME_APP_ADJ,
+                PROCESS_CAPABILITY_FOREGROUND_MICROPHONE, TEST_APP1_NAME,
+                this::setHomeProcess,
+                TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+                PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+                this::setHasForegroundServices,
+                BIND_AUTO_CREATE | BIND_INCLUDE_CAPABILITIES,
+                atLeastOnce(), atLeastOnce());
+    }
+
+    @Test
+    public void testServiceDistinctBindingOomAdjFreezeCaller() throws Exception {
+        // Enable the flags.
+        mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+        // Verify that there should be 0 oom adj update
+        performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+                PROCESS_STATE_CACHED_EMPTY, CACHED_APP_MIN_ADJ, PROCESS_CAPABILITY_NONE,
+                TEST_APP1_NAME, null,
+                TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+                PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+                this::setHasForegroundServices,
+                BIND_AUTO_CREATE,
+                never(), never());
+
+        // Disable the flags.
+        mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+        // Verify that there should be at least 1 oom adj update
+        performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+                PROCESS_STATE_CACHED_EMPTY, CACHED_APP_MIN_ADJ, PROCESS_CAPABILITY_NONE,
+                TEST_APP1_NAME, null,
+                TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+                PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+                this::setHasForegroundServices,
+                BIND_AUTO_CREATE,
+                atLeastOnce(), atLeastOnce());
+    }
+
+    @SuppressWarnings("GuardedBy")
+    private void performTestServiceDistinctBindingOomAdj(int clientPid, int clientUid,
+            int clientProcState, int clientAdj, int clientCap, String clientPackageName,
+            Consumer<ProcessRecord> clientAppFixer,
+            int servicePid, int serviceUid, int serviceProcState, int serviceAdj,
+            int serviceCap, String servicePackageName, String serviceName,
+            Consumer<ProcessRecord> serviceAppFixer, int bindingFlags,
+            VerificationMode bindMode, VerificationMode unbindMode) throws Exception {
+        final ProcessRecord clientApp = addProcessRecord(
+                clientPid,
+                clientUid,
+                clientProcState,
+                clientAdj,
+                clientCap,
+                clientPackageName
+        );
+        final ProcessRecord serviceApp = addProcessRecord(
+                servicePid,
+                serviceUid,
+                serviceProcState,
+                serviceAdj,
+                serviceCap,
+                servicePackageName
+        );
+        final Intent serviceIntent = createServiceIntent(servicePackageName, serviceName,
+                serviceUid);
+        final IServiceConnection serviceConnection = mock(IServiceConnection.class);
+        if (clientAppFixer != null) clientAppFixer.accept(clientApp);
+        if (serviceAppFixer != null) serviceAppFixer.accept(serviceApp);
+
+        // Make a self binding.
+        assertNotEquals(0, mAms.bindService(
+                clientApp.getThread(), // caller
+                null,                  // token
+                serviceIntent,         // service
+                null,                  // resolveType
+                serviceConnection,     // connection
+                bindingFlags,          // flags
+                clientPackageName,     // callingPackage
+                USER_SYSTEM            // userId
+        ));
+
+        verify(mAms.mOomAdjuster, bindMode).updateOomAdjPendingTargetsLocked(anyInt());
+        clearInvocations(mAms.mOomAdjuster);
+
+        if (clientApp.isFreezable()) {
+            verify(mAms.mOomAdjuster.mCachedAppOptimizer,
+                    times(Flags.serviceBindingOomAdjPolicy() ? 1 : 0))
+                    .freezeAppAsyncInternalLSP(eq(clientApp), eq(0L), anyBoolean());
+            clearInvocations(mAms.mOomAdjuster.mCachedAppOptimizer);
+        }
+
+        // Unbind the service.
+        mAms.unbindService(serviceConnection);
+
+        verify(mAms.mOomAdjuster, unbindMode).updateOomAdjPendingTargetsLocked(anyInt());
+        clearInvocations(mAms.mOomAdjuster);
+
+        removeProcessRecord(clientApp);
+        removeProcessRecord(serviceApp);
+    }
+
+    private void setHasForegroundServices(ProcessRecord app) {
+        app.mServices.setHasForegroundServices(true,
+                FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED, false);
+    }
+
+    private void setHomeProcess(ProcessRecord app) {
+        final WindowProcessController wpc = app.getWindowProcessController();
+        doReturn(true).when(wpc).isHomeProcess();
+    }
+
+    @SuppressWarnings("GuardedBy")
+    private ProcessRecord addProcessRecord(int pid, int uid, int procState, int adj, int cap,
+                String packageName) {
+        final IApplicationThread appThread = mock(IApplicationThread.class);
+        final IBinder threadBinder = mock(IBinder.class);
+        final ProcessRecord app = makeProcessRecord(pid, uid, uid, null, 0,
+                procState, adj, cap, 0L, 0L, packageName, packageName, mAms);
+
+        app.makeActive(appThread, mAms.mProcessStats);
+        doReturn(threadBinder).when(appThread).asBinder();
+        mProcessList.addProcessNameLocked(app);
+        mProcessList.updateLruProcessLocked(app, false, null);
+
+        setFieldValue(ProcessRecord.class, app, "mWindowProcessController",
+                mock(WindowProcessController.class));
+
+        doReturn(app.getSetCapability()).when(mAms.mOomAdjuster).getDefaultCapability(
+                eq(app), anyInt());
+
+        return app;
+    }
+
+    @SuppressWarnings("GuardedBy")
+    private Intent createServiceIntent(String packageName, String serviceName, int serviceUid) {
+        final ComponentName compName = new ComponentName(packageName, serviceName);
+        final Intent serviceIntent = new Intent().setComponent(compName);
+        final ResolveInfo rInfo = new ResolveInfo();
+        rInfo.serviceInfo = makeServiceInfo(compName.getClassName(), compName.getPackageName(),
+                serviceUid);
+        doReturn(rInfo).when(mPackageManagerInt).resolveService(any(Intent.class), any(),
+                anyLong(), anyInt(), anyInt());
+
+        return serviceIntent;
+    }
+
+    @SuppressWarnings("GuardedBy")
+    private void removeProcessRecord(ProcessRecord app) {
+        app.setKilled(true);
+        mProcessList.removeProcessNameLocked(app.processName, app.uid);
+        mProcessList.removeLruProcessLocked(app);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
+            int connectionGroup, int procState, int adj, int cap, long pss, long rss,
+            String processName, String packageName, ActivityManagerService ams) {
+        final ProcessRecord app = ApplicationExitInfoTest.makeProcessRecord(pid, uid, packageUid,
+                definingUid, connectionGroup, procState, pss, rss, processName, packageName, ams);
+        app.mState.setCurProcState(procState);
+        app.mState.setSetProcState(procState);
+        app.mState.setCurAdj(adj);
+        app.mState.setSetAdj(adj);
+        app.mState.setCurCapability(cap);
+        app.mState.setSetCapability(cap);
+        app.mState.setCached(procState >= PROCESS_STATE_LAST_ACTIVITY || adj >= CACHED_APP_MIN_ADJ);
+        return app;
+    }
+
+    @SuppressWarnings("GuardedBy")
+    private ServiceInfo makeServiceInfo(String serviceName, String packageName, int packageUid) {
+        final ServiceInfo sInfo = new ServiceInfo();
+        sInfo.name = serviceName;
+        sInfo.processName = packageName;
+        sInfo.packageName = packageName;
+        sInfo.applicationInfo = new ApplicationInfo();
+        sInfo.applicationInfo.uid = packageUid;
+        sInfo.applicationInfo.packageName = packageName;
+        sInfo.exported = true;
+        return sInfo;
+    }
+
+    private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
+        try {
+            Field field = clazz.getDeclaredField(fieldName);
+            field.setAccessible(true);
+            Field mfield = Field.class.getDeclaredField("accessFlags");
+            mfield.setAccessible(true);
+            mfield.setInt(field, mfield.getInt(field) & ~(Modifier.FINAL | Modifier.PRIVATE));
+            field.set(obj, val);
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+        }
+    }
+
+    private class TestInjector extends Injector {
+        TestInjector(Context context) {
+            super(context);
+        }
+
+        @Override
+        public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+                Handler handler) {
+            return mAppOpsService;
+        }
+
+        @Override
+        public Handler getUiHandler(ActivityManagerService service) {
+            return mHandlerThread.getThreadHandler();
+        }
+
+        @Override
+        public ProcessList getProcessList(ActivityManagerService service) {
+            return mProcessList;
+        }
+
+        @Override
+        public ActiveServices getActiveServices(ActivityManagerService service) {
+            if (mActiveServices == null) {
+                mActiveServices = spy(new ActiveServices(service));
+            }
+            return mActiveServices;
+        }
+
+        @Override
+        public int getCallingUid() {
+            return mCurrentCallingUid;
+        }
+
+        @Override
+        public int getCallingPid() {
+            return mCurrentCallingPid;
+        }
+
+        @Override
+        public long clearCallingIdentity() {
+            return (((long) mCurrentCallingUid) << 32) | mCurrentCallingPid;
+        }
+
+        @Override
+        public void restoreCallingIdentity(long ident) {
+        }
+
+        @Override
+        public AppErrors getAppErrors() {
+            return mAppErrors;
+        }
+
+        @Override
+        public IntentFirewall getIntentFirewall() {
+            return mIntentFirewall;
+        }
+    }
+
+    // TODO: [b/302724778] Remove manual JNI load
+    static {
+        System.loadLibrary("mockingservicestestjni");
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index fc2e5b0..0703db2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -118,7 +118,8 @@
 @Presubmit
 public class GameManagerServiceTests {
     @Mock MockContext mMockContext;
-    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
+                SetFlagsRule.DefaultInitValueType.NULL_DEFAULT);
     private static final String TAG = "GameManagerServiceTests";
     private static final String PACKAGE_NAME_INVALID = "com.android.app";
     private static final int USER_ID_1 = 1001;
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
index 4095be7..18dc114 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
@@ -20,11 +20,13 @@
 
 import android.annotation.NonNull;
 import android.app.backup.BackupHelper;
+import android.app.backup.BackupHelperWithLogger;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.ArraySet;
 
 import static org.mockito.Mockito.when;
@@ -32,7 +34,10 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.backup.Flags;
+
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -55,6 +60,9 @@
     @Mock
     private PackageManager mPackageManagerMock;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -71,7 +79,7 @@
 
         mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0);
 
-        assertThat(mSystemBackupAgent.mAddedHelpers)
+        assertThat(mSystemBackupAgent.mAddedHelpersKey)
                 .containsExactly(
                         "account_sync_settings",
                         "preferred_activities",
@@ -96,7 +104,7 @@
 
         mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0);
 
-        assertThat(mSystemBackupAgent.mAddedHelpers)
+        assertThat(mSystemBackupAgent.mAddedHelpersKey)
                 .containsExactly(
                         "account_sync_settings",
                         "preferred_activities",
@@ -118,7 +126,7 @@
 
         mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0);
 
-        assertThat(mSystemBackupAgent.mAddedHelpers)
+        assertThat(mSystemBackupAgent.mAddedHelpersKey)
                 .containsExactly(
                         "account_sync_settings",
                         "notifications",
@@ -134,7 +142,7 @@
 
         mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0);
 
-        assertThat(mSystemBackupAgent.mAddedHelpers)
+        assertThat(mSystemBackupAgent.mAddedHelpersKey)
                 .containsExactly(
                         "account_sync_settings",
                         "preferred_activities",
@@ -147,12 +155,42 @@
                         "companion");
     }
 
+    @Test
+    public void onAddHelperIfEligibleForUser_flagIsOff_helpersHaveNoLogger() {
+        UserHandle userHandle = new UserHandle(UserHandle.USER_SYSTEM);
+        when(mUserManagerMock.isProfile()).thenReturn(false);
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_METRICS_SYSTEM_BACKUP_AGENTS);
+
+        mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0);
+
+        for (BackupHelperWithLogger helper:mSystemBackupAgent.mAddedHelpers){
+            assertThat(helper.isLoggerSet()).isFalse();
+        }
+    }
+
+    @Test
+    public void onAddHelperIfEligibleForUser_flagIsOn_helpersHaveLogger() {
+        UserHandle userHandle = new UserHandle(UserHandle.USER_SYSTEM);
+        when(mUserManagerMock.isProfile()).thenReturn(false);
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_METRICS_SYSTEM_BACKUP_AGENTS);
+
+        mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0);
+
+        for (BackupHelperWithLogger helper:mSystemBackupAgent.mAddedHelpers){
+            assertThat(helper.isLoggerSet()).isTrue();
+        }
+    }
+
     private class TestableSystemBackupAgent extends SystemBackupAgent {
-        final Set<String> mAddedHelpers = new ArraySet<>();
+        final Set<String> mAddedHelpersKey = new ArraySet<>();
+        final Set<BackupHelperWithLogger> mAddedHelpers = new ArraySet<>();
 
         @Override
         public void addHelper(String keyPrefix, BackupHelper helper) {
-            mAddedHelpers.add(keyPrefix);
+            mAddedHelpersKey.add(keyPrefix);
+            if (helper instanceof BackupHelperWithLogger) {
+                mAddedHelpers.add((BackupHelperWithLogger) helper);
+            }
         }
 
         @Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 650c473..28471b3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -26,15 +26,22 @@
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
+import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
+import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import static com.android.server.job.controllers.FlexibilityController.FLEXIBLE_CONSTRAINTS;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES;
 import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS;
 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_APPLIED_CONSTRAINTS;
 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_DEADLINE_PROXIMITY_LIMIT;
 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE;
-import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS;
-import static com.android.server.job.controllers.FlexibilityController.FLEXIBLE_CONSTRAINTS;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINES;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
 import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
@@ -50,24 +57,33 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.AlarmManager;
 import android.app.AppGlobals;
 import android.app.job.JobInfo;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.net.NetworkRequest;
 import android.os.Looper;
+import android.os.PowerManager;
 import android.provider.DeviceConfig;
 import android.util.ArraySet;
+import android.util.EmptyArray;
+import android.util.SparseArray;
 
 import com.android.server.AppSchedulingModuleThread;
+import com.android.server.DeviceIdleInternal;
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.JobStore;
@@ -77,6 +93,7 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.MockitoSession;
@@ -84,6 +101,7 @@
 import org.mockito.stubbing.Answer;
 
 import java.time.Clock;
+import java.time.Duration;
 import java.time.Instant;
 import java.time.ZoneOffset;
 import java.util.ArrayList;
@@ -95,6 +113,7 @@
     private static final long FROZEN_TIME = 100L;
 
     private MockitoSession mMockingSession;
+    private BroadcastReceiver mBroadcastReceiver;
     private FlexibilityController mFlexibilityController;
     private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
     private JobStore mJobStore;
@@ -106,6 +125,8 @@
     @Mock
     private Context mContext;
     @Mock
+    private DeviceIdleInternal mDeviceIdleInternal;
+    @Mock
     private JobSchedulerService mJobSchedulerService;
     @Mock
     private PrefetchController mPrefetchController;
@@ -128,10 +149,13 @@
         // Called in FlexibilityController constructor.
         when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
         when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
+        doNothing().when(mAlarmManager).setExact(anyInt(), anyLong(), anyString(), any(), any());
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mPackageManager.hasSystemFeature(
                 PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(false);
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_EMBEDDED)).thenReturn(false);
+        doReturn(mDeviceIdleInternal)
+                .when(() -> LocalServices.getService(DeviceIdleInternal.class));
         // Used in FlexibilityController.FcConstants.
         doAnswer((Answer<Void>) invocationOnMock -> null)
                 .when(() -> DeviceConfig.addOnPropertiesChangedListener(
@@ -146,7 +170,7 @@
                         eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER), ArgumentMatchers.<String>any()));
         //used to get jobs by UID
         mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
-        when(mJobSchedulerService.getJobStore()).thenReturn(mJobStore);
+        doReturn(mJobStore).when(mJobSchedulerService).getJobStore();
         // Used in JobStatus.
         doReturn(mock(PackageManagerInternal.class))
                 .when(() -> LocalServices.getService(PackageManagerInternal.class));
@@ -156,16 +180,29 @@
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
         // Initialize real objects.
+        doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(any());
+        ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
         mFlexibilityController = new FlexibilityController(mJobSchedulerService,
                 mPrefetchController);
         mFcConfig = mFlexibilityController.getFcConfig();
 
         mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
 
-        setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "50,60,70,80");
+        setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+                "500=50|60|70|80"
+                        + ",400=50|60|70|80"
+                        + ",300=50|60|70|80"
+                        + ",200=50|60|70|80"
+                        + ",100=50|60|70|80");
         setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, 0L);
         setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS);
         waitForQuietModuleThread();
+
+        verify(mContext).registerReceiver(receiverCaptor.capture(),
+                ArgumentMatchers.argThat(filter ->
+                        filter.hasAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED)));
+        mBroadcastReceiver = receiverCaptor.getValue();
     }
 
     @After
@@ -175,6 +212,11 @@
         }
     }
 
+    private void advanceElapsedClock(long incrementMs) {
+        JobSchedulerService.sElapsedRealtimeClock = Clock.offset(
+                sElapsedRealtimeClock, Duration.ofMillis(incrementMs));
+    }
+
     private void setDeviceConfigInt(String key, int val) {
         mDeviceConfigPropertiesBuilder.setInt(key, val);
         updateDeviceConfig(key);
@@ -212,6 +254,7 @@
         JobStatus js = JobStatus.createFromJobInfo(
                 jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, "FCTest", testTag);
         js.enqueueTime = FROZEN_TIME;
+        js.setStandbyBucket(ACTIVE_INDEX);
         if (js.hasFlexibilityConstraint()) {
             js.setNumAppliedFlexibleConstraints(Integer.bitCount(
                     mFlexibilityController.getRelevantAppliedConstraintsLocked(js)));
@@ -224,9 +267,12 @@
      */
     @Test
     public void testDefaultVariableValues() {
-        assertEquals(Integer.bitCount(FLEXIBLE_CONSTRAINTS),
-                mFlexibilityController.mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS.length
-        );
+        SparseArray<int[]> defaultPercentsToDrop =
+                FlexibilityController.FcConfig.DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
+        for (int i = 0; i < defaultPercentsToDrop.size(); ++i) {
+            assertEquals(Integer.bitCount(FLEXIBLE_CONSTRAINTS),
+                    defaultPercentsToDrop.valueAt(i).length);
+        }
     }
 
     @Test
@@ -353,10 +399,13 @@
     @Test
     public void testOnConstantsUpdated_FallbackDeadline() {
         JobStatus js = createJobStatus("testFallbackDeadlineConfig", createJob(0));
-        assertEquals(DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS,
-                mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0L));
-        setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 100L);
-        assertEquals(100L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0L));
+        final long nowElapsed = sElapsedRealtimeClock.millis();
+        assertEquals(DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.get(JobInfo.PRIORITY_DEFAULT),
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0L));
+        setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 123L);
+        setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
+                "500=500,400=400,300=300,200=200,100=100");
+        assertEquals(300L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0L));
     }
 
     @Test
@@ -366,10 +415,32 @@
         JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb);
         assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
-        setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,20,30,40");
+        setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+                "500=1|2|3|4"
+                        + ",400=5|6|7|8"
+                        + ",300=10|20|30|40"
+                        + ",200=50|51|52|53"
+                        + ",100=54|55|56|57");
         assertArrayEquals(
-                mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS,
-                new int[] {10, 20, 30, 40});
+                mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+                        .get(JobInfo.PRIORITY_MAX),
+                new int[]{1, 2, 3, 4});
+        assertArrayEquals(
+                mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+                        .get(JobInfo.PRIORITY_HIGH),
+                new int[]{5, 6, 7, 8});
+        assertArrayEquals(
+                mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+                        .get(JobInfo.PRIORITY_DEFAULT),
+                new int[]{10, 20, 30, 40});
+        assertArrayEquals(
+                mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+                        .get(JobInfo.PRIORITY_LOW),
+                new int[]{50, 51, 52, 53});
+        assertArrayEquals(
+                mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+                        .get(JobInfo.PRIORITY_MIN),
+                new int[]{54, 55, 56, 57});
         assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10,
                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
         js.setNumDroppedFlexibleConstraints(1);
@@ -382,24 +453,65 @@
 
     @Test
     public void testOnConstantsUpdated_PercentsToDropConstraintsInvalidValues() {
-        JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
-        JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb);
-        js.enqueueTime = JobSchedulerService.sElapsedRealtimeClock.millis();
-        assertEquals(js.enqueueTime + HOUR_IN_MILLIS / 2,
-                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
-        setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,20a,030,40");
-        assertEquals(js.enqueueTime + HOUR_IN_MILLIS / 2,
-                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
-        setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,40");
-        assertEquals(js.enqueueTime + HOUR_IN_MILLIS / 2,
-                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
-        setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "50,40,10,40");
-        assertEquals(js.enqueueTime + HOUR_IN_MILLIS / 2,
-                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+        // No priority mapping
+        setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS, "10,20,30,40");
+        final SparseArray<int[]> defaultPercentsToDrop =
+                FlexibilityController.FcConfig.DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
+        final SparseArray<int[]> percentsToDrop =
+                mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
+        for (int i = 0; i < defaultPercentsToDrop.size(); ++i) {
+            assertArrayEquals(defaultPercentsToDrop.valueAt(i), percentsToDrop.valueAt(i));
+        }
+
+        // Invalid priority-percentList string
+        setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+                "500=10,20a,030,40"
+                        + ",400=20|40|60|80"
+                        + ",300=25|50|75|80"
+                        + ",200=40|50|60|80"
+                        + ",100=20|40|60|80");
+        for (int i = 0; i < defaultPercentsToDrop.size(); ++i) {
+            assertArrayEquals(defaultPercentsToDrop.valueAt(i), percentsToDrop.valueAt(i));
+        }
+
+        // Invalid percentList strings
+        setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+                "500=10|20a|030|40" // Letters
+                        + ",400=10|40" // Not enough
+                        + ",300=.|50|_|80" // Characters
+                        + ",200=50|40|10|40" // Out of order
+                        + ",100=30|60|90|101"); // Over 100
+        for (int i = 0; i < defaultPercentsToDrop.size(); ++i) {
+            assertArrayEquals(defaultPercentsToDrop.valueAt(i), percentsToDrop.valueAt(i));
+        }
+
+        // Only partially invalid
+        setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+                "500=10|20a|030|40" // Letters
+                        + ",400=10|40" // Not enough
+                        + ",300=.|50|_|80" // Characters
+                        + ",200=10|20|30|40" // Valid
+                        + ",100=20|40|60|80"); // Valid
+        assertArrayEquals(defaultPercentsToDrop.get(JobInfo.PRIORITY_MAX),
+                percentsToDrop.get(JobInfo.PRIORITY_MAX));
+        assertArrayEquals(defaultPercentsToDrop.get(JobInfo.PRIORITY_HIGH),
+                percentsToDrop.get(JobInfo.PRIORITY_HIGH));
+        assertArrayEquals(defaultPercentsToDrop.get(JobInfo.PRIORITY_DEFAULT),
+                percentsToDrop.get(JobInfo.PRIORITY_DEFAULT));
+        assertArrayEquals(new int[]{10, 20, 30, 40}, percentsToDrop.get(JobInfo.PRIORITY_LOW));
+        assertArrayEquals(new int[]{20, 40, 60, 80}, percentsToDrop.get(JobInfo.PRIORITY_MIN));
     }
 
     @Test
     public void testGetNextConstraintDropTimeElapsedLocked() {
+        setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 200 * HOUR_IN_MILLIS);
+        setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
+                "500=" + HOUR_IN_MILLIS
+                        + ",400=" + 25 * HOUR_IN_MILLIS
+                        + ",300=" + 50 * HOUR_IN_MILLIS
+                        + ",200=" + 100 * HOUR_IN_MILLIS
+                        + ",100=" + 200 * HOUR_IN_MILLIS);
+
         long nextTimeToDropNumConstraints;
 
         // no delay, deadline
@@ -431,31 +543,34 @@
 
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(130400100, nextTimeToDropNumConstraints);
+        assertEquals(FROZEN_TIME + 800000L + (50 * HOUR_IN_MILLIS) / 2,
+                nextTimeToDropNumConstraints);
         js.setNumDroppedFlexibleConstraints(1);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(156320100L, nextTimeToDropNumConstraints);
+        assertEquals(FROZEN_TIME + 800000L + (50 * HOUR_IN_MILLIS) * 6 / 10,
+                nextTimeToDropNumConstraints);
         js.setNumDroppedFlexibleConstraints(2);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(182240100L, nextTimeToDropNumConstraints);
+        assertEquals(FROZEN_TIME + 800000L + (50 * HOUR_IN_MILLIS) * 7 / 10,
+                nextTimeToDropNumConstraints);
 
         // no delay, no deadline
-        jb = createJob(0);
+        jb = createJob(0).setPriority(JobInfo.PRIORITY_LOW);
         js = createJobStatus("time", jb);
 
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(129600100, nextTimeToDropNumConstraints);
+        assertEquals(FROZEN_TIME + (100 * HOUR_IN_MILLIS) / 2, nextTimeToDropNumConstraints);
         js.setNumDroppedFlexibleConstraints(1);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(155520100L, nextTimeToDropNumConstraints);
+        assertEquals(FROZEN_TIME + (100 * HOUR_IN_MILLIS) * 6 / 10, nextTimeToDropNumConstraints);
         js.setNumDroppedFlexibleConstraints(2);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(181440100L, nextTimeToDropNumConstraints);
+        assertEquals(FROZEN_TIME + (100 * HOUR_IN_MILLIS) * 7 / 10, nextTimeToDropNumConstraints);
 
         // delay, deadline
         jb = createJob(0)
@@ -483,13 +598,13 @@
     @Test
     public void testCurPercent() {
         long deadline = 100 * MINUTE_IN_MILLIS;
-        long nowElapsed;
+        long nowElapsed = FROZEN_TIME;
         JobInfo.Builder jb = createJob(0).setOverrideDeadline(deadline);
         JobStatus js = createJobStatus("time", jb);
 
         assertEquals(FROZEN_TIME, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
         assertEquals(deadline + FROZEN_TIME,
-                mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME));
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, FROZEN_TIME));
         nowElapsed = FROZEN_TIME + 60 * MINUTE_IN_MILLIS;
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
@@ -516,7 +631,8 @@
         assertEquals(FROZEN_TIME + delay,
                 mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
         assertEquals(deadline + FROZEN_TIME,
-                mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME + delay));
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed,
+                        FROZEN_TIME + delay));
 
         nowElapsed = FROZEN_TIME + delay + 60 * MINUTE_IN_MILLIS;
         JobSchedulerService.sElapsedRealtimeClock =
@@ -598,10 +714,10 @@
     @Test
     public void testGetLifeCycleBeginningElapsedLocked_Prefetch() {
         // prefetch with lifecycle
-        when(mPrefetchController.getLaunchTimeThresholdMs()).thenReturn(700L);
+        doReturn(700L).when(mPrefetchController).getLaunchTimeThresholdMs();
         JobInfo.Builder jb = createJob(0).setPrefetch(true);
         JobStatus js = createJobStatus("time", jb);
-        when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(900L);
+        doReturn(900L).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js);
         assertEquals(900L - 700L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
         // prefetch with enqueue
         jb = createJob(0).setPrefetch(true);
@@ -616,7 +732,7 @@
         // prefetch without estimate
         mFlexibilityController.mPrefetchLifeCycleStart
                 .add(js.getUserId(), js.getSourcePackageName(), 500L);
-        when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(Long.MAX_VALUE);
+        doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js);
         jb = createJob(0).setPrefetch(true);
         js = createJobStatus("time", jb);
         assertEquals(500L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
@@ -639,34 +755,63 @@
 
     @Test
     public void testGetLifeCycleEndElapsedLocked_Prefetch() {
+        final long nowElapsed = sElapsedRealtimeClock.millis();
+
         // prefetch no estimate
         JobInfo.Builder jb = createJob(0).setPrefetch(true);
         JobStatus js = createJobStatus("time", jb);
-        when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(Long.MAX_VALUE);
-        assertEquals(Long.MAX_VALUE, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+        doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js);
+        assertEquals(Long.MAX_VALUE,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
+
         // prefetch with estimate
         jb = createJob(0).setPrefetch(true);
         js = createJobStatus("time", jb);
-        when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(1000L);
-        assertEquals(1000L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+        doReturn(1000L).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js);
+        assertEquals(1000L,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
     }
 
     @Test
     public void testGetLifeCycleEndElapsedLocked_NonPrefetch() {
+        setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
+                "500=" + HOUR_IN_MILLIS
+                        + ",400=" + 2 * HOUR_IN_MILLIS
+                        + ",300=" + 3 * HOUR_IN_MILLIS
+                        + ",200=" + 4 * HOUR_IN_MILLIS
+                        + ",100=" + 5 * HOUR_IN_MILLIS);
+
+        final long nowElapsed = sElapsedRealtimeClock.millis();
+
         // deadline
         JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
         JobStatus js = createJobStatus("time", jb);
         assertEquals(HOUR_IN_MILLIS + FROZEN_TIME,
-                mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
+
         // no deadline
-        jb = createJob(0);
-        js = createJobStatus("time", jb);
-        assertEquals(FROZEN_TIME + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS,
-                mFlexibilityController.getLifeCycleEndElapsedLocked(js, 100L));
+        assertEquals(FROZEN_TIME + 2 * HOUR_IN_MILLIS,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(
+                        createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_HIGH)),
+                        nowElapsed, 100L));
+        assertEquals(FROZEN_TIME + 3 * HOUR_IN_MILLIS,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(
+                        createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT)),
+                        nowElapsed, 100L));
+        assertEquals(FROZEN_TIME + 4 * HOUR_IN_MILLIS,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(
+                        createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_LOW)),
+                        nowElapsed, 100L));
+        assertEquals(FROZEN_TIME + 5 * HOUR_IN_MILLIS,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(
+                        createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_MIN)),
+                        nowElapsed, 100L));
     }
 
     @Test
     public void testGetLifeCycleEndElapsedLocked_Rescheduled() {
+        final long nowElapsed = sElapsedRealtimeClock.millis();
+
         JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
         JobStatus js = createJobStatus("time", jb);
         js = new JobStatus(
@@ -674,20 +819,91 @@
                 0, FROZEN_TIME, FROZEN_TIME);
 
         assertEquals(mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
-                mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
 
         js = new JobStatus(
                 js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, /* numSystemStops */ 1,
                 0, FROZEN_TIME, FROZEN_TIME);
 
         assertEquals(2 * mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
-                mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
 
         js = new JobStatus(
                 js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0, /* numSystemStops */ 10,
                 0, FROZEN_TIME, FROZEN_TIME);
         assertEquals(mFcConfig.MAX_RESCHEDULED_DEADLINE_MS,
-                mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
+    }
+
+    @Test
+    public void testGetLifeCycleEndElapsedLocked_ScoreAddition() {
+        setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
+                "500=" + HOUR_IN_MILLIS
+                        + ",400=" + HOUR_IN_MILLIS
+                        + ",300=" + HOUR_IN_MILLIS
+                        + ",200=" + HOUR_IN_MILLIS
+                        + ",100=" + HOUR_IN_MILLIS);
+        setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES,
+                "500=5,400=4,300=3,200=2,100=1");
+        setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS,
+                "500=" + 5 * MINUTE_IN_MILLIS
+                        + ",400=" + 4 * MINUTE_IN_MILLIS
+                        + ",300=" + 3 * MINUTE_IN_MILLIS
+                        + ",200=" + 2 * MINUTE_IN_MILLIS
+                        + ",100=" + 1 * MINUTE_IN_MILLIS);
+
+        final long nowElapsed = sElapsedRealtimeClock.millis();
+
+        JobStatus jsMax = createJobStatus("testScoreCalculation",
+                createJob(0).setExpedited(true).setPriority(JobInfo.PRIORITY_MAX));
+        JobStatus jsHigh = createJobStatus("testScoreCalculation",
+                createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
+        JobStatus jsDefault = createJobStatus("testScoreCalculation",
+                createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT));
+        JobStatus jsLow = createJobStatus("testScoreCalculation",
+                createJob(0).setPriority(JobInfo.PRIORITY_LOW));
+        JobStatus jsMin = createJobStatus("testScoreCalculation",
+                createJob(0).setPriority(JobInfo.PRIORITY_MIN));
+        // Make score = 15
+        mFlexibilityController.prepareForExecutionLocked(jsMax);
+        mFlexibilityController.prepareForExecutionLocked(jsHigh);
+        mFlexibilityController.prepareForExecutionLocked(jsDefault);
+        mFlexibilityController.prepareForExecutionLocked(jsLow);
+        mFlexibilityController.prepareForExecutionLocked(jsMin);
+
+        // deadline
+        JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
+        JobStatus js = createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition", jb);
+        assertEquals(HOUR_IN_MILLIS + FROZEN_TIME,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
+
+        final long earliestMs = 123L;
+        // no deadline
+        assertEquals(earliestMs + HOUR_IN_MILLIS + 5 * 15 * MINUTE_IN_MILLIS,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(
+                        createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
+                                createJob(0).setExpedited(true).setPriority(JobInfo.PRIORITY_MAX)),
+                        nowElapsed, earliestMs));
+        assertEquals(earliestMs + HOUR_IN_MILLIS + 4 * 15 * MINUTE_IN_MILLIS,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(
+                        createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
+                                createJob(0).setPriority(JobInfo.PRIORITY_HIGH)),
+                        nowElapsed, earliestMs));
+        assertEquals(earliestMs + HOUR_IN_MILLIS + 3 * 15 * MINUTE_IN_MILLIS,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(
+                        createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
+                                createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT)),
+                        nowElapsed, earliestMs));
+        assertEquals(earliestMs + HOUR_IN_MILLIS + 2 * 15 * MINUTE_IN_MILLIS,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(
+                        createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
+                                createJob(0).setPriority(JobInfo.PRIORITY_LOW)),
+                        nowElapsed, earliestMs));
+        assertEquals(earliestMs + HOUR_IN_MILLIS + 1 * 15 * MINUTE_IN_MILLIS,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(
+                        createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
+                                createJob(0).setPriority(JobInfo.PRIORITY_MIN)),
+                        nowElapsed, earliestMs));
     }
 
     @Test
@@ -696,13 +912,21 @@
         // Stop satisfied constraints from causing a false positive.
         js.setNumAppliedFlexibleConstraints(100);
         synchronized (mFlexibilityController.mLock) {
-            when(mJobSchedulerService.isCurrentlyRunningLocked(js)).thenReturn(true);
+            doReturn(true).when(mJobSchedulerService).isCurrentlyRunningLocked(js);
             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
         }
     }
 
     @Test
     public void testFlexibilityTracker() {
+        setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 100 * HOUR_IN_MILLIS);
+        setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
+                "500=" + 100 * HOUR_IN_MILLIS
+                        + ",400=" + 100 * HOUR_IN_MILLIS
+                        + ",300=" + 100 * HOUR_IN_MILLIS
+                        + ",200=" + 100 * HOUR_IN_MILLIS
+                        + ",100=" + 100 * HOUR_IN_MILLIS);
+
         FlexibilityController.FlexibilityTracker flexTracker =
                 mFlexibilityController.new FlexibilityTracker(4);
         // Plus one for jobs with 0 required constraint.
@@ -774,8 +998,7 @@
             assertEquals(1, trackedJobs.get(3).size());
             assertEquals(0, trackedJobs.get(4).size());
 
-            final long nowElapsed = ((DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 2)
-                    + HOUR_IN_MILLIS);
+            final long nowElapsed = 51 * HOUR_IN_MILLIS;
             JobSchedulerService.sElapsedRealtimeClock =
                     Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
@@ -847,14 +1070,84 @@
     }
 
     @Test
+    public void testAllowlistedAppBypass() {
+        setPowerWhitelistExceptIdle();
+        mFlexibilityController.onSystemServicesReady();
+
+        JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
+        JobStatus jsDefault = createJobStatus("testAllowlistedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT));
+        JobStatus jsLow = createJobStatus("testAllowlistedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_LOW));
+        JobStatus jsMin = createJobStatus("testAllowlistedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_MIN));
+        jsHigh.setStandbyBucket(EXEMPTED_INDEX);
+        jsDefault.setStandbyBucket(EXEMPTED_INDEX);
+        jsLow.setStandbyBucket(EXEMPTED_INDEX);
+        jsMin.setStandbyBucket(EXEMPTED_INDEX);
+
+        setPowerWhitelistExceptIdle();
+        synchronized (mFlexibilityController.mLock) {
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
+        }
+
+        setPowerWhitelistExceptIdle(SOURCE_PACKAGE);
+        synchronized (mFlexibilityController.mLock) {
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
+        }
+    }
+
+    @Test
+    public void testForegroundAppBypass() {
+        JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
+        JobStatus jsDefault = createJobStatus("testAllowlistedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT));
+        JobStatus jsLow = createJobStatus("testAllowlistedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_LOW));
+        JobStatus jsMin = createJobStatus("testAllowlistedAppBypass",
+                createJob(0).setPriority(JobInfo.PRIORITY_MIN));
+
+        doReturn(JobInfo.BIAS_DEFAULT).when(mJobSchedulerService).getUidBias(mSourceUid);
+        synchronized (mFlexibilityController.mLock) {
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
+        }
+
+        setUidBias(mSourceUid, JobInfo.BIAS_BOUND_FOREGROUND_SERVICE);
+        synchronized (mFlexibilityController.mLock) {
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
+        }
+
+        setUidBias(mSourceUid, JobInfo.BIAS_FOREGROUND_SERVICE);
+        synchronized (mFlexibilityController.mLock) {
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
+            assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
+            assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
+        }
+    }
+
+    @Test
     public void testTopAppBypass() {
-        JobInfo.Builder jb = createJob(0);
+        JobInfo.Builder jb = createJob(0).setPriority(JobInfo.PRIORITY_MIN);
         JobStatus js = createJobStatus("testTopAppBypass", jb);
         mJobStore.add(js);
 
         // Needed because if before and after Uid bias is the same, nothing happens.
-        when(mJobSchedulerService.getUidBias(mSourceUid))
-                .thenReturn(JobInfo.BIAS_FOREGROUND_SERVICE);
+        doReturn(JobInfo.BIAS_DEFAULT).when(mJobSchedulerService).getUidBias(mSourceUid);
 
         synchronized (mFlexibilityController.mLock) {
             mFlexibilityController.maybeStartTrackingJobLocked(js, null);
@@ -865,7 +1158,7 @@
             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
             assertTrue(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));
 
-            setUidBias(mSourceUid, JobInfo.BIAS_FOREGROUND_SERVICE);
+            setUidBias(mSourceUid, JobInfo.BIAS_SYNC_INITIALIZATION);
 
             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
             assertFalse(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));
@@ -1119,66 +1412,161 @@
 
     @Test
     public void testCalculateNumDroppedConstraints() {
-        JobInfo.Builder jb = createJob(22);
-        JobStatus js = createJobStatus("testCalculateNumDroppedConstraints", jb);
-        long nowElapsed = FROZEN_TIME;
+        setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
+                "500=" + HOUR_IN_MILLIS
+                        + ",400=" + 5 * HOUR_IN_MILLIS
+                        + ",300=" + 8 * HOUR_IN_MILLIS
+                        + ",200=" + 10 * HOUR_IN_MILLIS
+                        + ",100=" + 20 * HOUR_IN_MILLIS);
+        setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+                "500=20|40|60|80"
+                        + ",400=20|40|60|80"
+                        + ",300=25|50|75|80"
+                        + ",200=40|50|60|80"
+                        + ",100=20|40|60|80");
 
-        mFlexibilityController.mFlexibilityTracker.add(js);
+        JobStatus jsHigh = createJobStatus("testCalculateNumDroppedConstraints",
+                createJob(24).setPriority(JobInfo.PRIORITY_HIGH));
+        JobStatus jsDefault = createJobStatus("testCalculateNumDroppedConstraints",
+                createJob(23).setPriority(JobInfo.PRIORITY_DEFAULT));
+        JobStatus jsLow = createJobStatus("testCalculateNumDroppedConstraints",
+                createJob(22).setPriority(JobInfo.PRIORITY_LOW));
+        JobStatus jsMin = createJobStatus("testCalculateNumDroppedConstraints",
+                createJob(21).setPriority(JobInfo.PRIORITY_MIN));
+        final long startElapsed = FROZEN_TIME;
+        long nowElapsed = startElapsed;
 
-        assertEquals(3, js.getNumRequiredFlexibleConstraints());
-        assertEquals(0, js.getNumDroppedFlexibleConstraints());
-        assertEquals(1, mFlexibilityController
+        mFlexibilityController.mFlexibilityTracker.add(jsHigh);
+        mFlexibilityController.mFlexibilityTracker.add(jsDefault);
+        mFlexibilityController.mFlexibilityTracker.add(jsLow);
+        mFlexibilityController.mFlexibilityTracker.add(jsMin);
+
+        assertEquals(3, jsHigh.getNumRequiredFlexibleConstraints());
+        assertEquals(0, jsHigh.getNumDroppedFlexibleConstraints());
+        assertEquals(3, jsDefault.getNumRequiredFlexibleConstraints());
+        assertEquals(0, jsDefault.getNumDroppedFlexibleConstraints());
+        assertEquals(3, jsLow.getNumRequiredFlexibleConstraints());
+        assertEquals(0, jsLow.getNumDroppedFlexibleConstraints());
+        assertEquals(3, jsMin.getNumRequiredFlexibleConstraints());
+        assertEquals(0, jsMin.getNumDroppedFlexibleConstraints());
+        assertEquals(4, mFlexibilityController
                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
 
-        nowElapsed += DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 5;
+        nowElapsed = startElapsed + HOUR_IN_MILLIS;
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
         mFlexibilityController.mFlexibilityTracker
-                .setNumDroppedFlexibleConstraints(js, 1);
+                .calculateNumDroppedConstraints(jsHigh, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker
+                .calculateNumDroppedConstraints(jsDefault, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker
+                .calculateNumDroppedConstraints(jsLow, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker
+                .calculateNumDroppedConstraints(jsMin, nowElapsed);
 
-        assertEquals(2, js.getNumRequiredFlexibleConstraints());
-        assertEquals(1, js.getNumDroppedFlexibleConstraints());
-        assertEquals(1, mFlexibilityController
-                .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
-
-        mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
-
-        assertEquals(2, js.getNumRequiredFlexibleConstraints());
-        assertEquals(1, js.getNumDroppedFlexibleConstraints());
-        assertEquals(1, mFlexibilityController
-                .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
-
-        nowElapsed = FROZEN_TIME;
-        JobSchedulerService.sElapsedRealtimeClock =
-                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
-
-        mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
-
-        assertEquals(3, js.getNumRequiredFlexibleConstraints());
-        assertEquals(0, js.getNumDroppedFlexibleConstraints());
-        assertEquals(1, mFlexibilityController
+        assertEquals(2, jsHigh.getNumRequiredFlexibleConstraints());
+        assertEquals(1, jsHigh.getNumDroppedFlexibleConstraints());
+        assertEquals(3, jsDefault.getNumRequiredFlexibleConstraints());
+        assertEquals(0, jsDefault.getNumDroppedFlexibleConstraints());
+        assertEquals(3, jsLow.getNumRequiredFlexibleConstraints());
+        assertEquals(0, jsLow.getNumDroppedFlexibleConstraints());
+        assertEquals(3, jsMin.getNumRequiredFlexibleConstraints());
+        assertEquals(0, jsMin.getNumDroppedFlexibleConstraints());
+        assertEquals(3, mFlexibilityController
                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
+        assertEquals(1, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
 
-        nowElapsed += DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 9;
+        nowElapsed = startElapsed;
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
-        mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker
+                .calculateNumDroppedConstraints(jsHigh, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker
+                .calculateNumDroppedConstraints(jsDefault, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker
+                .calculateNumDroppedConstraints(jsLow, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker
+                .calculateNumDroppedConstraints(jsMin, nowElapsed);
 
-        assertEquals(0, js.getNumRequiredFlexibleConstraints());
-        assertEquals(3, js.getNumDroppedFlexibleConstraints());
+        assertEquals(3, jsHigh.getNumRequiredFlexibleConstraints());
+        assertEquals(0, jsHigh.getNumDroppedFlexibleConstraints());
+        assertEquals(3, jsDefault.getNumRequiredFlexibleConstraints());
+        assertEquals(0, jsDefault.getNumDroppedFlexibleConstraints());
+        assertEquals(3, jsLow.getNumRequiredFlexibleConstraints());
+        assertEquals(0, jsLow.getNumDroppedFlexibleConstraints());
+        assertEquals(3, jsMin.getNumRequiredFlexibleConstraints());
+        assertEquals(0, jsMin.getNumDroppedFlexibleConstraints());
+        assertEquals(4, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
+        assertEquals(0, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
+        assertEquals(0, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(1).size());
+        assertEquals(0, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(0).size());
 
-        nowElapsed = FROZEN_TIME + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 6;
+        nowElapsed = startElapsed + 3 * HOUR_IN_MILLIS;
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
-        mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker
+                .calculateNumDroppedConstraints(jsHigh, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker
+                .calculateNumDroppedConstraints(jsDefault, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker
+                .calculateNumDroppedConstraints(jsLow, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker
+                .calculateNumDroppedConstraints(jsMin, nowElapsed);
 
-        assertEquals(1, js.getNumRequiredFlexibleConstraints());
-        assertEquals(2, js.getNumDroppedFlexibleConstraints());
+        assertEquals(0, jsHigh.getNumRequiredFlexibleConstraints());
+        assertEquals(3, jsHigh.getNumDroppedFlexibleConstraints());
+        assertEquals(2, jsDefault.getNumRequiredFlexibleConstraints());
+        assertEquals(1, jsDefault.getNumDroppedFlexibleConstraints());
+        assertEquals(3, jsLow.getNumRequiredFlexibleConstraints());
+        assertEquals(0, jsLow.getNumDroppedFlexibleConstraints());
+        assertEquals(3, jsMin.getNumRequiredFlexibleConstraints());
+        assertEquals(0, jsMin.getNumDroppedFlexibleConstraints());
+        assertEquals(2, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
+        assertEquals(1, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
+        assertEquals(0, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(1).size());
+        assertEquals(1, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(0).size());
+
+        nowElapsed = startElapsed + 4 * HOUR_IN_MILLIS;
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+
+        mFlexibilityController.mFlexibilityTracker
+                .calculateNumDroppedConstraints(jsHigh, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker
+                .calculateNumDroppedConstraints(jsDefault, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker
+                .calculateNumDroppedConstraints(jsLow, nowElapsed);
+        mFlexibilityController.mFlexibilityTracker
+                .calculateNumDroppedConstraints(jsMin, nowElapsed);
+
+        assertEquals(0, jsHigh.getNumRequiredFlexibleConstraints());
+        assertEquals(3, jsHigh.getNumDroppedFlexibleConstraints());
+        assertEquals(1, jsDefault.getNumRequiredFlexibleConstraints());
+        assertEquals(2, jsDefault.getNumDroppedFlexibleConstraints());
+        assertEquals(2, jsLow.getNumRequiredFlexibleConstraints());
+        assertEquals(1, jsLow.getNumDroppedFlexibleConstraints());
+        assertEquals(2, jsMin.getNumRequiredFlexibleConstraints());
+        assertEquals(1, jsMin.getNumDroppedFlexibleConstraints());
+        assertEquals(0, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
+        assertEquals(2, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
         assertEquals(1, mFlexibilityController
                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(1).size());
+        assertEquals(1, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(0).size());
     }
 
     @Test
@@ -1187,9 +1575,9 @@
         JobInfo.Builder jb = createJob(22).setPrefetch(true);
         JobStatus js = createJobStatus("onPrefetchCacheUpdated", jb);
         jobs.add(js);
-        when(mPrefetchController.getLaunchTimeThresholdMs()).thenReturn(7 * HOUR_IN_MILLIS);
-        when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(
-                1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS);
+        doReturn(7 * HOUR_IN_MILLIS).when(mPrefetchController).getLaunchTimeThresholdMs();
+        doReturn(1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS)
+                .when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js);
 
         mFlexibilityController.maybeStartTrackingJobLocked(js, null);
 
@@ -1207,7 +1595,7 @@
                         .get(js.getSourceUserId(), js.getSourcePackageName()));
         assertEquals(150L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
         assertEquals(1150L,
-                mFlexibilityController.getLifeCycleEndElapsedLocked(js, 150L));
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 150L));
         assertEquals(0, mFlexibilityController.getCurPercentOfLifecycleLocked(js, FROZEN_TIME));
         assertEquals(650L, mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js));
@@ -1216,6 +1604,80 @@
                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
     }
 
+    @Test
+    public void testScoreCalculation() {
+        JobStatus jsMax = createJobStatus("testScoreCalculation",
+                createJob(0).setExpedited(true).setPriority(JobInfo.PRIORITY_MAX));
+        JobStatus jsHigh = createJobStatus("testScoreCalculation",
+                createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
+        JobStatus jsDefault = createJobStatus("testScoreCalculation",
+                createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT));
+        JobStatus jsLow = createJobStatus("testScoreCalculation",
+                createJob(0).setPriority(JobInfo.PRIORITY_LOW));
+        JobStatus jsMin = createJobStatus("testScoreCalculation",
+                createJob(0).setPriority(JobInfo.PRIORITY_MIN));
+
+        long nowElapsed = sElapsedRealtimeClock.millis();
+        assertEquals(0,
+                mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+        mFlexibilityController.prepareForExecutionLocked(jsMax);
+        assertEquals(5,
+                mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+        advanceElapsedClock(30 * MINUTE_IN_MILLIS);
+        nowElapsed += 30 * MINUTE_IN_MILLIS;
+        mFlexibilityController.prepareForExecutionLocked(jsMax);
+        assertEquals(10,
+                mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+        advanceElapsedClock(31 * MINUTE_IN_MILLIS);
+        nowElapsed += 31 * MINUTE_IN_MILLIS;
+        mFlexibilityController.prepareForExecutionLocked(jsHigh);
+        assertEquals(14,
+                mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+        advanceElapsedClock(2 * HOUR_IN_MILLIS);
+        nowElapsed += 2 * HOUR_IN_MILLIS;
+        mFlexibilityController.prepareForExecutionLocked(jsDefault);
+        assertEquals(17,
+                mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+        advanceElapsedClock(3 * HOUR_IN_MILLIS);
+        nowElapsed += 3 * HOUR_IN_MILLIS;
+        mFlexibilityController.prepareForExecutionLocked(jsLow);
+        assertEquals(19,
+                mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+        advanceElapsedClock(3 * HOUR_IN_MILLIS);
+        nowElapsed += 3 * HOUR_IN_MILLIS;
+        mFlexibilityController.prepareForExecutionLocked(jsMin);
+        assertEquals(20,
+                mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+        advanceElapsedClock(3 * HOUR_IN_MILLIS);
+        nowElapsed += 3 * HOUR_IN_MILLIS;
+        mFlexibilityController.prepareForExecutionLocked(jsMax);
+        assertEquals(25,
+                mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+        mFlexibilityController.unprepareFromExecutionLocked(jsMax);
+        assertEquals(20,
+                mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+        // 24 hours haven't passed yet. The jobs in the first hour bucket should still be included.
+        advanceElapsedClock(12 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS - 1);
+        nowElapsed += 12 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS - 1;
+        assertEquals(20,
+                mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+        // Passed the 24 hour mark. The jobs in the first hour bucket should no longer be included.
+        advanceElapsedClock(2);
+        nowElapsed += 2;
+        assertEquals(10,
+                mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+    }
+
     /**
      * The beginning of a lifecycle for prefetch jobs includes the cached maximum of the last time
      * the estimated launch time was updated and the last time the app was opened.
@@ -1231,7 +1693,7 @@
 
         final ArraySet<String> pkgs = new ArraySet<>();
         pkgs.add(js.getSourcePackageName());
-        when(mJobSchedulerService.getPackagesForUidLocked(mSourceUid)).thenReturn(pkgs);
+        doReturn(pkgs).when(mJobSchedulerService).getPackagesForUidLocked(mSourceUid);
 
         setUidBias(mSourceUid, BIAS_TOP_APP);
         setUidBias(mSourceUid, BIAS_FOREGROUND_SERVICE);
@@ -1245,7 +1707,6 @@
         setUidBias(mSourceUid, BIAS_FOREGROUND_SERVICE);
         assertEquals(100L, (long) mFlexibilityController
                 .mPrefetchLifeCycleStart.get(js.getSourceUserId(), js.getSourcePackageName()));
-
     }
 
     @Test
@@ -1259,7 +1720,7 @@
     }
 
     private void runTestUnsupportedDevice(String feature) {
-        when(mPackageManager.hasSystemFeature(feature)).thenReturn(true);
+        doReturn(true).when(mPackageManager).hasSystemFeature(feature);
         mFlexibilityController =
                 new FlexibilityController(mJobSchedulerService, mPrefetchController);
         assertFalse(mFlexibilityController.isEnabled());
@@ -1279,6 +1740,16 @@
         }
     }
 
+    private void setPowerWhitelistExceptIdle(String... packages) {
+        doReturn(packages == null ? EmptyArray.STRING : packages)
+                .when(mDeviceIdleInternal).getFullPowerWhitelistExceptIdle();
+        if (mBroadcastReceiver != null) {
+            mBroadcastReceiver.onReceive(mContext,
+                    new Intent(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED));
+            waitForQuietModuleThread();
+        }
+    }
+
     private void setUidBias(int uid, int bias) {
         int prevBias = mJobSchedulerService.getUidBias(uid);
         doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 293391f..c6608e6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -45,6 +45,7 @@
 import static com.android.server.job.controllers.JobStatus.NO_EARLIEST_RUNTIME;
 import static com.android.server.job.controllers.JobStatus.NO_LATEST_RUNTIME;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -78,6 +79,7 @@
 
 import java.time.Clock;
 import java.time.ZoneOffset;
+import java.util.Arrays;
 
 @RunWith(AndroidJUnit4.class)
 public class JobStatusTest {
@@ -138,6 +140,35 @@
     }
 
     @Test
+    public void testApplyBasicPiiFilters_email() {
+        assertEquals("[EMAIL]", JobStatus.applyBasicPiiFilters("test@email.com"));
+        assertEquals("[EMAIL]", JobStatus.applyBasicPiiFilters("test+plus@email.com"));
+        assertEquals("[EMAIL]", JobStatus.applyBasicPiiFilters("t.e_st+plus-minus@email.com"));
+
+        assertEquals("prefix:[EMAIL]", JobStatus.applyBasicPiiFilters("prefix:test@email.com"));
+
+        assertEquals("not-an-email", JobStatus.applyBasicPiiFilters("not-an-email"));
+    }
+
+    @Test
+    public void testApplyBasicPiiFilters_mixture() {
+        assertEquals("[PHONE]:[EMAIL]",
+                JobStatus.applyBasicPiiFilters("123-456-7890:test+plus@email.com"));
+        assertEquals("prefix:[PHONE]:[EMAIL]",
+                JobStatus.applyBasicPiiFilters("prefix:123-456-7890:test+plus@email.com"));
+    }
+
+    @Test
+    public void testApplyBasicPiiFilters_phone() {
+        assertEquals("[PHONE]", JobStatus.applyBasicPiiFilters("123-456-7890"));
+        assertEquals("[PHONE]", JobStatus.applyBasicPiiFilters("+1-234-567-8900"));
+
+        assertEquals("prefix:[PHONE]", JobStatus.applyBasicPiiFilters("prefix:123-456-7890"));
+
+        assertEquals("not-a-phone-number", JobStatus.applyBasicPiiFilters("not-a-phone-number"));
+    }
+
+    @Test
     public void testCanRunInBatterySaver_regular() {
         final JobInfo jobInfo =
                 new JobInfo.Builder(101, new ComponentName("foo", "bar")).build();
@@ -245,6 +276,42 @@
     }
 
     @Test
+    public void testGetFilteredDebugTags() {
+        final JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+                .addDebugTag("test@email.com")
+                .addDebugTag("123-456-7890")
+                .addDebugTag("random")
+                .build();
+        JobStatus job = createJobStatus(jobInfo);
+        String[] expected = new String[]{"[EMAIL]", "[PHONE]", "random"};
+        String[] result = job.getFilteredDebugTags();
+        Arrays.sort(expected);
+        Arrays.sort(result);
+        assertArrayEquals(expected, result);
+    }
+
+    @Test
+    public void testGetFilteredTraceTag() {
+        JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+                .setTraceTag("test@email.com")
+                .build();
+        JobStatus job = createJobStatus(jobInfo);
+        assertEquals("[EMAIL]", job.getFilteredTraceTag());
+
+        jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+                .setTraceTag("123-456-7890")
+                .build();
+        job = createJobStatus(jobInfo);
+        assertEquals("[PHONE]", job.getFilteredTraceTag());
+
+        jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+                .setTraceTag("random")
+                .build();
+        job = createJobStatus(jobInfo);
+        assertEquals("random", job.getFilteredTraceTag());
+    }
+
+    @Test
     public void testIsUserVisibleJob() {
         JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
                 .setUserInitiated(false)
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java b/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java
index 8d9a6c5..9a143d5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java
@@ -21,6 +21,8 @@
 import static org.junit.Assert.assertThrows;
 
 import android.content.Context;
+import android.frameworks.location.altitude.GetGeoidHeightRequest;
+import android.frameworks.location.altitude.GetGeoidHeightResponse;
 import android.location.Location;
 import android.location.altitude.AltitudeConverter;
 
@@ -176,4 +178,20 @@
         assertThrows(IllegalArgumentException.class,
                 () -> mAltitudeConverter.addMslAltitudeToLocation(mContext, location));
     }
+
+    @Test
+    public void testGetGeoidHeight_expectedBehavior() throws IOException {
+        GetGeoidHeightRequest request = new GetGeoidHeightRequest();
+        request.latitudeDegrees = -35.334815;
+        request.longitudeDegrees = -45;
+        // Requires data to be loaded from raw assets.
+        GetGeoidHeightResponse response = mAltitudeConverter.getGeoidHeight(mContext, request);
+        assertThat(response.geoidHeightMeters).isWithin(2).of(-5.0622);
+        assertThat(response.geoidHeightErrorMeters).isGreaterThan(0f);
+        assertThat(response.geoidHeightErrorMeters).isLessThan(1f);
+        assertThat(response.expirationDistanceMeters).isWithin(1).of(-6.33);
+        assertThat(response.additionalGeoidHeightErrorMeters).isGreaterThan(0f);
+        assertThat(response.additionalGeoidHeightErrorMeters).isLessThan(1f);
+        assertThat(response.success).isTrue();
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
index 4eba219..efab19c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
@@ -29,6 +29,7 @@
 
 import android.content.Context;
 import android.location.Location;
+import android.location.LocationRequest;
 import android.location.LocationResult;
 import android.location.provider.ProviderRequest;
 import android.platform.test.annotations.Presubmit;
@@ -218,4 +219,22 @@
         verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
         verify(mListener, after(75).times(1)).onReportLocation(any(LocationResult.class));
     }
+
+    @Test
+    public void testNoThrottle_highAccuracy() {
+        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(
+                50).setQuality(LocationRequest.QUALITY_HIGH_ACCURACY).build();
+
+        mProvider.getController().setRequest(request);
+        verify(mDelegate).onSetRequest(request);
+
+        LocationResult loc = createLocationResult("test_provider", mRandom);
+        mDelegateProvider.reportLocation(loc);
+        verify(mListener, times(1)).onReportLocation(loc);
+
+        mInjector.getDeviceStationaryHelper().setStationary(true);
+        mInjector.getDeviceIdleHelper().setIdle(true);
+        verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+        verify(mListener, after(75).times(1)).onReportLocation(any(LocationResult.class));
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index 0403c64..a65ef00 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -233,7 +233,7 @@
         Exception e = assertThrows(
                 SecurityException.class,
                 () -> mArchiveManager.requestArchive(PACKAGE, "different", mIntentSender,
-                        UserHandle.CURRENT, 0));
+                        UserHandle.CURRENT));
         assertThat(e).hasMessageThat().isEqualTo(
                 String.format(
                         "The UID %s of callerPackageName set by the caller doesn't match the "
@@ -250,7 +250,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
-                        UserHandle.CURRENT, 0));
+                        UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 String.format("Package %s not found.", PACKAGE));
@@ -260,8 +260,7 @@
     public void archiveApp_packageNotInstalledForUser() throws IntentSender.SendIntentException {
         mPackageSetting.modifyUserState(UserHandle.CURRENT.getIdentifier()).setInstalled(false);
 
-        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT,
-                0);
+        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
         rule.mocks().getHandler().flush();
 
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -291,7 +290,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
-                        UserHandle.CURRENT, 0));
+                        UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo("No installer found");
     }
@@ -305,7 +304,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
-                        UserHandle.CURRENT, 0));
+                        UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 "Installer does not support unarchival");
@@ -319,7 +318,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
-                        UserHandle.CURRENT, 0));
+                        UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 TextUtils.formatSimple("The app %s does not have a main activity.", PACKAGE));
@@ -331,8 +330,7 @@
         doThrow(e).when(mArchiveManager).storeIcon(eq(PACKAGE),
                 any(LauncherActivityInfo.class), eq(mUserId), anyInt(), anyInt());
 
-        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT,
-                0);
+        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
         rule.mocks().getHandler().flush();
 
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -355,7 +353,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
-                        UserHandle.CURRENT, 0));
+                        UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 TextUtils.formatSimple("The app %s is opted out of archiving.", PACKAGE));
@@ -363,14 +361,13 @@
 
     @Test
     public void archiveApp_withNoAdditionalFlags_success() {
-        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT,
-                0);
+        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
         rule.mocks().getHandler().flush();
 
         verify(mInstallerService).uninstall(
                 eq(new VersionedPackage(PACKAGE, PackageManager.VERSION_CODE_HIGHEST)),
                 eq(CALLER_PACKAGE), eq(DELETE_ARCHIVE | DELETE_KEEP_DATA), eq(mIntentSender),
-                eq(UserHandle.CURRENT.getIdentifier()), anyInt());
+                eq(UserHandle.CURRENT.getIdentifier()), anyInt(), anyInt());
 
         ArchiveState expectedArchiveState = createArchiveState();
         ArchiveState actualArchiveState = mPackageSetting.readUserState(
@@ -386,16 +383,15 @@
 
     @Test
     public void archiveApp_withAdditionalFlags_success() {
-        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT,
-                PackageManager.DELETE_SHOW_DIALOG);
+        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
         rule.mocks().getHandler().flush();
 
         verify(mInstallerService).uninstall(
                 eq(new VersionedPackage(PACKAGE, PackageManager.VERSION_CODE_HIGHEST)),
                 eq(CALLER_PACKAGE),
-                eq(DELETE_ARCHIVE | DELETE_KEEP_DATA | PackageManager.DELETE_SHOW_DIALOG),
+                eq(DELETE_ARCHIVE | DELETE_KEEP_DATA),
                 eq(mIntentSender),
-                eq(UserHandle.CURRENT.getIdentifier()), anyInt());
+                eq(UserHandle.CURRENT.getIdentifier()), anyInt(), anyInt());
 
         ArchiveState expectedArchiveState = createArchiveState();
         ArchiveState actualArchiveState = mPackageSetting.readUserState(
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index fd6aa0c..656bc71 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -19,9 +19,11 @@
 import static android.os.UserManager.DISALLOW_SMS;
 import static android.os.UserManager.DISALLOW_USER_SWITCH;
 import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
+import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -30,20 +32,27 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
 
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
+import android.app.KeyguardManager;
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
+import android.multiuser.Flags;
+import android.os.PowerManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.Pair;
@@ -52,6 +61,7 @@
 
 import androidx.test.annotation.UiThreadTest;
 
+import com.android.dx.mockito.inline.extended.MockedVoidMethod;
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.testing.ExtendedMockitoRule;
@@ -65,6 +75,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -115,8 +126,11 @@
             .spyStatic(LocalServices.class)
             .spyStatic(SystemProperties.class)
             .mockStatic(Settings.Global.class)
+            .mockStatic(Settings.Secure.class)
             .build();
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private final Object mPackagesLock = new Object();
     private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
             .getTargetContext();
@@ -133,6 +147,8 @@
     private @Mock StorageManager mStorageManager;
     private @Mock LockSettingsInternal mLockSettingsInternal;
     private @Mock PackageManagerInternal mPackageManagerInternal;
+    private @Mock KeyguardManager mKeyguardManager;
+    private @Mock PowerManager mPowerManager;
 
     /**
      * Reference to the {@link UserManagerService} being tested.
@@ -156,6 +172,8 @@
         when(mDeviceStorageMonitorInternal.isMemoryLow()).thenReturn(false);
         mockGetLocalService(DeviceStorageMonitorInternal.class, mDeviceStorageMonitorInternal);
         when(mSpiedContext.getSystemService(StorageManager.class)).thenReturn(mStorageManager);
+        when(mSpiedContext.getSystemService(KeyguardManager.class)).thenReturn(mKeyguardManager);
+        when(mSpiedContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
         mockGetLocalService(LockSettingsInternal.class, mLockSettingsInternal);
         mockGetLocalService(PackageManagerInternal.class, mPackageManagerInternal);
         doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
@@ -538,7 +556,7 @@
     @Test
     public void testCreateUserWithLongName_TruncatesName() {
         UserInfo user = mUms.createUserWithThrow(generateLongString(), USER_TYPE_FULL_SECONDARY, 0);
-        assertThat(user.name.length()).isEqualTo(500);
+        assertThat(user.name.length()).isEqualTo(UserManager.MAX_USER_NAME_LENGTH);
         UserInfo user1 = mUms.createUserWithThrow("Test", USER_TYPE_FULL_SECONDARY, 0);
         assertThat(user1.name.length()).isEqualTo(4);
     }
@@ -550,6 +568,160 @@
         assertTrue(hasRestrictionsInUserXMLFile(user.id));
     }
 
+    @Test
+    public void testAutoLockPrivateProfile() {
+        UserManagerService mSpiedUms = spy(mUms);
+        UserInfo privateProfileUser =
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                        USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+        Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync(
+                eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), any(),
+                any());
+
+        mSpiedUms.autoLockPrivateSpace();
+
+        Mockito.verify(mSpiedUms).setQuietModeEnabledAsync(
+                eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true),
+                any(), any());
+    }
+
+    @Test
+    public void testAutoLockOnDeviceLockForPrivateProfile() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+        UserManagerService mSpiedUms = spy(mUms);
+        UserInfo privateProfileUser =
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+        mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
+        Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync(
+                eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), any(),
+                any());
+
+        mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(true);
+
+        Mockito.verify(mSpiedUms).setQuietModeEnabledAsync(
+                eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true),
+                        any(), any());
+    }
+
+    @Test
+    public void testAutoLockOnDeviceLockForPrivateProfile_keyguardUnlocked() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+        UserManagerService mSpiedUms = spy(mUms);
+        UserInfo privateProfileUser =
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+        mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
+
+        mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(false);
+
+        // Verify that no operation to disable quiet mode is not called
+        Mockito.verify(mSpiedUms, never()).setQuietModeEnabledAsync(
+                eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true),
+                any(), any());
+    }
+
+    @Test
+    public void testAutoLockOnDeviceLockForPrivateProfile_flagDisabled() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+        UserManagerService mSpiedUms = spy(mUms);
+        UserInfo privateProfileUser =
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+
+        mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(true);
+
+        // Verify that no auto-lock operations take place
+        verify((MockedVoidMethod) () -> Settings.Secure.getInt(any(),
+                eq(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK), anyInt()), never());
+        Mockito.verify(mSpiedUms, never()).setQuietModeEnabledAsync(
+                eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true),
+                any(), any());
+    }
+
+    @Test
+    public void testAutoLockAfterInactityForPrivateProfile() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+        UserManagerService mSpiedUms = spy(mUms);
+        mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
+        when(mPowerManager.isInteractive()).thenReturn(false);
+
+        UserInfo privateProfileUser =
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                        USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+        Mockito.doNothing().when(mSpiedUms).scheduleMessageToAutoLockPrivateSpace(
+                eq(privateProfileUser.getUserHandle().getIdentifier()), any(),
+                anyLong());
+
+
+        mSpiedUms.maybeScheduleMessageToAutoLockPrivateSpace();
+
+        Mockito.verify(mSpiedUms).scheduleMessageToAutoLockPrivateSpace(
+                eq(privateProfileUser.getUserHandle().getIdentifier()), any(), anyLong());
+    }
+
+    @Test
+    public void testSetOrUpdateAutoLockPreference_noPrivateProfile() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+
+        mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
+                Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
+
+        Mockito.verify(mSpiedContext, never()).registerReceiver(any(), any(), any(), any());
+        Mockito.verify(mSpiedContext, never()).unregisterReceiver(any());
+        Mockito.verify(mKeyguardManager, never()).removeKeyguardLockedStateListener((any()));
+        Mockito.verify(mKeyguardManager, never()).addKeyguardLockedStateListener(any(), any());
+    }
+
+    @Test
+    public void testSetOrUpdateAutoLockPreference() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+        mUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                        USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+
+        // Set the preference to auto lock on device lock
+        mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
+                Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
+
+        // Verify that keyguard state listener was added
+        Mockito.verify(mKeyguardManager).addKeyguardLockedStateListener(any(), any());
+        //Verity that keyguard state listener was not removed
+        Mockito.verify(mKeyguardManager, never()).removeKeyguardLockedStateListener(any());
+        // Broadcasts are already unregistered when UserManagerService starts and the flag
+        // isDeviceInactivityBroadcastReceiverRegistered is false
+        Mockito.verify(mSpiedContext, never()).registerReceiver(any(), any(), any(), any());
+        Mockito.verify(mSpiedContext, never()).unregisterReceiver(any());
+
+        Mockito.clearInvocations(mKeyguardManager);
+        Mockito.clearInvocations(mSpiedContext);
+
+        // Now set the preference to auto-lock on inactivity
+        mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
+                Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
+
+        // Verify that inactivity broadcasts are registered
+        Mockito.verify(mSpiedContext, times(2)).registerReceiver(any(), any(), any(), any());
+        // Verify that keyguard state listener is removed
+        Mockito.verify(mKeyguardManager).removeKeyguardLockedStateListener(any());
+        // Verify that all other operations don't take place
+        Mockito.verify(mSpiedContext, never()).unregisterReceiver(any());
+        Mockito.verify(mKeyguardManager, never()).addKeyguardLockedStateListener(any(), any());
+
+        Mockito.clearInvocations(mKeyguardManager);
+        Mockito.clearInvocations(mSpiedContext);
+
+        // Finally, set the preference to don't auto-lock
+        mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
+                Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER);
+
+        // Verify that inactivity broadcasts are unregistered and keyguard listener was removed
+        Mockito.verify(mSpiedContext).unregisterReceiver(any());
+        Mockito.verify(mKeyguardManager).removeKeyguardLockedStateListener(any());
+        // Verify that no broadcasts were registered and no listeners were added
+        Mockito.verify(mSpiedContext, never()).registerReceiver(any(), any(), any(), any());
+        Mockito.verify(mKeyguardManager, never()).addKeyguardLockedStateListener(any(), any());
+    }
+
     /**
      * Returns true if the user's XML file has Default restrictions
      * @param userId Id of the user.
@@ -632,6 +804,12 @@
                 SystemProperties.getBoolean(eq("fw.show_multiuserui"), anyBoolean()));
     }
 
+    private void mockAutoLockForPrivateSpace(int val) {
+        doReturn(val).when(() ->
+                Settings.Secure.getIntForUser(any(), eq(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK),
+                        anyInt(), anyInt()));
+    }
+
     private void mockCurrentUser(@UserIdInt int userId) {
         mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/selinux/RateLimiterTest.java b/services/tests/mockingservicestests/src/com/android/server/selinux/RateLimiterTest.java
new file mode 100644
index 0000000..01c7fbe
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/selinux/RateLimiterTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.selinux;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.os.Clock;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class RateLimiterTest {
+
+    private final MockClock mMockClock = new MockClock();
+
+    @Test
+    public void testRateLimiter_1QPS() {
+        RateLimiter rateLimiter = new RateLimiter(mMockClock, Duration.ofSeconds(1));
+
+        // First acquire is granted.
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        // Next acquire is negated because it's too soon.
+        assertThat(rateLimiter.tryAcquire()).isFalse();
+        // Wait >=1 seconds.
+        mMockClock.currentTimeMillis += Duration.ofSeconds(1).toMillis();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+    }
+
+    @Test
+    public void testRateLimiter_3QPS() {
+        RateLimiter rateLimiter =
+                new RateLimiter(
+                        mMockClock,
+                        Duration.ofSeconds(1).dividedBy(3).truncatedTo(ChronoUnit.MILLIS));
+
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        mMockClock.currentTimeMillis += Duration.ofSeconds(1).dividedBy(2).toMillis();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        mMockClock.currentTimeMillis += Duration.ofSeconds(1).dividedBy(3).toMillis();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        mMockClock.currentTimeMillis += Duration.ofSeconds(1).dividedBy(4).toMillis();
+        assertThat(rateLimiter.tryAcquire()).isFalse();
+    }
+
+    @Test
+    public void testRateLimiter_infiniteQPS() {
+        RateLimiter rateLimiter = new RateLimiter(mMockClock, Duration.ofMillis(0));
+
+        // so many permits.
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+
+        mMockClock.currentTimeMillis += Duration.ofSeconds(10).toMillis();
+        // still so many permits.
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+
+        mMockClock.currentTimeMillis += Duration.ofDays(-10).toMillis();
+        // only going backwards in time you will stop the permits.
+        assertThat(rateLimiter.tryAcquire()).isFalse();
+        assertThat(rateLimiter.tryAcquire()).isFalse();
+        assertThat(rateLimiter.tryAcquire()).isFalse();
+    }
+
+    @Test
+    public void testRateLimiter_negativeQPS() {
+        RateLimiter rateLimiter = new RateLimiter(mMockClock, Duration.ofMillis(-10));
+
+        // Negative QPS is effectively turning of the rate limiter.
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        mMockClock.currentTimeMillis += Duration.ofSeconds(1000).toMillis();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+        assertThat(rateLimiter.tryAcquire()).isTrue();
+    }
+
+    private static final class MockClock extends Clock {
+
+        public long currentTimeMillis = 0;
+
+        @Override
+        public long currentTimeMillis() {
+            return currentTimeMillis;
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java b/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
new file mode 100644
index 0000000..b36c9bd
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.selinux;
+
+import static com.android.server.selinux.SelinuxAuditLogBuilder.PATH_MATCHER;
+import static com.android.server.selinux.SelinuxAuditLogBuilder.SCONTEXT_MATCHER;
+import static com.android.server.selinux.SelinuxAuditLogBuilder.TCONTEXT_MATCHER;
+import static com.android.server.selinux.SelinuxAuditLogBuilder.toCategories;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.selinux.SelinuxAuditLogBuilder.SelinuxAuditLog;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SelinuxAuditLogsBuilderTest {
+
+    private final SelinuxAuditLogBuilder mAuditLogBuilder = new SelinuxAuditLogBuilder();
+
+    @Test
+    public void testMatcher_scontext() {
+        assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0").matches()).isTrue();
+        assertThat(SCONTEXT_MATCHER.group("stype")).isEqualTo("sdk_sandbox_audit");
+        assertThat(SCONTEXT_MATCHER.group("scategories")).isNull();
+
+        assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:c123,c456").matches()).isTrue();
+        assertThat(SCONTEXT_MATCHER.group("stype")).isEqualTo("sdk_sandbox_audit");
+        assertThat(toCategories(SCONTEXT_MATCHER.group("scategories")))
+                .isEqualTo(new int[] {123, 456});
+
+        assertThat(SCONTEXT_MATCHER.reset("u:r:not_sdk_sandbox:s0").matches()).isFalse();
+        assertThat(SCONTEXT_MATCHER.reset("u:object_r:sdk_sandbox_audit:s0").matches()).isFalse();
+        assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:p123").matches()).isFalse();
+    }
+
+    @Test
+    public void testMatcher_tcontext() {
+        assertThat(TCONTEXT_MATCHER.reset("u:object_r:target_type:s0").matches()).isTrue();
+        assertThat(TCONTEXT_MATCHER.group("ttype")).isEqualTo("target_type");
+        assertThat(TCONTEXT_MATCHER.group("tcategories")).isNull();
+
+        assertThat(TCONTEXT_MATCHER.reset("u:object_r:target_type2:s0:c666").matches()).isTrue();
+        assertThat(TCONTEXT_MATCHER.group("ttype")).isEqualTo("target_type2");
+        assertThat(toCategories(TCONTEXT_MATCHER.group("tcategories"))).isEqualTo(new int[] {666});
+
+        assertThat(TCONTEXT_MATCHER.reset("u:r:target_type:s0").matches()).isFalse();
+        assertThat(TCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:x456").matches()).isFalse();
+    }
+
+    @Test
+    public void testMatcher_path() {
+        assertThat(PATH_MATCHER.reset("\"/data\"").matches()).isTrue();
+        assertThat(PATH_MATCHER.group("path")).isEqualTo("/data");
+        assertThat(PATH_MATCHER.reset("\"/data/local\"").matches()).isTrue();
+        assertThat(PATH_MATCHER.group("path")).isEqualTo("/data/local");
+        assertThat(PATH_MATCHER.reset("\"/data/local/tmp\"").matches()).isTrue();
+        assertThat(PATH_MATCHER.group("path")).isEqualTo("/data/local");
+
+        assertThat(PATH_MATCHER.reset("\"/data/local").matches()).isFalse();
+        assertThat(PATH_MATCHER.reset("\"_data_local\"").matches()).isFalse();
+    }
+
+    @Test
+    public void testSelinuxAuditLogsBuilder_noOptionals() {
+        mAuditLogBuilder.reset(
+                "granted { p } scontext=u:r:sdk_sandbox_audit:s0 tcontext=u:object_r:t:s0"
+                        + " tclass=c");
+        assertAuditLog(
+                mAuditLogBuilder.build(), true, new String[] {"p"}, "sdk_sandbox_audit", "t", "c");
+
+        mAuditLogBuilder.reset(
+                "tclass=c2 granted { p2 } tcontext=u:object_r:t2:s0"
+                        + " scontext=u:r:sdk_sandbox_audit:s0");
+        assertAuditLog(
+                mAuditLogBuilder.build(),
+                true,
+                new String[] {"p2"},
+                "sdk_sandbox_audit",
+                "t2",
+                "c2");
+    }
+
+    @Test
+    public void testSelinuxAuditLogsBuilder_withCategories() {
+        mAuditLogBuilder.reset(
+                "granted { p } scontext=u:r:sdk_sandbox_audit:s0:c123"
+                        + " tcontext=u:object_r:t:s0:c456,c666 tclass=c");
+        assertAuditLog(
+                mAuditLogBuilder.build(),
+                true,
+                new String[] {"p"},
+                "sdk_sandbox_audit",
+                new int[] {123},
+                "t",
+                new int[] {456, 666},
+                "c",
+                null,
+                false);
+    }
+
+    @Test
+    public void testSelinuxAuditLogsBuilder_withPath() {
+        mAuditLogBuilder.reset(
+                "granted { p } scontext=u:r:sdk_sandbox_audit:s0 path=\"/very/long/path\""
+                        + " tcontext=u:object_r:t:s0 tclass=c");
+        assertAuditLog(
+                mAuditLogBuilder.build(),
+                true,
+                new String[] {"p"},
+                "sdk_sandbox_audit",
+                null,
+                "t",
+                null,
+                "c",
+                "/very/long",
+                false);
+    }
+
+    @Test
+    public void testSelinuxAuditLogsBuilder_withPermissive() {
+        mAuditLogBuilder.reset(
+                "granted { p } scontext=u:r:sdk_sandbox_audit:s0 permissive=0"
+                        + " tcontext=u:object_r:t:s0 tclass=c");
+        assertAuditLog(
+                mAuditLogBuilder.build(),
+                true,
+                new String[] {"p"},
+                "sdk_sandbox_audit",
+                null,
+                "t",
+                null,
+                "c",
+                null,
+                false);
+
+        mAuditLogBuilder.reset(
+                "granted { p } scontext=u:r:sdk_sandbox_audit:s0 tcontext=u:object_r:t:s0 tclass=c"
+                        + " permissive=1");
+        assertAuditLog(
+                mAuditLogBuilder.build(),
+                true,
+                new String[] {"p"},
+                "sdk_sandbox_audit",
+                null,
+                "t",
+                null,
+                "c",
+                null,
+                true);
+    }
+
+    private void assertAuditLog(
+            SelinuxAuditLog auditLog,
+            boolean granted,
+            String[] permissions,
+            String sType,
+            String tType,
+            String tClass) {
+        assertAuditLog(
+                auditLog, granted, permissions, sType, null, tType, null, tClass, null, false);
+    }
+
+    private void assertAuditLog(
+            SelinuxAuditLog auditLog,
+            boolean granted,
+            String[] permissions,
+            String sType,
+            int[] sCategories,
+            String tType,
+            int[] tCategories,
+            String tClass,
+            String path,
+            boolean permissive) {
+        assertThat(auditLog).isNotNull();
+        assertThat(auditLog.mGranted).isEqualTo(granted);
+        assertThat(auditLog.mPermissions).isEqualTo(permissions);
+        assertThat(auditLog.mSType).isEqualTo(sType);
+        assertThat(auditLog.mSCategories).isEqualTo(sCategories);
+        assertThat(auditLog.mTType).isEqualTo(tType);
+        assertThat(auditLog.mTCategories).isEqualTo(tCategories);
+        assertThat(auditLog.mTClass).isEqualTo(tClass);
+        assertThat(auditLog.mPath).isEqualTo(path);
+        assertThat(auditLog.mPermissive).isEqualTo(permissive);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java b/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
new file mode 100644
index 0000000..9758ea5
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
@@ -0,0 +1,644 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.selinux;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+
+import android.util.EventLog;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.os.Clock;
+import com.android.internal.util.FrameworkStatsLog;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoSession;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+@RunWith(AndroidJUnit4.class)
+public class SelinuxAuditLogsCollectorTest {
+
+    // Fake tag to use for testing
+    private static final int ANSWER_TAG = 42;
+
+    private final MockClock mClock = new MockClock();
+
+    private final SelinuxAuditLogsCollector mSelinuxAutidLogsCollector =
+            // Ignore rate limiting for tests
+            new SelinuxAuditLogsCollector(
+                    new RateLimiter(mClock, /* window= */ Duration.ofMillis(0)),
+                    new QuotaLimiter(
+                            mClock, /* windowSize= */ Duration.ofHours(1), /* maxPermits= */ 5));
+
+    private MockitoSession mMockitoSession;
+
+    @Before
+    public void setUp() {
+        // move the clock forward for the limiters.
+        mClock.currentTimeMillis += Duration.ofHours(1).toMillis();
+        // Ignore what was written in the event logs by previous tests.
+        mSelinuxAutidLogsCollector.mLastWrite = Instant.now();
+
+        mMockitoSession =
+                mockitoSession().initMocks(this).mockStatic(FrameworkStatsLog.class).startMocking();
+    }
+
+    @After
+    public void tearDown() {
+        mMockitoSession.finishMocking();
+    }
+
+    @Test
+    public void testWriteSdkSandboxAuditLogs() {
+        writeTestLog("granted", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm1", "sdk_sandbox_audit", "ttype1", "tclass1");
+
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                true,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype",
+                                null,
+                                "tclass",
+                                null,
+                                false));
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm1"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype1",
+                                null,
+                                "tclass1",
+                                null,
+                                false));
+    }
+
+    @Test
+    public void testWriteSdkSandboxAuditLogs_multiplePerms() {
+        writeTestLog("denied", "perm1 perm2", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm3 perm4", "sdk_sandbox_audit", "ttype", "tclass");
+
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm1", "perm2"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype",
+                                null,
+                                "tclass",
+                                null,
+                                false));
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm3", "perm4"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype",
+                                null,
+                                "tclass",
+                                null,
+                                false));
+    }
+
+    @Test
+    public void testWriteSdkSandboxAuditLogs_withPaths() {
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/good/path");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/very/long/path");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/short_path");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "not_a_path");
+
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype",
+                                null,
+                                "tclass",
+                                "/good/path",
+                                false));
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype",
+                                null,
+                                "tclass",
+                                "/very/long",
+                                false));
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype",
+                                null,
+                                "tclass",
+                                "/short_path",
+                                false));
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype",
+                                null,
+                                "tclass",
+                                null,
+                                false));
+    }
+
+    @Test
+    public void testWriteSdkSandboxAuditLogs_withCategories() {
+        writeTestLog(
+                "denied", "perm", "sdk_sandbox_audit", new int[] {123}, "ttype", null, "tclass");
+        writeTestLog(
+                "denied",
+                "perm",
+                "sdk_sandbox_audit",
+                new int[] {123, 456},
+                "ttype",
+                null,
+                "tclass");
+        writeTestLog(
+                "denied", "perm", "sdk_sandbox_audit", null, "ttype", new int[] {666}, "tclass");
+        writeTestLog(
+                "denied",
+                "perm",
+                "sdk_sandbox_audit",
+                new int[] {123, 456},
+                "ttype",
+                new int[] {666, 777},
+                "tclass");
+
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                new int[] {123},
+                                "ttype",
+                                null,
+                                "tclass",
+                                null,
+                                false));
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                new int[] {123, 456},
+                                "ttype",
+                                null,
+                                "tclass",
+                                null,
+                                false));
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype",
+                                new int[] {666},
+                                "tclass",
+                                null,
+                                false));
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                new int[] {123, 456},
+                                "ttype",
+                                new int[] {666, 777},
+                                "tclass",
+                                null,
+                                false));
+    }
+
+    @Test
+    public void testWriteSdkSandboxAuditLogs_withPathAndCategories() {
+        writeTestLog(
+                "denied",
+                "perm",
+                "sdk_sandbox_audit",
+                new int[] {123},
+                "ttype",
+                new int[] {666},
+                "tclass",
+                "/a/path");
+
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                new int[] {123},
+                                "ttype",
+                                new int[] {666},
+                                "tclass",
+                                "/a/path",
+                                false));
+    }
+
+    @Test
+    public void testWriteSdkSandboxAuditLogs_permissive() {
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", true);
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", false);
+
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype",
+                                null,
+                                "tclass",
+                                null,
+                                false),
+                times(2));
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.SELINUX_AUDIT_LOG,
+                                false,
+                                new String[] {"perm"},
+                                "sdk_sandbox_audit",
+                                null,
+                                "ttype",
+                                null,
+                                "tclass",
+                                null,
+                                true));
+    }
+
+    @Test
+    public void testNotWriteAuditLogs_notSdkSandbox() {
+        writeTestLog("denied", "perm", "stype", "ttype", "tclass");
+
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                anyInt(),
+                                anyBoolean(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyBoolean()),
+                never());
+    }
+
+    @Test
+    public void testWriteSdkSandboxAuditLogs_upToQuota() {
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        // These are not pushed.
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                anyInt(),
+                                anyBoolean(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyBoolean()),
+                times(5));
+    }
+
+    @Test
+    public void testWriteSdkSandboxAuditLogs_resetQuota() {
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                anyInt(),
+                                anyBoolean(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyBoolean()),
+                times(5));
+
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        // move the clock forward to reset the quota limiter.
+        mClock.currentTimeMillis += Duration.ofHours(1).toMillis();
+        done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                anyInt(),
+                                anyBoolean(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyBoolean()),
+                times(10));
+    }
+
+    @Test
+    public void testNotWriteAuditLogs_stopRequested() {
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        // These are not pushed.
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+
+        mSelinuxAutidLogsCollector.mStopRequested.set(true);
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+        assertThat(done).isFalse();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                anyInt(),
+                                anyBoolean(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyBoolean()),
+                never());
+
+        mSelinuxAutidLogsCollector.mStopRequested.set(false);
+        done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+        assertThat(done).isTrue();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                anyInt(),
+                                anyBoolean(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyBoolean()),
+                times(5));
+    }
+
+    @Test
+    public void testAuditLogs_resumeJobDoesNotExceedLimit() {
+        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        mSelinuxAutidLogsCollector.mStopRequested.set(true);
+
+        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+        assertThat(done).isFalse();
+        verify(
+                () ->
+                        FrameworkStatsLog.write(
+                                anyInt(),
+                                anyBoolean(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyString(),
+                                any(),
+                                anyBoolean()),
+                never());
+    }
+
+    private static void writeTestLog(
+            String granted, String permissions, String sType, String tType, String tClass) {
+        EventLog.writeEvent(
+                ANSWER_TAG,
+                String.format(
+                        "avc: %s { %s } scontext=u:r:%s:s0 tcontext=u:object_r:%s:s0 tclass=%s",
+                        granted, permissions, sType, tType, tClass));
+    }
+
+    private static void writeTestLog(
+            String granted,
+            String permissions,
+            String sType,
+            String tType,
+            String tClass,
+            String path) {
+        EventLog.writeEvent(
+                ANSWER_TAG,
+                String.format(
+                        "avc: %s { %s } path=\"%s\" scontext=u:r:%s:s0 tcontext=u:object_r:%s:s0"
+                                + " tclass=%s",
+                        granted, permissions, path, sType, tType, tClass));
+    }
+
+    private static void writeTestLog(
+            String granted,
+            String permissions,
+            String sType,
+            int[] sCategories,
+            String tType,
+            int[] tCategories,
+            String tClass) {
+        EventLog.writeEvent(
+                ANSWER_TAG,
+                String.format(
+                        "avc: %s { %s } scontext=u:r:%s:s0%s tcontext=u:object_r:%s:s0%s tclass=%s",
+                        granted,
+                        permissions,
+                        sType,
+                        toCategoriesString(sCategories),
+                        tType,
+                        toCategoriesString(tCategories),
+                        tClass));
+    }
+
+    private static void writeTestLog(
+            String granted,
+            String permissions,
+            String sType,
+            int[] sCategories,
+            String tType,
+            int[] tCategories,
+            String tClass,
+            String path) {
+        EventLog.writeEvent(
+                ANSWER_TAG,
+                String.format(
+                        "avc: %s { %s } path=\"%s\" scontext=u:r:%s:s0%s"
+                                + " tcontext=u:object_r:%s:s0%s tclass=%s",
+                        granted,
+                        permissions,
+                        path,
+                        sType,
+                        toCategoriesString(sCategories),
+                        tType,
+                        toCategoriesString(tCategories),
+                        tClass));
+    }
+
+    private static void writeTestLog(
+            String granted,
+            String permissions,
+            String sType,
+            String tType,
+            String tClass,
+            boolean permissive) {
+        EventLog.writeEvent(
+                ANSWER_TAG,
+                String.format(
+                        "avc: %s { %s } scontext=u:r:%s:s0 tcontext=u:object_r:%s:s0 tclass=%s"
+                                + " permissive=%s",
+                        granted, permissions, sType, tType, tClass, permissive ? "1" : "0"));
+    }
+
+    private static String toCategoriesString(int[] categories) {
+        return (categories == null || categories.length == 0)
+                ? ""
+                : ":c"
+                        + Arrays.stream(categories)
+                                .mapToObj(String::valueOf)
+                                .collect(Collectors.joining(",c"));
+    }
+
+    private static final class MockClock extends Clock {
+
+        public long currentTimeMillis = 0;
+
+        @Override
+        public long currentTimeMillis() {
+            return currentTimeMillis;
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index 97e94e3..b415682 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -47,7 +47,10 @@
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricManager;
-import android.net.Uri;
+import android.hardware.biometrics.SensorProperties;
+import android.hardware.face.FaceManager;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.fingerprint.FingerprintManager;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -70,6 +73,7 @@
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker;
 import com.android.modules.utils.testing.ExtendedMockitoRule;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -106,6 +110,8 @@
 
     private static final String URI_SCHEME_PACKAGE = "package";
     private static final int TEST_USER_ID = 50;
+    private static final UserInfo TEST_USER =
+            new UserInfo(TEST_USER_ID, "user", UserInfo.FLAG_FULL);
     private static final int PARENT_USER_ID = 60;
     private static final int PROFILE_USER_ID = 70;
     private static final long[] PARENT_BIOMETRIC_SIDS = new long[] { 600L, 601L };
@@ -118,6 +124,8 @@
     private @Mock ActivityManager mActivityManager;
     private @Mock BiometricManager mBiometricManager;
     private @Mock DevicePolicyManager mDevicePolicyManager;
+    private @Mock FaceManager mFaceManager;
+    private @Mock FingerprintManager mFingerprintManager;
     private @Mock IKeystoreAuthorization mKeystoreAuthorization;
     private @Mock LockPatternUtils mLockPatternUtils;
     private @Mock PackageManager mPackageManager;
@@ -134,6 +142,9 @@
         when(mActivityManager.isUserRunning(TEST_USER_ID)).thenReturn(true);
         doReturn(mock(IActivityManager.class)).when(() -> ActivityManager.getService());
 
+        when(mFaceManager.getSensorProperties()).thenReturn(List.of());
+        when(mFingerprintManager.getSensorProperties()).thenReturn(List.of());
+
         doReturn(mKeystoreAuthorization).when(() -> Authorization.getService());
 
         when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
@@ -162,13 +173,16 @@
         when(mPackageManager.checkPermission(any(), any())).thenReturn(
                 PackageManager.PERMISSION_GRANTED);
 
-        when(mUserManager.getAliveUsers()).thenReturn(
-                List.of(new UserInfo(TEST_USER_ID, "user", UserInfo.FLAG_FULL)));
+        when(mUserManager.getAliveUsers()).thenReturn(List.of(TEST_USER));
+        when(mUserManager.getEnabledProfileIds(TEST_USER_ID)).thenReturn(new int[0]);
+        when(mUserManager.getUserInfo(TEST_USER_ID)).thenReturn(TEST_USER);
 
         when(mWindowManager.isKeyguardLocked()).thenReturn(true);
 
         mMockContext.addMockSystemService(ActivityManager.class, mActivityManager);
         mMockContext.addMockSystemService(BiometricManager.class, mBiometricManager);
+        mMockContext.addMockSystemService(FaceManager.class, mFaceManager);
+        mMockContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
         mMockContext.setMockPackageManager(mPackageManager);
         mMockContext.addMockSystemService(UserManager.class, mUserManager);
         doReturn(mWindowManager).when(() -> WindowManagerGlobal.getWindowManagerService());
@@ -280,7 +294,7 @@
                 "com.android/.SystemTrustAgent");
         addTrustAgent(newAgentComponentName, /* isSystemApp= */ true);
 
-        mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
+        notifyPackageChanged(newAgentComponentName);
 
         assertThat(mEnabledTrustAgents).containsExactly(newAgentComponentName);
         assertThat(mKnownTrustAgents).containsExactly(newAgentComponentName);
@@ -299,7 +313,7 @@
                 "com.android/.SystemTrustAgent");
         addTrustAgent(newAgentComponentName, /* isSystemApp= */ true);
 
-        mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
+        notifyPackageChanged(newAgentComponentName);
 
         assertThat(mEnabledTrustAgents).containsExactly(defaultTrustAgent);
         assertThat(mKnownTrustAgents).containsExactly(defaultTrustAgent, newAgentComponentName);
@@ -312,7 +326,7 @@
                 "com.user/.UserTrustAgent");
         addTrustAgent(newAgentComponentName, /* isSystemApp= */ false);
 
-        mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
+        notifyPackageChanged(newAgentComponentName);
 
         assertThat(mEnabledTrustAgents).isEmpty();
         assertThat(mKnownTrustAgents).containsExactly(newAgentComponentName);
@@ -330,7 +344,7 @@
         // Simulate user turning off systemTrustAgent2
         mLockPatternUtils.setEnabledTrustAgents(List.of(systemTrustAgent1), TEST_USER_ID);
 
-        mMockContext.sendPackageChangedBroadcast(systemTrustAgent2);
+        notifyPackageChanged(systemTrustAgent2);
 
         assertThat(mEnabledTrustAgents).containsExactly(systemTrustAgent1);
     }
@@ -363,9 +377,9 @@
         when(mWindowManager.isKeyguardLocked()).thenReturn(true);
         mTrustManager.reportKeyguardShowingChanged();
         verify(mKeystoreAuthorization)
-                .onDeviceLocked(eq(PARENT_USER_ID), eq(PARENT_BIOMETRIC_SIDS));
+                .onDeviceLocked(eq(PARENT_USER_ID), eq(PARENT_BIOMETRIC_SIDS), eq(false));
         verify(mKeystoreAuthorization)
-                .onDeviceLocked(eq(PROFILE_USER_ID), eq(PARENT_BIOMETRIC_SIDS));
+                .onDeviceLocked(eq(PROFILE_USER_ID), eq(PARENT_BIOMETRIC_SIDS), eq(false));
     }
 
     // Tests that when the device is locked for a managed profile with a *separate* challenge, the
@@ -382,7 +396,188 @@
 
         mTrustManager.setDeviceLockedForUser(PROFILE_USER_ID, true);
         verify(mKeystoreAuthorization)
-                .onDeviceLocked(eq(PROFILE_USER_ID), eq(PROFILE_BIOMETRIC_SIDS));
+                .onDeviceLocked(eq(PROFILE_USER_ID), eq(PROFILE_BIOMETRIC_SIDS), eq(false));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockEnabled_whenWeakFingerprintIsSetupAndAllowed()
+            throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFingerprint(SensorProperties.STRENGTH_WEAK);
+        verifyWeakUnlockEnabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockEnabled_whenWeakFaceIsSetupAndAllowed() throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFace(SensorProperties.STRENGTH_WEAK);
+        verifyWeakUnlockEnabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockEnabled_whenConvenienceFingerprintIsSetupAndAllowed()
+            throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFingerprint(SensorProperties.STRENGTH_CONVENIENCE);
+        verifyWeakUnlockEnabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockEnabled_whenConvenienceFaceIsSetupAndAllowed()
+            throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFace(SensorProperties.STRENGTH_CONVENIENCE);
+        verifyWeakUnlockEnabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenStrongAuthRequired() throws Exception {
+        setupStrongAuthTracker(StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, true);
+        setupFace(SensorProperties.STRENGTH_WEAK);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenNonStrongBiometricNotAllowed() throws Exception {
+        setupStrongAuthTracker(StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED,
+                /* isNonStrongBiometricAllowed= */ false);
+        setupFace(SensorProperties.STRENGTH_WEAK);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenWeakFingerprintSensorIsPresentButNotEnrolled()
+            throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFingerprint(SensorProperties.STRENGTH_WEAK, /* enrolled= */ false);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenWeakFaceSensorIsPresentButNotEnrolled()
+            throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFace(SensorProperties.STRENGTH_WEAK, /* enrolled= */ false);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void
+            testKeystoreWeakUnlockDisabled_whenWeakFingerprintIsSetupButForbiddenByDevicePolicy()
+            throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFingerprint(SensorProperties.STRENGTH_WEAK);
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, TEST_USER_ID))
+                .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenWeakFaceIsSetupButForbiddenByDevicePolicy()
+            throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFace(SensorProperties.STRENGTH_WEAK);
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, TEST_USER_ID))
+                .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FACE);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenOnlyStrongFingerprintIsSetup() throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFingerprint(SensorProperties.STRENGTH_STRONG);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenOnlyStrongFaceIsSetup() throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFace(SensorProperties.STRENGTH_STRONG);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenNoBiometricsAreSetup() throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        verifyWeakUnlockDisabled();
+    }
+
+    private void setupStrongAuthTrackerToAllowEverything() throws Exception {
+        setupStrongAuthTracker(StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED, true);
+    }
+
+    private void setupStrongAuthTracker(int strongAuthFlags, boolean isNonStrongBiometricAllowed)
+            throws Exception {
+        bootService();
+        mService.onUserSwitching(null, new SystemService.TargetUser(TEST_USER));
+
+        ArgumentCaptor<StrongAuthTracker> strongAuthTracker =
+                ArgumentCaptor.forClass(StrongAuthTracker.class);
+        verify(mLockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture());
+        strongAuthTracker.getValue().getStub().onStrongAuthRequiredChanged(
+                strongAuthFlags, TEST_USER_ID);
+        strongAuthTracker.getValue().getStub().onIsNonStrongBiometricAllowedChanged(
+                isNonStrongBiometricAllowed, TEST_USER_ID);
+        mService.waitForIdle();
+    }
+
+    private void setupFingerprint(int strength) {
+        setupFingerprint(strength, /* enrolled= */ true);
+    }
+
+    private void setupFingerprint(int strength, boolean enrolled) {
+        int sensorId = 100;
+        List<SensorProperties.ComponentInfo> componentInfo = List.of();
+        SensorProperties sensor = new SensorProperties(sensorId, strength, componentInfo);
+        when(mFingerprintManager.getSensorProperties()).thenReturn(List.of(sensor));
+        when(mFingerprintManager.hasEnrolledTemplates(TEST_USER_ID)).thenReturn(enrolled);
+    }
+
+    private void setupFace(int strength) {
+        setupFace(strength, /* enrolled= */ true);
+    }
+
+    private void setupFace(int strength, boolean enrolled) {
+        int sensorId = 100;
+        List<SensorProperties.ComponentInfo> componentInfo = List.of();
+        FaceSensorProperties sensor = new FaceSensorProperties(
+                sensorId, strength, componentInfo, FaceSensorProperties.TYPE_RGB);
+        when(mFaceManager.getSensorProperties()).thenReturn(List.of(sensor));
+        when(mFaceManager.hasEnrolledTemplates(TEST_USER_ID)).thenReturn(enrolled);
+    }
+
+    private void verifyWeakUnlockEnabled() throws Exception {
+        verifyWeakUnlockValue(true);
+    }
+
+    private void verifyWeakUnlockDisabled() throws Exception {
+        verifyWeakUnlockValue(false);
+    }
+
+    // Simulates a device unlock and a device lock, then verifies that the expected
+    // weakUnlockEnabled flag was passed to Keystore's onDeviceLocked method.
+    private void verifyWeakUnlockValue(boolean expectedWeakUnlockEnabled) throws Exception {
+        when(mWindowManager.isKeyguardLocked()).thenReturn(false);
+        mTrustManager.reportKeyguardShowingChanged();
+        verify(mKeystoreAuthorization).onDeviceUnlocked(TEST_USER_ID, null);
+
+        when(mWindowManager.isKeyguardLocked()).thenReturn(true);
+        mTrustManager.reportKeyguardShowingChanged();
+        verify(mKeystoreAuthorization).onDeviceLocked(eq(TEST_USER_ID), any(),
+                eq(expectedWeakUnlockEnabled));
     }
 
     private void setupMocksForProfile(boolean unifiedChallenge) {
@@ -440,11 +635,16 @@
                 permission, PackageManager.PERMISSION_GRANTED);
     }
 
+    private void notifyPackageChanged(ComponentName changedComponent) {
+        mService.mPackageMonitor.onPackageChanged(
+                changedComponent.getPackageName(),
+                UserHandle.of(TEST_USER_ID).getUid(1234),
+                new String[] { changedComponent.getClassName() });
+    }
+
     /** A mock Context that allows the test process to send protected broadcasts. */
     private static final class MockContext extends TestableContext {
 
-        private final ArrayList<BroadcastReceiver> mPackageChangedBroadcastReceivers =
-                new ArrayList<>();
         private final ArrayList<BroadcastReceiver> mUserStartedBroadcastReceivers =
                 new ArrayList<>();
 
@@ -458,9 +658,6 @@
                 UserHandle user, IntentFilter filter, @Nullable String broadcastPermission,
                 @Nullable Handler scheduler) {
 
-            if (filter.hasAction(Intent.ACTION_PACKAGE_CHANGED)) {
-                mPackageChangedBroadcastReceivers.add(receiver);
-            }
             if (filter.hasAction(Intent.ACTION_USER_STARTED)) {
                 mUserStartedBroadcastReceivers.add(receiver);
             }
@@ -473,20 +670,6 @@
                 @Nullable String receiverPermission, @Nullable Bundle options) {
         }
 
-        void sendPackageChangedBroadcast(ComponentName changedComponent) {
-            Intent intent = new Intent(
-                    Intent.ACTION_PACKAGE_CHANGED,
-                    Uri.fromParts(URI_SCHEME_PACKAGE,
-                            changedComponent.getPackageName(), /* fragment= */ null))
-                    .putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
-                            new String[]{changedComponent.getClassName()})
-                    .putExtra(Intent.EXTRA_USER_HANDLE, TEST_USER_ID)
-                    .putExtra(Intent.EXTRA_UID, UserHandle.of(TEST_USER_ID).getUid(1234));
-            for (BroadcastReceiver receiver : mPackageChangedBroadcastReceivers) {
-                receiver.onReceive(this, intent);
-            }
-        }
-
         void sendUserStartedBroadcast() {
             Intent intent = new Intent(Intent.ACTION_USER_STARTED)
                     .putExtra(Intent.EXTRA_USER_HANDLE, TEST_USER_ID);
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index 05e0e8f..f49f638 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -11,6 +11,7 @@
         "src/com/android/server/power/stats/MultiStateStatsTest.java",
         "src/com/android/server/power/stats/PowerStatsAggregatorTest.java",
         "src/com/android/server/power/stats/PowerStatsCollectorTest.java",
+        "src/com/android/server/power/stats/PowerStatsExporterTest.java",
         "src/com/android/server/power/stats/PowerStatsSchedulerTest.java",
         "src/com/android/server/power/stats/PowerStatsStoreTest.java",
         "src/com/android/server/power/stats/PowerStatsUidResolverTest.java",
@@ -43,6 +44,7 @@
         "servicestests-utils",
         "platform-test-annotations",
         "flag-junit",
+        "ravenwood-junit",
     ],
 
     libs: [
@@ -83,6 +85,9 @@
     ],
     srcs: [
         ":power_stats_ravenwood_tests",
+
+        "src/com/android/server/power/stats/BatteryUsageStatsRule.java",
+        "src/com/android/server/power/stats/MockBatteryStatsImpl.java",
         "src/com/android/server/power/stats/MockClock.java",
     ],
     auto_gen_config: true,
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java
index 4ea0805..3f058a2 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java
@@ -37,12 +37,12 @@
     private static final double PRECISION = 0.00001;
 
     @Rule
-    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
+    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+                    .setAveragePower(PowerProfile.POWER_BATTERY_CAPACITY, 4000.0);
 
     @Test
     public void testDischargeTotals() {
         // Nominal battery capacity should be ignored
-        mStatsRule.setAveragePower(PowerProfile.POWER_BATTERY_CAPACITY, 1234.0);
 
         final BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
 
@@ -84,8 +84,6 @@
 
     @Test
     public void testDischargeTotals_chargeUahUnavailable() {
-        mStatsRule.setAveragePower(PowerProfile.POWER_BATTERY_CAPACITY, 4000.0);
-
         final BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
 
         batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index bb70080..9251376 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -21,6 +21,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -96,18 +99,11 @@
         mClock.realtime = 123;
 
         mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
-                mStepDetailsCalculator, mClock, mMonotonicClock, mTracer) {
-            @Override
-            public boolean readFileToParcel(Parcel out, AtomicFile file) {
-                mReadFiles.add(file.getBaseFile().getName());
-                return super.readFileToParcel(out, file);
-            }
-        };
+                mStepDetailsCalculator, mClock, mMonotonicClock, mTracer);
 
         when(mStepDetailsCalculator.getHistoryStepDetails())
                 .thenReturn(new BatteryStats.HistoryStepDetails());
 
-
         mHistoryPrinter = new BatteryStats.HistoryPrinter();
     }
 
@@ -276,6 +272,15 @@
 
         mReadFiles.clear();
 
+        // Make an immutable copy and spy on it
+        mHistory = spy(mHistory.copy());
+
+        doAnswer(invocation -> {
+            AtomicFile file = invocation.getArgument(1);
+            mReadFiles.add(file.getBaseFile().getName());
+            return invocation.callRealMethod();
+        }).when(mHistory).readFileToParcel(any(), any());
+
         // Prepare history for iteration
         mHistory.iterate(0, MonotonicClock.UNDEFINED);
 
@@ -309,6 +314,15 @@
 
         mReadFiles.clear();
 
+        // Make an immutable copy and spy on it
+        mHistory = spy(mHistory.copy());
+
+        doAnswer(invocation -> {
+            AtomicFile file = invocation.getArgument(1);
+            mReadFiles.add(file.getBaseFile().getName());
+            return invocation.callRealMethod();
+        }).when(mHistory).readFileToParcel(any(), any());
+
         // Prepare history for iteration
         mHistory.iterate(1000, 3000);
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index e61dd0b..ba2b538 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -18,11 +18,11 @@
 
 import static org.mockito.ArgumentMatchers.anyDouble;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import android.annotation.XmlRes;
-import android.content.Context;
 import android.net.NetworkStats;
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
@@ -32,6 +32,7 @@
 import android.os.HandlerThread;
 import android.os.UidBatteryConsumer;
 import android.os.UserBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
@@ -54,12 +55,12 @@
                     .powerProfileModeledOnly()
                     .includePowerModels()
                     .build();
-    private final Context mContext;
 
     private final PowerProfile mPowerProfile;
     private final MockClock mMockClock = new MockClock();
-    private final MockBatteryStatsImpl mBatteryStats;
-    private final Handler mHandler;
+    private final File mHistoryDir;
+    private MockBatteryStatsImpl mBatteryStats;
+    private Handler mHandler;
 
     private BatteryUsageStats mBatteryUsageStats;
     private boolean mScreenOn;
@@ -67,6 +68,10 @@
     private SparseArray<int[]> mCpusByPolicy = new SparseArray<>();
     private SparseArray<int[]> mFreqsByPolicy = new SparseArray<>();
 
+    private int mDisplayCount = -1;
+    private int mPerUidModemModel = -1;
+    private NetworkStats mNetworkStats;
+
     public BatteryUsageStatsRule() {
         this(0, null);
     }
@@ -76,22 +81,41 @@
     }
 
     public BatteryUsageStatsRule(long currentTime, File historyDir) {
-        HandlerThread bgThread = new HandlerThread("bg thread");
-        bgThread.start();
-        mHandler = new Handler(bgThread.getLooper());
-        mContext = InstrumentationRegistry.getContext();
-        mPowerProfile = spy(new PowerProfile(mContext, true /* forTest */));
+        mHandler = mock(Handler.class);
+        mPowerProfile = spy(new PowerProfile());
         mMockClock.currentTime = currentTime;
-        mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir, mHandler);
-        mBatteryStats.setPowerProfile(mPowerProfile);
+        mHistoryDir = historyDir;
+
+        if (!RavenwoodRule.isUnderRavenwood()) {
+            lateInitBatteryStats();
+        }
 
         mCpusByPolicy.put(0, new int[]{0, 1, 2, 3});
         mCpusByPolicy.put(4, new int[]{4, 5, 6, 7});
         mFreqsByPolicy.put(0, new int[]{300000, 1000000, 2000000});
         mFreqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000});
+    }
+
+    private void lateInitBatteryStats() {
+        if (mBatteryStats != null) return;
+
+        mBatteryStats = new MockBatteryStatsImpl(mMockClock, mHistoryDir, mHandler);
+        mBatteryStats.setPowerProfile(mPowerProfile);
         mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
 
         mBatteryStats.onSystemReady();
+
+        if (mDisplayCount != -1) {
+            mBatteryStats.setDisplayCountLocked(mDisplayCount);
+        }
+        if (mPerUidModemModel != -1) {
+            synchronized (mBatteryStats) {
+                mBatteryStats.setPerUidModemModel(mPerUidModemModel);
+            }
+        }
+        if (mNetworkStats != null) {
+            mBatteryStats.setNetworkStats(mNetworkStats);
+        }
     }
 
     public MockClock getMockClock() {
@@ -103,7 +127,7 @@
     }
 
     public BatteryUsageStatsRule setTestPowerProfile(@XmlRes int xmlId) {
-        mPowerProfile.forceInitForTesting(mContext, xmlId);
+        mPowerProfile.forceInitForTesting(InstrumentationRegistry.getContext(), xmlId);
         return this;
     }
 
@@ -116,7 +140,10 @@
         }
         mCpusByPolicy.put(policy, relatedCpus);
         mFreqsByPolicy.put(policy, frequencies);
-        mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
+        if (mBatteryStats != null) {
+            mBatteryStats.setCpuScalingPolicies(
+                    new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
+        }
         return this;
     }
 
@@ -178,13 +205,19 @@
 
     public BatteryUsageStatsRule setNumDisplays(int value) {
         when(mPowerProfile.getNumDisplays()).thenReturn(value);
-        mBatteryStats.setDisplayCountLocked(value);
+        mDisplayCount = value;
+        if (mBatteryStats != null) {
+            mBatteryStats.setDisplayCountLocked(mDisplayCount);
+        }
         return this;
     }
 
     public BatteryUsageStatsRule setPerUidModemModel(int perUidModemModel) {
-        synchronized (mBatteryStats) {
-            mBatteryStats.setPerUidModemModel(perUidModemModel);
+        mPerUidModemModel = perUidModemModel;
+        if (mBatteryStats != null) {
+            synchronized (mBatteryStats) {
+                mBatteryStats.setPerUidModemModel(mPerUidModemModel);
+            }
         }
         return this;
     }
@@ -214,7 +247,10 @@
     }
 
     public void setNetworkStats(NetworkStats networkStats) {
-        mBatteryStats.setNetworkStats(networkStats);
+        mNetworkStats = networkStats;
+        if (mBatteryStats != null) {
+            mBatteryStats.setNetworkStats(mNetworkStats);
+        }
     }
 
     @Override
@@ -222,13 +258,18 @@
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
-                noteOnBattery();
+                before();
                 base.evaluate();
             }
         };
     }
 
-    private void noteOnBattery() {
+    private void before() {
+        lateInitBatteryStats();
+        HandlerThread bgThread = new HandlerThread("bg thread");
+        bgThread.start();
+        mHandler = new Handler(bgThread.getLooper());
+        mBatteryStats.setHandler(mHandler);
         mBatteryStats.setOnBatteryInternal(true);
         mBatteryStats.getOnBatteryTimeBase().setRunning(true, 0, 0);
         mBatteryStats.getOnBatteryScreenOffTimeBase().setRunning(!mScreenOn, 0, 0);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index fb71ac8..78c4bac 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -271,6 +271,10 @@
     public void writeSyncLocked() {
     }
 
+    public void setHandler(Handler handler) {
+        mHandler = handler;
+    }
+
     public static class DummyExternalStatsSync implements ExternalStatsSync {
         public int flags = 0;
 
@@ -315,4 +319,3 @@
         }
     }
 }
-
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
index 3c48262..3560a26 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
@@ -16,8 +16,6 @@
 
 package com.android.server.power.stats;
 
-import static androidx.test.InstrumentationRegistry.getContext;
-
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -34,6 +32,7 @@
 import android.os.Parcel;
 import android.os.PersistableBundle;
 import android.os.UidBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -48,6 +47,8 @@
 import org.junit.runner.RunWith;
 
 import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
 import java.util.List;
 
 @RunWith(AndroidJUnit4.class)
@@ -57,7 +58,12 @@
     private static final int APP_UID2 = 84;
     private static final double TOLERANCE = 0.01;
 
-    @Rule
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
+    @Rule(order = 1)
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
             .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720)
             .setCpuScalingPolicy(0, new int[]{0}, new int[]{100})
@@ -75,8 +81,8 @@
     private PowerStats.Descriptor mPowerStatsDescriptor;
 
     @Before
-    public void setup() {
-        File storeDirectory = new File(getContext().getCacheDir(), getClass().getSimpleName());
+    public void setup() throws IOException {
+        File storeDirectory = Files.createTempDirectory("PowerStatsExporterTest").toFile();
         clearDirectory(storeDirectory);
 
         AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java
index 4dae2d5..8e53d52 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java
@@ -28,6 +28,9 @@
 import android.os.BatteryConsumer;
 import android.os.Binder;
 import android.os.Process;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -38,6 +41,7 @@
 import com.android.internal.os.KernelSingleUidTimeReader;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.power.EnergyConsumerStats;
+import com.android.server.power.optimization.Flags;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -54,6 +58,8 @@
 @RunWith(AndroidJUnit4.class)
 @SuppressWarnings("GuardedBy")
 public class SystemServicePowerCalculatorTest {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     private static final double PRECISION = 0.000001;
     private static final int APP_UID1 = 100;
@@ -108,6 +114,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DISABLE_SYSTEM_SERVICE_POWER_ATTR)
     public void testPowerProfileBasedModel() {
         prepareBatteryStats(null);
 
@@ -135,6 +142,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DISABLE_SYSTEM_SERVICE_POWER_ATTR)
     public void testMeasuredEnergyBasedModel() {
         final boolean[] supportedPowerBuckets =
                 new boolean[EnergyConsumerStats.NUMBER_STANDARD_POWER_BUCKETS];
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 0045026..e22d99d 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -29,11 +29,14 @@
         "src/**/*.java",
         "src/**/*.kt",
 
-        "test-apps/JobTestApp/src/**/*.java",
-
         "test-apps/SuspendTestApp/src/**/*.java",
     ],
+
+    kotlincflags: [
+        "-Werror",
+    ],
     static_libs: [
+        "cts-input-lib",
         "frameworks-base-testutils",
         "services.accessibility",
         "services.appwidget",
@@ -80,6 +83,7 @@
         "securebox",
         "flag-junit",
         "ravenwood-junit",
+        "net_flags_lib",
     ],
 
     libs: [
@@ -124,7 +128,6 @@
     },
 
     data: [
-        ":JobTestApp",
         ":SimpleServiceTestApp1",
         ":SimpleServiceTestApp2",
         ":SimpleServiceTestApp3",
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 0089d4c..5e5181b 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -109,6 +109,7 @@
     <uses-permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES" />
     <uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB" />
     <uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" />
+    <uses-permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
     <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
     <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
 
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index b1d5039..27c522d 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -29,7 +29,6 @@
         <option name="cleanup-apks" value="true" />
         <option name="install-arg" value="-t" />
         <option name="test-file-name" value="FrameworksServicesTests.apk" />
-        <option name="test-file-name" value="JobTestApp.apk" />
         <option name="test-file-name" value="SuspendTestApp.apk" />
         <option name="test-file-name" value="SimpleServiceTestApp1.apk" />
         <option name="test-file-name" value="SimpleServiceTestApp2.apk" />
diff --git a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java b/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
deleted file mode 100644
index 523c5c0..0000000
--- a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.test.AndroidTestCase;
-
-import com.android.server.os.TombstoneProtos;
-import com.android.server.os.TombstoneProtos.Tombstone;
-
-public class BootReceiverTest extends AndroidTestCase {
-    private static final String TAG = "BootReceiverTest";
-
-    public void testRemoveMemoryFromTombstone() {
-        Tombstone tombstoneBase = Tombstone.newBuilder()
-                .setBuildFingerprint("build_fingerprint")
-                .setRevision("revision")
-                .setPid(123)
-                .setTid(23)
-                .setUid(34)
-                .setSelinuxLabel("selinux_label")
-                .addCommandLine("cmd1")
-                .addCommandLine("cmd2")
-                .addCommandLine("cmd3")
-                .setProcessUptime(300)
-                .setAbortMessage("abort")
-                .addCauses(TombstoneProtos.Cause.newBuilder()
-                        .setHumanReadable("cause1")
-                        .setMemoryError(TombstoneProtos.MemoryError.newBuilder()
-                                .setTool(TombstoneProtos.MemoryError.Tool.SCUDO)
-                                .setType(TombstoneProtos.MemoryError.Type.DOUBLE_FREE)))
-                .addLogBuffers(TombstoneProtos.LogBuffer.newBuilder().setName("name").addLogs(
-                        TombstoneProtos.LogMessage.newBuilder()
-                                .setTimestamp("123")
-                                .setMessage("message")))
-                .addOpenFds(TombstoneProtos.FD.newBuilder().setFd(1).setPath("path"))
-                .build();
-
-        Tombstone tombstoneWithoutMemory = tombstoneBase.toBuilder()
-                .putThreads(1, TombstoneProtos.Thread.newBuilder()
-                        .setId(1)
-                        .setName("thread1")
-                        .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1))
-                        .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2))
-                        .addBacktraceNote("backtracenote1")
-                        .addUnreadableElfFiles("files1")
-                        .setTaggedAddrCtrl(1)
-                        .setPacEnabledKeys(10)
-                        .build())
-                .build();
-
-        Tombstone tombstoneWithMemory = tombstoneBase.toBuilder()
-                .addMemoryMappings(TombstoneProtos.MemoryMapping.newBuilder()
-                        .setBeginAddress(1)
-                        .setEndAddress(100)
-                        .setOffset(10)
-                        .setRead(true)
-                        .setWrite(true)
-                        .setExecute(false)
-                        .setMappingName("mapping")
-                        .setBuildId("build")
-                        .setLoadBias(70))
-                .putThreads(1, TombstoneProtos.Thread.newBuilder()
-                        .setId(1)
-                        .setName("thread1")
-                        .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1))
-                        .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2))
-                        .addBacktraceNote("backtracenote1")
-                        .addUnreadableElfFiles("files1")
-                        .addMemoryDump(TombstoneProtos.MemoryDump.newBuilder()
-                                .setRegisterName("register1")
-                                .setMappingName("mapping")
-                                .setBeginAddress(10))
-                        .setTaggedAddrCtrl(1)
-                        .setPacEnabledKeys(10)
-                        .build())
-                .build();
-
-        assertThat(BootReceiver.removeMemoryFromTombstone(tombstoneWithMemory))
-                .isEqualTo(tombstoneWithoutMemory);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterInputTest.kt b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterInputTest.kt
new file mode 100644
index 0000000..52c7d8d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterInputTest.kt
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.accessibility
+
+import android.hardware.display.DisplayManagerGlobal
+import android.os.SystemClock
+import android.view.Display
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.DisplayAdjustments
+import android.view.DisplayInfo
+import android.view.IInputFilterHost
+import android.view.InputDevice.SOURCE_TOUCHSCREEN
+import android.view.InputEvent
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_MOVE
+import android.view.MotionEvent.ACTION_UP
+import android.view.MotionEvent.ACTION_HOVER_ENTER
+import android.view.MotionEvent.ACTION_HOVER_EXIT
+import android.view.MotionEvent.ACTION_HOVER_MOVE
+import android.view.WindowManagerPolicyConstants.FLAG_PASS_TO_USER
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.cts.input.inputeventmatchers.withDeviceId
+import com.android.cts.input.inputeventmatchers.withMotionAction
+import com.android.server.LocalServices
+import com.android.server.accessibility.magnification.MagnificationProcessor
+import com.android.server.wm.WindowManagerInternal
+import java.util.concurrent.LinkedBlockingQueue
+import org.hamcrest.Matchers.allOf
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.stubbing.OngoingStubbing
+
+
+/**
+ * Create a MotionEvent with the provided action, eventTime, and source
+ */
+fun createMotionEvent(action: Int, downTime: Long, eventTime: Long, source: Int, deviceId: Int):
+        MotionEvent {
+    val x = 1f
+    val y = 2f
+    val pressure = 3f
+    val size = 1f
+    val metaState = 0
+    val xPrecision = 0f
+    val yPrecision = 0f
+    val edgeFlags = 0
+    val displayId = 0
+    return MotionEvent.obtain(downTime, eventTime, action, x, y, pressure, size, metaState,
+        xPrecision, yPrecision, deviceId, edgeFlags, source, displayId)
+}
+
+/**
+ * Tests for AccessibilityInputFilter, focusing on the input event processing as seen by the callers
+ * of the InputFilter interface.
+ * The main interaction with AccessibilityInputFilter in these tests is with the filterInputEvent
+ * and sendInputEvent APIs of InputFilter.
+ */
+@RunWith(AndroidJUnit4::class)
+class AccessibilityInputFilterInputTest {
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
+
+    private companion object{
+        const val ALL_A11Y_FEATURES = (AccessibilityInputFilter.FLAG_FEATURE_AUTOCLICK
+                or AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION
+                or AccessibilityInputFilter.FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER
+                or AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER
+                or AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS
+                or AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS)
+    }
+
+    @Rule
+    @JvmField
+    val mocks: MockitoRule = MockitoJUnit.rule()
+
+    @Mock
+    private lateinit var mockA11yController: WindowManagerInternal.AccessibilityControllerInternal
+
+    @Mock
+    private lateinit var mockWindowManagerService: WindowManagerInternal
+
+    @Mock
+    private lateinit var mockMagnificationProcessor: MagnificationProcessor
+
+    private val inputEvents = LinkedBlockingQueue<InputEvent>()
+    private val verifier = BlockingQueueEventVerifier(inputEvents)
+
+    @Mock
+    private lateinit var host: IInputFilterHost
+    private lateinit var ams: AccessibilityManagerService
+    private lateinit var a11yInputFilter: AccessibilityInputFilter
+    private val touchDeviceId = 1
+
+    @Before
+    fun setUp() {
+        val context = instrumentation.context
+        LocalServices.removeServiceForTest(WindowManagerInternal::class.java)
+        LocalServices.addService(WindowManagerInternal::class.java, mockWindowManagerService)
+
+        whenever(mockA11yController.isAccessibilityTracingEnabled).thenReturn(false)
+        whenever(
+            mockWindowManagerService.accessibilityController).thenReturn(
+            mockA11yController)
+
+        ams = Mockito.spy(AccessibilityManagerService(context))
+        val displayList = arrayListOf(createStubDisplay(DEFAULT_DISPLAY, DisplayInfo()))
+        whenever(ams.validDisplayList).thenReturn(displayList)
+        whenever(ams.magnificationProcessor).thenReturn(mockMagnificationProcessor)
+
+        doAnswer {
+            val event = it.getArgument(0) as MotionEvent
+            inputEvents.add(MotionEvent.obtain(event))
+        }.`when`(host).sendInputEvent(any(), anyInt())
+
+        a11yInputFilter = AccessibilityInputFilter(context, ams)
+        a11yInputFilter.install(host)
+    }
+
+    @After
+    fun tearDown() {
+        if (this::a11yInputFilter.isInitialized) {
+            a11yInputFilter.uninstall()
+        }
+    }
+
+    /**
+     * When no features are enabled, the events pass through the filter without getting modified.
+     */
+    @Test
+    fun testSingleDeviceTouchEventsWithoutA11yFeatures() {
+        enableFeatures(0)
+
+        val downTime = SystemClock.uptimeMillis()
+        val downEvent = createMotionEvent(
+            ACTION_DOWN, downTime, downTime, SOURCE_TOUCHSCREEN, touchDeviceId)
+        send(downEvent)
+        verifier.assertReceivedMotion(
+            allOf(withMotionAction(ACTION_DOWN), withDeviceId(touchDeviceId)))
+
+        val moveEvent = createMotionEvent(
+            ACTION_MOVE, downTime, SystemClock.uptimeMillis(), SOURCE_TOUCHSCREEN, touchDeviceId)
+        send(moveEvent)
+        verifier.assertReceivedMotion(
+            allOf(withMotionAction(ACTION_MOVE), withDeviceId(touchDeviceId)))
+
+        val upEvent = createMotionEvent(
+            ACTION_UP, downTime, SystemClock.uptimeMillis(), SOURCE_TOUCHSCREEN, touchDeviceId)
+        send(upEvent)
+        verifier.assertReceivedMotion(
+            allOf(withMotionAction(ACTION_UP), withDeviceId(touchDeviceId)))
+
+        verifier.assertNoEvents()
+    }
+
+    /**
+     * Enable all a11y features and send a touchscreen stream of DOWN -> MOVE -> UP events.
+     * These get converted into HOVER_ENTER -> HOVER_MOVE -> HOVER_EXIT events by the input filter.
+     */
+    @Test
+    fun testSingleDeviceTouchEventsWithAllA11yFeatures() {
+        enableFeatures(ALL_A11Y_FEATURES)
+
+        val downTime = SystemClock.uptimeMillis()
+        val downEvent = createMotionEvent(
+            ACTION_DOWN, downTime, downTime, SOURCE_TOUCHSCREEN, touchDeviceId)
+        send(MotionEvent.obtain(downEvent))
+
+        // DOWN event gets transformed to HOVER_ENTER
+        verifier.assertReceivedMotion(
+            allOf(withMotionAction(ACTION_HOVER_ENTER), withDeviceId(touchDeviceId)))
+
+        // MOVE becomes HOVER_MOVE
+        val moveEvent = createMotionEvent(
+            ACTION_MOVE, downTime, SystemClock.uptimeMillis(), SOURCE_TOUCHSCREEN, touchDeviceId)
+        send(moveEvent)
+        verifier.assertReceivedMotion(
+            allOf(withMotionAction(ACTION_HOVER_MOVE), withDeviceId(touchDeviceId)))
+
+        // UP becomes HOVER_EXIT
+        val upEvent = createMotionEvent(
+            ACTION_UP, downTime, SystemClock.uptimeMillis(), SOURCE_TOUCHSCREEN, touchDeviceId)
+        send(upEvent)
+
+        verifier.assertReceivedMotion(
+            allOf(withMotionAction(ACTION_HOVER_EXIT), withDeviceId(touchDeviceId)))
+
+        verifier.assertNoEvents()
+    }
+
+    /**
+     * Enable all a11y features and send a touchscreen event stream. In the middle of the gesture,
+     * disable the a11y features.
+     * When the a11y features are disabled, the filter generates HOVER_EXIT without further input
+     * from the dispatcher.
+     */
+    @Test
+    fun testSingleDeviceTouchEventsDisableFeaturesMidGesture() {
+        enableFeatures(ALL_A11Y_FEATURES)
+
+        val downTime = SystemClock.uptimeMillis()
+        val downEvent = createMotionEvent(
+            ACTION_DOWN, downTime, downTime, SOURCE_TOUCHSCREEN, touchDeviceId)
+        send(MotionEvent.obtain(downEvent))
+
+        // DOWN event gets transformed to HOVER_ENTER
+        verifier.assertReceivedMotion(
+            allOf(withMotionAction(ACTION_HOVER_ENTER), withDeviceId(touchDeviceId)))
+        verifier.assertNoEvents()
+
+        enableFeatures(0)
+        verifier.assertReceivedMotion(
+            allOf(withMotionAction(ACTION_HOVER_EXIT), withDeviceId(touchDeviceId)))
+        verifier.assertNoEvents()
+
+        val moveEvent = createMotionEvent(
+            ACTION_MOVE, downTime, SystemClock.uptimeMillis(), SOURCE_TOUCHSCREEN, touchDeviceId)
+        send(moveEvent)
+        val upEvent = createMotionEvent(
+            ACTION_UP, downTime, SystemClock.uptimeMillis(), SOURCE_TOUCHSCREEN, touchDeviceId)
+        send(upEvent)
+        // As the original gesture continues, no additional events should be getting sent by the
+        // filter because the HOVER_EXIT above already effectively finished the current gesture and
+        // the DOWN event was never sent to the host.
+
+        // Bug: the down event was swallowed, so the remainder of the gesture should be swallowed
+        // too. However, the MOVE and UP events are currently passed back to the dispatcher.
+        // TODO(b/310014874) - ensure a11y sends consistent input streams to the dispatcher
+        verifier.assertReceivedMotion(
+            allOf(withMotionAction(ACTION_MOVE), withDeviceId(touchDeviceId)))
+        verifier.assertReceivedMotion(
+            allOf(withMotionAction(ACTION_UP), withDeviceId(touchDeviceId)))
+
+        verifier.assertNoEvents()
+    }
+
+    private fun createStubDisplay(displayId: Int, displayInfo: DisplayInfo): Display {
+        val display = Display(DisplayManagerGlobal.getInstance(), displayId,
+            displayInfo, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS)
+        return display
+    }
+
+    private fun send(event: InputEvent) {
+        // We need to make a copy of the event before sending it to the filter, because the filter
+        // will recycle it, but the caller of this function might want to still be able to use
+        // this event for subsequent checks
+        val eventCopy = if (event is MotionEvent) MotionEvent.obtain(event) else event
+        a11yInputFilter.filterInputEvent(eventCopy, FLAG_PASS_TO_USER)
+    }
+
+    private fun enableFeatures(features: Int) {
+        instrumentation.runOnMainSync { a11yInputFilter.setUserAndEnabledFeatures(0, features) }
+    }
+}
+
+private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 57c3a1d..95cfc2a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -22,6 +22,7 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
 import static android.view.accessibility.Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG;
+import static android.view.accessibility.Flags.FLAG_SKIP_ACCESSIBILITY_WARNING_DIALOG_FOR_TRUSTED_SERVICES;
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
@@ -82,6 +83,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.compatibility.common.util.TestUtils;
+import com.android.internal.R;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.server.LocalServices;
 import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener;
@@ -857,8 +859,7 @@
     @RequiresFlagsEnabled(FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
     public void testIsAccessibilityServiceWarningRequired_requiredByDefault() {
         mockManageAccessibilityGranted(mTestableContext);
-        final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
-        info.setComponentName(COMPONENT_NAME);
+        final AccessibilityServiceInfo info = mockAccessibilityServiceInfo(COMPONENT_NAME);
 
         assertThat(mA11yms.isAccessibilityServiceWarningRequired(info)).isTrue();
     }
@@ -867,10 +868,9 @@
     @RequiresFlagsEnabled(FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
     public void testIsAccessibilityServiceWarningRequired_notRequiredIfAlreadyEnabled() {
         mockManageAccessibilityGranted(mTestableContext);
-        final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo();
-        info_a.setComponentName(COMPONENT_NAME);
-        final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo();
-        info_b.setComponentName(new ComponentName("package_b", "class_b"));
+        final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo(COMPONENT_NAME);
+        final AccessibilityServiceInfo info_b = mockAccessibilityServiceInfo(
+                new ComponentName("package_b", "class_b"));
         final AccessibilityUserState userState = mA11yms.getCurrentUserState();
         userState.mEnabledServices.clear();
         userState.mEnabledServices.add(info_b.getComponentName());
@@ -883,12 +883,12 @@
     @RequiresFlagsEnabled(FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
     public void testIsAccessibilityServiceWarningRequired_notRequiredIfExistingShortcut() {
         mockManageAccessibilityGranted(mTestableContext);
-        final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo();
-        info_a.setComponentName(new ComponentName("package_a", "class_a"));
-        final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo();
-        info_b.setComponentName(new ComponentName("package_b", "class_b"));
-        final AccessibilityServiceInfo info_c = new AccessibilityServiceInfo();
-        info_c.setComponentName(new ComponentName("package_c", "class_c"));
+        final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo(
+                new ComponentName("package_a", "class_a"));
+        final AccessibilityServiceInfo info_b = mockAccessibilityServiceInfo(
+                new ComponentName("package_b", "class_b"));
+        final AccessibilityServiceInfo info_c = mockAccessibilityServiceInfo(
+                new ComponentName("package_c", "class_c"));
         final AccessibilityUserState userState = mA11yms.getCurrentUserState();
         userState.mAccessibilityButtonTargets.clear();
         userState.mAccessibilityButtonTargets.add(info_b.getComponentName().flattenToString());
@@ -900,6 +900,51 @@
         assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_c)).isFalse();
     }
 
+    @Test
+    @RequiresFlagsEnabled({
+            FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG,
+            FLAG_SKIP_ACCESSIBILITY_WARNING_DIALOG_FOR_TRUSTED_SERVICES})
+    public void testIsAccessibilityServiceWarningRequired_notRequiredIfAllowlisted() {
+        mockManageAccessibilityGranted(mTestableContext);
+        final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo(
+                new ComponentName("package_a", "class_a"), true);
+        final AccessibilityServiceInfo info_b = mockAccessibilityServiceInfo(
+                new ComponentName("package_b", "class_b"), false);
+        final AccessibilityServiceInfo info_c = mockAccessibilityServiceInfo(
+                new ComponentName("package_c", "class_c"), true);
+        mTestableContext.getOrCreateTestableResources().addOverride(
+                R.array.config_trustedAccessibilityServices,
+                new String[]{
+                        info_b.getComponentName().flattenToString(),
+                        info_c.getComponentName().flattenToString()});
+
+        // info_a is not in the allowlist => require the warning
+        assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_a)).isTrue();
+        // info_b is not preinstalled => require the warning
+        assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_b)).isTrue();
+        // info_c is both in the allowlist and preinstalled => do not require the warning
+        assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_c)).isFalse();
+    }
+
+    private static AccessibilityServiceInfo mockAccessibilityServiceInfo(
+            ComponentName componentName) {
+        return mockAccessibilityServiceInfo(componentName, false);
+    }
+
+    private static AccessibilityServiceInfo mockAccessibilityServiceInfo(
+            ComponentName componentName,
+            boolean isSystemApp) {
+        AccessibilityServiceInfo accessibilityServiceInfo =
+                Mockito.spy(new AccessibilityServiceInfo());
+        accessibilityServiceInfo.setComponentName(componentName);
+        ResolveInfo mockResolveInfo = Mockito.mock(ResolveInfo.class);
+        when(accessibilityServiceInfo.getResolveInfo()).thenReturn(mockResolveInfo);
+        mockResolveInfo.serviceInfo = Mockito.mock(ServiceInfo.class);
+        mockResolveInfo.serviceInfo.applicationInfo = Mockito.mock(ApplicationInfo.class);
+        when(mockResolveInfo.serviceInfo.applicationInfo.isSystemApp()).thenReturn(isSystemApp);
+        return accessibilityServiceInfo;
+    }
+
     // Single package intents can trigger multiple PackageMonitor callbacks.
     // Collect the state of the lock in a set, since tests only care if calls
     // were all locked or all unlocked.
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/BlockingQueueEventVerifier.kt b/services/tests/servicestests/src/com/android/server/accessibility/BlockingQueueEventVerifier.kt
new file mode 100644
index 0000000..b12f537
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/BlockingQueueEventVerifier.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.accessibility
+
+import android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS
+import android.view.InputEvent
+import android.view.MotionEvent
+import java.time.Duration
+import java.util.concurrent.BlockingQueue
+import java.util.concurrent.TimeUnit
+import org.junit.Assert.fail
+
+import org.hamcrest.Matcher
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Assert.assertNull
+
+private fun <T> getEvent(queue: BlockingQueue<T>, timeout: Duration): T? {
+    return queue.poll(timeout.toMillis(), TimeUnit.MILLISECONDS)
+}
+
+class BlockingQueueEventVerifier(val queue: BlockingQueue<InputEvent>) {
+    fun assertReceivedMotion(matcher: Matcher<MotionEvent>) {
+        val event = getMotionEvent()
+        assertThat("MotionEvent checks", event, matcher)
+    }
+
+    fun assertNoEvents() {
+        val event = getEvent(queue, Duration.ofMillis(50))
+        assertNull(event)
+    }
+
+    private fun getMotionEvent(): MotionEvent {
+        val event = getEvent(queue, Duration.ofMillis(DEFAULT_DISPATCHING_TIMEOUT_MILLIS.toLong()))
+        if (event == null) {
+            fail("Did not get an event")
+        }
+        if (event is MotionEvent) {
+            return event
+        }
+        fail("Instead of motion, got $event")
+        throw RuntimeException("should not reach here")
+    }
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
index c0051c6..eee3752 100644
--- a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
@@ -133,7 +133,8 @@
         verify(mAnrApp.mErrorState, timeout(TIMEOUT_MS)).appNotResponding(
                 eq(activityShortComponentName), eq(appInfo), eq(parentShortComponentName),
                 eq(parentProcess), eq(aboveSystem), eq(timeoutRecord), eq(mAuxExecutorService),
-                eq(false) /* onlyDumpSelf */, eq(false) /*isContinuousAnr*/, eq(mEarlyDumpFuture));
+                anyBoolean() /* onlyDumpSelf */, eq(false) /*isContinuousAnr*/,
+                eq(mEarlyDumpFuture));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
index feb6bd9..467c15d 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
@@ -54,7 +54,7 @@
         mBgThread.start();
         File systemDir = context.getCacheDir();
         Handler handler = new Handler(mBgThread.getLooper());
-        mBatteryStatsService = new BatteryStatsService(context, systemDir, handler);
+        mBatteryStatsService = new BatteryStatsService(context, systemDir);
     }
 
     @After
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 77b1455..26934d8 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -763,8 +763,7 @@
 
         mUserController.startUser(TEST_USER_ID, USER_START_MODE_BACKGROUND);
 
-        verify(mInjector.mStorageManagerMock, never())
-                .unlockCeStorage(eq(TEST_USER_ID), anyInt(), any());
+        verify(mInjector.mStorageManagerMock, never()).unlockCeStorage(eq(TEST_USER_ID), any());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java
index 9c8276a..84c0ab3 100644
--- a/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.audio;
 
+import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_NONE;
 import static android.media.AudioManager.GET_DEVICES_OUTPUTS;
 import static android.media.AudioPlaybackConfiguration.PLAYER_UPDATE_DEVICE_ID;
 import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_4;
@@ -22,6 +23,7 @@
 import static android.media.MediaFormat.KEY_AAC_DRC_EFFECT_TYPE;
 import static android.media.MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION;
 import static android.media.MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL;
+import static android.os.Process.myPid;
 
 import static com.android.server.audio.LoudnessCodecHelper.SPL_RANGE_LARGE;
 import static com.android.server.audio.LoudnessCodecHelper.SPL_RANGE_MEDIUM;
@@ -37,6 +39,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.media.AudioAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.AudioPlaybackConfiguration;
@@ -64,13 +67,16 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Random;
 
 @RunWith(AndroidJUnit4.class)
 @Presubmit
 public class LoudnessCodecHelperTest {
     private static final String TAG = "LoudnessCodecHelperTest";
 
+    private static final int TEST_USAGE = AudioAttributes.USAGE_MEDIA;
+
+    private static final int TEST_CONTENT = AudioAttributes.CONTENT_TYPE_MUSIC;
+
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
 
@@ -83,94 +89,84 @@
 
     private final int mInitialApcPiid = 1;
 
+    private int mSessionId;
+
     @Before
     public void setUp() throws Exception {
         mLoudnessHelper = new LoudnessCodecHelper(mAudioService);
+        mSessionId = 1;
 
         when(mAudioService.getActivePlaybackConfigurations()).thenReturn(
-                getApcListForPiids(mInitialApcPiid));
+                getApcListForApcWithPiidSid(mInitialApcPiid, mSessionId, 0));
 
         when(mDispatcher.asBinder()).thenReturn(Mockito.mock(IBinder.class));
     }
 
     @Test
-    public void registerDispatcher_sendsInitialUpdateOnStart() throws Exception {
+    public void registerDispatcher_sendsUpdateOnAddCodec() throws Exception {
         mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
 
-        mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid,
-                List.of(getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4)));
+        mLoudnessHelper.startLoudnessCodecUpdates(mSessionId);
+        mLoudnessHelper.addLoudnessCodecInfo(mSessionId, /*mediaCodecHash=*/222,
+                getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D));
 
-        verify(mDispatcher).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), any());
+        verify(mDispatcher).dispatchLoudnessCodecParameterChange(eq(mSessionId), any());
     }
 
     @Test
-    public void unregisterDispatcher_noInitialUpdateOnStart() throws Exception {
+    public void unregisterDispatcher_noUpdateOnAdd() throws Exception {
         mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
         mLoudnessHelper.unregisterLoudnessCodecUpdatesDispatcher(mDispatcher);
 
-        mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid,
-                List.of(getLoudnessInfo(/*isDownmixing=*/false, CODEC_METADATA_TYPE_MPEG_D)));
-
-        verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
-                any());
-    }
-
-    @Test
-    public void addCodecInfo_sendsInitialUpdateAfterStart() throws Exception {
-        mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
-
-        mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid,
-                List.of(getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4)));
-        mLoudnessHelper.addLoudnessCodecInfo(mInitialApcPiid, /*mediaCodecHash=*/222,
+        mLoudnessHelper.startLoudnessCodecUpdates(mSessionId);
+        mLoudnessHelper.addLoudnessCodecInfo(mSessionId, /*mediaCodecHash=*/222,
                 getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D));
 
-        verify(mDispatcher, times(2)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
+        verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mSessionId),
                 any());
     }
 
     @Test
-    public void addCodecInfoForUnstartedPiid_noUpdateSent() throws Exception {
-        final int newPiid = 2;
+    public void addCodecInfoForDifferentId_noUpdateSent() throws Exception {
+        final int newSessionId = mSessionId + 1;
         mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
 
-        mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid,
-                List.of(getLoudnessInfo(/*isDownmixing=*/true,
-                        CODEC_METADATA_TYPE_MPEG_4)));
-        mLoudnessHelper.addLoudnessCodecInfo(newPiid, /*mediaCodecHash=*/222,
+        mLoudnessHelper.startLoudnessCodecUpdates(mSessionId);
+        mLoudnessHelper.addLoudnessCodecInfo(newSessionId, /*mediaCodecHash=*/222,
                 getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D));
 
-        verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
+        verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mSessionId),
                 any());
     }
 
     @Test
-    public void updateCodecParameters_updatesOnlyStartedPiids() throws Exception {
-        final int newPiid = 2;
-        mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
-
-        mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid,
-                List.of(getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4)));
-        //does not trigger dispatch since active apc list does not contain newPiid
-        mLoudnessHelper.startLoudnessCodecUpdates(newPiid,
-                List.of(getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D)));
-        verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
-                any());
-
-        // triggers dispatch for new active apc with newPiid
-        mLoudnessHelper.updateCodecParameters(getApcListForPiids(newPiid));
-        verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(newPiid), any());
-    }
-
-    @Test
     public void updateCodecParameters_noStartedPiids_noDispatch() throws Exception {
         mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
-        mLoudnessHelper.addLoudnessCodecInfo(mInitialApcPiid, /*mediaCodecHash=*/222,
+        mLoudnessHelper.addLoudnessCodecInfo(mSessionId, /*mediaCodecHash=*/222,
                 getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D));
 
-        mLoudnessHelper.updateCodecParameters(getApcListForPiids(mInitialApcPiid));
+        mLoudnessHelper.updateCodecParameters(
+                getApcListForApcWithPiidSid(mInitialApcPiid, mSessionId, 1));
 
-        // no dispatch since mInitialApcPiid was not started
-        verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
+        // no dispatch since mSessionId was not started
+        verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mSessionId),
+                any());
+    }
+
+    @Test
+    public void updateCodecParameters_dispatchUpdates() throws Exception {
+        final LoudnessCodecInfo info = getLoudnessInfo(/*isDownmixing=*/true,
+                CODEC_METADATA_TYPE_MPEG_4);
+        mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
+
+        mLoudnessHelper.startLoudnessCodecUpdates(mSessionId);
+        mLoudnessHelper.addLoudnessCodecInfo(mSessionId, /*mediaCodecHash=*/222, info);
+
+        mLoudnessHelper.updateCodecParameters(
+                getApcListForApcWithPiidSid(mInitialApcPiid, mSessionId, 1));
+
+        // second dispatch since player configurations were updated
+        verify(mDispatcher, times(2)).dispatchLoudnessCodecParameterChange(eq(mSessionId),
                 any());
     }
 
@@ -180,13 +176,15 @@
                 CODEC_METADATA_TYPE_MPEG_4);
         mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
 
-        mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, List.of(info));
-        mLoudnessHelper.removeLoudnessCodecInfo(mInitialApcPiid, info);
+        mLoudnessHelper.startLoudnessCodecUpdates(mSessionId);
+        mLoudnessHelper.addLoudnessCodecInfo(mSessionId, /*mediaCodecHash=*/222, info);
+        mLoudnessHelper.removeLoudnessCodecInfo(mSessionId, info);
 
-        mLoudnessHelper.updateCodecParameters(getApcListForPiids(mInitialApcPiid));
+        mLoudnessHelper.updateCodecParameters(
+                getApcListForApcWithPiidSid(mInitialApcPiid, mSessionId, 1));
 
         // no second dispatch since codec info was removed for updates
-        verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
+        verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mSessionId),
                 any());
     }
 
@@ -196,13 +194,14 @@
                 CODEC_METADATA_TYPE_MPEG_4);
         mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
 
-        mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, List.of(info));
-        mLoudnessHelper.stopLoudnessCodecUpdates(mInitialApcPiid);
+        mLoudnessHelper.startLoudnessCodecUpdates(mSessionId);
+        mLoudnessHelper.stopLoudnessCodecUpdates(mSessionId);
 
-        mLoudnessHelper.updateCodecParameters(getApcListForPiids(mInitialApcPiid));
+        mLoudnessHelper.updateCodecParameters(
+                getApcListForApcWithPiidSid(mInitialApcPiid, mSessionId, 1));
 
         // no second dispatch since piid was removed for updates
-        verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
+        verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mSessionId),
                 any());
     }
 
@@ -308,23 +307,28 @@
         assertEquals(6, loudnessParameters.getInt(KEY_AAC_DRC_EFFECT_TYPE));
     }
 
-    private List<AudioPlaybackConfiguration> getApcListForPiids(int... piids) {
+    private List<AudioPlaybackConfiguration> getApcListForApcWithPiidSid(int piid, int sessionId,
+            int devIdx) {
         final ArrayList<AudioPlaybackConfiguration> apcList = new ArrayList<>();
 
         AudioDeviceInfo[] devicesStatic = AudioManager.getDevicesStatic(GET_DEVICES_OUTPUTS);
-        assumeTrue(devicesStatic.length > 0);
-        int index = new Random().nextInt(devicesStatic.length);
-        Log.d(TAG, "Out devices number " + devicesStatic.length + ". Picking index " + index);
-        int deviceId = devicesStatic[index].getId();
+        assumeTrue(devIdx < devicesStatic.length);
+        Log.d(TAG, "Out devices number " + devicesStatic.length + ". Picking index " + devIdx);
+        int deviceId = devicesStatic[devIdx].getId();
 
-        for (int piid : piids) {
-            PlayerBase.PlayerIdCard idCard = Mockito.mock(PlayerBase.PlayerIdCard.class);
-            AudioPlaybackConfiguration apc =
-                    new AudioPlaybackConfiguration(idCard, piid, /*uid=*/1, /*pid=*/1);
-            apc.handleStateEvent(PLAYER_UPDATE_DEVICE_ID, deviceId);
+        PlayerBase.PlayerIdCard idCard = Mockito.mock(PlayerBase.PlayerIdCard.class);
+        AudioPlaybackConfiguration apc =
+                new AudioPlaybackConfiguration(idCard, piid, /*uid=*/1, /*pid=*/myPid());
+        apc.handleStateEvent(PLAYER_UPDATE_DEVICE_ID, deviceId);
+        apc.handleSessionIdEvent(sessionId);
+        apc.handleAudioAttributesEvent(new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_MEDIA)
+                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+                .setAllowedCapturePolicy(ALLOW_CAPTURE_BY_NONE)
+                .build());
 
-            apcList.add(apc);
-        }
+        apcList.add(apc);
+
         return apcList;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 88b2ed4..071db68 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.biometrics;
 
+import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
 import static android.Manifest.permission.MANAGE_BIOMETRIC;
 import static android.Manifest.permission.TEST_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -491,6 +492,22 @@
     }
 
     @Test
+    public void testRegisterAuthenticationStateListener_callsFaceService() throws Exception {
+        mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+        setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
+
+        mAuthService = new AuthService(mContext, mInjector);
+        mAuthService.onStart();
+
+        final AuthenticationStateListener listener = mock(AuthenticationStateListener.class);
+
+        mAuthService.mImpl.registerAuthenticationStateListener(listener);
+
+        waitForIdle();
+        verify(mFaceService).registerAuthenticationStateListener(eq(listener));
+    }
+
+    @Test
     public void testRegisterKeyguardCallback_callsBiometricServiceRegisterKeyguardCallback()
             throws Exception {
         setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
index 1b9e6fb..c7300bb 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
@@ -20,6 +20,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.mock;
@@ -33,11 +34,15 @@
 import android.hardware.biometrics.AuthenticateOptions;
 import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.IBiometricContextListener.FoldState;
+import android.hardware.biometrics.common.DisplayState;
 import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.common.OperationReason;
 import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.testing.TestableContext;
 import android.view.Display;
 import android.view.DisplayInfo;
@@ -72,6 +77,9 @@
     @Rule
     public TestableContext mContext = new TestableContext(
             InstrumentationRegistry.getInstrumentation().getContext());
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Mock
     private IStatusBarService mStatusBarService;
@@ -153,6 +161,15 @@
     }
 
     @Test
+    public void testGetIsHardwareIgnoringTouches() throws RemoteException {
+        mListener.onHardwareIgnoreTouchesChanged(true);
+        assertThat(mProvider.isHardwareIgnoringTouches()).isTrue();
+
+        mListener.onHardwareIgnoreTouchesChanged(false);
+        assertThat(mProvider.isHardwareIgnoringTouches()).isFalse();
+    }
+
+    @Test
     public void testGetDockedState() {
         final List<Integer> states = List.of(Intent.EXTRA_DOCK_STATE_DESK,
                 Intent.EXTRA_DOCK_STATE_CAR, Intent.EXTRA_DOCK_STATE_UNDOCKED);
@@ -386,6 +403,37 @@
         }
     }
 
+    @Test
+    public void testSubscribe_thenStartHal() throws RemoteException {
+        Consumer<OperationContext> updateConsumer = mock(Consumer.class);
+        Consumer<OperationContext> startHalConsumer = mock(Consumer.class);
+        AuthenticateOptions options = new FingerprintAuthenticateOptions.Builder().build();
+        OperationContextExt context = mProvider.updateContext(mOpContext, false /* crypto */);
+
+        assertThat(context.getDisplayState()).isEqualTo(DisplayState.UNKNOWN);
+        assertThat(context.getFoldState()).isEqualTo(IBiometricContextListener.FoldState.UNKNOWN);
+
+        mListener.onDisplayStateChanged(DisplayState.LOCKSCREEN);
+        mListener.onFoldChanged(FoldState.FULLY_CLOSED);
+        mProvider.subscribe(context, startHalConsumer, updateConsumer, options);
+
+        assertThat(context.getDisplayState()).isEqualTo(DisplayState.LOCKSCREEN);
+        assertThat(context.getFoldState()).isEqualTo(FoldState.FULLY_CLOSED);
+        verify(updateConsumer, never()).accept(context.toAidlContext());
+        verify(startHalConsumer).accept(context.toAidlContext(options));
+    }
+
+    @Test
+    public void testSubscribe_withInvalidOptions() {
+        Consumer<OperationContext> updateConsumer = mock(Consumer.class);
+        Consumer<OperationContext> startHalConsumer = mock(Consumer.class);
+        AuthenticateOptions options = mock(AuthenticateOptions.class);
+        OperationContextExt context = mProvider.updateContext(mOpContext, false /* crypto */);
+
+        assertThrows(IllegalStateException.class, () -> mProvider.subscribe(
+                context, startHalConsumer, updateConsumer, options));
+    }
+
     private static byte reason(int type) {
         if (type == StatusBarManager.SESSION_BIOMETRIC_PROMPT) {
             return OperationReason.BIOMETRIC_PROMPT;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java
index 5cff48d..4119352 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.AuthenticateReason;
 import android.hardware.biometrics.common.OperationContext;
@@ -48,11 +49,13 @@
     public void testConvertsWakeReason_whenPowerReason() {
         final OperationContext context = new OperationContext();
         context.wakeReason = WakeReason.WAKE_MOTION;
-        final OperationContextExt ctx = new OperationContextExt(context, false);
+        final OperationContextExt ctx = new OperationContextExt(context, false,
+                BiometricAuthenticator.TYPE_NONE);
 
         final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
         final int[] reasonDetails = BiometricFrameworkStatsLogger
-                .toProtoWakeReasonDetails(new OperationContextExt(context, false));
+                .toProtoWakeReasonDetails(
+                        new OperationContextExt(context, false, BiometricAuthenticator.TYPE_NONE));
 
         assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_WAKE_MOTION);
         assertThat(reasonDetails).isEmpty();
@@ -63,7 +66,8 @@
         final OperationContext context = new OperationContext();
         context.authenticateReason = AuthenticateReason.faceAuthenticateReason(
                 AuthenticateReason.Face.ASSISTANT_VISIBLE);
-        final OperationContextExt ctx = new OperationContextExt(context, false);
+        final OperationContextExt ctx = new OperationContextExt(context, false,
+                BiometricAuthenticator.TYPE_NONE);
 
         final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
         final int[] reasonDetails = BiometricFrameworkStatsLogger
@@ -79,7 +83,8 @@
         final OperationContext context = new OperationContext();
         context.authenticateReason = AuthenticateReason.vendorAuthenticateReason(
                 new AuthenticateReason.Vendor());
-        final OperationContextExt ctx = new OperationContextExt(context, false);
+        final OperationContextExt ctx = new OperationContextExt(context, false,
+                BiometricAuthenticator.TYPE_NONE);
 
         final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
         final int[] reasonDetails = BiometricFrameworkStatsLogger
@@ -96,7 +101,8 @@
         context.wakeReason = WakeReason.WAKE_KEY;
         context.authenticateReason = AuthenticateReason.faceAuthenticateReason(
                 AuthenticateReason.Face.PRIMARY_BOUNCER_SHOWN);
-        final OperationContextExt ctx = new OperationContextExt(context, false);
+        final OperationContextExt ctx = new OperationContextExt(context, false,
+                BiometricAuthenticator.TYPE_NONE);
 
         final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
         final int[] reasonDetails = BiometricFrameworkStatsLogger
@@ -113,7 +119,8 @@
         context.wakeReason = WakeReason.LID;
         context.authenticateReason = AuthenticateReason.vendorAuthenticateReason(
                 new AuthenticateReason.Vendor());
-        final OperationContextExt ctx = new OperationContextExt(context, false);
+        final OperationContextExt ctx = new OperationContextExt(context, false,
+                BiometricAuthenticator.TYPE_NONE);
 
         final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
         final int[] reasonDetails = BiometricFrameworkStatsLogger
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java
index 32284fd..767b426 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java
@@ -18,17 +18,19 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.when;
+
 import android.content.Intent;
 import android.hardware.biometrics.AuthenticateOptions;
+import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.common.DisplayState;
 import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.common.OperationReason;
+import android.hardware.biometrics.common.OperationState;
 import android.platform.test.annotations.Presubmit;
 import android.view.Surface;
 
-import static org.mockito.Mockito.when;
-
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.InstanceId;
@@ -58,7 +60,7 @@
 
         final OperationContext aidlContext = newAidlContext();
 
-        context = new OperationContextExt(aidlContext, false);
+        context = new OperationContextExt(aidlContext, false, BiometricAuthenticator.TYPE_NONE);
         assertThat(context.toAidlContext()).isSameInstanceAs(aidlContext);
 
         final int id = 5;
@@ -96,7 +98,8 @@
         );
 
         for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
-            final OperationContextExt context = new OperationContextExt(newAidlContext(), true);
+            final OperationContextExt context = new OperationContextExt(newAidlContext(), true,
+                    BiometricAuthenticator.TYPE_NONE);
             when(mBiometricContext.getDisplayState()).thenReturn(entry.getKey());
             assertThat(context.update(mBiometricContext, context.isCrypto()).getDisplayState())
                     .isEqualTo(entry.getValue());
@@ -124,7 +127,7 @@
         updatesFromSource(null, OperationReason.UNKNOWN);
     }
 
-    private  void updatesFromSource(BiometricContextSessionInfo sessionInfo, int sessionType) {
+    private void updatesFromSource(BiometricContextSessionInfo sessionInfo, int sessionType) {
         final int rotation = Surface.ROTATION_270;
         final int foldState = IBiometricContextListener.FoldState.HALF_OPENED;
         final int dockState = Intent.EXTRA_DOCK_STATE_CAR;
@@ -135,9 +138,11 @@
         when(mBiometricContext.getDockedState()).thenReturn(dockState);
         when(mBiometricContext.isDisplayOn()).thenReturn(true);
         when(mBiometricContext.getDisplayState()).thenReturn(displayState);
+        when(mBiometricContext.isHardwareIgnoringTouches()).thenReturn(true);
 
         final OperationContextExt context = new OperationContextExt(newAidlContext(),
-                sessionType == OperationReason.BIOMETRIC_PROMPT);
+                sessionType == OperationReason.BIOMETRIC_PROMPT,
+                BiometricAuthenticator.TYPE_FINGERPRINT);
 
         assertThat(context.update(mBiometricContext, context.isCrypto())).isSameInstanceAs(context);
 
@@ -154,6 +159,46 @@
         assertThat(context.getOrientation()).isEqualTo(rotation);
         assertThat(context.isDisplayOn()).isTrue();
         assertThat(context.getDisplayState()).isEqualTo(DisplayState.AOD);
+        assertThat(
+            context.getOperationState().getFingerprintOperationState().isHardwareIgnoringTouches
+        ).isTrue();
+    }
+
+    @Test
+    public void hasNullOperationState() {
+        OperationContextExt context = new OperationContextExt(false);
+        assertThat(context.toAidlContext()).isNotNull();
+
+        final OperationContext aidlContext = newAidlContext();
+
+        context = new OperationContextExt(aidlContext, false, BiometricAuthenticator.TYPE_NONE);
+        assertThat(context.getOperationState()).isNull();
+    }
+
+    @Test
+    public void hasFaceOperationState() {
+        OperationContextExt context = new OperationContextExt(false);
+        assertThat(context.toAidlContext()).isNotNull();
+
+        final OperationContext aidlContext = newAidlContext();
+
+        context = new OperationContextExt(aidlContext, false,
+                BiometricAuthenticator.TYPE_FACE);
+        assertThat(context.getOperationState().getTag()).isEqualTo(
+                OperationState.faceOperationState);
+    }
+
+    @Test
+    public void hasFingerprintOperationState() {
+        OperationContextExt context = new OperationContextExt(false);
+        assertThat(context.toAidlContext()).isNotNull();
+
+        final OperationContext aidlContext = newAidlContext();
+
+        context = new OperationContextExt(aidlContext, false,
+                BiometricAuthenticator.TYPE_FINGERPRINT);
+        assertThat(context.getOperationState().getTag()).isEqualTo(
+                OperationState.fingerprintOperationState);
     }
 
     private static OperationContext newAidlContext() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
index 2d9d868..4604b31 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
@@ -72,6 +72,7 @@
                 mToken, mClientCallback);
         client.start(mSchedulerCallback);
         assertTrue(client.mHalOperationRunning);
+        verify(mClientCallback).getModality();
         verify(mSchedulerCallback).onClientStarted(eq(client));
 
         // Pretend that it got canceled by the user.
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 8929900..f7480de 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -44,12 +44,18 @@
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.fingerprint.Fingerprint;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
 import android.testing.TestableLooper;
@@ -60,6 +66,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.nano.BiometricSchedulerProto;
@@ -95,14 +102,39 @@
     @Rule
     public final TestableContext mContext = new TestableContext(
             InstrumentationRegistry.getContext(), null);
-    private BiometricScheduler mScheduler;
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+    private BiometricScheduler<IFingerprint, ISession> mScheduler;
     private IBinder mToken;
+    private int mCurrentUserId = UserHandle.USER_SYSTEM;
+    private boolean mShouldFailStopUser = false;
+    private final List<Integer> mStartedUsers = new ArrayList<>();
+    private final StartUserClient.UserStartedCallback<ISession> mUserStartedCallback =
+            (newUserId, newUser, halInterfaceVersion) -> {
+                mStartedUsers.add(newUserId);
+                mCurrentUserId = newUserId;
+            };
+    private int mUsersStoppedCount = 0;
+    private final StopUserClient.UserStoppedCallback mUserStoppedCallback =
+            () -> {
+                mUsersStoppedCount++;
+                mCurrentUserId = UserHandle.USER_NULL;
+            };
+    private boolean mStartOperationsFinish = true;
+    private int mStartUserClientCount = 0;
     @Mock
     private IBiometricService mBiometricService;
     @Mock
     private BiometricContext mBiometricContext;
     @Mock
     private AuthSessionCoordinator mAuthSessionCoordinator;
+    @Mock
+    private BiometricLogger mBiometricLogger;
+    @Mock
+    private ISession mSession;
+    @Mock
+    private IFingerprint mFingerprint;
 
     @Before
     public void setUp() {
@@ -111,9 +143,39 @@
         when(mAuthSessionCoordinator.getLockoutStateFor(anyInt(), anyInt())).thenReturn(
                 BIOMETRIC_SUCCESS);
         when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
-        mScheduler = new BiometricScheduler(TAG, new Handler(TestableLooper.get(this).getLooper()),
-                BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityTracker */,
-                mBiometricService, LOG_NUM_RECENT_OPERATIONS);
+        if (Flags.deHidl()) {
+            mScheduler = new BiometricScheduler<>(
+                    new Handler(TestableLooper.get(this).getLooper()),
+                    BiometricScheduler.SENSOR_TYPE_UNKNOWN,
+                    null /* gestureAvailabilityDispatcher */,
+                    mBiometricService,
+                    LOG_NUM_RECENT_OPERATIONS,
+                    () -> mCurrentUserId,
+                    new UserSwitchProvider<IFingerprint, ISession>() {
+                        @NonNull
+                        @Override
+                        public StopUserClient<ISession> getStopUserClient(int userId) {
+                            return new TestStopUserClient(mContext, () -> mSession, mToken, userId,
+                                    TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
+                                    mUserStoppedCallback, () -> mShouldFailStopUser);
+                        }
+
+                        @NonNull
+                        @Override
+                        public StartUserClient<IFingerprint, ISession> getStartUserClient(
+                                int newUserId) {
+                            mStartUserClientCount++;
+                            return new TestStartUserClient(mContext, () -> mFingerprint, mToken,
+                                    newUserId, TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
+                                    mUserStartedCallback, mStartOperationsFinish);
+                        }
+                    });
+        } else {
+            mScheduler = new BiometricScheduler<>(
+                    new Handler(TestableLooper.get(this).getLooper()),
+                    BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityTracker */,
+                    mBiometricService, LOG_NUM_RECENT_OPERATIONS);
+        }
     }
 
     @Test
@@ -479,6 +541,7 @@
         final boolean isEnroll = client instanceof TestEnrollClient;
 
         mScheduler.scheduleClientMonitor(client);
+        waitForIdle();
         if (started) {
             mScheduler.startPreparedClient(client.getCookie());
         }
@@ -789,6 +852,172 @@
         assertEquals(1,  client1.getFingerprints().size());
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testScheduleOperation_whenNoUser() {
+        mCurrentUserId = UserHandle.USER_NULL;
+
+        final BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+        when(nextClient.getTargetUserId()).thenReturn(0);
+
+        mScheduler.scheduleClientMonitor(nextClient);
+        waitForIdle();
+
+        assertThat(mUsersStoppedCount).isEqualTo(0);
+        assertThat(mStartedUsers).containsExactly(0);
+        verify(nextClient).start(any());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testScheduleOperation_whenNoUser_notStarted() {
+        mCurrentUserId = UserHandle.USER_NULL;
+        mStartOperationsFinish = false;
+
+        final BaseClientMonitor[] nextClients = new BaseClientMonitor[]{
+                mock(BaseClientMonitor.class),
+                mock(BaseClientMonitor.class),
+                mock(BaseClientMonitor.class)
+        };
+        for (BaseClientMonitor client : nextClients) {
+            when(client.getTargetUserId()).thenReturn(5);
+            mScheduler.scheduleClientMonitor(client);
+            waitForIdle();
+        }
+
+        assertThat(mUsersStoppedCount).isEqualTo(0);
+        assertThat(mStartedUsers).isEmpty();
+        assertThat(mStartUserClientCount).isEqualTo(1);
+        for (BaseClientMonitor client : nextClients) {
+            verify(client, never()).start(any());
+        }
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testScheduleOperation_whenNoUser_notStarted_andReset() {
+        mCurrentUserId = UserHandle.USER_NULL;
+        mStartOperationsFinish = false;
+
+        final BaseClientMonitor client = mock(BaseClientMonitor.class);
+
+        when(client.getTargetUserId()).thenReturn(5);
+
+        mScheduler.scheduleClientMonitor(client);
+        waitForIdle();
+
+        final TestStartUserClient startUserClient =
+                (TestStartUserClient) mScheduler.mCurrentOperation.getClientMonitor();
+        mScheduler.reset();
+
+        assertThat(mScheduler.mCurrentOperation).isNull();
+
+        final BiometricSchedulerOperation fakeOperation = new BiometricSchedulerOperation(
+                mock(BaseClientMonitor.class), new ClientMonitorCallback() {});
+        mScheduler.mCurrentOperation = fakeOperation;
+        startUserClient.mCallback.onClientFinished(startUserClient, true);
+
+        assertThat(fakeOperation).isSameInstanceAs(mScheduler.mCurrentOperation);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testScheduleOperation_whenSameUser() {
+        mCurrentUserId = 10;
+
+        BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+        when(nextClient.getTargetUserId()).thenReturn(mCurrentUserId);
+
+        mScheduler.scheduleClientMonitor(nextClient);
+
+        waitForIdle();
+
+        verify(nextClient).start(any());
+        assertThat(mUsersStoppedCount).isEqualTo(0);
+        assertThat(mStartedUsers).isEmpty();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testScheduleOperation_whenDifferentUser() {
+        mCurrentUserId = 10;
+
+        final int nextUserId = 11;
+        BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+        when(nextClient.getTargetUserId()).thenReturn(nextUserId);
+
+        mScheduler.scheduleClientMonitor(nextClient);
+
+        waitForIdle();
+        assertThat(mUsersStoppedCount).isEqualTo(1);
+
+        waitForIdle();
+        assertThat(mStartedUsers).containsExactly(nextUserId);
+
+        waitForIdle();
+        verify(nextClient).start(any());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testStartUser_alwaysStartsNextOperation() {
+        mCurrentUserId = UserHandle.USER_NULL;
+
+        BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+        when(nextClient.getTargetUserId()).thenReturn(10);
+
+        mScheduler.scheduleClientMonitor(nextClient);
+
+        waitForIdle();
+        verify(nextClient).start(any());
+
+        // finish first operation
+        mScheduler.getInternalCallback().onClientFinished(nextClient, true /* success */);
+        waitForIdle();
+
+        // schedule second operation but swap out the current operation
+        // before it runs so that it's not current when it's completion callback runs
+        nextClient = mock(BaseClientMonitor.class);
+        when(nextClient.getTargetUserId()).thenReturn(11);
+        mScheduler.scheduleClientMonitor(nextClient);
+
+        waitForIdle();
+        verify(nextClient).start(any());
+        assertThat(mStartedUsers).containsExactly(10, 11).inOrder();
+        assertThat(mUsersStoppedCount).isEqualTo(1);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testStartUser_failsClearsStopUserClient() {
+        mCurrentUserId = UserHandle.USER_NULL;
+
+        // When a stop user client fails, check that mStopUserClient
+        // is set to null to prevent the scheduler from getting stuck.
+        BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+        when(nextClient.getTargetUserId()).thenReturn(10);
+
+        mScheduler.scheduleClientMonitor(nextClient);
+
+        waitForIdle();
+        verify(nextClient).start(any());
+
+        // finish first operation
+        mScheduler.getInternalCallback().onClientFinished(nextClient, true /* success */);
+        waitForIdle();
+
+        // schedule second operation but swap out the current operation
+        // before it runs so that it's not current when it's completion callback runs
+        nextClient = mock(BaseClientMonitor.class);
+        when(nextClient.getTargetUserId()).thenReturn(11);
+        mShouldFailStopUser = true;
+        mScheduler.scheduleClientMonitor(nextClient);
+
+        waitForIdle();
+        assertThat(mStartedUsers).containsExactly(10, 11).inOrder();
+        assertThat(mUsersStoppedCount).isEqualTo(0);
+        assertThat(mScheduler.getStopUserClient()).isEqualTo(null);
+    }
 
     private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
         return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer));
@@ -1069,4 +1298,82 @@
             return mFingerprints;
         }
     }
+
+    private interface StopUserClientShouldFail {
+        boolean shouldFail();
+    }
+
+    private class TestStopUserClient extends StopUserClient<ISession> {
+        private StopUserClientShouldFail mShouldFailClient;
+        TestStopUserClient(@NonNull Context context,
+                @NonNull Supplier<ISession> lazyDaemon, @Nullable IBinder token, int userId,
+                int sensorId, @NonNull BiometricLogger logger,
+                @NonNull BiometricContext biometricContext,
+                @NonNull UserStoppedCallback callback, StopUserClientShouldFail shouldFail) {
+            super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
+            mShouldFailClient = shouldFail;
+        }
+
+        @Override
+        protected void startHalOperation() {
+
+        }
+
+        @Override
+        public void start(@NonNull ClientMonitorCallback callback) {
+            super.start(callback);
+            if (mShouldFailClient.shouldFail()) {
+                getCallback().onClientFinished(this, false /* success */);
+                // When the above fails, it means that the HAL has died, in this case we
+                // need to ensure the UserSwitchCallback correctly returns the NULL user handle.
+                mCurrentUserId = UserHandle.USER_NULL;
+            } else {
+                onUserStopped();
+            }
+        }
+
+        @Override
+        public void unableToStart() {
+
+        }
+    }
+
+    private static class TestStartUserClient extends StartUserClient<IFingerprint, ISession> {
+
+        @Mock
+        private ISession mSession;
+        private final boolean mShouldFinish;
+        ClientMonitorCallback mCallback;
+
+        TestStartUserClient(@NonNull Context context,
+                @NonNull Supplier<IFingerprint> lazyDaemon, @Nullable IBinder token, int userId,
+                int sensorId, @NonNull BiometricLogger logger,
+                @NonNull BiometricContext biometricContext,
+                @NonNull UserStartedCallback<ISession> callback, boolean shouldFinish) {
+            super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
+            mShouldFinish = shouldFinish;
+        }
+
+        @Override
+        protected void startHalOperation() {
+
+        }
+
+        @Override
+        public void start(@NonNull ClientMonitorCallback callback) {
+            super.start(callback);
+
+            mCallback = callback;
+            if (mShouldFinish) {
+                mUserStartedCallback.onUserStarted(
+                        getTargetUserId(), mSession, 1 /* halInterfaceVersion */);
+                callback.onClientFinished(this, true /* success */);
+            }
+        }
+
+        @Override
+        public void unableToStart() {
+
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
index 0815fe5..dd5d826 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
@@ -33,6 +33,9 @@
 import android.os.IBinder;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -40,6 +43,7 @@
 import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 
@@ -66,6 +70,9 @@
 
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
 
     private Handler mHandler;
     private UserAwareBiometricScheduler mScheduler;
@@ -122,6 +129,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void testScheduleOperation_whenNoUser() {
         mCurrentUserId = UserHandle.USER_NULL;
 
@@ -183,6 +191,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void testScheduleOperation_whenSameUser() {
         mCurrentUserId = 10;
 
@@ -199,6 +208,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void testScheduleOperation_whenDifferentUser() {
         mCurrentUserId = 10;
 
@@ -219,6 +229,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void testStartUser_alwaysStartsNextOperation() {
         BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
         when(nextClient.getTargetUserId()).thenReturn(10);
@@ -246,6 +257,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void testStartUser_failsClearsStopUserClient() {
         // When a stop user client fails, check that mStopUserClient
         // is set to null to prevent the scheduler from getting stuck.
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
index 3aaac2e..c8a5583d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.biometrics.sensors.face;
 
+import static android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
 import static android.hardware.face.FaceSensorProperties.TYPE_UNKNOWN;
@@ -234,6 +235,26 @@
     }
 
     @Test
+    public void testAuthenticateInBackground() throws Exception {
+        FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
+                .build();
+        initService();
+        mFaceService.mServiceWrapper.registerAuthenticators(List.of());
+        waitForRegistration();
+
+        mContext.getTestablePermissions().setPermission(
+                USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_DENIED);
+        mContext.getTestablePermissions().setPermission(
+                USE_BACKGROUND_FACE_AUTHENTICATION, PackageManager.PERMISSION_GRANTED);
+
+        final long operationId = 5;
+        mFaceService.mServiceWrapper.authenticateInBackground(mToken, operationId,
+                mFaceServiceReceiver, faceAuthenticateOptions);
+
+        assertThat(faceAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT);
+    }
+
+    @Test
     public void testOptionsForDetect() throws Exception {
         FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
                 .setOpPackageName(ComponentName.unflattenFromString(OP_PACKAGE_NAME)
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 3a3dd6e..84c3684 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.biometrics.sensors.face.aidl;
 
+import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
@@ -49,15 +50,22 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.TestableContext;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.OperationContextExt;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutTracker;
@@ -81,6 +89,8 @@
 @SmallTest
 public class FaceAuthenticationClientTest {
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final int USER_ID = 12;
     private static final long OP_ID = 32;
     private static final int WAKE_REASON = WakeReason.LIFT;
@@ -89,6 +99,9 @@
     @Rule
     public final TestableContext mContext = new TestableContext(
             InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Mock
     private ISession mHal;
@@ -105,6 +118,8 @@
     @Mock
     private ClientMonitorCallback mCallback;
     @Mock
+    private AuthenticationStateListeners mAuthenticationStateListeners;
+    @Mock
     private AidlResponseHandler mAidlResponseHandler;
     @Mock
     private ActivityTaskManager mActivityTaskManager;
@@ -120,6 +135,8 @@
     private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
     @Captor
     private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
+    @Captor
+    private ArgumentCaptor<Consumer<OperationContext>> mStartHalConsumerCaptor;
 
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
@@ -142,6 +159,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void authWithContext_v2() throws RemoteException {
         final FaceAuthenticationClient client = createClient(2);
         client.start(mCallback);
@@ -182,6 +200,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void notifyHalWhenContextChanges() throws RemoteException {
         final FaceAuthenticationClient client = createClient();
         client.start(mCallback);
@@ -204,6 +223,36 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void subscribeContextAndStartHal() throws RemoteException {
+        final FaceAuthenticationClient client = createClient();
+        client.start(mCallback);
+
+        verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+                mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
+
+        mStartHalConsumerCaptor.getValue().accept(
+                mOperationContextCaptor.getValue().toAidlContext());
+        final ArgumentCaptor<OperationContext> captor =
+                ArgumentCaptor.forClass(OperationContext.class);
+
+        verify(mHal).authenticateWithContext(eq(OP_ID), captor.capture());
+
+        OperationContext opContext = captor.getValue();
+
+        assertThat(opContext).isSameInstanceAs(
+                mOperationContextCaptor.getValue().toAidlContext());
+
+        mContextInjector.getValue().accept(opContext);
+
+        verify(mHal).onContextChanged(same(opContext));
+
+        client.stopHalOperation();
+
+        verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
+    }
+
+    @Test
     public void cancelsAuthWhenNotInForeground() throws Exception {
         final ActivityManager.RunningTaskInfo topTask = new ActivityManager.RunningTaskInfo();
         topTask.topActivity = new ComponentName("other", "thing");
@@ -264,6 +313,29 @@
         verify(mHal, never()).authenticate(anyInt());
     }
 
+    @Test
+    public void testAuthenticationStateListeners_onAuthenticationSucceeded()
+            throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+        final FaceAuthenticationClient client = createClient();
+        client.start(mCallback);
+        client.onAuthenticated(new Face("friendly", 1 /* faceId */, 2 /* deviceId */),
+                true /* authenticated */, new ArrayList<>());
+
+        verify(mAuthenticationStateListeners).onAuthenticationSucceeded(anyInt(), anyInt());
+    }
+
+    @Test
+    public void testAuthenticationStateListeners_onAuthenticationFailed() throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+        final FaceAuthenticationClient client = createClient();
+        client.start(mCallback);
+        client.onAuthenticated(new Face("friendly", 1 /* faceId */, 2 /* deviceId */),
+                false /* authenticated */, new ArrayList<>());
+
+        verify(mAuthenticationStateListeners).onAuthenticationFailed(anyInt(), anyInt());
+    }
+
     private FaceAuthenticationClient createClient() throws RemoteException {
         return createClient(2 /* version */, mClientMonitorCallbackConverter,
                 false /* allowBackgroundAuthentication */,
@@ -311,7 +383,8 @@
                 false /* requireConfirmation */,
                 mBiometricLogger, mBiometricContext, true /* isStrongBiometric */,
                 mUsageStats, lockoutTracker, allowBackgroundAuthentication,
-                null /* sensorPrivacyManager */, 0 /* biometricStrength */) {
+                null /* sensorPrivacyManager */, 0 /* biometricStrength */,
+                mAuthenticationStateListeners) {
             @Override
             protected ActivityTaskManager getActivityTaskManager() {
                 return mActivityTaskManager;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
index fbf0e13..e626f73 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
@@ -37,11 +37,16 @@
 import android.os.RemoteException;
 import android.os.Vibrator;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.testing.TestableContext;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.OperationContextExt;
@@ -71,6 +76,9 @@
     @Rule
     public final TestableContext mContext = new TestableContext(
             InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Mock
     private ISession mHal;
@@ -91,6 +99,8 @@
     @Captor
     private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
     @Captor
+    private ArgumentCaptor<Consumer<OperationContext>> mStartHalCaptor;
+    @Captor
     private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
 
     @Rule
@@ -114,6 +124,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void detectWithContext_v2() throws RemoteException {
         final FaceDetectClient client = createClient(2);
         client.start(mCallback);
@@ -132,6 +143,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void notifyHalWhenContextChanges() throws RemoteException {
         final FaceDetectClient client = createClient();
         client.start(mCallback);
@@ -154,6 +166,35 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void subscribeContextAndStartHal() throws RemoteException {
+        final FaceDetectClient client = createClient();
+        client.start(mCallback);
+
+        verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+                mStartHalCaptor.capture(), mContextInjector.capture(), any());
+
+        mStartHalCaptor.getValue().accept(mOperationContextCaptor.getValue().toAidlContext());
+        final ArgumentCaptor<OperationContext> captor =
+                ArgumentCaptor.forClass(OperationContext.class);
+
+        verify(mHal).detectInteractionWithContext(captor.capture());
+
+        OperationContext opContext = captor.getValue();
+
+        assertThat(opContext).isSameInstanceAs(
+                mOperationContextCaptor.getValue().toAidlContext());
+
+        mContextInjector.getValue().accept(opContext);
+
+        verify(mHal).onContextChanged(same(opContext));
+
+        client.stopHalOperation();
+
+        verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
+    }
+
+    @Test
     public void doesNotPlayHapticOnInteractionDetected() throws Exception {
         final FaceDetectClient client = createClient();
         client.start(mCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
index d662620..43ed07a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
@@ -28,17 +28,21 @@
 import static org.mockito.Mockito.when;
 
 import android.hardware.biometrics.common.OperationContext;
-import android.hardware.biometrics.face.FaceEnrollOptions;
 import android.hardware.biometrics.face.ISession;
 import android.hardware.face.Face;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.testing.TestableContext;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.OperationContextExt;
@@ -68,6 +72,9 @@
     @Rule
     public final TestableContext mContext = new TestableContext(
             InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Mock
     private ISession mHal;
@@ -89,6 +96,8 @@
     private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
     @Captor
     private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
+    @Captor
+    private ArgumentCaptor<Consumer<OperationContext>> mStartHalConsumer;
 
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
@@ -109,6 +118,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void enrollWithContext_v2() throws RemoteException {
         final FaceEnrollClient client = createClient(2);
         client.start(mCallback);
@@ -123,6 +133,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void notifyHalWhenContextChanges() throws RemoteException {
         final FaceEnrollClient client = createClient(3);
         client.start(mCallback);
@@ -145,6 +156,37 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void subscribeContextAndStartHal() throws RemoteException {
+        final FaceEnrollClient client = createClient(3);
+        client.start(mCallback);
+
+        verify(mBiometricContext).subscribe(
+                mOperationContextCaptor.capture(), mStartHalConsumer.capture(),
+                mContextInjector.capture(), any());
+
+        mStartHalConsumer.getValue().accept(mOperationContextCaptor.getValue().toAidlContext());
+        final ArgumentCaptor<OperationContext> captor =
+                ArgumentCaptor.forClass(OperationContext.class);
+
+        verify(mHal).enrollWithContext(any(), anyByte(), any(), any(), captor.capture());
+
+        OperationContext opContext = captor.getValue();
+
+        assertThat(opContext).isSameInstanceAs(
+                mOperationContextCaptor.getValue().toAidlContext());
+
+        mContextInjector.getValue().accept(opContext);
+
+        verify(mHal).onContextChanged(same(opContext));
+
+        client.stopHalOperation();
+
+        verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void enrollWithFaceOptions() throws RemoteException {
         final FaceEnrollClient client = createClient(4);
         client.start(mCallback);
@@ -152,6 +194,20 @@
         verify(mHal).enrollWithOptions(any());
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void enrollWithFaceOptionsAfterSubscribingContext() throws RemoteException {
+        final FaceEnrollClient client = createClient(4);
+        client.start(mCallback);
+
+        verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+                mStartHalConsumer.capture(), any(), any());
+
+        mStartHalConsumer.getValue().accept(mOperationContextCaptor.getValue().toAidlContext());
+
+        verify(mHal).enrollWithOptions(any());
+    }
+
     private FaceEnrollClient createClient() throws RemoteException {
         return createClient(200 /* version */);
     }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 8b1a291..7648bd17 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -36,8 +36,10 @@
 import android.hardware.biometrics.face.ISession;
 import android.hardware.biometrics.face.SensorProps;
 import android.hardware.face.HidlFaceSensorConfig;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserManager;
+import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
@@ -49,6 +51,7 @@
 import com.android.internal.R;
 import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -87,15 +90,14 @@
     private BiometricContext mBiometricContext;
     @Mock
     private BiometricStateCallback mBiometricStateCallback;
+    @Mock
+    private AuthenticationStateListeners mAuthenticationStateListeners;
 
+    private final TestLooper mLooper = new TestLooper();
     private SensorProps[] mSensorProps;
     private LockoutResetDispatcher mLockoutResetDispatcher;
     private FaceProvider mFaceProvider;
 
-    private static void waitForIdle() {
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-    }
-
     @Before
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
@@ -120,8 +122,9 @@
         mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
 
         mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
-                mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext,
-                mDaemon, false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */);
+                mAuthenticationStateListeners, mSensorProps, TAG, mLockoutResetDispatcher,
+                mBiometricContext, mDaemon, new Handler(mLooper.getLooper()),
+                false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */);
     }
 
     @Test
@@ -154,8 +157,9 @@
         final HidlFaceSensorConfig[] hidlFaceSensorConfig =
                 new HidlFaceSensorConfig[]{faceSensorConfig};
         mFaceProvider = new FaceProvider(mContext,
-                mBiometricStateCallback, hidlFaceSensorConfig, TAG,
+                mBiometricStateCallback, mAuthenticationStateListeners, hidlFaceSensorConfig, TAG,
                 mLockoutResetDispatcher, mBiometricContext, mDaemon,
+                new Handler(mLooper.getLooper()),
                 true /* resetLockoutRequiresChallenge */,
                 true /* testHalEnabled */);
 
@@ -210,4 +214,12 @@
             assertEquals(0, scheduler.getCurrentPendingCount());
         }
     }
+
+    private void waitForIdle() {
+        if (Flags.deHidl()) {
+            mLooper.dispatchAll();
+        } else {
+            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index e7f7195..fe9cd43 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -31,6 +31,7 @@
 import android.content.Context;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
 import android.hardware.biometrics.face.SensorProps;
 import android.hardware.face.FaceSensorPropertiesInternal;
@@ -40,6 +41,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
@@ -49,6 +51,7 @@
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.UserSwitchProvider;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -74,6 +77,8 @@
     @Mock
     private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
     @Mock
+    private UserSwitchProvider<IFace, ISession> mUserSwitchProvider;
+    @Mock
     private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;
     @Mock
     private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -84,16 +89,16 @@
     @Mock
     private AuthSessionCoordinator mAuthSessionCoordinator;
     @Mock
-    FaceProvider mFaceProvider;
+    private FaceProvider mFaceProvider;
     @Mock
-    BaseClientMonitor mClientMonitor;
+    private BaseClientMonitor mClientMonitor;
     @Mock
-    AidlSession mCurrentSession;
+    private AidlSession mCurrentSession;
 
     private final TestLooper mLooper = new TestLooper();
     private final LockoutCache mLockoutCache = new LockoutCache();
 
-    private UserAwareBiometricScheduler mScheduler;
+    private BiometricScheduler<IFace, ISession> mScheduler;
     private AidlResponseHandler mHalCallback;
 
     @Before
@@ -101,16 +106,26 @@
         MockitoAnnotations.initMocks(this);
 
         when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
-
         when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
 
-        mScheduler = new UserAwareBiometricScheduler(TAG,
-                new Handler(mLooper.getLooper()),
-                BiometricScheduler.SENSOR_TYPE_FACE,
-                null /* gestureAvailabilityDispatcher */,
-                mBiometricService,
-                () -> USER_ID,
-                mUserSwitchCallback);
+        if (Flags.deHidl()) {
+            mScheduler = new BiometricScheduler<>(
+                    new Handler(mLooper.getLooper()),
+                    BiometricScheduler.SENSOR_TYPE_FACE,
+                    null /* gestureAvailabilityDispatcher */,
+                    mBiometricService,
+                    2 /* recentOperationsLimit */,
+                    () -> USER_ID,
+                    mUserSwitchProvider);
+        } else {
+            mScheduler = new UserAwareBiometricScheduler<>(TAG,
+                    new Handler(mLooper.getLooper()),
+                    BiometricScheduler.SENSOR_TYPE_FACE,
+                    null /* gestureAvailabilityDispatcher */,
+                    mBiometricService,
+                    () -> USER_ID,
+                    mUserSwitchCallback);
+        }
         mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
                 mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
                 mHardwareUnavailableCallback);
@@ -146,18 +161,8 @@
     @Test
     public void onBinderDied_noErrorOnNullClient() {
         mLooper.dispatchAll();
-
-        final SensorProps sensorProps = new SensorProps();
-        sensorProps.commonProps = new CommonProps();
-        sensorProps.commonProps.sensorId = 1;
-        final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
-                sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
-                sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */,
-                sensorProps.sensorType, sensorProps.supportsDetectInteraction,
-                sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
-        final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext,
-                null /* handler */, internalProp, mLockoutResetDispatcher, mBiometricContext);
-        sensor.init(mLockoutResetDispatcher, mFaceProvider);
+        final Sensor sensor = getSensor();
+        mScheduler = sensor.getScheduler();
         mScheduler.reset();
 
         assertNull(mScheduler.getCurrentClient());
@@ -175,18 +180,8 @@
         when(mClientMonitor.getTargetUserId()).thenReturn(USER_ID);
         when(mClientMonitor.isInterruptable()).thenReturn(false);
 
-        final SensorProps sensorProps = new SensorProps();
-        sensorProps.commonProps = new CommonProps();
-        sensorProps.commonProps.sensorId = 1;
-        final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
-                sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
-                sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */,
-                sensorProps.sensorType, sensorProps.supportsDetectInteraction,
-                sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
-        final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null,
-                internalProp, mLockoutResetDispatcher, mBiometricContext, mCurrentSession);
-        sensor.init(mLockoutResetDispatcher, mFaceProvider);
-        mScheduler = (UserAwareBiometricScheduler) sensor.getScheduler();
+        final Sensor sensor = getSensor();
+        mScheduler = sensor.getScheduler();
         sensor.mCurrentSession = new AidlSession(0, mock(ISession.class),
                 USER_ID, mHalCallback);
 
@@ -206,4 +201,20 @@
         verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(eq(SENSOR_ID));
         verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
     }
+
+    private Sensor getSensor() {
+        final SensorProps sensorProps = new SensorProps();
+        sensorProps.commonProps = new CommonProps();
+        sensorProps.commonProps.sensorId = 1;
+        final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
+                sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
+                sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */,
+                sensorProps.sensorType, sensorProps.supportsDetectInteraction,
+                sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
+        final Sensor sensor = new Sensor(mFaceProvider, mContext,
+                null /* handler */, internalProp, mBiometricContext);
+        sensor.init(mLockoutResetDispatcher, mFaceProvider);
+
+        return sensor;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index e558c4d..949d6ee 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -38,17 +38,23 @@
 import android.os.Looper;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -69,6 +75,10 @@
     private static final int USER_ID = 20;
     private static final float FRR_THRESHOLD = 0.2f;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Mock
     private Context mContext;
     @Mock
@@ -81,6 +91,8 @@
     private BiometricContext mBiometricContext;
     @Mock
     private BiometricStateCallback mBiometricStateCallback;
+    @Mock
+    private AuthenticationStateListeners mAuthenticationStateListeners;
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -116,8 +128,8 @@
 
         Face10.sSystemClock = Clock.fixed(
                 Instant.ofEpochMilli(100), ZoneId.of("America/Los_Angeles"));
-        mFace10 = new Face10(mContext, mBiometricStateCallback, sensorProps,
-                mLockoutResetDispatcher, mHandler, mScheduler, mBiometricContext);
+        mFace10 = new Face10(mContext, mBiometricStateCallback, mAuthenticationStateListeners,
+                sensorProps, mLockoutResetDispatcher, mHandler, mScheduler, mBiometricContext);
         mBinder = new Binder();
     }
 
@@ -142,6 +154,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void scheduleGenerateChallenge_cachesResult() {
         final IFaceServiceReceiver[] mocks = IntStream.range(0, 3)
                 .mapToObj(i -> mock(IFaceServiceReceiver.class))
@@ -160,6 +173,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void scheduleRevokeChallenge_waitsUntilEmpty() {
         final long challenge = 22;
         final IFaceServiceReceiver[] mocks = IntStream.range(0, 3)
@@ -179,6 +193,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void scheduleRevokeChallenge_doesNotWaitForever() {
         mFace10.scheduleGenerateChallenge(
                 SENSOR_ID, USER_ID, mBinder, mock(IFaceServiceReceiver.class), TAG);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
index 4e43332..940fe69 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
@@ -45,7 +45,6 @@
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
-import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
@@ -88,9 +87,9 @@
     @Mock
     private IBiometricsFace mDaemon;
     @Mock
-    AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+    private AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
     @Mock
-    BiometricUtils<Face> mBiometricUtils;
+    private BiometricUtils<Face> mBiometricUtils;
 
     private final TestLooper mLooper = new TestLooper();
     private HidlToAidlSensorAdapter mHidlToAidlSensorAdapter;
@@ -118,20 +117,14 @@
         mContext.getOrCreateTestableResources();
 
         final String config = String.format("%d:8:15", SENSOR_ID);
-        final BiometricScheduler scheduler = new BiometricScheduler(TAG,
-                new Handler(mLooper.getLooper()),
-                BiometricScheduler.SENSOR_TYPE_FACE,
-                null /* gestureAvailabilityTracker */,
-                mBiometricService, 10 /* recentOperationsLimit */);
         final HidlFaceSensorConfig faceSensorConfig = new HidlFaceSensorConfig();
         faceSensorConfig.parse(config, mContext);
-        mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter(TAG, mFaceProvider,
+        mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter(mFaceProvider,
                 mContext, new Handler(mLooper.getLooper()), faceSensorConfig,
                 mLockoutResetDispatcherForSensor, mBiometricContext,
                 false /* resetLockoutRequiresChallenge */, mInternalCleanupAndGetFeatureRunnable,
                 mAuthSessionCoordinator, mDaemon, mAidlResponseHandlerCallback);
         mHidlToAidlSensorAdapter.init(mLockoutResetDispatcherForSensor, mFaceProvider);
-        mHidlToAidlSensorAdapter.setScheduler(scheduler);
         mHidlToAidlSensorAdapter.handleUserChanged(USER_ID);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 774ea5b..f96d9e8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
+import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
 
 import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR;
@@ -55,6 +56,7 @@
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -157,6 +159,9 @@
     private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
     @Captor
     private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
+    @Captor
+    private ArgumentCaptor<Consumer<OperationContext>> mStartHalConsumerCaptor;
+
     private final TestLooper mLooper = new TestLooper();
 
     @Before
@@ -174,12 +179,19 @@
     public void authNoContext_v1() throws RemoteException {
         final FingerprintAuthenticationClient client = createClient(1);
         client.start(mCallback);
+        if (Flags.deHidl()) {
+            verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+                    mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
+            mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
+                    .getValue().toAidlContext());
+        }
 
         verify(mHal).authenticate(eq(OP_ID));
         verify(mHal, never()).authenticateWithContext(anyLong(), any());
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void authWithContext_v2() throws RemoteException {
         final FingerprintAuthenticationClient client = createClient(2);
         client.start(mCallback);
@@ -261,15 +273,24 @@
     public void luxProbeWhenAwake() throws RemoteException {
         when(mBiometricContext.isAwake()).thenReturn(false);
         when(mBiometricContext.isAod()).thenReturn(false);
+
         final FingerprintAuthenticationClient client = createClient();
         client.start(mCallback);
+        if (Flags.deHidl()) {
+            verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+                    mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
+            mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
+                    .getValue().toAidlContext());
+        }
 
         final ArgumentCaptor<OperationContext> captor =
                 ArgumentCaptor.forClass(OperationContext.class);
         verify(mHal).authenticateWithContext(eq(OP_ID), captor.capture());
         OperationContext opContext = captor.getValue();
-        verify(mBiometricContext).subscribe(
-                mOperationContextCaptor.capture(), mContextInjector.capture());
+        if (!Flags.deHidl()) {
+            verify(mBiometricContext).subscribe(
+                    mOperationContextCaptor.capture(), mContextInjector.capture());
+        }
         assertThat(mOperationContextCaptor.getValue().toAidlContext())
                 .isSameInstanceAs(opContext);
 
@@ -304,6 +325,12 @@
         when(mBiometricContext.isAod()).thenReturn(false);
         final FingerprintAuthenticationClient client = createClient();
         client.start(mCallback);
+        if (Flags.deHidl()) {
+            verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+                    mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
+            mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
+                    .getValue().toAidlContext());
+        }
 
         verify(mLuxProbe, isAwake ? times(1) : never()).enable();
     }
@@ -314,13 +341,21 @@
         when(mBiometricContext.isAod()).thenReturn(true);
         final FingerprintAuthenticationClient client = createClient();
         client.start(mCallback);
+        if (Flags.deHidl()) {
+            verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+                    mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
+            mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
+                    .getValue().toAidlContext());
+        }
 
         final ArgumentCaptor<OperationContext> captor =
                 ArgumentCaptor.forClass(OperationContext.class);
         verify(mHal).authenticateWithContext(eq(OP_ID), captor.capture());
         OperationContext opContext = captor.getValue();
-        verify(mBiometricContext).subscribe(
-                mOperationContextCaptor.capture(), mContextInjector.capture());
+        if (!Flags.deHidl()) {
+            verify(mBiometricContext).subscribe(
+                    mOperationContextCaptor.capture(), mContextInjector.capture());
+        }
         assertThat(opContext).isSameInstanceAs(
                 mOperationContextCaptor.getValue().toAidlContext());
 
@@ -344,6 +379,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void notifyHalWhenContextChanges() throws RemoteException {
         final FingerprintAuthenticationClient client = createClient();
         client.start(mCallback);
@@ -366,6 +402,36 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void subscribeContextAndStartHal() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient();
+        client.start(mCallback);
+
+        verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+                mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
+
+        mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
+                .getValue().toAidlContext());
+        final ArgumentCaptor<OperationContext> captor =
+                ArgumentCaptor.forClass(OperationContext.class);
+
+        verify(mHal).authenticateWithContext(eq(OP_ID), captor.capture());
+
+        OperationContext opContext = captor.getValue();
+
+        assertThat(opContext).isSameInstanceAs(
+                mOperationContextCaptor.getValue().toAidlContext());
+
+        mContextInjector.getValue().accept(opContext);
+
+        verify(mHal).onContextChanged(same(opContext));
+
+        client.stopHalOperation();
+
+        verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
+    }
+
+    @Test
     public void showHideOverlay_cancel() throws RemoteException {
         showHideOverlay(c -> c.cancel());
     }
@@ -451,6 +517,29 @@
     }
 
     @Test
+    public void testAuthenticationStateListeners_onAuthenticationSucceeded()
+            throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+        final FingerprintAuthenticationClient client = createClient();
+        client.start(mCallback);
+        client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */,
+                2 /* deviceId */), true /* authenticated */, new ArrayList<>());
+
+        verify(mAuthenticationStateListeners).onAuthenticationSucceeded(anyInt(), anyInt());
+    }
+
+    @Test
+    public void testAuthenticationStateListeners_onAuthenticationFailed() throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+        final FingerprintAuthenticationClient client = createClient();
+        client.start(mCallback);
+        client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */,
+                2 /* deviceId */), false /* authenticated */, new ArrayList<>());
+
+        verify(mAuthenticationStateListeners).onAuthenticationFailed(anyInt(), anyInt());
+    }
+
+    @Test
     public void cancelsAuthWhenNotInForeground() throws Exception {
         final ActivityManager.RunningTaskInfo topTask = new ActivityManager.RunningTaskInfo();
         topTask.topActivity = new ComponentName("other", "thing");
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
index a467c84..9edb8dd 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -35,11 +35,16 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.testing.TestableContext;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.OperationContextExt;
@@ -67,6 +72,9 @@
     @Rule
     public final TestableContext mContext = new TestableContext(
             InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Mock
     private ISession mHal;
@@ -88,6 +96,8 @@
     private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
     @Captor
     private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
+    @Captor
+    private ArgumentCaptor<Consumer<OperationContext>> mStartHalConsumerCaptor;
 
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
@@ -109,6 +119,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void detectNoContext_v2() throws RemoteException {
         final FingerprintDetectClient client = createClient(2);
 
@@ -127,6 +138,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void notifyHalWhenContextChanges() throws RemoteException {
         final FingerprintDetectClient client = createClient();
         client.start(mCallback);
@@ -149,6 +161,31 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void subscribeContextAndStartHal() throws RemoteException {
+        final FingerprintDetectClient client = createClient();
+        client.start(mCallback);
+
+        verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+                mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
+
+        mStartHalConsumerCaptor.getValue().accept(
+                mOperationContextCaptor.getValue().toAidlContext());
+        final ArgumentCaptor<OperationContext> captor =
+                ArgumentCaptor.forClass(OperationContext.class);
+        verify(mHal).detectInteractionWithContext(captor.capture());
+        OperationContext opContext = captor.getValue();
+
+        assertThat(opContext).isSameInstanceAs(
+                mOperationContextCaptor.getValue().toAidlContext());
+        mContextInjector.getValue().accept(opContext);
+        verify(mHal).onContextChanged(same(opContext));
+
+        client.stopHalOperation();
+        verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
+    }
+
+    @Test
     public void testWhenListenerIsNull() {
         final AidlSession aidl = new AidlSession(0, mHal, USER_ID, mAidlResponseHandler);
         final FingerprintDetectClient client =  new FingerprintDetectClient(mContext, () -> aidl,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index e7d4a2e..951c9393 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -40,12 +40,17 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.TestableContext;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.CallbackWithProbe;
@@ -73,6 +78,9 @@
 public class FingerprintEnrollClientTest {
 
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
 
     private static final byte[] HAT = new byte[69];
     private static final int USER_ID = 8;
@@ -117,6 +125,8 @@
     private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
     @Captor
     private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
+    @Captor
+    private ArgumentCaptor<Consumer<OperationContext>> mStartHalConsumerCaptor;
 
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
@@ -140,6 +150,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void enrollWithContext_v2() throws RemoteException {
         final FingerprintEnrollClient client = createClient(2);
 
@@ -236,6 +247,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
     public void notifyHalWhenContextChanges() throws RemoteException {
         final FingerprintEnrollClient client = createClient();
         client.start(mCallback);
@@ -257,6 +269,30 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void subscribeContextAndStartHal() throws RemoteException {
+        final FingerprintEnrollClient client = createClient();
+        client.start(mCallback);
+
+        verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+                mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
+
+        mStartHalConsumerCaptor.getValue().accept(
+                mOperationContextCaptor.getValue().toAidlContext());
+        final ArgumentCaptor<OperationContext> captor =
+                ArgumentCaptor.forClass(OperationContext.class);
+        verify(mHal).enrollWithContext(any(), captor.capture());
+        OperationContext opContext = captor.getValue();
+
+        mContextInjector.getValue().accept(
+                mOperationContextCaptor.getValue().toAidlContext());
+        verify(mHal).onContextChanged(same(opContext));
+
+        client.stopHalOperation();
+        verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
+    }
+
+    @Test
     public void showHideOverlay_cancel() throws RemoteException {
         showHideOverlay(c -> c.cancel());
     }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index bf5986c..258be57 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -38,8 +38,10 @@
 import android.hardware.biometrics.fingerprint.SensorLocation;
 import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.fingerprint.HidlFingerprintSensorConfig;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserManager;
+import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
@@ -92,14 +94,12 @@
     @Mock
     private BiometricContext mBiometricContext;
 
+    private final TestLooper mLooper = new TestLooper();
+
     private SensorProps[] mSensorProps;
     private LockoutResetDispatcher mLockoutResetDispatcher;
     private FingerprintProvider mFingerprintProvider;
 
-    private static void waitForIdle() {
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-    }
-
     @Before
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
@@ -126,7 +126,8 @@
         mFingerprintProvider = new FingerprintProvider(mContext,
                 mBiometricStateCallback, mAuthenticationStateListeners, mSensorProps, TAG,
                 mLockoutResetDispatcher, mGestureAvailabilityDispatcher, mBiometricContext,
-                mDaemon, false /* resetLockoutRequiresHardwareAuthToken */,
+                mDaemon, new Handler(mLooper.getLooper()),
+                false /* resetLockoutRequiresHardwareAuthToken */,
                 true /* testHalEnabled */);
     }
 
@@ -159,6 +160,7 @@
                 mBiometricStateCallback, mAuthenticationStateListeners,
                 hidlFingerprintSensorConfigs, TAG, mLockoutResetDispatcher,
                 mGestureAvailabilityDispatcher, mBiometricContext, mDaemon,
+                new Handler(mLooper.getLooper()),
                 false /* resetLockoutRequiresHardwareAuthToken */,
                 true /* testHalEnabled */);
 
@@ -215,4 +217,12 @@
             assertEquals(0, scheduler.getCurrentPendingCount());
         }
     }
+
+    private void waitForIdle() {
+        if (Flags.deHidl()) {
+            mLooper.dispatchAll();
+        } else {
+            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index 126a05e..b4c2ee8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -32,14 +32,17 @@
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.common.CommonProps;
 import android.hardware.biometrics.face.SensorProps;
+import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
@@ -49,6 +52,7 @@
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.UserSwitchProvider;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
 import org.junit.Before;
@@ -75,6 +79,8 @@
     @Mock
     private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
     @Mock
+    private UserSwitchProvider<IFingerprint, ISession> mUserSwitchProvider;
+    @Mock
     private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;
     @Mock
     private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -92,11 +98,13 @@
     private AidlSession mCurrentSession;
     @Mock
     private BaseClientMonitor mClientMonitor;
+    @Mock
+    private HandlerThread mThread;
 
     private final TestLooper mLooper = new TestLooper();
     private final LockoutCache mLockoutCache = new LockoutCache();
 
-    private BiometricScheduler mScheduler;
+    private BiometricScheduler<IFingerprint, ISession> mScheduler;
     private AidlResponseHandler mHalCallback;
 
     @Before
@@ -105,14 +113,26 @@
 
         when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
         when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+        when(mThread.getLooper()).thenReturn(mLooper.getLooper());
 
-        mScheduler = new UserAwareBiometricScheduler(TAG,
-                new Handler(mLooper.getLooper()),
-                BiometricScheduler.SENSOR_TYPE_FP_OTHER,
-                null /* gestureAvailabilityDispatcher */,
-                mBiometricService,
-                () -> USER_ID,
-                mUserSwitchCallback);
+        if (Flags.deHidl()) {
+            mScheduler = new BiometricScheduler<>(
+                    new Handler(mLooper.getLooper()),
+                    BiometricScheduler.SENSOR_TYPE_FP_OTHER,
+                    null /* gestureAvailabilityDispatcher */,
+                    mBiometricService,
+                    2 /* recentOperationsLimit */,
+                    () -> USER_ID,
+                    mUserSwitchProvider);
+        } else {
+            mScheduler = new UserAwareBiometricScheduler<>(TAG,
+                    new Handler(mLooper.getLooper()),
+                    BiometricScheduler.SENSOR_TYPE_FP_OTHER,
+                    null /* gestureAvailabilityDispatcher */,
+                    mBiometricService,
+                    () -> USER_ID,
+                    mUserSwitchCallback);
+        }
         mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
                 mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
                 mHardwareUnavailableCallback);
@@ -153,18 +173,7 @@
         when(mClientMonitor.getTargetUserId()).thenReturn(USER_ID);
         when(mClientMonitor.isInterruptable()).thenReturn(false);
 
-        final SensorProps sensorProps = new SensorProps();
-        sensorProps.commonProps = new CommonProps();
-        sensorProps.commonProps.sensorId = 1;
-        final FingerprintSensorPropertiesInternal internalProp = new
-                FingerprintSensorPropertiesInternal(
-                        sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
-                        sensorProps.commonProps.maxEnrollmentsPerUser, null,
-                        sensorProps.sensorType, false /* resetLockoutRequiresHardwareAuthToken */);
-        final Sensor sensor = new Sensor("SensorTest", mFingerprintProvider, mContext,
-                null /* handler */, internalProp, mLockoutResetDispatcher,
-                mGestureAvailabilityDispatcher, mBiometricContext, mCurrentSession);
-        sensor.init(mGestureAvailabilityDispatcher, mLockoutResetDispatcher);
+        final Sensor sensor = getSensor();
         mScheduler = sensor.getScheduler();
         sensor.mCurrentSession = new AidlSession(0, mock(ISession.class),
                 USER_ID, mHalCallback);
@@ -185,4 +194,21 @@
         verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(eq(SENSOR_ID));
         verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
     }
+
+    private Sensor getSensor() {
+        final SensorProps sensorProps = new SensorProps();
+        sensorProps.commonProps = new CommonProps();
+        sensorProps.commonProps.sensorId = 1;
+        final FingerprintSensorPropertiesInternal internalProp = new
+                FingerprintSensorPropertiesInternal(
+                sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
+                sensorProps.commonProps.maxEnrollmentsPerUser, null,
+                sensorProps.sensorType, false /* resetLockoutRequiresHardwareAuthToken */);
+        final Sensor sensor = new Sensor(mFingerprintProvider, mContext,
+                null /* handler */, internalProp,
+                mBiometricContext, mCurrentSession);
+        sensor.init(mGestureAvailabilityDispatcher, mLockoutResetDispatcher);
+
+        return sensor;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
index 89a4961..cbbc545 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
@@ -36,12 +36,12 @@
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.HidlFingerprintSensorConfig;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 import android.testing.TestableContext;
 
-import androidx.annotation.NonNull;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
 
@@ -49,17 +49,11 @@
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.StartUserClient;
-import com.android.server.biometrics.sensors.StopUserClient;
-import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
-import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
 import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient;
 import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
 import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient;
@@ -111,60 +105,18 @@
     private BiometricUtils<Fingerprint> mBiometricUtils;
     @Mock
     private AuthenticationStateListeners mAuthenticationStateListeners;
+    @Mock
+    private HandlerThread mThread;
 
     private final TestLooper mLooper = new TestLooper();
     private HidlToAidlSensorAdapter mHidlToAidlSensorAdapter;
     private final TestableContext mContext = new TestableContext(
             ApplicationProvider.getApplicationContext());
 
-    private final UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback =
-            new UserAwareBiometricScheduler.UserSwitchCallback() {
-                @NonNull
-                @Override
-                public StopUserClient<?> getStopUserClient(int userId) {
-                    return new StopUserClient<IBiometricsFingerprint>(mContext,
-                            mHidlToAidlSensorAdapter::getIBiometricsFingerprint, null, USER_ID,
-                            SENSOR_ID, mLogger, mBiometricContext, () -> {}) {
-                        @Override
-                        protected void startHalOperation() {
-                            getCallback().onClientFinished(this, true /* success */);
-                        }
-
-                        @Override
-                        public void unableToStart() {}
-                    };
-                }
-
-                @NonNull
-                @Override
-                public StartUserClient<?, ?> getStartUserClient(int newUserId) {
-                    return new StartUserClient<IBiometricsFingerprint, AidlSession>(mContext,
-                            mHidlToAidlSensorAdapter::getIBiometricsFingerprint, null,
-                            USER_ID, SENSOR_ID,
-                            mLogger, mBiometricContext,
-                            (newUserId1, newUser, halInterfaceVersion) ->
-                                    mHidlToAidlSensorAdapter.handleUserChanged(newUserId1)) {
-                        @Override
-                        public void start(@NonNull ClientMonitorCallback callback) {
-                            super.start(callback);
-                            startHalOperation();
-                        }
-
-                        @Override
-                        protected void startHalOperation() {
-                            mUserStartedCallback.onUserStarted(USER_ID, null, 0);
-                            getCallback().onClientFinished(this, true /* success */);
-                        }
-
-                        @Override
-                        public void unableToStart() {}
-                    };
-                }
-            };;
-
     @Before
     public void setUp() throws RemoteException {
         when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+        when(mThread.getLooper()).thenReturn(mLooper.getLooper());
         doAnswer((answer) -> {
             mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
                     .onEnrollmentProgress(1 /* enrollmentId */, 0 /* remaining */);
@@ -175,26 +127,18 @@
         mContext.getOrCreateTestableResources();
 
         final String config = String.format("%d:2:15", SENSOR_ID);
-        final UserAwareBiometricScheduler scheduler = new UserAwareBiometricScheduler(TAG,
-                new Handler(mLooper.getLooper()),
-                BiometricScheduler.SENSOR_TYPE_FP_OTHER,
-                null /* gestureAvailabilityDispatcher */,
-                mBiometricService,
-                () -> USER_ID,
-                mUserSwitchCallback);
         final HidlFingerprintSensorConfig fingerprintSensorConfig =
                 new HidlFingerprintSensorConfig();
         fingerprintSensorConfig.parse(config, mContext);
-        mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter(TAG,
+        mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter(
                 mFingerprintProvider, mContext, new Handler(mLooper.getLooper()),
                 fingerprintSensorConfig, mLockoutResetDispatcherForSensor,
-                mGestureAvailabilityDispatcher, mBiometricContext,
-                false /* resetLockoutRequiresHardwareAuthToken */,
+                mBiometricContext, false /* resetLockoutRequiresHardwareAuthToken */,
                 mInternalCleanupRunnable, mAuthSessionCoordinator, mDaemon,
                 mAidlResponseHandlerCallback);
         mHidlToAidlSensorAdapter.init(mGestureAvailabilityDispatcher,
                 mLockoutResetDispatcherForSensor);
-        mHidlToAidlSensorAdapter.setScheduler(scheduler);
+
         mHidlToAidlSensorAdapter.handleUserChanged(USER_ID);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index ccbbaa5..5943832 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -33,19 +33,21 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.WindowManager;
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.input.flags.Flags;
 import com.android.server.LocalServices;
 import com.android.server.input.InputManagerInternal;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -58,6 +60,9 @@
     private static final String LANGUAGE_TAG = "en-US";
     private static final String LAYOUT_TYPE = "qwerty";
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock
     private InputManagerInternal mInputManagerInternalMock;
     @Mock
@@ -72,11 +77,12 @@
 
     @Before
     public void setUp() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER);
+
         MockitoAnnotations.initMocks(this);
         mInputManagerMockHelper = new InputManagerMockHelper(
                 TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock);
 
-        doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
         LocalServices.removeServiceForTest(InputManagerInternal.class);
         LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
 
@@ -129,11 +135,7 @@
         mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
                 /* displayId= */ 1);
         verify(mNativeWrapperMock).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
-        verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
-        doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
         mInputController.unregisterInputDevice(deviceToken);
-        verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(
-                eq(Display.INVALID_DISPLAY));
     }
 
     @Test
@@ -143,14 +145,11 @@
         mInputController.createMouse("mouse1", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
                 /* displayId= */ 1);
         verify(mNativeWrapperMock).openUinputMouse(eq("mouse1"), eq(1), eq(1), anyString());
-        verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
         final IBinder deviceToken2 = new Binder();
         mInputController.createMouse("mouse2", /*vendorId= */ 1, /*productId= */ 1, deviceToken2,
                 /* displayId= */ 2);
         verify(mNativeWrapperMock).openUinputMouse(eq("mouse2"), eq(1), eq(1), anyString());
-        verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(2));
         mInputController.unregisterInputDevice(deviceToken);
-        verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 276c832..5442af8 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -339,9 +339,10 @@
         LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
 
         mSetFlagsRule.initAllFlagsToReleaseConfigDefault();
+        mSetFlagsRule.enableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER);
 
-        doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
-        doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt());
+        doNothing().when(mInputManagerInternalMock)
+                .setMousePointerAccelerationEnabled(anyBoolean(), anyInt());
         doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt());
         LocalServices.removeServiceForTest(InputManagerInternal.class);
         LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
@@ -607,6 +608,20 @@
     }
 
     @Test
+    public void testIsInputDeviceOwnedByVirtualDevice() {
+        assertThat(mLocalService.isInputDeviceOwnedByVirtualDevice(INPUT_DEVICE_ID)).isFalse();
+
+        final int fd = 1;
+        mInputController.addDeviceForTesting(BINDER, fd,
+                InputController.InputDeviceDescriptor.TYPE_KEYBOARD, DISPLAY_ID_1, PHYS,
+                DEVICE_NAME_1, INPUT_DEVICE_ID);
+        assertThat(mLocalService.isInputDeviceOwnedByVirtualDevice(INPUT_DEVICE_ID)).isTrue();
+
+        mInputController.unregisterInputDevice(BINDER);
+        assertThat(mLocalService.isInputDeviceOwnedByVirtualDevice(INPUT_DEVICE_ID)).isFalse();
+    }
+
+    @Test
     public void getDeviceIdsForUid_noRunningApps_returnsNull() {
         assertThat(mLocalService.getDeviceIdsForUid(UID_1)).isEmpty();
         assertThat(mVdmNative.getDeviceIdsForUid(UID_1)).isEmpty();
@@ -814,6 +829,40 @@
     }
 
     @Test
+    public void getDisplayNameForPersistentDeviceId_nonExistentPeristentId_returnsNull() {
+        assertThat(mVdm.getDisplayNameForPersistentDeviceId("nonExistentPersistentId")).isNull();
+    }
+
+    @Test
+    public void getDisplayNameForPersistentDeviceId_defaultDevicePeristentId_returnsNull() {
+        assertThat(mVdm.getDisplayNameForPersistentDeviceId(
+                VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT))
+                .isNull();
+    }
+
+    @Test
+    public void getDisplayNameForPersistentDeviceId_validVirtualDevice_returnsCorrectId() {
+        mVdms.onCdmAssociationsChanged(List.of(mAssociationInfo));
+        CharSequence persistentIdDisplayName =
+                mVdm.getDisplayNameForPersistentDeviceId(mDeviceImpl.getPersistentDeviceId());
+        assertThat(persistentIdDisplayName.toString())
+                .isEqualTo(mAssociationInfo.getDisplayName().toString());
+    }
+
+    @Test
+    public void getDisplayNameForPersistentDeviceId_noVirtualDevice_returnsCorrectId() {
+        CharSequence displayName = "New display name for the new association";
+        mVdms.onCdmAssociationsChanged(List.of(
+                createAssociationInfo(2, AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
+                        displayName)));
+
+        CharSequence persistentIdDisplayName =
+                mVdm.getDisplayNameForPersistentDeviceId(
+                        VirtualDeviceImpl.createPersistentDeviceId(2));
+        assertThat(persistentIdDisplayName.toString()).isEqualTo(displayName.toString());
+    }
+
+    @Test
     public void onAppsOnVirtualDeviceChanged_singleVirtualDevice_listenersNotified() {
         ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
         mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
@@ -1284,7 +1333,6 @@
         mInputController.addDeviceForTesting(BINDER, fd,
                 InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS,
                 DEVICE_NAME_1, INPUT_DEVICE_ID);
-        doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
         assertThat(mDeviceImpl.sendButtonEvent(BINDER,
                 new VirtualMouseButtonEvent.Builder()
                         .setButtonCode(buttonCode)
@@ -1314,7 +1362,6 @@
         mInputController.addDeviceForTesting(BINDER, fd,
                 InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1,
                 INPUT_DEVICE_ID);
-        doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
         assertThat(mDeviceImpl.sendRelativeEvent(BINDER,
                 new VirtualMouseRelativeEvent.Builder()
                         .setRelativeX(x)
@@ -1345,7 +1392,6 @@
         mInputController.addDeviceForTesting(BINDER, fd,
                 InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1,
                 INPUT_DEVICE_ID);
-        doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
         assertThat(mDeviceImpl.sendScrollEvent(BINDER,
                 new VirtualMouseScrollEvent.Builder()
                         .setXAxisMovement(x)
@@ -1956,7 +2002,7 @@
                         mRunningAppsChangedCallback,
                         params,
                         new DisplayManagerGlobal(mIDisplayManager),
-                        new VirtualCameraController());
+                        new VirtualCameraController(DEVICE_POLICY_DEFAULT));
         mVdms.addVirtualDevice(virtualDeviceImpl);
         assertThat(virtualDeviceImpl.getAssociationId()).isEqualTo(mAssociationInfo.getId());
         assertThat(virtualDeviceImpl.getPersistentDeviceId())
@@ -1979,8 +2025,14 @@
     }
 
     private AssociationInfo createAssociationInfo(int associationId, String deviceProfile) {
+        return createAssociationInfo(
+                associationId, deviceProfile, /* displayName= */ deviceProfile);
+    }
+
+    private AssociationInfo createAssociationInfo(int associationId, String deviceProfile,
+            CharSequence displayName) {
         return new AssociationInfo(associationId, /* userId= */ 0, /* packageName=*/ null,
-                /* tag= */ null, MacAddress.BROADCAST_ADDRESS, /* displayName= */ "", deviceProfile,
+                /* tag= */ null, MacAddress.BROADCAST_ADDRESS, displayName, deviceProfile,
                 /* associatedDevice= */ null, /* selfManaged= */ true,
                 /* notifyOnDeviceNearby= */ false, /* revoked= */ false, /* pending= */ false,
                 /* timeApprovedMs= */0, /* lastTimeConnectedMs= */0, /* systemDataSyncFlags= */ -1);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
index 9b28b81..81981e6 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
@@ -16,27 +16,35 @@
 
 package com.android.server.companion.virtual.camera;
 
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.camera.VirtualCameraConfig.SENSOR_ORIENTATION_0;
+import static android.companion.virtual.camera.VirtualCameraConfig.SENSOR_ORIENTATION_90;
+import static android.graphics.ImageFormat.YUV_420_888;
+import static android.graphics.PixelFormat.RGBA_8888;
+import static android.hardware.camera2.CameraMetadata.LENS_FACING_BACK;
+import static android.hardware.camera2.CameraMetadata.LENS_FACING_FRONT;
+
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.annotation.NonNull;
 import android.companion.virtual.camera.VirtualCameraCallback;
 import android.companion.virtual.camera.VirtualCameraConfig;
-import android.companion.virtual.camera.VirtualCameraStreamConfig;
 import android.companion.virtualcamera.IVirtualCameraService;
 import android.companion.virtualcamera.VirtualCameraConfiguration;
-import android.graphics.ImageFormat;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.platform.test.annotations.Presubmit;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.view.Surface;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
 
 import org.junit.After;
 import org.junit.Before;
@@ -49,21 +57,30 @@
 import java.util.List;
 
 @Presubmit
-@RunWith(AndroidTestingRunner.class)
+@RunWith(JUnitParamsRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class VirtualCameraControllerTest {
 
     private static final String CAMERA_NAME_1 = "Virtual camera 1";
     private static final int CAMERA_WIDTH_1 = 100;
     private static final int CAMERA_HEIGHT_1 = 200;
+    private static final int CAMERA_FORMAT_1 = YUV_420_888;
+    private static final int CAMERA_MAX_FPS_1 = 30;
+    private static final int CAMERA_SENSOR_ORIENTATION_1 = SENSOR_ORIENTATION_0;
+    private static final int CAMERA_LENS_FACING_1 = LENS_FACING_BACK;
 
     private static final String CAMERA_NAME_2 = "Virtual camera 2";
     private static final int CAMERA_WIDTH_2 = 400;
     private static final int CAMERA_HEIGHT_2 = 600;
-    private static final int CAMERA_FORMAT = ImageFormat.YUV_420_888;
+    private static final int CAMERA_FORMAT_2 = RGBA_8888;
+    private static final int CAMERA_MAX_FPS_2 = 60;
+    private static final int CAMERA_SENSOR_ORIENTATION_2 = SENSOR_ORIENTATION_90;
+    private static final int CAMERA_LENS_FACING_2 = LENS_FACING_FRONT;
 
     @Mock
     private IVirtualCameraService mVirtualCameraServiceMock;
+    @Mock
+    private VirtualCameraCallback mVirtualCameraCallbackMock;
 
     private VirtualCameraController mVirtualCameraController;
     private final HandlerExecutor mCallbackHandler =
@@ -72,7 +89,8 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mVirtualCameraController = new VirtualCameraController(mVirtualCameraServiceMock);
+        mVirtualCameraController = new VirtualCameraController(mVirtualCameraServiceMock,
+                DEVICE_POLICY_CUSTOM);
         when(mVirtualCameraServiceMock.registerCamera(any(), any())).thenReturn(true);
     }
 
@@ -81,10 +99,12 @@
         mVirtualCameraController.close();
     }
 
+    @Parameters(method = "getAllLensFacingDirections")
     @Test
-    public void registerCamera_registersCamera() throws Exception {
+    public void registerCamera_registersCamera(int lensFacing) throws Exception {
         mVirtualCameraController.registerCamera(createVirtualCameraConfig(
-                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_NAME_1));
+                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1, CAMERA_NAME_1,
+                CAMERA_SENSOR_ORIENTATION_1, lensFacing));
 
         ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor =
                 ArgumentCaptor.forClass(VirtualCameraConfiguration.class);
@@ -92,13 +112,15 @@
         VirtualCameraConfiguration virtualCameraConfiguration = configurationCaptor.getValue();
         assertThat(virtualCameraConfiguration.supportedStreamConfigs.length).isEqualTo(1);
         assertVirtualCameraConfiguration(virtualCameraConfiguration, CAMERA_WIDTH_1,
-                CAMERA_HEIGHT_1, CAMERA_FORMAT);
+                CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1, CAMERA_SENSOR_ORIENTATION_1,
+                lensFacing);
     }
 
     @Test
     public void unregisterCamera_unregistersCamera() throws Exception {
         VirtualCameraConfig config = createVirtualCameraConfig(
-                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_NAME_1);
+                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1, CAMERA_NAME_1,
+                CAMERA_SENSOR_ORIENTATION_1, CAMERA_LENS_FACING_1);
         mVirtualCameraController.registerCamera(config);
 
         mVirtualCameraController.unregisterCamera(config);
@@ -109,9 +131,11 @@
     @Test
     public void close_unregistersAllCameras() throws Exception {
         mVirtualCameraController.registerCamera(createVirtualCameraConfig(
-                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_NAME_1));
+                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1, CAMERA_NAME_1,
+                CAMERA_SENSOR_ORIENTATION_1, CAMERA_LENS_FACING_1));
         mVirtualCameraController.registerCamera(createVirtualCameraConfig(
-                CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT, CAMERA_NAME_2));
+                CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT_2, CAMERA_MAX_FPS_2, CAMERA_NAME_2,
+                CAMERA_SENSOR_ORIENTATION_2, CAMERA_LENS_FACING_2));
 
         mVirtualCameraController.close();
 
@@ -123,38 +147,65 @@
                 configurationCaptor.getAllValues();
         assertThat(virtualCameraConfigurations).hasSize(2);
         assertVirtualCameraConfiguration(virtualCameraConfigurations.get(0), CAMERA_WIDTH_1,
-                CAMERA_HEIGHT_1, CAMERA_FORMAT);
+                CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1, CAMERA_SENSOR_ORIENTATION_1,
+                CAMERA_LENS_FACING_1);
         assertVirtualCameraConfiguration(virtualCameraConfigurations.get(1), CAMERA_WIDTH_2,
-                CAMERA_HEIGHT_2, CAMERA_FORMAT);
+                CAMERA_HEIGHT_2, CAMERA_FORMAT_2, CAMERA_MAX_FPS_2, CAMERA_SENSOR_ORIENTATION_2,
+                CAMERA_LENS_FACING_2);
+    }
+
+    @Parameters(method = "getAllLensFacingDirections")
+    @Test
+    public void registerMultipleSameLensFacingCameras_withCustomCameraPolicy_throwsException(
+            int lensFacing) {
+        mVirtualCameraController.registerCamera(createVirtualCameraConfig(
+                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1, CAMERA_NAME_1,
+                CAMERA_SENSOR_ORIENTATION_1, lensFacing));
+        assertThrows(IllegalArgumentException.class,
+                () -> mVirtualCameraController.registerCamera(createVirtualCameraConfig(
+                        CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT_2, CAMERA_MAX_FPS_2,
+                        CAMERA_NAME_2, CAMERA_SENSOR_ORIENTATION_2, lensFacing)));
+    }
+
+    @Parameters(method = "getAllLensFacingDirections")
+    @Test
+    public void registerCamera_withDefaultCameraPolicy_throwsException(int lensFacing) {
+        mVirtualCameraController.close();
+        mVirtualCameraController = new VirtualCameraController(
+                mVirtualCameraServiceMock, DEVICE_POLICY_DEFAULT);
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mVirtualCameraController.registerCamera(createVirtualCameraConfig(
+                        CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1,
+                        CAMERA_NAME_1, CAMERA_SENSOR_ORIENTATION_1, lensFacing)));
     }
 
     private VirtualCameraConfig createVirtualCameraConfig(
-            int width, int height, int format, String displayName) {
-        return new VirtualCameraConfig.Builder()
-                .addStreamConfig(width, height, format)
-                .setName(displayName)
-                .setVirtualCameraCallback(mCallbackHandler, createNoOpCallback())
+            int width, int height, int format, int maximumFramesPerSecond,
+            String name, int sensorOrientation, int lensFacing) {
+        return new VirtualCameraConfig.Builder(name)
+                .addStreamConfig(width, height, format, maximumFramesPerSecond)
+                .setVirtualCameraCallback(mCallbackHandler, mVirtualCameraCallbackMock)
+                .setSensorOrientation(sensorOrientation)
+                .setLensFacing(lensFacing)
                 .build();
     }
 
     private static void assertVirtualCameraConfiguration(
-            VirtualCameraConfiguration configuration, int width, int height, int format) {
+            VirtualCameraConfiguration configuration, int width, int height, int format,
+            int maxFps, int sensorOrientation, int lensFacing) {
         assertThat(configuration.supportedStreamConfigs[0].width).isEqualTo(width);
         assertThat(configuration.supportedStreamConfigs[0].height).isEqualTo(height);
         assertThat(configuration.supportedStreamConfigs[0].pixelFormat).isEqualTo(format);
+        assertThat(configuration.supportedStreamConfigs[0].maxFps).isEqualTo(maxFps);
+        assertThat(configuration.sensorOrientation).isEqualTo(sensorOrientation);
+        assertThat(configuration.lensFacing).isEqualTo(lensFacing);
     }
 
-    private static VirtualCameraCallback createNoOpCallback() {
-        return new VirtualCameraCallback() {
-
-            @Override
-            public void onStreamConfigured(
-                    int streamId,
-                    @NonNull Surface surface,
-                    @NonNull VirtualCameraStreamConfig streamConfig) {}
-
-            @Override
-            public void onStreamClosed(int streamId) {}
+    private static Integer[] getAllLensFacingDirections() {
+        return new Integer[] {
+                LENS_FACING_BACK,
+                LENS_FACING_FRONT
         };
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraStreamConfigTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraStreamConfigTest.java
index d9a38eb..206c111 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraStreamConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraStreamConfigTest.java
@@ -35,19 +35,20 @@
 
     private static final int VGA_WIDTH = 640;
     private static final int VGA_HEIGHT = 480;
+    private static final int MAX_FPS_1 = 30;
 
     private static final int QVGA_WIDTH = 320;
     private static final int QVGA_HEIGHT = 240;
+    private static final int MAX_FPS_2 = 60;
 
     @Test
     public void testEquals() {
         VirtualCameraStreamConfig vgaYuvStreamConfig = new VirtualCameraStreamConfig(VGA_WIDTH,
-                VGA_HEIGHT,
-                ImageFormat.YUV_420_888);
+                VGA_HEIGHT, ImageFormat.YUV_420_888, MAX_FPS_1);
         VirtualCameraStreamConfig qvgaYuvStreamConfig = new VirtualCameraStreamConfig(QVGA_WIDTH,
-                QVGA_HEIGHT, ImageFormat.YUV_420_888);
+                QVGA_HEIGHT, ImageFormat.YUV_420_888, MAX_FPS_2);
         VirtualCameraStreamConfig vgaRgbaStreamConfig = new VirtualCameraStreamConfig(VGA_WIDTH,
-                VGA_HEIGHT, PixelFormat.RGBA_8888);
+                VGA_HEIGHT, PixelFormat.RGBA_8888, MAX_FPS_1);
 
         new EqualsTester()
                 .addEqualityGroup(vgaYuvStreamConfig, reparcel(vgaYuvStreamConfig))
@@ -66,6 +67,4 @@
             parcel.recycle();
         }
     }
-
-
 }
diff --git a/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java
index d850c73..57f3cc0 100644
--- a/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java
@@ -22,6 +22,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 
+import androidx.annotation.NonNull;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -40,10 +41,12 @@
 public final class CredentialManagerServiceTest {
 
     Context mContext = null;
+    MockSettingsWrapper mSettingsWrapper = null;
 
     @Before
     public void setUp() throws CertificateException {
         mContext = ApplicationProvider.getApplicationContext();
+        mSettingsWrapper = new MockSettingsWrapper(mContext);
     }
 
     @Test
@@ -81,7 +84,8 @@
                 Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
                 "com.example.test/com.example.test.TestActivity");
 
-        CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test");
+        CredentialManagerService.updateProvidersWhenPackageRemoved(
+                mSettingsWrapper, "com.example.test");
 
         assertThat(getSettingsKey(Settings.Secure.AUTOFILL_SERVICE)).isEqualTo("");
         assertThat(getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE))
@@ -101,7 +105,8 @@
         setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE, testCredentialValue);
         setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, testCredentialPrimaryValue);
 
-        CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test3");
+        CredentialManagerService.updateProvidersWhenPackageRemoved(
+                mSettingsWrapper, "com.example.test3");
 
         // Since the provider removed was not a primary provider then we should do nothing.
         assertThat(getSettingsKey(Settings.Secure.AUTOFILL_SERVICE))
@@ -125,7 +130,8 @@
                 Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
                 "com.example.test/com.example.test.TestActivity");
 
-        CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test");
+        CredentialManagerService.updateProvidersWhenPackageRemoved(
+                mSettingsWrapper, "com.example.test");
 
         assertThat(getSettingsKey(Settings.Secure.AUTOFILL_SERVICE)).isEqualTo("");
         assertThat(getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE))
@@ -144,7 +150,8 @@
         setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE, testCredentialValue);
         setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, testCredentialPrimaryValue);
 
-        CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test3");
+        CredentialManagerService.updateProvidersWhenPackageRemoved(
+                mSettingsWrapper, "com.example.test3");
 
         // Since the provider removed was not a primary provider then we should do nothing.
         assertCredentialPropertyEquals(
@@ -176,12 +183,36 @@
         assertThat(actualValueSet).isEqualTo(newValueSet);
     }
 
-    private void setSettingsKey(String key, String value) {
-        assertThat(Settings.Secure.putString(mContext.getContentResolver(), key, value)).isTrue();
+    private void setSettingsKey(String name, String value) {
+        assertThat(
+                        mSettingsWrapper.putStringForUser(
+                                name, value, UserHandle.myUserId(), true))
+                .isTrue();
     }
 
-    private String getSettingsKey(String key) {
-        return Settings.Secure.getStringForUser(
-                mContext.getContentResolver(), key, UserHandle.myUserId());
+    private String getSettingsKey(String name) {
+        return mSettingsWrapper.getStringForUser(name, UserHandle.myUserId());
+    }
+
+    private static final class MockSettingsWrapper
+            extends CredentialManagerService.SettingsWrapper {
+
+        MockSettingsWrapper(@NonNull Context context) {
+            super(context);
+        }
+
+        /** Updates the string value of a system setting */
+        @Override
+        public boolean putStringForUser(
+                String name,
+                String value,
+                int userHandle,
+                boolean overrideableByRestore) {
+            // This will ensure that when the settings putStringForUser method is called by
+            // CredentialManagerService that the overrideableByRestore bit is true.
+            assertThat(overrideableByRestore).isTrue();
+
+            return Settings.Secure.putStringForUser(getContentResolver(), name, value, userHandle);
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java b/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
index 6bc0fbf..0f3f27a 100644
--- a/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
@@ -39,8 +39,8 @@
 import android.credentials.CredentialOption;
 import android.credentials.GetCredentialException;
 import android.credentials.GetCredentialResponse;
-import android.credentials.ui.GetCredentialProviderData;
-import android.credentials.ui.ProviderPendingIntentResponse;
+import android.credentials.selection.GetCredentialProviderData;
+import android.credentials.selection.ProviderPendingIntentResponse;
 import android.net.Uri;
 import android.os.Bundle;
 import android.service.credentials.CallingAppInfo;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 543fa57..1bd6e29 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -659,6 +659,64 @@
         mTestLooper.dispatchAll();
         assertThat(mActiveMediaSessionsPaused).isFalse();
     }
+    @Test
+    public void handleRoutingInformation_physicalAddressOfSender_Tv_activeSourceChange() {
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+                HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+                HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mPowerManager.setInteractive(true);
+        // Physical address reported in this message is the same as message sender's (TV) physical
+        // address.
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRoutingInformation(Constants.ADDR_TV, 0x0000);
+
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message))
+                .isEqualTo(Constants.HANDLED);
+        assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
+        assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
+                0x0000);
+        // Active source's logical address is invalidated.
+        // See {@link HdmiCecLocalDevicePlayback#handleRoutingChangeAndInformation}.
+        assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(
+                ADDR_INVALID);
+        assertThat(mPowerManager.isInteractive()).isFalse();
+    }
+
+    @Test
+    public void handleRoutingInformation_physicalAddressOfSender_notTv_noActiveSourceChange() {
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+                HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+                HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        // Add a device to the network and assert that this device is included in the list of
+        // devices.
+        HdmiDeviceInfo infoPlayback = HdmiDeviceInfo.cecDeviceBuilder()
+                .setLogicalAddress(Constants.ADDR_PLAYBACK_3)
+                .setPhysicalAddress(0x1000)
+                .setPortId(PORT_1)
+                .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK)
+                .setVendorId(0x1000)
+                .setDisplayName("Playback 3")
+                .build();
+        mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoPlayback);
+        mPowerManager.setInteractive(true);
+        // Physical address reported in this message is the same as message sender's (Playback_3)
+        // physical address.
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRoutingInformation(Constants.ADDR_PLAYBACK_3, 0x1000);
+
+        assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message))
+                .isEqualTo(Constants.HANDLED);
+        assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
+                mPlaybackPhysicalAddress);
+        assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(
+                mPlaybackLogicalAddress);
+        assertThat(mPowerManager.isInteractive()).isTrue();
+    }
 
     @Test
     public void handleSetStreamPath() {
@@ -1775,6 +1833,11 @@
 
     @Test
     public void wakeUp_hotPlugIn_invokesDeviceDiscoveryOnce() {
+        // There might be a leftover HotplugDetectionAction that can interfere with the test.
+        mHdmiCecLocalDevicePlayback.removeAction(HotplugDetectionAction.class);
+
+        long pollingDelay = TimeUnit.SECONDS.toMillis(60);
+        mHdmiCecController.setPollDevicesDelay(pollingDelay);
         mNativeWrapper.setPollAddressResponse(Constants.ADDR_PLAYBACK_2, SendMessageResult.SUCCESS);
         mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
         mTestLooper.dispatchAll();
@@ -1783,6 +1846,14 @@
         mTestLooper.dispatchAll();
 
         assertThat(mHdmiCecLocalDevicePlayback.getActions(DeviceDiscoveryAction.class)).hasSize(1);
+        mTestLooper.moveTimeForward(pollingDelay);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage givePhysicalAddress = HdmiCecMessageBuilder.buildGivePhysicalAddress(
+                Constants.ADDR_PLAYBACK_1,
+                Constants.ADDR_PLAYBACK_2);
+        assertThat(mNativeWrapper.getResultMessages().stream()
+                .filter(message -> message.equals(givePhysicalAddress)).count()).isEqualTo(1);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 0973d46..67ae998 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -25,6 +25,7 @@
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -169,6 +170,11 @@
                     }
 
                     @Override
+                    boolean isPowerOnOrTransient() {
+                        return true;
+                    }
+
+                    @Override
                     void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
                         mDeviceEventListeners.add(new DeviceEventListener(device, status));
                     }
@@ -1807,4 +1813,69 @@
         // TV should only send <Give Osd Name> once
         assertEquals(1, Collections.frequency(mNativeWrapper.getResultMessages(), giveOsdName));
     }
+
+    @Test
+    public void initiateCecByWakeupMessage_selectInternalSourceAfterDelay_broadcastsActiveSource() {
+        HdmiCecMessage activeSourceFromTv =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_WAKE_UP_MESSAGE);
+        mTestLooper.dispatchAll();
+
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        mHdmiCecLocalDeviceTv.deviceSelect(ADDR_TV, new TestCallback());
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(activeSourceFromTv);
+    }
+
+    @Test
+    public void initiateCecByWakeupMessage_selectInternalSource_doesNotBroadcastActiveSource() {
+        HdmiCecMessage activeSourceFromTv =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_WAKE_UP_MESSAGE);
+        mTestLooper.dispatchAll();
+
+        mHdmiCecLocalDeviceTv.deviceSelect(ADDR_TV, new TestCallback());
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
+    }
+
+    @Test
+    public void handleStandby_fromActiveSource_standby() {
+        mPowerManager.setInteractive(true);
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mHdmiControlService.setActiveSource(ADDR_PLAYBACK_1, 0x1000,
+                "HdmiCecLocalDeviceTvTest");
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage standbyMessage = HdmiCecMessageBuilder.buildStandby(ADDR_PLAYBACK_1,
+                ADDR_TV);
+        assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(standbyMessage))
+                .isEqualTo(Constants.HANDLED);
+        mTestLooper.dispatchAll();
+
+        assertThat(mPowerManager.isInteractive()).isFalse();
+    }
+
+    @Test
+    public void handleStandby_fromNonActiveSource_noStandby() {
+        mPowerManager.setInteractive(true);
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mHdmiControlService.setActiveSource(ADDR_PLAYBACK_2, 0x2000,
+                "HdmiCecLocalDeviceTvTest");
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage standbyMessage = HdmiCecMessageBuilder.buildStandby(ADDR_PLAYBACK_1,
+                ADDR_TV);
+        assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(standbyMessage))
+                .isEqualTo(Constants.HANDLED);
+        mTestLooper.dispatchAll();
+
+        assertThat(mPowerManager.isInteractive()).isTrue();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java
deleted file mode 100644
index d82e6ab..0000000
--- a/services/tests/servicestests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.inputmethod;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import android.icu.util.ULocale;
-import android.util.ArrayMap;
-import android.util.AtomicFile;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodSubtype;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AdditionalSubtypeUtilsTest {
-
-    @Test
-    public void testSaveAndLoad() throws Exception {
-        // Prepares the data to be saved.
-        InputMethodSubtype subtype1 = new InputMethodSubtype.InputMethodSubtypeBuilder()
-                .setSubtypeNameOverride("Subtype1")
-                .setLanguageTag("en-US")
-                .build();
-        InputMethodSubtype subtype2 = new InputMethodSubtype.InputMethodSubtypeBuilder()
-                .setSubtypeNameOverride("Subtype2")
-                .setLanguageTag("zh-CN")
-                .setPhysicalKeyboardHint(new ULocale("en_US"), "qwerty")
-                .build();
-        String fakeImeId = "fakeImeId";
-        ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-        methodMap.put(fakeImeId, new InputMethodInfo("", "", "", ""));
-        ArrayMap<String, List<InputMethodSubtype>> allSubtypes = new ArrayMap<>();
-        allSubtypes.put(fakeImeId, List.of(subtype1, subtype2));
-
-        // Save & load.
-        AtomicFile atomicFile = new AtomicFile(
-                new File(InstrumentationRegistry.getContext().getCacheDir(), "subtypes.xml"));
-        AdditionalSubtypeUtils.saveToFile(allSubtypes, methodMap, atomicFile);
-        ArrayMap<String, List<InputMethodSubtype>> loadedSubtypes = new ArrayMap<>();
-        AdditionalSubtypeUtils.loadFromFile(loadedSubtypes, atomicFile);
-
-        // Verifies the loaded data.
-        assertEquals(1, loadedSubtypes.size());
-        List<InputMethodSubtype> subtypes = loadedSubtypes.get(fakeImeId);
-        assertNotNull(subtypes);
-        assertEquals(2, subtypes.size());
-
-        verifySubtype(subtypes.get(0), subtype1);
-        verifySubtype(subtypes.get(1), subtype2);
-    }
-
-    private void verifySubtype(InputMethodSubtype subtype, InputMethodSubtype expectedSubtype) {
-        assertEquals(expectedSubtype.getLanguageTag(), subtype.getLanguageTag());
-        assertEquals(expectedSubtype.getPhysicalKeyboardHintLanguageTag(),
-                subtype.getPhysicalKeyboardHintLanguageTag());
-        assertEquals(expectedSubtype.getPhysicalKeyboardHintLayoutType(),
-                subtype.getPhysicalKeyboardHintLayoutType());
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java
deleted file mode 100644
index 6eedeea..0000000
--- a/services/tests/servicestests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.inputmethod;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Arrays;
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public final class HardwareKeyboardShortcutControllerTest {
-
-    @Test
-    public void testForwardRotation() {
-        final List<String> handles = Arrays.asList("0", "1", "2", "3");
-        assertEquals("2", HardwareKeyboardShortcutController.getNeighborItem(handles, "1", true));
-        assertEquals("3", HardwareKeyboardShortcutController.getNeighborItem(handles, "2", true));
-        assertEquals("0", HardwareKeyboardShortcutController.getNeighborItem(handles, "3", true));
-    }
-
-    @Test
-    public void testBackwardRotation() {
-        final List<String> handles = Arrays.asList("0", "1", "2", "3");
-        assertEquals("0", HardwareKeyboardShortcutController.getNeighborItem(handles, "1", false));
-        assertEquals("3", HardwareKeyboardShortcutController.getNeighborItem(handles, "0", false));
-        assertEquals("2", HardwareKeyboardShortcutController.getNeighborItem(handles, "3", false));
-    }
-
-    @Test
-    public void testNotMatching() {
-        final List<String> handles = Arrays.asList("0", "1", "2", "3");
-        assertNull(HardwareKeyboardShortcutController.getNeighborItem(handles, "X", true));
-        assertNull(HardwareKeyboardShortcutController.getNeighborItem(handles, "X", false));
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
deleted file mode 100644
index fd65807..0000000
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.inputmethod;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.INVALID_DISPLAY;
-import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
-import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.inputmethod.SoftInputShowHideReason;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class InputMethodManagerServiceTests {
-    static final int SYSTEM_DECORATION_SUPPORT_DISPLAY_ID = 2;
-    static final int NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID = 3;
-
-    static InputMethodManagerService.ImeDisplayValidator sChecker =
-            (displayId) -> {
-                switch (displayId) {
-                    case SYSTEM_DECORATION_SUPPORT_DISPLAY_ID:
-                        return DISPLAY_IME_POLICY_LOCAL;
-                    case NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID:
-                        return DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
-                    default:
-                        throw new IllegalArgumentException("Unknown displayId=" + displayId);
-                }
-            };
-
-    static InputMethodManagerService.ImeDisplayValidator sMustNotBeCalledChecker =
-            (displayId) -> {
-                fail("Should not pass to display config check for this test case.");
-                return DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
-            };
-
-    @Test
-    public void testComputeImeDisplayId_defaultDisplayId() {
-        // Make sure that there is a short-circuit for DEFAULT_DISPLAY.
-        assertEquals(DEFAULT_DISPLAY,
-                InputMethodManagerService.computeImeDisplayIdForTarget(
-                        DEFAULT_DISPLAY, sMustNotBeCalledChecker));
-    }
-
-    @Test
-    public void testComputeImeDisplayId_InvalidDisplayId() {
-        // Make sure that there is a short-circuit for INVALID_DISPLAY.
-        assertEquals(DEFAULT_DISPLAY,
-                InputMethodManagerService.computeImeDisplayIdForTarget(
-                        INVALID_DISPLAY, sMustNotBeCalledChecker));
-    }
-
-    @Test
-    public void testComputeImeDisplayId_noSystemDecorationSupportDisplay() {
-        // Presume display didn't support system decoration.
-        // Make sure IME displayId is DEFAULT_DISPLAY.
-        assertEquals(DEFAULT_DISPLAY,
-                InputMethodManagerService.computeImeDisplayIdForTarget(
-                        NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, sChecker));
-    }
-
-    @Test
-    public void testComputeImeDisplayId_withSystemDecorationSupportDisplay() {
-        // Presume display support system decoration.
-        // Make sure IME displayId is the same display.
-        assertEquals(SYSTEM_DECORATION_SUPPORT_DISPLAY_ID,
-                InputMethodManagerService.computeImeDisplayIdForTarget(
-                        SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, sChecker));
-    }
-
-    @Test
-    public void testSoftInputShowHideHistoryDump_withNulls_doesntThrow() {
-        var writer = new StringWriter();
-        var history = new InputMethodManagerService.SoftInputShowHideHistory();
-        history.addEntry(new InputMethodManagerService.SoftInputShowHideHistory.Entry(
-                null,
-                null,
-                null,
-                SOFT_INPUT_STATE_UNSPECIFIED,
-                SoftInputShowHideReason.SHOW_SOFT_INPUT,
-                false,
-                null,
-                null,
-                null,
-                null));
-
-        history.dump(new PrintWriter(writer), "" /* prefix */);
-
-        // Asserts that dump doesn't throw an NPE.
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
deleted file mode 100644
index 0884b78..0000000
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ /dev/null
@@ -1,473 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.inputmethod;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.content.ComponentName;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodSubtype;
-import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl;
-import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class InputMethodSubtypeSwitchingControllerTest {
-    private static final String DUMMY_PACKAGE_NAME = "dummy package name";
-    private static final String DUMMY_IME_LABEL = "dummy ime label";
-    private static final String DUMMY_SETTING_ACTIVITY_NAME = "";
-    private static final boolean DUMMY_IS_AUX_IME = false;
-    private static final boolean DUMMY_FORCE_DEFAULT = false;
-    private static final boolean DUMMY_IS_VR_IME = false;
-    private static final int DUMMY_IS_DEFAULT_RES_ID = 0;
-    private static final String SYSTEM_LOCALE = "en_US";
-    private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
-
-    private static InputMethodSubtype createDummySubtype(final String locale) {
-        final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder();
-        return builder.setSubtypeNameResId(0)
-                .setSubtypeIconResId(0)
-                .setSubtypeLocale(locale)
-                .setIsAsciiCapable(true)
-                .build();
-    }
-
-    private static void addDummyImeSubtypeListItems(List<ImeSubtypeListItem> items,
-            String imeName, String imeLabel, List<String> subtypeLocales,
-            boolean supportsSwitchingToNextInputMethod) {
-        final ResolveInfo ri = new ResolveInfo();
-        final ServiceInfo si = new ServiceInfo();
-        final ApplicationInfo ai = new ApplicationInfo();
-        ai.packageName = DUMMY_PACKAGE_NAME;
-        ai.enabled = true;
-        si.applicationInfo = ai;
-        si.enabled = true;
-        si.packageName = DUMMY_PACKAGE_NAME;
-        si.name = imeName;
-        si.exported = true;
-        si.nonLocalizedLabel = imeLabel;
-        ri.serviceInfo = si;
-        List<InputMethodSubtype> subtypes = null;
-        if (subtypeLocales != null) {
-            subtypes = new ArrayList<InputMethodSubtype>();
-            for (String subtypeLocale : subtypeLocales) {
-                subtypes.add(createDummySubtype(subtypeLocale));
-            }
-        }
-        final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME,
-                DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID,
-                DUMMY_FORCE_DEFAULT, supportsSwitchingToNextInputMethod, DUMMY_IS_VR_IME);
-        if (subtypes == null) {
-            items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi,
-                    NOT_A_SUBTYPE_ID, null, SYSTEM_LOCALE));
-        } else {
-            for (int i = 0; i < subtypes.size(); ++i) {
-                final String subtypeLocale = subtypeLocales.get(i);
-                items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale,
-                        SYSTEM_LOCALE));
-            }
-        }
-    }
-
-    private static ImeSubtypeListItem createDummyItem(ComponentName imeComponentName,
-            String imeName, String subtypeName, String subtypeLocale, int subtypeIndex,
-            String systemLocale) {
-        final ResolveInfo ri = new ResolveInfo();
-        final ServiceInfo si = new ServiceInfo();
-        final ApplicationInfo ai = new ApplicationInfo();
-        ai.packageName = imeComponentName.getPackageName();
-        ai.enabled = true;
-        si.applicationInfo = ai;
-        si.enabled = true;
-        si.packageName = imeComponentName.getPackageName();
-        si.name = imeComponentName.getClassName();
-        si.exported = true;
-        si.nonLocalizedLabel = DUMMY_IME_LABEL;
-        ri.serviceInfo = si;
-        ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-        subtypes.add(new InputMethodSubtypeBuilder()
-                .setSubtypeNameResId(0)
-                .setSubtypeIconResId(0)
-                .setSubtypeLocale(subtypeLocale)
-                .setIsAsciiCapable(true)
-                .build());
-        final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME,
-                DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID,
-                DUMMY_FORCE_DEFAULT, true /* supportsSwitchingToNextInputMethod */,
-                DUMMY_IS_VR_IME);
-        return new ImeSubtypeListItem(imeName, subtypeName, imi, subtypeIndex, subtypeLocale,
-                systemLocale);
-    }
-
-    private static List<ImeSubtypeListItem> createEnabledImeSubtypes() {
-        final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>();
-        addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr"),
-                true /* supportsSwitchingToNextInputMethod*/);
-        addDummyImeSubtypeListItems(items, "switchUnawareLatinIme", "switchUnawareLatinIme",
-                Arrays.asList("en_UK", "hi"),
-                false /* supportsSwitchingToNextInputMethod*/);
-        addDummyImeSubtypeListItems(items, "subtypeUnawareIme", "subtypeUnawareIme", null,
-                false /* supportsSwitchingToNextInputMethod*/);
-        addDummyImeSubtypeListItems(items, "JapaneseIme", "JapaneseIme", Arrays.asList("ja_JP"),
-                true /* supportsSwitchingToNextInputMethod*/);
-        addDummyImeSubtypeListItems(items, "switchUnawareJapaneseIme", "switchUnawareJapaneseIme",
-                Arrays.asList("ja_JP"), false /* supportsSwitchingToNextInputMethod*/);
-        return items;
-    }
-
-    private static List<ImeSubtypeListItem> createDisabledImeSubtypes() {
-        final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>();
-        addDummyImeSubtypeListItems(items,
-                "UnknownIme", "UnknownIme",
-                Arrays.asList("en_US", "hi"),
-                true /* supportsSwitchingToNextInputMethod*/);
-        addDummyImeSubtypeListItems(items,
-                "UnknownSwitchingUnawareIme", "UnknownSwitchingUnawareIme",
-                Arrays.asList("en_US"),
-                false /* supportsSwitchingToNextInputMethod*/);
-        addDummyImeSubtypeListItems(items, "UnknownSubtypeUnawareIme",
-                "UnknownSubtypeUnawareIme", null,
-                false /* supportsSwitchingToNextInputMethod*/);
-        return items;
-    }
-
-    private void assertNextInputMethod(final ControllerImpl controller,
-            final boolean onlyCurrentIme,
-            final ImeSubtypeListItem currentItem, final ImeSubtypeListItem nextItem) {
-        InputMethodSubtype subtype = null;
-        if (currentItem.mSubtypeName != null) {
-            subtype = createDummySubtype(currentItem.mSubtypeName.toString());
-        }
-        final ImeSubtypeListItem nextIme = controller.getNextInputMethod(onlyCurrentIme,
-                currentItem.mImi, subtype);
-        assertEquals(nextItem, nextIme);
-    }
-
-    private void assertRotationOrder(final ControllerImpl controller,
-            final boolean onlyCurrentIme,
-            final ImeSubtypeListItem... expectedRotationOrderOfImeSubtypeList) {
-        final int numItems = expectedRotationOrderOfImeSubtypeList.length;
-        for (int i = 0; i < numItems; i++) {
-            final int currentIndex = i;
-            final int nextIndex = (currentIndex + 1) % numItems;
-            final ImeSubtypeListItem currentItem =
-                    expectedRotationOrderOfImeSubtypeList[currentIndex];
-            final ImeSubtypeListItem nextItem = expectedRotationOrderOfImeSubtypeList[nextIndex];
-            assertNextInputMethod(controller, onlyCurrentIme, currentItem, nextItem);
-        }
-    }
-
-    private void onUserAction(final ControllerImpl controller,
-            final ImeSubtypeListItem subtypeListItem) {
-        InputMethodSubtype subtype = null;
-        if (subtypeListItem.mSubtypeName != null) {
-            subtype = createDummySubtype(subtypeListItem.mSubtypeName.toString());
-        }
-        controller.onUserActionLocked(subtypeListItem.mImi, subtype);
-    }
-
-    @Test
-    public void testControllerImpl() throws Exception {
-        final List<ImeSubtypeListItem> disabledItems = createDisabledImeSubtypes();
-        final ImeSubtypeListItem disabledIme_en_us = disabledItems.get(0);
-        final ImeSubtypeListItem disabledIme_hi = disabledItems.get(1);
-        final ImeSubtypeListItem disabledSwitchingUnawareIme = disabledItems.get(2);
-        final ImeSubtypeListItem disabledSubtypeUnawareIme = disabledItems.get(3);
-
-        final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes();
-        final ImeSubtypeListItem latinIme_en_us = enabledItems.get(0);
-        final ImeSubtypeListItem latinIme_fr = enabledItems.get(1);
-        final ImeSubtypeListItem switchingUnawareLatinIme_en_uk = enabledItems.get(2);
-        final ImeSubtypeListItem switchingUnawareLatinIme_hi = enabledItems.get(3);
-        final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4);
-        final ImeSubtypeListItem japaneseIme_ja_jp = enabledItems.get(5);
-        final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(6);
-
-        final ControllerImpl controller = ControllerImpl.createFrom(
-                null /* currentInstance */, enabledItems);
-
-        // switching-aware loop
-        assertRotationOrder(controller, false /* onlyCurrentIme */,
-                latinIme_en_us, latinIme_fr, japaneseIme_ja_jp);
-
-        // switching-unaware loop
-        assertRotationOrder(controller, false /* onlyCurrentIme */,
-                switchingUnawareLatinIme_en_uk, switchingUnawareLatinIme_hi, subtypeUnawareIme,
-                switchUnawareJapaneseIme_ja_jp);
-
-        // test onlyCurrentIme == true
-        assertRotationOrder(controller, true /* onlyCurrentIme */,
-                latinIme_en_us, latinIme_fr);
-        assertRotationOrder(controller, true /* onlyCurrentIme */,
-                switchingUnawareLatinIme_en_uk, switchingUnawareLatinIme_hi);
-        assertNextInputMethod(controller, true /* onlyCurrentIme */,
-                subtypeUnawareIme, null);
-        assertNextInputMethod(controller, true /* onlyCurrentIme */,
-                japaneseIme_ja_jp, null);
-        assertNextInputMethod(controller, true /* onlyCurrentIme */,
-                switchUnawareJapaneseIme_ja_jp, null);
-
-        // Make sure that disabled IMEs are not accepted.
-        assertNextInputMethod(controller, false /* onlyCurrentIme */,
-                disabledIme_en_us, null);
-        assertNextInputMethod(controller, false /* onlyCurrentIme */,
-                disabledIme_hi, null);
-        assertNextInputMethod(controller, false /* onlyCurrentIme */,
-                disabledSwitchingUnawareIme, null);
-        assertNextInputMethod(controller, false /* onlyCurrentIme */,
-                disabledSubtypeUnawareIme, null);
-        assertNextInputMethod(controller, true /* onlyCurrentIme */,
-                disabledIme_en_us, null);
-        assertNextInputMethod(controller, true /* onlyCurrentIme */,
-                disabledIme_hi, null);
-        assertNextInputMethod(controller, true /* onlyCurrentIme */,
-                disabledSwitchingUnawareIme, null);
-        assertNextInputMethod(controller, true /* onlyCurrentIme */,
-                disabledSubtypeUnawareIme, null);
-    }
-
-    @Test
-    public void testControllerImplWithUserAction() throws Exception {
-        final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes();
-        final ImeSubtypeListItem latinIme_en_us = enabledItems.get(0);
-        final ImeSubtypeListItem latinIme_fr = enabledItems.get(1);
-        final ImeSubtypeListItem switchingUnawarelatinIme_en_uk = enabledItems.get(2);
-        final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3);
-        final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4);
-        final ImeSubtypeListItem japaneseIme_ja_jp = enabledItems.get(5);
-        final ImeSubtypeListItem switchUnawareJapaneseIme_ja_jp = enabledItems.get(6);
-
-        final ControllerImpl controller = ControllerImpl.createFrom(
-                null /* currentInstance */, enabledItems);
-
-        // === switching-aware loop ===
-        assertRotationOrder(controller, false /* onlyCurrentIme */,
-                latinIme_en_us, latinIme_fr, japaneseIme_ja_jp);
-        // Then notify that a user did something for latinIme_fr.
-        onUserAction(controller, latinIme_fr);
-        assertRotationOrder(controller, false /* onlyCurrentIme */,
-                latinIme_fr, latinIme_en_us, japaneseIme_ja_jp);
-        // Then notify that a user did something for latinIme_fr again.
-        onUserAction(controller, latinIme_fr);
-        assertRotationOrder(controller, false /* onlyCurrentIme */,
-                latinIme_fr, latinIme_en_us, japaneseIme_ja_jp);
-        // Then notify that a user did something for japaneseIme_ja_JP.
-        onUserAction(controller, latinIme_fr);
-        assertRotationOrder(controller, false /* onlyCurrentIme */,
-                japaneseIme_ja_jp, latinIme_fr, latinIme_en_us);
-        // Check onlyCurrentIme == true.
-        assertNextInputMethod(controller, true /* onlyCurrentIme */,
-                japaneseIme_ja_jp, null);
-        assertRotationOrder(controller, true /* onlyCurrentIme */,
-                latinIme_fr, latinIme_en_us);
-        assertRotationOrder(controller, true /* onlyCurrentIme */,
-                latinIme_en_us, latinIme_fr);
-
-        // === switching-unaware loop ===
-        assertRotationOrder(controller, false /* onlyCurrentIme */,
-                switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi, subtypeUnawareIme,
-                switchUnawareJapaneseIme_ja_jp);
-        // User action should be ignored for switching unaware IMEs.
-        onUserAction(controller, switchingUnawarelatinIme_hi);
-        assertRotationOrder(controller, false /* onlyCurrentIme */,
-                switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi, subtypeUnawareIme,
-                switchUnawareJapaneseIme_ja_jp);
-        // User action should be ignored for switching unaware IMEs.
-        onUserAction(controller, switchUnawareJapaneseIme_ja_jp);
-        assertRotationOrder(controller, false /* onlyCurrentIme */,
-                switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi, subtypeUnawareIme,
-                switchUnawareJapaneseIme_ja_jp);
-        // Check onlyCurrentIme == true.
-        assertRotationOrder(controller, true /* onlyCurrentIme */,
-                switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi);
-        assertNextInputMethod(controller, true /* onlyCurrentIme */,
-                subtypeUnawareIme, null);
-        assertNextInputMethod(controller, true /* onlyCurrentIme */,
-                switchUnawareJapaneseIme_ja_jp, null);
-
-        // Rotation order should be preserved when created with the same subtype list.
-        final List<ImeSubtypeListItem> sameEnabledItems = createEnabledImeSubtypes();
-        final ControllerImpl newController = ControllerImpl.createFrom(controller,
-                sameEnabledItems);
-        assertRotationOrder(newController, false /* onlyCurrentIme */,
-                japaneseIme_ja_jp, latinIme_fr, latinIme_en_us);
-        assertRotationOrder(newController, false /* onlyCurrentIme */,
-                switchingUnawarelatinIme_en_uk, switchingUnawarelatinIme_hi, subtypeUnawareIme,
-                switchUnawareJapaneseIme_ja_jp);
-
-        // Rotation order should be initialized when created with a different subtype list.
-        final List<ImeSubtypeListItem> differentEnabledItems = Arrays.asList(
-                latinIme_en_us, latinIme_fr, switchingUnawarelatinIme_en_uk,
-                switchUnawareJapaneseIme_ja_jp);
-        final ControllerImpl anotherController = ControllerImpl.createFrom(controller,
-                differentEnabledItems);
-        assertRotationOrder(anotherController, false /* onlyCurrentIme */,
-                latinIme_en_us, latinIme_fr);
-        assertRotationOrder(anotherController, false /* onlyCurrentIme */,
-                switchingUnawarelatinIme_en_uk, switchUnawareJapaneseIme_ja_jp);
-    }
-
-    @Test
-    public void testImeSubtypeListItem() throws Exception {
-        final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>();
-        addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme",
-                Arrays.asList("en_US", "fr", "en", "en_uk", "enn", "e", "EN_US"),
-                true /* supportsSwitchingToNextInputMethod*/);
-        final ImeSubtypeListItem item_en_us = items.get(0);
-        final ImeSubtypeListItem item_fr = items.get(1);
-        final ImeSubtypeListItem item_en = items.get(2);
-        final ImeSubtypeListItem item_enn = items.get(3);
-        final ImeSubtypeListItem item_e = items.get(4);
-        final ImeSubtypeListItem item_en_us_allcaps = items.get(5);
-
-        assertTrue(item_en_us.mIsSystemLocale);
-        assertFalse(item_fr.mIsSystemLocale);
-        assertFalse(item_en.mIsSystemLocale);
-        assertFalse(item_en.mIsSystemLocale);
-        assertFalse(item_enn.mIsSystemLocale);
-        assertFalse(item_e.mIsSystemLocale);
-        assertFalse(item_en_us_allcaps.mIsSystemLocale);
-
-        assertTrue(item_en_us.mIsSystemLanguage);
-        assertFalse(item_fr.mIsSystemLanguage);
-        assertTrue(item_en.mIsSystemLanguage);
-        assertFalse(item_enn.mIsSystemLocale);
-        assertFalse(item_e.mIsSystemLocale);
-        assertFalse(item_en_us_allcaps.mIsSystemLocale);
-    }
-
-    @SuppressWarnings("SelfComparison")
-    @Test
-    public void testImeSubtypeListComparator() throws Exception {
-        final ComponentName imeX1 = new ComponentName("com.example.imeX", "Ime1");
-        final ComponentName imeX2 = new ComponentName("com.example.imeX", "Ime2");
-        final ComponentName imeY1 = new ComponentName("com.example.imeY", "Ime1");
-        final ComponentName imeZ1 = new ComponentName("com.example.imeZ", "Ime1");
-        {
-            final List<ImeSubtypeListItem> items = Arrays.asList(
-                    // Subtypes of two IMEs that have the same display name "X".
-                    // Subtypes that has the same locale of the system's.
-                    createDummyItem(imeX1, "X", "E", "en_US", 0, "en_US"),
-                    createDummyItem(imeX2, "X", "E", "en_US", 0, "en_US"),
-                    createDummyItem(imeX1, "X", "Z", "en_US", 3, "en_US"),
-                    createDummyItem(imeX2, "X", "Z", "en_US", 3, "en_US"),
-                    createDummyItem(imeX1, "X", "", "en_US", 6, "en_US"),
-                    createDummyItem(imeX2, "X", "", "en_US", 6, "en_US"),
-                    // Subtypes that has the same language of the system's.
-                    createDummyItem(imeX1, "X", "E", "en", 1, "en_US"),
-                    createDummyItem(imeX2, "X", "E", "en", 1, "en_US"),
-                    createDummyItem(imeX1, "X", "Z", "en", 4, "en_US"),
-                    createDummyItem(imeX2, "X", "Z", "en", 4, "en_US"),
-                    createDummyItem(imeX1, "X", "", "en", 7, "en_US"),
-                    createDummyItem(imeX2, "X", "", "en", 7, "en_US"),
-                    // Subtypes that has different language than the system's.
-                    createDummyItem(imeX1, "X", "A", "hi_IN", 27, "en_US"),
-                    createDummyItem(imeX2, "X", "A", "hi_IN", 27, "en_US"),
-                    createDummyItem(imeX1, "X", "E", "ja", 2, "en_US"),
-                    createDummyItem(imeX2, "X", "E", "ja", 2, "en_US"),
-                    createDummyItem(imeX1, "X", "Z", "ja", 5, "en_US"),
-                    createDummyItem(imeX2, "X", "Z", "ja", 5, "en_US"),
-                    createDummyItem(imeX1, "X", "", "ja", 8, "en_US"),
-                    createDummyItem(imeX2, "X", "", "ja", 8, "en_US"),
-
-                    // Subtypes of IME "Y".
-                    // Subtypes that has the same locale of the system's.
-                    createDummyItem(imeY1, "Y", "E", "en_US", 9, "en_US"),
-                    createDummyItem(imeY1, "Y", "Z", "en_US", 12, "en_US"),
-                    createDummyItem(imeY1, "Y", "", "en_US", 15, "en_US"),
-                    // Subtypes that has the same language of the system's.
-                    createDummyItem(imeY1, "Y", "E", "en", 10, "en_US"),
-                    createDummyItem(imeY1, "Y", "Z", "en", 13, "en_US"),
-                    createDummyItem(imeY1, "Y", "", "en", 16, "en_US"),
-                    // Subtypes that has different language than the system's.
-                    createDummyItem(imeY1, "Y", "A", "hi_IN", 28, "en_US"),
-                    createDummyItem(imeY1, "Y", "E", "ja", 11, "en_US"),
-                    createDummyItem(imeY1, "Y", "Z", "ja", 14, "en_US"),
-                    createDummyItem(imeY1, "Y", "", "ja", 17, "en_US"),
-
-                    // Subtypes of IME Z.
-                    // Subtypes that has the same locale of the system's.
-                    createDummyItem(imeZ1, "", "E", "en_US", 18, "en_US"),
-                    createDummyItem(imeZ1, "", "Z", "en_US", 21, "en_US"),
-                    createDummyItem(imeZ1, "", "", "en_US", 24, "en_US"),
-                    // Subtypes that has the same language of the system's.
-                    createDummyItem(imeZ1, "", "E", "en", 19, "en_US"),
-                    createDummyItem(imeZ1, "", "Z", "en", 22, "en_US"),
-                    createDummyItem(imeZ1, "", "", "en", 25, "en_US"),
-                    // Subtypes that has different language than the system's.
-                    createDummyItem(imeZ1, "", "A", "hi_IN", 29, "en_US"),
-                    createDummyItem(imeZ1, "", "E", "ja", 20, "en_US"),
-                    createDummyItem(imeZ1, "", "Z", "ja", 23, "en_US"),
-                    createDummyItem(imeZ1, "", "", "ja", 26, "en_US"));
-
-            // Ensure {@link java.lang.Comparable#compareTo} contracts are satisfied.
-            for (int i = 0; i < items.size(); ++i) {
-                final ImeSubtypeListItem item1 = items.get(i);
-                // Ensures sgn(x.compareTo(y)) == -sgn(y.compareTo(x)).
-                assertTrue(item1 + " has the same order of itself", item1.compareTo(item1) == 0);
-                // Ensures (x.compareTo(y) > 0 && y.compareTo(z) > 0) implies x.compareTo(z) > 0.
-                for (int j = i + 1; j < items.size(); ++j) {
-                    final ImeSubtypeListItem item2 = items.get(j);
-                    // Ensures sgn(x.compareTo(y)) == -sgn(y.compareTo(x)).
-                    assertTrue(item1 + " is less than " + item2, item1.compareTo(item2) < 0);
-                    assertTrue(item2 + " is greater than " + item1, item2.compareTo(item1) > 0);
-                }
-            }
-        }
-
-        {
-            // Following two items have the same priority.
-            final ImeSubtypeListItem nonSystemLocale1 =
-                    createDummyItem(imeX1, "X", "A", "ja_JP", 0, "en_US");
-            final ImeSubtypeListItem nonSystemLocale2 =
-                    createDummyItem(imeX1, "X", "A", "hi_IN", 1, "en_US");
-            assertTrue(nonSystemLocale1.compareTo(nonSystemLocale2) == 0);
-            assertTrue(nonSystemLocale2.compareTo(nonSystemLocale1) == 0);
-            // But those aren't equal to each other.
-            assertFalse(nonSystemLocale1.equals(nonSystemLocale2));
-            assertFalse(nonSystemLocale2.equals(nonSystemLocale1));
-        }
-
-        {
-            // Check if ComponentName is also taken into account when comparing two items.
-            final ImeSubtypeListItem ime1 = createDummyItem(imeX1, "X", "A", "ja_JP", 0, "en_US");
-            final ImeSubtypeListItem ime2 = createDummyItem(imeX2, "X", "A", "ja_JP", 0, "en_US");
-            assertTrue(ime1.compareTo(ime2) < 0);
-            assertTrue(ime2.compareTo(ime1) > 0);
-            // But those aren't equal to each other.
-            assertFalse(ime1.equals(ime2));
-            assertFalse(ime2.equals(ime1));
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
deleted file mode 100644
index 6b85a32..0000000
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ /dev/null
@@ -1,1403 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.inputmethod;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.in;
-import static org.hamcrest.Matchers.not;
-import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.IContentProvider;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.os.Build;
-import android.os.LocaleList;
-import android.os.Parcel;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.test.mock.MockContentResolver;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.IntArray;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodSubtype;
-import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
-
-import androidx.annotation.NonNull;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.inputmethod.StartInputFlags;
-
-import com.google.common.truth.Truth;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.Objects;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class InputMethodUtilsTest {
-    private static final boolean IS_AUX = true;
-    private static final boolean IS_DEFAULT = true;
-    private static final boolean IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE = true;
-    private static final boolean IS_ASCII_CAPABLE = true;
-    private static final boolean IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE = true;
-    private static final boolean CHECK_COUNTRY = true;
-    private static final Locale LOCALE_EN = new Locale("en");
-    private static final Locale LOCALE_EN_US = new Locale("en", "US");
-    private static final Locale LOCALE_EN_GB = new Locale("en", "GB");
-    private static final Locale LOCALE_EN_IN = new Locale("en", "IN");
-    private static final Locale LOCALE_FI = new Locale("fi");
-    private static final Locale LOCALE_FI_FI = new Locale("fi", "FI");
-    private static final Locale LOCALE_FIL = new Locale("fil");
-    private static final Locale LOCALE_FIL_PH = new Locale("fil", "PH");
-    private static final Locale LOCALE_FR = new Locale("fr");
-    private static final Locale LOCALE_FR_CA = new Locale("fr", "CA");
-    private static final Locale LOCALE_HI = new Locale("hi");
-    private static final Locale LOCALE_JA_JP = new Locale("ja", "JP");
-    private static final Locale LOCALE_ZH_CN = new Locale("zh", "CN");
-    private static final Locale LOCALE_ZH_TW = new Locale("zh", "TW");
-    private static final Locale LOCALE_IN = new Locale("in");
-    private static final Locale LOCALE_ID = new Locale("id");
-    private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
-    private static final String SUBTYPE_MODE_VOICE = "voice";
-    private static final String SUBTYPE_MODE_HANDWRITING = "handwriting";
-    private static final String SUBTYPE_MODE_ANY = null;
-    private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
-    private static final String EXTRA_VALUE_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
-            "EnabledWhenDefaultIsNotAsciiCapable";
-
-    @Test
-    public void testVoiceImes() throws Exception {
-        // locale: en_US
-        assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US,
-                "FakeDefaultEnKeyboardIme", "FakeDefaultAutoVoiceIme");
-        assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US,
-                "FakeDefaultEnKeyboardIme", "FakeNonDefaultAutoVoiceIme0",
-                "FakeNonDefaultAutoVoiceIme1");
-        assertDefaultEnabledMinimumImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US,
-                "FakeDefaultEnKeyboardIme");
-        assertDefaultEnabledMinimumImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US,
-                "FakeDefaultEnKeyboardIme");
-
-        // locale: en_GB
-        assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB,
-                "FakeDefaultEnKeyboardIme", "FakeDefaultAutoVoiceIme");
-        assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB,
-                "FakeDefaultEnKeyboardIme", "FakeNonDefaultAutoVoiceIme0",
-                "FakeNonDefaultAutoVoiceIme1");
-        assertDefaultEnabledMinimumImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB,
-                "FakeDefaultEnKeyboardIme");
-        assertDefaultEnabledMinimumImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB,
-                "FakeDefaultEnKeyboardIme");
-
-        // locale: ja_JP
-        assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP,
-                "FakeDefaultEnKeyboardIme", "FakeDefaultAutoVoiceIme");
-        assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP,
-                "FakeDefaultEnKeyboardIme", "FakeNonDefaultAutoVoiceIme0",
-                "FakeNonDefaultAutoVoiceIme1");
-        assertDefaultEnabledMinimumImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP,
-                "FakeDefaultEnKeyboardIme");
-        assertDefaultEnabledMinimumImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP,
-                "FakeDefaultEnKeyboardIme");
-    }
-
-    @Test
-    public void testKeyboardImes() throws Exception {
-        // locale: en_US
-        assertDefaultEnabledImes(getSamplePreinstalledImes("en-rUS"), LOCALE_EN_US,
-                "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
-        assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("en-rUS"), LOCALE_EN_US,
-                "com.android.apps.inputmethod.latin");
-
-        // locale: en_GB
-        assertDefaultEnabledImes(getSamplePreinstalledImes("en-rGB"), LOCALE_EN_GB,
-                "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
-        assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("en-rGB"), LOCALE_EN_GB,
-                "com.android.apps.inputmethod.latin");
-
-        // locale: en_IN
-        assertDefaultEnabledImes(getSamplePreinstalledImes("en-rIN"), LOCALE_EN_IN,
-                "com.android.apps.inputmethod.hindi",
-                "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
-        assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("en-rIN"), LOCALE_EN_IN,
-                "com.android.apps.inputmethod.hindi",
-                "com.android.apps.inputmethod.latin");
-
-        // locale: hi
-        assertDefaultEnabledImes(getSamplePreinstalledImes("hi"), LOCALE_HI,
-                "com.android.apps.inputmethod.hindi", "com.android.apps.inputmethod.latin",
-                "com.android.apps.inputmethod.voice");
-        assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("hi"), LOCALE_HI,
-                "com.android.apps.inputmethod.hindi", "com.android.apps.inputmethod.latin");
-
-        // locale: ja_JP
-        assertDefaultEnabledImes(getSamplePreinstalledImes("ja-rJP"), LOCALE_JA_JP,
-                "com.android.apps.inputmethod.japanese", "com.android.apps.inputmethod.voice");
-        assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("ja-rJP"), LOCALE_JA_JP,
-                "com.android.apps.inputmethod.japanese");
-
-        // locale: zh_CN
-        assertDefaultEnabledImes(getSamplePreinstalledImes("zh-rCN"), LOCALE_ZH_CN,
-                "com.android.apps.inputmethod.pinyin", "com.android.apps.inputmethod.voice");
-        assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("zh-rCN"), LOCALE_ZH_CN,
-                "com.android.apps.inputmethod.pinyin");
-
-        // locale: zh_TW
-        // Note: In this case, no IME is suitable for the system locale. Hence we will pick up a
-        // fallback IME regardless of the "default" attribute.
-        assertDefaultEnabledImes(getSamplePreinstalledImes("zh-rTW"), LOCALE_ZH_TW,
-                "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
-        assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("zh-rTW"), LOCALE_ZH_TW,
-                "com.android.apps.inputmethod.latin");
-    }
-
-    @Test
-    public void testParcelable() throws Exception {
-        final ArrayList<InputMethodInfo> originalList = getSamplePreinstalledImes("en-rUS");
-        final List<InputMethodInfo> clonedList = cloneViaParcel(originalList);
-        assertNotNull(clonedList);
-        final List<InputMethodInfo> clonedClonedList = cloneViaParcel(clonedList);
-        assertNotNull(clonedClonedList);
-        assertEquals(originalList, clonedList);
-        assertEquals(clonedList, clonedClonedList);
-        assertEquals(originalList.size(), clonedList.size());
-        assertEquals(clonedList.size(), clonedClonedList.size());
-        for (int imeIndex = 0; imeIndex < originalList.size(); ++imeIndex) {
-            verifyEquality(originalList.get(imeIndex), clonedList.get(imeIndex));
-            verifyEquality(clonedList.get(imeIndex), clonedClonedList.get(imeIndex));
-        }
-    }
-
-    @Test
-    public void testGetImplicitlyApplicableSubtypesLocked() throws Exception {
-        final InputMethodSubtype nonAutoEnUS = createFakeInputMethodSubtype("en_US",
-                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
-                IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoEnGB = createFakeInputMethodSubtype("en_GB",
-                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
-                IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoFrCA = createFakeInputMethodSubtype("fr_CA",
-                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
-                IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoFr = createFakeInputMethodSubtype("fr_CA",
-                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
-                IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoFil = createFakeInputMethodSubtype("fil",
-                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
-                IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoIn = createFakeInputMethodSubtype("in",
-                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
-                IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoId = createFakeInputMethodSubtype("id",
-                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
-                IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype autoSubtype = createFakeInputMethodSubtype("auto",
-                SUBTYPE_MODE_KEYBOARD, !IS_AUX, IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
-                IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoJa = createFakeInputMethodSubtype("ja",
-                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
-                !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoHi = createFakeInputMethodSubtype("hi",
-                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
-                !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoSrCyrl = createFakeInputMethodSubtype("sr",
-                "sr-Cyrl", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
-                !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
-                !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoSrLatn = createFakeInputMethodSubtype("sr_ZZ",
-                "sr-Latn", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
-                !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
-                !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoHandwritingEn = createFakeInputMethodSubtype("en",
-                SUBTYPE_MODE_HANDWRITING, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
-                !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoHandwritingFr = createFakeInputMethodSubtype("fr",
-                SUBTYPE_MODE_HANDWRITING, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
-                !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoHandwritingSrCyrl = createFakeInputMethodSubtype("sr",
-                "sr-Cyrl", SUBTYPE_MODE_HANDWRITING, !IS_AUX,
-                !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
-                !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoHandwritingSrLatn = createFakeInputMethodSubtype("sr_ZZ",
-                "sr-Latn", SUBTYPE_MODE_HANDWRITING, !IS_AUX,
-                !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
-                !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype =
-                createFakeInputMethodSubtype("zz", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
-                        !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
-                        IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2 =
-                createFakeInputMethodSubtype("zz", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
-                        !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
-                        IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-
-        // Make sure that an automatic subtype (overridesImplicitlyEnabledSubtype:true) is
-        // selected no matter what locale is specified.
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoEnUS);
-            subtypes.add(nonAutoEnGB);
-            subtypes.add(nonAutoJa);
-            subtypes.add(nonAutoFil);
-            subtypes.add(autoSubtype);  // overridesImplicitlyEnabledSubtype == true
-            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
-            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
-            subtypes.add(nonAutoHandwritingEn);
-            subtypes.add(nonAutoHandwritingFr);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            final ArrayList<InputMethodSubtype> result =
-                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            new LocaleList(LOCALE_EN_US), imi);
-            assertEquals(1, result.size());
-            verifyEquality(autoSubtype, result.get(0));
-        }
-
-        // Make sure that a subtype whose locale is exactly equal to the specified locale is
-        // selected as long as there is no no automatic subtype
-        // (overridesImplicitlyEnabledSubtype:true) in the given list.
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoEnUS);  // locale == "en_US"
-            subtypes.add(nonAutoEnGB);
-            subtypes.add(nonAutoJa);
-            subtypes.add(nonAutoFil);
-            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
-            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
-            subtypes.add(nonAutoHandwritingEn);
-            subtypes.add(nonAutoHandwritingFr);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            final ArrayList<InputMethodSubtype> result =
-                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            new LocaleList(LOCALE_EN_US), imi);
-            assertEquals(2, result.size());
-            verifyEquality(nonAutoEnUS, result.get(0));
-            verifyEquality(nonAutoHandwritingEn, result.get(1));
-        }
-
-        // Make sure that a subtype whose locale is exactly equal to the specified locale is
-        // selected as long as there is no automatic subtype
-        // (overridesImplicitlyEnabledSubtype:true) in the given list.
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoEnUS);
-            subtypes.add(nonAutoEnGB); // locale == "en_GB"
-            subtypes.add(nonAutoJa);
-            subtypes.add(nonAutoFil);
-            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
-            subtypes.add(nonAutoHandwritingEn);
-            subtypes.add(nonAutoHandwritingFr);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            final ArrayList<InputMethodSubtype> result =
-                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            new LocaleList(LOCALE_EN_GB), imi);
-            assertEquals(2, result.size());
-            verifyEquality(nonAutoEnGB, result.get(0));
-            verifyEquality(nonAutoHandwritingEn, result.get(1));
-        }
-
-        // If there is no automatic subtype (overridesImplicitlyEnabledSubtype:true) and
-        // any subtype whose locale is exactly equal to the specified locale in the given list,
-        // try to find a subtype whose language is equal to the language part of the given locale.
-        // Here make sure that a subtype (locale: "fr_CA") can be found with locale: "fr".
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoFrCA);  // locale == "fr_CA"
-            subtypes.add(nonAutoJa);
-            subtypes.add(nonAutoFil);
-            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
-            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
-            subtypes.add(nonAutoHandwritingEn);
-            subtypes.add(nonAutoHandwritingFr);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            final ArrayList<InputMethodSubtype> result =
-                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            new LocaleList(LOCALE_FR), imi);
-            assertEquals(2, result.size());
-            verifyEquality(nonAutoFrCA, result.get(0));
-            verifyEquality(nonAutoHandwritingFr, result.get(1));
-        }
-        // Then make sure that a subtype (locale: "fr") can be found with locale: "fr_CA".
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoFr);  // locale == "fr"
-            subtypes.add(nonAutoJa);
-            subtypes.add(nonAutoFil);
-            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
-            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
-            subtypes.add(nonAutoHandwritingEn);
-            subtypes.add(nonAutoHandwritingFr);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            final ArrayList<InputMethodSubtype> result =
-                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            new LocaleList(LOCALE_FR_CA), imi);
-            assertEquals(2, result.size());
-            verifyEquality(nonAutoFrCA, result.get(0));
-            verifyEquality(nonAutoHandwritingFr, result.get(1));
-        }
-
-        // Make sure that subtypes which have "EnabledWhenDefaultIsNotAsciiCapable" in its
-        // extra value is selected if and only if all other selected IMEs are not AsciiCapable.
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoEnUS);
-            subtypes.add(nonAutoJa);    // not ASCII capable
-            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
-            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
-            subtypes.add(nonAutoHandwritingEn);
-            subtypes.add(nonAutoHandwritingFr);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            final ArrayList<InputMethodSubtype> result =
-                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            new LocaleList(LOCALE_JA_JP), imi);
-            assertEquals(3, result.size());
-            verifyEquality(nonAutoJa, result.get(0));
-            verifyEquality(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype, result.get(1));
-            verifyEquality(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2, result.get(2));
-        }
-
-        // Make sure that if there is no subtype that matches the language requested, then we just
-        // use the first keyboard subtype.
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoHi);
-            subtypes.add(nonAutoEnUS);
-            subtypes.add(nonAutoHandwritingEn);
-            subtypes.add(nonAutoHandwritingFr);
-            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            final ArrayList<InputMethodSubtype> result =
-                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            new LocaleList(LOCALE_JA_JP), imi);
-            assertEquals(1, result.size());
-            verifyEquality(nonAutoHi, result.get(0));
-        }
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoEnUS);
-            subtypes.add(nonAutoHi);
-            subtypes.add(nonAutoHandwritingEn);
-            subtypes.add(nonAutoHandwritingFr);
-            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            final ArrayList<InputMethodSubtype> result =
-                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            new LocaleList(LOCALE_JA_JP), imi);
-            assertEquals(1, result.size());
-            verifyEquality(nonAutoEnUS, result.get(0));
-        }
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoHandwritingEn);
-            subtypes.add(nonAutoHandwritingFr);
-            subtypes.add(nonAutoEnUS);
-            subtypes.add(nonAutoHi);
-            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            final ArrayList<InputMethodSubtype> result =
-                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            new LocaleList(LOCALE_JA_JP), imi);
-            assertEquals(1, result.size());
-            verifyEquality(nonAutoEnUS, result.get(0));
-        }
-
-        // Make sure that both language and script are taken into account to find the best matching
-        // subtype.
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoEnUS);
-            subtypes.add(nonAutoSrCyrl);
-            subtypes.add(nonAutoSrLatn);
-            subtypes.add(nonAutoHandwritingEn);
-            subtypes.add(nonAutoHandwritingFr);
-            subtypes.add(nonAutoHandwritingSrCyrl);
-            subtypes.add(nonAutoHandwritingSrLatn);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            final ArrayList<InputMethodSubtype> result =
-                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            new LocaleList(Locale.forLanguageTag("sr-Latn-RS")), imi);
-            assertEquals(2, result.size());
-            assertThat(nonAutoSrLatn, is(in(result)));
-            assertThat(nonAutoHandwritingSrLatn, is(in(result)));
-        }
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoEnUS);
-            subtypes.add(nonAutoSrCyrl);
-            subtypes.add(nonAutoSrLatn);
-            subtypes.add(nonAutoHandwritingEn);
-            subtypes.add(nonAutoHandwritingFr);
-            subtypes.add(nonAutoHandwritingSrCyrl);
-            subtypes.add(nonAutoHandwritingSrLatn);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            final ArrayList<InputMethodSubtype> result =
-                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            new LocaleList(Locale.forLanguageTag("sr-Cyrl-RS")), imi);
-            assertEquals(2, result.size());
-            assertThat(nonAutoSrCyrl, is(in(result)));
-            assertThat(nonAutoHandwritingSrCyrl, is(in(result)));
-        }
-
-        // Make sure that secondary locales are taken into account to find the best matching
-        // subtype.
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoEnUS);
-            subtypes.add(nonAutoEnGB);
-            subtypes.add(nonAutoSrCyrl);
-            subtypes.add(nonAutoSrLatn);
-            subtypes.add(nonAutoFr);
-            subtypes.add(nonAutoFrCA);
-            subtypes.add(nonAutoHandwritingEn);
-            subtypes.add(nonAutoHandwritingFr);
-            subtypes.add(nonAutoHandwritingSrCyrl);
-            subtypes.add(nonAutoHandwritingSrLatn);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            final ArrayList<InputMethodSubtype> result =
-                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            new LocaleList(
-                                    Locale.forLanguageTag("sr-Latn-RS-x-android"),
-                                    Locale.forLanguageTag("ja-JP"),
-                                    Locale.forLanguageTag("fr-FR"),
-                                    Locale.forLanguageTag("en-GB"),
-                                    Locale.forLanguageTag("en-US")),
-                            imi);
-            assertEquals(6, result.size());
-            assertThat(nonAutoEnGB, is(in(result)));
-            assertThat(nonAutoFr, is(in(result)));
-            assertThat(nonAutoSrLatn, is(in(result)));
-            assertThat(nonAutoHandwritingEn, is(in(result)));
-            assertThat(nonAutoHandwritingFr, is(in(result)));
-            assertThat(nonAutoHandwritingSrLatn, is(in(result)));
-        }
-
-        // Make sure that 3-letter language code can be handled.
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoEnUS);
-            subtypes.add(nonAutoFil);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            final ArrayList<InputMethodSubtype> result =
-                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            new LocaleList(LOCALE_FIL_PH), imi);
-            assertEquals(1, result.size());
-            verifyEquality(nonAutoFil, result.get(0));
-        }
-
-        // Make sure that we never end up matching "fi" (finnish) with "fil" (filipino).
-        // Also make sure that the first subtype will be used as the last-resort candidate.
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoJa);
-            subtypes.add(nonAutoEnUS);
-            subtypes.add(nonAutoFil);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            final ArrayList<InputMethodSubtype> result =
-                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            new LocaleList(LOCALE_FI), imi);
-            assertEquals(1, result.size());
-            verifyEquality(nonAutoJa, result.get(0));
-        }
-
-        // Make sure that "in" and "id" conversion is taken into account.
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoIn);
-            subtypes.add(nonAutoEnUS);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            final ArrayList<InputMethodSubtype> result =
-                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            new LocaleList(LOCALE_IN), imi);
-            assertEquals(1, result.size());
-            verifyEquality(nonAutoIn, result.get(0));
-        }
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoIn);
-            subtypes.add(nonAutoEnUS);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            final ArrayList<InputMethodSubtype> result =
-                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            new LocaleList(LOCALE_ID), imi);
-            assertEquals(1, result.size());
-            verifyEquality(nonAutoIn, result.get(0));
-        }
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoId);
-            subtypes.add(nonAutoEnUS);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            final ArrayList<InputMethodSubtype> result =
-                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            new LocaleList(LOCALE_IN), imi);
-            assertEquals(1, result.size());
-            verifyEquality(nonAutoId, result.get(0));
-        }
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoId);
-            subtypes.add(nonAutoEnUS);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            final ArrayList<InputMethodSubtype> result =
-                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            new LocaleList(LOCALE_ID), imi);
-            assertEquals(1, result.size());
-            verifyEquality(nonAutoId, result.get(0));
-        }
-
-        // If there is no automatic subtype (overridesImplicitlyEnabledSubtype:true) and the system
-        // provides multiple locales, we try to enable multiple subtypes.
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoEnUS);
-            subtypes.add(nonAutoFrCA);
-            subtypes.add(nonAutoIn);
-            subtypes.add(nonAutoJa);
-            subtypes.add(nonAutoFil);
-            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
-            subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            final ArrayList<InputMethodSubtype> result =
-                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            new LocaleList(LOCALE_FR, LOCALE_EN_US, LOCALE_JA_JP), imi);
-            assertThat(nonAutoFrCA, is(in(result)));
-            assertThat(nonAutoEnUS, is(in(result)));
-            assertThat(nonAutoJa, is(in(result)));
-            assertThat(nonAutoIn, not(is(in(result))));
-            assertThat(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype, not(is(in(result))));
-            assertThat(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype, not(is(in(result))));
-        }
-    }
-
-    @Test
-    public void testContainsSubtypeOf() throws Exception {
-        final InputMethodSubtype nonAutoEnUS = createFakeInputMethodSubtype("en_US",
-                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
-                IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoFil = createFakeInputMethodSubtype("fil",
-                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
-                IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoFilPH = createFakeInputMethodSubtype("fil_PH",
-                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
-                IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoIn = createFakeInputMethodSubtype("in",
-                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
-                IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        final InputMethodSubtype nonAutoId = createFakeInputMethodSubtype("id",
-                SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
-                IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoEnUS);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-
-            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN, !CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN, CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY,
-                    SUBTYPE_MODE_VOICE));
-            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY,
-                    SUBTYPE_MODE_VOICE));
-            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY,
-                    SUBTYPE_MODE_ANY));
-            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY,
-                    SUBTYPE_MODE_ANY));
-
-            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_GB, !CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_GB, CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-        }
-
-        // Make sure that 3-letter language code ("fil") can be handled.
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoFil);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL, !CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL, CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, !CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-
-            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI, !CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI, CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI_FI, !CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI_FI, CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-        }
-
-        // Make sure that 3-letter language code ("fil_PH") can be handled.
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoFilPH);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL, !CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL, CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, !CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-
-            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI, !CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI, CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI_FI, !CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI_FI, CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-        }
-
-        // Make sure that a subtype whose locale is "in" can be queried with "id".
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoIn);
-            subtypes.add(nonAutoEnUS);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_IN, !CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_IN, CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_ID, !CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_ID, CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-        }
-
-        // Make sure that a subtype whose locale is "id" can be queried with "in".
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(nonAutoId);
-            subtypes.add(nonAutoEnUS);
-            final InputMethodInfo imi = createFakeInputMethodInfo(
-                    "com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
-                    subtypes);
-            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_IN, !CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_IN, CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_ID, !CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-            assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_ID, CHECK_COUNTRY,
-                    SUBTYPE_MODE_KEYBOARD));
-        }
-    }
-
-    @Test
-    public void testChooseSystemVoiceIme() throws Exception {
-        final InputMethodInfo systemIme = createFakeInputMethodInfo("SystemIme", "fake.voice0",
-                true /* isSystem */);
-
-        // Returns null when the config value is null.
-        {
-            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-            methodMap.put(systemIme.getId(), systemIme);
-            assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(methodMap, null, ""));
-        }
-
-        // Returns null when the config value is empty.
-        {
-            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-            methodMap.put(systemIme.getId(), systemIme);
-            assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(methodMap, "", ""));
-        }
-
-        // Returns null when the configured package doesn't have an IME.
-        {
-            assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(new ArrayMap<>(),
-                    systemIme.getPackageName(), ""));
-        }
-
-        // Returns the right one when the current default is null.
-        {
-            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-            methodMap.put(systemIme.getId(), systemIme);
-            assertEquals(systemIme, InputMethodInfoUtils.chooseSystemVoiceIme(methodMap,
-                    systemIme.getPackageName(), null));
-        }
-
-        // Returns the right one when the current default is empty.
-        {
-            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-            methodMap.put(systemIme.getId(), systemIme);
-            assertEquals(systemIme, InputMethodInfoUtils.chooseSystemVoiceIme(methodMap,
-                    systemIme.getPackageName(), ""));
-        }
-
-        // Returns null when the current default isn't found.
-        {
-            assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(new ArrayMap<>(),
-                    systemIme.getPackageName(), systemIme.getId()));
-        }
-
-        // Returns null when there are multiple IMEs defined by the config package.
-        {
-            final InputMethodInfo secondIme = createFakeInputMethodInfo(systemIme.getPackageName(),
-                    "fake.voice1", true /* isSystem */);
-            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-            methodMap.put(systemIme.getId(), systemIme);
-            methodMap.put(secondIme.getId(), secondIme);
-            assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(methodMap,
-                    systemIme.getPackageName(), ""));
-        }
-
-        // Returns the current one when the current default and config point to the same package.
-        {
-            final InputMethodInfo secondIme = createFakeInputMethodInfo("SystemIme", "fake.voice1",
-                    true /* isSystem */);
-            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-            methodMap.put(systemIme.getId(), systemIme);
-            methodMap.put(secondIme.getId(), secondIme);
-            assertEquals(systemIme, InputMethodInfoUtils.chooseSystemVoiceIme(methodMap,
-                    systemIme.getPackageName(), systemIme.getId()));
-        }
-
-        // Doesn't return the current default if it isn't a system app.
-        {
-            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-            final InputMethodInfo nonSystemIme = createFakeInputMethodInfo("NonSystemIme",
-                    "fake.voice0", false /* isSystem */);
-            methodMap.put(nonSystemIme.getId(), nonSystemIme);
-            assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(methodMap,
-                    nonSystemIme.getPackageName(), nonSystemIme.getId()));
-        }
-
-        // Returns null if the configured one isn't a system app.
-        {
-            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-            final InputMethodInfo nonSystemIme = createFakeInputMethodInfo(
-                    "FakeDefaultAutoVoiceIme", "fake.voice0", false /* isSystem */);
-            methodMap.put(systemIme.getId(), systemIme);
-            methodMap.put(nonSystemIme.getId(), nonSystemIme);
-            assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(methodMap,
-                    nonSystemIme.getPackageName(), ""));
-        }
-    }
-
-    private void assertDefaultEnabledImes(final ArrayList<InputMethodInfo> preinstalledImes,
-            final Locale systemLocale, String... expectedImeNames) {
-        final Context context = createTargetContextWithLocales(new LocaleList(systemLocale));
-        final String[] actualImeNames = getPackageNames(
-                InputMethodInfoUtils.getDefaultEnabledImes(context, preinstalledImes));
-        assertEquals(expectedImeNames.length, actualImeNames.length);
-        for (int i = 0; i < expectedImeNames.length; ++i) {
-            assertEquals(expectedImeNames[i], actualImeNames[i]);
-        }
-    }
-
-    private void assertDefaultEnabledMinimumImes(final ArrayList<InputMethodInfo> preinstalledImes,
-            final Locale systemLocale, String... expectedImeNames) {
-        final Context context = createTargetContextWithLocales(new LocaleList(systemLocale));
-        final String[] actualImeNames = getPackageNames(
-                InputMethodInfoUtils.getDefaultEnabledImes(context, preinstalledImes,
-                        true /* onlyMinimum */));
-        assertEquals(expectedImeNames.length, actualImeNames.length);
-        for (int i = 0; i < expectedImeNames.length; ++i) {
-            assertEquals(expectedImeNames[i], actualImeNames[i]);
-        }
-    }
-
-    private static List<InputMethodInfo> cloneViaParcel(final List<InputMethodInfo> list) {
-        Parcel p = null;
-        try {
-            p = Parcel.obtain();
-            p.writeTypedList(list);
-            p.setDataPosition(0);
-            return p.createTypedArrayList(InputMethodInfo.CREATOR);
-        } finally {
-            if (p != null) {
-                p.recycle();
-            }
-        }
-    }
-
-    private Context createTargetContextWithLocales(final LocaleList locales) {
-        final Configuration resourceConfiguration = new Configuration();
-        resourceConfiguration.setLocales(locales);
-        return InstrumentationRegistry.getInstrumentation()
-                .getTargetContext()
-                .createConfigurationContext(resourceConfiguration);
-    }
-
-    private String[] getPackageNames(final ArrayList<InputMethodInfo> imis) {
-        final String[] packageNames = new String[imis.size()];
-        for (int i = 0; i < imis.size(); ++i) {
-            packageNames[i] = imis.get(i).getPackageName();
-        }
-        return packageNames;
-    }
-
-    private static void verifyEquality(InputMethodInfo expected, InputMethodInfo actual) {
-        assertEquals(expected, actual);
-        assertEquals(expected.getSubtypeCount(), actual.getSubtypeCount());
-        for (int subtypeIndex = 0; subtypeIndex < expected.getSubtypeCount(); ++subtypeIndex) {
-            final InputMethodSubtype expectedSubtype = expected.getSubtypeAt(subtypeIndex);
-            final InputMethodSubtype actualSubtype = actual.getSubtypeAt(subtypeIndex);
-            verifyEquality(expectedSubtype, actualSubtype);
-        }
-    }
-
-    private static void verifyEquality(InputMethodSubtype expected, InputMethodSubtype actual) {
-        assertEquals(expected, actual);
-        assertEquals(expected.hashCode(), actual.hashCode());
-    }
-
-    private static InputMethodInfo createFakeInputMethodInfo(String packageName, String name,
-            boolean isSystem) {
-        final ResolveInfo ri = new ResolveInfo();
-        final ServiceInfo si = new ServiceInfo();
-        final ApplicationInfo ai = new ApplicationInfo();
-        ai.packageName = packageName;
-        ai.enabled = true;
-        if (isSystem) {
-            ai.flags |= ApplicationInfo.FLAG_SYSTEM;
-        }
-        si.applicationInfo = ai;
-        si.enabled = true;
-        si.packageName = packageName;
-        si.name = name;
-        si.exported = true;
-        ri.serviceInfo = si;
-        return new InputMethodInfo(ri, false, "", Collections.emptyList(), 1, true);
-    }
-
-    private static InputMethodInfo createFakeInputMethodInfo(String packageName, String name,
-            CharSequence label, boolean isAuxIme, boolean isDefault,
-            List<InputMethodSubtype> subtypes) {
-        final ResolveInfo ri = new ResolveInfo();
-        final ServiceInfo si = new ServiceInfo();
-        final ApplicationInfo ai = new ApplicationInfo();
-        ai.packageName = packageName;
-        ai.enabled = true;
-        ai.flags |= ApplicationInfo.FLAG_SYSTEM;
-        si.applicationInfo = ai;
-        si.enabled = true;
-        si.packageName = packageName;
-        si.name = name;
-        si.exported = true;
-        si.nonLocalizedLabel = label;
-        ri.serviceInfo = si;
-        return new InputMethodInfo(ri, isAuxIme, "", subtypes, 1, isDefault);
-    }
-
-    private static InputMethodSubtype createFakeInputMethodSubtype(String locale, String mode,
-            boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype,
-            boolean isAsciiCapable, boolean isEnabledWhenDefaultIsNotAsciiCapable) {
-        return createFakeInputMethodSubtype(locale, null /* languageTag */, mode, isAuxiliary,
-                overridesImplicitlyEnabledSubtype, isAsciiCapable,
-                isEnabledWhenDefaultIsNotAsciiCapable);
-    }
-
-    private static InputMethodSubtype createFakeInputMethodSubtype(String locale,
-            String languageTag, String mode, boolean isAuxiliary,
-            boolean overridesImplicitlyEnabledSubtype, boolean isAsciiCapable,
-            boolean isEnabledWhenDefaultIsNotAsciiCapable) {
-        final StringBuilder subtypeExtraValue = new StringBuilder();
-        if (isEnabledWhenDefaultIsNotAsciiCapable) {
-            subtypeExtraValue.append(EXTRA_VALUE_PAIR_SEPARATOR);
-            subtypeExtraValue.append(EXTRA_VALUE_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
-        }
-
-        return new InputMethodSubtypeBuilder()
-                .setSubtypeNameResId(0)
-                .setSubtypeIconResId(0)
-                .setSubtypeLocale(locale)
-                .setLanguageTag(languageTag)
-                .setSubtypeMode(mode)
-                .setSubtypeExtraValue(subtypeExtraValue.toString())
-                .setIsAuxiliary(isAuxiliary)
-                .setOverridesImplicitlyEnabledSubtype(overridesImplicitlyEnabledSubtype)
-                .setIsAsciiCapable(isAsciiCapable)
-                .build();
-    }
-
-    private static ArrayList<InputMethodInfo> getImesWithDefaultVoiceIme() {
-        ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>();
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createFakeInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
-                    IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
-                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
-                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
-                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createFakeInputMethodInfo("FakeDefaultAutoVoiceIme",
-                    "fake.voice0", "FakeVoice0", IS_AUX, IS_DEFAULT, subtypes));
-        }
-        preinstalledImes.addAll(getImesWithoutDefaultVoiceIme());
-        return preinstalledImes;
-    }
-
-    private static ArrayList<InputMethodInfo> getImesWithoutDefaultVoiceIme() {
-        ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>();
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createFakeInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
-                    IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
-                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
-                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
-                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createFakeInputMethodInfo("FakeNonDefaultAutoVoiceIme0",
-                    "fake.voice1", "FakeVoice1", IS_AUX, !IS_DEFAULT, subtypes));
-        }
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createFakeInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
-                    IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
-                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
-                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
-                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createFakeInputMethodInfo("FakeNonDefaultAutoVoiceIme1",
-                    "fake.voice2", "FakeVoice2", IS_AUX, !IS_DEFAULT, subtypes));
-        }
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
-                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
-                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createFakeInputMethodInfo("FakeNonDefaultVoiceIme2",
-                    "fake.voice3", "FakeVoice3", IS_AUX, !IS_DEFAULT, subtypes));
-        }
-        {
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
-                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
-                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createFakeInputMethodInfo("FakeDefaultEnKeyboardIme",
-                    "fake.keyboard0", "FakeKeyboard0", !IS_AUX, IS_DEFAULT, subtypes));
-        }
-        return preinstalledImes;
-    }
-
-    private static boolean contains(final String[] textList, final String textToBeChecked) {
-        if (textList == null) {
-            return false;
-        }
-        for (final String text : textList) {
-            if (Objects.equals(textToBeChecked, text)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private static ArrayList<InputMethodInfo> getSamplePreinstalledImes(final String localeString) {
-        ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>();
-
-        // a fake Voice IME
-        {
-            final boolean isDefaultIme = false;
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createFakeInputMethodSubtype("", SUBTYPE_MODE_VOICE, IS_AUX,
-                    IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
-                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.voice",
-                    "com.android.inputmethod.voice", "FakeVoiceIme", IS_AUX, isDefaultIme,
-                    subtypes));
-        }
-        // a fake Hindi IME
-        {
-            final boolean isDefaultIme = contains(new String[]{ "hi", "en-rIN" }, localeString);
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            // TODO: This subtype should be marked as IS_ASCII_CAPABLE
-            subtypes.add(createFakeInputMethodSubtype("en_IN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
-                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
-                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createFakeInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
-                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
-                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.hindi",
-                    "com.android.inputmethod.hindi", "FakeHindiIme", !IS_AUX, isDefaultIme,
-                    subtypes));
-        }
-
-        // a fake Pinyin IME
-        {
-            final boolean isDefaultIme = contains(new String[]{ "zh-rCN" }, localeString);
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createFakeInputMethodSubtype("zh_CN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
-                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
-                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.pinyin",
-                    "com.android.apps.inputmethod.pinyin", "FakePinyinIme", !IS_AUX, isDefaultIme,
-                    subtypes));
-        }
-
-        // a fake Korean IME
-        {
-            final boolean isDefaultIme = contains(new String[]{ "ko" }, localeString);
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createFakeInputMethodSubtype("ko", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
-                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
-                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.korean",
-                    "com.android.apps.inputmethod.korean", "FakeKoreanIme", !IS_AUX, isDefaultIme,
-                    subtypes));
-        }
-
-        // a fake Latin IME
-        {
-            final boolean isDefaultIme = contains(
-                    new String[]{ "en-rUS", "en-rGB", "en-rIN", "en", "hi" }, localeString);
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createFakeInputMethodSubtype("en_US", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
-                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
-                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createFakeInputMethodSubtype("en_GB", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
-                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
-                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createFakeInputMethodSubtype("en_IN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
-                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
-                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createFakeInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
-                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
-                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.latin",
-                    "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, isDefaultIme,
-                    subtypes));
-        }
-
-        // a fake Japanese IME
-        {
-            final boolean isDefaultIme = contains(new String[]{ "ja", "ja-rJP" }, localeString);
-            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
-            subtypes.add(createFakeInputMethodSubtype("ja", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
-                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
-                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            subtypes.add(createFakeInputMethodSubtype("emoji", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
-                    !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
-                    !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
-            preinstalledImes.add(createFakeInputMethodInfo("com.android.apps.inputmethod.japanese",
-                    "com.android.apps.inputmethod.japanese", "FakeJapaneseIme", !IS_AUX,
-                    isDefaultIme, subtypes));
-        }
-
-        return preinstalledImes;
-    }
-
-    @Test
-    public void testIsSoftInputModeStateVisibleAllowed() {
-        // On pre-P devices, SOFT_INPUT_STATE_VISIBLE/SOFT_INPUT_STATE_ALWAYS_VISIBLE are always
-        // allowed, regardless of the focused view state.
-        assertTrue(InputMethodUtils.isSoftInputModeStateVisibleAllowed(
-                Build.VERSION_CODES.O_MR1, 0));
-        assertTrue(InputMethodUtils.isSoftInputModeStateVisibleAllowed(
-                Build.VERSION_CODES.O_MR1, StartInputFlags.VIEW_HAS_FOCUS));
-        assertTrue(InputMethodUtils.isSoftInputModeStateVisibleAllowed(
-                Build.VERSION_CODES.O_MR1,
-                StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR));
-
-        // On P+ devices, SOFT_INPUT_STATE_VISIBLE/SOFT_INPUT_STATE_ALWAYS_VISIBLE are allowed only
-        // when there is a focused View and its View#onCheckIsTextEditor() returns true.
-        assertFalse(InputMethodUtils.isSoftInputModeStateVisibleAllowed(
-                Build.VERSION_CODES.P, 0));
-        assertFalse(InputMethodUtils.isSoftInputModeStateVisibleAllowed(
-                Build.VERSION_CODES.P, StartInputFlags.VIEW_HAS_FOCUS));
-        assertTrue(InputMethodUtils.isSoftInputModeStateVisibleAllowed(
-                Build.VERSION_CODES.P,
-                StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR));
-    }
-
-    private static IntArray createSubtypeHashCodeArrayFromStr(String subtypeHashCodesStr) {
-        final IntArray subtypes = new IntArray();
-        final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
-                new TextUtils.SimpleStringSplitter(';');
-        if (TextUtils.isEmpty(subtypeHashCodesStr)) {
-            return subtypes;
-        }
-        imeSubtypeSplitter.setString(subtypeHashCodesStr);
-        while (imeSubtypeSplitter.hasNext()) {
-            subtypes.add(Integer.parseInt(imeSubtypeSplitter.next()));
-        }
-        return subtypes;
-    }
-
-    private static void verifyUpdateEnabledImeString(@NonNull String expectedEnabledImeStr,
-            @NonNull String initialEnabledImeStr, @NonNull String imeId,
-            @NonNull String enabledSubtypeHashCodesStr) {
-        assertEquals(expectedEnabledImeStr,
-                InputMethodUtils.InputMethodSettings.updateEnabledImeString(initialEnabledImeStr,
-                        imeId, createSubtypeHashCodeArrayFromStr(enabledSubtypeHashCodesStr)));
-    }
-
-    private static TestContext createMockContext(int userId) {
-        return new TestContext(InstrumentationRegistry.getInstrumentation()
-                .getTargetContext(), userId);
-    }
-
-    private static class TestContext extends ContextWrapper {
-        private int mUserId;
-        private ContentResolver mResolver;
-        private Resources mResources;
-
-        private static TestContext sSecondaryUserContext;
-
-        TestContext(@NonNull Context context, int userId) {
-            super(context);
-            mUserId = userId;
-            mResolver = mock(MockContentResolver.class);
-            when(mResolver.acquireProvider(Settings.Secure.CONTENT_URI)).thenReturn(
-                    mock(IContentProvider.class));
-            mResources = mock(Resources.class);
-
-            final Configuration configuration = new Configuration();
-            if (userId == 0) {
-                configuration.setLocale(LOCALE_EN_US);
-            } else {
-                configuration.setLocale(LOCALE_FR_CA);
-            }
-            doReturn(configuration).when(mResources).getConfiguration();
-        }
-
-        @Override
-        public Context createContextAsUser(UserHandle user, int flags) {
-            if (user.getIdentifier() != UserHandle.USER_SYSTEM) {
-                return sSecondaryUserContext = new TestContext(this, user.getIdentifier());
-            }
-            return this;
-        }
-
-        @Override
-        public int getUserId() {
-            return mUserId;
-        }
-
-        @Override
-        public ContentResolver getContentResolver() {
-            return mResolver;
-        }
-
-        @Override
-        public Resources getResources() {
-            return mResources;
-        }
-
-        static Context getSecondaryUserContext() {
-            return sSecondaryUserContext;
-        }
-    }
-
-    private static void verifySplitEnabledImeStr(@NonNull String enabledImeStr,
-            @NonNull String... expected) {
-        final ArrayList<String> actual = new ArrayList<>();
-        InputMethodUtils.splitEnabledImeStr(enabledImeStr, actual::add);
-        if (expected.length == 0) {
-            Truth.assertThat(actual).isEmpty();
-        } else {
-            Truth.assertThat(actual).containsExactlyElementsIn(expected);
-        }
-    }
-
-    @Test
-    public void testSplitEnabledImeStr() {
-        verifySplitEnabledImeStr("");
-        verifySplitEnabledImeStr("com.android/.ime1", "com.android/.ime1");
-        verifySplitEnabledImeStr("com.android/.ime1;1;2;3", "com.android/.ime1");
-        verifySplitEnabledImeStr("com.android/.ime1;1;2;3:com.android/.ime2",
-                "com.android/.ime1", "com.android/.ime2");
-        verifySplitEnabledImeStr("com.android/.ime1:com.android/.ime2",
-                "com.android/.ime1", "com.android/.ime2");
-        verifySplitEnabledImeStr("com.android/.ime1:com.android/.ime2:com.android/.ime3",
-                "com.android/.ime1", "com.android/.ime2", "com.android/.ime3");
-        verifySplitEnabledImeStr("com.android/.ime1;1:com.android/.ime2;1:com.android/.ime3;1",
-                "com.android/.ime1", "com.android/.ime2", "com.android/.ime3");
-    }
-
-    @Test
-    public void testConcatEnabledImeIds() {
-        Truth.assertThat(InputMethodUtils.concatEnabledImeIds("")).isEmpty();
-        Truth.assertThat(InputMethodUtils.concatEnabledImeIds("", "com.android/.ime1"))
-                .isEqualTo("com.android/.ime1");
-        Truth.assertThat(InputMethodUtils.concatEnabledImeIds(
-                        "com.android/.ime1", "com.android/.ime1"))
-                .isEqualTo("com.android/.ime1");
-        Truth.assertThat(InputMethodUtils.concatEnabledImeIds(
-                        "com.android/.ime1", "com.android/.ime2"))
-                .isEqualTo("com.android/.ime1:com.android/.ime2");
-        Truth.assertThat(InputMethodUtils.concatEnabledImeIds(
-                        "com.android/.ime1", "com.android/.ime2", "com.android/.ime3"))
-                .isEqualTo("com.android/.ime1:com.android/.ime2:com.android/.ime3");
-        Truth.assertThat(InputMethodUtils.concatEnabledImeIds(
-                        "com.android/.ime1:com.android/.ime2", "com.android/.ime1"))
-                .isEqualTo("com.android/.ime1:com.android/.ime2");
-        Truth.assertThat(InputMethodUtils.concatEnabledImeIds(
-                        "com.android/.ime1:com.android/.ime2", "com.android/.ime3"))
-                .isEqualTo("com.android/.ime1:com.android/.ime2:com.android/.ime3");
-    }
-
-    @Test
-    public void updateEnabledImeStringTest() {
-        // No change cases
-        verifyUpdateEnabledImeString(
-                "com.android/.ime1",
-                "com.android/.ime1", "com.android/.ime1", "");
-        verifyUpdateEnabledImeString(
-                "com.android/.ime1",
-                "com.android/.ime1", "com.android/.ime2", "");
-
-        // To enable subtypes
-        verifyUpdateEnabledImeString(
-                "com.android/.ime1",
-                "com.android/.ime1", "com.android/.ime2", "");
-        verifyUpdateEnabledImeString(
-                "com.android/.ime1;1",
-                "com.android/.ime1", "com.android/.ime1", "1");
-
-        verifyUpdateEnabledImeString(
-                "com.android/.ime1;1;2;3",
-                "com.android/.ime1", "com.android/.ime1", "1;2;3");
-
-        verifyUpdateEnabledImeString(
-                "com.android/.ime1;1;2;3:com.android/.ime2",
-                "com.android/.ime1:com.android/.ime2", "com.android/.ime1", "1;2;3");
-        verifyUpdateEnabledImeString(
-                "com.android/.ime0:com.android/.ime1;1;2;3",
-                "com.android/.ime0:com.android/.ime1", "com.android/.ime1", "1;2;3");
-        verifyUpdateEnabledImeString(
-                "com.android/.ime0:com.android/.ime1;1;2;3:com.android/.ime2",
-                "com.android/.ime0:com.android/.ime1:com.android/.ime2", "com.android/.ime1",
-                "1;2;3");
-
-        // To reset enabled subtypes
-        verifyUpdateEnabledImeString(
-                "com.android/.ime1",
-                "com.android/.ime1;1", "com.android/.ime1", "");
-        verifyUpdateEnabledImeString(
-                "com.android/.ime1",
-                "com.android/.ime1;1;2;3", "com.android/.ime1", "");
-        verifyUpdateEnabledImeString(
-                "com.android/.ime1:com.android/.ime2",
-                "com.android/.ime1;1;2;3:com.android/.ime2", "com.android/.ime1", "");
-
-        verifyUpdateEnabledImeString(
-                "com.android/.ime0:com.android/.ime1",
-                "com.android/.ime0:com.android/.ime1;1;2;3", "com.android/.ime1", "");
-        verifyUpdateEnabledImeString(
-                "com.android/.ime0:com.android/.ime1:com.android/.ime2",
-                "com.android/.ime0:com.android/.ime1;1;2;3:com.android/.ime2", "com.android/.ime1",
-                "");
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java
deleted file mode 100644
index 255cb64..0000000
--- a/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java
+++ /dev/null
@@ -1,397 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.inputmethod;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
-
-import android.os.LocaleList;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Locale;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class LocaleUtilsTest {
-
-    private static final LocaleUtils.LocaleExtractor<Locale> sIdentityMapper = source -> source;
-
-    @Test
-    public void testFilterByLanguageEmptyLanguageList() throws Exception {
-        final ArrayList<Locale> availableLocales = new ArrayList<>();
-        availableLocales.add(Locale.forLanguageTag("en-US"));
-        availableLocales.add(Locale.forLanguageTag("fr-CA"));
-        availableLocales.add(Locale.forLanguageTag("in"));
-        availableLocales.add(Locale.forLanguageTag("ja"));
-        availableLocales.add(Locale.forLanguageTag("fil"));
-
-        final LocaleList preferredLocales = LocaleList.getEmptyLocaleList();
-
-        final ArrayList<Locale> dest = new ArrayList<>();
-        LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-        assertEquals(0, dest.size());
-    }
-
-    @Test
-    public void testFilterDoesNotMatchAnything() throws Exception {
-        final ArrayList<Locale> availableLocales = new ArrayList<>();
-        availableLocales.add(Locale.forLanguageTag("en-US"));
-        availableLocales.add(Locale.forLanguageTag("fr-CA"));
-        availableLocales.add(Locale.forLanguageTag("in"));
-        availableLocales.add(Locale.forLanguageTag("ja"));
-        availableLocales.add(Locale.forLanguageTag("fil"));
-
-        final LocaleList preferredLocales = LocaleList.forLanguageTags("zh-Hans-TW");
-
-        final ArrayList<Locale> dest = new ArrayList<>();
-        LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-        assertEquals(0, dest.size());
-    }
-
-    @Test
-    public void testFilterByLanguageEmptySource() throws Exception {
-        final ArrayList<Locale> availableLocales = new ArrayList<>();
-
-        final LocaleList preferredLocales = LocaleList.forLanguageTags("fr,en-US,ja-JP");
-
-        final ArrayList<Locale> dest = new ArrayList<>();
-        LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-        assertEquals(0, dest.size());
-    }
-
-    @Test
-    public void testFilterByLanguageNullAvailableLocales() throws Exception {
-        {
-            final LocaleList preferredLocales =
-                    LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN");
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(null);
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(0, dest.size());
-        }
-        {
-            final LocaleList preferredLocales =
-                    LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN");
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(null);
-            availableLocales.add(null);
-            availableLocales.add(null);
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(0, dest.size());
-        }
-        {
-            final LocaleList preferredLocales =
-                    LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN");
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(null);
-            availableLocales.add(Locale.forLanguageTag("en-US"));
-            availableLocales.add(null);
-            availableLocales.add(null);
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(1, dest.size());
-            assertEquals(availableLocales.get(1), dest.get(0));  // "en-US"
-        }
-        {
-            final LocaleList preferredLocales =
-                    LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN");
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(null);
-            availableLocales.add(Locale.forLanguageTag("en"));
-            availableLocales.add(null);
-            availableLocales.add(null);
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(1, dest.size());
-            assertEquals(availableLocales.get(1), dest.get(0));  // "en"
-        }
-        {
-            final LocaleList preferredLocales =
-                    LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN");
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(null);
-            availableLocales.add(Locale.forLanguageTag("ja-JP"));
-            availableLocales.add(null);
-            availableLocales.add(null);
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(0, dest.size());
-        }
-    }
-
-    @Test
-    public void testFilterByLanguage() throws Exception {
-        {
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(Locale.forLanguageTag("en-US"));
-            availableLocales.add(Locale.forLanguageTag("fr-CA"));
-            availableLocales.add(Locale.forLanguageTag("in"));
-            availableLocales.add(Locale.forLanguageTag("ja"));
-            availableLocales.add(Locale.forLanguageTag("fil"));
-
-            final LocaleList preferredLocales = LocaleList.forLanguageTags("fr,en-US,ja-JP");
-
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(3, dest.size());
-            assertEquals(availableLocales.get(1), dest.get(0));  // "fr-CA"
-            assertEquals(availableLocales.get(0), dest.get(1));  // "en-US"
-            assertEquals(availableLocales.get(3), dest.get(2));  // "ja"
-        }
-        {
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(Locale.forLanguageTag("en-US"));
-            availableLocales.add(Locale.forLanguageTag("en-GB"));
-            availableLocales.add(Locale.forLanguageTag("en-IN"));
-
-            final LocaleList preferredLocales = LocaleList.forLanguageTags("en-US");
-
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(1, dest.size());
-            assertEquals(availableLocales.get(0), dest.get(0));  // "en-US"
-        }
-    }
-
-    @Test
-    public void testFilterByLanguageTheSameLanguage() throws Exception {
-        {
-            final LocaleList preferredLocales =
-                    LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN");
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(Locale.forLanguageTag("fr-CA"));
-            availableLocales.add(Locale.forLanguageTag("en-US"));
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(1, dest.size());
-            assertEquals(availableLocales.get(1), dest.get(0));  // "en-US"
-        }
-        {
-            final LocaleList preferredLocales =
-                    LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN");
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(Locale.forLanguageTag("fr-CA"));
-            availableLocales.add(Locale.forLanguageTag("en"));
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(1, dest.size());
-            assertEquals(availableLocales.get(1), dest.get(0));  // "en"
-        }
-        {
-            final LocaleList preferredLocales =
-                    LocaleList.forLanguageTags("en-AU,en-GB,en-US,en,en-IN");
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(Locale.forLanguageTag("fr-CA"));
-            availableLocales.add(Locale.forLanguageTag("en-CA"));
-            availableLocales.add(Locale.forLanguageTag("en-IN"));
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(1, dest.size());
-            assertEquals(availableLocales.get(2), dest.get(0));  // "en-IN"
-        }
-        {
-            final LocaleList preferredLocales =
-                    LocaleList.forLanguageTags("en-AU,en-GB,en-US,en-IN");
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(Locale.forLanguageTag("fr-CA"));
-            availableLocales.add(Locale.forLanguageTag("en-CA"));
-            availableLocales.add(Locale.forLanguageTag("en-NZ"));
-            availableLocales.add(Locale.forLanguageTag("en-BZ"));
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(1, dest.size());
-            assertEquals(availableLocales.get(1), dest.get(0));  // "en-CA"
-        }
-    }
-
-    @Test
-    public void testFilterByLanguageFallbackRules() throws Exception {
-        {
-            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn-RS");
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-BA"));
-            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-CS"));
-            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-ME"));
-            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-RS"));
-            availableLocales.add(Locale.forLanguageTag("sr-Latn-BA"));
-            availableLocales.add(Locale.forLanguageTag("sr-Latn-CS"));
-            availableLocales.add(Locale.forLanguageTag("sr-Latn-ME"));
-            availableLocales.add(Locale.forLanguageTag("sr-Latn-RS"));
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(1, dest.size());
-            assertEquals(availableLocales.get(7), dest.get(0));  // "sr-Latn-RS"
-        }
-        {
-            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn-RS-x-android");
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-BA"));
-            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-CS"));
-            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-ME"));
-            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-RS"));
-            availableLocales.add(Locale.forLanguageTag("sr-Latn-BA"));
-            availableLocales.add(Locale.forLanguageTag("sr-Latn-CS"));
-            availableLocales.add(Locale.forLanguageTag("sr-Latn-ME"));
-            availableLocales.add(Locale.forLanguageTag("sr-Latn-RS"));
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(1, dest.size());
-            assertEquals(availableLocales.get(7), dest.get(0));  // "sr-Latn-RS"
-        }
-        {
-            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn-RS");
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-BA-x-android"));
-            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-CS-x-android"));
-            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-ME-x-android"));
-            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-RS-x-android"));
-            availableLocales.add(Locale.forLanguageTag("sr-Latn-BA-x-android"));
-            availableLocales.add(Locale.forLanguageTag("sr-Latn-CS-x-android"));
-            availableLocales.add(Locale.forLanguageTag("sr-Latn-ME-x-android"));
-            availableLocales.add(Locale.forLanguageTag("sr-Latn-RS-x-android"));
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(1, dest.size());
-            assertEquals(availableLocales.get(7), dest.get(0));  // "sr-Latn-RS-x-android"
-        }
-
-        {
-            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn-RS");
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(Locale.forLanguageTag("sr"));
-            availableLocales.add(Locale.forLanguageTag("sr-Cyrl"));
-            availableLocales.add(Locale.forLanguageTag("sr-Latn"));
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(1, dest.size());
-            assertEquals(availableLocales.get(2), dest.get(0));  // "sr-Latn"
-        }
-
-        {
-            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-RS");
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(Locale.forLanguageTag("sr"));
-            availableLocales.add(Locale.forLanguageTag("sr-RS"));
-            availableLocales.add(Locale.forLanguageTag("sr-Latn"));
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(1, dest.size());
-            assertEquals(availableLocales.get(0), dest.get(0));  // "sr"
-        }
-
-        {
-            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn");
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(Locale.forLanguageTag("sr"));
-            availableLocales.add(Locale.forLanguageTag("sr-RS"));
-            availableLocales.add(Locale.forLanguageTag("sr-Latn"));
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(1, dest.size());
-            assertEquals(availableLocales.get(2), dest.get(0));  // "sr-Latn"
-        }
-
-        {
-            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr");
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(Locale.forLanguageTag("sr"));
-            availableLocales.add(Locale.forLanguageTag("sr-RS"));
-            availableLocales.add(Locale.forLanguageTag("sr-Latn"));
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(1, dest.size());
-            assertEquals(availableLocales.get(0), dest.get(0));  // "sr"
-        }
-        {
-            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr");
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(Locale.forLanguageTag("sr-Latn"));
-            availableLocales.add(Locale.forLanguageTag("sr-RS"));
-            availableLocales.add(Locale.forLanguageTag("sr"));
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(1, dest.size());
-            assertEquals(availableLocales.get(1), dest.get(0));  // "sr-RS"
-        }
-
-        {
-            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr");
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-RS"));
-            availableLocales.add(Locale.forLanguageTag("sr-Latn-RS"));
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(1, dest.size());
-            assertEquals(availableLocales.get(0), dest.get(0));  // "sr-Cyrl-RS"
-        }
-        {
-            final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-Latn");
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(Locale.forLanguageTag("sr-Latn-RS"));
-            availableLocales.add(Locale.forLanguageTag("sr-Cyrl-RS"));
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(1, dest.size());
-            assertEquals(availableLocales.get(0), dest.get(0));  // "sr-Latn-RS"
-        }
-    }
-
-    @Test
-    public void testFilterKnownLimitation() throws Exception {
-        // Following test cases are not for intentional behavior but checks for preventing the
-        // behavior from becoming worse.
-        {
-            final LocaleList preferredLocales = LocaleList.forLanguageTags("ja-Hrkt");
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(Locale.forLanguageTag("ja-Jpan"));
-            availableLocales.add(Locale.forLanguageTag("ja-Hrkt"));
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(1, dest.size());
-            // Should be ja-Jpan since it supports ja-Hrkt and listed before ja-Hrkt.
-            assertEquals(availableLocales.get(1), dest.get(0));
-        }
-        {
-            final LocaleList preferredLocales = LocaleList.forLanguageTags("zh-Hani");
-            final ArrayList<Locale> availableLocales = new ArrayList<>();
-            availableLocales.add(Locale.forLanguageTag("zh-Hans"));
-            availableLocales.add(Locale.forLanguageTag("zh-Hant"));
-            availableLocales.add(Locale.forLanguageTag("zh-Hanb"));
-            availableLocales.add(Locale.forLanguageTag("zh-Hani"));
-            final ArrayList<Locale> dest = new ArrayList<>();
-            LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
-            assertEquals(1, dest.size());
-            // Should be zh-Hans since it supports zh-Hani. Also zh-Hant, zh-Hanb supports zh-Hani.
-            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/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
deleted file mode 100644
index e871fc5..0000000
--- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job;
-
-import static com.android.servicestests.apps.jobtestapp.TestJobService.ACTION_JOB_STARTED;
-import static com.android.servicestests.apps.jobtestapp.TestJobService.ACTION_JOB_STOPPED;
-import static com.android.servicestests.apps.jobtestapp.TestJobService.JOB_PARAMS_EXTRA_KEY;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.app.IActivityManager;
-import android.app.job.JobParameters;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.IDeviceIdleController;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.servicestests.apps.jobtestapp.TestJobActivity;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests that background restrictions on jobs work as expected.
- * This test requires test-apps/JobTestApp to be installed on the device.
- * To run this test from root of checkout:
- * <pre>
- *  mmm -j32 frameworks/base/services/tests/servicestests/
- *  adb install -r $OUT/data/app/JobTestApp/JobTestApp.apk
- *  adb install -r $OUT/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
- *  adb  shell am instrument -e class 'com.android.server.job.BackgroundRestrictionsTest' -w \
- *  com.android.frameworks.servicestests
- * </pre>
- */
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class BackgroundRestrictionsTest {
-    private static final String TAG = BackgroundRestrictionsTest.class.getSimpleName();
-    private static final String TEST_APP_PACKAGE = "com.android.servicestests.apps.jobtestapp";
-    private static final String TEST_APP_ACTIVITY = TEST_APP_PACKAGE + ".TestJobActivity";
-    private static final long POLL_INTERVAL = 500;
-    private static final long DEFAULT_WAIT_TIMEOUT = 10_000;
-
-    private Context mContext;
-    private AppOpsManager mAppOpsManager;
-    private IDeviceIdleController mDeviceIdleController;
-    private IActivityManager mIActivityManager;
-    private volatile int mTestJobId = -1;
-    private int mTestPackageUid;
-    /* accesses must be synchronized on itself */
-    private final TestJobStatus mTestJobStatus = new TestJobStatus();
-    private final BroadcastReceiver mJobStateChangeReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final JobParameters params = intent.getParcelableExtra(JOB_PARAMS_EXTRA_KEY);
-            Log.d(TAG, "Received action " + intent.getAction());
-            synchronized (mTestJobStatus) {
-                switch (intent.getAction()) {
-                    case ACTION_JOB_STARTED:
-                        mTestJobStatus.running = true;
-                        mTestJobStatus.jobId = params.getJobId();
-                        mTestJobStatus.stopReason = JobParameters.STOP_REASON_UNDEFINED;
-                        break;
-                    case ACTION_JOB_STOPPED:
-                        mTestJobStatus.running = false;
-                        mTestJobStatus.jobId = params.getJobId();
-                        mTestJobStatus.stopReason = params.getStopReason();
-                        break;
-                }
-            }
-        }
-    };
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
-        mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
-                ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
-        mIActivityManager = ActivityManager.getService();
-        mTestPackageUid = mContext.getPackageManager().getPackageUid(TEST_APP_PACKAGE, 0);
-        mTestJobStatus.reset();
-        final IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(ACTION_JOB_STARTED);
-        intentFilter.addAction(ACTION_JOB_STOPPED);
-        mContext.registerReceiver(mJobStateChangeReceiver, intentFilter,
-                Context.RECEIVER_EXPORTED_UNAUDITED);
-        setAppOpsModeAllowed(true);
-        setPowerExemption(false);
-    }
-
-    private void scheduleTestJob() {
-        mTestJobId = (int) (SystemClock.uptimeMillis() / 1000);
-        final Intent scheduleJobIntent = new Intent(TestJobActivity.ACTION_START_JOB);
-        scheduleJobIntent.putExtra(TestJobActivity.EXTRA_JOB_ID_KEY, mTestJobId);
-        scheduleJobIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        scheduleJobIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY));
-        mContext.startActivity(scheduleJobIntent);
-    }
-
-    private void scheduleAndAssertJobStarted() throws Exception {
-        scheduleTestJob();
-        Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY);
-        assertTrue("Job did not start after scheduling", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
-    }
-
-    @FlakyTest
-    @Test
-    public void testPowerExemption() throws Exception {
-        scheduleAndAssertJobStarted();
-        setAppOpsModeAllowed(false);
-        mIActivityManager.makePackageIdle(TEST_APP_PACKAGE, UserHandle.USER_CURRENT);
-        assertTrue("Job did not stop after putting app under bg-restriction",
-                awaitJobStop(DEFAULT_WAIT_TIMEOUT,
-                        JobParameters.STOP_REASON_BACKGROUND_RESTRICTION));
-
-        setPowerExemption(true);
-        scheduleTestJob();
-        Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY);
-        assertTrue("Job did not start when the app was in the power exemption list",
-                awaitJobStart(DEFAULT_WAIT_TIMEOUT));
-
-        setPowerExemption(false);
-        assertTrue("Job did not stop after removing from the power exemption list",
-                awaitJobStop(DEFAULT_WAIT_TIMEOUT,
-                        JobParameters.STOP_REASON_BACKGROUND_RESTRICTION));
-
-        scheduleTestJob();
-        Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY);
-        assertFalse("Job started under bg-restrictions", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
-        setPowerExemption(true);
-        assertTrue("Job did not start when the app was in the power exemption list",
-                awaitJobStart(DEFAULT_WAIT_TIMEOUT));
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        final Intent cancelJobsIntent = new Intent(TestJobActivity.ACTION_CANCEL_JOBS);
-        cancelJobsIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY));
-        cancelJobsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(cancelJobsIntent);
-        mContext.unregisterReceiver(mJobStateChangeReceiver);
-        Thread.sleep(500); // To avoid race with register in the next setUp
-        setAppOpsModeAllowed(true);
-        setPowerExemption(false);
-    }
-
-    private void setPowerExemption(boolean exempt) throws RemoteException {
-        if (exempt) {
-            mDeviceIdleController.addPowerSaveWhitelistApp(TEST_APP_PACKAGE);
-        } else {
-            mDeviceIdleController.removePowerSaveWhitelistApp(TEST_APP_PACKAGE);
-        }
-    }
-
-    private void setAppOpsModeAllowed(boolean allow) {
-        mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mTestPackageUid,
-                TEST_APP_PACKAGE, allow ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
-    }
-
-    private boolean awaitJobStart(long timeout) throws InterruptedException {
-        return waitUntilTrue(timeout, () -> {
-            synchronized (mTestJobStatus) {
-                return (mTestJobStatus.jobId == mTestJobId) && mTestJobStatus.running;
-            }
-        });
-    }
-
-    private boolean awaitJobStop(long timeout, @JobParameters.StopReason int expectedStopReason)
-            throws InterruptedException {
-        return waitUntilTrue(timeout, () -> {
-            synchronized (mTestJobStatus) {
-                return (mTestJobStatus.jobId == mTestJobId) && !mTestJobStatus.running
-                        && (expectedStopReason == JobParameters.STOP_REASON_UNDEFINED
-                        || mTestJobStatus.stopReason == expectedStopReason);
-            }
-        });
-    }
-
-    private boolean waitUntilTrue(long timeout, Condition condition) throws InterruptedException {
-        final long deadLine = SystemClock.uptimeMillis() + timeout;
-        do {
-            Thread.sleep(POLL_INTERVAL);
-        } while (!condition.isTrue() && SystemClock.uptimeMillis() < deadLine);
-        return condition.isTrue();
-    }
-
-    private static final class TestJobStatus {
-        int jobId;
-        int stopReason;
-        boolean running;
-
-        private void reset() {
-            running = false;
-            stopReason = jobId = 0;
-        }
-    }
-
-    private interface Condition {
-        boolean isTrue();
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index f5d50d1..6986cab 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -305,9 +305,9 @@
         doAnswer(invocation -> {
             Object[] args = invocation.getArguments();
             mStorageManager.unlockCeStorage(/* userId= */ (int) args[0],
-                    /* secret= */ (byte[]) args[2]);
+                    /* secret= */ (byte[]) args[1]);
             return null;
-        }).when(sm).unlockCeStorage(anyInt(), anyInt(), any());
+        }).when(sm).unlockCeStorage(anyInt(), any());
 
         doAnswer(invocation -> {
             Object[] args = invocation.getArguments();
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 5a62d92..5081198 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -16,6 +16,8 @@
 
 package com.android.server.locksettings;
 
+import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS;
+
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
@@ -30,25 +32,30 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.PropertyInvalidatedCache;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.gatekeeper.GateKeeperResponse;
 import android.text.TextUtils;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.widget.ILockSettingsStateListener;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -59,6 +66,7 @@
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Before
     public void setUp() {
@@ -399,6 +407,60 @@
     }
 
     @Test
+    public void testVerifyCredential_notifyLockSettingsStateListeners_whenGoodPassword()
+            throws Exception {
+        mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS);
+        final LockscreenCredential password = newPassword("password");
+        setCredential(PRIMARY_USER_ID, password);
+        final ILockSettingsStateListener listener = mockLockSettingsStateListener();
+        mLocalService.registerLockSettingsStateListener(listener);
+
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */)
+                        .getResponseCode());
+
+        verify(listener).onAuthenticationSucceeded(PRIMARY_USER_ID);
+    }
+
+    @Test
+    public void testVerifyCredential_notifyLockSettingsStateListeners_whenBadPassword()
+            throws Exception {
+        mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS);
+        final LockscreenCredential password = newPassword("password");
+        setCredential(PRIMARY_USER_ID, password);
+        final LockscreenCredential badPassword = newPassword("badPassword");
+        final ILockSettingsStateListener listener = mockLockSettingsStateListener();
+        mLocalService.registerLockSettingsStateListener(listener);
+
+        assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+                mService.verifyCredential(badPassword, PRIMARY_USER_ID, 0 /* flags */)
+                        .getResponseCode());
+
+        verify(listener).onAuthenticationFailed(PRIMARY_USER_ID);
+    }
+
+    @Test
+    public void testLockSettingsStateListener_registeredThenUnregistered() throws Exception {
+        mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS);
+        final LockscreenCredential password = newPassword("password");
+        setCredential(PRIMARY_USER_ID, password);
+        final LockscreenCredential badPassword = newPassword("badPassword");
+        final ILockSettingsStateListener listener = mockLockSettingsStateListener();
+
+        mLocalService.registerLockSettingsStateListener(listener);
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */)
+                        .getResponseCode());
+        verify(listener).onAuthenticationSucceeded(PRIMARY_USER_ID);
+
+        mLocalService.unregisterLockSettingsStateListener(listener);
+        assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+                mService.verifyCredential(badPassword, PRIMARY_USER_ID, 0 /* flags */)
+                        .getResponseCode());
+        verify(listener, never()).onAuthenticationFailed(PRIMARY_USER_ID);
+    }
+
+    @Test
     public void testSetCredentialNotPossibleInSecureFrpModeDuringSuw() {
         setUserSetupComplete(false);
         setSecureFrpMode(true);
@@ -537,4 +599,12 @@
             assertNotEquals(0, mGateKeeperService.getSecureUserId(userId));
         }
     }
+
+    private ILockSettingsStateListener mockLockSettingsStateListener() {
+        ILockSettingsStateListener listener = mock(ILockSettingsStateListener.Stub.class);
+        IBinder binder = mock(IBinder.class);
+        when(binder.isBinderAlive()).thenReturn(true);
+        when(listener.asBinder()).thenReturn(binder);
+        return listener;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index 097cc51..abd3abe 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -49,6 +49,7 @@
 import static org.testng.Assert.assertThrows;
 
 import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions.LaunchCookie;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.pm.ApplicationInfo;
@@ -784,7 +785,7 @@
             @RecordContent int recordedContent)
             throws NameNotFoundException {
         MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
-        projection.setLaunchCookie(mock(IBinder.class));
+        projection.setLaunchCookie(new LaunchCookie());
         projection.start(mIMediaProjectionCallback);
         projection.notifyVirtualDisplayCreated(10);
         // Waiting for user to review consent.
@@ -825,7 +826,7 @@
     public void testSetUserReviewGrantedConsentResult_displayMirroring_noPriorSession()
             throws NameNotFoundException {
         MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
-        projection.setLaunchCookie(mock(IBinder.class));
+        projection.setLaunchCookie(new LaunchCookie());
         projection.start(mIMediaProjectionCallback);
         // Skip setting the prior session details.
 
@@ -844,7 +845,7 @@
     public void testSetUserReviewGrantedConsentResult_displayMirroring_sessionNotWaiting()
             throws NameNotFoundException {
         MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
-        projection.setLaunchCookie(mock(IBinder.class));
+        projection.setLaunchCookie(new LaunchCookie());
         projection.start(mIMediaProjectionCallback);
         // Session is not waiting for user's consent.
         doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
index 13dc120..d6d2b6d 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.net;
 
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -327,12 +328,20 @@
         isRestrictedForLowPowerStandby.put(INetd.FIREWALL_RULE_DENY, true);
         expected.put(FIREWALL_CHAIN_LOW_POWER_STANDBY, isRestrictedForLowPowerStandby);
 
+        // Background chain
+        final ArrayMap<Integer, Boolean> isRestrictedInBackground = new ArrayMap<>();
+        isRestrictedInBackground.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+        isRestrictedInBackground.put(INetd.FIREWALL_RULE_ALLOW, false);
+        isRestrictedInBackground.put(INetd.FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_BACKGROUND, isRestrictedInBackground);
+
         final int[] chains = {
                 FIREWALL_CHAIN_STANDBY,
                 FIREWALL_CHAIN_POWERSAVE,
                 FIREWALL_CHAIN_DOZABLE,
                 FIREWALL_CHAIN_RESTRICTED,
-                FIREWALL_CHAIN_LOW_POWER_STANDBY
+                FIREWALL_CHAIN_LOW_POWER_STANDBY,
+                FIREWALL_CHAIN_BACKGROUND
         };
         final int[] states = {
                 INetd.FIREWALL_RULE_ALLOW,
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 2a76452..4451cae 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -26,12 +26,14 @@
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
 import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
 import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
 import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
 import static android.net.ConnectivityManager.BLOCKED_REASON_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
@@ -48,8 +50,13 @@
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_FOREGROUND;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_NOT_IN_BACKGROUND;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_ALLOWLIST;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_TOP;
+import static android.net.NetworkPolicyManager.BACKGROUND_THRESHOLD_STATE;
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
 import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
@@ -64,6 +71,7 @@
 import static android.net.NetworkTemplate.MATCH_CARRIER;
 import static android.net.NetworkTemplate.MATCH_MOBILE;
 import static android.net.NetworkTemplate.MATCH_WIFI;
+import static android.os.PowerExemptionManager.REASON_OTHER;
 import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
 import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED;
 import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT;
@@ -146,6 +154,8 @@
 import android.os.Handler;
 import android.os.INetworkManagementService;
 import android.os.PersistableBundle;
+import android.os.PowerExemptionManager;
+import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
 import android.os.RemoteException;
@@ -153,6 +163,9 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -169,6 +182,7 @@
 import android.util.Range;
 import android.util.RecurrenceRule;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.FlakyTest;
@@ -243,6 +257,9 @@
 public class NetworkPolicyManagerServiceTest {
     private static final String TAG = "NetworkPolicyManagerServiceTest";
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private static final long TEST_START = 1194220800000L;
     private static final String TEST_IFACE = "test0";
     private static final String TEST_WIFI_NETWORK_KEY = "TestWifiNetworkKey";
@@ -285,6 +302,7 @@
     private @Mock TelephonyManager mTelephonyManager;
     private @Mock UserManager mUserManager;
     private @Mock NetworkStatsManager mStatsManager;
+    private @Mock PowerExemptionManager mPowerExemptionManager;
     private TestDependencies mDeps;
 
     private ArgumentCaptor<ConnectivityManager.NetworkCallback> mNetworkCallbackCaptor =
@@ -302,6 +320,7 @@
     private NetworkPolicyManagerService mService;
 
     private final ArraySet<BroadcastReceiver> mRegisteredReceivers = new ArraySet<>();
+    private BroadcastReceiver mPowerAllowlistReceiver;
 
     /**
      * In some of the tests while initializing NetworkPolicyManagerService,
@@ -446,6 +465,7 @@
     @Before
     public void callSystemReady() throws Exception {
         MockitoAnnotations.initMocks(this);
+        when(mPowerExemptionManager.getAllowListedAppIds(anyBoolean())).thenReturn(new int[0]);
 
         final Context context = InstrumentationRegistry.getContext();
 
@@ -482,6 +502,8 @@
                         return mUserManager;
                     case Context.NETWORK_STATS_SERVICE:
                         return mStatsManager;
+                    case Context.POWER_EXEMPTION_SERVICE:
+                        return mPowerExemptionManager;
                     default:
                         return super.getSystemService(name);
                 }
@@ -495,6 +517,9 @@
             @Override
             public Intent registerReceiver(BroadcastReceiver receiver,
                     IntentFilter filter, String broadcastPermission, Handler scheduler) {
+                if (filter.hasAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED)) {
+                    mPowerAllowlistReceiver = receiver;
+                }
                 mRegisteredReceivers.add(receiver);
                 return super.registerReceiver(receiver, filter, broadcastPermission, scheduler);
             }
@@ -2066,6 +2091,12 @@
         expectHasUseRestrictedNetworksPermission(UID_A, true);
         expectHasUseRestrictedNetworksPermission(UID_B, false);
 
+        // Set low enough proc-states to ensure these uids are allowed in the background chain.
+        // To maintain clean separation between separate firewall chains, the tests could
+        // check for the specific blockedReasons in the uidBlockedState.
+        callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE - 1, 21);
+        callAndWaitOnUidStateChanged(UID_B, BACKGROUND_THRESHOLD_STATE - 1, 21);
+
         Map<Integer, Integer> firewallUidRules = new ArrayMap<>();
         doAnswer(arg -> {
             int[] uids = arg.getArgument(1);
@@ -2113,7 +2144,111 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+    public void testBackgroundChainEnabled() throws Exception {
+        verify(mNetworkManager).setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true);
+    }
+
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+    public void testBackgroundChainOnProcStateChange() throws Exception {
+        // initialization calls setFirewallChainEnabled, so we want to reset the invocations.
+        clearInvocations(mNetworkManager);
+
+        mService.mBackgroundRestrictionDelayMs = 500; // To avoid waiting too long in tests.
+
+        // The app will be blocked when there is no prior proc-state.
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+        int procStateSeq = 23;
+        callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE - 1, procStateSeq++);
+
+        verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, UID_A,
+                FIREWALL_RULE_ALLOW);
+        assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+
+        callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE + 1, procStateSeq++);
+
+        // The app should be blocked after a delay. Posting a message just after the delay and
+        // waiting for it to complete to ensure that the blocking code has executed.
+        waitForDelayedMessageOnHandler(mService.mBackgroundRestrictionDelayMs + 1);
+
+        verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, UID_A,
+                FIREWALL_RULE_DEFAULT);
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+    public void testBackgroundChainOnAllowlistChange() throws Exception {
+        // initialization calls setFirewallChainEnabled, so we want to reset the invocations.
+        clearInvocations(mNetworkManager);
+
+        // The apps will be blocked when there is no prior proc-state.
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+        assertTrue(mService.isUidNetworkingBlocked(UID_B, false));
+
+        final int procStateSeq = 29;
+        callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE + 1, procStateSeq);
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+        when(mPowerExemptionManager.getAllowListedAppIds(anyBoolean()))
+                .thenReturn(new int[]{APP_ID_A, APP_ID_B});
+        final SparseIntArray firewallUidRules = new SparseIntArray();
+        doAnswer(arg -> {
+            final int[] uids = arg.getArgument(1);
+            final int[] rules = arg.getArgument(2);
+            assertTrue(uids.length == rules.length);
+
+            for (int i = 0; i < uids.length; ++i) {
+                firewallUidRules.put(uids[i], rules[i]);
+            }
+            return null;
+        }).when(mNetworkManager).setFirewallUidRules(eq(FIREWALL_CHAIN_BACKGROUND),
+                any(int[].class), any(int[].class));
+
+        mPowerAllowlistReceiver.onReceive(mServiceContext, null);
+
+        assertEquals(FIREWALL_RULE_ALLOW, firewallUidRules.get(UID_A, -1));
+        assertEquals(FIREWALL_RULE_ALLOW, firewallUidRules.get(UID_B, -1));
+
+        assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+        assertFalse(mService.isUidNetworkingBlocked(UID_B, false));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+    public void testBackgroundChainOnTempAllowlistChange() throws Exception {
+        // initialization calls setFirewallChainEnabled, so we want to reset the invocations.
+        clearInvocations(mNetworkManager);
+
+        // The app will be blocked as is no prior proc-state.
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+        final int procStateSeq = 19;
+        callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE + 1, procStateSeq);
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+        final NetworkPolicyManagerInternal internal = LocalServices.getService(
+                NetworkPolicyManagerInternal.class);
+
+        internal.onTempPowerSaveWhitelistChange(APP_ID_A, true, REASON_OTHER, "testing");
+
+        verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, UID_A,
+                FIREWALL_RULE_ALLOW);
+        assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+
+        internal.onTempPowerSaveWhitelistChange(APP_ID_A, false, REASON_OTHER, "testing");
+
+        verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, UID_A,
+                FIREWALL_RULE_DEFAULT);
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+    }
+
+    @Test
     public void testLowPowerStandbyAllowlist() throws Exception {
+        // Chain background is also enabled but these procstates are important enough to be exempt.
         callAndWaitOnUidStateChanged(UID_A, PROCESS_STATE_TOP, 0);
         callAndWaitOnUidStateChanged(UID_B, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
         callAndWaitOnUidStateChanged(UID_C, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
@@ -2200,7 +2335,21 @@
                 ALLOWED_REASON_TOP), BLOCKED_REASON_NONE);
         effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_LOW_POWER_STANDBY,
                 ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST), BLOCKED_REASON_NONE);
-        // TODO: test more combinations of blocked reasons.
+
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND,
+                ALLOWED_REASON_NOT_IN_BACKGROUND), BLOCKED_REASON_NONE);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND
+                        | BLOCKED_REASON_BATTERY_SAVER, ALLOWED_REASON_NOT_IN_BACKGROUND),
+                BLOCKED_REASON_BATTERY_SAVER);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND
+                        | BLOCKED_REASON_DOZE, ALLOWED_REASON_NOT_IN_BACKGROUND),
+                BLOCKED_REASON_DOZE);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND,
+                        ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS), BLOCKED_REASON_APP_BACKGROUND);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND,
+                ALLOWED_REASON_POWER_SAVE_ALLOWLIST), BLOCKED_REASON_NONE);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND,
+                ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST), BLOCKED_REASON_NONE);
 
         for (Map.Entry<Pair<Integer, Integer>, Integer> test : effectiveBlockedReasons.entrySet()) {
             final int expectedEffectiveBlockedReasons = test.getValue();
@@ -2529,7 +2678,6 @@
     private FutureIntent mRestrictBackgroundChanged;
 
     private void postMsgAndWaitForCompletion() throws InterruptedException {
-        final Handler handler = mService.getHandlerForTesting();
         final CountDownLatch latch = new CountDownLatch(1);
         mService.getHandlerForTesting().post(latch::countDown);
         if (!latch.await(5, TimeUnit.SECONDS)) {
@@ -2537,6 +2685,14 @@
         }
     }
 
+    private void waitForDelayedMessageOnHandler(long delayMs) throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mService.getHandlerForTesting().postDelayed(latch::countDown, delayMs);
+        if (!latch.await(delayMs + 5_000, TimeUnit.MILLISECONDS)) {
+            fail("Timed out waiting for delayed msg to be handled");
+        }
+    }
+
     private void setSubscriptionPlans(int subId, SubscriptionPlan[] plans, String callingPackage)
             throws InterruptedException {
         mService.setSubscriptionPlans(subId, plans, 0, callingPackage);
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
index 10f27ca..72fa949 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
@@ -80,7 +80,7 @@
         @BeforeClass
         @JvmStatic
         fun checkAllCasesUniquelyNamed() {
-            val duplicateCaseNames = CASES.mapIndexed { caseIndex, testCase ->
+            val duplicateCaseNames = CASES.mapIndexed { _, testCase ->
                 testCase.failures.map {
                     makeTestName(testCase, it.first, Params.Type.FAILURE)
                 } + testCase.allowed.map {
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index dc1d2c5..1c6d36b 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -17,16 +17,19 @@
 package com.android.server.os;
 
 import android.app.admin.flags.Flags;
-import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
 
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
 
 import android.app.role.RoleManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.BugreportManager.BugreportCallback;
 import android.os.IBinder;
@@ -48,6 +51,8 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.io.FileDescriptor;
 import java.util.concurrent.CompletableFuture;
@@ -66,6 +71,9 @@
     private BugreportManagerServiceImpl mService;
     private BugreportManagerServiceImpl.BugreportFileManager mBugreportFileManager;
 
+    @Mock
+    private PackageManager mPackageManager;
+
     private int mCallingUid = 1234;
     private String mCallingPackage  = "test.package";
     private AtomicFile mMappingFile;
@@ -74,7 +82,8 @@
     private String mBugreportFile2 = "bugreport-file2.zip";
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
         mContext = InstrumentationRegistry.getInstrumentation().getContext();
         mMappingFile = new AtomicFile(mContext.getFilesDir(), "bugreport-mapping.xml");
         ArraySet<String> mAllowlistedPackages = new ArraySet<>();
@@ -83,6 +92,7 @@
                 new BugreportManagerServiceImpl.Injector(mContext, mAllowlistedPackages,
                         mMappingFile));
         mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager(mMappingFile);
+        when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())).thenReturn(mCallingUid);
     }
 
     @After
@@ -115,12 +125,13 @@
 
         assertThrows(IllegalArgumentException.class, () ->
                 mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
-                        mContext, callingInfo, Process.myUserHandle().getIdentifier(),
-                        "unknown-file.zip", /* forceUpdateMapping= */ true));
+                        mContext, mPackageManager,  callingInfo,
+                        Process.myUserHandle().getIdentifier(), "unknown-file.zip",
+                        /* forceUpdateMapping= */ true));
 
         // No exception should be thrown.
         mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
-                mContext, callingInfo, mContext.getUserId(), mBugreportFile,
+                mContext, mPackageManager, callingInfo, mContext.getUserId(), mBugreportFile,
                 /* forceUpdateMapping= */ true);
     }
 
@@ -132,7 +143,7 @@
                 callingInfo, mBugreportFile, /* keepOnRetrieval= */ true);
 
         mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
-                mContext, callingInfo, mContext.getUserId(), mBugreportFile,
+                mContext, mPackageManager, callingInfo, mContext.getUserId(), mBugreportFile,
                 /* forceUpdateMapping= */ true);
 
         assertThat(mBugreportFileManager.mBugreportFilesToPersist).containsExactly(mBugreportFile);
@@ -148,10 +159,10 @@
 
         // No exception should be thrown.
         mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
-                mContext, callingInfo, mContext.getUserId(), mBugreportFile,
+                mContext, mPackageManager, callingInfo, mContext.getUserId(), mBugreportFile,
                 /* forceUpdateMapping= */ true);
         mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
-                mContext, callingInfo, mContext.getUserId(), mBugreportFile2,
+                mContext, mPackageManager, callingInfo, mContext.getUserId(), mBugreportFile2,
                 /* forceUpdateMapping= */ true);
     }
 
@@ -160,8 +171,9 @@
         Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
         assertThrows(IllegalArgumentException.class,
                 () -> mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
-                        mContext, callingInfo, Process.myUserHandle().getIdentifier(),
-                        "test-file.zip", /* forceUpdateMapping= */ true));
+                        mContext, mPackageManager, callingInfo,
+                        Process.myUserHandle().getIdentifier(), "test-file.zip",
+                        /* forceUpdateMapping= */ true));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
index daf18ed..8656f60 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
@@ -9,13 +9,16 @@
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
 
+import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -27,6 +30,7 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -97,7 +101,6 @@
     private Looper mLooper;
     private File mFile;
 
-
     @Mock
     private Context mContext;
     @Mock
@@ -108,8 +111,10 @@
     private UsageStatsManagerInternal mUsageStatsManagerInternal;
     @Mock
     private PermissionManagerServiceInternal mPermissionManager;
+
     @Captor
     private ArgumentCaptor<PackageManagerInternal.PackageListObserver> mPackageListObserverCaptor;
+
     @Captor
     private ArgumentCaptor<UsageEventListener> mUsageEventListenerCaptor;
 
@@ -119,11 +124,12 @@
 
         mTestLooper = new TestLooper();
         mLooper = mTestLooper.getLooper();
-        mFile = new File(
-                InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(),
-                "test");
-        mBackgroundInstallControlService = new BackgroundInstallControlService(
-                new MockInjector(mContext));
+        mFile =
+                new File(
+                        InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(),
+                        "test");
+        mBackgroundInstallControlService =
+                new BackgroundInstallControlService(new MockInjector(mContext));
 
         verify(mUsageStatsManagerInternal).registerListener(mUsageEventListenerCaptor.capture());
         mUsageEventListener = mUsageEventListenerCaptor.getValue();
@@ -143,8 +149,7 @@
         assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
         mBackgroundInstallControlService.initBackgroundInstalledPackages();
         assertNotNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
-        assertEquals(0,
-                mBackgroundInstallControlService.getBackgroundInstalledPackages().size());
+        assertEquals(0, mBackgroundInstallControlService.getBackgroundInstalledPackages().size());
     }
 
     @Test
@@ -161,12 +166,9 @@
         // Write test data to the file on the disk.
         try {
             ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream);
-            long token = protoOutputStream.start(
-                    BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
-            protoOutputStream.write(
-                    BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1);
-            protoOutputStream.write(
-                    BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1);
+            long token = protoOutputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+            protoOutputStream.write(BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1);
+            protoOutputStream.write(BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1);
             protoOutputStream.end(token);
             protoOutputStream.flush();
             atomicFile.finishWrite(fileOutputStream);
@@ -198,20 +200,14 @@
         try {
             ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream);
 
-            long token = protoOutputStream.start(
-                    BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
-            protoOutputStream.write(
-                    BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1);
-            protoOutputStream.write(
-                    BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1);
+            long token = protoOutputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+            protoOutputStream.write(BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1);
+            protoOutputStream.write(BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1);
             protoOutputStream.end(token);
 
-            token = protoOutputStream.start(
-                    BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
-            protoOutputStream.write(
-                    BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_2);
-            protoOutputStream.write(
-                    BackgroundInstalledPackageProto.USER_ID, USER_ID_2 + 1);
+            token = protoOutputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+            protoOutputStream.write(BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_2);
+            protoOutputStream.write(BackgroundInstalledPackageProto.USER_ID, USER_ID_2 + 1);
             protoOutputStream.end(token);
 
             protoOutputStream.flush();
@@ -241,7 +237,7 @@
         // Read the file on the disk to verify
         var packagesInDisk = new SparseSetArray<>();
         AtomicFile atomicFile = new AtomicFile(mFile);
-        try (FileInputStream fileInputStream  = atomicFile.openRead()) {
+        try (FileInputStream fileInputStream = atomicFile.openRead()) {
             ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
 
             while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
@@ -249,23 +245,25 @@
                         != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
                     continue;
                 }
-                long token = protoInputStream.start(
-                        BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+                long token =
+                        protoInputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
                 String packageName = null;
                 int userId = UserHandle.USER_NULL;
                 while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                     switch (protoInputStream.getFieldNumber()) {
                         case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
-                            packageName = protoInputStream.readString(
-                                    BackgroundInstalledPackageProto.PACKAGE_NAME);
+                            packageName =
+                                    protoInputStream.readString(
+                                            BackgroundInstalledPackageProto.PACKAGE_NAME);
                             break;
                         case (int) BackgroundInstalledPackageProto.USER_ID:
-                            userId = protoInputStream.readInt(
-                                    BackgroundInstalledPackageProto.USER_ID) - 1;
+                            userId =
+                                    protoInputStream.readInt(
+                                            BackgroundInstalledPackageProto.USER_ID)
+                                            - 1;
                             break;
                         default:
-                            fail("Undefined field in proto: "
-                                    + protoInputStream.getFieldNumber());
+                            fail("Undefined field in proto: " + protoInputStream.getFieldNumber());
                     }
                 }
                 protoInputStream.end(token);
@@ -296,7 +294,7 @@
         // Read the file on the disk to verify
         var packagesInDisk = new SparseSetArray<>();
         AtomicFile atomicFile = new AtomicFile(mFile);
-        try (FileInputStream fileInputStream  = atomicFile.openRead()) {
+        try (FileInputStream fileInputStream = atomicFile.openRead()) {
             ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
 
             while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
@@ -304,23 +302,25 @@
                         != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
                     continue;
                 }
-                long token = protoInputStream.start(
-                        BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+                long token =
+                        protoInputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
                 String packageName = null;
                 int userId = UserHandle.USER_NULL;
                 while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                     switch (protoInputStream.getFieldNumber()) {
                         case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
-                            packageName = protoInputStream.readString(
-                                    BackgroundInstalledPackageProto.PACKAGE_NAME);
+                            packageName =
+                                    protoInputStream.readString(
+                                            BackgroundInstalledPackageProto.PACKAGE_NAME);
                             break;
                         case (int) BackgroundInstalledPackageProto.USER_ID:
-                            userId = protoInputStream.readInt(
-                                    BackgroundInstalledPackageProto.USER_ID) - 1;
+                            userId =
+                                    protoInputStream.readInt(
+                                            BackgroundInstalledPackageProto.USER_ID)
+                                            - 1;
                             break;
                         default:
-                            fail("Undefined field in proto: "
-                                    + protoInputStream.getFieldNumber());
+                            fail("Undefined field in proto: " + protoInputStream.getFieldNumber());
                     }
                 }
                 protoInputStream.end(token);
@@ -353,7 +353,7 @@
         // Read the file on the disk to verify
         var packagesInDisk = new SparseSetArray<>();
         AtomicFile atomicFile = new AtomicFile(mFile);
-        try (FileInputStream fileInputStream  = atomicFile.openRead()) {
+        try (FileInputStream fileInputStream = atomicFile.openRead()) {
             ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
 
             while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
@@ -361,23 +361,25 @@
                         != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
                     continue;
                 }
-                long token = protoInputStream.start(
-                        BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+                long token =
+                        protoInputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
                 String packageName = null;
                 int userId = UserHandle.USER_NULL;
                 while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                     switch (protoInputStream.getFieldNumber()) {
                         case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
-                            packageName = protoInputStream.readString(
-                                    BackgroundInstalledPackageProto.PACKAGE_NAME);
+                            packageName =
+                                    protoInputStream.readString(
+                                            BackgroundInstalledPackageProto.PACKAGE_NAME);
                             break;
                         case (int) BackgroundInstalledPackageProto.USER_ID:
-                            userId = protoInputStream.readInt(
-                                    BackgroundInstalledPackageProto.USER_ID) - 1;
+                            userId =
+                                    protoInputStream.readInt(
+                                            BackgroundInstalledPackageProto.USER_ID)
+                                            - 1;
                             break;
                         default:
-                            fail("Undefined field in proto: "
-                                    + protoInputStream.getFieldNumber());
+                            fail("Undefined field in proto: " + protoInputStream.getFieldNumber());
                     }
                 }
                 protoInputStream.end(token);
@@ -399,51 +401,55 @@
 
     @Test
     public void testHandleUsageEvent_permissionDenied() {
-        assertEquals(0,
-                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
-        doReturn(PackageManager.PERMISSION_DENIED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
-                USER_ID_1, INSTALLER_NAME_1, 0);
+        assertEquals(
+                0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mPermissionManager)
+                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, USER_ID_1, INSTALLER_NAME_1, 0);
         mTestLooper.dispatchAll();
-        assertEquals(0,
-                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        assertEquals(
+                0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
     }
 
     @Test
     public void testHandleUsageEvent_permissionGranted() {
-        assertEquals(0,
-                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
-        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
-                USER_ID_1, INSTALLER_NAME_1, 0);
+        assertEquals(
+                0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        doReturn(PERMISSION_GRANTED)
+                .when(mPermissionManager)
+                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, USER_ID_1, INSTALLER_NAME_1, 0);
         mTestLooper.dispatchAll();
-        assertEquals(1,
-                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        assertEquals(
+                1, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
     }
 
     @Test
     public void testHandleUsageEvent_ignoredEvent() {
-        assertEquals(0,
-                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
-        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(UsageEvents.Event.USER_INTERACTION,
-                USER_ID_1, INSTALLER_NAME_1, 0);
+        assertEquals(
+                0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        doReturn(PERMISSION_GRANTED)
+                .when(mPermissionManager)
+                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(UsageEvents.Event.USER_INTERACTION, USER_ID_1, INSTALLER_NAME_1, 0);
         mTestLooper.dispatchAll();
-        assertEquals(0,
-                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        assertEquals(
+                0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
     }
 
     @Test
     public void testHandleUsageEvent_firstActivityResumedHalfTimeFrame() {
-        assertEquals(0,
-                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
-        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
-                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
+        assertEquals(
+                0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        doReturn(PERMISSION_GRANTED)
+                .when(mPermissionManager)
+                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(
+                UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1,
+                INSTALLER_NAME_1,
+                USAGE_EVENT_TIMESTAMP_1);
         mTestLooper.dispatchAll();
 
         var installerForegroundTimeFrames =
@@ -461,14 +467,18 @@
 
     @Test
     public void testHandleUsageEvent_firstActivityResumedOneTimeFrame() {
-        assertEquals(0,
-                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
-        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
-                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
-        generateUsageEvent(Event.ACTIVITY_STOPPED,
-                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+        assertEquals(
+                0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        doReturn(PERMISSION_GRANTED)
+                .when(mPermissionManager)
+                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(
+                UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1,
+                INSTALLER_NAME_1,
+                USAGE_EVENT_TIMESTAMP_1);
+        generateUsageEvent(
+                Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
         mTestLooper.dispatchAll();
 
         var installerForegroundTimeFrames =
@@ -486,16 +496,23 @@
 
     @Test
     public void testHandleUsageEvent_firstActivityResumedOneAndHalfTimeFrame() {
-        assertEquals(0,
-                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
-        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
-                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
-        generateUsageEvent(Event.ACTIVITY_STOPPED,
-                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
-        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
-                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
+        assertEquals(
+                0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        doReturn(PERMISSION_GRANTED)
+                .when(mPermissionManager)
+                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(
+                UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1,
+                INSTALLER_NAME_1,
+                USAGE_EVENT_TIMESTAMP_1);
+        generateUsageEvent(
+                Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+        generateUsageEvent(
+                UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1,
+                INSTALLER_NAME_1,
+                USAGE_EVENT_TIMESTAMP_3);
         mTestLooper.dispatchAll();
 
         var installerForegroundTimeFrames =
@@ -517,12 +534,13 @@
 
     @Test
     public void testHandleUsageEvent_firstNoneActivityResumed() {
-        assertEquals(0,
-                mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
-        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(Event.ACTIVITY_STOPPED,
-                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
+        assertEquals(
+                0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+        doReturn(PERMISSION_GRANTED)
+                .when(mPermissionManager)
+                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(
+                Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
         mTestLooper.dispatchAll();
 
         var installerForegroundTimeFrames =
@@ -535,27 +553,26 @@
     }
 
     @Test
-    public void testHandleUsageEvent_packageAddedNoUsageEvent() throws
-            NoSuchFieldException, PackageManager.NameNotFoundException {
+    public void testHandleUsageEvent_packageAddedNoUsageEvent()
+            throws NoSuchFieldException, PackageManager.NameNotFoundException {
         assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
-        InstallSourceInfo installSourceInfo = new InstallSourceInfo(
-                /* initiatingPackageName = */ INSTALLER_NAME_1,
-                /* initiatingPackageSigningInfo = */ null,
-                /* originatingPackageName = */ null,
-                /* installingPackageName = */ INSTALLER_NAME_1);
+        InstallSourceInfo installSourceInfo =
+                new InstallSourceInfo(
+                        /* initiatingPackageName= */ INSTALLER_NAME_1,
+                        /* initiatingPackageSigningInfo= */ null,
+                        /* originatingPackageName= */ null,
+                        /* installingPackageName= */ INSTALLER_NAME_1);
         assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
         when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
         ApplicationInfo appInfo = mock(ApplicationInfo.class);
 
-        when(mPackageManager.getApplicationInfoAsUser(
-                eq(PACKAGE_NAME_1),
-                any(),
-                anyInt())
-        ).thenReturn(appInfo);
+        when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
+                .thenReturn(appInfo);
 
-        long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
-                - (System.currentTimeMillis() - SystemClock.uptimeMillis());
-        FieldSetter.setField(appInfo,
+        long createTimestamp =
+                PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+        FieldSetter.setField(
+                appInfo,
                 ApplicationInfo.class.getDeclaredField("createTimestamp"),
                 createTimestamp);
 
@@ -572,27 +589,26 @@
     }
 
     @Test
-    public void testHandleUsageEvent_packageAddedInsideTimeFrame() throws
-            NoSuchFieldException, PackageManager.NameNotFoundException {
+    public void testHandleUsageEvent_packageAddedInsideTimeFrame()
+            throws NoSuchFieldException, PackageManager.NameNotFoundException {
         assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
-        InstallSourceInfo installSourceInfo = new InstallSourceInfo(
-                /* initiatingPackageName = */ INSTALLER_NAME_1,
-                /* initiatingPackageSigningInfo = */ null,
-                /* originatingPackageName = */ null,
-                /* installingPackageName = */ INSTALLER_NAME_1);
+        InstallSourceInfo installSourceInfo =
+                new InstallSourceInfo(
+                        /* initiatingPackageName= */ INSTALLER_NAME_1,
+                        /* initiatingPackageSigningInfo= */ null,
+                        /* originatingPackageName= */ null,
+                        /* installingPackageName= */ INSTALLER_NAME_1);
         assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
         when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
         ApplicationInfo appInfo = mock(ApplicationInfo.class);
 
-        when(mPackageManager.getApplicationInfoAsUser(
-                eq(PACKAGE_NAME_1),
-                any(),
-                anyInt())
-        ).thenReturn(appInfo);
+        when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
+                .thenReturn(appInfo);
 
-        long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
-                - (System.currentTimeMillis() - SystemClock.uptimeMillis());
-        FieldSetter.setField(appInfo,
+        long createTimestamp =
+                PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+        FieldSetter.setField(
+                appInfo,
                 ApplicationInfo.class.getDeclaredField("createTimestamp"),
                 createTimestamp);
 
@@ -604,12 +620,16 @@
         // The 2 usage events make the package adding inside a time frame.
         // So it's not a background install. Thus, it's null for the return of
         // mBackgroundInstallControlService.getBackgroundInstalledPackages()
-        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
-                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
-        generateUsageEvent(Event.ACTIVITY_STOPPED,
-                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+        doReturn(PERMISSION_GRANTED)
+                .when(mPermissionManager)
+                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(
+                UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1,
+                INSTALLER_NAME_1,
+                USAGE_EVENT_TIMESTAMP_1);
+        generateUsageEvent(
+                Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
 
         mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
         mTestLooper.dispatchAll();
@@ -617,27 +637,26 @@
     }
 
     @Test
-    public void testHandleUsageEvent_packageAddedOutsideTimeFrame1() throws
-            NoSuchFieldException, PackageManager.NameNotFoundException {
+    public void testHandleUsageEvent_packageAddedOutsideTimeFrame1()
+            throws NoSuchFieldException, PackageManager.NameNotFoundException {
         assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
-        InstallSourceInfo installSourceInfo = new InstallSourceInfo(
-                /* initiatingPackageName = */ INSTALLER_NAME_1,
-                /* initiatingPackageSigningInfo = */ null,
-                /* originatingPackageName = */ null,
-                /* installingPackageName = */ INSTALLER_NAME_1);
+        InstallSourceInfo installSourceInfo =
+                new InstallSourceInfo(
+                        /* initiatingPackageName= */ INSTALLER_NAME_1,
+                        /* initiatingPackageSigningInfo= */ null,
+                        /* originatingPackageName= */ null,
+                        /* installingPackageName= */ INSTALLER_NAME_1);
         assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
         when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
         ApplicationInfo appInfo = mock(ApplicationInfo.class);
 
-        when(mPackageManager.getApplicationInfoAsUser(
-                eq(PACKAGE_NAME_1),
-                any(),
-                anyInt())
-        ).thenReturn(appInfo);
+        when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
+                .thenReturn(appInfo);
 
-        long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
-                - (System.currentTimeMillis() - SystemClock.uptimeMillis());
-        FieldSetter.setField(appInfo,
+        long createTimestamp =
+                PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+        FieldSetter.setField(
+                appInfo,
                 ApplicationInfo.class.getDeclaredField("createTimestamp"),
                 createTimestamp);
 
@@ -650,12 +669,16 @@
         // Compared to testHandleUsageEvent_packageAddedInsideTimeFrame,
         // it's a background install. Thus, it's not null for the return of
         // mBackgroundInstallControlService.getBackgroundInstalledPackages()
-        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
-                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
-        generateUsageEvent(Event.ACTIVITY_STOPPED,
-                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
+        doReturn(PERMISSION_GRANTED)
+                .when(mPermissionManager)
+                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(
+                UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1,
+                INSTALLER_NAME_1,
+                USAGE_EVENT_TIMESTAMP_2);
+        generateUsageEvent(
+                Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
 
         mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
         mTestLooper.dispatchAll();
@@ -665,28 +688,28 @@
         assertEquals(1, packages.size());
         assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
     }
+
     @Test
-    public void testHandleUsageEvent_packageAddedOutsideTimeFrame2() throws
-            NoSuchFieldException, PackageManager.NameNotFoundException {
+    public void testHandleUsageEvent_packageAddedOutsideTimeFrame2()
+            throws NoSuchFieldException, PackageManager.NameNotFoundException {
         assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
-        InstallSourceInfo installSourceInfo = new InstallSourceInfo(
-                /* initiatingPackageName = */ INSTALLER_NAME_1,
-                /* initiatingPackageSigningInfo = */ null,
-                /* originatingPackageName = */ null,
-                /* installingPackageName = */ INSTALLER_NAME_1);
+        InstallSourceInfo installSourceInfo =
+                new InstallSourceInfo(
+                        /* initiatingPackageName= */ INSTALLER_NAME_1,
+                        /* initiatingPackageSigningInfo= */ null,
+                        /* originatingPackageName= */ null,
+                        /* installingPackageName= */ INSTALLER_NAME_1);
         assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
         when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
         ApplicationInfo appInfo = mock(ApplicationInfo.class);
 
-        when(mPackageManager.getApplicationInfoAsUser(
-                eq(PACKAGE_NAME_1),
-                any(),
-                anyInt())
-        ).thenReturn(appInfo);
+        when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
+                .thenReturn(appInfo);
 
-        long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
-                - (System.currentTimeMillis() - SystemClock.uptimeMillis());
-        FieldSetter.setField(appInfo,
+        long createTimestamp =
+                PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+        FieldSetter.setField(
+                appInfo,
                 ApplicationInfo.class.getDeclaredField("createTimestamp"),
                 createTimestamp);
 
@@ -700,12 +723,16 @@
         // Compared to testHandleUsageEvent_packageAddedInsideTimeFrame,
         // it's a background install. Thus, it's not null for the return of
         // mBackgroundInstallControlService.getBackgroundInstalledPackages()
-        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
-                USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_2);
-        generateUsageEvent(Event.ACTIVITY_STOPPED,
-                USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_3);
+        doReturn(PERMISSION_GRANTED)
+                .when(mPermissionManager)
+                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(
+                UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_2,
+                INSTALLER_NAME_2,
+                USAGE_EVENT_TIMESTAMP_2);
+        generateUsageEvent(
+                Event.ACTIVITY_STOPPED, USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_3);
 
         mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
         mTestLooper.dispatchAll();
@@ -715,31 +742,31 @@
         assertEquals(1, packages.size());
         assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
     }
+
     @Test
-    public void testHandleUsageEvent_packageAddedThroughAdb() throws
-            NoSuchFieldException, PackageManager.NameNotFoundException {
+    public void testHandleUsageEvent_packageAddedThroughAdb()
+            throws NoSuchFieldException, PackageManager.NameNotFoundException {
         assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
         // This test is a duplicate of testHandleUsageEvent_packageAddedThroughAdb except the
         // initiatingPackageName used to be null but is now "com.android.shell". This test ensures
         // that the behavior is still the same for when the initiatingPackageName is null.
-        InstallSourceInfo installSourceInfo = new InstallSourceInfo(
-                /* initiatingPackageName = */ null,
-                /* initiatingPackageSigningInfo = */ null,
-                /* originatingPackageName = */ null,
-                /* installingPackageName = */ INSTALLER_NAME_1);
+        InstallSourceInfo installSourceInfo =
+                new InstallSourceInfo(
+                        /* initiatingPackageName= */ null,
+                        /* initiatingPackageSigningInfo= */ null,
+                        /* originatingPackageName= */ null,
+                        /* installingPackageName= */ INSTALLER_NAME_1);
         // b/265203007
         when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
         ApplicationInfo appInfo = mock(ApplicationInfo.class);
 
-        when(mPackageManager.getApplicationInfoAsUser(
-                eq(PACKAGE_NAME_1),
-                any(),
-                anyInt())
-        ).thenReturn(appInfo);
+        when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
+                .thenReturn(appInfo);
 
-        long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
-                - (System.currentTimeMillis() - SystemClock.uptimeMillis());
-        FieldSetter.setField(appInfo,
+        long createTimestamp =
+                PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+        FieldSetter.setField(
+                appInfo,
                 ApplicationInfo.class.getDeclaredField("createTimestamp"),
                 createTimestamp);
 
@@ -751,12 +778,16 @@
         // for ADB installs the initiatingPackageName used to be null, despite being detected
         // as a background install. Since we do not want to treat side-loaded apps as background
         // install getBackgroundInstalledPackages() is expected to return null
-        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
-                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
-        generateUsageEvent(Event.ACTIVITY_STOPPED,
-                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
+        doReturn(PERMISSION_GRANTED)
+                .when(mPermissionManager)
+                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(
+                UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1,
+                INSTALLER_NAME_1,
+                USAGE_EVENT_TIMESTAMP_2);
+        generateUsageEvent(
+                Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
 
         mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
         mTestLooper.dispatchAll();
@@ -764,31 +795,31 @@
         var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
         assertNull(packages);
     }
+
     @Test
-    public void testHandleUsageEvent_packageAddedThroughAdb2() throws
-            NoSuchFieldException, PackageManager.NameNotFoundException {
+    public void testHandleUsageEvent_packageAddedThroughAdb2()
+            throws NoSuchFieldException, PackageManager.NameNotFoundException {
         assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
         // This test is a duplicate of testHandleUsageEvent_packageAddedThroughAdb except the
         // initiatingPackageName used to be null but is now "com.android.shell". This test ensures
         // that the behavior is still the same after this change.
-        InstallSourceInfo installSourceInfo = new InstallSourceInfo(
-                /* initiatingPackageName = */ "com.android.shell",
-                /* initiatingPackageSigningInfo = */ null,
-                /* originatingPackageName = */ null,
-                /* installingPackageName = */ INSTALLER_NAME_1);
+        InstallSourceInfo installSourceInfo =
+                new InstallSourceInfo(
+                        /* initiatingPackageName= */ "com.android.shell",
+                        /* initiatingPackageSigningInfo= */ null,
+                        /* originatingPackageName= */ null,
+                        /* installingPackageName= */ INSTALLER_NAME_1);
         // b/265203007
         when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
         ApplicationInfo appInfo = mock(ApplicationInfo.class);
 
-        when(mPackageManager.getApplicationInfoAsUser(
-                eq(PACKAGE_NAME_1),
-                any(),
-                anyInt())
-        ).thenReturn(appInfo);
+        when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
+                .thenReturn(appInfo);
 
-        long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
-                - (System.currentTimeMillis() - SystemClock.uptimeMillis());
-        FieldSetter.setField(appInfo,
+        long createTimestamp =
+                PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+        FieldSetter.setField(
+                appInfo,
                 ApplicationInfo.class.getDeclaredField("createTimestamp"),
                 createTimestamp);
 
@@ -800,12 +831,16 @@
         // for ADB installs the initiatingPackageName is com.android.shell, despite being detected
         // as a background install. Since we do not want to treat side-loaded apps as background
         // install getBackgroundInstalledPackages() is expected to return null
-        doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
-                anyString(), anyString(), anyInt(), anyInt());
-        generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
-                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
-        generateUsageEvent(Event.ACTIVITY_STOPPED,
-                USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
+        doReturn(PERMISSION_GRANTED)
+                .when(mPermissionManager)
+                .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+        generateUsageEvent(
+                UsageEvents.Event.ACTIVITY_RESUMED,
+                USER_ID_1,
+                INSTALLER_NAME_1,
+                USAGE_EVENT_TIMESTAMP_2);
+        generateUsageEvent(
+                Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
 
         mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
         mTestLooper.dispatchAll();
@@ -813,6 +848,7 @@
         var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
         assertNull(packages);
     }
+
     @Test
     public void testPackageRemoved() {
         assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
@@ -859,8 +895,7 @@
         packages.add(packageInfo2);
         var packageInfo3 = makePackageInfo(PACKAGE_NAME_3);
         packages.add(packageInfo3);
-        doReturn(packages).when(mPackageManager).getInstalledPackagesAsUser(
-                any(), anyInt());
+        doReturn(packages).when(mPackageManager).getInstalledPackagesAsUser(any(), anyInt());
 
         var resultPackages =
                 mBackgroundInstallControlService.getBackgroundInstalledPackages(0L, USER_ID_1);
@@ -870,18 +905,30 @@
         assertFalse(resultPackages.getList().contains(packageInfo3));
     }
 
+    @Test(expected = SecurityException.class)
+    public void enforceCallerPermissionsThrowsSecurityException() {
+        doThrow(new SecurityException("test")).when(mContext)
+                .enforceCallingOrSelfPermission(eq(GET_BACKGROUND_INSTALLED_PACKAGES), anyString());
+
+        mBackgroundInstallControlService.enforceCallerPermissions();
+    }
+
+    @Test
+    public void enforceCallerPermissionsDoesNotThrowSecurityException() {
+        //enforceCallerQueryPackagesPermissions do not throw
+
+        mBackgroundInstallControlService.enforceCallerPermissions();
+    }
+
     /**
      * Mock a usage event occurring.
      *
      * @param usageEventId id of a usage event
-     * @param userId user id of a usage event
-     * @param pkgName package name of a usage event
-     * @param timestamp timestamp of a usage event
+     * @param userId       user id of a usage event
+     * @param pkgName      package name of a usage event
+     * @param timestamp    timestamp of a usage event
      */
-    private void generateUsageEvent(int usageEventId,
-            int userId,
-            String pkgName,
-            long timestamp) {
+    private void generateUsageEvent(int usageEventId, int userId, String pkgName, long timestamp) {
         Event event = new Event(usageEventId, timestamp);
         event.mPackage = pkgName;
         mUsageEventListener.onUsageEvent(userId, event);
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 0f5fb91..d50affb 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -406,8 +406,8 @@
 
     public void testPushDynamicShortcut() {
         // Change the max number of shortcuts.
-        mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5");
-
+        mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5,"
+                + ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1");
         setCaller(CALLING_PACKAGE_1, USER_0);
 
         final ShortcutInfo s1 = makeShortcut("s1");
@@ -545,6 +545,57 @@
                 eq(CALLING_PACKAGE_1), eq("s9"), eq(USER_0));
     }
 
+    public void testPushDynamicShortcut_CallsToUsageStatsManagerAreThrottled()
+            throws InterruptedException {
+        mService.updateConfigurationLocked(
+                ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=500");
+
+        // Verify calls to UsageStatsManagerInternal#reportShortcutUsage are throttled.
+        setCaller(CALLING_PACKAGE_1, USER_0);
+        {
+            final ShortcutInfo si = makeShortcut("s0");
+            mManager.pushDynamicShortcut(si);
+        }
+        verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+                eq(CALLING_PACKAGE_1), eq("s0"), eq(USER_0));
+        Mockito.reset(mMockUsageStatsManagerInternal);
+        for (int i = 2; i <= 10; i++) {
+            final ShortcutInfo si = makeShortcut("s" + i);
+            mManager.pushDynamicShortcut(si);
+        }
+        verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
+                any(), any(), anyInt());
+
+        // Verify pkg2 isn't blocked by pkg1, but consecutive calls from pkg2 are throttled as well.
+        setCaller(CALLING_PACKAGE_2, USER_0);
+        {
+            final ShortcutInfo si = makeShortcut("s1");
+            mManager.pushDynamicShortcut(si);
+        }
+        verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+                eq(CALLING_PACKAGE_2), eq("s1"), eq(USER_0));
+        Mockito.reset(mMockUsageStatsManagerInternal);
+        for (int i = 2; i <= 10; i++) {
+            final ShortcutInfo si = makeShortcut("s" + i);
+            mManager.pushDynamicShortcut(si);
+        }
+        verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
+                any(), any(), anyInt());
+
+        Mockito.reset(mMockUsageStatsManagerInternal);
+        // Let time passes which resets the throttle
+        Thread.sleep(505);
+        // Verify UsageStatsManagerInternal#reportShortcutUsed can be called again
+        setCaller(CALLING_PACKAGE_1, USER_0);
+        mManager.pushDynamicShortcut(makeShortcut("s10"));
+        setCaller(CALLING_PACKAGE_2, USER_0);
+        mManager.pushDynamicShortcut(makeShortcut("s10"));
+        verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+                eq(CALLING_PACKAGE_1), any(), eq(USER_0));
+        verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+                eq(CALLING_PACKAGE_2), any(), eq(USER_0));
+    }
+
     public void testUnlimitedCalls() {
         setCaller(CALLING_PACKAGE_1, USER_0);
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index d7ed7c2..8d8dc9c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -23,6 +23,7 @@
 import android.content.pm.UserProperties;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.Xml;
 
 import androidx.test.filters.MediumTest;
@@ -31,6 +32,7 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -52,10 +54,13 @@
 @RunWith(AndroidJUnit4.class)
 @MediumTest
 public class UserManagerServiceUserPropertiesTest {
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     /** Test that UserProperties can properly read the xml information that it writes. */
     @Test
     public void testWriteReadXml() throws Exception {
+        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         final UserProperties defaultProps = new UserProperties.Builder()
                 .setShowInLauncher(21)
                 .setStartWithParent(false)
@@ -73,6 +78,7 @@
                 .setDeleteAppWithParent(false)
                 .setAlwaysVisible(false)
                 .setCrossProfileContentSharingStrategy(0)
+                .setProfileApiVisibility(34)
                 .build();
         final UserProperties actualProps = new UserProperties(defaultProps);
         actualProps.setShowInLauncher(14);
@@ -90,6 +96,7 @@
         actualProps.setDeleteAppWithParent(true);
         actualProps.setAlwaysVisible(true);
         actualProps.setCrossProfileContentSharingStrategy(1);
+        actualProps.setProfileApiVisibility(36);
 
         // Write the properties to xml.
         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -114,6 +121,7 @@
     /** Tests parcelling an object in which all properties are present. */
     @Test
     public void testParcelUnparcel() throws Exception {
+        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         final UserProperties originalProps = new UserProperties.Builder()
                 .setShowInLauncher(2145)
                 .build();
@@ -124,6 +132,7 @@
     /** Tests copying a UserProperties object varying permissions. */
     @Test
     public void testCopyLacksPermissions() throws Exception {
+        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         final UserProperties defaultProps = new UserProperties.Builder()
                 .setShowInLauncher(2145)
                 .setStartWithParent(true)
@@ -134,6 +143,7 @@
                 .setAuthAlwaysRequiredToDisableQuietMode(false)
                 .setAllowStoppingUserWithDelayedLocking(false)
                 .setAlwaysVisible(true)
+                .setProfileApiVisibility(110)
                 .build();
         final UserProperties orig = new UserProperties(defaultProps);
         orig.setShowInLauncher(2841);
@@ -209,6 +219,8 @@
                 copy::isCredentialShareableWithParent, true);
         assertEqualGetterOrThrows(orig::getCrossProfileContentSharingStrategy,
                 copy::getCrossProfileContentSharingStrategy, true);
+        assertEqualGetterOrThrows(orig::getProfileApiVisibility, copy::getProfileApiVisibility,
+                true);
     }
 
     /**
@@ -270,5 +282,6 @@
         assertThat(expected.getAlwaysVisible()).isEqualTo(actual.getAlwaysVisible());
         assertThat(expected.getCrossProfileContentSharingStrategy())
                 .isEqualTo(actual.getCrossProfileContentSharingStrategy());
+        assertThat(expected.getProfileApiVisibility()).isEqualTo(actual.getProfileApiVisibility());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 7083706..1ee604e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -41,6 +41,7 @@
 import android.os.Bundle;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.ArrayMap;
 
 import androidx.test.InstrumentationRegistry;
@@ -50,6 +51,7 @@
 import com.android.frameworks.servicestests.R;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -71,9 +73,11 @@
     public void setup() {
         mResources = InstrumentationRegistry.getTargetContext().getResources();
     }
-
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     @Test
     public void testUserTypeBuilder_createUserType() {
+        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         final Bundle restrictions = makeRestrictionsBundle("r1", "r2");
         final Bundle systemSettings = makeSettingsBundle("s1", "s2");
         final Bundle secureSettings = makeSettingsBundle("secure_s1", "secure_s2");
@@ -97,7 +101,8 @@
                 .setInheritDevicePolicy(340)
                 .setDeleteAppWithParent(true)
                 .setAlwaysVisible(true)
-                .setCrossProfileContentSharingStrategy(1);
+                .setCrossProfileContentSharingStrategy(1)
+                .setProfileApiVisibility(34);
 
         final UserTypeDetails type = new UserTypeDetails.Builder()
                 .setName("a.name")
@@ -180,6 +185,7 @@
         assertTrue(type.getDefaultUserPropertiesReference().getAlwaysVisible());
         assertEquals(1, type.getDefaultUserPropertiesReference()
                 .getCrossProfileContentSharingStrategy());
+        assertEquals(34, type.getDefaultUserPropertiesReference().getProfileApiVisibility());
 
         assertEquals(23, type.getBadgeLabel(0));
         assertEquals(24, type.getBadgeLabel(1));
@@ -199,6 +205,7 @@
 
     @Test
     public void testUserTypeBuilder_defaults() {
+        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         UserTypeDetails type = new UserTypeDetails.Builder()
                 .setName("name") // Required (no default allowed)
                 .setBaseType(FLAG_FULL) // Required (no default allowed)
@@ -238,6 +245,8 @@
                 props.getShowInQuietMode());
         assertEquals(UserProperties.CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION,
                 props.getCrossProfileContentSharingStrategy());
+        assertEquals(UserProperties.PROFILE_API_VISIBILITY_VISIBLE,
+                props.getProfileApiVisibility());
 
         assertFalse(type.hasBadge());
     }
@@ -310,6 +319,7 @@
     /** Tests {@link UserTypeFactory#customizeBuilders} for a reasonable xml file. */
     @Test
     public void testUserTypeFactoryCustomize_profile() throws Exception {
+        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         final String userTypeAosp1 = "android.test.1"; // Profile user that is not customized
         final String userTypeAosp2 = "android.test.2"; // Profile user that is customized
         final String userTypeOem1 = "custom.test.1"; // Custom-defined profile
@@ -332,7 +342,8 @@
                 .setShowInQuietMode(24)
                 .setDeleteAppWithParent(true)
                 .setAlwaysVisible(false)
-                .setCrossProfileContentSharingStrategy(1);
+                .setCrossProfileContentSharingStrategy(1)
+                .setProfileApiVisibility(36);
 
         final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
         builders.put(userTypeAosp1, new UserTypeDetails.Builder()
@@ -383,6 +394,7 @@
         assertFalse(aospType.getDefaultUserPropertiesReference().getAlwaysVisible());
         assertEquals(1, aospType.getDefaultUserPropertiesReference()
                 .getCrossProfileContentSharingStrategy());
+        assertEquals(36, aospType.getDefaultUserPropertiesReference().getProfileApiVisibility());
 
         // userTypeAosp2 should be modified.
         aospType = builders.get(userTypeAosp2).createUserTypeDetails();
@@ -439,6 +451,7 @@
         assertTrue(aospType.getDefaultUserPropertiesReference().getAlwaysVisible());
         assertEquals(0, aospType.getDefaultUserPropertiesReference()
                 .getCrossProfileContentSharingStrategy());
+        assertEquals(36, aospType.getDefaultUserPropertiesReference().getProfileApiVisibility());
 
         // userTypeOem1 should be created.
         UserTypeDetails.Builder customType = builders.get(userTypeOem1);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index a743fff..db561c4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 import static org.testng.Assert.assertEquals;
@@ -33,10 +34,12 @@
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.os.PersistableBundle;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Postsubmit;
 import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
@@ -53,6 +56,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -95,6 +99,8 @@
     private UserSwitchWaiter mUserSwitchWaiter;
     private UserRemovalWaiter mUserRemovalWaiter;
     private int mOriginalCurrentUserId;
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Before
     public void setUp() throws Exception {
@@ -166,6 +172,7 @@
 
     @Test
     public void testCloneUser() throws Exception {
+        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         assumeCloneEnabled();
         UserHandle mainUser = mUserManager.getMainUser();
         assumeTrue("Main user is null", mainUser != null);
@@ -222,6 +229,7 @@
                 .isEqualTo(cloneUserProperties.getCrossProfileContentSharingStrategy());
         assertThrows(SecurityException.class, cloneUserProperties::getDeleteAppWithParent);
         assertThrows(SecurityException.class, cloneUserProperties::getAlwaysVisible);
+        assertThrows(SecurityException.class, cloneUserProperties::getProfileApiVisibility);
         compareDrawables(mUserManager.getUserBadge(),
                 Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain()));
 
@@ -303,6 +311,7 @@
 
     @Test
     public void testPrivateProfile() throws Exception {
+        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         UserHandle mainUser = mUserManager.getMainUser();
         assumeTrue("Main user is null", mainUser != null);
         // Get the default properties for private profile user type.
@@ -344,7 +353,8 @@
         assertThrows(SecurityException.class, privateProfileUserProperties::getDeleteAppWithParent);
         assertThrows(SecurityException.class,
                 privateProfileUserProperties::getAllowStoppingUserWithDelayedLocking);
-
+        assertThrows(SecurityException.class,
+                privateProfileUserProperties::getProfileApiVisibility);
         compareDrawables(mUserManager.getUserBadge(),
                 Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain()));
 
@@ -1632,6 +1642,106 @@
         assertThat(mainUserCount).isEqualTo(1);
     }
 
+    @Test
+    public void testAddUserAccountData_validStringValuesAreSaved_validBundleIsSaved() {
+        assumeManagedUsersSupported();
+
+        String userName = "User";
+        String accountName = "accountName";
+        String accountType = "accountType";
+        String arrayKey = "StringArrayKey";
+        String stringKey = "StringKey";
+        String intKey = "IntKey";
+        String nestedBundleKey = "PersistableBundleKey";
+        String value1 = "Value 1";
+        String value2 = "Value 2";
+        String value3 = "Value 3";
+
+        UserInfo userInfo = mUserManager.createUser(userName,
+                UserManager.USER_TYPE_FULL_SECONDARY, 0);
+
+        PersistableBundle accountOptions = new PersistableBundle();
+        String[] stringArray = {value1, value2};
+        accountOptions.putInt(intKey, 1234);
+        PersistableBundle nested = new PersistableBundle();
+        nested.putString(stringKey, value3);
+        accountOptions.putPersistableBundle(nestedBundleKey, nested);
+        accountOptions.putStringArray(arrayKey, stringArray);
+
+        mUserManager.clearSeedAccountData();
+        mUserManager.setSeedAccountData(mContext.getUserId(), accountName,
+                accountType, accountOptions);
+
+        //assert userName accountName and accountType were saved correctly
+        assertTrue(mUserManager.getUserInfo(userInfo.id).name.equals(userName));
+        assertTrue(mUserManager.getSeedAccountName().equals(accountName));
+        assertTrue(mUserManager.getSeedAccountType().equals(accountType));
+
+        //assert bundle with correct values was added
+        assertThat(mUserManager.getSeedAccountOptions().containsKey(arrayKey)).isTrue();
+        assertThat(mUserManager.getSeedAccountOptions().getPersistableBundle(nestedBundleKey)
+                .getString(stringKey)).isEqualTo(value3);
+        assertThat(mUserManager.getSeedAccountOptions().getStringArray(arrayKey)[0])
+                .isEqualTo(value1);
+
+        mUserManager.removeUser(userInfo.id);
+    }
+
+    @Test
+    public void testAddUserAccountData_invalidStringValuesAreTruncated_invalidBundleIsDropped() {
+        assumeManagedUsersSupported();
+
+        String tooLongString = generateLongString();
+        String userName = "User " + tooLongString;
+        String accountType = "Account Type " + tooLongString;
+        String accountName = "accountName " + tooLongString;
+        String arrayKey = "StringArrayKey";
+        String stringKey = "StringKey";
+        String intKey = "IntKey";
+        String nestedBundleKey = "PersistableBundleKey";
+        String value1 = "Value 1";
+        String value2 = "Value 2";
+
+        UserInfo userInfo = mUserManager.createUser(userName,
+                UserManager.USER_TYPE_FULL_SECONDARY, 0);
+
+        PersistableBundle accountOptions = new PersistableBundle();
+        String[] stringArray = {value1, value2};
+        accountOptions.putInt(intKey, 1234);
+        PersistableBundle nested = new PersistableBundle();
+        nested.putString(stringKey, tooLongString);
+        accountOptions.putPersistableBundle(nestedBundleKey, nested);
+        accountOptions.putStringArray(arrayKey, stringArray);
+        mUserManager.clearSeedAccountData();
+        mUserManager.setSeedAccountData(mContext.getUserId(), accountName,
+                accountType, accountOptions);
+
+        //assert userName was truncated
+        assertTrue(mUserManager.getUserInfo(userInfo.id).name.length()
+                == UserManager.MAX_USER_NAME_LENGTH);
+
+        //assert accountName and accountType got truncated
+        assertTrue(mUserManager.getSeedAccountName().length()
+                == UserManager.MAX_ACCOUNT_STRING_LENGTH);
+        assertTrue(mUserManager.getSeedAccountType().length()
+                == UserManager.MAX_ACCOUNT_STRING_LENGTH);
+
+        //assert bundle with invalid values was dropped
+        assertThat(mUserManager.getSeedAccountOptions() == null).isTrue();
+
+        mUserManager.removeUser(userInfo.id);
+    }
+
+    private String generateLongString() {
+        String partialString = "Test Name Test Name Test Name Test Name Test Name Test Name Test "
+                + "Name Test Name Test Name Test Name "; //String of length 100
+        StringBuilder resultString = new StringBuilder();
+        for (int i = 0; i < 600; i++) {
+            resultString.append(partialString);
+        }
+        return resultString.toString();
+    }
+
     private boolean isPackageInstalledForUser(String packageName, int userId) {
         try {
             return mPackageManager.getPackageInfoAsUser(packageName, 0, userId) != null;
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
index 757abde..e3ee21a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
@@ -436,6 +436,13 @@
         validateTagCount("action", 20000, tag)
         validateTagCount("category", 40000, tag)
         validateTagCount("data", 40000, tag)
+        validateTagCount("uri-relative-filter-group", 100, tag)
+    }
+
+    @Test
+    fun parseUriRelativeFilterGroupTag() {
+        val tag = "uri-relative-filter-group"
+        validateTagCount("data", 100, tag)
     }
 
     @Test
@@ -465,6 +472,54 @@
             R.styleable.AndroidManifestData_pathAdvancedPattern,
             4000
         )
+        validateTagAttr(tag, "query", R.styleable.AndroidManifestData_query, 4000)
+        validateTagAttr(
+            tag,
+            "queryPattern",
+            R.styleable.AndroidManifestData_queryPattern,
+            4000
+        )
+        validateTagAttr(
+            tag,
+            "queryPrefix",
+            R.styleable.AndroidManifestData_queryPrefix,
+            4000
+        )
+        validateTagAttr(tag,
+            "querySuffix",
+            R.styleable.AndroidManifestData_querySuffix,
+            4000
+        )
+        validateTagAttr(
+            tag,
+            "queryAdvancedPattern",
+            R.styleable.AndroidManifestData_queryAdvancedPattern,
+            4000
+        )
+        validateTagAttr(tag, "fragment", R.styleable.AndroidManifestData_query, 4000)
+        validateTagAttr(
+            tag,
+            "fragmentPattern",
+            R.styleable.AndroidManifestData_fragmentPattern,
+            4000
+        )
+        validateTagAttr(
+            tag,
+            "fragmentPrefix",
+            R.styleable.AndroidManifestData_fragmentPrefix,
+            4000
+        )
+        validateTagAttr(tag,
+            "fragmentSuffix",
+            R.styleable.AndroidManifestData_fragmentSuffix,
+            4000
+        )
+        validateTagAttr(
+            tag,
+            "fragmentAdvancedPattern",
+            R.styleable.AndroidManifestData_fragmentAdvancedPattern,
+            4000
+        )
         validateTagAttr(tag, "mimeType", R.styleable.AndroidManifestData_mimeType, 255)
         validateTagAttr(tag, "mimeGroup", R.styleable.AndroidManifestData_mimeGroup, 1024)
     }
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
index 9d56a36..5e11e17 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
 import android.content.rollback.PackageRollbackInfo;
 import android.util.SparseIntArray;
@@ -81,7 +82,8 @@
             + "'installedUsers':[55,79],"
             + "'ceSnapshotInodes':[]}],'isStaged':false,'causePackages':[{'packageName':'hello',"
             + "'longVersionCode':23},{'packageName':'something','longVersionCode':999}],"
-            + "'committedSessionId':45654465},'timestamp':'2019-10-01T12:29:08.855Z',"
+            + "'committedSessionId':45654465, 'rollbackImpactLevel':1},"
+            + "'timestamp':'2019-10-01T12:29:08.855Z',"
             + "'originalSessionId':567,'state':'enabling','apkSessionId':-1,"
             + "'restoreUserDataInProgress':true, 'userId':0,"
             + "'installerPackageName':'some.installer'}";
@@ -138,6 +140,8 @@
         assertThat(rollback.getOriginalSessionId()).isEqualTo(567);
         assertThat(rollback.info.getRollbackId()).isEqualTo(ID);
         assertThat(rollback.info.getPackages()).isEmpty();
+        assertThat(rollback.info.getRollbackImpactLevel()).isEqualTo(
+                PackageManager.ROLLBACK_USER_IMPACT_LOW);
         assertThat(rollback.isEnabling()).isTrue();
         assertThat(rollback.getExtensionVersions().toString())
                 .isEqualTo(extensionVersions.toString());
@@ -158,6 +162,8 @@
 
         assertThat(rollback.info.getRollbackId()).isEqualTo(ID);
         assertThat(rollback.info.getPackages()).isEmpty();
+        assertThat(rollback.info.getRollbackImpactLevel()).isEqualTo(
+                PackageManager.ROLLBACK_USER_IMPACT_LOW);
         assertThat(rollback.isEnabling()).isTrue();
         assertThat(rollback.getExtensionVersions().toString())
                 .isEqualTo(extensionVersions.toString());
@@ -175,6 +181,7 @@
         origRb.info.getCausePackages().add(new VersionedPackage("com.made.up", 2));
         origRb.info.getCausePackages().add(new VersionedPackage("com.pack.age", 99));
         origRb.info.setCommittedSessionId(123456);
+        origRb.info.setRollbackImpactLevel(PackageManager.ROLLBACK_USER_IMPACT_HIGH);
 
         PackageRollbackInfo pkgInfo1 =
                 new PackageRollbackInfo(new VersionedPackage("com.made.up", 18),
@@ -226,6 +233,7 @@
         expectedRb.info.getCausePackages().add(new VersionedPackage("hello", 23));
         expectedRb.info.getCausePackages().add(new VersionedPackage("something", 999));
         expectedRb.info.setCommittedSessionId(45654465);
+        expectedRb.info.setRollbackImpactLevel(PackageManager.ROLLBACK_USER_IMPACT_HIGH);
 
         PackageRollbackInfo pkgInfo1 = new PackageRollbackInfo(new VersionedPackage("blah", 55),
                 new VersionedPackage("blah1", 50), new ArrayList<>(), new ArrayList<>(),
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt
index 150822b..c07c4d7 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt
@@ -18,12 +18,13 @@
 
 import android.content.Context
 import android.util.Xml
-import androidx.test.InstrumentationRegistry
+import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.SystemConfig
 import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertThrows
 import org.junit.Rule
 import org.junit.Test
-import org.junit.rules.ExpectedException
 import org.junit.rules.TemporaryFolder
 
 class SystemConfigNamedActorTest {
@@ -37,14 +38,11 @@
         private const val PACKAGE_TWO = "com.test.actor.two"
     }
 
-    private val context: Context = InstrumentationRegistry.getContext()
+    private val context: Context = InstrumentationRegistry.getInstrumentation().context
 
     @get:Rule
     val tempFolder = TemporaryFolder(context.filesDir)
 
-    @get:Rule
-    val expected = ExpectedException.none()
-
     private var uniqueCounter = 0
 
     @Test
@@ -193,11 +191,9 @@
             </config>
         """.write()
 
-        expected.expect(IllegalStateException::class.java)
-        expected.expectMessage("Defining $ACTOR_ONE as $PACKAGE_ONE " +
+        val exc = assertThrows(IllegalStateException::class.java) { assertPermissions() }
+        assertEquals(exc.message, "Defining $ACTOR_ONE as $PACKAGE_ONE " +
                 "for the android namespace is not allowed")
-
-        assertPermissions()
     }
 
     @Test
@@ -217,11 +213,9 @@
             </config>
         """.write()
 
-        expected.expect(IllegalStateException::class.java)
-        expected.expectMessage("Duplicate actor definition for $NAMESPACE_TEST/$ACTOR_ONE;" +
+        val exc = assertThrows(IllegalStateException::class.java) { assertPermissions() }
+        assertEquals(exc.message, "Duplicate actor definition for $NAMESPACE_TEST/$ACTOR_ONE;" +
                 " defined as both $PACKAGE_ONE and $PACKAGE_TWO")
-
-        assertPermissions()
     }
 
     private fun String.write() = tempFolder.root.resolve("${uniqueCounter++}.xml")
@@ -230,5 +224,5 @@
     private fun assertPermissions() = SystemConfig(false).apply {
         val parser = Xml.newPullParser()
         readPermissions(parser, tempFolder.root, 0)
-    }. let { assertThat(it.namedActors) }
+    }.let { assertThat(it.namedActors) }
 }
diff --git a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
index 769ec5f..3218586 100644
--- a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
@@ -345,15 +345,18 @@
                 intent, UID_PRIMARY_CAMERA, PKG_SOCIAL, USER_PRIMARY), service);
 
         // Verify that everything is good with the world
-        assertTrue(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ));
+        assertTrue(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ,
+                /* isFullAccessForContentUri */ false));
 
         // Finish activity; service should hold permission
         activity.removeUriPermissions();
-        assertTrue(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ));
+        assertTrue(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ,
+                /* isFullAccessForContentUri */ false));
 
         // And finishing service should wrap things up
         service.removeUriPermissions();
-        assertFalse(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ));
+        assertFalse(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ,
+                /* isFullAccessForContentUri */ false));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
index 3530e38..ae0a758 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
@@ -85,7 +85,7 @@
     private void enablePackageForUser(String packageName, boolean enable, int userId) {
         Map<Integer, PackageInfo> userPackages = mPackages.get(packageName);
         if (userPackages == null) {
-            throw new IllegalArgumentException("There is no package called " + packageName);
+            return;
         }
         PackageInfo packageInfo = userPackages.get(userId);
         packageInfo.applicationInfo.enabled = enable;
diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
index 32082e3..5a06327 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -127,12 +127,21 @@
     private void checkCertainPackageUsedAfterWebViewBootPreparation(String expectedProviderName,
             WebViewProviderInfo[] webviewPackages) {
         checkCertainPackageUsedAfterWebViewBootPreparation(
-                expectedProviderName, webviewPackages, 1);
+                expectedProviderName, webviewPackages, 1, null);
     }
 
     private void checkCertainPackageUsedAfterWebViewBootPreparation(String expectedProviderName,
-            WebViewProviderInfo[] webviewPackages, int numRelros) {
+            WebViewProviderInfo[] webviewPackages, String userSetting) {
+        checkCertainPackageUsedAfterWebViewBootPreparation(
+                expectedProviderName, webviewPackages, 1, userSetting);
+    }
+
+    private void checkCertainPackageUsedAfterWebViewBootPreparation(String expectedProviderName,
+            WebViewProviderInfo[] webviewPackages, int numRelros, String userSetting) {
         setupWithPackagesAndRelroCount(webviewPackages, numRelros);
+        if (userSetting != null) {
+            mTestSystemImpl.updateUserSetting(null, userSetting);
+        }
         // Add (enabled and valid) package infos for each provider
         setEnabledAndValidPackageInfos(webviewPackages);
 
@@ -280,7 +289,7 @@
                 singlePackage,
                 new WebViewProviderInfo[] {
                     new WebViewProviderInfo(singlePackage, "", true /*def av*/, false, null)},
-                2);
+                2, null);
     }
 
     // Ensure that package with valid signatures is chosen rather than package with invalid
@@ -295,14 +304,16 @@
         Signature invalidPackageSignature = new Signature("33");
 
         WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
-            new WebViewProviderInfo(invalidPackage, "", true, false, new String[]{
-                        Base64.encodeToString(
-                                invalidExpectedSignature.toByteArray(), Base64.DEFAULT)}),
             new WebViewProviderInfo(validPackage, "", true, false, new String[]{
                         Base64.encodeToString(
-                                validSignature.toByteArray(), Base64.DEFAULT)})
+                                validSignature.toByteArray(), Base64.DEFAULT)}),
+            new WebViewProviderInfo(invalidPackage, "", true, false, new String[]{
+                        Base64.encodeToString(
+                                invalidExpectedSignature.toByteArray(), Base64.DEFAULT)})
         };
         setupWithPackagesNonDebuggable(packages);
+        // Start with the setting pointing to the invalid package
+        mTestSystemImpl.updateUserSetting(null, invalidPackage);
         mTestSystemImpl.setPackageInfo(createPackageInfo(invalidPackage, true /* enabled */,
                     true /* valid */, true /* installed */, new Signature[]{invalidPackageSignature}
                     , 0 /* updateTime */));
@@ -339,7 +350,9 @@
     }
 
     @Test
-    public void testFailListingEmptyWebviewPackages() {
+    @RequiresFlagsDisabled("android.webkit.update_service_v2")
+    // If the flag is set, will throw an exception because of no available by default provider.
+    public void testEmptyConfig() {
         WebViewProviderInfo[] packages = new WebViewProviderInfo[0];
         setupWithPackages(packages);
         setEnabledAndValidPackageInfos(packages);
@@ -352,14 +365,26 @@
         WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
         assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status);
         assertEquals(null, mWebViewUpdateServiceImpl.getCurrentWebViewPackage());
+    }
 
-        // Now install a package
+    @Test
+    public void testFailListingEmptyWebviewPackages() {
         String singlePackage = "singlePackage";
-        packages = new WebViewProviderInfo[]{
+        WebViewProviderInfo[] packages = new WebViewProviderInfo[]{
             new WebViewProviderInfo(singlePackage, "", true, false, null)};
         setupWithPackages(packages);
-        setEnabledAndValidPackageInfos(packages);
 
+        runWebViewBootPreparationOnMainSync();
+
+        Mockito.verify(mTestSystemImpl, Mockito.never()).onWebViewProviderChanged(
+                Matchers.anyObject());
+
+        WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
+        assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status);
+        assertEquals(null, mWebViewUpdateServiceImpl.getCurrentWebViewPackage());
+
+        // Now install the package
+        setEnabledAndValidPackageInfos(packages);
         mWebViewUpdateServiceImpl.packageStateChanged(singlePackage,
                 WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID);
 
@@ -370,7 +395,7 @@
         // Remove the package again
         mTestSystemImpl.removePackageInfo(singlePackage);
         mWebViewUpdateServiceImpl.packageStateChanged(singlePackage,
-                WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID);
+                WebViewUpdateService.PACKAGE_REMOVED, TestSystemImpl.PRIMARY_USER_ID);
 
         // Package removed - ensure our interface states that there is no package
         response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
@@ -455,6 +480,8 @@
             new WebViewProviderInfo(firstPackage, "", true, false, null),
             new WebViewProviderInfo(secondPackage, "", true, false, null)};
         setupWithPackages(packages);
+        // Start with the setting pointing to the second package
+        mTestSystemImpl.updateUserSetting(null, secondPackage);
         // Have all packages be enabled, so that we can change provider however we want to
         setEnabledAndValidPackageInfos(packages);
 
@@ -463,9 +490,9 @@
         runWebViewBootPreparationOnMainSync();
 
         Mockito.verify(mTestSystemImpl).onWebViewProviderChanged(
-                Mockito.argThat(new IsPackageInfoWithName(firstPackage)));
+                Mockito.argThat(new IsPackageInfoWithName(secondPackage)));
 
-        assertEquals(firstPackage,
+        assertEquals(secondPackage,
                 mWebViewUpdateServiceImpl.getCurrentWebViewPackage().packageName);
 
         new Thread(new Runnable() {
@@ -474,12 +501,13 @@
                 WebViewProviderResponse threadResponse =
                     mWebViewUpdateServiceImpl.waitForAndGetProvider();
                 assertEquals(WebViewFactory.LIBLOAD_SUCCESS, threadResponse.status);
-                assertEquals(secondPackage, threadResponse.packageInfo.packageName);
-                // Verify that we killed the first package if we performed a settings change -
-                // otherwise we had to disable the first package, in which case its dependents
+                assertEquals(firstPackage, threadResponse.packageInfo.packageName);
+                // Verify that we killed the second package if we performed a settings change -
+                // otherwise we had to disable the second package, in which case its dependents
                 // should have been killed by the framework.
                 if (settingsChange) {
-                    Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(firstPackage));
+                    Mockito.verify(mTestSystemImpl)
+                            .killPackageDependents(Mockito.eq(secondPackage));
                 }
                 countdown.countDown();
             }
@@ -490,32 +518,36 @@
         }
 
         if (settingsChange) {
-            mWebViewUpdateServiceImpl.changeProviderAndSetting(secondPackage);
+            mWebViewUpdateServiceImpl.changeProviderAndSetting(firstPackage);
         } else {
-            // Enable the second provider
-            mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */,
+            // Enable the first provider
+            mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, true /* enabled */,
                         true /* valid */, true /* installed */));
             mWebViewUpdateServiceImpl.packageStateChanged(
-                    secondPackage, WebViewUpdateService.PACKAGE_CHANGED, TestSystemImpl.PRIMARY_USER_ID);
+                    firstPackage,
+                    WebViewUpdateService.PACKAGE_CHANGED,
+                    TestSystemImpl.PRIMARY_USER_ID);
 
             // Ensure we haven't changed package yet.
-            assertEquals(firstPackage,
+            assertEquals(secondPackage,
                     mWebViewUpdateServiceImpl.getCurrentWebViewPackage().packageName);
 
-            // Switch provider by disabling the first one
-            mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, false /* enabled */,
+            // Switch provider by disabling the second one
+            mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, false /* enabled */,
                         true /* valid */, true /* installed */));
             mWebViewUpdateServiceImpl.packageStateChanged(
-                    firstPackage, WebViewUpdateService.PACKAGE_CHANGED, TestSystemImpl.PRIMARY_USER_ID);
+                    secondPackage,
+                    WebViewUpdateService.PACKAGE_CHANGED,
+                    TestSystemImpl.PRIMARY_USER_ID);
         }
         mWebViewUpdateServiceImpl.notifyRelroCreationCompleted();
-        // first package done, should start on second
+        // second package done, should start on first
 
         Mockito.verify(mTestSystemImpl).onWebViewProviderChanged(
-                Mockito.argThat(new IsPackageInfoWithName(secondPackage)));
+                Mockito.argThat(new IsPackageInfoWithName(firstPackage)));
 
         mWebViewUpdateServiceImpl.notifyRelroCreationCompleted();
-        // second package done, the other thread should now be unblocked
+        // first package done, the other thread should now be unblocked
         try {
             countdown.await();
         } catch (InterruptedException e) {
@@ -526,6 +558,7 @@
      * Scenario for testing re-enabling a fallback package.
      */
     @Test
+    @RequiresFlagsDisabled("android.webkit.update_service_v2")
     public void testFallbackPackageEnabling() {
         String testPackage = "testFallback";
         WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
@@ -555,6 +588,9 @@
      * 3. Primary should be used
      */
     @Test
+    @RequiresFlagsDisabled("android.webkit.update_service_v2")
+    // If the flag is set, we don't automitally switch to secondary package unless it is
+    // chosen directly.
     public void testInstallingPrimaryPackage() {
         String primaryPackage = "primary";
         String secondaryPackage = "secondary";
@@ -586,16 +622,16 @@
     }
 
     @Test
-    public void testRemovingPrimarySelectsSecondarySingleUser() {
+    public void testRemovingSecondarySelectsPrimarySingleUser() {
         for (PackageRemovalType removalType : REMOVAL_TYPES) {
-            checkRemovingPrimarySelectsSecondary(false /* multiUser */, removalType);
+            checkRemovingSecondarySelectsPrimary(false /* multiUser */, removalType);
         }
     }
 
     @Test
-    public void testRemovingPrimarySelectsSecondaryMultiUser() {
+    public void testRemovingSecondarySelectsPrimaryMultiUser() {
         for (PackageRemovalType removalType : REMOVAL_TYPES) {
-            checkRemovingPrimarySelectsSecondary(true /* multiUser */, removalType);
+            checkRemovingSecondarySelectsPrimary(true /* multiUser */, removalType);
         }
     }
 
@@ -609,7 +645,7 @@
 
     private PackageRemovalType[] REMOVAL_TYPES = PackageRemovalType.class.getEnumConstants();
 
-    public void checkRemovingPrimarySelectsSecondary(boolean multiUser,
+    private void checkRemovingSecondarySelectsPrimary(boolean multiUser,
             PackageRemovalType removalType) {
         String primaryPackage = "primary";
         String secondaryPackage = "secondary";
@@ -620,6 +656,8 @@
                     secondaryPackage, "", true /* default available */, false /* fallback */,
                     null)};
         setupWithPackages(packages);
+        // Start with the setting pointing to the secondary package
+        mTestSystemImpl.updateUserSetting(null, secondaryPackage);
         int secondaryUserId = 10;
         int userIdToChangePackageFor = multiUser ? secondaryUserId : TestSystemImpl.PRIMARY_USER_ID;
         if (multiUser) {
@@ -629,31 +667,31 @@
         setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages);
 
         runWebViewBootPreparationOnMainSync();
-        checkPreparationPhasesForPackage(primaryPackage, 1);
+        checkPreparationPhasesForPackage(secondaryPackage, 1);
 
         boolean enabled = !(removalType == PackageRemovalType.DISABLE);
         boolean installed = !(removalType == PackageRemovalType.UNINSTALL);
         boolean hidden = (removalType == PackageRemovalType.HIDE);
-        // Disable primary package and ensure secondary becomes used
+        // Disable secondary package and ensure primary becomes used
         mTestSystemImpl.setPackageInfoForUser(userIdToChangePackageFor,
-                createPackageInfo(primaryPackage, enabled /* enabled */, true /* valid */,
+                createPackageInfo(secondaryPackage, enabled /* enabled */, true /* valid */,
                     installed /* installed */, null /* signature */, 0 /* updateTime */,
                     hidden /* hidden */));
-        mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage,
+        mWebViewUpdateServiceImpl.packageStateChanged(secondaryPackage,
                 removalType == PackageRemovalType.DISABLE
                 ? WebViewUpdateService.PACKAGE_CHANGED : WebViewUpdateService.PACKAGE_REMOVED,
                 userIdToChangePackageFor); // USER ID
-        checkPreparationPhasesForPackage(secondaryPackage, 1);
+        checkPreparationPhasesForPackage(primaryPackage, 1);
 
-        // Again enable primary package and verify primary is used
+        // Again enable secondary package and verify secondary is used
         mTestSystemImpl.setPackageInfoForUser(userIdToChangePackageFor,
-                createPackageInfo(primaryPackage, true /* enabled */, true /* valid */,
+                createPackageInfo(secondaryPackage, true /* enabled */, true /* valid */,
                     true /* installed */));
-        mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage,
+        mWebViewUpdateServiceImpl.packageStateChanged(secondaryPackage,
                 removalType == PackageRemovalType.DISABLE
                 ? WebViewUpdateService.PACKAGE_CHANGED : WebViewUpdateService.PACKAGE_ADDED,
                 userIdToChangePackageFor);
-        checkPreparationPhasesForPackage(primaryPackage, 2);
+        checkPreparationPhasesForPackage(secondaryPackage, 2);
     }
 
     /**
@@ -671,18 +709,20 @@
                     secondaryPackage, "", true /* default available */, false /* fallback */,
                     null)};
         setupWithPackages(packages);
+        // Start with the setting pointing to the secondary package
+        mTestSystemImpl.updateUserSetting(null, secondaryPackage);
         setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages);
         int newUser = 100;
         mTestSystemImpl.addUser(newUser);
-        // Let the primary package be uninstalled for the new user
-        mTestSystemImpl.setPackageInfoForUser(newUser,
-                createPackageInfo(primaryPackage, true /* enabled */, true /* valid */,
-                        false /* installed */));
+        // Let the secondary package be uninstalled for the new user
         mTestSystemImpl.setPackageInfoForUser(newUser,
                 createPackageInfo(secondaryPackage, true /* enabled */, true /* valid */,
+                        false /* installed */));
+        mTestSystemImpl.setPackageInfoForUser(newUser,
+                createPackageInfo(primaryPackage, true /* enabled */, true /* valid */,
                         true /* installed */));
         mWebViewUpdateServiceImpl.handleNewUser(newUser);
-        checkPreparationPhasesForPackage(secondaryPackage, 1 /* numRelros */);
+        checkPreparationPhasesForPackage(primaryPackage, 1 /* numRelros */);
     }
 
     /**
@@ -780,9 +820,9 @@
         String chosenPackage = "chosenPackage";
         String nonChosenPackage = "non-chosenPackage";
         WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
-            new WebViewProviderInfo(chosenPackage, "", true /* default available */,
-                    false /* fallback */, null),
             new WebViewProviderInfo(nonChosenPackage, "", true /* default available */,
+                    false /* fallback */, null),
+            new WebViewProviderInfo(chosenPackage, "", true /* default available */,
                     false /* fallback */, null)};
 
         setupWithPackages(packages);
@@ -810,6 +850,9 @@
     }
 
     @Test
+    @RequiresFlagsDisabled("android.webkit.update_service_v2")
+    // If the flag is set, we don't automitally switch to second package unless it is chosen
+    // directly.
     public void testRecoverFailedListingWebViewPackagesAddedPackage() {
         checkRecoverAfterFailListingWebviewPackages(false);
     }
@@ -874,22 +917,22 @@
                     false /* fallback */, null),
             new WebViewProviderInfo(secondPackage, "", true /* default available */,
                     false /* fallback */, null)};
-        checkCertainPackageUsedAfterWebViewBootPreparation(firstPackage, packages);
+        checkCertainPackageUsedAfterWebViewBootPreparation(secondPackage, packages, secondPackage);
 
         // Replace or remove the current webview package
         if (replaced) {
             mTestSystemImpl.setPackageInfo(
-                    createPackageInfo(firstPackage, true /* enabled */, false /* valid */,
+                    createPackageInfo(secondPackage, true /* enabled */, false /* valid */,
                         true /* installed */));
-            mWebViewUpdateServiceImpl.packageStateChanged(firstPackage,
+            mWebViewUpdateServiceImpl.packageStateChanged(secondPackage,
                     WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID);
         } else {
-            mTestSystemImpl.removePackageInfo(firstPackage);
-            mWebViewUpdateServiceImpl.packageStateChanged(firstPackage,
+            mTestSystemImpl.removePackageInfo(secondPackage);
+            mWebViewUpdateServiceImpl.packageStateChanged(secondPackage,
                     WebViewUpdateService.PACKAGE_REMOVED, TestSystemImpl.PRIMARY_USER_ID);
         }
 
-        checkPreparationPhasesForPackage(secondPackage, 1);
+        checkPreparationPhasesForPackage(firstPackage, 1);
 
         Mockito.verify(mTestSystemImpl, Mockito.never()).killPackageDependents(
                 Mockito.anyObject());
@@ -1073,10 +1116,12 @@
     }
 
     /**
-     * Ensure that the update service does use an uninstalled package when that is the only
+     * Ensure that the update service does not use an uninstalled package even if it is the only
      * package available.
      */
     @Test
+    @RequiresFlagsDisabled("android.webkit.update_service_v2")
+    // If the flag is set, we return the package even if it is not installed.
     public void testWithSingleUninstalledPackage() {
         String testPackageName = "test.package.name";
         WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] {
@@ -1115,12 +1160,14 @@
         String installedPackage = "installedPackage";
         String uninstalledPackage = "uninstalledPackage";
         WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] {
-            new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */,
-                    false /* fallback */, null),
             new WebViewProviderInfo(installedPackage, "", true /* available by default */,
+                    false /* fallback */, null),
+            new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */,
                     false /* fallback */, null)};
 
         setupWithPackages(webviewPackages);
+        // Start with the setting pointing to the uninstalled package
+        mTestSystemImpl.updateUserSetting(null, uninstalledPackage);
         int secondaryUserId = 5;
         if (multiUser) {
             mTestSystemImpl.addUser(secondaryUserId);
@@ -1128,7 +1175,7 @@
             setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, webviewPackages);
             mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(
                     installedPackage, true /* enabled */, true /* valid */, true /* installed */));
-            // Hide or uninstall the primary package for the second user
+            // Hide or uninstall the secondary package for the second user
             mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */,
                     true /* valid */, (testUninstalled ? false : true) /* installed */,
                     null /* signatures */, 0 /* updateTime */, (testHidden ? true : false)));
@@ -1166,12 +1213,14 @@
         String installedPackage = "installedPackage";
         String uninstalledPackage = "uninstalledPackage";
         WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] {
-            new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */,
-                    false /* fallback */, null),
             new WebViewProviderInfo(installedPackage, "", true /* available by default */,
+                    false /* fallback */, null),
+            new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */,
                     false /* fallback */, null)};
 
         setupWithPackages(webviewPackages);
+        // Start with the setting pointing to the uninstalled package
+        mTestSystemImpl.updateUserSetting(null, uninstalledPackage);
         int secondaryUserId = 412;
         mTestSystemImpl.addUser(secondaryUserId);
 
@@ -1221,12 +1270,14 @@
         String installedPackage = "installedPackage";
         String uninstalledPackage = "uninstalledPackage";
         WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] {
-            new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */,
-                    false /* fallback */, null),
             new WebViewProviderInfo(installedPackage, "", true /* available by default */,
+                    false /* fallback */, null),
+            new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */,
                     false /* fallback */, null)};
 
         setupWithPackages(webviewPackages);
+        // Start with the setting pointing to the uninstalled package
+        mTestSystemImpl.updateUserSetting(null, uninstalledPackage);
         int secondaryUserId = 4;
         mTestSystemImpl.addUser(secondaryUserId);
 
@@ -1433,11 +1484,16 @@
                 new WebViewProviderInfo(newSdkPackage.packageName, "", true, false, null);
         WebViewProviderInfo currentSdkProviderInfo =
                 new WebViewProviderInfo(currentSdkPackage.packageName, "", true, false, null);
-        WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
-            new WebViewProviderInfo(oldSdkPackage.packageName, "", true, false, null),
-            currentSdkProviderInfo, newSdkProviderInfo};
+        WebViewProviderInfo[] packages =
+                new WebViewProviderInfo[] {
+                    currentSdkProviderInfo,
+                    new WebViewProviderInfo(oldSdkPackage.packageName, "", true, false, null),
+                    newSdkProviderInfo
+                };
         setupWithPackages(packages);
-;
+        // Start with the setting pointing to the invalid package
+        mTestSystemImpl.updateUserSetting(null, oldSdkPackage.packageName);
+
         mTestSystemImpl.setPackageInfo(newSdkPackage);
         mTestSystemImpl.setPackageInfo(currentSdkPackage);
         mTestSystemImpl.setPackageInfo(oldSdkPackage);
@@ -1467,4 +1523,74 @@
         assertEquals(
                 defaultPackage1, mWebViewUpdateServiceImpl.getDefaultWebViewPackage().packageName);
     }
+
+    @Test
+    @RequiresFlagsEnabled("android.webkit.update_service_v2")
+    public void testDefaultWebViewPackageEnabling() {
+        String testPackage = "testDefault";
+        WebViewProviderInfo[] packages =
+                new WebViewProviderInfo[] {
+                    new WebViewProviderInfo(
+                            testPackage,
+                            "",
+                            true /* default available */,
+                            false /* fallback */,
+                            null)
+                };
+        setupWithPackages(packages);
+        mTestSystemImpl.setPackageInfo(
+                createPackageInfo(
+                        testPackage, false /* enabled */, true /* valid */, true /* installed */));
+
+        // Check that the boot time logic re-enables the default package.
+        runWebViewBootPreparationOnMainSync();
+        Mockito.verify(mTestSystemImpl)
+                .enablePackageForAllUsers(
+                        Matchers.anyObject(), Mockito.eq(testPackage), Mockito.eq(true));
+    }
+
+    private void testDefaultPackageChosen(PackageInfo packageInfo) {
+        WebViewProviderInfo[] packages =
+                new WebViewProviderInfo[] {
+                    new WebViewProviderInfo(packageInfo.packageName, "", true, false, null)
+                };
+        setupWithPackages(packages);
+        mTestSystemImpl.setPackageInfo(packageInfo);
+
+        runWebViewBootPreparationOnMainSync();
+        mWebViewUpdateServiceImpl.notifyRelroCreationCompleted();
+
+        assertEquals(
+                packageInfo.packageName,
+                mWebViewUpdateServiceImpl.getCurrentWebViewPackage().packageName);
+
+        WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
+        assertEquals(packageInfo.packageName, response.packageInfo.packageName);
+    }
+
+    @Test
+    @RequiresFlagsEnabled("android.webkit.update_service_v2")
+    public void testDisabledDefaultPackageChosen() {
+        PackageInfo disabledPackage =
+                createPackageInfo(
+                        "disabledPackage",
+                        false /* enabled */,
+                        true /* valid */,
+                        true /* installed */);
+
+        testDefaultPackageChosen(disabledPackage);
+    }
+
+    @Test
+    @RequiresFlagsEnabled("android.webkit.update_service_v2")
+    public void testUninstalledDefaultPackageChosen() {
+        PackageInfo uninstalledPackage =
+                createPackageInfo(
+                        "uninstalledPackage",
+                        true /* enabled */,
+                        true /* valid */,
+                        false /* installed */);
+
+        testDefaultPackageChosen(uninstalledPackage);
+    }
 }
diff --git a/services/tests/servicestests/test-apps/JobTestApp/Android.bp b/services/tests/servicestests/test-apps/JobTestApp/Android.bp
deleted file mode 100644
index 6458bcd..0000000
--- a/services/tests/servicestests/test-apps/JobTestApp/Android.bp
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test_helper_app {
-    name: "JobTestApp",
-
-    sdk_version: "current",
-
-    srcs: ["**/*.java"],
-
-    dex_preopt: {
-        enabled: false,
-    },
-    optimize: {
-        enabled: false,
-    },
-}
diff --git a/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml
deleted file mode 100644
index ac35805..0000000
--- a/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.servicestests.apps.jobtestapp">
-
-    <application>
-        <service android:name=".TestJobService"
-                 android:permission="android.permission.BIND_JOB_SERVICE" />
-        <activity android:name=".TestJobActivity"
-                  android:exported="true" />
-    </application>
-
-</manifest>
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/JobTestApp/OWNERS b/services/tests/servicestests/test-apps/JobTestApp/OWNERS
deleted file mode 100644
index 6f207fb1..0000000
--- a/services/tests/servicestests/test-apps/JobTestApp/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /apex/jobscheduler/OWNERS
diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java
deleted file mode 100644
index 99eb196..0000000
--- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.servicestests.apps.jobtestapp;
-
-import android.app.Activity;
-import android.app.job.JobInfo;
-import android.app.job.JobScheduler;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-
-public class TestJobActivity extends Activity {
-    private static final String TAG = TestJobActivity.class.getSimpleName();
-    private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp";
-
-    public static final String EXTRA_JOB_ID_KEY = PACKAGE_NAME + ".extra.JOB_ID";
-    public static final String ACTION_START_JOB = PACKAGE_NAME + ".action.START_JOB";
-    public static final String ACTION_CANCEL_JOBS = PACKAGE_NAME + ".action.CANCEL_JOBS";
-    public static final int JOB_INITIAL_BACKOFF = 10_000;
-    public static final int JOB_MINIMUM_LATENCY = 5_000;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        ComponentName jobServiceComponent = new ComponentName(this, TestJobService.class);
-        JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
-        final Intent intent = getIntent();
-        switch (intent.getAction()) {
-            case ACTION_CANCEL_JOBS:
-                jobScheduler.cancelAll();
-                Log.d(TAG, "Cancelled all jobs for " + getPackageName());
-                break;
-            case ACTION_START_JOB:
-                final int jobId = intent.getIntExtra(EXTRA_JOB_ID_KEY, hashCode());
-                JobInfo.Builder jobBuilder = new JobInfo.Builder(jobId, jobServiceComponent)
-                        .setBackoffCriteria(JOB_INITIAL_BACKOFF, JobInfo.BACKOFF_POLICY_LINEAR)
-                        .setMinimumLatency(JOB_MINIMUM_LATENCY)
-                        .setOverrideDeadline(JOB_MINIMUM_LATENCY);
-                final int result = jobScheduler.schedule(jobBuilder.build());
-                if (result != JobScheduler.RESULT_SUCCESS) {
-                    Log.e(TAG, "Could not schedule job " + jobId);
-                } else {
-                    Log.d(TAG, "Successfully scheduled job with id " + jobId);
-                }
-                break;
-            default:
-                Log.e(TAG, "Unknown action " + intent.getAction());
-        }
-        finish();
-    }
-}
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
deleted file mode 100644
index b8585f2..0000000
--- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.servicestests.apps.jobtestapp;
-
-import android.annotation.TargetApi;
-import android.app.job.JobParameters;
-import android.app.job.JobService;
-import android.content.Intent;
-import android.util.Log;
-
-@TargetApi(24)
-public class TestJobService extends JobService {
-    private static final String TAG = TestJobService.class.getSimpleName();
-    private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp";
-    public static final String ACTION_JOB_STARTED = PACKAGE_NAME + ".action.JOB_STARTED";
-    public static final String ACTION_JOB_STOPPED = PACKAGE_NAME + ".action.JOB_STOPPED";
-    public static final String JOB_PARAMS_EXTRA_KEY = PACKAGE_NAME + ".extra.JOB_PARAMETERS";
-
-    @Override
-    public boolean onStartJob(JobParameters params) {
-        Log.i(TAG, "Test job executing: " + params.getJobId());
-        Intent reportJobStartIntent = new Intent(ACTION_JOB_STARTED);
-        reportJobStartIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params)
-                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        sendBroadcast(reportJobStartIntent);
-        return true;
-    }
-
-    @Override
-    public boolean onStopJob(JobParameters params) {
-        Log.i(TAG, "Test job stopped executing: " + params.getJobId());
-        Intent reportJobStopIntent = new Intent(ACTION_JOB_STOPPED);
-        reportJobStopIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params)
-                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        sendBroadcast(reportJobStopIntent);
-        // Deadline constraint is dropped on reschedule, so it's more reliable to use a new job.
-        return false;
-    }
-}
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/Android.bp b/services/tests/servicestests/test-apps/PackageParserApp/Android.bp
index 3e78f9a..131b380 100644
--- a/services/tests/servicestests/test-apps/PackageParserApp/Android.bp
+++ b/services/tests/servicestests/test-apps/PackageParserApp/Android.bp
@@ -102,3 +102,17 @@
     resource_dirs: ["res"],
     manifest: "AndroidManifestApp6.xml",
 }
+
+android_test_helper_app {
+    name: "PackageParserTestApp7",
+    sdk_version: "current",
+    srcs: ["**/*.java"],
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    resource_dirs: ["res"],
+    manifest: "AndroidManifestApp7.xml",
+}
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp7.xml b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp7.xml
new file mode 100644
index 0000000..cb87a48
--- /dev/null
+++ b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp7.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.servicestests.apps.packageparserapp" >
+
+    <application>
+        <activity android:name=".TestActivity"
+                  android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="http"
+                    android:host="www.example.com" />
+                <uri-relative-filter-group android:allow="false">
+                    <data android:pathPrefix="/gizmos" />
+                    <data android:queryPattern=".*query=string.*" />
+                    <data android:fragment="fragment" />
+                </uri-relative-filter-group>
+                <uri-relative-filter-group>
+                    <data android:query="query=string" />
+                    <data android:fragmentSuffix="fragment" />
+                </uri-relative-filter-group>
+                <uri-relative-filter-group>
+                    <data android:path="/gizmos" />
+                    <data android:query=".*query=string.*" />
+                    <data android:fragment="fragment" />
+                </uri-relative-filter-group>
+                <uri-relative-filter-group>
+                    <data android:pathPrefix="/gizmos" />
+                    <data android:queryPrefix=".*query=string.*" />
+                    <data android:fragmentPrefix="fragment" />
+                </uri-relative-filter-group>
+                <uri-relative-filter-group>
+                    <data android:pathPattern="/gizmos" />
+                    <data android:queryPattern=".*query=string.*" />
+                    <data android:fragmentPattern="fragment" />
+                </uri-relative-filter-group>
+                <uri-relative-filter-group>
+                    <data android:pathAdvancedPattern="/gizmos" />
+                    <data android:queryAdvancedPattern=".*query=string.*" />
+                    <data android:fragmentAdvancedPattern="fragment" />
+                </uri-relative-filter-group>
+                <uri-relative-filter-group>
+                    <data android:pathSuffix="/gizmos" />
+                    <data android:querySuffix=".*query=string.*" />
+                    <data android:fragmentSuffix="fragment" />
+                </uri-relative-filter-group>
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 4e1c72a..2f29d10 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -47,6 +47,7 @@
         "flag-junit",
         "notification_flags_lib",
         "platform-test-rules",
+        "SettingsLib",
     ],
 
     libs: [
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index db46532..839cf7c 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -17,6 +17,9 @@
 package com.android.server;
 
 import static android.Manifest.permission.MODIFY_DAY_NIGHT_MODE;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_DAY;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
 import static android.app.UiModeManager.MODE_NIGHT_AUTO;
 import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
 import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
@@ -32,6 +35,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static junit.framework.Assert.fail;
 import static junit.framework.TestCase.assertFalse;
 import static junit.framework.TestCase.assertTrue;
 
@@ -65,6 +69,7 @@
 import android.Manifest;
 import android.app.Activity;
 import android.app.AlarmManager;
+import android.app.Flags;
 import android.app.IOnProjectionStateChangedListener;
 import android.app.IUiModeManager;
 import android.content.BroadcastReceiver;
@@ -84,6 +89,8 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.test.FakePermissionEnforcer;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.service.dreams.DreamManagerInternal;
 import android.test.mock.MockContentResolver;
@@ -98,6 +105,7 @@
 
 import org.junit.Before;
 import org.junit.Ignore;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -109,6 +117,7 @@
 import java.time.LocalTime;
 import java.time.ZoneId;
 import java.util.List;
+import java.util.Map;
 import java.util.function.Consumer;
 
 @RunWith(AndroidTestingRunner.class)
@@ -159,6 +168,11 @@
     private TwilightListener mTwilightListener;
     private FakePermissionEnforcer mPermissionEnforcer;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
+            SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
+
+
     @Before
     public void setUp() {
         // The AIDL stub will use PermissionEnforcer to check permission from the caller.
@@ -1437,6 +1451,51 @@
         verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
     }
 
+    private void testAttentionModeThemeOverlay(boolean modeNight) throws RemoteException {
+        //setup
+        if (modeNight) {
+            mService.setNightMode(MODE_NIGHT_YES);
+            assertTrue(mUiManagerService.getConfiguration().isNightModeActive());
+        } else {
+            mService.setNightMode(MODE_NIGHT_NO);
+            assertFalse(mUiManagerService.getConfiguration().isNightModeActive());
+        }
+
+        // attention modes with expected night modes
+        Map<Integer, Boolean> modes = Map.of(
+                MODE_ATTENTION_THEME_OVERLAY_OFF, modeNight,
+                MODE_ATTENTION_THEME_OVERLAY_DAY, false,
+                MODE_ATTENTION_THEME_OVERLAY_NIGHT, true
+        );
+
+        // test
+        for (int aMode : modes.keySet()) {
+            try {
+                mService.setAttentionModeThemeOverlay(aMode);
+
+                int appliedAMode = mService.getAttentionModeThemeOverlay();
+                boolean nMode = modes.get(aMode);
+
+                assertEquals(aMode, appliedAMode);
+                assertEquals(isNightModeActivated(), nMode);
+            } catch (RemoteException e) {
+                fail("Error communicating with server: " + e.getMessage());
+            }
+        }
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void testAttentionModeThemeOverlay_nightModeDisabled() throws RemoteException {
+        testAttentionModeThemeOverlay(false);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void testAttentionModeThemeOverlay_nightModeEnabled() throws RemoteException {
+        testAttentionModeThemeOverlay(true);
+    }
+
     private void triggerDockIntent() {
         final Intent dockedIntent =
                 new Intent(Intent.ACTION_DOCK_EVENT)
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
index 30843d2..3797dbb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -16,7 +16,8 @@
 
 package com.android.server.notification;
 
-import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
 import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
 import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
 
@@ -121,28 +122,49 @@
         verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
         verify(mColorDisplayManager).setSaturationLevel(eq(0));
         verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
-        verify(mUiModeManager).setNightModeActivatedForCustomMode(
-                eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+        verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
     }
 
     @Test
-    public void apply_removesPreviouslyAppliedEffects() {
+    public void apply_removesEffects() {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
 
         ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder()
                 .setShouldSuppressAmbientDisplay(true)
                 .setShouldDimWallpaper(true)
+                .setShouldDisplayGrayscale(true)
+                .setShouldUseNightMode(true)
                 .build();
         mApplier.apply(previousEffects, UPDATE_ORIGIN_USER);
         verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
+        verify(mColorDisplayManager).setSaturationLevel(eq(0));
         verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
+        verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
 
         ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build();
         mApplier.apply(noEffects, UPDATE_ORIGIN_USER);
 
         verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false));
+        verify(mColorDisplayManager).setSaturationLevel(eq(100));
         verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f));
-        verifyZeroInteractions(mColorDisplayManager, mUiModeManager);
+        verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_OFF));
+    }
+
+    @Test
+    public void apply_removesOnlyPreviouslyAppliedEffects() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder()
+                .setShouldSuppressAmbientDisplay(true)
+                .build();
+        mApplier.apply(previousEffects, UPDATE_ORIGIN_USER);
+        verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
+
+        ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build();
+        mApplier.apply(noEffects, UPDATE_ORIGIN_USER);
+
+        verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false));
+        verifyZeroInteractions(mColorDisplayManager, mWallpaperManager, mUiModeManager);
     }
 
     @Test
@@ -150,6 +172,7 @@
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
         mContext.addMockSystemService(ColorDisplayManager.class, null);
         mContext.addMockSystemService(WallpaperManager.class, null);
+        mApplier = new DefaultDeviceEffectsApplier(mContext);
 
         ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
                 .setShouldSuppressAmbientDisplay(true)
@@ -177,7 +200,7 @@
         verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
 
         verify(mPowerManager, never()).suppressAmbientDisplay(anyString(), anyBoolean());
-        verify(mUiModeManager, never()).setNightModeActivatedForCustomMode(anyInt(), anyBoolean());
+        verify(mUiModeManager, never()).setAttentionModeThemeOverlay(anyInt());
     }
 
     @Test
@@ -223,8 +246,7 @@
         screenOffReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
 
         // So the effect is applied, and we stopped listening for this event.
-        verify(mUiModeManager).setNightModeActivatedForCustomMode(
-                eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+        verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
         verify(mContext).unregisterReceiver(eq(screenOffReceiver));
     }
 
@@ -239,8 +261,7 @@
                 origin.value());
 
         // Effect was applied, and no broadcast receiver was registered.
-        verify(mUiModeManager).setNightModeActivatedForCustomMode(
-                eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+        verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
         verify(mContext, never()).registerReceiver(any(), any(), anyInt());
     }
 
@@ -256,8 +277,7 @@
                 origin.value());
 
         // Effect was applied, and no broadcast receiver was registered.
-        verify(mUiModeManager).setNightModeActivatedForCustomMode(
-                eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+        verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
         verify(mContext, never()).registerReceiver(any(), any(), anyInt());
     }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index bfd2df2d..e75afcc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -27,12 +27,13 @@
 import static android.media.AudioAttributes.USAGE_NOTIFICATION;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
 
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.any;
@@ -43,6 +44,7 @@
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -59,7 +61,10 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.graphics.Color;
@@ -80,6 +85,7 @@
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IAccessibilityManager;
@@ -100,6 +106,7 @@
 import java.util.List;
 import java.util.Objects;
 
+import java.util.Set;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -132,6 +139,8 @@
     KeyguardManager mKeyguardManager;
     @Mock
     private UserManager mUserManager;
+    @Mock
+    private PackageManager mPackageManager;
     NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
     private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
         1 << 30);
@@ -171,11 +180,14 @@
     private static final int CUSTOM_LIGHT_OFF = 10000;
     private static final int MAX_VIBRATION_DELAY = 1000;
     private static final float DEFAULT_VOLUME = 1.0f;
+    private BroadcastReceiver mAvalancheBroadcastReceiver;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         getContext().addMockSystemService(Vibrator.class, mVibrator);
+        getContext().addMockSystemService(PackageManager.class, mPackageManager);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(false);
 
         when(mAudioManager.isAudioFocusExclusive()).thenReturn(false);
         when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer);
@@ -214,8 +226,9 @@
 
     private void initAttentionHelper(TestableFlagResolver flagResolver) {
         mAttentionHelper = new NotificationAttentionHelper(getContext(), mock(LightsManager.class),
-            mAccessibilityManager, getContext().getPackageManager(), mUserManager, mUsageStats,
-            mService.mNotificationManagerPrivate, mock(ZenModeHelper.class), flagResolver);
+                mAccessibilityManager, mPackageManager, mUserManager, mUsageStats,
+                mService.mNotificationManagerPrivate, mock(ZenModeHelper.class), flagResolver);
+        mAttentionHelper.onSystemReady();
         mAttentionHelper.setVibratorHelper(spy(new VibratorHelper(getContext())));
         mAttentionHelper.setAudioManager(mAudioManager);
         mAttentionHelper.setSystemReady(true);
@@ -226,6 +239,29 @@
         mAttentionHelper.setScreenOn(false);
         mAttentionHelper.setInCallStateOffHook(false);
         mAttentionHelper.mNotificationPulseEnabled = true;
+
+        if (Flags.crossAppPoliteNotifications()) {
+            // Capture BroadcastReceiver for avalanche triggers
+            ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+                    ArgumentCaptor.forClass(BroadcastReceiver.class);
+            ArgumentCaptor<IntentFilter> intentFilterCaptor =
+                    ArgumentCaptor.forClass(IntentFilter.class);
+            verify(getContext(), atLeastOnce()).registerReceiverAsUser(
+                    broadcastReceiverCaptor.capture(),
+                    any(), intentFilterCaptor.capture(), any(), any());
+            List<BroadcastReceiver> broadcastReceivers = broadcastReceiverCaptor.getAllValues();
+            List<IntentFilter> intentFilters = intentFilterCaptor.getAllValues();
+
+            assertThat(broadcastReceivers.size()).isAtLeast(1);
+            assertThat(intentFilters.size()).isAtLeast(1);
+            for (int i = 0; i < intentFilters.size(); i++) {
+                final IntentFilter filter = intentFilters.get(i);
+                if (filter.hasAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
+                    mAvalancheBroadcastReceiver = broadcastReceivers.get(i);
+                }
+            }
+            assertThat(mAvalancheBroadcastReceiver).isNotNull();
+        }
     }
 
     //
@@ -2040,7 +2076,7 @@
     }
 
     @Test
-    public void testBeepVolume_politeNotif_GlobalStrategy() throws Exception {
+    public void testBeepVolume_politeNotif_AvalancheStrategy() throws Exception {
         mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
         mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
         TestableFlagResolver flagResolver = new TestableFlagResolver();
@@ -2048,6 +2084,11 @@
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
         initAttentionHelper(flagResolver);
 
+        // Trigger avalanche trigger intent
+        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        intent.putExtra("state", false);
+        mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+
         NotificationRecord r = getBeepyNotification();
 
         // set up internal state
@@ -2078,7 +2119,8 @@
     }
 
     @Test
-    public void testBeepVolume_politeNotif_GlobalStrategy_ChannelHasUserSound() throws Exception {
+    public void testBeepVolume_politeNotif_AvalancheStrategy_ChannelHasUserSound()
+            throws Exception {
         mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
         mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
         TestableFlagResolver flagResolver = new TestableFlagResolver();
@@ -2086,6 +2128,11 @@
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
         initAttentionHelper(flagResolver);
 
+        // Trigger avalanche trigger intent
+        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        intent.putExtra("state", false);
+        mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+
         NotificationRecord r = getBeepyNotification();
 
         // set up internal state
@@ -2364,6 +2411,82 @@
         assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
+    @Test
+    public void testAvalancheStrategyTriggers() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        final int avalancheTimeoutMs = 100;
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_AVALANCHE_TIMEOUT, avalancheTimeoutMs);
+        initAttentionHelper(flagResolver);
+
+        // Trigger avalanche trigger intents
+        for (String intentAction
+                : NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_INTENTS) {
+            // Set the action and extras to trigger the avalanche strategy
+            Intent intent = new Intent(intentAction);
+            Pair<String, Boolean> extras =
+                    NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS
+                        .get(intentAction);
+            if (extras != null) {
+                intent.putExtra(extras.first, extras.second);
+            }
+            mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+            assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isTrue();
+
+            // Wait for avalanche timeout
+            Thread.sleep(avalancheTimeoutMs + 1);
+
+            // Check that avalanche strategy is inactive
+            assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isFalse();
+        }
+    }
+
+    @Test
+    public void testAvalancheStrategyTriggers_disabledExtras() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        initAttentionHelper(flagResolver);
+
+        for (String intentAction
+                : NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_INTENTS) {
+            Intent intent = new Intent(intentAction);
+            Pair<String, Boolean> extras =
+                    NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS
+                        .get(intentAction);
+            // Test only for intents with extras
+            if (extras != null) {
+                // Set the action extras to NOT trigger the avalanche strategy
+                intent.putExtra(extras.first, !extras.second);
+                mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+                // Check that avalanche strategy is inactive
+                assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isFalse();
+            }
+        }
+    }
+
+    @Test
+    public void testAvalancheStrategyTriggers_nonAvalancheIntents() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        initAttentionHelper(flagResolver);
+
+        // Broadcast intents that are not avalanche triggers
+        final Set<String> notAvalancheTriggerIntents = Set.of(
+                Intent.ACTION_USER_ADDED,
+                Intent.ACTION_SCREEN_ON,
+                Intent.ACTION_POWER_CONNECTED
+        );
+        for (String intentAction : notAvalancheTriggerIntents) {
+            Intent intent = new Intent(intentAction);
+            mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+            // Check that avalanche strategy is inactive
+            assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isFalse();
+        }
+    }
+
     static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
         private final int mRepeatIndex;
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
index c10c3c2..9b25f58 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
@@ -183,7 +183,6 @@
         assertThat(mHistoryManager.doesHistoryExistForUser(mProfileId)).isFalse();
         verify(mDb, times(2)).disableHistory();
     }
-
     @Test
     public void testAddProfile_historyEnabledInPrimary() {
         // create a history
@@ -610,4 +609,14 @@
 
         assertThat(mHistoryManager.isHistoryEnabled(USER_SYSTEM)).isFalse();
     }
+    @Test
+    public void testDelayedPackageRemoval_userLocked() {
+        String pkg = "pkg";
+        mHistoryManager.onPackageRemoved(USER_SYSTEM, pkg);
+        mHistoryManager.onUserUnlocked(USER_SYSTEM);
+        mHistoryManager.onUserStopped(USER_SYSTEM);
+        mHistoryManager.onPackageRemoved(USER_SYSTEM, pkg);
+
+        // no exception, yay
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index 53ca704..bf850cf 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -44,8 +44,10 @@
 import android.content.pm.ShortcutInfo;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Icon;
+import android.os.BadParcelableException;
 import android.os.Binder;
 import android.os.Build;
+import android.os.DeadObjectException;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -99,6 +101,20 @@
     }
 
     @Test
+    public void testGetActiveNotifications_handlesBinderErrors() throws RemoteException {
+        TestListenerService service = new TestListenerService();
+        INotificationManager noMan = service.getNoMan();
+        when(noMan.getActiveNotificationsFromListener(any(), any(), anyInt()))
+                .thenThrow(new BadParcelableException("oops", new DeadObjectException("")));
+
+        assertNotNull(service.getActiveNotifications());
+        assertNotNull(service.getActiveNotifications(NotificationListenerService.TRIM_FULL));
+        assertNotNull(service.getActiveNotifications(new String[0]));
+        assertNull(service.getActiveNotifications(
+                new String[0], NotificationListenerService.TRIM_LIGHT));
+    }
+
+    @Test
     public void testGetActiveNotifications_preP_mapsExtraPeople() throws RemoteException {
         TestListenerService service = new TestListenerService();
         service.attachBaseContext(mContext);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index c1f35cc..9c2cba8 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -224,6 +224,7 @@
 import android.os.UserManager;
 import android.os.WorkSource;
 import android.permission.PermissionManager;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.platform.test.rule.DeniedDevices;
@@ -13792,8 +13793,7 @@
         NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
         mBinderService.setNotificationPolicy("package", policy, false);
 
-        verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy),
-                eq(ZenModeConfig.UPDATE_ORIGIN_APP));
+        verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy));
     }
 
     @Test
@@ -13859,7 +13859,7 @@
             verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
         } else {
             verify(zenModeHelper).applyGlobalPolicyAsImplicitZenRule(anyString(), anyInt(),
-                    eq(policy), anyInt());
+                    eq(policy));
         }
     }
 
@@ -13979,6 +13979,58 @@
 
     @Test
     @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    public void requestInterruptionFilterFromListener_fromApp_doesNotSetGlobalZen()
+            throws Exception {
+        mService.setCallerIsNormalPackage();
+        mService.mZenModeHelper = mock(ZenModeHelper.class);
+        ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+        when(mListeners.checkServiceTokenLocked(any())).thenReturn(info);
+        info.component = new ComponentName("pkg", "cls");
+
+        mBinderService.requestInterruptionFilterFromListener(mock(INotificationListener.class),
+                INTERRUPTION_FILTER_PRIORITY);
+
+        verify(mService.mZenModeHelper).applyGlobalZenModeAsImplicitZenRule(eq("pkg"), eq(mUid),
+                eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS));
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    public void requestInterruptionFilterFromListener_fromSystem_setsGlobalZen()
+            throws Exception {
+        mService.isSystemUid = true;
+        mService.mZenModeHelper = mock(ZenModeHelper.class);
+        ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+        when(mListeners.checkServiceTokenLocked(any())).thenReturn(info);
+        info.component = new ComponentName("pkg", "cls");
+
+        mBinderService.requestInterruptionFilterFromListener(mock(INotificationListener.class),
+                INTERRUPTION_FILTER_PRIORITY);
+
+        verify(mService.mZenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS),
+                eq(null), eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(),
+                eq("pkg"), eq(mUid));
+    }
+
+    @Test
+    @DisableFlags(android.app.Flags.FLAG_MODES_API)
+    public void requestInterruptionFilterFromListener_flagOff_callsRequestFromListener()
+            throws Exception {
+        mService.setCallerIsNormalPackage();
+        mService.mZenModeHelper = mock(ZenModeHelper.class);
+        ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+        when(mListeners.checkServiceTokenLocked(any())).thenReturn(info);
+        info.component = new ComponentName("pkg", "cls");
+
+        mBinderService.requestInterruptionFilterFromListener(mock(INotificationListener.class),
+                INTERRUPTION_FILTER_PRIORITY);
+
+        verify(mService.mZenModeHelper).requestFromListener(eq(info.component),
+                eq(INTERRUPTION_FILTER_PRIORITY), eq(mUid), /* fromSystemOrSystemUi= */ eq(false));
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
     @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
     public void updateAutomaticZenRule_implicitRuleWithoutCPS_disallowedFromApp() throws Exception {
         setUpRealZenTest();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
index ea948ca..863cda4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -118,12 +118,19 @@
     // Constructors that should be used to create instances of specific classes. Overrides scoring.
     private static final ImmutableMap<Class<?>, Constructor<?>> PREFERRED_CONSTRUCTORS;
 
+    // Setter methods that receive String parameters, but where those Strings represent Uris
+    // (and are visited/validated).
+    private static final ImmutableSet<Method> SETTERS_WITH_STRING_AS_URI;
+
     static {
         try {
             PREFERRED_CONSTRUCTORS = ImmutableMap.of(
                     Notification.Builder.class,
                     Notification.Builder.class.getConstructor(Context.class, String.class));
 
+            SETTERS_WITH_STRING_AS_URI = ImmutableSet.of(
+                    Person.Builder.class.getMethod("setUri", String.class));
+
             EXCLUDED_SETTERS_OVERLOADS = ImmutableMultimap.<Class<?>, Method>builder()
                     .put(RemoteViews.class,
                             // b/245950570: Tries to connect to service and will crash.
@@ -257,7 +264,7 @@
             @Nullable Class<?> styleClass, @Nullable Class<?> extenderClass,
             @Nullable Class<?> actionExtenderClass, boolean includeRemoteViews) {
         SpecialParameterGenerator specialGenerator = new SpecialParameterGenerator(context);
-        Set<Class<?>> excludedClasses = includeRemoteViews
+        ImmutableSet<Class<?>> excludedClasses = includeRemoteViews
                 ? ImmutableSet.of()
                 : ImmutableSet.of(RemoteViews.class);
         Location location = Location.root(Notification.Builder.class);
@@ -294,7 +301,7 @@
     }
 
     private static Object generateObject(Class<?> clazz, Location where,
-            Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
+            ImmutableSet<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
         if (excludingClasses.contains(clazz)) {
             throw new IllegalArgumentException(
                     String.format("Asked to generate a %s but it's part of the excluded set (%s)",
@@ -369,7 +376,7 @@
     }
 
     private static Object constructEmpty(Class<?> clazz, Location where,
-            Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
+            ImmutableSet<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
         Constructor<?> bestConstructor;
         if (PREFERRED_CONSTRUCTORS.containsKey(clazz)) {
             // Use the preferred constructor.
@@ -431,7 +438,7 @@
     }
 
     private static void invokeAllSetters(Object instance, Location where, boolean allOverloads,
-            boolean includingVoidMethods, Set<Class<?>> excludingParameterTypes,
+            boolean includingVoidMethods, ImmutableSet<Class<?>> excludingParameterTypes,
             SpecialParameterGenerator specialGenerator) {
         for (Method setter : ReflectionUtils.getAllSetters(instance.getClass(), where,
                 allOverloads, includingVoidMethods, excludingParameterTypes)) {
@@ -462,24 +469,34 @@
     }
 
     private static Object[] generateParameters(Executable executable, Location where,
-            Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
+            ImmutableSet<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
         Log.i(TAG, "About to generate parameters for " + ReflectionUtils.methodToString(executable)
                 + " in " + where);
         Type[] parameterTypes = executable.getGenericParameterTypes();
         Object[] parameterValues = new Object[parameterTypes.length];
         for (int i = 0; i < parameterTypes.length; i++) {
-            parameterValues[i] = generateParameter(
-                    parameterTypes[i],
+            boolean generateUriAsString = false;
+            Type parameterType = parameterTypes[i];
+            if (SETTERS_WITH_STRING_AS_URI.contains(executable)
+                    && parameterType.equals(String.class)) {
+                generateUriAsString = true;
+            }
+            Object value = generateParameter(
+                    generateUriAsString ? Uri.class : parameterType,
                     where.plus(executable,
                             String.format("[%d,%s]", i, parameterTypes[i].getTypeName())),
                     excludingClasses,
                     specialGenerator);
+            if (generateUriAsString) {
+                value = ((Uri) value).toString();
+            }
+            parameterValues[i] = value;
         }
         return parameterValues;
     }
 
     private static Object generateParameter(Type parameterType, Location where,
-            Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
+            ImmutableSet<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
         if (parameterType instanceof Class<?> parameterClass) {
             return generateObject(
                     parameterClass,
@@ -487,7 +504,8 @@
                     excludingClasses,
                     specialGenerator);
         } else if (parameterType instanceof ParameterizedType parameterizedType) {
-            if (parameterizedType.getRawType().equals(List.class)
+            if ((parameterizedType.getRawType().equals(List.class)
+                    || parameterizedType.getRawType().equals(ArrayList.class))
                     && parameterizedType.getActualTypeArguments()[0] instanceof Class<?>) {
                 ArrayList listValue = new ArrayList();
                 for (int i = 0; i < NUM_ELEMENTS_IN_ARRAY; i++) {
@@ -503,12 +521,14 @@
     }
 
     private static class ReflectionUtils {
-        static Set<Class<?>> getConcreteSubclasses(Class<?> clazz, Class<?> containerClass) {
-            return Arrays.stream(containerClass.getDeclaredClasses())
-                    .filter(
-                            innerClass -> clazz.isAssignableFrom(innerClass)
-                                    && !Modifier.isAbstract(innerClass.getModifiers()))
-                    .collect(Collectors.toSet());
+        static ImmutableSet<Class<?>> getConcreteSubclasses(Class<?> clazz,
+                Class<?> containerClass) {
+            return ImmutableSet.copyOf(
+                    Arrays.stream(containerClass.getDeclaredClasses())
+                            .filter(
+                                    innerClass -> clazz.isAssignableFrom(innerClass)
+                                            && !Modifier.isAbstract(innerClass.getModifiers()))
+                            .collect(Collectors.toSet()));
         }
 
         static String methodToString(Executable executable) {
@@ -611,9 +631,16 @@
     }
 
     private static class SpecialParameterGenerator {
+
+        private static final ImmutableSet<Class<?>> INTERESTING_CLASSES_WITH_SPECIAL_GENERATION =
+                ImmutableSet.of(Uri.class, Icon.class, Intent.class, PendingIntent.class,
+                        RemoteViews.class);
+
         private static final ImmutableSet<Class<?>> INTERESTING_CLASSES =
-                ImmutableSet.of(Person.class, Uri.class, Icon.class, Intent.class,
-                        PendingIntent.class, RemoteViews.class);
+                new ImmutableSet.Builder<Class<?>>()
+                        .addAll(INTERESTING_CLASSES_WITH_SPECIAL_GENERATION)
+                        .add(Person.class) // Constructed via reflection, but high-score.
+                        .build();
         private static final ImmutableSet<Class<?>> MOCKED_CLASSES = ImmutableSet.of();
 
         private static final ImmutableMap<Class<?>, Object> PRIMITIVE_VALUES =
@@ -637,7 +664,7 @@
         }
 
         static boolean canGenerate(Class<?> clazz) {
-            return INTERESTING_CLASSES.contains(clazz)
+            return INTERESTING_CLASSES_WITH_SPECIAL_GENERATION.contains(clazz)
                     || MOCKED_CLASSES.contains(clazz)
                     || clazz.equals(Context.class)
                     || clazz.equals(Bundle.class)
@@ -672,17 +699,6 @@
                 return Icon.createWithContentUri(iconUri);
             }
 
-            if (clazz == Person.class) {
-                // TODO(b/310189261): Person.setUri takes a string instead of a URI. We should
-                //  find a way to use the SpecialParameterGenerator instead of this custom one.
-                Uri personUri = generateUri(
-                        where.plus(Person.Builder.class).plus("setUri", String.class));
-                Uri iconUri = generateUri(where.plus(Person.Builder.class).plus("setIcon",
-                        Icon.class).plus(Icon.class).plus("createWithContentUri", Uri.class));
-                return new Person.Builder().setUri(personUri.toString()).setIcon(
-                        Icon.createWithContentUri(iconUri)).setName("John Doe").build();
-            }
-
             if (clazz == Intent.class) {
                 return new Intent("action", generateUri(where.plus(Intent.class)));
             }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
index 08af09c..99d5a6d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
@@ -143,12 +143,13 @@
                 Policy.policyState(false, true), 0);
 
         ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
-        assertThat(zenPolicy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+        assertThat(zenPolicy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_ALLOW);
 
         Policy notAllowed = new Policy(0, 0, 0, 0,
                 Policy.policyState(false, false), 0);
         ZenPolicy zenPolicyNotAllowed = notificationPolicyToZenPolicy(notAllowed);
-        assertThat(zenPolicyNotAllowed.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_NONE);
+        assertThat(zenPolicyNotAllowed.getPriorityChannelsAllowed()).isEqualTo(
+                ZenPolicy.STATE_DISALLOW);
     }
 
     @Test
@@ -158,12 +159,12 @@
                 Policy.policyState(false, true), 0);
 
         ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
-        assertThat(zenPolicy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_UNSET);
+        assertThat(zenPolicy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_UNSET);
 
         Policy notAllowed = new Policy(0, 0, 0, 0,
                 Policy.policyState(false, false), 0);
         ZenPolicy zenPolicyNotAllowed = notificationPolicyToZenPolicy(notAllowed);
-        assertThat(zenPolicyNotAllowed.getAllowedChannels())
-                .isEqualTo(ZenPolicy.CHANNEL_TYPE_UNSET);
+        assertThat(zenPolicyNotAllowed.getPriorityChannelsAllowed()).isEqualTo(
+                ZenPolicy.STATE_UNSET);
     }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
index 3d8ec2e..f604f1e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
@@ -52,7 +52,6 @@
                 .setShouldMaximizeDoze(true)
                 .setShouldUseNightMode(false)
                 .setShouldSuppressAmbientDisplay(false).setShouldSuppressAmbientDisplay(true)
-                .setUserModifiedFields(8)
                 .build();
 
         assertThat(deviceEffects.shouldDimWallpaper()).isTrue();
@@ -65,7 +64,6 @@
         assertThat(deviceEffects.shouldMinimizeRadioUsage()).isFalse();
         assertThat(deviceEffects.shouldUseNightMode()).isFalse();
         assertThat(deviceEffects.shouldSuppressAmbientDisplay()).isTrue();
-        assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(8);
     }
 
     @Test
@@ -97,7 +95,6 @@
                 .setShouldMinimizeRadioUsage(true)
                 .setShouldUseNightMode(true)
                 .setShouldSuppressAmbientDisplay(true)
-                .setUserModifiedFields(6)
                 .build();
 
         Parcel parcel = Parcel.obtain();
@@ -116,7 +113,6 @@
         assertThat(copy.shouldUseNightMode()).isTrue();
         assertThat(copy.shouldSuppressAmbientDisplay()).isTrue();
         assertThat(copy.shouldDisplayGrayscale()).isFalse();
-        assertThat(copy.getUserModifiedFields()).isEqualTo(6);
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index 177d645..539bb37 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -60,6 +60,7 @@
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.time.Instant;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -163,7 +164,7 @@
                 .allowConversations(CONVERSATION_SENDERS_IMPORTANT)
                 .showLights(false)
                 .showInAmbientDisplay(false)
-                .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE)
+                .allowPriorityChannels(false)
                 .build();
 
         Policy originalPolicy = config.toNotificationPolicy();
@@ -254,7 +255,7 @@
                 .allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS)
                 .allowMessages(ZenPolicy.PEOPLE_TYPE_STARRED)
                 .allowConversations(ZenPolicy.CONVERSATION_SENDERS_NONE)
-                .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE)
+                .allowPriorityChannels(false)
                 .build();
 
         ZenModeConfig config = getMutedAllConfig();
@@ -283,8 +284,7 @@
                 actual.getPriorityConversationSenders());
         assertEquals(expected.getPriorityCallSenders(), actual.getPriorityCallSenders());
         assertEquals(expected.getPriorityMessageSenders(), actual.getPriorityMessageSenders());
-        assertEquals(expected.getAllowedChannels(), actual.getAllowedChannels());
-        assertEquals(expected.getUserModifiedFields(), actual.getUserModifiedFields());
+        assertEquals(expected.getPriorityChannelsAllowed(), actual.getPriorityChannelsAllowed());
     }
 
     @Test
@@ -341,45 +341,32 @@
         ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
         rule.zenPolicy = null;
         rule.zenDeviceEffects = null;
-
         assertThat(rule.canBeUpdatedByApp()).isTrue();
 
         rule.userModifiedFields = 1;
+
         assertThat(rule.canBeUpdatedByApp()).isFalse();
     }
 
     @Test
     public void testCanBeUpdatedByApp_policyModified() throws Exception {
-        ZenPolicy.Builder policyBuilder = new ZenPolicy.Builder().setUserModifiedFields(0);
-        ZenPolicy policy = policyBuilder.build();
-
         ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
-        rule.zenPolicy = policy;
-
-        assertThat(rule.userModifiedFields).isEqualTo(0);
+        rule.zenPolicy = new ZenPolicy();
         assertThat(rule.canBeUpdatedByApp()).isTrue();
 
-        policy = policyBuilder.setUserModifiedFields(1).build();
-        assertThat(policy.getUserModifiedFields()).isEqualTo(1);
-        rule.zenPolicy = policy;
+        rule.zenPolicyUserModifiedFields = 1;
+
         assertThat(rule.canBeUpdatedByApp()).isFalse();
     }
 
     @Test
     public void testCanBeUpdatedByApp_deviceEffectsModified() throws Exception {
-        ZenDeviceEffects.Builder deviceEffectsBuilder =
-                new ZenDeviceEffects.Builder().setUserModifiedFields(0);
-        ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
-
         ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
-        rule.zenDeviceEffects = deviceEffects;
-
-        assertThat(rule.userModifiedFields).isEqualTo(0);
+        rule.zenDeviceEffects = new ZenDeviceEffects.Builder().build();
         assertThat(rule.canBeUpdatedByApp()).isTrue();
 
-        deviceEffects = deviceEffectsBuilder.setUserModifiedFields(1).build();
-        assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(1);
-        rule.zenDeviceEffects = deviceEffects;
+        rule.zenDeviceEffectsUserModifiedFields = 1;
+
         assertThat(rule.canBeUpdatedByApp()).isFalse();
     }
 
@@ -405,8 +392,11 @@
         rule.allowManualInvocation = ALLOW_MANUAL;
         rule.type = TYPE;
         rule.userModifiedFields = 16;
+        rule.zenPolicyUserModifiedFields = 5;
+        rule.zenDeviceEffectsUserModifiedFields = 2;
         rule.iconResName = ICON_RES_NAME;
         rule.triggerDescription = TRIGGER_DESC;
+        rule.deletionInstant = Instant.ofEpochMilli(1701790147000L);
 
         Parcel parcel = Parcel.obtain();
         rule.writeToParcel(parcel, 0);
@@ -430,11 +420,15 @@
         assertEquals(rule.iconResName, parceled.iconResName);
         assertEquals(rule.type, parceled.type);
         assertEquals(rule.userModifiedFields, parceled.userModifiedFields);
+        assertEquals(rule.zenPolicyUserModifiedFields, parceled.zenPolicyUserModifiedFields);
+        assertEquals(rule.zenDeviceEffectsUserModifiedFields,
+                parceled.zenDeviceEffectsUserModifiedFields);
         assertEquals(rule.triggerDescription, parceled.triggerDescription);
         assertEquals(rule.zenPolicy, parceled.zenPolicy);
+        assertEquals(rule.deletionInstant, parceled.deletionInstant);
+
         assertEquals(rule, parceled);
         assertEquals(rule.hashCode(), parceled.hashCode());
-
     }
 
     @Test
@@ -508,8 +502,11 @@
         rule.allowManualInvocation = ALLOW_MANUAL;
         rule.type = TYPE;
         rule.userModifiedFields = 4;
+        rule.zenPolicyUserModifiedFields = 5;
+        rule.zenDeviceEffectsUserModifiedFields = 2;
         rule.iconResName = ICON_RES_NAME;
         rule.triggerDescription = TRIGGER_DESC;
+        rule.deletionInstant = Instant.ofEpochMilli(1701790147000L);
 
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         writeRuleXml(rule, baos);
@@ -537,8 +534,12 @@
         assertEquals(rule.allowManualInvocation, fromXml.allowManualInvocation);
         assertEquals(rule.type, fromXml.type);
         assertEquals(rule.userModifiedFields, fromXml.userModifiedFields);
+        assertEquals(rule.zenPolicyUserModifiedFields, fromXml.zenPolicyUserModifiedFields);
+        assertEquals(rule.zenDeviceEffectsUserModifiedFields,
+                fromXml.zenDeviceEffectsUserModifiedFields);
         assertEquals(rule.triggerDescription, fromXml.triggerDescription);
         assertEquals(rule.iconResName, fromXml.iconResName);
+        assertEquals(rule.deletionInstant, fromXml.deletionInstant);
     }
 
     @Test
@@ -689,10 +690,9 @@
                 .allowSystem(true)
                 .allowReminders(false)
                 .allowEvents(true)
-                .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE)
+                .allowPriorityChannels(false)
                 .hideAllVisualEffects()
                 .showVisualEffect(ZenPolicy.VISUAL_EFFECT_AMBIENT, true)
-                .setUserModifiedFields(4)
                 .build();
 
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -716,7 +716,7 @@
         assertEquals(policy.getPriorityCategorySystem(), fromXml.getPriorityCategorySystem());
         assertEquals(policy.getPriorityCategoryReminders(), fromXml.getPriorityCategoryReminders());
         assertEquals(policy.getPriorityCategoryEvents(), fromXml.getPriorityCategoryEvents());
-        assertEquals(policy.getAllowedChannels(), fromXml.getAllowedChannels());
+        assertEquals(policy.getPriorityChannelsAllowed(), fromXml.getPriorityChannelsAllowed());
 
         assertEquals(policy.getVisualEffectFullScreenIntent(),
                 fromXml.getVisualEffectFullScreenIntent());
@@ -727,7 +727,6 @@
         assertEquals(policy.getVisualEffectAmbient(), fromXml.getVisualEffectAmbient());
         assertEquals(policy.getVisualEffectNotificationList(),
                 fromXml.getVisualEffectNotificationList());
-        assertEquals(policy.getUserModifiedFields(), fromXml.getUserModifiedFields());
     }
 
     private ZenModeConfig getMutedRingerConfig() {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
index 7e92e42..2e64645 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
@@ -65,18 +65,24 @@
 @TestableLooper.RunWithLooper
 public class ZenModeDiffTest extends UiServiceTestCase {
     // Base set of exempt fields independent of fields that are enabled/disabled via flags.
-    // version is not included in the diff; manual & automatic rules have special handling
+    // version is not included in the diff; manual & automatic rules have special handling;
+    // deleted rules are not included in the diff.
     public static final Set<String> ZEN_MODE_CONFIG_EXEMPT_FIELDS =
-            Set.of("version", "manualRule", "automaticRules");
+            android.app.Flags.modesApi()
+                    ? Set.of("version", "manualRule", "automaticRules", "deletedRules")
+                    : Set.of("version", "manualRule", "automaticRules");
 
     // Differences for flagged fields are only generated if the flag is enabled.
-    // TODO: b/310620812 - Remove this exempt list when flag is inlined.
+    // "Metadata" fields (userModifiedFields & co, deletionInstant) are not compared.
     private static final Set<String> ZEN_RULE_EXEMPT_FIELDS =
             android.app.Flags.modesApi()
-                    ? Set.of()
+                    ? Set.of("userModifiedFields", "zenPolicyUserModifiedFields",
+                            "zenDeviceEffectsUserModifiedFields", "deletionInstant")
                     : Set.of(RuleDiff.FIELD_TYPE, RuleDiff.FIELD_TRIGGER_DESCRIPTION,
                             RuleDiff.FIELD_ICON_RES, RuleDiff.FIELD_ALLOW_MANUAL,
-                            RuleDiff.FIELD_ZEN_DEVICE_EFFECTS, RuleDiff.FIELD_USER_MODIFIED_FIELDS);
+                            RuleDiff.FIELD_ZEN_DEVICE_EFFECTS, "userModifiedFields",
+                            "zenPolicyUserModifiedFields", "zenDeviceEffectsUserModifiedFields",
+                            "deletionInstant");
 
     // allowPriorityChannels is flagged by android.app.modes_api
     public static final Set<String> ZEN_MODE_CONFIG_FLAGGED_FIELDS =
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
index 29208f4..7d6e12c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
@@ -546,13 +546,13 @@
         // Create a policy to allow channels through, which means shouldIntercept is false
         ZenModeConfig config = new ZenModeConfig();
         Policy policy = config.toNotificationPolicy(new ZenPolicy.Builder()
-                .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY)
+                .allowPriorityChannels(true)
                 .build());
         assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
 
         // Now create a policy which does not allow priority channels:
         policy = config.toNotificationPolicy(new ZenPolicy.Builder()
-                .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE)
+                .allowPriorityChannels(false)
                 .build());
         assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 9eed974..87e822c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -43,8 +43,10 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
 import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
 import static android.provider.Settings.Global.ZEN_MODE_OFF;
 import static android.service.notification.Condition.SOURCE_SCHEDULE;
 import static android.service.notification.Condition.SOURCE_USER_ACTION;
@@ -57,6 +59,7 @@
 import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
 import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
 import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
 import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
 
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.LOG_DND_STATE_EVENTS;
@@ -66,6 +69,7 @@
 import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW;
 import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE;
 
+import static com.google.common.collect.Iterables.getOnlyElement;
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.Assert.assertEquals;
@@ -92,6 +96,7 @@
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.withSettings;
 
+import android.Manifest;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.AppGlobals;
@@ -104,6 +109,7 @@
 import android.content.ContentResolver;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
@@ -116,7 +122,9 @@
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Process;
+import android.os.SimpleClock;
 import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
@@ -172,12 +180,16 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 @SmallTest
 @SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
@@ -185,8 +197,9 @@
 @TestableLooper.RunWithLooper
 public class ZenModeHelperTest extends UiServiceTestCase {
 
-    private static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE";
-    private static final String SCHEDULE_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE";
+    private static final String EVENTS_DEFAULT_RULE_ID = ZenModeConfig.EVENTS_DEFAULT_RULE_ID;
+    private static final String SCHEDULE_DEFAULT_RULE_ID =
+            ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID;
     private static final String CUSTOM_PKG_NAME = "not.android";
     private static final String CUSTOM_APP_LABEL = "This is not Android";
     private static final int CUSTOM_PKG_UID = 1;
@@ -227,14 +240,19 @@
     public TestWithLooperRule mLooperRule = new TestWithLooperRule();
 
     ConditionProviders mConditionProviders;
-    @Mock NotificationManager mNotificationManager;
-    @Mock PackageManager mPackageManager;
+    @Mock
+    NotificationManager mNotificationManager;
+    @Mock
+    PackageManager mPackageManager;
     private Resources mResources;
     private TestableLooper mTestableLooper;
+    private final TestClock mTestClock = new TestClock();
     private ZenModeHelper mZenModeHelper;
     private ContentResolver mContentResolver;
-    @Mock DeviceEffectsApplier mDeviceEffectsApplier;
-    @Mock AppOpsManager mAppOps;
+    @Mock
+    DeviceEffectsApplier mDeviceEffectsApplier;
+    @Mock
+    AppOpsManager mAppOps;
     TestableFlagResolver mTestFlagResolver = new TestableFlagResolver();
     ZenModeEventLoggerFake mZenModeEventLogger;
 
@@ -268,7 +286,7 @@
         mConditionProviders.addSystemProvider(new CountdownConditionProvider());
         mConditionProviders.addSystemProvider(new ScheduleConditionProvider());
         mZenModeEventLogger = new ZenModeEventLoggerFake(mPackageManager);
-        mZenModeHelper = new ZenModeHelper(mContext, mTestableLooper.getLooper(),
+        mZenModeHelper = new ZenModeHelper(mContext, mTestableLooper.getLooper(), mTestClock,
                 mConditionProviders, mTestFlagResolver, mZenModeEventLogger);
 
         ResolveInfo ri = new ResolveInfo();
@@ -278,37 +296,41 @@
         when(mPackageManager.getPackageUidAsUser(eq(CUSTOM_PKG_NAME), anyInt()))
                 .thenReturn(CUSTOM_PKG_UID);
         when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(
-                new String[] {pkg});
+                new String[]{pkg});
 
         ApplicationInfo appInfoSpy = spy(new ApplicationInfo());
         appInfoSpy.icon = ICON_RES_ID;
         when(appInfoSpy.loadLabel(any())).thenReturn(CUSTOM_APP_LABEL);
         when(mPackageManager.getApplicationInfo(eq(CUSTOM_PKG_NAME), anyInt()))
                 .thenReturn(appInfoSpy);
+        when(mPackageManager.getApplicationInfo(eq(mContext.getPackageName()), anyInt()))
+                .thenReturn(appInfoSpy);
         mZenModeHelper.mPm = mPackageManager;
 
         mZenModeEventLogger.reset();
     }
 
     private XmlResourceParser getDefaultConfigParser() throws IOException, XmlPullParserException {
-        String xml = "<zen version=\"8\" user=\"0\">\n"
-                + "<allow calls=\"false\" repeatCallers=\"false\" messages=\"true\" "
-                + "reminders=\"false\" events=\"false\" callsFrom=\"1\" messagesFrom=\"2\" "
-                + "visualScreenOff=\"true\" alarms=\"true\" "
-                + "media=\"true\" system=\"false\" conversations=\"true\""
-                + " conversationsFrom=\"2\"/>\n"
-                + "<automatic ruleId=\"" + EVENTS_DEFAULT_RULE_ID
-                + "\" enabled=\"false\" snoozing=\"false\""
-                + " name=\"Event\" zen=\"1\""
-                + " component=\"android/com.android.server.notification.EventConditionProvider\""
-                + " conditionId=\"condition://android/event?userId=-10000&amp;calendar=&amp;"
+        String xml = "<zen version=\"10\">\n"
+                + "<allow alarms=\"true\" media=\"true\" system=\"false\" calls=\"true\" "
+                + "callsFrom=\"2\" messages=\"true\"\n"
+                + "messagesFrom=\"2\" reminders=\"false\" events=\"false\" "
+                + "repeatCallers=\"true\" convos=\"true\"\n"
+                + "convosFrom=\"2\"/>\n"
+                + "<automatic ruleId=" + EVENTS_DEFAULT_RULE_ID
+                + " enabled=\"false\" snoozing=\"false\""
+                + " name=\"Event\" zen=\"1\"\n"
+                + "  component=\"android/com.android.server.notification.EventConditionProvider\"\n"
+                + "  conditionId=\"condition://android/event?userId=-10000&amp;calendar=&amp;"
                 + "reply=1\"/>\n"
-                + "<automatic ruleId=\"" + SCHEDULE_DEFAULT_RULE_ID + "\" enabled=\"false\""
-                + " snoozing=\"false\" name=\"Sleeping\" zen=\"1\""
-                + " component=\"android/com.android.server.notification.ScheduleConditionProvider\""
-                + " conditionId=\"condition://android/schedule?days=1.2.3.4.5.6.7 &amp;start=22.0"
-                + "&amp;end=7.0&amp;exitAtAlarm=true\"/>"
-                + "<disallow visualEffects=\"511\" />"
+                + "<automatic ruleId=" + SCHEDULE_DEFAULT_RULE_ID + " enabled=\"false\""
+                + " snoozing=\"false\" name=\"Sleeping\"\n zen=\"1\""
+                + " component=\"android/com.android.server.notification"
+                + ".ScheduleConditionProvider\"\n"
+                + " conditionId=\"condition://android/schedule?days=1.2.3.4.5.6.7&amp;start=22.0"
+                + "&amp;end=7.0&amp;exitAtAlarm=true\"/>\n"
+                + "<disallow visualEffects=\"157\" />\n"
+                + "<state areChannelsBypassingDnd=\"false\" />\n"
                 + "</zen>";
         TypedXmlPullParser parser = Xml.newFastPullParser();
         parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), null);
@@ -394,7 +416,7 @@
     @Test
     public void testZenOff_NoMuteApplied() {
         mZenModeHelper.mZenMode = ZEN_MODE_OFF;
-        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
         mZenModeHelper.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS
                 | PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0, 0);
         mZenModeHelper.applyRestrictions();
@@ -407,7 +429,7 @@
     @Test
     public void testZenOn_NotificationApplied() {
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
         // The most permissive policy
         mZenModeHelper.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS
                 | PRIORITY_CATEGORY_MEDIA | PRIORITY_CATEGORY_MESSAGES
@@ -428,7 +450,7 @@
     @Test
     public void testZenOn_StarredCallers_CallTypesBlocked() {
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
         // The most permissive policy
         mZenModeHelper.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS
                 | PRIORITY_CATEGORY_MEDIA | PRIORITY_CATEGORY_MESSAGES
@@ -448,7 +470,7 @@
     @Test
     public void testZenOn_AllCallers_CallTypesAllowed() {
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
         // The most permissive policy
         mZenModeHelper.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS
                 | PRIORITY_CATEGORY_MEDIA | PRIORITY_CATEGORY_MESSAGES
@@ -467,7 +489,7 @@
     @Test
     public void testZenOn_AllowAlarmsMedia_NoAlarmMediaMuteApplied() {
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
         mZenModeHelper.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS
                 | PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0, 0);
 
@@ -479,7 +501,7 @@
     @Test
     public void testZenOn_DisallowAlarmsMedia_AlarmMediaMuteApplied() {
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
         mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelper.applyRestrictions();
         verifyApplyRestrictions(true, true, AudioAttributes.USAGE_ALARM);
@@ -492,7 +514,7 @@
     @Test
     public void testTotalSilence() {
         mZenModeHelper.mZenMode = Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
-        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
         mZenModeHelper.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS
                 | PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0, 0);
         mZenModeHelper.applyRestrictions();
@@ -511,7 +533,7 @@
     @Test
     public void testAlarmsOnly_alarmMediaMuteNotApplied() {
         mZenModeHelper.mZenMode = Settings.Global.ZEN_MODE_ALARMS;
-        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
         mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelper.applyRestrictions();
 
@@ -531,7 +553,7 @@
     @Test
     public void testAlarmsOnly_callsMuteApplied() {
         mZenModeHelper.mZenMode = Settings.Global.ZEN_MODE_ALARMS;
-        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
         mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelper.applyRestrictions();
 
@@ -545,7 +567,7 @@
     public void testAlarmsOnly_allZenConfigToggledCannotBypass_alarmMuteNotApplied() {
         // Only audio attributes with SUPPRESIBLE_NEVER can bypass
         mZenModeHelper.mZenMode = Settings.Global.ZEN_MODE_ALARMS;
-        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
         mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelper.applyRestrictions();
 
@@ -557,7 +579,7 @@
         // Only audio attributes with SUPPRESIBLE_NEVER can bypass
         // with special case USAGE_ASSISTANCE_SONIFICATION
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
         mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelper.applyRestrictions();
 
@@ -578,7 +600,7 @@
 
     @Test
     public void testApplyRestrictions_whitelist_priorityOnlyMode() {
-        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelper.applyRestrictions();
@@ -593,7 +615,7 @@
 
     @Test
     public void testApplyRestrictions_whitelist_alarmsOnlyMode() {
-        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
         mZenModeHelper.mZenMode = Global.ZEN_MODE_ALARMS;
         mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelper.applyRestrictions();
@@ -608,7 +630,7 @@
 
     @Test
     public void testApplyRestrictions_whitelist_totalSilenceMode() {
-        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+        mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
         mZenModeHelper.mZenMode = Global.ZEN_MODE_NO_INTERRUPTIONS;
         mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelper.applyRestrictions();
@@ -993,7 +1015,7 @@
         mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
 
         assertEquals("Config mismatch: current vs expected: "
-                + new ZenModeDiff.ConfigDiff(mZenModeHelper.mConfig, expected), expected,
+                        + new ZenModeDiff.ConfigDiff(mZenModeHelper.mConfig, expected), expected,
                 mZenModeHelper.mConfig);
     }
 
@@ -1141,7 +1163,7 @@
                 .allowAlarms(true)
                 .allowRepeatCallers(false)
                 .allowCalls(PEOPLE_TYPE_STARRED)
-                .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE)
+                .allowPriorityChannels(false)
                 .build();
         mZenModeHelper.mConfig.automaticRules.put(rule.id, rule);
         List<StatsEvent> events = new LinkedList<>();
@@ -1164,13 +1186,35 @@
                 assertThat(policy.getAllowCallsFrom().getNumber())
                         .isEqualTo(DNDProtoEnums.PEOPLE_STARRED);
                 assertThat(policy.getAllowChannels().getNumber())
-                        .isEqualTo(DNDProtoEnums.CHANNEL_TYPE_NONE);
+                        .isEqualTo(DNDProtoEnums.CHANNEL_POLICY_NONE);
             }
         }
         assertTrue("couldn't find custom rule", foundCustomEvent);
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void testProtoWithAutoRuleWithModifiedFields() throws Exception {
+        setupZenConfig();
+        mZenModeHelper.mConfig.automaticRules = new ArrayMap<>();
+        ZenRule rule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS, CUSTOM_RULE_ID);
+        rule.userModifiedFields = AutomaticZenRule.FIELD_NAME;
+        rule.zenPolicyUserModifiedFields = ZenPolicy.FIELD_PRIORITY_CATEGORY_MEDIA;
+        rule.zenDeviceEffectsUserModifiedFields = ZenDeviceEffects.FIELD_GRAYSCALE;
+        mZenModeHelper.mConfig.automaticRules.put(rule.id, rule);
+
+        List<StatsEvent> events = new ArrayList<>();
+        mZenModeHelper.pullRules(events);
+
+        assertThat(events).hasSize(2); // Global config + 1 automatic rule
+        DNDModeProto ruleProto = StatsEventTestUtils.convertToAtom(events.get(1)).getDndModeRule();
+        assertThat(ruleProto.getRuleModifiedFields()).isEqualTo(rule.userModifiedFields);
+        assertThat(ruleProto.getPolicyModifiedFields()).isEqualTo(rule.zenPolicyUserModifiedFields);
+        assertThat(ruleProto.getDeviceEffectsModifiedFields()).isEqualTo(
+                rule.zenDeviceEffectsUserModifiedFields);
+    }
+
+    @Test
     public void ruleUidsCached() throws Exception {
         setupZenConfig();
         // one enabled automatic rule
@@ -1197,7 +1241,7 @@
     @Test
     public void ruleUidAutomaticZenRuleRemovedUpdatesCache() throws Exception {
         when(mContext.checkCallingPermission(anyString()))
-                .thenReturn(PackageManager.PERMISSION_GRANTED);
+                .thenReturn(PERMISSION_GRANTED);
 
         setupZenConfig();
         // one enabled automatic rule
@@ -1300,7 +1344,7 @@
         mZenModeHelper.readXml(parser, true, UserHandle.USER_SYSTEM);
 
         assertEquals("Config mismatch: current vs original: "
-                + new ZenModeDiff.ConfigDiff(mZenModeHelper.mConfig, original),
+                        + new ZenModeDiff.ConfigDiff(mZenModeHelper.mConfig, original),
                 original, mZenModeHelper.mConfig);
         assertEquals(original.hashCode(), mZenModeHelper.mConfig.hashCode());
     }
@@ -1742,6 +1786,225 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void testReadXml_onModesApi_noUpgrade() throws Exception {
+        // When reading XML for something that is already on the modes API system, make sure no
+        // rules' policies get changed.
+        setupZenConfig();
+
+        // Shared for rules
+        ArrayMap<String, ZenModeConfig.ZenRule> enabledAutoRules = new ArrayMap<>();
+        final ScheduleInfo weeknights = new ScheduleInfo();
+
+        // Custom rule with a custom policy
+        ZenModeConfig.ZenRule customRule = new ZenModeConfig.ZenRule();
+        customRule.enabled = true;
+        customRule.name = "Custom Rule";
+        customRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        customRule.conditionId = ZenModeConfig.toScheduleConditionId(weeknights);
+        customRule.component = new ComponentName("android", "ScheduleConditionProvider");
+        ZenPolicy policy = new ZenPolicy.Builder()
+                .allowCalls(PEOPLE_TYPE_CONTACTS)
+                .allowAlarms(true)
+                .allowRepeatCallers(false)
+                .build();
+        // Fill in policy fields, since on modes api we do not expect any rules to have unset fields
+        customRule.zenPolicy = mZenModeHelper.getDefaultZenPolicy().overwrittenWith(policy);
+        enabledAutoRules.put("customRule", customRule);
+        mZenModeHelper.mConfig.automaticRules = enabledAutoRules;
+
+        // set version to post-modes-API = 11
+        ByteArrayOutputStream baos = writeXmlAndPurge(11);
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(baos.toByteArray())), null);
+        parser.nextTag();
+        mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+        // basic check: global config maintained
+        setupZenConfigMaintained();
+
+        // Find our automatic rules.
+        ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
+        assertThat(rules).hasSize(1);
+        assertThat(rules).containsKey("customRule");
+        ZenRule rule = rules.get("customRule");
+        assertThat(rule.zenPolicy).isEqualTo(customRule.zenPolicy);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void testReadXml_upgradeToModesApi_makesCustomPolicies() throws Exception {
+        // When reading in an XML file written from a pre-modes-API version, confirm that we create
+        // a custom policy matching the global config for any automatic rule with no specified
+        // policy.
+        setupZenConfig();
+
+        ArrayMap<String, ZenModeConfig.ZenRule> enabledAutoRule = new ArrayMap<>();
+        ZenModeConfig.ZenRule customRule = new ZenModeConfig.ZenRule();
+        final ScheduleInfo weeknights = new ScheduleInfo();
+        customRule.enabled = true;
+        customRule.name = "Custom Rule";
+        customRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        customRule.conditionId = ZenModeConfig.toScheduleConditionId(weeknights);
+        customRule.component = new ComponentName("android", "ScheduleConditionProvider");
+        enabledAutoRule.put("customRule", customRule);  // no custom policy set
+        mZenModeHelper.mConfig.automaticRules = enabledAutoRule;
+
+        // set version to pre-modes-API = 10
+        ByteArrayOutputStream baos = writeXmlAndPurge(10);
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(baos.toByteArray())), null);
+        parser.nextTag();
+        mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+        // basic check: global config maintained
+        setupZenConfigMaintained();
+
+        // Find our automatic rule and check that it has a policy set now
+        ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
+        assertThat(rules).hasSize(1);
+        assertThat(rules).containsKey("customRule");
+        ZenRule rule = rules.get("customRule");
+        assertThat(rule.zenPolicy).isNotNull();
+
+        // Check policy values as set up in setupZenConfig() to confirm they match
+        assertThat(rule.zenPolicy.getPriorityCategoryAlarms()).isEqualTo(STATE_DISALLOW);
+        assertThat(rule.zenPolicy.getPriorityCategoryMedia()).isEqualTo(STATE_DISALLOW);
+        assertThat(rule.zenPolicy.getPriorityCategorySystem()).isEqualTo(STATE_DISALLOW);
+        assertThat(rule.zenPolicy.getPriorityCategoryReminders()).isEqualTo(STATE_ALLOW);
+        assertThat(rule.zenPolicy.getPriorityCategoryCalls()).isEqualTo(STATE_ALLOW);
+        assertThat(rule.zenPolicy.getPriorityCallSenders()).isEqualTo(PEOPLE_TYPE_STARRED);
+        assertThat(rule.zenPolicy.getPriorityCategoryMessages()).isEqualTo(STATE_ALLOW);
+        assertThat(rule.zenPolicy.getPriorityCategoryConversations()).isEqualTo(STATE_ALLOW);
+        assertThat(rule.zenPolicy.getPriorityCategoryEvents()).isEqualTo(STATE_ALLOW);
+        assertThat(rule.zenPolicy.getPriorityCategoryRepeatCallers()).isEqualTo(STATE_ALLOW);
+        assertThat(rule.zenPolicy.getVisualEffectBadge()).isEqualTo(STATE_DISALLOW);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void testReadXml_upgradeToModesApi_fillsInCustomPolicies() throws Exception {
+        // When reading in an XML file written from a pre-modes-API version, confirm that for an
+        // underspecified ZenPolicy, we fill in all of the gaps with things from the global config
+        // in order to maintain consistency of behavior.
+        setupZenConfig();
+
+        ArrayMap<String, ZenModeConfig.ZenRule> enabledAutoRule = new ArrayMap<>();
+        ZenModeConfig.ZenRule customRule = new ZenModeConfig.ZenRule();
+        final ScheduleInfo weeknights = new ScheduleInfo();
+        customRule.enabled = true;
+        customRule.name = "Custom Rule";
+        customRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        customRule.conditionId = ZenModeConfig.toScheduleConditionId(weeknights);
+        customRule.component = new ComponentName("android", "ScheduleConditionProvider");
+        customRule.zenPolicy = new ZenPolicy.Builder()
+                .allowAlarms(true)
+                .allowMedia(true)
+                .allowRepeatCallers(false)
+                .build();
+        enabledAutoRule.put("customRule", customRule);
+        mZenModeHelper.mConfig.automaticRules = enabledAutoRule;
+
+        // set version to pre-modes-API = 10
+        ByteArrayOutputStream baos = writeXmlAndPurge(10);
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(baos.toByteArray())), null);
+        parser.nextTag();
+        mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+        // basic check: global config maintained
+        setupZenConfigMaintained();
+
+        // Find our automatic rule and check that it has a policy set now
+        ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
+        assertThat(rules).hasSize(1);
+        assertThat(rules).containsKey("customRule");
+        ZenRule rule = rules.get("customRule");
+        assertThat(rule.zenPolicy).isNotNull();
+
+        // Check unset policy values match values in setupZenConfig().
+        // Check that set policy values match the values set in the policy.
+        assertThat(rule.zenPolicy.getPriorityCategoryAlarms()).isEqualTo(STATE_ALLOW);
+        assertThat(rule.zenPolicy.getPriorityCategoryMedia()).isEqualTo(STATE_ALLOW);
+        assertThat(rule.zenPolicy.getPriorityCategoryRepeatCallers()).isEqualTo(STATE_DISALLOW);
+
+        // Check that the rest is filled in from the default
+        assertThat(rule.zenPolicy.getPriorityCategorySystem()).isEqualTo(STATE_DISALLOW);
+        assertThat(rule.zenPolicy.getPriorityCategoryReminders()).isEqualTo(STATE_ALLOW);
+        assertThat(rule.zenPolicy.getPriorityCategoryCalls()).isEqualTo(STATE_ALLOW);
+        assertThat(rule.zenPolicy.getPriorityCallSenders()).isEqualTo(PEOPLE_TYPE_STARRED);
+        assertThat(rule.zenPolicy.getPriorityCategoryMessages()).isEqualTo(STATE_ALLOW);
+        assertThat(rule.zenPolicy.getPriorityCategoryConversations()).isEqualTo(STATE_ALLOW);
+        assertThat(rule.zenPolicy.getPriorityCategoryEvents()).isEqualTo(STATE_ALLOW);
+        assertThat(rule.zenPolicy.getVisualEffectBadge()).isEqualTo(STATE_DISALLOW);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void testReadXml_upgradeToModesApi_existingDefaultRulesGetCustomPolicy()
+            throws Exception {
+        setupZenConfig();
+
+        // Default rules, if they exist and have no policies, should get a snapshot of the global
+        // policy, even if they are disabled upon upgrade.
+        ArrayMap<String, ZenModeConfig.ZenRule> automaticRules = new ArrayMap<>();
+        ZenModeConfig.ZenRule defaultScheduleRule = new ZenModeConfig.ZenRule();
+        final ScheduleInfo defaultScheduleRuleInfo = new ScheduleInfo();
+        defaultScheduleRule.enabled = false;
+        defaultScheduleRule.name = "Default Schedule Rule";
+        defaultScheduleRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        defaultScheduleRule.conditionId = ZenModeConfig.toScheduleConditionId(
+                defaultScheduleRuleInfo);
+        defaultScheduleRule.id = ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID;
+        automaticRules.put(ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID, defaultScheduleRule);
+
+        ZenModeConfig.ZenRule defaultEventRule = new ZenModeConfig.ZenRule();
+        final ScheduleInfo defaultEventRuleInfo = new ScheduleInfo();
+        defaultEventRule.enabled = false;
+        defaultEventRule.name = "Default Event Rule";
+        defaultEventRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        defaultEventRule.conditionId = ZenModeConfig.toScheduleConditionId(
+                defaultEventRuleInfo);
+        defaultEventRule.id = ZenModeConfig.EVENTS_DEFAULT_RULE_ID;
+        automaticRules.put(ZenModeConfig.EVENTS_DEFAULT_RULE_ID, defaultEventRule);
+
+        mZenModeHelper.mConfig.automaticRules = automaticRules;
+
+        // set previous version
+        ByteArrayOutputStream baos = writeXmlAndPurge(10);
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(baos.toByteArray())), null);
+        parser.nextTag();
+        mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+        // check default rules
+        ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
+        assertThat(rules.size()).isGreaterThan(0);
+        for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
+            assertThat(rules).containsKey(defaultId);
+            ZenRule rule = rules.get(defaultId);
+            assertThat(rule.zenPolicy).isNotNull();
+
+            // Check policy values as set up in setupZenConfig() to confirm they match
+            assertThat(rule.zenPolicy.getPriorityCategoryAlarms()).isEqualTo(STATE_DISALLOW);
+            assertThat(rule.zenPolicy.getPriorityCategoryMedia()).isEqualTo(STATE_DISALLOW);
+            assertThat(rule.zenPolicy.getPriorityCategorySystem()).isEqualTo(STATE_DISALLOW);
+            assertThat(rule.zenPolicy.getPriorityCategoryReminders()).isEqualTo(STATE_ALLOW);
+            assertThat(rule.zenPolicy.getPriorityCategoryCalls()).isEqualTo(STATE_ALLOW);
+            assertThat(rule.zenPolicy.getPriorityCallSenders()).isEqualTo(PEOPLE_TYPE_STARRED);
+            assertThat(rule.zenPolicy.getPriorityCategoryMessages()).isEqualTo(STATE_ALLOW);
+            assertThat(rule.zenPolicy.getPriorityCategoryConversations()).isEqualTo(STATE_ALLOW);
+            assertThat(rule.zenPolicy.getPriorityCategoryEvents()).isEqualTo(STATE_ALLOW);
+            assertThat(rule.zenPolicy.getPriorityCategoryRepeatCallers()).isEqualTo(STATE_ALLOW);
+            assertThat(rule.zenPolicy.getVisualEffectBadge()).isEqualTo(STATE_DISALLOW);
+        }
+    }
+
+    @Test
     public void testCountdownConditionSubscription() throws Exception {
         ZenModeConfig config = new ZenModeConfig();
         mZenModeHelper.mConfig = config;
@@ -1779,7 +2042,7 @@
     public void testDoNotUpdateModifiedDefaultAutoRule() {
         // mDefaultConfig is set to default config in setup by getDefaultConfigParser
         when(mContext.checkCallingPermission(anyString()))
-                .thenReturn(PackageManager.PERMISSION_GRANTED);
+                .thenReturn(PERMISSION_GRANTED);
 
         // shouldn't update rule that's been modified
         ZenModeConfig.ZenRule updatedDefaultRule = new ZenModeConfig.ZenRule();
@@ -1805,7 +2068,7 @@
     public void testDoNotUpdateEnabledDefaultAutoRule() {
         // mDefaultConfig is set to default config in setup by getDefaultConfigParser
         when(mContext.checkCallingPermission(anyString()))
-                .thenReturn(PackageManager.PERMISSION_GRANTED);
+                .thenReturn(PERMISSION_GRANTED);
 
         // shouldn't update the rule that's enabled
         ZenModeConfig.ZenRule updatedDefaultRule = new ZenModeConfig.ZenRule();
@@ -1832,7 +2095,7 @@
         // mDefaultConfig is set to default config in setup by getDefaultConfigParser
         final String defaultRuleName = "rule name test";
         when(mContext.checkCallingPermission(anyString()))
-                .thenReturn(PackageManager.PERMISSION_GRANTED);
+                .thenReturn(PERMISSION_GRANTED);
 
         // will update rule that is not enabled and modified
         ZenModeConfig.ZenRule customDefaultRule = new ZenModeConfig.ZenRule();
@@ -2000,6 +2263,69 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void testAddAutomaticZenRule_modesApi_fillsInDefaultValues() {
+        // When a new automatic zen rule is added with only some fields filled in, ensure that
+        // all unset fields are filled in with device defaults.
+
+        // Zen rule with null policy: should get entirely the default state
+        AutomaticZenRule zenRule1 = new AutomaticZenRule("name",
+                new ComponentName("android", "ScheduleConditionProvider"),
+                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+        String id1 = mZenModeHelper.addAutomaticZenRule("android", zenRule1,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+
+        // Zen rule with partially-filled policy: should get all of the filled fields set, and the
+        // rest filled with default state
+        AutomaticZenRule zenRule2 = new AutomaticZenRule("name",
+                null,
+                new ComponentName(mContext.getPackageName(), "ScheduleConditionProvider"),
+                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                new ZenPolicy.Builder()
+                        .allowCalls(PEOPLE_TYPE_NONE)
+                        .allowMessages(PEOPLE_TYPE_CONTACTS)
+                        .showFullScreenIntent(true)
+                        .build(),
+                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+        String id2 = mZenModeHelper.addAutomaticZenRule("android", zenRule2,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+
+        // rule 1 should exist
+        assertThat(id1).isNotNull();
+        ZenModeConfig.ZenRule rule1InConfig = mZenModeHelper.mConfig.automaticRules.get(id1);
+        assertThat(rule1InConfig).isNotNull();
+        assertThat(rule1InConfig.zenPolicy).isNotNull();  // we passed in null; it should now not be
+
+        // all of rule 1 should be the device default's policy
+        assertThat(rule1InConfig.zenPolicy).isEqualTo(mZenModeHelper.getDefaultZenPolicy());
+
+        // rule 2 should exist
+        assertThat(id2).isNotNull();
+        ZenModeConfig.ZenRule rule2InConfig = mZenModeHelper.mConfig.automaticRules.get(id2);
+        assertThat(rule2InConfig).isNotNull();
+
+        // rule 2: values set from the policy itself
+        assertThat(rule2InConfig.zenPolicy.getPriorityCallSenders()).isEqualTo(PEOPLE_TYPE_NONE);
+        assertThat(rule2InConfig.zenPolicy.getPriorityMessageSenders())
+                .isEqualTo(PEOPLE_TYPE_CONTACTS);
+        assertThat(rule2InConfig.zenPolicy.getVisualEffectFullScreenIntent())
+                .isEqualTo(ZenPolicy.STATE_ALLOW);
+
+        // the rest of rule 2's settings should be the device defaults
+        assertThat(rule2InConfig.zenPolicy.getPriorityConversationSenders())
+                .isEqualTo(CONVERSATION_SENDERS_IMPORTANT);
+        assertThat(rule2InConfig.zenPolicy.getPriorityCategorySystem())
+                .isEqualTo(ZenPolicy.STATE_DISALLOW);
+        assertThat(rule2InConfig.zenPolicy.getPriorityCategoryAlarms())
+                .isEqualTo(ZenPolicy.STATE_ALLOW);
+        assertThat(rule2InConfig.zenPolicy.getVisualEffectPeek())
+                .isEqualTo(ZenPolicy.STATE_DISALLOW);
+        assertThat(rule2InConfig.zenPolicy.getVisualEffectNotificationList())
+                .isEqualTo(ZenPolicy.STATE_ALLOW);
+    }
+
+    @Test
     public void testSetAutomaticZenRuleState_nullPkg() {
         AutomaticZenRule zenRule = new AutomaticZenRule("name",
                 null,
@@ -2226,12 +2552,7 @@
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
-        // savedRule.getDeviceEffects() is equal to zde, except for the userModifiedFields.
-        // So we clear before comparing.
-        ZenDeviceEffects savedEffects = new ZenDeviceEffects.Builder(savedRule.getDeviceEffects())
-                .setUserModifiedFields(0).build();
-
-        assertThat(savedEffects).isEqualTo(zde);
+        assertThat(savedRule.getDeviceEffects()).isEqualTo(zde);
     }
 
     @Test
@@ -2321,12 +2642,131 @@
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
-        // savedRule.getDeviceEffects() is equal to updateFromUser, except for the
-        // userModifiedFields, so we clear before comparing.
-        ZenDeviceEffects savedEffects = new ZenDeviceEffects.Builder(savedRule.getDeviceEffects())
-                .setUserModifiedFields(0).build();
+        assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromUser);
+    }
 
-        assertThat(savedEffects).isEqualTo(updateFromUser);
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void updateAutomaticZenRule_nullPolicy_doesNothing() {
+        // Test that when updateAutomaticZenRule is called with a null policy, nothing changes
+        // about the existing policy.
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+                new AutomaticZenRule.Builder("Rule", CONDITION_ID)
+                        .setOwner(OWNER)
+                        .setZenPolicy(new ZenPolicy.Builder()
+                                .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // default is stars
+                                .build())
+                        .build(),
+                UPDATE_ORIGIN_APP, "reasons", 0);
+
+        mZenModeHelper.updateAutomaticZenRule(ruleId,
+                new AutomaticZenRule.Builder("Rule", CONDITION_ID)
+                        // no zen policy
+                        .build(),
+                UPDATE_ORIGIN_APP, "reasons", 0);
+
+        AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+        assertThat(savedRule.getZenPolicy().getPriorityCategoryCalls())
+                .isEqualTo(ZenPolicy.STATE_DISALLOW);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void updateAutomaticZenRule_overwritesExistingPolicy() {
+        // Test that when updating an automatic zen rule with an existing policy, the newly set
+        // fields overwrite those from the previous policy, but unset fields in the new policy
+        // keep values from the previous one.
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+                new AutomaticZenRule.Builder("Rule", CONDITION_ID)
+                        .setOwner(OWNER)
+                        .setZenPolicy(new ZenPolicy.Builder()
+                                .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // default is stars
+                                .allowAlarms(false)
+                                .allowReminders(true)
+                                .build())
+                        .build(),
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
+
+        mZenModeHelper.updateAutomaticZenRule(ruleId,
+                new AutomaticZenRule.Builder("Rule", CONDITION_ID)
+                        .setZenPolicy(new ZenPolicy.Builder()
+                                .allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS)
+                                .build())
+                        .build(),
+                UPDATE_ORIGIN_APP, "reasons", 0);
+
+        AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+        assertThat(savedRule.getZenPolicy().getPriorityCategoryCalls())
+                .isEqualTo(ZenPolicy.STATE_ALLOW);  // from update
+        assertThat(savedRule.getZenPolicy().getPriorityCallSenders())
+                .isEqualTo(ZenPolicy.PEOPLE_TYPE_CONTACTS);  // from update
+        assertThat(savedRule.getZenPolicy().getPriorityCategoryAlarms())
+                .isEqualTo(ZenPolicy.STATE_DISALLOW);  // from original
+        assertThat(savedRule.getZenPolicy().getPriorityCategoryReminders())
+                .isEqualTo(ZenPolicy.STATE_ALLOW);  // from original
+    }
+
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void addAutomaticZenRule_withTypeBedtime_replacesDisabledSleeping() {
+        ZenRule sleepingRule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
+        sleepingRule.enabled = false;
+        sleepingRule.userModifiedFields = 0;
+        sleepingRule.name = "ZZZZZZZ...";
+        mZenModeHelper.mConfig.automaticRules.clear();
+        mZenModeHelper.mConfig.automaticRules.put(sleepingRule.id, sleepingRule);
+
+        AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime Mode (TM)", CONDITION_ID)
+                .setType(TYPE_BEDTIME)
+                .build();
+        String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime, UPDATE_ORIGIN_APP,
+                "reason", CUSTOM_PKG_UID);
+
+        assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(bedtimeRuleId);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void addAutomaticZenRule_withTypeBedtime_keepsEnabledSleeping() {
+        ZenRule sleepingRule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
+        sleepingRule.enabled = true;
+        sleepingRule.userModifiedFields = 0;
+        sleepingRule.name = "ZZZZZZZ...";
+        mZenModeHelper.mConfig.automaticRules.clear();
+        mZenModeHelper.mConfig.automaticRules.put(sleepingRule.id, sleepingRule);
+
+        AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime Mode (TM)", CONDITION_ID)
+                .setType(TYPE_BEDTIME)
+                .build();
+        String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime, UPDATE_ORIGIN_APP,
+                "reason", CUSTOM_PKG_UID);
+
+        assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(
+                ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID, bedtimeRuleId);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void addAutomaticZenRule_withTypeBedtime_keepsCustomizedSleeping() {
+        ZenRule sleepingRule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
+        sleepingRule.enabled = false;
+        sleepingRule.userModifiedFields = AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
+        sleepingRule.name = "ZZZZZZZ...";
+        mZenModeHelper.mConfig.automaticRules.clear();
+        mZenModeHelper.mConfig.automaticRules.put(sleepingRule.id, sleepingRule);
+
+        AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime Mode (TM)", CONDITION_ID)
+                .setType(TYPE_BEDTIME)
+                .build();
+        String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime, UPDATE_ORIGIN_APP,
+                "reason", CUSTOM_PKG_UID);
+
+        assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(
+                ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID, bedtimeRuleId);
     }
 
     @Test
@@ -2372,7 +2812,8 @@
         DISABLED(false, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI);
 
         private final boolean mEnabled;
-        @ConfigChangeOrigin private final int mOriginForUserActionInSystemUi;
+        @ConfigChangeOrigin
+        private final int mOriginForUserActionInSystemUi;
 
         ModesApiFlag(boolean enabled, @ConfigChangeOrigin int originForUserActionInSystemUi) {
             this.mEnabled = enabled;
@@ -2418,7 +2859,7 @@
         //   - rules active = 1
         //   - user action = true (system-based turning zen mode on)
         //   - package uid = system (as set above)
-        //   - resulting DNDPolicyProto the same as the values in setupZenConfig()
+        //   - resulting DNDPolicyProto the same as the values in setupZenConfig() (global policy)
         assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId(),
                 mZenModeEventLogger.getEventId(0));
         assertEquals(ZEN_MODE_OFF, mZenModeEventLogger.getPrevZenMode(0));
@@ -2512,7 +2953,7 @@
         //   - 1 rule (newly) active
         //   - automatic (is not a user action)
         //   - package UID is written to be the rule *owner* even though it "comes from system"
-        //   - zen policy is the same as the set-up zen config
+        //   - zen policy is the default as it's unspecified
         assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId(),
                 mZenModeEventLogger.getEventId(0));
         assertEquals(ZEN_MODE_OFF, mZenModeEventLogger.getPrevZenMode(0));
@@ -2521,10 +2962,10 @@
         assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
         assertFalse(mZenModeEventLogger.getIsUserAction(0));
         assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0));
-        checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(0));
+        checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(0));
 
         // When the automatic rule is disabled, this should turn off zen mode and also count as a
-        // user action.
+        // user action. We don't care what the consolidated policy is when DND turns off.
         assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_OFF.getId(),
                 mZenModeEventLogger.getEventId(1));
         assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getPrevZenMode(1));
@@ -2747,28 +3188,28 @@
         // First: turn on rule 1
         mZenModeHelper.setAutomaticZenRuleState(id,
                 new Condition(zenRule.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,  Process.SYSTEM_UID);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // Second: turn on rule 2
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,  Process.SYSTEM_UID);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // Third: turn on rule 3
         mZenModeHelper.setAutomaticZenRuleState(id3,
                 new Condition(zenRule3.getConditionId(), "", STATE_TRUE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,  Process.SYSTEM_UID);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // Fourth: Turn *off* rule 2
         mZenModeHelper.setAutomaticZenRuleState(id2,
                 new Condition(zenRule2.getConditionId(), "", STATE_FALSE),
-                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,  Process.SYSTEM_UID);
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
 
         // This should result in a total of four events
         assertEquals(4, mZenModeEventLogger.numLoggedChanges());
 
         // Event 1: rule 1 turns on. We expect this to turn on DND (zen mode) overall, so that's
-        // what the event should reflect. At this time, the policy is the same as initial setup.
+        // what the event should reflect. At this time, the policy is the default.
         assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId(),
                 mZenModeEventLogger.getEventId(0));
         assertEquals(ZEN_MODE_OFF, mZenModeEventLogger.getPrevZenMode(0));
@@ -2776,7 +3217,7 @@
         assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
         assertFalse(mZenModeEventLogger.getIsUserAction(0));
         assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0));
-        checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(0));
+        checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(0));
 
         // Event 2: rule 2 turns on. This should not change anything about the policy, so the only
         // change is that there are more rules active now.
@@ -2785,7 +3226,7 @@
         assertEquals(2, mZenModeEventLogger.getNumRulesActive(1));
         assertFalse(mZenModeEventLogger.getIsUserAction(1));
         assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(1));
-        checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1));
+        checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(1));
 
         // Event 3: rule 3 turns on. This should trigger a policy change, and be classified as such,
         // but meanwhile also change the number of active rules.
@@ -2838,12 +3279,16 @@
         mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
         setupZenConfig();
 
+        // Explicitly set up all rules with the same policy as the manual rule so there will be
+        // no policy changes in this test case.
+        ZenPolicy manualRulePolicy = mZenModeHelper.mConfig.toZenPolicy();
+
         // Rule 1, owned by a package
         AutomaticZenRule zenRule = new AutomaticZenRule("name",
                 null,
                 new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
-                null,
+                manualRulePolicy,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
                 UPDATE_ORIGIN_APP, "test", Process.SYSTEM_UID);
@@ -2853,7 +3298,7 @@
                 null,
                 new ComponentName("android", "ScheduleConditionProvider"),
                 ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
-                null,
+                manualRulePolicy,
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
                 modesApiFlag.mOriginForUserActionInSystemUi, "test", Process.SYSTEM_UID);
@@ -3026,7 +3471,7 @@
         DNDPolicyProto origDndProto = mZenModeEventLogger.getPolicyProto(0);
         checkDndProtoMatchesSetupZenConfig(origDndProto);
         assertThat(origDndProto.getAllowChannels().getNumber())
-                .isEqualTo(DNDProtoEnums.CHANNEL_TYPE_PRIORITY);
+                .isEqualTo(DNDProtoEnums.CHANNEL_POLICY_PRIORITY);
 
         // Second message where we change the policy:
         //   - DND_POLICY_CHANGED (indicates only the policy changed and nothing else)
@@ -3038,7 +3483,7 @@
                 .isEqualTo(DNDProtoEnums.UNKNOWN_RULE);
         DNDPolicyProto dndProto = mZenModeEventLogger.getPolicyProto(1);
         assertThat(dndProto.getAllowChannels().getNumber())
-                .isEqualTo(DNDProtoEnums.CHANNEL_TYPE_NONE);
+                .isEqualTo(DNDProtoEnums.CHANNEL_POLICY_NONE);
     }
 
     @Test
@@ -3084,7 +3529,8 @@
     }
 
     @Test
-    public void testUpdateConsolidatedPolicy_defaultRulesOnly() {
+    @DisableFlags(Flags.FLAG_MODES_API)
+    public void testUpdateConsolidatedPolicy_preModesApiDefaultRulesOnly_takesGlobalDefault() {
         setupZenConfig();
 
         // When there's one automatic rule active and it doesn't specify a policy, test that the
@@ -3117,12 +3563,39 @@
     }
 
     @Test
-    public void testUpdateConsolidatedPolicy_customPolicyOnly() {
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void testUpdateConsolidatedPolicy_modesApiDefaultRulesOnly_takesDeviceDefault() {
+        setupZenConfig();
+
+        // When there's one automatic rule active and it doesn't specify a policy, test that the
+        // resulting consolidated policy is one that matches the default *device* settings.
+        AutomaticZenRule zenRule = new AutomaticZenRule("name",
+                null,
+                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                null,  // null policy
+                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+
+        // enable the rule
+        mZenModeHelper.setAutomaticZenRuleState(id,
+                new Condition(zenRule.getConditionId(), "", STATE_TRUE),
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+
+        // inspect the consolidated policy, which should match the device default settings.
+        assertThat(ZenAdapters.notificationPolicyToZenPolicy(mZenModeHelper.mConsolidatedPolicy))
+                .isEqualTo(mZenModeHelper.getDefaultZenPolicy());
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MODES_API)
+    public void testUpdateConsolidatedPolicy_preModesApiCustomPolicyOnly_fillInWithGlobal() {
         setupZenConfig();
 
         // when there's only one automatic rule active and it has a custom policy, make sure that's
-        // what the consolidated policy reflects whether or not it's stricter than what the default
-        // would specify.
+        // what the consolidated policy reflects whether or not it's stricter than what the global
+        // config would specify.
         ZenPolicy customPolicy = new ZenPolicy.Builder()
                 .allowAlarms(true)  // more lenient than default
                 .allowMedia(true)  // more lenient than default
@@ -3161,7 +3634,51 @@
     }
 
     @Test
-    public void testUpdateConsolidatedPolicy_defaultAndCustomActive() {
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void testUpdateConsolidatedPolicy_modesApiCustomPolicyOnly_fillInWithDeviceDefault() {
+        setupZenConfig();
+
+        // when there's only one automatic rule active and it has a custom policy, make sure that's
+        // what the consolidated policy reflects whether or not it's stricter than what the default
+        // would specify.
+        ZenPolicy customPolicy = new ZenPolicy.Builder()
+                .allowSystem(true)  // more lenient than default
+                .allowRepeatCallers(false)  // more restrictive than default
+                .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE)  // more restrictive than default
+                .showFullScreenIntent(true)  // more lenient
+                .showBadges(false)  // more restrictive
+                .build();
+
+        AutomaticZenRule zenRule = new AutomaticZenRule("name",
+                null,
+                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                customPolicy,
+                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+
+        // enable the rule; this will update the consolidated policy
+        mZenModeHelper.setAutomaticZenRuleState(id,
+                new Condition(zenRule.getConditionId(), "", STATE_TRUE),
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+
+        // since this is the only active rule, the consolidated policy should match the custom
+        // policy for every field specified, and take default values for unspecified things
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isTrue();  // default
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isTrue();  // default
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isTrue();  // custom
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isFalse();  // default
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowCalls()).isFalse();  // custom
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowMessages()).isTrue(); // default
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers()).isFalse();  // custom
+        assertThat(mZenModeHelper.mConsolidatedPolicy.showBadges()).isFalse();  // custom
+        assertThat(mZenModeHelper.mConsolidatedPolicy.showFullScreenIntents()).isTrue();  // custom
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MODES_API)
+    public void testUpdateConsolidatedPolicy_preModesApiDefaultAndCustomActive_mergesWithGlobal() {
         setupZenConfig();
 
         // when there are two rules active, one inheriting the default policy and one setting its
@@ -3221,13 +3738,75 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void testUpdateConsolidatedPolicy_modesApiDefaultAndCustomActive_mergesWithDefault() {
+        setupZenConfig();
+
+        // when there are two rules active, one inheriting the default policy and one setting its
+        // own custom policy, they should be merged to form the most restrictive combination.
+
+        // rule 1: no custom policy
+        AutomaticZenRule zenRule = new AutomaticZenRule("name",
+                null,
+                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                null,
+                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+        String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+
+        // enable rule 1
+        mZenModeHelper.setAutomaticZenRuleState(id,
+                new Condition(zenRule.getConditionId(), "", STATE_TRUE),
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+
+        // custom policy for rule 2
+        ZenPolicy customPolicy = new ZenPolicy.Builder()
+                .allowAlarms(false) // more restrictive than default
+                .allowSystem(true)  // more lenient than default
+                .allowRepeatCallers(false)  // more restrictive than default
+                .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE)  // more restrictive than default
+                .showBadges(false)  // more restrictive
+                .showPeeking(true)  // more lenient
+                .build();
+
+        AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
+                null,
+                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                customPolicy,
+                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+        String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+
+        // enable rule 2; this will update the consolidated policy
+        mZenModeHelper.setAutomaticZenRuleState(id2,
+                new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+
+        // now both rules should be on, and the consolidated policy should reflect the most
+        // restrictive option of each of the two
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isFalse();  // custom stricter
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isTrue();  // default
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isFalse();  // default stricter
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isFalse();  // default
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowCalls()).isFalse();  // custom stricter
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowMessages()).isTrue(); // default
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowConversations()).isTrue();  // default
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers())
+                .isFalse();  // custom stricter
+        assertThat(mZenModeHelper.mConsolidatedPolicy.showBadges()).isFalse();  // custom stricter
+        assertThat(mZenModeHelper.mConsolidatedPolicy.showPeeking()).isFalse();  // default stricter
+    }
+
+    @Test
     public void testUpdateConsolidatedPolicy_allowChannels() {
         mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
         setupZenConfig();
 
         // one rule, custom policy, allows channels
         ZenPolicy customPolicy = new ZenPolicy.Builder()
-                .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY)
+                .allowPriorityChannels(true)
                 .build();
 
         AutomaticZenRule zenRule = new AutomaticZenRule("name",
@@ -3249,7 +3828,7 @@
 
         // add new rule with policy that disallows channels
         ZenPolicy strictPolicy = new ZenPolicy.Builder()
-                .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE)
+                .allowPriorityChannels(false)
                 .build();
 
         AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
@@ -3284,7 +3863,10 @@
                 null,
                 new ComponentName(CUSTOM_PKG_NAME, "cls"),
                 Uri.parse("priority"),
-                new ZenPolicy.Builder().allowMedia(true).build(),
+                new ZenPolicy.Builder()
+                        .allowMedia(true)
+                        .allowSystem(true)
+                        .build(),
                 NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
         String rule1Id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
                 zenRuleWithPriority, UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
@@ -3306,10 +3888,10 @@
                 UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
 
         // Consolidated Policy should be default + rule1.
-        assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isFalse();  // default
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isTrue();  // default
         assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isTrue(); // priority rule
-        assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isFalse();  // default
-        assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isTrue();  // default
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isTrue();  // priority rule
+        assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isFalse();  // default
         assertThat(mZenModeHelper.mConsolidatedPolicy.allowCalls()).isTrue();  // default
         assertThat(mZenModeHelper.mConsolidatedPolicy.allowMessages()).isTrue(); // default
         assertThat(mZenModeHelper.mConsolidatedPolicy.allowConversations()).isTrue();  // default
@@ -3320,7 +3902,7 @@
     public void zenRuleToAutomaticZenRule_allFields() {
         mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
         when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(
-                new String[] {OWNER.getPackageName()});
+                new String[]{OWNER.getPackageName()});
 
         ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
         rule.configurationActivity = CONFIG_ACTIVITY;
@@ -3339,7 +3921,6 @@
 
         rule.allowManualInvocation = ALLOW_MANUAL;
         rule.type = TYPE;
-        rule.userModifiedFields = AutomaticZenRule.FIELD_NAME;
         rule.iconResName = ICON_RES_NAME;
         rule.triggerDescription = TRIGGER_DESC;
 
@@ -3354,7 +3935,6 @@
         assertEquals(POLICY, actual.getZenPolicy());
         assertEquals(CONFIG_ACTIVITY, actual.getConfigurationActivity());
         assertEquals(TYPE, actual.getType());
-        assertEquals(AutomaticZenRule.FIELD_NAME, actual.getUserModifiedFields());
         assertEquals(ALLOW_MANUAL, actual.isManualInvocationAllowed());
         assertEquals(CREATION_TIME, actual.getCreationTime());
         assertEquals(OWNER.getPackageName(), actual.getPackageName());
@@ -3366,7 +3946,7 @@
     public void automaticZenRuleToZenRule_allFields() {
         mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
         when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(
-                new String[] {OWNER.getPackageName()});
+                new String[]{OWNER.getPackageName()});
 
         AutomaticZenRule azr = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
                 .setEnabled(true)
@@ -3381,29 +3961,32 @@
                 .setManualInvocationAllowed(ALLOW_MANUAL)
                 .build();
 
-        ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(OWNER.getPackageName(), azr,
+                UPDATE_ORIGIN_APP, "add", CUSTOM_PKG_UID);
 
-        mZenModeHelper.populateZenRule(OWNER.getPackageName(), azr, rule, UPDATE_ORIGIN_APP, true);
+        ZenModeConfig.ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
 
-        assertEquals(NAME, rule.name);
-        assertEquals(OWNER, rule.component);
-        assertEquals(CONDITION_ID, rule.conditionId);
-        assertEquals(INTERRUPTION_FILTER_ZR, rule.zenMode);
-        assertEquals(ENABLED, rule.enabled);
-        assertEquals(POLICY, rule.zenPolicy);
-        assertEquals(CONFIG_ACTIVITY, rule.configurationActivity);
-        assertEquals(TYPE, rule.type);
-        assertEquals(ALLOW_MANUAL, rule.allowManualInvocation);
-        assertEquals(OWNER.getPackageName(), rule.getPkg());
-        assertEquals(ICON_RES_NAME, rule.iconResName);
+        assertThat(storedRule).isNotNull();
+        assertEquals(NAME, storedRule.name);
+        assertEquals(OWNER, storedRule.component);
+        assertEquals(CONDITION_ID, storedRule.conditionId);
+        assertEquals(INTERRUPTION_FILTER_ZR, storedRule.zenMode);
+        assertEquals(ENABLED, storedRule.enabled);
+        assertEquals(mZenModeHelper.getDefaultZenPolicy().overwrittenWith(POLICY),
+                storedRule.zenPolicy);
+        assertEquals(CONFIG_ACTIVITY, storedRule.configurationActivity);
+        assertEquals(TYPE, storedRule.type);
+        assertEquals(ALLOW_MANUAL, storedRule.allowManualInvocation);
+        assertEquals(OWNER.getPackageName(), storedRule.getPkg());
+        assertEquals(ICON_RES_NAME, storedRule.iconResName);
         // Because the origin of the update is the app, we don't expect the bitmask to change.
-        assertEquals(0, rule.userModifiedFields);
-        assertEquals(TRIGGER_DESC, rule.triggerDescription);
+        assertEquals(0, storedRule.userModifiedFields);
+        assertEquals(TRIGGER_DESC, storedRule.triggerDescription);
     }
 
     @Test
     @EnableFlags(Flags.FLAG_MODES_API)
-    public void automaticZenRuleToZenRule_updatesNameUnlessUserModified() {
+    public void updateAutomaticZenRule_fromApp_updatesNameUnlessUserModified() {
         // Add a starting rule with the name OriginalName.
         AutomaticZenRule azrBase = new AutomaticZenRule.Builder("OriginalName", CONDITION_ID)
                 .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
@@ -3420,7 +4003,6 @@
                 Process.SYSTEM_UID);
         rule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(rule.getName()).isEqualTo("NewName");
-        assertThat(rule.canUpdate()).isTrue();
 
         // The user modifies some other field in the rule, which makes the rule as a whole not
         // app modifiable.
@@ -3429,10 +4011,6 @@
                 .build();
         mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_USER, "reason",
                 Process.SYSTEM_UID);
-        rule = mZenModeHelper.getAutomaticZenRule(ruleId);
-        assertThat(rule.getUserModifiedFields())
-                .isEqualTo(AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
-        assertThat(rule.canUpdate()).isFalse();
 
         // ...but the app can still modify the name, because the name itself hasn't been modified
         // by the user.
@@ -3452,8 +4030,6 @@
                 Process.SYSTEM_UID);
         rule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(rule.getName()).isEqualTo("UserProvidedName");
-        assertThat(rule.getUserModifiedFields()).isEqualTo(AutomaticZenRule.FIELD_NAME
-                | AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
 
         // The app is no longer able to modify the name.
         azrUpdate = new AutomaticZenRule.Builder(rule)
@@ -3467,7 +4043,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_MODES_API)
-    public void automaticZenRuleToZenRule_updatesBitmaskAndValueForUserOrigin() {
+    public void updateAutomaticZenRule_fromUser_updatesBitmaskAndValue() {
         // Adds a starting rule with empty zen policies and device effects
         AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
                 .setZenPolicy(new ZenPolicy.Builder().build())
@@ -3480,12 +4056,12 @@
 
         // Modifies the zen policy and device effects
         ZenPolicy policy = new ZenPolicy.Builder(rule.getZenPolicy())
-                .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY)
+                .allowPriorityChannels(false)
                 .build();
         ZenDeviceEffects deviceEffects =
                 new ZenDeviceEffects.Builder(rule.getDeviceEffects())
-                .setShouldDisplayGrayscale(true)
-                .build();
+                        .setShouldDisplayGrayscale(true)
+                        .build();
         AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
                 .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                 .setZenPolicy(policy)
@@ -3499,85 +4075,23 @@
 
         // UPDATE_ORIGIN_USER should change the bitmask and change the values.
         assertThat(rule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
-        assertThat(rule.getUserModifiedFields())
-                .isEqualTo(AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
-        assertThat(rule.getZenPolicy().getUserModifiedFields())
-                .isEqualTo(ZenPolicy.FIELD_ALLOW_CHANNELS);
-        assertThat(rule.getZenPolicy().getAllowedChannels())
-                .isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY);
-        assertThat(rule.getDeviceEffects().getUserModifiedFields())
-                .isEqualTo(ZenDeviceEffects.FIELD_GRAYSCALE);
-        assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_MODES_API)
-    public void automaticZenRuleToZenRule_doesNotUpdateValuesForInitUserOrigin() {
-        // Adds a starting rule with empty zen policies and device effects
-        AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
-                .setInterruptionFilter(INTERRUPTION_FILTER_ALL) // Already the default, no change
-                .setZenPolicy(new ZenPolicy.Builder()
-                        .allowReminders(false)
-                        .build())
-                .setDeviceEffects(new ZenDeviceEffects.Builder()
-                        .setShouldDisplayGrayscale(false)
-                        .build())
-                .build();
-        // Adds the rule using the user, to set user-modified bits.
-        String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                azrBase, UPDATE_ORIGIN_USER, "reason", Process.SYSTEM_UID);
-        AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
-        assertThat(rule.canUpdate()).isFalse();
-        assertThat(rule.getUserModifiedFields()).isEqualTo(AutomaticZenRule.FIELD_NAME);
-
-        ZenPolicy policy = new ZenPolicy.Builder(rule.getZenPolicy())
-                .allowReminders(true)
-                .build();
-        ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder(rule.getDeviceEffects())
-                .setShouldDisplayGrayscale(true)
-                .build();
-        AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
-                .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
-                .setZenPolicy(policy)
-                .setDeviceEffects(deviceEffects)
-                .build();
-
-        // Attempts to update the rule with the AZR from origin init user.
-        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_INIT_USER, "reason",
-                Process.SYSTEM_UID);
-        AutomaticZenRule unchangedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
-
-        // UPDATE_ORIGIN_INIT_USER does not change the bitmask or values if rule is user modified.
-        // TODO: b/318506692 - Remove once we check that INIT origins can't call add/updateAZR.
-        assertThat(unchangedRule.getUserModifiedFields()).isEqualTo(rule.getUserModifiedFields());
-        assertThat(unchangedRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALL);
-        assertThat(unchangedRule.getZenPolicy().getUserModifiedFields()).isEqualTo(
-                rule.getZenPolicy().getUserModifiedFields());
-        assertThat(unchangedRule.getZenPolicy().getPriorityCategoryReminders()).isEqualTo(
+        assertThat(rule.getZenPolicy().getPriorityChannelsAllowed()).isEqualTo(
                 ZenPolicy.STATE_DISALLOW);
-        assertThat(unchangedRule.getDeviceEffects().getUserModifiedFields()).isEqualTo(
-                rule.getDeviceEffects().getUserModifiedFields());
-        assertThat(unchangedRule.getDeviceEffects().shouldDisplayGrayscale()).isFalse();
 
-        // Creates a new rule with the AZR from origin init user.
-        String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                azrUpdate, UPDATE_ORIGIN_INIT_USER, "reason", Process.SYSTEM_UID);
-        AutomaticZenRule newRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+        assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
 
-        // UPDATE_ORIGIN_INIT_USER does change the values if the rule is new,
-        // but does not update the bitmask.
-        assertThat(newRule.getUserModifiedFields()).isEqualTo(0);
-        assertThat(newRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
-        assertThat(newRule.getZenPolicy().getUserModifiedFields()).isEqualTo(0);
-        assertThat(newRule.getZenPolicy().getPriorityCategoryReminders())
-                .isEqualTo(ZenPolicy.STATE_ALLOW);
-        assertThat(newRule.getDeviceEffects().getUserModifiedFields()).isEqualTo(0);
-        assertThat(newRule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+        ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(storedRule.userModifiedFields)
+                .isEqualTo(AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
+        assertThat(storedRule.zenPolicyUserModifiedFields)
+                .isEqualTo(ZenPolicy.FIELD_ALLOW_CHANNELS);
+        assertThat(storedRule.zenDeviceEffectsUserModifiedFields)
+                .isEqualTo(ZenDeviceEffects.FIELD_GRAYSCALE);
     }
 
     @Test
     @EnableFlags(Flags.FLAG_MODES_API)
-    public void automaticZenRuleToZenRule_updatesValuesForSystemUiOrigin() {
+    public void updateAutomaticZenRule_fromSystemUi_updatesValues() {
         // Adds a starting rule with empty zen policies and device effects
         AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
                 .setInterruptionFilter(INTERRUPTION_FILTER_ALL)
@@ -3613,17 +4127,19 @@
         rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
         // UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI should change the value but NOT update the bitmask.
-        assertThat(rule.getUserModifiedFields()).isEqualTo(0);
-        assertThat(rule.getZenPolicy().getUserModifiedFields()).isEqualTo(0);
         assertThat(rule.getZenPolicy().getPriorityCategoryReminders())
                 .isEqualTo(ZenPolicy.STATE_ALLOW);
-        assertThat(rule.getDeviceEffects().getUserModifiedFields()).isEqualTo(0);
         assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+
+        ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(storedRule.userModifiedFields).isEqualTo(0);
+        assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(0);
+        assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(0);
     }
 
     @Test
     @EnableFlags(Flags.FLAG_MODES_API)
-    public void automaticZenRuleToZenRule_updatesValuesIfRuleNotUserModified() {
+    public void updateAutomaticZenRule_fromApp_updatesValuesIfRuleNotUserModified() {
         // Adds a starting rule with empty zen policies and device effects
         AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
                 .setInterruptionFilter(INTERRUPTION_FILTER_ALL)
@@ -3638,7 +4154,6 @@
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
                 azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
         AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
-        assertThat(rule.canUpdate()).isTrue();
 
         ZenPolicy policy = new ZenPolicy.Builder()
                 .allowReminders(true)
@@ -3646,57 +4161,59 @@
         ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder()
                 .setShouldDisplayGrayscale(true)
                 .build();
-        AutomaticZenRule azrUpdate =  new AutomaticZenRule.Builder(rule)
+        AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
                 .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
                 .setZenPolicy(policy)
                 .setDeviceEffects(deviceEffects)
                 .build();
 
-        // Since the rule is not already user modified, UPDATE_ORIGIN_UNKNOWN can modify the rule.
+        // Since the rule is not already user modified, UPDATE_ORIGIN_APP can modify the rule.
         // The bitmask is not modified.
-        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_UNKNOWN, "reason",
+        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_APP, "reason",
                 Process.SYSTEM_UID);
-        AutomaticZenRule unchangedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
-        assertThat(unchangedRule.getUserModifiedFields()).isEqualTo(rule.getUserModifiedFields());
-        assertThat(unchangedRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
-        assertThat(unchangedRule.getZenPolicy().getUserModifiedFields()).isEqualTo(
-                rule.getZenPolicy().getUserModifiedFields());
-        assertThat(unchangedRule.getZenPolicy().getPriorityCategoryReminders())
+        ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(storedRule.userModifiedFields).isEqualTo(0);
+
+        assertThat(storedRule.zenMode).isEqualTo(ZEN_MODE_ALARMS);
+        assertThat(storedRule.zenPolicy.getPriorityCategoryReminders())
                 .isEqualTo(ZenPolicy.STATE_ALLOW);
-        assertThat(unchangedRule.getDeviceEffects().getUserModifiedFields()).isEqualTo(
-                rule.getDeviceEffects().getUserModifiedFields());
-        assertThat(unchangedRule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+        assertThat(storedRule.zenDeviceEffects.shouldDisplayGrayscale()).isTrue();
+        assertThat(storedRule.userModifiedFields).isEqualTo(0);
+        assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(0);
+        assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(0);
 
         // Creates another rule, this time from user. This will have user modified bits set.
         String ruleIdUser = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
                 azrBase, UPDATE_ORIGIN_USER, "reason", Process.SYSTEM_UID);
-        AutomaticZenRule ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser);
-        assertThat(ruleUser.canUpdate()).isFalse();
+        storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleIdUser);
+        int ruleModifiedFields = storedRule.userModifiedFields;
+        int rulePolicyModifiedFields = storedRule.zenPolicyUserModifiedFields;
+        int ruleDeviceEffectsModifiedFields = storedRule.zenDeviceEffectsUserModifiedFields;
 
-        // Zen rule update coming from unknown origin. This cannot fully update the rule, because
+        // Zen rule update coming from the app again. This cannot fully update the rule, because
         // the rule is already considered user modified.
-        mZenModeHelper.updateAutomaticZenRule(ruleIdUser, azrUpdate, UPDATE_ORIGIN_UNKNOWN,
+        mZenModeHelper.updateAutomaticZenRule(ruleIdUser, azrUpdate, UPDATE_ORIGIN_APP,
                 "reason", Process.SYSTEM_UID);
-        ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser);
+        AutomaticZenRule ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser);
 
-        // UPDATE_ORIGIN_UNKNOWN can only change the value if the rule is not already user modified,
+        // The app can only change the value if the rule is not already user modified,
         // so the rule is not changed, and neither is the bitmask.
         assertThat(ruleUser.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALL);
-        // Interruption Filter All is the default value, so it's not included as a modified field.
-        assertThat(ruleUser.getUserModifiedFields() | AutomaticZenRule.FIELD_NAME).isGreaterThan(0);
-        assertThat(ruleUser.getZenPolicy().getUserModifiedFields()
-                | ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS).isGreaterThan(0);
         assertThat(ruleUser.getZenPolicy().getPriorityCategoryReminders())
                 .isEqualTo(ZenPolicy.STATE_DISALLOW);
-        assertThat(ruleUser.getDeviceEffects().getUserModifiedFields()
-                | ZenDeviceEffects.FIELD_GRAYSCALE).isGreaterThan(0);
         assertThat(ruleUser.getDeviceEffects().shouldDisplayGrayscale()).isFalse();
+
+        storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleIdUser);
+        assertThat(storedRule.userModifiedFields).isEqualTo(ruleModifiedFields);
+        assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(rulePolicyModifiedFields);
+        assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(
+                ruleDeviceEffectsModifiedFields);
     }
 
     @Test
     @EnableFlags(Flags.FLAG_MODES_API)
-    public void automaticZenRuleToZenRule_updatesValuesIfRuleNew() {
+    public void addAutomaticZenRule_updatesValues() {
         // Adds a starting rule with empty zen policies and device effects
         AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
                 .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
@@ -3707,21 +4224,22 @@
                         .setShouldDisplayGrayscale(true)
                         .build())
                 .build();
-        // Adds the rule using origin unknown, to show that a new rule is always allowed.
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                azrBase, UPDATE_ORIGIN_UNKNOWN, "reason", Process.SYSTEM_UID);
+                azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
         AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
         // The values are modified but the bitmask is not.
-        assertThat(rule.canUpdate()).isTrue();
         assertThat(rule.getZenPolicy().getPriorityCategoryReminders())
                 .isEqualTo(ZenPolicy.STATE_ALLOW);
         assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+
+        ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(storedRule.canBeUpdatedByApp()).isTrue();
     }
 
     @Test
     @EnableFlags(Flags.FLAG_MODES_API)
-    public void automaticZenRuleToZenRule_nullDeviceEffectsUpdate() {
+    public void updateAutomaticZenRule_nullDeviceEffectsUpdate() {
         // Adds a starting rule with empty zen policies and device effects
         AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
                 .setDeviceEffects(new ZenDeviceEffects.Builder().build())
@@ -3736,9 +4254,9 @@
                 .setDeviceEffects(null)
                 .build();
 
-        // Zen rule update coming from unknown origin, but since the rule isn't already
+        // Zen rule update coming from app, but since the rule isn't already
         // user modified, it can be updated.
-        mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_UNKNOWN, "reason",
+        mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_APP, "reason",
                 Process.SYSTEM_UID);
         rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
@@ -3748,30 +4266,31 @@
 
     @Test
     @EnableFlags(Flags.FLAG_MODES_API)
-    public void automaticZenRuleToZenRule_nullPolicyUpdate() {
-        // Adds a starting rule with empty zen policies and device effects
+    public void updateAutomaticZenRule_nullPolicyUpdate() {
+        // Adds a starting rule with set zen policy and empty device effects
         AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
-                .setZenPolicy(new ZenPolicy.Builder().build())
+                .setZenPolicy(POLICY)
                 .build();
         // Adds the rule using the app, to avoid having any user modified bits set.
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
                 azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
         AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
-        assertThat(rule.canUpdate()).isTrue();
 
         AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase)
                 // Set zen policy to null
                 .setZenPolicy(null)
                 .build();
 
-        // Zen rule update coming from unknown origin, but since the rule isn't already
+        // Zen rule update coming from app, but since the rule isn't already
         // user modified, it can be updated.
-        mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_UNKNOWN, "reason",
+        mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_APP, "reason",
                 Process.SYSTEM_UID);
         rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
-        // When AZR's ZenPolicy is null, we expect the updated rule's policy to be null.
-        assertThat(rule.getZenPolicy()).isNull();
+        // When AZR's ZenPolicy is null, we expect the updated rule's policy to be unchanged
+        // (equivalent to the provided policy, with additional fields filled in with defaults).
+        assertThat(rule.getZenPolicy()).isEqualTo(
+                mZenModeHelper.getDefaultZenPolicy().overwrittenWith(POLICY));
     }
 
     @Test
@@ -3788,11 +4307,10 @@
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
                 azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
         AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
-        assertThat(rule.canUpdate()).isTrue();
 
         // Create a fully populated ZenPolicy.
         ZenPolicy policy = new ZenPolicy.Builder()
-                .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE) // Differs from the default
+                .allowPriorityChannels(false) // Differs from the default
                 .allowReminders(true) // Differs from the default
                 .allowEvents(true) // Differs from the default
                 .allowConversations(ZenPolicy.CONVERSATION_SENDERS_IMPORTANT)
@@ -3822,17 +4340,20 @@
 
         // New ZenPolicy differs from the default config
         assertThat(rule.getZenPolicy()).isNotNull();
-        assertThat(rule.getZenPolicy().getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_NONE);
-        assertThat(rule.canUpdate()).isFalse();
-        assertThat(rule.getZenPolicy().getUserModifiedFields()).isEqualTo(
+        assertThat(rule.getZenPolicy().getPriorityChannelsAllowed()).isEqualTo(
+                ZenPolicy.STATE_DISALLOW);
+
+        ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(storedRule.canBeUpdatedByApp()).isFalse();
+        assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(
                 ZenPolicy.FIELD_ALLOW_CHANNELS
-                | ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS
-                | ZenPolicy.FIELD_PRIORITY_CATEGORY_EVENTS
-                | ZenPolicy.FIELD_PRIORITY_CATEGORY_SYSTEM
-                | ZenPolicy.FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT
-                | ZenPolicy.FIELD_VISUAL_EFFECT_LIGHTS
-                | ZenPolicy.FIELD_VISUAL_EFFECT_PEEK
-                | ZenPolicy.FIELD_VISUAL_EFFECT_AMBIENT
+                        | ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS
+                        | ZenPolicy.FIELD_PRIORITY_CATEGORY_EVENTS
+                        | ZenPolicy.FIELD_PRIORITY_CATEGORY_SYSTEM
+                        | ZenPolicy.FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT
+                        | ZenPolicy.FIELD_VISUAL_EFFECT_LIGHTS
+                        | ZenPolicy.FIELD_VISUAL_EFFECT_PEEK
+                        | ZenPolicy.FIELD_VISUAL_EFFECT_AMBIENT
         );
     }
 
@@ -3847,7 +4368,6 @@
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
                 azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
         AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
-        assertThat(rule.canUpdate()).isTrue();
 
         ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder()
                 .setShouldDisplayGrayscale(true)
@@ -3864,8 +4384,10 @@
         // New ZenDeviceEffects is used; all fields considered set, since previously were null.
         assertThat(rule.getDeviceEffects()).isNotNull();
         assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
-        assertThat(rule.canUpdate()).isFalse();
-        assertThat(rule.getDeviceEffects().getUserModifiedFields()).isEqualTo(
+
+        ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(storedRule.canBeUpdatedByApp()).isFalse();
+        assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(
                 ZenDeviceEffects.FIELD_GRAYSCALE);
     }
 
@@ -3994,6 +4516,7 @@
         final int[] actualStatus = new int[2];
         ZenModeHelper.Callback callback = new ZenModeHelper.Callback() {
             int i = 0;
+
             @Override
             void onAutomaticRuleStatusChanged(int userId, String pkg, String id, int status) {
                 if (Objects.equals(createdId, id)) {
@@ -4034,6 +4557,7 @@
         final int[] actualStatus = new int[2];
         ZenModeHelper.Callback callback = new ZenModeHelper.Callback() {
             int i = 0;
+
             @Override
             void onAutomaticRuleStatusChanged(int userId, String pkg, String id, int status) {
                 if (Objects.equals(createdId, id)) {
@@ -4180,6 +4704,7 @@
                         .build()),
                 eq(UPDATE_ORIGIN_APP));
     }
+
     @Test
     public void testDeviceEffects_noChangeToConsolidatedEffects_notApplied() {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
@@ -4237,6 +4762,7 @@
                 .setShouldDimWallpaper(true)
                 .setShouldUseNightMode(true)
                 .build();
+        user1Rule.zenPolicy = new ZenPolicy();
         verifyNoMoreInteractions(mDeviceEffectsApplier);
 
         mZenModeHelper.onUserSwitched(1);
@@ -4255,6 +4781,428 @@
     }
 
     @Test
+    public void removeAndAddAutomaticZenRule_wasCustomized_isRestored() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        // Start with a rule.
+        mZenModeHelper.mConfig.automaticRules.clear();
+        mTestClock.setNowMillis(1000);
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+                .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+                .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+                UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
+
+        // User customizes it.
+        AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
+                .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+                .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, UPDATE_ORIGIN_USER, "userUpdate",
+                Process.SYSTEM_UID);
+
+        // App deletes it.
+        mTestClock.advanceByMillis(1000);
+        mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_APP, "delete it",
+                CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
+        assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
+
+        // App adds it again.
+        mTestClock.advanceByMillis(1000);
+        String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+                UPDATE_ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+
+        // Verify that the rule was restored:
+        // - id and creation time is the same as the original one.
+        // - ZenPolicy is the one that the user had set.
+        // - rule still has the user-modified fields.
+        AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+        assertThat(finalRule.getCreationTime()).isEqualTo(1000); // And not 3000.
+        assertThat(newRuleId).isEqualTo(ruleId);
+        assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
+        assertThat(finalRule.getZenPolicy().getPriorityCategoryRepeatCallers()).isEqualTo(
+                ZenPolicy.STATE_ALLOW);
+
+        ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(storedRule.userModifiedFields).isEqualTo(
+                AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
+        assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(
+                ZenPolicy.FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS);
+
+        // Also, we discarded the "deleted rule" since we already used it for restoration.
+        assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
+    }
+
+    @Test
+    public void removeAndAddAutomaticZenRule_wasNotCustomized_isNotRestored() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        // Start with a single rule.
+        mZenModeHelper.mConfig.automaticRules.clear();
+        mTestClock.setNowMillis(1000);
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+                .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+                .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+                UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
+
+        // App deletes it.
+        mTestClock.advanceByMillis(1000);
+        mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_APP, "delete it",
+                CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
+        assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
+
+        // App adds it again.
+        mTestClock.advanceByMillis(1000);
+        String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+                UPDATE_ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+
+        // Verify that the rule was recreated. This means id and creation time are new.
+        AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+        assertThat(finalRule.getCreationTime()).isEqualTo(3000);
+        assertThat(newRuleId).isNotEqualTo(ruleId);
+    }
+
+    @Test
+    public void removeAndAddAutomaticZenRule_recreatedButNotByApp_isNotRestored() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        // Start with a single rule.
+        mZenModeHelper.mConfig.automaticRules.clear();
+        mTestClock.setNowMillis(1000);
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+                .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+                .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+                UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
+
+        // User customizes it.
+        mTestClock.advanceByMillis(1000);
+        AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
+                .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+                .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, UPDATE_ORIGIN_USER, "userUpdate",
+                Process.SYSTEM_UID);
+
+        // App deletes it.
+        mTestClock.advanceByMillis(1000);
+        mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_APP, "delete it",
+                CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
+        assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
+
+        // User creates it again (unusual case, but ok).
+        mTestClock.advanceByMillis(1000);
+        String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+                UPDATE_ORIGIN_USER, "add it anew", CUSTOM_PKG_UID);
+
+        // Verify that the rule was recreated. This means id and creation time are new, and the rule
+        // matches the latest data supplied to addAZR.
+        AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+        assertThat(finalRule.getCreationTime()).isEqualTo(4000);
+        assertThat(newRuleId).isNotEqualTo(ruleId);
+        assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
+        assertThat(finalRule.getZenPolicy().getPriorityCategoryRepeatCallers()).isEqualTo(
+                ZenPolicy.STATE_DISALLOW);
+
+        // Also, we discarded the "deleted rule" since we're not interested in recreating it.
+        assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
+    }
+
+    @Test
+    public void removeAndAddAutomaticZenRule_removedByUser_isNotRestored() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+        // Start with a single rule.
+        mZenModeHelper.mConfig.automaticRules.clear();
+        mTestClock.setNowMillis(1000);
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+                .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+                .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+                UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
+
+        // User customizes it.
+        mTestClock.advanceByMillis(1000);
+        AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
+                .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+                .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, UPDATE_ORIGIN_USER, "userUpdate",
+                Process.SYSTEM_UID);
+
+        // User deletes it.
+        mTestClock.advanceByMillis(1000);
+        mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_USER, "delete it",
+                CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
+        assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
+
+        // App creates it again.
+        mTestClock.advanceByMillis(1000);
+        String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+                UPDATE_ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+
+        // Verify that the rule was recreated. This means id and creation time are new.
+        AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+        assertThat(finalRule.getCreationTime()).isEqualTo(4000);
+        assertThat(newRuleId).isNotEqualTo(ruleId);
+    }
+
+    @Test
+    public void removeAutomaticZenRule_preservedForRestoringByPackageAndConditionId() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+        mContext.getTestablePermissions().setPermission(Manifest.permission.MANAGE_NOTIFICATIONS,
+                PERMISSION_GRANTED); // So that canManageAZR passes although packages don't match.
+        mZenModeHelper.mConfig.automaticRules.clear();
+
+        // Start with a bunch of customized rules where conditionUris are not unique.
+        String id1 = mZenModeHelper.addAutomaticZenRule("pkg1",
+                new AutomaticZenRule.Builder("Test1", Uri.parse("uri1")).build(), UPDATE_ORIGIN_APP,
+                "add it", CUSTOM_PKG_UID);
+        String id2 = mZenModeHelper.addAutomaticZenRule("pkg1",
+                new AutomaticZenRule.Builder("Test2", Uri.parse("uri2")).build(), UPDATE_ORIGIN_APP,
+                "add it", CUSTOM_PKG_UID);
+        String id3 = mZenModeHelper.addAutomaticZenRule("pkg1",
+                new AutomaticZenRule.Builder("Test3", Uri.parse("uri2")).build(), UPDATE_ORIGIN_APP,
+                "add it", CUSTOM_PKG_UID);
+        String id4 = mZenModeHelper.addAutomaticZenRule("pkg2",
+                new AutomaticZenRule.Builder("Test4", Uri.parse("uri1")).build(), UPDATE_ORIGIN_APP,
+                "add it", CUSTOM_PKG_UID);
+        String id5 = mZenModeHelper.addAutomaticZenRule("pkg2",
+                new AutomaticZenRule.Builder("Test5", Uri.parse("uri1")).build(), UPDATE_ORIGIN_APP,
+                "add it", CUSTOM_PKG_UID);
+        for (ZenRule zenRule : mZenModeHelper.mConfig.automaticRules.values()) {
+            zenRule.userModifiedFields = AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
+        }
+
+        mZenModeHelper.removeAutomaticZenRule(id1, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+        mZenModeHelper.removeAutomaticZenRule(id2, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+        mZenModeHelper.removeAutomaticZenRule(id3, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+        mZenModeHelper.removeAutomaticZenRule(id4, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+        mZenModeHelper.removeAutomaticZenRule(id5, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+
+        assertThat(mZenModeHelper.mConfig.deletedRules.keySet())
+                .containsExactly("pkg1|uri1", "pkg1|uri2", "pkg2|uri1");
+        assertThat(mZenModeHelper.mConfig.deletedRules.values().stream().map(zr -> zr.name)
+                .collect(Collectors.toList()))
+                .containsExactly("Test1", "Test3", "Test5");
+    }
+
+    @Test
+    public void removeAllZenRules_preservedForRestoring() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+        mZenModeHelper.mConfig.automaticRules.clear();
+
+        mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+                new AutomaticZenRule.Builder("Test1", Uri.parse("uri1")).build(), UPDATE_ORIGIN_APP,
+                "add it", CUSTOM_PKG_UID);
+        mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+                new AutomaticZenRule.Builder("Test2", Uri.parse("uri2")).build(), UPDATE_ORIGIN_APP,
+                "add it", CUSTOM_PKG_UID);
+
+        for (ZenRule zenRule : mZenModeHelper.mConfig.automaticRules.values()) {
+            zenRule.userModifiedFields = AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
+        }
+
+        mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), UPDATE_ORIGIN_APP,
+                "begone", CUSTOM_PKG_UID);
+
+        assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(2);
+    }
+
+    @Test
+    public void removeAllZenRules_fromSystem_deletesPreservedRulesToo() {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+        mZenModeHelper.mConfig.automaticRules.clear();
+
+        // Start with deleted rules from 2 different packages.
+        Instant now = Instant.ofEpochMilli(1701796461000L);
+        ZenRule pkg1Rule = newZenRule("pkg1", now.minus(1, ChronoUnit.DAYS), now);
+        ZenRule pkg2Rule = newZenRule("pkg2", now.minus(2, ChronoUnit.DAYS), now);
+        mZenModeHelper.mConfig.deletedRules.put(ZenModeConfig.deletedRuleKey(pkg1Rule), pkg1Rule);
+        mZenModeHelper.mConfig.deletedRules.put(ZenModeConfig.deletedRuleKey(pkg2Rule), pkg2Rule);
+
+        mZenModeHelper.removeAutomaticZenRules("pkg1",
+                UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "goodbye pkg1", Process.SYSTEM_UID);
+
+        // Preserved rules from pkg1 are gone; those from pkg2 are still there.
+        assertThat(mZenModeHelper.mConfig.deletedRules.values().stream().map(r -> r.pkg)
+                .collect(Collectors.toSet())).containsExactly("pkg2");
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void removeAndAddAutomaticZenRule_wasActive_isRestoredAsInactive() {
+        // Start with a rule.
+        mZenModeHelper.mConfig.automaticRules.clear();
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+                .setConditionId(CONDITION_ID)
+                .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+                UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+
+        // User customizes it.
+        AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
+                .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, UPDATE_ORIGIN_USER, "userUpdate",
+                Process.SYSTEM_UID);
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+
+        // App activates it.
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
+
+        // App deletes it.
+        mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_APP, "delete it",
+                CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
+        assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+
+        // App adds it again.
+        String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+                UPDATE_ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+
+        // The rule is restored...
+        assertThat(newRuleId).isEqualTo(ruleId);
+        AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+        assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
+
+        // ... but it is NOT active
+        ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(newRuleId);
+        assertThat(storedRule.isAutomaticActive()).isFalse();
+        assertThat(storedRule.isTrueOrUnknown()).isFalse();
+        assertThat(storedRule.condition).isNull();
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void removeAndAddAutomaticZenRule_wasSnoozed_isRestoredAsInactive() {
+        // Start with a rule.
+        mZenModeHelper.mConfig.automaticRules.clear();
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+                .setConditionId(CONDITION_ID)
+                .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+                UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+
+        // User customizes it.
+        AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
+                .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, UPDATE_ORIGIN_USER, "userUpdate",
+                Process.SYSTEM_UID);
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+
+        // App activates it.
+        mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+                CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
+
+        // User snoozes it.
+        mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+                "snoozing", "systemui", Process.SYSTEM_UID);
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+
+        // App deletes it.
+        mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_APP, "delete it",
+                CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
+        assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
+
+        // App adds it again.
+        String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+                UPDATE_ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+
+        // The rule is restored...
+        assertThat(newRuleId).isEqualTo(ruleId);
+        AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+        assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
+
+        // ... but it is NEITHER active NOR snoozed.
+        ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(newRuleId);
+        assertThat(storedRule.isAutomaticActive()).isFalse();
+        assertThat(storedRule.isTrueOrUnknown()).isFalse();
+        assertThat(storedRule.condition).isNull();
+        assertThat(storedRule.snoozing).isFalse();
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+    }
+
+    @Test
+    public void testRuleCleanup() throws Exception {
+        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+        Instant now = Instant.ofEpochMilli(1701796461000L);
+        Instant yesterday = now.minus(1, ChronoUnit.DAYS);
+        Instant aWeekAgo = now.minus(7, ChronoUnit.DAYS);
+        Instant twoMonthsAgo = now.minus(60, ChronoUnit.DAYS);
+        mTestClock.setNowMillis(now.toEpochMilli());
+
+        when(mPackageManager.getPackageInfo(eq("good_pkg"), anyInt()))
+                .thenReturn(new PackageInfo());
+        when(mPackageManager.getPackageInfo(eq("bad_pkg"), anyInt()))
+                .thenThrow(new PackageManager.NameNotFoundException("bad_pkg is not here"));
+
+        // Set up a config for another user containing:
+        ZenModeConfig config = new ZenModeConfig();
+        config.user = 42;
+        mZenModeHelper.mConfigs.put(42, config);
+        // okay rules (not deleted, package exists, with a range of creation dates).
+        config.automaticRules.put("ar1", newZenRule("good_pkg", now, null));
+        config.automaticRules.put("ar2", newZenRule("good_pkg", yesterday, null));
+        config.automaticRules.put("ar3", newZenRule("good_pkg", twoMonthsAgo, null));
+        // newish rules for a missing package
+        config.automaticRules.put("ar4", newZenRule("bad_pkg", yesterday, null));
+        // oldish rules belonging to a missing package
+        config.automaticRules.put("ar5", newZenRule("bad_pkg", aWeekAgo, null));
+        // rules deleted recently
+        config.deletedRules.put("del1", newZenRule("good_pkg", twoMonthsAgo, yesterday));
+        config.deletedRules.put("del2", newZenRule("good_pkg", twoMonthsAgo, aWeekAgo));
+        // rules deleted a long time ago
+        config.deletedRules.put("del3", newZenRule("good_pkg", twoMonthsAgo, twoMonthsAgo));
+        // rules for a missing package, created recently and deleted recently
+        config.deletedRules.put("del4", newZenRule("bad_pkg", yesterday, now));
+        // rules for a missing package, created a long time ago and deleted recently
+        config.deletedRules.put("del5", newZenRule("bad_pkg", twoMonthsAgo, now));
+        // rules for a missing package, created a long time ago and deleted a long time ago
+        config.deletedRules.put("del6", newZenRule("bad_pkg", twoMonthsAgo, twoMonthsAgo));
+
+        mZenModeHelper.onUserUnlocked(42); // copies config and cleans it up.
+
+        assertThat(mZenModeHelper.mConfig.automaticRules.keySet())
+                .containsExactly("ar1", "ar2", "ar3", "ar4");
+        assertThat(mZenModeHelper.mConfig.deletedRules.keySet())
+                .containsExactly("del1", "del2", "del4");
+    }
+
+    private static ZenRule newZenRule(String pkg, Instant createdAt, @Nullable Instant deletedAt) {
+        ZenRule rule = new ZenRule();
+        rule.pkg = pkg;
+        rule.creationTime = createdAt.toEpochMilli();
+        rule.deletionInstant = deletedAt;
+        // Plus stuff so that isValidAutomaticRule() passes
+        rule.name = "A rule from " + pkg + " created on " + createdAt;
+        rule.conditionId = Uri.parse(rule.name);
+        return rule;
+    }
+
+    @Test
     public void applyGlobalZenModeAsImplicitZenRule_createsImplicitRuleAndActivatesIt() {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
         mZenModeHelper.mConfig.automaticRules.clear();
@@ -4263,10 +5211,11 @@
                 ZEN_MODE_IMPORTANT_INTERRUPTIONS);
 
         assertThat(mZenModeHelper.mConfig.automaticRules.values())
-                .comparingElementsUsing(IGNORE_TIMESTAMPS)
+                .comparingElementsUsing(IGNORE_METADATA)
                 .containsExactly(
                         expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
-                                null, true));
+                                mZenModeHelper.mConfig.toZenPolicy(), // copy of global config
+                                true));
     }
 
     @Test
@@ -4283,9 +5232,74 @@
                 ZEN_MODE_ALARMS);
 
         assertThat(mZenModeHelper.mConfig.automaticRules.values())
-                .comparingElementsUsing(IGNORE_TIMESTAMPS)
+                .comparingElementsUsing(IGNORE_METADATA)
                 .containsExactly(
-                        expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_ALARMS, null, true));
+                        expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_ALARMS,
+                                mZenModeHelper.mConfig.toZenPolicy(), // copy of global config
+                                true));
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    public void applyGlobalZenModeAsImplicitZenRule_ruleCustomized_doesNotUpdateRule() {
+        mZenModeHelper.mConfig.automaticRules.clear();
+        String pkg = mContext.getPackageName();
+
+        // From app, call "setInterruptionFilter" and create and implicit rule.
+        mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+                ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+        String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
+        assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
+                .isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+        // From user, update that rule's interruption filter.
+        AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+        AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
+                .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
+                Process.SYSTEM_UID);
+
+        // From app, call "setInterruptionFilter" again.
+        mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+                ZEN_MODE_NO_INTERRUPTIONS);
+
+        // The app's update was ignored, and the user's update is still current, and the current
+        // mode is the one they chose.
+        assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
+                .isEqualTo(ZEN_MODE_ALARMS);
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    public void applyGlobalZenModeAsImplicitZenRule_ruleCustomizedButNotFilter_updatesRule() {
+        mZenModeHelper.mConfig.automaticRules.clear();
+        String pkg = mContext.getPackageName();
+
+        // From app, call "setInterruptionFilter" and create and implicit rule.
+        mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+                ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+        String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
+        assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
+                .isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+        // From user, update something in that rule, but not the interruption filter.
+        AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+        AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
+                .setName("Renamed")
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
+                Process.SYSTEM_UID);
+
+        // From app, call "setInterruptionFilter" again.
+        mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+                ZEN_MODE_NO_INTERRUPTIONS);
+
+        // The app's update was accepted, and the current mode is the one that they wanted.
+        assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
+                .isEqualTo(ZEN_MODE_NO_INTERRUPTIONS);
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_NO_INTERRUPTIONS);
     }
 
     @Test
@@ -4358,18 +5372,17 @@
         Policy policy = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
                 PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
                 Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
-        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, policy,
-                UPDATE_ORIGIN_APP);
+        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, policy);
 
         ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
                 .disallowAllSounds()
                 .allowCalls(PEOPLE_TYPE_CONTACTS)
                 .allowConversations(CONVERSATION_SENDERS_IMPORTANT)
                 .hideAllVisualEffects()
-                .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY)
+                .allowPriorityChannels(true)
                 .build();
         assertThat(mZenModeHelper.mConfig.automaticRules.values())
-                .comparingElementsUsing(IGNORE_TIMESTAMPS)
+                .comparingElementsUsing(IGNORE_METADATA)
                 .containsExactly(
                         expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                                 expectedZenPolicy, /* conditionActive= */ null));
@@ -4384,37 +5397,113 @@
                 PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
                 Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
         mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
-                original, UPDATE_ORIGIN_APP);
+                original);
 
         // Change priorityCallSenders: contacts -> starred.
         Policy updated = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
                 PRIORITY_SENDERS_STARRED, PRIORITY_SENDERS_STARRED,
                 Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
-        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, updated,
-                UPDATE_ORIGIN_APP);
+        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, updated);
 
         ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
                 .disallowAllSounds()
                 .allowCalls(PEOPLE_TYPE_STARRED)
                 .allowConversations(CONVERSATION_SENDERS_IMPORTANT)
                 .hideAllVisualEffects()
-                .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY)
+                .allowPriorityChannels(true)
                 .build();
         assertThat(mZenModeHelper.mConfig.automaticRules.values())
-                .comparingElementsUsing(IGNORE_TIMESTAMPS)
+                .comparingElementsUsing(IGNORE_METADATA)
                 .containsExactly(
                         expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                                 expectedZenPolicy, /* conditionActive= */ null));
     }
 
     @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    public void applyGlobalPolicyAsImplicitZenRule_ruleCustomized_doesNotUpdateRule() {
+        mZenModeHelper.mConfig.automaticRules.clear();
+        String pkg = mContext.getPackageName();
+
+        // From app, call "setNotificationPolicy" and create and implicit rule.
+        Policy originalPolicy = new Policy(PRIORITY_CATEGORY_MEDIA, 0, 0);
+        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy);
+        String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
+
+        // Store this for checking later.
+        ZenPolicy originalEffectiveZenPolicy = new ZenPolicy.Builder(
+                mZenModeHelper.mConfig.toZenPolicy()).allowMedia(true).build();
+
+        // From user, update that rule's policy.
+        AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+        ZenPolicy userUpdateZenPolicy = new ZenPolicy.Builder().disallowAllSounds()
+                .allowAlarms(true).build();
+        AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
+                .setZenPolicy(userUpdateZenPolicy)
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
+                Process.SYSTEM_UID);
+
+        // From app, call "setNotificationPolicy" again.
+        Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0);
+        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, appUpdatePolicy);
+
+        // The app's update was ignored, and the user's update is still current.
+        assertThat(mZenModeHelper.mConfig.automaticRules.values())
+                .comparingElementsUsing(IGNORE_METADATA)
+                .containsExactly(
+                        expectedImplicitRule(pkg, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                                // the final policy for the rule should contain the user's update
+                                // overlaid on top of the original existing policy.
+                                originalEffectiveZenPolicy.overwrittenWith(userUpdateZenPolicy),
+                                /* conditionActive= */ null));
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    public void applyGlobalPolicyAsImplicitZenRule_ruleCustomizedButNotZenPolicy_updatesRule() {
+        mZenModeHelper.mConfig.automaticRules.clear();
+        String pkg = mContext.getPackageName();
+
+        // From app, call "setNotificationPolicy" and create and implicit rule.
+        Policy originalPolicy = new Policy(PRIORITY_CATEGORY_MEDIA, 0, 0);
+        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy);
+        String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
+
+        // Store this for checking later.
+        ZenPolicy originalEffectiveZenPolicy = new ZenPolicy.Builder(
+                mZenModeHelper.mConfig.toZenPolicy()).allowMedia(true).build();
+
+        // From user, update something in that rule, but not the ZenPolicy.
+        AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+        AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
+                .setName("Rule renamed, not touching policy")
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
+                Process.SYSTEM_UID);
+
+        // From app, call "setNotificationPolicy" again.
+        Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0);
+        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, appUpdatePolicy);
+
+        // The app's update was applied.
+        ZenPolicy appsSecondZenPolicy = new ZenPolicy.Builder()
+                .disallowAllSounds()
+                .allowSystem(true)
+                .allowPriorityChannels(true)
+                .build();
+        assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenPolicy)
+                .isEqualTo(originalEffectiveZenPolicy.overwrittenWith(appsSecondZenPolicy));
+    }
+
+    @Test
     public void applyGlobalPolicyAsImplicitZenRule_flagOff_ignored() {
         mSetFlagsRule.disableFlags(android.app.Flags.FLAG_MODES_API);
         mZenModeHelper.mConfig.automaticRules.clear();
 
         withoutWtfCrash(
                 () -> mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME,
-                        CUSTOM_PKG_UID, new Policy(0, 0, 0), UPDATE_ORIGIN_APP));
+                        CUSTOM_PKG_UID, new Policy(0, 0, 0)));
 
         assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty();
     }
@@ -4427,7 +5516,7 @@
                 Policy.getAllSuppressedVisualEffects(), STATE_FALSE,
                 CONVERSATION_SENDERS_IMPORTANT);
         mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
-                writtenPolicy, UPDATE_ORIGIN_APP);
+                writtenPolicy);
 
         Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
                 CUSTOM_PKG_NAME);
@@ -4436,14 +5525,18 @@
     }
 
     @Test
-    public void getNotificationPolicyFromImplicitZenRule_ruleWithoutPolicy_returnsGlobalPolicy() {
+    public void getNotificationPolicyFromImplicitZenRule_ruleWithoutPolicy_copiesGlobalPolicy() {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
-
-        mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
-                ZEN_MODE_ALARMS);
         mZenModeHelper.mConfig.allowCalls = true;
         mZenModeHelper.mConfig.allowConversations = false;
 
+        // Implicit rule will get the global policy at the time of rule creation.
+        mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
+                ZEN_MODE_ALARMS);
+
+        // If the policy then changes afterwards, we should keep the snapshotted version.
+        mZenModeHelper.mConfig.allowCalls = false;
+
         Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
                 CUSTOM_PKG_NAME);
 
@@ -4467,7 +5560,7 @@
         assertThat(readPolicy.allowConversations()).isFalse();
     }
 
-    private static final Correspondence<ZenRule, ZenRule> IGNORE_TIMESTAMPS =
+    private static final Correspondence<ZenRule, ZenRule> IGNORE_METADATA =
             Correspondence.transforming(zr -> {
                 Parcel p = Parcel.obtain();
                 try {
@@ -4475,12 +5568,15 @@
                     p.setDataPosition(0);
                     ZenRule copy = new ZenRule(p);
                     copy.creationTime = 0;
+                    copy.userModifiedFields = 0;
+                    copy.zenPolicyUserModifiedFields = 0;
+                    copy.zenDeviceEffectsUserModifiedFields = 0;
                     return copy;
                 } finally {
                     p.recycle();
                 }
             },
-            "Ignoring timestamps");
+              "Ignoring timestamp and userModifiedFields");
 
     private ZenRule expectedImplicitRule(String ownerPkg, int zenMode, ZenPolicy policy,
             @Nullable Boolean conditionActive) {
@@ -4490,7 +5586,7 @@
         if (conditionActive != null) {
             rule.condition = conditionActive
                     ? new Condition(rule.conditionId,
-                            mContext.getString(R.string.zen_mode_implicit_activated), STATE_TRUE)
+                    mContext.getString(R.string.zen_mode_implicit_activated), STATE_TRUE)
                     : new Condition(rule.conditionId,
                             mContext.getString(R.string.zen_mode_implicit_deactivated),
                             STATE_FALSE);
@@ -4558,8 +5654,35 @@
         assertEquals(STATE_ALLOW, dndProto.getNotificationList().getNumber());
     }
 
+    private void checkDndProtoMatchesDefaultZenConfig(DNDPolicyProto dndProto) {
+        if (!Flags.modesApi()) {
+            checkDndProtoMatchesSetupZenConfig(dndProto);
+            return;
+        }
+
+        // When modes_api flag is on, the default zen config is the device defaults.
+        assertThat(dndProto.getAlarms().getNumber()).isEqualTo(STATE_ALLOW);
+        assertThat(dndProto.getMedia().getNumber()).isEqualTo(STATE_ALLOW);
+        assertThat(dndProto.getSystem().getNumber()).isEqualTo(STATE_DISALLOW);
+        assertThat(dndProto.getReminders().getNumber()).isEqualTo(STATE_DISALLOW);
+        assertThat(dndProto.getCalls().getNumber()).isEqualTo(STATE_ALLOW);
+        assertThat(dndProto.getAllowCallsFrom().getNumber()).isEqualTo(PEOPLE_STARRED);
+        assertThat(dndProto.getMessages().getNumber()).isEqualTo(STATE_ALLOW);
+        assertThat(dndProto.getAllowMessagesFrom().getNumber()).isEqualTo(PEOPLE_STARRED);
+        assertThat(dndProto.getEvents().getNumber()).isEqualTo(STATE_DISALLOW);
+        assertThat(dndProto.getRepeatCallers().getNumber()).isEqualTo(STATE_ALLOW);
+        assertThat(dndProto.getFullscreen().getNumber()).isEqualTo(STATE_DISALLOW);
+        assertThat(dndProto.getLights().getNumber()).isEqualTo(STATE_DISALLOW);
+        assertThat(dndProto.getPeek().getNumber()).isEqualTo(STATE_DISALLOW);
+        assertThat(dndProto.getStatusBar().getNumber()).isEqualTo(STATE_ALLOW);
+        assertThat(dndProto.getBadge().getNumber()).isEqualTo(STATE_ALLOW);
+        assertThat(dndProto.getAmbient().getNumber()).isEqualTo(STATE_DISALLOW);
+        assertThat(dndProto.getNotificationList().getNumber()).isEqualTo(STATE_ALLOW);
+    }
+
     private static void withoutWtfCrash(Runnable test) {
-        Log.TerribleFailureHandler oldHandler = Log.setWtfHandler((tag, what, system) -> {});
+        Log.TerribleFailureHandler oldHandler = Log.setWtfHandler((tag, what, system) -> {
+        });
         try {
             test.run();
         } finally {
@@ -4856,4 +5979,25 @@
             return parser.nextTag();
         }
     }
+
+    private static class TestClock extends SimpleClock {
+        private long mNowMillis = 441644400000L;
+
+        private TestClock() {
+            super(ZoneOffset.UTC);
+        }
+
+        @Override
+        public long millis() {
+            return mNowMillis;
+        }
+
+        private void setNowMillis(long millis) {
+            mNowMillis = millis;
+        }
+
+        private void advanceByMillis(long millis) {
+            mNowMillis += millis;
+        }
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
index 21c96d6..3a88294 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
@@ -213,13 +213,12 @@
         ZenPolicy unset = builder.build();
 
         // priority channels allowed
-        builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+        builder.allowPriorityChannels(true);
         ZenPolicy channelsPriority = builder.build();
 
         // unset applied, channels setting keeps its state
         channelsPriority.apply(unset);
-        assertThat(channelsPriority.getAllowedChannels())
-                .isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+        assertThat(channelsPriority.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_ALLOW);
     }
 
     @Test
@@ -227,15 +226,15 @@
         mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
 
         ZenPolicy.Builder builder = new ZenPolicy.Builder();
-        builder.allowChannels(ZenPolicy.CHANNEL_TYPE_NONE);
+        builder.allowPriorityChannels(false);
         ZenPolicy none = builder.build();
 
-        builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+        builder.allowPriorityChannels(true);
         ZenPolicy priority = builder.build();
 
         // priority channels (less strict state) cannot override a setting that sets it to none
         none.apply(priority);
-        assertThat(none.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_NONE);
+        assertThat(none.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_DISALLOW);
     }
 
     @Test
@@ -243,15 +242,15 @@
         mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
 
         ZenPolicy.Builder builder = new ZenPolicy.Builder();
-        builder.allowChannels(ZenPolicy.CHANNEL_TYPE_NONE);
+        builder.allowPriorityChannels(false);
         ZenPolicy none = builder.build();
 
-        builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+        builder.allowPriorityChannels(true);
         ZenPolicy priority = builder.build();
 
         // applying a policy with channelType=none overrides priority setting
         priority.apply(none);
-        assertThat(priority.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_NONE);
+        assertThat(priority.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_DISALLOW);
     }
 
     @Test
@@ -261,12 +260,84 @@
         ZenPolicy.Builder builder = new ZenPolicy.Builder();
         ZenPolicy unset = builder.build();
 
-        builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+        builder.allowPriorityChannels(true);
         ZenPolicy priority = builder.build();
 
         // applying a policy with a set channel type actually goes through
         unset.apply(priority);
-        assertThat(unset.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+        assertThat(unset.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_ALLOW);
+    }
+
+    @Test
+    public void testZenPolicyOverwrite_allUnsetPolicies() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy unset = builder.build();
+
+        builder.allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS);
+        builder.allowMedia(false);
+        builder.allowEvents(true);
+        builder.showFullScreenIntent(false);
+        builder.showInNotificationList(false);
+        ZenPolicy set = builder.build();
+
+        ZenPolicy overwritten = set.overwrittenWith(unset);
+        assertThat(overwritten).isEqualTo(set);
+
+        // should actually work the other way too.
+        ZenPolicy overwrittenWithSet = unset.overwrittenWith(set);
+        assertThat(overwrittenWithSet).isEqualTo(set);
+    }
+
+    @Test
+    public void testZenPolicyOverwrite_someOverlappingFields_takeNewPolicy() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+
+        ZenPolicy p1 = new ZenPolicy.Builder()
+                .allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS)
+                .allowMessages(ZenPolicy.PEOPLE_TYPE_STARRED)
+                .allowMedia(false)
+                .showBadges(true)
+                .build();
+
+        ZenPolicy p2 = new ZenPolicy.Builder()
+                .allowRepeatCallers(false)
+                .allowConversations(ZenPolicy.CONVERSATION_SENDERS_IMPORTANT)
+                .allowMessages(ZenPolicy.PEOPLE_TYPE_NONE)
+                .showBadges(false)
+                .showPeeking(true)
+                .build();
+
+        // when p1 is overwritten with p2, all values from p2 win regardless of strictness, and
+        // remaining fields take values from p1.
+        ZenPolicy p1OverwrittenWithP2 = p1.overwrittenWith(p2);
+        assertThat(p1OverwrittenWithP2.getPriorityCallSenders())
+                .isEqualTo(ZenPolicy.PEOPLE_TYPE_CONTACTS);  // from p1
+        assertThat(p1OverwrittenWithP2.getPriorityMessageSenders())
+                .isEqualTo(ZenPolicy.PEOPLE_TYPE_NONE);  // from p2
+        assertThat(p1OverwrittenWithP2.getPriorityCategoryRepeatCallers())
+                .isEqualTo(ZenPolicy.STATE_DISALLOW);  // from p2
+        assertThat(p1OverwrittenWithP2.getPriorityCategoryMedia())
+                .isEqualTo(ZenPolicy.STATE_DISALLOW);  // from p1
+        assertThat(p1OverwrittenWithP2.getVisualEffectBadge())
+                .isEqualTo(ZenPolicy.STATE_DISALLOW);  // from p2
+        assertThat(p1OverwrittenWithP2.getVisualEffectPeek())
+                .isEqualTo(ZenPolicy.STATE_ALLOW); // from p2
+
+        ZenPolicy p2OverwrittenWithP1 = p2.overwrittenWith(p1);
+        assertThat(p2OverwrittenWithP1.getPriorityCallSenders())
+                .isEqualTo(ZenPolicy.PEOPLE_TYPE_CONTACTS);  // from p1
+        assertThat(p2OverwrittenWithP1.getPriorityMessageSenders())
+                .isEqualTo(ZenPolicy.PEOPLE_TYPE_STARRED);  // from p1
+        assertThat(p2OverwrittenWithP1.getPriorityCategoryRepeatCallers())
+                .isEqualTo(ZenPolicy.STATE_DISALLOW);  // from p2
+        assertThat(p2OverwrittenWithP1.getPriorityCategoryMedia())
+                .isEqualTo(ZenPolicy.STATE_DISALLOW);  // from p1
+        assertThat(p2OverwrittenWithP1.getVisualEffectBadge())
+                .isEqualTo(ZenPolicy.STATE_ALLOW);  // from p1
+        assertThat(p2OverwrittenWithP1.getVisualEffectPeek())
+                .isEqualTo(ZenPolicy.STATE_ALLOW); // from p2
     }
 
     @Test
@@ -308,7 +379,7 @@
         ZenPolicy.Builder builder = new ZenPolicy.Builder();
 
         ZenPolicy policy = builder.build();
-        assertThat(policy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_UNSET);
+        assertThat(policy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_UNSET);
     }
 
     @Test
@@ -622,10 +693,10 @@
 
         // allowChannels should be unset, not be modifiable, and not show up in any output
         ZenPolicy.Builder builder = new ZenPolicy.Builder();
-        builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+        builder.allowPriorityChannels(true);
         ZenPolicy policy = builder.build();
 
-        assertThat(policy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_UNSET);
+        assertThat(policy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_UNSET);
         assertThat(policy.toString().contains("allowChannels")).isFalse();
     }
 
@@ -635,40 +706,14 @@
 
         // allow priority channels
         ZenPolicy.Builder builder = new ZenPolicy.Builder();
-        builder.allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+        builder.allowPriorityChannels(true);
         ZenPolicy policy = builder.build();
-        assertThat(policy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+        assertThat(policy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_ALLOW);
 
         // disallow priority channels
-        builder.allowChannels(ZenPolicy.CHANNEL_TYPE_NONE);
+        builder.allowPriorityChannels(false);
         policy = builder.build();
-        assertThat(policy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_NONE);
-    }
-
-    @Test
-    public void testFromParcel() {
-        ZenPolicy.Builder builder = new ZenPolicy.Builder();
-        builder.setUserModifiedFields(10);
-
-        ZenPolicy policy = builder.build();
-        assertThat(policy.getUserModifiedFields()).isEqualTo(10);
-
-        Parcel parcel = Parcel.obtain();
-        policy.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-
-        ZenPolicy fromParcel = ZenPolicy.CREATOR.createFromParcel(parcel);
-        assertThat(fromParcel.getUserModifiedFields()).isEqualTo(10);
-    }
-
-    @Test
-    public void testPolicy_userModifiedFields() {
-        ZenPolicy.Builder builder = new ZenPolicy.Builder();
-        builder.setUserModifiedFields(10);
-        assertThat(builder.build().getUserModifiedFields()).isEqualTo(10);
-
-        builder.setUserModifiedFields(0);
-        assertThat(builder.build().getUserModifiedFields()).isEqualTo(0);
+        assertThat(policy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_DISALLOW);
     }
 
     @Test
@@ -676,8 +721,8 @@
         ZenPolicy.Builder builder = new ZenPolicy.Builder();
         ZenPolicy policy = builder.allowRepeatCallers(true).allowAlarms(false)
                 .showLights(true).showBadges(false)
-                .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY)
-                .setUserModifiedFields(20).build();
+                .allowPriorityChannels(true)
+                .build();
 
         ZenPolicy newPolicy = new ZenPolicy.Builder(policy).build();
 
@@ -689,8 +734,7 @@
         assertThat(newPolicy.getVisualEffectBadge()).isEqualTo(ZenPolicy.STATE_DISALLOW);
         assertThat(newPolicy.getVisualEffectPeek()).isEqualTo(ZenPolicy.STATE_UNSET);
 
-        assertThat(newPolicy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY);
-        assertThat(newPolicy.getUserModifiedFields()).isEqualTo(20);
+        assertThat(newPolicy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_ALLOW);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 7c2f7ee..c8abd8d 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -166,6 +166,7 @@
 
     @Mock
     private PhoneWindowManager.ButtonOverridePermissionChecker mButtonOverridePermissionChecker;
+    @Mock private WindowWakeUpPolicy mWindowWakeUpPolicy;
 
     @Mock private IBinder mInputToken;
     @Mock private IBinder mImeTargetWindowToken;
@@ -230,6 +231,10 @@
         TalkbackShortcutController getTalkbackShortcutController() {
             return new TestTalkbackShortcutController(mContext);
         }
+
+        WindowWakeUpPolicy getWindowWakeUpPolicy() {
+            return mWindowWakeUpPolicy;
+        }
     }
 
     TestPhoneWindowManager(Context context, boolean supportSettingsUpdate) {
@@ -620,7 +625,8 @@
 
     void assertPowerWakeUp() {
         mTestLooper.dispatchAll();
-        verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
+        verify(mWindowWakeUpPolicy)
+                .wakeUpFromKey(anyLong(), eq(KeyEvent.KEYCODE_POWER), anyBoolean());
     }
 
     void assertNoPowerSleep() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
index 6e5baee..37e0818 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
@@ -46,6 +46,7 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.platform.test.annotations.Presubmit;
 import android.util.Log;
 import android.util.Rational;
@@ -63,6 +64,7 @@
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Build/Install/Run:
@@ -119,6 +121,29 @@
     }
 
     @Test
+    public void testAbortListenerCalled() {
+        AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+        ActivityOptions options = ActivityOptions.makeBasic();
+        options.setOnAnimationAbortListener(new IRemoteCallback.Stub() {
+            @Override
+            public void sendResult(Bundle data) {
+                callbackCalled.set(true);
+            }
+        });
+
+        // Verify that the callback is called on abort
+        options.abort();
+        assertTrue(callbackCalled.get());
+
+        // Verify that the callback survives saving to bundle
+        ActivityOptions optionsCopy = ActivityOptions.fromBundle(options.toBundle());
+        callbackCalled.set(false);
+        optionsCopy.abort();
+        assertTrue(callbackCalled.get());
+    }
+
+    @Test
     public void testTransferLaunchCookie() {
         final Binder cookie = new Binder();
         final ActivityOptions options = ActivityOptions.makeBasic();
@@ -279,7 +304,9 @@
                 case "android.activity.pendingIntentCreatorBackgroundActivityStartMode":
                     // KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE
                 case "android.activity.launchCookie": // KEY_LAUNCH_COOKIE
+                case "android:activity.animAbortListener": // KEY_ANIM_ABORT_LISTENER
                     // Existing keys
+
                     break;
                 default:
                     unknownKeys.add(key);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index f5282cb..31d6fa3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1762,32 +1762,6 @@
         assertEquals(1, task.getChildCount());
     }
 
-    /**
-     * Test that an activity will not be destroyed if it is marked as non-destroyable.
-     */
-    @Test
-    public void testSafelyDestroy_nonDestroyable() {
-        final ActivityRecord activity = createActivityWithTask();
-        doReturn(false).when(activity).isDestroyable();
-
-        activity.safelyDestroy("test");
-
-        verify(activity, never()).destroyImmediately(anyString());
-    }
-
-    /**
-     * Test that an activity will not be destroyed if it is marked as non-destroyable.
-     */
-    @Test
-    public void testSafelyDestroy_destroyable() {
-        final ActivityRecord activity = createActivityWithTask();
-        doReturn(true).when(activity).isDestroyable();
-
-        activity.safelyDestroy("test");
-
-        verify(activity).destroyImmediately(anyString());
-    }
-
     @Test
     public void testRemoveImmediately() {
         final Consumer<Consumer<ActivityRecord>> test = setup -> {
@@ -3309,7 +3283,7 @@
         // keyguard to back to the app, expect IME insets is not frozen
         app.mActivityRecord.commitVisibility(true, false);
         mDisplayContent.updateImeInputAndControlTarget(app);
-        mDisplayContent.mWmService.mRoot.performSurfacePlacement();
+        performSurfacePlacementAndWaitForWindowAnimator();
 
         assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
 
@@ -3358,14 +3332,14 @@
         mDisplayContent.setImeLayeringTarget(app2);
         app2.mActivityRecord.commitVisibility(true, false);
         mDisplayContent.updateImeInputAndControlTarget(app2);
-        mDisplayContent.mWmService.mRoot.performSurfacePlacement();
+        performSurfacePlacementAndWaitForWindowAnimator();
 
         // Verify after unfreezing app2's IME insets state, we won't dispatch visible IME insets
         // to client if the app didn't request IME visible.
         assertFalse(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput);
 
         if (Flags.bundleClientTransactionFlag()) {
-            verify(app2.getProcess()).scheduleClientTransactionItem(
+            verify(app2.getProcess(), atLeastOnce()).scheduleClientTransactionItem(
                     isA(WindowStateResizeItem.class));
         } else {
             verify(app2.mClient, atLeastOnce()).resized(any(), anyBoolean(), any(),
@@ -3412,7 +3386,7 @@
         // frozen until the input started.
         mDisplayContent.setImeLayeringTarget(app1);
         mDisplayContent.updateImeInputAndControlTarget(app1);
-        mDisplayContent.mWmService.mRoot.performSurfacePlacement();
+        performSurfacePlacementAndWaitForWindowAnimator();
 
         assertEquals(app1, mDisplayContent.getImeInputTarget());
         assertFalse(activity1.mImeInsetsFrozenUntilStartInput);
@@ -3748,6 +3722,68 @@
         assertFalse(ar.moveFocusableActivityToTop("test"));
     }
 
+    @Test
+    public void testPauseConfigDispatch() throws RemoteException {
+        final Task task = new TaskBuilder(mSupervisor)
+                .setDisplay(mDisplayContent).setCreateActivity(true).build();
+        final ActivityRecord activity = task.getTopNonFinishingActivity();
+        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
+                TYPE_BASE_APPLICATION);
+        attrs.setTitle("AppWindow");
+        final TestWindowState appWindow = createWindowState(attrs, activity);
+        activity.addWindow(appWindow);
+
+        clearInvocations(mClientLifecycleManager);
+        clearInvocations(activity);
+
+        Configuration ro = activity.getRequestedOverrideConfiguration();
+        ro.windowConfiguration.setBounds(new Rect(20, 0, 120, 200));
+        activity.onRequestedOverrideConfigurationChanged(ro);
+        activity.ensureActivityConfiguration();
+        mWm.mRoot.performSurfacePlacement();
+
+        // policy will center the bounds, so just check for matching size here.
+        assertEquals(100, activity.getWindowConfiguration().getBounds().width());
+        assertEquals(100, appWindow.getWindowConfiguration().getBounds().width());
+        // No scheduled transactions since it asked for a restart.
+        verify(mClientLifecycleManager, times(1)).scheduleTransaction(any());
+        verify(activity, times(1)).setLastReportedConfiguration(any(), any());
+        assertTrue(appWindow.mResizeReported);
+
+        // act like everything drew and went idle
+        appWindow.mResizeReported = false;
+        makeLastConfigReportedToClient(appWindow, true);
+
+        // Now pause dispatch and try to resize
+        activity.pauseConfigurationDispatch();
+
+        ro.windowConfiguration.setBounds(new Rect(20, 0, 150, 200));
+        activity.onRequestedOverrideConfigurationChanged(ro);
+        activity.ensureActivityConfiguration();
+        mWm.mRoot.performSurfacePlacement();
+
+        // Activity should get new config (core-side)
+        assertEquals(130, activity.getWindowConfiguration().getBounds().width());
+        // But windows should not get new config.
+        assertEquals(100, appWindow.getWindowConfiguration().getBounds().width());
+        // The client shouldn't receive any changes
+        verify(mClientLifecycleManager, times(1)).scheduleTransaction(any());
+        // and lastReported shouldn't be set.
+        verify(activity, times(1)).setLastReportedConfiguration(any(), any());
+        // There should be no resize reported to client.
+        assertFalse(appWindow.mResizeReported);
+
+        // Now resume dispatch
+        activity.resumeConfigurationDispatch();
+        mWm.mRoot.performSurfacePlacement();
+
+        // Windows and client should now receive updates
+        verify(activity, times(2)).setLastReportedConfiguration(any(), any());
+        verify(mClientLifecycleManager, times(2)).scheduleTransaction(any());
+        assertEquals(130, appWindow.getWindowConfiguration().getBounds().width());
+        assertTrue(appWindow.mResizeReported);
+    }
+
     private ICompatCameraControlCallback getCompatCameraControlCallback() {
         return new ICompatCameraControlCallback.Stub() {
             @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
index 98f1843..03d3029 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
@@ -17,13 +17,31 @@
 package com.android.server.wm;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 
+import android.content.ComponentName;
+import android.graphics.ColorSpace;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
 import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
+import android.view.Surface;
+import android.window.TaskSnapshot;
 
 import androidx.test.filters.SmallTest;
 
@@ -32,6 +50,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 
 /**
  * Test class for {@link ActivitySnapshotController}.
@@ -45,6 +64,7 @@
 public class ActivitySnapshotControllerTests extends WindowTestsBase {
 
     private ActivitySnapshotController mActivitySnapshotController;
+
     @Before
     public void setUp() throws Exception {
         spyOn(mWm.mSnapshotController.mActivitySnapshotController);
@@ -154,4 +174,90 @@
         assertEquals(openingWindowBelow.mActivityRecord,
                 mActivitySnapshotController.mPendingLoadActivity.valueAt(0));
     }
+
+    /**
+     * Simulate multiple TaskFragments inside a task.
+     */
+    @Test
+    public void testMultipleActivitiesLoadSnapshot() {
+        final Task testTask = createTask(mDisplayContent);
+        final ActivityRecord activityA = createActivityRecord(testTask);
+        final ActivityRecord activityB = createActivityRecord(testTask);
+        final ActivityRecord activityC = createActivityRecord(testTask);
+        final TaskSnapshot taskSnapshot = createSnapshot();
+
+        final int[] mixedCode = new int[3];
+        mixedCode[0] = ActivitySnapshotController.getSystemHashCode(activityA);
+        mixedCode[1] = ActivitySnapshotController.getSystemHashCode(activityB);
+        mixedCode[2] = ActivitySnapshotController.getSystemHashCode(activityC);
+
+        mActivitySnapshotController.addUserSavedFile(testTask.mUserId, taskSnapshot, mixedCode);
+        mActivitySnapshotController.mCache.putSnapshot(activityA, taskSnapshot);
+        mActivitySnapshotController.mCache.putSnapshot(activityB, taskSnapshot);
+        mActivitySnapshotController.mCache.putSnapshot(activityC, taskSnapshot);
+
+        assertTrue(mActivitySnapshotController.hasRecord(activityA));
+        assertTrue(mActivitySnapshotController.hasRecord(activityB));
+
+        // If A is removed, B and C should also be removed because they share the same snapshot.
+        mActivitySnapshotController.onAppRemoved(activityA);
+        assertFalse(mActivitySnapshotController.hasRecord(activityA));
+        assertFalse(mActivitySnapshotController.hasRecord(activityB));
+        final ActivityRecord[] singleActivityList = new ActivityRecord[1];
+        singleActivityList[0] = activityA;
+        assertNull(mActivitySnapshotController.getSnapshot(singleActivityList));
+        singleActivityList[0] = activityB;
+        assertNull(mActivitySnapshotController.getSnapshot(singleActivityList));
+        final ActivityRecord[] activities = new ActivityRecord[3];
+        activities[0] = activityA;
+        activities[1] = activityB;
+        activities[2] = activityC;
+        assertNull(mActivitySnapshotController.getSnapshot(activities));
+
+        // Reset and test load snapshot
+        mActivitySnapshotController.addUserSavedFile(testTask.mUserId, taskSnapshot, mixedCode);
+        // Request to load by B, nothing will be loaded because the snapshot was [A,B,C].
+        mActivitySnapshotController.mPendingLoadActivity.add(activityB);
+        mActivitySnapshotController.loadActivitySnapshot();
+        verify(mActivitySnapshotController, never()).loadSnapshotInner(any(), any());
+
+        // Able to load snapshot when requesting for all A, B, C
+        mActivitySnapshotController.mPendingLoadActivity.clear();
+        mActivitySnapshotController.mPendingLoadActivity.add(activityA);
+        mActivitySnapshotController.mPendingLoadActivity.add(activityB);
+        mActivitySnapshotController.mPendingLoadActivity.add(activityC);
+        final ArraySet<ActivityRecord> verifyList = new ArraySet<>();
+        verifyList.add(activityA);
+        verifyList.add(activityB);
+        verifyList.add(activityC);
+        mActivitySnapshotController.loadActivitySnapshot();
+        verify(mActivitySnapshotController).loadSnapshotInner(argThat(
+                argument -> {
+                    final ArrayList<ActivityRecord> argumentList = new ArrayList<>(
+                            Arrays.asList(argument));
+                    return verifyList.containsAll(argumentList)
+                            && argumentList.containsAll(verifyList);
+                }),
+                any());
+
+        for (int i = activities.length - 1; i >= 0; --i) {
+            mActivitySnapshotController.mCache.putSnapshot(activities[i], taskSnapshot);
+        }
+        // The loaded snapshot can be retrieved only if the activities match exactly.
+        singleActivityList[0] = activityB;
+        assertNull(mActivitySnapshotController.getSnapshot(singleActivityList));
+        assertEquals(taskSnapshot, mActivitySnapshotController.getSnapshot(activities));
+    }
+
+    private TaskSnapshot createSnapshot() {
+        HardwareBuffer buffer = mock(HardwareBuffer.class);
+        doReturn(100).when(buffer).getWidth();
+        doReturn(100).when(buffer).getHeight();
+        return new TaskSnapshot(1, 0 /* captureTime */, new ComponentName("", ""), buffer,
+                ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT,
+                Surface.ROTATION_0, new Point(100, 100), new Rect() /* contentInsets */,
+                new Rect() /* letterboxInsets*/, false /* isLowResolution */,
+                true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, 0 /* mSystemUiVisibility */,
+                false /* isTranslucent */, false /* hasImeSurface */);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index c82f751..29faed1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -132,8 +132,10 @@
 import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -425,6 +427,12 @@
         doNothing().when(mMockPackageManager).notifyPackageUse(anyString(), anyInt());
         doReturn(mock(PackageArchiver.class)).when(mMockPackageManager).getPackageArchiver();
 
+        final AndroidPackage mockPackage = mock(AndroidPackage.class);
+        final SigningDetails signingDetails = mock(SigningDetails.class);
+        doReturn(mockPackage).when(mMockPackageManager).getPackage(anyInt());
+        doReturn(signingDetails).when(mockPackage).getSigningDetails();
+        doReturn(false).when(signingDetails).hasAncestorOrSelfWithDigest(any());
+
         final Intent intent = new Intent();
         intent.addFlags(launchFlags);
         intent.setComponent(ActivityBuilder.getDefaultComponent());
@@ -557,6 +565,86 @@
         return Pair.create(splitPrimaryActivity, splitSecondActivity);
     }
 
+    /**
+     * This test ensures that if the intent is being delivered to a desktop mode unfocused task
+     * while it is already on top, reports it as delivering to top.
+     */
+    @Test
+    public void testDesktopModeDeliverToTop() {
+        final ActivityStarter starter = prepareStarter(
+                FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | FLAG_ACTIVITY_SINGLE_TOP,
+                false /* mockGetRootTask */);
+        final List<ActivityRecord> activities = createActivitiesInDesktopMode();
+
+        // Set focus back to the first task.
+        activities.get(0).moveFocusableActivityToTop("testDesktopModeDeliverToTop");
+
+        // Start activity and delivered new intent.
+        starter.getIntent().setComponent(activities.get(3).mActivityComponent);
+        doReturn(activities.get(3)).when(mRootWindowContainer).findTask(any(), any());
+        final int result = starter.setReason("testDesktopModeDeliverToTop").execute();
+
+        // Ensure result is delivering intent to top.
+        assertEquals(START_DELIVERED_TO_TOP, result);
+    }
+
+    /**
+     * This test ensures that if the intent is being delivered to a desktop mode unfocused task
+     * reports it is brought to front instead of delivering to top.
+     */
+    @Test
+    public void testDesktopModeTaskToFront() {
+        final ActivityStarter starter = prepareStarter(
+                FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | FLAG_ACTIVITY_SINGLE_TOP, false);
+        final List<ActivityRecord> activities = createActivitiesInDesktopMode();
+        final ActivityRecord desktopModeFocusActivity = activities.get(0);
+        final ActivityRecord desktopModeReusableActivity = activities.get(1);
+        final ActivityRecord desktopModeTopActivity = new ActivityBuilder(mAtm).setCreateTask(true)
+                .setParentTask(desktopModeReusableActivity.getRootTask()).build();
+        assertTrue(desktopModeTopActivity.inMultiWindowMode());
+
+        // Let first stack has focus.
+        desktopModeFocusActivity.moveFocusableActivityToTop("testDesktopModeTaskToFront");
+
+        // Start activity and delivered new intent.
+        starter.getIntent().setComponent(desktopModeReusableActivity.mActivityComponent);
+        doReturn(desktopModeReusableActivity).when(mRootWindowContainer).findTask(any(), any());
+        final int result = starter.setReason("testDesktopModeMoveToFront").execute();
+
+        // Ensure result is moving task to front.
+        assertEquals(START_TASK_TO_FRONT, result);
+    }
+
+    /** Returns 4 activities. */
+    private List<ActivityRecord> createActivitiesInDesktopMode() {
+        final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
+        List<ActivityRecord> activityRecords = new ArrayList<>();
+
+        for (int i = 0; i < 4; i++) {
+            Rect bounds = new Rect(desktopOrganizer.getDefaultDesktopTaskBounds());
+            bounds.offset(20 * i, 20 * i);
+            desktopOrganizer.createTask(bounds);
+        }
+
+        for (int i = 0; i < 4; i++) {
+            activityRecords.add(new TaskBuilder(mSupervisor)
+                    .setParentTask(desktopOrganizer.mTasks.get(i))
+                    .setCreateActivity(true)
+                    .build()
+                    .getTopMostActivity());
+        }
+
+        for (int i = 0; i < 4; i++) {
+            activityRecords.get(i).setVisibleRequested(true);
+        }
+
+        for (int i = 0; i < 4; i++) {
+            assertEquals(desktopOrganizer.mTasks.get(i), activityRecords.get(i).getRootTask());
+        }
+
+        return activityRecords;
+    }
+
     @Test
     public void testMoveVisibleTaskToFront() {
         final ActivityRecord activity = new TaskBuilder(mSupervisor)
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index d83824a..c44be7b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -56,6 +56,7 @@
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.util.ArraySet;
 import android.view.WindowManager;
 import android.window.BackAnimationAdapter;
@@ -69,6 +70,7 @@
 import android.window.WindowOnBackInvokedDispatcher;
 
 import com.android.server.LocalServices;
+import com.android.window.flags.Flags;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -81,6 +83,12 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
+/**
+ * Tests for the {@link BackNavigationController} class.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:BackNavigationControllerTests
+ */
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class BackNavigationControllerTests extends WindowTestsBase {
@@ -623,6 +631,22 @@
                 0, navigationObserver.getCount());
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG)
+    public void testAdjacentFocusInActivityEmbedding() {
+        Task task = createTask(mDefaultDisplay);
+        TaskFragment primary = createTaskFragmentWithActivity(task);
+        TaskFragment secondary = createTaskFragmentWithActivity(task);
+        primary.setAdjacentTaskFragment(secondary);
+        secondary.setAdjacentTaskFragment(primary);
+
+        WindowState windowState = mock(WindowState.class);
+        doReturn(windowState).when(mWm).getFocusedWindowLocked();
+        doReturn(primary).when(windowState).getTaskFragment();
+
+        startBackNavigation();
+        verify(mWm).moveFocusToAdjacentWindow(any(), anyInt());
+    }
 
     /**
      * Test with
@@ -742,7 +766,7 @@
 
         MockitoSession mockitoSession = mockitoSession().mockStatic(BackNavigationController.class)
                 .strictness(Strictness.LENIENT).startMocking();
-        doReturn(taskSnapshot).when(() -> BackNavigationController.getSnapshot(any()));
+        doReturn(taskSnapshot).when(() -> BackNavigationController.getSnapshot(any(), any()));
         when(resourcesSpy.getBoolean(
                 com.android.internal.R.bool.config_predictShowStartingSurface))
                 .thenReturn(preferWindowlessSurface);
diff --git a/services/tests/wmtests/src/com/android/server/wm/CompatModePackagesTests.java b/services/tests/wmtests/src/com/android/server/wm/CompatModePackagesTests.java
index c187263..9137594 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CompatModePackagesTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CompatModePackagesTests.java
@@ -18,6 +18,7 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.server.wm.CompatScaleProvider.COMPAT_SCALE_MODE_GAME;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -27,6 +28,8 @@
 
 import android.app.GameManagerInternal;
 import android.content.pm.ApplicationInfo;
+import android.content.res.CompatibilityInfo.CompatScale;
+import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
@@ -55,6 +58,17 @@
     public void setUp() {
         mAtm = mSystemServicesTestRule.getActivityTaskManagerService();
         mGm = mock(GameManagerInternal.class);
+        mAtm.registerCompatScaleProvider(COMPAT_SCALE_MODE_GAME, new CompatScaleProvider() {
+            @Override
+            public CompatScale getCompatScale(String packageName, int uid) {
+                int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+                float scalingFactor = mGm.getResolutionScalingFactor(packageName, userId);
+                if (scalingFactor > 0) {
+                    return new CompatScale(1f / scalingFactor);
+                }
+                return null;
+            }
+        });
     }
 
     @After
@@ -67,7 +81,7 @@
         LocalServices.addService(GameManagerInternal.class, mGm);
         float scale = 0.25f;
         doReturn(scale).when(mGm).getResolutionScalingFactor(anyString(), anyInt());
-        assertEquals(mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID), 1 / scale,
+        assertEquals(1 / scale, mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID),
                 0.01f);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 782d89c..95850ac 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -2131,8 +2131,8 @@
         // Once transition starts, rotation is applied and transition shows DC rotating.
         testPlayer.startTransition();
         waitUntilHandlersIdle();
-        verify(activity1).ensureActivityConfiguration(anyBoolean(), anyBoolean());
-        verify(activity2).ensureActivityConfiguration(anyBoolean(), anyBoolean());
+        verify(activity1).ensureActivityConfiguration(anyBoolean());
+        verify(activity2).ensureActivityConfiguration(anyBoolean());
         assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation());
         assertNotNull(testPlayer.mLastReady);
         assertTrue(testPlayer.mController.isPlaying());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index be96e60..9e00f92 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -283,12 +283,12 @@
 
         policy.screenTurnedOff();
         policy.setAwake(false);
-        policy.screenTurnedOn(null /* screenOnListener */);
+        policy.screenTurningOn(null /* screenOnListener */);
         assertTrue(wpc.isShowingUiWhileDozing());
         policy.screenTurnedOff();
         assertFalse(wpc.isShowingUiWhileDozing());
 
-        policy.screenTurnedOn(null /* screenOnListener */);
+        policy.screenTurningOn(null /* screenOnListener */);
         assertTrue(wpc.isShowingUiWhileDozing());
         policy.setAwake(true);
         assertFalse(wpc.isShowingUiWhileDozing());
diff --git a/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java b/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java
index 71dbc57..2986372 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertTrue;
 
 import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
 
 import androidx.test.filters.SmallTest;
 
@@ -28,7 +29,6 @@
 import org.junit.After;
 import org.junit.Test;
 
-import java.util.Collections;
 import java.util.Set;
 
 /**
@@ -52,18 +52,21 @@
 
     @After
     public void tearDown() {
-        mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        mSensitiveContentPackages.clearBlockedApps();
     }
 
     @Test
-    public void setShouldBlockScreenCaptureForApp() {
-        Set<PackageInfo> blockedApps =
+    public void addBlockScreenCaptureForApps() {
+        ArraySet<PackageInfo> blockedApps = new ArraySet(
                 Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
                         new PackageInfo(APP_PKG_1, APP_UID_2),
                         new PackageInfo(APP_PKG_2, APP_UID_1),
-                        new PackageInfo(APP_PKG_2, APP_UID_2));
+                        new PackageInfo(APP_PKG_2, APP_UID_2)
+                ));
 
-        mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedApps);
+        boolean modified = mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+
+        assertTrue(modified);
 
         assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
         assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
@@ -79,15 +82,93 @@
     }
 
     @Test
-    public void setShouldBlockScreenCaptureForApp_empty() {
-        Set<PackageInfo> blockedApps =
+    public void addBlockScreenCaptureForApps_addedTwice() {
+        ArraySet<PackageInfo> blockedApps = new ArraySet(
                 Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
                         new PackageInfo(APP_PKG_1, APP_UID_2),
                         new PackageInfo(APP_PKG_2, APP_UID_1),
-                        new PackageInfo(APP_PKG_2, APP_UID_2));
+                        new PackageInfo(APP_PKG_2, APP_UID_2)
+                ));
 
-        mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedApps);
-        mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+        boolean modified = mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+
+        assertFalse(modified);
+
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
+
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
+
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+    }
+
+    @Test
+    public void addBlockScreenCaptureForApps_withPartialPreviousPackages() {
+        ArraySet<PackageInfo> blockedApps = new ArraySet(
+                Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
+                        new PackageInfo(APP_PKG_1, APP_UID_2),
+                        new PackageInfo(APP_PKG_2, APP_UID_1),
+                        new PackageInfo(APP_PKG_2, APP_UID_2)
+                ));
+
+        mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+        boolean modified = mSensitiveContentPackages
+                .addBlockScreenCaptureForApps(
+                        new ArraySet(Set.of(new PackageInfo(APP_PKG_3, APP_UID_1))));
+
+        assertTrue(modified);
+
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
+
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
+
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+    }
+
+    @Test
+    public void clearBlockedApps() {
+        ArraySet<PackageInfo> blockedApps = new ArraySet(
+                Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
+                        new PackageInfo(APP_PKG_1, APP_UID_2),
+                        new PackageInfo(APP_PKG_2, APP_UID_1),
+                        new PackageInfo(APP_PKG_2, APP_UID_2)
+                ));
+
+        mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+        boolean modified = mSensitiveContentPackages.clearBlockedApps();
+
+        assertTrue(modified);
+
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
+
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
+
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+    }
+
+    @Test
+    public void clearBlockedApps_alreadyEmpty() {
+        boolean modified = mSensitiveContentPackages.clearBlockedApps();
+
+        assertFalse(modified);
 
         assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
         assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 5518c60..0c1a9c3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -197,10 +197,10 @@
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         // Translucent Activity
         final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setActivityTheme(android.R.style.Theme_Translucent)
                 .setLaunchedFromUid(mActivity.getUid())
                 .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
                 .build();
-        doReturn(false).when(translucentActivity).fillsParent();
         mTask.addChild(translucentActivity);
 
         translucentActivity.setState(DESTROYED, "testing");
@@ -225,10 +225,10 @@
 
         // Translucent Activity
         final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setActivityTheme(android.R.style.Theme_Translucent)
                 .setLaunchedFromUid(mActivity.getUid())
                 .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
                 .build();
-        doReturn(false).when(translucentActivity).fillsParent();
         mTask.addChild(translucentActivity);
 
         spyOn(translucentActivity.mLetterboxUiController);
@@ -300,10 +300,10 @@
 
         // Translucent Activity
         final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setActivityTheme(android.R.style.Theme_Translucent)
                 .setLaunchedFromUid(mActivity.getUid())
                 .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
                 .build();
-        doReturn(false).when(translucentActivity).fillsParent();
         mTask.addChild(translucentActivity);
 
         spyOn(translucentActivity.mLetterboxUiController);
@@ -376,10 +376,10 @@
 
         // Launch translucent Activity
         final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setActivityTheme(android.R.style.Theme_Translucent)
                 .setLaunchedFromUid(mActivity.getUid())
                 .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
                 .build();
-        doReturn(false).when(translucentActivity).fillsParent();
         mTask.addChild(translucentActivity);
         // Transparent strategy applied
         assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior());
@@ -404,10 +404,10 @@
 
         // Launch translucent Activity
         final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setActivityTheme(android.R.style.Theme_Translucent)
                 .setLaunchedFromUid(mActivity.getUid())
                 .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
                 .build();
-        doReturn(false).when(translucentActivity).fillsParent();
         mTask.addChild(translucentActivity);
         // Transparent strategy applied
         assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior());
@@ -441,10 +441,10 @@
 
         // Launch translucent Activity
         final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setActivityTheme(android.R.style.Theme_Translucent)
                 .setLaunchedFromUid(mActivity.getUid())
                 .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
                 .build();
-        doReturn(false).when(translucentActivity).fillsParent();
         mTask.addChild(translucentActivity);
         // Transparent strategy applied
         assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior());
@@ -465,12 +465,12 @@
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         // Translucent Activity
         final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setActivityTheme(android.R.style.Theme_Translucent)
                 .setLaunchedFromUid(mActivity.getUid())
                 .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
                 .setMinAspectRatio(1.1f)
                 .setMaxAspectRatio(3f)
                 .build();
-        doReturn(false).when(translucentActivity).fillsParent();
         mTask.addChild(translucentActivity);
         // We check bounds
         final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds();
@@ -493,9 +493,9 @@
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         // Translucent Activity
         final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setActivityTheme(android.R.style.Theme_Translucent)
                 .setLaunchedFromUid(mActivity.getUid())
                 .build();
-        doReturn(false).when(translucentActivity).fillsParent();
         final Configuration requestedConfig =
                 translucentActivity.getRequestedOverrideConfiguration();
         final WindowConfiguration translucentWinConf = requestedConfig.windowConfiguration;
@@ -525,12 +525,12 @@
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         // Translucent Activity
         final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setActivityTheme(android.R.style.Theme_Translucent)
                 .setLaunchedFromUid(mActivity.getUid())
                 .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
                 .setMinAspectRatio(1.1f)
                 .setMaxAspectRatio(3f)
                 .build();
-        doReturn(false).when(translucentActivity).fillsParent();
         mTask.addChild(translucentActivity);
         // We check bounds
         final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds();
@@ -538,10 +538,10 @@
         assertEquals(opaqueBounds, translucentRequestedBounds);
         // Launch another translucent activity
         final ActivityRecord translucentActivity2 = new ActivityBuilder(mAtm)
+                .setActivityTheme(android.R.style.Theme_Translucent)
                 .setLaunchedFromUid(mActivity.getUid())
                 .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
                 .build();
-        doReturn(false).when(translucentActivity2).fillsParent();
         mTask.addChild(translucentActivity2);
         // We check bounds
         final Rect translucent2RequestedBounds = translucentActivity2.getRequestedOverrideBounds();
@@ -558,9 +558,9 @@
         // simplicity.
         doReturn(true).when(mActivity).isEmbedded();
         // Translucent Activity
-        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm).build();
+        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setActivityTheme(android.R.style.Theme_Translucent).build();
         doReturn(false).when(translucentActivity).matchParentBounds();
-        doReturn(false).when(translucentActivity).fillsParent();
         mTask.addChild(translucentActivity);
         // Check the strategy has not being applied
         assertFalse(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior());
@@ -580,10 +580,10 @@
         assertFalse(mActivity.inSizeCompatMode());
         // We launch a transparent activity
         final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setActivityTheme(android.R.style.Theme_Translucent)
                 .setLaunchedFromUid(mActivity.getUid())
                 .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
                 .build();
-        doReturn(false).when(translucentActivity).fillsParent();
         mTask.addChild(translucentActivity);
         // It should not be in SCM
         assertFalse(translucentActivity.inSizeCompatMode());
@@ -600,12 +600,16 @@
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         // Translucent Activity
         final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setActivityTheme(android.R.style.Theme_Translucent)
                 .setLaunchedFromUid(mActivity.getUid())
                 .build();
-        doReturn(false).when(translucentActivity).fillsParent();
-        spyOn(mActivity);
+        assertFalse(translucentActivity.fillsParent());
+        assertTrue(mActivity.fillsParent());
+        mActivity.finishing = true;
+        assertFalse(mActivity.occludesParent());
         mTask.addChild(translucentActivity);
-        verify(mActivity).isFinishing();
+        // The translucent activity won't inherit letterbox behavior from a finishing activity.
+        assertFalse(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior());
     }
 
     @Test
@@ -619,10 +623,10 @@
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
         // We launch a transparent activity
         final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setActivityTheme(android.R.style.Theme_Translucent)
                 .setLaunchedFromUid(mActivity.getUid())
                 .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
                 .build();
-        doReturn(false).when(translucentActivity).fillsParent();
         mTask.addChild(translucentActivity);
         assertEquals(translucentActivity.getBounds(), mActivity.getBounds());
 
@@ -655,10 +659,10 @@
 
         // We launch a transparent activity
         final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setActivityTheme(android.R.style.Theme_Translucent)
                 .setLaunchedFromUid(mActivity.getUid())
                 .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
                 .build();
-        doReturn(false).when(translucentActivity).fillsParent();
         mTask.addChild(translucentActivity);
 
         // The transparent activity inherits the compat display insets of the opaque activity
@@ -882,8 +886,6 @@
 
         spyOn(mActivity.mLetterboxUiController);
         doReturn(true).when(mActivity.mLetterboxUiController)
-                .isSurfaceReadyToShow(any());
-        doReturn(true).when(mActivity.mLetterboxUiController)
                 .isSurfaceVisible(any());
 
         assertTrue(mActivity.mLetterboxUiController.shouldShowLetterboxUi(
@@ -1020,8 +1022,17 @@
         // Activity is sandboxed due to fixed aspect ratio.
         assertActivityMaxBoundsSandboxed();
 
+        // Prepare the states for verifying relaunching after changing orientation.
+        mActivity.finishRelaunching();
+        mActivity.setState(RESUMED, "testFixedAspectRatioOrientationChangeOrientation");
+        mActivity.setLastReportedConfiguration(mAtm.getGlobalConfiguration(),
+                mActivity.getConfiguration());
+
         // Change the fixed orientation.
         mActivity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+        assertTrue(mActivity.isRelaunching());
+        assertTrue(mActivity.mLetterboxUiController
+                .getIsRelaunchingAfterRequestedOrientationChanged());
 
         assertFitted();
         assertEquals(originalBounds.width(), mActivity.getBounds().height());
@@ -4781,6 +4792,7 @@
                 new WindowManager.LayoutParams(TYPE_STATUS_BAR);
         final Binder owner = new Binder();
         attrs.gravity = android.view.Gravity.TOP;
+        attrs.height = STATUS_BAR_HEIGHT;
         attrs.layoutInDisplayCutoutMode =
                 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         attrs.setFitInsetsTypes(0 /* types */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java b/services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java
index 8bd5473..2ea5dc4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java
@@ -28,6 +28,7 @@
 import android.os.Looper;
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
+import android.util.Log;
 
 import androidx.test.filters.MediumTest;
 
@@ -147,6 +148,8 @@
     private void setExceptionListAndWaitForCallback(String commaSeparatedList) {
         CountDownLatch latch = new CountDownLatch(1);
         mOnUpdateDeviceConfig = rawList -> {
+            Log.i(getClass().getSimpleName(), "updateDeviceConfig expected="
+                    + commaSeparatedList + " actual=" + rawList);
             if (commaSeparatedList.equals(rawList)) {
                 latch.countDown();
             }
@@ -154,8 +157,11 @@
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                 KEY_SPLASH_SCREEN_EXCEPTION_LIST, commaSeparatedList, false);
         try {
-            assertTrue("Timed out waiting for DeviceConfig to be updated.",
-                    latch.await(1, TimeUnit.SECONDS));
+            if (!latch.await(1, TimeUnit.SECONDS)) {
+                Log.w(getClass().getSimpleName(),
+                        "Timed out waiting for DeviceConfig to be updated. Force update.");
+                mList.updateDeviceConfig(commaSeparatedList);
+            }
         } catch (InterruptedException e) {
             Assert.fail(e.getMessage());
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
index 339162a..dfea2fc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
@@ -44,7 +44,6 @@
 import android.view.animation.Animation;
 import android.view.animation.TranslateAnimation;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import com.android.server.AnimationThread;
@@ -147,9 +146,10 @@
         assertFinishCallbackNotCalled();
     }
 
-    @FlakyTest(bugId = 71719744)
     @Test
     public void testCancel_sneakyCancelBeforeUpdate() throws Exception {
+        final CountDownLatch animationCancelled = new CountDownLatch(1);
+
         mSurfaceAnimationRunner = new SurfaceAnimationRunner(null, () -> new ValueAnimator() {
             {
                 setFloatValues(0f, 1f);
@@ -162,6 +162,7 @@
                     // interleaving of multiple threads. Muahahaha
                     if (animation.getCurrentPlayTime() > 0) {
                         mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface);
+                        animationCancelled.countDown();
                     }
                     listener.onAnimationUpdate(animation);
                 });
@@ -170,11 +171,7 @@
         when(mMockAnimationSpec.getDuration()).thenReturn(200L);
         mSurfaceAnimationRunner.startAnimation(mMockAnimationSpec, mMockSurface, mMockTransaction,
                 this::finishedCallback);
-
-        // We need to wait for two frames: The first frame starts the animation, the second frame
-        // actually cancels the animation.
-        waitUntilNextFrame();
-        waitUntilNextFrame();
+        assertTrue(animationCancelled.await(1, SECONDS));
         assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
         verify(mMockAnimationSpec, atLeastOnce()).apply(any(), any(), eq(0L));
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index e9fe4bb..98ca094 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static android.Manifest.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE;
+import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -24,14 +26,18 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_PARENT_TASK;
 import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_TASK_FRAGMENT;
@@ -46,13 +52,16 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
 
+import android.content.pm.SigningDetails;
 import android.content.res.Configuration;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
 import android.view.SurfaceControl;
+import android.view.View;
 import android.window.ITaskFragmentOrganizer;
 import android.window.TaskFragmentAnimationParams;
 import android.window.TaskFragmentInfo;
@@ -60,17 +69,24 @@
 
 import androidx.test.filters.MediumTest;
 
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.window.flags.Flags;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+
+import java.util.Collections;
+import java.util.Set;
 
 /**
  * Test class for {@link TaskFragment}.
  *
  * Build/Install/Run:
- *  atest WmTests:TaskFragmentTest
+ * atest WmTests:TaskFragmentTest
  */
 @MediumTest
 @Presubmit
@@ -435,6 +451,10 @@
         // Not ready if the task is still visible when the TaskFragment becomes empty.
         doReturn(true).when(task).isVisibleRequested();
         assertFalse(taskFragment.isReadyToTransit());
+
+        // Ready if the mAllowTransitionWhenEmpty flag is true.
+        taskFragment.setAllowTransitionWhenEmpty(true);
+        assertTrue(taskFragment.isReadyToTransit());
     }
 
     @Test
@@ -536,6 +556,113 @@
     }
 
     @Test
+    public void testIsAllowedToEmbedActivityInTrustedModeByHostPackage() {
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setCreateParentTask()
+                .createActivityCount(1)
+                .build();
+        final ActivityRecord activity = taskFragment.getTopMostActivity();
+
+        final String mockCert = "MOCKCERT";
+        final AndroidPackage hostPackage = mock(AndroidPackage.class);
+        final SigningDetails signingDetails = mock(SigningDetails.class);
+        when(signingDetails.hasAncestorOrSelfWithDigest(any()))
+                .thenAnswer(invocation -> ((Set) invocation.getArgument(0)).contains(mockCert));
+        doReturn(signingDetails).when(hostPackage).getSigningDetails();
+
+        // Should return false when no certs are specified
+        assertFalse(taskFragment.isAllowedToEmbedActivityInTrustedModeByHostPackage(
+                activity, hostPackage));
+
+        // Should return true when the cert is specified in <activity>
+        activity.info.setKnownActivityEmbeddingCerts(Set.of(mockCert));
+        assertTrue(taskFragment.isAllowedToEmbedActivityInTrustedModeByHostPackage(
+                activity, hostPackage));
+
+        // Should return false when the certs specified in <activity> doesn't match
+        activity.info.setKnownActivityEmbeddingCerts(Set.of("WRONGCERT"));
+        assertFalse(taskFragment.isAllowedToEmbedActivityInTrustedModeByHostPackage(
+                activity, hostPackage));
+
+        // Should return true when the certs is specified in <application>
+        activity.info.setKnownActivityEmbeddingCerts(Collections.emptySet());
+        activity.info.applicationInfo.setKnownActivityEmbeddingCerts(Set.of(mockCert));
+        assertTrue(taskFragment.isAllowedToEmbedActivityInTrustedModeByHostPackage(
+                activity, hostPackage));
+
+        // When the certs is specified in both <activity> and <application>, <activity> takes
+        // precedence
+        activity.info.setKnownActivityEmbeddingCerts(Set.of("WRONGCERT"));
+        activity.info.applicationInfo.setKnownActivityEmbeddingCerts(Set.of(mockCert));
+        assertFalse(taskFragment.isAllowedToEmbedActivityInTrustedModeByHostPackage(
+                activity, hostPackage));
+    }
+
+    @Test
+    public void testIsAllowedToBeEmbeddedInTrustedMode_withManageActivityTasksPermission() {
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setCreateParentTask()
+                .createActivityCount(1)
+                .build();
+        final ActivityRecord activity = taskFragment.getTopMostActivity();
+
+        // Not allow embedding activity if not a trusted host.
+        assertEquals(EMBEDDING_DISALLOWED_UNTRUSTED_HOST,
+                taskFragment.isAllowedToEmbedActivity(activity));
+
+        MockitoSession session =
+                mockitoSession().spyStatic(ActivityTaskManagerService.class).startMocking();
+        try {
+            doReturn(PERMISSION_GRANTED).when(() -> {
+                return ActivityTaskManagerService.checkPermission(
+                        eq(MANAGE_ACTIVITY_TASKS), anyInt(), anyInt());
+            });
+            // With the MANAGE_ACTIVITY_TASKS permission, trusted embedding is always allowed.
+            assertTrue(taskFragment.isAllowedToBeEmbeddedInTrustedMode());
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
+    public void testIsAllowedToEmbedActivityInUntrustedMode_withUntrustedEmbeddingAnyAppPermission(
+    ) {
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setCreateParentTask()
+                .createActivityCount(1)
+                .build();
+        final ActivityRecord activity = taskFragment.getTopMostActivity();
+
+        // Not allow embedding activity if not a trusted host.
+        assertEquals(EMBEDDING_DISALLOWED_UNTRUSTED_HOST,
+                taskFragment.isAllowedToEmbedActivity(activity));
+
+        MockitoSession session =
+                mockitoSession()
+                        .spyStatic(ActivityTaskManagerService.class)
+                        .spyStatic(Flags.class)
+                        .startMocking();
+        try {
+            doReturn(PERMISSION_GRANTED).when(() -> {
+                return ActivityTaskManagerService.checkPermission(
+                        eq(EMBED_ANY_APP_IN_UNTRUSTED_MODE), anyInt(), anyInt());
+            });
+            // With the EMBED_ANY_APP_IN_UNTRUSTED_MODE permission, untrusted embedding is always
+            // allowed, but it doesn't always allow trusted embedding.
+            doReturn(true).when(() -> Flags.untrustedEmbeddingAnyAppPermission());
+            assertTrue(taskFragment.isAllowedToEmbedActivityInUntrustedMode(activity));
+            assertFalse(taskFragment.isAllowedToEmbedActivityInTrustedMode(activity));
+
+            // If the flag is disabled, the permission doesn't have effect.
+            doReturn(false).when(() -> Flags.untrustedEmbeddingAnyAppPermission());
+            assertFalse(taskFragment.isAllowedToEmbedActivityInUntrustedMode(activity));
+            assertFalse(taskFragment.isAllowedToEmbedActivityInTrustedMode(activity));
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
     public void testIgnoreRequestedOrientationForActivityEmbeddingSplit() {
         // Setup two activities in ActivityEmbedding split.
         final Task task = createTask(mDisplayContent);
@@ -695,4 +822,75 @@
         mTaskFragment.getDimBounds(dimBounds);
         assertEquals(taskFragmentBounds, dimBounds);
     }
+
+    @Test
+    public void testMoveFocusToAdjacentWindow() {
+        // Setup two activities in ActivityEmbedding split.
+        final Task task = createTask(mDisplayContent);
+        final TaskFragment taskFragmentLeft = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .createActivityCount(2)
+                .setOrganizer(mOrganizer)
+                .setFragmentToken(new Binder())
+                .build();
+        final TaskFragment taskFragmentRight = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .createActivityCount(1)
+                .setOrganizer(mOrganizer)
+                .setFragmentToken(new Binder())
+                .build();
+        taskFragmentLeft.setAdjacentTaskFragment(taskFragmentRight);
+        taskFragmentLeft.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        taskFragmentRight.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        task.setBounds(0, 0, 1200, 1000);
+        taskFragmentLeft.setBounds(0, 0, 600, 1000);
+        taskFragmentRight.setBounds(600, 0, 1200, 1000);
+        final ActivityRecord appLeftTop = taskFragmentLeft.getTopMostActivity();
+        final ActivityRecord appLeftBottom = taskFragmentLeft.getBottomMostActivity();
+        final ActivityRecord appRightTop = taskFragmentRight.getTopMostActivity();
+        appLeftTop.setVisibleRequested(true);
+        appRightTop.setVisibleRequested(true);
+        final WindowState winLeftTop = createAppWindow(appLeftTop, "winLeftTop");
+        final WindowState winLeftBottom = createAppWindow(appLeftBottom, "winLeftBottom");
+        final WindowState winRightTop = createAppWindow(appRightTop, "winRightTop");
+        winLeftTop.setHasSurface(true);
+        winRightTop.setHasSurface(true);
+
+        taskFragmentLeft.setResumedActivity(appLeftTop, "test");
+        taskFragmentRight.setResumedActivity(appRightTop, "test");
+        appLeftTop.setState(RESUMED, "test");
+        appRightTop.setState(RESUMED, "test");
+        mDisplayContent.mFocusedApp = appRightTop;
+
+        // Make the appLeftTop be the focused activity and ensure the focused app is updated.
+        appLeftTop.moveFocusableActivityToTop("test");
+        assertEquals(winLeftTop, mDisplayContent.mCurrentFocus);
+
+        // Send request from a non-focused window with valid direction.
+        assertFalse(mWm.moveFocusToAdjacentWindow(null, winLeftBottom.mClient, View.FOCUS_RIGHT));
+        // The focus should remain the same.
+        assertEquals(winLeftTop, mDisplayContent.mCurrentFocus);
+
+        // Send request from the focused window with valid direction.
+        assertTrue(mWm.moveFocusToAdjacentWindow(null, winLeftTop.mClient, View.FOCUS_RIGHT));
+        // The focus should change.
+        assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
+
+        // Send request from the focused window with invalid direction.
+        assertFalse(mWm.moveFocusToAdjacentWindow(null, winRightTop.mClient, View.FOCUS_UP));
+        // The focus should remain the same.
+        assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
+
+        // Send request from the focused window with valid direction.
+        assertTrue(mWm.moveFocusToAdjacentWindow(null, winRightTop.mClient, View.FOCUS_BACKWARD));
+        // The focus should change.
+        assertEquals(winLeftTop, mDisplayContent.mCurrentFocus);
+    }
+
+    private WindowState createAppWindow(ActivityRecord app, String name) {
+        final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, app, name,
+                0 /* ownerId */, false /* ownerCanAddInternalSystemWindow */, new TestIWindow());
+        mWm.mWindowMap.put(win.mClient.asBinder(), win);
+        return win;
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index 07cfbf0..16f963f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -628,6 +628,27 @@
     }
 
     @Test
+    public void testLaunchWindowingModeUpdatesExistingTask() {
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
+                WINDOWING_MODE_FREEFORM);
+
+        mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
+        ActivityRecord activity = createSourceActivity(freeformDisplay);
+        final Task task = activity.getTask();
+        task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
+
+        assertEquals(RESULT_CONTINUE,
+                new CalculateRequestBuilder()
+                        .setTask(task)
+                        .setOptions(options)
+                        .calculate());
+
+        assertEquals(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode);
+    }
+
+    @Test
     public void testBoundsInOptionsInfersFreeformWithResizeableActivity() {
         final ActivityOptions options = ActivityOptions.makeBasic();
         options.setLaunchBounds(new Rect(0, 0, 100, 100));
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index b360800..961fdfb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -1822,6 +1822,35 @@
         verify(fragment2).assignLayer(t, 2);
     }
 
+    @Test
+    public void testMoveTaskFragmentsToBottomIfNeeded() {
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
+        final ActivityRecord unembeddedActivity = task.getTopMostActivity();
+
+        final TaskFragment fragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final TaskFragment fragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final TaskFragment fragment3 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        doReturn(true).when(fragment1).isMoveToBottomIfClearWhenLaunch();
+        doReturn(false).when(fragment2).isMoveToBottomIfClearWhenLaunch();
+        doReturn(true).when(fragment3).isMoveToBottomIfClearWhenLaunch();
+
+        assertEquals(unembeddedActivity, task.mChildren.get(0));
+        assertEquals(fragment1, task.mChildren.get(1));
+        assertEquals(fragment2, task.mChildren.get(2));
+        assertEquals(fragment3, task.mChildren.get(3));
+
+        final int[] finishCount = {0};
+        task.moveTaskFragmentsToBottomIfNeeded(unembeddedActivity, finishCount);
+
+        // fragment1 and fragment3 should be moved to the bottom of the task
+        assertEquals(fragment1, task.mChildren.get(0));
+        assertEquals(fragment3, task.mChildren.get(1));
+        assertEquals(unembeddedActivity, task.mChildren.get(2));
+        assertEquals(fragment2, task.mChildren.get(3));
+        assertEquals(2, finishCount[0]);
+    }
+
     private Task getTestTask() {
         return new TaskBuilder(mSupervisor).setCreateActivity(true).build();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 45ecc3f..00ecd00 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -215,7 +215,7 @@
                 doReturn(false).when(newDisplay).supportsSystemDecorations();
             }
             // Update the display policy to make the screen fully turned on so animation is allowed
-            displayPolicy.screenTurnedOn(null /* screenOnListener */);
+            displayPolicy.screenTurningOn(null /* screenOnListener */);
             displayPolicy.finishKeyguardDrawn();
             displayPolicy.finishWindowsDrawn();
             displayPolicy.finishScreenTurningOn();
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 51df1d4..7d8eb90 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1602,6 +1602,33 @@
     }
 
     @Test
+    public void testTransientWithParallelLaunch() {
+        final Task recentTask = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
+        final ActivityRecord recent = new ActivityBuilder(mAtm).setTask(recentTask)
+                .setVisible(false).build();
+        final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final Task appTask = app.getTask();
+        registerTestTransitionPlayer();
+        final TransitionController controller = mRootWindowContainer.mTransitionController;
+        final Transition transition = createTestTransition(TRANSIT_OPEN, controller);
+        transition.mParallelCollectType = Transition.PARALLEL_TYPE_RECENTS;
+        controller.moveToCollecting(transition);
+        transition.collect(recentTask);
+        transition.collect(appTask);
+        transition.setTransientLaunch(recent, appTask);
+        recentTask.moveToFront("move-recent-to-front");
+        transition.setAllReady();
+        transition.start();
+        // Assume that the app starts another activity in its task.
+        final Transition newTransition = controller.createAndStartCollecting(TRANSIT_OPEN);
+
+        assertEquals(newTransition, controller.getCollectingTransition());
+        assertTrue(controller.mWaitingTransitions.contains(transition));
+        assertTrue(controller.isTransientHide(appTask));
+        assertTrue(controller.isTransientVisible(appTask));
+    }
+
+    @Test
     public void testNotReadyPushPop() {
         final TransitionController controller = new TestTransitionController(mAtm);
         controller.setSyncEngine(mWm.mSyncEngine);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 6216acb..73d386a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -32,6 +32,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.window.flags.Flags.multiCrop;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -39,6 +40,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -484,6 +486,7 @@
 
     @Test
     public void testUpdateWallpaperOffset_resize_shouldCenterEnabled() {
+        assumeFalse(multiCrop());
         final DisplayContent dc = new TestDisplayContent.Builder(mAtm, INITIAL_WIDTH,
                 INITIAL_HEIGHT).build();
         dc.mWallpaperController.setShouldOffsetWallpaperCenter(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
index d255271..e9ece5d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
 import static org.junit.Assert.assertEquals;
@@ -23,6 +26,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.times;
 
 import android.content.Intent;
 import android.platform.test.annotations.Presubmit;
@@ -35,6 +39,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Test class for {@link WindowContainerTransaction}.
  *
@@ -45,7 +52,6 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class WindowContainerTransactionTests extends WindowTestsBase {
-
     @Test
     public void testRemoveTask() {
         final Task rootTask = createTask(mDisplayContent);
@@ -72,6 +78,123 @@
         verify(mAtm.getLockTaskController(), atLeast(1)).clearLockedTask(rootTask);
     }
 
+    @Test
+    public void testDesktopMode_tasksAreBroughtToFront() {
+        final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
+        TaskDisplayArea tda = desktopOrganizer.mDefaultTDA;
+        List<ActivityRecord> activityRecords = new ArrayList<>();
+        int numberOfTasks = 4;
+        desktopOrganizer.createFreeformTasksWithActivities(desktopOrganizer,
+                activityRecords, numberOfTasks);
+
+        final Task task = createTask(mDisplayContent);
+        final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+        task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+        // Bring home to front of the tasks
+        desktopOrganizer.bringHomeToFront();
+
+        // Bring tasks in front of the home
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        desktopOrganizer.bringDesktopTasksToFront(wct);
+        applyTransaction(wct);
+
+        // Verify tasks are resumed and in correct z-order
+        verify(mRootWindowContainer, times(2)).ensureActivitiesVisible();
+        for (int i = 0; i < numberOfTasks - 1; i++) {
+            assertTrue(tda.mChildren
+                    .indexOf(desktopOrganizer.mTasks.get(i).getRootTask())
+                    < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(i + 1).getRootTask()));
+        }
+    }
+
+    @Test
+    public void testDesktopMode_moveTaskToDesktop() {
+        final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
+        TaskDisplayArea tda = desktopOrganizer.mDefaultTDA;
+        List<ActivityRecord> activityRecords = new ArrayList<>();
+        int numberOfTasks = 4;
+        desktopOrganizer.createFreeformTasksWithActivities(desktopOrganizer,
+                activityRecords, numberOfTasks);
+
+        final Task task = createTask(mDisplayContent);
+        final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+        task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+        // Bring home to front of the tasks
+        desktopOrganizer.bringHomeToFront();
+
+        // Bring tasks in front of the home and newly moved task to on top of them
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        desktopOrganizer.bringDesktopTasksToFront(wct);
+        desktopOrganizer.addMoveToDesktopChanges(wct, task, true);
+        wct.setBounds(task.getTaskInfo().token, desktopOrganizer.getDefaultDesktopTaskBounds());
+        applyTransaction(wct);
+
+        // Verify tasks are resumed
+        verify(mRootWindowContainer, times(2)).ensureActivitiesVisible();
+
+        // Tasks are in correct z-order
+        for (int i = 0; i < numberOfTasks - 1; i++) {
+            assertTrue(tda.mChildren
+                    .indexOf(desktopOrganizer.mTasks.get(i).getRootTask())
+                    < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(i + 1).getRootTask()));
+        }
+        // New task is on top of other tasks
+        assertTrue(tda.mChildren
+                .indexOf(desktopOrganizer.mTasks.get(3).getRootTask())
+                < tda.mChildren.indexOf(task));
+
+        // New task is in freeform and has specified bounds
+        assertEquals(WINDOWING_MODE_FREEFORM, task.getWindowingMode());
+        assertEquals(desktopOrganizer.getDefaultDesktopTaskBounds(), task.getBounds());
+    }
+
+
+    @Test
+    public void testDesktopMode_moveTaskToFullscreen() {
+        final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
+        List<ActivityRecord> activityRecords = new ArrayList<>();
+        int numberOfTasks = 4;
+        desktopOrganizer.createFreeformTasksWithActivities(desktopOrganizer,
+                activityRecords, numberOfTasks);
+
+        Task taskToMove = desktopOrganizer.mTasks.get(numberOfTasks - 1);
+
+        // Bring tasks in front of the home and newly moved task to on top of them
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        desktopOrganizer.addMoveToFullscreen(wct, taskToMove, false);
+        applyTransaction(wct);
+
+        // New task is in freeform
+        assertEquals(WINDOWING_MODE_FULLSCREEN, taskToMove.getWindowingMode());
+    }
+
+    @Test
+    public void testDesktopMode_moveTaskToFront() {
+        final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
+        TaskDisplayArea tda = desktopOrganizer.mDefaultTDA;
+        List<ActivityRecord> activityRecords = new ArrayList<>();
+        int numberOfTasks = 5;
+        desktopOrganizer.createFreeformTasksWithActivities(desktopOrganizer,
+                activityRecords, numberOfTasks);
+
+        // Bring task 2 on top of other tasks
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.reorder(desktopOrganizer.mTasks.get(2).getTaskInfo().token, true /* onTop */);
+        applyTransaction(wct);
+
+        // Tasks are in correct z-order
+        assertTrue(tda.mChildren.indexOf(desktopOrganizer.mTasks.get(0).getRootTask())
+                < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(1).getRootTask()));
+        assertTrue(tda.mChildren.indexOf(desktopOrganizer.mTasks.get(1).getRootTask())
+                < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(3).getRootTask()));
+        assertTrue(tda.mChildren.indexOf(desktopOrganizer.mTasks.get(3).getRootTask())
+                < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(4).getRootTask()));
+        assertTrue(tda.mChildren.indexOf(desktopOrganizer.mTasks.get(4).getRootTask())
+                < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(2).getRootTask()));
+    }
+
     private Task createTask(int taskId) {
         return new Task.Builder(mAtm)
                 .setTaskId(taskId)
@@ -87,3 +210,4 @@
         }
     }
 }
+
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index a1cc8d5..06afa38 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -115,7 +115,6 @@
 import org.mockito.ArgumentCaptor;
 
 import java.util.ArrayList;
-import java.util.Collections;
 
 /**
  * Build/Install/Run:
@@ -139,7 +138,7 @@
 
     @After
     public void tearDown() {
-        mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        mWm.mSensitiveContentPackages.clearBlockedApps();
     }
 
     @Test
@@ -824,7 +823,7 @@
     }
 
     @Test
-    public void setShouldBlockScreenCaptureForApp() {
+    public void addBlockScreenCaptureForApps() {
         String testPackage = "test";
         int ownerId1 = 20;
         int ownerId2 = 21;
@@ -833,7 +832,7 @@
         blockedPackages.add(blockedPackage);
 
         WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
-        wmInternal.setShouldBlockScreenCaptureForApp(blockedPackages);
+        wmInternal.addBlockScreenCaptureForApps(blockedPackages);
 
         assertTrue(mWm.mSensitiveContentPackages
                 .shouldBlockScreenCaptureForApp(testPackage, ownerId1));
@@ -843,7 +842,47 @@
     }
 
     @Test
-    public void setShouldBlockScreenCaptureForApp_emptySet_clearsCache() {
+    public void addBlockScreenCaptureForApps_duplicate_verifyNoRefresh() {
+        String testPackage = "test";
+        int ownerId1 = 20;
+        int ownerId2 = 21;
+        PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
+        ArraySet<PackageInfo> blockedPackages = new ArraySet();
+        blockedPackages.add(blockedPackage);
+
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+        wmInternal.addBlockScreenCaptureForApps(blockedPackages);
+        wmInternal.addBlockScreenCaptureForApps(blockedPackages);
+
+        verify(mWm, times(1)).refreshScreenCaptureDisabled();
+    }
+
+    @Test
+    public void addBlockScreenCaptureForApps_notDuplicate_verifyRefresh() {
+        String testPackage = "test";
+        int ownerId1 = 20;
+        int ownerId2 = 21;
+        PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
+        PackageInfo blockedPackage2 = new PackageInfo(testPackage, ownerId2);
+        ArraySet<PackageInfo> blockedPackages = new ArraySet();
+        blockedPackages.add(blockedPackage);
+        ArraySet<PackageInfo> blockedPackages2 = new ArraySet();
+        blockedPackages2.add(blockedPackage);
+        blockedPackages2.add(blockedPackage2);
+
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+        wmInternal.addBlockScreenCaptureForApps(blockedPackages);
+        wmInternal.addBlockScreenCaptureForApps(blockedPackages2);
+
+        assertTrue(mWm.mSensitiveContentPackages
+                .shouldBlockScreenCaptureForApp(testPackage, ownerId1));
+        assertTrue(mWm.mSensitiveContentPackages
+                .shouldBlockScreenCaptureForApp(testPackage, ownerId2));
+        verify(mWm, times(2)).refreshScreenCaptureDisabled();
+    }
+
+    @Test
+    public void clearBlockedApps_clearsCache() {
         String testPackage = "test";
         int ownerId1 = 20;
         PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
@@ -851,8 +890,8 @@
         blockedPackages.add(blockedPackage);
 
         WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
-        wmInternal.setShouldBlockScreenCaptureForApp(blockedPackages);
-        wmInternal.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        wmInternal.addBlockScreenCaptureForApps(blockedPackages);
+        wmInternal.clearBlockedApps();
 
         assertFalse(mWm.mSensitiveContentPackages
                 .shouldBlockScreenCaptureForApp(testPackage, ownerId1));
@@ -860,6 +899,14 @@
     }
 
     @Test
+    public void clearBlockedApps_alreadyEmpty_verifyNoRefresh() {
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+        wmInternal.clearBlockedApps();
+
+        verify(mWm, never()).refreshScreenCaptureDisabled();
+    }
+
+    @Test
     public void testisLetterboxBackgroundMultiColored() {
         assertThat(setupLetterboxConfigurationWithBackgroundType(
                 LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING)).isTrue();
@@ -1021,7 +1068,7 @@
             invocationOnMock.callRealMethod();
             return null;
         }).when(surface).lockCanvas(any());
-        mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId, mTransaction);
+        mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId);
         waitUntilHandlersIdle();
         try {
             verify(surface).lockCanvas(any());
@@ -1029,14 +1076,9 @@
             clearInvocations(surface);
             // Invalidate and redraw.
             mWm.mAccessibilityController.onDisplaySizeChanged(mDisplayContent);
-            mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId, mTransaction);
+            mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId);
             // Turn off magnification to release surface.
             mWm.mAccessibilityController.setMagnificationCallbacks(displayId, null);
-            if (!com.android.window.flags.Flags.drawMagnifierBorderOutsideWmlock()) {
-                verify(surface).release();
-                assertTrue(lockCanvasInWmLock[0]);
-                return;
-            }
             waitUntilHandlersIdle();
             // lockCanvas must not be called after releasing.
             verify(surface, never()).lockCanvas(any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index fb4edfa..a0562aa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -136,7 +136,7 @@
 
     @After
     public void tearDown() {
-        mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+        mWm.mSensitiveContentPackages.clearBlockedApps();
     }
 
     @Test
@@ -1398,7 +1398,7 @@
         PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
         ArraySet<PackageInfo> blockedPackages = new ArraySet();
         blockedPackages.add(blockedPackage);
-        mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedPackages);
+        mWm.mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedPackages);
 
         assertTrue(window1.isSecureLocked());
         assertFalse(window2.isSecureLocked());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 60e84b0..be83744 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -115,10 +116,12 @@
 import android.window.TaskFragmentOrganizer;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
 
 import com.android.internal.policy.AttributeCache;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
 import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
 
 import org.junit.After;
@@ -132,7 +135,9 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 
 /** Common base class for window manager unit test classes. */
 class WindowTestsBase extends SystemServiceTestsBase {
@@ -225,7 +230,7 @@
         mDefaultDisplay = mWm.mRoot.getDefaultDisplay();
         // Update the display policy to make the screen fully turned on so animation is allowed
         final DisplayPolicy displayPolicy = mDefaultDisplay.getDisplayPolicy();
-        displayPolicy.screenTurnedOn(null /* screenOnListener */);
+        displayPolicy.screenTurningOn(null /* screenOnListener */);
         displayPolicy.finishKeyguardDrawn();
         displayPolicy.finishWindowsDrawn();
         displayPolicy.finishScreenTurningOn();
@@ -286,6 +291,18 @@
         mAtm.mWindowManager.mLetterboxConfiguration
                 .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(false);
 
+        // Setup WallpaperController crop utils with a simple center-align strategy
+        WallpaperCropUtils cropUtils = (displaySize, bitmapSize, suggestedCrops, rtl) -> {
+            Rect crop = new Rect(0, 0, displaySize.x, displaySize.y);
+            crop.scale(Math.min(
+                    ((float) bitmapSize.x) / displaySize.x,
+                    ((float) bitmapSize.y) / displaySize.y));
+            crop.offset((bitmapSize.x - crop.width()) / 2, (bitmapSize.y - crop.height()) / 2);
+            return crop;
+        };
+        mDisplayContent.mWallpaperController.setWallpaperCropUtils(cropUtils);
+        mDefaultDisplay.mWallpaperController.setWallpaperCropUtils(cropUtils);
+
         checkDeviceSpecificOverridesNotApplied();
     }
 
@@ -1054,6 +1071,21 @@
     }
 
     /**
+     * Performs surface placement and waits for WindowAnimator to complete the frame. It is used
+     * to execute the callbacks if the surface placement is expected to add some callbacks via
+     * {@link WindowAnimator#addAfterPrepareSurfacesRunnable}.
+     */
+    void performSurfacePlacementAndWaitForWindowAnimator() {
+        mWm.mAnimator.ready();
+        if (!mWm.mWindowPlacerLocked.isTraversalScheduled()) {
+            mRootWindowContainer.performSurfacePlacement();
+        } else {
+            waitHandlerIdle(mWm.mAnimationHandler);
+        }
+        waitUntilWindowAnimatorIdle();
+    }
+
+    /**
      * Avoids rotating screen disturbed by some conditions. It is usually used for the default
      * display that is not the instance of {@link TestDisplayContent} (it bypasses the conditions).
      *
@@ -1864,6 +1896,120 @@
         }
     }
 
+    static class TestDesktopOrganizer extends WindowOrganizerTests.StubOrganizer {
+        final int mDesktopModeDefaultWidthDp = 840;
+        final int mDesktopModeDefaultHeightDp = 630;
+        final int mDesktopDensity = 284;
+        final int mOverrideDensity = 285;
+
+        final ActivityTaskManagerService mService;
+        final TaskDisplayArea mDefaultTDA;
+        List<Task> mTasks;
+        final DisplayContent mDisplay;
+        Rect mStableBounds;
+        Task mHomeTask;
+
+        TestDesktopOrganizer(ActivityTaskManagerService service, DisplayContent display) {
+            mService = service;
+            mDefaultTDA = display.getDefaultTaskDisplayArea();
+            mDisplay = display;
+            mService.mTaskOrganizerController.registerTaskOrganizer(this);
+            mTasks = new ArrayList<>();
+            mStableBounds = display.getBounds();
+            mHomeTask =  mDefaultTDA.getRootHomeTask();
+        }
+        TestDesktopOrganizer(ActivityTaskManagerService service) {
+            this(service, service.mTaskSupervisor.mRootWindowContainer.getDefaultDisplay());
+        }
+
+        public Task createTask(Rect bounds) {
+            Task task = mService.mTaskOrganizerController.createRootTask(
+                    mDisplay, WINDOWING_MODE_FREEFORM, null);
+            task.setBounds(bounds);
+            mTasks.add(task);
+            spyOn(task);
+            return task;
+        }
+
+        public Rect getDefaultDesktopTaskBounds() {
+            int width = (int) (mDesktopModeDefaultWidthDp
+                    * (mOverrideDensity / mDesktopDensity) + 0.5f);
+            int height = (int) (mDesktopModeDefaultHeightDp
+                    * (mOverrideDensity / mDesktopDensity) + 0.5f);
+            Rect outBounds = new Rect();
+
+            outBounds.set(0, 0, width, height);
+            // Center the task in stable bounds
+            outBounds.offset(
+                    mStableBounds.centerX() - outBounds.centerX(),
+                    mStableBounds.centerY() - outBounds.centerY()
+            );
+            return outBounds;
+        }
+
+        public void createFreeformTasksWithActivities(TestDesktopOrganizer desktopOrganizer,
+                List<ActivityRecord> activityRecords, int numberOfTasks) {
+            for (int i = 0; i < numberOfTasks; i++) {
+                Rect bounds = new Rect(desktopOrganizer.getDefaultDesktopTaskBounds());
+                bounds.offset(20 * i, 20 * i);
+                desktopOrganizer.createTask(bounds);
+            }
+
+            for (int i = 0; i < numberOfTasks; i++) {
+                activityRecords.add(new TaskBuilder(mService.mTaskSupervisor)
+                        .setParentTask(desktopOrganizer.mTasks.get(i))
+                        .setCreateActivity(true)
+                        .build()
+                        .getTopMostActivity());
+            }
+
+            for (int i = 0; i < numberOfTasks; i++) {
+                activityRecords.get(i).setVisibleRequested(true);
+            }
+
+            for (int i = 0; i < numberOfTasks; i++) {
+                assertEquals(desktopOrganizer.mTasks.get(i), activityRecords.get(i).getRootTask());
+            }
+        }
+
+        public void bringHomeToFront() {
+            WindowContainerTransaction wct = new WindowContainerTransaction();
+            wct.reorder(mHomeTask.getTaskInfo().token, true /* onTop */);
+            applyTransaction(wct);
+        }
+
+        public void bringDesktopTasksToFront(WindowContainerTransaction wct) {
+            for (Task task: mTasks) {
+                wct.reorder(task.getTaskInfo().token, true /* onTop */);
+            }
+        }
+
+        public void addMoveToDesktopChanges(WindowContainerTransaction wct, Task task,
+                boolean overrideDensity) {
+            wct.setWindowingMode(task.getTaskInfo().token, WINDOWING_MODE_FREEFORM);
+            wct.reorder(task.getTaskInfo().token, true /* onTop */);
+            if (overrideDensity) {
+                wct.setDensityDpi(task.getTaskInfo().token, mOverrideDensity);
+            }
+        }
+
+        public void addMoveToFullscreen(WindowContainerTransaction wct, Task task,
+                boolean overrideDensity) {
+            wct.setWindowingMode(task.getTaskInfo().token, WINDOWING_MODE_FULLSCREEN);
+            wct.setBounds(task.getTaskInfo().token, new Rect());
+            if (overrideDensity) {
+                wct.setDensityDpi(task.getTaskInfo().token, mOverrideDensity);
+            }
+        }
+
+        private void applyTransaction(@androidx.annotation.NonNull WindowContainerTransaction wct) {
+            if (!wct.isEmpty()) {
+                mService.mWindowOrganizerController.applyTransaction(wct);
+            }
+        }
+    }
+
+
     static TestWindowToken createTestWindowToken(int type, DisplayContent dc) {
         return createTestWindowToken(type, dc, false /* persistOnEmpty */);
     }
diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java
index 336bfdd..a8fd6f2 100644
--- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java
+++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java
@@ -35,11 +35,13 @@
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.Keep;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.RingBuffer;
 import com.android.server.usage.BroadcastResponseStatsTracker.NotificationEventType;
 
+import java.util.function.IntFunction;
+import java.util.function.Supplier;
+
 public class BroadcastResponseStatsLogger {
 
     private static final int MAX_LOG_SIZE =
@@ -49,10 +51,10 @@
 
     @GuardedBy("mLock")
     private final LogBuffer mBroadcastEventsBuffer = new LogBuffer(
-            BroadcastEvent.class, MAX_LOG_SIZE);
+            BroadcastEvent::new, BroadcastEvent[]::new, MAX_LOG_SIZE);
     @GuardedBy("mLock")
     private final LogBuffer mNotificationEventsBuffer = new LogBuffer(
-            NotificationEvent.class, MAX_LOG_SIZE);
+            NotificationEvent::new, NotificationEvent[]::new, MAX_LOG_SIZE);
 
     void logBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage,
             UserHandle targetUser, long idForResponseEvent,
@@ -96,8 +98,8 @@
 
     private static final class LogBuffer<T extends Data> extends RingBuffer<T> {
 
-        LogBuffer(Class<T> classType, int capacity) {
-            super(classType, capacity);
+        LogBuffer(Supplier<T> newItem, IntFunction<T[]> newBacking, int capacity) {
+            super(newItem, newBacking, capacity);
         }
 
         void logBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage,
@@ -179,8 +181,7 @@
         }
     }
 
-    @Keep
-    public static final class BroadcastEvent implements Data {
+    private static final class BroadcastEvent implements Data {
         public int sourceUid;
         public int targetUserId;
         public int targetUidProcessState;
@@ -200,8 +201,7 @@
         }
     }
 
-    @Keep
-    public static final class NotificationEvent implements Data {
+    private static final class NotificationEvent implements Data {
         public int type;
         public String packageName;
         public int userId;
@@ -218,7 +218,7 @@
         }
     }
 
-    public interface Data {
+    private interface Data {
         void reset();
     }
 }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 08f719e..a58cf5f 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1526,14 +1526,15 @@
      * Called by the Binder stub.
      */
     UsageEvents queryEvents(int userId, long beginTime, long endTime, int flags) {
-        return queryEventsWithTypes(userId, beginTime, endTime, flags, EmptyArray.INT);
+        return queryEventsWithQueryFilters(userId, beginTime, endTime, flags,
+                /* eventTypeFilter= */ EmptyArray.INT, /* pkgNameFilter= */ null);
     }
 
     /**
      * Called by the Binder stub.
      */
-    UsageEvents queryEventsWithTypes(int userId, long beginTime, long endTime, int flags,
-            int[] eventTypeFilter) {
+    UsageEvents queryEventsWithQueryFilters(int userId, long beginTime, long endTime, int flags,
+            int[] eventTypeFilter, ArraySet<String> pkgNameFilter) {
         synchronized (mLock) {
             if (!mUserUnlockedStates.contains(userId)) {
                 Slog.w(TAG, "Failed to query events for locked user " + userId);
@@ -1544,7 +1545,7 @@
             if (service == null) {
                 return null; // user was stopped or removed
             }
-            return service.queryEvents(beginTime, endTime, flags, eventTypeFilter);
+            return service.queryEvents(beginTime, endTime, flags, eventTypeFilter, pkgNameFilter);
         }
     }
 
@@ -2276,7 +2277,7 @@
         }
 
         private UsageEvents queryEventsHelper(int userId, long beginTime, long endTime,
-                String callingPackage, int[] eventTypeFilter) {
+                String callingPackage, int[] eventTypeFilter, ArraySet<String> pkgNameFilter) {
             final int callingUid = Binder.getCallingUid();
             final int callingPid = Binder.getCallingPid();
             final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(
@@ -2295,8 +2296,8 @@
                 if (hideLocusIdEvents) flags |= UsageEvents.HIDE_LOCUS_EVENTS;
                 if (obfuscateNotificationEvents) flags |= UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS;
 
-                return UsageStatsService.this.queryEventsWithTypes(userId, beginTime, endTime,
-                        flags, eventTypeFilter);
+                return UsageStatsService.this.queryEventsWithQueryFilters(userId,
+                        beginTime, endTime, flags, eventTypeFilter, pkgNameFilter);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -2414,7 +2415,8 @@
             }
 
             return queryEventsHelper(UserHandle.getCallingUserId(), beginTime, endTime,
-                    callingPackage, /* eventTypeFilter= */ EmptyArray.INT);
+                    callingPackage, /* eventTypeFilter= */ EmptyArray.INT,
+                    /* pkgNameFilter= */ null);
         }
 
         @Override
@@ -2440,7 +2442,8 @@
             }
 
             return queryEventsHelper(userId, query.getBeginTimeMillis(),
-                    query.getEndTimeMillis(), callingPackage, query.getEventTypeFilter());
+                    query.getEndTimeMillis(), callingPackage, query.getEventTypes(),
+                    /* pkgNameFilter= */ new ArraySet<>(query.getPackageNames()));
         }
 
         @Override
@@ -2476,7 +2479,8 @@
             }
 
             return queryEventsHelper(userId, beginTime, endTime, callingPackage,
-                    /* eventTypeFilter= */ EmptyArray.INT);
+                    /* eventTypeFilter= */ EmptyArray.INT,
+                    /* pkgNameFilter= */ null);
         }
 
         @Override
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 3bc7752..96f45fa 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -536,13 +536,14 @@
     }
 
     UsageEvents queryEvents(final long beginTime, final long endTime, int flags,
-            int[] eventTypeFilter) {
+            int[] eventTypeFilter, ArraySet<String> pkgNameFilter) {
         if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
             return null;
         }
 
         // Ensure valid event type filter.
         final boolean isQueryForAllEvents = ArrayUtils.isEmpty(eventTypeFilter);
+        final boolean isQueryForAllPackages = pkgNameFilter == null || pkgNameFilter.isEmpty();
         final boolean[] queryEventFilter = new boolean[Event.MAX_EVENT_TYPE + 1];
         if (!isQueryForAllEvents) {
             for (int eventType : eventTypeFilter) {
@@ -589,6 +590,11 @@
                             if ((flags & OBFUSCATE_INSTANT_APPS) == OBFUSCATE_INSTANT_APPS) {
                                 event = event.getObfuscatedIfInstantApp();
                             }
+
+                            if (!isQueryForAllPackages && !pkgNameFilter.contains(event.mPackage)) {
+                                continue;
+                            }
+
                             if (event.mPackage != null) {
                                 names.add(event.mPackage);
                             }
diff --git a/services/usb/lint-baseline.xml b/services/usb/lint-baseline.xml
index c2c0a35..62a2ee5 100644
--- a/services/usb/lint-baseline.xml
+++ b/services/usb/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.1.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
 
     <issue
         id="NonUserGetterCalled"
@@ -12,4 +12,4 @@
             column="42"/>
     </issue>
 
-</issues>
+</issues>
\ No newline at end of file
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
index 8773cab..49ad461 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
@@ -24,6 +24,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.telephony.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -61,7 +62,8 @@
             SubscriptionManager subscriptionManager,
             TelephonyManager telephonyManager,
             Callback callback) {
-        mSubscriptionManager = Objects.requireNonNull(subscriptionManager);
+        mSubscriptionManager = Objects.requireNonNull(subscriptionManager)
+                .createForAllUserProfiles();
         mTelephonyManager = Objects.requireNonNull(telephonyManager);
         mCallback = Objects.requireNonNull(callback);
         mSubscriptionManager.addOnSubscriptionsChangedListener(
@@ -117,12 +119,28 @@
     private boolean checkCallStatus() {
         List<SubscriptionInfo> infoList = mSubscriptionManager.getActiveSubscriptionInfoList();
         if (infoList == null) return false;
-        return infoList.stream()
-                .filter(s -> (s.getSubscriptionId() != SubscriptionManager.INVALID_SUBSCRIPTION_ID))
-                .anyMatch(s -> isCallOngoingFromState(
-                                        mTelephonyManager
-                                                .createForSubscriptionId(s.getSubscriptionId())
-                                                .getCallStateForSubscription()));
+        if (!Flags.enforceTelephonyFeatureMapping()) {
+            return infoList.stream()
+                    .filter(s -> (s.getSubscriptionId()
+                            != SubscriptionManager.INVALID_SUBSCRIPTION_ID))
+                    .anyMatch(s -> isCallOngoingFromState(
+                            mTelephonyManager
+                                    .createForSubscriptionId(s.getSubscriptionId())
+                                    .getCallStateForSubscription()));
+        } else {
+            return infoList.stream()
+                    .filter(s -> (s.getSubscriptionId()
+                            != SubscriptionManager.INVALID_SUBSCRIPTION_ID))
+                    .anyMatch(s -> {
+                        try {
+                            return isCallOngoingFromState(mTelephonyManager
+                                    .createForSubscriptionId(s.getSubscriptionId())
+                                    .getCallStateForSubscription());
+                        } catch (UnsupportedOperationException e) {
+                            return false;
+                        }
+                    });
+        }
     }
 
     private void updateTelephonyListeners() {
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index 24d3918..a1407869 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -19,8 +19,8 @@
 import static android.telecom.CallException.TRANSACTION_EXCEPTION_KEY;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.os.Binder;
 import android.os.Bundle;
@@ -30,8 +30,8 @@
 import android.os.ResultReceiver;
 import android.text.TextUtils;
 
-import com.android.internal.telecom.ClientTransactionalServiceRepository;
 import com.android.internal.telecom.ICallControl;
+import com.android.server.telecom.flags.Flags;
 
 import java.util.List;
 import java.util.Objects;
@@ -50,20 +50,13 @@
 @SuppressLint("NotCloseable")
 public final class CallControl {
     private static final String TAG = CallControl.class.getSimpleName();
-    private static final String INTERFACE_ERROR_MSG = "Call Control is not available";
     private final String mCallId;
     private final ICallControl mServerInterface;
-    private final PhoneAccountHandle mPhoneAccountHandle;
-    private final ClientTransactionalServiceRepository mRepository;
 
     /** @hide */
-    public CallControl(@NonNull String callId, @Nullable ICallControl serverInterface,
-            @NonNull ClientTransactionalServiceRepository repository,
-            @NonNull PhoneAccountHandle pah) {
+    public CallControl(@NonNull String callId, @NonNull ICallControl serverInterface) {
         mCallId = callId;
         mServerInterface = serverInterface;
-        mRepository = repository;
-        mPhoneAccountHandle = pah;
     }
 
     /**
@@ -95,16 +88,14 @@
      */
     public void setActive(@CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<Void, CallException> callback) {
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.setActive(mCallId,
-                        new CallControlResultReceiver("setActive", executor, callback));
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        try {
+            mServerInterface.setActive(mCallId,
+                    new CallControlResultReceiver("setActive", executor, callback));
 
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -132,16 +123,12 @@
         validateVideoState(videoState);
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.answer(videoState, mCallId,
-                        new CallControlResultReceiver("answer", executor, callback));
+        try {
+            mServerInterface.answer(videoState, mCallId,
+                    new CallControlResultReceiver("answer", executor, callback));
 
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -163,16 +150,14 @@
      */
     public void setInactive(@CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<Void, CallException> callback) {
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.setInactive(mCallId,
-                        new CallControlResultReceiver("setInactive", executor, callback));
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        try {
+            mServerInterface.setInactive(mCallId,
+                    new CallControlResultReceiver("setInactive", executor, callback));
 
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -211,15 +196,11 @@
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
         validateDisconnectCause(disconnectCause);
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.disconnect(mCallId, disconnectCause,
-                        new CallControlResultReceiver("disconnect", executor, callback));
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        try {
+            mServerInterface.disconnect(mCallId, disconnectCause,
+                    new CallControlResultReceiver("disconnect", executor, callback));
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -243,15 +224,13 @@
      */
     public void startCallStreaming(@CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<Void, CallException> callback) {
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.startCallStreaming(mCallId,
-                        new CallControlResultReceiver("startCallStreaming", executor, callback));
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        try {
+            mServerInterface.startCallStreaming(mCallId,
+                    new CallControlResultReceiver("startCallStreaming", executor, callback));
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -279,15 +258,44 @@
         Objects.requireNonNull(callEndpoint);
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.requestCallEndpointChange(callEndpoint,
-                        new CallControlResultReceiver("endpointChange", executor, callback));
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        try {
+            mServerInterface.requestCallEndpointChange(callEndpoint,
+                    new CallControlResultReceiver("requestCallEndpointChange", executor, callback));
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Request a new mute state.  Note: {@link CallEventCallback#onMuteStateChanged(boolean)}
+     * will be called every time the mute state is changed and can be used to track the current
+     * mute state.
+     *
+     * @param isMuted  The new mute state.  Passing in a {@link Boolean#TRUE} for the isMuted
+     *                 parameter will mute the call.  {@link Boolean#FALSE} will unmute the call.
+     * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+     *                 will be called on.
+     * @param callback The {@link OutcomeReceiver} that will be completed on the Telecom side
+     *                 that details success or failure of the requested operation.
+     *
+     *                 {@link OutcomeReceiver#onResult} will be called if Telecom has
+     *                 successfully changed the mute state.
+     *
+     *                 {@link OutcomeReceiver#onError} will be called if Telecom has failed to
+     *                 switch to the mute state.  A {@link CallException} will be
+     *                 passed that details why the operation failed.
+     */
+    @FlaggedApi(Flags.FLAG_SET_MUTE_STATE)
+    public void requestMuteState(boolean isMuted, @CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, CallException> callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        try {
+            mServerInterface.setMuteState(isMuted,
+                    new CallControlResultReceiver("requestMuteState", executor, callback));
+
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -313,14 +321,10 @@
     public void sendEvent(@NonNull String event, @NonNull Bundle extras) {
         Objects.requireNonNull(event);
         Objects.requireNonNull(extras);
-        if (mServerInterface != null) {
-            try {
-                mServerInterface.sendEvent(mCallId, event, extras);
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        } else {
-            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        try {
+            mServerInterface.sendEvent(mCallId, event, extras);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index 94c737d..b7706a9 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.MODIFY_PHONE_STATE;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
@@ -30,11 +31,15 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.ArraySet;
+
+import com.android.internal.telephony.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Represents a distinct method to place or receive a phone call. Apps which can place calls and
@@ -491,6 +496,7 @@
     private final Bundle mExtras;
     private boolean mIsEnabled;
     private String mGroupId;
+    private final Set<PhoneAccountHandle> mSimultaneousCallingRestriction;
 
     @Override
     public boolean equals(Object o) {
@@ -508,7 +514,9 @@
                 Objects.equals(mShortDescription, that.mShortDescription) &&
                 Objects.equals(mSupportedUriSchemes, that.mSupportedUriSchemes) &&
                 areBundlesEqual(mExtras, that.mExtras) &&
-                Objects.equals(mGroupId, that.mGroupId);
+                Objects.equals(mGroupId, that.mGroupId)
+                && Objects.equals(mSimultaneousCallingRestriction,
+                        that.mSimultaneousCallingRestriction);
     }
 
     @Override
@@ -516,7 +524,7 @@
         return Objects.hash(mAccountHandle, mAddress, mSubscriptionAddress, mCapabilities,
                 mHighlightColor, mLabel, mShortDescription, mSupportedUriSchemes,
                 mSupportedAudioRoutes,
-                mExtras, mIsEnabled, mGroupId);
+                mExtras, mIsEnabled, mGroupId, mSimultaneousCallingRestriction);
     }
 
     /**
@@ -537,6 +545,7 @@
         private Bundle mExtras;
         private boolean mIsEnabled = false;
         private String mGroupId = "";
+        private Set<PhoneAccountHandle> mSimultaneousCallingRestriction = null;
 
         /**
          * Creates a builder with the specified {@link PhoneAccountHandle} and label.
@@ -571,6 +580,9 @@
             mExtras = phoneAccount.getExtras();
             mGroupId = phoneAccount.getGroupId();
             mSupportedAudioRoutes = phoneAccount.getSupportedAudioRoutes();
+            if (phoneAccount.hasSimultaneousCallingRestriction()) {
+                mSimultaneousCallingRestriction = phoneAccount.getSimultaneousCallingRestriction();
+            }
         }
 
         /**
@@ -787,6 +799,57 @@
         }
 
         /**
+         * Restricts the ability of this {@link PhoneAccount} to ONLY support simultaneous calling
+         * with the other {@link PhoneAccountHandle}s in this Set.
+         * <p>
+         * If two or more {@link PhoneAccount}s support calling simultaneously, it means that
+         * Telecom allows the user to place additional outgoing calls and receive additional
+         * incoming calls using other {@link PhoneAccount}s while this PhoneAccount also has one or
+         * more active calls.
+         * <p>
+         * If this setter method is never called or cleared using
+         * {@link #clearSimultaneousCallingRestriction()}, there is no restriction and all
+         * {@link PhoneAccount}s registered to Telecom by this package support simultaneous calling.
+         * <p>
+         * Note: Simultaneous calling restrictions can only be placed on {@link PhoneAccount}s that
+         * were registered by the same application. Simultaneous calling across applications is
+         * always possible as long as the {@link Connection} supports hold. If a
+         * {@link PhoneAccountHandle} is included here and the package name doesn't match this
+         * application's package name, {@link TelecomManager#registerPhoneAccount(PhoneAccount)}
+         * will throw a {@link SecurityException}.
+         *
+         * @param handles The other {@link PhoneAccountHandle}s that support calling simultaneously
+         * with this one. Use {@link #clearSimultaneousCallingRestriction()} to remove the
+         * restriction and allow simultaneous calling to be supported across all
+         * {@link PhoneAccount}s registered by this package.
+         * @return The Builder used to set up the new PhoneAccount.
+         */
+        @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+        public @NonNull Builder setSimultaneousCallingRestriction(
+                @NonNull Set<PhoneAccountHandle> handles) {
+            if (handles == null) {
+                throw new IllegalArgumentException("the Set of PhoneAccountHandles must not be "
+                        + "null");
+            }
+            mSimultaneousCallingRestriction = handles;
+            return this;
+        }
+
+        /**
+         * Clears a previously set simultaneous calling restriction set when
+         * {@link PhoneAccount.Builder#Builder(PhoneAccount)} is used to create a new PhoneAccount
+         * from an existing one.
+         *
+         * @return The Builder used to set up the new PhoneAccount.
+         * @see #setSimultaneousCallingRestriction(Set)
+         */
+        @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+        public @NonNull Builder clearSimultaneousCallingRestriction() {
+            mSimultaneousCallingRestriction = null;
+            return this;
+        }
+
+        /**
          * Creates an instance of a {@link PhoneAccount} based on the current builder settings.
          *
          * @return The {@link PhoneAccount}.
@@ -810,7 +873,8 @@
                     mExtras,
                     mSupportedAudioRoutes,
                     mIsEnabled,
-                    mGroupId);
+                    mGroupId,
+                    mSimultaneousCallingRestriction);
         }
     }
 
@@ -827,7 +891,8 @@
             Bundle extras,
             int supportedAudioRoutes,
             boolean isEnabled,
-            String groupId) {
+            String groupId,
+            Set<PhoneAccountHandle> simultaneousCallingRestriction) {
         mAccountHandle = account;
         mAddress = address;
         mSubscriptionAddress = subscriptionAddress;
@@ -841,6 +906,7 @@
         mSupportedAudioRoutes = supportedAudioRoutes;
         mIsEnabled = isEnabled;
         mGroupId = groupId;
+        mSimultaneousCallingRestriction = simultaneousCallingRestriction;
     }
 
     public static Builder builder(
@@ -1050,6 +1116,49 @@
         return (mCapabilities & CAPABILITY_SELF_MANAGED) == CAPABILITY_SELF_MANAGED;
     }
 
+    /**
+     * If a restriction is set (see {@link #hasSimultaneousCallingRestriction()}), this method
+     * returns the Set of {@link PhoneAccountHandle}s that are allowed to support calls
+     * simultaneously with this {@link PhoneAccount}.
+     * <p>
+     * If this {@link PhoneAccount} is busy with one or more ongoing calls, a restriction is set on
+     * this PhoneAccount (see {@link #hasSimultaneousCallingRestriction()} to check),  and a new
+     * incoming or outgoing call is received or placed on a PhoneAccount that is not in this Set,
+     * Telecom will reject or cancel the pending call in favor of keeping the ongoing call alive.
+     * <p>
+     * Note: Simultaneous calling restrictions can only be placed on {@link PhoneAccount}s that
+     * were registered by the same application. Simultaneous calling across applications is
+     * always possible as long as the {@link Connection} supports hold.
+     *
+     * @return the Set of {@link PhoneAccountHandle}s that this {@link PhoneAccount} supports
+     * simultaneous calls with.
+     * @throws IllegalStateException If there is no restriction set on this {@link PhoneAccount}
+     * and this method is called. Whether or not there is a restriction can be checked using
+     * {@link #hasSimultaneousCallingRestriction()}.
+     */
+    @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+    public @NonNull Set<PhoneAccountHandle> getSimultaneousCallingRestriction() {
+        if (mSimultaneousCallingRestriction == null) {
+            throw new IllegalStateException("This method can not be called if there is no "
+                    + "simultaneous calling restriction. See #hasSimultaneousCallingRestriction");
+        }
+        return mSimultaneousCallingRestriction;
+    }
+
+    /**
+     * Whether or not this {@link PhoneAccount} contains a simultaneous calling restriction on it.
+     *
+     * @return {@code true} if this PhoneAccount contains a simultaneous calling restriction,
+     * {@code false} if it does not. Use {@link #getSimultaneousCallingRestriction()} to query which
+     * other {@link PhoneAccount}s support simultaneous calling with this one.
+     * @see #getSimultaneousCallingRestriction() for more information on how the sinultaneous
+     * calling restriction works.
+     */
+    @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+    public boolean hasSimultaneousCallingRestriction() {
+        return mSimultaneousCallingRestriction != null;
+    }
+
     //
     // Parcelable implementation
     //
@@ -1095,6 +1204,12 @@
         out.writeBundle(mExtras);
         out.writeString(mGroupId);
         out.writeInt(mSupportedAudioRoutes);
+        if (mSimultaneousCallingRestriction == null) {
+            out.writeBoolean(false);
+        } else {
+            out.writeBoolean(true);
+            out.writeTypedList(mSimultaneousCallingRestriction.stream().toList());
+        }
     }
 
     public static final @android.annotation.NonNull Creator<PhoneAccount> CREATOR
@@ -1140,6 +1255,13 @@
         mExtras = in.readBundle();
         mGroupId = in.readString();
         mSupportedAudioRoutes = in.readInt();
+        if (in.readBoolean()) {
+            List<PhoneAccountHandle> list = new ArrayList<>();
+            in.readTypedList(list, PhoneAccountHandle.CREATOR);
+            mSimultaneousCallingRestriction = new ArraySet<>(list);
+        } else {
+            mSimultaneousCallingRestriction = null;
+        }
     }
 
     @Override
@@ -1161,6 +1283,17 @@
         sb.append(mExtras);
         sb.append(" GroupId: ");
         sb.append(Log.pii(mGroupId));
+        sb.append(" SC Restrictions: ");
+        if (hasSimultaneousCallingRestriction()) {
+            sb.append("[ ");
+            for (PhoneAccountHandle handle : mSimultaneousCallingRestriction) {
+                sb.append(handle);
+                sb.append(" ");
+            }
+            sb.append("]");
+        } else {
+            sb.append("[NONE]");
+        }
         sb.append("]");
         return sb.toString();
     }
diff --git a/telecomm/java/android/telecom/Response.java b/telecomm/java/android/telecom/Response.java
deleted file mode 100644
index ce7a761..0000000
--- a/telecomm/java/android/telecom/Response.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telecom;
-
-/**
- * @hide
- */
-public interface Response<IN, OUT> {
-
-    /**
-     * Provide a set of results.
-     *
-     * @param request The original request.
-     * @param result The results.
-     */
-    void onResult(IN request, OUT... result);
-
-    /**
-     * Indicates the inability to provide results.
-     *
-     * @param request The original request.
-     * @param code An integer code indicating the reason for failure.
-     * @param msg A message explaining the reason for failure.
-     */
-    void onError(IN request, int code, String msg);
-}
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 57b13e9..2c6e1e4 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1049,8 +1049,17 @@
     public static final int PRESENTATION_UNAVAILABLE = 5;
 
 
+    /**
+     * Controls audio route for video calls.
+     * 0 - Use the default audio routing strategy.
+     * 1 - Disable the speaker. Route the audio to Headset or Bluetooth
+     *     or Earpiece, based on the default audio routing strategy.
+     * @hide
+     */
+    public static final String PROPERTY_VIDEOCALL_AUDIO_OUTPUT = "persist.radio.call.audio.output";
+
     /*
-     * Values for the adb property "persist.radio.videocall.audio.output"
+     * Values for the adb property "persist.radio.call.audio.output"
      */
     /** @hide */
     public static final int AUDIO_OUTPUT_ENABLE_SPEAKER = 0;
@@ -1351,7 +1360,7 @@
     /**
      * Returns a list of {@link PhoneAccountHandle}s which can be used to make and receive phone
      * calls. The returned list includes those accounts which have been explicitly enabled by
-     * the user or other users visible to the user.
+     * the user or enabled by other users but visible to the user.
      *
      * @see #EXTRA_PHONE_ACCOUNT_HANDLE
      * @return A list of {@code PhoneAccountHandle} objects.
@@ -1477,7 +1486,7 @@
             return service.getCallCapablePhoneAccounts(includeDisabledAccounts,
                     mContext.getOpPackageName(), mContext.getAttributionTag(), true).getList();
         } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
+            throw e.rethrowFromSystemServer();
         }
     }
 
diff --git a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
index 71e9184..467e89c 100644
--- a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
+++ b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
@@ -208,8 +208,7 @@
                 if (resultCode == TELECOM_TRANSACTION_SUCCESS) {
 
                     // create the interface object that the client will interact with
-                    CallControl control = new CallControl(callId, callControl, mRepository,
-                            mPhoneAccountHandle);
+                    CallControl control = new CallControl(callId, callControl);
                     // give the client the object via the OR that was passed into addCall
                     pendingControl.onResult(control);
 
diff --git a/telecomm/java/com/android/internal/telecom/ICallControl.aidl b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
index 5e2c923..372e4a12 100644
--- a/telecomm/java/com/android/internal/telecom/ICallControl.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
@@ -32,5 +32,6 @@
     void disconnect(String callId, in DisconnectCause disconnectCause, in ResultReceiver callback);
     void startCallStreaming(String callId, in ResultReceiver callback);
     void requestCallEndpointChange(in CallEndpoint callEndpoint, in ResultReceiver callback);
+    void setMuteState(boolean isMuted, in ResultReceiver callback);
     void sendEvent(String callId, String event, in Bundle extras);
 }
\ No newline at end of file
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 250c3a5..86eed2f 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -196,7 +196,7 @@
         // We have READ_PHONE_STATE permission, so return true as long as the AppOps bit hasn't been
         // revoked.
         AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
-        return appOps.noteOp(AppOpsManager.OPSTR_READ_PHONE_STATE, uid, callingPackage,
+        return appOps.noteOpNoThrow(AppOpsManager.OPSTR_READ_PHONE_STATE, uid, callingPackage,
                 callingFeatureId, null) == AppOpsManager.MODE_ALLOWED;
     }
 
@@ -249,7 +249,7 @@
         // We have READ_PHONE_STATE permission, so return true as long as the AppOps bit hasn't been
         // revoked.
         AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
-        return appOps.noteOp(AppOpsManager.OPSTR_READ_PHONE_STATE, uid, callingPackage,
+        return appOps.noteOpNoThrow(AppOpsManager.OPSTR_READ_PHONE_STATE, uid, callingPackage,
                 callingFeatureId, null) == AppOpsManager.MODE_ALLOWED;
     }
 
@@ -521,7 +521,7 @@
         // We have READ_CALL_LOG permission, so return true as long as the AppOps bit hasn't been
         // revoked.
         AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
-        return appOps.noteOp(AppOpsManager.OPSTR_READ_CALL_LOG, uid, callingPackage,
+        return appOps.noteOpNoThrow(AppOpsManager.OPSTR_READ_CALL_LOG, uid, callingPackage,
                 callingPackageName, null) == AppOpsManager.MODE_ALLOWED;
     }
 
@@ -601,8 +601,9 @@
      *
      * @return true if caller has ACCESS_LAST_KNOWN_CELL_ID permission else false.
      */
+    @RequiresPermission(Manifest.permission.ACCESS_LAST_KNOWN_CELL_ID)
     public static boolean checkLastKnownCellIdAccessPermission(Context context) {
-        return context.checkCallingOrSelfPermission("android.permission.ACCESS_LAST_KNOWN_CELL_ID")
+        return context.checkCallingOrSelfPermission(Manifest.permission.ACCESS_LAST_KNOWN_CELL_ID)
                 == PackageManager.PERMISSION_GRANTED;
     }
 
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index c7b84a3..e5a94c3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2179,6 +2179,14 @@
      */
     public static final String KEY_MMS_NETWORK_RELEASE_TIMEOUT_MILLIS_INT =
             "mms_network_release_timeout_millis_int";
+    /**
+     * Maximum size in bytes of the PDU to send or download when connected to a non-terrestrial
+     * network. MmsService will return a result code of MMS_ERROR_TOO_LARGE_FOR_TRANSPORT if
+     * the PDU exceeds this limit when connected to a non-terrestrial network.
+     * @hide
+     */
+    public static final String KEY_MMS_MAX_NTN_PAYLOAD_SIZE_BYTES_INT =
+            "mms_max_ntn_payload_size_bytes_int";
 
     /**
      * The flatten {@link android.content.ComponentName componentName} of the activity that can
@@ -9430,16 +9438,6 @@
             "missed_incoming_call_sms_originator_string_array";
 
     /**
-     * String array of Apn Type configurations.
-     * The entries should be of form "APN_TYPE_NAME:priority".
-     * priority is an integer that is sorted from highest to lowest.
-     * example: cbs:5
-     *
-     * @hide
-     */
-    public static final String KEY_APN_PRIORITY_STRING_ARRAY = "apn_priority_string_array";
-
-    /**
      * Network capability priority for determine the satisfy order in telephony. The priority is
      * from the lowest 0 to the highest 100. The long-lived network shall have the lowest priority.
      * This allows other short-lived requests like MMS requests to be established. Emergency request
@@ -9683,6 +9681,38 @@
             "parameters_used_for_ntn_lte_signal_bar_int";
 
     /**
+     * Indicating whether plmns associated with carrier satellite can be exposed to user when
+     * manually scanning available cellular network.
+     * If key is {@code true}, satellite plmn should not be exposed to user and should be
+     * automatically set, {@code false} otherwise. Default value is {@code true}.
+     *
+     * @hide
+     */
+    public static final String KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL =
+            "remove_satellite_plmn_in_manual_network_scan_bool";
+
+    /**
+     * An integer key holds the time interval for refreshing or re-querying the satellite
+     * entitlement status from the entitlement server to ensure it is the latest.
+     *
+     * The default value is 30 days (1 month).
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT =
+            "satellite_entitlement_status_refresh_days_int";
+
+    /**
+     * This configuration enables device to query the entitlement server to get the satellite
+     * configuration.
+     * This will need agreement the carrier before enabling this flag.
+     *
+     * The default value is false.
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL =
+            "satellite_entitlement_supported_bool";
+
+    /**
      * Indicating whether DUN APN should be disabled when the device is roaming. In that case,
      * the default APN (i.e. internet) will be used for tethering.
      *
@@ -10354,6 +10384,7 @@
         sDefaults.putInt(KEY_MMS_SMS_TO_MMS_TEXT_THRESHOLD_INT, -1);
         sDefaults.putInt(KEY_MMS_SUBJECT_MAX_LENGTH_INT, 40);
         sDefaults.putInt(KEY_MMS_NETWORK_RELEASE_TIMEOUT_MILLIS_INT, 5 * 1000);
+        sDefaults.putInt(KEY_MMS_MAX_NTN_PAYLOAD_SIZE_BYTES_INT, 3 * 1000);
         sDefaults.putString(KEY_MMS_EMAIL_GATEWAY_NUMBER_STRING, "");
         sDefaults.putString(KEY_MMS_HTTP_PARAMS_STRING, "");
         sDefaults.putString(KEY_MMS_NAI_SUFFIX_STRING, "");
@@ -10723,17 +10754,14 @@
                 TimeUnit.DAYS.toMillis(1));
         sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_ORIGINATOR_STRING_ARRAY,
                 new String[0]);
-        sDefaults.putStringArray(KEY_APN_PRIORITY_STRING_ARRAY, new String[] {
-                "enterprise:0", "default:1", "mms:2", "supl:2", "dun:2", "hipri:3", "fota:2",
-                "ims:2", "cbs:2", "ia:2", "emergency:2", "mcx:3", "xcap:3"
-        });
 
         // Do not modify the priority unless you know what you are doing. This will have significant
         // impacts on the order of data network setup.
         sDefaults.putStringArray(
                 KEY_TELEPHONY_NETWORK_CAPABILITY_PRIORITIES_STRING_ARRAY, new String[] {
                         "eims:90", "supl:80", "mms:70", "xcap:70", "cbs:50", "mcx:50", "fota:50",
-                        "ims:40", "dun:30", "enterprise:20", "internet:20"
+                        "ims:40", "rcs:40", "dun:30", "enterprise:20", "internet:20",
+                        "prioritize_bandwidth:20", "prioritize_latency:20"
                 });
         sDefaults.putStringArray(
                 KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY, new String[] {
@@ -10745,9 +10773,10 @@
                         // registration state changes) retry can still happen.
                         "permanent_fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|"
                                 + "-3|65543|65547|2252|2253|2254, retry_interval=2500",
-                        "capabilities=mms|supl|cbs, retry_interval=2000",
-                        "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|"
-                                + "5000|10000|15000|20000|40000|60000|120000|240000|"
+                        "capabilities=mms|supl|cbs|rcs, retry_interval=2000",
+                        "capabilities=internet|enterprise|dun|ims|fota|xcap|mcx|"
+                                + "prioritize_bandwidth|prioritize_latency, retry_interval="
+                                + "2500|3000|5000|10000|15000|20000|40000|60000|120000|240000|"
                                 + "600000|1200000|1800000, maximum_retries=20"
                 });
         sDefaults.putStringArray(
@@ -10787,6 +10816,9 @@
                 });
         sDefaults.putInt(KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT,
                 CellSignalStrengthLte.USE_RSRP);
+        sDefaults.putBoolean(KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true);
+        sDefaults.putInt(KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT, 30);
+        sDefaults.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false);
         sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
         sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
         sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
@@ -10955,6 +10987,9 @@
      * @return A {@link PersistableBundle} containing the config for the given subId, or default
      *         values for an invalid subId.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+     *
      * @deprecated Use {@link #getConfigForSubId(int, String...)} instead.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@@ -11002,6 +11037,9 @@
      * @return A {@link PersistableBundle} with key/value mapping for the specified configuration
      * on success, or an empty (but never null) bundle on failure (for example, when the calling app
      * has no permission).
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresPermission(anyOf = {
             Manifest.permission.READ_PHONE_STATE,
@@ -11047,6 +11085,9 @@
      * @param overrideValues Key-value pairs of the values that are to be overridden. If set to
      *                       {@code null}, this will remove all previous overrides and set the
      *                       carrier configuration back to production values.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -11104,6 +11145,10 @@
      *
      * @see #getConfigForSubId
      * @see #getConfig(String...)
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+     *
      * @deprecated use {@link #getConfig(String...)} instead.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@@ -11138,6 +11183,9 @@
      * configs on success, or an empty (but never null) bundle on failure.
      * @see #getConfigForSubId(int, String...)
      * @see SubscriptionManager#getDefaultSubscriptionId()
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresPermission(anyOf = {
             Manifest.permission.READ_PHONE_STATE,
@@ -11189,6 +11237,9 @@
      *
      * <p>This method returns before the reload has completed, and {@link
      * android.service.carrier.CarrierService#onLoadConfig} will be called from an arbitrary thread.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -11212,6 +11263,8 @@
      * <p>Depending on simState, the config may be cleared or loaded from config app. This is only
      * used by SubscriptionInfoUpdater.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -11234,6 +11287,8 @@
      * Gets the package name for a default carrier service.
      * @return the package name for a default carrier service; empty string if not available.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @NonNull
@@ -11287,6 +11342,9 @@
      * @param subId the subscription ID, normally obtained from {@link SubscriptionManager}.
      *
      * @see #getConfigForSubId
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
diff --git a/telephony/java/android/telephony/CarrierInfo.java b/telephony/java/android/telephony/CarrierInfo.java
new file mode 100644
index 0000000..da77a45
--- /dev/null
+++ b/telephony/java/android/telephony/CarrierInfo.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.telephony.Rlog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * CarrierInfo that is used to represent the carrier lock information details.
+ *
+ * @hide
+ */
+public final class CarrierInfo implements Parcelable {
+
+    /**
+     * Used to create a {@link CarrierInfo} from a {@link Parcel}.
+     *
+     * @hide
+     */
+    public static final @android.annotation.NonNull Creator<CarrierInfo> CREATOR =
+            new Creator<CarrierInfo>() {
+                /**
+                 * Create a new instance of the Parcelable class, instantiating it
+                 * from the given Parcel whose data had previously been written by
+                 * {@link Parcelable#writeToParcel Parcelable.writeToParcel()}.
+                 *
+                 * @param source The Parcel to read the object's data from.
+                 * @return Returns a new instance of the Parcelable class.
+                 */
+                @Override
+                public CarrierInfo createFromParcel(Parcel source) {
+                    return new CarrierInfo(source);
+                }
+
+                /**
+                 * Create a new array of the Parcelable class.
+                 *
+                 * @param size Size of the array.
+                 * @return Returns an array of the Parcelable class, with every entry
+                 * initialized to null.
+                 */
+                @Override
+                public CarrierInfo[] newArray(int size) {
+                    return new CarrierInfo[size];
+                }
+
+            };
+    @NonNull
+    private String mMcc;
+    @NonNull
+    private String mMnc;
+    @Nullable
+    private String mSpn;
+    @Nullable
+    private String mGid1;
+    @Nullable
+    private String mGid2;
+    @Nullable
+    private String mImsiPrefix;
+    /** Ehplmn is String combination of MCC,MNC */
+    @Nullable
+    private List<String> mEhplmn;
+    @Nullable
+    private String mIccid;
+    @Nullable
+    private String mImpi;
+
+    /** @hide */
+    @NonNull
+    public String getMcc() {
+        return mMcc;
+    }
+
+    /** @hide */
+    @NonNull
+    public String getMnc() {
+        return mMnc;
+    }
+
+    /** @hide */
+    @Nullable
+    public String getSpn() {
+        return mSpn;
+    }
+
+    /** @hide */
+    @Nullable
+    public String getGid1() {
+        return mGid1;
+    }
+
+    /** @hide */
+    @Nullable
+    public String getGid2() {
+        return mGid2;
+    }
+
+    /** @hide */
+    @Nullable
+    public String getImsiPrefix() {
+        return mImsiPrefix;
+    }
+
+    /** @hide */
+    @Nullable
+    public String getIccid() {
+        return mIccid;
+    }
+
+    /** @hide */
+    @Nullable
+    public String getImpi() {
+        return mImpi;
+    }
+
+    /**
+     * Returns the list of EHPLMN.
+     *
+     * @return List of String that represent Ehplmn.
+     * @hide
+     */
+    @NonNull
+    public List<String> getEhplmn() {
+        return mEhplmn;
+    }
+
+    /** @hide */
+    public CarrierInfo(@NonNull String mcc, @NonNull String mnc, @Nullable String spn,
+            @Nullable String gid1, @Nullable String gid2, @Nullable String imsi,
+            @Nullable String iccid, @Nullable String impi, @Nullable List<String> plmnArrayList) {
+        mMcc = mcc;
+        mMnc = mnc;
+        mSpn = spn;
+        mGid1 = gid1;
+        mGid2 = gid2;
+        mImsiPrefix = imsi;
+        mIccid = iccid;
+        mImpi = impi;
+        mEhplmn = plmnArrayList;
+    }
+
+    /**
+     * Describe the kinds of special objects contained in this Parcelable
+     * instance's marshaled representation. For example, if the object will
+     * include a file descriptor in the output of {@link #writeToParcel(Parcel, int)},
+     * the return value of this method must include the
+     * {@link #CONTENTS_FILE_DESCRIPTOR} bit.
+     *
+     * @return a bitmask indicating the set of special object types marshaled
+     * by this Parcelable object instance.
+     * @hide
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Flatten this object in to a Parcel.
+     *
+     * @param dest  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written.
+     *              May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+     * @hide
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mMcc);
+        dest.writeString8(mMnc);
+        dest.writeString8(mSpn);
+        dest.writeString8(mGid1);
+        dest.writeString8(mGid2);
+        dest.writeString8(mImsiPrefix);
+        dest.writeString8(mIccid);
+        dest.writeString8(mImpi);
+        dest.writeStringList(mEhplmn);
+    }
+
+    /** @hide */
+    public CarrierInfo(Parcel in) {
+        mEhplmn = new ArrayList<String>();
+        mMcc = in.readString8();
+        mMnc = in.readString8();
+        mSpn = in.readString8();
+        mGid1 = in.readString8();
+        mGid2 = in.readString8();
+        mImsiPrefix = in.readString8();
+        mIccid = in.readString8();
+        mImpi = in.readString8();
+        in.readStringList(mEhplmn);
+    }
+
+
+    /** @hide */
+    @android.annotation.NonNull
+    @Override
+    public String toString() {
+        return "CarrierInfo MCC = " + mMcc + "   MNC = " + mMnc + "  SPN = " + mSpn + "   GID1 = "
+                + mGid1 + "   GID2 = " + mGid2 + "   IMSI = " + getPrintableImsi() + "   ICCID = "
+                + SubscriptionInfo.getPrintableId(mIccid) + "  IMPI = " + mImpi + "  EHPLMN = [ "
+                + getEhplmn_toString() + " ]";
+    }
+
+    private String getEhplmn_toString() {
+        return String.join("  ", mEhplmn);
+    }
+
+    private String getPrintableImsi() {
+        boolean enablePiiLog = Rlog.isLoggable("CarrierInfo", Log.VERBOSE);
+        return ((mImsiPrefix != null && mImsiPrefix.length() > 6) ? mImsiPrefix.substring(0, 6)
+                + Rlog.pii(enablePiiLog, mImsiPrefix.substring(6)) : mImsiPrefix);
+    }
+}
diff --git a/telephony/java/android/telephony/CarrierRestrictionRules.java b/telephony/java/android/telephony/CarrierRestrictionRules.java
index cc768bc..2b0d626 100644
--- a/telephony/java/android/telephony/CarrierRestrictionRules.java
+++ b/telephony/java/android/telephony/CarrierRestrictionRules.java
@@ -84,13 +84,75 @@
     /** The same configuration is applied to all SIM slots independently. */
     public static final int MULTISIM_POLICY_NONE = 0;
 
-    /** Any SIM card can be used as far as one SIM card matching the configuration is present. */
+    /**
+     * Indicates that any SIM card can be used as far as one valid card is present in the device.
+     * For the modem, a SIM card is valid when its content (i.e. MCC, MNC, GID, SPN) matches the
+     * carrier restriction configuration.
+     */
     public static final int MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT = 1;
 
+    /**
+     * Indicates that the SIM lock policy applies uniformly to all sim slots.
+     * @hide
+     */
+    public static final int MULTISIM_POLICY_APPLY_TO_ALL_SLOTS = 2;
+
+    /**
+     * The SIM lock configuration applies exclusively to sim slot 1, leaving
+     * all other sim slots unlocked irrespective of the SIM card in slot 1
+     * @hide
+     */
+    public static final int MULTISIM_POLICY_APPLY_TO_ONLY_SLOT_1 = 3;
+
+    /**
+     * Valid sim cards must be present on sim slot1 in order
+     * to use other sim slots.
+     * @hide
+     */
+    public static final int MULTISIM_POLICY_VALID_SIM_MUST_PRESENT_ON_SLOT_1 = 4;
+
+    /**
+     * Valid sim card must be present on slot1 and it must be in full service
+     * in order to use other sim slots.
+     * @hide
+     */
+    public static final int MULTISIM_POLICY_ACTIVE_SERVICE_ON_SLOT_1_TO_UNBLOCK_OTHER_SLOTS = 5;
+
+    /**
+     * Valid sim card be present on any slot and it must be in full service
+     * in order to use other sim slots.
+     * @hide
+     */
+    public static final int MULTISIM_POLICY_ACTIVE_SERVICE_ON_ANY_SLOT_TO_UNBLOCK_OTHER_SLOTS = 6;
+
+    /**
+     * Valid sim cards must be present on all slots. If any SIM cards become
+     * invalid then device would set other SIM cards as invalid as well.
+     * @hide
+     */
+    public static final int MULTISIM_POLICY_ALL_SIMS_MUST_BE_VALID = 7;
+
+    /**
+     * In case there is no match policy listed above.
+     * @hide
+     */
+    public static final int MULTISIM_POLICY_SLOT_POLICY_OTHER = 8;
+
+
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "MULTISIM_POLICY_",
-            value = {MULTISIM_POLICY_NONE, MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT})
+            value = {MULTISIM_POLICY_NONE,
+                    MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT,
+                    MULTISIM_POLICY_APPLY_TO_ALL_SLOTS,
+                    MULTISIM_POLICY_APPLY_TO_ONLY_SLOT_1,
+                    MULTISIM_POLICY_VALID_SIM_MUST_PRESENT_ON_SLOT_1,
+                    MULTISIM_POLICY_ACTIVE_SERVICE_ON_SLOT_1_TO_UNBLOCK_OTHER_SLOTS,
+                    MULTISIM_POLICY_ACTIVE_SERVICE_ON_ANY_SLOT_TO_UNBLOCK_OTHER_SLOTS,
+                    MULTISIM_POLICY_ALL_SIMS_MUST_BE_VALID,
+                    MULTISIM_POLICY_SLOT_POLICY_OTHER
+            })
     public @interface MultiSimPolicy {}
 
     /** @hide */
@@ -104,6 +166,8 @@
 
     private List<CarrierIdentifier> mAllowedCarriers;
     private List<CarrierIdentifier> mExcludedCarriers;
+    private List<CarrierInfo> mAllowedCarrierInfo;
+    private List<CarrierInfo> mExcludedCarrierInfo;
     @CarrierRestrictionDefault
     private int mCarrierRestrictionDefault;
     @MultiSimPolicy
@@ -114,6 +178,8 @@
     private CarrierRestrictionRules() {
         mAllowedCarriers = new ArrayList<CarrierIdentifier>();
         mExcludedCarriers = new ArrayList<CarrierIdentifier>();
+        mAllowedCarrierInfo = new ArrayList<CarrierInfo>();
+        mExcludedCarrierInfo = new ArrayList<CarrierInfo>();
         mCarrierRestrictionDefault = CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED;
         mMultiSimPolicy = MULTISIM_POLICY_NONE;
         mCarrierRestrictionStatus = TelephonyManager.CARRIER_RESTRICTION_STATUS_UNKNOWN;
@@ -122,12 +188,17 @@
     private CarrierRestrictionRules(Parcel in) {
         mAllowedCarriers = new ArrayList<CarrierIdentifier>();
         mExcludedCarriers = new ArrayList<CarrierIdentifier>();
-
+        mAllowedCarrierInfo = new ArrayList<CarrierInfo>();
+        mExcludedCarrierInfo = new ArrayList<CarrierInfo>();
         in.readTypedList(mAllowedCarriers, CarrierIdentifier.CREATOR);
         in.readTypedList(mExcludedCarriers, CarrierIdentifier.CREATOR);
         mCarrierRestrictionDefault = in.readInt();
         mMultiSimPolicy = in.readInt();
         mCarrierRestrictionStatus = in.readInt();
+        if (Flags.carrierRestrictionRulesEnhancement()) {
+            in.readTypedList(mAllowedCarrierInfo, CarrierInfo.CREATOR);
+            in.readTypedList(mExcludedCarrierInfo, CarrierInfo.CREATOR);
+        }
     }
 
     /**
@@ -165,6 +236,25 @@
     }
 
     /**
+     * Retrieves list of excluded carrierInfos
+     *
+     * @return the list of excluded carrierInfos
+     * @hide
+     */
+    public @NonNull List<CarrierInfo> getExcludedCarriersInfoList() {
+        return mExcludedCarrierInfo;
+    }
+
+    /**
+     * Retrieves list of excluded carrierInfos
+     *
+     * @return the list of excluded carrierInfos
+     * @hide
+     */
+    public @NonNull List<CarrierInfo> getAllowedCarriersInfoList() {
+        return mAllowedCarrierInfo;
+    }
+    /**
      * Retrieves the default behavior of carrier restrictions
      */
     public @CarrierRestrictionDefault int getDefaultCarrierRestriction() {
@@ -326,6 +416,10 @@
         out.writeInt(mCarrierRestrictionDefault);
         out.writeInt(mMultiSimPolicy);
         out.writeInt(mCarrierRestrictionStatus);
+        if (Flags.carrierRestrictionRulesEnhancement()) {
+            out.writeTypedList(mAllowedCarrierInfo);
+            out.writeTypedList(mExcludedCarrierInfo);
+        }
     }
 
     /**
@@ -357,7 +451,16 @@
     public String toString() {
         return "CarrierRestrictionRules(allowed:" + mAllowedCarriers + ", excluded:"
                 + mExcludedCarriers + ", default:" + mCarrierRestrictionDefault
-                + ", multisim policy:" + mMultiSimPolicy + ")";
+                + ", multisim policy:" + mMultiSimPolicy + getCarrierInfoList() + ")";
+    }
+
+    private String getCarrierInfoList() {
+        if (Flags.carrierRestrictionRulesEnhancement()) {
+            return ",  allowedCarrierInfoList:" + mAllowedCarrierInfo
+                    + ", excludedCarrierInfoList:" + mExcludedCarrierInfo;
+        } else {
+            return "";
+        }
     }
 
     /**
@@ -382,6 +485,12 @@
             mRules.mAllowedCarriers.clear();
             mRules.mExcludedCarriers.clear();
             mRules.mCarrierRestrictionDefault = CARRIER_RESTRICTION_DEFAULT_ALLOWED;
+            if (Flags.carrierRestrictionRulesEnhancement()) {
+                mRules.mCarrierRestrictionStatus =
+                        TelephonyManager.CARRIER_RESTRICTION_STATUS_NOT_RESTRICTED;
+                mRules.mAllowedCarrierInfo.clear();
+                mRules.mExcludedCarrierInfo.clear();
+            }
             return this;
         }
 
@@ -439,5 +548,29 @@
             mRules.mCarrierRestrictionStatus = carrierRestrictionStatus;
             return this;
         }
+
+        /**
+         * Set list of allowed carrierInfo
+         *
+         * @param allowedCarrierInfo list of allowed CarrierInfo
+         * @hide
+         */
+        public @NonNull Builder setAllowedCarrierInfo(
+                @NonNull List<CarrierInfo> allowedCarrierInfo) {
+            mRules.mAllowedCarrierInfo = new ArrayList<CarrierInfo>(allowedCarrierInfo);
+            return this;
+        }
+
+        /**
+         * Set list of allowed carrierInfo
+         *
+         * @param excludedCarrierInfo list of allowed CarrierInfo
+         * @hide
+         */
+        public @NonNull Builder setExcludedCarrierInfo(
+                @NonNull List<CarrierInfo> excludedCarrierInfo) {
+            mRules.mExcludedCarrierInfo = new ArrayList<CarrierInfo>(excludedCarrierInfo);
+            return this;
+        }
     }
 }
diff --git a/telephony/java/android/telephony/SecurityAlgorithmUpdate.java b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
index c902016..57209eb 100644
--- a/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
+++ b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
@@ -129,11 +129,15 @@
     public static final int CONNECTION_EVENT_NAS_SIGNALLING_LTE = 4;
     public static final int CONNECTION_EVENT_AS_SIGNALLING_LTE = 5;
     public static final int CONNECTION_EVENT_VOLTE_SIP = 6;
-    public static final int CONNECTION_EVENT_VOLTE_RTP = 7;
-    public static final int CONNECTION_EVENT_NAS_SIGNALLING_5G = 8;
-    public static final int CONNECTION_EVENT_AS_SIGNALLING_5G = 9;
-    public static final int CONNECTION_EVENT_VONR_SIP = 10;
-    public static final int CONNECTION_EVENT_VONR_RTP = 11;
+    public static final int CONNECTION_EVENT_VOLTE_SIP_SOS = 7;
+    public static final int CONNECTION_EVENT_VOLTE_RTP = 8;
+    public static final int CONNECTION_EVENT_VOLTE_RTP_SOS = 9;
+    public static final int CONNECTION_EVENT_NAS_SIGNALLING_5G = 10;
+    public static final int CONNECTION_EVENT_AS_SIGNALLING_5G = 11;
+    public static final int CONNECTION_EVENT_VONR_SIP = 12;
+    public static final int CONNECTION_EVENT_VONR_SIP_SOS = 13;
+    public static final int CONNECTION_EVENT_VONR_RTP = 14;
+    public static final int CONNECTION_EVENT_VONR_RTP_SOS = 15;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -141,9 +145,11 @@
             CONNECTION_EVENT_PS_SIGNALLING_GPRS, CONNECTION_EVENT_CS_SIGNALLING_3G,
             CONNECTION_EVENT_PS_SIGNALLING_3G, CONNECTION_EVENT_NAS_SIGNALLING_LTE,
             CONNECTION_EVENT_AS_SIGNALLING_LTE, CONNECTION_EVENT_VOLTE_SIP,
-            CONNECTION_EVENT_VOLTE_RTP, CONNECTION_EVENT_NAS_SIGNALLING_5G,
+            CONNECTION_EVENT_VOLTE_SIP_SOS, CONNECTION_EVENT_VOLTE_RTP,
+            CONNECTION_EVENT_VOLTE_RTP_SOS, CONNECTION_EVENT_NAS_SIGNALLING_5G,
             CONNECTION_EVENT_AS_SIGNALLING_5G, CONNECTION_EVENT_VONR_SIP,
-            CONNECTION_EVENT_VONR_RTP})
+            CONNECTION_EVENT_VONR_SIP_SOS, CONNECTION_EVENT_VONR_RTP,
+            CONNECTION_EVENT_VONR_RTP_SOS})
     public @interface ConnectionEvent {
     }
 
@@ -169,6 +175,7 @@
     public static final int SECURITY_ALGORITHM_NEA1 = 56;
     public static final int SECURITY_ALGORITHM_NEA2 = 57;
     public static final int SECURITY_ALGORITHM_NEA3 = 58;
+    public static final int SECURITY_ALGORITHM_SIP_NO_IPSEC_CONFIG = 66;
     public static final int SECURITY_ALGORITHM_IMS_NULL = 67;
     public static final int SECURITY_ALGORITHM_SIP_NULL = 68;
     public static final int SECURITY_ALGORITHM_AES_GCM = 69;
@@ -178,6 +185,7 @@
     public static final int SECURITY_ALGORITHM_AES_EDE3_CBC = 73;
     public static final int SECURITY_ALGORITHM_HMAC_SHA1_96 = 74;
     public static final int SECURITY_ALGORITHM_HMAC_MD5_96 = 75;
+    public static final int SECURITY_ALGORITHM_RTP = 85;
     public static final int SECURITY_ALGORITHM_SRTP_NULL = 86;
     public static final int SECURITY_ALGORITHM_SRTP_AES_COUNTER = 87;
     public static final int SECURITY_ALGORITHM_SRTP_AES_F8 = 88;
@@ -199,15 +207,16 @@
             SECURITY_ALGORITHM_UEA2, SECURITY_ALGORITHM_EEA0, SECURITY_ALGORITHM_EEA1,
             SECURITY_ALGORITHM_EEA2, SECURITY_ALGORITHM_EEA3, SECURITY_ALGORITHM_NEA0,
             SECURITY_ALGORITHM_NEA1, SECURITY_ALGORITHM_NEA2, SECURITY_ALGORITHM_NEA3,
-            SECURITY_ALGORITHM_IMS_NULL, SECURITY_ALGORITHM_SIP_NULL, SECURITY_ALGORITHM_AES_GCM,
+            SECURITY_ALGORITHM_SIP_NO_IPSEC_CONFIG, SECURITY_ALGORITHM_IMS_NULL,
+            SECURITY_ALGORITHM_SIP_NULL, SECURITY_ALGORITHM_AES_GCM,
             SECURITY_ALGORITHM_AES_GMAC, SECURITY_ALGORITHM_AES_CBC,
             SECURITY_ALGORITHM_DES_EDE3_CBC, SECURITY_ALGORITHM_AES_EDE3_CBC,
             SECURITY_ALGORITHM_HMAC_SHA1_96, SECURITY_ALGORITHM_HMAC_MD5_96,
-            SECURITY_ALGORITHM_SRTP_NULL, SECURITY_ALGORITHM_SRTP_AES_COUNTER,
-            SECURITY_ALGORITHM_SRTP_AES_F8, SECURITY_ALGORITHM_SRTP_HMAC_SHA1,
-            SECURITY_ALGORITHM_ENCR_AES_GCM_16, SECURITY_ALGORITHM_ENCR_AES_CBC,
-            SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128, SECURITY_ALGORITHM_UNKNOWN,
-            SECURITY_ALGORITHM_OTHER, SECURITY_ALGORITHM_ORYX})
+            SECURITY_ALGORITHM_RTP, SECURITY_ALGORITHM_SRTP_NULL,
+            SECURITY_ALGORITHM_SRTP_AES_COUNTER, SECURITY_ALGORITHM_SRTP_AES_F8,
+            SECURITY_ALGORITHM_SRTP_HMAC_SHA1, SECURITY_ALGORITHM_ENCR_AES_GCM_16,
+            SECURITY_ALGORITHM_ENCR_AES_CBC, SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128,
+            SECURITY_ALGORITHM_UNKNOWN, SECURITY_ALGORITHM_OTHER, SECURITY_ALGORITHM_ORYX})
     public @interface SecurityAlgorithm {
     }
 
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index c958aba..b7baabf 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -3123,6 +3123,12 @@
     @FlaggedApi(Flags.FLAG_MMS_DISABLED_ERROR)
     public static final int MMS_ERROR_MMS_DISABLED_BY_CARRIER = 12;
 
+    /**
+     * The MMS pdu was too large to send or too large to download over the current connection.
+     * @hide
+     */
+    public static final int MMS_ERROR_TOO_LARGE_FOR_TRANSPORT = 13;
+
     /** Intent extra name for MMS sending result data in byte array type */
     public static final String EXTRA_MMS_DATA = "android.telephony.extra.MMS_DATA";
     /** Intent extra name for HTTP status code for MMS HTTP failure in integer type */
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 5615602..1bf11df 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1315,7 +1315,7 @@
      * A source of phone number: the EF-MSISDN (see 3GPP TS 31.102),
      * or EF-MDN for CDMA (see 3GPP2 C.P0065-B), from UICC application.
      *
-     * <p>The availability and a of the number depends on the carrier.
+     * <p>The availability and accuracy of the number depends on the carrier.
      * The number may be updated by over-the-air update to UICC applications
      * from the carrier, or by other means with physical access to the SIM.
      */
@@ -1557,12 +1557,21 @@
      * caller can see all subscription across user profiles as it does today today even if it's
      * {@code false}.
      */
-    private boolean mIsForAllUserProfiles = false;
+    private final boolean mIsForAllUserProfiles;
 
     /** @hide */
     @UnsupportedAppUsage
     public SubscriptionManager(Context context) {
-        if (DBG) logd("SubscriptionManager created");
+        this(context, false /*isForAllUserProfiles*/);
+    }
+
+    /**  Constructor */
+    private SubscriptionManager(Context context, boolean isForAllUserProfiles) {
+        if (DBG) {
+            logd("SubscriptionManager created "
+                    + (isForAllUserProfiles ? "for all user profile" : ""));
+        }
+        mIsForAllUserProfiles = isForAllUserProfiles;
         mContext = context;
     }
 
@@ -1609,14 +1618,15 @@
     }
 
     /**
-     * Register for changes to the list of active {@link SubscriptionInfo} records or to the
-     * individual records themselves. When a change occurs the onSubscriptionsChanged method of
-     * the listener will be invoked immediately if there has been a notification. The
-     * onSubscriptionChanged method will also be triggered once initially when calling this
-     * function.
+     * Register for changes to the list of {@link SubscriptionInfo} records or to the
+     * individual records (active or inactive) themselves. When a change occurs, the
+     * {@link OnSubscriptionsChangedListener#onSubscriptionsChanged()} method of
+     * the listener will be invoked immediately. The
+     * {@link OnSubscriptionsChangedListener#onSubscriptionsChanged()} method will also be invoked
+     * once initially when calling this method.
      *
      * @param listener an instance of {@link OnSubscriptionsChangedListener} with
-     *                 onSubscriptionsChanged overridden.
+     * {@link OnSubscriptionsChangedListener#onSubscriptionsChanged()} overridden.
      * @param executor the executor that will execute callbacks.
      */
     public void addOnSubscriptionsChangedListener(
@@ -1944,7 +1954,6 @@
      *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-    // @RequiresPermission(TODO(b/308809058))
     public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
         List<SubscriptionInfo> activeList = null;
 
@@ -1998,9 +2007,12 @@
     }
 
     /**
-     * Convert this subscription manager instance into one that can see all subscriptions across
+     * Create a new subscription manager instance that can see all subscriptions across
      * user profiles.
      *
+     * The permission check for accessing all subscriptions will be enforced upon calling the
+     * individual APIs linked below.
+     *
      * @return a SubscriptionManager that can see all subscriptions regardless its user profile
      * association.
      *
@@ -2008,13 +2020,10 @@
      * @see #getActiveSubscriptionInfoCount
      * @see UserHandle
      */
-    @FlaggedApi(Flags.FLAG_WORK_PROFILE_API_SPLIT)
-    // @RequiresPermission(TODO(b/308809058))
-    // The permission check for accessing all subscriptions will be enforced upon calling the
-    // individual APIs linked above.
+    @FlaggedApi(Flags.FLAG_ENFORCE_SUBSCRIPTION_USER_FILTER)
+    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES)
     @NonNull public SubscriptionManager createForAllUserProfiles() {
-        mIsForAllUserProfiles = true;
-        return this;
+        return new SubscriptionManager(mContext, true/*isForAllUserProfiles*/);
     }
 
     /**
@@ -2207,7 +2216,6 @@
      *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-    // @RequiresPermission(TODO(b/308809058))
     public int getActiveSubscriptionInfoCount() {
         int result = 0;
 
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 1b47dfe..c1ceaef 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -25,6 +25,7 @@
 import android.Manifest;
 import android.annotation.BytesLong;
 import android.annotation.CallbackExecutor;
+import android.annotation.CurrentTimeMillisLong;
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.LongDef;
@@ -6445,10 +6446,6 @@
      * targeting API level 31+.
      *
      * @return the current call state.
-     *
-     * @throws UnsupportedOperationException If the device does not have
-     *          {@link PackageManager#FEATURE_TELECOM}.
-     *
      * @deprecated Use {@link #getCallStateForSubscription} to retrieve the call state for a
      * specific telephony subscription (which allows carrier privileged apps),
      * {@link TelephonyCallback.CallStateListener} for real-time call state updates, or
@@ -6456,7 +6453,6 @@
      * device.
      */
     @RequiresPermission(value = android.Manifest.permission.READ_PHONE_STATE, conditional = true)
-    @RequiresFeature(PackageManager.FEATURE_TELECOM)
     @Deprecated
     public @CallState int getCallState() {
         if (mContext != null) {
@@ -6893,6 +6889,7 @@
         }
     }
 
+    // TODO(b/316183370): replace all @code with @link in javadoc after feature is released
     /**
      * @return true if the current device is "voice capable".
      * <p>
@@ -6906,7 +6903,10 @@
      * PackageManager.FEATURE_TELEPHONY system feature, which is available
      * on any device with a telephony radio, even if the device is
      * data-only.
-     * @deprecated Replaced by {@link #isDeviceVoiceCapable()}
+     * @deprecated Replaced by {@code #isDeviceVoiceCapable()}. Starting from Android 15, voice
+     * capability may also be overridden by carriers for a given subscription. For voice capable
+     * device (when {@code #isDeviceVoiceCapable} return {@code true}), caller should check for
+     * subscription-level voice capability as well. See {@code #isDeviceVoiceCapable} for details.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     @Deprecated
@@ -6928,9 +6928,10 @@
      * .FEATURE_TELEPHONY system feature, which is available on any device with a telephony
      * radio, even if the device is data-only.
      * <p>
-     * To check if a subscription is "voice capable", call method
-     * {@link SubscriptionInfo#getServiceCapabilities()} and compare  the result with
-     * bitmask {@link SubscriptionManager#SERVICE_CAPABILITY_VOICE}.
+     * Starting from Android 15, voice capability may also be overridden by carrier for a given
+     * subscription on a voice capable device. To check if a subscription is "voice capable",
+     * call method {@code SubscriptionInfo#getServiceCapabilities()} and check if
+     * {@code SubscriptionManager#SERVICE_CAPABILITY_VOICE} is included.
      *
      * @see SubscriptionInfo#getServiceCapabilities()
      */
@@ -6948,7 +6949,10 @@
      * <p>
      * Note: Voicemail waiting sms, cell broadcasting sms, and MMS are
      *       disabled when device doesn't support sms.
-     * @deprecated Replaced by {@link #isDeviceSmsCapable()}
+     * @deprecated Replaced by {@code #isDeviceSmsCapable()}. Starting from Android 15, SMS
+     * capability may also be overridden by carriers for a given subscription. For SMS capable
+     * device (when {@code #isDeviceSmsCapable} return {@code true}), caller should check for
+     * subscription-level SMS capability as well. See {@code #isDeviceSmsCapable} for details.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public boolean isSmsCapable() {
@@ -6966,9 +6970,10 @@
      * Note: Voicemail waiting SMS, cell broadcasting SMS, and MMS are
      *       disabled when device doesn't support SMS.
      * <p>
-     * To check if a subscription is "SMS capable", call method
-     * {@link SubscriptionInfo#getServiceCapabilities()} and compare result with
-     * bitmask {@link SubscriptionManager#SERVICE_CAPABILITY_SMS}.
+     * Starting from Android 15, SMS capability may also be overridden by carriers for a given
+     * subscription on an SMS capable device. To check if a subscription is "SMS capable",
+     * call method {@code SubscriptionInfo#getServiceCapabilities()} and check if
+     * {@code SubscriptionManager#SERVICE_CAPABILITY_SMS} is included.
      *
      * @see SubscriptionInfo#getServiceCapabilities()
      */
@@ -10782,9 +10787,7 @@
     }
 
     /**
-     * @throws UnsupportedOperationException If the device does not have
-     *          {@link PackageManager#FEATURE_TELECOM}.
-     * @deprecated Use {@link android.telecom.TelecomManager#isInCall} instead
+   * @deprecated Use {@link android.telecom.TelecomManager#isInCall} instead
      * @hide
      */
     @Deprecated
@@ -10793,15 +10796,12 @@
             android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
             android.Manifest.permission.READ_PHONE_STATE
     })
-    @RequiresFeature(PackageManager.FEATURE_TELECOM)
     public boolean isOffhook() {
         TelecomManager tm = (TelecomManager) mContext.getSystemService(TELECOM_SERVICE);
         return tm.isInCall();
     }
 
     /**
-     * @throws UnsupportedOperationException If the device does not have
-     *          {@link PackageManager#FEATURE_TELECOM}.
      * @deprecated Use {@link android.telecom.TelecomManager#isRinging} instead
      * @hide
      */
@@ -10811,15 +10811,12 @@
             android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
             android.Manifest.permission.READ_PHONE_STATE
     })
-    @RequiresFeature(PackageManager.FEATURE_TELECOM)
     public boolean isRinging() {
         TelecomManager tm = (TelecomManager) mContext.getSystemService(TELECOM_SERVICE);
         return tm.isRinging();
     }
 
     /**
-     * @throws UnsupportedOperationException If the device does not have
-     *          {@link PackageManager#FEATURE_TELECOM}.
      * @deprecated Use {@link android.telecom.TelecomManager#isInCall} instead
      * @hide
      */
@@ -10829,7 +10826,6 @@
             android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
             android.Manifest.permission.READ_PHONE_STATE
     })
-    @RequiresFeature(PackageManager.FEATURE_TELECOM)
     public boolean isIdle() {
         TelecomManager tm = (TelecomManager) mContext.getSystemService(TELECOM_SERVICE);
         return !tm.isInCall();
@@ -12044,11 +12040,8 @@
      *
      * @return {@code true} if the device supports TTY mode, and {@code false} otherwise.
      *
-     * @throws UnsupportedOperationException If the device does not have
-     *          {@link PackageManager#FEATURE_TELECOM}.
      */
     @Deprecated
-    @RequiresFeature(PackageManager.FEATURE_TELECOM)
     public boolean isTtyModeSupported() {
         try {
             TelecomManager telecomManager = null;
@@ -13156,7 +13149,7 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public ServiceState getServiceStateForSubscriber(int subId) {
-        return getServiceStateForSubscriber(getSubId(), false, false);
+        return getServiceStateForSubscriber(subId, false, false);
     }
 
     /**
@@ -18500,8 +18493,7 @@
 
     /**
      * Get last known cell identity.
-     * Require {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and
-     * com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID, otherwise throws SecurityException.
+     *
      * If there is current registered network this value will be same as the registered cell
      * identity. If the device goes out of service the previous cell identity is cached and
      * will be returned. If the cache age of the Cell identity is more than 24 hours
@@ -18509,8 +18501,10 @@
      * @return last known cell identity {@CellIdentity}.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
     @RequiresPermission(allOf = {Manifest.permission.ACCESS_FINE_LOCATION,
-            "com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID"})
+            Manifest.permission.ACCESS_LAST_KNOWN_CELL_ID})
     public @Nullable CellIdentity getLastKnownCellIdentity() {
         try {
             ITelephony telephony = getITelephony();
@@ -18586,13 +18580,11 @@
      * an overall "device can make voice calls" boolean, they can use {@link
      * ServiceState#getNetworkRegistrationInfo} to check CS registration state.
      *
-     * <p>TODO(b/215240050) In the future, this API will be removed and replaced with a new superset
-     * API to disentangle the "true" {@link ServiceState} meaning of "this is the connection status
-     * to the tower" from IMS registration state and over-the-top voice calling capabilities.
-     *
      * @hide
      */
     @TestApi
+    @SystemApi
+    @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
     @RequiresPermission(Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE)
     public void setVoiceServiceStateOverride(boolean hasService) {
         try {
@@ -18723,51 +18715,93 @@
      * call diagnostic data
      * @hide
      */
-    public static class EmergencyCallDiagnosticParams {
+    @SystemApi
+    @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+    public static final class EmergencyCallDiagnosticParams {
+        public static final class Builder {
+            private boolean mCollectTelecomDumpSys;
+            private boolean mCollectTelephonyDumpsys;
 
-       private boolean mCollectTelecomDumpSys;
-       private boolean mCollectTelephonyDumpsys;
-       private boolean mCollectLogcat;
+            // If this is set to a value other than -1L, then the logcat collection is enabled.
+            // Logcat lines with this time or greater are collected how much is collected is
+            // dependent on internal implementation. Time represented as milliseconds since boot.
+            private long mLogcatStartTimeMillis = sUnsetLogcatStartTime;
 
-        //logcat lines with this time or greater are collected
-        //how much is collected is dependent on internal implementation.
-        //Time represented as milliseconds since January 1, 1970 UTC
+            /**
+             * Allows enabling of telecom dumpsys collection.
+             * @param collectTelecomDumpsys Determines whether telecom dumpsys should be collected.
+             * @return Builder instance corresponding to the configured call diagnostic params.
+             */
+            public @NonNull Builder setTelecomDumpSysCollectionEnabled(
+                    boolean collectTelecomDumpsys) {
+                mCollectTelecomDumpSys = collectTelecomDumpsys;
+                return this;
+            }
+
+            /**
+             * Allows enabling of telephony dumpsys collection.
+             * @param collectTelephonyDumpsys Determines if telephony dumpsys should be collected.
+             * @return Builder instance corresponding to the configured call diagnostic params.
+             */
+            public @NonNull Builder setTelephonyDumpSysCollectionEnabled(
+                    boolean collectTelephonyDumpsys) {
+                mCollectTelephonyDumpsys = collectTelephonyDumpsys;
+                return this;
+            }
+
+            /**
+             * Allows enabling of logcat (system,radio) collection.
+             * @param startTimeMillis Enables logcat collection as of the indicated timestamp.
+             * @return Builder instance corresponding to the configured call diagnostic params.
+             */
+            public @NonNull Builder setLogcatCollectionStartTimeMillis(
+                    @CurrentTimeMillisLong long startTimeMillis) {
+                mLogcatStartTimeMillis = startTimeMillis;
+                return this;
+            }
+
+            /**
+             * Build the EmergencyCallDiagnosticParams from the provided Builder config.
+             * @return {@link EmergencyCallDiagnosticParams} instance from provided builder.
+             */
+            public @NonNull EmergencyCallDiagnosticParams build() {
+                return new EmergencyCallDiagnosticParams(mCollectTelecomDumpSys,
+                        mCollectTelephonyDumpsys, mLogcatStartTimeMillis);
+            }
+        }
+
+        private boolean mCollectTelecomDumpSys;
+        private boolean mCollectTelephonyDumpsys;
+        private boolean mCollectLogcat;
         private long mLogcatStartTimeMillis;
 
+        private static long sUnsetLogcatStartTime = -1L;
+
+        private EmergencyCallDiagnosticParams(boolean collectTelecomDumpSys,
+                boolean collectTelephonyDumpsys, long logcatStartTimeMillis) {
+            mCollectTelecomDumpSys = collectTelecomDumpSys;
+            mCollectTelephonyDumpsys = collectTelephonyDumpsys;
+            mLogcatStartTimeMillis = logcatStartTimeMillis;
+            mCollectLogcat = logcatStartTimeMillis != sUnsetLogcatStartTime;
+        }
 
         public boolean isTelecomDumpSysCollectionEnabled() {
             return mCollectTelecomDumpSys;
         }
 
-        public void setTelecomDumpSysCollection(boolean collectTelecomDumpSys) {
-            mCollectTelecomDumpSys = collectTelecomDumpSys;
-        }
-
         public boolean isTelephonyDumpSysCollectionEnabled() {
             return mCollectTelephonyDumpsys;
         }
 
-        public void setTelephonyDumpSysCollection(boolean collectTelephonyDumpsys) {
-            mCollectTelephonyDumpsys = collectTelephonyDumpsys;
-        }
-
         public boolean isLogcatCollectionEnabled() {
             return mCollectLogcat;
         }
 
-        public long getLogcatStartTime()
+        public long getLogcatCollectionStartTimeMillis()
         {
             return mLogcatStartTimeMillis;
         }
 
-        public void setLogcatCollection(boolean collectLogcat, long startTimeMillis) {
-            mCollectLogcat = collectLogcat;
-            if(mCollectLogcat)
-            {
-                mLogcatStartTimeMillis = startTimeMillis;
-            }
-        }
-
         @Override
         public String toString() {
             return "EmergencyCallDiagnosticParams{" +
@@ -18783,11 +18817,12 @@
      * Request telephony to persist state for debugging emergency call failures.
      *
      * @param dropboxTag Tag to use when persisting data to dropbox service.
-     *
-     * @see params Parameters controlling what is collected
+     * @param params Parameters controlling what is collected.
      *
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
     @RequiresPermission(android.Manifest.permission.DUMP)
     public void persistEmergencyCallDiagnosticData(@NonNull String dropboxTag,
             @NonNull EmergencyCallDiagnosticParams params) {
@@ -18800,7 +18835,7 @@
             if (telephony != null) {
                 telephony.persistEmergencyCallDiagnosticData(dropboxTag,
                         params.isLogcatCollectionEnabled(),
-                        params.getLogcatStartTime(),
+                        params.getLogcatCollectionStartTimeMillis(),
                         params.isTelecomDumpSysCollectionEnabled(),
                         params.isTelephonyDumpSysCollectionEnabled());
             }
@@ -18923,6 +18958,62 @@
     }
 
     /**
+     * Enables or disables notifications sent when cellular null cipher or integrity algorithms
+     * are in use by the cellular modem.
+     *
+     * @throws IllegalStateException if the Telephony process is not currently available
+     * @throws SecurityException if the caller does not have the required privileges
+     * @throws UnsupportedOperationException if the modem does not support reporting on ciphering
+     * and integrity algorithms in use
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY)
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    @SystemApi
+    public void setEnableNullCipherNotifications(boolean enable) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                telephony.setEnableNullCipherNotifications(enable);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "setEnableNullCipherNotifications RemoteException", ex);
+            ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get whether notifications are enabled for null cipher or integrity algorithms in use by the
+     * cellular modem.
+     *
+     * @throws IllegalStateException if the Telephony process is not currently available
+     * @throws SecurityException if the caller does not have the required privileges
+     * @throws UnsupportedOperationException if the modem does not support reporting on ciphering
+     * and integrity algorithms in use
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY)
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @SystemApi
+    public boolean isNullCipherNotificationsEnabled() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.isNullCipherNotificationsEnabled();
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "isNullCipherNotificationsEnabled RemoteException", ex);
+            ex.rethrowFromSystemServer();
+        }
+        return false;
+    }
+
+
+    /**
      * Get current cell broadcast message identifier ranges.
      *
      * @throws SecurityException if the caller does not have the required permission
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index 9dd83d1..b6f9e1f 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -451,7 +451,7 @@
 
     /**
      * Return the network validation status that was initiated by {@link
-     * DataService.DataServiceProvider#requestValidation}
+     * DataService.DataServiceProvider#requestNetworkValidation}
      *
      * @return The network validation status of data connection.
      */
@@ -931,7 +931,7 @@
 
         /**
          * Set the network validation status that corresponds to the state of the network validation
-         * request started by {@link DataService.DataServiceProvider#requestValidation}
+         * request started by {@link DataService.DataServiceProvider#requestNetworkValidation}
          *
          * @param status The network validation status.
          * @return The same instance of the builder.
diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java
index 80e91a3..f04e1c9 100644
--- a/telephony/java/android/telephony/data/DataService.java
+++ b/telephony/java/android/telephony/data/DataService.java
@@ -415,13 +415,13 @@
          *     request validation to the DataService and checks if the request has been submitted.
          */
         @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
-        public void requestValidation(int cid,
+        public void requestNetworkValidation(int cid,
                 @NonNull @CallbackExecutor Executor executor,
                 @NonNull @DataServiceCallback.ResultCode Consumer<Integer> resultCodeCallback) {
             Objects.requireNonNull(executor, "executor cannot be null");
             Objects.requireNonNull(resultCodeCallback, "resultCodeCallback cannot be null");
 
-            Log.d(TAG, "requestValidation: " + cid);
+            Log.d(TAG, "requestNetworkValidation: " + cid);
 
             // The default implementation is to return unsupported.
             executor.execute(() -> resultCodeCallback
@@ -741,7 +741,7 @@
                 case DATA_SERVICE_REQUEST_VALIDATION:
                     if (serviceProvider == null) break;
                     ValidationRequest validationRequest = (ValidationRequest) message.obj;
-                    serviceProvider.requestValidation(
+                    serviceProvider.requestNetworkValidation(
                             validationRequest.cid,
                             validationRequest.executor,
                             FunctionalUtils
@@ -924,9 +924,10 @@
         }
 
         @Override
-        public void requestValidation(int slotIndex, int cid, IIntegerConsumer resultCodeCallback) {
+        public void requestNetworkValidation(int slotIndex, int cid,
+                IIntegerConsumer resultCodeCallback) {
             if (resultCodeCallback == null) {
-                loge("requestValidation: resultCodeCallback is null");
+                loge("requestNetworkValidation: resultCodeCallback is null");
                 return;
             }
             ValidationRequest validationRequest =
diff --git a/telephony/java/android/telephony/data/IDataService.aidl b/telephony/java/android/telephony/data/IDataService.aidl
index 15f8881..c36c302 100644
--- a/telephony/java/android/telephony/data/IDataService.aidl
+++ b/telephony/java/android/telephony/data/IDataService.aidl
@@ -48,5 +48,5 @@
     void cancelHandover(int slotId, int cid, IDataServiceCallback callback);
     void registerForUnthrottleApn(int slotIndex, IDataServiceCallback callback);
     void unregisterForUnthrottleApn(int slotIndex, IDataServiceCallback callback);
-    void requestValidation(int slotId, int cid, IIntegerConsumer callback);
+    void requestNetworkValidation(int slotId, int cid, IIntegerConsumer callback);
 }
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index 4c37f7d..b84ff29 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -16,6 +16,7 @@
 
 package android.telephony.ims;
 
+import android.annotation.FlaggedApi;
 import android.annotation.LongDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -46,6 +47,7 @@
 
 import com.android.ims.internal.IImsFeatureStatusCallback;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.lang.annotation.Retention;
@@ -152,12 +154,36 @@
     public static final long CAPABILITY_TERMINAL_BASED_CALL_WAITING = 1 << 2;
 
     /**
+     * This ImsService supports the capability to manage calls on multiple subscriptions at the same
+     * time.
+     * <p>
+     * When set, this ImsService supports managing calls on multiple subscriptions at the same time
+     * for all WLAN network configurations. Telephony will allow new outgoing/incoming IMS calls to
+     * be set up on other subscriptions while there is an ongoing call. The ImsService must also
+     * support managing calls on WWAN + WWAN configurations whenever the modem also reports
+     * simultaneous calling availability, which can be listened to using the
+     * {@link android.telephony.TelephonyCallback.SimultaneousCellularCallingSupportListener} API.
+     * Telephony will only allow additional ongoing/incoming IMS calls on another subscription to be
+     * set up on WWAN + WWAN configurations when the modem reports that simultaneous cellular
+     * calling is allowed at the current time on both subscriptions where there are ongoing calls.
+     * <p>
+     * When unset (default), this ImsService can not support calls on multiple subscriptions at the
+     * same time for any WLAN or WWAN configurations, so pending outgoing call placed on another
+     * cellular subscription while there is an ongoing call will be cancelled by Telephony.
+     * Similarly, any incoming call notification on another cellular subscription while there is an
+     * ongoing call will be rejected.
+     * @hide TODO: move this to system API when we have a backing implementation + CTS testing
+     */
+    @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+    public static final long CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING = 1 << 3;
+
+    /**
      * Used for internal correctness checks of capabilities set by the ImsService implementation and
      * tracks the index of the largest defined flag in the capabilities long.
      * @hide
      */
     public static final long CAPABILITY_MAX_INDEX =
-            Long.numberOfTrailingZeros(CAPABILITY_TERMINAL_BASED_CALL_WAITING);
+            Long.numberOfTrailingZeros(CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING);
 
     /**
      * @hide
diff --git a/telephony/java/android/telephony/ims/RegistrationManager.java b/telephony/java/android/telephony/ims/RegistrationManager.java
index 54ceaed..9f83da9 100644
--- a/telephony/java/android/telephony/ims/RegistrationManager.java
+++ b/telephony/java/android/telephony/ims/RegistrationManager.java
@@ -84,7 +84,7 @@
                 SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK,
                 SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT,
                 SUGGESTED_ACTION_TRIGGER_RAT_BLOCK,
-                SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCK
+                SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCKS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SuggestedAction {}
@@ -116,9 +116,10 @@
 
     /**
      * Indicates that the IMS registration on current RAT failed multiple times.
-     * The radio shall block the current RAT and search for other available RATs in the
-     * background. If no other RAT is available that meets the carrier requirements, the
-     * radio may remain on the current RAT for internet service. The radio clears all
+     * The radio shall block the {@link ImsRegistrationImplBase.ImsRegistrationTech}
+     * included with this and search for other available RATs in the background.
+     * If no other RAT is available that meets the carrier requirements, the
+     * radio may remain on the blocked RAT for internet service. The radio clears all
      * RATs marked as unavailable if the IMS service is registered to the carrier network.
      * @hide
      */
@@ -133,7 +134,7 @@
      */
     @SystemApi
     @FlaggedApi(Flags.FLAG_ADD_RAT_RELATED_SUGGESTED_ACTION_TO_IMS_REGISTRATION)
-    int SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCK = 4;
+    int SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCKS = 4;
 
     /**@hide*/
     // Translate ImsRegistrationImplBase API to new AccessNetworkConstant because WLAN
diff --git a/telephony/java/android/telephony/satellite/AntennaPosition.java b/telephony/java/android/telephony/satellite/AntennaPosition.java
index 8842886..d6440fc 100644
--- a/telephony/java/android/telephony/satellite/AntennaPosition.java
+++ b/telephony/java/android/telephony/satellite/AntennaPosition.java
@@ -35,10 +35,10 @@
 @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
 public final class AntennaPosition implements Parcelable {
     /** Antenna direction used for satellite communication. */
-    @NonNull AntennaDirection mAntennaDirection;
+    @NonNull private AntennaDirection mAntennaDirection;
 
     /** Enum corresponding to device hold position to be used by the end user. */
-    @SatelliteManager.DeviceHoldPosition int mSuggestedHoldPosition;
+    @SatelliteManager.DeviceHoldPosition private int mSuggestedHoldPosition;
 
     /**
      * @hide
diff --git a/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl
new file mode 100644
index 0000000..9ff73e2
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+/**
+ * Interface for satellite state change callback.
+ * @hide
+ */
+oneway interface ISatelliteModemStateCallback {
+    /**
+     * Indicates that the satellite modem state has changed.
+     *
+     * @param state The current satellite modem state.
+     */
+    void onSatelliteModemStateChanged(in int state);
+}
diff --git a/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl
deleted file mode 100644
index cd9d81e..0000000
--- a/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony.satellite;
-
-/**
- * Interface for satellite state change callback.
- * @hide
- */
-oneway interface ISatelliteStateCallback {
-    /**
-     * Indicates that the satellite modem state has changed.
-     *
-     * @param state The current satellite modem state.
-     */
-    void onSatelliteModemStateChanged(in int state);
-}
diff --git a/telephony/java/android/telephony/satellite/PointingInfo.java b/telephony/java/android/telephony/satellite/PointingInfo.java
index 022a856..9440b65 100644
--- a/telephony/java/android/telephony/satellite/PointingInfo.java
+++ b/telephony/java/android/telephony/satellite/PointingInfo.java
@@ -17,6 +17,7 @@
 package android.telephony.satellite;
 
 import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
@@ -108,11 +109,19 @@
         return sb.toString();
     }
 
+    /**
+     * Returns the azimuth of the satellite, in degrees.
+     */
+    @FloatRange(from = -180, to = 180)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public float getSatelliteAzimuthDegrees() {
         return mSatelliteAzimuthDegrees;
     }
 
+    /**
+     * Returns the elevation of the satellite, in degrees.
+     */
+    @FloatRange(from = -90, to = 90)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public float getSatelliteElevationDegrees() {
         return mSatelliteElevationDegrees;
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
index b5763c3..9aaa986 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
@@ -22,10 +22,15 @@
 
 import com.android.internal.telephony.flags.Flags;
 
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
  * A callback class for listening to satellite datagrams.
+ * {@link SatelliteDatagramCallback} is registered to telephony when an app which invokes
+ * {@link SatelliteManager#registerForIncomingDatagram(Executor, SatelliteDatagramCallback)},
+ * and {@link #onSatelliteDatagramReceived(long, SatelliteDatagram, int, Consumer)} will be invoked
+ * when a new datagram is received from satellite.
  *
  * @hide
  */
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index e09bd20..b97822a 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -34,7 +34,6 @@
 import android.os.OutcomeReceiver;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
-import android.os.ServiceSpecificException;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyFrameworkInitializer;
@@ -49,8 +48,10 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.time.Duration;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -75,8 +76,9 @@
     private static final ConcurrentHashMap<SatelliteProvisionStateCallback,
             ISatelliteProvisionStateCallback> sSatelliteProvisionStateCallbackMap =
             new ConcurrentHashMap<>();
-    private static final ConcurrentHashMap<SatelliteStateCallback, ISatelliteStateCallback>
-            sSatelliteStateCallbackMap = new ConcurrentHashMap<>();
+    private static final ConcurrentHashMap<SatelliteModemStateCallback,
+            ISatelliteModemStateCallback>
+            sSatelliteModemStateCallbackMap = new ConcurrentHashMap<>();
     private static final ConcurrentHashMap<SatelliteTransmissionUpdateCallback,
             ISatelliteTransmissionUpdateCallback> sSatelliteTransmissionUpdateCallbackMap =
             new ConcurrentHashMap<>();
@@ -144,7 +146,7 @@
 
     /**
      * Bundle key to get the response from
-     * {@link #requestIsSatelliteEnabled(Executor, OutcomeReceiver)}.
+     * {@link #requestIsEnabled(Executor, OutcomeReceiver)}.
      * @hide
      */
 
@@ -160,7 +162,7 @@
 
     /**
      * Bundle key to get the response from
-     * {@link #requestIsSatelliteSupported(Executor, OutcomeReceiver)}.
+     * {@link #requestIsSupported(Executor, OutcomeReceiver)}.
      * @hide
      */
 
@@ -168,7 +170,7 @@
 
     /**
      * Bundle key to get the response from
-     * {@link #requestSatelliteCapabilities(Executor, OutcomeReceiver)}.
+     * {@link #requestCapabilities(Executor, OutcomeReceiver)}.
      * @hide
      */
 
@@ -176,7 +178,7 @@
 
     /**
      * Bundle key to get the response from
-     * {@link #requestIsSatelliteProvisioned(Executor, OutcomeReceiver)}.
+     * {@link #requestIsProvisioned(Executor, OutcomeReceiver)}.
      * @hide
      */
 
@@ -184,7 +186,7 @@
 
     /**
      * Bundle key to get the response from
-     * {@link #requestIsSatelliteCommunicationAllowedForCurrentLocation(Executor, OutcomeReceiver)}.
+     * {@link #requestIsCommunicationAllowedForCurrentLocation(Executor, OutcomeReceiver)}.
      * @hide
      */
 
@@ -333,6 +335,12 @@
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_MODEM_BUSY = 22;
 
+    /**
+     * Telephony process is not currently available or satellite is not supported.
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int SATELLITE_RESULT_ILLEGAL_STATE = 23;
+
     /** @hide */
     @IntDef(prefix = {"SATELLITE_RESULT_"}, value = {
             SATELLITE_RESULT_SUCCESS,
@@ -357,7 +365,8 @@
             SATELLITE_RESULT_NOT_AUTHORIZED,
             SATELLITE_RESULT_NOT_SUPPORTED,
             SATELLITE_RESULT_REQUEST_IN_PROGRESS,
-            SATELLITE_RESULT_MODEM_BUSY
+            SATELLITE_RESULT_MODEM_BUSY,
+            SATELLITE_RESULT_ILLEGAL_STATE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SatelliteResult {}
@@ -484,7 +493,7 @@
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
+    public void requestEnabled(boolean enableSatellite, boolean enableDemoMode,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(executor);
@@ -507,7 +516,7 @@
             }
         } catch (RemoteException ex) {
             Rlog.e(TAG, "requestSatelliteEnabled() RemoteException: ", ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -523,11 +532,10 @@
      *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    public void requestIsSatelliteEnabled(@NonNull @CallbackExecutor Executor executor,
+    public void requestIsEnabled(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
@@ -558,11 +566,12 @@
                 };
                 telephony.requestIsSatelliteEnabled(mSubId, receiver);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
             loge("requestIsSatelliteEnabled() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -578,7 +587,6 @@
      *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -613,17 +621,23 @@
                 };
                 telephony.requestIsDemoModeEnabled(mSubId, receiver);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
             loge("requestIsDemoModeEnabled() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
     /**
      * Request to get whether the satellite service is supported on the device.
      *
+     * <p>
+     * Note: This API only checks whether the device supports the satellite feature. The result will
+     * not be affected by whether the device is provisioned.
+     * </p>
+     *
      * @param executor The executor on which the callback will be called.
      * @param callback The callback object to which the result will be delivered.
      *                 If the request is successful, {@link OutcomeReceiver#onResult(Object)}
@@ -631,11 +645,9 @@
      *                 service is supported on the device and {@code false} otherwise.
      *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
      *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
-     *
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    public void requestIsSatelliteSupported(@NonNull @CallbackExecutor Executor executor,
+    public void requestIsSupported(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
@@ -666,11 +678,12 @@
                 };
                 telephony.requestIsSatelliteSupported(mSubId, receiver);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
             loge("requestIsSatelliteSupported() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -685,11 +698,10 @@
      *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    public void requestSatelliteCapabilities(@NonNull @CallbackExecutor Executor executor,
+    public void requestCapabilities(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<SatelliteCapabilities, SatelliteException> callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
@@ -721,11 +733,12 @@
                 };
                 telephony.requestSatelliteCapabilities(mSubId, receiver);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
             loge("requestSatelliteCapabilities() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -920,10 +933,19 @@
     @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1;
 
+    /**
+     * Satellite communication restricted by entitlement server. This can be triggered based on
+     * the EntitlementStatus value received from the entitlement server to enable or disable
+     * satellite.
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT = 2;
+
     /** @hide */
     @IntDef(prefix = "SATELLITE_COMMUNICATION_RESTRICTION_REASON_", value = {
             SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER,
-            SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION
+            SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION,
+            SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SatelliteCommunicationRestrictionReason {}
@@ -942,11 +964,11 @@
      * @param callback The callback to notify of satellite transmission updates.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    public void startSatelliteTransmissionUpdates(@NonNull @CallbackExecutor Executor executor,
+    @SuppressWarnings("SamShouldBeLast")
+    public void startTransmissionUpdates(@NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener,
             @NonNull SatelliteTransmissionUpdateCallback callback) {
         Objects.requireNonNull(executor);
@@ -992,11 +1014,12 @@
                 telephony.startSatelliteTransmissionUpdates(mSubId, errorCallback,
                         internalCallback);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(
+                        () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
             loge("startSatelliteTransmissionUpdates() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1007,22 +1030,22 @@
      * {@link #SATELLITE_RESULT_SUCCESS}. All other results that this operation failed.
      *
      * @param callback The callback that was passed to {@link
-     * #startSatelliteTransmissionUpdates(Executor, Consumer, SatelliteTransmissionUpdateCallback)}.
+     * #startTransmissionUpdates(Executor, Consumer, SatelliteTransmissionUpdateCallback)}.
      * @param executor The executor on which the error code listener will be called.
      * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    public void stopSatelliteTransmissionUpdates(
-            @NonNull SatelliteTransmissionUpdateCallback callback,
-            @NonNull @CallbackExecutor Executor executor,
-            @SatelliteResult @NonNull Consumer<Integer> resultListener) {
+    public void stopTransmissionUpdates(@NonNull SatelliteTransmissionUpdateCallback callback,
+            @SuppressWarnings("ListenerLast") @NonNull @CallbackExecutor Executor executor,
+            @SuppressWarnings("ListenerLast") @SatelliteResult @NonNull
+            Consumer<Integer> resultListener) {
         Objects.requireNonNull(callback);
         Objects.requireNonNull(executor);
         Objects.requireNonNull(resultListener);
+
         ISatelliteTransmissionUpdateCallback internalCallback =
                 sSatelliteTransmissionUpdateCallbackMap.remove(callback);
 
@@ -1046,11 +1069,12 @@
                             () -> resultListener.accept(SATELLITE_RESULT_INVALID_ARGUMENTS)));
                 }
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(
+                        () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
             loge("stopSatelliteTransmissionUpdates() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1072,7 +1096,7 @@
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData,
+    public void provisionService(@NonNull String token, @NonNull byte[] provisionData,
             @Nullable CancellationSignal cancellationSignal,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
@@ -1095,11 +1119,12 @@
                 cancelRemote = telephony.provisionSatelliteService(mSubId, token, provisionData,
                         errorCallback);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(
+                        () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
             loge("provisionSatelliteService() RemoteException=" + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
         if (cancellationSignal != null) {
             cancellationSignal.setRemote(cancelRemote);
@@ -1112,20 +1137,19 @@
      * {@link SatelliteProvisionStateCallback#onSatelliteProvisionStateChanged(boolean)}
      * should report as deprovisioned.
      * For provisioning satellite service, refer to
-     * {@link #provisionSatelliteService(String, byte[], CancellationSignal, Executor, Consumer)}
+     * {@link #provisionService(String, byte[], CancellationSignal, Executor, Consumer)}
      *
      * @param token The token of the device/subscription to be deprovisioned.
      *              This should match with the token passed as input in
-     *              {@link #provisionSatelliteService(String, byte[], CancellationSignal, Executor,
+     *              {@link #provisionService(String, byte[], CancellationSignal, Executor,
      *              Consumer)}
      * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    public void deprovisionSatelliteService(@NonNull String token,
+    public void deprovisionService(@NonNull String token,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(token);
@@ -1144,11 +1168,12 @@
                 };
                 telephony.deprovisionSatelliteService(mSubId, token, errorCallback);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(
+                        () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
             loge("deprovisionSatelliteService() RemoteException=" + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1165,7 +1190,7 @@
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    @SatelliteResult public int registerForSatelliteProvisionStateChanged(
+    @SatelliteResult public int registerForProvisionStateChanged(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull SatelliteProvisionStateCallback callback) {
         Objects.requireNonNull(executor);
@@ -1191,7 +1216,7 @@
             }
         } catch (RemoteException ex) {
             loge("registerForSatelliteProvisionStateChanged() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
         return SATELLITE_RESULT_REQUEST_FAILED;
     }
@@ -1201,14 +1226,14 @@
      * If callback was not registered before, the request will be ignored.
      *
      * @param callback The callback that was passed to
-     * {@link #registerForSatelliteProvisionStateChanged(Executor, SatelliteProvisionStateCallback)}
+     * {@link #registerForProvisionStateChanged(Executor, SatelliteProvisionStateCallback)}
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    public void unregisterForSatelliteProvisionStateChanged(
+    public void unregisterForProvisionStateChanged(
             @NonNull SatelliteProvisionStateCallback callback) {
         Objects.requireNonNull(callback);
         ISatelliteProvisionStateCallback internalCallback =
@@ -1220,14 +1245,14 @@
                 if (internalCallback != null) {
                     telephony.unregisterForSatelliteProvisionStateChanged(mSubId, internalCallback);
                 } else {
-                    loge("unregisterForSatelliteProvisionStateChanged: No internal callback.");
+                    loge("unregisterForProvisionStateChanged: No internal callback.");
                 }
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException ex) {
-            loge("unregisterForSatelliteProvisionStateChanged() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            loge("unregisterForProvisionStateChanged() RemoteException: " + ex);
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1243,11 +1268,10 @@
      *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    public void requestIsSatelliteProvisioned(@NonNull @CallbackExecutor Executor executor,
+    public void requestIsProvisioned(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
@@ -1278,11 +1302,12 @@
                 };
                 telephony.requestIsSatelliteProvisioned(mSubId, receiver);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
             loge("requestIsSatelliteProvisioned() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1299,30 +1324,31 @@
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    @SatelliteResult public int registerForSatelliteModemStateChanged(
+    @SatelliteResult public int registerForModemStateChanged(
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull SatelliteStateCallback callback) {
+            @NonNull SatelliteModemStateCallback callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
 
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                ISatelliteStateCallback internalCallback = new ISatelliteStateCallback.Stub() {
+                ISatelliteModemStateCallback internalCallback =
+                        new ISatelliteModemStateCallback.Stub() {
                     @Override
                     public void onSatelliteModemStateChanged(int state) {
                         executor.execute(() -> Binder.withCleanCallingIdentity(() ->
                                 callback.onSatelliteModemStateChanged(state)));
                     }
                 };
-                sSatelliteStateCallbackMap.put(callback, internalCallback);
+                sSatelliteModemStateCallbackMap.put(callback, internalCallback);
                 return telephony.registerForSatelliteModemStateChanged(mSubId, internalCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException ex) {
             loge("registerForSatelliteModemStateChanged() RemoteException:" + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
         return SATELLITE_RESULT_REQUEST_FAILED;
     }
@@ -1332,31 +1358,33 @@
      * If callback was not registered before, the request will be ignored.
      *
      * @param callback The callback that was passed to
-     * {@link #registerForSatelliteModemStateChanged(Executor, SatelliteStateCallback)}.
+     * {@link #registerForModemStateChanged(Executor, SatelliteModemStateCallback)}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    public void unregisterForSatelliteModemStateChanged(@NonNull SatelliteStateCallback callback) {
+    public void unregisterForModemStateChanged(
+            @NonNull SatelliteModemStateCallback callback) {
         Objects.requireNonNull(callback);
-        ISatelliteStateCallback internalCallback = sSatelliteStateCallbackMap.remove(callback);
+        ISatelliteModemStateCallback internalCallback = sSatelliteModemStateCallbackMap.remove(
+                callback);
 
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
                 if (internalCallback != null) {
-                    telephony.unregisterForSatelliteModemStateChanged(mSubId, internalCallback);
+                    telephony.unregisterForModemStateChanged(mSubId, internalCallback);
                 } else {
-                    loge("unregisterForSatelliteModemStateChanged: No internal callback.");
+                    loge("unregisterForModemStateChanged: No internal callback.");
                 }
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException ex) {
-            loge("unregisterForSatelliteModemStateChanged() RemoteException:" + ex);
-            ex.rethrowFromSystemServer();
+            loge("unregisterForModemStateChanged() RemoteException:" + ex);
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1364,7 +1392,7 @@
      * Register to receive incoming datagrams over satellite.
      *
      * To poll for pending satellite datagrams, refer to
-     * {@link #pollPendingSatelliteDatagrams(Executor, Consumer)}
+     * {@link #pollPendingDatagrams(Executor, Consumer)}
      *
      * @param executor The executor on which the callback will be called.
      * @param callback The callback to handle incoming datagrams over satellite.
@@ -1377,7 +1405,7 @@
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    @SatelliteResult public int registerForSatelliteDatagram(
+    @SatelliteResult public int registerForIncomingDatagram(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull SatelliteDatagramCallback callback) {
         Objects.requireNonNull(executor);
@@ -1410,13 +1438,13 @@
                             }
                         };
                 sSatelliteDatagramCallbackMap.put(callback, internalCallback);
-                return telephony.registerForSatelliteDatagram(mSubId, internalCallback);
+                return telephony.registerForIncomingDatagram(mSubId, internalCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException ex) {
-            loge("registerForSatelliteDatagram() RemoteException:" + ex);
-            ex.rethrowFromSystemServer();
+            loge("registerForIncomingDatagram() RemoteException:" + ex);
+            ex.rethrowAsRuntimeException();
         }
         return SATELLITE_RESULT_REQUEST_FAILED;
     }
@@ -1426,14 +1454,14 @@
      * If callback was not registered before, the request will be ignored.
      *
      * @param callback The callback that was passed to
-     * {@link #registerForSatelliteDatagram(Executor, SatelliteDatagramCallback)}.
+     * {@link #registerForIncomingDatagram(Executor, SatelliteDatagramCallback)}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    public void unregisterForSatelliteDatagram(@NonNull SatelliteDatagramCallback callback) {
+    public void unregisterForIncomingDatagram(@NonNull SatelliteDatagramCallback callback) {
         Objects.requireNonNull(callback);
         ISatelliteDatagramCallback internalCallback =
                 sSatelliteDatagramCallbackMap.remove(callback);
@@ -1442,16 +1470,16 @@
             ITelephony telephony = getITelephony();
             if (telephony != null) {
                 if (internalCallback != null) {
-                    telephony.unregisterForSatelliteDatagram(mSubId, internalCallback);
+                    telephony.unregisterForIncomingDatagram(mSubId, internalCallback);
                 } else {
-                    loge("unregisterForSatelliteDatagram: No internal callback.");
+                    loge("unregisterForIncomingDatagram: No internal callback.");
                 }
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException ex) {
-            loge("unregisterForSatelliteDatagram() RemoteException:" + ex);
-            ex.rethrowFromSystemServer();
+            loge("unregisterForIncomingDatagram() RemoteException:" + ex);
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1468,11 +1496,10 @@
      * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    public void pollPendingSatelliteDatagrams(@NonNull @CallbackExecutor Executor executor,
+    public void pollPendingDatagrams(@NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(resultListener);
@@ -1487,13 +1514,14 @@
                                 () -> resultListener.accept(result)));
                     }
                 };
-                telephony.pollPendingSatelliteDatagrams(mSubId, internalCallback);
+                telephony.pollPendingDatagrams(mSubId, internalCallback);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(
+                        () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
-            loge("pollPendingSatelliteDatagrams() RemoteException:" + ex);
-            ex.rethrowFromSystemServer();
+            loge("pollPendingDatagrams() RemoteException:" + ex);
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1521,11 +1549,10 @@
      * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    public void sendSatelliteDatagram(@DatagramType int datagramType,
+    public void sendDatagram(@DatagramType int datagramType,
             @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
@@ -1543,14 +1570,15 @@
                                 () -> resultListener.accept(result)));
                     }
                 };
-                telephony.sendSatelliteDatagram(mSubId, datagramType, datagram,
+                telephony.sendDatagram(mSubId, datagramType, datagram,
                         needFullScreenPointingUI, internalCallback);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(
+                        () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
-            loge("sendSatelliteDatagram() RemoteException:" + ex);
-            ex.rethrowFromSystemServer();
+            loge("sendDatagram() RemoteException:" + ex);
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1567,11 +1595,10 @@
      *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    public void requestIsSatelliteCommunicationAllowedForCurrentLocation(
+    public void requestIsCommunicationAllowedForCurrentLocation(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
@@ -1601,15 +1628,16 @@
                         }
                     }
                 };
-                telephony.requestIsSatelliteCommunicationAllowedForCurrentLocation(mSubId,
+                telephony.requestIsCommunicationAllowedForCurrentLocation(mSubId,
                         receiver);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
-            loge("requestIsSatelliteCommunicationAllowedForCurrentLocation() RemoteException: "
+            loge("requestIsCommunicationAllowedForCurrentLocation() RemoteException: "
                     + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1625,7 +1653,6 @@
      *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1661,11 +1688,12 @@
                 };
                 telephony.requestTimeForNextSatelliteVisibility(mSubId, receiver);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
             loge("requestTimeForNextSatelliteVisibility() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1693,7 +1721,7 @@
             }
         } catch (RemoteException ex) {
             loge("informDeviceAlignedToSatellite() RemoteException:" + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1707,7 +1735,7 @@
      * <ul>
      * <li>Users want to enable it.</li>
      * <li>There is no satellite communication restriction, which is added by
-     * {@link #addSatelliteAttachRestrictionForCarrier(int, Executor, Consumer)}</li>
+     * {@link #addAttachRestrictionForCarrier(int, int, Executor, Consumer)}</li>
      * <li>The carrier config {@link
      * android.telephony.CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} is set to
      * {@code true}.</li>
@@ -1719,22 +1747,21 @@
      * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      * @throws IllegalArgumentException if the subscription is invalid.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
-    public void requestSatelliteAttachEnabledForCarrier(int subId, boolean enableSatellite,
+    public void requestAttachEnabledForCarrier(int subId, boolean enableSatellite,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(resultListener);
 
         if (enableSatellite) {
-            removeSatelliteAttachRestrictionForCarrier(subId,
+            removeAttachRestrictionForCarrier(subId,
                     SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, executor, resultListener);
         } else {
-            addSatelliteAttachRestrictionForCarrier(subId,
+            addAttachRestrictionForCarrier(subId,
                     SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, executor, resultListener);
         }
     }
@@ -1758,13 +1785,13 @@
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
-    public void requestIsSatelliteAttachEnabledForCarrier(int subId,
+    public void requestIsAttachEnabledForCarrier(int subId,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
 
-        Set<Integer> restrictionReason = getSatelliteAttachRestrictionReasonsForCarrier(subId);
+        Set<Integer> restrictionReason = getAttachRestrictionReasonsForCarrier(subId);
         executor.execute(() -> callback.onResult(
                 !restrictionReason.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER)));
     }
@@ -1779,12 +1806,11 @@
      * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      * @throws IllegalArgumentException if the subscription is invalid.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
-    public void addSatelliteAttachRestrictionForCarrier(int subId,
+    public void addAttachRestrictionForCarrier(int subId,
             @SatelliteCommunicationRestrictionReason int reason,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
@@ -1802,13 +1828,14 @@
                                 () -> resultListener.accept(result)));
                     }
                 };
-                telephony.addSatelliteAttachRestrictionForCarrier(subId, reason, errorCallback);
+                telephony.addAttachRestrictionForCarrier(subId, reason, errorCallback);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(
+                        () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
-            loge("addSatelliteAttachRestrictionForCarrier() RemoteException:" + ex);
-            ex.rethrowFromSystemServer();
+            loge("addAttachRestrictionForCarrier() RemoteException:" + ex);
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1822,12 +1849,11 @@
      * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      * @throws IllegalArgumentException if the subscription is invalid.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
-    public void removeSatelliteAttachRestrictionForCarrier(int subId,
+    public void removeAttachRestrictionForCarrier(int subId,
             @SatelliteCommunicationRestrictionReason int reason,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
@@ -1845,19 +1871,20 @@
                                 () -> resultListener.accept(result)));
                     }
                 };
-                telephony.removeSatelliteAttachRestrictionForCarrier(subId, reason, errorCallback);
+                telephony.removeAttachRestrictionForCarrier(subId, reason, errorCallback);
             } else {
-                throw new IllegalStateException("telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(
+                        () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
-            loge("removeSatelliteAttachRestrictionForCarrier() RemoteException:" + ex);
-            ex.rethrowFromSystemServer();
+            loge("removeAttachRestrictionForCarrier() RemoteException:" + ex);
+            ex.rethrowAsRuntimeException();
         }
     }
 
     /**
      * Get reasons for disallowing satellite attach, as requested by
-     * {@link #addSatelliteAttachRestrictionForCarrier(int, Executor, Consumer)}
+     * {@link #addAttachRestrictionForCarrier(int, int, Executor, Consumer)}
      *
      * @param subId The subscription ID of the carrier.
      * @return Set of reasons for disallowing satellite communication.
@@ -1869,7 +1896,7 @@
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @SatelliteCommunicationRestrictionReason
     @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
-    @NonNull public Set<Integer> getSatelliteAttachRestrictionReasonsForCarrier(int subId) {
+    @NonNull public Set<Integer> getAttachRestrictionReasonsForCarrier(int subId) {
         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
             throw new IllegalArgumentException("Invalid subscription ID");
         }
@@ -1878,7 +1905,7 @@
             ITelephony telephony = getITelephony();
             if (telephony != null) {
                 int[] receivedArray =
-                        telephony.getSatelliteAttachRestrictionReasonsForCarrier(subId);
+                        telephony.getAttachRestrictionReasonsForCarrier(subId);
                 if (receivedArray.length == 0) {
                     logd("receivedArray is empty, create empty set");
                     return new HashSet<>();
@@ -1889,8 +1916,8 @@
                 throw new IllegalStateException("Telephony service is null.");
             }
         } catch (RemoteException ex) {
-            loge("getSatelliteAttachRestrictionReasonsForCarrier() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            loge("getAttachRestrictionReasonsForCarrier() RemoteException: " + ex);
+            ex.rethrowAsRuntimeException();
         }
         return new HashSet<>();
     }
@@ -1912,11 +1939,12 @@
      * The {@link NtnSignalStrength#NTN_SIGNAL_STRENGTH_NONE} will be returned if there is no
      * signal strength data available.
      * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} will return a
-     * {@link SatelliteException} with the {@link SatelliteResult}.
+     * {@link SatelliteException} with the {@link SatelliteResult}, or return a
+     * {@link IllegalStateException} if the Telephony process is not currently available or
+     * satellite is not supported, or return a {@link RuntimeException} when remote procedure call
+     * has failed.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available or
-     * satellite is not supported.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1952,11 +1980,12 @@
                 };
                 telephony.requestNtnSignalStrength(mSubId, receiver);
             } else {
-                throw new IllegalStateException("Telephony service is null.");
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
             loge("requestNtnSignalStrength() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -1977,12 +2006,11 @@
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
-     * @throws SatelliteException if the callback registration operation fails.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void registerForNtnSignalStrengthChanged(@NonNull @CallbackExecutor Executor executor,
-            @NonNull NtnSignalStrengthCallback callback) throws SatelliteException {
+            @NonNull NtnSignalStrengthCallback callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
 
@@ -2004,12 +2032,9 @@
             } else {
                 throw new IllegalStateException("Telephony service is null.");
             }
-        } catch (ServiceSpecificException ex) {
-            logd("registerForNtnSignalStrengthChanged() registration fails: " + ex.errorCode);
-            throw new SatelliteException(ex.errorCode);
         } catch (RemoteException ex) {
             loge("registerForNtnSignalStrengthChanged() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -2052,7 +2077,7 @@
             }
         } catch (RemoteException ex) {
             loge("unregisterForNtnSignalStrengthChanged() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            ex.rethrowAsRuntimeException();
         }
     }
 
@@ -2067,7 +2092,7 @@
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    @SatelliteResult public int registerForSatelliteCapabilitiesChanged(
+    @SatelliteResult public int registerForCapabilitiesChanged(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull SatelliteCapabilitiesCallback callback) {
         Objects.requireNonNull(executor);
@@ -2087,13 +2112,13 @@
                             }
                         };
                 sSatelliteCapabilitiesCallbackMap.put(callback, internalCallback);
-                return telephony.registerForSatelliteCapabilitiesChanged(mSubId, internalCallback);
+                return telephony.registerForCapabilitiesChanged(mSubId, internalCallback);
             } else {
                 throw new IllegalStateException("Telephony service is null.");
             }
         } catch (RemoteException ex) {
-            loge("registerForSatelliteCapabilitiesChanged() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            loge("registerForCapabilitiesChanged() RemoteException: " + ex);
+            ex.rethrowAsRuntimeException();
         }
         return SATELLITE_RESULT_REQUEST_FAILED;
     }
@@ -2103,14 +2128,14 @@
      * If callback was not registered before, the request will be ignored.
      *
      * @param callback The callback that was passed to.
-     * {@link #registerForSatelliteCapabilitiesChanged(Executor, SatelliteCapabilitiesCallback)}.
+     * {@link #registerForCapabilitiesChanged(Executor, SatelliteCapabilitiesCallback)}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    public void unregisterForSatelliteCapabilitiesChanged(
+    public void unregisterForCapabilitiesChanged(
             @NonNull SatelliteCapabilitiesCallback callback) {
         Objects.requireNonNull(callback);
         ISatelliteCapabilitiesCallback internalCallback =
@@ -2120,19 +2145,48 @@
             ITelephony telephony = getITelephony();
             if (telephony != null) {
                 if (internalCallback != null) {
-                    telephony.unregisterForSatelliteCapabilitiesChanged(mSubId, internalCallback);
+                    telephony.unregisterForCapabilitiesChanged(mSubId, internalCallback);
                 } else {
-                    loge("unregisterForSatelliteCapabilitiesChanged: No internal callback.");
+                    loge("unregisterForCapabilitiesChanged: No internal callback.");
                 }
             } else {
                 throw new IllegalStateException("Telephony service is null.");
             }
         } catch (RemoteException ex) {
-            loge("unregisterForSatelliteCapabilitiesChanged() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
+            loge("unregisterForCapabilitiesChanged() RemoteException: " + ex);
+            ex.rethrowAsRuntimeException();
         }
     }
 
+    /**
+     * Get all satellite PLMNs for which attach is enable for carrier.
+     *
+     * @param subId subId The subscription ID of the carrier.
+     *
+     * @return List of plmn for carrier satellite service. If no plmn is available, empty list will
+     * be returned.
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    @NonNull public List<String> getSatellitePlmnsForCarrier(int subId) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            throw new IllegalArgumentException("Invalid subscription ID");
+        }
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getSatellitePlmnsForCarrier(subId);
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("getSatellitePlmnsForCarrier() RemoteException: " + ex);
+            ex.rethrowAsRuntimeException();
+        }
+        return new ArrayList<>();
+    }
+
     private static ITelephony getITelephony() {
         ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
                 .getTelephonyServiceManager()
diff --git a/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java
new file mode 100644
index 0000000..8d33c88
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
+
+import com.android.internal.telephony.flags.Flags;
+
+/**
+ * A callback class for monitoring satellite modem state change events.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public interface SatelliteModemStateCallback {
+    /**
+     * Called when satellite modem state changes.
+     * @param state The new satellite modem state.
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state);
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
deleted file mode 100644
index bfe6e10..0000000
--- a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony.satellite;
-
-import android.annotation.FlaggedApi;
-import android.annotation.SystemApi;
-
-import com.android.internal.telephony.flags.Flags;
-
-/**
- * A callback class for monitoring satellite modem state change events.
- *
- * @hide
- */
-@SystemApi
-@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-public interface SatelliteStateCallback {
-    /**
-     * Called when satellite modem state changes.
-     * @param state The new satellite modem state.
-     */
-    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state);
-}
diff --git a/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
index b7712bd..655da740 100644
--- a/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
+++ b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
@@ -16,7 +16,7 @@
 
 package android.telephony.satellite.stub;
 
-import android.telephony.satellite.NtnSignalStrength;
+import android.telephony.satellite.stub.NtnSignalStrength;
 
 /**
  * Consumer pattern for a request that receives the signal strength of non-terrestrial network from
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index c0d0830..abacd15 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -181,7 +181,7 @@
             executeMethodAsync(
                     () -> SatelliteImplBase.this
                             .sendSatelliteDatagram(datagram, isEmergency, resultCallback),
-                    "sendSatelliteDatagram");
+                    "sendDatagram");
         }
 
         @Override
@@ -201,7 +201,7 @@
                     () -> SatelliteImplBase.this
                             .requestIsSatelliteCommunicationAllowedForCurrentLocation(
                                     resultCallback, callback),
-                    "requestIsSatelliteCommunicationAllowedForCurrentLocation");
+                    "requestIsCommunicationAllowedForCurrentLocation");
         }
 
         @Override
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 9b5ee0c..43eb111 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -72,7 +72,7 @@
 import android.telephony.satellite.ISatelliteDatagramCallback;
 import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
-import android.telephony.satellite.ISatelliteStateCallback;
+import android.telephony.satellite.ISatelliteModemStateCallback;
 import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.satellite.SatelliteCapabilities;
 import android.telephony.satellite.SatelliteDatagram;
@@ -2896,7 +2896,7 @@
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    int registerForSatelliteModemStateChanged(int subId, ISatelliteStateCallback callback);
+    int registerForSatelliteModemStateChanged(int subId, ISatelliteModemStateCallback callback);
 
     /**
      * Unregisters for modem state changed from satellite modem.
@@ -2907,7 +2907,7 @@
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void unregisterForSatelliteModemStateChanged(int subId, ISatelliteStateCallback callback);
+    void unregisterForModemStateChanged(int subId, ISatelliteModemStateCallback callback);
 
    /**
      * Register to receive incoming datagrams over satellite.
@@ -2919,18 +2919,18 @@
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    int registerForSatelliteDatagram(int subId, ISatelliteDatagramCallback callback);
+    int registerForIncomingDatagram(int subId, ISatelliteDatagramCallback callback);
 
    /**
      * Unregister to stop receiving incoming datagrams over satellite.
      * If callback was not registered before, the request will be ignored.
      *
      * @param subId The subId of the subscription to unregister for incoming satellite datagrams.
-     * @param callback The callback that was passed to registerForSatelliteDatagram.
+     * @param callback The callback that was passed to registerForIncomingDatagram.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void unregisterForSatelliteDatagram(int subId, ISatelliteDatagramCallback callback);
+    void unregisterForIncomingDatagram(int subId, ISatelliteDatagramCallback callback);
 
    /**
     * Poll pending satellite datagrams over satellite.
@@ -2940,7 +2940,7 @@
     */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
                 + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void pollPendingSatelliteDatagrams(int subId, IIntegerConsumer callback);
+    void pollPendingDatagrams(int subId, IIntegerConsumer callback);
 
    /**
     * Send datagram over satellite.
@@ -2954,9 +2954,8 @@
     */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void sendSatelliteDatagram(int subId, int datagramType,
-             in SatelliteDatagram datagram, in boolean needFullScreenPointingUI,
-             IIntegerConsumer callback);
+    void sendDatagram(int subId, int datagramType, in SatelliteDatagram datagram,
+            in boolean needFullScreenPointingUI, IIntegerConsumer callback);
 
     /**
      * Request to get whether satellite communication is allowed for the current location.
@@ -2968,8 +2967,7 @@
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestIsSatelliteCommunicationAllowedForCurrentLocation(int subId,
-            in ResultReceiver receiver);
+    void requestIsCommunicationAllowedForCurrentLocation(int subId, in ResultReceiver receiver);
 
     /**
      * Request to get the time after which the satellite will be visible.
@@ -3078,6 +3076,16 @@
             in List<String> satelliteCountryCodes);
 
     /**
+     * This API can be used in only testing to override oem-enabled satellite provision status.
+     *
+     * @param reset {@code true} mean the overriding status should not be used, {@code false}
+     *              otherwise.
+     * @param isProvisioned The overriding provision status.
+     * @return {@code true} if the provision status is set successfully, {@code false} otherwise.
+     */
+    boolean setOemEnabledSatelliteProvisionStatus(in boolean reset, in boolean isProvisioned);
+
+    /**
      * Test method to confirm the file contents are not altered.
      */
      @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
@@ -3094,8 +3102,7 @@
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void addSatelliteAttachRestrictionForCarrier(int subId, int reason,
-            in IIntegerConsumer callback);
+    void addAttachRestrictionForCarrier(int subId, int reason, in IIntegerConsumer callback);
 
     /**
      * Remove a restriction reason for disallowing satellite communication.
@@ -3107,12 +3114,11 @@
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void removeSatelliteAttachRestrictionForCarrier(int subId, int reason,
-            in IIntegerConsumer callback);
+    void removeAttachRestrictionForCarrier(int subId, int reason, in IIntegerConsumer callback);
 
     /**
      * Get reasons for disallowing satellite communication, as requested by
-     * {@link #addSatelliteAttachRestrictionForCarrier(int, int)}.
+     * {@link #addAttachRestrictionForCarrier(int, int)}.
      *
      * @param subId The subId of the subscription to request for.
      *
@@ -3120,7 +3126,7 @@
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    int[] getSatelliteAttachRestrictionReasonsForCarrier(int subId);
+    int[] getAttachRestrictionReasonsForCarrier(int subId);
 
     /**
      * Request to get the signal strength of the satellite connection.
@@ -3160,8 +3166,7 @@
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void unregisterForNtnSignalStrengthChanged(int subId,
-            in INtnSignalStrengthCallback callback);
+    void unregisterForNtnSignalStrengthChanged(int subId, in INtnSignalStrengthCallback callback);
 
     /**
      * Registers for satellite capabilities change event from the satellite service.
@@ -3171,19 +3176,18 @@
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    int registerForSatelliteCapabilitiesChanged(int subId,
-            in ISatelliteCapabilitiesCallback callback);
+    int registerForCapabilitiesChanged(int subId, in ISatelliteCapabilitiesCallback callback);
 
     /**
      * Unregisters for satellite capabilities change event from the satellite service.
      * If callback was not registered before, the request will be ignored.
      *
      * @param callback The callback that was passed to.
-     * {@link #registerForSatelliteCapabilitiesChanged(Executor, SatelliteCapabilitiesCallback)}.
+     * {@link #registerForCapabilitiesChanged(Executor, SatelliteCapabilitiesCallback)}.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void unregisterForSatelliteCapabilitiesChanged(int subId,
+    void unregisterForCapabilitiesChanged(int subId,
             in ISatelliteCapabilitiesCallback callback);
 
     /**
@@ -3230,4 +3234,45 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
         + "android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)")
     boolean isCellularIdentifierDisclosureNotificationsEnabled();
+
+    /**
+     * Enables or disables notifications sent when cellular null cipher or integrity algorithms
+     * are in use by the cellular modem.
+     *
+     * @throws IllegalStateException if the Telephony process is not currently available
+     * @throws SecurityException if the caller does not have the required privileges
+     * @throws UnsupportedOperationException if the modem does not support reporting on ciphering
+     * and integrity algorithms in use
+     * @hide
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+        + "android.Manifest.permission.MODIFY_PHONE_STATE)")
+    void setEnableNullCipherNotifications(boolean enable);
+
+    /**
+     * Get whether notifications are enabled for null cipher or integrity algorithms in use by the
+     * cellular modem.
+     *
+     * @throws IllegalStateException if the Telephony process is not currently available
+     * @throws SecurityException if the caller does not have the required privileges
+     * @throws UnsupportedOperationException if the modem does not support reporting on ciphering
+     * and integrity algorithms in use
+     * @hide
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+        + "android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)")
+    boolean isNullCipherNotificationsEnabled();
+
+    /**
+     * Get the aggregated satellite plmn list. This API collects plmn data from multiple sources,
+     * including carrier config, entitlement server, and config update.
+     *
+     * @param subId subId The subscription ID of the carrier.
+     *
+     * @return List of plmns for carrier satellite service. If no plmn is available, empty list will
+     * be returned.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    List<String> getSatellitePlmnsForCarrier(int subId);
 }
diff --git a/test-base/Android.bp b/test-base/Android.bp
index 527159a..70a9540 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -120,12 +120,13 @@
     path: "src",
 }
 
-// Make the current.txt available for use by the cts/tests/signature tests.
+// Make the current.txt available for use by the cts/tests/signature and /vendor tests.
 // ========================================================================
 filegroup {
     name: "android-test-base-current.txt",
     visibility: [
         "//cts/tests/signature/api",
+        "//vendor:__subpackages__",
     ],
     srcs: [
         "api/current.txt",
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 2ff7413..f37d2d1 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -77,12 +77,13 @@
     auto_gen_config: true,
 }
 
-// Make the current.txt available for use by the cts/tests/signature tests.
+// Make the current.txt available for use by the cts/tests/signature and /vendor tests.
 // ========================================================================
 filegroup {
     name: "android-test-mock-current.txt",
     visibility: [
         "//cts/tests/signature/api",
+        "//vendor:__subpackages__",
     ],
     srcs: [
         "api/current.txt",
diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java
index f5f9d97..cf38bea 100644
--- a/test-mock/src/android/test/mock/MockContext.java
+++ b/test-mock/src/android/test/mock/MockContext.java
@@ -822,12 +822,6 @@
         throw new UnsupportedOperationException();
     }
 
-    /** {@hide} */
-    @Override
-    public int getUserId() {
-        throw new UnsupportedOperationException();
-    }
-
     @Override
     public Context createConfigurationContext(Configuration overrideConfiguration) {
         throw new UnsupportedOperationException();
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index 13a5dac..21e09d3 100644
--- a/test-runner/Android.bp
+++ b/test-runner/Android.bp
@@ -79,12 +79,13 @@
     ],
 }
 
-// Make the current.txt available for use by the cts/tests/signature tests.
+// Make the current.txt available for use by the cts/tests/signature and /vendor tests.
 // ========================================================================
 filegroup {
     name: "android-test-runner-current.txt",
     visibility: [
         "//cts/tests/signature/api",
+        "//vendor:__subpackages__",
     ],
     srcs: [
         "api/current.txt",
diff --git a/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
index 2bc056e..cee27b6 100644
--- a/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
+++ b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
@@ -16,6 +16,8 @@
 
 package android.transparency.test.app;
 
+import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES;
+
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -27,6 +29,7 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.internal.os.IBinaryTransparencyService.AppInfo;
 
 import org.junit.Before;
@@ -116,7 +119,12 @@
     @Test
     public void testCollectAllSilentInstalledMbaInfo() {
         // Action
-        var appInfoList = mBt.collectAllSilentInstalledMbaInfo(new Bundle());
+        var appInfoList =
+                ShellIdentityUtils.invokeMethodWithShellPermissions(
+                        mBt,
+                        (Bt) ->
+                                mBt.collectAllSilentInstalledMbaInfo(new Bundle()),
+                        GET_BACKGROUND_INSTALLED_PACKAGES);
 
         // Verify
         assertThat(appInfoList).isNotEmpty();  // because we just installed from the host side
diff --git a/tests/Camera2Tests/CameraToo/tests/Android.bp b/tests/Camera2Tests/CameraToo/tests/Android.bp
new file mode 100644
index 0000000..8339a2c
--- /dev/null
+++ b/tests/Camera2Tests/CameraToo/tests/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "frameworks_base_license",
+    ],
+}
+
+android_test {
+    name: "CameraTooTests",
+    instrumentation_for: "CameraToo",
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+    static_libs: [
+        "androidx.test.rules",
+        "mockito-target-minus-junit4",
+    ],
+    data: [
+        ":CameraToo",
+    ],
+}
diff --git a/tests/Camera2Tests/CameraToo/tests/Android.mk b/tests/Camera2Tests/CameraToo/tests/Android.mk
deleted file mode 100644
index dfa64f1..0000000
--- a/tests/Camera2Tests/CameraToo/tests/Android.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright (C) 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := CameraTooTests
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../NOTICE
-LOCAL_INSTRUMENTATION_FOR := CameraToo
-LOCAL_SDK_VERSION := current
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules mockito-target-minus-junit4
-
-include $(BUILD_PACKAGE)
diff --git a/tests/Camera2Tests/CameraToo/tests/AndroidTest.xml b/tests/Camera2Tests/CameraToo/tests/AndroidTest.xml
new file mode 100644
index 0000000..884c095
--- /dev/null
+++ b/tests/Camera2Tests/CameraToo/tests/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs CameraToo tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CameraTooTests.apk" />
+        <option name="test-file-name" value="CameraToo.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.example.android.camera2.cameratoo.tests" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
index 80c1e5be..217659e 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
@@ -43,9 +43,7 @@
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
-import java.util.Optional;
 import java.util.stream.Collectors;
 
 /**
@@ -178,8 +176,14 @@
             this.deviceFrameRate = deviceFrameRate;
         }
 
+        FrameRateTimeoutException(List<Float> expectedFrameRates, float deviceFrameRate) {
+            this.expectedFrameRates = expectedFrameRates;
+            this.deviceFrameRate = deviceFrameRate;
+        }
+
         public float expectedFrameRate;
         public float deviceFrameRate;
+        public List<Float> expectedFrameRates;
     }
 
     public enum Api {
@@ -502,31 +506,37 @@
     }
 
     private void waitForStableFrameRate(TestSurface... surfaces) throws InterruptedException {
-        verifyCompatibleAndStableFrameRate(0, surfaces);
+        verifyFrameRates(List.of(), surfaces);
     }
 
     private void verifyExactAndStableFrameRate(
             float expectedFrameRate,
             TestSurface... surfaces) throws InterruptedException {
-        verifyFrameRate(expectedFrameRate, false, surfaces);
+        verifyFrameRate(List.of(expectedFrameRate), false, surfaces);
     }
 
     private void verifyCompatibleAndStableFrameRate(
             float expectedFrameRate,
             TestSurface... surfaces) throws InterruptedException {
-        verifyFrameRate(expectedFrameRate, true, surfaces);
+        verifyFrameRate(List.of(expectedFrameRate), true, surfaces);
     }
 
-    // Set expectedFrameRate to 0.0 to verify only stable frame rate.
-    private void verifyFrameRate(
-            float expectedFrameRate, boolean multiplesAllowed,
+    /** Verify stable frame rate at one of the expectedFrameRates. */
+    private void verifyFrameRates(List<Float> expectedFrameRates, TestSurface... surfaces)
+            throws InterruptedException {
+        verifyFrameRate(expectedFrameRates, true, surfaces);
+    }
+
+    // Set expectedFrameRates to empty to verify only stable frame rate.
+    private void verifyFrameRate(List<Float> expectedFrameRates, boolean multiplesAllowed,
             TestSurface... surfaces) throws InterruptedException {
         Log.i(TAG, "Verifying compatible and stable frame rate");
         long nowNanos = System.nanoTime();
         long gracePeriodEndTimeNanos =
                 nowNanos + FRAME_RATE_SWITCH_GRACE_PERIOD_SECONDS * 1_000_000_000L;
         while (true) {
-            if (expectedFrameRate > FRAME_RATE_TOLERANCE) { // expectedFrameRate > 0
+            if (expectedFrameRates.size() == 1) {
+                float expectedFrameRate = expectedFrameRates.get(0);
                 // Wait until we switch to a compatible frame rate.
                 Log.i(TAG,
                         String.format(
@@ -557,11 +567,25 @@
             while (endTimeNanos > nowNanos) {
                 int numModeChangedEvents = mModeChangedEvents.size();
                 if (waitForEvents(endTimeNanos, surfaces)) {
-                    Log.i(TAG,
-                            String.format("Stable frame rate %.2f verified",
-                                    multiplesAllowed ? mDisplayModeRefreshRate
-                                                     : mDisplayRefreshRate));
-                    return;
+                    // Verify any expected frame rate since there are multiple that will suffice.
+                    // Mainly to account for running tests on real devices, where other non-test
+                    // layers may affect the outcome.
+                    if (expectedFrameRates.size() > 1) {
+                        for (float expectedFrameRate : expectedFrameRates) {
+                            if (isFrameRateMultiple(mDisplayModeRefreshRate, expectedFrameRate)) {
+                                return;
+                            }
+                        }
+                        // The frame rate is stable but it is not one of the expected frame rates.
+                        throw new FrameRateTimeoutException(
+                                expectedFrameRates, mDisplayModeRefreshRate);
+                    } else {
+                        Log.i(TAG,
+                                String.format("Stable frame rate %.2f verified",
+                                        multiplesAllowed ? mDisplayModeRefreshRate
+                                                         : mDisplayRefreshRate));
+                        return;
+                    }
                 }
                 nowNanos = System.nanoTime();
                 if (mModeChangedEvents.size() > numModeChangedEvents) {
@@ -623,12 +647,28 @@
                             // caused the timeout failure. Wait for a bit to see if we get notified
                             // of a precondition violation, and if so, retry the test. Otherwise
                             // fail.
-                            assertTrue(String.format(
-                                               "Timed out waiting for a stable and compatible frame"
-                                                       + " rate. expected=%.2f received=%.2f."
-                                                       + " Stack trace: " + stackTrace,
-                                               exc.expectedFrameRate, exc.deviceFrameRate),
-                                    waitForPreconditionViolation());
+                            if (exc.expectedFrameRates.isEmpty()) {
+                                assertTrue(
+                                        String.format(
+                                                "Timed out waiting for a stable and compatible"
+                                                        + " frame rate."
+                                                        + " expected=%.2f received=%.2f."
+                                                        + " Stack trace: " + stackTrace,
+                                                exc.expectedFrameRate, exc.deviceFrameRate),
+                                        waitForPreconditionViolation());
+                            } else {
+                                assertTrue(
+                                        String.format(
+                                                "Timed out waiting for a stable and compatible"
+                                                        + " frame rate."
+                                                        + " expected={%.2f} received=%.2f."
+                                                        + " Stack trace: " + stackTrace,
+                                                exc.expectedFrameRates.stream()
+                                                        .map(Object::toString)
+                                                        .collect(Collectors.joining(", ")),
+                                                exc.deviceFrameRate),
+                                        waitForPreconditionViolation());
+                            }
                         }
 
                         if (!testPassed) {
@@ -679,10 +719,15 @@
                     "**** Running testSurfaceControlFrameRateCompatibility with compatibility "
                             + compatibility);
 
-            float expectedFrameRate = getExpectedFrameRateForCompatibility(compatibility);
+            List<Float> expectedFrameRates = getExpectedFrameRateForCompatibility(compatibility);
+            Log.i(TAG,
+                    "Expected frame rates: "
+                            + expectedFrameRates.stream()
+                                      .map(Object::toString)
+                                      .collect(Collectors.joining(", ")));
             int initialNumEvents = mModeChangedEvents.size();
             surface.setFrameRate(30.f, compatibility);
-            verifyExactAndStableFrameRate(expectedFrameRate, surface);
+            verifyFrameRates(expectedFrameRates, surface);
             verifyModeSwitchesDontChangeResolution(initialNumEvents, mModeChangedEvents.size());
         });
     }
@@ -699,10 +744,10 @@
         runOneSurfaceTest((TestSurface surface) -> {
             Log.i(TAG, "**** Running testSurfaceControlFrameRateCategory for category " + category);
 
-            float expectedFrameRate = getExpectedFrameRateForCategory(category);
+            List<Float> expectedFrameRates = getExpectedFrameRateForCategory(category);
             int initialNumEvents = mModeChangedEvents.size();
             surface.setFrameRateCategory(category);
-            verifyCompatibleAndStableFrameRate(expectedFrameRate, surface);
+            verifyFrameRates(expectedFrameRates, surface);
             verifyModeSwitchesDontChangeResolution(initialNumEvents, mModeChangedEvents.size());
         });
     }
@@ -771,41 +816,41 @@
                 "frame rate strategy=" + parentStrategy);
     }
 
-    private float getExpectedFrameRateForCompatibility(int compatibility) {
+    private List<Float> getExpectedFrameRateForCompatibility(int compatibility) {
         assumeTrue("**** testSurfaceControlFrameRateCompatibility SKIPPED for compatibility "
                         + compatibility,
                 compatibility == Surface.FRAME_RATE_COMPATIBILITY_GTE);
 
         Display display = getDisplay();
-        Optional<Float> expectedFrameRate = getRefreshRates(display.getMode(), display)
-                                                    .stream()
-                                                    .filter(rate -> rate >= 30.f)
-                                                    .min(Comparator.naturalOrder());
+        List<Float> expectedFrameRates = getRefreshRates(display.getMode(), display)
+                                                 .stream()
+                                                 .filter(rate -> rate >= 30.f)
+                                                 .collect(Collectors.toList());
 
         assumeTrue("**** testSurfaceControlFrameRateCompatibility SKIPPED because no refresh rate "
                         + "is >= 30",
-                expectedFrameRate.isPresent());
-        return expectedFrameRate.get();
+                !expectedFrameRates.isEmpty());
+        return expectedFrameRates;
     }
 
-    private float getExpectedFrameRateForCategory(int category) {
+    private List<Float> getExpectedFrameRateForCategory(int category) {
         Display display = getDisplay();
         List<Float> frameRates = getRefreshRates(display.getMode(), display);
 
         if (category == Surface.FRAME_RATE_CATEGORY_DEFAULT) {
             // Max due to default vote and no other frame rate specifications.
-            return Collections.max(frameRates);
+            return List.of(Collections.max(frameRates));
         } else if (category == Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE) {
-            return Collections.min(frameRates);
+            return frameRates;
         }
 
         FpsRange categoryRange = convertCategory(category);
-        Optional<Float> expectedFrameRate = frameRates.stream()
-                                                    .filter(fps -> categoryRange.includes(fps))
-                                                    .min(Comparator.naturalOrder());
+        List<Float> expectedFrameRates = frameRates.stream()
+                                                 .filter(fps -> categoryRange.includes(fps))
+                                                 .collect(Collectors.toList());
         assumeTrue("**** testSurfaceControlFrameRateCategory SKIPPED for category " + category,
-                expectedFrameRate.isPresent());
-        return expectedFrameRate.get();
+                !expectedFrameRates.isEmpty());
+        return expectedFrameRates;
     }
 
     private FpsRange convertCategory(int category) {
diff --git a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index b0ca4d2..79d3a10 100644
--- a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -17,7 +17,10 @@
 package com.android.server.wm.flicker.rotation
 
 import android.platform.test.annotations.Presubmit
+import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject
 import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.common.traces.component.IComponentMatcher
+import android.tools.common.traces.surfaceflinger.Display
 import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
@@ -57,9 +60,8 @@
     @Test
     open fun appLayerRotates_StartingPos() {
         flicker.assertLayersStart {
-            this.entry.displays.map { display ->
-                this.visibleRegion(testApp).coversExactly(display.layerStackSpace)
-            }
+            val display = getDisplay(testApp)
+            this.visibleRegion(testApp).coversAtLeast(display.layerStackSpace)
         }
     }
 
@@ -68,12 +70,20 @@
     @Test
     open fun appLayerRotates_EndingPos() {
         flicker.assertLayersEnd {
-            this.entry.displays.map { display ->
-                this.visibleRegion(testApp).coversExactly(display.layerStackSpace)
-            }
+            val display = getDisplay(testApp)
+            this.visibleRegion(testApp).coversAtLeast(display.layerStackSpace)
         }
     }
 
+    private fun LayerTraceEntrySubject.getDisplay(componentMatcher: IComponentMatcher): Display {
+        val stackId = this.layer {
+            componentMatcher.layerMatchesAnyOf(it) && it.isVisible
+        }?.layer?.stackId ?: -1
+
+        return this.entry.displays.firstOrNull { it.layerStackId == stackId }
+            ?: error("Unable to find visible layer for $componentMatcher")
+    }
+
     override fun cujCompleted() {
         super.cujCompleted()
         appLayerRotates_StartingPos()
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
index 29cbf01..c44d943 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
@@ -24,8 +24,8 @@
 import android.view.View;
 import android.widget.ToggleButton;
 
+import androidx.window.embedding.ActivityEmbeddingController;
 import androidx.window.embedding.SplitAttributes;
-import androidx.window.embedding.SplitAttributesCalculatorParams;
 import androidx.window.embedding.SplitController;
 
 /**
@@ -66,7 +66,9 @@
                     @Override
                     public void onClick(View v) {
                         // This triggers a recalcuation of splitatributes.
-                        mSplitController.invalidateTopVisibleSplitAttributes();
+                        ActivityEmbeddingController
+                                .getInstance(ActivityEmbeddingSecondaryActivity.this)
+                                .invalidateVisibleActivityStacks();
                     }
         });
         findViewById(R.id.secondary_enter_pip_button).setOnClickListener(
diff --git a/tests/FsVerityTest/AndroidTest.xml b/tests/FsVerityTest/AndroidTest.xml
index d2537f6..f2d7990 100644
--- a/tests/FsVerityTest/AndroidTest.xml
+++ b/tests/FsVerityTest/AndroidTest.xml
@@ -15,6 +15,7 @@
 -->
 <configuration description="fs-verity end-to-end test">
     <option name="test-suite-tag" value="apct" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user"/>
 
     <object type="module_controller" class="com.android.tradefed.testtype.suite.module.ShippingApiLevelModuleController">
         <!-- fs-verity is required since R/30 -->
diff --git a/tests/FsVerityTest/src/com/android/fsverity/FsVerityHostTest.java b/tests/FsVerityTest/src/com/android/fsverity/FsVerityHostTest.java
index be479f2..1b02792 100644
--- a/tests/FsVerityTest/src/com/android/fsverity/FsVerityHostTest.java
+++ b/tests/FsVerityTest/src/com/android/fsverity/FsVerityHostTest.java
@@ -25,6 +25,7 @@
 import android.security.Flags;
 
 import com.android.blockdevicewriter.BlockDeviceWriter;
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -52,7 +53,6 @@
     private static final String TARGET_PACKAGE = "com.android.fsverity";
 
     private static final String BASENAME = "test.file";
-    private static final String TARGET_PATH = "/data/data/" + TARGET_PACKAGE + "/files/" + BASENAME;
 
     @Rule
     public final CheckFlagsRule mCheckFlagsRule =
@@ -63,11 +63,11 @@
         prepareTest(10000);
 
         ITestDevice device = getDevice();
-        BlockDeviceWriter.damageFileAgainstBlockDevice(device, TARGET_PATH, 0);
-        BlockDeviceWriter.damageFileAgainstBlockDevice(device, TARGET_PATH, 8192);
+        BlockDeviceWriter.damageFileAgainstBlockDevice(device, getTargetFilePath(), 0);
+        BlockDeviceWriter.damageFileAgainstBlockDevice(device, getTargetFilePath(), 8192);
         BlockDeviceWriter.dropCaches(device);
 
-        verifyRead(TARGET_PATH, "0,2");
+        verifyRead(getTargetFilePath(), "0,2");
     }
 
     @Test
@@ -75,12 +75,17 @@
         prepareTest(128 * 4096 + 1);
 
         ITestDevice device = getDevice();
-        BlockDeviceWriter.damageFileAgainstBlockDevice(device, TARGET_PATH, 4096);
-        BlockDeviceWriter.damageFileAgainstBlockDevice(device, TARGET_PATH, 100 * 4096);
-        BlockDeviceWriter.damageFileAgainstBlockDevice(device, TARGET_PATH, 128 * 4096 + 1);
+        BlockDeviceWriter.damageFileAgainstBlockDevice(device, getTargetFilePath(), 4096);
+        BlockDeviceWriter.damageFileAgainstBlockDevice(device, getTargetFilePath(), 100 * 4096);
+        BlockDeviceWriter.damageFileAgainstBlockDevice(device, getTargetFilePath(), 128 * 4096 + 1);
         BlockDeviceWriter.dropCaches(device);
 
-        verifyRead(TARGET_PATH, "1,100,128");
+        verifyRead(getTargetFilePath(), "1,100,128");
+    }
+
+    private String getTargetFilePath() throws DeviceNotAvailableException {
+        return "/data/user/" + getDevice().getCurrentUser() + "/" + TARGET_PACKAGE + "/files/"
+                + BASENAME;
     }
 
     private void prepareTest(int fileSize) throws Exception {
@@ -97,7 +102,7 @@
         options.setTestClassName(TARGET_PACKAGE + ".Helper");
         options.setTestMethodName("verifyFileRead");
         options.addInstrumentationArg("brokenBlockIndicesCsv", indicesCsv);
-        options.addInstrumentationArg("filePath", TARGET_PATH);
+        options.addInstrumentationArg("filePath", getTargetFilePath());
         assertThat(runDeviceTests(options)).isTrue();
     }
 }
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index c1784f3..566e51a 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -22,9 +22,9 @@
 import android.hardware.display.DisplayViewport
 import android.hardware.input.InputManager
 import android.hardware.input.InputManagerGlobal
-import android.os.IInputConstants
 import android.os.test.TestLooper
 import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
 import android.provider.Settings
 import android.test.mock.MockContentResolver
 import android.view.Display
@@ -73,6 +73,9 @@
     @get:Rule
     val fakeSettingsProviderRule = FakeSettingsProvider.rule()!!
 
+    @get:Rule
+    val setFlagsRule = SetFlagsRule()
+
     @Mock
     private lateinit var native: NativeInputManagerService
 
@@ -145,7 +148,6 @@
         verify(native).setTouchpadTapToClickEnabled(anyBoolean())
         verify(native).setTouchpadRightClickZoneEnabled(anyBoolean())
         verify(native).setShowTouches(anyBoolean())
-        verify(native).reloadPointerIcons()
         verify(native).setMotionClassifierEnabled(anyBoolean())
         verify(native).setMaximumObscuringOpacityForTouch(anyFloat())
         verify(native).setStylusPointerIconEnabled(anyBoolean())
@@ -172,6 +174,8 @@
 
     @Test
     fun testSetVirtualMousePointerDisplayId() {
+        setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
         // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
         // until the native callback happens.
         var countDownLatch = CountDownLatch(1)
@@ -223,6 +227,8 @@
 
     @Test
     fun testSetVirtualMousePointerDisplayId_unsuccessfulUpdate() {
+        setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
         // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
         // until the native callback happens.
         val countDownLatch = CountDownLatch(1)
@@ -248,6 +254,8 @@
 
     @Test
     fun testSetVirtualMousePointerDisplayId_competingRequests() {
+        setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
         val firstRequestSyncLatch = CountDownLatch(1)
         doAnswer {
             firstRequestSyncLatch.countDown()
@@ -291,18 +299,21 @@
 
     @Test
     fun onDisplayRemoved_resetAllAdditionalInputProperties() {
+        setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
         setVirtualMousePointerDisplayIdAndVerify(10)
 
         localService.setPointerIconVisible(false, 10)
+        verify(native).setPointerIconVisibility(10, false)
         verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
-        localService.setPointerAcceleration(5f, 10)
-        verify(native).setPointerAcceleration(eq(5f))
+        localService.setMousePointerAccelerationEnabled(false, 10)
+        verify(native).setMousePointerAccelerationEnabled(10, false)
 
         service.onDisplayRemoved(10)
+        verify(native).setPointerIconVisibility(10, true)
         verify(native).displayRemoved(eq(10))
         verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED))
-        verify(native).setPointerAcceleration(
-            eq(IInputConstants.DEFAULT_POINTER_ACCELERATION.toFloat()))
+        verify(native).setMousePointerAccelerationEnabled(10, true)
         verifyNoMoreInteractions(native)
 
         // This call should not block because the virtual mouse pointer override was never removed.
@@ -314,42 +325,48 @@
 
     @Test
     fun updateAdditionalInputPropertiesForOverrideDisplay() {
+        setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
         setVirtualMousePointerDisplayIdAndVerify(10)
 
         localService.setPointerIconVisible(false, 10)
         verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
-        localService.setPointerAcceleration(5f, 10)
-        verify(native).setPointerAcceleration(eq(5f))
+        verify(native).setPointerIconVisibility(10, false)
+        localService.setMousePointerAccelerationEnabled(false, 10)
+        verify(native).setMousePointerAccelerationEnabled(10, false)
 
         localService.setPointerIconVisible(true, 10)
         verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED))
-        localService.setPointerAcceleration(1f, 10)
-        verify(native).setPointerAcceleration(eq(1f))
+        verify(native).setPointerIconVisibility(10, true)
+        localService.setMousePointerAccelerationEnabled(true, 10)
+        verify(native).setMousePointerAccelerationEnabled(10, true)
 
-        // Verify that setting properties on a different display is not propagated until the
-        // pointer is moved to that display.
         localService.setPointerIconVisible(false, 20)
-        localService.setPointerAcceleration(6f, 20)
+        verify(native).setPointerIconVisibility(20, false)
+        localService.setMousePointerAccelerationEnabled(false, 20)
+        verify(native).setMousePointerAccelerationEnabled(20, false)
         verifyNoMoreInteractions(native)
 
         clearInvocations(native)
         setVirtualMousePointerDisplayIdAndVerify(20)
 
         verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
-        verify(native).setPointerAcceleration(eq(6f))
     }
 
     @Test
     fun setAdditionalInputPropertiesBeforeOverride() {
-        localService.setPointerIconVisible(false, 10)
-        localService.setPointerAcceleration(5f, 10)
+        setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
 
+        localService.setPointerIconVisible(false, 10)
+        localService.setMousePointerAccelerationEnabled(false, 10)
+
+        verify(native).setPointerIconVisibility(10, false)
+        verify(native).setMousePointerAccelerationEnabled(10, false)
         verifyNoMoreInteractions(native)
 
         setVirtualMousePointerDisplayIdAndVerify(10)
 
         verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
-        verify(native).setPointerAcceleration(eq(5f))
     }
 
     @Test
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index bbd4567..7343ba1c 100644
--- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.input
 
+import android.app.NotificationManager
 import android.content.Context
 import android.content.ContextWrapper
 import android.content.pm.ActivityInfo
@@ -129,6 +130,8 @@
 
     @Mock
     private lateinit var packageManager: PackageManager
+    @Mock
+    private lateinit var notificationManager: NotificationManager
     private lateinit var keyboardLayoutManager: KeyboardLayoutManager
 
     private lateinit var imeInfo: InputMethodInfo
@@ -163,6 +166,8 @@
         keyboardLayoutManager = Mockito.spy(
             KeyboardLayoutManager(context, native, dataStore, testLooper.looper)
         )
+        Mockito.`when`(context.getSystemService(Mockito.eq(Context.NOTIFICATION_SERVICE)))
+                .thenReturn(notificationManager)
         setupInputDevices()
         setupBroadcastReceiver()
         setupIme()
@@ -946,6 +951,48 @@
         }
     }
 
+    @Test
+    fun testNotificationShown_onInputDeviceChanged() {
+        val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype()))
+        Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
+        Mockito.doReturn(false).`when`(keyboardLayoutManager).isVirtualDevice(
+            ArgumentMatchers.eq(keyboardDevice.id)
+        )
+        NewSettingsApiFlag(true).use {
+            keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
+            ExtendedMockito.verify(
+                notificationManager,
+                Mockito.times(1)
+            ).notifyAsUser(
+                ArgumentMatchers.isNull(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.any(),
+                ArgumentMatchers.any()
+            )
+        }
+    }
+
+    @Test
+    fun testNotificationNotShown_onInputDeviceChanged_forVirtualDevice() {
+        val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype()))
+        Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
+        Mockito.doReturn(true).`when`(keyboardLayoutManager).isVirtualDevice(
+            ArgumentMatchers.eq(keyboardDevice.id)
+        )
+        NewSettingsApiFlag(true).use {
+            keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
+            ExtendedMockito.verify(
+                notificationManager,
+                Mockito.never()
+            ).notifyAsUser(
+                ArgumentMatchers.isNull(),
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.any(),
+                ArgumentMatchers.any()
+            )
+        }
+    }
+
     private fun assertCorrectLayout(
         device: InputDevice,
         imeSubtype: InputMethodSubtype,
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index cbdcb88..518183f 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -30,6 +30,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.content.rollback.RollbackInfo;
 import android.content.rollback.RollbackManager;
 import android.os.UserManager;
@@ -146,7 +147,8 @@
             assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
 
             // Upgrade from v1 to v2, with rollbacks enabled.
-            Install.single(TestApp.A2).setEnableRollback().commit();
+            Install.single(TestApp.A2).setEnableRollback().setRollbackImpactLevel(
+                    PackageManager.ROLLBACK_USER_IMPACT_HIGH).commit();
             assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
 
             // The app should now be available for rollback.
@@ -154,6 +156,8 @@
             assertThat(available).isNotStaged();
             assertThat(available).packagesContainsExactly(
                     Rollback.from(TestApp.A2).to(TestApp.A1));
+            assertThat(available.getRollbackImpactLevel()).isEqualTo(
+                    PackageManager.ROLLBACK_USER_IMPACT_HIGH);
 
             // We should not have received any rollback requests yet.
             // TODO: Possibly flaky if, by chance, some other app on device
@@ -264,6 +268,8 @@
             RollbackInfo rollbackB = waitForAvailableRollback(TestApp.B);
             assertThat(rollbackB).packagesContainsExactly(
                     Rollback.from(TestApp.B2).to(TestApp.B1));
+            assertThat(rollbackB.getRollbackImpactLevel()).isEqualTo(
+                    PackageManager.ROLLBACK_USER_IMPACT_LOW);
 
             // Register rollback committed receiver
             RollbackBroadcastReceiver rollbackReceiver = new RollbackBroadcastReceiver();
diff --git a/tests/SurfaceControlViewHostTest/AndroidManifest.xml b/tests/SurfaceControlViewHostTest/AndroidManifest.xml
index e50cbc5..71f01ac 100644
--- a/tests/SurfaceControlViewHostTest/AndroidManifest.xml
+++ b/tests/SurfaceControlViewHostTest/AndroidManifest.xml
@@ -32,6 +32,16 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+
+        <activity android:name="SurfaceInputTestActivity"
+            android:label="Surface Input Test"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
         <service android:name=".EmbeddedWindowService"
             android:process="com.android.test.viewembed.embedded_process"/>
     </application>
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java
index abc15b4..14230fe 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java
@@ -23,15 +23,21 @@
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.hardware.display.DisplayManager;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.util.Log;
+import android.view.Choreographer;
 import android.view.Display;
 import android.view.Gravity;
+import android.view.Surface;
+import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
@@ -43,6 +49,9 @@
 
     private Handler mHandler;
 
+    private IBinder mInputToken;
+    private SurfaceControl mSurfaceControl;
+
     @Override
     public void onCreate() {
         super.onCreate();
@@ -101,9 +110,47 @@
                 }
             });
         }
+
         @Override
         public void relayout(WindowManager.LayoutParams lp) {
             mHandler.post(() -> mVr.relayout(lp));
         }
+
+        @Override
+        public void attachEmbeddedSurfaceControl(SurfaceControl parentSc, int displayId,
+                IBinder hostToken) {
+            mHandler.post(() -> {
+                Paint paint = new Paint();
+                paint.setTextSize(40);
+                paint.setColor(Color.WHITE);
+
+                mSurfaceControl = new SurfaceControl.Builder().setName("Child SurfaceControl")
+                        .setParent(parentSc).setBufferSize(500, 500).build();
+                new SurfaceControl.Transaction().show(mSurfaceControl).apply();
+
+                Surface surface = new Surface(mSurfaceControl);
+                Canvas c = surface.lockCanvas(null);
+                c.drawColor(Color.BLUE);
+                c.drawText("Remote", 250, 250, paint);
+                surface.unlockCanvasAndPost(c);
+                WindowManager wm = getSystemService(WindowManager.class);
+                wm.registerBatchedSurfaceControlInputReceiver(displayId, hostToken,
+                        mSurfaceControl,
+                        Choreographer.getInstance(), event -> {
+                            Log.d(TAG, "onInputEvent-remote " + event);
+                            return false;
+                        });
+
+            });
+        }
+
+        @Override
+        public void tearDownEmbeddedSurfaceControl() {
+            if (mSurfaceControl != null) {
+                WindowManager wm = getSystemService(WindowManager.class);
+                wm.unregisterSurfaceControlInputReceiver(mSurfaceControl);
+                new SurfaceControl.Transaction().remove(mSurfaceControl).apply();
+            }
+        }
     }
 }
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl
index 9e9faf0..6b65b40e 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl
@@ -19,8 +19,11 @@
 import android.os.IBinder;
 import com.android.test.viewembed.IAttachEmbeddedWindowCallback;
 import android.view.WindowManager.LayoutParams;
+import android.view.SurfaceControl;
 
 interface IAttachEmbeddedWindow {
     void attachEmbedded(IBinder hostToken, int width, int height, in IAttachEmbeddedWindowCallback callback);
     void relayout(in LayoutParams lp);
+    oneway void attachEmbeddedSurfaceControl(in SurfaceControl parentSurfaceControl, int displayId, IBinder hostToken);
+    oneway void tearDownEmbeddedSurfaceControl();
 }
\ No newline at end of file
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
new file mode 100644
index 0000000..7330ec1
--- /dev/null
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.viewembed;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.AttachedSurfaceControl;
+import android.view.Choreographer;
+import android.view.Gravity;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+
+/**
+ * Used to manually test that {@link android.view.SurfaceControlInputReceiver} API works.
+ */
+public class SurfaceInputTestActivity extends Activity {
+
+    private static final String TAG = "SurfaceInputTestActivity";
+    private SurfaceView mLocalSurfaceView;
+    private SurfaceView mRemoteSurfaceView;
+    private IAttachEmbeddedWindow mIAttachEmbeddedWindow;
+    private SurfaceControl mParentSurfaceControl;
+
+    private SurfaceControl mLocalSurfaceControl;
+
+    private final ServiceConnection mConnection = new ServiceConnection() {
+        // Called when the connection with the service is established
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            Log.d(TAG, "Service Connected");
+            mIAttachEmbeddedWindow = IAttachEmbeddedWindow.Stub.asInterface(service);
+            loadEmbedded();
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            Log.d(TAG, "Service Disconnected");
+            mIAttachEmbeddedWindow = null;
+        }
+    };
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        ViewTreeObserver viewTreeObserver = getWindow().getDecorView().getViewTreeObserver();
+        viewTreeObserver.addOnPreDrawListener(
+                new ViewTreeObserver.OnPreDrawListener() {
+                    @Override
+                    public boolean onPreDraw() {
+                        addLocalChildSurfaceControl(getWindow().getRootSurfaceControl());
+                        viewTreeObserver.removeOnPreDrawListener(this);
+                        return true;
+                    }
+                });
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        LinearLayout content = new LinearLayout(this);
+        mLocalSurfaceView = new SurfaceView(this);
+        content.addView(mLocalSurfaceView, new LinearLayout.LayoutParams(
+                500, 500, Gravity.CENTER_HORIZONTAL | Gravity.TOP));
+
+        mRemoteSurfaceView = new SurfaceView(this);
+        content.addView(mRemoteSurfaceView, new LinearLayout.LayoutParams(
+                500, 500, Gravity.CENTER_HORIZONTAL | Gravity.TOP));
+
+        setContentView(content);
+
+        mLocalSurfaceView.setZOrderOnTop(true);
+        mLocalSurfaceView.getHolder().addCallback(mLocalSurfaceViewCallback);
+
+        mRemoteSurfaceView.setZOrderOnTop(true);
+        mRemoteSurfaceView.getHolder().addCallback(mRemoteSurfaceViewHolder);
+
+        Intent intent = new Intent(this, EmbeddedWindowService.class);
+        intent.setAction(IAttachEmbeddedWindow.class.getName());
+        Log.d(TAG, "bindService");
+        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (mLocalSurfaceControl != null) {
+            getWindowManager().unregisterSurfaceControlInputReceiver(mLocalSurfaceControl);
+            new SurfaceControl.Transaction().remove(mLocalSurfaceControl).apply();
+        }
+    }
+
+    private void addLocalChildSurfaceControl(AttachedSurfaceControl attachedSurfaceControl) {
+        mLocalSurfaceControl = new SurfaceControl.Builder().setName("LocalSC")
+                .setBufferSize(100, 100).build();
+        attachedSurfaceControl.buildReparentTransaction(mLocalSurfaceControl)
+                .setVisibility(mLocalSurfaceControl, true)
+                .setCrop(mLocalSurfaceControl, new Rect(0, 0, 100, 100))
+                .setPosition(mLocalSurfaceControl, 250, 1000)
+                .setLayer(mLocalSurfaceControl, 1).apply();
+
+        Paint paint = new Paint();
+        paint.setColor(Color.WHITE);
+        paint.setTextSize(20);
+
+        Surface surface = new Surface(mLocalSurfaceControl);
+        Canvas c = surface.lockCanvas(null);
+        c.drawColor(Color.GREEN);
+        c.drawText("Local SC", 0, 0, paint);
+        surface.unlockCanvasAndPost(c);
+        WindowManager wm = getSystemService(WindowManager.class);
+        wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(),
+                attachedSurfaceControl.getHostToken(), mLocalSurfaceControl,
+                Choreographer.getInstance(), event -> {
+                    Log.d(TAG, "onInputEvent-sc " + event);
+                    return false;
+                });
+    }
+
+    private final SurfaceHolder.Callback mLocalSurfaceViewCallback = new SurfaceHolder.Callback() {
+        @Override
+        public void surfaceCreated(@NonNull SurfaceHolder holder) {
+            Paint paint = new Paint();
+            paint.setColor(Color.WHITE);
+            paint.setTextSize(40);
+
+            Canvas c = holder.lockCanvas();
+            c.drawColor(Color.RED);
+            c.drawText("Local", 250, 250, paint);
+            holder.unlockCanvasAndPost(c);
+
+            WindowManager wm = getSystemService(WindowManager.class);
+            wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(),
+                    mLocalSurfaceView.getHostToken(), mLocalSurfaceView.getSurfaceControl(),
+                    Choreographer.getInstance(), event -> {
+                        Log.d(TAG, "onInputEvent-local " + event);
+                        return false;
+                    });
+        }
+
+        @Override
+        public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
+                int height) {
+
+        }
+
+        @Override
+        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
+            getWindowManager().unregisterSurfaceControlInputReceiver(
+                    mLocalSurfaceView.getSurfaceControl());
+        }
+    };
+
+    private final SurfaceHolder.Callback mRemoteSurfaceViewHolder = new SurfaceHolder.Callback() {
+        @Override
+        public void surfaceCreated(@NonNull SurfaceHolder holder) {
+            mParentSurfaceControl = mRemoteSurfaceView.getSurfaceControl();
+            loadEmbedded();
+        }
+
+        @Override
+        public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
+                int height) {
+        }
+
+        @Override
+        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
+            if (mIAttachEmbeddedWindow != null) {
+                try {
+                    mIAttachEmbeddedWindow.tearDownEmbeddedSurfaceControl();
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to tear down embedded SurfaceControl", e);
+                }
+            }
+        }
+    };
+
+    private void loadEmbedded() {
+        if (mParentSurfaceControl == null || mIAttachEmbeddedWindow == null) {
+            return;
+        }
+        try {
+            mIAttachEmbeddedWindow.attachEmbeddedSurfaceControl(mParentSurfaceControl,
+                    getDisplayId(), mRemoteSurfaceView.getHostToken());
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to load embedded SurfaceControl", e);
+        }
+    }
+}
diff --git a/tests/testables/src/android/testing/TestableContext.java b/tests/testables/src/android/testing/TestableContext.java
index 0f04d6a..fa8fdfa 100644
--- a/tests/testables/src/android/testing/TestableContext.java
+++ b/tests/testables/src/android/testing/TestableContext.java
@@ -62,8 +62,9 @@
  */
 public class TestableContext extends ContextWrapper implements TestRule {
 
-    private final TestableContentResolver mTestableContentResolver;
-    private final TestableSettingsProvider mSettingsProvider;
+    private TestableContentResolver mTestableContentResolver;
+    private TestableSettingsProvider mSettingsProvider;
+    private RuntimeException mSettingsProviderFailure;
 
     private ArrayList<MockServiceResolver> mMockServiceResolvers;
     private ArrayMap<String, Object> mMockSystemServices;
@@ -83,12 +84,24 @@
 
     public TestableContext(Context base, LeakCheck check) {
         super(base);
-        mTestableContentResolver = new TestableContentResolver(base);
-        ContentProviderClient settings = base.getContentResolver()
-                .acquireContentProviderClient(Settings.AUTHORITY);
-        mSettingsProvider = TestableSettingsProvider.getFakeSettingsProvider(settings);
-        mTestableContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
-        mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+
+        // Configure TestableSettingsProvider when possible; if we fail to initialize some
+        // underlying infrastructure then remember the error and report it later when a test
+        // attempts to interact with it
+        try {
+            ContentProviderClient settings = base.getContentResolver()
+                    .acquireContentProviderClient(Settings.AUTHORITY);
+            mSettingsProvider = TestableSettingsProvider.getFakeSettingsProvider(settings);
+            mTestableContentResolver = new TestableContentResolver(base);
+            mTestableContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
+            mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+            mSettingsProviderFailure = null;
+        } catch (Throwable t) {
+            mTestableContentResolver = null;
+            mSettingsProvider = null;
+            mSettingsProviderFailure = new RuntimeException(
+                    "Failed to initialize TestableSettingsProvider", t);
+        }
         mReceiver = check != null ? check.getTracker("receiver") : null;
         mService = check != null ? check.getTracker("service") : null;
         mComponent = check != null ? check.getTracker("component") : null;
@@ -171,11 +184,17 @@
     }
 
     TestableSettingsProvider getSettingsProvider() {
+        if (mSettingsProviderFailure != null) {
+            throw mSettingsProviderFailure;
+        }
         return mSettingsProvider;
     }
 
     @Override
     public TestableContentResolver getContentResolver() {
+        if (mSettingsProviderFailure != null) {
+            throw mSettingsProviderFailure;
+        }
         return mTestableContentResolver;
     }
 
@@ -515,12 +534,16 @@
         return new TestWatcher() {
             @Override
             protected void succeeded(Description description) {
-                mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+                if (mSettingsProvider != null) {
+                    mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+                }
             }
 
             @Override
             protected void failed(Throwable e, Description description) {
-                mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+                if (mSettingsProvider != null) {
+                    mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+                }
             }
         }.apply(base, description);
     }
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index 82e40b1..60c25b7 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -32,6 +32,7 @@
 import java.lang.annotation.Target;
 import java.lang.reflect.Field;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -76,7 +77,7 @@
     }
 
     private TestableLooper(TestLooperManager wrapper, Looper l) {
-        mQueueWrapper = wrapper;
+        mQueueWrapper = Objects.requireNonNull(wrapper);
         setupQueue(l);
     }
 
@@ -282,65 +283,94 @@
         return InstrumentationRegistry.getInstrumentation().acquireLooperManager(l);
     }
 
-    private static final Map<Object, TestableLooper> sLoopers = new ArrayMap<>();
+    private static final Map<Object, TestableLooperHolder> sLoopers = new ArrayMap<>();
 
     /**
      * For use with {@link RunWithLooper}, used to get the TestableLooper that was
      * automatically created for this test.
      */
     public static TestableLooper get(Object test) {
-        return sLoopers.get(test);
+        final TestableLooperHolder looperHolder = sLoopers.get(test);
+        return (looperHolder != null) ? looperHolder.mTestableLooper : null;
     }
 
     public static void remove(Object test) {
         sLoopers.remove(test);
     }
 
-    static class LooperFrameworkMethod extends FrameworkMethod {
+    /**
+     * Holder object that contains {@link TestableLooper} so that its initialization can be
+     * deferred until a test case is actually run, instead of forcing it to be created at
+     * {@link FrameworkMethod} construction time.
+     *
+     * This deferral is important because some test environments may configure
+     * {@link Looper#getMainLooper()} as part of a {@code Rule} instead of assuming it's globally
+     * initialized and unconditionally available.
+     */
+    private static class TestableLooperHolder {
+        private final boolean mSetAsMain;
+        private final Object mTest;
+
+        private TestableLooper mTestableLooper;
+        private Looper mLooper;
+        private Handler mHandler;
         private HandlerThread mHandlerThread;
 
-        private final TestableLooper mTestableLooper;
-        private final Looper mLooper;
-        private final Handler mHandler;
+        public TestableLooperHolder(boolean setAsMain, Object test) {
+            mSetAsMain = setAsMain;
+            mTest = test;
+        }
 
-        public LooperFrameworkMethod(FrameworkMethod base, boolean setAsMain, Object test) {
-            super(base.getMethod());
+        public void ensureInit() {
+            if (mLooper != null) return;
             try {
-                mLooper = setAsMain ? Looper.getMainLooper() : createLooper();
+                mLooper = mSetAsMain ? Looper.getMainLooper() : createLooper();
                 mTestableLooper = new TestableLooper(mLooper, false);
-                if (!setAsMain) {
-                    mTestableLooper.getLooper().getThread().setName(test.getClass().getName());
+                if (!mSetAsMain) {
+                    mTestableLooper.getLooper().getThread().setName(mTest.getClass().getName());
                 }
             } catch (Exception e) {
                 throw new RuntimeException(e);
             }
-            sLoopers.put(test, mTestableLooper);
             mHandler = new Handler(mLooper);
         }
 
-        public LooperFrameworkMethod(TestableLooper other, FrameworkMethod base) {
+        private Looper createLooper() {
+            // TODO: Find way to share these.
+            mHandlerThread = new HandlerThread(TestableLooper.class.getSimpleName());
+            mHandlerThread.start();
+            return mHandlerThread.getLooper();
+        }
+    }
+
+    static class LooperFrameworkMethod extends FrameworkMethod {
+        private TestableLooperHolder mLooperHolder;
+
+        public LooperFrameworkMethod(FrameworkMethod base, TestableLooperHolder looperHolder) {
             super(base.getMethod());
-            mLooper = other.mLooper;
-            mTestableLooper = other;
-            mHandler = Handler.createAsync(mLooper);
+            mLooperHolder = looperHolder;
         }
 
         public static FrameworkMethod get(FrameworkMethod base, boolean setAsMain, Object test) {
-            if (sLoopers.containsKey(test)) {
-                return new LooperFrameworkMethod(sLoopers.get(test), base);
+            TestableLooperHolder looperHolder = sLoopers.get(test);
+            if (looperHolder == null) {
+                looperHolder = new TestableLooperHolder(setAsMain, test);
+                sLoopers.put(test, looperHolder);
             }
-            return new LooperFrameworkMethod(base, setAsMain, test);
+            return new LooperFrameworkMethod(base, looperHolder);
         }
 
         @Override
         public Object invokeExplosively(Object target, Object... params) throws Throwable {
-            if (Looper.myLooper() == mLooper) {
+            mLooperHolder.ensureInit();
+            if (Looper.myLooper() == mLooperHolder.mLooper) {
                 // Already on the right thread from another statement, just execute then.
                 return super.invokeExplosively(target, params);
             }
-            boolean set = mTestableLooper.mQueueWrapper == null;
+            boolean set = mLooperHolder.mTestableLooper.mQueueWrapper == null;
             if (set) {
-                mTestableLooper.mQueueWrapper = acquireLooperManager(mLooper);
+                mLooperHolder.mTestableLooper.mQueueWrapper = acquireLooperManager(
+                        mLooperHolder.mLooper);
             }
             try {
                 Object[] ret = new Object[1];
@@ -352,11 +382,11 @@
                         throw new LooperException(throwable);
                     }
                 };
-                Message m = Message.obtain(mHandler, execute);
+                Message m = Message.obtain(mLooperHolder.mHandler, execute);
 
                 // Dispatch our message.
                 try {
-                    mTestableLooper.mQueueWrapper.execute(m);
+                    mLooperHolder.mTestableLooper.mQueueWrapper.execute(m);
                 } catch (LooperException e) {
                     throw e.getSource();
                 } catch (RuntimeException re) {
@@ -373,27 +403,20 @@
                 return ret[0];
             } finally {
                 if (set) {
-                    mTestableLooper.mQueueWrapper.release();
-                    mTestableLooper.mQueueWrapper = null;
-                    if (HOLD_MAIN_THREAD && mLooper == Looper.getMainLooper()) {
+                    mLooperHolder.mTestableLooper.mQueueWrapper.release();
+                    mLooperHolder.mTestableLooper.mQueueWrapper = null;
+                    if (HOLD_MAIN_THREAD && mLooperHolder.mLooper == Looper.getMainLooper()) {
                         TestableInstrumentation.releaseMain();
                     }
                 }
             }
         }
 
-        private Looper createLooper() {
-            // TODO: Find way to share these.
-            mHandlerThread = new HandlerThread(TestableLooper.class.getSimpleName());
-            mHandlerThread.start();
-            return mHandlerThread.getLooper();
-        }
-
         @Override
         protected void finalize() throws Throwable {
             super.finalize();
-            if (mHandlerThread != null) {
-                mHandlerThread.quit();
+            if (mLooperHolder.mHandlerThread != null) {
+                mLooperHolder.mHandlerThread.quit();
             }
         }
 
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index f846164..20b7f1f 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -269,6 +269,7 @@
     @Test
     public void testCreatedTransformsAreApplied() throws Exception {
         verifyVcnTransformsApplied(mGatewayConnection, false /* expectForwardTransform */);
+        verify(mUnderlyingNetworkController).updateInboundTransform(any(), any());
     }
 
     @Test
@@ -327,6 +328,8 @@
                             eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any());
         }
 
+        verify(mUnderlyingNetworkController).updateInboundTransform(any(), any());
+
         assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
 
         final List<ChildSaProposal> saProposals =
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
index 692c8a8..49665f7 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
@@ -27,15 +27,18 @@
 import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY;
 
 import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR;
+import static com.android.server.vcn.VcnGatewayConnection.SAFEMODE_TIMEOUT_SECONDS;
 import static com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration;
 import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
 import static com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent;
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.CALLS_REAL_METHODS;
@@ -55,6 +58,7 @@
 import android.net.TelephonyNetworkSpecifier;
 import android.net.vcn.VcnGatewayConnectionConfig;
 import android.net.vcn.VcnGatewayConnectionConfigTest;
+import android.net.vcn.VcnManager;
 import android.net.vcn.VcnTransportInfo;
 import android.net.wifi.WifiInfo;
 import android.os.ParcelUuid;
@@ -81,6 +85,7 @@
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 
 /** Tests for TelephonySubscriptionTracker */
 @RunWith(AndroidJUnit4.class)
@@ -352,4 +357,71 @@
                         any(Executor.class),
                         any(ConnectivityDiagnosticsCallback.class));
     }
+
+    private void verifyGetSafeModeTimeoutMs(
+            boolean isInTestMode,
+            boolean isConfigTimeoutSupported,
+            PersistableBundleWrapper carrierConfig,
+            long expectedTimeoutMs)
+            throws Exception {
+        doReturn(isInTestMode).when(mVcnContext).isInTestMode();
+        doReturn(isConfigTimeoutSupported).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled();
+
+        final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class);
+        doReturn(carrierConfig).when(snapshot).getCarrierConfigForSubGrp(TEST_SUB_GRP);
+
+        final long result =
+                VcnGatewayConnection.getSafeModeTimeoutMs(mVcnContext, snapshot, TEST_SUB_GRP);
+
+        assertEquals(expectedTimeoutMs, result);
+    }
+
+    @Test
+    public void testGetSafeModeTimeoutMs_configTimeoutUnsupported() throws Exception {
+        verifyGetSafeModeTimeoutMs(
+                false /* isInTestMode */,
+                false /* isConfigTimeoutSupported */,
+                null /* carrierConfig */,
+                TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
+    }
+
+    @Test
+    public void testGetSafeModeTimeoutMs_configTimeoutSupported() throws Exception {
+        final int carrierConfigTimeoutSeconds = 20;
+        final PersistableBundleWrapper carrierConfig = mock(PersistableBundleWrapper.class);
+        doReturn(carrierConfigTimeoutSeconds)
+                .when(carrierConfig)
+                .getInt(eq(VcnManager.VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY), anyInt());
+
+        verifyGetSafeModeTimeoutMs(
+                false /* isInTestMode */,
+                true /* isConfigTimeoutSupported */,
+                carrierConfig,
+                TimeUnit.SECONDS.toMillis(carrierConfigTimeoutSeconds));
+    }
+
+    @Test
+    public void testGetSafeModeTimeoutMs_configTimeoutSupported_carrierConfigNull()
+            throws Exception {
+        verifyGetSafeModeTimeoutMs(
+                false /* isInTestMode */,
+                true /* isConfigTimeoutSupported */,
+                null /* carrierConfig */,
+                TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
+    }
+
+    @Test
+    public void testGetSafeModeTimeoutMs_configTimeoutOverrideTestModeDefault() throws Exception {
+        final int carrierConfigTimeoutSeconds = 20;
+        final PersistableBundleWrapper carrierConfig = mock(PersistableBundleWrapper.class);
+        doReturn(carrierConfigTimeoutSeconds)
+                .when(carrierConfig)
+                .getInt(eq(VcnManager.VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY), anyInt());
+
+        verifyGetSafeModeTimeoutMs(
+                true /* isInTestMode */,
+                true /* isConfigTimeoutSupported */,
+                carrierConfig,
+                TimeUnit.SECONDS.toMillis(carrierConfigTimeoutSeconds));
+    }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index edced87..e29e462 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -67,6 +67,8 @@
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
 import com.android.server.vcn.VcnGatewayConnection.VcnChildSessionCallback;
+import com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
+import com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent;
 import com.android.server.vcn.VcnGatewayConnection.VcnWakeLock;
 import com.android.server.vcn.routeselection.UnderlyingNetworkController;
 import com.android.server.vcn.routeselection.UnderlyingNetworkRecord;
@@ -118,13 +120,7 @@
             NetworkCapabilities networkCapabilities,
             LinkProperties linkProperties,
             boolean isBlocked) {
-        return new UnderlyingNetworkRecord(
-                network,
-                networkCapabilities,
-                linkProperties,
-                isBlocked,
-                false /* isSelected */,
-                0 /* priorityClass */);
+        return new UnderlyingNetworkRecord(network, networkCapabilities, linkProperties, isBlocked);
     }
 
     protected static final String TEST_TCP_BUFFER_SIZES_1 = "1,2,3,4";
@@ -226,6 +222,9 @@
         doReturn(mTestLooper.getLooper()).when(mVcnContext).getLooper();
         doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider();
         doReturn(mFeatureFlags).when(mVcnContext).getFeatureFlags();
+        doReturn(true).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled();
+        doReturn(true).when(mVcnContext).isFlagIpSecTransformStateEnabled();
+        doReturn(true).when(mVcnContext).isFlagNetworkMetricMonitorEnabled();
 
         doReturn(mUnderlyingNetworkController)
                 .when(mDeps)
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
new file mode 100644
index 0000000..9daba6a
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vcn.routeselection;
+
+import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY;
+import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY;
+
+import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.PACKET_LOSS_UNAVALAIBLE;
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.net.IpSecTransformState;
+import android.os.OutcomeReceiver;
+import android.os.PowerManager;
+
+import com.android.server.vcn.routeselection.IpSecPacketLossDetector.PacketLossCalculator;
+import com.android.server.vcn.routeselection.NetworkMetricMonitor.IpSecTransformWrapper;
+import com.android.server.vcn.routeselection.NetworkMetricMonitor.NetworkMetricMonitorCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.concurrent.TimeUnit;
+
+public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase {
+    private static final String TAG = IpSecPacketLossDetectorTest.class.getSimpleName();
+
+    private static final int REPLAY_BITMAP_LEN_BYTE = 512;
+    private static final int REPLAY_BITMAP_LEN_BIT = REPLAY_BITMAP_LEN_BYTE * 8;
+    private static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD = 5;
+    private static final long POLL_IPSEC_STATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(30L);
+
+    @Mock private IpSecTransformWrapper mIpSecTransform;
+    @Mock private NetworkMetricMonitorCallback mMetricMonitorCallback;
+    @Mock private PersistableBundleWrapper mCarrierConfig;
+    @Mock private IpSecPacketLossDetector.Dependencies mDependencies;
+    @Spy private PacketLossCalculator mPacketLossCalculator = new PacketLossCalculator();
+
+    @Captor private ArgumentCaptor<OutcomeReceiver> mTransformStateReceiverCaptor;
+    @Captor private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
+
+    private IpSecPacketLossDetector mIpSecPacketLossDetector;
+    private IpSecTransformState mTransformStateInitial;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mTransformStateInitial = newTransformState(0, 0, newReplayBitmap(0));
+
+        when(mCarrierConfig.getInt(
+                        eq(VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY), anyInt()))
+                .thenReturn((int) TimeUnit.MILLISECONDS.toSeconds(POLL_IPSEC_STATE_INTERVAL_MS));
+        when(mCarrierConfig.getInt(
+                        eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY),
+                        anyInt()))
+                .thenReturn(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD);
+
+        when(mDependencies.getPacketLossCalculator()).thenReturn(mPacketLossCalculator);
+
+        mIpSecPacketLossDetector =
+                new IpSecPacketLossDetector(
+                        mVcnContext,
+                        mNetwork,
+                        mCarrierConfig,
+                        mMetricMonitorCallback,
+                        mDependencies);
+    }
+
+    private static IpSecTransformState newTransformState(
+            long rxSeqNo, long packtCount, byte[] replayBitmap) {
+        return new IpSecTransformState.Builder()
+                .setRxHighestSequenceNumber(rxSeqNo)
+                .setPacketCount(packtCount)
+                .setReplayBitmap(replayBitmap)
+                .build();
+    }
+
+    private static byte[] newReplayBitmap(int receivedPktCnt) {
+        final BitSet bitSet = new BitSet(REPLAY_BITMAP_LEN_BIT);
+        for (int i = 0; i < receivedPktCnt; i++) {
+            bitSet.set(i);
+        }
+        return Arrays.copyOf(bitSet.toByteArray(), REPLAY_BITMAP_LEN_BYTE);
+    }
+
+    private void verifyStopped() {
+        assertFalse(mIpSecPacketLossDetector.isStarted());
+        assertFalse(mIpSecPacketLossDetector.isValidationFailed());
+        assertNull(mIpSecPacketLossDetector.getLastTransformState());
+
+        // No event scheduled
+        mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS);
+        assertNull(mTestLooper.nextMessage());
+    }
+
+    @Test
+    public void testInitialization() throws Exception {
+        assertFalse(mIpSecPacketLossDetector.isSelectedUnderlyingNetwork());
+        verifyStopped();
+    }
+
+    private OutcomeReceiver<IpSecTransformState, RuntimeException>
+            startMonitorAndCaptureStateReceiver() {
+        mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(true /* setIsSelected */);
+        mIpSecPacketLossDetector.setInboundTransformInternal(mIpSecTransform);
+
+        // Trigger the runnable
+        mTestLooper.dispatchAll();
+
+        verify(mIpSecTransform)
+                .getIpSecTransformState(any(), mTransformStateReceiverCaptor.capture());
+        return mTransformStateReceiverCaptor.getValue();
+    }
+
+    @Test
+    public void testStartMonitor() throws Exception {
+        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+                startMonitorAndCaptureStateReceiver();
+
+        assertTrue(mIpSecPacketLossDetector.isStarted());
+        assertFalse(mIpSecPacketLossDetector.isValidationFailed());
+        assertTrue(mIpSecPacketLossDetector.isSelectedUnderlyingNetwork());
+        assertEquals(mIpSecTransform, mIpSecPacketLossDetector.getInboundTransformInternal());
+
+        // Mock receiving a state
+        xfrmStateReceiver.onResult(mTransformStateInitial);
+
+        // Verify the first polled state is stored
+        assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState());
+        verify(mPacketLossCalculator, never())
+                .getPacketLossRatePercentage(any(), any(), anyString());
+
+        // Verify next poll is scheduled
+        assertNull(mTestLooper.nextMessage());
+        mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS);
+        assertNotNull(mTestLooper.nextMessage());
+    }
+
+    @Test
+    public void testStartedMonitor_enterDozeMoze() throws Exception {
+        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+                startMonitorAndCaptureStateReceiver();
+
+        // Mock receiving a state
+        xfrmStateReceiver.onResult(mTransformStateInitial);
+        assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState());
+
+        // Mock entering doze mode
+        final Intent intent = mock(Intent.class);
+        when(intent.getAction()).thenReturn(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+        when(mPowerManagerService.isDeviceIdleMode()).thenReturn(true);
+
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(), any(), any(), any());
+        final BroadcastReceiver broadcastReceiver = mBroadcastReceiverCaptor.getValue();
+        broadcastReceiver.onReceive(mContext, intent);
+
+        assertNull(mIpSecPacketLossDetector.getLastTransformState());
+    }
+
+    @Test
+    public void testStartedMonitor_updateInboundTransform() throws Exception {
+        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+                startMonitorAndCaptureStateReceiver();
+
+        // Mock receiving a state
+        xfrmStateReceiver.onResult(mTransformStateInitial);
+        assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState());
+
+        // Update the inbound transform
+        final IpSecTransformWrapper newTransform = mock(IpSecTransformWrapper.class);
+        mIpSecPacketLossDetector.setInboundTransformInternal(newTransform);
+
+        // Verifications
+        assertNull(mIpSecPacketLossDetector.getLastTransformState());
+        mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS);
+        mTestLooper.dispatchAll();
+        verify(newTransform).getIpSecTransformState(any(), any());
+    }
+
+    @Test
+    public void testStartedMonitor_updateCarrierConfig() throws Exception {
+        startMonitorAndCaptureStateReceiver();
+
+        final int additionalPollIntervalMs = (int) TimeUnit.SECONDS.toMillis(10L);
+        when(mCarrierConfig.getInt(
+                        eq(VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY), anyInt()))
+                .thenReturn(
+                        (int)
+                                TimeUnit.MILLISECONDS.toSeconds(
+                                        POLL_IPSEC_STATE_INTERVAL_MS + additionalPollIntervalMs));
+        mIpSecPacketLossDetector.setCarrierConfig(mCarrierConfig);
+        mTestLooper.dispatchAll();
+
+        // The already scheduled event is still fired with the old timeout
+        mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS);
+        mTestLooper.dispatchAll();
+
+        // The next scheduled event will take 10 more seconds to fire
+        mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS);
+        assertNull(mTestLooper.nextMessage());
+        mTestLooper.moveTimeForward(additionalPollIntervalMs);
+        assertNotNull(mTestLooper.nextMessage());
+    }
+
+    @Test
+    public void testStopMonitor() throws Exception {
+        mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(true /* setIsSelected */);
+        mIpSecPacketLossDetector.setInboundTransformInternal(mIpSecTransform);
+
+        assertTrue(mIpSecPacketLossDetector.isStarted());
+        assertNotNull(mTestLooper.nextMessage());
+
+        // Unselect the monitor
+        mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(false /* setIsSelected */);
+        verifyStopped();
+    }
+
+    @Test
+    public void testClose() throws Exception {
+        mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(true /* setIsSelected */);
+        mIpSecPacketLossDetector.setInboundTransformInternal(mIpSecTransform);
+
+        assertTrue(mIpSecPacketLossDetector.isStarted());
+        assertNotNull(mTestLooper.nextMessage());
+
+        // Stop the monitor
+        mIpSecPacketLossDetector.close();
+        verifyStopped();
+        verify(mIpSecTransform).close();
+    }
+
+    @Test
+    public void testTransformStateReceiverOnResultWhenStopped() throws Exception {
+        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+                startMonitorAndCaptureStateReceiver();
+        xfrmStateReceiver.onResult(mTransformStateInitial);
+
+        // Unselect the monitor
+        mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(false /* setIsSelected */);
+        verifyStopped();
+
+        xfrmStateReceiver.onResult(newTransformState(1, 1, newReplayBitmap(1)));
+        verify(mPacketLossCalculator, never())
+                .getPacketLossRatePercentage(any(), any(), anyString());
+    }
+
+    @Test
+    public void testTransformStateReceiverOnError() throws Exception {
+        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+                startMonitorAndCaptureStateReceiver();
+        xfrmStateReceiver.onResult(mTransformStateInitial);
+
+        xfrmStateReceiver.onError(new RuntimeException("Test"));
+        verify(mPacketLossCalculator, never())
+                .getPacketLossRatePercentage(any(), any(), anyString());
+    }
+
+    private void checkHandleLossRate(
+            int mockPacketLossRate, boolean isLastStateExpectedToUpdate, boolean isCallbackExpected)
+            throws Exception {
+        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+                startMonitorAndCaptureStateReceiver();
+        doReturn(mockPacketLossRate)
+                .when(mPacketLossCalculator)
+                .getPacketLossRatePercentage(any(), any(), anyString());
+
+        // Mock receiving two states with mTransformStateInitial and an arbitrary transformNew
+        final IpSecTransformState transformNew = newTransformState(1, 1, newReplayBitmap(1));
+        xfrmStateReceiver.onResult(mTransformStateInitial);
+        xfrmStateReceiver.onResult(transformNew);
+
+        // Verifications
+        verify(mPacketLossCalculator)
+                .getPacketLossRatePercentage(
+                        eq(mTransformStateInitial), eq(transformNew), anyString());
+
+        if (isLastStateExpectedToUpdate) {
+            assertEquals(transformNew, mIpSecPacketLossDetector.getLastTransformState());
+        } else {
+            assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState());
+        }
+
+        if (isCallbackExpected) {
+            verify(mMetricMonitorCallback).onValidationResultReceived();
+        } else {
+            verify(mMetricMonitorCallback, never()).onValidationResultReceived();
+        }
+    }
+
+    @Test
+    public void testHandleLossRate_validationPass() throws Exception {
+        checkHandleLossRate(
+                2, true /* isLastStateExpectedToUpdate */, true /* isCallbackExpected */);
+    }
+
+    @Test
+    public void testHandleLossRate_validationFail() throws Exception {
+        checkHandleLossRate(
+                22, true /* isLastStateExpectedToUpdate */, true /* isCallbackExpected */);
+    }
+
+    @Test
+    public void testHandleLossRate_resultUnavalaible() throws Exception {
+        checkHandleLossRate(
+                PACKET_LOSS_UNAVALAIBLE,
+                false /* isLastStateExpectedToUpdate */,
+                false /* isCallbackExpected */);
+    }
+
+    private void checkGetPacketLossRate(
+            IpSecTransformState oldState, IpSecTransformState newState, int expectedLossRate)
+            throws Exception {
+        assertEquals(
+                expectedLossRate,
+                mPacketLossCalculator.getPacketLossRatePercentage(oldState, newState, TAG));
+    }
+
+    private void checkGetPacketLossRate(
+            IpSecTransformState oldState,
+            int rxSeqNo,
+            int packetCount,
+            int packetInWin,
+            int expectedDataLossRate)
+            throws Exception {
+        final IpSecTransformState newState =
+                newTransformState(rxSeqNo, packetCount, newReplayBitmap(packetInWin));
+        checkGetPacketLossRate(oldState, newState, expectedDataLossRate);
+    }
+
+    @Test
+    public void testGetPacketLossRate_replayWindowUnchanged() throws Exception {
+        checkGetPacketLossRate(
+                mTransformStateInitial, mTransformStateInitial, PACKET_LOSS_UNAVALAIBLE);
+        checkGetPacketLossRate(mTransformStateInitial, 3000, 2000, 2000, PACKET_LOSS_UNAVALAIBLE);
+    }
+
+    @Test
+    public void testGetPacketLossRate_againstInitialState() throws Exception {
+        checkGetPacketLossRate(mTransformStateInitial, 7000, 7001, 4096, 0);
+        checkGetPacketLossRate(mTransformStateInitial, 7000, 6000, 4096, 15);
+        checkGetPacketLossRate(mTransformStateInitial, 7000, 6000, 4000, 14);
+    }
+
+    @Test
+    public void testGetPktLossRate_oldHiSeqSmallerThanWinSize_overlappedWithNewWin()
+            throws Exception {
+        final IpSecTransformState oldState = newTransformState(2000, 1500, newReplayBitmap(1500));
+
+        checkGetPacketLossRate(oldState, 5000, 5001, 4096, 0);
+        checkGetPacketLossRate(oldState, 5000, 4000, 4096, 29);
+        checkGetPacketLossRate(oldState, 5000, 4000, 4000, 27);
+    }
+
+    @Test
+    public void testGetPktLossRate_oldHiSeqSmallerThanWinSize_notOverlappedWithNewWin()
+            throws Exception {
+        final IpSecTransformState oldState = newTransformState(2000, 1500, newReplayBitmap(1500));
+
+        checkGetPacketLossRate(oldState, 7000, 7001, 4096, 0);
+        checkGetPacketLossRate(oldState, 7000, 5000, 4096, 37);
+        checkGetPacketLossRate(oldState, 7000, 5000, 3000, 21);
+    }
+
+    @Test
+    public void testGetPktLossRate_oldHiSeqLargerThanWinSize_overlappedWithNewWin()
+            throws Exception {
+        final IpSecTransformState oldState = newTransformState(10000, 5000, newReplayBitmap(3000));
+
+        checkGetPacketLossRate(oldState, 12000, 8096, 4096, 0);
+        checkGetPacketLossRate(oldState, 12000, 7000, 4096, 36);
+        checkGetPacketLossRate(oldState, 12000, 7000, 3000, 0);
+    }
+
+    @Test
+    public void testGetPktLossRate_oldHiSeqLargerThanWinSize_notOverlappedWithNewWin()
+            throws Exception {
+        final IpSecTransformState oldState = newTransformState(10000, 5000, newReplayBitmap(3000));
+
+        checkGetPacketLossRate(oldState, 20000, 16096, 4096, 0);
+        checkGetPacketLossRate(oldState, 20000, 14000, 4096, 19);
+        checkGetPacketLossRate(oldState, 20000, 14000, 3000, 10);
+    }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
new file mode 100644
index 0000000..6015e931
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vcn.routeselection;
+
+import static com.android.server.vcn.VcnTestUtils.setupSystemService;
+import static com.android.server.vcn.routeselection.UnderlyingNetworkControllerTest.getLinkPropertiesWithName;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.IpSecConfig;
+import android.net.IpSecTransform;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.FeatureFlags;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.ParcelUuid;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+import android.telephony.TelephonyManager;
+
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.VcnContext;
+import com.android.server.vcn.VcnNetworkProvider;
+
+import org.junit.Before;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Set;
+import java.util.UUID;
+
+public abstract class NetworkEvaluationTestBase {
+    protected static final String SSID = "TestWifi";
+    protected static final String SSID_OTHER = "TestWifiOther";
+    protected static final String PLMN_ID = "123456";
+    protected static final String PLMN_ID_OTHER = "234567";
+
+    protected static final int SUB_ID = 1;
+    protected static final int WIFI_RSSI = -60;
+    protected static final int WIFI_RSSI_HIGH = -50;
+    protected static final int WIFI_RSSI_LOW = -80;
+    protected static final int CARRIER_ID = 1;
+    protected static final int CARRIER_ID_OTHER = 2;
+
+    protected static final int LINK_UPSTREAM_BANDWIDTH_KBPS = 1024;
+    protected static final int LINK_DOWNSTREAM_BANDWIDTH_KBPS = 2048;
+
+    protected static final int TEST_MIN_UPSTREAM_BANDWIDTH_KBPS = 100;
+    protected static final int TEST_MIN_DOWNSTREAM_BANDWIDTH_KBPS = 200;
+
+    protected static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
+
+    protected static final NetworkCapabilities WIFI_NETWORK_CAPABILITIES =
+            new NetworkCapabilities.Builder()
+                    .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                    .setSignalStrength(WIFI_RSSI)
+                    .setSsid(SSID)
+                    .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS)
+                    .setLinkDownstreamBandwidthKbps(LINK_DOWNSTREAM_BANDWIDTH_KBPS)
+                    .build();
+
+    protected static final TelephonyNetworkSpecifier TEL_NETWORK_SPECIFIER =
+            new TelephonyNetworkSpecifier.Builder().setSubscriptionId(SUB_ID).build();
+    protected static final NetworkCapabilities CELL_NETWORK_CAPABILITIES =
+            new NetworkCapabilities.Builder()
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
+                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                    .setSubscriptionIds(Set.of(SUB_ID))
+                    .setNetworkSpecifier(TEL_NETWORK_SPECIFIER)
+                    .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS)
+                    .setLinkDownstreamBandwidthKbps(LINK_DOWNSTREAM_BANDWIDTH_KBPS)
+                    .build();
+
+    protected static final LinkProperties LINK_PROPERTIES = getLinkPropertiesWithName("test_iface");
+
+    @Mock protected Context mContext;
+    @Mock protected Network mNetwork;
+    @Mock protected FeatureFlags mFeatureFlags;
+    @Mock protected com.android.net.flags.FeatureFlags mCoreNetFeatureFlags;
+    @Mock protected TelephonySubscriptionSnapshot mSubscriptionSnapshot;
+    @Mock protected TelephonyManager mTelephonyManager;
+    @Mock protected IPowerManager mPowerManagerService;
+
+    protected TestLooper mTestLooper;
+    protected VcnContext mVcnContext;
+    protected PowerManager mPowerManager;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mNetwork.getNetId()).thenReturn(-1);
+
+        mTestLooper = new TestLooper();
+        mVcnContext =
+                spy(
+                        new VcnContext(
+                                mContext,
+                                mTestLooper.getLooper(),
+                                mock(VcnNetworkProvider.class),
+                                false /* isInTestMode */));
+        doNothing().when(mVcnContext).ensureRunningOnLooperThread();
+
+        doReturn(true).when(mVcnContext).isFlagNetworkMetricMonitorEnabled();
+        doReturn(true).when(mVcnContext).isFlagIpSecTransformStateEnabled();
+
+        setupSystemService(
+                mContext, mTelephonyManager, Context.TELEPHONY_SERVICE, TelephonyManager.class);
+        when(mTelephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(mTelephonyManager);
+        when(mTelephonyManager.getNetworkOperator()).thenReturn(PLMN_ID);
+        when(mTelephonyManager.getSimSpecificCarrierId()).thenReturn(CARRIER_ID);
+
+        mPowerManager =
+                new PowerManager(
+                        mContext,
+                        mPowerManagerService,
+                        mock(IThermalService.class),
+                        mock(Handler.class));
+        setupSystemService(mContext, mPowerManager, Context.POWER_SERVICE, PowerManager.class);
+    }
+
+    protected IpSecTransform makeDummyIpSecTransform() throws Exception {
+        return new IpSecTransform(mContext, new IpSecConfig());
+    }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
index 2266041..d85c515 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
@@ -24,152 +24,48 @@
 import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS;
 import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS;
 
-import static com.android.server.vcn.VcnTestUtils.setupSystemService;
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_FALLBACK;
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_INVALID;
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesCellPriorityRule;
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesPriorityRule;
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesWifiPriorityRule;
-import static com.android.server.vcn.routeselection.UnderlyingNetworkControllerTest.getLinkPropertiesWithName;
 import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
-import android.content.Context;
-import android.net.LinkProperties;
-import android.net.Network;
 import android.net.NetworkCapabilities;
-import android.net.TelephonyNetworkSpecifier;
 import android.net.vcn.VcnCellUnderlyingNetworkTemplate;
 import android.net.vcn.VcnGatewayConnectionConfig;
 import android.net.vcn.VcnManager;
 import android.net.vcn.VcnUnderlyingNetworkTemplate;
 import android.net.vcn.VcnWifiUnderlyingNetworkTemplate;
-import android.os.ParcelUuid;
 import android.os.PersistableBundle;
-import android.os.test.TestLooper;
-import android.telephony.TelephonyManager;
 import android.util.ArraySet;
 
-import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
-import com.android.server.vcn.VcnContext;
-import com.android.server.vcn.VcnNetworkProvider;
-
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
-import java.util.UUID;
 
-public class NetworkPriorityClassifierTest {
-    private static final String SSID = "TestWifi";
-    private static final String SSID_OTHER = "TestWifiOther";
-    private static final String PLMN_ID = "123456";
-    private static final String PLMN_ID_OTHER = "234567";
-
-    private static final int SUB_ID = 1;
-    private static final int WIFI_RSSI = -60;
-    private static final int WIFI_RSSI_HIGH = -50;
-    private static final int WIFI_RSSI_LOW = -80;
-    private static final int CARRIER_ID = 1;
-    private static final int CARRIER_ID_OTHER = 2;
-
-    private static final int LINK_UPSTREAM_BANDWIDTH_KBPS = 1024;
-    private static final int LINK_DOWNSTREAM_BANDWIDTH_KBPS = 2048;
-
-    private static final int TEST_MIN_UPSTREAM_BANDWIDTH_KBPS = 100;
-    private static final int TEST_MIN_DOWNSTREAM_BANDWIDTH_KBPS = 200;
-
-    private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
-
-    private static final NetworkCapabilities WIFI_NETWORK_CAPABILITIES =
-            new NetworkCapabilities.Builder()
-                    .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
-                    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                    .setSignalStrength(WIFI_RSSI)
-                    .setSsid(SSID)
-                    .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS)
-                    .setLinkDownstreamBandwidthKbps(LINK_DOWNSTREAM_BANDWIDTH_KBPS)
-                    .build();
-
-    private static final TelephonyNetworkSpecifier TEL_NETWORK_SPECIFIER =
-            new TelephonyNetworkSpecifier.Builder().setSubscriptionId(SUB_ID).build();
-    private static final NetworkCapabilities CELL_NETWORK_CAPABILITIES =
-            new NetworkCapabilities.Builder()
-                    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                    .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
-                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                    .setSubscriptionIds(Set.of(SUB_ID))
-                    .setNetworkSpecifier(TEL_NETWORK_SPECIFIER)
-                    .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS)
-                    .setLinkDownstreamBandwidthKbps(LINK_DOWNSTREAM_BANDWIDTH_KBPS)
-                    .build();
-
-    private static final LinkProperties LINK_PROPERTIES = getLinkPropertiesWithName("test_iface");
-
-    @Mock private Network mNetwork;
-    @Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
-    @Mock private TelephonyManager mTelephonyManager;
-
-    private TestLooper mTestLooper;
-    private VcnContext mVcnContext;
+public class NetworkPriorityClassifierTest extends NetworkEvaluationTestBase {
     private UnderlyingNetworkRecord mWifiNetworkRecord;
     private UnderlyingNetworkRecord mCellNetworkRecord;
 
     @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
+    public void setUp() throws Exception {
+        super.setUp();
 
-        final Context mockContext = mock(Context.class);
-        mTestLooper = new TestLooper();
-        mVcnContext =
-                spy(
-                        new VcnContext(
-                                mockContext,
-                                mTestLooper.getLooper(),
-                                mock(VcnNetworkProvider.class),
-                                false /* isInTestMode */));
-        doNothing().when(mVcnContext).ensureRunningOnLooperThread();
-
-        setupSystemService(
-                mockContext, mTelephonyManager, Context.TELEPHONY_SERVICE, TelephonyManager.class);
-        when(mTelephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(mTelephonyManager);
-        when(mTelephonyManager.getNetworkOperator()).thenReturn(PLMN_ID);
-        when(mTelephonyManager.getSimSpecificCarrierId()).thenReturn(CARRIER_ID);
-
-        mWifiNetworkRecord =
-                getTestNetworkRecord(
-                        WIFI_NETWORK_CAPABILITIES,
-                        VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
-        mCellNetworkRecord =
-                getTestNetworkRecord(
-                        CELL_NETWORK_CAPABILITIES,
-                        VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
+        mWifiNetworkRecord = getTestNetworkRecord(WIFI_NETWORK_CAPABILITIES);
+        mCellNetworkRecord = getTestNetworkRecord(CELL_NETWORK_CAPABILITIES);
     }
 
-    private UnderlyingNetworkRecord getTestNetworkRecord(
-            NetworkCapabilities nc, List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates) {
-        return new UnderlyingNetworkRecord(
-                mNetwork,
-                nc,
-                LINK_PROPERTIES,
-                false /* isBlocked */,
-                mVcnContext,
-                underlyingNetworkTemplates,
-                SUB_GROUP,
-                mSubscriptionSnapshot,
-                null /* currentlySelected */,
-                null /* carrierConfig */);
+    private UnderlyingNetworkRecord getTestNetworkRecord(NetworkCapabilities nc) {
+        return new UnderlyingNetworkRecord(mNetwork, nc, LINK_PROPERTIES, false /* isBlocked */);
     }
 
     @Test
@@ -186,14 +82,14 @@
                         mWifiNetworkRecord,
                         SUB_GROUP,
                         mSubscriptionSnapshot,
-                        null /* currentlySelecetd */,
+                        false /* isSelected */,
                         null /* carrierConfig */));
     }
 
     private void verifyMatchesPriorityRuleForUpstreamBandwidth(
             int entryUpstreamBandwidth,
             int exitUpstreamBandwidth,
-            UnderlyingNetworkRecord currentlySelected,
+            boolean isSelected,
             boolean expectMatch) {
         final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
                 new VcnWifiUnderlyingNetworkTemplate.Builder()
@@ -208,14 +104,14 @@
                         mWifiNetworkRecord,
                         SUB_GROUP,
                         mSubscriptionSnapshot,
-                        currentlySelected,
+                        isSelected,
                         null /* carrierConfig */));
     }
 
     private void verifyMatchesPriorityRuleForDownstreamBandwidth(
             int entryDownstreamBandwidth,
             int exitDownstreamBandwidth,
-            UnderlyingNetworkRecord currentlySelected,
+            boolean isSelected,
             boolean expectMatch) {
         final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
                 new VcnWifiUnderlyingNetworkTemplate.Builder()
@@ -231,7 +127,7 @@
                         mWifiNetworkRecord,
                         SUB_GROUP,
                         mSubscriptionSnapshot,
-                        currentlySelected,
+                        isSelected,
                         null /* carrierConfig */));
     }
 
@@ -240,7 +136,7 @@
         verifyMatchesPriorityRuleForUpstreamBandwidth(
                 TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS,
                 TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
-                null /* currentlySelected */,
+                false /* isSelected */,
                 true);
     }
 
@@ -249,7 +145,7 @@
         verifyMatchesPriorityRuleForUpstreamBandwidth(
                 LINK_UPSTREAM_BANDWIDTH_KBPS + 1,
                 LINK_UPSTREAM_BANDWIDTH_KBPS + 1,
-                null /* currentlySelected */,
+                false /* isSelected */,
                 false);
     }
 
@@ -258,7 +154,7 @@
         verifyMatchesPriorityRuleForDownstreamBandwidth(
                 TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
                 TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
-                null /* currentlySelected */,
+                false /* isSelected */,
                 true);
     }
 
@@ -267,7 +163,7 @@
         verifyMatchesPriorityRuleForDownstreamBandwidth(
                 LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1,
                 LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1,
-                null /* currentlySelected */,
+                false /* isSelected */,
                 false);
     }
 
@@ -276,7 +172,7 @@
         verifyMatchesPriorityRuleForUpstreamBandwidth(
                 TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
                 TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
-                mWifiNetworkRecord,
+                true /* isSelected */,
                 true);
     }
 
@@ -285,7 +181,7 @@
         verifyMatchesPriorityRuleForUpstreamBandwidth(
                 LINK_UPSTREAM_BANDWIDTH_KBPS + 1,
                 LINK_UPSTREAM_BANDWIDTH_KBPS + 1,
-                mWifiNetworkRecord,
+                true /* isSelected */,
                 false);
     }
 
@@ -294,7 +190,7 @@
         verifyMatchesPriorityRuleForDownstreamBandwidth(
                 TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
                 TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
-                mWifiNetworkRecord,
+                true /* isSelected */,
                 true);
     }
 
@@ -303,7 +199,7 @@
         verifyMatchesPriorityRuleForDownstreamBandwidth(
                 LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1,
                 LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1,
-                mWifiNetworkRecord,
+                true /* isSelected */,
                 false);
     }
 
@@ -318,14 +214,12 @@
                                 TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
                                 TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS)
                         .build();
-        final UnderlyingNetworkRecord selectedNetworkRecord =
-                isSelectedNetwork ? mWifiNetworkRecord : null;
         assertEquals(
                 expectMatch,
                 checkMatchesWifiPriorityRule(
                         wifiNetworkPriority,
                         mWifiNetworkRecord,
-                        selectedNetworkRecord,
+                        isSelectedNetwork,
                         carrierConfig == null
                                 ? null
                                 : new PersistableBundleWrapper(carrierConfig)));
@@ -381,7 +275,7 @@
                 checkMatchesWifiPriorityRule(
                         wifiNetworkPriority,
                         mWifiNetworkRecord,
-                        null /* currentlySelecetd */,
+                        false /* isSelected */,
                         null /* carrierConfig */));
     }
 
@@ -516,7 +410,7 @@
                         mCellNetworkRecord,
                         SUB_GROUP,
                         mSubscriptionSnapshot,
-                        null /* currentlySelected */,
+                        false /* isSelected */,
                         null /* carrierConfig */));
     }
 
@@ -543,7 +437,16 @@
 
     @Test
     public void testCalculatePriorityClass() throws Exception {
-        assertEquals(2, mCellNetworkRecord.priorityClass);
+        final int priorityClass =
+                NetworkPriorityClassifier.calculatePriorityClass(
+                        mVcnContext,
+                        mCellNetworkRecord,
+                        VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot,
+                        false /* isSelected */,
+                        null /* carrierConfig */);
+        assertEquals(2, priorityClass);
     }
 
     private void checkCalculatePriorityClassFailToMatchAny(
@@ -561,10 +464,19 @@
             ncBuilder.addCapability(NET_CAPABILITY_INTERNET);
         }
 
-        final UnderlyingNetworkRecord nonDunNetworkRecord =
-                getTestNetworkRecord(ncBuilder.build(), templatesRequireDun);
+        final UnderlyingNetworkRecord nonDunNetworkRecord = getTestNetworkRecord(ncBuilder.build());
 
-        assertEquals(expectedPriorityClass, nonDunNetworkRecord.priorityClass);
+        final int priorityClass =
+                NetworkPriorityClassifier.calculatePriorityClass(
+                        mVcnContext,
+                        nonDunNetworkRecord,
+                        templatesRequireDun,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot,
+                        false /* isSelected */,
+                        null /* carrierConfig */);
+
+        assertEquals(expectedPriorityClass, priorityClass);
     }
 
     @Test
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
index 2941fde..588624b 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
@@ -29,13 +29,12 @@
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.WIFI_EXIT_RSSI_THRESHOLD_DEFAULT;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -48,6 +47,8 @@
 
 import android.content.Context;
 import android.net.ConnectivityManager;
+import android.net.IpSecConfig;
+import android.net.IpSecTransform;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -67,9 +68,11 @@
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.VcnContext;
 import com.android.server.vcn.VcnNetworkProvider;
+import com.android.server.vcn.routeselection.UnderlyingNetworkController.Dependencies;
 import com.android.server.vcn.routeselection.UnderlyingNetworkController.NetworkBringupCallback;
 import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkControllerCallback;
 import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkListener;
+import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -77,6 +80,7 @@
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -152,12 +156,17 @@
     @Mock private CarrierConfigManager mCarrierConfigManager;
     @Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
     @Mock private UnderlyingNetworkControllerCallback mNetworkControllerCb;
+    @Mock private NetworkEvaluatorCallback mEvaluatorCallback;
     @Mock private Network mNetwork;
 
+    @Spy private Dependencies mDependencies = new Dependencies();
+
     @Captor private ArgumentCaptor<UnderlyingNetworkListener> mUnderlyingNetworkListenerCaptor;
+    @Captor private ArgumentCaptor<NetworkEvaluatorCallback> mEvaluatorCallbackCaptor;
 
     private TestLooper mTestLooper;
     private VcnContext mVcnContext;
+    private UnderlyingNetworkEvaluator mNetworkEvaluator;
     private UnderlyingNetworkController mUnderlyingNetworkController;
 
     @Before
@@ -172,7 +181,7 @@
                                 mTestLooper.getLooper(),
                                 mVcnNetworkProvider,
                                 false /* isInTestMode */));
-        resetVcnContext();
+        resetVcnContext(mVcnContext);
 
         setupSystemService(
                 mContext,
@@ -189,18 +198,36 @@
 
         when(mSubscriptionSnapshot.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(INITIAL_SUB_IDS);
 
+        mNetworkEvaluator =
+                spy(
+                        new UnderlyingNetworkEvaluator(
+                                mVcnContext,
+                                mNetwork,
+                                VcnGatewayConnectionConfigTest.buildTestConfig()
+                                        .getVcnUnderlyingNetworkPriorities(),
+                                SUB_GROUP,
+                                mSubscriptionSnapshot,
+                                null,
+                                mEvaluatorCallback));
+        doReturn(mNetworkEvaluator)
+                .when(mDependencies)
+                .newUnderlyingNetworkEvaluator(any(), any(), any(), any(), any(), any(), any());
+
         mUnderlyingNetworkController =
                 new UnderlyingNetworkController(
                         mVcnContext,
                         VcnGatewayConnectionConfigTest.buildTestConfig(),
                         SUB_GROUP,
                         mSubscriptionSnapshot,
-                        mNetworkControllerCb);
+                        mNetworkControllerCb,
+                        mDependencies);
     }
 
-    private void resetVcnContext() {
-        reset(mVcnContext);
-        doNothing().when(mVcnContext).ensureRunningOnLooperThread();
+    private void resetVcnContext(VcnContext vcnContext) {
+        reset(vcnContext);
+        doNothing().when(vcnContext).ensureRunningOnLooperThread();
+        doReturn(true).when(vcnContext).isFlagNetworkMetricMonitorEnabled();
+        doReturn(true).when(vcnContext).isFlagIpSecTransformStateEnabled();
     }
 
     // Package private for use in NetworkPriorityClassifierTest
@@ -226,11 +253,13 @@
         final ConnectivityManager cm = mock(ConnectivityManager.class);
         setupSystemService(mContext, cm, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
         final VcnContext vcnContext =
-                new VcnContext(
-                        mContext,
-                        mTestLooper.getLooper(),
-                        mVcnNetworkProvider,
-                        true /* isInTestMode */);
+                spy(
+                        new VcnContext(
+                                mContext,
+                                mTestLooper.getLooper(),
+                                mVcnNetworkProvider,
+                                true /* isInTestMode */));
+        resetVcnContext(vcnContext);
 
         new UnderlyingNetworkController(
                 vcnContext,
@@ -489,13 +518,7 @@
             NetworkCapabilities networkCapabilities,
             LinkProperties linkProperties,
             boolean isBlocked) {
-        return new UnderlyingNetworkRecord(
-                network,
-                networkCapabilities,
-                linkProperties,
-                isBlocked,
-                false /* isSelected */,
-                0 /* priorityClass */);
+        return new UnderlyingNetworkRecord(network, networkCapabilities, linkProperties, isBlocked);
     }
 
     @Test
@@ -515,24 +538,12 @@
         UnderlyingNetworkRecord recordC =
                 new UnderlyingNetworkRecord(
                         mNetwork,
-                        INITIAL_NETWORK_CAPABILITIES,
-                        INITIAL_LINK_PROPERTIES,
-                        false /* isBlocked */,
-                        true /* isSelected */,
-                        -1 /* priorityClass */);
-        UnderlyingNetworkRecord recordD =
-                getTestNetworkRecord(
-                        mNetwork,
                         UPDATED_NETWORK_CAPABILITIES,
                         UPDATED_LINK_PROPERTIES,
                         false /* isBlocked */);
 
         assertEquals(recordA, recordB);
-        assertEquals(recordA, recordC);
-        assertNotEquals(recordA, recordD);
-
-        assertTrue(UnderlyingNetworkRecord.isEqualIncludingPriorities(recordA, recordB));
-        assertFalse(UnderlyingNetworkRecord.isEqualIncludingPriorities(recordA, recordC));
+        assertNotEquals(recordA, recordC);
     }
 
     @Test
@@ -540,6 +551,58 @@
         verifyRegistrationOnAvailableAndGetCallback();
     }
 
+    @Test
+    public void testUpdateSubscriptionSnapshotAndCarrierConfig() {
+        verifyRegistrationOnAvailableAndGetCallback();
+
+        TelephonySubscriptionSnapshot subscriptionUpdate =
+                mock(TelephonySubscriptionSnapshot.class);
+        when(subscriptionUpdate.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(UPDATED_SUB_IDS);
+
+        mUnderlyingNetworkController.updateSubscriptionSnapshot(subscriptionUpdate);
+
+        verify(mNetworkEvaluator).reevaluate(any(), any(), any(), any());
+    }
+
+    @Test
+    public void testUpdateIpSecTransform() {
+        verifyRegistrationOnAvailableAndGetCallback();
+
+        final UnderlyingNetworkRecord expectedRecord =
+                getTestNetworkRecord(
+                        mNetwork,
+                        INITIAL_NETWORK_CAPABILITIES,
+                        INITIAL_LINK_PROPERTIES,
+                        false /* isBlocked */);
+        final IpSecTransform expectedTransform = new IpSecTransform(mContext, new IpSecConfig());
+
+        mUnderlyingNetworkController.updateInboundTransform(expectedRecord, expectedTransform);
+        verify(mNetworkEvaluator).setInboundTransform(expectedTransform);
+    }
+
+    @Test
+    public void testOnEvaluationResultChanged() {
+        verifyRegistrationOnAvailableAndGetCallback();
+
+        // Verify #reevaluateNetworks is called by checking #getNetworkRecord
+        verify(mNetworkEvaluator).getNetworkRecord();
+
+        // Trigger the callback
+        verify(mDependencies)
+                .newUnderlyingNetworkEvaluator(
+                        any(),
+                        any(),
+                        any(),
+                        any(),
+                        any(),
+                        any(),
+                        mEvaluatorCallbackCaptor.capture());
+        mEvaluatorCallbackCaptor.getValue().onEvaluationResultChanged();
+
+        // Verify #reevaluateNetworks is called again
+        verify(mNetworkEvaluator, times(2)).getNetworkRecord();
+    }
+
     private UnderlyingNetworkListener verifyRegistrationOnAvailableAndGetCallback() {
         return verifyRegistrationOnAvailableAndGetCallback(INITIAL_NETWORK_CAPABILITIES);
     }
@@ -583,6 +646,7 @@
                         INITIAL_LINK_PROPERTIES,
                         false /* isBlocked */);
         verifyOnSelectedUnderlyingNetworkChanged(expectedRecord);
+        verify(mNetworkEvaluator).setIsSelected(eq(true), any(), any(), any(), any());
         return cb;
     }
 
@@ -667,7 +731,7 @@
 
         cb.onBlockedStatusChanged(mNetwork, true /* isBlocked */);
 
-        verifyOnSelectedUnderlyingNetworkChanged(null);
+        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(null);
     }
 
     @Test
@@ -675,6 +739,7 @@
         UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
 
         cb.onLost(mNetwork);
+        verify(mNetworkEvaluator).close();
 
         verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(null);
     }
@@ -713,7 +778,8 @@
                 VcnGatewayConnectionConfigTest.buildTestConfig(networkTemplates),
                 SUB_GROUP,
                 mSubscriptionSnapshot,
-                mNetworkControllerCb);
+                mNetworkControllerCb,
+                mDependencies);
 
         verify(cm)
                 .registerNetworkCallback(
@@ -724,30 +790,44 @@
         return mUnderlyingNetworkListenerCaptor.getValue();
     }
 
-    private UnderlyingNetworkRecord bringupNetworkAndGetRecord(
+    private UnderlyingNetworkEvaluator bringupNetworkAndGetEvaluator(
             UnderlyingNetworkListener cb,
             NetworkCapabilities requestNetworkCaps,
-            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
-            UnderlyingNetworkRecord currentlySelected) {
+            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates) {
         final Network network = mock(Network.class);
         final NetworkCapabilities responseNetworkCaps =
                 buildResponseNwCaps(requestNetworkCaps, INITIAL_SUB_IDS);
+        final UnderlyingNetworkEvaluator evaluator =
+                spy(
+                        new UnderlyingNetworkEvaluator(
+                                mVcnContext,
+                                network,
+                                underlyingNetworkTemplates,
+                                SUB_GROUP,
+                                mSubscriptionSnapshot,
+                                null,
+                                mEvaluatorCallback));
+        doReturn(evaluator)
+                .when(mDependencies)
+                .newUnderlyingNetworkEvaluator(any(), any(), any(), any(), any(), any(), any());
 
         cb.onAvailable(network);
         cb.onCapabilitiesChanged(network, responseNetworkCaps);
         cb.onLinkPropertiesChanged(network, INITIAL_LINK_PROPERTIES);
         cb.onBlockedStatusChanged(network, false /* isFalse */);
-        return new UnderlyingNetworkRecord(
-                network,
-                responseNetworkCaps,
-                INITIAL_LINK_PROPERTIES,
-                false /* isBlocked */,
-                mVcnContext,
-                underlyingNetworkTemplates,
-                SUB_GROUP,
-                mSubscriptionSnapshot,
-                currentlySelected,
-                null /* carrierConfig */);
+
+        return evaluator;
+    }
+
+    private void verifySelectNetwork(UnderlyingNetworkEvaluator expectedEvaluator) {
+        verifyOnSelectedUnderlyingNetworkChanged(expectedEvaluator.getNetworkRecord());
+        verify(expectedEvaluator).setIsSelected(eq(true), any(), any(), any(), any());
+    }
+
+    private void verifyNeverSelectNetwork(UnderlyingNetworkEvaluator expectedEvaluator) {
+        verify(mNetworkControllerCb, never())
+                .onSelectedUnderlyingNetworkChanged(eq(expectedEvaluator.getNetworkRecord()));
+        verify(expectedEvaluator, never()).setIsSelected(eq(true), any(), any(), any(), any());
     }
 
     @Test
@@ -759,19 +839,15 @@
         UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates);
 
         // Bring up CBS network
-        final UnderlyingNetworkRecord cbsNetworkRecord =
-                bringupNetworkAndGetRecord(
-                        cb,
-                        CBS_NETWORK_CAPABILITIES,
-                        networkTemplates,
-                        null /* currentlySelected */);
-        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(cbsNetworkRecord));
+        final UnderlyingNetworkEvaluator cbsNetworkEvaluator =
+                bringupNetworkAndGetEvaluator(cb, CBS_NETWORK_CAPABILITIES, networkTemplates);
+        verifySelectNetwork(cbsNetworkEvaluator);
 
         // Bring up DUN network
-        final UnderlyingNetworkRecord dunNetworkRecord =
-                bringupNetworkAndGetRecord(
-                        cb, DUN_NETWORK_CAPABILITIES, networkTemplates, cbsNetworkRecord);
-        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(dunNetworkRecord));
+        final UnderlyingNetworkEvaluator dunNetworkEvaluator =
+                bringupNetworkAndGetEvaluator(cb, DUN_NETWORK_CAPABILITIES, networkTemplates);
+        verifySelectNetwork(dunNetworkEvaluator);
+        verify(cbsNetworkEvaluator).setIsSelected(eq(false), any(), any(), any(), any());
     }
 
     @Test
@@ -783,20 +859,14 @@
         UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates);
 
         // Bring up DUN network
-        final UnderlyingNetworkRecord dunNetworkRecord =
-                bringupNetworkAndGetRecord(
-                        cb,
-                        DUN_NETWORK_CAPABILITIES,
-                        networkTemplates,
-                        null /* currentlySelected */);
-        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(dunNetworkRecord));
+        final UnderlyingNetworkEvaluator dunNetworkEvaluator =
+                bringupNetworkAndGetEvaluator(cb, DUN_NETWORK_CAPABILITIES, networkTemplates);
+        verifySelectNetwork(dunNetworkEvaluator);
 
         // Bring up CBS network
-        final UnderlyingNetworkRecord cbsNetworkRecord =
-                bringupNetworkAndGetRecord(
-                        cb, CBS_NETWORK_CAPABILITIES, networkTemplates, dunNetworkRecord);
-        verify(mNetworkControllerCb, never())
-                .onSelectedUnderlyingNetworkChanged(eq(cbsNetworkRecord));
+        final UnderlyingNetworkEvaluator cbsNetworkEvaluator =
+                bringupNetworkAndGetEvaluator(cb, CBS_NETWORK_CAPABILITIES, networkTemplates);
+        verifyNeverSelectNetwork(cbsNetworkEvaluator);
     }
 
     @Test
@@ -808,13 +878,9 @@
         UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates);
 
         // Bring up an Internet network without DUN capability
-        final UnderlyingNetworkRecord networkRecord =
-                bringupNetworkAndGetRecord(
-                        cb,
-                        INITIAL_NETWORK_CAPABILITIES,
-                        networkTemplates,
-                        null /* currentlySelected */);
-        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(networkRecord));
+        final UnderlyingNetworkEvaluator evaluator =
+                bringupNetworkAndGetEvaluator(cb, INITIAL_NETWORK_CAPABILITIES, networkTemplates);
+        verifySelectNetwork(evaluator);
     }
 
     @Test
@@ -825,10 +891,8 @@
                 new VcnCellUnderlyingNetworkTemplate.Builder().setDun(MATCH_REQUIRED).build());
         UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates);
 
-        bringupNetworkAndGetRecord(
-                cb, CBS_NETWORK_CAPABILITIES, networkTemplates, null /* currentlySelected */);
-
-        verify(mNetworkControllerCb, never())
-                .onSelectedUnderlyingNetworkChanged(any(UnderlyingNetworkRecord.class));
+        final UnderlyingNetworkEvaluator evaluator =
+                bringupNetworkAndGetEvaluator(cb, CBS_NETWORK_CAPABILITIES, networkTemplates);
+        verifyNeverSelectNetwork(evaluator);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
new file mode 100644
index 0000000..aa81efe
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vcn.routeselection;
+
+import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY;
+
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_INVALID;
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.net.IpSecTransform;
+import android.net.vcn.VcnGatewayConnectionConfig;
+
+import com.android.server.vcn.routeselection.NetworkMetricMonitor.NetworkMetricMonitorCallback;
+import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.Dependencies;
+import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+
+import java.util.concurrent.TimeUnit;
+
+public class UnderlyingNetworkEvaluatorTest extends NetworkEvaluationTestBase {
+    private static final int PENALTY_TIMEOUT_MIN = 10;
+    private static final long PENALTY_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(PENALTY_TIMEOUT_MIN);
+
+    @Mock private PersistableBundleWrapper mCarrierConfig;
+    @Mock private IpSecPacketLossDetector mIpSecPacketLossDetector;
+    @Mock private Dependencies mDependencies;
+    @Mock private NetworkEvaluatorCallback mEvaluatorCallback;
+
+    @Captor private ArgumentCaptor<NetworkMetricMonitorCallback> mMetricMonitorCbCaptor;
+
+    private UnderlyingNetworkEvaluator mNetworkEvaluator;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        when(mDependencies.newIpSecPacketLossDetector(any(), any(), any(), any()))
+                .thenReturn(mIpSecPacketLossDetector);
+
+        when(mCarrierConfig.getIntArray(
+                        eq(VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY), anyObject()))
+                .thenReturn(new int[] {PENALTY_TIMEOUT_MIN});
+
+        mNetworkEvaluator = newValidUnderlyingNetworkEvaluator();
+    }
+
+    private UnderlyingNetworkEvaluator newUnderlyingNetworkEvaluator() {
+        return new UnderlyingNetworkEvaluator(
+                mVcnContext,
+                mNetwork,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig,
+                mEvaluatorCallback,
+                mDependencies);
+    }
+
+    private UnderlyingNetworkEvaluator newValidUnderlyingNetworkEvaluator() {
+        final UnderlyingNetworkEvaluator evaluator = newUnderlyingNetworkEvaluator();
+
+        evaluator.setNetworkCapabilities(
+                CELL_NETWORK_CAPABILITIES,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+        evaluator.setLinkProperties(
+                LINK_PROPERTIES,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+        evaluator.setIsBlocked(
+                false /* isBlocked */,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+
+        return evaluator;
+    }
+
+    @Test
+    public void testInitializedEvaluator() throws Exception {
+        final UnderlyingNetworkEvaluator evaluator = newUnderlyingNetworkEvaluator();
+
+        assertFalse(evaluator.isValid());
+        assertEquals(mNetwork, evaluator.getNetwork());
+        assertEquals(PRIORITY_INVALID, evaluator.getPriorityClass());
+
+        try {
+            evaluator.getNetworkRecord();
+            fail("Expected to fail because evaluator is not valid");
+        } catch (Exception expected) {
+        }
+    }
+
+    @Test
+    public void testValidEvaluator() {
+        final UnderlyingNetworkEvaluator evaluator = newUnderlyingNetworkEvaluator();
+        evaluator.setNetworkCapabilities(
+                CELL_NETWORK_CAPABILITIES,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+        evaluator.setLinkProperties(
+                LINK_PROPERTIES,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+        evaluator.setIsBlocked(
+                false /* isBlocked */,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+
+        final UnderlyingNetworkRecord expectedRecord =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        CELL_NETWORK_CAPABILITIES,
+                        LINK_PROPERTIES,
+                        false /* isBlocked */);
+
+        assertTrue(evaluator.isValid());
+        assertEquals(mNetwork, evaluator.getNetwork());
+        assertEquals(2, evaluator.getPriorityClass());
+        assertEquals(expectedRecord, evaluator.getNetworkRecord());
+    }
+
+    private void checkSetSelectedNetwork(boolean isSelected) {
+        mNetworkEvaluator.setIsSelected(
+                isSelected,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+        verify(mIpSecPacketLossDetector).setIsSelectedUnderlyingNetwork(isSelected);
+    }
+
+    @Test
+    public void testSetIsSelected_selected() throws Exception {
+        checkSetSelectedNetwork(true /* isSelectedExpected */);
+    }
+
+    @Test
+    public void testSetIsSelected_unselected() throws Exception {
+        checkSetSelectedNetwork(false /* isSelectedExpected */);
+    }
+
+    @Test
+    public void testSetIpSecTransform_onSelectedNetwork() throws Exception {
+        final IpSecTransform transform = makeDummyIpSecTransform();
+
+        // Make the network selected
+        mNetworkEvaluator.setIsSelected(
+                true,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+        mNetworkEvaluator.setInboundTransform(transform);
+
+        verify(mIpSecPacketLossDetector).setInboundTransform(transform);
+    }
+
+    @Test
+    public void testSetIpSecTransform_onUnSelectedNetwork() throws Exception {
+        mNetworkEvaluator.setIsSelected(
+                false,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+        mNetworkEvaluator.setInboundTransform(makeDummyIpSecTransform());
+
+        verify(mIpSecPacketLossDetector, never()).setInboundTransform(any());
+    }
+
+    @Test
+    public void close() throws Exception {
+        mNetworkEvaluator.close();
+
+        verify(mIpSecPacketLossDetector).close();
+        mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+        assertNull(mTestLooper.nextMessage());
+    }
+
+    private NetworkMetricMonitorCallback getMetricMonitorCbCaptor() throws Exception {
+        verify(mDependencies)
+                .newIpSecPacketLossDetector(any(), any(), any(), mMetricMonitorCbCaptor.capture());
+
+        return mMetricMonitorCbCaptor.getValue();
+    }
+
+    private void checkPenalizeNetwork() throws Exception {
+        assertFalse(mNetworkEvaluator.isPenalized());
+
+        // Validation failed
+        when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(true);
+        getMetricMonitorCbCaptor().onValidationResultReceived();
+
+        // Verify the evaluator is penalized
+        assertTrue(mNetworkEvaluator.isPenalized());
+        verify(mEvaluatorCallback).onEvaluationResultChanged();
+    }
+
+    @Test
+    public void testRcvValidationResult_penalizeNetwork_penaltyTimeout() throws Exception {
+        checkPenalizeNetwork();
+
+        // Penalty timeout
+        mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        // Verify the evaluator is not penalized
+        assertFalse(mNetworkEvaluator.isPenalized());
+        verify(mEvaluatorCallback, times(2)).onEvaluationResultChanged();
+    }
+
+    @Test
+    public void testRcvValidationResult_penalizeNetwork_passValidation() throws Exception {
+        checkPenalizeNetwork();
+
+        // Validation passed
+        when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(false);
+        getMetricMonitorCbCaptor().onValidationResultReceived();
+
+        // Verify the evaluator is not penalized and penalty timeout is canceled
+        assertFalse(mNetworkEvaluator.isPenalized());
+        verify(mEvaluatorCallback, times(2)).onEvaluationResultChanged();
+        mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+        assertNull(mTestLooper.nextMessage());
+    }
+
+    @Test
+    public void testRcvValidationResult_penalizeNetwork_closeEvaluator() throws Exception {
+        checkPenalizeNetwork();
+
+        mNetworkEvaluator.close();
+
+        // Verify penalty timeout is canceled
+        mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+        assertNull(mTestLooper.nextMessage());
+    }
+
+    @Test
+    public void testRcvValidationResult_PenaltyStateUnchanged() throws Exception {
+        assertFalse(mNetworkEvaluator.isPenalized());
+
+        // Validation passed
+        when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(false);
+        getMetricMonitorCbCaptor().onValidationResultReceived();
+
+        // Verifications
+        assertFalse(mNetworkEvaluator.isPenalized());
+        verify(mEvaluatorCallback, never()).onEvaluationResultChanged();
+    }
+
+    @Test
+    public void testSetCarrierConfig() throws Exception {
+        final int additionalTimeoutMin = 10;
+        when(mCarrierConfig.getIntArray(
+                        eq(VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY), anyObject()))
+                .thenReturn(new int[] {PENALTY_TIMEOUT_MIN + additionalTimeoutMin});
+
+        // Update evaluator and penalize the network
+        mNetworkEvaluator.reevaluate(
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+        checkPenalizeNetwork();
+
+        // Verify penalty timeout is changed
+        mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+        assertNull(mTestLooper.nextMessage());
+        mTestLooper.moveTimeForward(TimeUnit.MINUTES.toMillis(additionalTimeoutMin));
+        assertNotNull(mTestLooper.nextMessage());
+
+        // Verify NetworkMetricMonitor is notified
+        verify(mIpSecPacketLossDetector).setCarrierConfig(any());
+    }
+
+    @Test
+    public void testCompare() throws Exception {
+        when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(true);
+        getMetricMonitorCbCaptor().onValidationResultReceived();
+
+        final UnderlyingNetworkEvaluator penalized = mNetworkEvaluator;
+        final UnderlyingNetworkEvaluator notPenalized = newValidUnderlyingNetworkEvaluator();
+
+        assertEquals(penalized.getPriorityClass(), notPenalized.getPriorityClass());
+
+        final int result =
+                UnderlyingNetworkEvaluator.getComparator(mVcnContext)
+                        .compare(penalized, notPenalized);
+        assertEquals(1, result);
+    }
+}
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 45dd02c..642a561 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -56,6 +56,7 @@
 #include "java/JavaClassGenerator.h"
 #include "java/ManifestClassGenerator.h"
 #include "java/ProguardRules.h"
+#include "link/FeatureFlagsFilter.h"
 #include "link/Linkers.h"
 #include "link/ManifestFixer.h"
 #include "link/NoDefaultResourceRemover.h"
@@ -1987,6 +1988,21 @@
     context_->SetNameManglerPolicy(NameManglerPolicy{context_->GetCompilationPackage()});
     context_->SetSplitNameDependencies(app_info_.split_name_dependencies);
 
+    std::unique_ptr<xml::XmlResource> pre_flags_filter_manifest_xml = manifest_xml->Clone();
+
+    FeatureFlagsFilterOptions flags_filter_options;
+    if (context_->GetMinSdkVersion() > SDK_UPSIDE_DOWN_CAKE) {
+      // For API version > U, PackageManager will dynamically read the flag values and disable
+      // manifest elements accordingly when parsing the manifest.
+      // For API version <= U, we remove disabled elements from the manifest with the filter.
+      flags_filter_options.remove_disabled_elements = false;
+      flags_filter_options.flags_must_have_value = false;
+    }
+    FeatureFlagsFilter flags_filter(options_.feature_flag_values, flags_filter_options);
+    if (!flags_filter.Consume(context_, manifest_xml.get())) {
+      return 1;
+    }
+
     // Override the package ID when it is "android".
     if (context_->GetCompilationPackage() == "android") {
       context_->SetPackageId(kAndroidPackageId);
@@ -2283,7 +2299,12 @@
         }
 
         if (options_.generate_java_class_path) {
-          if (!WriteManifestJavaFile(manifest_xml.get())) {
+          // The FeatureFlagsFilter may remove <permission> and <permission-group> elements that
+          // generate constants in the Manifest Java file. While we want those permissions and
+          // permission groups removed in the SDK (i.e., if a feature flag is disabled), the
+          // constants should still remain so that code referencing it (e.g., within a feature
+          // flag check) will still compile. Therefore we use the manifest XML before the filter.
+          if (!WriteManifestJavaFile(pre_flags_filter_manifest_xml.get())) {
             error = true;
           }
         }
@@ -2531,7 +2552,7 @@
   }
 
   for (const std::string& arg : all_feature_flags_args) {
-    if (ParseFeatureFlagsParameter(arg, context.GetDiagnostics(), &options_.feature_flag_values)) {
+    if (!ParseFeatureFlagsParameter(arg, context.GetDiagnostics(), &options_.feature_flag_values)) {
       return 1;
     }
   }
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 26713fd..dc18b1c 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -330,7 +330,11 @@
             "should only be used together with the --static-lib flag.",
         &options_.merge_only);
     AddOptionalSwitch("-v", "Enables verbose logging.", &verbose_);
-    AddOptionalFlagList("--feature-flags", "Placeholder, to be implemented.", &feature_flags_args_);
+    AddOptionalFlagList("--feature-flags",
+                        "Specify the values of feature flags. The pairs in the argument\n"
+                        "are separated by ',' and the name is separated from the value by '='.\n"
+                        "Example: \"flag1=true,flag2=false,flag3=\" (flag3 has no given value).",
+                        &feature_flags_args_);
   }
 
   int Action(const std::vector<std::string>& args) override;
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 7096f5c..6cc42f1 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -16,11 +16,10 @@
 
 #include "Link.h"
 
-#include <android-base/file.h>
-
-#include "AppInfo.h"
 #include "Diagnostics.h"
 #include "LoadedApk.h"
+#include "android-base/file.h"
+#include "android-base/stringprintf.h"
 #include "test/Test.h"
 
 using testing::Eq;
@@ -993,4 +992,221 @@
   ASSERT_FALSE(Link(link_args, &diag));
 }
 
+static void BuildSDKWithFeatureFlagAttr(const std::string& apk_path, const std::string& java_path,
+                                        CommandTestFixture* fixture, android::IDiagnostics* diag) {
+  const std::string android_values =
+      R"(<resources>
+          <staging-public-group type="attr" first-id="0x01fe0063">
+            <public name="featureFlag" />
+          </staging-public-group>
+          <attr name="featureFlag" format="string" />
+         </resources>)";
+
+  SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values};
+  BuildSDK({source_xml}, apk_path, java_path, fixture, diag);
+}
+
+TEST_F(LinkTest, FeatureFlagDisabled_SdkAtMostUDC) {
+  StdErrDiagnostics diag;
+  const std::string android_apk = GetTestPath("android.apk");
+  const std::string android_java = GetTestPath("android-java");
+  BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+  const std::string manifest_contents = android::base::StringPrintf(
+      R"(<uses-sdk android:minSdkVersion="%d" />"
+          <permission android:name="FOO" android:featureFlag="flag" />)",
+      SDK_UPSIDE_DOWN_CAKE);
+  auto app_manifest = ManifestBuilder(this)
+                          .SetPackageName("com.example.app")
+                          .AddContents(manifest_contents)
+                          .Build();
+
+  const std::string app_java = GetTestPath("app-java");
+  auto app_link_args = LinkCommandBuilder(this)
+                           .SetManifestFile(app_manifest)
+                           .AddParameter("-I", android_apk)
+                           .AddParameter("--java", app_java)
+                           .AddParameter("--feature-flags", "flag=false");
+
+  const std::string app_apk = GetTestPath("app.apk");
+  BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+  // Permission element should be removed if flag is disabled
+  auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+  ASSERT_THAT(apk, NotNull());
+  auto apk_manifest = apk->GetManifest();
+  ASSERT_THAT(apk_manifest, NotNull());
+  auto root = apk_manifest->root.get();
+  ASSERT_THAT(root, NotNull());
+  auto maybe_removed = root->FindChild({}, "permission");
+  ASSERT_THAT(maybe_removed, IsNull());
+
+  // Code for the permission should be generated even if the element is removed
+  const std::string manifest_java = app_java + "/com/example/app/Manifest.java";
+  std::string manifest_java_contents;
+  ASSERT_TRUE(android::base::ReadFileToString(manifest_java, &manifest_java_contents));
+  EXPECT_THAT(manifest_java_contents, HasSubstr(" public static final String FOO=\"FOO\";"));
+}
+
+TEST_F(LinkTest, FeatureFlagEnabled_SdkAtMostUDC) {
+  StdErrDiagnostics diag;
+  const std::string android_apk = GetTestPath("android.apk");
+  const std::string android_java = GetTestPath("android-java");
+  BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+  const std::string manifest_contents = android::base::StringPrintf(
+      R"(<uses-sdk android:minSdkVersion="%d" />"
+          <permission android:name="FOO" android:featureFlag="flag" />)",
+      SDK_UPSIDE_DOWN_CAKE);
+  auto app_manifest = ManifestBuilder(this)
+                          .SetPackageName("com.example.app")
+                          .AddContents(manifest_contents)
+                          .Build();
+
+  auto app_link_args = LinkCommandBuilder(this)
+                           .SetManifestFile(app_manifest)
+                           .AddParameter("-I", android_apk)
+                           .AddParameter("--feature-flags", "flag=true");
+
+  const std::string app_apk = GetTestPath("app.apk");
+  BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+  // Permission element should be kept if flag is enabled
+  auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+  ASSERT_THAT(apk, NotNull());
+  auto apk_manifest = apk->GetManifest();
+  ASSERT_THAT(apk_manifest, NotNull());
+  auto root = apk_manifest->root.get();
+  ASSERT_THAT(root, NotNull());
+  auto maybe_removed = root->FindChild({}, "permission");
+  ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST_F(LinkTest, FeatureFlagWithNoValue_SdkAtMostUDC) {
+  StdErrDiagnostics diag;
+  const std::string android_apk = GetTestPath("android.apk");
+  const std::string android_java = GetTestPath("android-java");
+  BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+  const std::string manifest_contents = android::base::StringPrintf(
+      R"(<uses-sdk android:minSdkVersion="%d" />"
+          <permission android:name="FOO" android:featureFlag="flag" />)",
+      SDK_UPSIDE_DOWN_CAKE);
+  auto app_manifest = ManifestBuilder(this)
+                          .SetPackageName("com.example.app")
+                          .AddContents(manifest_contents)
+                          .Build();
+
+  auto app_link_args = LinkCommandBuilder(this)
+                           .SetManifestFile(app_manifest)
+                           .AddParameter("-I", android_apk)
+                           .AddParameter("--feature-flags", "flag=");
+
+  // Flags must have values if <= UDC
+  const std::string app_apk = GetTestPath("app.apk");
+  ASSERT_FALSE(Link(app_link_args.Build(app_apk), &diag));
+}
+
+TEST_F(LinkTest, FeatureFlagDisabled_SdkAfterUDC) {
+  StdErrDiagnostics diag;
+  const std::string android_apk = GetTestPath("android.apk");
+  const std::string android_java = GetTestPath("android-java");
+  BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+  const std::string manifest_contents = android::base::StringPrintf(
+      R"(<uses-sdk android:minSdkVersion="%d" />"
+          <permission android:name="FOO" android:featureFlag="flag" />)",
+      SDK_CUR_DEVELOPMENT);
+  auto app_manifest = ManifestBuilder(this)
+                          .SetPackageName("com.example.app")
+                          .AddContents(manifest_contents)
+                          .Build();
+
+  auto app_link_args = LinkCommandBuilder(this)
+                           .SetManifestFile(app_manifest)
+                           .AddParameter("-I", android_apk)
+                           .AddParameter("--feature-flags", "flag=false");
+
+  const std::string app_apk = GetTestPath("app.apk");
+  BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+  // Permission element should be kept if > UDC, regardless of flag value
+  auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+  ASSERT_THAT(apk, NotNull());
+  auto apk_manifest = apk->GetManifest();
+  ASSERT_THAT(apk_manifest, NotNull());
+  auto root = apk_manifest->root.get();
+  ASSERT_THAT(root, NotNull());
+  auto maybe_removed = root->FindChild({}, "permission");
+  ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST_F(LinkTest, FeatureFlagEnabled_SdkAfterUDC) {
+  StdErrDiagnostics diag;
+  const std::string android_apk = GetTestPath("android.apk");
+  const std::string android_java = GetTestPath("android-java");
+  BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+  const std::string manifest_contents = android::base::StringPrintf(
+      R"(<uses-sdk android:minSdkVersion="%d" />"
+          <permission android:name="FOO" android:featureFlag="flag" />)",
+      SDK_CUR_DEVELOPMENT);
+  auto app_manifest = ManifestBuilder(this)
+                          .SetPackageName("com.example.app")
+                          .AddContents(manifest_contents)
+                          .Build();
+
+  auto app_link_args = LinkCommandBuilder(this)
+                           .SetManifestFile(app_manifest)
+                           .AddParameter("-I", android_apk)
+                           .AddParameter("--feature-flags", "flag=true");
+
+  const std::string app_apk = GetTestPath("app.apk");
+  BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+  // Permission element should be kept if > UDC, regardless of flag value
+  auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+  ASSERT_THAT(apk, NotNull());
+  auto apk_manifest = apk->GetManifest();
+  ASSERT_THAT(apk_manifest, NotNull());
+  auto root = apk_manifest->root.get();
+  ASSERT_THAT(root, NotNull());
+  auto maybe_removed = root->FindChild({}, "permission");
+  ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST_F(LinkTest, FeatureFlagWithNoValue_SdkAfterUDC) {
+  StdErrDiagnostics diag;
+  const std::string android_apk = GetTestPath("android.apk");
+  const std::string android_java = GetTestPath("android-java");
+  BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+  const std::string manifest_contents = android::base::StringPrintf(
+      R"(<uses-sdk android:minSdkVersion="%d" />"
+          <permission android:name="FOO" android:featureFlag="flag" />)",
+      SDK_CUR_DEVELOPMENT);
+  auto app_manifest = ManifestBuilder(this)
+                          .SetPackageName("com.example.app")
+                          .AddContents(manifest_contents)
+                          .Build();
+
+  auto app_link_args = LinkCommandBuilder(this)
+                           .SetManifestFile(app_manifest)
+                           .AddParameter("-I", android_apk)
+                           .AddParameter("--feature-flags", "flag=");
+
+  const std::string app_apk = GetTestPath("app.apk");
+  BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+  // Permission element should be kept if > UDC, regardless of flag value
+  auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+  ASSERT_THAT(apk, NotNull());
+  auto apk_manifest = apk->GetManifest();
+  ASSERT_THAT(apk_manifest, NotNull());
+  auto root = apk_manifest->root.get();
+  ASSERT_THAT(root, NotNull());
+  auto maybe_removed = root->FindChild({}, "permission");
+  ASSERT_THAT(maybe_removed, NotNull());
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index f056110..1a82021 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -381,9 +381,9 @@
     return true;
   }
 
-  bool FlattenTypeSpec(const ResourceTableTypeView& type,
-                       const std::vector<ResourceTableEntryView>& sorted_entries,
-                       BigBuffer* buffer) {
+  ResTable_typeSpec* FlattenTypeSpec(const ResourceTableTypeView& type,
+                                     const std::vector<ResourceTableEntryView>& sorted_entries,
+                                     BigBuffer* buffer) {
     ChunkWriter type_spec_writer(buffer);
     ResTable_typeSpec* spec_header =
         type_spec_writer.StartChunk<ResTable_typeSpec>(RES_TABLE_TYPE_SPEC_TYPE);
@@ -391,7 +391,7 @@
 
     if (sorted_entries.empty()) {
       type_spec_writer.Finish();
-      return true;
+      return spec_header;
     }
 
     // We can't just take the size of the vector. There may be holes in the
@@ -427,7 +427,7 @@
       }
     }
     type_spec_writer.Finish();
-    return true;
+    return spec_header;
   }
 
   bool FlattenTypes(BigBuffer* buffer) {
@@ -450,7 +450,8 @@
       expected_type_id++;
       type_pool_.MakeRef(type.named_type.to_string());
 
-      if (!FlattenTypeSpec(type, type.entries, buffer)) {
+      const auto type_spec_header = FlattenTypeSpec(type, type.entries, buffer);
+      if (!type_spec_header) {
         return false;
       }
 
@@ -511,6 +512,10 @@
           return false;
         }
       }
+
+      // And now we can update the type entries count in the typeSpec header.
+      type_spec_header->typesCount = android::util::HostToDevice16(uint16_t(std::min<uint32_t>(
+          config_to_entry_list_map.size(), std::numeric_limits<uint16_t>::max())));
     }
     return true;
   }
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk
deleted file mode 100644
index 6361f9b..0000000
--- a/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk
+++ /dev/null
@@ -1,2 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk
deleted file mode 100644
index 27b6068..0000000
--- a/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk
+++ /dev/null
@@ -1,32 +0,0 @@
-//
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_PACKAGE_NAME := AaptTestMergeOnly_App
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_EXPORT_PACKAGE_RESOURCES := true
-LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_ANDROID_LIBRARIES := \
-    AaptTestMergeOnly_LeafLib \
-    AaptTestMergeOnly_LocalLib
-include $(BUILD_PACKAGE)
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk
deleted file mode 100644
index c084849..0000000
--- a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-//
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_MODULE := AaptTestMergeOnly_LeafLib
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_MODULE_TAGS := tests
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_MIN_SDK_VERSION := 21
-LOCAL_AAPT_FLAGS := --merge-only
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk
deleted file mode 100644
index 699ad79..0000000
--- a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-//
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_MODULE := AaptTestMergeOnly_LocalLib
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_MODULE_TAGS := tests
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_MIN_SDK_VERSION := 21
-LOCAL_AAPT_FLAGS := --merge-only
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/integration-tests/NamespaceTest/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/Android.mk
deleted file mode 100644
index 6361f9b..0000000
--- a/tools/aapt2/integration-tests/NamespaceTest/Android.mk
+++ /dev/null
@@ -1,2 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk
deleted file mode 100644
index 98b7440..0000000
--- a/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_PACKAGE_NAME := AaptTestNamespace_App
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_EXPORT_PACKAGE_RESOURCES := true
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_STATIC_ANDROID_LIBRARIES := \
-    AaptTestNamespace_LibOne \
-    AaptTestNamespace_LibTwo
-include $(BUILD_PACKAGE)
diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk
deleted file mode 100644
index dd41702..0000000
--- a/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_MODULE := AaptTestNamespace_LibOne
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_MODULE_TAGS := tests
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_MIN_SDK_VERSION := 21
-
-# We need this to retain the R.java generated for this library.
-LOCAL_JAR_EXCLUDE_FILES := none
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk
deleted file mode 100644
index 0d11bcb..0000000
--- a/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk
+++ /dev/null
@@ -1,34 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_MODULE := AaptTestNamespace_LibTwo
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_MIN_SDK_VERSION := 21
-
-# We need this to retain the R.java generated for this library.
-LOCAL_JAR_EXCLUDE_FILES := none
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/integration-tests/NamespaceTest/Split/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/Split/Android.mk
deleted file mode 100644
index 30375728..0000000
--- a/tools/aapt2/integration-tests/NamespaceTest/Split/Android.mk
+++ /dev/null
@@ -1,32 +0,0 @@
-#
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_PACKAGE_NAME := AaptTestNamespace_Split
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_APK_LIBRARIES := AaptTestNamespace_App
-LOCAL_RES_LIBRARIES := AaptTestNamespace_App
-LOCAL_AAPT_FLAGS := --package-id 0x80 --rename-manifest-package com.android.aapt.namespace.app
-include $(BUILD_PACKAGE)
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
index 8c644cf..a7f6f55 100644
--- a/tools/aapt2/java/AnnotationProcessor.cpp
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -64,29 +64,31 @@
     {"@FlaggedApi", AnnotationRule::kFlaggedApi, "@android.annotation.FlaggedApi", true},
 }};
 
-void AnnotationProcessor::AppendCommentLine(std::string comment) {
+void AnnotationProcessor::AppendCommentLine(std::string comment, bool add_api_annotations) {
   static constexpr std::string_view sDeprecated = "@deprecated";
 
-  // Treat deprecated specially, since we don't remove it from the source comment.
-  if (comment.find(sDeprecated) != std::string::npos) {
-    annotation_parameter_map_[AnnotationRule::kDeprecated] = "";
-  }
+  if (add_api_annotations) {
+    // Treat deprecated specially, since we don't remove it from the source comment.
+    if (comment.find(sDeprecated) != std::string::npos) {
+      annotation_parameter_map_[AnnotationRule::kDeprecated] = "";
+    }
 
-  for (const AnnotationRule& rule : sAnnotationRules) {
-    std::string::size_type idx = comment.find(rule.doc_str.data());
-    if (idx != std::string::npos) {
-      // Captures all parameters associated with the specified annotation rule
-      // by matching the first pair of parentheses after the rule.
-      std::regex re(std::string(rule.doc_str).append(R"(\s*\((.+)\))"));
-      std::smatch match_result;
-      const bool is_match = std::regex_search(comment, match_result, re);
-      if (is_match && rule.preserve_params) {
-        annotation_parameter_map_[rule.bit_mask] = match_result[1].str();
-        comment.erase(comment.begin() + match_result.position(),
-                      comment.begin() + match_result.position() + match_result.length());
-      } else {
-        annotation_parameter_map_[rule.bit_mask] = "";
-        comment.erase(comment.begin() + idx, comment.begin() + idx + rule.doc_str.size());
+    for (const AnnotationRule& rule : sAnnotationRules) {
+      std::string::size_type idx = comment.find(rule.doc_str.data());
+      if (idx != std::string::npos) {
+        // Captures all parameters associated with the specified annotation rule
+        // by matching the first pair of parentheses after the rule.
+        std::regex re(std::string(rule.doc_str).append(R"(\s*\((.+)\))"));
+        std::smatch match_result;
+        const bool is_match = std::regex_search(comment, match_result, re);
+        if (is_match && rule.preserve_params) {
+          annotation_parameter_map_[rule.bit_mask] = match_result[1].str();
+          comment.erase(comment.begin() + match_result.position(),
+                        comment.begin() + match_result.position() + match_result.length());
+        } else {
+          annotation_parameter_map_[rule.bit_mask] = "";
+          comment.erase(comment.begin() + idx, comment.begin() + idx + rule.doc_str.size());
+        }
       }
     }
   }
@@ -109,12 +111,12 @@
   comment_ << "\n * " << std::move(comment);
 }
 
-void AnnotationProcessor::AppendComment(StringPiece comment) {
+void AnnotationProcessor::AppendComment(StringPiece comment, bool add_api_annotations) {
   // We need to process line by line to clean-up whitespace and append prefixes.
   for (StringPiece line : util::Tokenize(comment, '\n')) {
     line = util::TrimWhitespace(line);
     if (!line.empty()) {
-      AppendCommentLine(std::string(line));
+      AppendCommentLine(std::string(line), add_api_annotations);
     }
   }
 }
diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h
index db3437e..2217ab3 100644
--- a/tools/aapt2/java/AnnotationProcessor.h
+++ b/tools/aapt2/java/AnnotationProcessor.h
@@ -60,7 +60,10 @@
 
   // Adds more comments. Resources can have value definitions for various configurations, and
   // each of the definitions may have comments that need to be processed.
-  void AppendComment(android::StringPiece comment);
+  //
+  // If add_api_annotations is false, annotations found in the comment (e.g., "@SystemApi")
+  // will NOT be converted to Java annotations.
+  void AppendComment(android::StringPiece comment, bool add_api_annotations = true);
 
   void AppendNewLine();
 
@@ -73,7 +76,7 @@
   bool has_comments_ = false;
   std::unordered_map<uint32_t, std::string> annotation_parameter_map_;
 
-  void AppendCommentLine(std::string line);
+  void AppendCommentLine(std::string line, bool add_api_annotations);
 };
 
 }  // namespace aapt
diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp
index e98e96b..e5eee34 100644
--- a/tools/aapt2/java/AnnotationProcessor_test.cpp
+++ b/tools/aapt2/java/AnnotationProcessor_test.cpp
@@ -136,7 +136,28 @@
   EXPECT_THAT(annotations, HasSubstr("This is a system API"));
 }
 
-TEST(AnnotationProcessor, ExtractsFirstSentence) {
+TEST(AnnotationProcessorTest, DoNotAddApiAnnotations) {
+  AnnotationProcessor processor;
+  processor.AppendComment(
+      "@SystemApi This is a system API\n"
+      "@FlaggedApi This is a flagged API\n"
+      "@TestApi This is a test API\n"
+      "@deprecated Deprecate me\n", /*add_api_annotations=*/
+      false);
+
+  std::string annotations;
+  StringOutputStream out(&annotations);
+  Printer printer(&out);
+  processor.Print(&printer);
+  out.Flush();
+
+  EXPECT_THAT(annotations, Not(HasSubstr("@android.annotation.SystemApi")));
+  EXPECT_THAT(annotations, Not(HasSubstr("@android.annotation.FlaggedApi")));
+  EXPECT_THAT(annotations, Not(HasSubstr("@android.annotation.TestApi")));
+  EXPECT_THAT(annotations, Not(HasSubstr("@Deprecated")));
+}
+
+TEST(AnnotationProcessorTest, ExtractsFirstSentence) {
   EXPECT_THAT(AnnotationProcessor::ExtractFirstSentence("This is the only sentence"),
               Eq("This is the only sentence"));
   EXPECT_THAT(AnnotationProcessor::ExtractFirstSentence(
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index 58f6564..6e73b01 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -180,7 +180,10 @@
            << "<td>" << std::hex << symbol.value << std::dec << "</td>"
            << "<td>" << util::TrimWhitespace(symbol.symbol.GetComment())
            << "</td></tr>";
-      processor->AppendComment(line.str());
+      // add_api_annotations is false since we don't want any annotations
+      // (e.g., "@deprecated")/ found in the enum/flag values to be propagated
+      // up to the attribute.
+      processor->AppendComment(line.str(), /*add_api_annotations=*/false);
     }
     processor->AppendComment("</table>");
   }
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index 40395ed..bca9f4b 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -324,7 +324,58 @@
   EXPECT_THAT(output, HasSubstr(expected_text));
 }
 
-TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {}
+TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {
+  std::unique_ptr<Attribute> flagAttr =
+      test::AttributeBuilder()
+          .SetTypeMask(android::ResTable_map::TYPE_FLAGS)
+          .SetComment("Flag attribute")
+          .AddItemWithComment("flagOne", 0x01, "Flag comment 1")
+          .AddItemWithComment("flagTwo", 0x02, "@deprecated Flag comment 2")
+          .Build();
+  std::unique_ptr<Attribute> enumAttr =
+      test::AttributeBuilder()
+          .SetTypeMask(android::ResTable_map::TYPE_ENUM)
+          .SetComment("Enum attribute")
+          .AddItemWithComment("enumOne", 0x01, "@TestApi Enum comment 1")
+          .AddItemWithComment("enumTwo", 0x02, "Enum comment 2")
+          .Build();
+
+  std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+                                             .AddValue("android:attr/one", std::move(flagAttr))
+                                             .AddValue("android:attr/two", std::move(enumAttr))
+                                             .Build();
+
+  std::unique_ptr<IAaptContext> context =
+      test::ContextBuilder()
+          .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+          .SetNameManglerPolicy(NameManglerPolicy{"android"})
+          .Build();
+  JavaClassGeneratorOptions options;
+  options.use_final = false;
+  JavaClassGenerator generator(context.get(), table.get(), options);
+
+  std::string output;
+  StringOutputStream out(&output);
+  ASSERT_TRUE(generator.Generate("android", &out));
+  out.Flush();
+
+  // Special annotations from the enum/flag values should NOT generate
+  // annotations for the attribute value.
+  EXPECT_THAT(output, Not(HasSubstr("@Deprecated")));
+  EXPECT_THAT(output, Not(HasSubstr("@android.annotation.TestApi")));
+
+  EXPECT_THAT(output, HasSubstr("Flag attribute"));
+  EXPECT_THAT(output, HasSubstr("flagOne"));
+  EXPECT_THAT(output, HasSubstr("Flag comment 1"));
+  EXPECT_THAT(output, HasSubstr("flagTwo"));
+  EXPECT_THAT(output, HasSubstr("@deprecated Flag comment 2"));
+
+  EXPECT_THAT(output, HasSubstr("Enum attribute"));
+  EXPECT_THAT(output, HasSubstr("enumOne"));
+  EXPECT_THAT(output, HasSubstr("@TestApi Enum comment 1"));
+  EXPECT_THAT(output, HasSubstr("enumTwo"));
+  EXPECT_THAT(output, HasSubstr("Enum comment 2"));
+}
 
 TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) {
   Attribute attr;
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 0b16e2c..d03f97e 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -415,6 +415,8 @@
   intent_filter_action["action"].Action(RequiredNameIsNotEmpty);
   intent_filter_action["category"].Action(RequiredNameIsNotEmpty);
   intent_filter_action["data"];
+  intent_filter_action["uri-relative-filter-group"];
+  intent_filter_action["uri-relative-filter-group"]["data"];
 
   // Common <meta-data> actions.
   xml::XmlNodeAction meta_data_action;
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index 65f63dc..b5934e4 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -177,12 +177,25 @@
   return *this;
 }
 
+AttributeBuilder& AttributeBuilder::SetComment(StringPiece comment) {
+  attr_->SetComment(comment);
+  return *this;
+}
+
 AttributeBuilder& AttributeBuilder::AddItem(StringPiece name, uint32_t value) {
   attr_->symbols.push_back(
       Attribute::Symbol{Reference(ResourceName({}, ResourceType::kId, name)), value});
   return *this;
 }
 
+AttributeBuilder& AttributeBuilder::AddItemWithComment(StringPiece name, uint32_t value,
+                                                       StringPiece comment) {
+  Reference ref(ResourceName({}, ResourceType::kId, name));
+  ref.SetComment(comment);
+  attr_->symbols.push_back(Attribute::Symbol{ref, value});
+  return *this;
+}
+
 std::unique_ptr<Attribute> AttributeBuilder::Build() {
   return std::move(attr_);
 }
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 098535d..9ee44ba 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -116,7 +116,10 @@
   AttributeBuilder();
   AttributeBuilder& SetTypeMask(uint32_t typeMask);
   AttributeBuilder& SetWeak(bool weak);
+  AttributeBuilder& SetComment(android::StringPiece comment);
   AttributeBuilder& AddItem(android::StringPiece name, uint32_t value);
+  AttributeBuilder& AddItemWithComment(android::StringPiece name, uint32_t value,
+                                       android::StringPiece comment);
   std::unique_ptr<Attribute> Build();
 
  private:
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index 93c1b61..02e4beae 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -251,10 +251,13 @@
     return false;
   }
 
-  for (StringPiece line : util::Tokenize(contents, ' ')) {
+  for (StringPiece line : util::Tokenize(contents, '\n')) {
     line = util::TrimWhitespace(line);
-    if (!line.empty()) {
-      out_arglist->emplace_back(line);
+    for (StringPiece arg : util::Tokenize(line, ' ')) {
+      arg = util::TrimWhitespace(arg);
+      if (!arg.empty()) {
+        out_arglist->emplace_back(arg);
+      }
     }
   }
   return true;
@@ -270,10 +273,13 @@
     return false;
   }
 
-  for (StringPiece line : util::Tokenize(contents, ' ')) {
+  for (StringPiece line : util::Tokenize(contents, '\n')) {
     line = util::TrimWhitespace(line);
-    if (!line.empty()) {
-      out_argset->emplace(line);
+    for (StringPiece arg : util::Tokenize(line, ' ')) {
+      arg = util::TrimWhitespace(arg);
+      if (!arg.empty()) {
+        out_argset->emplace(arg);
+      }
     }
   }
   return true;
diff --git a/tools/aapt2/util/Files_test.cpp b/tools/aapt2/util/Files_test.cpp
index 6c38080..618a3e0 100644
--- a/tools/aapt2/util/Files_test.cpp
+++ b/tools/aapt2/util/Files_test.cpp
@@ -25,6 +25,9 @@
 
 using ::android::base::StringPrintf;
 
+using ::testing::ElementsAre;
+using ::testing::UnorderedElementsAre;
+
 namespace aapt {
 namespace file {
 
@@ -34,9 +37,11 @@
 constexpr const char sTestDirSep = '/';
 #endif
 
-class FilesTest : public ::testing::Test {
+class FilesTest : public TestDirectoryFixture {
  public:
   void SetUp() override {
+    TestDirectoryFixture::SetUp();
+
     std::stringstream builder;
     builder << "hello" << sDirSep << "there";
     expected_path_ = builder.str();
@@ -66,6 +71,42 @@
   EXPECT_EQ(expected_path_, base);
 }
 
+TEST_F(FilesTest, AppendArgsFromFile) {
+  const std::string args_file = GetTestPath("args.txt");
+  WriteFile(args_file,
+            "  \n"
+            "arg1 arg2   arg3  \n"
+            "   arg4 arg5");
+  std::vector<std::string> args;
+  std::string error;
+  ASSERT_TRUE(AppendArgsFromFile(args_file, &args, &error));
+  EXPECT_THAT(args, ElementsAre("arg1", "arg2", "arg3", "arg4", "arg5"));
+}
+
+TEST_F(FilesTest, AppendArgsFromFile_InvalidFile) {
+  std::vector<std::string> args;
+  std::string error;
+  ASSERT_FALSE(AppendArgsFromFile(GetTestPath("not_found.txt"), &args, &error));
+}
+
+TEST_F(FilesTest, AppendSetArgsFromFile) {
+  const std::string args_file = GetTestPath("args.txt");
+  WriteFile(args_file,
+            "  \n"
+            "arg2 arg4   arg1  \n"
+            "   arg5 arg3");
+  std::unordered_set<std::string> args;
+  std::string error;
+  ASSERT_TRUE(AppendSetArgsFromFile(args_file, &args, &error));
+  EXPECT_THAT(args, UnorderedElementsAre("arg1", "arg2", "arg3", "arg4", "arg5"));
+}
+
+TEST_F(FilesTest, AppendSetArgsFromFile_InvalidFile) {
+  std::unordered_set<std::string> args;
+  std::string error;
+  ASSERT_FALSE(AppendSetArgsFromFile(GetTestPath("not_found.txt"), &args, &error));
+}
+
 #ifdef _WIN32
 TEST_F(FilesTest, WindowsMkdirsLongPath) {
   // Creating directory paths longer than the Windows maximum path length (260 charatcers) should
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/MessageQueue_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/MessageQueue_host.java
index 2e47d48..65da4a1 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/MessageQueue_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/MessageQueue_host.java
@@ -28,6 +28,7 @@
 
     private final Object mPoller = new Object();
     private volatile boolean mPolling;
+    private volatile boolean mPendingWake;
 
     private void validate() {
         if (mDeleted) {
@@ -62,7 +63,9 @@
         synchronized (q.mPoller) {
             q.mPolling = true;
             try {
-                if (timeoutMillis == 0) {
+                if (q.mPendingWake) {
+                    // Calling with pending wake returns immediately
+                } else if (timeoutMillis == 0) {
                     // Calling epoll_wait() with 0 returns immediately
                 } else if (timeoutMillis == -1) {
                     q.mPoller.wait();
@@ -72,6 +75,8 @@
             } catch (InterruptedException e) {
                 Thread.currentThread().interrupt();
             }
+            // Any reason for returning counts as a "wake", so clear pending
+            q.mPendingWake = false;
             q.mPolling = false;
         }
     }
@@ -79,6 +84,7 @@
     public static void nativeWake(long ptr) {
         var q = getInstance(ptr);
         synchronized (q.mPoller) {
+            q.mPendingWake = true;
             q.mPoller.notifyAll();
         }
     }
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
index 1ec1d5f..2f6a361 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
@@ -15,42 +15,181 @@
  */
 package com.android.hoststubgen.nativesubstitution;
 
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Predicate;
+
 public class SystemProperties_host {
+    private static final Object sLock = new Object();
+
+    /** Active system property values */
+    @GuardedBy("sLock")
+    private static Map<String, String> sValues;
+    /** Predicate tested to determine if a given key can be read. */
+    @GuardedBy("sLock")
+    private static Predicate<String> sKeyReadablePredicate;
+    /** Predicate tested to determine if a given key can be written. */
+    @GuardedBy("sLock")
+    private static Predicate<String> sKeyWritablePredicate;
+    /** Callback to trigger when values are changed */
+    @GuardedBy("sLock")
+    private static Runnable sChangeCallback;
+
+    /**
+     * Reverse mapping that provides a way back to an original key from the
+     * {@link System#identityHashCode(Object)} of {@link String#intern}.
+     */
+    @GuardedBy("sLock")
+    private static SparseArray<String> sKeyHandles = new SparseArray<>();
+
+    public static void native_init$ravenwood(Map<String, String> values,
+            Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate,
+            Runnable changeCallback) {
+        synchronized (sLock) {
+            sValues = Objects.requireNonNull(values);
+            sKeyReadablePredicate = Objects.requireNonNull(keyReadablePredicate);
+            sKeyWritablePredicate = Objects.requireNonNull(keyWritablePredicate);
+            sChangeCallback = Objects.requireNonNull(changeCallback);
+            sKeyHandles.clear();
+        }
+    }
+
+    public static void native_reset$ravenwood() {
+        synchronized (sLock) {
+            sValues = null;
+            sKeyReadablePredicate = null;
+            sKeyWritablePredicate = null;
+            sChangeCallback = null;
+            sKeyHandles.clear();
+        }
+    }
+
+    public static void native_set(String key, String val) {
+        synchronized (sLock) {
+            Objects.requireNonNull(key);
+            Preconditions.requireNonNullViaRavenwoodRule(sValues);
+            if (!sKeyWritablePredicate.test(key)) {
+                throw new IllegalArgumentException(
+                        "Write access to system property '" + key + "' denied via RavenwoodRule");
+            }
+            if (key.startsWith("ro.") && sValues.containsKey(key)) {
+                throw new IllegalArgumentException(
+                        "System property '" + key + "' already defined once; cannot redefine");
+            }
+            if ((val == null) || val.isEmpty()) {
+                sValues.remove(key);
+            } else {
+                sValues.put(key, val);
+            }
+            sChangeCallback.run();
+        }
+    }
+
     public static String native_get(String key, String def) {
-        throw new RuntimeException("Not implemented yet");
+        synchronized (sLock) {
+            Objects.requireNonNull(key);
+            Preconditions.requireNonNullViaRavenwoodRule(sValues);
+            if (!sKeyReadablePredicate.test(key)) {
+                throw new IllegalArgumentException(
+                        "Read access to system property '" + key + "' denied via RavenwoodRule");
+            }
+            return sValues.getOrDefault(key, def);
+        }
     }
+
     public static int native_get_int(String key, int def) {
-        throw new RuntimeException("Not implemented yet");
+        try {
+            return Integer.parseInt(native_get(key, ""));
+        } catch (NumberFormatException ignored) {
+            return def;
+        }
     }
+
     public static long native_get_long(String key, long def) {
-        throw new RuntimeException("Not implemented yet");
+        try {
+            return Long.parseLong(native_get(key, ""));
+        } catch (NumberFormatException ignored) {
+            return def;
+        }
     }
+
     public static boolean native_get_boolean(String key, boolean def) {
-        throw new RuntimeException("Not implemented yet");
+        return parseBoolean(native_get(key, ""), def);
     }
 
     public static long native_find(String name) {
-        throw new RuntimeException("Not implemented yet");
+        synchronized (sLock) {
+            Preconditions.requireNonNullViaRavenwoodRule(sValues);
+            if (sValues.containsKey(name)) {
+                name = name.intern();
+                final int handle = System.identityHashCode(name);
+                sKeyHandles.put(handle, name);
+                return handle;
+            } else {
+                return 0;
+            }
+        }
     }
+
     public static String native_get(long handle) {
-        throw new RuntimeException("Not implemented yet");
+        synchronized (sLock) {
+            return native_get(sKeyHandles.get((int) handle), "");
+        }
     }
+
     public static int native_get_int(long handle, int def) {
-        throw new RuntimeException("Not implemented yet");
+        synchronized (sLock) {
+            return native_get_int(sKeyHandles.get((int) handle), def);
+        }
     }
+
     public static long native_get_long(long handle, long def) {
-        throw new RuntimeException("Not implemented yet");
+        synchronized (sLock) {
+            return native_get_long(sKeyHandles.get((int) handle), def);
+        }
     }
+
     public static boolean native_get_boolean(long handle, boolean def) {
-        throw new RuntimeException("Not implemented yet");
+        synchronized (sLock) {
+            return native_get_boolean(sKeyHandles.get((int) handle), def);
+        }
     }
-    public static void native_set(String key, String def) {
-        throw new RuntimeException("Not implemented yet");
-    }
+
     public static void native_add_change_callback() {
-        throw new RuntimeException("Not implemented yet");
+        // Ignored; callback always registered via init above
     }
+
     public static void native_report_sysprop_change() {
-        throw new RuntimeException("Not implemented yet");
+        // Report through callback always registered via init above
+        synchronized (sLock) {
+            Preconditions.requireNonNullViaRavenwoodRule(sValues);
+            sChangeCallback.run();
+        }
+    }
+
+    private static boolean parseBoolean(String val, boolean def) {
+        // Matches system/libbase/include/android-base/parsebool.h
+        if (val == null) return def;
+        switch (val) {
+            case "1":
+            case "on":
+            case "true":
+            case "y":
+            case "yes":
+                return true;
+            case "0":
+            case "false":
+            case "n":
+            case "no":
+            case "off":
+                return false;
+            default:
+                return def;
+        }
     }
 }
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java
new file mode 100644
index 0000000..379c4ae
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.okhttp.internalandroidapi;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.List;
+
+/**
+ * A domain name service that resolves IP addresses for host names.
+ * @hide
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface Dns {
+    /**
+     * Returns the IP addresses of {@code hostname}, in the order they should
+     * be attempted.
+     *
+     * @hide
+     */
+    List<InetAddress> lookup(String hostname) throws UnknownHostException;
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
index 8ca4732..76bac92 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
@@ -24,6 +24,7 @@
         private val classes: ClassNodes,
         val aidlPolicy: FilterPolicyWithReason?,
         val featureFlagsPolicy: FilterPolicyWithReason?,
+        val syspropsPolicy: FilterPolicyWithReason?,
         fallback: OutputFilter
 ) : DelegatingFilter(fallback) {
     override fun getPolicyForClass(className: String): FilterPolicyWithReason {
@@ -33,6 +34,9 @@
         if (featureFlagsPolicy != null && classes.isFeatureFlagsClass(className)) {
             return featureFlagsPolicy
         }
+        if (syspropsPolicy != null && classes.isSyspropsClass(className)) {
+            return syspropsPolicy
+        }
         return super.getPolicyForClass(className)
     }
 }
@@ -57,3 +61,13 @@
             || className.endsWith("/FeatureFlagsImpl")
             || className.endsWith("/FakeFeatureFlagsImpl");
 }
+
+/**
+ * @return if a given class "seems like" a sysprops class.
+ */
+private fun ClassNodes.isSyspropsClass(className: String): Boolean {
+    // Matches template classes defined here:
+    // https://cs.android.com/android/platform/superproject/main/+/main:system/tools/sysprop/
+    return className.startsWith("android/sysprop/")
+            && className.endsWith("Properties")
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index d38a6e3..7fdd944 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -64,6 +64,7 @@
 
         var aidlPolicy: FilterPolicyWithReason? = null
         var featureFlagsPolicy: FilterPolicyWithReason? = null
+        var syspropsPolicy: FilterPolicyWithReason? = null
 
         try {
             BufferedReader(FileReader(filename)).use { reader ->
@@ -141,6 +142,14 @@
                                         featureFlagsPolicy =
                                                 policy.withReason("$FILTER_REASON (feature flags)")
                                     }
+                                    SpecialClass.Sysprops -> {
+                                        if (syspropsPolicy != null) {
+                                            throw ParseException(
+                                                    "Policy for sysprops already defined")
+                                        }
+                                        syspropsPolicy =
+                                                policy.withReason("$FILTER_REASON (sysprops)")
+                                    }
                                 }
                             }
                         }
@@ -205,10 +214,10 @@
         }
 
         var ret: OutputFilter = imf
-        if (aidlPolicy != null || featureFlagsPolicy != null) {
+        if (aidlPolicy != null || featureFlagsPolicy != null || syspropsPolicy != null) {
             log.d("AndroidHeuristicsFilter enabled")
             ret = AndroidHeuristicsFilter(
-                    classes, aidlPolicy, featureFlagsPolicy, imf)
+                    classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, imf)
         }
         return ret
     }
@@ -218,6 +227,7 @@
     NotSpecial,
     Aidl,
     FeatureFlags,
+    Sysprops,
 }
 
 private fun resolveSpecialClass(className: String): SpecialClass {
@@ -227,6 +237,7 @@
     when (className.lowercase()) {
         ":aidl" -> return SpecialClass.Aidl
         ":feature_flags" -> return SpecialClass.FeatureFlags
+        ":sysprops" -> return SpecialClass.Sysprops
     }
     throw ParseException("Invalid special class name \"$className\"")
 }